最近帮同学实现了一个简单的图像识别程序,顺便复习了一下 python 的知识。
问题背景大致如下,需要从图片中按照指定尺寸识别并裁剪出包含目标对象的矩形区域。
准备工作
在 python 中也有依赖包管理工具,类似 Node.js 中的 npm,这就是 pip:
sudo easy_install pip
既然涉及到图像识别,使用 OpenCV 无疑是最佳选择,而且针对不同语言都有官方或者非官方的实现版本。
我这里使用了一个非官方的库 opencv-python
:
pip install opencv-python
另外,由于涉及到科学计算,numpy
也是需要安装的:
python -m pip install --user numpy scipy matplotlib ipython jupyter pandas sympy nose
到这里准备工作就算完成了,可以仔细思考下这个问题的解决思路。
大致思路
首先从原始输入图片中不难看出,其中包含了很多噪音,去除他们会给后续处理带来便利。 其次,我们需要将灰度图转换成黑白双色图,便于后续的轮廓识别。 然后,通过轮廓识别找出矩形边框,根据需要的裁剪尺寸作出调整。 最后,根据调整后的区域在原图中进行裁剪。
下面让我们依次根据这个思路实现。
具体实现
先学习一波 python 的语法知识。
python 语法
类似 ES6 中的模块语法,通过 import
我们可以引入指定模块或者其中的方法:
import cv2
from math import floor
from os import makedirs,listdir
另外,由于使用了中文注释,需要在源码顶部第一行声明编码类型:
#coding:utf-8
作为一个 python 初学者,看到很多 python 代码中包含下面一段类似程序入口的判断。原来一个 python 文件既可以独立运行,也可以被其他文件引用,通过 __name__
能判断运行环境:
if __name__ == "__main__":
main()
读取图片
OpenCV 提供了图片的读写方法,比如 imread()
将图片读入数组中:
original_img = cv2.imread(join(input_dir,src_img))
去噪
OpenCV 提供了去噪方法,这里我们使用针对灰度图的:
denoised_img = cv2.fastNlMeansDenoising(original_img,None,10,7,21)
轮廓检测
把 8 位的灰度图转换成黑白图,设置阈值 127。另外这里出现了 python 中方法多个返回值,不需要的可以命名为 _
,比如这里第一个返回值是原始数组,我们并不需要:
_, binary_img = cv2.threshold(gray_img,127,255,cv2.THRESH_BINARY)
关于轮廓检测 OpenCV 有专门的章节介绍。值得注意的是,由于对象内部也会存在“黑点”,构成很小的区域,我们需要通过计算轮廓面积,筛选掉这些小小的区域:
_, contours, hierarchy = cv2.findContours(binary_img,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
if cv2.contourArea(cnt)>1000:
# 后续处理轮廓
现在我们得到了轮廓区域的包围矩形,需要根据裁剪矩形做出调整。 首先我们对齐两个矩形的中心,然后检测是否触碰到了整个图片的边缘:
# 轮廓区域
x,y,w,h = cv2.boundingRect(cnt)
# 对齐中心
crop_y = y+floor((h-crop_h)/2)
crop_x = x+floor((w-crop_w)/2)
# 边缘检测
crop_x = min(max(0, crop_x),original_img_w-crop_w)
crop_y = min(max(0, crop_y),original_img_h-crop_h)
最后在原图中裁剪,python 操作矩阵数组真是方便:
crop_img = original_img[crop_y:crop_y+crop_h,crop_x:crop_x+crop_w]
output_path = '...'
cv2.imwrite(output_path, crop_img)
总结
经常看到使用 python 编写机器学习,网络爬虫的文章。个人感觉语法类似 Ruby 和 JS 的混合体,非常值得学习。 尤其是这次结合 OpenCV 的尝试,让我觉得可以用他们继续实现更加有趣的功能,例如 OCR 和人脸识别等等,即使完全不了解其中的算法细节。