我有一些 OpenCV 经验,但在检测下图中的轮廓时确实遇到问题。 我能够检测到显示的一些黑色圆圈,但这些是传感器上的污垢,我真的想检测图像中的轨迹,如第二张图像中的红色轮廓所示。
我正在尝试使用 Canny 边缘检测和
cv.ADAPTIVE_THRESH_GAUSSIAN_C
,它给出了最好的结果,但很难获得正确的参数,并且结果因图像而异。 我还尝试过使用 cv.fastNlMeansDenoising
去噪,以及 Canny 边缘检测之前的高斯模糊,但结果并没有太大改善。
检测跌落相对容易,但检测轨迹则不然。
我可能必须写一些东西来尝试通过逐行扫描图像来检测轨道,但这看起来很笨拙,而且我不知道如何开始。 我正在使用 OpenCV 和 Python。
有人有什么建议吗?
我不确定是否应该将此作为答案发布。这很大程度上是从我的答案中复制的(从噪声图像中检测线)。基本思想与在图像上卷积线核以突出显示线相同。如果重复太多,我可以删除这个答案,只留下链接回另一个答案的评论。
有三个可调整的参数:
当前设置在图像中获得两个额外的小点,如果需要,可以按大小(opencv 轮廓区域)过滤掉这些点。
import cv2
import math
import numpy as np
# returns integer point positions with 0.5 ties-to-even rule
def intify(point):
point[0] = round(point[0]);
point[1] = round(point[1]);
return point
# translate 2d point in-place
def translate2D(point, shift, sign=1):
x = point[0] + (shift[0] * sign)
y = point[1] + (shift[1] * sign)
point[0] = x
point[1] = y
# rotate a 2d point in-place
def rotate2D(point, angle_deg, anchor=[0,0]):
rads = math.radians(angle_deg);
translate2D(point, anchor, sign=-1)
x,y = point
rx = x * math.cos(rads) - y * math.sin(rads)
ry = x * math.sin(rads) + y * math.cos(rads)
point[0] = rx
point[1] = ry
translate2D(point, anchor, sign=1)
# moves a point to a new origin frame
def pointToFrame(point, pos, angle, no_round=False):
rotate2D(point, angle)
translate2D(point, pos)
if not no_round:
intify(point)
# rescale image
def scale(img, scale_factor):
h,w = img.shape[:2];
h = int(h*scale_factor);
w = int(w*scale_factor);
return cv2.resize(img, (w,h));
# chops padding
def unpad(img, padsize):
h,w = img.shape[:2];
img = img[padsize:h-padsize, padsize:w-padsize];
return img;
def build_kernel(angle, ksize, width):
kernel = np.zeros((ksize,ksize), np.float32)
# get line endpoints
middle = int(ksize / 2)
center = [middle, middle]
start = [-ksize, 0]
end = [ksize, 0]
pointToFrame(start, center, angle)
pointToFrame(end, center, angle)
# draw the masking line
kernel = cv2.line(kernel, start, end, 1, width)
kernel /= np.sum(kernel) # normalize
return kernel
# load image
filepath = "deleteme_noisy_img.png";
img = cv2.imread(filepath, cv2.IMREAD_GRAYSCALE)
orig_height, orig_width = img.shape[:2]
# pad to avoid edge dropoff
pad = int(orig_width/2)
img = cv2.copyMakeBorder(img, pad, pad, pad, pad, cv2.BORDER_REFLECT, None)
# get mask
_, mask = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
height,width = mask.shape
# background canvas
canvas = np.zeros_like(mask)
# tweakable settings
klength = 71
kwidth = 5
threshold = 180
# draw line
angle = 0
filenum = 0
while angle < 180:
# create a line kernel
kernel = build_kernel(angle, klength, kwidth)
angle += 1;
# convolve
conv = cv2.filter2D(mask, -1, kernel, borderType=cv2.BORDER_CONSTANT)
# paint onto canvas
_, linemask = cv2.threshold(conv, threshold, 255, cv2.THRESH_BINARY) # tweakable sensitivity setting
canvas = cv2.bitwise_or(canvas, linemask)
# chop off padding
display_canvas = unpad(canvas, pad)
display_conv = unpad(conv, pad)
# stack the images
sbs = np.hstack((display_canvas, display_conv))
sbs = scale(sbs, 0.5)
# show
cv2.imshow("side-by-side", sbs)
key = cv2.waitKey(1)
if key == ord('q'):
break
# save to folder for gif
# filename = "images/" + str(filenum).zfill(5) + ".png"
# cv2.imwrite(filename, sbs)
# filenum += 1
cv2.destroyAllWindows()
# draw on image
display_canvas = unpad(canvas, pad)
orig_img = cv2.imread(filepath)
orig_img[display_canvas == 255] = (0,0,200)
# show image
cv2.imshow("Image", orig_img)
cv2.imshow("Canvas", display_canvas)
# cv2.imwrite("red.png", orig_img)
cv2.waitKey(0)