本文将使用OpenCV C++ 进行对象计算。
原图如图所示。本案例想做的是统计图像中有多少个物体。简单来说就是通过统计有效轮廓来计数。本案例其实最重要的是如何进行图像预处理,如何才能够将这些轮廓有效区分开。所以,具体图像要设定一个符合特定需求的算法。接下来,我将一一演示如何一步步进行操作的。
首先进行图像灰度化,再进行二值化处理。这些都是很基本的图像预处理操作。由于我们的图像是单峰图像,在这是使用的是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
经过二值化得到的图像明显黏在一起的。所以得使用腐蚀操作将它们稍微分离一下。在这里,我使用的kernel稍微大了一点。这个根据图像特征自行设定。
//进行腐蚀操作,稍微断开一些联通区域
Mat kernel = getStructuringElement(MORPH_RECT, Size(15, 15));
Mat erosion;
erode(thresh, erosion, kernel);
如图为进行腐蚀操作后的效果,可以看出已经稍微分离了一点,但还不能完全分离。所以还得进行进一步操作。
OpenCV 的distanceTransform 用于计算图像中每一个非零点距离自己最近零点的距离。图像上越亮的点,代表离零点距离越远。进行距离变换,可以查找出亮包,找出物体的中心。
//进行距离变换,查找出亮包 找出物体的中心
Mat dist;
distanceTransform(erosion, dist, DIST_L2, 3);
normalize(dist, dist, 0, 1, NORM_MINMAX);
//使用自适应阈值
Mat dist_8U;
dist.convertTo(dist_8U, CV_8UC1);
adaptiveThreshold(dist_8U, dist_8U, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 81,0);
通过使用自适应阈值,已经可以将物体完全分离开了。不过,有些轮廓是断开的,所以还得通过膨胀操作将它们有效连接起来作为一个整体。
//进行膨胀处理,将有效轮廓联通
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、通过统计有效轮廓数量从而统计出实际物体数量。
联系客服