以前做过的项目都是通过 ffmpeg c++ 来捕获摄像机的 RSTP 视频流来处理视频帧,抽空看了一下海康的SDK说明,使用 python ctypes方式a实现了对海康SDK DLL的调用, 可以进行视频预览、抓图、抽帧、云台控制、布防等任务,由于调用的是C++库,速度也很快。如果不要求对视频帧进行实时智能算法分析的话,python的速度也能满足要求,而且开发效率高。
下面简介一下开发流程与关键步骤。
1、海康SDK开发包简介
在海康威视官网注册后,可以下载开发最新的SDK,其中包含说明书。
海康 SDK 构成与其它C/C++的SDK是类似的,主要由 头文件,库文件( ,lib, .so) 以及静态链接库DLL组成, linux下库文件只有.so。
python项目导入SDK 相当简单,只需要将相关DLL文件拷贝至python项目文件夹即可,主要是下面几个文件
HCNetSDK.dll
HCCore.dll
HCPreview.dll
PlayCtrl.dll
HCCoreDevCfg.dll
或者放入1个特定文件夹,将这个文件夹加入到系统环境变量path中。只要程序运行时可以找到 DLL 就可以。
2、调用SDK的基本流程
说明书上写得很详细,以实现预览的过程为例,流程如下:
3、主要步骤介绍
1)如何导入SDK DLL
python如何调用 C/C++ DLL,请参考本人文章:Python 使用 ctypes 调用 C/C++ DLL 动态链接库
由于海康的接口较多,对每个接口函数及参数重新申明,这是个体力活。因此建议大家在学习完ctypes以后,参考本文,自己用python ctypes 语法来封装你所需要的海康SDK 接口。
用 ctypes 调用sdk函数的方式,基本思路是:
- 先用ctype来声明 SDK接口函数的形参与返回值,目的是python代码类型与 C++类型能匹配上
- 建议采用签名函数方式,分别申明函数参数artstypes,返回值类型 restype,好处是使用结构清晰。
也可以使用 Cython 来调用海康SDK,调用速度可以直接与C++编程相比了。然而,Cython学习的难度明显高于 ctypes,如果C++平时用得少,不建议用 Cython.
用ctypes 声明 SDK接口函数的形参与返回值
首先要做的就是用python ctypes 将 DLL中的接口函数的形参重新定义。如 SDK中注册设备的方法:
LONG NET_DVR_Login_V40(
LPNET_DVR_USER_LOGIN_INFO pLoginInfo,
LPNET_DVR_DEVICEINFO_V40 lpDeviceInfo);
两个形参是结构指针,python中没有直接对应的类型,因此需要通过ctypes 类型重新定义:
NET_DVR_Login_V40()参数
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),
("cbLoginResult", fLoginResultCallBack),
("pUser", c_void_p),
("bUseAsynLogin", c_uint32),
("byProxyType", c_byte),
("byUseUTCTime", c_byte),
("byLoginMode", c_byte),
("byHttps", c_byte),
("iProxyID", c_uint32),
("byVerifyMode", c_byte),
("byRes2", c_byte * 119)]
LPNET_DVR_USER_LOGIN_INFO = POINTER(NET_DVR_USER_LOGIN_INFO)
设备参数结构体 V40
class NET_DVR_DEVICEINFO_V40(ctypes.Structure):
_fields_ = [
('struDeviceV30', NET_DVR_DEVICEINFO_V30),
('bySupportLock', c_byte),
('byRetryLoginTime', c_byte),
('byPasswordLevel', c_byte),
('byProxyType', c_byte),
('dwSurplusLockTime', c_uint32),
('byCharEncodeType', c_byte),
('bySupportDev5', c_byte),
('bySupport', c_byte),
('byLoginMode', c_byte),
('dwOEMCode', c_uint32),
('iResidualValidity', c_uint32),
('byResidualValidity', c_byte),
('bySingleStartDTalkChan', c_byte),
('bySingleDTalkChanNums', c_byte),
('byPassWordResetLevel', c_byte),
('bySupportStreamEncrypt', c_byte),
('byMarketType', c_byte),
('byRes2', c_byte * 238)
]
LPNET_DVR_DEVICEINFO_V40 = POINTER(NET_DVR_DEVICEINFO_V40)
调用sdk函数示例
下面用注册设备函数 NET_DVR_Login_V40 为例,展示初始化参数,赋值,调用dll函数步骤:
def LoginDev(sdk):
'''
device_info = NET_DVR_DEVICEINFO_V30()
lUserId = Objdll.NET_DVR_Login_V30(
DEV_IP, DEV_PORT, DEV_USER_NAME, DEV_PASSWORD, byref(device_info))
'''
struLoginInfo = NET_DVR_USER_LOGIN_INFO()
struLoginInfo.bUseAsynLogin = 0
struLoginInfo.sDeviceAddress = bytes("192.168.99.247", "ascii")
struLoginInfo.wPort = 8000
struLoginInfo.sUserName = bytes("admin", "ascii")
struLoginInfo.sPassword = bytes("Admin&123", "ascii")
struLoginInfo.byLoginMode = 0
struDeviceInfoV40 = NET_DVR_DEVICEINFO_V40()
UserID = sdk.NET_DVR_Login_V40(
byref(struLoginInfo), byref(struDeviceInfoV40))
return (UserID, struDeviceInfoV40)
其它sdk函数调用过程也是类似的。
2) 开发框架说明l
因为只是1个练习,为了省事,采用了 python Tkinter 来开发界面,也可以使用QT来做。
3) 几个技术点说明
设备注册后,必须要调用 NET_DVR_RealPlay_V40() 函数进行预览画面,后面的抓图、读帧以及云台控制均要求先执行这一步。
该函数定义如下:
LONG NET_DVR_RealPlay_V40(
LONG lUserID,
LPNET_DVR_PREVIEWINFO lpPreviewInfo,
REALDATACALLBACK fRealDataCallBack_V30,
void *pUser);
可以有1个回调函数 REALDATACALLBACK
typedef void(CALLBACK *REALDATACALLBACK)(
LONG lRealHandle,
DWORD dwDataType,
BYTE *pBuffer,
DWORD dwBufSize,
void *pUser);
注意要计划好显示视频的窗口控件,获取该窗口的句柄,传给NET_DVR_RealPlay_V40,或回调函数。
捕获视频码流以及解码显示均由该回调函数完成,捕获原始的YUV视频帧也在此进行。
如果要对视频进行分析,有两种方法
1)通过sdk的抓图接口函数抓图进行分析,如 NET_DVR_CapturePicture
2) 实时性要求高,可将预览码流中的原始YUV帧l转换为RGB后,再进行
处理。
- 代码如下
import os
import platform
import tkinter
from tkinter import *
from tkinter import ttk
from HCNetSDK import *
from PlayCtrl import *
from time import sleep
import ctypes
WINDOWS_FLAG = True
win = None
funcRealDataCallBack_V30 = None
PlayCtrl_Port = c_long(-1)
Playctrldll = None
FuncDecCB = None
def GetPlatform():
sysstr = platform.system()
print('' + sysstr)
if sysstr != "Windows":
global WINDOWS_FLAG
WINDOWS_FLAG = False
def SetSDKInitCfg():
if WINDOWS_FLAG:
strPath = os.getcwd().encode('gbk')
sdk_ComPath = NET_DVR_LOCAL_SDK_PATH()
sdk_ComPath.sPath = strPath
Objdll.NET_DVR_SetSDKInitCfg(2, byref(sdk_ComPath))
Objdll.NET_DVR_SetSDKInitCfg(3, create_string_buffer(
strPath + b'\libcrypto-1_1-x64.dll'))
Objdll.NET_DVR_SetSDKInitCfg(
4, create_string_buffer(strPath + b'\libssl-1_1-x64.dll'))
else:
strPath = os.getcwd().encode('utf-8')
sdk_ComPath = NET_DVR_LOCAL_SDK_PATH()
sdk_ComPath.sPath = strPath
Objdll.NET_DVR_SetSDKInitCfg(2, byref(sdk_ComPath))
Objdll.NET_DVR_SetSDKInitCfg(
3, create_string_buffer(strPath + b'/libcrypto.so.1.1'))
Objdll.NET_DVR_SetSDKInitCfg(
4, create_string_buffer(strPath + b'/libssl.so.1.1'))
def LoginDev(sdk):
'''
device_info = NET_DVR_DEVICEINFO_V30()
lUserId = Objdll.NET_DVR_Login_V30(
DEV_IP, DEV_PORT, DEV_USER_NAME, DEV_PASSWORD, byref(device_info))
'''
struLoginInfo = NET_DVR_USER_LOGIN_INFO()
struLoginInfo.bUseAsynLogin = 0
struLoginInfo.sDeviceAddress = bytes("192.168..200", "ascii")
struLoginInfo.wPort = 8000
struLoginInfo.sUserName = bytes("admin", "ascii")
struLoginInfo.sPassword = bytes("123456", "ascii")
struLoginInfo.byLoginMode = 0
struDeviceInfoV40 = NET_DVR_DEVICEINFO_V40()
UserID = sdk.NET_DVR_Login_V40(
byref(struLoginInfo), byref(struDeviceInfoV40))
return (UserID, struDeviceInfoV40)
def DecCBFun(nPort, pBuf, nSize, pFrameInfo, nUser, nReserved2):
if pFrameInfo.contents.nType == 3:
sFileName = ('pic/test_stamp[%d].jpg' %
pFrameInfo.contents.nStamp)
nWidth = pFrameInfo.contents.nWidth
nHeight = pFrameInfo.contents.nHeight
nType = pFrameInfo.contents.nType
dwFrameNum = pFrameInfo.contents.dwFrameNum
nStamp = pFrameInfo.contents.nStamp
print(nWidth, nHeight, nType, dwFrameNum, nStamp, sFileName)
lRet = Playctrldll.PlayM4_ConvertToJpegFile(
pBuf, nSize, nWidth, nHeight, nType, c_char_p(sFileName.encode()))
if lRet == 0:
print('PlayM4_ConvertToJpegFile fail, error code is:',
Playctrldll.PlayM4_GetLastError(nPort))
else:
print('PlayM4_ConvertToJpegFile success')
def RealDataCallBack_V30(lPlayHandle, dwDataType, pBuffer, dwBufSize, pUser):
if dwDataType == NET_DVR_SYSHEAD:
Playctrldll.PlayM4_SetStreamOpenMode(PlayCtrl_Port, 0)
if Playctrldll.PlayM4_OpenStream(PlayCtrl_Port, pBuffer, dwBufSize, 1024*1024):
global FuncDecCB
FuncDecCB = DECCBFUNWIN(DecCBFun)
Playctrldll.PlayM4_SetDecCallBackExMend(
PlayCtrl_Port, FuncDecCB, None, 0, None)
if Playctrldll.PlayM4_Play(PlayCtrl_Port, cv.winfo_id()):
print(u'播放库播放成功')
else:
print(u'播放库播放失败')
else:
print(u'播放库打开流失败')
elif dwDataType == NET_DVR_STREAMDATA:
Playctrldll.PlayM4_InputData(PlayCtrl_Port, pBuffer, dwBufSize)
else:
print(u'其他数据,长度:', dwBufSize)
def OpenPreview(Objdll, lUserId, callbackFun):
'''
打开预览
'''
preview_info = NET_DVR_PREVIEWINFO()
preview_info.hPlayWnd = 0
preview_info.lChannel = 1
preview_info.dwStreamType = 0
preview_info.dwLinkMode = 0
preview_info.bBlocked = 1
preview_info.dwDisplayBufNum = 15
lRealPlayHandle = Objdll.NET_DVR_RealPlay_V40(
lUserId, byref(preview_info), callbackFun, None)
return lRealPlayHandle
def InputData(fileMp4, Playctrldll):
while True:
pFileData = fileMp4.read(4096)
if pFileData is None:
break
if not Playctrldll.PlayM4_InputData(PlayCtrl_Port, pFileData, len(pFileData)):
break
def click_capture():
print("clicked capture button")
pFileName = ctypes.c_char_p()
pFileName.value = bytes("pic/image.jpg", "utf-8")
res = Objdll.NET_DVR_CapturePicture(lRealPlayHandle, pFileName)
if res:
print("Successfullly capture picture, ", pFileName.value)
def click_left():
print("clicked button up")
def click_right():
print("clicked button")
def click_up():
print("clicked button")
def click_down():
print("clicked button")
if __name__ == '__main__':
win = tkinter.Tk()
win.resizable(0, 0)
win.overrideredirect(True)
sw = win.winfo_screenwidth()
sh = win.winfo_screenheight()
ww = 800
wh = 650
x = (sw - ww) / 2
y = (sh - wh) / 2
win.geometry("%dx%d+%d+%d" % (ww, wh, x, y))
cv = Canvas(
win,
width=760,
height=460,
bg="white",
)
cv.place(x=20, y=10)
btn_left = Button(win, text=" 左 ",
command=click_left).place(x=100, y=530)
btn_right = Button(win, text=" 右 ",
command=click_right).place(x=180, y=530)
btn_top = Button(win, text=" 上 ", command=click_up).place(x=145, y=495)
btn_down = Button(win, text=" 下 ",
command=click_down).place(x=145, y=565)
btn_capture = Button(win, text=" 放大 ",
command=click_capture).place(x=280, y=500)
btn_capture = Button(win, text=" 缩小 ",
command=click_capture).place(x=280, y=530)
btn_capture = Button(win, text=" 截图 ",
command=click_capture).place(x=280, y=560)
lbl_ip = Label(win, text="IP地址", fg="#111").place(x=480, y=490)
ent_ip = Entry(win).place(x=550, y=490)
lbl_port = Label(win, text="端口", fg="#111").place(x=480, y=515)
ent_port = Entry(win).place(x=550, y=515)
lbl_name = Label(win, text="登录名", fg="#111").place(x=480, y=540)
ent_name = Entry(win).place(x=550, y=540)
lbl_password = Label(win, text="密码", fg="#111").place(x=480, y=565)
password = StringVar()
password_entry = ttk.Entry(
win,
textvariable=password,
show='*'
)
password_entry.place(x=550, y=565)
separator = ttk.Separator(win, orient='horizontal')
separator.place(x=10, y=600, width=790)
btn_q = Button(win, text=' 退出 ', command=win.quit)
btn_q.place(x=660, y=610)
dname = 'D:\workplace\dependency\hik_lib\HCNetSDK.dll'
Objdll = ctypes.cdll.LoadLibrary(dname)
dname = 'D:\workplace\dependency\hik_lib\PlayCtrl.dll'
Playctrldll = ctypes.cdll.LoadLibrary(dname)
print("load dll successfully")
Objdll.NET_DVR_Init()
print("init device successfully ")
Objdll.NET_DVR_SetLogToFile(
3, bytes('./SdkLog_Python/', encoding="utf-8"), False)
print("config log to SdkLog_Python ")
if not Playctrldll.PlayM4_GetPort(byref(PlayCtrl_Port)):
print(u'获取播放库句柄失败')
(lUserId, device_info) = LoginDev(Objdll)
if lUserId < 0:
err = Objdll.NET_DVR_GetLastError()
print('Login device fail, error code is: %d' %
Objdll.NET_DVR_GetLastError())
Objdll.NET_DVR_Cleanup()
exit()
print("login device ")
funcRealDataCallBack_V30 = REALDATACALLBACK(RealDataCallBack_V30)
lRealPlayHandle = OpenPreview(Objdll, lUserId, funcRealDataCallBack_V30)
if lRealPlayHandle < 0:
print('Open preview fail, error code is: %d' %
Objdll.NET_DVR_GetLastError())
Objdll.NET_DVR_Logout(lUserId)
Objdll.NET_DVR_Cleanup()
exit()
win.mainloop()
Objdll.NET_DVR_StopRealPlay(lRealPlayHandle)
if PlayCtrl_Port.value > -1:
Playctrldll.PlayM4_Stop(PlayCtrl_Port)
Playctrldll.PlayM4_CloseStream(PlayCtrl_Port)
Playctrldll.PlayM4_FreePort(PlayCtrl_Port)
PlayCtrl_Port = c_long(-1)
Objdll.NET_DVR_Logout(lUserId)
Objdll.NET_DVR_Cleanup()
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)