文章目錄
- 前言
- imgproc 基礎操作(上)
- 1. 顏色空間
- 2. 直方圖
- 3. 二值化
- 4. 腐蝕、膨脹、開閉運算
- 5. 梯度與輪廓
- 6. 簡易繪圖
- 7. 重映射
- 總結
前言
- 需要下載安裝OpenCV工具包的朋友,請前往 此處 ;
- 系統要求:Windows系統,LabVIEW>=2018,兼容32位和64位。
imgproc 基礎操作(上)
本文對于之前教程中,未曾集中詳細講解的基礎圖像處理內容,進行必要的補充。
這些功能基本都來源于 imgproc 模塊,大概可以分為以下幾個方面。
1. 顏色空間
此內容之前已有教程,詳見:【秣厲科技】LabVIEW工具包——OpenCV 教程(4):顏色空間
2. 直方圖
直方圖是一種統計表示方法,用于展示圖像中不同像素強度出現的頻率分布。
OpenCV中,用 calcHist 函數計算直方圖,簡單用法如下:
-
例2-1:灰度圖的直方圖統計,區間數為5,均勻分割 8U 范圍(0~255)
-
作為常用的一維直方圖,其輸出結果 Hist 是個列向量,每一個數值代表相應灰度區間內的像素數。
-
calcHist 參數含義:
參數 | 含義 |
---|---|
ImageArray | Mat 數組,存儲一張或多張輸入圖像,尺寸必須一致 |
Hist | 一個 Mat 對象,直方圖統計結果的輸出容器,類型為 32F |
channels | int 數組,指定需要做直方圖統計的通道序號。當 ImageArray 包含多張圖片時,通道序號依次遞增,比如第一張包含3個通道,序號為0、1、2;第二張也包含3個通道,序號為3、4、5,以此類推。 channels 只包含一個通道序號時,代表最常用的一維直方圖;當包含多個通道序號時,則是多維直方圖。 |
histSize | int 數組,指定每個通道(維度)的直方圖數量 |
ranges | float 數組,指定每個通道中像素強度(灰度)值的統計范圍。 當 uniform 為真,ranges 每通道只需兩個數值,即(總下限,總上限),將自動平均分割成 histSize 指定的份數; 當 uniform 為假,ranges 每通道需手動分割,如(h0,h1,h2,h3)代表三個范圍 h0-h1,h1-h2,h2-h3 ; 以上的每個范圍都遵循 “包含左端,不包含右端” 的原則,即左閉右開。 |
uniform | 布爾值,均一化標志,其功能如上面 ranges 所述。 |
accumulate | 布爾值,累加標志。 當 accumulate 為假,函數左端傳入的 Hist 將被清空后,再寫入本次直方圖統計結果; 當 accumulate 為真,函數左端傳入的 Hist 不會被清空,而是直接累加上本次直方圖統計結果。 |
Mask | Mat 類型的掩碼,可選參數,不連接代表全圖參與統計。 |
- 例2-2:灰度圖的直方圖統計,自定義非均勻分割區間
- 例2-3:將兩張彩色圖片的 R通道 直方圖累加在一起
- 通道順序是BGR,所以兩張圖片R通道的序號分別是2和5;
- 第一次 calcHist 的 accumulate 參數是真、假都可以,因為傳入的 Hist 初始為空矩陣;
- 但第二次 calcHist 的 accumulate 參數必須為真,才能實現累加。
- 例2-4:二維直方圖范例
- channels 同時給定2個通道序號,histSize 和 ranges 指定兩組分割區間,將進行二維直方圖統計;
- 假如兩組區間數分別為 M 和 N,那么輸出的二維直方圖尺寸就是 M x N;
- 每個元素 Hist (i,j) 代表 同時滿足 “第1個通道強度值落在第1組的第 i 區間,第2個通道強度值落在第2組的第 j 區間” 的像素數;
- 不難看出,將二維直方圖 “行向累加” 成一列,就是第1個通道的一維直方圖;“列向累加” 成一行,就是第2個通道的一維直方圖。
3. 二值化
二值化的任務,是將一張灰度圖,按照設定的閾值,轉化成僅有黑白兩種顏色的單通道圖像。
OpenCV中,用 threshold 函數實現灰度圖的二值化,簡單用法如下:
- 例3-1:灰度圖二值化,閾值127,最大值255,反向模式(大于閾值時置0,小于等于閾值時置最大值)
- threshold 參數含義:
參數 | 含義 |
---|---|
srcImage | 源圖像 |
dstImage | 目標圖像,即二值化的結果 |
thresh | 閾值 |
maxval | 最大值,即二值中的較大值(最小值固定為0) |
type | 轉化類型,共 8 種,含義見下表 |
retValue(返回值) | 當 type 設為 OTSU 或 TRIANGLE 時,返回自適應的閾值;否則原樣返回 thresh 參數值。 |
– | – |
type類型 | 含義 |
THRESH_BINARY | 閾值二值化模式,大于閾值時置 maxval ,小于等于閾值時置 0 |
THRESH_BINARY_INV | 反向閾值二值化模式,大于閾值時置 0 ,小于等于閾值時置 maxval |
THRESH_TRUNC | 截斷模式,大于閾值時置 thresh ,小于等于閾值時維持原值 |
THRESH_TOZERO | 取零模式,大于閾值時維持原值,小于等于閾值時置 0 |
THRESH_TOZERO_INV | 反向取零模式,大于閾值時置 0 ,小于等于閾值時維持原值 |
THRESH_MASK | 返回掩碼(本類型已不支持,冗余殘留) |
THRESH_OTSU | OTSU 自適應 閾值, 基于區分前景和背景。通常與其他模式疊加使用 |
THRESH_TRIANGLE | TRIANGLE 自適應 閾值, 基于尋找直方圖雙峰之間的谷底。 通常與其他模式疊加使用 |
- 例3-2:灰度圖二值化,采用OTSU自動確定閾值,再按 THRESH_BINARY_INV 模式轉化
4. 腐蝕、膨脹、開閉運算
腐蝕、膨脹、開閉運算都屬于形態學操作,這些通常在二值化圖像上進行。通過特殊的濾波規則,實現二值圖中白色區域的收縮、擴張、去噪、聯通、邊緣潤滑等效果。先腐蝕后膨脹,稱為開運算;先膨脹后腐蝕,稱為閉運算。
相關函數如下表:
函數 | 功能 |
---|---|
erode | 腐蝕,白色區域收縮,孤立的小型白色區域(噪聲點)甚至完全消失 |
dilate | 膨脹, 白色區域擴張,孤立的小型黑色區域(噪聲點)甚至完全消失 |
morphology | 可實現多種形態學運算,包括腐蝕、膨脹、開運算 和 閉運算(取決于 op 參數) |
getStructuringElement | 生成結構元素(一個小型二維Mat),作為上述運算的濾波 “核” |
- 例4-1:對同一個二值圖,用 9x9 的矩形核,分別進行腐蝕、膨脹、開運算
- 參考范例:examples/Molitec/OpenCV/imgproc/imgproc_3(binary).vi
- 簡單說明參數:
anchor 是錨點,即 “核” 在圖像上移動時的參考點,默認(-1,-1)代表中心點?;
iterations 是迭代次數;
borderType 邊界像素外推的方法,默認為 BORDER_CONSTANT ,即常量填充;
borderValue 邊界填充值,默認為 64F 的最大值 1.797693134862E+308 ;
op 是 morphology 函數獨有的參數,定義操作類型。開運算 MORPH_OPEN ,閉運算 MORPH_CLOSE;
5. 梯度與輪廓
圖像梯度指的是像素強度(灰度)的變化率,可以用來確定圖像的邊緣輪廓。
OpenCV中,常用的邊緣檢測函數包括:Canny、Scharr、Sobel 和 Laplacian。
其中 Canny 是個多態VI,包含 image 輸入 和 dxdy 輸入 兩種模式。
- 例5-1:對同一個灰度圖,分別用 Canny、Scharr、Sobel 和 Laplacian 進行梯度計算和邊緣檢測
- 參考范例:examples/Molitec/OpenCV/imgproc/imgproc_4(gradient).vi
- 各函數的 params 參數說明
Canny參數 | 含義 |
---|---|
threshold1 | 低閾值,像素梯度低于這個閾值的,一定不是邊緣。 |
threshold2 | 高閾值,像素梯度高于這個閾值的,一定是邊緣,且為 “強邊緣”。 如果介于兩個閾值之間,只有與強邊緣相鄰時,才被視為邊緣的一部分。 |
apertureSize | Sobel 算子尺寸,必須取1、3、5 或 7。 默認為3,代表使用 3x3 的卷積核計算梯度。 |
L2gradient | 布爾值,為真代表使用L2范數(歐幾里得距離),為假代表使用L1范數(曼哈頓距離) |
– | – |
Scharr參數 | |
ddepth | 目標圖像深度(數據類型,如 CV_8U 等),默認為-1,代表與源圖像深度相同。 |
dx | x方向的求導階數。0表示不計算x方向導數。 |
dy | y方向的求導階數。0表示不計算y方向導數。 |
scale | 計算導數時的縮放因子,默認為1。 |
delta | 增量值,加到計算出的導數上,默認值為0。 |
borderType | 邊界類型,默認為 BORDER_DEFAULT,等同于 BORDER_REFLECT_101 |
– | – |
Sobel參數 | |
ddepth | 同上 |
dx | 同上 |
dy | 同上 |
ksize | Sobel 算子尺寸,必須取1、3、5 或 7。 |
scale | 同上 |
delta | 同上 |
borderType | 同上 |
– | – |
Laplacian參數 | |
ddepth | 同上 |
ksize | Laplacian 算子尺寸,必須是奇數。 |
scale | 同上 |
delta | 同上 |
borderType | 同上 |
- 例5-2:采用 spatialGradient 分別計算dx、dy,再用 Canny 的dxdy模式進行邊緣檢測
- 這相當于把 例5-1 的 Canny 計算過程一分為二。
在經過 Canny、Scharr、Sobel 和 Laplacian 計算之后,我們往往需要把其中的邊緣輪廓提取出來,也就是把輸出的二值圖中 “連續的白色像素” 的坐標連接在一起,形成輪廓線條。
在OpenCV中,可以使用 findContours 提取梯度二值圖中的輪廓線條。
- 例5-3:使用 Canny + findContours 計算梯度并提取輪廓,最后用 drawContours 繪制輪廓
- findContours 參數
- mode 定義輪廓檢索模式,默認 RETR_EXTERNAL 代表只檢索最外層的輪廓;
- method 定義輪廓近似方法,默認 CHAIN_APPROX_SIMPLE 代表簡單近似法;
- offset 是坐標偏移量,默認(0,0);
- 輸出 contours? 是輪廓結果,其中 pts 數組是全部點坐標序列,npts 代表每個輪廓包含的點數,ncontours 代表輪廓個數。這與之前教程(5)中 drawContours 的輸入參數定義相同;
- 輸出 hierarchy 代表輪廓的層次結構,是1個(輪廓個數 x 4)的二維數組。每行4個整數,依次代表本行索引號輪廓的 “上一個、下一個、第一個、最后一個” 輪廓的索引號。-1 表示沒有。
從上圖 hierarchy 輸出可以分析:
第0行(1,-1,-1,-1)代表0號輪廓的上一個是1號輪廓,下一個是 -1(沒有);
第1行(2,0,-1,-1)代表1號輪廓的上一個是2號輪廓,下一個是0號輪廓;
第2行(3,1,-1,-1)代表2號輪廓的上一個是3號輪廓,下一個是1號輪廓;
…
最終得出,這6個輪廓的先后順序為:5,4,3,2,1,0
6. 簡易繪圖
此內容之前已有教程,詳見:【秣厲科技】LabVIEW工具包——OpenCV 教程(5):簡易繪圖
7. 重映射
圖像重映射,指的是按照一定規則,重新排布源圖像中的像素坐標位置,并渲染到目標圖像中。
之前教程提到的 resize 就是一種重映射,用于實現圖片的縮放。
- 例7-1:使用 resize 將源圖像尺寸縮小到原來的一半
- resize 參數
- dsize:目標圖像尺寸。如果 dsize 不為0,輸出圖像尺寸以 dsize 為準;否則,將由下面的 fx、fy 計算決定;
- fx:水平方向縮放因子;
- fy:垂直方向縮放因子;
- interpolation:插值方法,指的是當放大圖像時,填補空缺的方法。默認 ?INTER_LINEAR 雙線性插值法。
下面,再介紹兩種可以扭曲源圖像形狀的重映射:仿射變換(warpAffine)和 透視變換(warpPerspective)。
- 例7-2:分別使用 warpAffine 和 warpPerspective,將圖片中一塊 “四邊形” 區域重映射成 “矩形”
- 參考范例:examples/Molitec/OpenCV/imgproc/imgproc_7(wrap).vi
- 首先,錨定源圖像四邊形的4個頂點,再給定輸出矩形的4個頂點,將這兩組頂點分別輸入到 getAffineTransform 和 getPerspectiveTransform 中,運行得到各自的變換矩陣 M;
- 接著,使用 warpAffine 配合 getAffineTransform 輸出的 M,完成仿射變換;
- 使用 warpPerspective 配合 getPerspectiveTransform 輸出的 M,完成透視變換;
如果你想完全自定義一種映射關系,即手動給定每一個像素在新圖像中的坐標位置,那么可以使用 remap 函數。
- 例7-3:使用 remap 實現圖片的水平翻轉
- remap 函數 VI 的上方需要輸入兩個 Mat 參數,名稱分別為 map1 和 map2,用于逐像素指定映射后的新坐標;
- 上述 map1 和 map2 有兩種組織方式:
其一,map1 和 map2 都是單通道矩陣,那么 map1 指定所有像素映射之后的X坐標,map2 指定Y坐標;
其二,map1 是雙通道矩陣,那么由 map1 獨自指定(X,Y)坐標,而 map2 為空矩陣 即可。 - 參考范例:examples/Molitec/OpenCV/imgproc/imgproc_6(remap).vi
順便一提,如果只是需要進行圖片翻轉、旋轉這樣常規的操作,那完全沒必要用到 remap。使用 core 模塊下的 flip 和 rotate 等函數,就完全可以做到,而且更為簡潔。
core 模塊下也有不少重映射的方法,感興趣的讀者可以試一試。
總結
- 本系列博文作為LabVIEW工具包—OpenCV的教程,將以專欄的形式陸續發布和更新。
- 對工具包感興趣的朋友,歡迎下載試用:秣厲科技 - LabVIEW工具包 - OpenCV
- 各位看官有什么想法、建議、吐槽、批評,或新奇的需求,也歡迎留言討論。