点击上方
“
小白学视觉
”,选择加"
星标
"或“
置顶
”
重磅干货,第一时间送达
![](http://mmbiz.qpic.cn/mmbiz_jpg/ow6przZuPIENb0m5iawutIf90N2Ub3dcPuP2KXHJvaR1Fv2FnicTuOy3KcHuIEJbd9lUyOibeXqW8tEhoJGL98qOw/640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1&wx_co=1)
这段时间在和学弟打软件杯的比赛,有项任务就是机器人的视觉巡线,这虽然不是什么稀奇的事情,但是对于一开始不了解视觉的我来说可以说是很懵了,所以现在就想着和大家分享一下,来看看是如何基于opencv来实现巡线的。我这里以ubuntu20.04为例了
1.查看相机设备
首先要完成视觉巡线那必不可少的就是相机了,使用
来查看相机。
这里可以看到我有两个相机设备,一个是我电脑自带的相机video0,另一个是我的usb相机video1。
2.显示实时图像
新建一个工作空间,然后新建一个cpp文件,然后进行相机的初始化,以及调用窗口实时显示图像
#include
#include
#include
using namespace std;
int camera_width = 640;
int camera_height = 480;
int main(int argc, char const *argv[])
{
cv::VideoCapture cap(1);
cap.set(CAP_PROP_FRAME_WIDTH, camera_width);
cap.set
(CAP_PROP_FRAME_HEIGHT, camera_height);
while (true) {
cv::Mat color_image;
cap.read(color_image);
if (color_image.empty()) {
cerr << "Failed to capture image" << endl;
break;
}
imshow("Color Image", color_image);
char key = waitKey(1);
if (key == 'q') {
break;
}
}
cap.release();
destroyAllWindows();
return 0;
}
这里初始化cv::VideoCapture cap(1)传入的参数就是上面查看到的设备,如果想要调用系统自带相机,那就改为cap(0)。
3.巡线函数
我这里函数声明如下:
tuple<:mat class="code-snippet__keyword" style="box-sizing: border-box;">float, bool, bool, bool> followBlindPath(cv::Mat color_image)
由于我想要多个返回值所以就采用了tuple模版,后面采用tie函数进行解包,其中输入参数为要识别的图片,输出参数分别为经识别后标记的图片,以及水平方向上偏差(后面会具体解释是什么偏差),后面三个布尔值表示三个状态,分别为巡线,转弯和停止。
在识别开始之前,由于图片在opencv保存的格式默认为BGR格式图片,我们要转为HSV格式,因为后面的操作都是基于HSV图片进行的。
cv::cvtColor(color_image, hsvFrame, COLOR_BGR2HSV);
然后指定HSV的色域,scalar函数三个参数分别为色调(Hue)、饱和度(Saturation)和亮度(Value),我这里设置的值为黄色的色域。
cv::Scalar color_lower = cv::Scalar(10, 40, 120);
cv::Scalar color_upper = cv::Scalar(40, 255, 255);
cv::inRange(hsvFrame, color_lower, color_upper,color_mask);
inRange函数用于判断一个像素或像素矩阵是否在指定的范围内,hsvFrame是输入图像,返回图像color_mask是一个二值图像,即在色域内的为白色,色域外为黑色。
处理效果如下:
然后进行滤波,过滤掉一些噪声然后进行膨胀和腐蚀的操作使得图片识别效果更好
cv::Mat dilate_kernel = cv::getStructuringElement(MORPH_RECT, Size(10, 10));
cv::Mat erode_kernel = cv::getStructuringElement(MORPH_RECT, Size(5, 5));
cv::medianBlur(color_mask, color_mask, 9);
cv::dilate(color_mask, color_mask, dilate_kernel);
cv::erode(color_mask, color_mask, erode_kernel);
上面定义了用于膨胀和腐蚀的矩形结构,然后进行了滤波、膨胀和腐蚀。
效果如下:
cv::Mat mask_roi = cv::color_mask(Rect(0, 0, camera_width, 20));
我这里ROI区域为上面高20的区域,因为我实际的相机在下方,具体的ROI区域,还是要和相机的位置进行相应的设置
区域如下:
vector<vector<:point>> contours_roi;
vector hierarchy;
cv::findContours(mask_roi, contours_roi, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
if (!contours_roi.empty()) {
vector c = *max_element(contours_roi.begin(), contours_roi.end(), [](vector a, vector b) {
return contourArea(a) < contourArea(b);
});
cv::Rect bound_rect = boundingRect(c);
cv::rectangle(color_image, bound_rect, Scalar(255, 255, 255), 2);
int center_x = bound_rect.x + bound_rect.width / 2;
int center_y = bound_rect.y + bound_rect.height / 2;
cv::circle(color_image, Point(center_x, center_y), 5, Scalar(0, 0, 255), -1);
cv::imshow("Path3", color_image);
last_center_x = center_x;
return make_tuple(color_image, ((center_x / (float)camera_width) * 2 - 1), true, false, false);
}
findContours用于在二值图像中查找轮廓,其中:
-
contours:输出的轮廓向量,每个轮廓表示为一个 std::vector对象
-
hierarchy:可选的输出向量,包含了轮廓的层次结构信息。默认情况下,不输出层次结构信息,可以传入一个空的 cv::OutputArray 对象。
-
轮廓数据存储在 contours 和 hierarchy 两个输出参数中。
执行findContours过后,判断contours_roi.empty()是否为空,如果为空说明所划的区域未识别到黄线,转为了下一个状态,如果识别到了就找到面积最大的区域划矩形框。
其中采用rectangle()函数绘制矩形框,然后找到中心点(center_x,center_y),使用circle绘制点,同时返回值第一个bool值置为true,其他为false。
最终结果如下:
如果上面contours_roi.empty()为空,则说明所划的区域未识别到黄线,那么就需要重新划定剩下ROI区域
else {
mask_roi = cv::color_mask(Rect(0, 20, camera_width, 80));
vector<vector> contours_roi;
vector hierarchy;
cv::findContours(mask_roi, contours_roi, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
if (!contours_roi.empty()) {
vector c = *max_element(contours_roi.begin(), contours_roi.end(), [](vector a, vector b) {
return contourArea(a) < contourArea(b);
});
cv::Rect bound_rect = boundingRect(c);
cv::rectangle(color_image, bound_rect, Scalar(255, 255, 255), 2);
return