引言
記錄兩個基於二值圖像分析的較為經典的例子,希望能夠得到更多的啟發,從而想到更好的解決類似問題的思路。
💙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++實現)