一、數據準備
src2.BMP
src1.BMP
src.bmp
model.BMP
二、識別原理講解(sift特征提取)
SIFT(Scale-Invariant Feature Transform,尺度不變特征變換)是一種經典的圖像特征提取算法,核心優勢是不受圖像尺度縮放、旋轉、光照變化的影響,能穩定提取圖像中的關鍵特征點,廣泛用于圖像匹配、目標檢測、圖像拼接等場景。以下從核心原理、OpenCV 實現步驟、關鍵特性三方面展開介紹,全程不涉及公式。
核心原理(4 個關鍵步驟)
SIFT 的本質是通過 “模擬人眼對不同尺度物體的感知”,找到圖像中 “無論放大 / 縮小、旋轉都不變” 的特征點,并為每個特征點生成唯一的 “特征描述符”(用于后續匹配)。整個過程可拆解為 4 步:
1. 尺度空間極值檢測:找 “不受尺度影響” 的候選特征點
人眼觀察物體時,近距離看細節、遠距離看整體 ——SIFT 通過 “高斯模糊 + 圖像縮放” 構建 “尺度空間”,模擬這種感知過程:
- 對原始圖像做不同程度的高斯模糊(模糊程度逐漸增加),再對模糊后的圖像做下采樣(縮小尺寸),得到一系列 “不同尺度” 的圖像集合(稱為 “高斯金字塔”)。
- 在相鄰尺度的圖像間做差值(得到 “差分高斯金字塔”),然后在每個像素點的 “上下左右相鄰像素 + 相鄰尺度對應位置像素” 中比較,找到 “局部極值點”—— 這些點就是 “在不同尺度下都突出” 的候選特征點(比如小尺度下的角點、大尺度下的輪廓頂點)。
2. 特征點精確定位:剔除 “不穩定” 的候選點
第一步找到的候選點中,可能包含因噪聲、邊緣干擾產生的 “假特征點”,需要進一步篩選:
- 對每個候選極值點,分析其周圍像素的灰度變化,判斷該點是否是 “真正的特征點”(比如邊緣上的點會被剔除,因為邊緣在垂直方向的灰度變化不顯著,穩定性差)。
- 最終保留 “灰度變化顯著、在尺度上穩定” 的點,作為最終的 SIFT 特征點。
3. 特征點方向賦值:實現 “旋轉不變性”
為了讓特征點不受圖像旋轉影響,需要給每個特征點分配一個 “主方向”:
- 以特征點為中心,取一個小區域(比如半徑 16 像素的圓),統計該區域內所有像素的 “梯度方向”(即像素灰度變化的方向,比如從暗到亮的方向)和 “梯度大小”(灰度變化的強度)。
- 用 “直方圖” 統計這些梯度方向的分布,找到出現次數最多的方向(主方向),將該方向作為特征點的 “基準方向”—— 后續生成描述符時,會以這個主方向為參考,從而抵消旋轉的影響。
4. 生成特征描述符:讓特征 “可匹配”
每個特征點需要一個 “唯一標識”(描述符),用于和其他圖像中的特征點對比匹配:
- 以特征點為中心,取一個 16×16 的像素塊(按主方向對齊,避免旋轉干擾),將這個塊分成 4×4 的 16 個小格子(每個小格子 4×4 像素)。
- 對每個小格子,統計其中像素的梯度方向分布(用 8 個方向的直方圖表示),得到 8 個數值。
- 16 個小格子共生成 16×8=128 個數值,將這 128 個數值組成一個向量,就是該特征點的 “128 維 SIFT 描述符”。
- 最后會對描述符做 “歸一化” 處理(比如消除光照變化的影響:讓描述符向量的長度為 1),確保其在不同光照下仍能穩定匹配
三、對比檢測指紋(簡單)
這里我們需要用到(src1.BMP,src2.BMP,model.BMP)三張圖片,在(src1.BMP,src2.BMP)找出和model.BMP匹配的圖片
代碼示例:
1. 導入依賴庫
import cv2
導入 OpenCV 庫,它提供了強大的計算機視覺處理功能,包括 SIFT 特征提取和 FLANN 匹配器。
2. 核心認證函數?verification
該函數接收三個參數:
src
:待驗證的源圖像model
:作為標準的模型圖像threshold
:判斷認證通過的匹配點數量閾值,默認值為 500
函數執行流程:
(1)初始化 SIFT 特征提取器
sift = cv2.SIFT_create()
SIFT(尺度不變特征變換)是一種對尺度、旋轉、光照變化都具有穩健性的特征提取算法,非常適合用于圖像匹配。
(2)提取圖像特征點和描述符
kp1, des1 = sift.detectAndCompute(src, None)
kp2, des2 = sift.detectAndCompute(model, None)
kp1
/kp2
:分別是源圖像和模型圖像的特征點(KeyPoint)集合des1
/des2
:分別是對應特征點的描述符(Descriptor),是特征點的數字表示
(3)特征點檢查
if des1 is None or des2 is None:return "認證失敗" # 無特征點
如果任何一幅圖像無法提取到特征點,直接返回認證失敗。
(4)FLANN 特征匹配
flann = cv2.FlannBasedMatcher()
matches = flann.knnMatch(des1, des2, k=2)
- 使用 FLANN(快速最近鄰搜索庫)匹配器進行特征匹配,比暴力匹配更高效
knnMatch
?方法返回每個特征點的前 k 個最近鄰匹配(這里 k=2)
(5)篩選優質匹配
good_matches = [m for m, n in matches if m.distance < 0.8 * n.distance]
應用 Lowe's 比率測試篩選優質匹配:如果最佳匹配距離小于次佳匹配距離的 80%,則認為是一個好的匹配點,這能有效剔除誤匹配。
(6)返回認證結果
return "認證通過" if len(good_matches) >= threshold else "認證失敗"
如果優質匹配點數量達到或超過閾值,則認證通過,否則失敗。
3. 主程序
if __name__ == "__main__":src1 = cv2.imread("src1.BMP")src2 = cv2.imread("src2.BMP")model = cv2.imread("model.BMP")
讀取待驗證圖像(src1.BMP、src2.BMP)和模型圖像(model.BMP)。
if src1 is None or src2 is None or model is None:print("? 圖像讀取失敗,請檢查文件路徑")
檢查圖像是否成功讀取,如果有任何圖像讀取失敗,提示檢查文件路徑。
else:print("src1驗證結果:", verification(src1, model))print("src2驗證結果:", verification(src2, model))
如果所有圖像都成功讀取,則分別對 src1 和 src2 進行認證,并打印結果。
完整代碼:
import cv2def verification(src, model, threshold=500):"""使用SIFT + FLANN匹配,返回認證結果"""sift = cv2.SIFT_create()kp1, des1 = sift.detectAndCompute(src, None)kp2, des2 = sift.detectAndCompute(model, None)if des1 is None or des2 is None:return "認證失敗" # 無特征點flann = cv2.FlannBasedMatcher()matches = flann.knnMatch(des1, des2, k=2)good_matches = [m for m, n in matches if m.distance < 0.8 * n.distance]return "認證通過" if len(good_matches) >= threshold else "認證失敗"# ========== 主程序 ==========
if __name__ == "__main__":src1 = cv2.imread("src1.BMP")src2 = cv2.imread("src2.BMP")model = cv2.imread("model.BMP")if src1 is None or src2 is None or model is None:print("? 圖像讀取失敗,請檢查文件路徑")else:print("src1驗證結果:", verification(src1, model))print("src2驗證結果:", verification(src2, model))
運行結果:
四、進階任務(多圖片匹配)
現在需要對(src.bmp)在指紋庫里進行匹配并繪制出匹配上的點(指紋庫:database,已經上傳,可以自行下載)
代碼詳解:
1. 導入依賴庫
import os # 用于文件和目錄操作
import cv2 # OpenCV庫,用于圖像處理和特征提取
import numpy as np # 用于數值計算和數組操作
2. 核心函數:獲取匹配點
def get_good_matches(src, model):# 讀取源圖像和模板圖像img1 = cv2.imread(src)img2 = cv2.imread(model)if img1 is None or img2 is None:return [], [], []# 創建SIFT特征提取器并計算特征點和描述符sift = cv2.SIFT_create()kp1, des1 = sift.detectAndCompute(img1, None) # kp: 關鍵點, des: 描述符kp2, des2 = sift.detectAndCompute(img2, None)if des1 is None or des2 is None:return kp1 or [], kp2 or [], []# 使用FLANN匹配器進行特征匹配flann = cv2.FlannBasedMatcher()matches = flann.knnMatch(des1, des2, k=2) # k=2表示返回兩個最佳匹配# 應用Lowe's比例測試篩選良好的匹配點good = []for m, n in matches:# 如果最佳匹配距離小于次佳匹配的80%,則認為是好的匹配if m.distance < 0.8 * n.distance:good.append(m)return kp1, kp2, good
3. 計算匹配點個數
def getNum(src, model):_, _, good = get_good_matches(src, model)return len(good)
這個函數簡化了匹配點獲取過程,只返回良好匹配點的數量,用于比較不同模板的匹配程度。
4. 獲取指紋編號
def getID(src, database):max_num = 0best_name = "0.bmp" # 默認值,防止未匹配時報錯# 遍歷數據庫中的所有文件for file in os.listdir(database):model = os.path.join(database, file)num = getNum(src, model)print(f"文件名:{file},匹配點個數:{num}")# 記錄匹配點最多的文件if num > max_num:max_num = numbest_name = file# 如果匹配點數量大于等于100,則認為匹配有效ID = int(best_name[0]) if max_num >= 100 else 9999return ID
該函數通過比較輸入圖像與數據庫中所有圖像的匹配點數量,找到最相似的圖像,并返回其對應的 ID。
5. 根據 ID 獲取姓名
def getName(ID):nameID = {0: '張三', 1: '李四', 2: '王五', 3: '趙六', 4: '朱老七',5: '錢八', 6: '曹九', 7: '王二麻子', 8: 'andy', 9: 'Anna',9999: "沒找到"}return nameID.get(int(ID), "未知")
這是一個簡單的 ID 與姓名映射表,根據識別出的 ID 返回對應的姓名。
6. 繪制匹配點
def drawMatchesWithCircles(src, model, show=True):img1 = cv2.imread(src)img2 = cv2.imread(model)kp1, kp2, good_matches = get_good_matches(src, model)# 在兩張圖像上繪制匹配點(紅色實心圓)for match in good_matches:pt1 = tuple(map(int, kp1[match.queryIdx].pt)) # 源圖像上的匹配點pt2 = tuple(map(int, kp2[match.trainIdx].pt)) # 模板圖像上的匹配點cv2.circle(img1, pt1, 3, (0, 0, 255), -1) # -1表示填充圓cv2.circle(img2, pt2, 3, (0, 0, 255), -1)# 拼接兩張圖像以便對比顯示h1, w1 = img1.shape[:2]h2, w2 = img2.shape[:2]h = max(h1, h2) # 取最大高度w = w1 + w2 # 寬度相加combined = np.zeros((h, w, 3), dtype=np.uint8)combined[:h1, :w1] = img1combined[:h2, w1:w1+w2] = img2# 顯示結果if show:cv2.imshow("Matches with Circles", combined)cv2.waitKey(0)cv2.destroyAllWindows()return len(good_matches)
7. 主程序
if __name__ == "__main__":src = "src.bmp" # 待識別的源圖像database = "database" # 模板數據庫目錄# 執行識別流程ID = getID(src, database)name = getName(ID)print("識別結果是:", name)# 繪制最佳匹配的匹配點if ID != 9999:# 找到最佳匹配的模板文件best_model = os.path.join(database, [f for f in os.listdir(database) if f.startswith(str(ID))][0])print(f"\n正在繪制 {best_model} 與 {src} 的匹配點...")drawMatchesWithCircles(src, best_model)else:print("未找到有效匹配,不進行繪制。")
工作流程總結
- 讀取待識別圖像 (
src.bmp
) 和數據庫中的所有模板圖像 - 對每對圖像使用 SIFT 算法提取特征點并進行匹配
- 統計匹配點數量,找到匹配度最高的模板
- 根據模板的 ID 查找對應的姓名并輸出
- 可視化顯示最佳匹配的特征點對應關系
完整代碼:
import os
import cv2
import numpy as np############## 獲取匹配點(核心函數) #####################
def get_good_matches(src, model):img1 = cv2.imread(src)img2 = cv2.imread(model)if img1 is None or img2 is None:return [], [], []sift = cv2.SIFT_create()kp1, des1 = sift.detectAndCompute(img1, None)kp2, des2 = sift.detectAndCompute(img2, None)if des1 is None or des2 is None:return kp1 or [], kp2 or [], []flann = cv2.FlannBasedMatcher()matches = flann.knnMatch(des1, des2, k=2)good = []for m, n in matches:if m.distance < 0.8 * n.distance:good.append(m)return kp1, kp2, good############## 計算匹配個數 #####################
def getNum(src, model):_, _, good = get_good_matches(src, model)return len(good)############# 獲取指紋編號 ################
def getID(src, database):max_num = 0best_name = "0.bmp" # 默認值,防止未匹配時報錯for file in os.listdir(database):model = os.path.join(database, file)num = getNum(src, model)print(f"文件名:{file},匹配點個數:{num}")if num > max_num:max_num = numbest_name = fileID = int(best_name[0]) if max_num >= 100 else 9999return ID############# 獲取姓名 ################
def getName(ID):nameID = {0: '張三', 1: '李四', 2: '王五', 3: '趙六', 4: '朱老七',5: '錢八', 6: '曹九', 7: '王二麻子', 8: 'andy', 9: 'Anna',9999: "沒找到"}return nameID.get(int(ID), "未知")############## 繪制匹配點(實心小圓圈) #####################
def drawMatchesWithCircles(src, model, show=True):img1 = cv2.imread(src)img2 = cv2.imread(model)kp1, kp2, good_matches = get_good_matches(src, model)# 繪制匹配點for match in good_matches:pt1 = tuple(map(int, kp1[match.queryIdx].pt))pt2 = tuple(map(int, kp2[match.trainIdx].pt))cv2.circle(img1, pt1, 3, (0, 0, 255), -1)cv2.circle(img2, pt2, 3, (0, 0, 255), -1)# 拼接顯示h1, w1 = img1.shape[:2]h2, w2 = img2.shape[:2]h = max(h1, h2)w = w1 + w2combined = np.zeros((h, w, 3), dtype=np.uint8)combined[:h1, :w1] = img1combined[:h2, w1:w1+w2] = img2if show:cv2.imshow("Matches with Circles", combined)cv2.waitKey(0)cv2.destroyAllWindows()return len(good_matches)############# 主程序 ################
if __name__ == "__main__":src = "src.bmp"database = "database"ID = getID(src, database)name = getName(ID)print("識別結果是:", name)# 繪制最佳匹配if ID != 9999:best_model = os.path.join(database, [f for f in os.listdir(database) if f.startswith(str(ID))][0])print(f"\n正在繪制 {best_model} 與 {src} 的匹配點...")drawMatchesWithCircles(src, best_model)else:print("未找到有效匹配,不進行繪制。")詳細介紹代碼