用opencv在图片上进行排料,主函数里应该没什么问题,自定义函数里面的第一个循环,手动一条一条运行,是没有问题的,但是直接调试自动运行,就会出错,遍历不到圆中坐标,前几个圆没有问题,可以直接测试圆心坐标(1361,166)的。
本人刚刚自学,写的代码比较low,但是应该还是能看懂的,下面是用到的两张图片
#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,我下面没看见你修改过这个标志位,这意味着你这边是死循环了,就算没有你说的报错,这里也一直会在循环不停。
另外给你个建议,除非迫不得已,opencv里面最好不要去想着for循环遍历像素点,能整张Mat操作的最好直接操作,不然图片大一些,for循环遍历速度会很慢。就拿你上面的例子来说,jianceshifoulunkuonei()函数输入图片中,大部分都是没有用的像素值(黑色),但是你直接用遍历来操作获取到白色区域的点,这点看来并不是一种很好的策略。在你都已经知道圆心和半径的情况下,直接用点到圆心的距离来做会更好,更近一步,获取圆的外接矩形框,不在外接矩形框内的点肯定是圆外的了,直接抠图外接矩形框的遍历也比你遍历整张图效率高。而实际上就根本不需要这么做,像我上面说的,直接两个矩形框来判断会更快。
既然你自动运行会报错,那就在每隔一段加上打印,排查是哪里的问题。
这两张图,你的几个输入是多少
#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;
}