前言:本示例是本人为了帮老师带实验课现学现卖做的一个小项目,使用的方法不一定是最合适的,大家可以作为参考。
运行环境:Windows + OpenCV 3.4.1 + C++,环境配置大家自己找教程吧,网上教程很多。
项目目标:我们对工件进行检测,目标是识别出工件二维 bounding box,以及获取工件的中心点像素坐标和旋转角度。效果如下:
原图
效果图
代码如下:
#include<opencv2\opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;
int main()
{
// 原图
Mat srcImg = imread("1.png");
Mat midImg;
Mat dstImg = srcImg.clone();
namedWindow("【原图】", WINDOW_NORMAL);
imshow("【原图】", srcImg);
// 灰度化
cvtColor(srcImg, midImg,COLOR_BGR2GRAY);
namedWindow("【灰度图】", WINDOW_NORMAL);
imshow("【灰度图】", midImg);
// 中值滤波
medianBlur(midImg, midImg, 9);
namedWindow("【滤波后】", WINDOW_NORMAL);
imshow("【滤波后】", midImg);
// 二值化
threshold(midImg, midImg, 100, 255, 0);
namedWindow("【二值图】", WINDOW_NORMAL);
imshow("【二值图】", midImg);
// 形态学滤波,开运算
Mat element = getStructuringElement(MORPH_RECT, Size(10, 10));
morphologyEx(midImg, midImg, MORPH_OPEN, element);
namedWindow("【开运算后】", WINDOW_NORMAL);
imshow("【开运算后】", midImg);
// 轮廓提取
// (注:六角螺母的外层轮廓为i=2时的轮廓,若只要该轮廓,将i替换为2即可)
vector<vector<Point>> contours;
findContours(midImg, contours, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
Mat midImg1 = Mat::zeros(midImg.rows, midImg.cols, CV_8UC3);
for (int i = 0; i < contours.size(); i++)
{
Scalar color(255, 255, 255);
drawContours(midImg1, contours, i, color, 2);
}
namedWindow("【轮廓图】", WINDOW_NORMAL);
imshow("【轮廓图】", midImg1);
// 创建最小包围矩形及获取中心点图像坐标
// (注:六角螺母的外层轮廓为i=2时的轮廓,若只要该轮廓,将i替换为2即可)
for (int i = 0; i < contours.size(); i++)
{
//每个轮廓
vector<Point> points = contours[i];
//对给定的2D点集,寻找最小面积的包围矩形
RotatedRect box = minAreaRect(Mat(points));
Point2f vertex[4];
//将box 中存储的4 个顶点的坐标 存储到vertex[0]~vertex[3]中去
box.points(vertex);
//打印中心点位置及外接矩形角度
cout << "中心点位置(第" << i << "条轮廓):" << box.center << endl;
cout << "外接矩形角度(第" << i << "条轮廓):" << box.angle << endl;
//绘制出最小面积的包围矩形
line(dstImg, vertex[0], vertex[1], Scalar(200, 255, 200), 3, LINE_AA);
line(dstImg, vertex[1], vertex[2], Scalar(200, 255, 200), 3, LINE_AA);
line(dstImg, vertex[2], vertex[3], Scalar(200, 255, 200), 3, LINE_AA);
line(dstImg, vertex[3], vertex[0], Scalar(200, 255, 200), 3, LINE_AA);
//绘制中心的光标,为了容易理解,此处为手动计算中心点,也可以直接使用 box.center
Point center, l, r, u, d;
center.x = (vertex[0].x + vertex[2].x) / 2.0;
center.y = (vertex[0].y + vertex[2].y) / 2.0;
l.x = center.x - 10;
l.y = center.y;
r.x = center.x + 10;
r.y = center.y;
u.x = center.x;
u.y = center.y - 10;
d.x = center.x;
d.y = center.y + 10;
line(dstImg, l, r, Scalar(200, 255, 200), 2, LINE_AA);
line(dstImg, u, d, Scalar(200, 255, 200), 2, LINE_AA);
}
namedWindow("【最小包围矩形及获取中心点】", WINDOW_NORMAL);
imshow("【最小包围矩形及获取中心点】", dstImg);
waitKey(0);
}
- 注:如上代码中的 1.png 即为如上原图
- 注:展示效果为代码中 i = 2 的输出结果
经代码处理的中间图像如下:
图像滤波的目的是在最大限度地减少噪声干扰的同时,尽可能多地保留目标的特征。
为了克服常见的线性滤波器带来的图像细节模糊以及保护边缘信息,我们的滤波处理方式选择非线性滤波中的中值滤波。
中值滤波的的基本思想是用像素点邻域的灰度值的中值来代替该像素点的灰度值,从而消除孤立的噪声点。该方法对于斑点噪声和椒盐噪声来说尤其有用,因为它不依赖于邻域内哪些与典型值差别很大的值。并且中值滤波具有保存边缘的特性,能很好的保护边缘信息。
膨胀与腐蚀是一对相反的操作,都是对图像中高亮部分而言的。简单来讲,膨胀是图像中的高亮部分进行膨胀,类似于“领域扩张”,效果为比原来拥有更大面积的高亮区域;腐蚀是图像中高亮部分被腐蚀,类似于“领域被蚕食”,效果为比原来拥有更小面积的高亮区域。
开运算,其实就是先腐蚀后膨胀的过程。其表达式为: dst=open(src)=dilate(erode(src)) 。
文通过调用 OpenCV 中的 FindContours()函数实现对轮廓的提取,通过调用 minAreaRect()函数实现获取最小外接矩形,并通过该函数返回值中参数获取中心点及旋转角度。
以上代码也是可以实现多目标检测的,只需要根据输入图像稍微调整一下二值化操作中的阈值参数,即可以得到如下效果。
原图:
效果图: