Python + Opencv2 實現輪廓提取、顏色標記,區域面積計算!

做圖像處理時,會遇到這樣一個場景:找到圖像主體輪廓,這是其一,可能爲了凸顯輪廓,需要用指定的顏色進行標記;輪廓標記完可能任務還沒有結束,還需對輪廓所勾勒的像素面積區域統計計算。

本篇文章的主要內容就是要解決上面場景遇到的三個問問題

  • 找到圖像主題輪廓;
  • 用指定顏色對源圖像進行輪廓標記;
  • 計算輪廓中的主體;

實驗環境配置爲 Python + Opencv 3.4, 處理的圖像如下:

images.jpg

第一步,提取輪廓,Opencv 中的 findContours() 函數 可以直接提取輪廓,但對輸入圖像有一定要求

  • 一,輸入的圖像必須是單通道,三通道不允許;
  • 二,輸入的圖像數據類型需是 8UC1;否則程序會報錯的,報錯信息如下:
error: (-210) [start]FindContours supports only CV_8UC1 images when mode != CV_RETR_FLOODFILL otherwise supports CV_32SC1 images only in function cvStartFindContours_Impl

解決方法,在讀取時加入下面這行代碼進行數據格式轉換,同時解決上面兩個問題:

mat_img2 = cv2.imread(img_path,cv2.CV_8UC1)
  • 三、輸入的圖像背景需是黑色的,否則輪廓提取失敗,就以本次圖像爲例,如果直接提取效果如下:

1.jpg

圖片最外層是一層黑色部分,所以最終結果就是標記最外層;對這類背景非黑色的圖片做輪廓提取時,需要進行預處理:把背景變爲黑色

提供一個簡單辦法,閾值化處理:設定一個閾值 Threshold 和一個指定值 OutsideValue ,當圖像中像素滿足某種條件(大於或小於設定的閾值時),像素值發生變化

自適應閾值化分割

這裏用到的是 Opencv 提供的自適應閾值分割算法,其函數格式爲:

dst=cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C)

  • src 需要分割的圖像( adarray 類型);

  • maxValue ,滿足條件是替換的像素值,等價於上面提到的 OutsideValue;

  • adaptiveMetheod: 自適應閾值分割算法,Opencv 中提供兩種方法

1,ADAPTIVE_THRESH_MEAN_C : 最後的像素值 T(x,y)T(x,y) 爲原像素值 (x,y)(x,y) blocksizeblocksizeblocksize*blocksize 區域像素的平均值 CC;

2,ADAPTIVE_THRESH_GAUSSIAN_C : 最後像素值 T(x,y)T(x,y) 爲原像素值 (x,y)(x,y) 附近 blocksizeblocksizeblocksize*blocksize 區域大小最小值 CC;

  • thresholdType 閾值分割方法,Opencv 提供了5 種;

1,THRESH_BINARY:
dst(x,y)={maxvalif src(x,y)>thresh0otherwise dst(x,y) = \left\{ \begin{aligned} maxval & &if\ src(x,y)>thresh\\ 0 & & otherwise\\ \end{aligned} \right.
2,THRESH_BINARY_INV:
dst(x,y)={0if src(x,y)>threshmaxvalotherwise dst(x,y) = \left\{ \begin{aligned} 0 & &if\ src(x,y)>thresh\\ maxval & & otherwise\\ \end{aligned} \right.
3,THRESH_TRUNC:
dst(x,y)={thresholdif src(x,y)>threshsrc(x,y)otherwise dst(x,y)=\left\{ \begin{aligned} threshold & & if\ src(x,y)>thresh\\ src(x,y)& &otherwise\\ \end{aligned} \right.
4,THRESH_TOZERO:
dst(x,y)={src(x,y)if src(x,y)>thresh0otherwise dst(x,y)=\left\{\begin{aligned}src(x,y) & & if\ src(x,y)>thresh\\0& &otherwise\\\end{aligned}\right.
5,THRESH_TOZERO_INV;
dst(x,y)={0if src(x,y)>threshsrc(x,y)otherwise dst(x,y)=\left\{\begin{aligned}0 & & if\ src(x,y)>thresh\\src(x,y)& &otherwise\\\end{aligned}\right.

  • dst : 返回的閾值分割圖像(是 ndarray 類型)

下面這行代碼就是本次實驗設置的參數:

dst = cv2.adaptiveThreshold(mat_img2,210,cv2.BORDER_REPLICATE,cv2.THRESH_BINARY_INV,3,10)

自適應閾值分割的結果:

Snipaste_2020-05-09_23-49-22.jpg

輪廓提取

接下來就是進行輪廓提取了,用到的函數:

image, contours, hierarchy=cv2.findContours(image, mode, method)

  • image 返回的圖像,在 Opencv 4.0 之後就沒有這個參數了;
  • contours 標記的輪廓,以 list 形式存在,每個輪廓中都包含了輪廓像素的座標向量;
  • hierarchy 表示輪廓的繼承關係,一般用不到;d
  • image 後面image 表示需要標記輪廓的圖像,以 ndarray 格式存在;
  • mode 標記輪廓的模式,Opencv 提供了4種;

1,RETR_EXTERNAL;只提取整體外部輪廓;

2,RETR_LIST; 提取所有輪廓,不需要建立任何繼承關係;

3, RETR_CCOMP ;提取所有輪廓,最後形成連個水平集,外面一個,內部一個;

4, RETR_TREE ;提取所有輪廓,構建等級關係(父子繼承關係)

  • method :輪廓近似點連接方式,例如一個長方形,可以由數百個點連接而成,單節省內存的方式就是找到四個角點即可;
    • 其中前者爲 CHAIN_APPROX_NONE 後者爲 CHAIN_APPROX_SIMPLE

這裏分別對 mode 設置不同的參數,一個設爲 RETR_TREE (提取全部輪廓),一個設置 RETR_EXTRENAL (只提取最外部輪廓 );可以看一下提取輪廓效果:

RETR_TREE 結果:

Snipaste_2020-05-10_00-15-26.jpg

RETR_EXTRENAL 結果:

Snipaste_2020-05-10_00-14-54.jpg

是不是感受到了mode 不同導致輪廓的差距;一般只提取一個輪廓用 RETR_EXTRENAL,多個的話用 RETR_TREE;

輪廓標記

對輪廓顏色繪製,用到 的函數

cv2.drawContours(image, contours, contourIdx, color,thickness)

  • image 繪製輪廓的圖像 ndarray 格式;

  • contours ,findContours 函數找到的輪廓列表;

  • contourIdx 繪製輪廓的索引數,取整數時繪製特定索引的輪廓,爲負值時,繪製全部輪廓;

  • color 繪製輪廓所用到的顏色,這裏需要提醒一下, 想使用 RGB 彩色繪製時,必須保證 輸入的 image 爲三通道,否則輪廓線非黑即白;

  • thickness ,用於繪製輪廓線條的寬度,取負值時將繪製整個輪廓區域;

以下就是分別取 thickness 爲3(左)、-3(右) 繪製的結果

Snipaste_2020-05-10_00-40-26.jpg

輪廓區域面積計算

最後計算輪廓面積,用到 cv2.contourArea(contour) 函數,裏面的參數指的就是計算的輪廓

area = 0
for i in contours:
    area += cv2.contourArea(i)
print(area)


>>>16397.5 #最後結果

本篇文章用到的完整代碼如下:

import cv2

img_path = "E:/data_ceshi/images.jpg"
#讀取文件
mat_img = cv2.imread(img_path)
mat_img2 = cv2.imread(img_path,cv2.CV_8UC1)

#自適應分割
dst = cv2.adaptiveThreshold(mat_img2,210,cv2.BORDER_REPLICATE,cv2.THRESH_BINARY_INV,3,10)
#提取輪廓
img,contours,heridency = cv2.findContours(dst,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
#標記輪廓
cv2.drawContours(mat_img,contours,-1,(255,0,255),3)

#計算輪廓面積
area = 0
for i in contours:
    area += cv2.contourArea(i)
print(area)

#圖像show
cv2.imshow("window1",mat_img)
cv2.waitKey(0)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章