来看看怎么用OpenCV解构Twitter大牛jagarikin的视觉错觉图

原文链接:http://www.juzicode.com/archives/5075

Hello,大家好,我是桔子菌,今天我们来看些有趣的视觉错觉图。Twitter上有个叫jagarikin的大牛发布了很多这种图片。像下面这张图片里,你是不是看到这些小人在爬楼梯,爬完了又往下跳?

图源:Twitter@jagarikin

要判断是否有运动,当然首先是要找个静止的参照物,我们可以先锁定图片中最大部分的楼梯,其实并没有移动,只是颜色的变化看起来好像在移动。再以楼梯右上角为参考点,它右上方那个小人动作和位置都是静止的,同样的方法找些参考点逐个分析,除了右下角那个小人在原地奔跑,其他小人实际上都是静止的。

上面这张小人图片仔细看还能发现问题所在,像下面这张图片中左右两个彩色的圆,它们的中心位置有发生变化吗?圆的大小有发生变化吗?

图源:Twitter@jagarikin

这张图没有上一张那样有个大的参照物,判断起来有点困难了,如果以圆圈中间的箭头作参照物,看起来好像真的有大小变化和位置移动,但是真的如此吗?

直观感觉容易骗人,我们就来做一些“理性”分析,接下来用Python和OpenCV处理看看会是什么结果。

下载下来的原图是gif格式的,gif文件在OpenCV中并不能用imread()直接读取,可以用VedioCapture()把各帧图像提取出来,保存为jpg格式的静态图片:

#juzicode.com VX:桔子code
import os
import time
import cv2 
def split_image(fn,path='.\\'):
    '''
    从gif中分离图片
    '''
    fn_full_path = path+fn
    cap = cv2.VideoCapture(fn_full_path)
    count_img = 0
    while True:
        flag,img = cap.read()
        if flag is not True:break
        cv2.imshow('img',img)
        cv2.waitKey(10)
        cv2.imwrite(str(count_img)+'.jpg',img)
        count_img += 1
        print('count_img=',count_img)

if name == "__main__":
     img_fn = '1.gif'
     split_image(img_fn)

好家伙,这一个gif图片里面居然有199帧图片。

接下来我们就来分析这些图片,首先是imread()读取图片:

path = '.\\'
img_files = [f for f in os.listdir(path) if '.jpg' in f]
for  file in img_files:
    img_dest = cv2.imread(path+file)
    show_img('img_dest VX:juzicode/juzicode.com',img_dest,is_show=dbg_is_show,wait_time=100)

然后在RGB彩色空间里提取R、G、B分量二值化之后效果并不是很好。

我们知道彩色图像除了用RGB彩色空间也可以用HSV彩色空间表示,RGB转换为HSV后提取H、S、V分量:

img_hsv = cv2.cvtColor(img_dest, cv2.COLOR_BGR2HSV)#RGB转换为hsv
show_img("hsv", img_hsv,wait_time=100)
img_h, img_s, img_v = cv2.split(img_hsv) #分离hsv各分量
show_img('img_h',img_h,is_show=dbg_is_show,wait_time=200)
show_img('img_s',img_s,is_show=dbg_is_show,wait_time=100)
show_img('img_v',img_v,is_show=dbg_is_show,wait_time=200)  

下面是RGB彩色空间和HSV彩色空间对比的效果:

将H、S、V分量分别显示出来,发现下图右下角的S分量对比度极高,可以用来做二值化处理:

在S分量上进行二值化:

thresh_bin,img_bin= cv2.threshold(img_s,30,255,cv2.THRESH_BINARY)
print('thresh_bin',thresh_bin )
show_img('img_bin',img_bin,wait_time=100,is_show=dbg_is_show)  

二值化后的图像效果不错,前景背景区分非常明显,下图右侧是S分量图像,左侧是S分量二值化后的图像:

接下来就是在二值化图像中找圆心位置、计算圆的半径,等价于计算内外圆外框的起始位置和宽高,如果内外圆外框的起始位置和宽高没有变化或者变化极小,就能说明圆圈并没有移动位置、大小也没有发生变化。

res = cv2.findContours(img_bin,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)  
contours=res[0]  
print ('len(contours):',len(contours))
for i in range(0,len(contours)): 
    x, y, w, h = cv2.boundingRect(contours[i])#提取起点、宽高
    print(i,len(contours[i]))
    if len(contours[i])<80:continue #去除噪声
    text='x,y=(%d,%d) w,h=(%d,%d)'%(x, y, w, h)
    if x< 150: #这里只显示左侧圆的内外圆起始位置和宽高
        cv2.putText(img_dest, text, (x,y-10 ), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)  
        cv2.rectangle(img_dest, (x,y), (x+w,y+h), (0,0,255), 3)
    show_img("img_dest_rect", img_dest,wait_time=100) 

下图中右上角的子图显示的是左侧内外圆的外框起始位置(x,y)和宽高(w,h):

通过采集内外圆的起始位置(x,y)和宽高(w,h)4个参数,动态对比了199张图像,波动都只在3个像素以内:

如果将起始位置(x,y)和宽高(w,h)用matplotlib绘图,左右圆的内外圆组成的4个框图的起点位置(x,y)都聚集在4个角落位置,4个框图的宽高(w,h)都聚集在对角2个位置,对应了内圆和外圆的大小。

通过将原图中圆环的内外圆位置和大小经过数值化提取后,再比较定量的数值数据,可以得出明确的结论,原图中的圆环并没有挪动位置、改变大小。

现在有了定量的分析,你还认为原图中的2个圆圈位置和大小在发生变化吗?实际上原图中圆圈内的小箭头才是“罪魁祸首”,是它们在诱导你作出错误的判断。

聪明的你看到下面这个正方体是在转动的吗?

图源:Twitter@jagarikin

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注