无论你走到天涯海角,我都能找到你
RoboMaster 技术智囊团,用简单的方式带你入门机器人知识,每周日准时更新。
在 RoboMaster 比赛中,当一台机器人移动时,另一台机器人在无人操作的情况下,好像有着一双眼睛,可以自动瞄准移动的机器人。
红方追踪移动的蓝方
实际上,都是因为机器视觉大法好啊!
在这个场景中,机器人搭载的摄像头把图像实时传输到电脑,通过电脑程序分析。即使图像内目标物体快速移动,它的位置信息也会被获取并实时追踪,很难被甩掉。
什么?你不知道机器人上有电脑?来,先仔细地看一遍:
我们告诉电脑目标物体都有哪些特征,它就会完全按照我们预设的程序来运行,这种视觉处理方法可归为传统方式。
传统式的机器视觉适合入门学习。今天我们先来讲讲视觉识别中处理程序的一环:到底电脑是如何识别和处理图像的呢?想闭着眼睛操控机器人的小伙伴,注意听讲啦~
◆◆◆
《论语·卫灵公》:“工欲善其事,必先利其器。”做机器视觉也一样,我们需要高效的编程环境,以及让你事半功倍的武器——OpenCV 视觉库。OpenCV 是广泛应用的视觉识别库,它提供了开源的代码供直接调用。
全称 Open Source Computer Vision Library,直译成中文就是“开源的计算机视觉库”。它“普度众生”,可以运行在 Linux(一种用来干活的操作系统)、Windows(一种又能干活又能玩的操作系统)和 MacOS(一种只能运行在昂贵电脑上的操作系统)等操作系统上。
说起喜欢 OpenCV 的原因,很多参赛队员都谈到了两点。第一,OpenCV 是免费开源的,由一个非盈利性组织支持;第二,OpenCV 非常轻量级,这意味着,大部分程序在普通的电脑上也能运行。
OpenCV 有上百个函数,根据不同功能划分到不同的功能模块里。这些功能模块一点也不多,大概就那么十几二十吧(・∀・*)……
检验学霸之图
客官请留步!耐心看一看
作为初学者
你只需要先掌握
core、imgproc、highgui 这三个基本模块
因为这三个模块提供了最基础和常用的接口
为了生动地教学~
(为了让文章不被关掉)
小R贴心地准备了
一系列超详细的教程
带领你走进视觉识别的大门~
不用谢:)
OpenCV 的 core 内核模块为我们提供了最核心的类和接口。其中, Mat 类型将图像以矩阵的方式存储,更适合计算机做数值运算。
小R最喜欢的图竟然是个矩阵?!
以最简单的黑白位图照片为例。我们把图片放大之后,可以看到图像实际上是由许多明暗不同的小方块(像素)组成的。
位图图像是由很多像素组成的
在8位的灰度色彩空间里,我们把这些小方块用0~255的数字代替,数值越小代表的方块越暗。
8位灰度色彩空间
按照这个方法,我们就可以把图像用数字矩阵的方式存储。
所以,电脑程序分析图像的原理,很大程度上是矩阵中的数学运算。
(赶紧掏出了线性代数的课本)
◆◆◆
通常,视觉识别分为两步。第一步是“预处理”,通过一些基础算法,去除无用信息和噪声,放大颜色间的差异;第二步,就是利用其特征信息进行“物体检测”。
常见视觉识别步骤
接下来,我们就通过两个超实用的视觉识别例子,来理解预处理和物体检测。
选取讲解的素材需要非常慎重。好的,现在我们决定用萝卜君。
萝卜君原始图片
我们先用 core 内核模块中的 imread 读图像函数把这张图片以矩阵形式载入到内存中,并起个好听的名字叫做 src 。
Mat src = imread("萝卜君.jpg");
接下来,用遍历像素点的方法,找到所有蓝色像素,把它变成白色。伪代码如下:
i 从 0 循环至 图像.宽 :
j 从 0 循环至 图像.高 :
如果 像素点( i , j ) 是 蓝色
巴啦啦能量!像素点( i , j ) 变成 白色!
(好的,谢谢伪代码君)
完成这一步操作的 C++ 代码,有三种方法:
1、传统的C风格操作符[] (指针)
for (int i = 0; i < src.rows; i++) {
Vec3b* data = src.ptr<Vec3b>(i);
for (int j = 0; j < src.cols; j++) {
if (data[j][0] > 200 && data[j][2] < 50)
data[j] = Vec3b(255, 255, 255);
}
}
2、迭代器方法
MatIterator_<Vec3b> it, end;
for (it = src.begin<Vec3b>(), end = src.end<Vec3b>(); it != end; ++it)
if ((*it)[0] > 200 && (*it)[2] < 50) {
(*it)[0] = 255;
(*it)[1] = 255;
(*it)[2] = 255;
}
3、返回引用的动态地址访问函数*
*原文:On-the-fly address calculation with reference returning
for (int i = 0; i < src.rows; ++i)
for (int j = 0; j < src.cols; ++j) {
Vec3b pixel = src.at<Vec3b>(i, j);
if (pixel[0] > 200 && pixel[2] < 50) {
src.at<Vec3b>(i, j) = Vec3b(255, 255, 255);
}
}
这三种方法中,第 1 种方法效率最高,但在使用之前,你需要先确保该图像在内存内是连续存储的。建议操作之前先调用函数 isContinuous() 来判断。
但相比之下,第 2 种方法更安全。它会帮你自动跳过内存内的不连续空间,同时也保持了很高的效率。
第 3 种方法是被设定来随机访问像素点的,你要是用它来遍历图像,那效率堪称感人。等你找到萝卜君的时候,他可能把下周的视频都做完了。
那我们就挑一种自己喜爱的方法,完成这一步操作:遍历全图,把蓝色的像素点变成白色。它有点像我们常说的“抠图”。
背景处理对比
(这么干净的背景在现实中是不可能存在的)
经过去除背景之后的图片,仍然有很多像椒盐一样的噪声点,我们管它叫做"椒盐噪声"。
OpenCV 的 imgproc 模块,内含滤波、形态学处理、几何变换等大量实用的图像处理函数,把它们像互相组合起来,足以应对各种场景需求。
例如,图像非线性滤波算法中的中值滤波 medianBlur,对于滤除图像中的“椒盐噪声”,效果优异。
cv::medianBlur(src,dst,3);
滤除椒盐噪声对比
看到了吗!
左图中的“椒盐噪声”,在右图中就消失了!
(没看到的多看几遍)
经过预处理之后的图片
干~干~净~净~
这个时候我们就可以进行物体检测啦!
现在把萝卜君换成机器人
我们把机器人跟萝卜君一样,进行图片“预处理”,去掉背景杂色,只保留发光的灯条。
预处理后的图像
接下来,使用 findConours 函数,找出装甲片灯条的轮廓;再使用 minAreaRect 函数,将“看起来像矩形”的灯条直接拟合成 RotationRect 旋转矩形。将灯条变成规则的几何图案,更有利于之后的特征分析。
献上我们的代码君:
//在bin_img二值图中查找轮廓,并放到 contours 里
findContours(bin_img, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
//将灯条拟合成 RotationRect 旋转矩形
RotatedRect minRect = minAreaRect(Mat(contours[i]));
测距效果
这里的 minRect 就是一个 RotationRect 旋转矩形的实例,它包含了矩形的位置和形态信息。接下来,根据灯条姿态,结合利用装甲片、灯条尺寸等先验知识,就可以换算出目标装甲模块的相对坐标啦!
在 RoboMaster 比赛中,当获取到目标的相对坐标后,分析图像,就能进一步控制云台瞄准打击目标啦,妥妥的~
本期的机器视觉入门就到这里,讲了这么多,有没有听进去呀?祝大家学习路上大吉大利,将来一起吃“反鸡”!
想了解更多
可在 PC 端点击阅读原文下载
装甲模块自动识别开源代码
◆◆◆
与传统式对比起来,还有另外一种高级的机器视觉处理。
在 2017 赛季 RoboMaster 比赛中,出现了一个动态变化的九宫格大能量机关,机器人需要先识别机关上方随机出现的一行数码管数字,再按照其顺序识别下方随机分布、随机字体的九宫格数字。因为其字体是随机的,很难手动输入所有特征,这里就需要用到机器学习:给机器人一些事先准备好的例子,让它自己学习、摸索。
视觉识别数字
这种丑丑的手写体数字就无法用传统方式来识别了,关注我们,下一期更加高级的机器学习式视觉识别教学,周日准时带你飞。
◆◆◆
原文来自 RoboMaster 技术智囊团“华工机器人实验室”,文章部分有删改。
点个赞,为 RoboMater 和机器人的宣传加个油!