opencv实战——寻找缺失和靶心
引言
记录两个基于二值图像分析的较为经典的例子,希望能够得到更多的启发,从而想到更好的解决类似问题的思路。
💙01
问题一:寻找靶心
仔细观察上图,可以看到两个最直接的是靶心有十字交叉线,而在OpenCV形态学处理中,支持十字交叉结构元素,所以我们可以先检测两条线,然后获取十字交叉结构,最后对结构进行轮廓分析,获取中心点,即可获得最终的靶心位置,最终寻找到的靶心位置。
opencv实现:
Mat src = imread("D:/opencv练习图片/寻找靶心.jpg"); imshow("原图", src); Mat gray,binary,hline,vline; cvtColor(src, gray, COLOR_RGB2GRAY); //二值化 threshold(gray, binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU); imshow("二值化", binary); //形态学处理 Mat kernel1 = getStructuringElement(MORPH_RECT, Size(70, 1), Point(-1, -1)); Mat kernel2 = getStructuringElement(MORPH_RECT, Size(1, 50), Point(-1, -1)); morphologyEx(binary, hline, MORPH_OPEN, kernel1, Point(-1, -1)); morphologyEx(binary, vline, MORPH_OPEN, kernel2, Point(-1, -1)); vector<vector<Point>> contours; findContours(hline, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point()); Mat mask = Mat::zeros(hline.size(), CV_8U); int max = 0; int index = 0; for (int i = 0; i < contours.size(); i++) { double area = contourArea(contours[i]); if (area > max) { max = area; index = i; cout << index << endl; drawContours(mask, contours, index, Scalar(255, 255, 255), -1, 8); } } imshow("h", mask); vector<vector<Point>> contours1; findContours(vline, contours1, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point()); int max1 = 0; int index1 = 0; for (int i = 0; i < contours1.size(); i++) { double area = contourArea(contours1[i]); if (area > max1) { max1 = area; index1 = i; drawContours(mask, contours1, index1, Scalar(255, 255, 255), -1, 8); } } imshow("提取十字", mask); Mat kernel3 = getStructuringElement(MORPH_CROSS, Size(13, 13), Point(-1, -1)); morphologyEx(mask, mask, MORPH_OPEN, kernel3, Point(-1, -1)); imshow("交点", mask); vector<vector<Point>> contours3; findContours(mask, contours3, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point()); RotatedRect rect = minAreaRect(contours3[0]); Point center = rect.center; circle(src, center, 4, Scalar(0, 0, 255), 1, 8); imshow("结果", src);
💛02
问题二:计数并寻找其中的缺失点
仔细分析图像发现,中间都毫无另外的有个白色很亮的圆圈。
因此我们可以通过二值图像分析来提取 + 轮廓分析来提取到这些点,得到这些轮廓点之后通过分析整个轮廓区域得到倾斜角度,进行纠偏,然后通过X与Y投影进行分割,得到每个零件的中心位置坐标,根据每一行的间隔设置阈值,从而实现缺少部分部分的标出与件数统计
opencv分析:
(一)读入图像,预处理(形态学梯度,二值化)
Mat srcImage = imread("D:/opencv练习图片/工件计数.jpg"); namedWindow("原始图"); imshow("原始图", srcImage); Mat grayImage; cvtColor(srcImage, grayImage, COLOR_RGB2GRAY); Mat kernal = getStructuringElement(MORPH_RECT, Size(3, 3)); Mat gradientImage; morphologyEx(grayImage, gradientImage, MORPH_GRADIENT, kernal); namedWindow("gradientImage形态学梯度"); imshow("gradientImage形态学梯度", gradientImage); Mat thresholdImage; threshold(gradientImage, thresholdImage, 0, 255, THRESH_OTSU); namedWindow("二值化OTSU图"); imshow("二值化OTSU图", thresholdImage);
(二)再次形态学(保留圆形区域)
kernal = getStructuringElement(MORPH_ELLIPSE, Size(5, 5)); Mat openImage, closeImage; morphologyEx(thresholdImage, openImage, MORPH_OPEN, kernal); imshow("openImage", openImage); kernal = getStructuringElement(MORPH_ELLIPSE, Size(10, 10)); morphologyEx(openImage, closeImage, MORPH_CLOSE, kernal); imshow("closeImage", closeImage);
开运算:分割区域 闭运算:填充孔洞
(三)寻找轮廓并筛选,最后提取点
vector<vector<Point> > contours; vector<Vec4i> hierarchy; findContours(closeImage, contours, hierarchy, RETR_EXTERNAL,CHAIN_APPROX_SIMPLE); Mat resultImage = Mat::zeros(grayImage.rows, grayImage.cols, grayImage.type()); int total = 0;//计数值 for (int i = 0; i < contours.size(); i++) { double area = contourArea(contours[i]); if (area < 55) continue;//面积大于55的继续执行 total++;//计算有多少面积大于55的个数(工件数) RotatedRect rect = minAreaRect(contours[i]); circle(resultImage, rect.center, 5, Scalar(255), -1);//用圆显示 } imshow("提取点", resultImage);
(四)对点区域求最小外接矩形,计算角度并矫正
vector<Point> pts; for (int i = 0; i < resultImage.rows; i++) { for (int j = 0; j < resultImage.cols; j++) { if (resultImage.ptr<uchar>(i)[j] == 255) { pts.push_back(Point(j, i)); } } } RotatedRect rect = minAreaRect(pts); Point2f rectVertex[4]; rect.points(rectVertex); for (int i = 0; i < 4; i++) { putText(resultImage, to_string(i), rectVertex[i], FONT_HERSHEY_SIMPLEX, 1.0, Scalar(200)); line(resultImage, rectVertex[i], rectVertex[(i + 1) % 4], Scalar(100), 2, 8); } cout << "角度为:" << rect.angle; cout << "宽度为:" << rect.size.width; cout << "高度为:" << rect.size.height; namedWindow("resultImage"); imshow("最小外接矩形", resultImage); float angle = rect.angle - 90; Point center = rect.center; //计算旋转后的画布大小,并将旋转中心平移到新的旋转中心 Rect bbox = RotatedRect(center, Size(srcImage.cols, srcImage.rows), angle).boundingRect(); Mat matrix = getRotationMatrix2D(rect.center, angle, 1); matrix.at<double>(0, 2) += bbox.width / 2.0 - center.x; matrix.at<double>(1, 2) += bbox.height / 2.0 - center.y; warpAffine(resultImage, resultImage, matrix, resultImage.size()); warpAffine(srcImage, srcImage, matrix, srcImage.size()); imshow("矫正原图", srcImage); imshow("矫正二值图", resultImage);
😃 注意:
再通过RotatedRect类获得的矩形角度angle,在计算旋转矩阵时,应该再减去90度,才是旋转角度。
(五)构造查找近邻函数Y_projection()
void Y_projection(Mat &warp, Mat &src, int max_gap, int &first, int &end) { vector<int> y_bins; for (int i = 0; i < warp.cols; i++) { int found_y = 0; for (int j = first; j < end; j++) { if (warp.at<uchar>(j, i) == 255) { found_y += 1; } } if (found_y > 0) { y_bins.push_back(i); } } vector<int> y_tbins; for (int i = 0; i < y_bins.size() - 1; i++) { int gap = y_bins[i + 1] - y_bins[i]; if (gap >= 15) { y_tbins.push_back(y_bins[i + 1] - (gap / 2)); } if (gap >= 50) { circle(src, Point(y_bins[i + 1] - (gap / 2), (end - (end - first) / 2)), 5, Scalar(0, 255, 255), -1); } } }
(六)缺失排查,并显示结果
//计算每行工具所在的行数 vector<int> bins; vector<int> tbins; for (int i = 0; i < resultImage.rows; i++) { int found = 0; for (int j = 0; j < resultImage.cols; j++) { if (resultImage.at<uchar>(i, j) == 255) { found += 1; } } if (found > 0) { bins.push_back(i); } } for (int i = 0; i < (bins.size() - 1); i++) { int gap = bins[i + 1] - bins[i]; if (gap >= 15) { cout << "tbins: " << bins[i + 1] - (gap / 2) << endl; tbins.push_back(bins[i + 1] - (gap / 2)); } } //逐行排查缺失工具的位置 Mat dstImage; srcImage.copyTo(dstImage); int h = resultImage.rows - 1; for (int i = 0; i < tbins.size(); i++) { if (i == 0) { //第一排工具所占行数从0到第一个位置 Y_projection(resultImage, dstImage, 50, i, tbins[i]); } else if (i == (tbins.size() - 1)) { //最后一排工具所占行数从最后一个位置到图片最后一行 Y_projection(resultImage, dstImage, 50, tbins[i], h); } else { //中间行工具所占行数为上下两个位置之间 int end = tbins[i] - 1; Y_projection(resultImage, dstImage, 50, tbins[i - 1], end); } } putText(dstImage, "numbers: " + to_string(total), Point(50, 50), FONT_HERSHEY_SIMPLEX, 1.0, Scalar(0, 0, 255)); imshow("dstImage", dstImage); waitKey(0);
摘录于:Opencv学堂(自己用c++实现)