案例需求背景
农业领域经常需要计算对象个数或者在其他领域拍照自动计数,可以提高效率,降低成本
解决思路
- 二值分割
- 形态学处理
- 距离变换
- 连通区域计算
相应api
自适应阈值处理
在阈值处理操作中,仅通过设定固定阈值很难达到理想分割效果,我们需要从二值化的图像中分离目标区域和背景区域。实际上,目标和背景通常相互依存,我们可以从图像像素领域块的分布特征来自适应确定区域的二值化阈值。OpenCV中有自适应的阈值化函数。
void adaptiveThreshold(InputArray src,
OutputArray dst,
double maxValue,
int adaptiveMethod,
int thresholdType,
int bolckSize,
double C)
参数介绍
- srcgray:输入图像,单通道,单8位浮点类型Mat即可
- dstimage:输出图像
- maxVal:预设最大值
- adaptiveMethod:指定自适应阈值算法。可选择ADAPTIVE_THRESH_MEAN_C 或 ADAPTIVE_THRESH_GAUSSIAN_C两种
- thresholdType:指定阈值类型。可选择THRESH_BINARY或者THRESH_BINARY_INV两种。(即二进制阈值或反二进制阈值)
- blockSize:用来计算区域阈值,一般选择为3、5、7…等
- 参数C表示与算法有关的参数,它是一个从均值或加权均值提取的常数,可以是负数
对参数4与参数7内容的解释:
自适应阈值化计算大概过程是为每一个象素点单独计算的阈值,即每个像素点的阈值都是不同的,就是将该像素点周围B*B区域内的像素加权平均,然后减去一个常数C,从而得到该点的阈值。B由参数6指定,常数C由参数7指定。
ADAPTIVE_THRESH_MEAN_C,为局部邻域块的平均值,该算法是先求出块中的均值,再减去常数C。
ADAPTIVE_THRESH_GAUSSIAN_C,为局部邻域块的高斯加权和。该算法是在区域中(x, y)周围的像素根据高斯函数按照他们离中心点的距离进行加权计算,再减去常数C。
代码
#include <opencv2/opencv.hpp>
#include <opencv2/xfeatures2d.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;
#define PIC_PATH "/work/opencv_pic/"
#define PIC_NAME "case4.png"
int main(void)
{
Mat src,gray_src;
//获取完整的图片路径及名称
string pic = string(PIC_PATH)+string(PIC_NAME);
//打印图片路径
cout << "pic path is :"<<pic<<endl;
//读取图片
src = imread(pic);
//判断图片是否存在
if(src.empty())
{
cout<<"pic is not exist!!!!"<<endl;
return -1;
}
namedWindow("原图显示",WINDOW_NORMAL);
imshow("原图显示",src);
cvtColor(src,gray_src,COLOR_BGR2GRAY);
//图像二值化
Mat binaryimg;
//THRESH_TRIANGLE THRESH_OTSU两者都是自由确定阈值的宏 对于直方图只存在单峰的图像 THRESH_TRIANGLE 效果很好
//在这里检测图可以看出属于直方图单峰的情况 颜色相对单一 颜色区分明显
threshold(gray_src,binaryimg,0,255,THRESH_BINARY | THRESH_TRIANGLE );
imshow("二值化图像",binaryimg);
//图形学操作 为后续检测减少障碍 这里采取膨胀 减小黑色区域 使得粘连情况稍微好一点
Mat kernel = getStructuringElement(MORPH_RECT,Size(3,3),Point(-1,-1));
dilate(binaryimg,binaryimg,kernel,Point(-1,-1));
//距离变换 每个元素都能解析出单峰,距离计算原理 计算图像距离0点最近的距离
//图片做位反 将背景处理为0 特征区域处理为大的值
bitwise_not(binaryimg,binaryimg,Mat());
namedWindow("位反处理图像",WINDOW_NORMAL);
imshow("位反处理图像",binaryimg);
//距离变换
distanceTransform(binaryimg,binaryimg,DIST_L2,3);
//归一化
normalize(binaryimg,binaryimg,0,1.0,NORM_MINMAX);
namedWindow("距离变换图像",WINDOW_NORMAL);
imshow("距离变换图像",binaryimg);
//对距离变化图再进行阈值二值化处理 找出各个元素峰值
Mat dst_8u;
binaryimg.convertTo(dst_8u,CV_8UC1);
adaptiveThreshold(dst_8u,dst_8u,255,ADAPTIVE_THRESH_GAUSSIAN_C,THRESH_BINARY,175,0.0);
namedWindow("阈值自适应图像",WINDOW_NORMAL);
imshow("阈值自适应图像",dst_8u);
//由于前面做了bitwise_not 这里进行一步腐蚀 减小白色区域 消除粘连
kernel = getStructuringElement(MORPH_RECT,Size(3,3),Point(-1,-1));
erode(dst_8u,dst_8u,kernel,Point(-1,-1),3);
namedWindow("进一步腐蚀图像",WINDOW_NORMAL);
imshow("进一步腐蚀图像",dst_8u);
//联通区域计数
vector<vector<Point>> contours;
findContours(dst_8u,contours,RETR_EXTERNAL,CHAIN_APPROX_SIMPLE);
//绘制轮廓
RNG rng(123456);
Mat contourimg = Mat::zeros(src.size(),CV_8UC3);
for(size_t i=0;i<contours.size();i++)
{
drawContours(contourimg,
contours,i,Scalar(rng.uniform(0,255),rng.uniform(0,255),rng.uniform(0,255)),
-1,8,Mat());
}
namedWindow("绘制轮廓",WINDOW_NORMAL);
imshow("轮廓绘制",contourimg);
cout << "一共有 " << contours.size() <<" 个玉米粒"<<endl;
waitKey(0);
destroyAllWindows();
return 0;
}