我想检测直线上的圆形腐蚀和膨胀。对于膨胀,我尝试递归腐蚀图像,并且在每次递归时,我检查宽度/高度的宽高比。如果该比率小于4,则假定其轮廓为圆形,并针对每个此类轮廓从力矩和面积计算出圆心和半径。这是检测圆形膨胀的函数:
def detect_circular_dilations(img, contours):
contours_current, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
if len(contours_current) == 0:
return get_circles_from_contours(contours)
for c in contours_current:
x, y, w, h = cv2.boundingRect(c)
if w > h:
aspect_ratio = float(w) / h
else:
aspect_ratio = float(h) / w
if aspect_ratio < 4 and w < 20 and h < 20 and w > 5 and h > 5:
contours.append(c)
return detect_circular_dilations(cv2.erode(img, None, iterations=1), contours)
我要检测的圆形膨胀示例如下:
我还没有解决的另一个问题是圆形腐蚀的检测。圆形腐蚀的例子如下:
在这里,我用红色矩形标记了要检测的圆形腐蚀。可能会有一些较小的圆形图案(在左侧)不应该视为实际的圆形侵蚀。
有人知道检测这种圆形的最佳方法是什么?对于循环膨胀,我将不胜感激任何评论/建议,以便有可能使检测更加可靠。
谢谢!
答案 0 :(得分:1)
我要尝试的是用cv2.Canny()
找到线的两个边缘并搜索轮廓。如果按照轮廓线的宽度对轮廓进行排序,则前两个轮廓将是线条的边缘。之后,您可以计算一个边缘中每个点到另一边缘的最小距离。然后,您可以计算距离的中位数,并说,如果某个点的距离大于或小于该中位数(+/-公差),则该点的扩张或腐蚀会导致该线的扩张或腐蚀并将其附加到列表中。您可以根据需要在列表中进行排序,以消除噪音;如果噪音点不连续(在x轴上),则可以删除噪音点。
这是一个简单的例子:
import cv2
import numpy as np
from scipy import spatial
def detect_dilation(median, mindist, tolerance):
count = 0
for i in mindist:
if i > median + tolerance:
dilate.append((reshape_e1[count][0], reshape_e1[count][1]))
elif i < median - tolerance:
erode.append((reshape_e1[count][0], reshape_e1[count][1]))
else:
pass
count+=1
def other_axis(dilate, cnt):
temp = []
for i in dilate:
temp.append(i[0])
for i in cnt:
if i[0] in temp:
dilate.append((i[0],i[1]))
img = cv2.imread('1.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray,100,200)
_, contours, hierarchy = cv2.findContours(edges,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
contours.sort(key= lambda cnt :cv2.boundingRect(cnt)[3])
edge_1 = contours[0]
edge_2 = contours[1]
reshape_e1 = np.reshape(edge_1, (-1,2))
reshape_e2 =np.reshape(edge_2, (-1,2))
tree = spatial.cKDTree(reshape_e2)
mindist, minid = tree.query(reshape_e1)
median = np.median(mindist)
dilate = []
erode = []
detect_dilation(median,mindist,5)
other_axis(dilate, reshape_e2)
other_axis(erode, reshape_e2)
dilate = np.array(dilate).reshape((-1,1,2)).astype(np.int32)
erode = np.array(erode).reshape((-1,1,2)).astype(np.int32)
x,y,w,h = cv2.boundingRect(dilate)
cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
x,y,w,h = cv2.boundingRect(erode)
cv2.rectangle(img,(x,y),(x+w,y+h),(0,0,255),2)
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
结果:
编辑:
如果图片的折线(意味着更多的轮廓),则必须将每个轮廓视为单独的线。您可以在cv2.boundingRect()
的帮助下建立一个感兴趣的区域来实现这一目标。但是,当我尝试使用新上传的图片进行处理时,该过程并不十分可靠,因为您必须更改公差才能获得所需的结果。由于我不知道其他图像是什么样子,因此您可能需要一种更好的方法来获取平均距离和公差系数。这里的任何方式都是我所描述的示例(公差为15):
import cv2
import numpy as np
from scipy import spatial
def detect_dilation(median, mindist, tolerance):
count = 0
for i in mindist:
if i > median + tolerance:
dilate.append((reshape_e1[count][0], reshape_e1[count][1]))
elif i < median - tolerance:
erode.append((reshape_e1[count][0], reshape_e1[count][1]))
else:
pass
count+=1
def other_axis(dilate, cnt):
temp = []
for i in dilate:
temp.append(i[0])
for i in cnt:
if i[0] in temp:
dilate.append((i[0],i[1]))
img = cv2.imread('2.jpg')
gray_original = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh_original = cv2.threshold(gray_original, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
# Filling holes
_, contours, hierarchy = cv2.findContours(thresh_original,cv2.RETR_CCOMP,cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
cv2.drawContours(thresh_original,[cnt],0,255,-1)
_, contours, hierarchy = cv2.findContours(thresh_original,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
for cnt in contours:
x2,y,w2,h = cv2.boundingRect(cnt)
thresh = thresh_original[0:img.shape[:2][1], x2+20:x2+w2-20] # Region of interest for every "line"
edges = cv2.Canny(thresh,100,200)
_, contours, hierarchy = cv2.findContours(edges,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
contours.sort(key= lambda cnt: cv2.boundingRect(cnt)[3])
edge_1 = contours[0]
edge_2 = contours[1]
reshape_e1 = np.reshape(edge_1, (-1,2))
reshape_e2 =np.reshape(edge_2, (-1,2))
tree = spatial.cKDTree(reshape_e2)
mindist, minid = tree.query(reshape_e1)
median = np.median(mindist)
dilate = []
erode = []
detect_dilation(median,mindist,15)
other_axis(dilate, reshape_e2)
other_axis(erode, reshape_e2)
dilate = np.array(dilate).reshape((-1,1,2)).astype(np.int32)
erode = np.array(erode).reshape((-1,1,2)).astype(np.int32)
x,y,w,h = cv2.boundingRect(dilate)
if len(dilate) > 0:
cv2.rectangle(img[0:img.shape[:2][1], x2+20:x2+w2-20],(x,y),(x+w,y+h),(255,0,0),2)
x,y,w,h = cv2.boundingRect(erode)
if len(erode) > 0:
cv2.rectangle(img[0:img.shape[:2][1], x2+20:x2+w2-20],(x,y),(x+w,y+h),(0,0,255),2)
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
结果:
答案 1 :(得分:0)
通常使用距离变换和中间轴变换来解决此类问题。这些以某种方式相关,因为中间轴沿着距离变换的脊延伸。总体思路是:
计算图像的距离变换(对于每个前景像素,将距离返回到最近的背景像素;某些库以另一种方式实现此方式,在这种情况下,您需要计算反转图像的距离变换)。
计算内侧轴(或骨架)。
沿中间轴的距离变换的值是相关值,我们忽略所有其他像素。在这里,我们看到了线的局部半径。
局部最大值是扩张的质心。使用阈值来确定其中哪些是重要的扩张,哪些不是重要的(嘈杂的轮廓会导致许多局部最大值)。
局部最小值是侵蚀的质心。
例如,我使用下面的MATLAB代码获得以下输出。
这是我使用的代码。它与DIPimage 3一起使用MATLAB,作为原理的快速证明。使用您想使用的任何图像处理库,将其直接转换为Python应该很简单。
% Read in image and remove the red markup:
img = readim('https://i.stack.imgur.com/bNOTn.jpg');
img = img{3}>100;
img = closing(img,5);
% This is the algorithm described above:
img = fillholes(img); % Get rid of holes
radius = dt(img); % Distance transform
m = bskeleton(img); % Medial axis
radius(~m) = 0; % Ignore all pixels outside the medial axis
detection = dilation(radius,25)==radius & radius>25; % Local maxima with radius > 25
pos = findcoord(detection); % Coordinates of detections
radius = double(radius(detection)); % Radii of detections
% This is just to make the markup:
detection = newim(img,'bin');
for ii=1:numel(radius)
detection = drawshape(detection,2*radius(ii),pos(ii,:),'disk');
end
overlay(img,detection)