我一直在玩OpenCV(cv2)并检测线条和形状。假设我的女儿画了一幅画,就像这样:
我正在尝试编写一个Python脚本来分析绘图并将其转换为硬线/形状,如:
话虽这么说,我已经安装了opencv并尝试使用它,但除了能够在图像中绘制一条垂直线之外没有运气。下面是我的代码到目前为止,任何关于如何使用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)
不幸的是,对于一个简单的方形绘图,我得到的全部是:
其中框中的垂直线是代码的输出。
答案 0 :(得分:13)
这是我的尝试。它在C ++中,但可以很容易地移植到python,因为大多数都是OpenCV函数。
方法的简要概述,代码中的注释也应该有所帮助。
Short
对于每个轮廓,获取凸包(处理开放轮廓),并根据圆度进行分类。处理每种形状的方式不同。
注意:
<强>更新强> - 注意到在OpenCV 3.0.0中有函数minEnclosingTriangle。这可能有助于使用而不是我的过程来找到三角形顶点。但是,由于在代码中插入此函数是微不足道的,因此我将在代码中保留我的过程,以防万一没有OpenCV 3.0.0。
代码:
findContours
结果(#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;
}
和minEnclosingCircle
):
答案 1 :(得分:1)
您可以查看几个资源。
首先,您可以考虑在answers.opencv.org上提问。那里的opencv专家可能会更加集中。
其次,Samarth Brahmbhatt的Practical OpenCV一书以免费pdf格式提供,很容易在谷歌上找到。它包含许多与您要查找的内容相关的示例。
例如,您可以分隔不同的(非重叠)轮廓,如第68页的示例6.1所示。他有一个简单的程序,用于查找第78页的示例6.4中的圆和线。您还可以找到RANSAC在第82页的示例6.5中,基于椭圆查找器(更复杂,但在这里非常有用)。
这本书是用C ++编写的,但我认为它非常相关,只有你需要一个API引用才能将它翻译成python。
就个人而言,对于您的项目,我会一次分析一个轮廓,从他的椭圆查找器开始,如果找不到合适的椭圆,您可以使用可调阈值的Hough变换,并截断结果线他们的交叉点和bam!你有多边形。