1. 程式人生 > 實用技巧 >Chapter1 - 畫素(Pixel)操作

Chapter1 - 畫素(Pixel)操作

Chapter1 - 畫素(Pixel)操作

import cv2 as cv
import numpy as np
src = cv.imread("opencv.png")

一、改變畫素值

# copy image, deep copy
img_copied = np.copy(src)

# change value
img_changed = img_copied
img_copied[100:200, 200:300, :] = 255

# both `img_changed` and `img_copied` change.
cv.imshow("img_changed", img_changed)
cv.imshow("img_copied", img_copied)

cv.waitKey(0)
cv.destroyAllWindows()

執行結果

img_changed

img_copied

關於複製np.copy()

  • src的型別是numpy.ndarray,即放置同類型的元素的多維陣列。所以“複製圖片”要用np.copy()

  • python有直接引用、淺複製、深複製

  • 對於列表等可迴圈結構,即python list

    • b = a是直接引用,指向同一個物件,a改變,b也跟著改變
    • b = a.copy()是淺複製;a改變,b也改變
    • b = a.deepcopy()是深複製;a、b相互獨立
  • numpy listpython list不同:

    • img_copied = np.copy(src)深拷貝
      ;即src改變,但img_copied不變;
    • img_changed = img_copied是直接引用;img_copied改變,img_changed也要改變;

二、通過畫素值新建影象

# create image
img_created = np.zeros(src.shape, src.dtype) # value:all zero, Black
cv.imshow("img_created", img_created)

img_1 = np.zeros([512,512], dtype=np.uint8)
img_1[:,:] = 127 # set gray value: 127

img_2 = np.ones([512,512,3], dtype=np.uint8)
img_2[:,:,0] = 255 # "B":255,"G"="R"=1, Blue

cv.imshow("gray image: img_1", img_1)
cv.imshow("R\'s value=255: img_2", img_2)

cv.waitKey(0)
cv.destroyAllWindows()

注意

讀取彩色圖片時,會有三層,這三層代表的RGB是顛倒的,也就是BGR

  • img[:,:,0]代表B
  • img[:,:,1]代表G
  • img[:,:,2]代表R

改變RGB的值就意味著能對影象的色彩進行操作,那麼我們下面使用陣列的方式,把對應位置的BGR值做一下變化,觀察一下效果:

# read and write pixels
img_copied = np.copy(src)
row, column, channel = img_copied.shape
print("row: %d, column: %d, channel: %d" % (row, column, channel))

for i in range(row):
    for j in range(column):
        b, g, r = img_copied[i, j]
        b = 255 - b
        g = 255 - g
        r = 255 - r
        img_copied[i, j] = [b, g, r]
        
cv.imshow("read and write pixels", img_copied)

cv.waitKey(0)
cv.destroyAllWindows()
row: 610, column: 570, channel: 3

執行結果

在處理610×570這樣一個小型影象時,反應時間便明顯變長;

為了提高速度,一方面需要更好的裝置,另一方面上述程式碼最好改寫成支援CUDA加速;

兩個影象的畫素之間也是可以做算術運算的(只要不超出範圍即可),比如下面這個例子:

# pixel arithmetic operations
img_1 = cv.imread("test0.jpg")
img_2 = cv.imread("test1.jpg")

result = np.zeros(shape=[4,img_1.shape[0],img_1.shape[1],img_1.shape[2]], dtype=img_1.dtype)
for i in range(4):
    result[i][:,:,:] = np.zeros(shape=[img_1.shape[0],img_1.shape[1],img_1.shape[2]], dtype=img_1.dtype)

cv.add(img_1, img_2, result[0])
cv.imshow("add_result", result[0])

cv.subtract(img_1, img_2, result[1])
cv.imshow("sub_result", result[1])

cv.multiply(img_1, img_2, result[2])
cv.imshow("mul_result", result[2])

cv.divide(img_1, img_2, result[3])
cv.imshow("div_result", result[3])

cv.waitKey(0)
cv.destroyAllWindows()

算數運算的操作比較簡單,這裡不再詳述;

為了儲存每次算數運算的結果,可以每次都新建變數--初始化--承接結果;為了縮減初始化的工作量,新建了result這個4維矩陣;

result從巨集觀上看,可以認為是4個元素的list,每個元素都是一個影象型別(3維);這種提取過程需要會。

三、畫素值的邏輯運算

除了算術操作,我們也可以進行邏輯操作(logical operation),常見的有三種:

  • cv.bitwise_add() 邏輯與;
  • cv.bitwise_or() 邏輯或;
  • cv.bitwise_not() 邏輯否;
  • cv.bitwise_xor() 異或;同為0,異為1

當然,不僅可以對兩張圖片做邏輯操作,也可以只對一張圖片做邏輯操作,不過只能做not運算,圖中的白色會變成黑色;而其他邏輯操作,如and等會報錯;

下面,我們新建矩形和圓形圖片,對這兩張圖片進行邏輯操作,兩種圖形的用法分別如下:

cv.rectangle(img, (x,y), (x+h,y+h), (b,g,r), line_thickness)

  • (x,y)(x+h,y+h)分別對應了左上和右下角;這樣的話,第一個點的座標應該小於第二個點;但是如果第一個點的座標大於第二個點,會被認為是左下和右上角

  • (b,g,r)是矩形四條邊的BGR數值;注意是BGR而非RGB;如果這裡只寫一個255,代表B=255,G=R=0,也就是藍色;

  • line_thickness是邊的厚度;如果是負數,如-1,表示填充整個矩形,填充的顏色是上面線條的顏色

cv.circle(img, (x,y), r, (b,g,r), thickness)

  • (x,y)是圓的圓心座標,r是半徑;
  • 其他的和上面矩形類似;
# create rectangle
img_1 = np.zeros(shape=[400,400,3], dtype=np.uint8)
cv.rectangle(img_1, (25,25), (375,375), 255, -1)
cv.imshow("rectangle", img_1)

# create circle
img_2 = np.zeros(shape=[400,400,3], dtype=np.uint8)
cv.circle(img_2, (200,200), 200, 255, -1)
cv.imshow("circle", img_2)

# make logical operations
dst1 = cv.bitwise_and(img_1, img_2)
dst2 = cv.bitwise_or(img_1, img_2)
dst3 = cv.bitwise_xor(img_1, img_2)
dst4 = cv.bitwise_not(src)

cv.imshow("add", dst1)
cv.imshow("or", dst2)
cv.imshow("xor", dst3)
cv.imshow("not to picture", dst4)

cv.waitKey(0)
cv.destroyAllWindows()

執行結果

四、偽彩色applyColorMap函式

使用OpenCV的預定義的顏色對映來將灰度影象偽彩色化;顯示的效果類似於顏色濾鏡;

應用領域:空間中的行星和其他物體的灰度影象是採用偽彩色來顯示細節,並對不同顏色的不同材質對應的區域進行標記。比如下面對冥王星處理的圖片:

下面,我們對於圖片使用偽彩色效果:

img_cool = cv.applyColorMap(src, cv.COLORMAP_COOL)
img_jet = cv.applyColorMap(src, cv.COLORMAP_JET)

cv.imshow("colormap.cool", img_cool)
cv.imshow("colormap.jet", img_jet)

cv.waitKey(0)
cv.destroyAllWindows()

執行結果

五、影象通道的分離cv.split()和合並cv.merge()

# channel seperation
B, G, R = cv.split(src)
print("分離後的維數:", B.shape)

cv.imshow("Split Blue", B)
cv.imshow("Split Green", G)
cv.imshow("Split Red", R)

# channel merging
# zeros = np.zeros(shape=src.shape[:2], dtype=np.uint8)
zeros = np.zeros(shape=B.shape, dtype=np.uint8)
cv.imshow("Merge Blue", cv.merge([B, zeros, zeros]))
cv.imshow("Merge Green", cv.merge([zeros, G, zeros]))
cv.imshow("Merge Red", cv.merge([zeros, zeros, R]))

# merge orignal image
cv.imshow("orignal image", cv.merge([B, G, R]))

cv.waitKey(0)
cv.destroyAllWindows()
分離後的維數: (610, 570)

執行結果

B,G,R = cv.split(image)

  • 已知image.shape是610×570×3,如果只需要某個通道,那麼最後的3就會變成1,所以B G R的維數應該是610×570;

  • 如果使用mv = cv.split(image),那麼mvlist型別,有3個元素(np.array型別),每個元素是610×570維;

  • 如果單獨顯示通道的影象,發現三個通道的影象儘管不同,但都是灰色的,而不是期望中的紅色等。以R為例,分離後圖像會缺失G、B,但是使用imshow()方法後,三個通道都會變成R,即(R,R,R)當三個通道的值相等時,為灰度圖

那我們如果要想顯示紅色,但是還得要用imshow()這個函式,就需要使用合併,把自動生成的(R,R,R),變成(R,0,0),也就是上面zeros那個矩陣的作用;

cv.merge([B, G, R])

需要注意:B,G,R的維數一定要相同;分離後各個通道的維數是610×570,為了確保維數相同,有這種寫法:

  • zeros = np.zeros(shape=src.shape[:2], dtype=np.uint8)

  • 這裡src.shape是610×570×3,也就是這個列表有三個元素,注意:src.shape[:2]是取不到最後一個元素的,只能取src.shape[0]、src.shape[1]這兩個,src.shape[2]是取不到的;

  • zeros = np.zeros(shape=B.shape, dtype=np.uint8)這個寫法是等價的,更簡潔明瞭;

下面提供一種更簡單直白的寫法,思路是把無關通道的所有元素直接強行置0;比如:

result_split = cv.split(src)

# make G=R=0, only keep B;
result_split[1][:,:] = 0
result_split[2][:,:] = 0
dst = cv.merge(result_split)

cv.imshow("Merge Blue", dst)

cv.waitKey(0)
cv.destroyAllWindows()