前言
当前,国家电网正在研制用于更换电力金具的机器人,最基础的功能是电力螺栓的紧固和拆卸功能,这一功能的实现依赖于视觉检测和视觉定位算法,本文仅是其中的一小部分内容。
如何确定六边形角点和中心点(形心)
假设原始图片经过图像处理变成了中间这种黑白二值图,接下来通常要做的就是霍夫线变换求出六角螺栓的六条边线。
1、概率霍夫线变换确定直线|cv2.HoughLinesP()
经过霍夫线变换后可以得到大致的六条边线(上图绿色),但是由于螺栓圆角以及拍摄视角导致的螺栓边缘畸变,六条边线并不能形成完整的封闭六边形,这样就需要我们根据霍夫线变换得到的直线来推出相邻直线之间的交点。
有一个难点是,霍夫线变换输出的直线端点坐标矩阵并不是按照直线的顺序来排列的,比如上图的直线端点矩阵为:
[[[207 229 265 136]]
[[107 234 196 237]]
[[ 21 104 77 21]]
[[101 12 187 15]]
[[ 26 143 69 224]]
[[221 38 265 120]]]
每一行代表一条直线(的两端点坐标),但行与行之间并不一定是相邻直线。在不画出图像的时候,没法提前预知哪条直线对应哪条直线,这样就会给求角点带来困难。
比如下图,line2和line3求角点(交点)时,对应的点超出了图像的范围,而且正确的角点应该是line3和line5的交点,line5和line2的交点,所以求角点之前,必须知道直线的排序。
2、确定直线顺序
霍夫线变换输出矩阵第一行对应作为line1,即确定了point1和point2,对应的line2和line3应该是line1左右两条直线,point3和point5组成line2,point4和point6组成line3,大致按上图依次找出point1,2,…12,找到对应的端点坐标,就可以找出对应的角点,比如根据point1和point2确定的line1,point3和point5确定的line2,point4和point6确定的line3就可以求出两个角点,大致按下表对应关系:
【角点1】:line1(point1,2)和line2(point3,5)交点
【角点2】:line2(point3,5)和line3(point7,9)交点
【角点3】:line3(point7,9)和line4(point11,12)交点
【角点4】:line4(point11,12)和line5(point10,8)交点
【角点5】:line5(point10,8)和line6(point6,4)交点
【角点6】:line6(point6,4)和line1(point2,1)交点
针对point1,根据距离最小原则在另外十个点中找出合理的相邻端点,可以找到point3点,point3和point5霍夫线变换输出矩阵的同一行,进而可找到point5。
而point2则是与point1是霍夫线变换输出矩阵的同一行,可以由point2找到point4,进而找到point6。
再由point5找到point7,进而找到point9,由point6找到point8,进而找到point10。
再根据point9找到point11,进而找到point12(或者根据point10找到point12,进而找到point11)。
3、确定角点(直线交点)
参考链接1(两直线求角点python程序)
参考链接2(两直线求角点理论)
4、确定中心点(形心)
中心点确定方法有两种,一种比较简单,一种比较复杂,公式如下:
A代表六边形面积,Cx,Cy代表中心点横纵坐标
【简易方法】:
【复杂方法】:
前面这个是计算面积的高斯面积公式(也叫鞋带公式)
对于复杂方法,要求凸多边形序列(原因)要按顺时针或者逆时针计算(其中有一种方式面积为负值,记得取绝对值),无论顺时针还是逆时针坐标是没问题的。
Python代码
import cv2
import numpy as np
import math
# 计算两直线交点坐标
def calc_abc_from_line_2d(x0, y0, x1, y1):
a = y0 - y1
b = x1 - x0
c = x0 * y1 - x1 * y0
return a, b, c
def get_line_cross_point(line1, line2):
a0, b0, c0 = calc_abc_from_line_2d(*line1)
a1, b1, c1 = calc_abc_from_line_2d(*line2)
D = a0 * b1 - a1 * b0
if D == 0:
return None
x = (b0 * c1 - b1 * c0) / D
y = (a1 * c0 - a0 * c1) / D
return x, y
img = cv2.imread('5.png')
cv2.imshow('origin', img) # 显示原图
print("图片高和宽:", img.shape[0], img.shape[1]) # 图片高,宽
img = img[int(131-125):int(131+130), int(149-143):int(140+141)] # 图片截取ROI,去掉边缘的线
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imshow('gray', gray) # 显示灰度图
edges = cv2.Canny(gray, 250, 255, apertureSize=5)
cv2.imshow('edges', edges) # 显示Canny边缘
lines = cv2.HoughLinesP(edges, 1, np.pi/180, 50, minLineLength=10, maxLineGap=40) # 概率霍夫线变换
for line in lines:
x1, y1, x2, y2 = line[0]
cv2.line(img, (x1, y1), (x2, y2), (0, 255, 0), 2) # 在图上画出直线
# print(lines)
# cv2.imshow('show', img)
# cv2.waitKey()
# 找出任一直线相邻最近的直线,并求出对应的交点坐标
# 6条直线,12个端点,哪两个点距离最近
point1_x = lines[0][0][0]
point1_y = lines[0][0][1]
point2_x = lines[0][0][2]
point2_y = lines[0][0][3]
distance1 = []
distance2 = []
distance3 = []
distance4 = []
distance5 = []
distance6 = []
if len(lines) == 6:
# 根据point1,point2求出point3,point4,进而求出point5,point6
for i in range(1, len(lines)):
for j in range(0, 2):
if j != 1:
distance1.append((point1_x - lines[i][0][j]) * (point1_x - lines[i][0][j]) + (point1_y - lines[i][0][j+1]) * (point1_y - lines[i][0][j+1]))
distance2.append((point2_x - lines[i][0][j]) * (point2_x - lines[i][0][j]) + (point2_y - lines[i][0][j+1]) * (point2_y - lines[i][0][j+1]))
if j != 0:
distance1.append((point1_x - lines[i][0][j+1]) * (point1_x - lines[i][0][j+1]) + (point1_y - lines[i][0][j+2]) * (point1_y - lines[i][0][j+2]))
distance2.append((point2_x - lines[i][0][j+1]) * (point2_x - lines[i][0][j+1]) + (point2_y - lines[i][0][j+2]) * (point2_y - lines[i][0][j+2]))
index1 = math.ceil((distance1.index(min(distance1)) + 1) / 2)
index1b = math.floor((distance1.index(min(distance1)) + 1) / 2)
index2 = math.ceil((distance2.index(min(distance2)) + 1) / 2)
index2b = math.floor((distance2.index(min(distance2)) + 1) / 2)
if index1 == index1b:
point3_x = lines[index1][0][2]
point3_y = lines[index1][0][3]
point5_x = lines[index1][0][0]
point5_y = lines[index1][0][1]
else:
point3_x = lines[index1][0][0]
point3_y = lines[index1][0][1]
point5_x = lines[index1][0][2]
point5_y = lines[index1][0][3]
if index2 == index2b:
point4_x = lines[index2][0][2]
point4_y = lines[index2][0][3]
point6_x = lines[index2][0][0]
point6_y = lines[index2][0][1]
else:
point4_x = lines[index2][0][0]
point4_y = lines[index2][0][1]
point6_x = lines[index2][0][2]
point6_y = lines[index2][0][3]
LINES = lines.tolist()
del LINES[0]
del LINES[index1-1]
del LINES[index2-2]
LINES = np.array(LINES)
# 根据point5,point6求出point7,point8,进而求出point9,point10
for i in range(0, len(LINES)):
for j in range(0, 2):
if j != 1:
distance3.append((point5_x - LINES[i][0][j]) * (point5_x - LINES[i][0][j]) + (point5_y - LINES[i][0][j + 1]) * (point5_y - LINES[i][0][j + 1]))
distance4.append((point6_x - LINES[i][0][j]) * (point6_x - LINES[i][0][j]) + (point6_y - LINES[i][0][j + 1]) * (point6_y - LINES[i][0][j + 1]))
if j != 0:
distance3.append((point5_x - LINES[i][0][j + 1]) * (point5_x - LINES[i][0][j + 1]) + (point5_y - LINES[i][0][j + 2]) * (point5_y - LINES[i][0][j + 2]))
distance4.append((point6_x - LINES[i][0][j + 1]) * (point6_x - LINES[i][0][j + 1]) + (point6_y - LINES[i][0][j + 2]) * (point6_y - LINES[i][0][j + 2]))
index3 = math.ceil((distance3.index(min(distance3)) - 1) / 2)
index3b = math.floor((distance3.index(min(distance3)) - 1) / 2)
index4 = math.ceil((distance4.index(min(distance4)) - 1) / 2)
index4b = math.floor((distance4.index(min(distance4)) - 1) / 2)
if index3 == index3b:
point7_x = LINES[index3][0][2]
point7_y = LINES[index3][0][3]
point9_x = LINES[index3][0][0]
point9_y = LINES[index3][0][1]
else:
point7_x = LINES[index3][0][0]
point7_y = LINES[index3][0][1]
point9_x = LINES[index3][0][2]
point9_y = LINES[index3][0][3]
if index4 == index4b:
point8_x = LINES[index4][0][2]
point8_y = LINES[index4][0][3]
point10_x = LINES[index4][0][0]
point10_y = LINES[index4][0][1]
else:
point8_x = LINES[index4][0][0]
point8_y = LINES[index4][0][1]
point10_x = LINES[index4][0][2]
point10_y = LINES[index4][0][3]
LINEs = LINES.tolist()
if index3 < index4:
del LINEs[index3]
del LINEs[index4 - 1]
else:
del LINEs[index4]
del LINEs[index3 - 1]
LINEs = np.array(LINEs)
# 根据point9,point10,求出point11,point12
for i in range(0, len(LINEs)):
for j in range(0, 2):
if j != 1:
distance5.append((point9_x - LINEs[i][0][j]) * (point9_x - LINEs[i][0][j]) + (point9_y - LINEs[i][0][j + 1]) * (point9_y - LINEs[i][0][j + 1]))
if j != 0:
distance5.append((point9_x - LINEs[i][0][j + 1]) * (point9_x - LINEs[i][0][j + 1]) + (point9_y - LINEs[i][0][j + 2]) * (point9_y - LINEs[i][0][j + 2]))
index5 = math.ceil((distance5.index(min(distance5)) - 1) / 2)
index5b = math.floor((distance5.index(min(distance5)) - 1) / 2)
if index5 == index5b:
point11_x = LINEs[index5][0][2]
point11_y = LINEs[index5][0][3]
point12_x = LINEs[index5][0][0]
point12_y = LINEs[index5][0][1]
else:
point11_x = LINEs[index5][0][0]
point11_y = LINEs[index5][0][1]
point12_x = LINEs[index5][0][2]
point12_y = LINEs[index5][0][3]
# 计算相邻直线之间的交点
cross_pt1 = get_line_cross_point([point1_x, point1_y, point2_x, point2_y], [point3_x, point3_y, point5_x, point5_y])
print("交点坐标1:", cross_pt1)
cv2.circle(img, (int(cross_pt1[0]), int(cross_pt1[1])), 2, (0, 0, 255), 2)
cross_pt2 = get_line_cross_point([point3_x, point3_y, point5_x, point5_y], [point7_x, point7_y, point9_x, point9_y])
print("交点坐标2:", cross_pt2)
cv2.circle(img, (int(cross_pt2[0]), int(cross_pt2[1])), 2, (0, 0, 255), 2)
cross_pt3 = get_line_cross_point([point7_x, point7_y, point9_x, point9_y], [point11_x, point11_y, point12_x, point12_y])
print("交点坐标3:", cross_pt3)
cv2.circle(img, (int(cross_pt3[0]), int(cross_pt3[1])), 2, (0, 0, 255), 2)
cross_pt4 = get_line_cross_point([point11_x, point11_y, point12_x, point12_y], [point10_x, point10_y, point8_x, point8_y])
print("交点坐标4:", cross_pt4)
cv2.circle(img, (int(cross_pt4[0]), int(cross_pt4[1])), 2, (0, 0, 255), 2)
cross_pt5 = get_line_cross_point([point10_x, point10_y, point8_x, point8_y], [point6_x, point6_y, point4_x, point4_y])
print("交点坐标5:", cross_pt5)
cv2.circle(img, (int(cross_pt5[0]), int(cross_pt5[1])), 2, (0, 0, 255), 2)
cross_pt6 = get_line_cross_point([point6_x, point6_y, point4_x, point4_y], [point2_x, point2_y, point1_x, point1_y])
print("交点坐标6:", cross_pt6)
cv2.circle(img, (int(cross_pt6[0]), int(cross_pt6[1])), 2, (0, 0, 255), 2)
# 延长直线到交点
cv2.line(img, (int(cross_pt1[0]), int(cross_pt1[1])), (int(cross_pt2[0]), int(cross_pt2[1])), (0, 255, 0), 2)
cv2.line(img, (int(cross_pt2[0]), int(cross_pt2[1])), (int(cross_pt3[0]), int(cross_pt3[1])), (0, 255, 0), 2)
cv2.line(img, (int(cross_pt3[0]), int(cross_pt3[1])), (int(cross_pt4[0]), int(cross_pt4[1])), (0, 255, 0), 2)
cv2.line(img, (int(cross_pt4[0]), int(cross_pt4[1])), (int(cross_pt5[0]), int(cross_pt5[1])), (0, 255, 0), 2)
cv2.line(img, (int(cross_pt5[0]), int(cross_pt5[1])), (int(cross_pt6[0]), int(cross_pt6[1])), (0, 255, 0), 2)
cv2.line(img, (int(cross_pt6[0]), int(cross_pt6[1])), (int(cross_pt1[0]), int(cross_pt1[1])), (0, 255, 0), 2)
# 求形心坐标(简单求法)
center_x1 = (cross_pt1[0] + cross_pt2[0] + cross_pt3[0] + cross_pt4[0] + cross_pt5[0] + cross_pt6[0]) / 6
center_y1 = (cross_pt1[1] + cross_pt2[1] + cross_pt3[1] + cross_pt4[1] + cross_pt5[1] + cross_pt6[1]) / 6
# 求形心坐标(复杂求法)
# 1、计算六边形面积
A = 0
A += cross_pt1[0] * cross_pt2[1] - cross_pt2[0] * cross_pt1[1]
A += cross_pt2[0] * cross_pt3[1] - cross_pt3[0] * cross_pt2[1]
A += cross_pt3[0] * cross_pt4[1] - cross_pt4[0] * cross_pt3[1]
A += cross_pt4[0] * cross_pt5[1] - cross_pt5[0] * cross_pt4[1]
A += cross_pt5[0] * cross_pt6[1] - cross_pt6[0] * cross_pt5[1]
A += cross_pt6[0] * cross_pt1[1] - cross_pt1[0] * cross_pt6[1]
A = abs(A)/2
print("面积为:", A)
# 2、计算六边形形心
center_x2, center_y2 = 0, 0
center_x2 += (cross_pt1[0] + cross_pt2[0]) * (cross_pt1[0] * cross_pt2[1] - cross_pt2[0] * cross_pt1[1])
center_x2 += (cross_pt2[0] + cross_pt3[0]) * (cross_pt2[0] * cross_pt3[1] - cross_pt3[0] * cross_pt2[1])
center_x2 += (cross_pt3[0] + cross_pt4[0]) * (cross_pt3[0] * cross_pt4[1] - cross_pt4[0] * cross_pt3[1])
center_x2 += (cross_pt4[0] + cross_pt5[0]) * (cross_pt4[0] * cross_pt5[1] - cross_pt5[0] * cross_pt4[1])
center_x2 += (cross_pt5[0] + cross_pt6[0]) * (cross_pt5[0] * cross_pt6[1] - cross_pt6[0] * cross_pt5[1])
center_x2 += (cross_pt6[0] + cross_pt1[0]) * (cross_pt6[0] * cross_pt1[1] - cross_pt1[0] * cross_pt6[1])
center_y2 += (cross_pt1[1] + cross_pt2[1]) * (cross_pt1[0] * cross_pt2[1] - cross_pt2[0] * cross_pt1[1])
center_y2 += (cross_pt2[1] + cross_pt3[1]) * (cross_pt2[0] * cross_pt3[1] - cross_pt3[0] * cross_pt2[1])
center_y2 += (cross_pt3[1] + cross_pt4[1]) * (cross_pt3[0] * cross_pt4[1] - cross_pt4[0] * cross_pt3[1])
center_y2 += (cross_pt4[1] + cross_pt5[1]) * (cross_pt4[0] * cross_pt5[1] - cross_pt5[0] * cross_pt4[1])
center_y2 += (cross_pt5[1] + cross_pt6[1]) * (cross_pt5[0] * cross_pt6[1] - cross_pt6[0] * cross_pt5[1])
center_y2 += (cross_pt6[1] + cross_pt1[1]) * (cross_pt6[0] * cross_pt1[1] - cross_pt1[0] * cross_pt6[1])
center_x2 = abs(center_x2 / (6 * A))
center_y2 = abs(center_y2 / (6 * A))
print("方法一形心坐标:", center_x1, center_y1)
print("方法二形心坐标:", center_x2, center_y2)
print("偏差△x,△y:", abs(center_x1-center_x2), abs(center_y1-center_y2))
cv2.circle(img, (int(center_x1), int(center_y1)), 2, (0, 0, 255), 2) # 红色
cv2.circle(img, (int(center_x2), int(center_y2)), 2, (255, 0, 0), 2) # 蓝色
cv2.imshow('show', img)
cv2.waitKey()
上两张图片,取任一图片配合上述pyrhon程序即可。
再上一组检测结果:
图片高和宽: 262 282
交点坐标1: (201.8869956218199, 237.1984380546681)
交点坐标2: (73.71299435028249, 232.8779661016949)
交点坐标3: (12.211351017890191, 117.0260333127699)
交点坐标4: (83.48453326033398, 11.38899534629072)
交点坐标5: (209.07167630057805, 15.769942196531792)
交点坐标6: (269.61482820976494, 128.6003616636528)
面积为: 42621.005056368536
方法一形心坐标: 141.66372979344493 123.8102894459347
方法二形心坐标: 141.36025317025906 124.36577031627374
偏差△x,△y: 0.30347662318587254 0.5554808703390393