1. 逆透视变换(Inverse Perspective Mapping —— IPM)
后续会补充逆透视变换的名词和原理的解析。
在自动/辅助驾驶中,车道线的检测非常重要。在前视摄像头拍摄的图像中,由于透视效应的存在,本来平行的事物,在图像中确实相交的。而IPM变换就是消除这种透视效应,所以也叫逆透视。
这里主要讲用opencv的下面两个语句实现逆透视变换的步骤。
cv2.getPerspectiveTransform
cv2.warpPerspective
首先获取图像和摄像头的各种指标,并且转换成弧度制、计算目标梯形的各个长度
def PerspectiveTransform(camera_angle, inside_angle, height, originH, originW):
camera_with = camera_angle * 1.79 # 相机内视场水平角度
tiangle = (180 - camera_with) / 2 # 上水平视场与地面夹角
alin = math.radians(camera_with) # 转换为弧度
ti = math.radians(tiangle)
al = math.radians(camera_angle)
ba = math.radians(inside_angle)
origin_length = ((height * math.cos(ba) * math.cos(ba / 2)) / (math.cos(al))) / math.sin(math.pi / 2 - al - ba)
result_length = ((height * math.cos(ba) * math.cos(ba / 2)) / (math.cos(al)))
bottom = math.tan(alin / 2) * height / math.cos(al) * 2
head = bottom + origin_length / math.tan(ti)
计算得出变换后图像(梯形)的顶边x,y坐标,映射宽度直接作为结果的图宽度。
result_pic_reso = math.floor(origin_length / head * originW)
pixavg = originW / head
x1 = ((head - bottom) / 2) * pixavg
x2 = ((head - bottom) / 2 + bottom) * pixavg
定义原始图像和变换后图像的四点坐标
pts1 = np.float32([[0, 0], [originW, 0], [originW, originH], [0, originH]]) # 原始图像四边形顶点坐标
pts2 = np.float32([[0, 0], [originW, 0], [x2, result_pic_reso], [x1, result_pic_reso]]) # 变换后图像四边形顶点坐标(一侧拉长变细变成梯形)
得到3*3的变换矩阵
M = cv2.getPerspectiveTransform(pts1, pts2)
分别获得图像和mask的变换后的图像
dst = cv2.warpPerspective(img, M, (originW, result_pic_reso))
dst2 = cv2.warpPerspective(img2, M, (originW, result_pic_reso))
最后写入即可。
cv2.imwrite('./TransformedMask/trm.png', dst)
cv2.imwrite('./Transformedpic/1.png', dst2)
转换结果如图:
2. SIFT算法——基于sift特征点的图像拼接
尺度不变特征转换即SIFT (Scale-invariant feature transform)是一种计算机视觉的算法。它用来侦测与描述影像中的局部性特征,它在空间尺度中寻找极值点,并提取出其位置、尺度、旋转不变量,此算法由 David Lowe在1999年所发表,2004年完善总结。
其应用范围包含物体辨识、机器人地图感知与导航、影像缝合、3D模型建立、手势辨识、影像追踪和动作比对。SIFT算法的实质是在不同的尺度空间上查找关键点(特征点),并计算出关键点的方向。SIFT所查找到的关键点是一些十分突出,不会因光照,仿射变换和噪音等因素而变化的点,如角点、边缘点、暗区的亮点及亮区的暗点等。
本次task为缝合相机连续拍摄的图像,步骤如下:
(这里演示的是左右图片缝合,如果是要上下缝合的图片只需要对两个图片进行旋转,到合适角度)
1. 得到一左一右两个连续拍摄的图像
left = cv2.imread("./ContinusePics/2.jpg")
right = cv2.imread("./ContinusePics/1.jpg")
# 转为灰度图像
gray1 = cv2.cvtColor(left, cv2.THRESH_BINARY)
gray2 = cv2.cvtColor(right, cv2.THRESH_BINARY)
2. 创建SIFT对象,计算出keypoints(类型)和特征点的描述子
#创建SIFT对象
sift = cv2.SIFT_create()
# 有两个必须的参数,第一个是求解特征点和特征向量的图像。第二个参数决定是否对特定区域求解,传入一个mask。
# 返回值kps是对图像求解的特征点(keypoints),是一个一维向量,其中每一个元素属于keypoint类型。dp是与kps对应的特征向量,也是一个列表,其中每一个元素是长度为128的向量.
kpsA, dpA = sift.detectAndCompute(gray1, None)
kpsB, dpB = sift.detectAndCompute(gray2, None)
3. 进行特征点粗匹配和筛选,并利用阈值设置和双向交叉检查方法进行初步的筛选。暴力匹配法(Brute-force match)会尝试所有可能的匹配,使得它总能够找到最佳匹配同时导致消耗的时间远大于快速最近邻算法。找出特征点后, 由于匹配的特征点之间不仅要求特征描述向量距离最近,而且应该与其它特征点能够区分开来, 因此采用最近邻点与次近邻点距离比值来限制特征点匹配。 计算最近邻点的距离和次近邻点的距离的比值,若小于阈值(0.4)则保留,反之,则剔除。
# 大力出奇迹
bf = cv2.BFMatcher()
matches = bf.knnMatch(dpA, dpB, k=2)
# 去除不可靠的匹配
good_matches = []
for m in matches:
if len(m) == 2 and m[0].distance < 0.4 * m[1].distance:
good_matches.append((m[0].queryIdx, m[0].trainIdx))
4. 重新获得匹配后的两张图片各自的特征点
kps1 = np.float32([kp.pt for kp in kpsA])
kps2 = np.float32([kp.pt for kp in kpsB])
kps1 = np.float32([kps1[a[0]] for a in good_matches])
kps2 = np.float32([kps2[a[1]] for a in good_matches])
5. 利用 RANSAC 算法进行精匹配
RANSAC算法(RANdom SAmple Consensus(随机抽样一致)) 在SIFT特征筛选中的主要流程是
(1) 从样本集中随机抽选一个RANSAC样本,即4个匹配点对
(2) 根据这4个匹配点对计算变换矩阵M
(3) 根据样本集,变换矩阵M,和误差度量函数计算满足当前变换矩阵的一致集consensus,并返回一致集中元素个数
(4) 根据当前一致集中元素个数判断是否最优(最大)一致集,若是则更新当前最优一致集
(5) 更新当前错误概率p,若p大于允许的最小错误概率则重复(1)至(4)继续迭代,直到当前错误概率p小于最小错误概率
# findHomography: 计算多个二维点对之间的最优单映射变换矩阵 H(3行x3列) ,使用最小均方误差或者RANSAC方法
M, status = cv2.findHomography(kps2, kps1, cv2.RANSAC, 4.0)
对图像匹配结果进行逆透视变换
# 上面的M就是转换矩阵,所以可以直接用cv2.warpPerspective, 不需要先使用cv2.getPerspectiveTransform()得到转换矩阵。
result = cv2.warpPerspective(right, M, (left.shape[1] + right.shape[1], right.shape[0]))
图像缝合的结果:
原图↓
缝合后↓
示例2
图都删了,不让发。。。