yolov8n-pose转rknn格式,实现海康移动监测+行为识别

板子最好先安装mini-anaconda,我自己安装的python3.9版本

一、rknn官方sdk下载:
https://meta.zbox.filez.com/v/link/view/ef37a9687973439f94f5b06837f12527
提取码:rknn

在这里插入图片描述

在这里插入图片描述
二、模型转换(安装依赖文件)
ubuntu环境上先执行onnx–>rknn
在这里插入图片描述注意:rknn开发板上,不能使用RKNN,需要使用RKNNLite
在这里插入图片描述

报错,缺少库文件
解决办法:wget https://github.com/airockchip/rknn-toolkit2/blob/v2.3.0/rknpu2/runtime/Linux/librknn_api/aarch64/librknnrt.so
然后移动到/usr/lib 下

在这里插入图片描述

运行demo成功!!!
在这里插入图片描述

四、海康相机sdk

下载arm版本的sdk,添加所有的库文件

在这里插入图片描述在这里插入图片描述

完整代码:移动监测+行为识别+数据推送

from ctypes import *
import sys
import os
import time
from datetime import datetime
# import mysql.connector
from ultralytics import YOLO
import cv2
import requests
# import paho.mqtt.client as mqtt
import json
import numpy as np

# 在文件开头添加一个全局计数器字典
ALARM_COUNTERS = {
    "移动侦测报警": 0,
    "硬盘满": 0,
    "硬盘出错": 0,
    "信号丢失": 0,
    "移动侦测报警触发": 0,
    "非法访问": 0,
    "输入/输出视频制式不匹配": 0,
    "视频信号异常": 0,
    "录像/抓图异常": 0,
    "IP地址冲突": 0,
}

# 在全局变量区域添加最后抓图时间记录
LAST_CAPTURE_TIME = 0
CAPTURE_INTERVAL = 2  # 抓图间隔时间(秒)

# 在文件开头的全局变量区域添加
LAST_ALARM_TIME = 0  # 记录上次报警时间
ALARM_INTERVAL = 2  # 报警间隔时间(秒)

# 在文件开头的全局变量区域添加
LAST_DETECT_TIME = 0  # 记录上次人体检测时间
DETECT_INTERVAL = 60  # 人体检测间隔时间(秒)

# 添加设备信息结构体定义
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),            # 能力,位与结果为0表示不支持,1表示支持
        ("bySupport1", c_byte),           # 能力集扩充
        ("bySupport2", c_byte),           # 能力集扩充
        ("wDevType", c_uint16),           # 设备型号
        ("bySupport3", c_byte),           # 能力集扩充
        ("byMultiStreamProto", c_byte),   # 是否支持多码流
        ("byStartDChan", c_byte),         # 起始数字通道号
        ("byStartDTalkChan", c_byte),     # 起始数字对讲通道号
        ("byHighDChanNum", c_byte),       # 数字通道个数,高位
        ("bySupport4", c_byte),           # 能力集扩充
        ("byLanguageType", c_byte),       # 支持语种能力
        ("byVoiceInChanNum", c_byte),     # 音频输入通道数
        ("byStartVoiceInChanNo", c_byte), # 音频输入起始通道号
        ("bySupport5", c_byte),           # 能力集扩充
        ("bySupport6", c_byte),           # 能力集扩充
        ("byMirrorChanNum", c_byte),      # 镜像通道个数
        ("wStartMirrorChanNo", c_uint16), # 起始镜像通道号
        ("bySupport7", c_byte),           # 能力集扩充
        ("byRes2", c_byte * 2)            # 保留字节
    ]

# 添加布防参数结构体
class NET_DVR_SETUPALARM_PARAM(Structure):
    _fields_ = [
        ("dwSize", c_uint32),
        ("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 * 3),
        ("byAlarmTypeURL", c_byte),
        ("byCustomCtrl", c_byte)
    ]

# 添加抓图结构体
class NET_DVR_JPEGPARA(Structure):
    _fields_ = [
        ("wPicSize", c_ushort),
        ("wPicQuality", c_ushort)
    ]

# 添加新的抓图结构体
class NET_DVR_JPEGPARA_NEW(Structure):
    _fields_ = [
        ("wPicSize", c_ushort),        # 图片尺寸
        ("wPicQuality", c_ushort),     # 图片质量
        ("dwPicSize", c_uint32),       # 图片大小
        ("byRes", c_byte * 224)        # 保留字节
    ]

# 加载海康威视的SDK动态库
def load_hk_sdk():
    if os.name == 'nt':  # Windows系统
        sdk_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), './lib')
        os.environ['PATH'] += ';' + sdk_dir
        return CDLL(os.path.join(sdk_dir, 'HCNetSDK.dll'))
    else:  # Linux系统
        sdk_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'lib/linux')
        return CDLL(os.path.join(sdk_dir, 'libhcnetsdk.so'))

# 修改报警回调函数
@CFUNCTYPE(None, c_int, POINTER(c_int), POINTER(c_int), c_int, c_void_p)
def alarm_callback(lCommand, pAlarmer, pAlarmInfo, dwBufLen, pUser):
    global LAST_CAPTURE_TIME, LAST_ALARM_TIME, LAST_DETECT_TIME, ALARM_COUNTERS
    
    try:
        current_time = time.time()
        # 检查报警时间间隔
        time_since_last_alarm = current_time - LAST_ALARM_TIME
        if time_since_last_alarm < ALARM_INTERVAL:
            # print(f"距离上次报警仅过去 {time_since_last_alarm:.1f} 秒,忽略本次报警")
            return
            
        # 更新报警时间
        LAST_ALARM_TIME = current_time
        
        if lCommand == 0x1100:  # 移动侦测报警
            ALARM_COUNTERS["移动侦测报警"] += 1
            # print(f"移动侦测报警触发 (总计: {ALARM_COUNTERS['移动侦测报警']}次)")
        elif lCommand == 0x4000:  # 异常报警
            alarm_info = cast(pAlarmInfo, POINTER(NET_DVR_ALARMINFO)).contents
            exception_type = alarm_info.dwAlarmType
            # print(f"异常类型: {exception_type}")
            
            exception_types = {
                0: "信号量报警",
                1: "硬盘满",
                2: "信号丢失",
                3: "移动侦测",
                4: "硬盘未格式化",
                5: "读写硬盘出错",
                6: "遮挡报警",
                7: "制式不匹配",
                8: "非法访问",
            }
            
            exception_msg = exception_types.get(exception_type, "未知异常类型")
            if exception_msg not in ALARM_COUNTERS:
                ALARM_COUNTERS[exception_msg] = 0
            ALARM_COUNTERS[exception_msg] += 1
            # print(f"异常类型: {exception_msg} (总计: {ALARM_COUNTERS[exception_msg]}次)")
            
            # 当检测到移动侦测时,先检查上次异常检测的时间间隔
            if exception_type == 3:
                time_since_last_detect = current_time - LAST_DETECT_TIME
                if time_since_last_detect < DETECT_INTERVAL:
                    # print(f"距离上次异常检测仅过去 {time_since_last_detect:.1f} 秒,跳过本次检测")
                    return
                
                try:
                    monitor = cast(pUser, py_object).value
                    if monitor.capture_picture():
                        print(f"抓图成功,报警时间: {datetime.fromtimestamp(current_time)}")
                except Exception as e:
                    print(f"抓图过程出错: {str(e)}")
                    
    except Exception as e:
        print(f"报警回调函数出错: {str(e)}")

# 添加异常信息结构体
class NET_DVR_ALARMINFO(Structure):
    _fields_ = [
        ("dwAlarmType", c_uint32),        # 报警类型
        ("dwAlarmInputNumber", c_uint32),  # 报警输入端口
        ("dwAlarmOutputNumber", c_byte * 4),  # 报警输出端口
        ("dwAlarmRelateChannel", c_byte * 4),  # 报警触发的通道
        ("dwChannel", c_byte * 4),         # 现在报警的通道
        ("dwDiskNumber", c_byte * 4)       # 发生报警的硬盘
    ]

# 添加 MQTT 客户端类
class MQTTPublisher:
    def __init__(self):
        self.client = mqtt.Client()
        try:
            # 连接到 MQTT 服务器
            self.client.connect("127.0.0.1", 1883, 60)
            self.client.loop_start()
            print("MQTT 客户端连接成功")
        except Exception as e:
            print(f"MQTT 客户端连接失败: {str(e)}")

    def publish_alarm(self, device_id, alarm_type, detect_image_url, alarm_time):
        try:
            # 构建消息内容
            message = {
                "device_id": device_id,
                "alarm_type": alarm_type,
                "detect_image_url": detect_image_url,
                "alarm_time": alarm_time
            }
            # 发布消息
            self.client.publish("camera/alarm", json.dumps(message))
            print("MQTT 消息发送成功")
        except Exception as e:
            print(f"MQTT 消息发送失败: {str(e)}")

    def disconnect(self):
        self.client.loop_stop()
        self.client.disconnect()

# 添加数据库配置和操作类
class DatabaseManager:
    def __init__(self):
        # self.conn = mysql.connector.connect(
        #     host="localhost",
        #     user="root",
        #     password="",
        #     database="test"
        # )
        #self.cursor = self.conn.cursor()
        #self.mqtt_publisher = MQTTPublisher()
        self.access_token = None
        self.token_expire_time = 0

    def get_access_token(self):
        """获取访问令牌"""
        try:
            current_time = time.time()
            # 如果token未过期,直接返回
            if self.access_token and current_time < self.token_expire_time:
                return self.access_token

            # 请求新token
            data = {
                'client_id': 'video',
                'client_secret': 'isti123@',
                'grant_type': 'client_credentials'
            }
            headers = {
                'Content-Type': 'application/x-www-form-urlencoded'
            }
            
            response = requests.post(
                'http://192.168.30.9:9001/oauth/token',
                data=data,
                headers=headers
            )
            
            if response.status_code == 200:
                result = response.json()
                self.access_token = result['access_token']
                # 设置token过期时间(提前5分钟过期)
                self.token_expire_time = current_time + result['expires_in'] - 300
                print("获取token成功")
                return self.access_token
            else:
                print(f"获取token失败,状态码: {response.status_code}")
                print(f"响应内容: {response.text}")
                return None
        except Exception as e:
            print(f"获取token异常: {str(e)}")
            return None

    def push_to_api(self, device_id, alarm_type, detect_image_url):
        """推送数据到HTTP接口"""
        try:
            # 获取token
            token = self.get_access_token()
            if not token:
                print("无法获取token,取消推送")
                return

            # 从device_id中分离设备编号和通道号
            device_serial, channel_no = device_id.split('_')
            
            # 构建请求数据
            data = {
                "deviceSerial": device_serial,
                "channelNo": int(channel_no),
                "alarmType": str(alarm_type),
                "detectImageUrl": detect_image_url
            }
            
            # 构建请求头
            headers = {
                'Content-Type': 'application/json',
                'Authorization': f'Bearer {token}'
            }
            
            # 发送POST请求
            response = requests.post(
                'http://192.168.30.9:7887/video/detect/upload',
                json=data,
                headers=headers,
                timeout=5
            )

            print(f"推送数据: {data}")
            print(f"响应状态码: {response.status_code}")
            print(f"响应内容: {response.text}")
            
            if response.status_code == 200:
                print("数据推送成功")
            else:
                print(f"数据推送失败,状态码: {response.status_code}")
                print(f"响应内容: {response.text}")
                
        except Exception as e:
            print(f"数据推送异常: {str(e)}")

    def insert_alarm_record(self, device_id, alarm_type, detect_image_url=None):
        try:
            # 保存到数据库
            # sql = """INSERT INTO alarm_detect 
            #         (device_id, alarm_type, alarm_time, detect_image_url) 
            #         VALUES (%s, %s, %s, %s)"""
            # alarm_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            # values = (device_id, alarm_type, alarm_time, detect_image_url)
            # self.cursor.execute(sql, values)
            # self.conn.commit()
            # print("报警记录已插入数据库")

            # 发送 MQTT 消息
            # self.mqtt_publisher.publish_alarm(
            #     device_id=device_id,
            #     alarm_type=alarm_type,
            #     detect_image_url=detect_image_url,
            #     alarm_time=alarm_time
            # )

            # 推送数据到HTTP接口
            if detect_image_url:  # 只有在有图片URL时才推送
                self.push_to_api(device_id, alarm_type, detect_image_url)

        except Exception as e:
            print(f"推送数据失败: {str(e)}")

    def close(self):
        # self.cursor.close()
        # self.conn.close()
        # self.mqtt_publisher.disconnect()
        pass

    def upload_image(self, image_path):
        """上传图片到指定服务器"""
        try:
            # 获取token
            token = self.get_access_token()
            if not token:
                print("无法获取token,取消上传")
                return None

            with open(image_path, 'rb') as f:
                files = {'imageFile': f}
                headers = {
                    'Authorization': f'Bearer {token}'
                }
                print(f"开始上传图片: {image_path}")
                response = requests.post(
                    'http://192.168.30.9:7887/video/detect/image',
                    files=files,
                    headers=headers
                )
                print(f"上传响应状态码: {response.status_code}")
                print(f"上传响应内容: {response.text}")
                
                if response.status_code == 200:
                    result = response.json()
                    if 'data' in result:
                        print(f"获取到图片URL: {result['data']}")
                        return result['data']
                    else:
                        print("响应中没有data字段")
                else:
                    print(f"上传失败,状态码: {response.status_code}")
                return None
        except Exception as e:
            print(f"图片上传过程发生异常: {str(e)}")
            return None

class AlarmMonitor:
    def __init__(self, ip, port, username, password, device_no, channel):
        self.ip = ip
        self.port = port
        self.username = username
        self.password = password
        self.sdk = load_hk_sdk()
        self.device_handle = -1
        self.alarm_handle = -1
        
        # 使用设备编号+通道号作为设备ID
        self.device_id = f"{device_no}_{channel}"
        self.db = DatabaseManager()
        
        # 确保pic目录存在
        self.pic_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'pic')
        if not os.path.exists(self.pic_dir):
            os.makedirs(self.pic_dir)

        # 修改为使用 pose 检测模型
        self.model = YOLO("yolov8n-pose.pt")  # 初始化YOLO pose模型

    def init_device(self):
        # 初始化SDK
        init_result = self.sdk.NET_DVR_Init()
        if init_result == 0:
            print("SDK初始化失败")
            return False

        # 设置连接超时时间和重连时间
        self.sdk.NET_DVR_SetConnectTime(2000, 1)
        self.sdk.NET_DVR_SetReconnect(10000, True)

        # 注册设备
        device_info = NET_DVR_DEVICEINFO_V30()
        self.device_handle = self.sdk.NET_DVR_Login_V30(
            bytes(self.ip, 'utf-8'),
            self.port,
            bytes(self.username, 'utf-8'),
            bytes(self.password, 'utf-8'),
            byref(device_info)
        )

        if self.device_handle < 0:
            print("设备登录失败")
            self.get_last_error()
            return False
        
        print("设备登录成功")

        if self.device_handle >= 0:
            # 设置布防参数
            alarm_param = NET_DVR_SETUPALARM_PARAM()
            alarm_param.dwSize = sizeof(NET_DVR_SETUPALARM_PARAM)
            alarm_param.byLevel = 1  # 布防优先级
            alarm_param.byAlarmInfoType = 1  # 智能交通报警信息上传类型:0- 老报警信息(NET_DVR_PLATE_RESULT),1- 新报警信息(NET_ITS_PLATE_RESULT)
            
            # 布防
            self.alarm_handle = self.sdk.NET_DVR_SetupAlarmChan_V41(self.device_handle, byref(alarm_param))
            if self.alarm_handle < 0:
                print("布防失败")
                return False
            
            print("布防成功")
            return True
        return False

    def start_alarm_listen(self):
        if self.device_handle < 0:
            print("设备未登录")
            return False

        # 设置报警回调函数,传入self作为用户数据
        self.sdk.NET_DVR_SetDVRMessageCallBack_V31(alarm_callback, py_object(self))

        # 启动报警监听
        self.alarm_handle = self.sdk.NET_DVR_StartListen_V30(
            self.device_handle,
            bytes("0.0.0.0", 'utf-8'),
            8000,
            None,
            None
        )

        if self.alarm_handle < 0:
            print("启动报警监听失败")
            return False

        print("报警监听启动成功")
        return True

    def stop_alarm_listen(self):
        # 在停止监听前打印统计信息
        self.print_alarm_statistics()
        
        if self.alarm_handle >= 0:
            # 撤防
            self.sdk.NET_DVR_CloseAlarmChan_V30(self.alarm_handle)
            self.alarm_handle = -1

        if self.device_handle >= 0:
            self.sdk.NET_DVR_Logout(self.device_handle)
            self.device_handle = -1

        self.sdk.NET_DVR_Cleanup()
        self.db.close()  # 关闭数据库连接

    def get_last_error(self):
        error_code = self.sdk.NET_DVR_GetLastError()
        print(f"错误码: {error_code}")
        return error_code

    def print_alarm_statistics(self):
        print("\n报警统计信息:")
        print("-" * 40)
        for alarm_type, count in ALARM_COUNTERS.items():
            if count > 0:  # 只打印发生过的报警类型
                print(f"{alarm_type}: {count}次")
        print("-" * 40)

    def detect_person(self, image_path):
        """使用YOLO pose检测图片中是否有人体角度超过80度"""
        try:
            # 读取图片并旋转180度
            image = cv2.imread(image_path)
            image = cv2.rotate(image, cv2.ROTATE_180)

            # YOLO pose检测
            results = self.model.predict(
                image,
                save=False,
                project="results",
                name="my_results",
                exist_ok=True
            )
            
            has_angle_over_80 = False
            result_image_path = None
            
            # 检查是否检测到人并计算角度
            for result in results:
                keypoints = result.keypoints  # 获取关键点数据
                if len(keypoints) > 0:  # 如果检测到关键点
                    for i in range(len(keypoints)):
                        np_arr = keypoints[i].xy.cpu().numpy()
                        
                        # 获取关键点坐标
                        left_hip = np_arr[0][11]    # 左髋
                        left_knee = np_arr[0][13]   # 左膝
                        right_hip = np_arr[0][12]   # 右髋
                        right_knee = np_arr[0][14]  # 右膝
                        left_shoulder = np_arr[0][5] # 左肩
                        left_wrist = np_arr[0][9]    # 左手腕
                        right_shoulder = np_arr[0][6]# 右肩
                        right_wrist = np_arr[0][10]  # 右手腕

                        # 检查所有关键点是否有效
                        key_points = [left_hip, left_knee, right_hip, right_knee,
                                    left_shoulder, left_wrist, right_shoulder, right_wrist]
                        if not all(all(point) for point in key_points):
                            print("存在无效关键点,跳过当前姿态分析")
                            continue
                            
                        # 标记所有关键点
                        for j in range(len(np_arr[0])):
                            point = tuple(map(int, np_arr[0][j]))
                            cv2.circle(image, point, 10, (0, 255, 255), -1)  # 黄色,半径10,实心圆
                            cv2.circle(image, point, 10, (0, 0, 0), 2)  # 黑色边框
                        
                        # 转换为整数坐标
                        left_hip = tuple(map(int, left_hip))
                        left_knee = tuple(map(int, left_knee))
                        right_hip = tuple(map(int, right_hip))
                        right_knee = tuple(map(int, right_knee))
                        left_shoulder = tuple(map(int, left_shoulder))
                        left_wrist = tuple(map(int, left_wrist))
                        right_shoulder = tuple(map(int, right_shoulder))
                        right_wrist = tuple(map(int, right_wrist))

                        # 绘制四肢线段,使用不同颜色
                        cv2.line(image, left_hip, left_knee, (0, 0, 255), 8)     # 左腿-红色
                        cv2.line(image, right_hip, right_knee, (255, 0, 0), 8)   # 右腿-蓝色
                        cv2.line(image, left_shoulder, left_wrist, (0, 255, 0), 8)  # 左臂-绿色
                        cv2.line(image, right_shoulder, right_wrist, (255, 255, 0), 8)  # 右臂-青色

                        # 计算所有腿与手臂的夹角组合
                        legs = [
                            (np.array(left_knee) - np.array(left_hip), "左腿"),
                            (np.array(right_knee) - np.array(right_hip), "右腿")
                        ]
                        arms = [
                            (np.array(left_wrist) - np.array(left_shoulder), "左臂"),
                            (np.array(right_wrist) - np.array(right_shoulder), "右臂")
                        ]

                        # 检查所有可能的组合
                        for leg, leg_name in legs:
                            for arm, arm_name in arms:
                                angle = np.degrees(np.arctan2(leg[1], leg[0]) - 
                                                 np.arctan2(arm[1], arm[0]))
                                angle = angle + 360 if angle < 0 else angle
                                angle = angle if angle <= 180 else 360 - angle

                                print(f"{leg_name}{arm_name}的夹角: {angle}°")

                                if angle > 80:
                                    has_angle_over_80 = True
                                    # 计算异常组合的中点位置
                                    if leg_name == "左腿":
                                        warning_x = int((left_hip[0] + left_knee[0]) / 2)
                                        warning_y = int((left_hip[1] + left_knee[1]) / 2)
                                    else:
                                        warning_x = int((right_hip[0] + right_knee[0]) / 2)
                                        warning_y = int((right_hip[1] + right_knee[1]) / 2)
                                    
                                    # 绘制警告标记
                                    warning_size = 50
                                    warning_color = (0, 0, 255)  # 红色
                                    
                                    # 绘制三角形警告标志
                                    triangle_pts = np.array([
                                        [warning_x, warning_y - warning_size],
                                        [warning_x - warning_size//2, warning_y + warning_size//2],
                                        [warning_x + warning_size//2, warning_y + warning_size//2]
                                    ], np.int32)
                                    cv2.polylines(image, [triangle_pts], True, warning_color, 3)
                                    
                                    # 在三角形中心绘制感叹号
                                    cv2.putText(image, "!", 
                                              (warning_x - 5, warning_y + 10),
                                              cv2.FONT_HERSHEY_SIMPLEX, 
                                              1, warning_color, 3)

            # 保存处理后的图片
            result_image_path = f"results/my_results/{os.path.basename(image_path)}"
            cv2.imwrite(result_image_path, image)

            if has_angle_over_80:
                print(f"检测到角度超过80°")
                return True, result_image_path
            else:
                print("未检测到异常姿态")
                # todo 删除抓拍的图片以及处理后的图片
                return False, None

        except Exception as e:
            print(f"人体姿态检测失败: {str(e)}")
            return False, None

    def capture_picture(self):
        """抓取图片并保存"""
        global LAST_CAPTURE_TIME
        
        # 在开始抓图前检查时间间隔
        current_time = time.time()
        time_since_last_capture = current_time - LAST_CAPTURE_TIME
        
        if time_since_last_capture < CAPTURE_INTERVAL:
            print(f"距离上次抓图仅过去 {time_since_last_capture:.1f} 秒,需要等待 {CAPTURE_INTERVAL-time_since_last_capture:.1f} 秒")
            return False

        if self.device_handle < 0:
            print("设备未登录,无法抓图")
            return False

        try:
            # 在实际抓图前更新时间戳,防止并发
            LAST_CAPTURE_TIME = current_time
            print(f"开始抓图,更新时间戳: {datetime.fromtimestamp(LAST_CAPTURE_TIME)}")

            jpeg_para = NET_DVR_JPEGPARA_NEW()
            jpeg_para.wPicSize = 0xff
            jpeg_para.wPicQuality = 0
            
            buffer_size = 1024 * 1024
            picture_buffer = create_string_buffer(buffer_size)
            bytes_returned = c_ulong(0)

            timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
            picture_file = os.path.join(self.pic_dir, f'capture_{timestamp}.jpg')

            if self.sdk.NET_DVR_CaptureJPEGPicture_NEW(
                self.device_handle,
                1,
                byref(jpeg_para),
                picture_buffer,
                buffer_size,
                byref(bytes_returned)
            ):
                with open(picture_file, 'wb') as f:
                    f.write(picture_buffer.raw[:bytes_returned.value])
                print(f"抓图成功:{picture_file}")
                
                # 进行人体检测
                has_person, result_image_path = self.detect_person(picture_file)
                
                if has_person and result_image_path:
                    print("检测到异常姿态")
                    # 只有在检测到异常姿态时才更新检测时间
                    global LAST_DETECT_TIME
                    LAST_DETECT_TIME = current_time
                    print(f"更新异常检测时间: {datetime.fromtimestamp(LAST_DETECT_TIME)}")
                    
                    detect_image_url = self.db.upload_image(result_image_path)
                    if detect_image_url:
                        print(f"检测结果图片上传成功: {detect_image_url}")
                        self.db.insert_alarm_record(
                            device_id=self.device_id,
                            alarm_type='1',
                            detect_image_url=detect_image_url
                        )
                        return True
                else:
                    # 如果没有检测到异常姿态,删除图片
                    try:
                        os.remove(picture_file)
                        print(f"已删除抓拍图片: {picture_file}")
                        if result_image_path and os.path.exists(result_image_path):
                            os.remove(result_image_path)
                            print(f"已删除处理后的图片: {result_image_path}")
                    except Exception as e:
                        print(f"删除图片失败: {str(e)}")
                return False
            else:
                error_code = self.sdk.NET_DVR_GetLastError()
                print(f"抓图失败,错误码:{error_code}")
                # 如果抓图失败,重置时间戳
                LAST_CAPTURE_TIME = current_time - CAPTURE_INTERVAL
                return False
        except Exception as e:
            print(f"抓图过程发生异常: {str(e)}")
            # 发生异常时也重置时间戳
            LAST_CAPTURE_TIME = current_time - CAPTURE_INTERVAL
            return False

def main():
    # 设备列表
    devices = [
        {
            "ip": "192.168.200.166",
            "port": 8000,
            "username": "admin",
            "password": "isti1234",
            "device_no": "AA5336153",  # 设备编号
            "channel": "01"        # 通道号
        },
        {
            "ip": "192.168.200.167",
            "port": 8000,
            "username": "admin",
            "password": "password2",
            "device_no": "HK002",  # 设备编号
            "channel": "01"        # 通道号
        },
    ]

    monitors = []
    try:
        # 初始化所有设备的监控
        for device in devices:
            monitor = AlarmMonitor(
                device["ip"],
                device["port"],
                device["username"],
                device["password"],
                device["device_no"],    # 添加设备编号
                device["channel"]       # 添加通道号
            )
            if monitor.init_device():
                if monitor.start_alarm_listen():
                    print(f"设备 {device['device_no']}_{device['channel']} 开始监听报警...")
                    monitors.append(monitor)
                else:
                    print(f"设备 {device['device_no']}_{device['channel']} 启动报警监听失败")
            else:
                print(f"设备 {device['device_no']}_{device['channel']} 初始化失败")

        if monitors:
            print(f"成功启动 {len(monitors)} 个设备的监控")
            # 保持程序运行
            while True:
                time.sleep(1)
        else:
            print("没有成功启动的设备监控")

    except KeyboardInterrupt:
        print("\n程序退出...")
    finally:
        # 停止所有设备的监控
        for monitor in monitors:
            monitor.stop_alarm_listen()

if __name__ == "__main__":
    main() 
<think>嗯,用户现在需要在RK3588上开发一个海康工业相机视觉检测系统,并且需要构建自己的图形界面。首先,我需要回顾之前的对话历史,看看用户之前的问题是什么。之前用户问过关于在Buildroot系统上使用VSCode远程开发的问题,我回答了如何配置SSH和远程开发环境。现在用户的需求更深入了,涉及到具体的开发方式和图形界面构建。 首先,用户的需求是使用RK3588开发一个视觉检测系统,这涉及到图像处理、相机控制、实时性要求可能较高。海康工业相机通常支持SDK,比如海康的MVS或者MV-CN系列相机的SDK,可能需要集成到系统中。此外,用户需要构建自己的图形界面,所以需要选择一个适合嵌入式平台的GUI框架。 接下来,我需要考虑RK3588的硬件能力。RK3588是Rockchip的高性能处理器,四核A76+四核A55,支持NPU,适合边缘计算和图像处理。所以开发方式需要充分利用其硬件加速能力,比如使用OpenCL、NPU进行算法加速。 然后,开发方式的选择。用户之前可能已经配置好了Buildroot系统,但不确定是否使用Yocto或其他系统。不过Buildroot比较轻量,适合嵌入式系统。对于开发方式,可能有两种:本地开发或交叉编译。由于RK3588的性能足够,可能可以本地编译,但大型项目可能还是交叉编译更高效。不过用户之前配置了VSCode远程开发,可能更倾向于在RK3588上直接开发,或者交叉编译。 图形界面方面,嵌入式系统常用的有Qt、GTK、LVGL等。Qt功能强大,支持跨平台,适合复杂的图形界面,并且有较好的社区支持。GTK也是选项,但可能在嵌入式环境下资源占用较高。LVGL是轻量级的,适合资源有限的设备,但用户需要自定义界面的话,可能需要更多工作。考虑到RK3588性能不错,Qt可能更合适,尤其是如果用户需要复杂的UI和图形渲染。 相机SDK集成方面,海康的相机通常提供Linux的SDK,可能需要编译成库,并在应用中调用。需要确保RK3588的系统中有相应的依赖库,比如OpenCV、FFmpeg等。如果使用Python的话,可能更方便,但性能可能不如C++。不过RK3588的NPU加速可能需要C++的SDK支持。 视觉检测算法部分,可能需要用到OpenCV、TensorFlow Lite、PyTorch等框架。如果涉及深度学习模型,需要部署到RK3588的NPU上,可能需要使用Rockchip提供的RKNN Toolkit来换模型。这部分可能需要较多的优化工作。 实时性方面,虽然Linux不是实时系统,但通过内核配置(如PREEMPT_RT补丁)可以提高实时性。不过这可能超出了用户当前的需求,需要用户确认是否需要严格的实时处理。 开发环境的搭建,用户之前已经配置了VSCode远程开发,可以继续使用。在RK3588上安装必要的开发工具,比如gcc、gdb、cmake等。如果使用Qt,还需要安装Qt Creator或者配置VSCode的Qt插件。 图形界面的构建步骤可能需要:安装Qt库,设计UI界面,编写业务逻辑,集成相机SDK和视觉算法。如果用户不熟悉Qt,可能需要学习Qt的基本用法,比如信号槽机制、QWidget或QML的使用。 另外,系统优化方面,可能需要调整Buildroot的配置,包含必要的库和驱动,比如GPU驱动、NPU驱动,确保图形加速和算法加速可用。同时,考虑系统的启动时间、资源占用,确保视觉检测系统的流畅运行。 测试和调试部分,需要在实际环境中测试相机的连接、图像采集的延迟、处理算法的准确性和速度。可能还需要日志系统来记录运行状态,方便排查问题。 最后,用户可能需要考虑系统的可维护性和扩展性,比如模块化设计,方便后续添加新的功能或相机型号。 总结下来,推荐的开发方式可能是:基于C++使用Qt构建图形界面,集成海康相机SDK,利用OpenCV和RKNN进行视觉处理,通过VSCode远程开发进行代码编写和调试,同时优化系统配置以充分利用RK3588的硬件加速功能。</think>基于 RK3588 开发海康工业相机视觉检测系统,并构建自定义图形界面,可按照以下方案分步骤实施: --- ### 一、开发方式选择 #### 1. **本地开发 + 远程调试**(推荐) - **适用场景**:RK3588 性能足够直接运行开发环境(如安装基础编译工具链)。 - **优势**: - 直接调用硬件资源(GPU/NPU/摄像头接口),调试更直观。 - 实时验证图像采集与算法性能。 - **工具链**: - 语言:**C++**(高性能)或 **Python**(快速原型开发)。 - 编译器:GCC(C++)或预装 Python 解释器。 - 调试:GDB 或 VS Code 远程调试插件。 #### 2. **交叉编译 + 远程部署**(资源受限时) - **适用场景**:RK3588 存储/计算资源紧张,需在 PC 上编译后同步到设备。 - **工具链**: - 使用 Buildroot 生成的 **交叉编译工具链**(如 `aarch64-linux-gnu-gcc`)。 - 代码同步:`rsync` 或 `scp`。 - 调试:`gdbserver` 远程调试。 --- ### 二、图形界面方案 #### 1. **Qt 框架**(**首选**) - **优势**: - 跨平台支持,成熟度高,适合工业级应用。 - 支持硬件加速渲染(通过 RK3588 的 Mali-G610 GPU)。 - 提供丰富的控件库(QWidget/QML)和信号槽机制。 - **集成步骤**: 1. **安装 Qt 库**: - 在 Buildroot 中启用 Qt 支持: ```bash make menuconfig # Target packages → Graphic libraries and applications → Qt5 # 勾选 Qt5 GUI 模块(如 Qt5 Widgets、Qt5 Quick) ``` 2. **设计界面**: - 使用 **Qt Designer** 拖拽生成 UI 文件(`.ui`),或通过 **QML** 编写动态界面。 3. **绑定业务逻辑**: - 在 C++ 代码中连接相机 SDK 和视觉算法(如 OpenCV 图像处理)。 - 示例代码片段: ```cpp // 相机图像采集回调 void CameraCallback(cv::Mat frame) { // 调用视觉检测算法 cv::Mat processed = detectDefects(frame); // 更新界面显示 QImage img(processed.data, processed.cols, processed.rows, QImage::Format_RGB888); ui->label->setPixmap(QPixmap::fromImage(img)); } ``` #### 2. **GTK 框架**(轻量级替代) - **优势**:资源占用低,适合简单界面。 - **限制**:控件丰富性和开发效率低于 Qt。 - **集成步骤**: 1. 在 Buildroot 中启用 `gtk3` 包。 2. 使用 Glade 设计界面,通过 C 或 Python 绑定逻辑。 #### 3. **Web 界面**(远程监控场景) - **方案**: - 后端:使用 **Flask/Django** 提供 RESTful API,处理相机数据和算法结果。 - 前端:通过 HTML5 + JavaScript 实现交互界面,利用 WebGL 加速图像渲染。 - **适用场景**:需跨设备访问或降低客户端资源消耗。 --- ### 三、核心模块实现 #### 1. **海康相机 SDK 集成** - **步骤**: 1. 从海康官网下载 **MVS(Machine Vision Suite) SDK for Linux**。 2. 交叉编译或本地编译 SDK 动态库(`.so`),部署到 RK3588 的 `/usr/lib`。 3. 在代码中调用 SDK 接口: ```cpp #include "HCNetSDK.h" // 初始化相机 NET_DVR_Init(); NET_DVR_DEVICEINFO_V40 devInfo; LONG lUserID = NET_DVR_Login_V40("192.168.1.64", 8000, "admin", "password", &devInfo); ``` #### 2. **视觉检测算法** - **基础方案**: - **OpenCV**:实现图像预处理(滤波、二值化)、模板匹配、轮廓检测。 ```python import cv2 def detect_defects(image): gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) _, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) contours, _ = cv2.findContours(thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) return contours ``` - **深度学习方案**: - 使用 RK3588 NPU 加速: 1. 通过 **RKNN-Toolkit2** 将模型(ONNX/TensorFlow)换为 RKNN 格式。 2. 调用 NPU 推理接口: ```python from rknnlite import RKNNLite rknn = RKNNLite() rknn.load_rknn('model.rknn') rknn.init_runtime(target='rk3588') outputs = rknn.inference(inputs=[image]) ``` --- ### 四、系统优化建议 1. **硬件加速配置**: - 启用 GPU 渲染:在 Qt 中设置 `QT_QUICK_BACKEND=software`(软渲染)或使用 OpenGL ES 加速。 - NPU 利用率:通过多线程分离视觉推理和界面渲染任务。 2. **实时性保障**: - 内核配置:启用 `PREEMPT` 抢占式调度(需 Buildroot 配置 `BR2_LINUX_KERNEL_PREEMPT=y`)。 - 线程优先级:使用 `pthread_setschedparam` 提升相机采集线程的优先级。 3. **存储优化**: - 日志管理:限制日志文件大小(如 logrotate)。 - 内存缓存:减少图像传输时的拷贝操作(使用零拷贝技术)。 --- ### 五、开发流程示例 ```plaintext 1. 环境搭建 └─ 安装 Buildroot + Qt + OpenCV + 海康 SDK 2. 界面设计 └─ Qt Designer 绘制检测结果展示区、参数配置面板、实时视频流窗口 3. 相机控制 └─ 多线程采集图像 → 队列缓存 → 算法处理 4. 算法部署 └─ OpenCV 实现缺陷检测 → 结果标记 → 输出到界面 5. 性能调优 └─ 绑定 NPU 推理 → 启用 GPU 渲染 → 压力测试 ``` --- ### 六、注意事项 - **相机驱动兼容性**:确保海康 SDK 与 RK3588 内核版本(如 5.10)兼容。 - **界面响应速度**:避免在主线程中执行耗时操作(如算法推理),需使用多线程/异步回调。 - **安全性**:若需联网,启用防火墙并限制非必要端口访问。 --- ### 总结 推荐采用 **Qt + C++ + OpenCV** 作为核心开发方案,优势如下: - ✅ 高性能:充分利用 RK3588 的 GPU/NPU 加速。 - ✅ 灵活性:Qt 控件可高度定制工业检测界面。 - ✅ 稳定性:海康 SDK 与 Qt 的成熟度保障长期维护。 若追求开发速度,可结合 Python 快速验证算法逻辑,再逐步迁移到 C++ 优化性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值