找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1887

积分

0

好友

248

主题
发表于 7 天前 | 查看: 20| 回复: 0

如果你希望使用 C# 来实现对接海思(Hikvision)网络摄像头并完成车牌识别的功能,这是一个典型的硬件对接与智能识别结合的开发场景。其核心通常依赖于摄像头制造商提供的 SDK 接口。下面,我们将提供一套完整、可落地的 C# 对接方案。

一、开发前准备

在动手编写代码之前,我们需要准备好以下环境和资源:

  1. 海思摄像头 SDK
    :从设备供应商处获取对应型号的 SDK,其中应包含头文件(.h)、动态链接库(.dll/.so)、静态库以及详细的开发文档。

  2. 开发环境
    :Visual Studio 2019 或 2022(推荐),项目目标框架为 .NET Framework 4.6+ 或 .NET Core/.NET 5+。

  3. 硬件连接
    :确保摄像头已正确接入网络,并记录下其 IP 地址、端口号、登录用户名和密码,确保程序能够正常访问。

  4. 依赖库
    :将 SDK 中的核心库,如 HCNetSDK.dll(网络 SDK 核心库)、PlayCtrl.dll(视频播放库)等,复制到你的 C# 项目输出目录(例如 bin\Debug\)。

二、核心实现代码

以下是一个完整的 C# 示例,涵盖了设备登录、实时视频预览、车牌识别回调以及设备注销等核心功能模块。

using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace HisiCameraPlateRecognition
{
    public partial class PlateRecognitionForm : Form
    {
        // 海思SDK核心DLL导入(请根据实际SDK头文件进行调整)
        #region 海思SDK P/Invoke声明
        // 设备登录句柄
        private IntPtr _lUserID = IntPtr.Zero;
        // 预览句柄
        private IntPtr _lRealPlayHandle = IntPtr.Zero;

        // 登录设备
        [DllImport("HCNetSDK.dll", EntryPoint = "NET_DVR_Login_V30", SetLastError = true)]
        private static extern IntPtr NET_DVR_Login_V30(string sDVRIP, ushort wDVRPort, string sUserName, string sPassword, ref NET_DVR_DEVICEINFO_V30 lpDeviceInfo);

        // 注销登录
        [DllImport("HCNetSDK.dll", EntryPoint = "NET_DVR_Logout", SetLastError = true)]
        private static extern bool NET_DVR_Logout(IntPtr lUserID);

        // 启动实时预览
        [DllImport("HCNetSDK.dll", EntryPoint = "NET_DVR_RealPlay_V40", SetLastError = true)]
        private static extern IntPtr NET_DVR_RealPlay_V40(IntPtr lUserID, ref NET_DVR_PREVIEWINFO lpPreviewInfo, RealDataCallBack cbRealDataCallBack, IntPtr pUser);

        // 停止预览
        [DllImport("HCNetSDK.dll", EntryPoint = "NET_DVR_StopRealPlay", SetLastError = true)]
        private static extern bool NET_DVR_StopRealPlay(IntPtr lRealPlayHandle);

        // 设置车牌识别回调
        [DllImport("HCNetSDK.dll", EntryPoint = "NET_DVR_SetPlateRecogCallBack", SetLastError = true)]
        private static extern bool NET_DVR_SetPlateRecogCallBack(IntPtr lRealPlayHandle, PlateRecogCallBack cbPlateRecog, IntPtr pUser);

        // 设备信息结构体(根据SDK定义调整)
        [StructLayout(LayoutKind.Sequential)]
        public struct NET_DVR_DEVICEINFO_V30
        {
            public uint dwSerialNumber;       // 设备序列号
            public byte byAlarmInPortNum;     // 报警输入端口数
            public byte byAlarmOutPortNum;    // 报警输出端口数
            public byte byDiskNum;            // 硬盘数量
            public byte byDVRType;            // 设备类型
            public byte byChanNum;            // 模拟通道数
            public byte byStartChan;          // 起始通道号
            public byte byAudioChanNum;       // 音频通道数
            public byte byIPChanNum;          // 最大数字通道数
            public byte byZeroChanNum;        // 零通道编码数量
            public byte byMainProto;          // 主码流传输协议类型
            public byte bySubProto;           // 子码流传输协议类型
            public byte bySupport;            // 设备能力支持
            public byte byRes1;               // 保留
            public ushort wDevType;           // 设备型号
            public byte byRes2;               // 保留
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
            public byte[] bySerialNumber;     // 设备序列号字符串
            public byte bySupportV50;         // 是否支持V5.0协议
            public byte byRes3;               // 保留
            public uint dwSurplusLockTime;    // 剩余锁定时间
            public byte byRetryLoginTime;     // 剩余重试登录次数
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
            public byte[] byRes4;             // 保留
        }

        // 预览参数结构体
        [StructLayout(LayoutKind.Sequential)]
        public struct NET_DVR_PREVIEWINFO
        {
            public uint lChannel;             // 通道号
            public uint dwStreamType;         // 码流类型:0-主码流,1-子码流
            public uint dwLinkMode;           // 连接方式:0-TCP,1-UDP
            public IntPtr hPlayWnd;           // 预览窗口句柄
            public bool bBlocked;             // 是否阻塞取流
            public bool bPassbackRecord;      // 是否回调录像数据
            public uint dwDisplayBufNum;      // 显示缓冲区数量
            public IntPtr pUser;              // 用户数据
        }

        // 车牌识别结果结构体(核心)
        [StructLayout(LayoutKind.Sequential)]
        public struct NET_DVR_PLATE_RECOG_RESULT
        {
            public uint dwSize;               // 结构体大小
            public uint dwChannel;            // 通道号
            public uint dwTimeStamp;          // 时间戳
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
            public byte[] byPlateNumber;      // 车牌号码
            public uint dwPlateColor;         // 车牌颜色:0-蓝,1-黄,2-白,3-黑,4-绿
            public uint dwPlateType;          // 车牌类型
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)]
            public byte[] byReserved;         // 保留字段
        }

        // 实时数据回调委托
        public delegate void RealDataCallBack(IntPtr lRealPlayHandle, uint dwDataType, IntPtr pBuffer, uint dwBufSize, IntPtr pUser);

        // 车牌识别回调委托
        public delegate void PlateRecogCallBack(IntPtr lRealPlayHandle, ref NET_DVR_PLATE_RECOG_RESULT lpPlateRecogResult, IntPtr pUser);
        #endregion

        public PlateRecognitionForm()
        {
            InitializeComponent();
        }

        // 登录摄像头按钮点击事件
        private void btnLogin_Click(object sender, EventArgs e)
        {
            try
            {
                // 初始化设备信息结构体
                NET_DVR_DEVICEINFO_V30 deviceInfo = new NET_DVR_DEVICEINFO_V30();
                deviceInfo.bySerialNumber = new byte[32];

                // 摄像头参数(请替换为你的设备实际参数)
                string ip = txtIP.Text.Trim();          // 摄像头IP
                ushort port = ushort.Parse(txtPort.Text.Trim()); // 端口(默认8000)
                string user = txtUser.Text.Trim();     // 用户名(默认admin)
                string pwd = txtPwd.Text.Trim();       // 密码(设备密码)

                // 登录设备
                _lUserID = NET_DVR_Login_V30(ip, port, user, pwd, ref deviceInfo);
                if (_lUserID == IntPtr.Zero)
                {
                    int errorCode = Marshal.GetLastWin32Error();
                    MessageBox.Show($"登录失败!错误码:{errorCode}", "提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return;
                }

                MessageBox.Show("摄像头登录成功!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                btnStartPreview.Enabled = true;
                btnLogin.Enabled = false;
            }
            catch (Exception ex)
            {
                MessageBox.Show($"登录异常:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        // 启动预览并开启车牌识别
        private void btnStartPreview_Click(object sender, EventArgs e)
        {
            try
            {
                if (_lUserID == IntPtr.Zero)
                {
                    MessageBox.Show("请先登录摄像头!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    return;
                }

                // 初始化预览参数
                NET_DVR_PREVIEWINFO previewInfo = new NET_DVR_PREVIEWINFO();
                previewInfo.lChannel = 1;                  // 通道号(请根据实际情况调整)
                previewInfo.dwStreamType = 0;              // 主码流
                previewInfo.dwLinkMode = 0;                // TCP连接
                previewInfo.hPlayWnd = panelPreview.Handle;// 预览窗口(使用WinForm的Panel控件)
                previewInfo.bBlocked = true;               // 阻塞取流
                previewInfo.bPassbackRecord = false;

                // 启动实时预览
                _lRealPlayHandle = NET_DVR_RealPlay_V40(_lUserID, ref previewInfo, null, IntPtr.Zero);
                if (_lRealPlayHandle == IntPtr.Zero)
                {
                    int errorCode = Marshal.GetLastWin32Error();
                    MessageBox.Show($"预览启动失败!错误码:{errorCode}", "提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return;
                }

                // 设置车牌识别回调函数
                bool setCallBack = NET_DVR_SetPlateRecogCallBack(_lRealPlayHandle, PlateRecogCallback, IntPtr.Zero);
                if (!setCallBack)
                {
                    int errorCode = Marshal.GetLastWin32Error();
                    MessageBox.Show($"设置车牌识别回调失败!错误码:{errorCode}", "提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    NET_DVR_StopRealPlay(_lRealPlayHandle);
                    _lRealPlayHandle = IntPtr.Zero;
                    return;
                }

                MessageBox.Show("预览已启动,已开启车牌识别!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                btnStopPreview.Enabled = true;
                btnStartPreview.Enabled = false;
            }
            catch (Exception ex)
            {
                MessageBox.Show($"启动预览异常:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        // 车牌识别回调函数(核心逻辑)
        private void PlateRecogCallback(IntPtr lRealPlayHandle, ref NET_DVR_PLATE_RECOG_RESULT lpPlateRecogResult, IntPtr pUser)
        {
            try
            {
                // 转换车牌号码(字节数组转字符串)
                string plateNumber = Encoding.Default.GetString(lpPlateRecogResult.byPlateNumber).Trim('\0');
                if (string.IsNullOrEmpty(plateNumber)) return;

                // 转换车牌颜色
                string plateColor = lpPlateRecogResult.dwPlateColor switch
                {
                    0 => "蓝色",
                    1 => "黄色",
                    2 => "白色",
                    3 => "黑色",
                    4 => "绿色",
                    _ => "未知"
                };

                // 跨线程更新UI(WinForm必须)
                this.Invoke(new Action(() =>
                {
                    // 显示识别结果
                    txtPlateResult.AppendText($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 车牌:{plateNumber} | 颜色:{plateColor}\r\n");
                    // 滚动到最后一行
                    txtPlateResult.ScrollToCaret();

                    // 可选:保存识别结果到数据库/文件
                    // SavePlateResult(plateNumber, plateColor, DateTime.Now);
                }));
            }
            catch (Exception ex)
            {
                Console.WriteLine($"车牌识别回调异常:{ex.Message}");
            }
        }

        // 停止预览
        private void btnStopPreview_Click(object sender, EventArgs e)
        {
            try
            {
                if (_lRealPlayHandle != IntPtr.Zero)
                {
                    NET_DVR_StopRealPlay(_lRealPlayHandle);
                    _lRealPlayHandle = IntPtr.Zero;
                }

                if (_lUserID != IntPtr.Zero)
                {
                    NET_DVR_Logout(_lUserID);
                    _lUserID = IntPtr.Zero;
                }

                MessageBox.Show("已停止预览并注销设备!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                btnStopPreview.Enabled = false;
                btnStartPreview.Enabled = false;
                btnLogin.Enabled = true;
            }
            catch (Exception ex)
            {
                MessageBox.Show($"停止预览异常:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        // 窗体关闭时释放资源
        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            base.OnFormClosing(e);
            // 停止预览
            if (_lRealPlayHandle != IntPtr.Zero)
            {
                NET_DVR_StopRealPlay(_lRealPlayHandle);
            }
            // 注销登录
            if (_lUserID != IntPtr.Zero)
            {
                NET_DVR_Logout(_lUserID);
            }
        }

        // 可选:保存车牌识别结果到文件/数据库
        private void SavePlateResult(string plateNumber, string plateColor, DateTime recogTime)
        {
            // 这里可以实现保存到文本文件、SQL Server/MySQL等逻辑
            // 示例:保存到文本文件
            string logPath = $"PlateRecog_{DateTime.Now:yyyyMMdd}.log";
            string logContent = $"[{recogTime:yyyy-MM-dd HH:mm:ss}] 车牌:{plateNumber},颜色:{plateColor}\r\n";
            System.IO.File.AppendAllText(logPath, logContent, Encoding.UTF8);
        }
    }
}

三、代码关键说明

  1. P/Invoke 声明
    :海思 SDK 是基于 C/C++ 的本地库,因此 C# 需要通过 DllImport 属性来导入 SDK 中的核心函数。这是实现.NET 平台与本地代码交互的关键步骤。特别注意,结构体(如 NET_DVR_DEVICEINFO_V30NET_DVR_PLATE_RECOG_RESULT)的定义必须与 SDK 头文件中的原始定义在字段顺序、数据类型和数组大小上保持完全一致,任何偏差都可能导致函数调用失败或内存访问错误。

  2. 核心流程
    :整个流程是顺序执行的:

    • 登录设备:调用 NET_DVR_Login_V30 获取设备句柄 (_lUserID)。
    • 启动预览:调用 NET_DVR_RealPlay_V40 获取预览句柄 (_lRealPlayHandle),并将视频流绑定到指定的窗口控件上。
    • 设置回调:调用 NET_DVR_SetPlateRecogCallBack 注册车牌识别结果的回调函数。
    • 处理结果:在 PlateRecogCallback 函数中解析并显示车牌号码、颜色等信息。
    • 释放资源:必须按顺序停止预览 (NET_DVR_StopRealPlay) 并注销设备 (NET_DVR_Logout),以避免句柄和内存泄漏,这是保证程序健壮性的重要设计原则
  3. UI 控件说明

    • txtIP/txtPort/txtUser/txtPwd:用于输入摄像头网络参数的 TextBox 控件。
    • panelPreview:用于显示实时视频画面的 Panel 控件。
    • txtPlateResult:用于显示车牌识别结果的 TextBox 控件(需设置为多行、允许滚动)。
    • btnLogin/btnStartPreview/btnStopPreview:分别对应登录、开始预览、停止预览的功能按钮。
  4. 异常处理

    • 每次调用 SDK 函数后都应检查返回值(是否为 IntPtr.Zerofalse),并通过 Marshal.GetLastWin32Error() 获取详细的错误码,对照 SDK 文档进行问题排查。
    • 在窗体关闭事件 (OnFormClosing) 中,必须确保预览句柄和登录句柄被正确释放,这是避免资源泄漏的关键。

四、常见问题与解决方案

  1. DLL 加载失败

    • 确保将 SDK 包中的所有依赖 DLL(如 HCNetSDK.dll, PlayCtrl.dll, HCGeneralCfgMgr.dll 等)都拷贝到项目的输出目录下(例如 bin\Debug\bin\Release\)。
    • 注意平台匹配:SDK 通常分为 32 位和 64 位版本,你的 C# 项目目标平台必须与所使用的 SDK 版本一致(当前推荐使用 x64 平台)。
  2. 登录失败(错误码)

    • 错误码 28:用户名或密码错误。
    • 错误码 17:网络连接失败,请检查 IP 地址、端口号或网络防火墙设置。
    • 错误码 7:设备不在线或当前用户权限不足。
  3. 车牌识别无回调

    • 确认摄像头的车牌识别功能已在设备 Web 管理界面中启用并正确配置。
    • 检查预览时传入的通道号 (lChannel) 是否正确,部分设备通道号从 0 开始,部分从 1 开始。
    • 尝试使用主码流 (dwStreamType = 0) 进行预览,通常识别率更高。

总结

  1. 核心流程:C# 对接海思摄像头实现车牌识别的核心,是通过 P/Invoke 技术调用其 SDK 提供的本地函数,严格遵循「设备登录 → 启动预览 → 注册识别回调 → 解析结果 → 释放资源」这一流程。
  2. 关键注意事项:结构体定义必须精准匹配 SDK;动态库需与项目平台一致;回调函数中涉及 UI 更新需正确处理跨线程调用;务必在程序退出前释放所有 SDK 句柄。
  3. 调试技巧:善用 SDK 函数返回的错误码进行问题定位,优先排查网络连通性、设备配置、SDK 版本兼容性及平台匹配性等基础环节。将硬件对接与计算机视觉识别相结合,可以构建出强大的安防或智能交通应用。更多类似的软硬件集成与.NET开发实战,你可以在 云栈社区 找到丰富的讨论和资源。



上一篇:Spring Boot整合MyBatis实战:XML与注解方式对比与动态SQL指南
下一篇:Kubernetes中利用iptables实现无侵入流量劫持:Nginx转发至Envoy Sidecar
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-10 18:36 , Processed in 0.398936 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表