[英]Detecting lines and shapes in OpenCV using Python
I've been playing around with OpenCV (cv2) and detecting lines and shapes. 我一直在玩OpenCV(cv2)并检测线条和形状。 Say that my daughter drew a drawing, like so: 假设我的女儿画了一幅画,就像这样:
I am trying to write a Python script that would analyze the drawing and convert it into hard lines/shapes, something like: 我正在尝试编写一个Python脚本来分析绘图并将其转换为硬线/形状,如:
That being said, I have installed opencv and tried to play around with it, but have had no luck aside from being able to draw a single vertical line through the image. 话虽这么说,我已经安装了opencv并尝试使用它,但除了能够在图像中绘制一条垂直线之外没有运气。 Below is my code so far, any pointers or suggestions as to how I should go about doing this with opencv would be greatly appreciated. 下面是我的代码到目前为止,任何关于如何使用opencv进行此操作的指针或建议将不胜感激。
import cv2
import numpy as np
class File(object):
def __init__(self, filename):
self.filename = filename
def open(self, filename=None, mode='r'):
if filename is None:
filename = self.filename
return cv2.imread(filename), open(filename, mode)
def save(self, image=None, filename_override=None):
filename = "output/" + self.filename.split('/')[-1]
if filename_override:
filename = "output/" + filename_override
return cv2.imwrite(filename, image)
class Image(object):
def __init__(self, image):
self.image = image
def grayscale(self):
return cv2.cvtColor(self.image, cv2.COLOR_BGR2GRAY)
def edges(self):
return cv2.Canny(self.image, 0, 255)
def lines(self):
lines = cv2.HoughLinesP(self.image, 1, np.pi/2, 6, None, 50, 10)
for line in lines[0]:
pt1 = (line[0],line[1])
pt2 = (line[2],line[3])
cv2.line(self.image, pt1, pt2, (0,0,255), 2)
if __name__ == '__main__':
File = File('images/a.png')
Image = Image(File.open()[0])
Image.image = Image.grayscale()
Image.lines()
File.save(Image.image)
Unfortunately, for a simple square drawing, all I get back is: 不幸的是,对于一个简单的方形绘图,我得到的全部是:
where the vertical line in the box is the output from the code. 框中的垂直线是代码的输出。
Here is my attempt. 这是我的尝试。 It's in C++, but can be easily ported to python since most are OpenCV functions. 它是在C ++中,但可以很容易地移植到python,因为大多数是OpenCV函数。
A brief outline of the method, comments in the code should help, too. 该方法的简要概述,代码中的注释也应该有所帮助。
findContours
变薄,有薄的轮廓和帮助findContours
For each contour, get convex hull (to handle open contours), and classify according to circularity . 对于每个轮廓,获得凸包(以处理开放的轮廓),并根据圆度进行分类。 Handle each shape differently. 处理每个形状的方式不同
NOTES: 笔记:
UPDATE - Just noticed that in OpenCV 3.0.0 there is the function minEnclosingTriangle . 更新 - 注意到在OpenCV 3.0.0中有minEnclosingTriangle函数。 This might be helpful to use instead of my procedure to find the triangle vertices. 这可能有助于使用而不是我的过程来找到三角形顶点。 However, since inserting this function in the code would be trivial, I'll leave my procedure in the code in case one doesn't have OpenCV 3.0.0. 但是,由于在代码中插入此函数将是微不足道的,所以我将在代码中保留我的过程,以防一个人没有OpenCV 3.0.0。
The code: 编码:
#include <opencv2\opencv.hpp>
#include <vector>
#include <iostream>
using namespace std;
using namespace cv;
/////////////////////////////////////////////////////////////////////////////////////////////
// Thinning algorithm from here:
// https://github.com/bsdnoobz/zhang-suen-thinning
/////////////////////////////////////////////////////////////////////////////////////////////
void thinningIteration(cv::Mat& img, int iter)
{
CV_Assert(img.channels() == 1);
CV_Assert(img.depth() != sizeof(uchar));
CV_Assert(img.rows > 3 && img.cols > 3);
cv::Mat marker = cv::Mat::zeros(img.size(), CV_8UC1);
int nRows = img.rows;
int nCols = img.cols;
if (img.isContinuous()) {
nCols *= nRows;
nRows = 1;
}
int x, y;
uchar *pAbove;
uchar *pCurr;
uchar *pBelow;
uchar *nw, *no, *ne; // north (pAbove)
uchar *we, *me, *ea;
uchar *sw, *so, *se; // south (pBelow)
uchar *pDst;
// initialize row pointers
pAbove = NULL;
pCurr = img.ptr<uchar>(0);
pBelow = img.ptr<uchar>(1);
for (y = 1; y < img.rows - 1; ++y) {
// shift the rows up by one
pAbove = pCurr;
pCurr = pBelow;
pBelow = img.ptr<uchar>(y + 1);
pDst = marker.ptr<uchar>(y);
// initialize col pointers
no = &(pAbove[0]);
ne = &(pAbove[1]);
me = &(pCurr[0]);
ea = &(pCurr[1]);
so = &(pBelow[0]);
se = &(pBelow[1]);
for (x = 1; x < img.cols - 1; ++x) {
// shift col pointers left by one (scan left to right)
nw = no;
no = ne;
ne = &(pAbove[x + 1]);
we = me;
me = ea;
ea = &(pCurr[x + 1]);
sw = so;
so = se;
se = &(pBelow[x + 1]);
int A = (*no == 0 && *ne == 1) + (*ne == 0 && *ea == 1) +
(*ea == 0 && *se == 1) + (*se == 0 && *so == 1) +
(*so == 0 && *sw == 1) + (*sw == 0 && *we == 1) +
(*we == 0 && *nw == 1) + (*nw == 0 && *no == 1);
int B = *no + *ne + *ea + *se + *so + *sw + *we + *nw;
int m1 = iter == 0 ? (*no * *ea * *so) : (*no * *ea * *we);
int m2 = iter == 0 ? (*ea * *so * *we) : (*no * *so * *we);
if (A == 1 && (B >= 2 && B <= 6) && m1 == 0 && m2 == 0)
pDst[x] = 1;
}
}
img &= ~marker;
}
void thinning(const cv::Mat& src, cv::Mat& dst)
{
dst = src.clone();
dst /= 255; // convert to binary image
cv::Mat prev = cv::Mat::zeros(dst.size(), CV_8UC1);
cv::Mat diff;
do {
thinningIteration(dst, 0);
thinningIteration(dst, 1);
cv::absdiff(dst, prev, diff);
dst.copyTo(prev);
} while (cv::countNonZero(diff) > 0);
dst *= 255;
}
int main()
{
RNG rng(123);
// Read image
Mat3b src = imread("path_to_image");
// Convert to grayscale
Mat1b gray;
cvtColor(src, gray, COLOR_BGR2GRAY);
// Binarize
Mat1b bin;
threshold(gray, bin, 127, 255, THRESH_BINARY_INV);
// Perform thinning
thinning(bin, bin);
// Create result image
Mat3b res = src.clone();
// Find contours
vector<vector<Point>> contours;
findContours(bin.clone(), contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);
// For each contour
for (vector<Point>& contour : contours)
{
// Compute convex hull
vector<Point> hull;
convexHull(contour, hull);
// Compute circularity, used for shape classification
double area = contourArea(hull);
double perimeter = arcLength(hull, true);
double circularity = (4 * CV_PI * area) / (perimeter * perimeter);
// Shape classification
if (circularity > 0.9)
{
// CIRCLE
//{
// // Fit an ellipse ...
// RotatedRect rect = fitEllipse(contour);
// Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
// ellipse(res, rect, color, 5);
//}
{
// ... or find min enclosing circle
Point2f center;
float radius;
minEnclosingCircle(contour, center, radius);
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
circle(res, center, radius, color, 5);
}
}
else if (circularity > 0.75)
{
// RECTANGLE
//{
// // Minimum oriented bounding box ...
// RotatedRect rect = minAreaRect(contour);
// Point2f pts[4];
// rect.points(pts);
// Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
// for (int i = 0; i < 4; ++i)
// {
// line(res, pts[i], pts[(i + 1) % 4], color, 5);
// }
//}
{
// ... or bounding box
Rect box = boundingRect(contour);
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
rectangle(res, box, color, 5);
}
}
else if (circularity > 0.7)
{
// TRIANGLE
// Select the portion of the image containing only the wanted contour
Rect roi = boundingRect(contour);
Mat1b maskRoi(bin.rows, bin.cols, uchar(0));
rectangle(maskRoi, roi, Scalar(255), CV_FILLED);
Mat1b triangle(roi.height, roi.height, uchar(0));
bin.copyTo(triangle, maskRoi);
// Find min encolsing circle on the contour
Point2f center;
float radius;
minEnclosingCircle(contour, center, radius);
// decrease the size of the enclosing circle until it intersects the contour
// in at least 3 different points (i.e. the 3 vertices)
vector<vector<Point>> vertices;
do
{
vertices.clear();
radius--;
Mat1b maskCirc(bin.rows, bin.cols, uchar(0));
circle(maskCirc, center, radius, Scalar(255), 5);
maskCirc &= triangle;
findContours(maskCirc.clone(), vertices, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);
} while (vertices.size() < 3);
// Just get the first point in each vertex blob.
// You could get the centroid for a little better accuracy
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
line(res, vertices[0][0], vertices[1][0], color, 5);
line(res, vertices[1][0], vertices[2][0], color, 5);
line(res, vertices[2][0], vertices[0][0], color, 5);
}
else
{
cout << "Some other shape..." << endl;
}
}
return 0;
}
The results ( minEnclosingCircle
and boundingRect
): 结果( minEnclosingCircle
和boundingRect
):
The results ( fitEllipse
and minAreaRect
): 结果( fitEllipse
和minAreaRect
):
You might check out a couple of resources. 您可以查看几个资源。
First, you might consider asking questions at answers.opencv.org. 首先,您可以考虑在answers.opencv.org上提问。 There is probably a higher concentration of opencv specialists there. 那里的opencv专家可能更集中。
Second, the book Practical OpenCV by Samarth Brahmbhatt is available as a free pdf and is easily found on google. 其次,Samarth Brahmbhatt的Practical OpenCV一书以免费pdf格式提供,在谷歌上很容易找到。 It is contains many examples related to what you are looking for. 它包含许多与您要查找的内容相关的示例。
For example, you can separate different (non-overlapping) contours, as is shown in example 6.1 on page 68. He has a simple program for finding circles and lines in example 6.4 on page 78. You can also find a RANSAC-based ellipse-finder (much more complicated, but would be very useful here) in example 6.5 on page 82. 例如,您可以分隔不同的(非重叠)轮廓,如第68页的示例6.1所示。他有一个简单的程序,用于查找第78页的示例6.4中的圆和线。您还可以找到基于RANSAC的椭圆-finder(更复杂,但在这里非常有用)在第82页的示例6.5中。
The book is in C++, but I imagine it will be very relevant, only you will need an API reference to translate it to python. 这本书是用C ++编写的,但我认为它非常相关,只需要一个API引用就可以将它翻译成python。
Personally, for your proejct, I would analyze one contour at a time, starting with his ellipse finder, and where a suitable ellipse can not be found, you could use a Hough transform of adjustable threshold, and truncate the resulting lines at their intersections, and bam! 就个人而言,对于你的项目,我会一次分析一个轮廓,从他的椭圆查找器开始,并且在找不到合适的椭圆的地方,你可以使用可调阈值的霍夫变换,并截断它们交叉处的结果线,和巴姆! You have polygons. 你有多边形。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.