幾何變換

      在〈幾何變換〉中尚無留言

OpenCV有相當多的函數可以進行幾何變換,但真的要用起來,實在不方便。故本篇將其常用的功能集合成一個類別,改寫成一個好用的sdk – class MahalCv

縮放

cv2.resize(src, (新寬, 新高), 內插值方式)

內插值有如下三種
cv2.INTER_AREA
cv2.INTER_CUBIC(三次插值)
cv2.INTER_LINEAR(線性插值)
cv2.INTER_NEAREST(最近鄰插值)
cv2.INTER_LANCZOS4(lANCZOS插值)
以上的插值法都是為了解決放大縮小時失真的問題,有著非常複雜的演算法。但常用的就二種,CUBIC(速度較慢)及LINEAR(速度較快)

from MahalSdk import MahalSdk as cv
import pylab as plt
img = cv.read("1.jpg")
img = cv.resize(img, width=400)
plt.imshow(img[:,:,::-1])
print(img.shape)
plt.show()

sdk代碼如下

#sdk如下
import cv2
import numpy as np
class MahalSdk():
    @staticmethod
    def read(file):
        return cv2.imdecode(
            np.fromfile(file, dtype=np.uint8),
            cv2.IMREAD_COLOR
        )
    @staticmethod
    def resize(img, width=800):
        h, w, _ = img.shape
        r = h / w
        if r<1:#橫圖
            height = int(width * r)
        else:
            height = int(width / r)
        return cv2.resize(img, (width, height), interpolation=cv2.INTER_LINEAR)

影像裁切

想要取得影像中的某個區塊,可直接指定列及行。請注意,numpy的第一維為列,第二維為行,第三維為bgr值

import pylab as plt
from MahalSdk import MahalSdk as cv
img=cv.read("1.jpg")
p1=cv.Point(1600,0)
p2=cv.Point(3000,1500)
img=cv.crop(img, p1, p2)
plt.imshow(img[:,:,::-1])
plt.show()

sdk代碼如下

class MahalSdk():
    class Point():
        def __init__(self, x, y):
            self.x=x
            self.y=y
    @staticmethod
    def crop(img, p1, p2):
        return img[p1.y:p2.y, p1.x:p2.x]

平移

cv2.warpAffine(src, m, (新尺寸))

m 為平移參數。[1, 0. 500] 為水平平移,[0,1,500]為垂直平移
新尺寸為tuple型態, 是整個影像裁切後的尺寸,不是縮放後的尺寸,所以一般都是原始長寬。最後才使用resize縮放。

from MahalSdk import MahalSdk as cv
import pylab as plt
img = cv.read("1.jpg")
img = cv.shift(img, 100, 500)
plt.imshow(img[:,:,::-1])
print(img.shape)
plt.show()

sdk代碼如下

@staticmethod
def shift(img, x, y):
    h, w, _ = img.shape
    m = np.float32(
        [[1,0, x],
         [0,1,y]]
    )
    return cv2.warpAffine(img, m, (w, h ))

旋轉

cv2.getRotationMatrix2D((中心x, 中心y), 角度, 縮放)

import pylab as plt
from MahalSdk import MahalSdk as cv
img=cv.read("1.jpg")
img=cv.resize(img, width=800)
ax=plt.subplot(1,1,1)
center=cv.Point(400,400)
for i in range(0, 730,10):
    ax.clear()
    ax.imshow(cv.rotate(img, center=center,angle=i)[:,:,::-1])
    plt.pause(0.1)
plt.show()

sdk代碼如下

class MahalSdk():
    class Point():
        def __init__(self, x, y):
            self.x=x
            self.y=y
    @staticmethod
    def rotate(img, angle=0, center=None, scale=1):
        h, w, _ = img.shape
        if center is None:
            x = (w - 1) / 2
            y = (h - 1) / 2
        else:
            x = center.x
            y = center.y
        m = cv2.getRotationMatrix2D((x, y), angle, scale)
        return cv2.warpAffine(img, m, (w, h))

鏡射

from MahalSdk import MahalSdk as cv
import pylab as plt
img = cv.read("1.jpg")
img1 = cv.flip(img, direction=cv.Both)
plt.subplot(1,2,1).imshow(img[:,:,::-1])
plt.subplot(1,2,2).imshow(img1[:,:,::-1])
plt.show()

sdk代碼如下

class MahalSdk():
    Horizontal=1#類別變數
    Vertical=0
    Both=-1
    @staticmethod
    def flip(img, direction=Horizontal):
        return cv2.flip(img, direction)

透視圖(Prespective)

透視圖可以將非水平或非垂直的線拉直,如下圖所示

底下程式中,先選取原圖的四個點產生pts1陣列,其順序如上圖所示,依序為左上,右上,左下,右下。其座標為(x, y)。然後再選取要貼上的四個點pts2, 然後用getPerspectiveTransform()產生變換矩陣M, 再使用warpPerspective產生結果。

from MahalSdk import MahalSdk as cv
import numpy as np
import pylab as plt
img=cv.read('1.jpg')
pts1 = np.float32([[2000,0],[2800,0],[1500, 1500],[3500,1500]])
pts2 = np.float32([[0,0],[300,0],[0,300],[300,300]])
img2 = cv.perspective(img,pts1, pts2, 300,300)
plt.subplot(1,2,1).imshow(img[:,:,::-1])
plt.subplot(1,2,2).imshow(img2[:,:,::-1])
plt.show()

sdk 的代碼如下

@staticmethod
def perspective(img, pts1, pts2, width, height):
    m = cv2.getPerspectiveTransform(pts1, pts2)
    return cv2.warpPerspective(img, m, (width, height))

多張影像相加

若有多張影像要合成一張,則要考慮是前面蓋掉後面,或是每張都有其權重。多張影像相疊在一起可以使用cv2.addWeighted()函數。另需注意,每張影像的長寬皆必需一樣,才可以相加。底下是採用權重相加的效果

import cv2
import pylab as plt
from MahalSdk import MahalSdk as cv
img1 = cv.read("1.jpg")[0:3000, 0:4000]
img2 = cv.read("2.jpg")[0:3000, 0:4000]
merge = cv2.addWeighted(img1, 0.8, img2, 0.2, 0)
plt.imshow(merge[:,:,::-1])
plt.show()

上述addWeighted()的最後一個參數,為亮度,0為不變,愈大就愈接近白色(255)

np及cv2的相加,有所不一樣,請看如下代碼

x = np.uint8([250])
y = np.uint8([10])
print(cv2.add(x,y)) #會得到最高上限值 255
print(x+y) # 會得到除以256後的餘數
結果 :
[[255]]
[4]

從網路下載圖片

從網路上下載圖片, 需使用 urllib套件。此套件系統本身就有了,不需另行安裝。

經由 urllib 讀入的 byte,使用 np.asarray() 變成陣列,就是檔案的原始格式,然後再使用 cv2.imdecode() 解壓縮即可得到完整的 numpy 格式圖片。

from io import BytesIO
from urllib.request import urlopen
import pylab as plt
import cv2
import numpy as np
import requests
from PIL import Image

url="https://mahalbot.ddns.net/pictures/primitive/2019/20191102_%E5%85%AB%E5%8D%A6%E5%B1%B1/DSCN0166.jpg"
#使用 urlopen
r = urlopen(url)
file = np.asarray(bytearray(r.read()), dtype=np.uint8)
img = cv2.imdecode(file, cv2.IMREAD_COLOR)
plt.subplot(1,2,1).imshow(img[:,:,::-1])

#使用 requests
r = requests.get(url)
#Pillow 格式
pil = Image.open(BytesIO(r.content))
plt.subplot(1,2,2).imshow(pil)
plt.show()

cv2 to Pillow

使用 Image.fromarray() 可以將 cv2 格式轉成 Pillow 格式

from PIL import Image
from MahalSdk import MahalSdk as cv
import pylab as plt
img=cv.read("1.jpg")
pil = Image.fromarray(img[:,:,::-1])
plt.imshow(pil)
plt.show()

Pillow to cv2

使用 np.asarray() 可以將 Pillow 格式轉成 cv2 格式

from PIL import Image
import numpy as np
import pylab as plt
pil = Image.open("1.jpg")
img = np.asarray(pil)
plt.imshow(img)
plt.show()

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *