1. 程式人生 > 實用技巧 >python影象仿射變換實戰-提取手寫數字圖片樣本

python影象仿射變換實戰-提取手寫數字圖片樣本

在進行實戰專案之前,我們先來學習一個知識點:連通域的外界矩形

尋找外接矩形有兩種策略:

  • 一種是尋找輪廓邊緣的部分, 找到最外面的那個外接矩形, 為了區分, 我們稱之為正外接矩形 boundingRect, 如下圖綠色矩形部分.
  • 另外一種策略是矩形可以旋轉, 找到面積最小的矩形, 剛剛好可以把輪廓套在裡面,我們稱之為*最小外接矩形 * minAreaRect, 如下圖藍色矩形部分.

正外接矩形 boudningRect

函式比較簡單, 傳入唯一的引數是輪廓點集(單個) Points.

rect = cv2.boundingRect(cnt)
(x, y, w, h) = rect
複製程式碼

返回值 rect , 資料結構是tuple, 分別為矩形左上角座標(x, y), 與矩形的寬度w 高度h

我們依次列印矩形區域的資訊.

for cidx,cnt in enumerate(contours):
    (x, y, w, h) = cv2.boundingRect(cnt)
    print('RECT: x={}, y={}, w={}, h={}'.format(x, y, w, h))
複製程式碼

輸出結果:

RECT: x=92, y=378, w=94, h=64
RECT: x=381, y=328, w=69, h=102
RECT: x=234, y=265, w=86, h=70
RECT: x=53, y=260, w=61, h=95
RECT: x=420, y=184, w=49, h=66
RECT: x=65, y=124, w=48, h=83
RECT: x=281, y=71, w=70, h=108
複製程式碼

繪製在畫布上比較直觀,具體程式碼如下:

import numpy as np
import cv2

# 讀入黑背景下的彩色手寫數字
img = cv2.imread("color_number_handwriting.png")
# 轉換為gray灰度圖
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 尋找輪廓
contours, hier = cv2.findContours(gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# 宣告畫布 拷貝自img
canvas = np.copy(img)

for cidx,cnt in enumerate(contours):
    (x, y, w, h) = cv2.boundingRect(cnt)
    print('RECT: x={}, y={}, w={}, h={}'.format(x, y, w, h))
    # 原圖繪製圓形
    cv2.rectangle(canvas, pt1=(x, y), pt2=(x+w, y+h),color=(255, 255, 255), thickness=3)
    # 擷取ROI影象
    cv2.imwrite("number_boudingrect_cidx_{}.png".format(cidx), img[y:y+h, x:x+w])

cv2.imwrite("number_boundingrect_canvas.png", canvas)
複製程式碼

原始影象:


繪製結果:

擷取ROI圖片的操作比較簡單img[y:y+h, x:x+w]

# 擷取ROI影象
cv2.imwrite("number_boudingrect_cidx_{}.png".format(cidx), img[y:y+h, x:x+w])
複製程式碼

這樣我們就擷取到了獨立的單個數字的圖片:

最小外接矩形 minAreaRect

minAreaRect 函式用於獲取最小面積的矩形

minAreaRect = cv2.minAreaRect(cnt)
複製程式碼

我們列印一下minAreaRect 檢視其返回的資料結構:

((133.10528564453125, 404.7727966308594), (100.10702514648438, 57.51853942871094), -49.184913635253906)
複製程式碼

資料結構解析

((cx, cy), (width, height), theta)
複製程式碼
  • cx 矩形中心點x座標 center x
  • cy 矩形中心點y座標 center y
  • width 矩形寬度
  • height 矩形高度
  • theta 旋轉角度,角度(不是弧度)

注意: 上述值均為小數, 不可以直接用於圖片索引,或者矩形繪製.

詳情見圖

圖片來源 python opencv minAreaRect 生成最小外接矩形

注意:旋轉角度θ是水平軸(x軸)逆時針旋轉,與碰到的矩形的第一條邊的夾角。並且這個邊的邊長是width,另一條邊邊長是height。也就是說,在這裡,width與height不是按照長短來定義的。

在opencv中,座標系原點在左上角,相對於x軸,逆時針旋轉角度為負,順時針旋轉角度為正。

為了直觀起見, 我們可以直接這樣賦值

((cx, cy), (width, height), theta) = cv2.minAreaRect(cnt)
1
複製程式碼

完整一些的演示樣例:

for cidx,cnt in enumerate(contours):
    ((cx, cy), (width, height), theta) = cv2.minAreaRect(cnt)
    print('center: cx=%.3f, cy=%.3f, width=%.3f, height=%.3f, roate_angle=%.3f'%(cx, cy, width, height, theta))
複製程式碼

輸出結果:

center: cx=133.105, cy=404.773, width=100.107, height=57.519, roate_angle=-49.185 
center: cx=415.190, cy=378.853, width=66.508, height=100.537, roate_angle=-1.710  
center: cx=278.323, cy=296.089, width=71.608, height=78.065, roate_angle=-78.440  
center: cx=83.000, cy=307.000, width=60.000, height=94.000, roate_angle=0.000     
center: cx=448.346, cy=213.731, width=47.068, height=64.718, roate_angle=-11.310  
center: cx=89.642, cy=164.695, width=17.204, height=88.566, roate_angle=-25.427   
center: cx=330.578, cy=123.387, width=92.325, height=72.089, roate_angle=-66.666 
複製程式碼

完整程式碼展示:

import numpy as np
import cv2

# 讀入黑背景下的彩色手寫數字
img = cv2.imread("color_number_handwriting.png")
# 轉換為gray灰度圖
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 尋找輪廓
contours, hier = cv2.findContours(gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# 宣告畫布 拷貝自img
canvas = np.copy(img)

for cidx,cnt in enumerate(contours):
    minAreaRect = cv2.minAreaRect(cnt)
    # 轉換為整數點集座標
    rectCnt = np.int64(cv2.boxPoints(minAreaRect))
    # 繪製多邊形
    cv2.polylines(img=canvas, pts=[rectCnt], isClosed=True, color=(0,0,255), thickness=3)

cv2.imwrite("number_minarearect_canvas.png", canvas)
複製程式碼

提取最小外接矩形區域

我們可以根據minAreaRect 函式返回的資料結構, 以矩形中心(cx, cy)作為對原來影象旋轉的中心點,旋轉角度設定為theta

# 宣告旋轉矩陣
rotateMatrix = cv2.getRotationMatrix2D((cx, cy), theta, 1.0)
# 獲取旋轉後的影象
rotatedImg = cv2.warpAffine(img, rotateMatrix, (img.shape[1], img.shape[0]))
複製程式碼

具體程式碼如下:

'''
    利用minAreaRect繪製最小面積矩形並繪製
'''
import numpy as np
import cv2

# 讀入黑背景下的彩色手寫數字
img = cv2.imread("color_number_handwriting.png")
# 轉換為gray灰度圖
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 尋找輪廓
contours, hier = cv2.findContours(gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

for cidx,cnt in enumerate(contours):
    minAreaRect = cv2.minAreaRect(cnt)
    # 轉換為整數點集座標
    # rectCnt = np.int64(cv2.boxPoints(minAreaRect))
    ((cx, cy), (w, h), theta) = minAreaRect

    cx = int(cx)
    cy = int(cy)
    w = int(w)
    h = int(h)
    # 獲取旋轉矩陣
    rotateMatrix = cv2.getRotationMatrix2D((cx, cy), theta, 1.0)
    rotatedImg = cv2.warpAffine(img, rotateMatrix, (img.shape[1], img.shape[0]))
    pt1 = (int(cx - w/2), int(cy - h/2))
    pt2 = (int(cx + w/2), int(cy + h/2))
    # 原圖繪製矩形區域
    cv2.rectangle(rotatedImg, pt1=pt1, pt2=pt2,color=(255, 255, 255), thickness=3)
    # 繪製中心點
    cv2.circle(rotatedImg, (cx, cy), 5, color=(255, 0, 0), thickness=-1)
    cv2.imwrite("minarearect_cidx_{}.png".format(cidx), rotatedImg)
複製程式碼

數字樣本影象轉換為統一尺寸

我們截取了包含數字的外接矩形, 他們形狀各異。(可能需要手動旋轉)

如果是製作神經網路所需要的樣本圖片的話, 我們就需要將其放縮到統一大小。

接下來我們將圖片統一變換到 15*25 並轉換為二值化影象。


具體程式碼如下:

import numpy as np
import cv2
from glob import glob

img_paths = glob('./數字影象處理/*.png')

# 新的維度為10×20
new_dimension = (15, 25)

for img_path in img_paths:
    # 讀入灰度圖
    img = cv2.imread(img_path,cv2.IMREAD_GRAYSCALE)
    img_name = img_path.split('/')[-1]
    # 縮放
    resized = cv2.resize(img, new_dimension)
    # 二值化圖片
    ret,thresh = cv2.threshold(resized,10,255,0)

    cv2.imwrite('./number/'+img_name,thresh)