本文主要介绍借助Python和OpenCv库实现透视变换,以用来将2D图片中的某一部分以规则的形状展示出来(例如将拍歪的PPT矫正成为规则的四边形):
- 什么是透视变换;
- 透视变换实现思路;
- 测试效果
一、 什么是透视变换
百科:
透视变换(Perspective Transformation)是指利用透视中心、像点、目标点三点共线的条件,按透视旋转定律使承影面(透视面)绕迹线(透视轴)旋转某一角度,破坏原有的投影光线束,仍能保持承影面上投影几何图形不变的变换。(from 百科)
感觉还是有点抽象,看看下图 1就知道了:
如上图:摄像机就相当于透视中心,摄像机和荧幕之间就是像点的集合,荧幕上展示的就是目标点集合; 中心点和某一像点的连线和荧幕的交点就是这个像点的目标点;像点(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 这个解决方案-改变映射方向:
上图步骤一中的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函数,其主要工作:
- 获取用户的需要透视变换的图片;
- 将图片展示出来;
- 对窗口设置鼠标监听事件;
其代码如下:
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矫正的过程:
开始测试:
用户选择需要被透视变换的区域,如图 4:
透视之后的效果如图 5,从效果图上看,我们可以知道,透视之后的图片当中是没有黑点的;
扩展
上面整个过程相当于只是透视了图片的一部分,如果我们选择一个开始的区域,和一个结束的区域,对开始和结束区域之间的每一帧都进行透视变换,并且展示出来,将会有一个动画效果:眼睛从开始区域看到结束区域这样一个效果,大家可以想一想如何去实现(图 2 中的步骤三已经给了思路!!!)这里展示一下自己实现的效果:
展示过程图 6
展示过程图 7
声明:
- 联系作者,新浪微博私信 @谷谷_z
- 如果在文章当中发现有描述错误的地方,还请您不吝指出,万分感谢!
- 此文章系本人原创作品,转发请注明出处!