圖像處理案例實戰

1. 對象計數

農業領域經常需要計算對象額數或者在其它領域拍照自動計算,可提高效率,降低成本。現在需要統計計算圖片中玉米的顆粒數。


圖像處理案例實戰

方法:二值分割 + 形態學 + 距離變換 + 連通區域計算

<code>#include<opencv2>
#include<iostream>
using namespace std;
using namespace cv;
Mat srcImg, grayImg, binaryImg, dstImg;
void test(){
    srcImg = imread("case4.jpg");
    if (srcImg.empty())
    {
        cout <    }
    namedWindow("Original image", CV_WINDOW_NORMAL);
    imshow("Original image", srcImg);

    cvtColor(srcImg, grayImg, COLOR_BGR2GRAY);
    //1.二值分割
    threshold(grayImg, binaryImg, 0, 255, THRESH_BINARY | THRESH_TRIANGLE);  // (THRESH_BINARY | THRESH_OTSU,THRESH_BINARY | THRESH_TRIANGLE)

    namedWindow("binary image", CV_WINDOW_NORMAL);
    imshow("binary image", binaryImg);

    //2.形態學操作(因為背景是白色,所以通過膨脹來做)
    Mat kernel = getStructuringElement(MORPH_RECT, Size(7, 7), Point(-1, -1));
    dilate(binaryImg, binaryImg, kernel, Point(-1, -1),1);

    namedWindow("dilate image", CV_WINDOW_NORMAL);
    imshow("dilate image", binaryImg);

    //3.距離變換
    Mat distImg;
    bitwise_not(binaryImg, binaryImg);  //取反操作,這樣子背景就變成黑色,對象變成白色,才好做距離變換
    distanceTransform(binaryImg, distImg, CV_DIST_L2, 3);
    normalize(distImg, distImg, 0, 1.0, NORM_MINMAX);  //歸一化(小的距離顯示低的灰度0 - 1.0之間),找到最亮的地方(得到小山頭)

    namedWindow("dist image", CV_WINDOW_NORMAL);
    imshow("dist image", distImg);


    //4.閾值化二值分割(因為上面是求得到的是0 - 1.0之間的距離,要把它轉到8位的圖像)
    Mat distImg8u;
    distImg.convertTo(distImg8u, CV_8U);
    adaptiveThreshold(distImg8u, distImg8u, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 85, 0.0);  //局部閾值化,用高斯方法,85這個參數一定要是奇數
    kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
    dilate(distImg8u, distImg8u, kernel, Point(-1, -1), 5 );  //對距離變換的結果稍微的調整,把沒連接起來的接上(調最後一個參數)

    namedWindow("distBinary image", CV_WINDOW_NORMAL);
    imshow("distBinary image", distImg8u);

    //5.聯通區域計算
    vector<vector>> contours;
    findContours(distImg8u, contours, CV_RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);  //獲取連通區域的個數

    //6.把它畫出來
    Mat markersImg = Mat::zeros(srcImg.size(), CV_8UC3);
    RNG rng(12345);
    for (int i = 0; i     {
        drawContours(markersImg, contours, static_cast(i), Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)), -1, 8, Mat());
    }
    cout <
    namedWindow("Final result", CV_WINDOW_NORMAL);
    imshow("Final result", markersImg);
}
int main(){
    test();
    waitKey(0);
    return 0;
}
/<vector>/<iostream>/<opencv2>/<code>

效果圖:


圖像處理案例實戰

類似問題的解決思路:首先尋找一個合適的二值化方法,二值化之後看要不要做形態學操作(如果通過形態學操作後效果很好,能把對象分離出來,即沒有相連,獨立出來),再做距離變換,郵件裡距離變換再選擇合適的二值化方法再做二值化,再對它進行分割,最終分割出來的二值圖像,因為要統計它的個數,通過 findContours() 尋找輪廓,連通區域,統計連接矩形就可以實現。

2. 對象提取與檢測

照片來自太空我望遠鏡的星雲圖像,科學家想知道它的面積和周長。


圖像處理案例實戰

方法:二值分割 + 形態學 + 輪廓提取

例子代碼:

<code>#include<opencv2>
#include<iostream>
using namespace std;
using namespace cv;
void test(){
    Mat srcImg = imread("case6.jpg");
    if (srcImg.empty())
    {
        cout <    }
    namedWindow("Original image", CV_WINDOW_NORMAL);
    imshow("Original image", srcImg);

    //原始圖像周圍有小點噪聲,通過高斯模糊來降噪
    Mat blurImg;  
    GaussianBlur(srcImg, blurImg, Size(15, 15), 0, 0);  //使用 15 x 15 的內核降噪
    namedWindow("Blur image", CV_WINDOW_NORMAL);
    imshow("Blur image", srcImg);

    Mat grayImg, binaryImg;
    cvtColor(blurImg, grayImg, COLOR_BGR2GRAY);  //將原圖轉為灰度圖像

    //觀察原圖,背景如果用直方圖來衡量的話,直方圖應該很高,會有一個單峰,所以用 THRESH_BINARY | THRESH_TRIANGLE
    threshold(grayImg, binaryImg, 0, 255, THRESH_BINARY | THRESH_TRIANGLE);  //二值分割
    namedWindow("Binary image", CV_WINDOW_NORMAL);
    imshow("Binary image", binaryImg);

    //形態學操作
    Mat morphImg;
    Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, 1));
    morphologyEx(binaryImg, morphImg, MORPH_CLOSE, kernel, Point(-1, -1), 2);  //閉操作,把對象內的小洞補全  2 為迭代次數


    namedWindow("Morphology image", CV_WINDOW_NORMAL);
    imshow("Morphology image", morphImg);

    //獲取最大輪廓
    vector<vector>> contours;
    vector<vec4i> hireachy;
    findContours(morphImg, contours, hireachy, 
                 CV_RETR_EXTERNAL, CHAIN_APPROX_SIMPLE,
                 Point()); // CV_RETR_TREE 會把裡面的輪廓也畫出,CV_RETR_EXTERNAL只畫外面的輪廓

    Mat connImg = Mat::zeros(srcImg.size(), CV_8UC3);
    for (int i = 0; i     {
        Rect rect = boundingRect(contours[i]);  //找到最大的邊緣
        if (rect.width             continue;
        if (rect.width > (srcImg.cols - 20))  //過濾掉最外邊的輪廓
            continue;

        double area = contourArea(contours[i]);  //計算面積 (求面積是要把前景變成白色,背景變成黑色)
        double len = arcLength(contours[i], true);  //計算周長 true 求的是整個閉合的周長,false 為不閉合
        drawContours(connImg, contours, static_cast(i), Scalar(0, 0, 255), 1, 8, hireachy);

        cout <        cout <
        namedWindow("Result image", CV_WINDOW_NORMAL);
        imshow("Result image", connImg);
    }

}

int main(){
    test();
    waitKey(0);
    return 0;
}
/<vec4i>/<vector>/<iostream>/<opencv2>/<code>

效果圖:


圖像處理案例實戰


分享到:


相關文章: