------------------------------------------------------------------------------------------------------------------------------------------------------------
こうしん 2020-12.7
一般来说,发现移动侦测后,都需要进行抓图,
请参见
-------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------
こうしん 2021/3/15
之前一直使用的是单独的摄像机,没有发现移动侦测的回调bug,在此更正,
@CFUNCTYPE(c_int, c_long, MSesGCallback.NET_DVR_ALARMER, c_char_p, c_ulong, c_void_p)
中,第四个参数需要使用
POINTER(c_char)
-------------------------------------------------------------------------------------------------------------------------------------------------------------
在上篇博文里,我们完成了海康摄像头的配置、sdk的下载,在本篇文章,我们开始着手开发海康sdk
对于实现海康摄像头自带的移动侦测事件,大概的流程为
- 引入海康sdk
- 初始化海康sdk和设置连接时间
- 登录
- 设置报警回调函数
- 部防
- 撤防
- 退去
该流程,也是符合海康的官方sdk流程的
1 sdk引入
我们为了开发python版本的海康摄像头的相关功能,需要使用C++版本提供的sdk,假设在上篇博客中,下载的sdk路径为:D:\Backup\solo\hkoython\dll
采用如下代码读入海康sdk
from ctypes import *
class HkAdapter:
dll_path = r"D:\Backup\solo\hkoython\dll"
def load_hkdll(self):
# 载入HCCore.dll
hccode = WinDLL(__class__.dll_path + "\\HCCore.dll")
# 载入HCNetSDK.dll
hcnetsdk = cdll.LoadLibrary(__class__.dll_path + "\\HCNetSDK.dll")
return hcnetsdk
在代码段中,我们采用winDLL的形式,读入了两个dll文件,而目录下,有很多dll文件,我们是用不到的,我们只需要按照海康sdk读取我们需要的两个核心文件即可。
这里要注意,dll_path文件夹里的子文件夹 文件的名称都是不可以更改的,因为我们没有显式引入,但是hknetsdk.dll是会去调用其他dll的,因此我们保持原样放好即可。
我们这里将hcnetsdk变量返回,以后调用c++函数,采用hknetsdk.functionname()的形式即可
2.初始化
我们采用hknetsdk.functionname()的方式,调用初始化函数即可
def init(hksdk):
if not hksdk.NET_DVR_Init():
print("初始失败")
return False
if not hksdk.NET_DVR_SetConnectTime():
print("设置连接时间失败")
return False
print("初始化成功")
return True
在该init方法中,我们调用了海康dll提供的两个函数。都使用缺省参数。
NET_DVR_Init(): 初始化
NET_DVR_SetConnectTime():设置连接时间,缺省为DWORD dwWaitTime = 3000, DWORD dwTryTimes = 3
可以发现,采用ctypes调用这种没有参数函数相当简单
3.登录
3.1 ctypes结构体与数据类型对应
C++版的登录函数为
NET_DVR_USER_LOGIN_INFO loginInfo = { 0 };
NET_DVR_DEVICEINFO_V40 deviceInfo = { 0 };
//登录方式设置为同步
loginInfo.bUseAsynLogin = 0;
const char *deviceAddress, *userName, *passWord;
WORD wPort = 8000;
LONG userID;
deviceAddress = "192.168.8.112";
userName = "admin";
passWord = "hk123456";
strcpy_s(loginInfo.sDeviceAddress, deviceAddress);
strcpy_s(loginInfo.sUserName, userName);
strcpy_s(loginInfo.sPassword, passWord);
loginInfo.wPort = wPort;
userID = NET_DVR_Login_V40(&loginInfo, &deviceInfo);
可以看到,需要为登录函数NET_DVR_Login_V40传递两个参数,参数类型为两个结构体类型的地址,在ctypes中,使用Structure的子类,来定义结构体,固定写法为
class ClassName(Structure):
_fields_ = [
# 该标量的名称,形式都是固定的
# 每个元素为("结构体属性名",数据类型)的元组形式
#,结构体属性名字段必须与c++的必须要完全一致
("结构体属性1", 数据类型),
("结构体属性2", 数据类型),
...
]
结构体属性直接复制C++的即可,而数据类型需要对应到Ctype数据类型。如下图所示。
2.2 数据准备
我们分别按照C++的结构体说明,在一个文件里,定义两个类,分别表示登录所需要的结构体
from ctypes import *
class NET_DVR_DEVICEINFO_V30(Structure):
_fields_ = [
("sSerialNumber", c_byte * 48), # 序列号
("byAlarmInPortNum", c_byte), # 模拟报警输入个数
("byAlarmOutPortNum", c_byte), # 模拟报警输出个数
("byDiskNum", c_byte), # 硬盘个数
("byDVRType", c_byte), # 设备类型,详见下文列表
("byChanNum", c_byte),
("byStartChan", c_byte),
("byAudioChanNum", c_byte), # 设备语音对讲通道数
("byIPChanNum", c_byte),
("byZeroChanNum", c_byte), # 零通道编码个数
("byMainProto", c_byte), # 主码流传输协议类型:
("bySubProto", c_byte), # 字码流传输协议类型:
("bySupport", c_byte),
("bySupport1", c_byte),
("bySupport2", c_byte),
("wDevType", c_uint16), # 设备型号,详见下文列表
("bySupport3", c_byte),
("byMultiStreamProto", c_byte),
("byStartDChan", c_byte), # 起始数字通道号,0表示无数字通道,比如DVR或IPC
("byStartDTalkChan", c_byte),
("byHighDChanNum", c_byte), # 数字通道个数,高8位
("bySupport4", c_byte),
("byLanguageType", c_byte),
("byVoiceInChanNum", c_byte), # 音频输入通道数
("byStartVoiceInChanNo", c_byte), # 音频输入起始通道号,0表示无效
("bySupport5", c_byte),
("bySupport6", c_byte),
("byMirrorChanNum", c_byte), # 镜像通道个数,录播主机中用于表示导播通道
("wStartMirrorChanNo", c_uint16),
("bySupport7", c_byte),
("byRes2", c_byte )] # 保留,置为0
class NET_DVR_DEVICEINFO_V40(Structure):
_fields_ = [
# struDeviceV30结构体中包括接口体,我们只需要额外定义一下该子结构体即可
("struDeviceV30", NET_DVR_DEVICEINFO_V30),
("bySupportLock", c_byte),#设备是否支持锁定功能,bySupportLock为1时,dwSurplusLockTime和byRetryLoginTime有效
("byRetryLoginTime", c_byte),
("byPasswordLevel", c_byte),
("byProxyType", c_byte),
("dwSurplusLockTime", c_ulong),
("byCharEncodeType", c_byte),
("bySupportDev5", c_byte),
("bySupport", c_byte),
("byLoginMode", c_byte),
("dwOEMCode", c_ulong),
("iResidualValidity", c_int),
("byResidualValidity", c_byte),
("bySingleStartDTalkChan", c_byte),
("bySingleDTalkChanNums", c_byte),
("byPassWordResetLevel", c_byte),
("bySupportStreamEncrypt", c_byte),
("byMarketType", c_byte),
("byRes2", c_byte*253),
]
class NET_DVR_USER_LOGIN_INFO(Structure):
_fields_ = [
("sDeviceAddress", c_char * 129),
("byUseTransport", c_byte),
("wPort", c_uint16),
("sUserName", c_char * 64),
("sPassword", c_char * 64),
("bUseAsynLogin", c_int),
("byProxyType", c_byte),
("byUseUTCTime", c_byte),
("byLoginMode", c_byte),
("byHttps", c_byte),
("iProxyID", c_long),
("byVerifyMode", c_byte),
("byRes3", c_byte * 120)
]
3.3 login
我们获得了两个结构体,直接对其初始化,复制,调用登录即可。
def login(hksdk, url, usename, password, port=8000):
# python的String向ctype里的c_char传递的数据需要进行bytes编码
burl = bytes(url, "ascii")
busename = bytes(usename, "ascii")
bpassword = bytes(password, "ascii")
# 初始化两个结构体
login_info = NET_DVR_Login_V40.NET_DVR_USER_LOGIN_INFO()
device_info = NET_DVR_Login_V40.NET_DVR_DEVICEINFO_V40()
# 设置登录信息
login_info.wPort = port
login_info.bUseAsynLogin = 0
login_info.sUserName = busename
login_info.sPassword = bpassword
login_info.sDeviceAddress = burl
# 获得引用,byref基本等于c++的&符号
param_login = byref(login_info) # 传递的为指针则使用该种方式
param_device = byref(device_info)
# 执行NET_DVR_Login_V40函数,获取userid
useid = hksdk.NET_DVR_Login_V40(param_login, param_device)
# 登录成功时,useid的值为0、1.....,失败时为-1
# 可以调用NET_DVR_GetLastError查看错误码
if useid == -1:
print("登录失败,错误码为{}".format(hksdk.NET_DVR_GetLastError()))
else:
print("登录成功,用户id为{}".format(useid))
return useid
4 布防
4.1 设置报警回调函数
在海康sdk中,当触发了报警时间后,会自动调用用户设置的回调函数,该函数原型为
BOOL CALLBACK MSesGCallback(LONG lCommand, NET_DVR_ALARMER *pAlarmer,
char *pAlarmInfo, DWORD dwBufLen, void* pUser) ;
该回调函数需要的形参除了基本数据类型外,还需要一个结构体,因此我们先定义该结构体
from ctypes import *
class NET_DVR_ALARMER(Structure):
_fields_ = [
("byUserIDValid",c_byte),
("bySerialValid", c_byte),
("byVersionValid", c_byte),
("byDeviceNameValid", c_byte),
("byMacAddrValid",c_byte),
("byLinkPortValid", c_byte),
("byDeviceIPValid", c_byte),
("bySocketIPValid", c_byte),
("lUserID", c_long),
("sSerialNumber", c_byte*48),
("sDeviceName", c_char*32),
("byMacAddr", c_byte*6),
("wLinkPort", c_uint16),
("sDeviceIP", c_char*128),
("sSocketIP", c_char*128),
("byIpProtocol", c_byte),
("byRes1", c_byte*2),
("bJSONBroken", c_byte),
("wSocketPort", c_uint16),
("byRes2", c_byte*6)
]
我们写一个python方法作为回调函数
# 更正,此处第四个参数应该换成POINTER(c_char)
@CFUNCTYPE(c_int, c_long, MSesGCallback.NET_DVR_ALARMER, POINTER(c_char), c_ulong, c_void_p)
def alarm_callback(lCommand: c_long, pAlarmer: MSesGCallback.NET_DVR_ALARMER,
pAlarmInfo: POINTER(c_char), dwBufLen: c_ulong, pUser: c_void_p):
print("收到警报信息,类型为 {} \t发送者为 {}".format(lCommand, pAlarmer.lUserID))
if lCommand == 0x4000:
print("消息类型为 COMM_ALARM_V30", end=" --> ")
alarm_info = ALARM.NET_DVR_ALARMINFO_V30()
# 更正,换成POINTER(c_char)后,不需要调用decode方法
memmove(addressof(alarm_info), pAlarmInfo, sizeof(alarm_info))
if alarm_info.dwAlarmType == 3:
print("子事件为移动侦测")
else:
print("子事件 None")
else:
print("消息类型为None")
return 1
ctype可以用装饰器的形式来定义回调函数,明确返回值类型和输入值类型,在上述代码中为
@CFUNCTYPE(返回类型,参数类型1,参数类型2...)
该回调函数首先会判断是不是通用警报信息,如果是,在判断是不是为移动侦测。
在判断移动侦测时,我们使用了一个新的结构体NET_DVR_ALARMINFO_V30,我们将在4.2中,定义该结构体
4.2 启用回调函数,布防
布防函数为
NET_DVR_SETUPALARM_PARAM setupParam = { 0 };
setupParam.dwSize = sizeof(NET_DVR_SETUPALARM_PARAM);
setupParam.byLevel = 0; //优先级 0 高 1低
LONG handle = NET_DVR_SetupAlarmChan_V41(userID, &setupParam);
可见我们需要定义一个新的结构体NET_DVR_SETUPALARM_PARAM
class NET_DVR_SETUPALARM_PARAM(Structure):
_fields_ = [
("dwSize", c_ulong),
("byLevel", c_byte), # 布防优先级
("byAlarmInfoType", c_byte),
("byRetAlarmTypeV40", c_byte),
("byRetDevInfoVersion", c_byte),
("byRetVQDAlarmType", c_byte),
("byFaceAlarmDetection", c_byte),
("bySupport", c_byte),
("byBrokenNetHttp", c_byte),
("wTaskNo", c_uint16),
("byDeployType", c_byte),
("byRes1", c_byte*2),
("byAlarmTypeURL", c_byte),
("byCustomCtrl", c_byte),
]
class NET_DVR_ALARMINFO_V30(Structure):
_fields_ =[
("dwAlarmType",c_ulong),
("dwAlarmInputNumber",c_ulong),
("byAlarmOutputNumber",c_byte*96),
("byAlarmRelateChannel",c_byte*64),
("byChannel",c_byte*64),
("byChannel",c_byte*33)
]
据此,我们可以完成设置回调函数,启用布防
def deploy(hksdk, userid): # 布防
# 设置回调函数
callback_status = hksdk.NET_DVR_SetDVRMessageCallBack_V31(alarm_callback, None)
if callback_status == -1:
print("设置回调函数失败,错误码为".format(hksdk.NET_DVR_GetLastError()))
return -1
else:
print("设置回调函数成功")
# 启用布防
setup_alarm = ALARM.NET_DVR_SETUPALARM_PARAM()
setup_alarm.dwSize = sizeof(ALARM.NET_DVR_SETUPALARM_PARAM)
setup_alarm.byLevel = 0
setup_alarm.byAlarmInfoType = 1
handle = hksdk.NET_DVR_SetupAlarmChan_V41(userid, setup_alarm)
if handle < 0:
print("布防失败,错误码为{}".format(hksdk.NET_DVR_GetLastError()))
else:
print("布防成功")
return handle
5 撤防
撤防较为简单,只需要一个由布防得到的handle参数即可
def disdeploy(hksdk, handle): # 布防
disdeploy_result = hksdk.NET_DVR_CloseAlarmChan_V30(handle)
if disdeploy_result == -1:
print("撤防失败,错误码为{}".format(hksdk.NET_DVR_GetLastError()))
else:
print("撤防成功")
6 反初始化
将推出登录和反初始化做到一起,也较为简单,一个需要一个int类型参数useid,一个不需要参数
def uinit(hksdk, useid):
isOK = hksdk.NET_DVR_Logout(useid)
if isOK == -1:
print("登出失败错误码为{}".format(hksdk.NET_DVR_GetLastError()))
else:
print("登出成功")
hksdk.NET_DVR_Cleanup()
至此,我们所有的功能函数都写完了
7 run it
写一个main函数,执行上述过程
if __name__ == "__main__":
print("-----------初始化与登录---------")
hkadapter = HkAdapter()
hksdk = hkadapter.load_hkdll()
init(hksdk)
userid = login(hksdk, "xxx.xxx.xxx.xxx", "xxxxxx", "xxxxxxx")
print("----------初始化与登录完成---------")
print("-----------布防与撤防---------")
print("开始部防。。。")
handle = deploy(hksdk, userid)
if handle >= 0:
print("***************警告信息输出begin******************")
# 主线程sleep的时间就是程序布防的时间
time.sleep(100)
print("****************警告信息输出end*******************")
print("开始撤防。。。")
disdeploy(hksdk, handle)
print("-----------布防与撤防结束---------")
uinit(hksdk, userid)
8 After
很粗糙的写了一个小程序,还需要全方位的细致优化,大家有什么好的意见和建议可以留言
参考