Using OpenCV
背景
最近这几年人工智能大火,公司也想向现有产品(移动销售自动化管理软件)中加入些沾边的功能,于是想到现在销售填报数据时将现有的人工采集数据(超市货架中货品种类和数量统计)方式改用机器来做。让机器识别出货物首先要给机器大量的图片让它学习,然后通过图像识别技术来辨识超市货架的货物,并计算出数量。
本来按着这个思路下去应该是先进行 CoreML 等方式的调研,然后线下人员采集大量产品图片,结果公司领导不知道是基于什么原因,要求移动端开发先做个图像分割的应用,将一张超市货架的图片拍照后,再手动将每层货架标识出来,然后手动分割每层货架的产品,显然这种方式太没有技术含量了,所以用业余时间研究了一下 OpenCV ,看看有什么方式能实现自动检测出货架,并分割出每个产品。
经研究,通过直线检测方式能检测出货架位置,但是图像分割的效果由于光线以及产品摆放方式等原因会效果较差,趁着印象深刻的时候,把用到的知识点以及遇到的问题和解决方案总结一下。
书籍推荐
如果想用好 OpenCV,就要对 OpenCV 能做什么有非常清楚的了解,所以 《Learning OpenCV 3》 是个人感觉最有用的书,第三版好像还没有中文版,所以看了英文版,对比了下中文版的第一版,第三版内容更丰富目录结构更合理。另外推荐 《Mastering OpenCV with Practical Computer Vision Projects》,在没有思路的时候可以借鉴一下。
OpenCV 安装和使用
OpenCV 是 Open source computer vision 的缩写,它的目的是提供一种更简单的方式来快速处理复杂的视觉应用。
Mac 下安装 OpenCV,需要安装 cmake,终端下输入以下命令
brew update
brew install cmake
iOS 下创建一个Demo 通过 CocoaPods 方式添加相应的库到环境中 Podfile 文件如下
platform :ios, '8.0'
target 'testOpenCV' do
pod 'OpenCV-iOS', '~> 3.0'
end
注意,由于 OpenCV 是基于 C 语言的,所以 iOS 中调用相关方法时所对应的类的实现文件应该使用 .mm 为后缀。
图像滤波
模糊算法比较
中值滤波
是一种非线性平滑技术,它将每一像素点的灰度值设置为该点某邻域窗口内的所有像素点灰度值的中值.
高斯滤波
cv::GaussianBlur(image, blurImage, cv::Size(5,5), 3, 3);
注意高斯模糊的卷积滤波值应是奇数,原因可以看看高斯模糊原理
是一种线性平滑滤波,它对整幅图像进行加权平均的过程,每一个像素点的值,都由其本身和邻域内的其他像素值经过加权平均后得到。由于它的输出是领域像素的加权平均,同时离中心越近的像素权重越高,因此平滑效果更柔和,而且边缘保留的也更好。
双边滤波
是一种非线性的滤波方法,是结合图像的空间邻近度和像素值相似度的一种折衷处理,同时考虑空域信息和灰度相似性,达到保边去噪的目的。双边滤波器的好处是可以做边缘保存(edge preserving)。
形态
腐蚀和膨胀
这两个方法对于排除干扰有很好的作用,例如超市货架在图像处理时可能由于物品摆放或者光线等原因,导致直线检测时想要找到的一些细线可能被忽略,经过几次膨胀就能更准确的找到每层货架位置。
图像分析
Canny 边缘检测
cv::Canny(image, edgesImage, threshold1, threshold2);
Canny 算法是在 Laplace 算法基础上完善的,它跟 Laplace 的不同点是,Canny 算法首先从 x 和 y 方向计算出一阶导数,然后组合成 4 个方向的导数。这些方向导数达到局部最大值的点就是组成边缘的候选点。该算法查找边缘是通过一个假设的阈值来判断像素是否是边缘像素。像素的梯度大于上限阀值,则被认为是边缘像素,低于下限阀值则会被滤除,介于两者之间的话,当其联通像素高于上限阀值则被认为是边缘像素。Canny 建议上下限阀值比例在 2:1 到 3:1 之间。
直线检测
cv::HoughLinesP( InputArray image,
OutputArray lines,
double rho, // rho 的步长
double theta, // 角度的步长,单位是度
int threshold, // 阈值
double minLineLength = 0, // 线段的最小长度
double maxLineGap = 0 ); // 线段之间的最小距离
霍夫变换直线检测如果参数设置的不好,就会检测出很多杂乱无章的线条,所以一定要想好如何设置参数。例如在检查货架过程中,首先手动截图框定货架位置后,直线检测的结果在理想情况下直线长度应该近似图片宽度,但是由于货架上摆放了价签,直线被截断,所以过滤的时候可以选择图片宽度的一半或者相似的比例作为线段最小长度。货架一般是水平的,间距也不会太近,所以检测后过滤下角度,并把距离较近的直线忽略,就可以得到一个大致的货架位置。
轮廓检测
由于光线以及产品标签等特征,使用 opencv 的 findContour 方法获取到的轮廓和实际想要的结果存在较大偏差。所以换了一种思路来实现轮廓检测,主要借鉴的方法是投影法实现字符分割。
这种方法就是在产品摆放时如果有间距,那么它们的投影必然是有产品的地方是黑色,没有产品的地方是白色,所以只需要判断投影的像素值即可,这种方法下只要摆放商品不重叠,就能很好的分割出每个产品。为了试验效果 Demo 中将这个过程又化简,没有做投影,而是找到每层货架中间左右一行,扫描该行的像素,如果像素是黑色说明是有产品的,白色说明是空白。最近有一些其它事情要忙,所以没有实现投影方法,稍后有时间再整理。
另外该项目中还用了矩形检测等方法,这里暂不介绍了,有空再来更新。