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

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

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

一、 什么是透视变换



百科:

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

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

[Python&CV]- PPT拍歪了?OpenCV来帮你‘矫正’!_第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 这个解决方案-改变映射方向:

[Python&CV]- PPT拍歪了?OpenCV来帮你‘矫正’!_第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. 此文章系本人原创作品,转发请注明出处!

你可能感兴趣的:([Python&CV]- PPT拍歪了?OpenCV来帮你‘矫正’!)