-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathDashboardRecognition.py
More file actions
294 lines (255 loc) · 12 KB
/
DashboardRecognition.py
File metadata and controls
294 lines (255 loc) · 12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
import cv2 as cv
import numpy as np
import pytesseract
import cv2
# 找到轮廓上的其他点
def find_points_on_line(contour, vx, vy, x0, y0, center_x, center_y):
line_points = [] # 保存拟合直线上的轮廓点
# 获取直线的单位向量
unit_vector = np.squeeze(np.array([vx, vy]),axis = 1)
#print(unit_vector)
max_distance = 0 # 保存最远距离
farthest_point = None # 保存距离中心点最远的点
#print(contour)
for point in contour:
# 将轮廓点转换为 numpy 数组
#print(point)
point = np.array(point[0])
#print(point)
# 计算直线上点到参考点的向量
ref_point = np.squeeze(np.array([x0, y0]),axis = 1)
#print(ref_point)
vec_to_point = point - ref_point
#print(vec_to_point)
# 计算点到直线的距离(点到直线的投影长度)
distance_to_line = (np.cross(vec_to_point, unit_vector))
# 设置一个阈值,表示点在直线上的容忍范围
threshold = 0.1
# 如果点到直线的距离在阈值范围内,则认为点在直线上
if distance_to_line < threshold:
line_points.append(point)
# 计算点到中心点的距离
distance_to_center = np.linalg.norm(point - np.array([center_x, center_y]))
# 更新最远距离和最远点
if distance_to_center > max_distance:
max_distance = distance_to_center
farthest_point = point
#print(farthest_point[0])
if farthest_point is None:
return None
else:
return int(farthest_point[0]),int(farthest_point[1])
class DashboardRecognition:
def __init__(self, min_area=50 * 50):
self.min_area = min_area
self.image_width = 0
self.image_height = 0
self.bbox = None
self.dashboard_status = None
self.biggest = None
def detect(self, image, bbox=None):
if bbox is None:
bias = np.array([0, 0]).astype(np.int32) # hand landmarks bias to left-top
else:
bias = bbox[0] # hand landmarks bias to left-top
# crop image
image = image[bbox[0][1]:bbox[1][1], bbox[0][0]:bbox[1][0], :]
self.image_height, self.image_width = image.shape[:2]
self.bbox = None
# 转换为灰度图像
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
# 使用高斯滤波平滑图像
blur = cv.GaussianBlur(gray, (5, 5), 0)
# 使用霍夫变换检测圆形
circles = cv.HoughCircles(blur, cv.HOUGH_GRADIENT, 1.2, 100)
# 如果检测到圆形,绘制边界
if circles is not None:
# 将圆形坐标转换为整数
circles = np.round(circles[0, :]).astype("int")
# 定义一个阈值,表示两个圆心之间的最大距离,用于判断是否合并
threshold = 10
# 定义一个列表,用于存储合并后的圆形
merged_circles = []
# 遍历每个圆形
for (x1, y1, r1) in circles:
# 定义一个标志,表示当前的圆形是否已经被合并过
merged = False
# 遍历已经合并过的圆形列表
for i in range(len(merged_circles)):
# 获取已经合并过的圆形的坐标和半径
(x2, y2, r2) = merged_circles[i]
# 计算两个圆心之间的距离
distance = np.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
# 如果距离小于阈值,说明两个圆形可以合并
if distance < threshold:
# 将当前的圆形和已经合并过的圆形进行平均,得到新的圆形
if r1 >= r2:
merged_circles[i] = (x1, y1, r1)
else:
merged_circles[i] = (x2, y2, r2)
# 设置标志为True,表示当前的圆形已经被合并过
merged = True
# 跳出循环,不再遍历其他已经合并过的圆形
break
# 如果当前的圆形没有被合并过,就将它添加到已经合并过的圆形列表中
if not merged:
merged_circles.append((x1, y1, r1))
# 遍历已经合并过的圆形列表,找到最大的圆,作为检测结果
biggest = None
biggest_r = -1
for (x, y, r) in merged_circles:
if r > biggest_r:
biggest = (x, y, r)
biggest_r = r
# 将圆转换成bbox
if biggest is not None:
self.biggest = biggest
self.bbox = np.array([[biggest[0] - biggest[2], biggest[1] - biggest[2]],
[biggest[0] + biggest[2], biggest[1] + biggest[2]]] + bias, np.int32)
return self.__refine_bbox(self.bbox)
return None
def __refine_bbox(self, bbox):
# refine bbox
bbox[:, 0] = np.clip(bbox[:, 0], 0, self.image_width)
bbox[:, 1] = np.clip(bbox[:, 1], 0, self.image_height)
w, h = bbox[1] - bbox[0]
if w <= 0 or h <= 0 or w * h <= self.min_area:
return None
else:
return bbox
def get_status(self, image):
self.dashboard_status = None
if self.bbox is not None:
img_bgr = image
img_rectangle = image[self.bbox[0][1]:self.bbox[1][1], self.bbox[0][0]:self.bbox[1][0], :]
# 将图像转换为灰度图
img_gray = cv2.cvtColor(img_rectangle, cv2.COLOR_BGR2GRAY)
# 使用Canny边缘检测算法
edges = cv2.Canny(img_gray, 50, 150)
# 执行轮廓检测
contours_rec, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 初始化最大面积和最大面积的矩形轮廓
max_area = -1
max_contour = None
max_approx = None
mid_x = None
# 遍历所有轮廓
for contour in contours_rec:
# 计算轮廓的周长
perimeter = cv2.arcLength(contour, True)
# 近似轮廓,参数为轮廓周长的百分比(0.02表示轮廓周长的2%)
approx = cv2.approxPolyDP(contour, 0.02 * perimeter, True)
# 如果近似轮廓有4个顶点
if len(approx) == 4:
# 计算轮廓的角度
angles = []
for i in range(4):
p1 = approx[i][0]
p2 = approx[(i + 1) % 4][0]
p3 = approx[(i + 2) % 4][0]
v1 = p1 - p2
v2 = p3 - p2
angle = np.arccos(np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2)))
angles.append(np.degrees(angle))
# 判断轮廓的所有角是否接近于90度
if all(abs(angle - 90) < 10 for angle in angles):
# 计算矩形轮廓的面积
area = cv2.contourArea(contour)
# 如果当前矩形轮廓的面积大于最大面积,则更新最大面积和最大面积的矩形轮廓
if area > max_area:
max_area = area
max_contour = contour
max_approx = approx
# 如果找到了最大面积的矩形轮廓
if max_area is not None:
if (max_area / (self.biggest[2] ** 2)) < 0.04:
max_contour = None
#print(max_area / (self.biggest[2] ** 2))
if max_contour is not None:
# 绘制最大面积的矩形轮廓
point_1 = max_approx[0][0] + self.bbox[0]
point_2 = max_approx[1][0] + self.bbox[0]
point_3 = max_approx[2][0] + self.bbox[0]
if np.linalg.norm(point_2 - point_1) > np.linalg.norm(point_2 - point_3):
mid_x = int((point_1[0] + point_2[0]) / 2)
mid_y = int((point_1[1] + point_2[1]) / 2)
vector_ref_x = mid_x - self.biggest[0]
vector_ref_y = mid_y - self.biggest[1]
cv2.line(image,(self.biggest[0],self.biggest[1]),(mid_x,mid_y),(255,0,0),1)
else:
mid_x = int((point_1[0] + point_2[0]) / 2)
mid_y = int((point_1[1] + point_2[1]) / 2)
vector_ref_x = mid_x - self.biggest[0]
vector_ref_y = mid_y - self.biggest[1]
cv2.line(image,(self.biggest[0],self.biggest[1]),(mid_x,mid_y),(255,0,0),1)
#print(max_area / (self.biggest[2] ** 2))
x, y, w, h = cv2.boundingRect(max_contour)
x = x + self.bbox[0][0]
y = y + self.bbox[0][1]
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.drawContours(img_rectangle, [max_approx], 0, (0, 0, 255), 2)
# 取完整圆的60%部分
center = np.mean(self.bbox, axis=0)
#print(center)
scale_bbox = center + (self.bbox - center) * 0.5
scale_bbox = scale_bbox.astype(np.int32)
image = image[scale_bbox[0][1]:scale_bbox[1][1], scale_bbox[0][0]:scale_bbox[1][0], :]
#print(image.shape)
# 图片转换为灰度图
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
# 二值化,将黑色区域设为255,其他区域设为0
_, thresh = cv.threshold(gray, 100, 255, cv.THRESH_BINARY_INV)
# 寻找轮廓
contours, _ = cv.findContours(thresh, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
# 找到面积最大的轮廓
max_area = 0
max_contour = None
for c in contours:
area = cv.contourArea(c)
if area > max_area:
max_area = area
max_contour = c
# 如果找到了最大轮廓,绘制它并计算它的角度
if max_contour is not None:
rect = cv.minAreaRect(max_contour)
# 得到指针轮廓的最佳拟合直线,并获取其斜率
vx, vy, x0, y0 = cv.fitLine(max_contour, cv.DIST_L2, 0, 0.01, 0.01)
# 调用函数找到直线上距离中心点最远的点
center_x = image.shape[0] // 2
center_y = image.shape[1] // 2
farthest_point = find_points_on_line(max_contour, vx, vy, x0, y0, center_x, center_y)
if farthest_point:
farthest_point = farthest_point + scale_bbox[0]
cv.line(img_bgr, (self.biggest[0],self.biggest[1]),farthest_point,(0,0,255),1)
theta = None
# 若识别到参照物
if mid_x:
# 计算两个向量的角度
theta1 = np.arctan2(farthest_point[1] - self.biggest[1], farthest_point[0] - self.biggest[0])
theta2 = np.arctan2(mid_y - self.biggest[1], mid_x - self.biggest[0])
# 角度相减
theta = theta1 - theta2
theta = np.degrees(theta)
if theta < 0:
theta = theta + 360
# 判断状况
if theta is not None:
if 30 <= theta < 115:
self.dashboard_status = "low"
elif 115 <= theta <= 235:
self.dashboard_status = "mid"
elif 235 < theta <= 330:
self.dashboard_status = "high"
else:
self.dashboard_status = None
else:
self.dashboard_status = None
return self.dashboard_status
def visualize(self, image, status, thickness=1):
if self.bbox is not None:
# Draw bounding box on original image (in color)
cv.rectangle(image, self.bbox[0], self.bbox[1], (0, 255, 0), 2)
if status is not None:
cv.putText(image, status, (self.bbox[0][0], self.bbox[0][1] + 22 * thickness),
cv.FONT_HERSHEY_SIMPLEX, thickness, (0, 0, 255))
return image