[171104] 基於 Python OpenCV 圖像處理的顆粒物計數(如細胞和瓜子計數)

原創內容,禁止轉載!


導讀:這是 Python OpenCV 圖像處理 專欄的第4篇文章,主要介紹了使用 Python3 OpenCV3 圖像處理進行細胞計數的思想、方法,並給出了代碼實現。關於 Python3 OpenCV3 處理的基礎知識,可以參考這篇文章 [171102] Python3 OpenCV3 圖像處理基礎(Python3 + Numpy + Matplotlib + OpenCV3 + ...)。更多內容,請查看 [171101] Python3 OpenCV3 圖像處理專欄目錄。


Update 1:

只收藏不點贊的,都是壞人。。。詛咒你找不到女盆友。。。

Update 2:

昨天使用的是閾值分割法分理處「細胞」位置,然後今天發現在 HSV 空間中使用 inRange 獲取範圍,可以等到同樣甚至更好的效果,如下圖所示。

敲黑板,我就不貼這個新方法的代碼了!哼~

python opencv cells counting 細胞計數

python opencv cells counting 細胞計數

下面是原文:


以前寫 BUG 停不下來,現在寫 水文 停不下來。誰來拯救下強迫症患者,哎。。。

上一篇介紹了「基於縮略圖哈希比較的圖像檢索技術」,這篇介紹下 細胞計數的思路和技術背景,並給出一個小栗子。

額,急性拖延症犯了,先看會動漫去。


嗯,看了熬夜一晚上動漫,睡了個懶覺,接著來吧。


使用圖像處理進行細胞計數,就是統計圖片中的細胞數量。這是一類相關的圖像處理問題,還可以用到 顆粒技術、瓜子計數等問題上。

主要用到的技術:

色彩空間變換、濾波(高斯濾波、中值濾波、形態學濾波)、二值化、查找輪廓、對輪廓計算統計量等。

大體思路是:

(1) 選取單通道:對原始圖像進行色彩空間變換,如BGR圖像變換成灰度圖,或者轉換到 HSV 或 LAB 色彩空間,然後進行通道分離,選擇合適的通道作為輸入圖像。n(2) 濾波:對輸入單通道圖像進行濾波,如高斯濾波去除正態分布雜訊、中值濾波去除椒鹽雜訊、形態學濾波填充或擴大空洞。n(3) 二值化: 對濾波後的圖像二值化,可以使用均值為閾值,或者使用 OSTU 最大化類間方差選取閾值,或者自定義閾值,然後二值化。也可以選擇邊緣檢測如 Canny 進行 "二值化"。n(4) (可選)對二值化圖像形態學濾波,填充空洞,消除小的目標。n(5) 查找二值圖像輪廓:對處理後的二值圖像查找物體輪廓,得到輪廓序列。n(6) 輪廓篩選統計:然後對輪廓篩選(如按照包圍盒面積、最小包圍盒面積、周長、長寬及其比例等條件篩選),並獲取目標統計量。n

相關的 OpenCV 函數有:

(1) 色彩空間轉換

cv2.cvtColor(src, code[, dst[, dstCn]]) -> dstn@brief 變換色彩空間n@param src: 輸入圖像注意範圍CV_8U(0~255), CV_16U(0~65535), CV_32F(0~1.0)n@param dst: 輸出圖像和輸入圖像等大小同類型n@param code: 色彩空間變換方式n cv2.COLOR_BGR2GRAYcv2.COLOR_GRAY2RBGRn cv2.COLOR_BGR2LABcv2.COLOR_BGR2HSVcv2.COLOR_BGR2YUVn

(2) 高斯濾波

cv2.GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType]]]) -> dstn@brief 高斯濾波n@param src: 輸入圖像類型是 CV_8U, CV_16U, CV_16S, CV_32F or CV_64Fn@param dst: 輸出圖像和輸入圖像等大小同類型n@param ksize: 高斯核大小以元組tuple形式表示(width, height)必須是奇數長寬可不同n@param sigmaX | sigmaY : X | Y 方向的標準差(standard deviation)n 如果 sigmaY == 0 sigmaY = sigmaXn 如果 sigmaX == sigmaY == 0則使用 ksize 分別計算n@param borderType: 邊界類型有多種類型一般可以不用管n cv2.BORDER_CONSTANT常量)、 cv2.BORDER_REFLECT鏡像)、cv2.BORDER_REPLICATE 重複n cv2.BORDER_DEFAULT默認)、 cv2.BORDER_WRAP環繞n

(3)中值濾波

cv2.medianBlur(src, ksize[, dst]) -> dstn@brief 中值濾波滑動窗內像素值排序取中值作為結果n@param src: 輸入(1|3|4)通道圖像 ksize等於3或5時類型可以是CV_8U/CV_16U/CV_32F,更大的孔徑類型只能是 CV_8Un@param dst: 輸出等大小同類型圖像n@param ksize: 奇數正方形窗函數寬度 3,5,7, ...n

(4)閾值化

cv2.threshold(src, thresh, maxval, type[, dst]) -> retval, dstn@brief 二值化n@param src: 原圖像n@param dst: ...n@param thresh: 閾值n@param maxval: 閾值化後的最大值如1或255)n@param type: 閾值化方法( cv2.THRESH_BINARY, cv2.THRESH_BINARY_INV, cv2.THRESH_OTSU)n

(5)形態學濾波

cv2.getStructingElement(shape, ksize,[, anchor]) -> retvaln@brief 獲取特定尺寸和形狀的結構基元n@param retval: 返回基元n@param shape: 結構基元形狀,如cv2.MORPH_RECT, cv2.MORPH_CROSS, cv2.ELLIPSEn@param ksize: 核大小Size(width, height)n@param anchor: 錨點位置默認為中心(-1,-1)nncv2.morphologyEx(src, op, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) -> dstn@brief 形態學濾波in-place ?)n@param src: 輸入圖像 CV_8U, CV_16U, CV_16S, CV_32F or CV_64Fn@param dst: ...n@param op: 形態學操作類型如腐蝕(cv2.MORPH_ERODE)膨脹(cv2.MORPH_DILATE)(OPEN)(CLOSE)n@param kernel: 結構基元可以用 cv2.getStructingElement()獲取 qn@param anchor: 錨點, 負值如(-1,-1)代表基元中n@param iterations: 迭代次數如兩次MORPH_OPENn 等價於 erode -> erode -> dilate -> dilaten 而不是 erode -> dilate -> erode -> dilaten@param borderType: 邊界類型n@param borderValue: 邊界值n

(6) 查找二值圖像輪廓

cv2.findContours(image, mode, method[, contours[, hierarchy[, offset]]]) -> image, contours, hierarchyn@brief 查找二值圖像中的邊緣(contours)是進行形狀分析目標檢測和識別的有力工具n@param image: 輸入二值化CV_8U圖像非零值看做1零值看做0n@param mode: 邊緣檢索模式(cv2.RETR_SIMPLE, RETR_LIST等)n@param method: 邊緣逼近方法(cv2.CHAIN_APPROX_SIMPLE, cv2.CHAIN_APPROX_NONE等)n@param contours: 檢索的邊緣結果一般這個最重要n@param hierarchy: 檢索的邊緣層次n


這是細胞原彩色圖圖:

下面是細胞計數的簡單實現結果,從左往右步驟分別是:

(1) 原圖 => 灰度圖 => 高斯濾波 => 閾值化 => 形態學濾波n(2) 原圖 =>...=> 在形態學濾波後的二值圖像查找並篩選邊緣,得到結果n

圖(1) 細胞計數 python opencv cells counting

圖(2) 細胞計數 python opencv cells counting

當使用不同的參數,可以得到不同的結果,平均結果是 85 個左右。當然不是十分準確,主要問題有漏檢、誤檢、粘連。這裡僅作為展示用,精確的計數需要根據具體情況來調節參數,或者選擇更合適的演算法。


這裡給出核心代碼供參考學習:

#!/usr/bin/python3n# 2017.11.04 11:06:29 CSTn# 2017.11.04 22:59:25 CSTn"""n[Python OpenCV 圖像處理實現細胞計數](https://zhuanlan.zhihu.com/p/30723580)n"""nnimport cv2nimport numpy as npnn## 讀取圖像nimg = cv2.imread("cells.png")nn## 灰度化ngray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)n## 高斯濾波ngauss = cv2.GaussianBlur(gray, (5,5), 0)nn## 二值化n_, threshed = cv2.threshold(gauss, 150, 255, cv2.THRESH_BINARY_INV )nn## 獲取結構基元,進行形態學濾波nkernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3,3))nmorphed = cv2.morphologyEx(threshed, cv2.MORPH_OPEN, kernel, None, (-1,-1), 1)nn## 獲取邊緣n_, cnts, _ = cv2.findContours(morphed, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)nn## 繪製ncanvas = img.copy()ncv2.drawContours(canvas,cnts, -1, (0,200,200), 1)nn## 篩選並繪製nxcnts = []nfor cnt in cnts:n area = cv2.contourArea(cnt)n x,y,w,h = cv2.boundingRect(cnt)n if area < 7 or area/(w*h) < 0.3:n continuen xcnts.append(cnt)ncv2.drawContours(canvas, xcnts, -1, (100,20,200),1)nnprint("Cells nums: {}/{}".format(len(xcnts), len(cnts)))nn## 顯示結果ncv2.imshow("src", img)ncv2.imshow("dst", canvas)ncv2.waitKey()ncv2.destroyAllWindows()n

再補充瓜子計數。

原圖:

結果:

update X:

今天見到一個測量韭菜長度的,寫了個代碼試一下,效果還不錯。把結果貼出來湊個數。

原址:知乎用戶:imageJ測量長度,顯示結果是如何換算出來的,還有結果的每列代表什麼?

沒有完結的完結,手動撒花 。。。

推薦閱讀:

1.19【OpenCV圖像處理】Canny邊緣檢測
傅立葉變換頻譜圖怎麼看?
水下攝影如何調出通透的感覺?
如此細膩柔美的布光是怎樣做到的?
利用條件GANs的pix2pix進化版:高解析度圖像合成和語義操作 | PaperDaily #23

TAG:OpenCV | 图像处理 | 计算机视觉 |