使用 Python 检测 OpenCV 中的线条和形状

2024-04-14

我一直在使用 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)

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

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


这是我的尝试。它是用 C++ 编写的,但可以轻松移植到 python,因为大多数都是 OpenCV 函数。

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

  1. 加载图像
  2. 转换为灰度
  3. 对图像进行二值化(阈值)
  4. 瘦身,拥有纤细的轮廓并有帮助findContours
  5. 获取轮廓
  6. 对于每个轮廓,获取凸包(以处理开放轮廓),并根据。以不同的方式处理每个形状。

    • Circle:找到最小外接圆,或者最合适的椭圆
    • 长方形:找到边界框,或最小定向边界框。
    • Triangle:搜索最小外接圆与原始形状的交点,因为它们将在三角形的三个顶点处相交。

NOTES:

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

UPDATE- 刚刚注意到OpenCV 3.0.0中有这个功能最小外接三角形 http://docs.opencv.org/3.0-beta/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html#minenclosingtriangle。这可能有助于代替我的程序来查找三角形顶点。然而,由于在代码中插入这个函数是微不足道的,所以我将把我的过程留在代码中,以防万一没有 OpenCV 3.0.0。

代码:

#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): enter image description here

The results (fitEllipse and minAreaRect): enter image description here

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 Python 检测 OpenCV 中的线条和形状 的相关文章

随机推荐

  • 如何在QT中发送和接收UDP数据包

    我正在 QT 中编写一个小型应用程序 它通过本地网络发送广播 UDP 数据包 并等待来自网络上的一个或多个设备的 UDP 响应数据包 创建套接字并发送广播数据包 udpSocketSend new QUdpSocket this udpSo
  • 使用 iTextSharp 压缩 PDF [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我目前正在尝试重新压缩已创建的pdf 我正在尝试找到一种方法来重新压缩文档中的图像 以减小文件大小 我一直在尝试使用 DataLogi
  • Android Studio:编译器发生异常(1.8.0_242-release)

    运行应用程序时 无法完成编译 继续抛出错误 我安装了jdk并添加了java home路径 但仍然出现此错误 另外 当我写 flutter doctor v 时我没有任何问题 请通过 Java 错误报告页面 http bugreport ja
  • 从 AOSP master 切换到 froyo

    我刚刚用 repo 检查了 master 现在我想改用froyo 我怎样才能做到这一点而不需要再次下载一堆东西 我不想再次下载一堆东西 我只是希望能够像普通的 git 克隆一样在分支之间自由移动 由于您使用 repo 下载了所有内容 因此您
  • 迭代 EnumMap#entrySet

    枚举结束Map entrySet对于所有 Map 实现 特别是 EnumMap 不能按预期工作 IdentityHashMap这是 Josh Bloch 的示例代码谜题演示 谜题 5 http strangeloop2010 com sys
  • 一次热编码期间出现 RunTimeError

    我有一个数据集 其中类值以 1 步从 2 到 2 i e 2 1 0 1 2 其中 9 标识未标记的数据 使用一种热编码 self one hot encode labels 我收到以下错误 RuntimeError index 1 is
  • Python:第二个for循环未运行

    scores surfers results f open results txt for each line in results f name score each line split scores append float scor
  • __attribute__ 是否适用于声明中的所有变量?

    是否 attribute 指令适用于在一行中声明的所有成员 int a b c 声明三个 int 变量 int a b c 将变量 a 声明为 int 指针 将 b 和 c 声明为 int int attribute used a b c
  • 如何检测 firebase firestore 数据库中的实时侦听器错误?

    火库听众 https firebase google com docs firestore query data listen将在一段时间后随机关闭 可能是由于不活动 并且在 python 中没有简单的方法来捕获它们抛出的错误 因为它们将它
  • 我需要 T-SQL 中的国家/地区列表

    我有一个国家 地区表 其中包含国家 地区名称列 我需要 DDL 中现成的列表来填充 CountryName 列 该表驻留在 SQL Server 2008 中 你可以尝试这个脚本 CREATE TABLE tbl Countries Cou
  • 使用 D3.js 单击切换图例时重绘堆栈条形图

    我想使用 D3 js 实现带有切换图例的堆栈栏 单击图例时 堆栈栏应重新绘制 如果图例处于活动状态 则与图例对应的矩形板应消失 反之亦然 单击图例时 我无法正确更新与组元素和组元素内存在的矩形元素绑定的数据 在 DOM 树中 单击图例 矩形
  • c# protobuf-net反序列化时某些属性值始终为-1

    在我没有注意到这一点之前 我不确定这个问题是否是错误 或者只是这个 我创建一个Document类并声明 protobuf net 限制 ProtoContract public class Document ProtoMember 1 pr
  • 打开本地项目钥匙串?

    有谁知道如何打开本地物品钥匙串 小牛队的新功能 SecKeychainOpen 我希望能够请求用户允许使用该钥匙串中的某些条目进行访问SecKeychainFindInternetPassword 例如对于您调用的系统钥匙串SecKeych
  • 如何使用“typedef NS_ENUM”在一个 .h 文件中定义多个变量类型

    我用过typedef NS ENUM重新组织旧代码中的数据常量 使用方法在这里找到 https stackoverflow com a 42009056 2348597 every typedef被声明在一个单一的 h可以导入到项目中任何类
  • Rails 会话中存储的对象变成了字符串?

    通常我不会在 Rails 会话中存储对象 但我正在使用需要此功能的库 我遇到了一个非常奇怪的问题 其中存储的对象在重定向后显示为字符串 为了重现 我创建了一个示例 Rails 4 1 应用程序 rails new session test
  • 如何编写从配置文件中获取值的自定义正则表达式验证器?

    我必须在模型中对用户名属性使用正则表达式验证器 我从配置文件中获取这个正则表达式 RegularExpression UsernameValidationExpression UsernameValidationExpression val
  • 如何在打字中安装express?

    我正在尝试在我的应用程序中使用expressjs 安装后使用typings install express ambient save I run tsc 但我收到两个错误 types main ambient express index d
  • 使用 OpenCV 对非正面图像进行人脸检测

    我正在尝试使用 opencv 来检测人脸 脸部不是正面 相机从侧面拍摄脸部 因此只能看到一只眼睛和部分嘴巴 我尝试了具有多种配置的 HaarDetectObjects 但没有获得任何好处 我改变了级联并进行了测试 haarcascade f
  • 在折线图中的特定点添加标记

    我正在绘制一个绘图线图 并希望使用标记突出显示线图上的特定点 其中数据框中的另一列不是 NA 此外 当我将鼠标悬停在绘图上时 我只想查看位于标记点上的 y 值 而不是绘图的其余部分 这是一个可重现的示例 到目前为止我已经尝试做到这一点 li
  • 使用 Python 检测 OpenCV 中的线条和形状

    我一直在使用 OpenCV cv2 并检测线条和形状 假设我女儿画了一幅图画 如下所示 我正在尝试编写一个Python脚本来分析绘图并将其转换为硬线条 形状 例如 话虽这么说 我已经安装了 opencv 并尝试使用它 但除了能够在图像中绘制