使用ROS控制海康威视网络摄像头(二)

控制摄像头看海康威视的SDK即可,基本流程图如下,适用于多种功能:

其中注册设备现在用的是新函数NET_DVR_Login_V40,注册成功后,返回的用户 ID 作为功
能操作的唯一标识。

还有个重要函数NET_DVR_GetLastError(),返回最后操作的错误码,有网络通信、rtsp、解码错误。

控制摄像头用的是NET_DVR_SetDVRConfig函数,用到的参数有用户ID,设备配置命令(宏)等等;相对地,有函数NET_DVR_GetDVRConfig,可以获得PTZ状态,详见SDK。

最后还有个很关键的参数,就是两个函数中用到的位置结构体:

1
2
3
4
5
6
struct{
WORD wAction; //操作类型,仅在设置时有效。1-定位PTZ参数,2-定位P参数,3-定位T参数,4-定位Z参数,5-定位PT参数
WORD wPanPos; //P参数(水平参数)
WORD wTiltPos; //T参数(垂直参数)
WORD wZoomPos; //Z参数(变倍参数)
}NET_DVR_PTZPOS, *LPNET_DVR_PTZPOS;

本结构体中的wAction参数是设置时的操作类型,因此获取时该参数无效。实际显示的PTZ值是获取到的十六进制值的十分之一,如获取的水平参数P的值是0x1750,实际显示的P值为175度;获取到的垂直参数T的值是0x0789,实际显示的T值为78.9度;获取到的变倍参数Z的值是0x1100,实际显示的Z值为110倍。

下面是一个范例,摄像头向左右、上下转一定角度、焦距放大3倍,然后不断检测当前的定位和焦距,按Ctrl+C可以退出:

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
#include <ros/ros.h>
#include <signal.h>
#include <camera/camera.h>
#include "HCNetSDK.h"
#include <iostream>
#include <iomanip>

using namespace std;
bool EXIT = false;

//转换函数
unsigned long HexToDecMa(unsigned long wHex)
{
return (wHex / 4096) * 1000 + ((wHex % 4096) / 256) * 100 + ((wHex % 256) / 16) * 10 + (wHex % 16);
}

void CALLBACK g_ExceptionCallBack(DWORD dwType, LONG lUserID, LONG lHandle, void *pUser)
{
switch(dwType)
{
case EXCEPTION_RECONNECT: //预览时重连
ROS_INFO("----------reconnecting--------");
break;
default:
break;
}
}

int main(int argc, char *argv[])
{
ros::init(argc,argv,"hikvision_camera");
ros::NodeHandle nh;
// 初始化
NET_DVR_Init();
//设置连接时间与重连时间
NET_DVR_SetConnectTime(10000, 1);
NET_DVR_SetReconnect(10000, true);
// 初始化设备参数
LONG lUserID;
NET_DVR_USER_LOGIN_INFO struLoginInfo = {0};
struLoginInfo.bUseAsynLogin = 0; //同步登录方式
strcpy(struLoginInfo.sDeviceAddress, "192.168.73.64"); //设备IP地址
struLoginInfo.wPort = 8000; //设备服务端口
strcpy(struLoginInfo.sUserName, "admin"); //设备登录用户名
strcpy(struLoginInfo.sPassword, "admin1234"); //设备登录密码
//设备信息, 输出参数
NET_DVR_DEVICEINFO_V40 struDeviceInfoV40 = {0};
// 注册设备
lUserID = NET_DVR_Login_V40(&struLoginInfo, &struDeviceInfoV40);
if (lUserID < 0)
{
printf("Login error, %d\n", NET_DVR_GetLastError());
NET_DVR_Cleanup();
return -1;
}
//设置异常消息回调函数
NET_DVR_SetExceptionCallBack_V30(0, NULL,g_ExceptionCallBack, NULL);

if (!NET_DVR_SetCapturePictureMode(BMP_MODE))
{
cout << "Set Capture Picture Mode error!" << endl;
cout << "The error code is " << NET_DVR_GetLastError() << endl;
}
//读取PTZ
NET_DVR_PTZPOS m_ptzPosCurrent,m_ptzPos;
m_ptzPosCurrent.wAction = 1;
DWORD dwtmp=0;

m_ptzPos.wAction = 1;
m_ptzPos.wPanPos = 0x0750;
m_ptzPos.wTiltPos = 0x0550;
m_ptzPos.wZoomPos = 0x0010;
//设置PTZ参数
if (!NET_DVR_SetDVRConfig(lUserID, NET_DVR_SET_PTZPOS, 1, &m_ptzPos, sizeof(NET_DVR_PTZPOS)))
{
ROS_INFO("Set device config error:%d",NET_DVR_GetLastError());
return -1;
}
while (!EXIT)
{
//获取当前PTZ状态
if(!NET_DVR_GetDVRConfig(lUserID, NET_DVR_GET_PTZPOS, 1, &m_ptzPosCurrent, sizeof(NET_DVR_PTZPOS), &dwtmp))
{
ROS_INFO("Get PTZ failed:%d",NET_DVR_GetLastError());
break;
}
//打印PTZ
int m_iPara1 = HexToDecMa(m_ptzPosCurrent.wPanPos);
int m_iPara2 = HexToDecMa(m_ptzPosCurrent.wTiltPos);
int m_iPara3 = HexToDecMa(m_ptzPosCurrent.wZoomPos);

cout << "action: "<<m_ptzPosCurrent.wAction<<endl; //一直是1
cout << "P: " << m_iPara1 / 10 + 1 << endl;
cout << "T: " << m_iPara2 / 10 + 1<< endl;
cout << "Z: " << m_iPara3 / 10 << endl;
}
return 0;
}

结果运行后发现只有焦距设置和检测成功,两个方向的角度一直是0,反复检查都未发现问题,最后咨询海康威视的技术支持,发现设备竟然不支持这个功能。只好先记录下来,以防以后使用。

串转网的控制协议

另一种控制摄像头的方式是使用串口,基于Pelco-D协议,从程序向串口发命令进行控制。方便的是,厂家在摄像头内部实现了一个串口转网口的Pelco-D协议,我们不必考虑这个协议了,它使用的是UDP方式,向摄像机的10006端口发7个字节的十六进制数据,摄像头会反馈7个字节十六进制数据,从中可获得摄像头水平和垂直坐标,另外也可进行转动控制。

但这种方法归根到底是使用了串口,所以我们要开发的是一个“像”串口调试助手的程序,而不是“像”UDP调试助手的程序。程序不是要监听摄像头的1006端口,而是用UDP连接它之后,发数据(以十六进制形式,很重要),等待反馈后进行解析。

PELCO-D协议格式:
数据格式:8位数据、1位停止位,无效验位。波特率:2400-9600b/S

命令格式:
字节1 字节2 字节3 字节4 字节5 字节6 字节7
同步字节 地址码 指令码1 指令码2 数据码1 数据码2 校验码

  1. 该协议中所有数值都为十六进制数.
  2. 同步字节始终为0xFF.
  3. 地址码为摄像机的逻辑地址号,地址范围:01H–FFH  
  4. 校验码 = MOD[(字节2 + 字节3 + 字节4 + 字节5 + 字节6)/100H]

绝对坐标协议:
查询水平坐标: 向端口发送: FF 01 00 51 00 00 52
坐标反馈: FF 01 00 59 XX YY 校验码, 例如:FF 01 00 59 23 E4 61

问题

  • 海康sdk库调用NET_DVR_CaptureJPEGPicture函数出现错误码107

这个错误码是预览组件加载失败,如果HCNetSDKCom目录以及libhcnetsdk.so、libhpr.so、libHCCore文件和可执行文件在同一级目录下,则使用同级目录下的库文件。如果不在同一级目录下,则需要将以上文件的目录加载到动态库搜索路径中:在环境变量里添加export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/XXX:/XXX/HCNetSDKCom

  • NET_DVR_GetLastError错误码4

通道号错误。打开网页设置通道,然后再运行程序

  • NET_DVR_GetLastError错误码9

从设备接收数据失败,原因很简单,就是网络状况不好

  • NET_DVR_GetLastError错误码47

含义是:用户不存在。注册的用户 ID 已注销或不可用。同一个程序,控制摄像头转动,在我的电脑能用,换到工控机上报次错误。重新注册用户仍然报错。结果发现链接的库目录没有HCNetSDKCom时,报错107;有它的时候,报错47. 更奇怪的是,随便填用户名和密码时,不会报登录错误,此时获得的userID仍是0,正常来说,如果登录信息有错,userID应该为负。

查来查去,不明白为什么工控机与我的电脑有何不同。最后把以下文件夹和文件都和可执行文件放到一起,在CMakeList里链接相应地址,其中CMakeList链接的库文件是 libHCCore.so, libhpr.so, libPlayCtrl.so, libhcnetsdk.so , 这种情况下不设置环境变量也行

  • 调用NET_DVR_PTZControlWithSpeed_Other出现错误码107

使用函数NET_DVR_PTZControlWithSpeed_Other控制摄像头时,报错误码107,对应信息是要求预览组件,但是这个函数是不用预览的,最后重启摄像头又正常了.

  • 摄像头对焦失败

结果造成Tag识别失败,还会影响数据。有时画面甚至一直模糊,无法自动对焦成功。在摄像头旋转和放缩中都会出现这种情况。

  • 光线问题造成数据获取不准确

光线暗时,对较小的Tag识别不够准,可以使用SDK中的曝光

参考:
海康sdk库调用NET_DVR_CaptureJPEGPicture函数出现错误码107
海康SDK预览组件加载失败