[英]OpenCV detect blobs on the image
我需要找到(並在周圍繪制矩形)/獲取圖像上的最大和最小半徑斑點。 (以下樣本)
問題是為圖像找到正確的過濾器,以允許Canny
或Threshold
轉換來突出顯示斑點。 然后我將使用findContours
來查找矩形。
我試過:
Threshold
- 不同級別
blur->erode->erode->grayscale->canny
用各種“線條”改變圖像色調
等等。 更好的結果是檢測到一塊 (20-30%) 的斑點。 並且此信息不允許在 blob 周圍繪制矩形。 此外,感謝陰影,檢測到與斑點無關的陰影,因此也可以防止檢測該區域。
據我了解,我需要找到具有強烈對比度的計數器(不像陰影中那樣平滑)。 有沒有辦法用openCV做到這一點?
更新
分情況:圖1 、圖2 、圖3 、圖4 、圖5 、圖6 、圖7 、圖8 、圖9 、圖10 、圖11 、圖12
又一更新
我相信斑點在邊緣有對比區域。 所以,我試圖讓邊緣更強大:我創建了 2 個gray scale Mat: A and B
,為第二個應用Gaussian blur
- B
(減少一點噪音) ,然后我做了一些計算:去圍繞每個像素並找到“A”的Xi,Yi
和來自“B”的附近點之間的最大差異:
並將max
差異應用於Xi,Yi
。 所以我得到了這樣的東西:
我在正確的路上嗎? 順便說一句,我可以通過 OpenCV 方法達到這樣的目的嗎?
更新圖像去噪有助於減少噪音, Sobel
- 突出顯示輪廓,然后threshold
+ findContours
和custome convexHull
與我正在尋找的相似,但它對某些斑點不利。
由於輸入圖像之間存在較大差異,算法應該能夠適應這種情況。 由於 Canny 基於檢測高頻,我的算法將圖像的清晰度視為用於預處理自適應的參數。 我不想花一周時間找出所有數據的函數,所以我基於 2 張圖像應用了一個簡單的線性函數,然后用第三張圖像進行了測試。 這是我的結果:
請記住,這是一種非常基本的方法,只是證明了一點。 它將需要實驗、測試和改進。 這個想法是使用 Sobel 並對獲取的所有像素求和。 那個,除以圖像的大小,應該給你一個高頻的基本估計。 圖像的反應。 現在,通過實驗,我發現 CLAHE 濾波器的 clipLimit 值在 2 個測試用例中起作用,並發現了一個連接高頻的線性函數。 使用 CLAHE 濾波器響應輸入,產生良好的結果。
sobel = get_sobel(img)
clip_limit = (-2.556) * np.sum(sobel)/(img.shape[0] * img.shape[1]) + 26.557
這就是自適應部分。 現在是輪廓。 我花了一段時間才找到過濾噪音的正確方法。 我解決了一個簡單的技巧:使用兩次輪廓查找。 首先,我用它來過濾掉不必要的、嘈雜的輪廓。 然后我繼續使用一些形態魔法,最終為被檢測的對象生成正確的 blob(代碼中的更多細節)。 最后一步是根據計算的平均值過濾邊界矩形,因為在所有樣本上,斑點的大小相對相似。
import cv2
import numpy as np
def unsharp_mask(img, blur_size = (5,5), imgWeight = 1.5, gaussianWeight = -0.5):
gaussian = cv2.GaussianBlur(img, (5,5), 0)
return cv2.addWeighted(img, imgWeight, gaussian, gaussianWeight, 0)
def smoother_edges(img, first_blur_size, second_blur_size = (5,5), imgWeight = 1.5, gaussianWeight = -0.5):
img = cv2.GaussianBlur(img, first_blur_size, 0)
return unsharp_mask(img, second_blur_size, imgWeight, gaussianWeight)
def close_image(img, size = (5,5)):
kernel = np.ones(size, np.uint8)
return cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
def open_image(img, size = (5,5)):
kernel = np.ones(size, np.uint8)
return cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
def shrink_rect(rect, scale = 0.8):
center, (width, height), angle = rect
width = width * scale
height = height * scale
rect = center, (width, height), angle
return rect
def clahe(img, clip_limit = 2.0):
clahe = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=(5,5))
return clahe.apply(img)
def get_sobel(img, size = -1):
sobelx64f = cv2.Sobel(img,cv2.CV_64F,2,0,size)
abs_sobel64f = np.absolute(sobelx64f)
return np.uint8(abs_sobel64f)
img = cv2.imread("blobs4.jpg")
# save color copy for visualizing
imgc = img.copy()
# resize image to make the analytics easier (a form of filtering)
resize_times = 5
img = cv2.resize(img, None, img, fx = 1 / resize_times, fy = 1 / resize_times)
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# use sobel operator to evaluate high frequencies
sobel = get_sobel(img)
# experimentally calculated function - needs refining
clip_limit = (-2.556) * np.sum(sobel)/(img.shape[0] * img.shape[1]) + 26.557
# don't apply clahe if there is enough high freq to find blobs
if(clip_limit < 1.0):
clip_limit = 0.1
# limit clahe if there's not enough details - needs more tests
if(clip_limit > 8.0):
clip_limit = 8
# apply clahe and unsharp mask to improve high frequencies as much as possible
img = clahe(img, clip_limit)
img = unsharp_mask(img)
# filter the image to ensure edge continuity and perform Canny
# (values selected experimentally, using trackbars)
img_blurred = (cv2.GaussianBlur(img.copy(), (2*2+1,2*2+1), 0))
canny = cv2.Canny(img_blurred, 35, 95)
# find first contours
_, cnts, _ = cv2.findContours(canny.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
# prepare black image to draw contours
canvas = np.ones(img.shape, np.uint8)
for c in cnts:
l = cv2.arcLength(c, False)
x,y,w,h = cv2.boundingRect(c)
aspect_ratio = float(w)/h
# filter "bad" contours (values selected experimentally)
if l > 500:
continue
if l < 20:
continue
if aspect_ratio < 0.2:
continue
if aspect_ratio > 5:
continue
if l > 150 and (aspect_ratio > 10 or aspect_ratio < 0.1):
continue
# draw all the other contours
cv2.drawContours(canvas, [c], -1, (255, 255, 255), 2)
# perform closing and blurring, to close the gaps
canvas = close_image(canvas, (7,7))
img_blurred = cv2.GaussianBlur(canvas, (8*2+1,8*2+1), 0)
# smooth the edges a bit to make sure canny will find continuous edges
img_blurred = smoother_edges(img_blurred, (9,9))
kernel = np.ones((3,3), np.uint8)
# erode to make sure separate blobs are not touching each other
eroded = cv2.erode(img_blurred, kernel)
# perform necessary thresholding before Canny
_, im_th = cv2.threshold(eroded, 50, 255, cv2.THRESH_BINARY)
canny = cv2.Canny(im_th, 11, 33)
# find contours again. this time mostly the right ones
_, cnts, _ = cv2.findContours(canny.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# calculate the mean area of the contours' bounding rectangles
sum_area = 0
rect_list = []
for i,c in enumerate(cnts):
rect = cv2.minAreaRect(c)
_, (width, height), _ = rect
area = width*height
sum_area += area
rect_list.append(rect)
mean_area = sum_area / len(cnts)
# choose only rectangles that fulfill requirement:
# area > mean_area*0.6
for rect in rect_list:
_, (width, height), _ = rect
box = cv2.boxPoints(rect)
box = np.int0(box * 5)
area = width * height
if(area > mean_area*0.6):
# shrink the rectangles, since the shadows and reflections
# make the resulting rectangle a bit bigger
# the value was guessed - might need refinig
rect = shrink_rect(rect, 0.8)
box = cv2.boxPoints(rect)
box = np.int0(box * resize_times)
cv2.drawContours(imgc, [box], 0, (0,255,0),1)
# resize for visualizing purposes
imgc = cv2.resize(imgc, None, imgc, fx = 0.5, fy = 0.5)
cv2.imshow("imgc", imgc)
cv2.imwrite("result3.png", imgc)
cv2.waitKey(0)
總的來說,我認為這是一個非常有趣的問題,有點太大了,無法在這里回答。 我提出的方法應被視為路標,而不是完整的解決方案。 基本思想是:
自適應預處理。
兩次查找輪廓:用於過濾,然后用於實際分類。
根據平均大小過濾斑點。
謝謝你的樂趣和祝你好運!
這是我使用的代碼:
import cv2
from sympy import Point, Ellipse
import numpy as np
x1='C:\\Users\\Desktop\\python\\stack_over_flow\\XsXs9.png'
image = cv2.imread(x1,0)
image1 = cv2.imread(x1,1)
x,y=image.shape
median = cv2.GaussianBlur(image,(9,9),0)
median1 = cv2.GaussianBlur(image,(21,21),0)
a=median1-median
c=255-a
ret,thresh1 = cv2.threshold(c,12,255,cv2.THRESH_BINARY)
kernel=np.ones((5,5),np.uint8)
dilation = cv2.dilate(thresh1,kernel,iterations = 1)
kernel=np.ones((5,5),np.uint8)
opening = cv2.morphologyEx(dilation, cv2.MORPH_OPEN, kernel)
cv2.imwrite('D:\\test12345.jpg',opening)
ret,contours,hierarchy = cv2.findContours(opening,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
c=np.size(contours[:])
Blank_window=np.zeros([x,y,3])
Blank_window=np.uint8(Blank_window)
for u in range(0,c-1):
if (np.size(contours[u])>200):
ellipse = cv2.fitEllipse(contours[u])
(center,axes,orientation) =ellipse
majoraxis_length = max(axes)
minoraxis_length = min(axes)
eccentricity=(np.sqrt(1-(minoraxis_length/majoraxis_length)**2))
if (eccentricity<0.8):
cv2.drawContours(image1, contours, u, (255,1,255), 3)
cv2.imwrite('D:\\marked.jpg',image1)
這里的問題是找到一個接近圓形的物體。 這個簡單的解決方案基於找到每個輪廓的偏心率。 這種被檢測到的物體是水滴。
我有一個部分解決方案。
第一的
我最初將圖像轉換為 HSV 顏色空間並修改了值通道。 在這樣做時,我遇到了一些獨特的東西。 在幾乎每幅圖像中,水滴都有微小的光反射。 這在價值渠道中得到了明顯的強調。
將其反轉后,我能夠獲得以下結果:
示例 1:
示例 2:
示例 3:
第二
現在我們必須提取這些點的位置。 為此,我對獲得的反轉值通道進行了異常檢測。 我所說的異常是指它們中存在的黑點。
為了做到這一點,我計算了倒置值通道的中值。 我將中位數上下 70% 內的像素值分配為正常像素。 但是每一個超出這個范圍的像素值都是異常的。 黑點在那里完美契合。
示例 1:
示例 2:
示例 3:
對於少數圖像,結果並不理想。
如您所見,黑點是由於水滴特有的光反射造成的。 圖像中可能存在其他圓形邊緣,但反射將液滴與這些邊緣區分開來。
第三
現在既然我們有了這些黑點的位置,我們就可以進行高斯差分(DoG)(在問題的更新中也提到過)並獲得相關的邊緣信息。 如果獲得的黑點位置位於發現的邊緣內,則稱其為水滴。
免責聲明:此方法不適用於所有圖像。 您可以為此添加您的建議。
美好的一天,我正在研究這個主題,我給你的建議是; 首先,在使用了許多去噪濾波器(例如高斯濾波器)之后,再對圖像進行處理。 您可以不使用計數器對這些圓圈進行斑點檢測。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.