本系列專欄寫(xiě)作方式
本系列專欄將采用首創(chuàng)的問(wèn)答式寫(xiě)作形式,快速讓你學(xué)習(xí)到 OpenCV 的初級(jí)、中級(jí)、高級(jí)知識(shí)。
7. 在 Python OpenCV 尋找目標(biāo)區(qū)域以及邊緣擴(kuò)展的解決方案
針對(duì)圖像的特定區(qū)域進(jìn)行操作,在 OpenCV 中被稱作 ROI ,即目標(biāo)區(qū)域或者叫做感興趣區(qū)域,在處理圖像的時(shí)候,可以先定位一個(gè)目標(biāo)區(qū)域,然后再在該區(qū)域進(jìn)行細(xì)節(jié)篩選,這樣可以提高我們程序的速度和準(zhǔn)確性。
實(shí)現(xiàn) ROI 操作其實(shí)就是采用 numpy 對(duì)圖像進(jìn)行操作
例如下述測(cè)試代碼,我們需要尋找圖像指定區(qū)域
import cv2
import numpy as np
src = cv2.imread("./7_img.jpg")
# cv2.imshow("src",src)
cv2.imshow("src",src)
# 注意后面的列表獲取,格式為 [rows,cols]
roi_img = src[50:150, 100:150]
print(roi_img.shape)
cv2.imshow("roi_img", roi_img)
cv2.waitKey()
cv2.destroyAllWindows()
運(yùn)行結(jié)果如下所示,尤其需要注意的是代碼注釋部分的說(shuō)明,在做 ROI 的時(shí)候,需要篩選的區(qū)域用偽代碼表示如下 src[起始行像素:結(jié)束行像素,起始列像素:結(jié)束列像素]

這個(gè)地方經(jīng)常出現(xiàn)的一個(gè) BUG 如下
error: (-215:Assertion failed) size.width>0 && size.height>0 in function 'cv::imshow'
很多博客中會(huì)寫(xiě)是圖片文件中文路徑的問(wèn)題,但是其實(shí)這只是一種結(jié)果,真正的原因是圖像出現(xiàn)了空結(jié)構(gòu),也就是讀取圖像的 shape 屬性如果展示內(nèi)容為 (0,0,3),那必然出現(xiàn)上述錯(cuò)誤。例如下面代碼就存在該問(wèn)題。
import cv2
import numpy as np
src = cv2.imread("./7_img.jpg")
# cv2.imshow("src",src)
roi_img = src[50:50, 100:100]
print(roi_img.shape)
cv2.imshow("roi_img", roi_img)
cv2.waitKey()
cv2.destroyAllWindows()
確定目標(biāo)區(qū)域,最大的難點(diǎn)就是坐標(biāo)問(wèn)題,你可以多次嘗試一下,直到記住目標(biāo)區(qū)域的定位方式。
獲取到 ROI 區(qū)域之后,可以對(duì)其進(jìn)行修改,例如,下述代碼將 ROI 區(qū)域設(shè)置為灰度圖像
import cv2
import numpy as np
src = cv2.imread("./7_img.jpg")
# cv2.imshow("src",src)
cv2.imshow("src", src)
# 注意后面的列表獲取,格式為 [rows,cols]
roi_img = src[50:150, 100:150]
gray = cv2.cvtColor(roi_img, cv2.COLOR_BGR2GRAY)
src[50:150, 100:150] = gray
cv2.imshow("src", src)
cv2.waitKey()
cv2.destroyAllWindows()
直接運(yùn)行該代碼,會(huì)出現(xiàn)如下錯(cuò)誤
ValueError: could not broadcast input array from shape (100,50) into shape (100,50,3)
因?yàn)楹喜⒌膱D像通道數(shù)不對(duì),接下來(lái)你需要做的是將灰度圖擴(kuò)展為三通道形式,使用下述代碼即可實(shí)現(xiàn)。
# 灰度圖擴(kuò)展到 3 通道
grays = np.stack((gray,)*3, axis=-1)
print(grays)
src[50:150, 100:150] = grays
其中用到了 numpy.stack(arrays, axis=0) 函數(shù),將數(shù)組進(jìn)行連接。修改代碼之后得到的運(yùn)行結(jié)果如下所示。

OopenCV 圖像的拆分與合并
上述案例你已經(jīng)掌握了圖像目標(biāo)區(qū)域獲取的方式,接下來(lái)我們對(duì)圖像進(jìn)行一下通道的拆分與合并,具體會(huì)使用到兩個(gè)函數(shù),分別是 cv2.merge 和 cv2.split。
兩個(gè)函數(shù)的原型可以直接獲取
# splite 函數(shù)原型
mv = cv2.split(m[, mv])
# merge 函數(shù)原型
dst = cv2.merge(mv[, dst])
下面進(jìn)行圖像的拆分,并通過(guò) matplotlib 庫(kù)進(jìn)行圖片的展示:
import cv2
import numpy as np
import matplotlib.pyplot as plt
src = cv2.imread("7_img.jpg")
b, g, r = cv2.split(src)
plt.subplot(131)
plt.imshow(b, "gray")
plt.title("b")
plt.subplot(132)
plt.imshow(g, "gray")
plt.title("g")
plt.subplot(133)
plt.imshow(r, "gray")
plt.title("r")
plt.show()
運(yùn)行之后可以獲取每個(gè)通道的灰度圖,cv2.split 執(zhí)行的效率并不高,所以你使用的圖像如果過(guò)大,記得稍等片刻。

針對(duì)每個(gè)通道還可以進(jìn)行拆分賦值,例如,你可以將 BGR 順序顛倒,形成不同色彩的圖片。
## 交換通道順序,進(jìn)行合并
dst = cv2.merge((r,g,b))
cv2.imshow("dst",dst)
cv2.waitKey()
運(yùn)行之后,可以得到對(duì)應(yīng)的效果。

cv2.merge 和 cv2.split 函數(shù)參數(shù)列表,請(qǐng)重點(diǎn)比對(duì)原型進(jìn)行學(xué)習(xí)。
如果你想要單獨(dú)修改某一通道值,可以使用 numpy 進(jìn)行操作,例如我們將 R 通道的像素值修改為 0,使用下述代碼即可
import cv2
import numpy as np
import matplotlib.pyplot as plt
src = cv2.imread("7_img.jpg")
src[:,:,2] = 0
cv2.imshow("src1",src)
src[:,:,2] = 255
cv2.imshow("src2",src)
cv2.waitKey()

OpenCV 圖像邊緣擴(kuò)展
接下來(lái)將要學(xué)習(xí)的函數(shù)是圖像擴(kuò)邊操作,使用 cv2.copyMakeBorder 函數(shù),函數(shù)原型如下:
dst = cv2.copyMakeBorder(src, top, bottom, left, right, borderType[, dst[, value]])
先掌握幾個(gè)核心參數(shù) src 原圖像,top,bottom,left,right分別表示在原圖四周擴(kuò)充邊緣的大小。
關(guān)于 borderType 參數(shù),表示的需要填充的邊界類型,該值有多種取值,建議是自行嘗試,我們采用其中一個(gè)優(yōu)先進(jìn)行說(shuō)明。
import cv2
from matplotlib import pyplot as plt
src = cv2.imread("color.jpg")
# 后續(xù)繪圖使用 pyplot ,所以切換一下顏色通道的排序,相當(dāng)于從 BGR 轉(zhuǎn)換為 RGB
b, g, r = cv2.split(src)
img = cv2.merge([r, g, b])
replicate = cv2.copyMakeBorder(img, 20, 20, 20, 20, cv2.BORDER_REPLICATE)
print("原圖形狀",src.shape)
print("擴(kuò)充邊界之后的形狀",replicate.shape)
打印結(jié)果,注意像素的寬度和高度都擴(kuò)展了 40,擴(kuò)展之后的圖片,你可以自行比對(duì)。
原圖形狀 (624, 500, 3)
擴(kuò)充邊界之后的形狀 (664, 540, 3)
圖像擴(kuò)展邊界最后一個(gè)參數(shù) borderType 有如下取值
-
cv2.BORDER_CONSTANT:固定值填充; -
cv2.BORDER_REFLECT_101或者cv2.BORDER_DEFAULT:取鏡像對(duì)稱的像素填充; -
cv2.BORDER_REPLICATE:重復(fù)最后一個(gè)像素; -
cv2.BORDER_WRAP:取鏡像。
結(jié)合上文,可以實(shí)現(xiàn)這樣一個(gè)效果,找到 ROI 區(qū)域,然后對(duì)其進(jìn)行邊緣擴(kuò)展。
import cv2
from matplotlib import pyplot as plt
src = cv2.imread("7_img.jpg")
# 后續(xù)繪圖使用 pyplot ,所以切換一下顏色通道的排序,相當(dāng)于從 BGR 轉(zhuǎn)換為 RGB
b, g, r = cv2.split(src)
img = cv2.merge([r, g, b])
roi_img = img[120:170,200:250]
dst = cv2.copyMakeBorder(roi_img, 2, 2, 2, 2, cv2.BORDER_CONSTANT,value=[0,0,255])
img[118:172,198:252] = dst
plt.imshow(img)
plt.title('img')
plt.show()
執(zhí)行代碼之后,我們?cè)谠瓐D找到某個(gè)區(qū)域,然后對(duì)其進(jìn)行邊緣擴(kuò)展,實(shí)現(xiàn)了一個(gè)矩形的框選操作,效果如下:
