cv2透视变换逆变换_OpenCV-透视变换实现表盘刻度映射

一、对应问题

在指针表盘识别后,获得表盘的刻度点和指针点,如图一:

cv2透视变换逆变换_OpenCV-透视变换实现表盘刻度映射_第1张图片
图一: 原图(粉红色点为表盘刻度点,白色点为中心点,蓝色点为指针点)

但发现表盘面是3维空间上旋转了的,并非平行于观察者视界平面,于是我们希望把表盘面矫正到平行于观察者视界平面。

二、解决方法

  • 首先,依据表盘中的四个刻度点(粉色点),通过opencv透视变换将原图的表盘矫正到平行于观察者视界平面,如图二:

cv2透视变换逆变换_OpenCV-透视变换实现表盘刻度映射_第2张图片
图二:透视变换后的效果图
  • 接着,获取透明变换矫正后表盘中心点以及指针点的坐标位置,即图中白色中心点和蓝色的指针点的在二维透视目标图片上的坐标。
  • 然后,通过指针点与中心点的偏角关系计算出表盘读数。
  • 最后,画出模拟表盘及其读数, 如图三:

cv2透视变换逆变换_OpenCV-透视变换实现表盘刻度映射_第3张图片
图三:模拟表盘图

三、方法实现

1、Opencv的透视变换

关于透视变换的原理,可以参考我的【Opencv仿射变换和透明变换的原理与区别】

API接口函数:

  • cv2.getPerspectiveTransform(source, target)通过变换前后的四点坐标关系,获取透视变换的变换矩阵
    参数 source 为原图片中的四点坐标
    参数 target 为目标图片中的四点坐标
    数据格式如: [(x1,y1), (x2,y2),(x3,y3),(x4,y4)]
  • cv2.warpPerspective(image, M, dsize, flags, borderMode, borderValue)依据变换矩阵 M 对图像 image 做透视变换
    参数 image 输入图像
    参数 M 变换矩阵
    参数 dsize 变换后的目标图片大小, 元素顺序为 (width, height)
    参数 flags 插值方式,cv2.INTER_LINEAR 或 cv2.INTER_NEAREST
    参数 borderMode 边界补偿模式,cv2.BORDER_CONSTANT 或 cv2.BORDER_REPLICATE
    参数 borderValue 边界补偿值,默认为0
  • cv2.perspectiveTransform(point, M)依据变换矩阵 M 对点 p 做透视变换,得到透视变换后的点坐标
    参数 point 原坐标点,如 (x,y)
    参数 M 变换矩阵

2、直接上代码吧!

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)

你可能感兴趣的:(cv2透视变换逆变换)