python实现海康sdk二次开发,移动侦测事件(二)

python实现海康sdk二次开发,移动侦测事件(一)

------------------------------------------------------------------------------------------------------------------------------------------------------------

こうしん 2020-12.7

一般来说,发现移动侦测后,都需要进行抓图,

请参见

python 海康威视ipc抓图

-------------------------------------------------------------------------------------------------------------------------------------------------------------

------------------------------------------------------------------------------------------------------------------------------------------------------------

こうしん 2021/3/15

之前一直使用的是单独的摄像机,没有发现移动侦测的回调bug,在此更正,

@CFUNCTYPE(c_int, c_long, MSesGCallback.NET_DVR_ALARMER, c_char_p, c_ulong, c_void_p)

中,第四个参数需要使用

POINTER(c_char)

python 海康威视ipc抓图

-------------------------------------------------------------------------------------------------------------------------------------------------------------

在上篇博文里,我们完成了海康摄像头的配置、sdk的下载,在本篇文章,我们开始着手开发海康sdk

对于实现海康摄像头自带的移动侦测事件,大概的流程为

  1. 引入海康sdk
  2. 初始化海康sdk和设置连接时间
  3. 登录
  4. 设置报警回调函数
  5. 部防
  6. 撤防
  7. 退去

该流程,也是符合海康的官方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

很粗糙的写了一个小程序,还需要全方位的细致优化,大家有什么好的意见和建议可以留言

参考

Python实现海康威视SDK二次开发(开源库)

ctypes --- Python 的外部函数库

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值