《球机3D定位解析及ONVIF实现》


海康部分球机有3D定位功能,最近研究了一下海康球机SDK实现和任意球机ONVIF实现。


3D定位

简述

  • 3D定位是指通过设置图像目标坐标,旋转球机PTZ,使目标在球机视野中心;

  • 球机坐标系有三个轴分别控制pan(水平) tilt(垂直) zoom(倍率);
    在这里插入图片描述

3D定位原理分析

  • 3D定位原理见下图(C是镜头中心,O是图像中心点,P是目标点在图上位置,A是图像边界点)

在这里插入图片描述

  • 由上图可得

t a n ( ∠ A C O ) t a n ( ∠ P C O ) = A O P O \frac{tan(∠ACO)}{tan(∠PCO)} = \frac{AO}{PO} tan(PCO)tan(ACO)=POAO

  • 可得图像上的目标点P到图像视野中心的水平和垂直角度差
    { α V : 1 − > o = a r c t a n ( Y − H / 2 H / 2 ∗ t a n ( F O V v / 2 ) ) α H : 1 − > o = a r c t a n ( X − W / 2 W / 2 ∗ t a n ( F O V h / 2 ) ) \begin{cases} \alpha _{V:1->o}=arctan(\frac{Y-H/2}{H/2}*tan(FOV_v/2)) \\ \alpha_{H:1->o}=arctan(\frac{X-W/2}{W/2}*tan(FOV_h/2)) \end{cases} {αV:1>o=arctan(H/2YH/2tan(FOVv/2))αH:1>o=arctan(W/2XW/2tan(FOVh/2))
  • 通过水平和垂直视野角度差,转动云台指定角度即可。

海康SDK实现

  • 目前在海康球机看到有相关功能实现,点击3D,然后在图上选择想定位的目标点。
    在这里插入图片描述
  • 海康球机有相关SDK实现,SDK下载
  • 相关实现函数
NET_DVR_PTZSelZoomIn(LONG lRealHandle, LPNET_DVR_POINT_FRAME pStruPointFrame);
NET_DVR_PTZSelZoomIn_EX(LONG lUserID, LONG lChannel, LPNET_DVR_POINT_FRAME pStruPointFrame);
strcpy(cam_ip, "192.168.1.x");
cam_port = 8000;
strcpy(user_name, "admin");
strcpy(pwd, "xxxxx");

NET_DVR_DEVICEINFO_V30 DeviceInfoTmp;
memset(&DeviceInfoTmp, 0, sizeof(NET_DVR_DEVICEINFO_V30));

LONG lLoginID = NET_DVR_Login_V30(cam_ip, cam_port, user_name, pwd, &DeviceInfoTmp); // 注册

int std_cols = 2560;   // 图像长宽
int std_rows = 1440;
NET_DVR_POINT_FRAME posdata;

posdata.xTop = (int)(target_x * 255 / std_cols);	// 坐标归一化到(255, 255)
posdata.xBottom = posdata.xTop;
posdata.yTop = (int)(target_y * 255 / std_rows);
posdata.yBottom = posdata.yTop;
posdata.bCounter = 3;   // 没用
if (!NET_DVR_PTZSelZoomIn_EX(0, 1, &posdata)){
printf("3D定位失败, %d\n", NET_DVR_GetLastError());
	return -1;
}

ONVIF实现

  • 既然原理已知,考虑以下,研究一下3D定位通用方法,
    • 海康SDK有Linux和Windows的实现方式,但是在arm上没找到相关SDK?
    • 如果有别的品牌球机,没有海康SDK,需要如何实现?
    • 如果不是球机,只有一个云台和一个摄像头又要如何实现?
  • ONVIF协议作为标准通用的公有协议 ,可以实现摄像头PTZ控制
  • 需要获取球机的PTZ和视场角与ONVIF的PTZ对应关系,参考《球机的PTZ和视场角与ONVIF的PTZ对应关系》
  • 以海康球机DS-2DC22041W-D3/W为例

球机的PTZ与ONVIF的PTZ的对应关系

  • 球机的PTZ与onvif的ptz是线性关系
  • ONVIF与SDK的PTZ对应关系
ONVIF_PONVIF_TONVIF_ZSDK_PSDK_TSDK_Z
-1-10001
000.5180452.5
111350904
  • 拟合线性关系

{ p ′ = m i n ( 180 + 180 ∗ p , 360 ) t ′ = 45 + 45 ∗ t z ′ = 1 + 3 ∗ z \begin{cases} p' = min(180 + 180 * p, 360)\\ t'= 45 + 45*t \\ z'= 1 + 3*z\\ \end{cases} p=min(180+180p,360)t=45+45tz=1+3z

球机的视场角与ONVIF的Zoom的对应关系

{ f x = 2807.89 f y = 2804.10 \begin{cases} f_x=2807.89\\ f_y=2804.10 \end{cases} {fx=2807.89fy=2804.10

  • 计算出水平和垂直视场角

{ F O V H = 37.75 F O V V = 21.80 \begin{cases} FOV_H = 37.75\\ FOV_V=21.80 \end{cases} {FOVH=37.75FOVV=21.80

3D 定位

在这里插入图片描述

  • 主要分为ONVIF控制、3D控制和UI交互三部分

  • app.py负责UI交互

def onMouse(event, x, y, flags, param):
    if event == cv2.EVENT_LBUTTONDBLCLK:
        # print(x, y, flags, param)
        coor = (x,y)
        param.put(coor)
        if param.qsize() > 1:
            param.get()
        else:
            time.sleep(0.01)

def show_image(wnd_name, img, delay=0):
    cv2.namedWindow(wnd_name, cv2.WINDOW_NORMAL)
    cv2.resizeWindow(wnd_name, 1280, 720)
    cv2.imshow(wnd_name, img)
    ret = cv2.waitKey(delay)
    return ret

def img_put(queue, status):
    wnd_name = "frame"
    cap = cv2.VideoCapture(rtsp)
    if not cap.isOpened():
        print('error open cam')
        return
    num = 0
    while True:
        num += 1
        frame = cv2.flip(cap.read()[1], 0)
        status.value = show_image(wnd_name, frame, 1)
        if status.value == ord('q'):
            print("image put exit!")
            return
        cv2.setMouseCallback(wnd_name, onMouse, param=queue)

def img_get(queue, status):
    my_onvif = onvif_control(ip, usr, pwd)
    my_locate = locate(Size[0], Size[1], FOV[0], FOV[1])
    num = 0
    while True:
        num += 1
        if status.value == ord('q'):
            print("image get exit!")
            return
        if queue.qsize() > 0:
            coor = queue.get()
            pos_cur = my_onvif.get_status()
            pos_target = my_locate.locate(coor, pos_cur)
            my_onvif.abs_move(pos_target)

def app():
    multiprocessing.set_start_method(method='spawn')
    queue = multiprocessing.Queue(maxsize=5)
    processes = []
    status = Value('i', -1)
    processes.append(multiprocessing.Process(target=img_put, args=(queue, status)))
    processes.append(multiprocessing.Process(target=img_get, args=(queue, status)))
    for process in processes:
        process.daemon = True
        process.start()
    for process in processes:
        process.join()
    print("Done")
  • onvif_control.py负责ONVIF控制
class onvif_control:
    def __init__(self, ip, usr, pwd, hom_pos=None):
        self.usr = usr
        self.pwd = pwd
        self.hom_pos = hom_pos
        self.cam = ONVIFCamera(ip, 80, usr, pwd)
        self.ptz = self.cam.create_ptz_service()
        self.media = self.cam.create_media_service()
        self.imaging = self.cam.create_imaging_service()
        self.profile = self.cam.media.GetProfiles()[0]

    def get_status(self):
        request = self.cam.ptz.create_type('GetStatus')
        request.ProfileToken = self.profile.token
        status = self.cam.ptz.GetStatus(request)
        return {'pan': status.Position.PanTilt.x, 'tilt': status.Position.PanTilt.y, 'zoom': status.Position.Zoom.x}

    def move_home(self, speed={'pan':1, 'tilt':1, 'zoom':1}):
        request = self.ptz.create_type('AbsoluteMove')
        request.ProfileToken = self.profile.token
        request.Position = {'PanTilt':{'x':self.hom_pos['pan'], 'y':self.hom_pos['tilt']}, 'Zoom':self.hom_pos['zoom']}
        request.Speed = {'PanTilt':{'x':speed['pan'], 'y':speed['tilt']}, 'Zoom':speed['zoom']}
        self.ptz.AbsoluteMove(request)

    def abs_move(self, pos, speed={'pan':1, 'tilt':1, 'zoom':1}):
        request = self.ptz.create_type('AbsoluteMove')
        request.ProfileToken = self.profile.token
        request.Position = {'PanTilt':{'x':pos['pan'], 'y':pos['tilt']}, 'Zoom':pos['zoom']}
        request.Speed = {'PanTilt':{'x':speed['pan'], 'y':speed['tilt']}, 'Zoom':speed['zoom']}
        self.ptz.AbsoluteMove(request)

    def continuous_move(self, speed):
        request = self.ptz.create_type("ContinuousMove")
        request.ProfileToken =  self.profile.token
        request.Velocity = {'PanTilt':{'x':speed['pan'], 'y':speed['tilt']}, 'Zoom':speed['zoom']}
        self.ptz.ContinuousMove(request)

    def relative_move(self, pos, speed={'pan':1, 'tilt':1, 'zoom':1}):
        request = self.cam.ptz.create_type('RelativeMove')
        request.ProfileToken = self.profile.token
        request.Translation = {'PanTilt':{'x':pos['pan'], 'y':pos['tilt']}, 'Zoom':pos['zoom']}
        request.Speed = {'PanTilt':{'x':speed['pan'], 'y':speed['tilt']}, 'Zoom':speed['zoom']}
        self.ptz.RelativeMove(request)

    def stop(self):
        self.ptz.Stop({'ProfileToken': self.profile.token})

    def snap_image(self, path):
        import requests, os
        res = self.media.GetSnapshotUri({'ProfileToken': self.profile.token})
        auth = requests.auth.HTTPDigestAuth(self.usr, self.pwd)
        response = requests.get(url=res.Uri, auth=auth)
        path_out = os.path.join(path, 'tmp.jpg')
        with open(path_out, 'wb') as fp:
            fp.write(response.content)

    def get_rtsp(self):
        obj = self.media.create_type('GetStreamUri')
        obj.StreamSetup = {'Stream': 'RTP-Unicast', 'Transport': {'Protocol': 'RTSP'}}
        obj.ProfileToken = self.profile.token
        res_uri = self.media.GetStreamUri(obj)['Uri']
        return res_uri
  • locate_3d.py负责3D定位计算
class locate:
    def __init__(self, width, height, fov_h, fov_v):
        self.width = width
        self.height = height
        self.fov_h = fov_h
        self.fov_v = fov_v

    def ptz_to_sdk(self, pos_onvif):
        p_ = min(180 * pos_onvif['pan'] + 180, 360)
        t_ = 45 * pos_onvif['tilt'] + 45
        z_ = 3 * pos_onvif['zoom'] + 1
        return {'pan' :p_, 'tilt' :t_, 'zoom' :z_}

    def ptz_to_onvif(self, pos_sdk):
        p_ = (pos_sdk['pan'] - 180) / 180
        t_ = (pos_sdk['tilt'] - 45) / 45
        z_ = (pos_sdk['zoom'] - 1) / 3
        return {'pan' :p_, 'tilt' :t_, 'zoom' :z_}

    def locate(self, target, pos_onvif):
        # 图像目标点,移动到视野中心
        # pos转到sdk
        pos_sdk = self.ptz_to_sdk(pos_onvif)
        print("[pos_sdk]:", pos_sdk)
        x, y = target
        # 水平方向
        if x > (self.width / 2):
            delt_x = np.rad2deg(   np.arctan((x - self.width / 2) / (self.width / 2) * np.tan( np.deg2rad(self.fov_h /2)))    )
        else:
            delt_x = -np.rad2deg(  np.arctan((self.width / 2 - x) / (self.width / 2) * np.tan( np.deg2rad(self.fov_h /2)))    )
        # 垂直方向
        if y > (self.height / 2):
            delt_y = np.rad2deg(   np.arctan((y - self.height / 2) / (self.height / 2) * np.tan( np.deg2rad(self.fov_v / 2)))  )
        else:
            delt_y = -np.rad2deg(  np.arctan((self.height / 2 - y) / (self.height / 2) * np.tan( np.deg2rad(self.fov_v / 2)))  )
        # print("delt:[%.3f, %.3f]" % (delt_x, delt_y))
        pos_sdk['pan'] += delt_x        # 得到转换后的pt
        pos_sdk['tilt'] -= delt_y       # 相机是倒置放置的。。。
        pos_onvif = self.ptz_to_onvif(pos_sdk)        # pos转到onvif
        return pos_onvif

参考链接

  1. 一种利用快球跟踪系统实现监控的方法及装置

  2. 海康球机控制函数VC (PTZ控制+对准具体坐标点)

  3. 海康球机SDK下载

  • 9
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值