目的
需要提取出图中的数字,但是图中数字受到两种背景色的影响,因此需要先将其去掉。
实现
方法一(直接对数组操作)
首先按照官网示例看看图片的基本信息。
from PIL import Image
im = Image.open("xxx.jpg")
print(im.format, im.size, im.mode)
分别打印出图片的格式,尺寸,颜色模式如下:
JPEG (144, 231) RGB
图片是RGB模式也就意味着图片包含三个通道的信息,我们实际要处理的是231(高度)x144(宽度)x3(R,G,B)的数组。那么接下来以数组的形式读取:
import numpy as np
arr = np.array(im,dtype=int) #像素值默认为ubyte,范围为0-255,做加减会溢出
这里需要注意如果不写dtype=int在可能的加减运算中会有溢出警告提示,虽然在这里无加减,但为了规范,统一int导入utype导出。
随意打印几个特殊点的像素值进行分析:
print(arr[52][71]) #162,223,224
print(arr[181][56]) #224,159,223
print(arr[82][33]) #0,2,5
这里推荐进去左下选图片拾色,在线查看图片各点像素值。可以发现黑色的RGB值都很低,而其他两种颜色的RGB值较高。我们选取R通道进行操作:
for x in range(0,arr.shape[0]):
for y in range(0,arr.shape[1]):
if arr[x][y][0]>128:
arr[x][y]=255
遍历所有像素值,一旦R通道值大于128,则将其RGB三通道值变为255,也就是纯白。
new_im = Image.fromarray(arr.astype(np.ubyte))
new_im.show()
将数组以之前说的ubyte形式导出至Image对象,并显示处理后的图片。
// A code block
var foo = 'bar';
可以发现两种颜色基本被去掉,但有边角残余,完美主义者是不能接受的。
img = new_im.convert('1')
img.show()
再调用convert方法转换二值化。
可以发现残余被去掉了,但这样太麻烦了,能不能直接调用convert方法一步到位呢?
方法二(调用convert+操纵数组)
实际上调用convert方法也有两种实际上的操作,先讲复杂的。
im = im.convert('L')
im.show()
L代表灰度模式,1代表二值化(0和1)模式。为加深理解,这里先转化为灰度模式:
还是和之前一样变为数组导入,此时由于是灰度模式,会发现我们的数组维度变成了(231,144),没有三通道信息,和先前一样把大于128的值变为255。
import numpy as np
arr = np.array(im,dtype=int)
for x in range(0,arr.shape[0]):
for y in range(0,arr.shape[1]):
if arr[x][y]>128: #此时没有了第三个维度[0]
arr[x][y]=255
new_im = Image.fromarray(arr.astype(np.ubyte))
new_im.show()
先灰度再对数组进行操作就没有那些残余了。当然,还有更简单的方法。
方法三(一步到位convert)
肯定有人会和我一样的想法,为什么不直接二值化呢?让我们看看结果:
img = im.convert('1')
img.show()
为什么会有这么多点?不是把128以上的变成白,128以下的变成黑了吗?难不成背景颜色中的值其实相差很大?
其实,这是默认算法中的Floyd-Steinberg dither(弗洛伊德-斯滕伯格抖动)算法的影响。简单理解就是把该点的像素抖动到了附近的点上,原因是我们的人眼是对一个区域做出反应,所以该算法有助于降低图像的损失,但显然这是我们不需要的。
img = im.convert('1',dither=Image.NONE)
img.show()
img = im.convert('1',dither=Image.FLOYDSTEINBERG)
img.show()
真相大白了。原来是里面的dither参数有两个值可以接受,
一是Image.NONE,二是Image.FLOYDSTEINBERG。
默认的是带抖动的。
其实这里官方文档也写的不清楚,API里写的默认是dither=None。然后我把None填入发现没有变化,其实是Image.NONE。注意是全部大写的NONE。
总结
现在最大的感受就是一个库的官方教程都是虚的,把这些东西搬运来搬运去没有什么价值,只有进行实践才会发现有许多值得探索和注意的地方,即需要在看API的同时多敲代码。