挺简单的。
关联项目:
http://www.h13studio.com/基于Linux嵌入式开发板的轨道检测机器人-大创项目-持续更新中
http://www.h13studio.com/OpenCV在Arm-Linux上的安装
http://www.h13studio.com/OpenCV提取最大联通面积-铁轨轨道特征-二
寒假的时候花了两天略微学了一下OpenCV 觉得这玩意挺好玩的。
然后因为我们的大创项目就是需要对轻轨和铁轨的轨道特征进行提取,曲率计算,障碍物检测。
所以前两天又花了两个小时时间去了解了一下侵蚀算法 Canny算法 findContours算法等等。
然后就开始着手设计了一个提取铁轨轨道特征的算法。感觉还挺简单的。
在这里 我强烈推荐这个UP的OpenCV视频,学习完OpenCV基础操作之后再去学这个 感觉特别简单。
https://space.bilibili.com/517381507/video?keyword=opencv
算法思路
无论要设计什么样的算法,其算法思路终究还是围绕着被检测物的特征来设计的
这回我要提取的是铁轨的轨道特征,那么首先打开百度 搜索一下铁轨的图片。
https://image.baidu.com/search/index?tn=baiduimage&ct=201326592&lm=-1&cl=2&ie=gb18030&word=%CC%FA%B9%EC&fr=ala&ala=1&alatpl=adress&pos=0&hs=2&xthttps=111111
显然,铁轨的最大特征就是,他有两条又长又粗的轨道 →_→
并且这个轨道颜色和周围环境颜色差异还很大,那么我们就很容易过滤掉其他很多颜色变化较小的色块,突出颜色变化率较大的铁轨特征。
这里我首先选择了这张图片做demo。因为他不禁图片拍摄角度正合我意,并且旁边还有经常出现的树木干扰。
首先提取图片颜色变化率,之前我曾经自己写了一个很简单的图片颜色变化率算法,不过既然OpenCV官方都提供这个api了 也没必要重复造轮子了。
Sobel算法
函数原理: 其实就是对图像的每个通道的值进行求导和卷积。
函数原型:
处理效果:
这样一处理 就能增强不少铁轨的特征,减弱其他无关干扰。图片里面最主要信息的就是两根铁轨了。
不过还不能直接用这张图进行特征提取 还需要筛掉一部分亮度很低的区域,减少干扰,减小计算量。
Thershold二值化算法
函数原理: 一个比较简单的比较运算,对于高于/低于阀值的像素按照给予的参数进行对应操作。
函数原型:
处理效果:
因为咱轨道主要是白色,不是标准的RGB颜色,所以也不能简单的只提取一个单通道进行计算。
而这个时候 图片是彩色还是黑白就不是很重要了。不如转换成黑白图片进行处理降低计算量。
毕竟这个特征提取是要在Arm板子上跑的。
cvtColor颜色空间转换
函数原理: 一个图片通道转换函数,在这里用于将彩色图片转换为灰度图片。
函数原型:
处理效果:
这个时候 图片中的两根铁轨得到了很好的保留 而其他剩余的信息已经对后续处理产生不了很大影响了。
现在 我们只有一个目的: 找出图中最长的两根线 也就是咱的铁轨。
方案1: 画两根横向的细线 和铁轨相交 得到封闭图形 再直接利用OpenCV官方自带的 findContours
查找出图片中面积最大的轮廓即可。
方案2: 自己设计一个算法 提取图中最长的线。这个我下一篇会讲到我是如何设计的。
这里先用方案1,毕竟只会对于一些只会用Python人来说 如果不用官方api来处理大量运算的话 Python的性能是不够用的,放到Arm板子上只能通过降分辨率的方法来实现自定义的大量运算。
划线,得到大面积轮廓
这里直接用line
函数即可,具体划线的纵坐标取决于你摄像头摆放角度,不是很难。
处理效果:
findContours查找轮廓
函数原理: 我也不知道.. 反正就是查找轮廓常用函数。
函数原型:
处理效果:
效果出乎意料 居然把这个轮廓相交之后多余的线也保留了。
优化方案: 判断轮廓面积大于一个临界值之后就停止判断即可,这样概率上能减少一半的无用计算部分(但是我没有验证)。
但是保留铁轨是我们需要的,保留我们画上去的两根线就不合适了,不过这个很简单,直接把findContours
和cvtColor
产生的灰度图用逻辑与运算处理一下就OK。
and逻辑与运算
关于两条横线造成的干扰点问题,其实很好解决,只需要移动这两条横线的位置 之后用生成的两幅图进行and
操作即可完美消除。代价就是计算量加倍。
接下来就只剩下视角修正,曲线拟合以及曲率计算了,这些都是数学问题,很简单。
最终效果
原图&最终处理图
原图最终图叠加效果:
当然了 这个程序还有一个可以改进的部分: 目前二值图的阀值需要手动调参,这个到时候加个自适应算法就OK了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 分辨率: 1000 * 667 单线程,I5-9300H 性能测试:
Debug模式: sobel0: 86ms threshold0: 5ms cvtColor0: 10ms line0: 1ms contours0: 39ms dilate0: 5ms end0: 2ms all: 148ms
Release模式: sobel0: 13ms threshold0: 6ms cvtColor0: 1ms line0: 3ms contours0: 7ms dilate0: 2ms end0: 4ms all: 36ms
|
其实findContours
并没有花费太长时间,完全可以执行两次findContours
然后 and
出一张完美图片。
Just Like This:
最终代码
当然,我并没有做消除干扰点的算法,因为没有必要。强迫症同学请自行添加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
| #include <iostream> #include <opencv2/opencv.hpp> #include <opencv2/highgui/highgui_c.h> #include <vector>
#include <time.h>
int main() { using namespace cv; Mat input; input = imread("C:/Users/h13/Desktop/pathway.jpg"); clock_t tstart,tsobel0,tthreshold0,tcvtColor0,tline0,tcontours0,tdilate0,tend0; tstart = clock();
Mat sobel0; Sobel(input,sobel0,CV_8UC1,1,0); tsobel0 = clock();
Mat thershold0; threshold(sobel0, thershold0, 200, 255, THRESH_TOZERO); tthreshold0 = clock();
Mat gray0; cvtColor(thershold0, gray0, CV_BGR2GRAY); tcvtColor0 = clock();
Mat line0; line0 = gray0.clone(); line(line0, Point(0, gray0.rows * 0.5), Point(gray0.cols, gray0.rows * 0.5), Scalar(255), 1, CV_AA); line(line0, Point(0, gray0.rows * 0.85), Point(gray0.cols, gray0.rows * 0.85), Scalar(255), 1, CV_AA); tline0 = clock();
std::vector<std::vector<Point>> contours; std::vector<Vec4i> hierarchy; Mat findContours0 = Mat::zeros(gray0.size(), CV_8U); findContours(line0, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE,Point()); Mat Contours = Mat::zeros(gray0.size(), CV_8UC1); double maxarea=0, temparea = 0; int maxid = 0; for (int i = 0; i < contours.size(); i++) { temparea = fabs(contourArea(contours[i])); if (temparea > maxarea) { maxarea = temparea; maxid = i; } }
for (int j = 0; j < contours[maxid].size(); j++) { Point P = Point(contours[maxid][j].x, contours[maxid][j].y); Contours.at<uchar>(P) = 255; } drawContours(findContours0, contours, maxid, Scalar(255), 1, 8, hierarchy); tcontours0 = clock();
Mat and0; bitwise_and(gray0, findContours0, and0, noArray()); tcontours0 = clock();
Mat dilate0, structure_element3; structure_element3 = Mat::ones(3, 3, CV_8UC1);
structure_element3.at<uchar>(0, 1) = 1; structure_element3.at<uchar>(1, 1) = 1; structure_element3.at<uchar>(2, 1) = 1;
dilate(and0, dilate0, structure_element3); tdilate0 = clock();
std::vector<Mat> Channels; split(input, Channels); Channels[2] += dilate0;
Mat end0; merge(Channels, end0); tend0 = clock(); system("pause");
tsobel0, tthreshold0, tcvtColor0, tline0, tcontours0, tdilate0, tend0;
std::cout << "tsobel0: " << (tsobel0 - tstart) << "ms" << std::endl; std::cout << "tthreshold0: " << (tthreshold0 - tsobel0) << "ms" << std::endl; std::cout << "tcvtColor0: " << (tcvtColor0 - tthreshold0) << "ms" << std::endl; std::cout << "tline0: " << (tline0 - tcvtColor0) << "ms" << std::endl; std::cout << "tcontours0: " << (tcontours0 - tline0) << "ms" << std::endl; std::cout << "tdilate0: " << (tdilate0 - tcontours0) << "ms" << std::endl; std::cout << "tend0: " << (tend0 - tdilate0) << "ms" << std::endl; std::cout << "tall: " << (tend0 - tstart) << "ms" << std::endl; system("pause"); imshow("input", input); imshow("and0", and0); imshow("end", end0);
cv::waitKey(0); }
|
PS: 其实我自己实现的Sobel算法,有黑边,速度只比官方快了一点点(一半左右,也就是快了5ms),所以我还是用官方的了。
Video