什么是机器视觉?什么是前景检测?
机器视觉就是一门研究如何使机器“看”的学科(PS:大而空的话放到前面,来自维基百科)。
要理解前景检测这个词就需要从它的反义词“背景”理解,背景很好理解,我站到一个海蓝色的墙前面,我的背景就是墙,也可以说是海蓝色,那前景就是我了;前景检测就是找到我和背景不一样的地方。人一眼就可以看出来我站到墙前面,计算机可看不出来。
视觉
机器的视觉一般就是摄像机或视频文件,我们首先要从摄像拿到摄像机的画面,然后去对摄像机的画面进行处理。一般就是摄像机的帧率为21帧,就是一秒钟有多少张图,即21张图。
机器“眼中”的画面
机器眼中 所以东西都是01010101010101010这种东西,哈哈。经过一系列封装(如操作系统,文件格式、C++类库等等)最后可能我们可以在计算机内看到一个矩阵,以三通道BGR为例可能如下表(PS我猜的):
0xff(B) 0xff(G) 0xff(R) | 0xff(B) 0xff(G) 0xff(R) | 0xff(B) 0xff(G) 0xff(R) | 0xff(B) 0xff(G) 0xff(R) |
0xff(B) 0xff(G) 0xff(R) | 0xff(B) 0xff(G) 0xff(R) | 0xff(B) 0xff(G) 0xff(R) | 0xff(B) 0xff(G) 0xff(R) |
0xff(B) 0xff(G) 0xff(R) | 0xff(B) 0xff(G) 0xff(R) | 0xff(B) 0xff(G) 0xff(R) | 0xff(B) 0xff(G) 0xff(R) |
0xff(B) 0xff(G) 0xff(R) | 0xff(B) 0xff(G) 0xff(R) | 0xff(B) 0xff(G) 0xff(R) | 0xff(B) 0xff(G) 0xff(R) |
4*4的像素矩阵可能就是这样子的啦,这里也科普一下1920*1280就是有1920列1280行个这样的像素矩阵,计算机里面是什么样子的呢?先将16进制转成2进制
11111111(B) 11111111(G) 11111111(R) | 11111111(B) 11111111(G) 11111111(R) | 11111111(B) 11111111(G) 11111111(R) | 11111111(B) 11111111(G) 11111111(R) |
11111111(B) 11111111(G) 11111111(R) | 11111111(B) 11111111(G) 11111111(R) | 11111111(B) 11111111(G) 11111111(R) | 11111111(B) 11111111(G) 11111111(R) |
11111111(B) 11111111(G) 11111111(R) | 11111111(B) 11111111(G) 11111111(R) | 11111111(B) 11111111(G) 11111111(R) | 11111111(B) 11111111(G) 11111111(R) |
11111111(B) 11111111(G) 11111111(R) | 11111111(B) 11111111(G) 11111111(R) | 11111111(B) 11111111(G) 11111111(R) | 11111111(B) 11111111(G) 11111111(R) |
前景
假设我们的背景是白色的一个区域,我们前景是一个黑色到的点。那么这个矩阵会是什么样子的呢?
11111111(B) 11111111(G) 11111111(R) | 11111111(B) 11111111(G) 11111111(R) | 11111111(B) 11111111(G) 11111111(R) | 11111111(B) 11111111(G) 11111111(R) |
11111111(B) 11111111(G) 11111111(R) | 00000000(B) 00000000(G) 00000000(R) | 11111111(B) 11111111(G) 11111111(R) | 11111111(B) 11111111(G) 11111111(R) |
11111111(B) 11111111(G) 11111111(R) | 00000000(B) 00000000(G) 00000000(R) | 11111111(B) 11111111(G) 11111111(R) | 11111111(B) 11111111(G) 11111111(R) |
11111111(B) 11111111(G) 11111111(R) | 11111111(B) 11111111(G) 11111111(R) | 11111111(B) 11111111(G) 11111111(R) | 11111111(B) 11111111(G) 11111111(R) |
如何从背景中找到黑色的点呢?可能说循环一下找到三个0x00就不是一个黑点吗?那如果这个矩阵背景不是全部是1呢?如果我们不知道前景是黑色呢?这里就需要检测出前景了。
样本
如果背景是固定不变的,我们可以找到一张背景图做样本,如果背景会有变化,可以从码流选择一个我们认为可行的背景,来作为样本。例如后一帧和前一帧比较的前景是什么?或者后30帧和前一帧比较前景是什么。这样我们就可以在动态的码流中找到一些变化。
帧差法检测前景
因为最近项目用到了,我就写下来记录一下过程,其中用到的类库是Opencv,编程语言则是C++。
前面解释了那么多,好了直接上代码吧
cv::Mat src1,src2; //假设这两个是两个合适时段的原图片
//我们要从这两个原图中找到前景
cv::Mat dst1,dst2;
//首先做一个灰度处理
cv::cvtColor(src1,dst1,CV_BGR2GRAY);
cv::cvtColor(src2,dst2,CV_BGR2GRAY);
//高斯滤波
cv::GaussianBlur(dst1,dst1,cv::Size(3,3),0);
cv::GaussianBlur(dst2,dst2,cv::Size(3,3),0);
//两张图做差
cv::Mat diff_image;
cv::absdiff(dst1,dst2,diff_image);
//给帧差后的图做一个阈值限定,如下超过35阈值的即R+G+B>35的像素改为255,其他改为0
//这一操作为最后一个参数控制 cv::THRESH_BINART
cv::threshold(diff_image,diff_image,35,255,cv::THRESH_BINARY);
//创建一个28*28的核
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE,cv::Size(28,28));
cv::erode(diff_image,diff_image,(3,3),cv::Point(-1,-1),2); //腐蚀
cv::dilate(diff_image,diff_image,kernel,cv::Pint(-1,-1),2); //膨胀
//将我们处理好的图片的边界找到,会形成多个不规则的点集
std::vector<std::vector<cv::Point>> contours;
cv::findContours(roi_diff,contours,cv::RETR_EXTERNAL,cv::CHAIN_APPROX_SIMPLE);
//其实这里已经找到了前景的区域了,只需要你去区分一下这些点集围成的区域哪个是前景。
//可以使用boundingRect来将点集使用一个最小矩型来框出来
cv::Rect rect = cv::boundingRect(contours[0]);
总结
具体用什么方式来处理图片这个就需要因地制宜来,上面流程也是我们项目的一个小处理流程,当然为什么要用高斯滤波不用其他的,为什么要把腐蚀的核设为3*3,膨胀的核设为28*28,这个其实我也不知道。233333
下面说一下对于这个图像处理我自己的理解:
1、为什么要做灰度处理,灰度首先比BGR少了很多信息,运算速度就会快很多,而且灰度可以更专注于像素间的梯度,也就是边界等问题。
2、滤波:应该是让像素的特征更为相近,这样更好找出一个合适的特征,因为没有做滤波可能画面比较清晰,像素的差异比较大,这样很难找到规律吧。与之相反的操作应该就是锐化了。让图片更具加有差异边界更加清晰。
3、做差:这个就不多做解释了,有对比才有伤害嘛。我记得看李飞飞的一个视频里说过说这样做有一个缺点,就是错位后做差会导致误差变得无限大,导致识别失败。具体大家可以去网易云课堂上自己看,可能我收了网易云音乐的“钱”(音乐)吧。
4、阈值化:做差之后相近的像素相减几乎就是很接近与零0,所以可以设立一个阈值来将背景设置为0,把你感兴趣的位置增大或者不变都可以。
5、腐蚀很膨胀,后面是先腐蚀后膨胀,原理大概是,腐蚀掉一些可以忽略的奇异点,然后再膨胀大将大面积且邻近的区域连接起来,形成一个区域。
6、最后就是把边界画出来,到这里基本就找到了我们想要的前景,只需要将前景和一些不必要的区分开即可。