為后續項目學習做準備,我們需要了解LinuxOpenCV、Mediapipe、ROS、QT等知識。
一、圖像顯示與保存
1、基本原理
1.1 圖像像素存儲形式
首先得了解下圖像在計算機中存儲形式:(為了方便畫圖,每列像素值都寫一樣了)。對于只有黑白顏色的灰度圖,為單通道,一個像素塊對應矩陣中一個數字,數值為0到255, 其中0表示最暗(黑色) ,255表示最亮(白色)
對于采用RGB模式的彩色圖片,為三通道圖,Red、Green、Blue三原色,按不同比例相加,一個像素塊對應矩陣中的一個向量, 如[24,180, 50],分別表示三種顏色的比列, 即對應深度上的數字,如下圖所示:
?需要注意的是,由于歷史遺留問題,opencv采用BGR模式,而不是RGB
2、API說明
API名: cv2.destroyAllWindows( ) | 刪除所有我們建立的窗口 |
API名:?cv2.destroyWindow(指定窗口名) | 刪除特定的窗口可以使用,在括號內輸入你想刪?除的窗口名。 |
API名: cv2.imshow('窗口名', 圖像變量): | 用“窗口名”顯示圖像 |
API名:cv2.resizeWindow(‘窗口名’, 寬, 高) ? | 調整窗口大小 |
API名:cv2.imwrite('圖像文件地址',圖像變量) | 將圖像變量保存到指定位置的指定文件中 |
?API名:cv.namedWindow(winname, flags=None) | |
參數:winname | 窗口名字 |
flags窗口標志 | |
標志參數 | 作用 |
WINDOW_NORMAL | 顯示圖像后,允許用戶隨意調整窗口大小 |
WINDOW_AUTOSIZE | 根據圖像大小顯示窗口,不允許用戶調整大小 |
WINDOW_FREERATIO | 窗口大小自適應比例 |
WINDOW_KEEPRATIO | 保持圖像的比例 |
1、具體代碼展示標志效果 1.1 窗口大小可變 cv.namedWindow("show Image",cv.) 或者 cv.namedWindow("show Image",cv.WINDOW_GUI_NORMAL) 或者 cv.namedWindow("show Image",0) 此時的圖片是可以自由拉伸改變大小的 1.2 窗口大小不可變,自動適應圖片大小(默認) cv.namedWindow("show Image",cv.WINDOW_AUTOSIZE) 或者 cv.namedWindow("show Image",1) 1.3 窗口大小自適應比例 cv.namedWindow("show Image",cv.WINDOW_FREERATIO) 1.4 窗口大小跟隨圖片保持其比例 cv.namedWindow("show Image",cv.WINDOW_KEEPRATIO) | |
說明:如果在imshow()之前加上namedWindow()方法來顯示一張圖片的話,該窗口顯示的圖片是可交互的。 1、這里使用imshow()和namedWindow()方法時候窗口的標識名稱(傳遞的第一個參數)要一樣。 2、namedWindow()方法要寫在imshow()方法之前才可以。 | |
?API名:cv.namedWindow(winname, flags=None) | |
參數:winname | 窗口名字 |
flags窗口標志 | |
標志參數 | 作用 |
WINDOW_NORMAL | 顯示圖像后,允許用戶隨意調整窗口大小 |
WINDOW_AUTOSIZE | 根據圖像大小顯示窗口,不允許用戶調整大小 |
WINDOW_FREERATIO | 窗口大小自適應比例 |
WINDOW_KEEPRATIO | 保持圖像的比例 |
1、具體代碼展示標志效果 1.1 窗口大小可變 cv.namedWindow("show Image",cv.) 或者 cv.namedWindow("show Image",cv.WINDOW_GUI_NORMAL) 或者 cv.namedWindow("show Image",0) 此時的圖片是可以自由拉伸改變大小的 1.2 窗口大小不可變,自動適應圖片大小(默認) cv.namedWindow("show Image",cv.WINDOW_AUTOSIZE) 或者 cv.namedWindow("show Image",1) 1.3 窗口大小自適應比例 cv.namedWindow("show Image",cv.WINDOW_FREERATIO) 1.4 窗口大小跟隨圖片保持其比例 cv.namedWindow("show Image",cv.WINDOW_KEEPRATIO) | |
說明:如果在imshow()之前加上namedWindow()方法來顯示一張圖片的話,該窗口顯示的圖片是可交互的。 1、這里使用imshow()和namedWindow()方法時候窗口的標識名稱(傳遞的第一個參數)要一樣。 2、namedWindow()方法要寫在imshow()方法之前才可以。 |
三、代碼示例
使用 Matplotlib顯示圖片 |
import cv2 from matplotlib import pyplot as plt ? if __name__ == "__main__": ????img = cv2.imread('1.jpg',0) ????plt.imshow(img, cmap = 'gray', interpolation = 'bicubic') ????plt.xticks([]), plt.yticks([]) ?# to hide tick values on X and Y axis plt.show() |
使用 Matplotlib顯示圖片并進行一些操作 |
import cv2 from matplotlib import pyplot as plt ? if __name__ == "__main__": ????img = cv2.imread('2.jpg') ????edges = cv2.Canny(img,100,200) ? ????plt.subplot(121),plt.imshow(img,cmap = 'gray') ????plt.title('Original Image'), ?plt.xticks([]), plt.yticks([]) ????plt.subplot(122),??plt.imshow(edges,cmap = 'gray') ????plt.title('Edge Image'), plt.xticks([]), plt.yticks([]) ????plt.show() |
說明:plt.imshow()函數負責對圖像進行處理,并顯示其格式,而plt.show()則是將plt.imshow()處理后的函數顯示出來。 |
二、圖像處理基礎
1.1、獲取圖像屬性
所謂獲取圖像屬性,實際上就說當我們把一幅圖像讀入內存后,這幅圖像就如同一個三維或二維數組,那既然是個numpy數組,必然可以獲取它的shape、它的size以及它的dtype。乃至于,你可以像操作numpy數組那樣,在圖像上做諸如求平均值、方差之類的各種數學操作。因此,實際上獲取圖像的屬性操作也很簡單,比如下面這個例子就分別展示了如何獲得圖像的shape(指示是三維還是二維圖像,或者說彩色圖還是灰度圖)、size(圖像中像素的總數,依據這個總數以及每個像素的dtype就可以獲得這幅圖像當前總共占據多少內存存儲空間)、dtype(每個像素值的數據類型:float32還是int等等):
img = cv2.imread('test.jpg')
獲取圖像的屬性 | |
img.shape | 可以獲取圖像的形狀。他的返回值是:一個包含(行數(高),列數(寬), 通道數)的元組。 |
img.size | 返回圖像的像素數目 |
img.dtype | 返回返回圖像的數據類型 |
API名:cv2.imwrite('圖像文件地址',圖像變量) | 將圖像變量保存到指定位置的指定文件中 |
先查看原始圖像的shape、size、dtype:(675, 1200, 3) 2430000?uint8
再查看顏色模式為HSV后的圖像的shape、size、dtype:(675, 1200, 3) 2430000?uint8
最后查看變成灰度圖后圖像的shape、size、dtype:(675, 1200) 810000?uint8
從結果來看,當彩色圖變成灰度圖之后,通道數沒有了,意味著圖像的像素總數也會大幅減少。
1.2 圖像像素更改
????????圖像像素的更改行為,往往在機器視覺中是最最簡單的一種行為,其操作起來也如同我們更改一個list或者一個numpy數組中的元素那樣簡單。要知道,任何一幅圖像實際上讀到內存之后就是一個numpy數組,因此,圖像像素的修改就如同我們在修改一個n維數組中的某個元素那樣簡單。所不同的是,修改圖像像素,我們需要指定圖像像素的坐標:
?????????如果是RGB格式的,坐標就分別是R、G、B三個通道的值來共同定位一個像素;如果是HSV或別的什么顏色模式下的圖像,也如同像RGB那樣指定出三維坐標系各個坐標軸上的坐標值即可。
????????如果是一個灰度圖,則如同一個平面坐標系那般簡單,只需要指定橫縱坐標的值即可圈定一個像素。
????????當然,通常情況下,我們只需要給出像素的行和列坐標值即可獲得像素內的值。此時,對于RGB或HSV等三維顏色格式的圖像來說,返回的是圖像的三維坐標的值,比如RGB就是返回R、G、B;HSV的就是返回H、S、V;對于灰度圖來說則是返回的是灰度值。
????????同時,給定像素的行、列坐標值就可以依據像操作numpy二維數組那樣來給指定的像素點修改像素值。
獲取像素值并修改 參數含義:y坐標x:x坐標 bgr:BGR通道,0:B通道,1:G通道,2:R通道 | |
獲取像素值?img[y,x] | 獲取(x,y)處的通道值,返回列表
# 獲取像素值px = img[100, 200] |
img[y,x,bgr]? | 獲取(x,y)處一個通道值 獲取像素中的B通道顏色值blue = img[100, 200, 0] |
修改像素值?img[y,x]=[b,g,r] | 將一個顏色賦值給一個像素點 # 修改像素值img[100, 200] = [0, 123, 155] |
比如下面這段小代碼就很好地解釋了如何獲取不同顏色格式下的像素值內容:
import numpy as npimport cv2#讀入原始圖片,并將其進行顏色空間轉換為HSV和灰度圖#再分別讀出相同的行、列坐標值下的像素值以加深對像素值更操作的理解
import numpy as npimport cv2#讀入原始圖片,并將其進行顏色空間轉換為HSV和灰度圖#再分別讀出相同的行、列坐標值下的像素值以加深對像素值更操作的理解
img = cv2.imread(r'E:\tmp\maerdaifu.jpg',cv2.IMREAD_UNCHANGED)
hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
print('以下為不同顏色模式下獲取像素值的輸出結果')
print(img[100,100])
print(hsv[100,100])
print(gray[100,100])img[100,100]=[192, 212, 222]
hsv[100,100]=[198, 218, 228]
gray[100,100]=255print('以下為不同顏色模式下修改像素值的輸出結果')
print(img[100,100])
print(hsv[100,100])
print(gray[100,100])
以下為不同顏色模式下獲取像素值的輸出結果
[217 121 ?55][108 190 217]??
以下為不同顏色模式下修改像素值的輸出結果[192 212 222][198 218 228]
1.3 獲取圖像的感興趣區域(ROI)
????????圖像的感興趣區域(ROI:region of interest),是指在機器視覺、圖像處理中,從被處理的圖像以方框、圓、橢圓、不規則多邊形等方式勾勒出需要處理的區域,稱為感興趣區域。在圖像處理領域,感興趣區域是從圖像中選擇的一個圖像區域,這個區域是你的圖像分析所關注的重點。圈定該區域以便進行進一步處理。使用ROI圈定你想讀的目標,可以減少處理時間,增加精度。感興趣區是圖像的一部分,它通過在圖像上選擇或使用諸如設定閾值(thresholding) 或者從其他文件(如矢量> 轉換獲得等方法生成。感趣區可以是點、線、面不規則的形狀,通常用來作為圖像分類的樣本、掩膜、裁剪區或及其他操作。
????????比如,我想要摳出下面這幅圖中的房子:
圖1 馬爾代夫風景圖
????????那么首先我需要知道圖中的房子大約在哪些像素坐標范圍之內,也即其行坐標范圍和列坐標范圍。這里我們需要事先弄清楚感興趣區域在圖像中的坐標范圍:即行列坐標的坐標值范圍。
????????確定坐標范圍后,我們利用切片操作,像在Numpy數組中獲取一定范圍內的數據那樣,即可獲得我們感興趣的區域內的目標對象——房子。整個獲取ROI的代碼如下所示:
import cv2
#讀入原始圖片
img = cv2.imread(r'E:\tmp\maerdaifu.jpg',cv2.IMREAD_UNCHANGED)
#扣除我們感興趣的房子區域并予以單獨顯示
house = img[126:353,316:908]
cv2.imshow('HouseTest1', house)
#我們再把摳出來的房子放至原圖像的其他區域,再顯示操作過后的原始圖像img[326:553,500:1092]=house
cv2.imshow('HouseTest2', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
其運行結果如下:
圖2 摳出的ROI圖像
圖3 將ROI圖像重新放置回原始圖中的運行效果圖
????????從以上兩幅圖來看,我們大體上知道了如何來獲取一個ROI圖像。
????????需要注意的是:圖像在內存中的存儲格式是一個numpy數組,因此,當你給定行列坐標值范圍時,一定要把列坐標范圍放置在切片操作的第一個參數值上,行坐標值范圍為切片操作的第二個參數。這是大家在實際利用numpy數組開展切片操作或其他需要用到行列坐標參與運算時最最容易犯的錯誤之一。
1.4 圖像通道的拆分、合并操作
????????當我們需要單獨對圖像的通道進行操作,比如R、G、B三個通道都有不同的操作時,我們就需要用到通道的拆分。當拆分后的通道被處理結束之后,可能需要將其復原時,又需要用到通道的合并操作。由于我們再三強調過讀入一幅圖像到內存后,其實質就是讀入一個numpy數組,因此,在numpy的基礎知識講解中,如何拆分和合并不同的維度,這里就可以用同樣的API來操作,也即利用split()來拆分通道,利用merge()來合并通道,
拆分通道1:?b,g,r = cv2.split(m,mv) m:? 圖像imgmv:? 默認為None | # 分離顏色通道 b, g, r = cv2.split(img) cv2.imshow('Blue', b) cv2.imshow('Green', g) cv2.imshow('Red', r) |
拆分通道2: b=img[:,:,bgr] | # 其他方法 b1 = img[:, :, 0] # 僅取B通道 cv2.imshow('Blue-1', b1) b2 = cv2.imread('test.jpg') b2[:, :, 1:3] = 0 # 將0賦值給G、R通道 cv2.imshow('Blue-2', b2) |
合并通道?img = cv2.merge([X,Y,Z]) | mergeImg?= cv2.merge([b, g, r]) cv2.imshow('merge', mergeImg?) mergeImg2?= cv2.merge([h, s, v]) cv2.imshow('merge', mergeImg2?) |
import cv2#讀入原始圖片
img = cv2.imread(r'E:\tmp\maerdaifu.jpg',cv2.IMREAD_UNCHANGED)
r,g,b = cv2.split(img) #請大家注意一下merge函數的參數給定形式,如果不以list 或tuple方式來給定的話,必然會出錯
img = cv2.merge([r,g,b])
cv2.imshow('Test', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
其運行結果如下:
圖4 通道拆分合并效果圖
可以看出,通道經拆分再合并后得到的圖像與原始圖像并無二致。
#通道的分離與合并以及某個通道值的修改
import cv2 as cv
src=cv.imread('E:\imageload\example.png')
cv.namedWindow('first_image', cv.WINDOW_AUTOSIZE)
cv.imshow('first_image', src)
#三通道分離形成單通道圖片
b, g, r =cv.split(src)
cv.imshow("second_blue", b)
cv.imshow("second_green", g)
cv.imshow("second_red", r)
# 其中cv.imshow("second_red", r)可表示為r = cv2.split(src)[2]
#三個單通道合成一個三通道圖片
src = cv.merge([b, g, r])
cv.imshow('changed_image', src)#修改多通道里的某個通道的值
src[:, :, 2] = 0
cv.imshow('modify_image', src)cv.waitKey(0)
cv.destroyAllWindows()
5、添加邊界(padding)/為圖像擴邊
cv2.copyMakeBorder(src,top, bottom, left, right,borderType,value) | |
src: 輸入圖像 ? top, bottom, left, right 對應邊界的像素數目。 value: 邊界顏色,如果borderType為cv2.BORDER_CONSTANT時,傳入的邊界顏色值,如[0,255,0] | borderType: 要添加那種類型的邊界,類型如下 –cv2.BORDER_CONSTANT 添加有顏色的常數值邊界,還需要下一個參數(value)。 – cv2.BORDER_REFLECT邊界元素的鏡像。比如: fedcba|abcdefgh|hgfedcb – cv2.BORDER_REFLECT_101 or cv2.BORDER_DEFAULT 跟上面一樣,但稍作改動。例如: gfedcb|abcdefgh|gfedcba – cv2.BORDER_REPLICATE重復最后一個元素。例如: aaaaaa| abcdefgh|hhhhhhh – cv2.BORDER_WRAP 不知道怎么說了, 就像這樣: cdefgh| abcdefgh|abcdefg |
import cv2
import numpy as np
from matplotlib import pyplot as pltBLUE = [255, 0, 0]img = cv2.imread('test.jpg')
replicate = cv2.copyMakeBorder(img, 30, 30, 30, 30, cv2.BORDER_REPLICATE)
reflect = cv2.copyMakeBorder(img, 30, 30, 30, 30, cv2.BORDER_REFLECT)
reflect101 = cv2.copyMakeBorder(img, 30, 30, 30, 30, cv2.BORDER_REFLECT_101)
wrap = cv2.copyMakeBorder(img, 30, 30, 30, 30, cv2.BORDER_WRAP)
constant = cv2.copyMakeBorder(img, 30, 30, 30, 30, cv2.BORDER_CONSTANT, value=BLUE)plt.subplot(231),
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB), 'gray'), plt.title('ORIGINAL')
plt.subplot(232),
plt.imshow(cv2.cvtColor(replicate,cv2.COLOR_BGR2RGB),'gray'), plt.title('REPLICATE')
plt.subplot(233),
plt.imshow(cv2.cvtColor(reflect, cv2.COLOR_BGR2RGB), 'gray'), plt.title('REFLECT')
plt.subplot(234),
plt.imshow(cv2.cvtColor(reflect101, cv2.COLOR_BGR2RGB), 'gray'), plt.title('REFLECT_101')
plt.subplot(235),
plt.imshow(cv2.cvtColor(wrap, cv2.COLOR_BGR2RGB), 'gray'), plt.title('WRAP')
plt.subplot(236),
plt.imshow(cv2.cvtColor(constant, cv2.COLOR_BGR2RGB), 'gray'), plt.title('CONSTANT')
plt.show()
- 使用示例:
import cv2 as cv
import matplotlib.pyplot as pltimg2 = cv.imread(r"C:\Users\Administrator\Desktop\dog.jpg")
img = cv.cvtColor(img2,cv.COLOR_BGR2RGB) #matplotlib的圖像為RGB格式
constant = cv.copyMakeBorder(img,20,20,20,20,cv.BORDER_CONSTANT,value=[0,255,0]) #綠色
reflect = cv.copyMakeBorder(img,20,20,20,20,cv.BORDER_REFLECT)
reflect01 = cv.copyMakeBorder(img,20,20,20,20,cv.BORDER_REFLECT_101)
replicate = cv.copyMakeBorder(img,20,20,20,20,cv.BORDER_REPLICATE)
wrap = cv.copyMakeBorder(img,20,20,20,20,cv.BORDER_WRAP)
titles = ["constant","reflect","reflect01","replicate","wrap"]
images = [constant,reflect,reflect01,replicate,wrap]
for i in range(5):plt.subplot(2,3,i+1),plt.imshow(images[i]),plt.title(titles[i])plt.xticks([]),plt.yticks([])
plt.show()
??