在指针表盘识别后,获得表盘的刻度点和指针点,如图一:
图一: 原图(粉红色点为表盘刻度点,白色点为中心点,蓝色点为指针点)但发现表盘面是3维空间上旋转了的,并非平行于观察者视界平面,于是我们希望把表盘面矫正到平行于观察者视界平面。
关于透视变换的原理,可以参考我的【Opencv仿射变换和透明变换的原理与区别】
API接口函数:
import os
import cv2
import math
import utils
import numpy as np
from math import cos, sin, pi
# 模拟表盘的参数
MODULE_CFG = {
"scale_num": 6, #表盘主刻度数量
"scale_range": (0, 2.5), #刻度的数值范围
"angle_range": (225, -45) #表盘刻度的弧度范围
}
def get_module(module_cfg):
# 获取模拟表盘
center, radius, pointer_length = (200,200), 100, 75
angle_range = module_cfg["angle_range"]
scale_num = module_cfg["scale_num"]
scale_range = module_cfg["scale_range"]
per_angle_size = (max(angle_range) - min(angle_range)) / (scale_num-1)
angles = [float(max(angle_range)) - i*float(per_angle_size) for i in range(scale_num)]
cx, cy = center
scale_coors = []
per_value = (max(scale_range) - min(scale_range)) / (scale_num - 1)
scale_value = -1 * per_value
for i,a in enumerate(angles):
x1 = int(cx + radius*cos(a*pi/180))
y1 = int(cy + radius*sin(a*pi/180))
y1 = 2*cy - y1 #-= 2*(y1-cy)
scale_value += per_value
scale_coors.append([(x1,y1),round(scale_value,2)])
module_img = model_img = np.zeros((400,400,3), dtype=np.uint8)
return center, scale_coors, radius, pointer_length, module_img
def draw_result(module_cfg, center_coor, pointer_coor, save_path):
# 计算透视变换后的读数
min_scale = module_cfg["scale_range"][0]
max_scale = module_cfg["scale_range"][1]
start_angle = module_cfg["angle_range"][0]
end_angle = module_cfg["angle_range"][1]
mcx, mcy = center_coor[0], center_coor[1]
mpx, mpy = pointer_coor[0], pointer_coor[1]
raduis = math.sqrt( math.pow(mcx - mpx,2)+math.pow(mcy-mpy,2) )
pointer_angle = math.acos(( mpx - mcx ) / raduis) * 180 / pi
value_rate = (start_angle - pointer_angle) / (start_angle - end_angle)
value = str(round( (max_scale - min_scale) * value_rate, 5))
# 获取模板表盘
module_center, scale_coors, radius, pointer_length, module_img = get_module(module_cfg)
font = cv2.FONT_HERSHEY_SIMPLEX
# 画上表盘圆弧
cv2.circle(module_img, module_center, radius, (0,0,255), 3)
module_img[scale_coors[0][0][1]+2:,:,:] = 0
# 画上表盘中心
cv2.circle(module_img, module_center, 2, (255,255,255), 4)
# 画上表盘刻度
for i, sc in enumerate(scale_coors):
cv2.circle(module_img, sc[0], 1, (0,255,0), 4)
if i < len(scale_coors)/2:
text_loc = (sc[0][0]-40, sc[0][1])
else:
text_loc = (sc[0][0]+20, sc[0][1])
cv2.putText(module_img, str(round(sc[1],2)),text_loc, font, 0.5,(255,255,255), 1)
cv2.imwrite("./show_pics/module_image.jpg", module_img)
# 写上表盘读数
cv2.putText(module_img, value,(20,20), font, 0.5,(255,0,255), 1)
# 画上表盘指针
mpx = module_center[0] + pointer_length * math.cos(pointer_angle/180*pi)
mpy = module_center[1] - pointer_length * math.sin(pointer_angle/180*pi)
module_pointer = (int(mpx), int(mpy))
cv2.circle(module_img, module_pointer, 2, (255,0,0), 5)
cv2.line(module_img, module_center, module_pointer, (255,100,234), 3)
cv2.imwrite(save_path, module_img)
if __name__ == "__main__":
for x in os.listdir('./labelme'):
# 我只用标注好的数据
if x.split('.')[-1] != 'jpg':
continue
test_image = os.path.join('./labelme',x)
test_xml = os.path.join('./labelme',x.split('.')[0]+'.xml')
classes = ['center', 'pointer', 'scale', 'clock']
xml_data = utils.parse_xml(test_xml, classes)
orig_image = cv2.imread(test_image)
w, h, c = orig_image.shape
center = xml_data[0]["center"]
scales = xml_data[0]["scales"]
p1 = xml_data[0]['pointers'][0]
# 由于透视变换需要四个点,选取索引为0,2,3,5的刻度点
need_idx = [0, 2, 3, 5]
# 源坐标点
source = [scales[0], scales[2], scales[3], scales[5]]
# 目标坐标点
tcoors = get_module(MODULE_CFG)[1]
target = [tcoors[i][0] for i in need_idx]
# 透视变换获取变换图片
target_h, target_w = 500, 500
source, target = np.float32(source), np.float32(target)
M = cv2.getPerspectiveTransform(source, target) # 变换矩阵
warp_img = cv2.warpPerspective(orig_image, M, (target_w, target_w))
# 透视变换获取变换坐标,中心点和指针点
cx, cy = center[0], center[1]
x1, y1 = p1[0], p1[1]
c = np.array([cx,cy], dtype=np.float32).reshape(-1,1,2)
p = np.array([x1,y1], dtype=np.float32).reshape(-1,1,2)
mc = np.int32(cv2.perspectiveTransform(c, M)).tolist()[0][0]
mp = np.int32(cv2.perspectiveTransform(p, M)).tolist()[0][0]
# 画图
save_path = "./show_pics/"+x.split('.')[0]+"_result.jpg"
draw_result(MODULE_CFG, mc, mp, save_path)