OpenCV 中的圖像處理 002_圖像的幾何變換

本文主要內容來自於 OpenCV-Python 教程OpenCV 中的圖像處理 部分,這個部分的主要內容如下:

目標

  • 學習對圖像應用不同的幾何變換,比如平移,旋轉和仿射變換等等。
  • 我們將看到這些函數:cv.getPerspectiveTransform

變換

OpenCV 提供了兩個變換函數,cv.warpAffine 和 cv.warpPerspective,通過它們我們可以執行所有種類的變換。cv.warpAffine 接收一個 2x3 的變換矩陣,而 cv.warpPerspective 則接收一個 3x3 的變換矩陣作爲參數。

放縮

放縮只是改變圖像的大小。OpenCV 有一個函數 cv.resize() 用於完成這個操作。圖像的大小可以手動指定,或者可以指定放縮因子。放縮時可以使用不同的插值方法。用於縮小的首選插值方法是 cv.INTER_AREA,用於放大的是  cv.INTER_CUBIC (slow) & cv.INTER_LINEAR。默認情況下,cv.INTER_LINEAR 插值方法用於所有的調整大小。我們可以通過如下的方法改變一幅輸入圖像的大小:

def scaling():
    cv.samples.addSamplesDataSearchPath("/media/data/my_multimedia/opencv-4.x/samples/data")
    img = cv.imread(cv.samples.findFile('messi5.jpg'))
    res = cv.resize(img, None, fx=2, fy=2, interpolation=cv.INTER_CUBIC)
    cv.imshow('frame', res)

    # OR
    height, width = img.shape[:2]
    res = cv.resize(img, (2 * width, 2 * height), interpolation=cv.INTER_CUBIC)
    cv.waitKey()

    cv.destroyAllWindows()

放縮操作對許多以多幅圖像爲參數的操作比較有意義。當某個操作的輸入爲多幅圖像,且對輸入的圖像的大小有一定的限制,而實際的輸入圖像又難以滿足這種限制時,放縮可以幫我們改變部分圖像的大小,以滿足目標操作的輸入要求。比如多幅圖像的相加操作,圖像的水平拼接和豎直拼接等。

平移

平移是移動一個物體的位置。如果知道了 (x,y) 方向的偏移,並使其爲 ($t_x$,$t_y$),則我們可以創建如下的轉換矩陣: $$ \begin{bmatrix} 1&0&t_x \ 0&1&t_x \end{bmatrix} $$

$$ dst(x,y) = src(M_{11} ∗ x+M_{12}∗ y+M_{13}, M_{21}∗ x+M_{22}∗ y+M_{23}) \ = src(1∗x+0∗y+t_x, 0∗x+1∗y+t_y) $$

我們可以把它放入 np.float32 類型的 Numpy 數組並將其傳遞給 cv.warpAffine() 函數。可以參考下面移動 (100,50) 的例子:

def translation():
    cv.samples.addSamplesDataSearchPath("/media/data/my_multimedia/opencv-4.x/samples/data")
    img = cv.imread(cv.samples.findFile('messi5.jpg'))

    rows, cols, _ = img.shape
    M = np.float32([[1, 0, 100], [0, 1, 50]])
    dst = cv.warpAffine(img, M, (cols, rows))
    
    dst = cv.hconcat([img, dst])

    cv.imshow('frame', dst)
    cv.waitKey()

    cv.destroyAllWindows()

cv.warpAffine() 函數的第三個參數是輸出圖像的大小,它的形式應該是 (width, height)。記得 width = 列數,而 height = 行數。

看到的結果應該像下面這樣:

Translation

旋轉

將一幅圖像旋轉角度 θ,可以通過如下的轉換矩陣實現: $$ M = \begin{bmatrix} cosθ&−sinθ \ sinθ&cosθ \end{bmatrix} $$

但是 OpenCV 提供了帶有可調節旋轉中心的放縮旋轉,因此我們可以在任何喜歡的位置旋轉。修改後的變換矩陣由下式給出

$$ \begin{bmatrix} α&β&(1−α)⋅center.x−β⋅center.y \ −β&α&β⋅center.x+(1−α)⋅center.y \end{bmatrix} $$

其中: $$ α=scale⋅cosθ, \ β=scale⋅sinθ $$

爲了獲得這個旋轉矩陣,OpenCV 提供了一個函數,cv.getRotationMatrix2D。檢查如下的例子,它將圖像相對於中心旋轉 120 度且放大到 1.2 倍。

def rotation():
    cv.samples.addSamplesDataSearchPath("/media/data/my_multimedia/opencv-4.x/samples/data")
    img = cv.imread(cv.samples.findFile('messi5.jpg'))

    rows, cols, _ = img.shape

    # cols-1 and rows-1 are the coordinate limits.
    M = cv.getRotationMatrix2D(((cols - 1) / 2.0, (rows - 1) / 2.0), 120, 1.2)
    dst = cv.warpAffine(img, M, (cols, rows))

    dst = cv.hconcat([img, dst])
    cv.imshow('frame', dst)
    cv.waitKey()

    cv.destroyAllWindows()

來看下結果:

Rotation

放射變換

在仿射變換中,原始圖像中所有的平行線在輸出的圖像中依然是平行的。爲了找到變換矩陣,我們需要輸入圖像中的三個點,以及它們在輸出圖像中對應的位置。cv.getAffineTransform 將創建一個 2x3 的矩陣,它將被傳給 cv.warpAffine

檢查下面的例子,我們也將看到選中的點(它們用綠色標記):

def affine_transformation():
    img = np.zeros((512, 512, 3), np.uint8)
    cv.rectangle(img, (0, 0), (512, 512), (255, 255, 255), -1)

    cv.line(img, (0, 50), (512, 50), (0, 0, 0), 3)
    cv.line(img, (0, 150), (512, 150), (0, 0, 0), 3)
    cv.line(img, (0, 300), (512, 300), (0, 0, 0), 3)
    cv.line(img, (0, 450), (512, 450), (0, 0, 0), 3)

    cv.line(img, (100, 0), (100, 512), (0, 0, 0), 3)
    cv.line(img, (256, 0), (256, 512), (0, 0, 0), 3)
    cv.line(img, (412, 0), (412, 512), (0, 0, 0), 3)

    cv.rectangle(img, (60, 170), (430, 400), (0, 0, 0), 3)

    # img, center, radius, color, thickness=None
    cv.circle(img, (60, 50), 8, (0, 255, 0), -1)
    cv.circle(img, (280, 50), 8, (0, 255, 0), -1)
    cv.circle(img, (60, 270), 8, (0, 255, 0), -1)

    rows, cols, ch = img.shape

    pts1 = np.float32([[50, 50], [200, 50], [50, 200]])
    pts2 = np.float32([[10, 100], [200, 50], [100, 250]])

    M = cv.getAffineTransform(pts1, pts2)

    dst = cv.warpAffine(img, M, (cols, rows))

    plt.subplot(121), plt.imshow(img), plt.title('Input')
    plt.subplot(122), plt.imshow(dst), plt.title('Output')
    plt.show()


if __name__ == "__main__":
    affine_transformation()

可以看到下面的結果:

image

透視變換

對於透視變換,我們需要一個 3x3 的變換矩陣。即使在轉換之後,直線也將保持筆直。要找到這個變換矩陣,我們需要輸入圖像上的 4 個點和輸出圖像上的對應點。這 4 個點中,有 3 個不應該共線。然後可以通過函數 cv.getPerspectiveTransform 找到變換矩陣。然後用這個 3x3 變換矩陣應用 cv.warpPerspective

可以看下如下的代碼:

def perspective_transformation():
    cv.samples.addSamplesDataSearchPath("/media/data/my_multimedia/opencv-4.x/samples/data")
    img = cv.imread(cv.samples.findFile('sudoku.png'))
    rows, cols, ch = img.shape

    pts1 = np.float32([[70, 80], [490, 70], [30, 510], [515, 515]])
    pts2 = np.float32([[0, 0], [515, 0], [0, 515], [515, 515]])
    M = cv.getPerspectiveTransform(pts1, pts2)
    dst = cv.warpPerspective(img, M, (515, 515))

    cv.line(img, (0, int(rows / 2)), (cols, int(rows / 2)), (0, 255, 0), 3)
    cv.line(img, (int(cols / 2), 0), (int(cols / 2), rows), (0, 255, 0), 3)

    cv.circle(img, (70, 80), 8, (0, 255, 0), -1)
    cv.circle(img, (490, 70), 8, (0, 255, 0), -1)
    cv.circle(img, (30, 510), 8, (0, 255, 0), -1)
    cv.circle(img, (515, 515), 8, (0, 255, 0), -1)

    plt.subplot(121), plt.imshow(img), plt.title('Input')

    cv.line(dst, (0, int(rows / 2)), (cols, int(rows / 2)), (0, 255, 0), 3)
    cv.line(dst, (int(cols / 2), 0), (int(cols / 2), rows), (0, 255, 0), 3)
    plt.subplot(122), plt.imshow(dst), plt.title('Output')
    plt.show()


if __name__ == "__main__":
    perspective_transformation()

最終的結果如下圖:

image

其它資源

  1. "Computer Vision: Algorithms and Applications", Richard Szeliski

參考文檔

Geometric Transformations of Images

Markdown 中的常用 LaTex 數學公式

Markdown數學公式語法

Cmd Markdown 公式指導手冊

Done.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章