opencv检测点是否在轮廓内

用opencv在图片上进行排料,主函数里应该没什么问题,自定义函数里面的第一个循环,手动一条一条运行,是没有问题的,但是直接调试自动运行,就会出错,遍历不到圆中坐标,前几个圆没有问题,可以直接测试圆心坐标(1361,166)的。
本人刚刚自学,写的代码比较low,但是应该还是能看懂的,下面是用到的两张图片

img


img


#include <opencv2\opencv.hpp> //加载OpenCV4.1头文件。
#include <iostream>
#include <math.h>
#include <vector>
#include <core\types.hpp>


using namespace std;
using namespace cv;//OpenCV的命名空间

int jianceshifoulunkuonei(vector<vector<Point>> x, vector<vector<Point>> y, Mat paibanshiyan) //监测元件是否在板材上的函数
{
    Point2f bianlizuobiao;
    Point2f yuanzhongzuobiaozhuancun;
    Point2f yuanzhongzuobiao[100000];
    double bianlishifouzaiyunzhong;
    int jishu = 0;
    for (float i = 0; i <= paibanshiyan.rows; i++)
    {
        for (float c = 0; c <= paibanshiyan.cols; c++)
        {
            bianlizuobiao = { i,c };
            for (int g = 0; g <1;g++)
            {
                bianlishifouzaiyunzhong = pointPolygonTest(y[g], bianlizuobiao, false);
                if (bianlishifouzaiyunzhong >= 0)
                {
                    yuanzhongzuobiao[jishu] = bianlizuobiao;
                    jishu++;
                }
            }
        }
    }
    for (int t = 0; t < jishu;t++)
    {
        yuanzhongzuobiaozhuancun = yuanzhongzuobiao[t];
        for (int d = 0; d < x.size();d++)
        {
            double w = pointPolygonTest(x[d], yuanzhongzuobiaozhuancun, false);
            if (w<0)
            {
                return 0;
            }
        }
    }
    return 1;
}

int main()
{
    Mat heibai;//创建黑白所使用的类

    Mat c;
    Mat a = imread("C:/Users/刘春瑞/Desktop/模拟相机拍照1.jpg");//读取第一张图片,模拟拍第一张照片
    Mat     b = imread("C:/Users/刘春瑞/Desktop/模拟相机拍照2.jpg");//读取第二张图片,模拟拍第二张照片
    if (a.empty())       //判断两张照片是否存在
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }
    if (b.empty())       //判断两张照片是否存在 
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }
    vconcat(a, b, c);  //拼接两张图
    cvtColor(c, heibai, COLOR_BGR2GRAY);/*输出黑白的图片*/
    GaussianBlur(heibai, heibai, Size(3, 3), 0, 0); //对图像进行高斯滤波
    threshold(heibai, heibai, 5, 255, 1 | 8); //进行黑白转换,原图是白色为背景,黑色为板材,提取轮廓正好相反

    float bianju;  //创建元件距边变量
    float yuanju;  //创建排料的圆心距变量
    float banjing;  //创建元件半径变量
    float genhao3 = sqrt((float)3);  //创建根号三变量
    cout << ("输入边距\n");
    cin >> bianju;  //输入边距
    cout << ("输入圆距\n");
    cin >> yuanju;  //输入圆心距
    cout << ("圆形半径\n");
    cin >> banjing;  //输入元件半径
    float pailiaobanjing;  //创建排料半径变量
    pailiaobanjing = banjing + yuanju / 2;  //对排料半径赋值

    Point2f zuobiaoshuzu[20];  //排料过程中使用的圆心坐标
    Point2f shuchuzuobiao[500];  //最后需要的圆心坐标
    Point2f q[4];  //创建最小矩形用到的四个顶点坐标数组
    vector<vector<Point>> e;  //创建板材轮廓用的数组
    vector<Point> hull;
    vector<Vec4i> p;
    RotatedRect juxing;  //创建最小外接矩形的变量
    bool shifoukaishipaituxuanhuan = true;  //创建是否排样的变量
    Point2f hengxiangjia = { 2*pailiaobanjing,0 };  //横向加
    Point2f shuxiangjiacuokai = { pailiaobanjing,genhao3*pailiaobanjing };  //竖向加错开
    Point2f shuxiangjiacongtou = { -pailiaobanjing,genhao3*pailiaobanjing };  //竖向加从头
    findContours(heibai, e,0, 1, Point());  //寻找进行过黑白装换过后的图片中的轮廓,e为轮廓坐标
    if (e.empty())  //判断是否寻找到了轮廓,找到轮廓则获取最小外接矩形,否则不执行
    {
    }
    else
    {
        for (int i = 0; i < e.size(); i++)
        {
            juxing = minAreaRect(e[i]);  //寻找最小外接矩形
            juxing.points(q);  //获取矩形的顶点,赋值给四个角点坐标q,第三个是左上角
        }
    }
    if (shifoukaishipaituxuanhuan==true)
    {
        vector<vector<Point>> zz;/*排版图上的轮廓所有坐标*/
        zuobiaoshuzu[0]= { banjing + bianju , banjing + bianju };//确认第一个圆的圆心坐标
        zuobiaoshuzu[0] = zuobiaoshuzu[0]+q[2];//圆心坐标加上左上角角点坐标即为第一个圆心坐标
        zuobiaoshuzu[2] = zuobiaoshuzu[0];
        bool henghuozheshu = true;//需要判定是横向排,还是竖向排,true为横向,false为竖向
        bool jishuhuozheoushu = true;  //需要判定下一行是从头开始还是错开,true是错开即偶数行,false是从头即奇数行
        bool pailiaozhuangtai = true;  //创建是否继续拍料的变量

        for (int j = 0; pailiaozhuangtai == true;)
        {
            Mat paibanshiyan;/*创建一个排版用的图片,与原图相同*/
            heibai.copyTo(paibanshiyan);/*创建一个排版用的图片,与原图相同*/
            paibanshiyan = 0;
            circle(paibanshiyan, zuobiaoshuzu[2], pailiaobanjing, Scalar(255, 225, 255), -1);//在排版图上相同圆心画圆
                findContours(paibanshiyan, zz, 1, 1, Point());/*从排版图上提取圆的轮廓*/
                int r = jianceshifoulunkuonei(e, zz, paibanshiyan);

                if (r == 1)
                {
                    circle(heibai, zuobiaoshuzu[2], pailiaobanjing, Scalar(0, 0, 0), 1);//在原图上相同圆心画圆
                    shuchuzuobiao[j] = zuobiaoshuzu[2];
                    zuobiaoshuzu[2] = zuobiaoshuzu[2] + hengxiangjia;
                    j++;
                }
            
            else
            {
                if (jishuhuozheoushu==true)
                {
                    zuobiaoshuzu[2] = zuobiaoshuzu[0] + shuxiangjiacuokai;
                    zuobiaoshuzu[0] = zuobiaoshuzu[2];
                    jishuhuozheoushu = false;
                } 
                else
                {
                    zuobiaoshuzu[2] = zuobiaoshuzu[0] + shuxiangjiacongtou;
                    zuobiaoshuzu[0] = zuobiaoshuzu[2];
                    jishuhuozheoushu = true;
                }    
            }
        }
    }



    imshow("123", heibai);
    cin >> banjing;
}


引用chatgpt部分指引作答:
这段代码中的问题可能在于jianceshifoulunkuonei函数中的循环部分。根据你提供的描述,手动逐步执行该循环时没有问题,但在自动调试运行时出错。

有几种可能的原因导致这个问题:

1、图像处理步骤的顺序问题:请确保在调用jianceshifoulunkuonei函数之前已经完成了图像处理步骤,如阈值化、高斯滤波和轮廓提取。确认这些步骤的顺序是否正确。

2、轮廓坐标的传递问题:确保你正确地将e和zz传递给了jianceshifoulunkuonei函数。可以通过打印这两个变量的内容来验证是否传递正确。

3、轮廓遍历的问题:循环中使用了x和y作为轮廓的参数,但是在调用pointPolygonTest函数时,只使用了y[g]。这可能导致遍历轮廓时出现错误。请确保在遍历轮廓时使用正确的参数。

请逐一检查这些可能的问题,并进行调试和验证。

根据你的描述,你希望首先提取圆内的所有坐标,然后检查这些坐标是否都在轮廓 x 内。

在你的代码中,我注意到了一个可能导致问题的地方。在循环中,你使用 jishu 来追踪 yuanzhongzuobiao 数组中有效的坐标数量。然后,在第二个循环中,你遍历 yuanzhongzuobiao 数组的所有元素,并在每次迭代中检查这些坐标是否都在轮廓 x 内。

问题可能出现在 jishu 变量的使用上。由于你使用 jishu 来追踪有效的坐标数量,因此在第二个循环中,只应该遍历 yuanzhongzuobiao 数组中的前 jishu 个元素,而不是遍历整个数组。

以下是对你的代码做出的修改:

int jianceshifoulunkuonei(vector<vector<Point>> x, vector<vector<Point>> y, Mat paibanshiyan)
{
    Point2f bianlizuobiao;
    Point2f yuanzhongzuobiao[100000];
    int jishu = 0;

    for (float i = 0; i <= paibanshiyan.rows; i++)
    {
        for (float c = 0; c <= paibanshiyan.cols; c++)
        {
            bianlizuobiao = { i,c };
            for (int g = 0; g < 1; g++)
            {
                double bianlishifouzaiyunzhong = pointPolygonTest(y[g], bianlizuobiao, false);
                if (bianlishifouzaiyunzhong >= 0)
                {
                    yuanzhongzuobiao[jishu] = bianlizuobiao;
                    jishu++;
                }
            }
        }
    }

    for (int t = 0; t < jishu; t++)
    {
        Point2f yuanzhongzuobiaozhuancun = yuanzhongzuobiao[t];
        for (int d = 0; d < x.size(); d++)
        {
            double w = pointPolygonTest(x[d], yuanzhongzuobiaozhuancun, false);
            if (w < 0)
            {
                return 0;
            }
        }
    }

    return 1;
}

第二个循环中使用了 jishu 变量来控制迭代次数。

请尝试运行修改后的代码,看看是否解决了问题。

当然,你需要建议的话,当您检测小轮廓是否完全包含在大轮廓内时,可以使用以下步骤:

遍历小轮廓集合。
对于每个小轮廓,使用 pointPolygonTest 函数检查其所有点是否都在大轮廓内部。
如果有任何一个点不在大轮廓内,表示小轮廓未完全包含在大轮廓内,可以立即返回结果。
如果所有小轮廓的所有点都在大轮廓内,表示所有小轮廓都完全包含在大轮廓内。
以下是修改后的 jianceshifoulunkuonei 函数,实现了上述步骤:

int jianceshifoulunkuonei(const vector<Point>& bigContour, const vector<vector<Point>>& smallContours)
{
    for (const vector<Point>& smallContour : smallContours)
    {
        for (const Point& point : smallContour)
        {
            double distance = pointPolygonTest(bigContour, point, false);
            if (distance < 0)
            {
                return 0; // 小轮廓中的点不在大轮廓内
            }
        }
    }
    return 1; // 所有小轮廓都在大轮廓内
}

在调用 jianceshifoulunkuonei 函数时,将大轮廓传递为第一个参数,小轮廓集合传递为第二个参数。

请注意,此函数假设所有小轮廓都是由 vector 类型表示的,而大轮廓是由一个 vector 表示的。

希望这个解决方案能够满足您的需求。

我不懂你排料都知道圆的半径和圆心位置了,还要用轮廓pointPolygonTest来检测干嘛?只要圆心和半径之后,你轮廓是矩形的ROI,又不是不规则图像,直接用圆心加减半径得到圆的外接矩形circle_roi,直接用矩形位运算 ROI&circle_roi结果是否为circle_roi就可以知道ROI是否全部包含整个圆,也就是可以知道圆是否超过轮廓了(或者直接判断xywh是否超过也行),不需要那么复杂,又是找圆轮廓,遍历整张Mat,遍历轮廓点啥的,效率太慢了。
而且粗略运行了下,你这里一直都是true,我下面没看见你修改过这个标志位,这意味着你这边是死循环了,就算没有你说的报错,这里也一直会在循环不停。

img

img

另外给你个建议,除非迫不得已,opencv里面最好不要去想着for循环遍历像素点,能整张Mat操作的最好直接操作,不然图片大一些,for循环遍历速度会很慢。就拿你上面的例子来说,jianceshifoulunkuonei()函数输入图片中,大部分都是没有用的像素值(黑色),但是你直接用遍历来操作获取到白色区域的点,这点看来并不是一种很好的策略。在你都已经知道圆心和半径的情况下,直接用点到圆心的距离来做会更好,更近一步,获取圆的外接矩形框,不在外接矩形框内的点肯定是圆外的了,直接抠图外接矩形框的遍历也比你遍历整张图效率高。而实际上就根本不需要这么做,像我上面说的,直接两个矩形框来判断会更快。

既然你自动运行会报错,那就在每隔一段加上打印,排查是哪里的问题。
这两张图,你的几个输入是多少

  • 你可以参考下这个问题的回答, 看看是否对你有帮助, 链接: https://ask.csdn.net/questions/7585335
  • 我还给你找了一篇非常好的博客,你可以看看是否有帮助,链接:【OpenCV3图像处理】提取轮廓的凸包、外包矩形、最小外包矩形、最小外包圆
  • 除此之外, 这篇博客: 【OpenCV】学习笔记(一):基础操作部分中的 4.4.2 示例程序:图像对比度、亮度调整 部分也许能够解决你的问题, 你可以仔细阅读以下内容或跳转源博客中阅读:
  • #include <iostream>
    #include <cstdlib>
    #include <math.h>
    #include <opencv2/opencv.hpp>
    
    using namespace std;
    using namespace cv;
    
    static void on_ContrastAndBright(int, void *); //改变图像对比度和亮度值的回调函数
    //static void ShowHelpText(); 
    
    int g_nContrastValue; //对比度
    int g_nBrightValue;	  //亮度值
    Mat g_srcImage, g_dstImage;
    
    int main()
    {
    	//【1】读取输入图像
    	g_srcImage = imread("E:/result/MyPic.png");
    	if (!g_srcImage.data) { cout << "读取g_srcImage error~"; return false; }
    	g_dstImage = Mat::zeros(g_srcImage.size(), g_srcImage.type());
    	//【2】设置对比度和亮度
    	g_nBrightValue = 80;
    	g_nContrastValue = 80;
    	//【3】创建效果图窗口
    	namedWindow("【效果图窗口】", 1);
    	//【4】创建轨迹条
    	createTrackbar("对比度", "【效果图窗口】", &g_nContrastValue,
    		300, on_ContrastAndBright);
    	createTrackbar("亮  度", "【效果图窗口】", &g_nBrightValue,
    		200, on_ContrastAndBright);
    	//【5】进行回调函数初始化
    	on_ContrastAndBright(g_nContrastValue, 0);
    	on_ContrastAndBright(g_nBrightValue, 0);
    	//【6】按下"q",程序退出
    	while(char(waitKey(1)) != 'q'){}
    	destroyAllWindows();
    	return 0;
    }
    
    static void on_ContrastAndBright(int, void *)
    {
    	cvNamedWindow("【原图窗口】", 1);
    	//for计算:g_dstImage(i, j) = a*g_dstImage(i, j) + b
    	for (int y = 0; y < g_srcImage.rows; y++)
    	{
    		for (int x = 0; x < g_srcImage.cols; x++)
    		{
    			for (int c = 0; c < 3; c++)
    			{
    				g_dstImage.at<Vec3b>(y, x)[c] =
    					saturate_cast<uchar>((g_nContrastValue*0.01)*(g_srcImage.at<Vec3b>(y, x)[c])
    						+ g_nBrightValue);
    			}
    		}
    	}
    	imshow("【原图窗口】", g_srcImage);
    	imshow("【效果图窗口】", g_dstImage);
    }
    

在 OpenCV 中,可以使用 pointPolygonTest 函数来检测一个点是否在轮廓内。该函数接受三个参数:轮廓、点和一个布尔值。如果布尔值为 true,则表示只检测点到轮廓的距离;如果布尔值为 false,则表示检测点是否在轮廓内。
下面是一个示例代码:

import cv2
 # 读取图像并转换为灰度图像
img = cv2.imread('image.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
 # 二值化图像
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
 # 查找轮廓
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
 # 绘制轮廓
cv2.drawContours(img, contours, -1, (0, 255, 0), 2)
 # 检测点是否在轮廓内
point = (100, 100)
distance = cv2.pointPolygonTest(contours[0], point, True)
if distance > 0:
    print('点在轮廓内')
elif distance == 0:
    print('点在轮廓上')
else:
    print('点在轮廓外')
 # 显示图像
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

在上面的代码中,我们首先读取一张图像,并将其转换为灰度图像。然后,我们对灰度图像进行二值化处理,并查找图像中的轮廓。接下来,我们绘制轮廓,并使用 pointPolygonTest 函数检测一个点是否在轮廓内。最后,我们根据检测结果输出相应的信息,并显示图像。

可以参考下

#include<opencv2/opencv.hpp>
#include<iostream>
 
using namespace cv;
using namespace std;
 
int main(int argc,char*argv)
{
    Mat src = Mat::zeros(Size(512, 512), CV_8UC1);
 
    vector<Point2f>Poly = { Point2f(50, 300), Point2f(100, 200),Point2f(200, 200),Point2f(250, 300), Point2f(200, 400), Point2f(100, 400) };
    for (size_t i = 0; i < 5; i++)
    {
        line(src, Poly[i], Poly[i + 1], Scalar(255), 1, 8, 0);
    }
    line(src, Poly[5], Poly[0], Scalar(255), 1);
    imshow("src",src);
 
    vector<vector<Point>>contours;
    findContours(src, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);
 
    Mat drawImg=Mat::zeros(src.size(),CV_32FC3);
    drawContours(drawImg, contours, -1, Scalar(0, 0, 255));
    imshow("drawImg", drawImg);
 
    Mat resultImg = Mat::zeros(src.size(), CV_32FC1);
    for (int row = 0; row < src.rows; row++)
    {
        for (int col = 0; col < src.cols; col++)
        {
            resultImg.at<float>(col,row) = (float)pointPolygonTest(contours[0], Point2f((float)row, (float)col), true);
        }
    }
    imshow("resultImg", resultImg);
    
    Mat dst = Mat::zeros(src.size(), CV_8UC3);
    for (int row = 0; row < src.rows; row++)
    {
        for (int col = 0; col < src.cols; col++)
        {
            float distance = resultImg.at<float>(col, row);
            if (distance>0.0)
            {
                dst.at<Vec3b>(col, row)[0] = distance;
            }
            else if (distance<0.0)
            {
                dst.at<Vec3b>(col, row)[1] = saturate_cast<uchar>(abs(distance));
                dst.at<Vec3b>(col, row)[2] = saturate_cast<uchar>(abs(distance));
            }
            else
            {
                dst.at<Vec3b>(col, row)[2] = 255;
            }
        }
    }
    imshow("dst", dst);
 
    waitKey();
    return 0;
}