ctypes调用海康威视人脸抓拍机并将抓拍的人脸上传到指定地址

ctypes对海康抓拍机的使用

概述

本文涉及到如下比较重要的学习内容:

  1. 对c语言数组的操作
  2. 对c语言指针的操作(报错结构体指针和字符串指针等)
  3. 对海康抓拍机的操作
  4. 部分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;
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值