原文在这里,这里是自己的笔记。
效果:
代码思路:
还是用联通区域数量(轮廓数量)来实现玉米粒计数,但是这里很值得学习的是怎么把存在重叠区域的玉米粒分离开。
- 使用单峰阈值处理获得二值化图像(比自适应阈值的效果还有好)
- 进行腐蚀操作,稍微断开一些联通区域,初步将玉米粒分类
- 进行距离变换,查找出亮包,找出物体的中心;使用自适应阈值操作得到完全分开的玉米粒
- 膨胀操作将一些断开的的轮廓进行联通
- 通过计算有效联通区域进行玉米粒计数
代码有很多注释,很容易看懂:
#include <iostream>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat src, gray;
src = imread("/home/jason/work/01-img/yumi.png");
cvtColor(src, gray, COLOR_BGR2GRAY);
imshow("gray", gray);
//单峰图像使用THRESH_TRIANGLE做阈值,比THRESH_OTSU效果还好
// 这样就不用调阈值了,效果也很好
Mat thresh;
threshold(gray, thresh, 0, 255, THRESH_BINARY_INV | THRESH_TRIANGLE);
imshow("thresh",thresh);
// 腐蚀操作,稍微断开一些联通区域,将玉米粒分离
Mat kernel = getStructuringElement(MORPH_RECT, Size(15,15));/*kernelSize大一点效果比较好,5就效果很不好*/
Mat erosion;
erode(thresh, erosion, kernel);
imshow("erosi", erosion);
// 上一步操作还没有完全分离
// 所以进行距离变换,查找出亮包,找出物体的中心
Mat dist;
distanceTransform(erosion, dist, DIST_L2, 3);
normalize(dist,dist,1,0,NORM_MINMAX);
imshow("dist", dist);
cout<<"channel:" << dist.channels();
// 然后对距离变换结果,使用自适应阈值
// 会得到完全分开的玉米粒
Mat dist_8U;
dist.convertTo(dist_8U, CV_8UC1);/*本身就是单通道,可以不运行*/
adaptiveThreshold(dist_8U, dist_8U, 255, ADAPTIVE_THRESH_GAUSSIAN_C,
THRESH_BINARY, 81, 0);
imshow("dist_8U",dist_8U);
// 但是有些轮廓是断开的的 ,所以还得通过膨胀操作将有效轮廓联通
Mat dilation;
dilate(dist_8U, dilation, kernel);
imshow("dilation", dilation);
// 现在就可以通过计算有效联通区域来计数了
vector<vector<Point>> contours;
vector<vector<Point>> EffectiveContours;
findContours(dilation, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
for(size_t i=0; i<contours.size(); i++)
{
double area = contourArea(contours[i]);
if (area>500)
{
EffectiveContours.push_back(contours[i]);
}
}
// 展示
for(size_t i=0; i<EffectiveContours.size(); i++)
{
Rect rect = boundingRect(EffectiveContours[i]);
putText(src, to_string(i+1), Point(rect.x+10, rect.y+30), FONT_HERSHEY_SIMPLEX, 1, Scalar(255, 0,0),2);
}
char text[10];
sprintf(text, "%s%d", "Toal:", EffectiveContours.size());/* %s%d 整型化为字符串 */
putText(src, text, Point(10, 30), FONT_HERSHEY_SIMPLEX,1,Scalar(255,0,0),2);
imshow("result", src);
waitKey();
cout << "Hello World!" << endl;
return 0;
}