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++實現)

          


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM