使用Python

时间:2015-08-12 20:32:51

标签: python opencv image-processing

我一直在玩OpenCV(cv2)并检测线条和形状。假设我的女儿画了一幅画,就像这样:

enter image description here

我正在尝试编写一个Python脚本来分析绘图并将其转换为硬线/形状,如:

enter image description here

话虽这么说,我已经安装了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)

不幸的是,对于一个简单的方形绘图,我得到的全部是:

enter image description here

其中框中的垂直线是代码的输出。

2 个答案:

答案 0 :(得分:13)

这是我的尝试。它在C ++中,但可以很容易地移植到python,因为大多数都是OpenCV函数。

方法的简要概述,代码中的注释也应该有所帮助。

  1. 加载图片
  2. 转换为灰度
  3. 二进制化图像(阈值)
  4. 细化,有薄的轮廓并帮助Short
  5. 获取轮廓
  6. 对于每个轮廓,获取凸包(处理开放轮廓),并根据圆度进行分类。处理每种形状的方式不同。

    • 圆圈:找到最小的封闭圆圈,或最佳拟合椭圆
    • Recrangle :找到boundinx框或最小方向边框。
    • 三角形:搜索最小包围圆与原始形状的交点,因为它们将在三角形的三个顶点中相交。
  7. 注意:

    • 我需要将原始图像从具有透明度的png修改为3通道RGB。
    • 细化代码来自here。还有Python版本。
    • 圆形定义为:A测量形状与圆形的接近程度。例如。正六边形具有比正方形更高的圆度。定义为(\ frac {4 * \ pi * Area} {perimeter * perimeter})。这意味着圆的圆度为1,正方形的圆度为0.785,依此类推。
    • 由于轮廓,每个形状可能有多个检测。这些可以根据例如交叉结合条件过滤掉。我之前没有在代码中插入此部分,因为它需要额外的逻辑,而不是与查找形状的主要任务严格相关。

    <强>更新 - 注意到在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): enter image description here

    结果(boundingRectfitEllipse): enter image description here

答案 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!你有多边形。