如何对矩形表面进行角点检测
利用OPENCV对矩形表面进行角点检测
简单介绍一下思路,标记一个很像矩形的表面,首先得对图像或视频(以下只说图像,其实视频一样道理)进行预处理,尽可能消除噪声、不感兴趣部分的干扰,比如说我这个示例的图像中有几处灯光,但是我只想提取黄色两条小灯以及其连成的矩形。
示例目标大概样子
思路+代码分析
以下是一些头文件,有些可能用不上,这里用了ros在下一遍文章中将会进一步讲到如何用rviz显示提取的部分仿真内容,这里可以不包括,然后iostream是用来输出变量值来检查修正代码的,也用不上了。其他是一些Opencv的头文件。
#include <ros/ros.h>
#include <iostream>
#include <opencv2/opencv.hpp>
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <opencv2/highgui.hpp>
#include <opencv2/core/core.hpp>
然后声明一下cv和std命名空间,
using namespace std;
using namespace cv;
图像预处理请具体情况具体分析!示例首先通过转换成色调、饱和度、明度空间,然后通过灯光黄色提出黄色,然后二值化处理,最后是闭操作联通区域,消除了旁边的小光点,然后膨胀(可调整腐蚀核)。
cvtColor(frame, hsv, COLOR_BGR2HSV);
inRange(hsv, Scalar(0, 43, 46), Scalar(26, 255, 255), mask);
threshold(hsv, hsv, 70, 230, CV_THRESH_BINARY);
//闭操作联通+膨胀
dilate(mask, mask, Mat(), Point(-1, -1), 14);
erode(mask, mask, Mat(), Point(-1, -1), 19);
dilate(mask, mask, Mat(), Point(-1, -1), 3);
提取边缘,用到了findContours函数,提取方法是最大外围提取,然后保留图像终点部分。
vector<vector<Point> > cont;
findContours(mask, cont, CV_RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
用最小旋转矩形包围提取出来的每个轮廓(可能有好几个轮廓区,比如天花灯管),这个矩形可以不和屏幕垂直或平行,它与矩形物体长或宽垂直。
rect[i] = minAreaRect(cont[i]);
这个是画在原图上看的,可以用来检测算法是否合适(imshow('frame',frame)可见)。
line(frame, vertices[j], vertices[(j + 1) % 4], Scalar(255, 255, 0), 0.03);
舍去干扰点(上面有灯管),并且画出四个角点:
if (vertices[j].x > 0 && abs(vertices[j].y - vertices[(j + 1) % 4].y) < 15)
{
dot = Point2f((vertices[j].x + vertices[(j + 1) % 4].x) / 2, (vertices[j].y + vertices[(j + 1) % 4].y) / 2);
savedots.push_back(dot);
// cout << dot << endl;
circle(draw, dot, 1, Scalar(255, 0, 255), 2, 8, 0);
}
最后imshow一下,按q退出。
imshow("frame", frame);
imshow("draw", draw);
效果图:
完整代码
#include <ros/ros.h>
#include <iostream>
#include <opencv2/opencv.hpp>
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <opencv2/highgui.hpp>
#include <opencv2/core/core.hpp>
using namespace std;
using namespace cv;
int main(int argc, char **argv)
{
VideoCapture capture("/home/dale/cvrviz/water.avi");
if (!capture.isOpened())
{
//error in opening the video input
printf("could not load image..\n");
return false;
}
Mat frame, hsv, mask, cornerpoint;
while (ros::ok())
{
capture >> frame;
if (frame.empty())
break;
cvtColor(frame, hsv, COLOR_BGR2HSV);
inRange(hsv, Scalar(0, 43, 46), Scalar(26, 255, 255), mask);
threshold(hsv, hsv, 70, 230, CV_THRESH_BINARY);
//闭操作联通+膨胀
dilate(mask, mask, Mat(), Point(-1, -1), 14);
erode(mask, mask, Mat(), Point(-1, -1), 19);
dilate(mask, mask, Mat(), Point(-1, -1), 3);
vector<vector<Point> > cont;
findContours(mask, cont, CV_RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
Mat draw = Mat::zeros(mask.size(), CV_8UC3);
// imshow("mask", mask);
vector<RotatedRect> rect(cont.size());
Mat savepoint[4];
Point2f dot;
vector<Point2f> savedots;
if (cont.size() > 1)
{
for (int i = 0; i < cont.size(); i++)
{
//找出最小旋转矩形
rect[i] = minAreaRect(cont[i]);
//定义矩形的4个顶点
Point2f vertices[4];
// 排除上面灯管干扰,计算矩形的4个顶点
if (rect[i].center.y > 80)
{
rect[i].points(vertices);
}
for (int j = 0; j < 4; j++)
{
line(frame, vertices[j], vertices[(j + 1) % 4], Scalar(255, 255, 0), 0.03);
//提取corner points
if (vertices[j].x > 0 && abs(vertices[j].y - vertices[(j + 1) % 4].y) < 15)
{
dot = Point2f((vertices[j].x + vertices[(j + 1) % 4].x) / 2, (vertices[j].y + vertices[(j + 1) % 4].y) / 2);
savedots.push_back(dot);
// cout << dot << endl;
circle(draw, dot, 1, Scalar(255, 0, 255), 2, 8, 0);
}
}
}
imshow("frame", frame);
imshow("draw", draw);
//多甲板的话,可以对一个矩形临近点进行距离限定
}
int keyboard = waitKey(10);
if (keyboard == 'q' || keyboard == 27)
break;
}
capture.release();
return (0);
}