OpenCV C++案例实战九《对象计数》

OpenCV C++案例实战九《对象计数》


前言

本文将使用OpenCV C++ 进行对象计算。

一、图像预处理

请添加图片描述

原图如图所示。本案例想做的是统计图像中有多少个物体。简单来说就是通过统计有效轮廓来计数。本案例其实最重要的是如何进行图像预处理,如何才能够将这些轮廓有效区分开。所以,具体图像要设定一个符合特定需求的算法。接下来,我将一一演示如何一步步进行操作的。

1.灰度、阈值

首先进行图像灰度化,再进行二值化处理。这些都是很基本的图像预处理操作。由于我们的图像是单峰图像,在这是使用的是THRESH_TRIANGLE做阈值。

	Mat gray;
	cvtColor(src, gray, COLOR_BGR2GRAY);
<span class="token comment">//单峰图像使用THRESH_TRIANGLE做阈值</span>
Mat thresh<span class="token punctuation">;</span>
<span class="token function">threshold</span><span class="token punctuation">(</span>gray<span class="token punctuation">,</span> thresh<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">255</span><span class="token punctuation">,</span> THRESH_BINARY_INV <span class="token operator">|</span> THRESH_TRIANGLE<span class="token punctuation">)</span><span class="token punctuation">;</span>

    THRESH_TRIANGLE
    请添加图片描述
    THRESH_OTSU
    请添加图片描述
    结果如图所示。如果使用THRESH_OTSU的话,效果远没有THRESH_TRIANGLE好。

    2.腐蚀

    经过二值化得到的图像明显黏在一起的。所以得使用腐蚀操作将它们稍微分离一下。在这里,我使用的kernel稍微大了一点。这个根据图像特征自行设定。

    	//进行腐蚀操作,稍微断开一些联通区域
    	Mat kernel = getStructuringElement(MORPH_RECT, Size(15, 15));
    	Mat erosion;
    	erode(thresh, erosion, kernel);
    
     
     

      如图为进行腐蚀操作后的效果,可以看出已经稍微分离了一点,但还不能完全分离。所以还得进行进一步操作。
      请添加图片描述

      3.距离变换

      OpenCV 的distanceTransform 用于计算图像中每一个非零点距离自己最近零点的距离。图像上越亮的点,代表离零点距离越远。进行距离变换,可以查找出亮包,找出物体的中心。

      	//进行距离变换,查找出亮包 找出物体的中心
      	Mat dist;
      	distanceTransform(erosion, dist, DIST_L2, 3);
      	normalize(dist, dist, 0, 1, NORM_MINMAX);
      
       
       

        请添加图片描述

        4.自适应阈值

        	//使用自适应阈值
        	Mat dist_8U;
        	dist.convertTo(dist_8U, CV_8UC1);
        	adaptiveThreshold(dist_8U, dist_8U, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 81,0);
        

          通过使用自适应阈值,已经可以将物体完全分离开了。不过,有些轮廓是断开的,所以还得通过膨胀操作将它们有效连接起来作为一个整体。
          请添加图片描述

          5.膨胀

          	//进行膨胀处理,将有效轮廓联通
          	Mat dilation;
          	dilate(dist_8U, dilation, kernel);
          
           
           

            请添加图片描述
            至此,我们的图像预处理已经完成了。可以看出,我们已经将物体完全的分离开作为一个整体。接下来就是通过轮廓查找来进行计数了。

            二、轮廓查找

            	vector<vector<Point>>contours;
            	vector<vector<Point>>EffectiveContours;
            	findContours(dilation, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
            	//计算有效联通区域
            	for (int i = 0; i < contours.size(); i++)
            	{
            		double area = contourArea(contours[i]);
            
            	<span class="token keyword">if</span> <span class="token punctuation">(</span>area <span class="token operator">&gt;</span> <span class="token number">500</span><span class="token punctuation">)</span>
            	<span class="token punctuation">{<!-- --></span>
            		EffectiveContours<span class="token punctuation">.</span><span class="token function">push_back</span><span class="token punctuation">(</span>contours<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            	<span class="token punctuation">}</span>
            <span class="token punctuation">}</span>
            

              我们通过统计有效轮廓就可以统计出来到底有多少个物体了。接下来就是一些显示效果操作了。

              三、效果显示

              	for (int 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(0, 0, 255), 2);	
              		//rectangle(src, Rect(rect.x, rect.y, rect.width, rect.height), Scalar(0, 255, 0), 2);
              	}
              

              char text[10];
              sprintf_s(text, “%s%d”,“Total:”,EffectiveContours.size());
              putText(src, text, Point(10, 30), FONT_HERSHEY_SIMPLEX, 1, Scalar(0, 0, 255), 2);

                结果如图所示:
                请添加图片描述
                请添加图片描述

                四、源码

                #include<iostream>
                #include<opencv2/opencv.hpp>
                using namespace cv;
                using namespace std;
                

                int main()
                {

                Mat src = imread(“test-2.jpg”);
                if (src.empty())
                {
                cout << “No Image!” << endl;
                system(“pause”);
                return -1;
                }

                Mat gray;
                cvtColor(src, gray, COLOR_BGR2GRAY);

                //单峰图像使用THRESH_TRIANGLE做阈值
                Mat thresh;
                threshold(gray, thresh, 0, 255, THRESH_BINARY| THRESH_TRIANGLE);
                //imshow(“thresh”, thresh);

                //进行腐蚀操作,稍微断开一些联通区域
                Mat kernel = getStructuringElement(MORPH_RECT, Size(15, 15));
                Mat erosion;
                erode(thresh, erosion, kernel);
                //imshow(“erosion”, erosion);

                //进行距离变换,查找出亮包 找出物体的中心
                Mat dist;
                distanceTransform(erosion, dist, DIST_L2, 3);
                normalize(dist, dist, 0, 1, NORM_MINMAX);
                //imshow(“dist”, dist);

                //使用自适应阈值
                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(“close”, dilation);

                vector<vector<Point>>contours;
                vector<vector<Point>>EffectiveContours;
                findContours(dilation, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
                //计算有效联通区域
                for (int i = 0; i < contours.size(); i++)
                {
                double area = contourArea(contours[i]);

                if (area > 500)
                {
                EffectiveContours.push_back(contours[i]);
                }
                }

                for (int 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(0, 0, 255), 2);
                //rectangle(src, Rect(rect.x, rect.y, rect.width, rect.height), Scalar(0, 255, 0), 2);
                }

                char text[10];
                sprintf_s(text, “%s%d”,“Total:”,EffectiveContours.size());
                putText(src, text, Point(10, 30), FONT_HERSHEY_SIMPLEX, 1, Scalar(0, 0, 255), 2);

                imshow(“src”, src);
                waitKey(0);
                destroyAllWindows();
                system(“pause”);
                return 0;
                }


                  总结

                  本文使用OpenCV C++进行对象计数,关键步骤有以下几点。
                  1、使用灰度、阈值、形态学操作、距离变换等预处理操作将对象有效分离开成为一个整体。
                  2、通过统计有效轮廓数量从而统计出实际物体数量。

                  • 0
                    点赞
                  • 0
                    收藏
                    觉得还不错? 一键收藏
                  • 0
                    评论
                  评论
                  添加红包

                  请填写红包祝福语或标题

                  红包个数最小为10个

                  红包金额最低5元

                  当前余额3.43前往充值 >
                  需支付:10.00
                  成就一亿技术人!
                  领取后你会自动成为博主和红包主的粉丝 规则
                  hope_wisdom
                  发出的红包
                  实付
                  使用余额支付
                  点击重新获取
                  扫码支付
                  钱包余额 0

                  抵扣说明:

                  1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
                  2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

                  余额充值