工作原因,需要在开发板上对物体进行识别。一开始的思路是使用模板匹配对物体进行识别,但是这么做有几个缺点:
- 模板匹配过于昂贵,在开发板上以1080P分辨率匹配5次就要耗时约1秒
- 使用图像金字塔虽然可以提升速度,但是丢失的信息使准确率显著降低
- 模板匹配对角度不敏感,物体旋转角度以后则很难识别出来(可以旋转模型,但是速度又慢了)
- 匹配结果受光线影响较大
- 采图麻烦(懒
因此需要转换思路重新规划如何识别物体。因为开发板性能有限,而任务要求需要在半秒内识别出物体,并获取物体的中心点与角度。因此越轻量的解决办法越优先。
重新观察了一下物体,发现需要识别的物体有如下的特征。(敏感物体,不放图)物体为深绿色长方形,内部正中央的位置有一个圆,材质为塑料。(实际情况需要同时判别,并识别其它物体,这里仅以此具有代表性的物体举例)
遂放弃模板匹配,从形状入手规划一下新识别的流程——即识别内部的圆与物体的外接矩形轮廓。内部圆的圆心则为物体的圆心,外接矩形的角度则为物体的角度。
第一步:找圆心
OpenCV内部是有一个现成的识别圆形的方法的。
函数形式:
void HoughCircles(InputArray image, OutputArray circles, int method, double dp, double minDist, double param1 = 100, double param2 = 100, int minRadius = 0, int maxRadius = 0)
参数说明:
InputArray: 输入图像,数据类型一般用Mat型即可,需要是8位单通道灰度图像
OutputArray:存储检测到的圆的输出矢量
method:使用的检测方法,目前opencv只有霍夫梯度法一种方法可用,该参数填HOUGH_GRADIENT即可(opencv 4.1.0下)
dp:double类型的dp,用来检测圆心的累加器图像的分辨率于输入图像之比的倒数,且此参数允许创建一个比输入图像分辨率低的累加器。上述文字不好理解的话,来看例子吧。例如,如果dp= 1时,累加器和输入图像具有相同的分辨率。如果dp=2,累加器便有输入图像一半那么大的宽度和高度。
minDist:为霍夫变换检测到的圆的圆心之间的最小距离
param1:它是第三个参数method设置的检测方法的对应的参数。对当前唯一的方法霍夫梯度法CV_HOUGH_GRADIENT,它表示传递给canny边缘检测算子的高阈值,而低阈值为高阈值的一半。
param2:也是第三个参数method设置的检测方法的对应的参数,对当前唯一的方法霍夫梯度法HOUGH_GRADIENT,它表示在检测阶段圆心的累加器阈值。它越小的话,就可以检测到更多根本不存在的圆,而它越大的话,能通过检测的圆就更加接近完美的圆形了。
minRadius:表示圆半径的最小值
maxRadius:表示圆半径的最大值
以下为实际方法:(笔者的需求是找到最小的圆)
void LeftFrameAnalysisThread::findCircleCenter()
{
Mat ref;
vector<Vec3f> circles;
cvtColor(leftFrame, ref, CV_BGR2GRAY);
//return value: x, y, radius
HoughCircles(ref, circles, HOUGH_GRADIENT, 1, ref.rows/5, 150, 100, 0, 0);
int minimum;
int count;
if (circles.empty())
{
cout << "NO CIRCLE FOUND" << endl;
}
else
{
//find the smallest circle which is the slot
for (const auto& circle : circles)
{
int radius = cvRound(circle[2]);
if (radius < minimum)
{
minimum = radius;
}
}
Point center(cvRound(circles[count][0]), cvRound(circles[count][1]));
leftLocation = Point(center.x , center.y);
}
}
第二步:找矩形
2.1 过滤背景
在找矩形以前,我们首先需要过滤以下背景,最好的情况是物体为黑色,其他全为白色(这样后期可以用findContours找轮廓点)。因此我们需要首先对帧进行处理。众所周知过滤颜色的最好办法是将图像转化为HSV色彩空间,然后使用inrange方法生成针对特定颜色的掩膜,然后使用这个掩膜对图像的特定颜色进行保留以达到过滤的效果。
因此整体流程如下:
- 将采集图像从BGR空间转化为HSV色彩空间
- 使用inrange生成目标颜色的掩膜
- 使用bitwise_and将掩膜与原图像进行合并,获取只有目标颜色的图像
- 最后将图像转化为GRAY,并二值化,为后续的findContours做好准备
整体代码如下:
Mat LeftFrameAnalysisThread::extractColour(const cv::Mat& frame)
{
Mat ref = frame.clone();
cvtColor(ref, ref, COLOR_BGR2HSV); //to HSV
//extract colour area
Mat mask, result;
if (detectionMode == 0) //extract red colour
{
inRange(ref, Scalar(0, 143, 146), Scalar(180, 255, 255), mask);
}
else if (detectionMode == 1) //extract green colour
{
inRange(ref, Scalar(0, 127, 0), Scalar(120, 255, 120), mask);
}
bitwise_and(leftFrame, leftFrame, result, mask);
//convert into black and white
cvtColor(result, result, COLOR_BGR2GRAY);
//clean area
threshold(result, result, 20, 255, THRESH_BINARY); // to binary
return result;
}
2.2 颜色翻转
这里需要注意的是,上一步处理完的图像,目标物体为白色,而背景为黑色,我们需要将它反过来——即目标物体为黑色,背景为白色。方法也很简单,遍历所有像素点,将255变成0,0变成255即可。
代码如下:
Mat LeftFrameAnalysisThread::reverseBlackWhite(cv::Mat frame)
{
Mat ref = frame.clone();
int height = ref.rows;
int width = ref.cols;
for(int i= 0; i< height; i++)
{
for(int j=0; j< width; j++)
{
ref.at<uchar>(i, j)= 255- ref.at<uchar>(i, j); // 每一个像素反转
}
}
return ref;
}
2.3 找矩形
OpenCV没有直接识别矩形的方法,但是有通过点,画出矩形的方法。与一般的Rect不一样的是,通过RotateRect创建的矩形是带有角度的,正好符合此项目的需求。
关于RotateRect对于角度的判定,下面这张图解释的很清楚。
Opencv采用通用的图像坐标系,左上角为原点O(0,0),X轴向右递增,Y轴向下递增,单位为像素。
矩形4个顶点位置的确定,是理解其它各变量的基础,其中p[0]点是关键。
顶点p[0]的位置可以这样理解:
如果没有对边与Y轴平行,则Y坐标最大的点为p[0]点,如矩形(2)(3)(4);
如果有对边与Y轴平行,则有两个Y坐标最大的点。此时,左侧的点为p[0]点。如矩形(1)。
通俗的说就是RotatedRect的坐标点,Y轴最大的为P[0],p[0]围着center顺时针旋转, 旋转角度为负的话即是P[0]在左下角,为正P[0]是右下角
因此思路也很明确了:
- 首先进行闭操作,将之前没有连接到的边缘连接到一起
- 寻找轮廓
- 对获取到的轮廓,画最小内接矩形。通过面积大小/坐标位置过滤掉一些干扰结果
- 对长宽进行判断,默认为长大于宽,若不是则说明矩形为竖直状态,角度需要修正
- 获得最终的矩形的角度
代码如下:
void LeftFrameAnalysisThread::findRect()
{
///preprocess and store all the contours
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
RNG rng(12345);
blur(leftROI, leftROI, Size(3, 3));
Mat element = getStructuringElement(MORPH_RECT, Size(128, 64));
Mat element2 = getStructuringElement(MORPH_RECT, Size(32, 32));
dilate(leftROI, leftROI, element);
erode(leftROI, leftROI, element2);
findContours(leftROI, contours, hierarchy, CV_RETR_TREE, CHAIN_APPROX_NONE);
//if it is cash box, reverse the black and white pixels
if (detectionMode == 1)
{
leftROI = reverseBlackWhite(leftROI);
}
threshold(leftROI, leftROI, 20, 255, THRESH_BINARY); // to binary
imwrite("./Temp/afterProcessed.jpg",leftROI);
/// store rectangles based on all the contours
vector<RotatedRect> minRect( contours.size() );
int minArea, maxArea;
// different sizes for cash bag and cash box
if (detectionMode == 0)
{
minArea = 2000;
maxArea = 42000;
}else if (detectionMode == 1)
{
minArea = 10000;
maxArea = 160000;
}
for( int i = 0; i < contours.size(); i++ )
{
RotatedRect rect = minAreaRect(Mat(contours[i]));
cout << "area: ";
cout << rect.size.area() << endl;
if (rect.size.area() < minArea)
{
continue;
}
if (rect.size.area() > maxArea)
{
continue;
}
else if ((rect.center.x > 200 && rect.center.x < 800) && (rect.center.y > 200 && rect.center.y < 800))
{
if(rect.size.height > rect.size.width) //if the rectangle is placed vertically, adjust the degree
{
leftDegree = rect.angle;
leftDegree -= 90;
cout << "leftDegree: ";
cout << leftDegree << endl;
}
if (detectionMode == 0) // cash bags uses the rectangle to determine the center and degree
{
leftDegree = rect.angle;
leftLocation = rect.center;
cout << rect.center.x<<endl;
cout << rect.center.y<<endl;
minRect[i] = minAreaRect( Mat(contours[i]) );
break;
}
else if (detectionMode == 1) // cash box only uses the rectangle to determine the angle
{
leftDegree = rect.angle;
minRect[i] = minAreaRect( Mat(contours[i]) );
break;
}
}
}
/// draw rotatable rectangles
Mat drawing = Mat::zeros( leftROI.size(), CV_8UC3 );
for( int i = 0; i< contours.size(); i++ )
{
Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
// rotated rectangle
Point2f rect_points[4];
minRect[i].points( rect_points );
for( int j = 0; j < 4; j++ )
{
line( drawing, rect_points[j], rect_points[(j+1)%4], color, 1, 8 );
}
}
imwrite("./Temp/contours.jpg",drawing);
}
至此,物体的中心点与物体的角度都获取到了。
文章评论