3.2 用简单的矩阵操作处理图像
3.2.1 对图像进行复制与粘贴
如果我们想在上面的图片中加入其他元素,应该如何做呢?比如在3.1节的图中再加入一个球,顺便添加一些文字。
这里要加入一个球的话,由于图中已经有球了,所以实际上从图中复制即可。我们注意到在这张342×548的图中,球的纵坐标在300左右、横坐标在360左右,大小约为60像素,可以用numpy的数组切片img[280:340, 330:390] ,得到一个子矩阵,继而给原图中的其他位置赋值。添加文字的话,在查询opencv的文档后,可以用cv2.putText实现这一功能,如图3-4所示。
图3-4 复制足球并添加文字
3.2.2 把图像当成矩阵进行处理——二维码转换成矩阵
3.2.1小节进行的复制与粘贴只是处理矩阵的最简单操作。本书将讲一些稍微难一点的操作,就是将二维码转换成矩阵。当然,这里只是将二维码转换成0/1的矩阵而已,至于变成矩阵后如何从矩阵提取其中的内容,大家感兴趣的话可以进一步研究。
我们以如图3-5所示的景略集智数据科学的官方主页(jizhi.im)二维码为例,这里实际上是由25×25个像素点组成的,每个像素点或黑或白,但是这张图片并不止25×25个像素点,而是220×220个,那么问题就来了,如何将220×220个点映射到25×25个点的坐标上去?
图3-5 原始大小(220,220)二维码
我们首先读取图片,并转换为[0,255]的灰度值编码:
img_jizhiQR = cv2.imread("./3.png") img_jizhiQR = cv2.cvtColor(img_jizhiQR, cv2.COLOR_BGR2GRAY) print(img_jizhiQR.shape)
运算结果:
# out: (220,220)
接下来,从220×220个点映射到25×25个点,实际上直接做一个简单线性变换即可,即如果坐标是(20,20),则映射后的坐标点应该是(20/220*25, 20/220*25)=(0, 0),并且实际上很多点变换后对应(0, 0)这个点。
此步骤的代码如下:
运行结果如图3-6所示。
图3-6 原始二维码中像素值大小分布
这里有一部分点取值小于40,而另一部分点取值大于40。统计这一步的原因是,我们的映射会产生一些错位,即切割不准确,此时有一些本来应该是黑色的点,切的时候进来了一些白色,造成干扰。此时需要设定一个阈值,即只有大于若干白色点,我们才认为是白色,否则是黑色。很明显,这里可以设定这一阈值为40,查看结果如图3-7所示。
plt.imshow(np_25_counts>40, cmap="gray")
图3-7 原始二维码压缩到(25×25)的结果(设定二值化阈值为40)
我们注意到这个结果与变换前的二维码看起来完全相同,扫描后仍然是景略集智数据科学的主页https://jizhi.im。而实际上,这张图片的形状已经由之前的220×220缩小到了25×25。此时就可以对照二维码的编码标准,用我们缩小后的矩阵逐一解读二维码信息。
这里进一步展开说明:
第一,为什么有很多二维码工具我们还要讲这方面的内容。其实不是要解决生活中二维码的问题,而是以二维码这个最像矩阵的图像为例谈谈如何将图像当成矩阵处理;并且现在越来越多的数据分析公司喜欢把自己的招聘网页设计成奇怪的二维码,然后有人解出来的在招聘时加分,至于如何解决这类问题,其难点不外乎找出映射函数(比如映射为圆形)以及各种阈值(奇怪的颜色形状)。
第二,有SQL经验的读者会隐约感觉到,这里实际就是对图片坐标进行映射后进行了类似AVG(value) GROUP BY KEY的操作。有深度学习基础的读者,更是能意识到,这个问题实际上就类似深度神经网络中的一个池化(Pooling)过程,会在5.3节详细介绍。那么这个问题能不能用深度神经网络框架Tensorflow解决呢?答案是可以的,具体代码的含义后续会解释,当然这本书后续案例基本上都是使用Keras包装Tensorflow,以简化难度。
查看结果(如图3-8所示):
sns.distplot(res.ravel())
图3-8 查看结果
这里的阈值目测可能在130左右,可以用kmeans算法找到这个阈值,然后进行分类,其结果如图3-9所示:
from sklearn.cluster import KMeans kmeans = KMeans(n_clusters=2, random_state=0). fit(res.ravel().reshape(-1,1)) min(res.ravel()[kmeans.labels_==1]) minVal = min(res.ravel()[kmeans.labels_==1]) print(minVal) # 122.77778 plt.imshow(res[0,:,:,0] > 122.78, cmap="gray")
图3-9 二维码图
此时扫描此图,结果仍然是https://jizhi.im。
最后,我们增加难度。读者应该注意到,各个网站公众号实际使用的二维码边缘上有空白,中间有Logo。这种情况下,应该如何排除这些因素?作者这里提供一种思路,首先将之前的程序整理成函数:
这里仍然是使用TensorFlow执行卷积计算,然后对结果进行Kmeans分类,找出阈值,进行黑白的二值化。之前的代码比较分散,因此这里写成函数的形式,提高了复用率。接下来将函数应用在进行部分简单处理后的图像上,其结果如图3-10(从原始二维码图片(左),去掉边缘以及logo(中),最后将大小变成25×25的整个过程)所示:
图3-10 运行结果