使用python進行圖像處理—圖像變換(6)

圖像變換是指改變圖像的幾何形狀或空間位置的操作。常見的幾何變換包括平移、旋轉、縮放、剪切(shear)以及更復雜的仿射變換和透視變換。這些變換在圖像配準、圖像校正、創建特效等場景中非常有用。

6.1仿射變換(Affine Transformation)

仿射變換是一種線性變換,它可以表示為矩陣乘法和平移的組合。在二維圖像中,一個仿射變換可以由一個2x3的矩陣表示:

其中 ( (x, y) ) 是原始圖像中的點坐標,( (x’, y’) ) 是變換后圖像中的點坐標。這個矩陣可以實現平移、旋轉、縮放、剪切的任意組合。

在Pillow中,可以使用 img.transform(size, method, data, filter) 方法進行仿射變換。其中 method 是 Image.AFFINE,data 是一個包含六個浮點數的元組 ( (a_{11}, a_{12}, b_1, a_{21}, a_{22}, b_2) )。

其中 ( (x, y) ) 是原始圖像中的點坐標,( (x’, y’) ) 是變換后圖像中的點坐標。這個矩陣可以實現平移、旋轉、縮放、剪切的任意組合。

在Pillow中,可以使用 img.transform(size, method, data, filter) 方法進行仿射變換。其中 method 是 Image.AFFINE,data 是一個包含六個浮點數的元組 ( (a_{11}, a_{12}, b_1, a_{21}, a_{22}, b_2) )。

import numpy as np
from PIL import Image
import matplotlib.pyplot as plt# 假設我們有一個示例圖像 (例如,上面使用的 example.jpg 或模擬圖像)
try:img = Image.open('example.jpg').convert('RGB') # 確保是RGB模式print("成功加載圖像使用 Pillow Image.open")
except FileNotFoundError:print("示例圖像 example.jpg 未找到。")# 創建一個模擬圖像img = Image.new('RGB', (400, 300), color = 'lightblue')from PIL import ImageDrawdraw = ImageDraw.Draw(img)draw.text((50, 50), "Hello, Affine!", fill='black', font_size=30)print("已創建模擬圖像。")# 定義仿射變換矩陣的六個參數 (a11, a12, b1, a21, a22, b2)# 示例 1: 平移 (向右平移 50 像素,向下平移 30 像素)
# 變換矩陣參數: (1, 0, tx, 0, 1, ty)
tx = 50
ty = 30
affine_params_translate = (1, 0, tx, 0, 1, ty)# 應用仿射變換
# size: 輸出圖像的尺寸 (width, height)
# method: Image.AFFINE
# data: 仿射變換參數元組
# filter: 重采樣濾波器
translated_img = img.transform(img.size, Image.AFFINE, affine_params_translate, Image.Resampling.BILINEAR)# 示例 2: 旋轉 (逆時針旋轉 30 度)
# 旋轉矩陣參數: (cos(theta), -sin(theta), 0, sin(theta), cos(theta), 0)
theta_deg = 30
theta_rad = np.deg2rad(theta_deg) # 將角度轉換為弧度
cos_theta = np.cos(theta_rad)
sin_theta = np.sin(theta_rad)
# 如果繞原點旋轉,b1和b2為0
affine_params_rotate_origin = (cos_theta, -sin_theta, 0, sin_theta, cos_theta, 0)# 繞圖像中心旋轉需要額外的平移步驟,或者調整變換矩陣
# 假設圖像中心是 (cx, cy)
# 變換步驟:平移中心到原點 -> 旋轉 -> 平移回中心
# 對應的仿射矩陣參數可以通過矩陣乘法計算得到
# 更簡單的實現是先計算好中心點,然后構建變換參數
# 平移到原點: (1, 0, -cx, 0, 1, -cy)
# 旋轉: (cos, -sin, 0, sin, cos, 0)
# 平移回中心: (1, 0, cx, 0, 1, cy)
# 復合變換矩陣 = [平移回中心] * [旋轉] * [平移到原點]
# 計算過程比較繁瑣,Pillow的 rotate() 方法更常用,但這里演示 affine 的靈活性# 應用繞原點旋轉的仿射變換
rotated_img_affine = img.transform(img.size, Image.AFFINE, affine_params_rotate_origin, Image.Resampling.BILINEAR)
# 注意:繞原點旋轉可能會導致圖像部分移出畫布,需要調整輸出尺寸# 示例 3: 縮放 (x 方向縮放 1.2 倍,y 方向縮放 0.8 倍)
# 變換矩陣參數: (sx, 0, 0, 0, sy, 0)
sx = 1.2
sy = 0.8
affine_params_scale = (sx, 0, 0, 0, sy, 0)# 應用縮放仿射變換
scaled_img_affine = img.transform(img.size, Image.AFFINE, affine_params_scale, Image.Resampling.BILINEAR)# 示例 4: 剪切 (x 方向剪切,y 不變)
# 變換矩陣參數: (1, shx, 0, shy, 1, 0)
shx = 0.5 # x 方向剪切因子
shy = 0   # y 方向剪切因子 (這里 y 不變)
affine_params_shear = (1, shx, 0, shy, 1, 0)# 應用剪切仿射變換
sheared_img_affine = img.transform(img.size, Image.AFFINE, affine_params_shear, Image.Resampling.BILINEAR)# 顯示結果
plt.figure(figsize=(15, 10))plt.subplot(2, 3, 1)
plt.imshow(img)
plt.title('原始圖像')
plt.axis('off')plt.subplot(2, 3, 2)
plt.imshow(translated_img)
plt.title(f'平移 ({tx}, {ty})')
plt.axis('off')plt.subplot(2, 3, 3)
plt.imshow(rotated_img_affine) # 注意:繞原點旋轉,可能部分移出
plt.title(f'仿射旋轉 ({theta_deg} 度, 繞原點)')
plt.axis('off')plt.subplot(2, 3, 4)
plt.imshow(scaled_img_affine)
plt.title(f'縮放 ({sx}x, {sy}y)')
plt.axis('off')plt.subplot(2, 3, 5)
plt.imshow(sheared_img_affine)
plt.title(f'剪切 (shx={shx})')
plt.axis('off')plt.tight_layout()
plt.show()# 保存結果
translated_img.save('output_affine_translated.png')
rotated_img_affine.save('output_affine_rotated_origin.png')
scaled_img_affine.save('output_affine_scaled.png')
sheared_img_affine.save('output_affine_sheared.png')
print("仿射變換示例已完成并保存結果。")

代碼解釋:

  • img = Image.open('example.jpg').convert('RGB'):加載圖像并確保它是RGB模式,因為一些變換可能對模式敏感。
  • img.transform(size, method, data, filter):這是Pillow中執行各種變換的通用方法。
  • size:輸出圖像的尺寸元組(width, height)。可以保持原尺寸,也可以根據需要放大以容納整個變換后的圖像。
  • method=Image.AFFINE:指定變換方法為仿射變換。
  • data:一個包含6個浮點數的元組(a11, a12, b1, a21, a22, b2),對應仿射變換矩陣的前兩行。
  • filter=Image.Resampling.BILINEAR:指定重采樣濾波器。在圖像變換后,新的像素位置可能落在原始像素之間,需要通過重采樣(插值)來確定新像素的值。BILINEAR是雙線性插值,提供了較好的質量和速度平衡。其他選項如NEAREST (最近鄰插值,速度最快,質量最低)和LANCZOS (高質量濾波器,速度較慢)適用于不同場景。
  • affine_params_translate = (1, 0, tx, 0, 1, ty):平移變換的仿射參數。a11=1, a22=1保持縮放不變,a12=0, a21=0保持剪切不變,b1=tx, b2=ty實現平移。
  • theta_rad = np.deg2rad(theta_deg):將角度從度轉換為弧度,因為三角函數通常使用弧度。
  • cos_theta = np.cos(theta_rad)和sin_theta = np.sin(theta_rad):計算旋轉所需的正弦和余弦值。
  • affine_params_rotate_origin = (cos_theta, -sin_theta, 0, sin_theta, cos_theta, 0):繞原點逆時針旋轉的仿射參數。
  • affine_params_scale = (sx, 0, 0, 0, sy, 0):縮放變換的仿射參數。a11=sx, a22=sy實現x和y方向的縮放,其他參數為0保持不變形和不平移。
  • affine_params_shear = (1, shx, 0, shy, 1, 0):剪切變換的仿射參數。shx控制x方向的剪切,shy控制y方向的剪切。
  • 使用Matplotlib顯示原始圖像和各種仿射變換后的圖像。

通過調整這六個參數,我們可以組合實現復雜的仿射變換。

在OpenCV中,仿射變換通常通過cv2.getAffineTransform()計算由三個對應點對確定的仿射變換矩陣,然后使用cv2.warpAffine()應用變換。

import numpy as np
import cv2 # 導入OpenCV庫
import matplotlib.pyplot as plt# 假設我們有一個示例圖像 (加載為OpenCV格式)
# OpenCV 默認使用 BGR 模式加載彩色圖像
try:img_cv = cv2.imread('example.jpg')if img_cv is None:raise FileNotFoundErrorprint("成功加載圖像使用 cv2.imread")
except FileNotFoundError:print("示例圖像 example.jpg 未找到。")# 創建一個模擬圖像 (灰度)img_cv = np.full((300, 400), 150, dtype=np.uint8) # 灰度背景 150# cv2.putText(img_cv, "Hello, Affine!", (50, 150), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2) # 黑色文字print("已創建模擬灰度圖像。")# OpenCV 圖像通常是 NumPy 數組
rows, cols = img_cv.shape[:2] # 獲取圖像高度和寬度# 示例 1: 平移 (向右平移 50 像素,向下平移 30 像素)
tx = 50
ty = 30
# 平移矩陣 M: [[1, 0, tx], [0, 1, ty]]
M_translate = np.float32([[1, 0, tx],[0, 1, ty]]) # 必須是 float32 類型# 應用仿射變換 (cv2.warpAffine)
# src: 輸入圖像
# M: 2x3 變換矩陣 (float32 類型)
# dsize: 輸出圖像尺寸 (width, height)
# flags: 插值方法 (cv2.INTER_LINEAR, cv2.INTER_NEAREST, etc.)
# borderMode: 邊界處理方式 (cv2.BORDER_CONSTANT, cv2.BORDER_REPLICATE, etc.)
# borderValue: 邊界填充值 (borderMode=cv2.BORDER_CONSTANT 時使用)
translated_img_cv = cv2.warpAffine(img_cv, M_translate, (cols, rows)) # 輸出尺寸通常與原圖相同,部分會移出# 如果需要擴展輸出尺寸以包含整個平移后的圖像
# new_width = cols + abs(tx)
# new_height = rows + abs(ty)
# translated_img_cv_expanded = cv2.warpAffine(img_cv, M_translate, (new_width, new_height))# 示例 2: 旋轉 (逆時針旋轉 30 度,繞中心點)
angle_deg = 30
# 獲取旋轉矩陣
# cv2.getRotationMatrix2D(center, angle, scale)
# center: 旋轉中心點 (x, y)
# angle: 旋轉角度 (以度為單位,正值表示逆時針旋轉)
# scale: 縮放因子 (1.0 表示不縮放)
center = (cols // 2, rows // 2)
M_rotate = cv2.getRotationMatrix2D(center, angle_deg, 1.0)# 應用旋轉仿射變換
rotated_img_cv = cv2.warpAffine(img_cv, M_rotate, (cols, rows)) # 保持原尺寸,部分會裁剪# 如果需要擴展尺寸以包含整個旋轉后的圖像
# 可以通過計算旋轉后圖像的四個角點的新坐標,然后確定新的邊界框來獲取新的尺寸
# cv2.warpAffine 提供了額外的輸出尺寸計算功能,但手動計算更靈活
# 例如,計算新尺寸以便完整包含旋轉后的圖像:
# (x, y) 是原始圖像的四個角點坐標
# (x', y') = M * [x, y, 1].T
# 找到 (x', y') 的 min/max x 和 y 坐標,確定新的邊界框和尺寸
cos = np.abs(M_rotate[0, 0]) # cos(angle)
sin = np.abs(M_rotate[0, 1]) # sin(angle)
new_width = int(rows * sin + cols * cos)
new_height = int(rows * cos + cols * sin)
# 調整平移分量以將整個圖像移到新圖像的中心
M_rotate[0, 2] += (new_width / 2) - center[0]
M_rotate[1, 2] += (new_height / 2) - center[1]
rotated_img_cv_expanded = cv2.warpAffine(img_cv, M_rotate, (new_width, new_height))# 示例 3: 縮放 (x 和 y 方向都縮放 1.5 倍)
M_scale = np.float32([[1.5, 0, 0],[0, 1.5, 0]])# 應用縮放仿射變換
# 注意:這里需要指定新的輸出尺寸
scaled_img_cv = cv2.warpAffine(img_cv, M_scale, (int(cols * 1.5), int(rows * 1.5)))# 示例 4: 使用三對對應點進行仿射變換
# 定義原始圖像的三個點和它們在目標圖像中的對應位置
# points_original: [[x1, y1], [x2, y2], [x3, y3]]
# points_target: [[x1', y1'], [x2', y2'], [x3', y3']]
pts1 = np.float32([[50, 50], [200, 50], [50, 200]]) # 原始圖像的三個點
pts2 = np.float32([[10, 100], [200, 50], [100, 250]]) # 目標圖像中對應的三個點# 計算仿射變換矩陣 M
M_points = cv2.getAffineTransform(pts1, pts2)# 應用基于點的仿射變換
transformed_img_cv = cv2.warpAffine(img_cv, M_points, (cols, rows)) # 輸出尺寸可以調整# 顯示結果
plt.figure(figsize=(15, 10))plt.subplot(2, 3, 1)
plt.imshow(cv2.cvtColor(img_cv, cv2.COLOR_BGR2RGB)) # OpenCV是BGR,用Matplotlib顯示需要轉RGB
plt.title('原始圖像')
plt.axis('off')plt.subplot(2, 3, 2)
plt.imshow(cv2.cvtColor(translated_img_cv, cv2.COLOR_BGR2RGB))
plt.title('OpenCV 平移')
plt.axis('off')plt.subplot(2, 3, 3)
plt.imshow(cv2.cvtColor(rotated_img_cv_expanded, cv2.COLOR_BGR2RGB)) # 顯示擴展尺寸的旋轉結果
plt.title('OpenCV 旋轉 (繞中心)')
plt.axis('off')plt.subplot(2, 3, 4)
plt.imshow(cv2.cvtColor(scaled_img_cv, cv2.COLOR_BGR2RGB))
plt.title('OpenCV 縮放')
plt.axis('off')plt.subplot(2, 3, 5)
plt.imshow(cv2.cvtColor(transformed_img_cv, cv2.COLOR_BGR2RGB))
plt.title('OpenCV 基于點變換')
plt.axis('off')plt.tight_layout()
plt.show()# 保存結果 (OpenCV保存圖像)
cv2.imwrite('output_cv_affine_translated.jpg', translated_img_cv)
cv2.imwrite('output_cv_affine_rotated_expanded.jpg', rotated_img_cv_expanded)
cv2.imwrite('output_cv_affine_scaled.jpg', scaled_img_cv)
cv2.imwrite('output_cv_affine_transformed.jpg', transformed_img_cv)
print("OpenCV 仿射變換示例已完成并保存結果。")

代碼解釋:

  • import cv2:導入OpenCV庫。
  • img_cv = cv2.imread('example.jpg'):使用cv2.imread()加載圖像。OpenCV默認加載彩色圖像為BGR格式。
  • M_translate = np.float32([[1, 0, tx], [0, 1, ty]]):創建平移的2x3仿射變換矩陣。必須是float32類型。
  • cv2.warpAffine(img_cv, M_translate, (cols, rows)):應用仿射變換。
  • 第一個參數是輸入圖像。
  • 第二個參數是2x3的變換矩陣。
  • 第三個參數是輸出圖像的尺寸元組(width, height)。
  • cv2.getRotationMatrix2D(center, angle_deg, 1.0):獲取繞指定中心旋轉指定角度(度)和縮放因子為1.0的2x3仿射變換矩陣。
  • 計算旋轉后擴展尺寸:通過計算旋轉后圖像四個角點的新位置,可以確定包含整個旋轉圖像所需的最小矩形邊界框的尺寸。
  • M_scale = np.float32([[1.5, 0, 0], [0, 1.5, 0]]):創建縮放的2x3仿射變換矩陣。對角線元素[0, 0]和[1, 1]分別控制x和y方向的縮放因子。
  • cv2.getAffineTransform(pts1, pts2):根據原始圖像中的三對對應點pts1和目標圖像中的對應點pts2,計算確定這個仿射變換的2x3矩陣。仿射變換由三對非共線點唯一確定。
  • cv2.cvtColor(img_cv, cv2.COLOR_BGR2RGB):在使用Matplotlib顯示OpenCV加載的BGR格式圖像時,需要將其轉換為RGB格式,否則顏色會不正確。
  • cv2.imwrite('output_cv_affine_translated.jpg', translated_img_cv):使用cv2.imwrite()保存圖像。OpenCV會自動根據文件擴展名選擇編碼格式。
  • OpenCV在處理圖像變換方面功能強大且通常效率更高,尤其是在需要計算變換矩陣(如基于點對應)或進行復雜變換時。

第六章:圖像變換(續)

6.2透視變換(Perspective Transformation)
  • 透視變換,也稱為投影變換,比仿射變換更復雜,它可以改變圖像的“視角”。仿射變換保持平行線的平行性,而透視變換則不保留平行性,但保留直線的直線性。透視變換可以將圖像中的一個平面投影到另一個平面上,這對于校正由相機傾斜引起的圖像畸變(如掃描文檔的校正)、創建虛擬現實場景或進行圖像拼接非常重要。

  • 一個二維圖像的透視變換可以由一個3x3的矩陣表示:

  • 變換后的齊次坐標 ( (x’, y’, w’) ) 與二維笛卡爾坐標 ( (x_{new}, y_{new}) ) 的關系是:

  • [ x_{new} = \frac{x’}{w’} = \frac{a_{11}x + a_{12}y + b_1}{c_1x + c_2y + d}
    y_{new} = \frac{y’}{w’} = \frac{a_{21}x + a_{22}y + b_2}{c_1x + c_2y + d} ]

  • 這個矩陣有8個自由度(因為矩陣乘法結果可以整體乘以一個非零常數而不改變 ( x_{new} ) 和 ( y_{new} ),通常令 ( d=1 )),因此需要至少4對非共線的對應點來確定變換矩陣。

  • 在OpenCV中,可以使用 cv2.getPerspectiveTransform() 計算由四對對應點確定的透視變換矩陣,然后使用 cv2.warpPerspective() 應用變換。

import numpy as np
import cv2 # 導入OpenCV庫
import matplotlib.pyplot as plt# 假設我們有一個示例圖像 (加載為OpenCV格式)
# OpenCV 默認使用 BGR 模式加載彩色圖像
try:img_cv = cv2.imread('example.jpg')if img_cv is None:raise FileNotFoundErrorprint("成功加載圖像使用 cv2.imread")
except FileNotFoundError:print("示例圖像 example.jpg 未找到。")# 創建一個模擬圖像 (彩色),并在上面繪制一些形狀以方便觀察透視變換效果img_cv = np.full((400, 600, 3), 200, dtype=np.uint8) # 淺灰色背景# 繪制一個矩形cv2.rectangle(img_cv, (100, 100), (500, 300), (255, 0, 0), 5) # 藍色矩形# 繪制一些文字cv2.putText(img_cv, "Perspective Transform", (120, 200), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) # 紅色文字# 繪制一些點cv2.circle(img_cv, (100, 100), 10, (0, 255, 0), -1) # 綠色圓點 (左上角)cv2.circle(img_cv, (500, 100), 10, (0, 255, 0), -1) # 綠色圓點 (右上角)cv2.circle(img_cv, (500, 300), 10, (0, 255, 0), -1) # 綠色圓點 (右下角)cv2.circle(img_cv, (100, 300), 10, (0, 255, 0), -1) # 綠色圓點 (左下角)print("已創建模擬彩色圖像。")# 定義原始圖像的四個角點坐標 (必須是 float32 類型)
# 這些點通常是需要進行透視校正的平面上的四個角點
rows, cols = img_cv.shape[:2] # 獲取圖像高度和寬度
# 例如,原始圖像的四個角點
pts1 = np.float32([[100, 100], [500, 100], [500, 300], [100, 300]]) # 對應上面繪制的矩形的四個角點# 定義目標圖像中這四個點對應的位置 (必須是 float32 類型)
# 例如,將這四個點變換到一個新的矩形區域,實現“拉直”效果
# 假設目標矩形的角點
pts2 = np.float32([[50, 50], [550, 50], [550, 350], [50, 350]]) # 變換到一個更大的矩形區域,保持矩形形狀# 檢查點數量是否正確 (需要四對點)
if pts1.shape != (4, 2) or pts2.shape != (4, 2):print("錯誤: 需要提供四對對應點進行透視變換。")
else:# 1. 計算透視變換矩陣 M# cv2.getPerspectiveTransform(src, dst)# src: 原始圖像中的四對點 (float32)# dst: 目標圖像中對應的四對點 (float32)M_perspective = cv2.getPerspectiveTransform(pts1, pts2)print("計算得到的透視變換矩陣 M:\n", M_perspective)# 2. 應用透視變換 (cv2.warpPerspective)# src: 輸入圖像# M: 3x3 變換矩陣 (float32 類型)# dsize: 輸出圖像尺寸 (width, height)# flags: 插值方法 (cv2.INTER_LINEAR, cv2.INTER_NEAREST, etc.)# borderMode: 邊界處理方式# borderValue: 邊界填充值# 輸出尺寸可以根據目標點的位置自行決定,這里為了演示方便,先使用一個固定的較大尺寸output_width = 600output_height = 400transformed_img_cv = cv2.warpPerspective(img_cv, M_perspective, (output_width, output_height))# 顯示原始圖像和透視變換后的圖像plt.figure(figsize=(12, 6))plt.subplot(1, 2, 1)# OpenCV是BGR格式,Matplotlib顯示需要轉RGBplt.imshow(cv2.cvtColor(img_cv, cv2.COLOR_BGR2RGB))plt.title('原始圖像')# 在原始圖像上標記用于變換的點for pt in pts1:plt.plot(pt[0], pt[1], 'ro') # 用紅色圓點標記原始點plt.axis('off')plt.subplot(1, 2, 2)plt.imshow(cv2.cvtColor(transformed_img_cv, cv2.COLOR_BGR2RGB))plt.title('透視變換后圖像')# 在變換后圖像上標記目標點位置 (理論上變換后的點應該在這些位置)for pt in pts2:plt.plot(pt[0], pt[1], 'ro') # 用紅色圓點標記目標點plt.axis('off')plt.tight_layout()plt.show()# 保存結果 (OpenCV保存圖像)cv2.imwrite('output_cv_perspective_transformed.jpg', transformed_img_cv)print("OpenCV 透視變換示例已完成并保存為: output_cv_perspective_transformed.jpg")# 真實案例模擬:文檔掃描校正# 假設我們掃描了一份傾斜的文檔,需要將文檔區域“拉直”成一個矩形# 加載一個包含傾斜矩形區域的模擬圖像doc_img = np.full((500, 700, 3), 230, dtype=np.uint8) # 淺灰色背景# 模擬文檔區域 (傾斜的四邊形)doc_pts_src = np.float32([[150, 120], [550, 100], [600, 400], [100, 420]]) # 傾斜的四邊形角點# 模擬在文檔區域繪制一些內容cv2.putText(doc_img, "Scanned Document", (200, 250), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 0, 150), 2) # 藍色文字cv2.line(doc_img, tuple(doc_pts_src[0].astype(int)), tuple(doc_pts_src[1].astype(int)), (0, 100, 0), 2) # 綠色線cv2.line(doc_img, tuple(doc_pts_src[1].astype(int)), tuple(doc_pts_src[2].astype(int)), (0, 100, 0), 2)cv2.line(doc_img, tuple(doc_pts_src[2].astype(int)), tuple(doc_pts_src[3].astype(int)), (0, 100, 0), 2)cv2.line(doc_img, tuple(doc_pts_src[3].astype(int)), tuple(doc_pts_src[0].astype(int)), (0, 100, 0), 2)# 定義目標矩形區域的尺寸和位置# 假設目標是將文檔區域校正為一個 400x600 像素的矩形 (例如,寬度600,高度400)doc_output_width = 600doc_output_height = 400# 目標矩形的四個角點 (通常是規則的矩形)doc_pts_dst = np.float32([[0, 0], [doc_output_width - 1, 0], [doc_output_width - 1, doc_output_height - 1], [0, doc_output_height - 1]])# 計算透視變換矩陣M_doc_correct = cv2.getPerspectiveTransform(doc_pts_src, doc_pts_dst)# 應用透視變換進行校正corrected_doc_img = cv2.warpPerspective(doc_img, M_doc_correct, (doc_output_width, doc_output_height))# 顯示原始傾斜文檔圖像和校正后的圖像plt.figure(figsize=(14, 7))plt.subplot(1, 2, 1)plt.imshow(cv2.cvtColor(doc_img, cv2.COLOR_BGR2RGB))plt.title('原始傾斜文檔圖像')# 標記原始文檔角點for pt in doc_pts_src:plt.plot(pt[0], pt[1], 'ro')plt.axis('off')plt.subplot(1, 2, 2)plt.imshow(cv2.cvtColor(corrected_doc_img, cv2.COLOR_BGR2RGB))plt.title('透視校正后文檔圖像')# 標記目標文檔角點for pt in doc_pts_dst:plt.plot(pt[0], pt[1], 'ro')plt.axis('off')plt.tight_layout()plt.show()# 保存校正后的文檔圖像cv2.imwrite('output_cv_document_corrected.jpg', corrected_doc_img)print("文檔透視校正示例已完成并保存為: output_cv_document_corrected.jpg")

代碼解釋:

  • import numpy as np, import cv2, import matplotlib.pyplot as plt:導入所需的庫。
  • 加載或創建模擬圖像:為了示例,如果找不到‘example.jpg’,則創建一個包含矩形、文字和角點的模擬圖像,以便觀察透視變換如何改變形狀。
  • pts1 = np.float32([[100, 100], [500, 100], [500, 300], [100, 300]]):定義原始圖像中用于變換的四個點。這些點通常是圖像中一個已知平面(如書本封面、文檔頁面、地面標志等)的角點。**注意:**這些點必須是float32類型,這是OpenCV函數要求的。
  • pts2 = np.float32([[50, 50], [550, 50], [550, 350], [50, 350]]):定義目標圖像中與pts1中的點對應的位置。這里將原始矩形變換到一個更大的矩形,但保持矩形形狀,這相當于在應用透視變換的同時進行了縮放和平移。在文檔校正等應用中,pts2通常定義一個規則的矩形區域。**注意:**同樣必須是float32類型。
  • M_perspective = cv2.getPerspectiveTransform(pts1, pts2):使用cv2.getPerspectiveTransform()函數計算從pts1到pts2的透視變換矩陣。它需要四對對應點來唯一確定3x3的變換矩陣(最后一個元素通常固定為1)。
  • transformed_img_cv = cv2.warpPerspective(img_cv, M_perspective, (output_width, output_height)):使用cv2.warpPerspective()函數將計算出的透視變換應用到原始圖像上。
  • 第一個參數是輸入圖像。
  • 第二個參數是計算得到的3x3透視變換矩陣。
  • 第三個參數是輸出圖像的尺寸元組(width, height)。這個尺寸需要足夠大以包含變換后的感興趣區域。
  • Matplotlib顯示:將OpenCV的BGR圖像轉換為RGB格式后,使用Matplotlib顯示原始圖像和變換后的圖像,并在圖像上用紅點標記原始點和目標點,以便直觀比較。
  • cv2.imwrite(...):保存結果圖像。

文檔掃描校正案例:

  • doc_img = np.full(...):創建一個模擬的文檔掃描圖像,其中包含一個傾斜的矩形區域。
  • doc_pts_src = np.float32([[150, 120], [550, 100], [600, 400], [100, 420]]):定義模擬文檔區域(一個四邊形)的四個角點。在實際應用中,這些點可以通過圖像處理方法(如邊緣檢測、輪廓查找、角點檢測)或用戶手動標記獲得。
  • doc_pts_dst = np.float32([[0, 0], [doc_output_width - 1, 0], [doc_output_width - 1, doc_output_height - 1], [0, doc_output_height - 1]]):定義目標圖像中,文檔區域應該變換到的一個規則矩形的角點。這里設置為從(0, 0)到(width-1, height-1)的標準矩形,這將把傾斜的四邊形“拉直”為這個矩形。
  • M_doc_correct = cv2.getPerspectiveTransform(doc_pts_src, doc_pts_dst):計算從傾斜四邊形到目標矩形的透視變換矩陣。
  • corrected_doc_img = cv2.warpPerspective(doc_img, M_doc_correct, (doc_output_width, doc_output_height)):將變換矩陣應用到原始文檔圖像,生成校正后的圖像。

透視變換是圖像處理中用于處理平面投影畸變的重要工具,常用于文檔校正、圖像拼接、相機校準等領域。

6.3圖像插值(Image Interpolation)

在進行圖像縮放、旋轉、透視變換等幾何變換時,輸出圖像的像素位置可能不會精確地對應到輸入圖像的整數像素坐標上。這時就需要通過插值算法,利用輸入圖像中周圍已知像素的信息來估計新像素位置的值。插值算法的選擇會影響變換后圖像的質量和計算速度。

常見的圖像插值算法包括:

  • 最近鄰插值(Nearest Neighbor Interpolation):取離新像素位置最近的輸入像素的值作為新像素的值。
    • 優點:計算速度最快。
    • 缺點:會產生塊狀效應(馬賽克),引入鋸齒,圖像質量最低。
  • 雙線性插值(Bilinear Interpolation):考慮新像素位置周圍的2x2個輸入像素,通過對它們的值進行加權平均(線性插值)來計算新像素的值。權重與距離成反比。
    • 優點:圖像質量比最近鄰插值好,過渡比較平滑。
    • 缺點:會引入一定的模糊。
  • 雙三次插值(Bicubic Interpolation):考慮新像素位置周圍的4x4個輸入像素,通過對它們的值進行更復雜的加權平均(使用三次多項式插值)來計算新像素的值。
    • 優點:圖像質量通常比雙線性插值好,細節保留更多,邊緣更銳利。
    • 缺點:計算復雜度最高,速度相對較慢。
  • Lanczos插值(Lanczos Interpolation):一種高質量的插值方法,使用Lanczos函數作為濾波器。
    • 優點:在縮小圖像時效果很好,能有效抑制鋸齒和振鈴效應,保留較多細節。
    • 缺點:計算復雜度較高。

Pillow和OpenCV的圖像變換函數通常都提供了選擇插值方法的參數。

import numpy as np
from PIL import Image
import cv2
import matplotlib.pyplot as plt# 假設我們有一個示例圖像 (為了更好演示插值,使用一張有細節的圖片)
# 如果沒有,可以創建一個包含文字和線條的模擬圖像
try:# 嘗試加載一個真實圖像img_orig = Image.open('example.jpg').convert('RGB')print("成功加載圖像 example.jpg 用于插值示例。")
except FileNotFoundError:print("示例圖像 example.jpg 未找到,創建模擬圖像用于插值示例。")img_orig = Image.new('RGB', (400, 300), color = 'white')from PIL import ImageDraw, ImageFontdraw = ImageDraw.Draw(img_orig)try:# 嘗試加載一個字體文件font = ImageFont.truetype("arial.ttf", 40) # 使用Arial字體,大小40except IOError:# 如果找不到字體文件,使用默認字體font = ImageFont.load_default()print("未找到 arial.ttf,使用默認字體。")draw.text((20, 20), "Interpolation", fill='black', font=font)draw.line([(10, 150), (390, 150)], fill='red', width=3) # 紅線draw.line([(200, 10), (200, 290)], fill='blue', width=3) # 藍線for i in range(0, 300, 20): # 繪制一些斜線draw.line([(0, i), (i + 100, 0)], fill='green')print("已創建模擬圖像用于插值示例。")# 將圖像縮小到原尺寸的一半,并比較不同插值方法的效果
original_width, original_height = img_orig.size
new_width = original_width // 2
new_height = original_height // 2
new_size = (new_width, new_height)# Pillow 示例
# 最近鄰插值
img_nearest_pil = img_orig.resize(new_size, Image.Resampling.NEAREST)
# 雙線性插值
img_bilinear_pil = img_orig.resize(new_size, Image.Resampling.BILINEAR)
# 雙三次插值
img_bicubic_pil = img_orig.resize(new_size, Image.Resampling.BICUBIC)
# Lanczos 插值
img_lanczos_pil = img_orig.resize(new_size, Image.Resampling.LANCZOS)# OpenCV 示例 (需要將 Pillow Image 轉換為 OpenCV 格式 - NumPy 數組)
img_orig_cv = cv2.cvtColor(np.array(img_orig), cv2.COLOR_RGB2BGR) # Pillow 是RGB,OpenCV是BGR# 最近鄰插值
img_nearest_cv = cv2.resize(img_orig_cv, new_size, interpolation=cv2.INTER_NEAREST)
# 雙線性插值
img_bilinear_cv = cv2.resize(img_orig_cv, new_size, interpolation=cv2.INTER_LINEAR)
# 雙三次插值
img_bicubic_cv = cv2.resize(img_orig_cv, new_size, interpolation=cv2.INTER_CUBIC)
# Lanczos 插值
img_lanczos_cv = cv2.resize(img_orig_cv, new_size, interpolation=cv2.INTER_LANCZOS4) # OpenCV提供多種 Lanczos 核大小# 顯示原始圖像和不同插值方法縮小的結果
plt.figure(figsize=(15, 10))plt.subplot(2, 3, 1)
plt.imshow(img_orig)
plt.title('原始圖像')
plt.axis('off')plt.subplot(2, 3, 2)
plt.imshow(img_nearest_pil)
plt.title('Pillow 最近鄰插值')
plt.axis('off')plt.subplot(2, 3, 3)
plt.imshow(img_bilinear_pil)
plt.title('Pillow 雙線性插值')
plt.axis('off')plt.subplot(2, 3, 4)
plt.imshow(img_bicubic_pil)
plt.title('Pillow 雙三次插值')
plt.axis('off')plt.subplot(2, 3, 5)
plt.imshow(img_lanczos_pil)
plt.title('Pillow Lanczos 插值')
plt.axis('off')# OpenCV 顯示的結果需要轉回RGB
plt.subplot(2, 3, 6)
# 為了比較方便,這里只顯示一種OpenCV的結果,例如雙線性
plt.imshow(cv2.cvtColor(img_bilinear_cv, cv2.COLOR_BGR2RGB))
plt.title('OpenCV 雙線性插值')
plt.axis('off')plt.tight_layout()
plt.show()# 保存結果 (Pillow 和 OpenCV 各保存一個示例)
img_bilinear_pil.save('output_interpolation_bilinear_pil.jpg')
cv2.imwrite('output_interpolation_bilinear_cv.jpg', img_bilinear_cv)
print("不同插值方法的縮放示例已完成并保存結果。")

代碼解釋:

  • 加載或創建模擬圖像:為了清晰展示不同插值方法的區別,創建一個包含文字、直線和斜線的圖像作為示例。這些元素更容易顯示插值引入的鋸齒或模糊。
  • original_width, original_height = img_orig.size:獲取原始圖像的尺寸。
  • new_width = original_width // 2, new_height = original_height // 2, new_size = (new_width, new_height):計算縮小到一半后的目標尺寸。
  • img_orig.resize(new_size, Image.Resampling.NEAREST)等:使用Pillow的resize()方法進行縮放,并通過Image.Resampling常量指定不同的插值方法。Image.Resampling是Pillow 9.1.0+的推薦用法,早期版本使用Image.NEAREST等。
  • img_orig_cv = cv2.cvtColor(np.array(img_orig), cv2.COLOR_RGB2BGR):將Pillow Image對象(RGB模式)轉換為OpenCV兼容的NumPy數組(BGR模式),以便使用OpenCV的函數。
  • cv2.resize(img_orig_cv, new_size, interpolation=cv2.INTER_NEAREST)等:使用OpenCV的cv2.resize()函數進行縮放,并通過interpolation參數指定不同的插值方法,例如cv2.INTER_NEAREST, cv2.INTER_LINEAR, cv2.INTER_CUBIC, cv2.INTER_LANCZOS4。OpenCV提供了更多的插值選項,包括INTER_AREA(在縮小圖像時效果較好)和INTER_LINEAR_EXACT。
  • Matplotlib顯示:顯示原始圖像以及使用不同庫和不同插值方法縮小的結果,以便直觀比較圖像質量的差異。最近鄰插值通常看起來最差(鋸齒明顯),雙線性較平滑,雙三次和Lanczos通常提供更好的視覺效果,尤其是在保留邊緣和細節方面。

在實際應用中,選擇哪種插值方法取決于對速度和圖像質量的要求。對于實時應用或對速度要求極高的場景,最近鄰插值可能是首選。對于大多數一般的圖像處理任務,雙線性插值是一個不錯的折衷。對于需要最高圖像質量的場景(如圖像打印、醫學影像),雙三次或Lanczos插值更合適。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/909285.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/909285.shtml
英文地址,請注明出處:http://en.pswp.cn/news/909285.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

NLP-數據集介紹(并不全,文本類介紹)

目錄 第一章 STS(語義文本相似度) (重點)一、SemEval STS 年度任務(2012-2017)1. SemEval-2012 STS2. SemEval-2013 STS3. SemEval-2014 STS4. SemEval-2015 STS5. SemEval-2016 STS6. SemEval-2017 STS 二…

JS進階 Day01

1.作用域和作用域鏈 let不可訪問 var可訪問,因為沒有塊作用域這一說法 2.JS垃圾回收機制以及算法 下圖如上圖同理 下圖這個三個相互引用的,根部找不到,就進行清除。 3.JS閉包 4.變量和函數提升(了解) 5.函數剩余參數和展開運算符 還有種寫法 …

詳解Python當中的pip常用命令

想象一下,如果建造房屋時,每一塊磚、每一根鋼筋都需要你自己親手燒制和打造,那會是怎樣一番景象?軟件開發也是如此。如果沒有現成的、高質量的、可復用的代碼庫,開發者們就不得不重復“發明輪子”,效率低下…

LangChain面試內容整理-知識點10:文本嵌入模型(Embeddings)使用

文本嵌入(Embeddings)是將文字轉換為向量(高維數值向量)的過程和結果。在LangChain中,Embeddings模塊負責調用各種嵌入模型,將文本轉化為向量表示,以便后續在向量空間執行相似度搜索、聚類等操作。這在實現語義搜索、RAG中非常關鍵,因為向量可以讓計算機“理解”文本語…

To be or Not to be, That‘s a Token——論文閱讀筆記——Beyond the 80/20 Rule和R2R

本周又在同一方向上刷到兩篇文章,可以說,……同學們確實卷啊,要不卷卷開放場域的推理呢? 這兩篇都在講:如何巧妙的利用帶有分支能力的token來提高推理性能或效率的。 第一篇叫 Beyond the 80/20 Rule: High-Entropy Mi…

bisheng系列(三)- 本地部署(后端 1.2版本)

一、導讀 環境:Ubuntu 24.04、open Euler 23.03、Windows 11、WSL 2、Python 3.10 、bisheng 1.2.0 背景:需要bisheng二開商用,故而此處進行本地部署,便于后期調試開發 時間:20250612 說明:bisheng的1.2…

使用 PolarProxy+Proxifier 解密 TLS 流量

一、簡介 在分析惡意樣本或加密流量時,我們常常需要將 TLS 加密通信還原為明文。 本文介紹如何通過 PolarProxy 和Proxifier 解密 TLS 流量并保存為 pcap 文件,在 Wireshark 中進行進一步分析。 二、工具準備 ? PolarProxy(推薦 Windows x64 版本)? Proxifier(強制非瀏…

[技術積累]成熟的前端和后端開發框架

1、后端 1.1、低代碼開發框架 1.1.1、jeecg 官網:JEECG技術論壇 - 基于BPM的低代碼開發平臺 1.1.2、APIJSON github官網地址:https://github.com/APIJSON gitee官網地址:https://gitee.com/Tencent/APIJSON 官網地址:騰訊AP…

產品升級 | 新一代高性能數據采集平臺BRICK2 X11,助力ADAS與自動駕駛開發

隨著ADAS(高級駕駛輔助系統)和自動駕駛(AD)開發中對數據采集與處理的需求日益增長,高性能硬件的重要性愈發凸顯。 為此,康謀正式發布了其BRICK系列的最新產品——BRICK2 X11,作為BRICK2的直接升…

螞蟻集團法人變更:韓歆毅接任,公司治理的正常安排

企查查APP顯示,6月11日,螞蟻科技集團股份有限公司發生工商變更,井賢棟卸任法定代表人,由韓歆毅接任。同時,韓歆毅由董事、總經理變更為執行公司事務的董事、總經理。目前,井賢棟仍擔任該公司董事長職務。 接…

2025虛幻游戲逆向工程解包嘗試

2025虛幻游戲逆向工程解包 前言 在2025游戲模型提取攻略寫了,但是想要找的時候又忘了在哪篇文章中寫的,所以干脆專門寫一下。中間有許多坑。 一坑接一坑。 先說結論:用Umodel(UV Viewer)查看和導出模型。FModel雖然…

Qt學習及使用_第1部分_認識Qt---Qt開發基本流程

前言 學以致用,通過QT框架的學習,一邊實踐,一邊探索編程的方方面面. 參考書:<Qt 6 C開發指南>(以下稱"本書") 標識說明:概念用粗體傾斜.重點內容用(加粗黑體)---重點內容(紅字)---重點內容(加粗紅字), 本書原話內容用深藍色標識,比較重要的內容用加粗傾…

大模型的開發應用(十):對話風格微調項目(上):數據工程與模型選型

數據工程 1 項目介紹2 數據工程2.1 申請 API Key 并測試2.2 文本嵌入模型2.3 生成訓練集2.3.1 制作風格模板2.3.2 調用大模型獲取數據2.3.3 對大模型生成的數據進行質量過濾2.3.4 程序入口 2.4 數據轉換 3 模型選型3.1 候選模型與評估數據集3.2 模型評估 附錄&#xff08;對比不…

Jmeter壓測手冊:腳本配置、服務器環境搭建與運行

序 本文記錄了我在新公司的首次壓測遇到的一些問題以及解決方案。公司服務部署在國外&#xff0c;網絡延遲導致的壓不上去&#xff0c;需要本地調試腳本&#xff0c;然后用國外服務器壓測的過程。同時記錄了過程中遇到的一些問題&#xff0c;特別是Jmeter本身占用CPU資源&#…

立定跳遠--二分枚舉答案+cehck

P10909 [藍橋杯 2024 國 B] 立定跳遠 - 洛谷 #include<bits/stdc.h> using namespace std; #define N 100011 typedef long long ll; typedef pair<int,int> pii; int n,m; int a[N]; int an; bool check(int l) {int pos0;int c1;int wm;for(int i1;i<n;i){if…

藍橋杯20112 不同的總分值

問題描述 在今年藍橋杯的決賽中&#xff0c;一共有 10 道題目&#xff0c;每道題目的分數依次為 5 分&#xff0c;5 分&#xff0c;10 分&#xff0c;10 分&#xff0c;15 分&#xff0c;15 分&#xff0c;20 分&#xff0c;20 分&#xff0c;25 分&#xff0c;25 分。 假設某…

[論文閱讀] 系統架構 | 零售 IT 中的微服務與實時處理:開源工具鏈與部署策略綜述

零售IT中的微服務與實時處理&#xff1a;開源工具鏈與部署策略綜述 論文信息 Microservices and Real-Time Processing in Retail IT: A Review of Open-Source Toolchains and Deployment Strategies Aaditaa Vashisht (Department of Information Science and Engineering, …

【面板數據】A股上市公司注冊地所在地數據集(1991-2023年)

數據簡介&#xff1a;上市公司注冊地所在地數據指企業在工商行政管理部門登記注冊的法定住所信息&#xff0c;涵蓋省、市、區三級行政區劃及詳細地址。該數據是公司法律身份的核心標識&#xff0c;直接影響稅務管轄、訴訟歸屬、政策適用及市場準入等關鍵環節。更是連接企業行為…

21、Create React App的使用

Create React App 是官方支持的創建單頁 React 應用程序的方法。提供了一個現代的構建設置&#xff0c;無需配置。它雖然只是一個包&#xff0c;但不建議全局安裝。如果本地安裝過可先卸載&#xff0c;這樣能保證每次創建項目時使用最新版本的功能。 開始使用 可以使用npx&…

CSS盒子 flex彈性布局

使用flex彈性布局頁面效果圖&#xff08;源代碼在文章末尾&#xff09;&#xff1a; 目錄 flex彈性布局 一、基本容器 二、設置主軸方向 三、設置主軸對齊方式 四、設置交叉軸上的對齊方式 flex彈性布局 一、基本容器 Flexbox 是 CSS3 引入的一種一維布局模型&#xff0…