打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
OpenCV C 案例实战九《对象计数》

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

  • 前言
  • 一、图像预处理
    • 1.灰度、阈值
    • 2.腐蚀
    • 3.距离变换
    • 4.自适应阈值
    • 5.膨胀
  • 二、轮廓查找
  • 三、效果显示
  • 四、源码
  • 总结

前言

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

一、图像预处理

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

1.灰度、阈值

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

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

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

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]);

		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);

结果如图所示:


四、源码

#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、通过统计有效轮廓数量从而统计出实际物体数量。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
OpenCV-字典法实现数字识别(尺寸归一化+图像差值)
分水岭算法(理论 opencv实现)
自适应阈值(adaptiveThreshold)分割原理及实现
lesson2 —— OpenCV:Mat类的图像设置ROI
4、Halcom区域分割和区域边缘膨胀、腐蚀
直方图
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服