從零開始 OpenCV 學習課-3.影像的創建與修改
本系列面向 Python 小白,從零開始實戰解說 OpenCV 專案實戰,
OpenCV 中影像的資料結構是 ndarray 多維陣列,對影像的任何操作本質上都是對 ndarray 多維陣列的操作和運算,
本節介紹影像的格式和 Numpy 方法影像處理,提供完整例程和運行結果:查看影像屬性,像素讀取與編輯,創建空白、黑色、白色、隨機影像,影像復制,影像裁剪,ROI裁剪,影像拼接、影像通道的拆分與合并,
1. 影像基本知識
1.1 影像顏色的分類
按照顏色對影像進行分類,可以分為二值影像、灰度影像和彩色影像,
- 二值影像:只有黑色和白色兩種顏色的影像,每個像素點可以用 0/1 表示,0 表示黑色,1 表示白色,
- 灰度影像:只有灰度的影像,每個像素點用 8bit 數字 [0,255] 表示灰度,如:0 表示純黑,255 表示純白,
- 彩色影像:彩色影像通常采用紅色(R)、綠色(G)和藍色(B)三個色彩通道的組合表示,每個像素點可以用 3個 8bit 數字 [0,255] 分別表示紅色、綠色和藍色的顏色分量,如:(0,0,0) 表示黑色,(255,255,255) 表示白色,
彩色影像可以采用不同的表達方式,OpenCV 使用 BGR 格式,色彩通道按照 B/G/R 的順序排列;而 matplotlib、PyQt5、Pillow 中使用 RGB 格式,色彩通道按照 R/G/B 的順序排列的,
一些彩色影像格式還支持透明通道(alpha 通道),每個像素點用 8bit 數字 [0,255] 表示透明度,0 表示完全透明,255 表示完全不透明,
在數字影像處理中,可以根據需要對影像的通道順序進行轉換,或將彩色影像轉換為灰度影像、二值影像,
1.2 數字影像的表示
數字影像是通過柵格排列的像素組成的,在計算機中以多維資料集來表示和處理,
OpenCV 的 Python API 是基于 Numpy 來存盤和處理多維陣列,影像的資料結構是 ndarray 多維陣列,OpenCV 中對影像的任何操作,本質上都是對 ndarray 多維陣列的操作和運算,
OpenCV 中的二值影像和灰度影像用二維陣列 (h, w) 表示,陣列中的每個元素表示對應一個像素的灰度,每個像素的位深度為 8位,
OpenCV 中二值影像被作為特殊的灰度影像,每個像素點的值為 0(黑色)或 255(白色),
OpenCV 中的彩色影像用三維陣列 (h, w, ch=3) 表示,陣列中的每個元素對應一個像素的某種顏色分量,每個像素的位深度為 24位,
OpenCV 使用 BGR 格式,色彩通道順序為 B/G/R,因此 B 通道是 img[:, :, 0], G 通道是 img[:, :, 1], R 通道是 img[:, :, 2],

1.3 數字影像的屬性
OpenCV 中影像物件的資料結構是 ndarray 多維陣列,因此 ndarray 陣列的屬性和操作方法也都適用于 OpenCV 的影像物件,例如:
-
img.ndim:查看影像的維數,彩色影像的維數為 3,灰度影像的維數為 2,
-
img.shape:查看影像的形狀,即影像柵格的行數(高度)、列數(寬度)、通道數,
-
img.size:查看影像陣列元素總數,灰度影像的陣列元素總數為像素數量,彩色影像的陣列元素總數為像素數量與通道數的乘積,
基本例程:
# 1.11 影像陣列的屬性
imgFile = "../images/imgLena.tif" # 讀取檔案的路徑
img1 = cv2.imread(imgFile, flags=1) # flags=1 讀取彩色影像(BGR)
img2 = cv2.imread(imgFile, flags=0) # flags=0 讀取為灰度影像
# cv2.imshow("Demo1", img1) # 在視窗顯示影像
# key = cv2.waitKey(0) # 等待按鍵命令
# 維數(ndim), 形狀(shape), 元素總數(size), 元素型別(dtype)
print("Ndim of img1(BGR): {}, img2(Gray): {}".format(img1.ndim, img2.ndim)) # number of rows, columns and channels
print("Shape of img1(BGR): {}, img2(Gray): {}".format(img1.shape, img2.shape)) # number of rows, columns and channels
print("Size of img1(BGR): {}, img2(Gray): {}".format(img1.size, img2.size)) # size = rows * columns * channels
print("Dtype of img1(BGR): {}, img2(Gray): {}".format(img1.dtype, img2.dtype)) # uint8
本例程的運行結果如下:
Ndim of img1(BGR): 3, img2(Gray): 2
Shape of img1(BGR): (512, 512, 3), img2(Gray): (512, 512)
Size of img1(BGR): 786432, img2(Gray): 262144
Dtype of img1(BGR): uint8, img2(Gray): uint8
通過資源管理器查看彩色影像和灰度影像的屬性如下圖,彩色影像的位深度為 24,灰度影像的位深度為 8,
2. 像素的編輯
像素是構成數字影像的基本單位,像素處理是影像處理的基本操作,
對像素的訪問、修改,可以使用 Numpy 方法直接訪問陣列元素,
基本例程:
# 1.13 Numpy 獲取和修改像素值
img1 = cv2.imread("../images/imgLena.tif", flags=1) # flags=1 讀取彩色影像(BGR)
x, y = 10, 10 # 指定像素位置 x, y
# (1) 直接訪問陣列元素,獲取像素值(BGR)
pxBGR = img1[x,y] # 訪問陣列元素[x,y], 獲取像素 [x,y] 的值
print("x={}, y={}\nimg[x,y] = {}".format(x,y,img1[x,y]))
# (2) 直接訪問陣列元素,獲取像素通道的值
print("img[{},{},ch]:".format(x,y))
for i in range(3):
print(img1[x, y, i], end=' ') # i=0,1,2 對應 B,G,R 通道
# (3) img.item() 訪問陣列元素,獲取像素通道的值
print("\nimg.item({},{},ch):".format(x,y))
for i in range(3):
print(img1.item(x, y, i), end=' ') # i=0,1,2 對應 B,G,R 通道
# (4) 修改像素值:img.itemset() 訪問陣列元素,修改像素通道的值
ch, newValue = 0, 255
print("\noriginal img[x,y] = {}".format(img1[x,y]))
img1.itemset((x, y, ch), newValue) # 將 [x,y,channel] 的值修改為 newValue
print("updated img[x,y] = {}".format(img1[x,y]))
本例程的運行結果如下:
x=10, y=10
img[x,y] = [113 131 226]
img[10,10,ch]: 113 131 226
img.item(10,10,ch): 113 131 226
original img[x,y] = [113 131 226]
updated img[x,y] = [255 131 226]
3. 影像的創建
OpenCV 中影像物件的資料結構是 ndarray 多維陣列,因此可以用 Numpy 創建多維陣列來生成影像,特別對于空白、黑色、白色、隨機等特殊影像,用 Numpy 創建影像非常方便,
Numpy 可以使用 np.zeros() 等方法創建指定大小、型別的影像物件,也可以使用 np.zeros_like() 等方法創建與已有影像大小、型別相同的新影像,
函式說明:
numpy.empty(shape[, dtype, order]) # 回傳一個指定形狀和型別的空陣列
numpy.zeros(shape[, dtype, order]) # 回傳一個指定形狀和型別的全零陣列
numpy.ones(shape[, dtype, order]) # 回傳一個指定形狀和型別的全一陣列
numpy.empty_like(img) # 回傳一個與影像 img 形狀和型別相同的空陣列
numpy.zeros_like(img) # 回傳一個與影像 img 形狀和型別相同的全零陣列
numpy.ones_like(img) # 回傳一個與影像 img 形狀和型別相同的全一陣列
引數說明:
- shape:整型元組,定義回傳多維陣列的形狀
- dtype:資料型別,定義回傳多維陣列的型別,可選項
- img:ndarray 多維陣列,表示一個灰度或彩色影像
基本例程:
# 1.14 Numpy 創建影像
# 創建彩色影像(RGB)
# (1) 通過寬度高度值創建多維陣列
width, height, channels = 400, 300, 3 # 行/高度, 列/寬度, 通道數
imgEmpty = np.empty((width, height, channels), np.uint8) # 創建空白陣列
imgBlack = np.zeros((width, height, channels), np.uint8) # 創建黑色影像 RGB=0
imgWhite = np.ones((width, height, channels), np.uint8) * 255 # 創建白色影像 RGB=255
# (2) 創建相同形狀的多維陣列
img1 = cv2.imread("../images/imgLena.tif", flags=1) # flags=1 讀取彩色影像(BGR)
imgBlackLike = np.zeros_like(img1) # 創建與 img1 相同形狀的黑色影像
imgWhiteLike = np.ones_like(img1) * 255 # 創建與 img1 相同形狀的白色影像
# (3) 創建彩色隨機影像 RGB=random
import os
randomByteArray = bytearray(os.urandom(width * height * channels))
flatNumpyArray = np.array(randomByteArray)
imgRGBRand = flatNumpyArray.reshape(width, height, channels)
# (4) 創建灰度影像
imgGrayWhite = np.ones((width, height), np.uint8) * 255 # 創建白色影像 Gray=255
imgGrayBlack = np.zeros((width, height), np.uint8) # 創建黑色影像 Gray=0
imgGrayEye = np.eye(width) # 創建對角線元素為1 的單位矩陣
randomByteArray = bytearray(os.urandom(width * height))
flatNumpyArray = np.array(randomByteArray)
imgGrayRand = flatNumpyArray.reshape(width, height) # 創建灰度隨機影像 Gray=random
本例程的運行結果如下:

4. 影像的復制
使用 Numpy 的 np.copy() 函式可以進行影像的復制,不能通過直接賦值進行影像的復制,
函式說明:
arr = numpy.copy(img) # 回傳一個復制的影像
引數說明:
- img:ndarray 多維陣列,表示一個灰度或彩色影像
注意事項:
- Python 中的 “復制” 有無拷貝、淺拷貝和深拷貝之分,無拷貝相當于參考,淺拷貝只是對原變數記憶體地址的拷貝,深拷貝是對原變數(ndarray陣列)的所有資料的拷貝,
- Numpy 直接賦值是無拷貝,np.copy() 方法是深拷貝,切片操作是特殊的淺拷貝,
- 直接賦值得到的新影像相當于參考,改變新影像的值時原影像的值也發生改變;np.copy() 方法復制影像(ndarray陣列)得到的新影像才是深拷貝,改變復制影像的形狀或數值,原來影像并不會發生改變,
基本例程:
# 1.15 影像的復制
img1 = cv2.imread("../images/imgLena.tif", flags=1) # flags=1 讀取彩色影像(BGR)
img2 = img1.copy()
print("img2=img1.copy(), img2 is img1?", img2 is img1)
for col in range(100):
for row in range(100):
img2[col, row, :] = 0
img3 = cv2.imread("../images/imgLena.tif", flags=1) # flags=1 讀取彩色影像(BGR)
img4 = img3
print("img4=img3, img4 is img3?", img4 is img3)
for col in range(100):
for row in range(100):
img4[col, row, :] = 0
cv2.imshow("Demo1", img1) # 在視窗顯示影像
cv2.imshow("Demo2", img2) # 在視窗顯示影像
cv2.imshow("Demo3", img3) # 在視窗顯示影像
cv2.imshow("Demo4", img4) # 在視窗顯示影像
key = cv2.waitKey(0) # 等待按鍵命令
本例程中,img4=img3 直接賦值,改變 img4 的數值后 img3 的數值也被改變了;img2 = img1.copy(),改變 img2 的數值后 img1 并未發生改變,
本例程的運行結果如下,使用 np.copy() 方法得到的新影像才是深拷貝,
img2=img1.copy(), img2 is img1? False
img4=img3, img4 is img3? True
5. 影像的裁剪
用 Numpy 的切片方法可以進行影像的裁剪,操作簡單方便,
方法說明:
retval = img[y:y+h, x:x+w].copy()
- 對影像 img 裁剪并回傳指定的矩陣區域影像,
引數說明:
- img:影像資料,ndarray 多維陣列
- x, y:整數,像素值,裁剪矩形區域左上角的坐標值
- w, h:整數,像素值,裁剪矩形區域的寬度、高度
- 回傳值 retval:裁剪后獲得的 OpenCV 影像,nparray 多維陣列
注意事項:
- Numpy 多維陣列的切片是原始陣列的淺拷貝,切片修改后原始陣列也會改變,推薦采用 .copy() 進行深拷貝,得到原始影像的副本,
- Numpy 陣列切片,當上界或下界為陣列邊界時可以省略,如:img[y:, :x] 表示高度方向從 y 至影像底部(像素ymax),寬度方向從影像左側(像素 0)至 x,
基本例程:
# 1.16 影像的裁剪
img1 = cv2.imread("../images/imgLena.tif", flags=1) # flags=1 讀取彩色影像(BGR)
xmin, ymin, w, h = 180, 190, 200, 200 # 矩形裁剪區域 (ymin:ymin+h, xmin:xmin+w) 的位置引數
imgCrop = img1[ymin:ymin+h, xmin:xmin+w].copy() # 切片獲得裁剪后保留的影像區域
cv2.imshow("DemoCrop", imgCrop) # 在視窗顯示 彩色隨機影像
key = cv2.waitKey(0) # 等待按鍵命令
擴展例程:
函式 cv2.selectROI() 可以通過滑鼠選擇感興趣的矩形區域(ROI),
cv2.selectROI(windowName, img, showCrosshair=None, fromCenter=None):
使用 cv2.selectROI(),可以實作對 ROI 的裁剪,詳見例程 1.17,
# 1.17 影像的裁剪 (ROI)
img1 = cv2.imread("../images/imgLena.tif", flags=1) # flags=1 讀取彩色影像(BGR)
roi = cv2.selectROI(img1, showCrosshair=True, fromCenter=False)
xmin, ymin, w, h = roi # 矩形裁剪區域 (ymin:ymin+h, xmin:xmin+w) 的位置引數
imgROI = img1[ymin:ymin+h, xmin:xmin+w].copy() # 切片獲得裁剪后保留的影像區域
cv2.imshow("DemoRIO", imgROI)
cv2.waitKey(0)

6. 影像的拼接
用 Numpy 的陣列堆疊方法可以進行影像的拼接,操作簡單方便,
方法說明:
retval = numpy.hstack((img1, img2, …)) # 水平拼接
retval = numpy.vstack((img1, img2, …)) # 垂直拼接
- np.hstack() 按水平方向(列順序)拼接 2個或多個影像,影像的高度(陣列的行)必須相同,
- np.vstack() 按垂直方向(行順序)拼接 2個或多個影像,影像的寬度(陣列的列)必須相同,
- 綜合使用 np.hstack() 和 np.vstack() 函式,可以實作影像的矩陣拼接,
- np.hstack() 和 np.vstack() 只是簡單地將幾張影像直接堆疊而連成一張影像,并未對影像進行特征提取和邊緣處理,因而并不能實作影像的全景拼接,
引數說明:
- img1, img2, …:拼接前的影像,ndarray 多維陣列
- 回傳值 retval:拼接后的影像,ndarray 多維陣列
基本例程:
# 1.18 影像拼接
img1 = cv2.imread("../images/imgLena.tif") # 讀取彩色影像(BGR)
img2 = cv2.imread("../images/logoCV.png") # 讀取彩色影像(BGR)
img1 = cv2.resize(img1, (400, 400))
img2 = cv2.resize(img2, (300, 400))
img3 = cv2.resize(img2, (400, 300))
imgStackH = np.hstack((img1, img2)) # 高度相同影像可以橫向水平拼接
imgStackV = np.vstack((img1, img3)) # 寬度相同影像可以縱向垂直拼接
print("Horizontal stack:\nShape of img1, img2 and imgStackH: ", img1.shape, img2.shape, imgStackH.shape)
print("Vertical stack:\nShape of img1, img3 and imgStackV: ", img1.shape, img3.shape, imgStackV.shape)
cv2.imshow("DemoStackH", imgStackH) # 在視窗顯示影像 imgStackH
cv2.imshow("DemoStackV", imgStackV) # 在視窗顯示影像 imgStackV
key = cv2.waitKey(0) # 等待按鍵命令
本例程的運行結果如下:
Horizontal stack:
Shape of img1, img2 and imgStackH: (400, 400, 3) (400, 300, 3) (400, 700, 3)
Vertical stack:
Shape of img1, img3 and imgStackV: (400, 400, 3) (300, 400, 3) (700, 400, 3)

7. 影像通道的拆分
函式 cv2.split() 將 3 通道 BGR 彩色影像分離為 B、G、R 單通道影像,
函式說明:
cv2.split(img[, mv]) -> retval # 影像拆分為 BGR 通道
- 函式 cv2.split() 傳入一個影像陣列,并將影像拆分為 B/G/R 三個通道,
引數說明:
- img:影像資料,ndarray 多維陣列
- mv:指定的分拆通道(可選)
注意事項:
- 對于 openCV 使用的 BGR 格式影像,回傳的分拆通道的次序為 B、G、R 通道,
- BGR 彩色影像的資料形狀為 (width, height, channels=3),回傳的 B/G/R 通道的資料形狀為 (width, height),不能按照 BGR 彩色影像直接顯示,
- 如果直接用 imshow 顯示回傳的單通道物件,將被視為 (width, height) 形狀的灰度影像顯示,
- 如果要正確顯示某一顏色分量,需要增加另外兩個通道值(置 0)轉換為 BGR 三通道格式,再用 imshow 才能顯示為拆分通道的顏色,
- cv2.split() 操作復雜耗時,可以直接使用 NumPy 切片得到分離通道,
基本例程:
# 1.19 影像拆分通道
img1 = cv2.imread("../images/imgB1.jpg", flags=1) # flags=1 讀取彩色影像(BGR)
cv2.imshow("BGR", img1) # BGR 影像
# BGR 通道拆分
bImg, gImg, rImg = cv2.split(img1) # 拆分為 BGR 獨立通道
cv2.imshow("rImg", rImg) # 直接顯示紅色分量 rImg 顯示為灰度影像
# 將單通道擴展為三通道
imgZeros = np.zeros_like(img1) # 創建與 img1 相同形狀的黑色影像
imgZeros[:,:,2] = rImg # 在黑色影像模板添加紅色分量 rImg
cv2.imshow("channel R", imgZeros) # 擴展為 BGR 通道
print(img1.shape, rImg.shape, imgZeros.shape)
cv2.waitKey(0)
cv2.destroyAllWindows() # 釋放所有視窗
本例程的運行結果如下:
(512, 512, 3) (512, 512) (512, 512, 3)
運行結果表明:
- 彩色影像 img1 的形狀為 (512, 512, 3),拆分的 R 通道 rImg 的形狀為 (512, 512),
- 用 imshow 顯示 rImg,將被視為 (512, 512) 形狀的灰度影像顯示,不能顯示為紅色通道,
- 對 rImg 增加 B、G 兩個通道值(置 0)轉換為 BGR格式,再用 imshow 才能顯示紅色通道的顏色,

擴展例程:
使用 NumPy 切片得到分離通道更為簡便,而且運行速度比 cv2.split 更快,
# 1.20 影像拆分通道 (Numpy切片)
img1 = cv2.imread("../images/imgB1.jpg", flags=1) # flags=1 讀取彩色影像(BGR)
# 獲取 B 通道
bImg = img1.copy() # 獲取 BGR
bImg[:, :, 1] = 0 # G=0
bImg[:, :, 2] = 0 # R=0
# 獲取 G 通道
gImg = img1.copy() # 獲取 BGR
gImg[:, :, 0] = 0 # B=0
gImg[:, :, 2] = 0 # R=0
# 獲取 R 通道
rImg = img1.copy() # 獲取 BGR
rImg[:, :, 0] = 0 # B=0
rImg[:, :, 1] = 0 # G=0
# 消除 B 通道
grImg = img1.copy() # 獲取 BGR
grImg[:, :, 0] = 0 # B=0
plt.subplot(221), plt.title("1. B channel"), plt.axis('off')
bImg = cv2.cvtColor(bImg, cv2.COLOR_BGR2RGB) # 圖片格式轉換:BGR(OpenCV) -> RGB(PyQt5)
plt.imshow(bImg) # matplotlib 顯示 channel B
plt.subplot(222), plt.title("2. G channel"), plt.axis('off')
gImg = cv2.cvtColor(gImg, cv2.COLOR_BGR2RGB)
plt.imshow(gImg) # matplotlib 顯示 channel G
plt.subplot(223), plt.title("3. R channel"), plt.axis('off')
rImg = cv2.cvtColor(rImg, cv2.COLOR_BGR2RGB)
plt.imshow(rImg) # matplotlib 顯示 channel R
plt.subplot(224), plt.title("4. GR channel"), plt.axis('off')
grImg = cv2.cvtColor(grImg, cv2.COLOR_BGR2RGB)
plt.imshow(grImg) # matplotlib 顯示 channel GR
plt.show()
本例程的運行結果如下,GR channel 是消除 B通道(保留 G/R 通道的影像):

8. 影像通道的合并
函式 cv2.merge() 將 B、G、R 單通道合并為 3 通道 BGR 彩色影像,
函式說明:
cv2.merge(mv[, dst]) -> retval # BGR 通道合并
引數說明:
- mv:要合并的單通道
- dst:通道合并的影像,ndarray 多維陣列
注意事項:
- 進行合并的 B、G、R 單通道影像分量,資料形狀必須為 (width, height),而不是形狀為 (width, height, channels=3) 的藍色/綠色/紅色影像,
- 單通道影像分量的影像大小 (width, height) 必須相同才能進行合并,
- 顏色通道要按照 B、G、R 通道次序合并,才能得到 BGR 格式的合并結果,
- cv2.merge() 操作復雜耗時,推薦使用 NumPy 陣列合并函式 np.stack() 生成合成影像,
基本例程:
# 1.21 影像通道的合并
img1 = cv2.imread("../images/imgB1.jpg", flags=1) # flags=1 讀取彩色影像(BGR)
bImg, gImg, rImg = cv2.split(img1) # 拆分為 BGR 獨立通道
# cv2.merge 實作影像通道的合并
imgMerge = cv2.merge([bImg, gImg, rImg])
cv2.imshow("cv2Merge", imgMerge)
# Numpy 拼接實作影像通道的合并
imgStack = np.stack((bImg, gImg, rImg), axis=2)
cv2.imshow("npStack", imgStack)
print(imgMerge.shape, imgStack.shape)
print("imgMerge is imgStack?", np.array_equal(imgMerge, imgStack))
cv2.waitKey(0)
cv2.destroyAllWindows() # 釋放所有視窗
本例程的運行結果如下,imgMerge 與 imgStack 不僅形狀相同,而且每個位置的元素相等,表明 cv2.merge() 與 np.stack() 方法合并影像通道的結果是相同的,
(512, 512, 3) (512, 512, 3)
imgMerge is imgStack? True
【本節完】
著作權宣告:
歡迎關注『Python 小白從零開始 OpenCV 學習課 @ youcans』 原創作品
原創作品,轉載必須標注原文鏈接:https://blog.csdn.net/youcans/article/details/121068795
Copyright 2021 youcans, XUPT
Crated:2021-11-03
歡迎關注『Python 小白從零開始 OpenCV 學習課 @ youcans』 系列,持續更新中
Python 大白從零開始 OpenCV 學習課-1.安裝與環境配置
Python 大白從零開始 OpenCV 學習課-2.影像讀取與顯示
Python 大白從零開始 OpenCV 學習課-3.影像的創建與修改
Python 大白從零開始 OpenCV 學習課-4.影像的疊加與混合
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/350764.html
標籤:其他

