ctypes对海康抓拍机的使用
概述
本文涉及到如下比较重要的学习内容:
- 对c语言数组的操作
- 对c语言指针的操作(报错结构体指针和字符串指针等)
- 对海康抓拍机的操作
- 部分ctypes涉及到的c的概念讲解
基本概念补充
1、调用约定如果输入错误的影响。
假定c语言用stdll写的调用约定,python使用的windll调用约定。我们这里来进行一下测试
c
DLLEXPORT int __stdcall ex(int a, int b) {
return a -b;
}
python
from ctypes import *
dll = cdll.LoadLibrary("ctypes测试")
dllc = cdll.msvcrt
get_char = dll.get_char
get_char.restype = c_char_p
# x=c_char_p()
# x = get_char(x)
# print(x)
# # print(get_char())
#结构体字节码测试
# class BASE_STRUCT(Structure):
# _pack_=4
# _fields_=[
# ("x",c_short),
# ("y",c_longlong),
# ]
# print(dll.len_BASE_STRUCT())
# x=BASE_STRUCT()
# x.x=1
# print(sizeof(x))
#cdll测试
ex=dll.ex
print(ex(3,5))
输出结果为-2
答案是:没有影响。我也是醉了。。。
当然,如果在使用回调函数的时候,还是记得使用对应的调用约定,免得出一些什么奇怪的bug。
2、字节对齐的影响。
对于结构体和联合体而言,
其内部各成员是按照顺序在一段连续的内存里面排列,当系统需要某个值的时候,直接按照一定规律进行取值。而这其中为了让cpu操作更少的次数,所有的数据的第一位都是该内存的字节数据的整数倍的位置,比如:0位置排了一个1字节的数字1,那么1,-7位都会被抛弃(假设是x64代码编译),然后第8位才排列第二个数据。这样做是以空间换时间。
所以这里就存在一个问题,保证python的字节和c的字节要对齐。对齐方式有两种,1种为c语言对齐,及增加#pragma pack(8)
在代码头,另一种是python的结构体增加字段_pack_=8
字段。
个人建议两个一起用。。。
更详细的的操作我这里就不说了,毕竟我也不太懂c。
测试代码如下:
#define DLLEXPORT extern "C" __declspec(dllexport)
#pragma pack(8)
#include <iostream>
DLLEXPORT typedef struct {
short int x;
long long y;
}BASE_STRUCT;
DLLEXPORT int len_BASE_STRUCT() {
BASE_STRUCT x = { 0 };
return sizeof(x);
}
python
from ctypes import *
dll = cdll.LoadLibrary("ctypes测试")
dllc = cdll.msvcrt
class BASE_STRUCT(Structure):
_pack_=4
_fields_=[
("x",c_short),
("y",c_longlong),
]
print(dll.len_BASE_STRUCT())
x=BASE_STRUCT()
print(sizeof(x))
正式编程
结构体
千万要记得,结构体的数据不能填错,结构体要检查字节对齐。(检查方式为检查c的字节长度和python的字节长度)
# hk_struct.py
from ctypes import *
COMM_UPLOAD_FACESNAP_RESULT = 0x1112 # 人脸识别结果上传
class NET_DVR_TIME(Structure):
_fields_ = [
("dwYear", c_uint32),
("dwMonth", c_uint32),
("dwDay", c_uint32),
("dwHour", c_uint32),
("dwMinute", c_uint32),
("dwSecond", c_uint32),
]
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),
('dwDeviceVersion', c_uint32),
('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),
]
class NET_VCA_RECT(Structure):
"""
人脸框结构
"""
_fields_ = [
("fX", c_float),
("fY", c_float),
("fWidth", c_float),
("fHeight", c_float),
]
class NET_DVR_IPADDR(Structure):
_fields_ = [("sIpV4", c_char * 16), ("byIPv6", c_byte * 128)]
class NET_VCA_TARGET_INFO(Structure):
# 注意,可能需要在外面用=的方式传递fields
_fields_ = [("dwID", c_uint32),
("struRect", NET_VCA_RECT),
("byRes", c_byte * 4),
]
class NET_VCA_DEV_INFO(Structure):
_fields_ = [
("struDevIP", NET_DVR_IPADDR),#前端设备地址
("wPort", c_uint16), # 前端设备端口号
("byChannel", c_byte), #前端设备通道
("byIvmsChannel", c_byte) #Ivms 通道
]
class NET_VCA_HUMAN_FEATURE(Structure):
_fields_ = [
("byAgeGroup", c_byte),
("bySex", c_byte),
("byEyeGlass", c_byte),
("byAge", c_byte),
("byAgeDeviation", c_byte),
("byEthnic", c_byte),
("byMask", c_byte),
("bySmile", c_byte),
("byFaceExpres", c_byte),
("byBeard", c_byte),
("byRace", c_byte),
("byHat", c_byte),
("byRes", c_byte * 4),
]
class NET_VCA_FACESNAP_RESULT(Structure):
_pack_ = 8
_fields_ = [
('dwSize', c_uint32),
('dwRelativeTime', c_uint32),
('dwAbsTime', c_uint32),
('dwFacePicID', c_uint32),
('dwFaceScore', c_uint32),
('struTargetInfo', NET_VCA_TARGET_INFO),
('struRect', NET_VCA_RECT),
('struDevInfo', NET_VCA_DEV_INFO),
('dwFacePicLen', c_uint32),
('dwBackgroundPicLen', c_uint32),
('bySmart', c_byte),
('byAlarmEndMark', c_byte),
('byRepeatTimes', c_byte),
('byUploadEventDataType', c_byte),
('struFeature', NET_VCA_HUMAN_FEATURE),
('fStayDuration', c_float),
('sStorageIP', c_char * 16),
('wStoragePort', c_uint16),
('wDevInfoIvmsChannelEx', c_uint16),
('byFacePicQuality', c_byte),
('byUIDLen', c_byte),
('byLivenessDetectionStatus', c_byte),
('byAddInfo', c_byte),
('*pUIDBuffer', POINTER(c_byte)),
('*pAddInfoBuffer', POINTER(c_byte)),
('byTimeDiffFlag', c_byte),
('cTimeDifferenceH', c_char),
('cTimeDifferenceM', c_char),
('byBrokenNetHttp', c_byte),
('pBuffer1', POINTER(c_byte)),
('pBuffer2', POINTER(c_byte)),
]
# 结构体不要随便改顺序,会影响字节码读取。
class RETURN_STRUCT(Structure):
_fields_ = [
("faceBuffer", c_void_p),
("backgroundBuffer", c_void_p),
("face_size", c_uint32),
("background_size", c_uint32),
("dwAbsTime", POINTER(NET_DVR_TIME)),
("dwRelativeTime", POINTER(NET_DVR_TIME)),
("face_id", c_uint32),
("deviceInfo", POINTER(NET_VCA_DEV_INFO)),
]
需要使用的海康函数
由于这里自己将部分不重要的函数实现全部放在c里面了,所以生成的函数比较少。
# hk_dll.py
from ctypes import *
import os
from hk_struct import NET_VCA_FACESNAP_RESULT, NET_DVR_TIME, NET_DVR_ALARMER, RETURN_STRUCT
import ctypes
# 获取所有的库文件到一个列表
path = r"C:\Users\Cs\Desktop\hk_sdk\x64\Debug\\"
# 参考网上的部分代码
def file_name(file_dir):
pathss = []
for root, dirs, files in os.walk(file_dir):
for file in files:
pathss.append(path + file)
return pathss
print("注意使用的是stdcall,在linux要切换为cdll,参考:https://www.jianshu.com/p/d12c3b4ed1cc")
dll_list = file_name(path)
lUserID = 0
lChannel = 1
def get_fun(func_name):
"""
获取链接并返回函数名称对应的函数头
:param func_name:
:return:
"""
for HK_dll in dll_list:
try:
lib = ctypes.cdll.LoadLibrary(HK_dll)
try:
value = eval("lib.%s" % func_name)
# print("执行成功,返回值:" + str(value))
return value
except:
continue
except:
# print("库文件载入失败:" + HK_dll)
continue
print("没有找到接口!")
return False
dllc = cdll.msvcrt
memcpy = dllc.memcpy
memcpy.restype = c_void_p
memcpy.argtypes = (c_void_p, c_void_p, c_size_t)
malloc = dllc.malloc
malloc.restype = c_void_p
malloc.argtypes = (c_size_t,)
init_camera = get_fun("init_camera")
init_camera.restype = c_int
b64fs = get_fun("b64fs")
b64fs.restype = POINTER(RETURN_STRUCT)
b64fs.argtypes = (c_long, POINTER(NET_DVR_ALARMER), c_char_p, POINTER(RETURN_STRUCT))
b64fss = get_fun("b64fss")
b64fss.restype = RETURN_STRUCT
b64fss.argtypes = (c_long, POINTER(NET_DVR_ALARMER), c_char_p)
get_sizeof_STRUCT = get_fun("get_sizeof_STRUCT")
get_sizeof_STRUCT.restype = c_int
get___t = get_fun("get_result_NET_VCA_FACESNAP_RESULT")
get___t.argtypes = (c_long, POINTER(NET_DVR_ALARMER), c_char_p, POINTER(NET_VCA_FACESNAP_RESULT))
# get___t.restype=POINTER(NET_VCA_FACESNAP_RESULT)
len_NET_VCA_FACESNAP_RESULT = get_fun("len_NET_VCA_FACESNAP_RESULT")
# 用来检查结构体长度的
# print("len_c:",len_NET_VCA_FACESNAP_RESULT())
# print("len_p:",sizeof(NET_VCA_FACESNAP_RESULT))
get_NET_DVR_TIME = get_fun("get_NET_DVR_TIME")
get_NET_DVR_TIME.restype = NET_DVR_TIME
get_NET_DVR_TIME.argtypes = (c_uint32,)
开始使用的核心代码
"""
@version: 1.0
@author: chise
@time : 2019/09/14 11:15
"""
from function import message_callback, message_callback2, message_callback3, msgcbk4
from hk_dll import init_camera, get_sizeof_STRUCT
import ctypes
import time
from multiprocessing import Process
from hk_struct import RETURN_STRUCT
def init(ip, name, pwd):
ip = ctypes.c_char_p(bytes(ip, "utf-8"))
# ip=ctypes.c_char_p(ip)
name = ctypes.c_char_p(bytes(name, "utf-8"))
pwd = ctypes.c_char_p(bytes(pwd, "utf-8"))
print(get_sizeof_STRUCT())
print(ctypes.sizeof(RETURN_STRUCT()))
# init_camera(ip, name, pwd, message_callback3)
init_camera(ip, name, pwd, msgcbk4)
if __name__ == "__main__":
p1 = Process(target=init, args=("192.168.2.64", "admin", "admin"))
p1.start()
time.sleep(100000)
其他
还有一些基础配置和方法,这里就不写出来了。
对海康sdk进行一些封装
这里主要是进行编程的时候对ctypes还不是很了解,所以部分工作就在海康sdk上完成的。
怎么配置环境和编译不用我教了吧。。
//hk_sdk.c
#include <stdio.h>
#include <iostream>
#include "Windows.h"
#include <HCNetSDK.h>
#include "Base64.h"
#pragma warning(disable:4996)
#pragma pach(4)
using namespace std;
#define DLLEXPORT extern "C" __declspec(dllexport)
//时间解析宏定义
#define GET_YEAR(_time_) (((_time_)>>26) + 2000)
#define GET_MONTH(_time_) (((_time_)>>22) & 15)
#define GET_DAY(_time_) (((_time_)>>17) & 31)
#define GET_HOUR(_time_) (((_time_)>>12) & 31)
#define GET_MINUTE(_time_) (((_time_)>>6) & 63)
#define GET_SECOND(_time_) (((_time_)>>0) & 63)
DLLEXPORT int len_NET_VCA_FACESNAP_RESULT() {//获取结构体长度,判断是否没有进行字节对齐
NET_VCA_FACESNAP_RESULT struFaceSnap = { 0 };
return sizeof(NET_VCA_FACESNAP_RESULT);
}
DLLEXPORT NET_DVR_TIME get_NET_DVR_TIME(DWORD t) {//这个函数很坑,相对时间转换是错的。妈个鸡,看不懂海康的时间坐标系。
NET_DVR_TIME struAbsTime = { 0 };
struAbsTime.dwYear = GET_YEAR(t);
struAbsTime.dwMonth = GET_MONTH(t);
struAbsTime.dwDay = GET_DAY(t);
struAbsTime.dwHour = GET_HOUR(t);
struAbsTime.dwMinute = GET_MINUTE(t);
struAbsTime.dwSecond = GET_SECOND(t);
return struAbsTime;
}
DLLEXPORT int get_sizeof_STRUCT() {
RETURN_STRUCT xxxx = { 0 };
return sizeof(xxxx);
}
//原始的回调函数,留在这里做参考的,参考自己怎么写回调函数
BOOL CALLBACK MessageCallback(LONG lCommand, NET_DVR_ALARMER* pAlarmer, char* pAlarmInfo, DWORD dwBufLen, void* pUser)
{
switch (lCommand)
{
case COMM_ALARM_FACE_DETECTION: //人脸侦测报警信息
{
NET_DVR_FACE_DETECTION struFaceDetectionAlarm = { 0 };
memcpy(&struFaceDetectionAlarm, pAlarmInfo, sizeof(NET_DVR_FACE_DETECTION));
NET_DVR_TIME struAbsTime = { 0 };
struAbsTime.dwYear = GET_YEAR(struFaceDetectionAlarm.dwAbsTime);
struAbsTime.dwMonth = GET_MONTH(struFaceDetectionAlarm.dwAbsTime);
struAbsTime.dwDay = GET_DAY(struFaceDetectionAlarm.dwAbsTime);
struAbsTime.dwHour = GET_HOUR(struFaceDetectionAlarm.dwAbsTime);
struAbsTime.dwMinute = GET_MINUTE(struFaceDetectionAlarm.dwAbsTime);
struAbsTime.dwSecond = GET_SECOND(struFaceDetectionAlarm.dwAbsTime);
//保存抓拍场景图片
if (struFaceDetectionAlarm.dwBackgroundPicLen > 0 && struFaceDetectionAlarm.pBackgroundPicpBuffer != NULL)
{
char cFilename[256] = { 0 };
HANDLE hFile;
DWORD dwReturn;
char chTime[128];
sprintf(chTime, "%4.4d%2.2d%2.2d%2.2d%2.2d%2.2d", struAbsTime.dwYear, struAbsTime.dwMonth, struAbsTime.dwDay, struAbsTime.dwHour, struAbsTime.dwMinute, struAbsTime.dwSecond);
sprintf(cFilename, "FaceDetectionBackPic[%s][%s].jpg", struFaceDetectionAlarm.struDevInfo.struDevIP.sIpV4, chTime);
hFile = CreateFile(cFilename, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
break;
}
WriteFile(hFile, struFaceDetectionAlarm.pBackgroundPicpBuffer, struFaceDetectionAlarm.dwBackgroundPicLen, &dwReturn, NULL);
CloseHandle(hFile);
hFile = INVALID_HANDLE_VALUE;
}
printf("人脸侦测报警[0x%x]: Abs[%4.4d%2.2d%2.2d%2.2d%2.2d%2.2d] Dev[ip:%s,port:%d,ivmsChan:%d] \n", \
lCommand, struAbsTime.dwYear, struAbsTime.dwMonth, struAbsTime.dwDay, struAbsTime.dwHour, \
struAbsTime.dwMinute, struAbsTime.dwSecond, struFaceDetectionAlarm.struDevInfo.struDevIP.sIpV4, \
struFaceDetectionAlarm.struDevInfo.wPort, struFaceDetectionAlarm.struDevInfo.byIvmsChannel);
}
case COMM_UPLOAD_FACESNAP_RESULT: //人脸抓拍报警信息
{
NET_VCA_FACESNAP_RESULT struFaceSnap = { 0 };
memcpy(&struFaceSnap, pAlarmInfo, sizeof(NET_VCA_FACESNAP_RESULT));
NET_DVR_TIME struAbsTime = { 0 };
struAbsTime.dwYear = GET_YEAR(struFaceSnap.dwAbsTime);
struAbsTime.dwMonth = GET_MONTH(struFaceSnap.dwAbsTime);
struAbsTime.dwDay = GET_DAY(struFaceSnap.dwAbsTime);
struAbsTime.dwHour = GET_HOUR(struFaceSnap.dwAbsTime);
struAbsTime.dwMinute = GET_MINUTE(struFaceSnap.dwAbsTime);
struAbsTime.dwSecond = GET_SECOND(struFaceSnap.dwAbsTime);
//保存抓拍场景图片
if (struFaceSnap.dwBackgroundPicLen > 0 && struFaceSnap.pBuffer2 != NULL)
{
char cFilename[256] = { 0 };
HANDLE hFile;
DWORD dwReturn;
char chTime[128];
sprintf(chTime, "%4.4d%2.2d%2.2d%2.2d%2.2d%2.2d", struAbsTime.dwYear, struAbsTime.dwMonth, struAbsTime.dwDay, struAbsTime.dwHour, struAbsTime.dwMinute, struAbsTime.dwSecond);
sprintf(cFilename, "FaceSnapBackPic[%s][%s].jpg", struFaceSnap.struDevInfo.struDevIP.sIpV4, chTime);
hFile = CreateFile(cFilename, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
break;
}
WriteFile(hFile, struFaceSnap.pBuffer2, struFaceSnap.dwBackgroundPicLen, &dwReturn, NULL);
CloseHandle(hFile);
hFile = INVALID_HANDLE_VALUE;
}
printf("人脸抓拍报警[0x%x]: Abs[%4.4d%2.2d%2.2d%2.2d%2.2d%2.2d] Dev[ip:%s,port:%d,ivmsChan:%d] \n", \
lCommand, struAbsTime.dwYear, struAbsTime.dwMonth, struAbsTime.dwDay, struAbsTime.dwHour, \
struAbsTime.dwMinute, struAbsTime.dwSecond, struFaceSnap.struDevInfo.struDevIP.sIpV4, \
struFaceSnap.struDevInfo.wPort, struFaceSnap.struDevInfo.byIvmsChannel);
}
break;
default:
printf("其他报警,报警信息类型: 0x%x\n", lCommand);
break;
}
return TRUE;
}
//把初始化放在c语言里面了,其实这是不好的,万一被垃圾回收了呢。。。这里以后在python实现
DLLEXPORT int init_camera(char *ip, char *username, char *password, MSGCallBack_V31 p) {
//---------------------------------------
// 初始化
NET_DVR_Init();
//设置连接时间与重连时间
NET_DVR_SetConnectTime(2000, 1);
NET_DVR_SetReconnect(10000, true);
//---------------------------------------
// 注册设备
LONG lUserID;
//登录参数,包括设备地址、登录用户、密码等
NET_DVR_USER_LOGIN_INFO struLoginInfo = { 0 };
struLoginInfo.bUseAsynLogin = 0; //同步登录方式
strcpy(struLoginInfo.sDeviceAddress, ip); //设备IP地址
struLoginInfo.wPort = 8000; //设备服务端口
strcpy(struLoginInfo.sUserName, username); //设备登录用户名
strcpy(struLoginInfo.sPassword, password); //设备登录密码
//设备信息, 输出参数
NET_DVR_DEVICEINFO_V40 struDeviceInfoV40 = { 0 };
lUserID = NET_DVR_Login_V40(&struLoginInfo, &struDeviceInfoV40);
if (lUserID < 0)
{
printf("Login failed, error code: %d\n", NET_DVR_GetLastError());
NET_DVR_Cleanup();
return 0;
}
//设置报警回调函数
NET_DVR_SetDVRMessageCallBack_V31(p, NULL);
//启用布防
LONG lHandle;
NET_DVR_SETUPALARM_PARAM struAlarmParam = { 0 };
struAlarmParam.dwSize = sizeof(struAlarmParam);
struAlarmParam.byFaceAlarmDetection = 0; //人脸侦测报警,设备支持人脸侦测功能的前提下,上传COMM_ALARM_FACE_DETECTION类型报警信息
//其他报警布防参数不需要设置,不支持
lHandle = NET_DVR_SetupAlarmChan_V41(lUserID, &struAlarmParam);
if (lHandle < 0)
{
printf("NET_DVR_SetupAlarmChan_V41 error, %d\n", NET_DVR_GetLastError());
NET_DVR_Logout(lUserID);
NET_DVR_Cleanup();
return 0;
}
Sleep(5000000000); //等待过程中,如果设备上传报警信息,在报警回调函数里面接收和处理报警信息
//撤销布防上传通道
if (!NET_DVR_CloseAlarmChan_V30(lHandle))
{
printf("NET_DVR_CloseAlarmChan_V30 error, %d\n", NET_DVR_GetLastError());
NET_DVR_Logout(lUserID);
NET_DVR_Cleanup();
return 0;
}
//注销用户
NET_DVR_Logout(lUserID);
//释放SDK资源
NET_DVR_Cleanup();
return 0;
}