OpenCV 截取指定區域、黑化背景、透視轉換

 

一、cv2.getPerspectiveTransform

cv2.getPerspectiveTransform(src, dst) → retval

src:源圖像中待測矩形的四點座標

sdt:目標圖像中矩形的四點座標

 

一、cv2.warpAffine

放射變換函數,可實現旋轉,平移,縮放;變換後的平行線依舊平

cv2.warpAffine(src, M, dsize, dst=None, flags=None, borderMode=None, borderValue=None) --> dst

src:輸入圖像     dst:輸出圖像

M:2×3的變換矩陣

dsize:變換後輸出圖像尺寸

flag:插值方法

borderMode:邊界像素外擴方式

borderValue:邊界像素插值,默認用0填充

變換矩陣M可通過cv2.getAffineTransfrom(points1, points2)函數獲得

變換矩陣的獲取需要至少三組變換前後對應的點座標,設取原圖上的三個點組成矩陣points1,變換後的三個點組成的矩陣points2

points1 = np.float32([ [30,30], [100,40], [40,100] ])
points2 = np.float32([ [60,60], [40,100], [80,20] ])
 
M  =  cv2.getAffineTransform(points1, points2)
   =  array([[-0.33333333,  0.33333333, 60.   ],
             [ 0.66666667, -0.66666667, 60.   ]])
 
Affine_img = cv2.warpAffine(img, M, (img.shape[1], img.shape[0])) 

 

二、cv2.warpPerspective

透視變換函數,可保持直線不變形,但是平行線可能不再平行

cv2.warpPerspective(src, M, dsize, dst=None, flags=None, borderMode=None, borderValue=None) --> dst

其相關參數和cv2.warpAffine函數的類似,不再做介紹

它的變換矩陣可以通過cv2.getPerspectiveTransform()函數獲得,其原理和cv2.getAffineTransfrom()相同,只是投射變換至少需要四組變換前後對應的點座標,設取原圖上的四個點組成矩陣points1,變換後的四個點組成的矩陣points2

如:

points1 = np.float32([ [30,30], [10,40], [40,10], [5,15] ])
points2 = np.float32([ [0,0], [400,0], [0,400], [400,400] ])
 
M = cv2.getPerspectiveTransform(points1, points2)
  = array([[-9.08777969e+00, -4.54388985e+00,  4.08950086e+02],
           [-5.37005164e+00, -1.07401033e+01,  4.83304647e+02],
           [-1.15318417e-02, -1.35972461e-02,  1.00000000e+00]])
 
Perspective_img = cv2.warpPerspective(img, M, (img.shape[1], img.shape[0])) 

 

三、快速應用工具

# -*- coding: utf-8 -*-
"""
@author: yuki_ho
"""

import cv2
import numpy as np

# -----------------------鼠標操作相關------------------------------------------
lsPointsChoose = []
tpPointsChoose = []
pointsCount = 0
count = 0
pointsMax = 6


def on_mouse(event, x, y, flags, param):
    global img, point1, point2, count, pointsMax
    global lsPointsChoose, tpPointsChoose  # 存入選擇的點
    global pointsCount  # 對鼠標按下的點計數
    global img2, ROI_bymouse_flag
    img2 = img.copy()  # 此行代碼保證每次都重新再原圖畫  避免畫多了
    # -----------------------------------------------------------
    #    count=count+1
    #    print("callback_count",count)
    # --------------------------------------------------------------

    if event == cv2.EVENT_LBUTTONDOWN:  # 左鍵點擊
        pointsCount = pointsCount + 1
        # 爲了保存繪製的區域,畫的點稍晚清零
        # if (pointsCount == pointsMax + 1):
        #     pointsCount = 0
        #     tpPointsChoose = []
        print('pointsCount:', pointsCount)
        point1 = (x, y)
        print(x, y)
        # 畫出點擊的點
        cv2.circle(img2, point1, 10, (0, 255, 0), 2)

        # 將選取的點保存到list列表裏
        lsPointsChoose.append([x, y])  # 用於轉化爲darry 提取多邊形ROI
        tpPointsChoose.append((x, y))  # 用於畫點
        # ----------------------------------------------------------------------
        # 將鼠標選的點用直線連起來
        print(len(tpPointsChoose))
        for i in range(len(tpPointsChoose) - 1):
            print('i', i)
            cv2.line(img2, tpPointsChoose[i], tpPointsChoose[i + 1], (0, 0, 255), 2)
        # ----------------------------------------------------------------------
        # ----------點擊到pointMax時可以提取去繪圖----------------

        cv2.imshow('src', img2)

    # -------------------------右鍵按下清除軌跡-----------------------------
    if event == cv2.EVENT_RBUTTONDOWN:  # 右鍵點擊
        print("right-mouse")
        pointsCount = 0
        tpPointsChoose = []
        lsPointsChoose = []
        print(len(tpPointsChoose))
        for i in range(len(tpPointsChoose) - 1):
            print('i', i)
            cv2.line(img2, tpPointsChoose[i], tpPointsChoose[i + 1], (0, 0, 255), 2)
        cv2.imshow('src', img2)

    # -------------------------雙擊 結束選取-----------------------------
    if event == cv2.EVENT_LBUTTONDBLCLK:
        # -----------繪製感興趣區域-----------
        ROI_byMouse()
        ROI_bymouse_flag = 1
        lsPointsChoose = []


def ROI_byMouse():
    global src, ROI, ROI_flag, mask2
    mask = np.zeros(img.shape, np.uint8) # (450, 800, 3)
    pts = np.array([lsPointsChoose], np.int32)  # pts是多邊形的頂點列表(頂點集)
    pts = pts.reshape((-1, 1, 2))

    print(pts) #所勾選的座標

    # 這裏 reshape 的第一個參數爲-1, 表明這一維的長度是根據後面的維度的計算出來的。
    # OpenCV中需要先將多邊形的頂點座標變成頂點數×1×2維的矩陣,再來繪製

    # --------------畫多邊形---------------------
    mask = cv2.polylines(mask, [pts], True, (255, 255, 255))
    ##-------------填充多邊形---------------------
    mask2 = cv2.fillPoly(mask, [pts], (255, 255, 255))


    cv2.imshow('mask', mask2)
    cv2.imwrite('mask.jpg', mask2)

    contours, hierarchy = cv2.findContours(cv2.cvtColor(mask2, cv2.COLOR_BGR2GRAY), cv2.RETR_TREE,
                                                  cv2.CHAIN_APPROX_NONE)
    ROIarea = cv2.contourArea(contours[0])
    print("ROIarea:", ROIarea)
    ROI = cv2.bitwise_and(mask2, img)
    # cv2.imwrite('ROI.jpg', ROI)
    cv2.imshow('ROI', ROI)

    #暫時 下面 只針對 3 或 4 個點
    crop_change = transform4pts(ROI, pts, ROI.shape) if len(pts) > 3 else transform3pts(ROI, pts, ROI.shape)
    cv2.imshow('ROI_trans', crop_change)


def ROI_test(img_path):

    img = cv2.imread(img_path)

    # 自己定義座標點
    pts = np.array(
            [[[359,96]],
             [[739,97]],
             [[1164,661]],
             [[83,663]]
         ])

    crop_img = cropFill(img, pts)
    cv2.imshow('crop_img', crop_img)

    crop_change = transform4pts(crop_img, pts, crop_img.shape) if len(pts) > 3 else transform3pts(crop_img, pts, crop_img.shape)
    cv2.imshow('trans_img', crop_change)

    cv2.waitKey(0)
    cv2.destroyAllWindows()


# 截取並填充
def cropFill(src_img, pts):

    mask = np.zeros(src_img.shape, np.uint8) # 空的畫板

    # --------------畫多邊形---------------------
    mask = cv2.polylines(mask, [pts], True, (255, 255, 255))
    ##-------------填充多邊形---------------------
    mask2 = cv2.fillPoly(mask, [pts], (255, 255, 255))

    contours, hierarchy = cv2.findContours(cv2.cvtColor(mask2, cv2.COLOR_BGR2GRAY), cv2.RETR_TREE,
                                                  cv2.CHAIN_APPROX_NONE)

    ROI = cv2.bitwise_and(mask2, src_img)

    return ROI

# 3點 透視變換
def transform3pts(src_img, pts, out_size):
    # print(out_size) #(730, 1176, 3) # H W
    pts = pts.astype(np.float32)

    # 自定義吧
    dpts = np.array([
        [0, 0],
        [out_size[1], 0],
        [out_size[1], out_size[0]],
    ], dtype=np.float32)


    M = cv2.getAffineTransform(pts, dpts)
    img_result = cv2.warpAffine(src_img, M , (out_size[1],out_size[0])) #透視變換

    return img_result

# 4點 透視變換
def transform4pts(src_img, pts, out_size):
    # print(out_size) #(730, 1176, 3) # H W
    pts = pts.astype(np.float32)

    # 自定義吧
    dpts = np.array([
        [0, 0],
        [out_size[1], 0],
        [out_size[1], out_size[0]],
        [0, out_size[0]],
    ], dtype=np.float32)

    M = cv2.getPerspectiveTransform(pts, dpts) #變換矩陣  ABC變換到A'B'C'
    img_result = cv2.warpPerspective(src_img, M , (out_size[1],out_size[0])) #透視變換

    return img_result

if __name__ == '__main__':

    img = cv2.imread('6.jpg') #1176,730
    # ---------------------------------------------------------
    # --圖像預處理,設置其大小
    # height, width = img.shape[:2]
    # size = (int(width * 0.3), int(height * 0.3))
    # img = cv2.resize(img, size, interpolation=cv2.INTER_AREA)
    # ------------------------------------------------------------
    ROI = img.copy()
    cv2.namedWindow('src')
    cv2.setMouseCallback('src', on_mouse)
    cv2.imshow('src', img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

    # ROI_test('6.jpg')

 

四、效果

 

五、調整工具

import argparse
import os
import tkinter as tk
from tkinter import filedialog

import cv2
import numpy as np

parser = argparse.ArgumentParser()
# parser.add_argument('source', type=str)
parser.add_argument('--height', type=int, default=1080)
parser.add_argument('--width', type=int, default=1920)
args = parser.parse_args()
print(args)


root = tk.Tk()
root.title('不規則四邊形裁剪')

pos = {
    'TL': (tk.IntVar(root, value=0), tk.IntVar(root, value=0)),
    'TR': (tk.IntVar(root, value=0), tk.IntVar(root, value=1600)),
    'BL': (tk.IntVar(root, value=900), tk.IntVar(root, value=0)),
    'BR': (tk.IntVar(root, value=900), tk.IntVar(root, value=1600)),
}
output = (tk.IntVar(root, value=args.height), tk.IntVar(root, value=args.width))

img = None
fileName = None
fileExtension = None
inputRatio = None


def mark_line(img):
    if img is None:
        return
    img_temp = img.copy()
    cv2.line(
        img_temp,
        (pos['TL'][1].get(), pos['TL'][0].get()),
        (pos['TR'][1].get(), pos['TR'][0].get()),
        (0, 0, 255), 2, cv2.LINE_AA
    )
    cv2.line(
        img_temp,
        (pos['BL'][1].get(), pos['BL'][0].get()),
        (pos['BR'][1].get(), pos['BR'][0].get()),
        (0, 0, 255), 2, cv2.LINE_AA
    )
    cv2.line(
        img_temp,
        (pos['TL'][1].get(), pos['TL'][0].get()),
        (pos['BL'][1].get(), pos['BL'][0].get()),
        (0, 0, 255), 2, cv2.LINE_AA
    )
    cv2.line(
        img_temp,
        (pos['TR'][1].get(), pos['TR'][0].get()),
        (pos['BR'][1].get(), pos['BR'][0].get()),
        (0, 0, 255), 2, cv2.LINE_AA
    )
    return img_temp


def crop_image():
    sourcePoints = np.array([
        (pos['TL'][1].get(), pos['TL'][0].get()),
        (pos['TR'][1].get(), pos['TR'][0].get()),
        (pos['BR'][1].get(), pos['BR'][0].get()),
        (pos['BL'][1].get(), pos['BL'][0].get()),
    ], dtype=np.float32)
    print(sourcePoints)
    dstPoints = np.array([
        [0, 0],
        [output[1].get(), 0],
        [output[1].get(), output[0].get()],
        [0, output[0].get()],
    ], dtype=np.float32)
    print('output',output)
    print(dstPoints)

    M = cv2.getPerspectiveTransform(sourcePoints, dstPoints)
    print('M:',M)
    print('zuobiao:',(output[1].get(), output[0].get()) )
    img_result = cv2.warpPerspective(img, M, (output[1].get(), output[0].get()))
    return img_result


def show_images():
    if img is None:
        return

    resize_img_to_show('input', mark_line(img))
    resize_img_to_show('output', crop_image(), small=True)
    cv2.setMouseCallback('input', mouseClicked)


def resize_img_to_show(name, img_temp, small=False):
    if img_temp is None:
        return
    height = img_temp.shape[0]
    width = img_temp.shape[1]
    if small and height <= 700:
        ratio = 1
    else:
        ratio = 700 / height
    height = int(height * ratio)
    width = int(width * ratio)
    cv2.imshow(name, cv2.resize(img_temp, (width, height)))

    if name == 'input':
        global inputRatio
        inputRatio = ratio


def openFile():
    global img, fileName, fileExtension
    file_path = filedialog.askopenfilename()
    print(file_path)
    if file_path is not None:
        fileName = os.path.basename(file_path)
        fileExtension = os.path.splitext(file_path)[1]

        img = cv2.imread(file_path, cv2.IMREAD_UNCHANGED)
        pos['TL'][0].set(0)
        pos['TL'][1].set(0)
        pos['TR'][0].set(0)
        pos['TR'][1].set(img.shape[1] - 1)
        pos['BL'][0].set(img.shape[0] - 1)
        pos['BL'][1].set(0)
        pos['BR'][0].set(img.shape[0] - 1)
        pos['BR'][1].set(img.shape[1] - 1)
        show_images()


def saveFile():
    global img
    if img is None:
        return
    out_path = filedialog.asksaveasfilename(
        filetypes=[
            ('PNG File', '*.png'),
            ('JPEG File', '*.jpg'),
            ('All files', '*'),
        ],
        initialfile=fileName,
        defaultextension=fileExtension,
    )
    print(out_path)
    if out_path:
        img_result = crop_image()
        cv2.imwrite(out_path, img_result)


def changePos(corner, xy, diff):
    def wrapper():
        pos[corner][xy].set(pos[corner][xy].get() + diff)
        show_images()
    return wrapper


def changeOutput(xy, diff):
    def wrapper():
        output[xy].set(output[xy].get() + diff)
        show_images()
    return wrapper


def mouseClicked(event, p1, p0, flags, param):
    if event == cv2.EVENT_LBUTTONDOWN or (event == cv2.EVENT_MOUSEMOVE and (flags & cv2.EVENT_FLAG_LBUTTON)):
        p0 = int(p0 / inputRatio)
        p1 = int(p1 / inputRatio)

        print(p0, p1)
        minDis = 1e9
        minCorner = None
        for corner in pos:
            tempDis = (pos[corner][0].get() - p0)**2 + (pos[corner][1].get() - p1)**2
            if tempDis < minDis:
                minDis = tempDis
                minCorner = corner

        pos[minCorner][0].set(p0)
        pos[minCorner][1].set(p1)
        show_images()


ROW = 0
tk.Button(root, text='Open File', command=openFile).grid(row=ROW, column=0, columnspan=2, sticky='W')

for xy in [0, 1]:
    ROW += 1
    COL = -1
    for corner in ['TL', 'TR']:
        COL += 1
        tk.Label(root, text='{}{}'.format(corner, xy)).grid(row=ROW, column=COL)
        COL += 1
        tk.Entry(root, textvariable=pos[corner][xy]).grid(row=ROW, column=COL)
        for diff in [1, 10, 100]:
            COL += 1
            tk.Button(root, text=' {:+d} '.format(-diff), command=changePos(corner, xy, -diff)).grid(row=ROW, column=COL)
            COL += 1
            tk.Button(root, text=' {:+d} '.format(+diff), command=changePos(corner, xy, +diff)).grid(row=ROW, column=COL)

ROW += 1
tk.Label(root, text='').grid(row=ROW, column=0)

for xy in [0, 1]:
    ROW += 1
    COL = -1
    for corner in ['BL', 'BR']:
        COL += 1
        tk.Label(root, text='{}{}'.format(corner, xy)).grid(row=ROW, column=COL)
        COL += 1
        tk.Entry(root, textvariable=pos[corner][xy]).grid(row=ROW, column=COL)
        for diff in [1, 10, 100]:
            COL += 1
            tk.Button(root, text=' {:+d} '.format(-diff), command=changePos(corner, xy, -diff)).grid(row=ROW, column=COL)
            COL += 1
            tk.Button(root, text=' {:+d} '.format(+diff), command=changePos(corner, xy, +diff)).grid(row=ROW, column=COL)

ROW += 1
tk.Label(root, text='').grid(row=ROW, column=0)

ROW += 1
COL = -1
for xy in [0, 1]:
    COL += 1
    tk.Label(root, text='Out{}'.format(xy)).grid(row=ROW, column=COL)
    COL += 1
    tk.Entry(root, textvariable=output[xy]).grid(row=ROW, column=COL)
    for diff in [1, 10, 100]:
        COL += 1
        tk.Button(root, text=' {:+d} '.format(-diff), command=changeOutput(xy, -diff)).grid(row=ROW, column=COL)
        COL += 1
        tk.Button(root, text=' {:+d} '.format(+diff), command=changeOutput(xy, +diff)).grid(row=ROW, column=COL)

ROW += 1
tk.Button(root, text='Save File', command=saveFile).grid(row=ROW, column=0, columnspan=2, sticky='W')

root.mainloop()

 

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