图片二值化的逻辑是这样的:
(伪python代码)
for 点 in 灰度图片:
if 点.灰度 < 限度:
点 = 黑点
else:
点 = 白点
要想二值化一个图片,按照正常的脑回路,我就要将上述逻辑转换为代码:将图片转为numpy array,然后遍历每个点,灰度小于限度的,灰度变为0,灰度大于限度的,灰度变为255。
然而,众所周知,对一整个图片这么大的array,遍历的资源开销很大。
而在numpy中,有一类函数叫做ufunc
,这类函数会对数组中的全部元素进行同一操作。多个数据,同一操作,这不就是CPU的各种SIMD指令集干的事吗!numpy没理由不用这些指令集优化ufunc
。因此我猜测,用ufunc
代替遍历,就可节省资源开销。
那么问题来了:怎么把遍历改成ufunc
?这堆ufunc
似乎都不带条件判断啊?
好在虽然ufunc
都不提供条件判断,但是有的ufunc
存在“逻辑层面的条件判断”:比如floor(向下取整)
这个函数。
虽然floor
的实现不一定用了条件判断,但是它可以表示下面的逻辑:
for 一个整数 in 全体整数:
if 数字 >= 一个整数 and 数字 < 一个整数+1:
return 一个整数
因此,我们只要用一些奇妙的转换将大小的判断转化成floor
内部的逻辑判断,就可用ufunc
代替条件遍历。
上代码:
#img:一张灰度图片对应的numpy array
#threshold:灰度限度
def binarray(img,threshold):
a = img-np.array([[threshold]])
b = np.floor(a/np.array([[256]]))
c = b+np.array([[1]],dtype = np.int16)
bfilter = c.astype('uint8')
result = bfilter*np.array([[255]],dtype = np.uint8)
return result
解释一下上边的a
、b
、c
都是什么
a
:用广播机制让每一个像素点的灰度都减去threshold,小于threshold的值除后为负,其他数字为正
b
:用广播机制全体地板除256,将数组中的所有值压缩到小数范围内,因为灰度的范围就是0~255,小于threshold的值地板除后全变-1,其他数字地板除后全部变0
c
:广播机制,全体+1,现在正数是1
,负数是0
bfilter
:数据类型还原为图像的uint8
result
:广播机制,全体x255,二值化完毕
但是,还不够快,乘除还是太慢。
那么floor
是怎么实现不用条件判断完成其功能的?答案就是位运算,直面计算机的二进制。
浮点数由符号位、指数段、尾数段组成。(参考https://zhuanlan.zhihu.com/p/339949186)根据浮点数的原理,要向下取整,即削去小数部分,可以通过以下几步实现(不考虑负数):
虽然以上对floor
的分析和下面的代码没啥实际关系,但我确实被自己想出的这种“骚操作”惊到了,然后在不知为何的启发下想到了以下思路:
def binarray(img,threshold):
a = img.astype('int16')-np.array([[threshold]],dtype = np.int16)
b = np.right_shift(a,15)
c = b+np.array([[1]],dtype = np.int16)
bfilter = c.astype('uint8')
result = bfilter*np.array([[255]],dtype = np.uint8)
return result
我不知怎的就想起了高一时候信息老师说的:“整数的头一位表示正负”(要是这记性我能用在语文上就好了QwQ)于是有了以下过程:
a
:将原来的数据类型uint8
转换为int16
(不用int8
,因为装不下),再用广播机制让每一个像素点的灰度都减去threshold,小于threshold的值除后为负,其他数字为正
b
:右移15位,只保留第一位。此处有个坑:正数右移后,前面的位用0
补,负数右移后,前面的位用1
补,所以负数右移15位的结果不是1
,是-1
c
:同上
bfilter
:同上
result
:同上
有没有更快呢,计一下时:
位运算:0.0484583999999999
地板除:0.15314919999999987
成倍地快了!
现在我们再加入两组对照:
#从另一篇文章中拿来的
a = np.where((img < threshold), img, white)
b = np.where((a >= threshold), a, black)
result = b
#opencv
result = cv2.threshold(img,threshold,255,cv2.THRESH_BINARY)
然后再比一次:
np ufunc 位运算 0.05511350000000004
np ufunc 地板除 0.15484640000000005
opencv-python 0.006221599999999938
np.where 筛选 0.03421509999999994
非常的遗憾,位运算终究没比过numpy的内建方法,opencv更是一骑绝尘。
这告诉我们什么?越C越强大啊OwO 真求快那就学C啊
(可惜我这种知识有限的还只能学py,令人感叹)