拍摄这两组两个形状的图像:
我们想要找到每组形状的边缘之间的距离,包括边缘重叠的位置。
- 首先,我们导入必要的模块:
import cv2
import numpy as np
- 为此,我们首先需要检索图像中的每个形状作为轮廓列表。在上面的特定示例中,需要检测 4 个形状。要检索每个形状,我们需要使用遮罩来遮盖除感兴趣形状颜色之外的所有颜色:
def get_masked(img, lower, upper):
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(img_hsv, np.array(lower), np.array(upper))
img_mask = cv2.bitwise_and(img, img, mask=mask)
return img_mask
lower 和 upper 参数将确定不会从图像中屏蔽掉的最小 HVS 值和最大 HSV 值。给定正确的lower 和upper 参数,您将能够提取一张只有绿色形状的图像和一张只有蓝色形状的图像:
- 使用蒙版图像,您可以继续将它们处理成更清晰的轮廓。这是
preprocess 函数,其值可以在必要时进行调整:
def get_processed(img):
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_blur = cv2.GaussianBlur(img_gray, (7, 7), 7)
img_canny = cv2.Canny(img_blur, 50, 50)
kernel = np.ones((7, 7))
img_dilate = cv2.dilate(img_canny, kernel, iterations=2)
img_erode = cv2.erode(img_dilate, kernel, iterations=2)
return img_erode
传入蒙版图像会给你
- 图像经过遮罩和处理后,即可供 opencv 检测其轮廓:
def get_contours(img):
contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
return [cnt for cnt in contours if cv2.contourArea(cnt) > 500]
return 语句中的列表推导通过指定每个轮廓的面积必须大于 500 来过滤噪声。
- 现在,我们将定义一些稍后将使用的基本函数:
def get_centeroid(cnt):
length = len(cnt)
sum_x = np.sum(cnt[..., 0])
sum_y = np.sum(cnt[..., 1])
return int(sum_x / length), int(sum_y / length)
def get_pt_at_angle(pts, pt, ang):
angles = np.rad2deg(np.arctan2(*(pt - pts).T))
angles = np.where(angles < -90, angles + 450, angles + 90)
found= np.rint(angles) == ang
if np.any(found):
return pts[found][0]
函数的名称是不言自明的;第一个返回轮廓的中心点,第二个返回给定点数组pts 中的一个点,即相对于给定点pt 的给定角度ang。 get_pt_at_angle 函数中的 np.where 用于将起始角度 0 移动到正 x 轴,因为它默认位于正 y 轴。
- 是时候定义返回距离的函数了。首先定义一下,让这五个参数可以传入:
def get_distances(img, cnt1, cnt2, center, step):
每个参数的简要说明:
-
img,图片数组
-
cnt1,第一个造型
-
cnt2,第二个造型
-
center,距离计算的起点
-
step,每个值要跳转的度数
- 定义一个字典来存储距离,以角度为键,距离为值:
angles = dict()
- 循环遍历你想检索两个形状边缘的距离的每个角度,并找到两个轮廓的坐标,即迭代的 ct 角度,
angle,相对于原点,@987654358 @,使用我们之前定义的get_pt_at_angle 函数。
for angle in range(0, 360, step):
pt1 = get_pt_at_angle(cnt1, center, angle)
pt2 = get_pt_at_angle(cnt2, center, angle)
- 检查两个轮廓中是否存在与原点成特定角度的点:
if np.any(pt1) and np.any(pt2):
- 您可以使用
np.linalg.norm 方法获取两点之间的距离。我还让它绘制了文本和连接线以进行可视化。不要忘记将角度和值添加到angles 字典中,然后您可以跳出内部for 循环。在函数的最后,返回上面绘制了文本和线条的图像:
d = round(np.linalg.norm(pt1 - pt2))
cv2.putText(img, str(d), tuple(pt1), cv2.FONT_HERSHEY_PLAIN, 0.8, (0, 0, 0))
cv2.drawContours(img, np.array([[center, pt1]]), -1, (255, 0, 255), 1)
angles[angle] = d
return img, angles
- 最后,您可以使用在图像上定义的函数:
img = cv2.imread("shapes1.png")
img_green = get_masked(img, [10, 0, 0], [70, 255, 255])
img_blue = get_masked(img, [70, 0, 0], [179, 255, 255])
img_green_processed = get_processed(img_green)
img_blue_processed = get_processed(img_blue)
img_green_contours = get_contours(img_green_processed)
img_blue_contours = get_contours(img_blue_processed)
使用四个形状的图像,您可以看出img_green_contours 和img_blue_contours 将分别包含两个轮廓。但是您可能想知道:我是如何选择最小和最大 HSV 值的?好吧,我使用了跟踪条码。您可以运行以下代码,使用轨迹栏调整 HSV 值,直到找到图像中的所有内容都被遮盖(黑色)的范围,除了您要检索的形状:
import cv2
import numpy as np
def empty(a):
pass
cv2.namedWindow("TrackBars")
cv2.createTrackbar("Hue Min", "TrackBars", 0, 179, empty)
cv2.createTrackbar("Hue Max", "TrackBars", 179, 179, empty)
cv2.createTrackbar("Sat Min", "TrackBars", 0, 255, empty)
cv2.createTrackbar("Sat Max", "TrackBars", 255, 255, empty)
cv2.createTrackbar("Val Min", "TrackBars", 0, 255, empty)
cv2.createTrackbar("Val Max", "TrackBars", 255, 255, empty)
img = cv2.imread("shapes0.png")
while True:
h_min = cv2.getTrackbarPos("Hue Min", "TrackBars")
h_max = cv2.getTrackbarPos("Hue Max", "TrackBars")
s_min = cv2.getTrackbarPos("Sat Min", "TrackBars")
s_max = cv2.getTrackbarPos("Sat Max", "TrackBars")
v_min = cv2.getTrackbarPos("Val Min", "TrackBars")
v_max = cv2.getTrackbarPos("Val Max", "TrackBars")
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
lower = np.array([h_min, s_min, v_min])
upper = np.array([h_max, s_max, v_max])
mask = cv2.inRange(img_hsv, lower, upper)
img_masked = cv2.bitwise_and(img, img, mask=mask)
cv2.imshow("Image", img_masked)
if cv2.waitKey(1) & 0xFF == ord("q"): # If you press the q key
break
根据我选择的值,我得到:
- 平行循环通过蓝色形状轮廓和绿色形状轮廓,根据您希望原点位于哪个颜色形状的中心,您可以将该颜色轮廓传递给我们之前定义的
get_centeroid函数:
for cnt_blue, cnt_green in zip(img_blue_contours, img_green_contours[::-1]):
center = get_centeroid(cnt_blue)
img, angles = get_distances(img, cnt_green.squeeze(), cnt_blue.squeeze(), center, 30)
print(angles)
请注意,我使用30 作为步骤;这个数字可以改成4,我用30这样可视化会更清晰。
- 最后,我们可以显示图像了:
cv2.imshow("Image", img)
cv2.waitKey(0)
总共:
import cv2
import numpy as np
def get_masked(img, lower, upper):
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(img_hsv, np.array(lower), np.array(upper))
img_mask = cv2.bitwise_and(img, img, mask=mask)
return img_mask
def get_processed(img):
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_blur = cv2.GaussianBlur(img_gray, (7, 7), 7)
img_canny = cv2.Canny(img_blur, 50, 50)
kernel = np.ones((7, 7))
img_dilate = cv2.dilate(img_canny, kernel, iterations=2)
img_erode = cv2.erode(img_dilate, kernel, iterations=2)
return img_erode
def get_contours(img):
contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
return [cnt for cnt in contours if cv2.contourArea(cnt) > 500]
def get_centeroid(cnt):
length = len(cnt)
sum_x = np.sum(cnt[..., 0])
sum_y = np.sum(cnt[..., 1])
return int(sum_x / length), int(sum_y / length)
def get_pt_at_angle(pts, pt, ang):
angles = np.rad2deg(np.arctan2(*(pt - pts).T))
angles = np.where(angles < -90, angles + 450, angles + 90)
found= np.rint(angles) == ang
if np.any(found):
return pts[found][0]
def get_distances(img, cnt1, cnt2, center, step):
angles = dict()
for angle in range(0, 360, step):
pt1 = get_pt_at_angle(cnt1, center, angle)
pt2 = get_pt_at_angle(cnt2, center, angle)
if np.any(pt1) and np.any(pt2):
d = round(np.linalg.norm(pt1 - pt2))
cv2.putText(img, str(d), tuple(pt1), cv2.FONT_HERSHEY_PLAIN, 0.8, (0, 0, 0))
cv2.drawContours(img, np.array([[center, pt1]]), -1, (255, 0, 255), 1)
angles[angle] = d
return img, angles
img = cv2.imread("shapes1.png")
img_green = get_masked(img, [10, 0, 0], [70, 255, 255])
img_blue = get_masked(img, [70, 0, 0], [179, 255, 255])
img_green_processed = get_processed(img_green)
img_blue_processed = get_processed(img_blue)
img_green_contours = get_contours(img_green_processed)
img_blue_contours = get_contours(img_blue_processed)
for cnt_blue, cnt_green in zip(img_blue_contours, img_green_contours[::-1]):
center = get_centeroid(cnt_blue)
img, angles = get_distances(img, cnt_green.squeeze(), cnt_blue.squeeze(), center, 30)
print(angles)
cv2.imshow("Image", img)
cv2.waitKey(0)
输出:
{0: 5, 30: 4, 60: 29, 90: 25, 120: 31, 150: 8, 180: 5, 210: 7, 240: 14, 270: 12, 300: 14, 330: 21}
{0: 10, 30: 9, 60: 6, 90: 0, 120: 11, 150: 7, 180: 5, 210: 6, 240: 6, 270: 4, 300: 0, 330: 16}
注意:对于某些形状,字典中可能不存在某些角度。这将是由process 函数引起的;如果你调低一些值,你会得到更准确的结果,比如模糊 sigma