2024.9.13 Python与图像处理新国大EE5731课程大作业,SIFT 特征和描述符,单应性矩阵透视变换
1.SIFT特征点和描述符
import cv2
import numpy as np
import matplotlib.pyplot as plt
# read image
img =cv2.imread('im01.jpg',cv2.IMREAD_COLOR)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
plt.imshow(gray,plt.cm.gray)
提取图片,以灰度图像输出
#SIFT
sift = cv2.SIFT_create()
# keypoints detection
kp,des = sift.detectAndCompute(gray,None)
# plot keypoints
cv2.drawKeypoints(img,kp,img,flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
# plot
plt.figure(figsize=(8,6),dpi=100)
plt.imshow(img[:,:,::-1])
plt.title('SIFT')
plt.xticks([])
plt.yticks([])
plt.show
这段代码使用了 OpenCV 库中的 SIFT(Scale-Invariant Feature Transform)算法来检测图像的关键点(keypoints)并绘制它们。SIFT 是一种非常强大的图像特征提取算法,它能够检测图像中的局部特征,并且对缩放、旋转、亮度等变换具有鲁棒性。
代码逻辑:
1.创建 SIFT 对象
2.detectAndCompute() 是 SIFT 处理图像的关键函数。它接受一个灰度图像作为输入,并返回两个值:
kp (keypoints): 关键点,表示图像中的某些局部特征点的位置、尺度、方向等。每个关键点都有不同的属性,如坐标位置、尺度(表示特征点的大小)、方向等。
des (descriptors): 描述符,是关于每个关键点的特征向量。描述符是一个 128 维的向量,用于描述该特征点周围区域的特征信息。这些向量可以用于图像匹配或分类。
3.绘制关键点flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS: 使用这个标志,关键点会被以圆圈的形式绘制,并且每个圆圈的大小和方向会根据每个关键点的尺度和角度进行调整,使得你可以看到关键点的特性(即关键点的大小和方向)。
效果如下:
什么样的点会被认为是 SIFT 特征点?
角点(Corners): 图像中像素强度发生急剧变化的地方,比如边缘的交汇处。角点具有良好的局部不变性。
纹理丰富的区域: 图像中具有明显方向性或模式的区域,比如斑点或条纹。
图像中的独特结构: 比如物体的某些显著部分、边缘的转折点等。
SIFT 计算每个特征点周围的梯度信息,生成 128 维的描述符向量。这些描述符不仅能表示特征点周围的图像特征,还具有 旋转、缩放、光照不变性。这些描述符用于后续的图像匹配和识别。
在 SIFT 中,圈圈的大小代表每个关键点的 尺度(scale)。具体来说,SIFT 会在不同的尺度(即图像的不同分辨率)下检测图像中的特征点,这使得它可以检测到图像中大小不同的结构或特征。在绘制关键点时,圈圈的大小越大,表示该关键点的特征在更大的尺度上被检测到,也就是说,它代表了图像中较大的局部结构。
2.编写代码显示一个 GUI(图形用户界面),用户可以单击图像上的 4 个点。让用户选择 h1.jpg 上的 4 个点和 h2.jpg 上的 4 个点
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from scipy import linalg
import random as rm
import math
import cv2
from tkinter import *
from tkinter import messagebox
from PIL import Image, ImageTk
# the interchange of window axis to image axis
event2canvas = lambda e, c: (c.canvasx(e.x), c.canvasy(e.y))
#function to be called when mouse is clicked
def printcoords(event):
#outputting x and y coords to console
cx, cy = event2canvas(event, canvas)
# the axis of window and image is on the contrast
click = [event.y,event.x,cy,cx]
print ("(%d, %d) / (%d, %d)" % (event.y,event.x,cy,cx))
points.append(click)
i = len(points)
canvas.create_text(300+i*50, 670, text="(%d,%d)"%(event.y,event.x),font=("Arial",10))
这段代码的主要工作是在引入合适的库,然后图像缩放与保存,坐标转换函数,用于在鼠标点击事件发生时,将窗口坐标系的坐标 e.x 和 e.y 转换为画布上的坐标然后是鼠标点击事件处理函数:
event.x 和 event.y 是鼠标点击的窗口坐标。
使用 event2canvas 函数将窗口坐标 event.x 和 event.y 转换为画布坐标 cx 和 cy。
click = [event.y, event.x, cy, cx] 将窗口坐标和转换后的画布坐标存储在 click 变量中,表示点击的位置。
print() 输出坐标到控制台。
points.append(click) 将点击的坐标点保存到 points 列表中,用于后续处理。
canvas.create_text() 在画布的指定位置(图像下方)显示鼠标点击的坐标,i 是点击的次数。
# build a window object
root = Tk()
#box for points
points=[]
#size of the window
root.geometry("900x700")
root.title("CA1 Part3")
canvas=Canvas(root,bg='white',width=900,height=700)
canvas.pack()
# label = Label(root,text="You need to click 4 points on h1",font=("Arial",25),fg="black")
#setting up a tkinter canvas with scrollbars
# not use in this task
# frame = Frame(root, bd=2, relief=SUNKEN)
# frame.grid_rowconfigure(0, weight=1)
# frame.grid_columnconfigure(0, weight=1)
# xscroll = Scrollbar(frame, orient=HORIZONTAL)
# xscroll.grid(row=1, column=0, sticky=E+W)
# yscroll = Scrollbar(frame)
# yscroll.grid(row=0, column=1, sticky=N+S)
# canvas = Canvas(frame, bd=0, xscrollcommand=xscroll.set, yscrollcommand=yscroll.set)
# canvas.grid(row=0, column=0, sticky=N+S+E+W)
# xscroll.config(command=canvas.xview)
# yscroll.config(command=canvas.yview)
# frame.pack(fill=BOTH,expand=1)
#adding the image
img_h1 = Image.open('h1_1.jpg')
img_h1_photo = ImageTk.PhotoImage(img_h1)
img_h2 = Image.open('h2.jpg')
img_h2_photo = ImageTk.PhotoImage(img_h2)
Im = canvas.create_image(0,0,image=img_h1_photo,anchor=NW)
canvas.config(scrollregion=canvas.bbox(ALL))
Tx = canvas.create_text(200, 620, text="You need to click 4 points on h1",font=("Arial",20))
canvas.create_text(180, 670, text="The position of the points:",font=("Arial",20))
#mouseclick event
canvas.bind("<ButtonPress-1>",printcoords)
def change_img():
# set the event of button
canvas.itemconfig(Im,image=img_h2_photo)
canvas.itemconfig(Tx,text="You need to click 4 points on h2")
# set button
button = Button(root, text='Change to h2', command=change_img)
button.place(x=750, y=650)
# start
root.mainloop()
代码逻辑:
1.这里创建了一个 Tkinter 的根窗口 root,并将其大小设置为 900x700 像素。canvas 是一个 Tkinter 的画布对象,用于在窗口中绘制图像和文本,并作为鼠标点击事件的区域。
2.载入与显示图像,并转换成Tkingter的格式
3.显示提示信息
4.鼠标点击事件的绑定
5.图像切换功能
6.mainloop() 方法启动 Tkinter 的事件循环,等待用户的操作,并保持窗口持续运行。
初始状态下,窗口中显示 h1_1.jpg 图像,并在底部提示用户点击四个点。每次点击鼠标左键后,程序会输出点击的坐标。
窗口下方有一个按钮,点击该按钮后,画布上的图像会从 h1_1.jpg 切换为 h2.jpg,提示信息也会随之改变为“你需要在 h2 上点击 4 个点”。
注释掉的部分是窗口滑轮的部分
3.单应性矩阵
计算h1.jpg到h2.jpg的单应性矩阵,并显示单应性矩阵。利用单应性矩阵将h1.jpg转换为h2.jpg,并显示结果:
# H: homography computing
def hmat(points):
A = np.zeros([8,9])
b = []
for i in range(0,4):
# A matrix
h1_x = points[i][2]
h1_y = points[i][3]
h2_x = points[i+4][2]
h2_y = points[i+4][3]
A[2*i][:] = [h1_x,h1_y,1,0,0,0,-h2_x*h1_x,-h2_x*h1_y,-h2_x]
A[2*i+1][:] = [0,0,0,h1_x,h1_y,1,-h2_y*h1_x,-h2_y*h1_y,-h2_y]
# SVD
_, _, vt = linalg.svd(A)
H = vt[-1].reshape(3,3)
# H(3,3) = 1
H = H / H[2,2]
return H
这段代码用于计算单应性矩阵(Homography matrix),它用于在两个图像平面之间进行变换。单应性矩阵是一种 3x3 的矩阵,通常用于将一个平面的点映射到另一个平面,比如从图像 h1 上的四个点映射到图像 h2 上的四个对应点。通过给定的 4 对对应点,代码可以计算出用于变换的单应性矩阵 H。points 是一个包含 8 个点击点的数组,前 4 个点对应图像 h1,后 4 个点对应图像 h2
这时候我们已经可以计算单应性矩阵了,接下来就是计算环节。
# import image
h1 = cv2.imread('h1_1.jpg',cv2.IMREAD_COLOR)
h2 = cv2.imread('h2.jpg',cv2.IMREAD_COLOR)
# from BGR to RGB, CV2 default reading BGR
h1 = cv2.cvtColor(h1, cv2.COLOR_BGR2RGB)
h2 = cv2.cvtColor(h2, cv2.COLOR_BGR2RGB)
# compute the size of the image after transmitting
def get_size(h1,H):
[row,col,c] = h1.shape
# 4 cornor of the image h1
lt = np.array([[0,0,1]])
rt = np.array([[0,col,1]])
lb = np.array([[row,0,1]])
rb = np.array([[row,col,1]])
# edge matrix
edge = np.concatenate((lt,rt,lb,rb),axis = 0).T
T_edge = np.dot(H,edge)
# normalize
T_edge = T_edge[0:2,:]/T_edge[2,:]
T_edge = T_edge
print(T_edge)
return np.max(T_edge[0,:]),np.min(T_edge[0,:]),np.max(T_edge[1,:]),np.min(T_edge[1,:])
# transform h1 to h2
def transfer(h1,H):
# get output size
[max_x, min_x, max_y, min_y] = get_size(h1,H)
print([max_x, min_x, max_y, min_y])
x_diff = int(round(max_x - min_x+10))
y_diff = int(round(max_y - min_y+10))
[row,col,c] = h1.shape
# initialize the output image
h12h2 = np.zeros([x_diff+100,y_diff+100,c])
# print(h1.shape,h12h2.shape)
for j in range(0,row):
for k in range(0,col):
# h1 positon
p = np.array([[j,k,1]]).T
Tp = np.dot(H,p)
# normalize and move the image to the central
x = int(round(Tp[0,0]/Tp[2,0])-min_x)
y = int(round(Tp[1,0]/Tp[2,0])-min_y)
h12h2[x,y] = h1[j,k][:]
return h12h2.astype(int)
# show the original image h1
plt.imshow(h1)
# transfer h1 to h2 plane
h12h2 = transfer(h1,H)
plt.imshow(h12h2)
- 图像读取与颜色空间转换
- 计算转换后的图像尺寸(get_size 函数)
- 图像转换(transfer 函数)
- 可视化原始图像