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()); /<vector>/<iostream>/<opencv2>/<code>
}
cout <
namedWindow("Final result", CV_WINDOW_NORMAL);
imshow("Final result", markersImg);
}
int main(){
test();
waitKey(0);
return 0;
}
效果圖:
類似問題的解決思路:首先尋找一個合適的二值化方法,二值化之後看要不要做形態學操作(如果通過形態學操作後效果很好,能把對象分離出來,即沒有相連,獨立出來),再做距離變換,郵件裡距離變換再選擇合適的二值化方法再做二值化,再對它進行分割,最終分割出來的二值圖像,因為要統計它的個數,通過 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); /<vec4i>/<vector>/<iostream>/<opencv2>/<code>
cout < cout <
namedWindow("Result image", CV_WINDOW_NORMAL);
imshow("Result image", connImg);
}
}
int main(){
test();
waitKey(0);
return 0;
}
效果圖:
閱讀更多 業餘天王 的文章