汽车工业领域的法兰盘分布有螺纹孔,实际生产过程中,螺纹孔因加工问题未安上螺纹,本项目旨在对法兰盘螺纹孔定位并检测。
cv::Mat cannyImg;
int low_Val = 100, high_Val = 2 * low_Val; //进行Canny边缘检测的高阈值与低阈值
cv::Canny(image0, cannyImg, low_Val, high_Val, 3); //Canny边缘检测参数,默认apertureSize = 3
Mat closeSmallImg;
cv::Mat elementSmall = cv::getStructuringElement(cv::MORPH_ELLIPSE,
cv::Size(3, 3)
);
//不用开运算会把轮廓去掉
//不用9*9,9*9得到的螺纹孔更多,但背景的圆不易被剔除掉,后续思考改进
cv::morphologyEx(cannyImg, closeSmallImg, cv::MORPH_CLOSE, elementSmall);
//将小空隙填满,将临近的轮廓连接
vector<cv::Vec4i> hierarchy;
vector<vector<cv::Point> > contours;
cv::findContours(closeSmallImg, contours, hierarchy,
cv::RETR_TREE, cv::CHAIN_APPROX_NONE, cv::Point(0, 0));
Mat drawImg = Mat::zeros(cannyImg.size(), CV_8UC3);
cv::RNG rng(12345);
for (int i = 0; i < contours.size(); i++)
{
cv::Scalar color = cv::Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
cv::drawContours(drawImg, contours, i, color, 2, 8, hierarchy, 0, cv::Point());
}
cv::imshow("s3 Contours Image", drawImg);
//轮廓长度与轮廓间距离设置
int minCDis = 50; //筛选轮廓时,允许的最小轮廓间距离
int minCircleLen = 65; //筛选轮廓时定位圆孔的最小轮廓长度
int maxCircleLen = 75; //筛选轮廓时定位圆孔的最大轮廓长度
//圆环半径设置
float RH_mm = 140; //检测圆盘圆孔的半径 单位:毫米
float R0_mm = 115; //检测圆盘的内径 单位:毫米
float R1_mm = 160; //检测圆环的外径 单位:毫米
//二、标记ROI预览显示ROI
cv::Point estimateCenter = cv::Point(750, 500); //估计的ROI区域的中心坐标
int estimateRadius = 500; //估计的ROI区域的半径
Mat subImg = Mat::zeros(image0.size(), CV_8UC1); //初始化一个mat,其大小与srcImg一样,类型为8位单通道类型
image0.copyTo(subImg); //把src里的图像复制到subimg中
cv::cvtColor(subImg, subImg, cv::COLOR_GRAY2RGB); //把subimg转变为彩色图像
//thickness为1,lineType为8,shift为对应给定点的小数位数(0对结果不产生影响)
cv::circle(subImg, estimateCenter,
estimateRadius, cv::Scalar(0, 0, 255), 1, 8, 0); //在subimg上画圆
cv::imshow("s0 ROI Img", subImg);
const cv::Scalar RED = cv::Scalar(0, 0, 255);
const cv::Scalar GREEN = cv::Scalar(0, 255, 0);
const cv::Scalar BLUE = cv::Scalar(255, 0, 0);
const cv::Scalar YELLOW = cv::Scalar(0, 255, 255);
//1.不在估计区域范围内的轮廓去掉,相同重心的轮廓只保留一个contours2
// Get the moments,得到轮廓矩
vector<cv::Moments> mu(contours.size());
for (int j = 0; j < contours.size(); j++)
{
mu[j] = moments(contours[j], false);
}
/// Get the mass centers,重心
vector<cv::Point2f> mc(contours.size());
for (int j = 0; j < contours.size(); j++)
{
mc[j] = cv::Point2f(mu[j].m10 / mu[j].m00, mu[j].m01 / mu[j].m00);
std::cout << "xpos = " << mc[j].x << " ypos = " << mc[j].y << std::endl;
}
//开始筛选
vector<vector<cv::Point>> contours2;
vector<cv::Point2f> mc_finded;
for (int i = 0; i < contours.size(); ++i)
{
cv::Point r_pos = mc[i];
bool disFlag = true;
//先检查轮廓出现的位置,计算轮廓矩重心的距离与估计圆心的距离
float disR = calDistance2D(r_pos.x, r_pos.y, estimateCenter.x, estimateCenter.y);
if (disR > estimateRadius)
{ //在ROI区域外 则无需比较
disFlag = false;
}
else
{ //检查这个轮廓与已找到的轮廓中心是否很接近,如果是则排除 跳出循环
for (int j = 0; j < mc_finded.size(); ++j)
{
cv::Point f_pos = mc_finded[j];
float dis = calDistance2D(r_pos.x, r_pos.y, f_pos.x, f_pos.y);
if (dis <= minCDis) //轮廓中心之间的距离
{
disFlag = false;
break;
}
}
}
if (disFlag)
{ //此轮廓的中心点位置与前都不同
mc_finded.push_back(mc[i]);
contours2.push_back(contours[i]);
}
}
drawImg = Mat::zeros(cannyImg.size(), CV_8UC3);
for (int i = 0; i < contours2.size(); i++)
{
cv::Scalar color = cv::Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
cv::drawContours(drawImg, contours2, i, color, 2);
}
cv::imshow("s4 reSelected Contours2 Image", drawImg);
std::cout << " * Contour2 Size = " << contours2.size() << std::endl;
//2.检查轮廓的长度contours3
vector<float> contourLens;
for (int i = 0; i < contours2.size(); ++i)
{
float conLen = arcLength(contours2[i], true);
contourLens.push_back(conLen);
std::cout << " * Contours Length = " << conLen << std::endl;
}
vector<vector<cv::Point>> contours3;
for (int i = 0; i < contourLens.size(); ++i)
{
if ((contourLens[i] > 260) &&
(contourLens[i] < 400)) //检查轮廓的周长参数
{
contours3.push_back(contours2[i]);
//std::printf("Contours Idx = %d Length = %f\n", i, contourLens[i], true);
}
}
drawImg = Mat::zeros(cannyImg.size(), CV_8UC3);
for (int i = 0; i < contours3.size(); i++)
{
cv::Scalar color = cv::Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
cv::drawContours(drawImg, contours3, i, color, 2);
}
cv::imshow("s5 reSelected Contours3 Image", drawImg);
std::cout << " * Contour3 Size = " << contours3.size() << std::endl;
//3.检查外接矩形 如果外接矩形的长和宽相差超过阈值 则删除contours4
vector<vector<cv::Point>> contours4;
{
for (int i = 0; i < contours3.size(); i++)
{
cv::RotatedRect one_rRect = minAreaRect(contours3[i]);
float jRes = abs(one_rRect.size.width - one_rRect.size.height);
if (jRes < 10) //检查轮廓外接矩形的长和宽
{
contours4.push_back(contours3[i]);
}
}
drawImg = Mat::zeros(cannyImg.size(), CV_8UC3);
for (int i = 0; i < contours4.size(); i++)
{
cv::Scalar color = cv::Scalar(rng.uniform(0, 255), rng.uniform(0, 255),
rng.uniform(0, 255));
cv::drawContours(drawImg, contours4, i, color, 2);
}
cv::imshow("s6 check RECT Contours4 Image", drawImg);
std::cout << " * Contour4 Size = " << contours4.size() << std::endl;
}
//4.检查轮廓上各点到矩形中心的距离 计算每个轮廓距离的方差 选择方差最小的N个
vector<cv::Point2f> holeCenter;
vector<float> holeRadius;
int sCnt = 10;
if (contours4.size() >= sCnt)
{ //如果contours4的轮廓数大于N,则再从距离方差的角度筛选
//依次计算各轮廓上各点到轮廓中心距离序列的方差
std::vector<float> contours_variance(contours4.size());
for (int i = 0; i < contours4.size(); i++)
{
//计算轮廓的最小外接圆中心点
cv::Point2f circle_center;
float circle_radius;
minEnclosingCircle(contours4[i], circle_center, circle_radius);
//计算最小外接圆中心点到轮廓上各点的距离
std::vector<float> disList(contours4[i].size());
for (int j = 0; j < contours4[i].size(); j++)
{
cv::Point pos = contours4[i][j];
float dis = calDistance2D(pos.x, pos.y,
circle_center.x, circle_center.y);
disList[j] = dis;
}
//计算距离序列的方差
float varianceDis = calVariance_1DLine(disList);
contours_variance[i] = varianceDis;
std::cout << contours_variance[i] << std::endl;
}
//选择方差最小的N个轮廓
vector<vector<cv::Point>> contours4Clone(contours4);
contours4.clear();
for (int i = 0; i < sCnt; i++)
{
int minIdx = min_element(contours_variance.begin(), contours_variance.end()) - contours_variance.begin(); //在方差序列中找到最小值位置
std::vector<float>::iterator idxIt = contours_variance.begin() + minIdx; //方差序列中的迭代器
std::vector<vector<cv::Point>>::iterator contoursIt = contours4Clone.begin() + minIdx; //轮廓序列中的迭代器
contours4.push_back(*contoursIt); //把对应的轮廓存储到contours4中
contours_variance.erase(idxIt); //删除
contours4Clone.erase(contoursIt); //删除
}
}
//contours4中的轮廓依次计算外接矩形 轮廓中心和半径并存储contours5
vector<cv::Rect> boundRect;
vector<vector<cv::Point>> contours5;
holeCenter.clear();
holeRadius.clear();
for (int i = 0; i < contours4.size(); i++)
{
cv::Rect one_bRect;
one_bRect = boundingRect(Mat(contours4[i]));
cv::Point2f circle_center;
float circle_radius;
minEnclosingCircle(contours4[i], circle_center, circle_radius);
boundRect.push_back(one_bRect); //vector boundRect
holeCenter.push_back(circle_center);
holeRadius.push_back(circle_radius);
contours5.push_back(contours4[i]);
}
/// Draw polygonal contour + bonding rects + circles
drawImg = Mat::zeros(cannyImg.size(), CV_8UC3);
for (int i = 0; i < contours5.size(); i++) {
cv::Scalar color = GREEN;
drawContours(drawImg, contours5, i, color, 1, 8, vector<cv::Vec4i>(), 0, cv::Point());
rectangle(drawImg, boundRect[i].tl(), boundRect[i].br(), color, 2, 8, 0);
cv::circle(drawImg, holeCenter[i], 2, GREEN, 1, 8, 0);
cv::putText(drawImg, std::to_string(i), holeCenter[i], cv::FONT_HERSHEY_SCRIPT_SIMPLEX, 1, cv::Scalar(0, 0, 255), 1);
}
cv::imshow("s7 selected Contours5 Image", drawImg);
std::cout << " * Contour5 Size = " << contours5.size() << std::endl;
//根据圆周上的多点 计算圆心
cv::Point2f centerPos;
float cirRadius; //圆盘上孔到圆心的像素距离
calCircleParas(holeCenter, centerPos, cirRadius);
cv::circle(drawImg, centerPos, 2, cv::Scalar(0, 0, 255), 1, 8, 0);
cv::circle(drawImg, centerPos, cirRadius, cv::Scalar(0, 255, 0), 1, 8, 0);
cv::imshow("s8 selected Contours5 Image with circle", drawImg);
//在grad上标记
float R0_pixel, R1_pixel; //涂胶圆盘的外径和内径 像素单位
{
float pm_Ratio = cirRadius / RH_mm;
R0_pixel = pm_Ratio * R0_mm;
R1_pixel = pm_Ratio * R1_mm;
}
Mat roiOut = cv::Mat::zeros(image0.size(), CV_8UC1);
cv::circle(roiOut, centerPos, R1_pixel, cv::Scalar(255), -1, 8, 0);
Mat roiIn = cv::Mat::zeros(image0.size(), CV_8UC1);
cv::circle(roiIn, centerPos, R0_pixel, cv::Scalar(255), -1, 8, 0);
Mat roi = roiOut - roiIn;
Mat maskImg;
image0.copyTo(maskImg, roi);//掩膜提取
//保存图像后续***************************
cv::imwrite("C:\\Users\\sunrise_oneday\\Desktop\\20221107\\Roi\\1.jpg", maskImg);
//在src img上标记
cv::Mat colorSrc = cv::Mat::zeros(image0.size(), CV_8UC1);
image0.copyTo(colorSrc);
cv::cvtColor(colorSrc, colorSrc, cv::COLOR_GRAY2RGB);
cv::circle(colorSrc, centerPos, 2, RED, 1, 8, 0);
cv::circle(colorSrc, centerPos, cirRadius, GREEN, 1, 8, 0);
cv::circle(colorSrc, centerPos, R1_pixel, YELLOW, 1, 8, 0);
cv::circle(colorSrc, centerPos, R0_pixel, YELLOW, 1, 8, 0);
cv::imshow("s9 check on src Img", colorSrc);
float calDistance2D(float x0, float y0, float x1, float y1) //计算两点之间的距离
{
float res = sqrtf(pow((x0 - x1), 2) + pow((y0 - y1), 2));
return res;
}
float calAverageVal_1DLine(std::vector<float>& line) //计算一维数组的平均值
{
int cnt = line.size();
float sum = 0;
for (int i = 0; i < cnt; i++)
{
sum += line[i];
}
return (sum / cnt);
}
float calVariance_1DLine(std::vector<float>& line) //计算一维数组的方差
{
float avaVal = calAverageVal_1DLine(line);
int cnt = line.size();
float sum = 0;
for (int i = 0; i < cnt; i++)
{
sum += pow((line[i] - avaVal), 2);
}
float res = sum / (cnt - 1);
return res;
}
void calCircleParas(std::vector<cv::Point2f> points,
cv::Point2f& cirCenter,
float& cirRadius) //由圆周上的点来计算二维圆心与半径
{
int n = points.size();
cv::Mat dst = cv::Mat(n, 3, CV_32F, cv::Scalar(0));//初始化系数矩阵A
cv::Mat out = cv::Mat(n, 1, CV_32F, cv::Scalar(0));//初始化矩阵b
for (int i = 0; i < n; i++)
{
//计算n*3的系数矩阵
dst.at<float>(i, 0) = points[i].x;
dst.at<float>(i, 1) = points[i].y;
dst.at<float>(i, 2) = -1.;
//计算3*1的结果矩阵
out.at<float>(i, 0) = pow(points[i].x, 2) + pow(points[i].y, 2);
}
cv::Mat output;// = inv * out;//计算输出
//cv::Mat mM;
cv::solve(dst, out, output, cv::DECOMP_QR); //, CV_QR CV_SVD );
float a = output.at<float>(0, 0);
float b = output.at<float>(1, 0);
float c = output.at<float>(2, 0);
cirCenter.x = a / 2.;
cirCenter.y = b / 2.;
cirRadius = 0.5 * sqrtf(a * a + b * b - 4 * c);
}