[Python&CV]- PPT拍歪了?OpenCV来帮你‘矫正’!

本文主要介绍借助Python和OpenCv库实现透视变换,以用来将2D图片中的某一部分以规则的形状展示出来(例如将拍歪的PPT矫正成为规则的四边形):

  1. 什么是透视变换;
  2. 透视变换实现思路;
  3. 测试效果

一、 什么是透视变换



百科:

透视变换(Perspective Transformation)是指利用透视中心、像点、目标点三点共线的条件,按透视旋转定律使承影面(透视面)绕迹线(透视轴)旋转某一角度,破坏原有的投影光线束,仍能保持承影面上投影几何图形不变的变换。(from 百科)

感觉还是有点抽象,看看下图 1就知道了:

图 1 透视变换 (from sougou 百科)

如上图:摄像机就相当于透视中心,摄像机和荧幕之间就是像点的集合,荧幕上展示的就是目标点集合; 中心点和某一像点的连线和荧幕的交点就是这个像点的目标点;像点(x, y)和目标点(s, t)是一一对应的关系,我们可以使用一个[3x3]的矩阵A描述他们之间的映射关系:[s, t, 1]T = dot(A, [x, y, 1]T) --- A矩阵乘 (x, y)的齐次坐标的转置就是(s, t)的齐次坐标的转置,每一个透视变换都对应着特定的变换矩阵A

通过这样的透视变换可以把长方形的像点集合(原图)透视成为不规则的四边形甚至是三角形的目标点的集合,同样的,也可以把不规则的四边形的像点的集合(原图)透视成为规则的四边形,比如长方形,正方形。后者就是实现照片中的PPT被拍成不规则的形状,然后将其变换为长方形或者正方形的原理!

二、 如何实现该透视效果

要想实现透视效果,关键在于如何去求上面提到的那个变换关系矩阵 A[3x3],好在OpenCv给我们提供了getPerspectiveTransform() 这样一个函数,我们只需要传进去四个像点集合 和 与其对应的四个目标点集合,我们就可以得到这样变换关系矩阵A[3x3]了。

如果是这样变换的话,就有一个问题:当目标点集(变换后的图像)中元素比像点集合(原图)元素数量大很多时,由于这些点又都是离散的,必然在目标点集(变换后的图像)中,不会有像点集合中的点映射过来,这样的话我们显示出来的目标点集合(变换后的图像)当中就会有很多黑点,那怎么解决呢,我们看下图 2 这个解决方案-改变映射方向:

图 2 映射方案图解 (忽略步骤三)

上图步骤一中的P,就是上面求出的A的逆,那么对于getPerspectiveTransform() 的四个像点集合 和 与其对应的四个目标点集合参数,四个像点集合就是我们选定的原图中不规则四边形的四个顶点,对应的四个目标点集合就是展示窗口的左上,右上,左下,右下四个点集合。

代码实现:
**step 1. ** 实现窗口监听函数,让用户选出需要透视成为长方形的原图部分,其实现代码如下:

def  onMouse(event, x, y, flag, param):
    global select_point_num 
    global img

    if event == 4 and select_point_num <4:
        print x, y, select_point_num,

        # 已选择的点加 1
        select_point_num = select_point_num + 1

        # 将选择好的点添加到相应的数组当中
        point = (x,y)
        cv2.circle(img, point, 2, (0, 255, 0), 2)#修改最后一个参数

        # 划线
        if len(star_points) >= 1:
            # 取出最后一个点
            last_point = star_points[len(star_points)-1]
            # 划线
            cv2.line(img, point, last_point, (155, 155, 155), 2)

        if len(star_points) == 3:
            # 取出开始的一个点
            last_point = star_points[0]
            # 划线
            cv2.line(img, point, last_point, (155, 155, 155), 2)

        # 更新图片
        cv2.imshow(window, img)
        star_points.append(point)
        if len(star_points) == 4:
            rectify_that_part_of_photo()

如上面代码所示,我们将用户选择的四个点保存在了 star_points这样一个数组中,并且我们将用户选择的四个点用线连起来了,使得需要被透视变换的部分被圈定;

step 2. ** 实现透视函数,思路如上面展示所示,代码如下(这里说明一下,在A求出来之后,借助A获取透视变换之后的图像,其实openCv也提供了相应的函数 cv2.warpPerspective(),但是在自己配置好的环境中运行总是出现segmentation fault 11**,所以后面这一部分功能,都是自己实现的):

def  rectify_that_part_of_photo():
    global star_points
    global opened_pic_file

   # 打开一份备份img
    img_copy = cv2.imread(opened_pic_file)
    cv2.namedWindow("result_img", 0);


    origin_selected_conors = []
    rigin_selected_lu = (star_points[0][0],star_points[0][1])
    rigin_selected_ru = (star_points[1][0],star_points[1][1])
    rigin_selected_ld = (star_points[3][0],star_points[3][1])
    rigin_selected_rd = (star_points[2][0],star_points[2][1])

    # 添加到 origin_selected_conors
    origin_selected_conors.append(rigin_selected_lu)
    origin_selected_conors.append(rigin_selected_ru)
    origin_selected_conors.append(rigin_selected_rd)
    origin_selected_conors.append(rigin_selected_ld)

    # 变换过后图像展示在 一个 宽为 show_width 长为 show_height的长方形窗口
    show_window_conors = []
    show_window_lu = (0, 0)
    show_window_ru = (show_width-1, 0)
    show_window_ld = (0, show_height-1)
    show_window_rd = (show_width-1, show_height-1)

    # 添加到 show_window_conors中
    show_window_conors.append(show_window_lu)
    show_window_conors.append(show_window_ru)
    show_window_conors.append(show_window_rd)
    show_window_conors.append(show_window_ld)

    # 获得transform 函数
    transform = cv2.getPerspectiveTransform(np.array(show_window_conors, dtype=np.float32), np.array(origin_selected_conors, dtype=np.float32))

    # 
    transfered_pos = np.zeros([show_width, show_height, 2])
    for x in range(show_width):
        for y in range(show_height):
            temp_pos = np.dot(transform, np.array([x, y, 1]).T)
            transed_x = temp_pos[0]/temp_pos[2]
            transed_y = temp_pos[1]/temp_pos[2]
            transfered_pos[x][y] = (int(transed_x), int(transed_y))

    # 生成 一个空的彩色图像
    result_img = np.zeros((show_height, show_width, 3), np.uint8)
    print result_img.shape

    for x in range(show_width):
        for y in range(show_height):
            result_img[y][x] = img_copy[transfered_pos[x][y][1]][transfered_pos[x][y][0]]

    cv2.imshow("result_img", result_img);

**step 3. ** 最后实现main函数,其主要工作:

  1. 获取用户的需要透视变换的图片;
  2. 将图片展示出来;
  3. 对窗口设置鼠标监听事件;

其代码如下:

if __name__ == '__main__':
    # 获取用户的输入
    # opened_pic_file 输入的图片地址和文件名
    if len(sys.argv) != 2:
        print "please input the filename!!!"
        exit(0)
    else:
        opened_pic_file = sys.argv[1]


    img = cv2.imread(opened_pic_file)
    img2 = []

    cv2.namedWindow(window, cv2.WINDOW_NORMAL)

    cv2.imshow(window, img)

    # 2. 给用户注册鼠标点击事件
    cv2.setMouseCallback(window, onMouse, None);

    # 监听用户的输入,如果用户按了esc建,那么就将window给销毁
    key = cv2.waitKey(0)
    if key == 27:
        cv2.destroyWindow(window)

三、 测试效果

在网上找了一张图片,斜着地展示了一部白色的美得不可方物的Smartisan T1手机,如下图 3,我们将这台手机透视成为一个规则的长方形,来模拟我们现实生活中将拍歪的PPT矫正的过程:

图 3 Smartisan T1

开始测试:
用户选择需要被透视变换的区域,如图 4:

图 4 用户选择需要被透视的部分

透视之后的效果如图 5,从效果图上看,我们可以知道,透视之后的图片当中是没有黑点的;


图 5 透视之后的效果


扩展

上面整个过程相当于只是透视了图片的一部分,如果我们选择一个开始的区域,和一个结束的区域,对开始和结束区域之间的每一帧都进行透视变换,并且展示出来,将会有一个动画效果:眼睛从开始区域看到结束区域这样一个效果,大家可以想一想如何去实现(图 2 中的步骤三已经给了思路!!!)这里展示一下自己实现的效果:
展示过程图 6

图 6 过程-1

展示过程图 7

图 7 过程-2

声明:
  1. 联系作者,新浪微博私信 @谷谷_z
  2. 如果在文章当中发现有描述错误的地方,还请您不吝指出,万分感谢!
  3. 此文章系本人原创作品,转发请注明出处!
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容