目錄
簡介
一、了解圖像投影(透視)變換
一、定義與原理
二、應用場景
三、實現方法
二、案例分析
1. 輔助函數定義
1.1.cv_show 函數
1.2.order_points 函數
1.3.four_point_transform 函數
1.4.resize 函數
2. 主程序執行流程
2.1.圖像縮放處理
2.2.輪廓檢測
2.3.獲取最大輪廓(文檔邊緣)
2.4.透視變換并保存結果
3.總結
簡介
????????在計算機視覺的進階學習旅程中,圖像投影(透視)變換是一座連接理論與實戰的關鍵橋梁。無論是實現證件照的智能矯正、自動駕駛中道路場景的視角轉換,還是無人機航拍圖像的地理坐標映射,這一技術都扮演著不可或缺的核心角色。如果你曾困惑于 “為何傾斜拍攝的文檔會出現邊緣變形”“如何讓二維圖像呈現出三維空間的立體感”,或是在項目開發中急需解決圖像視角轉換的技術難題,那么本系列博客將為你提供系統性的解決方案。
一、了解圖像投影(透視)變換
????????圖像的透視變換(Perspective Transformation)是一種在圖像處理中廣泛使用的技術,它通過模擬人眼或相機鏡頭觀看三維空間物體時的透視效果,來改變圖像的視角和形狀。以下是對圖像透視變換的詳細解釋:
一、定義與原理
????????透視變換是一種非線性變換,它可以將一個二維坐標系中的點映射到三維坐標系中的點,然后再將其投影到另一個二維坐標系中的點。這種變換基于幾何學中的透視原理,通過一個3x3的變換矩陣來實現,該矩陣作用于圖像的每個像素坐標,從而進行坐標的映射轉換。透視變換能夠模擬真實世界中的透視效果,使物體看起來更接近、更遠或者從不同角度觀看。
二、應用場景
透視變換在圖像處理和計算機視覺領域有著廣泛的應用,包括但不限于以下幾個方面:
????????圖像校正:通過透視變換可以修正由于視角引起的圖像扭曲,如將拍攝的傾斜書本或建筑物照片校正為正視圖。
????????圖像合成:將兩個圖像中的物體或場景合成在一起,仿佛它們是從同一視角拍攝的。
虛擬現實(VR)和增強現實(AR):在VR和AR應用中,透視變換用于模擬真實世界的視角和深度感,提升用戶體驗。
????????目標檢測與跟蹤:在目標檢測和跟蹤任務中,透視變換可以用于調整圖像視角,以便更準確地識別和跟蹤目標。
????????三維重建:在三維重建過程中,透視變換是連接二維圖像與三維空間的關鍵技術之一。
三、實現方法
在OpenCV等圖像處理庫中,透視變換通常通過以下步驟實現:
????????選擇對應點:在原始圖像和目標圖像上分別選擇四個非共線的對應點。這些點通常是圖像中的顯著特征點,如紙上的角落、建筑物的邊緣等。
????????計算變換矩陣:使用OpenCV中的cv2.getPerspectiveTransform函數根據這些對應點計算透視變換矩陣。
????????應用變換矩陣:使用cv2.warpPerspective函數將計算得到的透視變換矩陣應用于原始圖像,從而得到變換后的圖像。
二、案例分析
了解了枯燥的理論我們用下面這個案例來分析
實現對一個小票進行圖像投影變換
1. 輔助函數定義
1.1.cv_show 函數
def cv_show(name, img):cv2.imshow(name, img) # 顯示圖像,name是窗口名稱,img是要顯示的圖像cv2.waitKey(0) # 等待用戶按鍵,0表示無限等待
這是一個簡化圖像顯示操作的函數,封裝了 OpenCV 的imshow
和waitKey
方法,方便在多個地方調用。
1.2.order_points 函數
def order_points(pts):# 初始化一個4x2的矩陣存儲排序后的坐標rect = np.zeros(shape=(4, 2), dtype="float32")# 按順序找到對應坐標:左上,右上,右下,左下s = pts.sum(axis=1) # 對每個點的x和y坐標求和rect[0] = pts[np.argmin(s)] # 最小的和對應左上角(x+y最小)rect[2] = pts[np.argmax(s)] # 最大的和對應右下角(x+y最大)diff = np.diff(pts, axis=1) # 計算每個點的y-x差值rect[1] = pts[np.argmin(diff)] # 最小的差值對應右上角(y-x最小)rect[3] = pts[np.argmax(diff)] # 最大的差值對應左下角(y-x最大)return rect
這個函數用于對四邊形的四個頂點進行排序,確保它們按 "左上→右上→右下→左下" 的順序排列,為后續的透視變換做準備。
1.3.four_point_transform 函數
def four_point_transform(image, pts):# 獲取排序后的坐標點rect = order_points(pts)(tl, tr, br, bl) = rect # 分別賦值給左上、右上、右下、左下# 計算寬度:取底部和頂部寬度的最大值widthA = np.sqrt(((br[0] - bl[0]) **2) + ((br[1] - bl[1])** 2))widthB = np.sqrt(((tr[0] - tl[0]) **2) + ((tr[1] - tl[1])** 2))maxWidth = max(int(widthA), int(widthB))# 計算高度:取右側和左側高度的最大值heightA = np.sqrt(((tr[0] - br[0]) **2) + ((tr[1] - br[1])** 2))heightB = np.sqrt(((tl[0] - bl[0]) **2) + ((tl[1] - bl[1])** 2))maxHeight = max(int(heightA), int(heightB))# 定義變換后的目標坐標dst = np.array([[0, 0], # 左上[maxWidth - 1, 0], # 右上[maxWidth - 1, maxHeight - 1], # 右下[0, maxHeight - 1] # 左下], dtype="float32")# 計算透視變換矩陣M = cv2.getPerspectiveTransform(rect, dst)# 應用透視變換warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))return warped
這是核心函數,實現了圖像的透視變換,將傾斜的四邊形區域轉換為正面矩形視圖,模擬了從不同角度拍攝的文檔轉換為正視圖的效果。
1.4.resize 函數
def resize(image, width=None, height=None, inter=cv2.INTER_AREA):dim = None # 存儲調整后的尺寸(h, w) = image.shape[:2] # 獲取原始圖像的高度和寬度# 如果寬高都未指定,直接返回原圖if width is None and height is None:return image# 如果寬度未指定,按高度比例縮放if width is None:r = height / float(h) # 計算縮放比例dim = (int(w * r), height) # 計算新的寬度# 否則按寬度比例縮放else:r = width / float(w) # 計算縮放比例dim = (width, int(h * r)) # 計算新的高度# 執行縮放操作resized = cv2.resize(image, dim, interpolation=inter)return resized
這個函數用于按比例調整圖像大小,避免圖像過大處理困難或過小影響精度。
2. 主程序執行流程
import cv2
import numpy as np# 讀取輸入圖像
image = cv2.imread('fapiao.jpg')
cv_show('image', image) # 顯示原始圖像
2.1.圖像縮放處理
# 計算縮小比率(以高度為基準縮放到500像素)
ratio = image.shape[0] / 500.0
orig = image.copy() # 保存原始圖像副本
image = resize(orig, height=500) # 按比例縮小圖像
cv_show('1', image) # 顯示縮小后的圖像
將圖像按比例縮小到高度為 500 像素,便于后續處理,同時記錄縮放比例,以便后期恢復原始尺寸。
2.2.輪廓檢測
print("STEP 1: 輪廓檢測")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 轉換為灰度圖
# 自動閾值二值化處理(OTSU算法)
edged = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
# 查找輪廓
cnts = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)[-2]
# 繪制所有輪廓并顯示
image_contours = cv2.drawContours(image.copy(), cnts, -1, color=(0, 0, 255), thickness=1)
cv_show('image_contours', image_contours)
這一步將圖像轉為灰度圖,再通過二值化處理突出邊緣,最后檢測出圖像中的所有輪廓。
2.3.獲取最大輪廓(文檔邊緣)
print("STEP 2: 獲取最大輪廓")
# 按輪廓面積排序,取最大的那個(通常是文檔的邊緣)
screenCnt = sorted(cnts, key=cv2.contourArea, reverse=True)[0]
print(screenCnt.shape) # 輸出輪廓形狀# 輪廓近似(將不規則輪廓近似為多邊形)
peri = cv2.arcLength(screenCnt, closed=True) # 計算輪廓周長
# 用Douglas-Peucker算法近似輪廓,0.05*peri是近似精度
screenCnt = cv2.approxPolyDP(screenCnt, 0.05 * peri, closed=True)
print(screenCnt.shape) # 輸出近似后的輪廓形狀# 繪制最大輪廓并顯示
image_contour = cv2.drawContours(image.copy(), [screenCnt], -1, color=(0, 255, 0), thickness=2)
cv2.imshow("image_contour", image_contour)
cv2.waitKey(0)
文檔通常是圖像中面積最大的矩形物體,所以這里通過面積排序找到最大輪廓,并通過輪廓近似算法將其轉換為四邊形。
2.4.透視變換并保存結果
# 應用四點透視變換,注意要將坐標還原到原始圖像尺寸
warped = four_point_transform(orig, screenCnt.reshape(4, 2) * ratio)
# 保存變換后的圖像
cv2.imwrite(filename='invoice_new.jpg', img=warped)
# 顯示變換后的圖像
cv2.namedWindow('xx', cv2.WINDOW_NORMAL) # 創建可調整大小的窗口
cv2.imshow("xx", mat=warped)
cv2.waitKey(0)
3.總結
整個程序的核心思想是:
- 讀取圖像并適當縮放
- 通過圖像處理技術找到文檔的邊緣輪廓
- 利用透視變換將傾斜的文檔轉換為正視圖
- 保存和顯示處理結果
最后我們還可以通過之前學習的旋轉、閾值處理和圖像形態學等讓圖片中的文字更加突出
import cv2
import numpy as npimg=cv2.imread('invoice_new.jpg')
img=cv2.resize(img,dsize=None,fx=0.4,fy=0.4)
rotated_image1 = cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE)
cv2.imshow('ni90',rotated_image1)
cv2.waitKey(0)ret, binary = cv2.threshold(rotated_image1, 125, 255, cv2.THRESH_BINARY)
cv2.imshow( 'binary', binary) # 偏白的變純白,偏黑的變純黑
cv2.waitKey(0)kernel = np.ones((2,2),np.uint8) # 這里kernel大小,修改為5*5試試
erosion_1 = cv2.erode(binary,kernel,iterations=1) #iterations改為5試試
cv2.imshow('erosion_1',erosion_1)
cv2.waitKey(0)