@[TOP](利用python-opencv 做出图像/视频的哈哈镜效果)
引言
在进行过相机校准的学习之后,我们已经理解了相机的外参/内参矩阵。
关于相机校准的原理以及内外参矩阵的介绍可以参考
我们可以做一个简单的应用:通过opencv-python cv 弯曲所生成的图像。
我们来看看原理:
原理
构建哈哈镜需要三个步骤:
-
构建一个虚拟相机
-
对于图像输入,先由图片定义虚拟世界坐标(图片位于XOY平面),将变形函数应用至其上(XOY以z轴为法线,可用plane.Z表示),
-
最后经过虚拟相机的参数矩阵转换到像素坐标并最终输出。
注:后面会看到,利用vcam包的虚拟相机可以让这个过程变得十分简单,但编写创建虚拟相机代码的过程有助于让我们更好地理解相机校准章节的内容
步骤
1.虚拟相机构建
我们需要构造外参矩阵M1,内参矩阵K,复合成为投影矩阵P。
来看代码:
#This program can create a vitual camera
import numpy as np
import cv2 as cv
# Translation Matrix T
T=np.array([1,0,0,Tx],[0,1,0,Ty],[0,0,1,Tz])
# Rotation Matrix R ,liberty degree is 3
# Every Rotation in a 3D space could be seperate to 3 diffrent Rotation transformation around 3 axis xyz orthogonal,
# So the Rotation Matrix is the product of 3 Rotation Matrix R=Rx*Ry*Rz
Rx = np.array([[1, 0, 0], [0, math.cos(alpha), -math.sin(alpha)], [0, math.sin(alpha), math.cos(alpha)]])
Ry = np.array([[math.cos(beta), 0, -math.sin(beta)],[0, 1, 0],[math.sin(beta),0,math.cos(beta)]])
Rz = np.array([[math.cos(gamma), -math.sin(gamma), 0],[math.sin(gamma),math.cos(gamma), 0],[0, 0, 1]])
R = np.matmul(Rx, np.matmul(Ry, Rz))
# Extrinsic Matrix M1
M1=np.matmul(R,T)
# Intrinsic Matrix K
K=np.array([fx,0,ox],[0,fy,y],[0,0,1])
# Final Matrix
P=np.matmul(K,M1)
可以看到我们先构建平移矩阵T(3x4),然后构建旋转矩阵R(由3个绕xyz轴的旋转矩阵复合而成)。
内参矩阵的结构可以参考相机校准的文章。
2.定义3D表面、
我们用numpy的meshgrid去构造3D表面,格数为输入图片的像素数量。然后reshape去变换为列向量,用np.concatenate去合并向量,最后用刚刚得到的P矩阵去把得到的3D坐标投影到像素坐标上
#input image
image=cv.imread('args')
H,W=image.shape[:2]
x=np.linspace(-W/2,W/2,W)
y=np.linspace(-H/2,H/2,H)
xv,yv=np.meshgrid(x,y)
#XY colomn vector
X=xv.reshape(-1,1)
Y=yv.reshape(-1,1)
# Plan Z=1,but we write 0*X to make it a vectcor
Z=0*X+1
pts3d = np.concatenate(([X],[Y],[Z],[X*0+1]))[:,:,0]
pts2d = np.matmul(P,pts3d)
u = pts2d[0,:]/(pts2d[2,:]+0.00000001)
v = pts2d[1,:]/(pts2d[2,:]+0.00000001)
3.图像重映射
重映射通过将输入图像的每个像素从其原始位置移动到由重映射功能定义的新位置来生成新图像。
这种数学定义是自然的,map_x和map_y为我们指出了像素(x,y)的像。然而基于图像的source image和destination image的尺寸差异,我们可能得到非整数的map_x和map_y,这将在目标图像上产生“holes”,既没有原像的点,这是我们所不愿意看到的。
因此我们决定采用如下的数学定义:
这种方式被称为“inverse warping”。
当dst维度大于src时,虽然会产生多对一的可能性,但是至少避免了产生holes。
从2D投影点pts2d中提取map_x,map_y。并将这两个参数传递给remap函数,作用在image上即可。
# Get mapx and mapy from the 2d projected points
map_x,map_y = c1.getMaps(pts2d)
# Applying remap function to input image (img) to generate the funny mirror effect
output = cv2.remap(img,map_x,map_y,interpolation=cv2.INTER_LINEAR)
cv2.imshow("Funny mirror",output)
cv2.waitKey(0)
利用vcam包的简单版本
vcam的简单配置
利用pip即可自动安装vcam
pip install vcam
1.图像扭曲
vcam为我们提供了非常简洁的代码包,我们仅需要输入想要得到的像素宽高即可构建虚拟相机:
camera1=vcam(H=H,W=W)
用这个虚拟相机,我们可以直接把3D点投影到像素坐标
pts2d = camera1.project(pts3d)
来看完整代码:
import numpy as np
import cv2 as cv
from vcam import vcam,meshGen
img=cv.imread('Huashengmi.jpg')
# Resize the image to a suitable size
img=cv.resize(img,(400,400))
# Get the width and height of the image
H,W=img.shape[:2]
# Creation of the virtual camera
camera1=vcam(H=H,W=W)
# Generate the plan
plane=meshGen(H,W)
# Apply the warp function to the plan XOY
plane.Z=np.sin(8*np.pi*(plane.X/plane.W))
# Some diffrent functions here
# plane.Z -= 100*np.sqrt((plane.X*1.0/plane.W)**2+(plane.Y*1.0/plane.H)**2)
# plane.Z += 20*np.exp(-0.5*((plane.X*1.0/plane.W)/0.1)**2)/(0.1*np.sqrt(2*np.pi))
pts3d=plane.getPlane()
# project the 3D points to the pixel coordinates
pts2d=camera1.project(pts3d)
# Get map_x and map_y from 2D points
map_x,map_y=camera1.getMaps(pts2d)
output=cv.remap(img,map_x,map_y,interpolation=cv.INTER_LINEAR)
# The image captured is mirrored, use cv2.flip to flip it
output=cv.flip(output,1)
# Display the final image
cv.imshow("Img",output)
cv.waitKey(0)
注释已经比较详尽,我们提供了几个扭曲函数并且利用反转函数去反转相机成的倒像。
2.视频扭曲
用cv2.Videocapture去获取前置摄像头的图像,进行上述步骤即可用电脑实现“哈哈镜”的效果:
import numpy as np
import cv2 as cv
from vcam import vcam,meshGen
# Activate the front camera
cap=cv.VideoCapture(0)
while True:
# Capture frames from the video
ret,frame=cap.read()
# Check if the capture was successful
if not ret :
print("Failed to capture the frames from camera")
# Resize the frames (Not really necessary)
frame=cv.resize(frame,(800,600))
# Get the width and height of the image
H,W=frame.shape[:2]
# Creation of the virtual camera
camera1=vcam(H=H,W=W)
# Generate the plan
plane=meshGen(H,W)
# Apply the warp function to the plan XOY
plane.Z=10*np.sin(6*np.pi*(plane.X/plane.W))
# Some diffrent functions here
# plane.Z -= 100*np.sqrt((plane.X*1.0/plane.W)**2+(plane.Y*1.0/plane.H)**2)
# plane.Z += 20*np.exp(-0.5*((plane.X*1.0/plane.W)/0.1)**2)/(0.1*np.sqrt(2*np.pi))
pts3d=plane.getPlane()
# project the 3D points to the pixel coordinates
pts2d=camera1.project(pts3d)
# Get map_x and map_y from 2D points
map_x,map_y=camera1.getMaps(pts2d)
output=cv.remap(frame,map_x,map_y,interpolation=cv.INTER_LINEAR)
# The image captured is mirrored, use cv2.flip to flip it
output=cv.flip(output,1)
# Display the current frame
cv.imshow("Funny mirror",output)
cv.waitKey(20)
我的摄像头输出图像时4:3的,我从480x640放大到了800x600
至此我们已经学会了简单的图像变形处理,接下来我可能会学习“镜像透视法”的图像扭曲,利用findhomography去处理图像。
参考了learn-opencv的Funny mirrors 教程:learn opencv Funny Mirrors