梯度直方图
2D透视变换(图像配准)
透视矩阵有8个未知数(右下角i恒为1),我们只需要标定原图像和新图像的四个角点,即可解出透视矩阵。解透视矩阵的步骤如下:
化为Ax=b形式后,求解方法有很多,经查阅资料,测试了两种方法。第一种是对A求广义逆,然后求出x值,第二种方法是利用最小二乘法求解矩阵。公式如下:
经过测试,发现最小二乘法解透视矩阵,与opencv自带函数解出的结果一致。
随后就是针对每个像素点遍历求坐标,再将原图像坐标的像素值赋给新图像对应坐标点即可。但是,变换后的矩阵有许多噪声点,可以通过滤波器消除噪声。
但如果目标标定点位选取不好,则会造成大量缺失点,因为原图像坐标×透视矩阵后,很可能输出的点超过新图像大小,为避免这个问题,本题将遍历每个新图像点,求对应原图像坐标,加上一些边界约束,保证新图像每个像素都能被赋值。
如果出现了图像超出坐标范围而显示不全的情况,感觉可以扩展新图像的空间大小(如500×500的图片,扩展为以相同中心点的1500×1500图片)。
下面是2D透视变换的代码,有三部分:调用opencv的鼠标回调函数标定原图和目标图的四个角点、求解透视矩阵、透视变换。
import cv2
import numpy as np
import time
def mark_points(img): #鼠标标记点位的坐标
Points = [] #初始化
def on_mouse(event, x, y, flags, param): #opencv调用函数的格式
if event == cv2.EVENT_LBUTTONDOWN: #如果是鼠标左键按下
Points.append([x,y]) #添加点位坐标
cv2.namedWindow('img', cv2.WINDOW_AUTOSIZE)
cv2.setMouseCallback('img', on_mouse) #调用鼠标函数
while True:
cv2.imshow('img', img)
# 按 q 键退出
if cv2.waitKey(1) & 0xFF == ord('q'):
cv2.destroyAllWindows()
break
return np.float32(Points) #转为numpy数组格式,方便科学计算
def calculate_perspective_matrix(originPoints, targetPoints):
# 构建线性方程组 Ax = b
A = []
b = []
for i in range(4): #一共有四组点,依次赋值
x_origin, y_origin = originPoints[i]
x_target, y_target = targetPoints[i]
A.append([x_origin, y_origin, 1, 0, 0, 0, -x_target*x_origin, -x_target*y_origin])
A.append([0, 0, 0, x_origin, y_origin, 1, -y_target*x_origin, -y_target*y_origin])
b.append(x_target)
b.append(y_target)
A = np.array(A)
b = np.array(b)
'''A_inv = np.dot(A.T,np.linalg.inv(np.dot(A,A.T))) #用广义逆求解矩阵
x = np.dot(A_inv,b)
matrix1 = np.append(x,1).reshape(3, 3)
print('the Generalized inverse matrix is :')
print(matrix1)'''
matrix = np.dot(np.dot(np.linalg.inv(np.dot(A.T,A)),A.T),b) #最小二乘法计算
matrix = np.append(matrix,1).reshape(3, 3) #添加透视矩阵中右下角的常量1,然后变为3×3矩阵
#print('the OLS matrix is :')
#print(matrix)
return matrix
def transformed(img,matrix):
result = np.zeros(img.shape,dtype=np.uint8)
for row in range(img.shape[0]): #对每一个像素坐标进行变换
for col in range(img.shape[1]):
temp = np.dot(np.linalg.inv(matrix),np.append([col,row],1))
temp /= temp[2] #将算出的数,转化为平面图像坐标系的数,即除以z轴数据
temp = np.round(temp).astype(int) #数据转为int型
if temp[0] < 0:
temp[0] = 0
if temp[0] >= img.shape[1]:
temp[0] = img.shape[1]-1
if temp[1] < 0:
temp[1] = 0
if temp[1] >= img.shape[0]:
temp[1] = img.shape[0]-1
result[row,col] = img[temp[1]][temp[0]]
return result
if __name__ == '__main__':
img = cv2.imread('img.jpg') #读取图片
print('please mark the 4 points of origin image')
originPoints = mark_points(img) #标记原图片四个角点
#targetPoints = np.float32([[0,0],[img.shape[1]/4,0],[img.shape[1]/4,img.shape[0]/4],[0,img.shape[0]/4]])
time.sleep(1)
print('please mark the 4 points of target image')
targetPoints = mark_points(img) #标记你目标图像的四个角点(如果想为完美矩形,可以用上方赋值,不用鼠标标记)
start =time.time() #一个计时器start
print('calculating the perspective matrix......')
matrix = calculate_perspective_matrix(originPoints, targetPoints) #计算透视矩阵
print('the perspective matrix is:')
print(matrix)
print('calculating the transformed image......')
result = transformed(img, matrix) #根据透视矩阵求变换后的图像
result = cv2.medianBlur(result,5) #中值滤波去噪声点
end = time.time() #一个计时器end
print('Running time: %s Seconds'%(end-start))
cv2.imshow('result', result) #展示图像
cv2.imwrite('result1.jpg',result) #保存
cv2.waitKey(0)
cv2.destroyAllWindows()
参数估计
上述广义逆矩阵可能会在计算上有偏差,最小二乘法只适用于标点精确的场合(最小二乘法没法避免离群点),可以使用RANSAC随机抽样一致性算法来进行参数估计。它的主要思想是:随机选择一组点,拟合变换参数,寻找与该变换相符的点,重新拟合参数。