如果你希望使用 C# 来实现对接海思(Hikvision)网络摄像头并完成车牌识别的功能,这是一个典型的硬件对接与智能识别结合的开发场景。其核心通常依赖于摄像头制造商提供的 SDK 接口。下面,我们将提供一套完整、可落地的 C# 对接方案。
一、开发前准备
在动手编写代码之前,我们需要准备好以下环境和资源:
-
海思摄像头 SDK
:从设备供应商处获取对应型号的 SDK,其中应包含头文件(.h)、动态链接库(.dll/.so)、静态库以及详细的开发文档。
-
开发环境
:Visual Studio 2019 或 2022(推荐),项目目标框架为 .NET Framework 4.6+ 或 .NET Core/.NET 5+。
-
硬件连接
:确保摄像头已正确接入网络,并记录下其 IP 地址、端口号、登录用户名和密码,确保程序能够正常访问。
-
依赖库
:将 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);
}
}
}
三、代码关键说明
-
P/Invoke 声明
:海思 SDK 是基于 C/C++ 的本地库,因此 C# 需要通过 DllImport 属性来导入 SDK 中的核心函数。这是实现.NET 平台与本地代码交互的关键步骤。特别注意,结构体(如 NET_DVR_DEVICEINFO_V30、NET_DVR_PLATE_RECOG_RESULT)的定义必须与 SDK 头文件中的原始定义在字段顺序、数据类型和数组大小上保持完全一致,任何偏差都可能导致函数调用失败或内存访问错误。
-
核心流程
:整个流程是顺序执行的:
- 登录设备:调用
NET_DVR_Login_V30 获取设备句柄 (_lUserID)。
- 启动预览:调用
NET_DVR_RealPlay_V40 获取预览句柄 (_lRealPlayHandle),并将视频流绑定到指定的窗口控件上。
- 设置回调:调用
NET_DVR_SetPlateRecogCallBack 注册车牌识别结果的回调函数。
- 处理结果:在
PlateRecogCallback 函数中解析并显示车牌号码、颜色等信息。
- 释放资源:必须按顺序停止预览 (
NET_DVR_StopRealPlay) 并注销设备 (NET_DVR_Logout),以避免句柄和内存泄漏,这是保证程序健壮性的重要设计原则。
-
UI 控件说明
:
txtIP/txtPort/txtUser/txtPwd:用于输入摄像头网络参数的 TextBox 控件。
panelPreview:用于显示实时视频画面的 Panel 控件。
txtPlateResult:用于显示车牌识别结果的 TextBox 控件(需设置为多行、允许滚动)。
btnLogin/btnStartPreview/btnStopPreview:分别对应登录、开始预览、停止预览的功能按钮。
-
异常处理
:
- 每次调用 SDK 函数后都应检查返回值(是否为
IntPtr.Zero 或 false),并通过 Marshal.GetLastWin32Error() 获取详细的错误码,对照 SDK 文档进行问题排查。
- 在窗体关闭事件 (
OnFormClosing) 中,必须确保预览句柄和登录句柄被正确释放,这是避免资源泄漏的关键。
四、常见问题与解决方案
-
DLL 加载失败
:
- 确保将 SDK 包中的所有依赖 DLL(如
HCNetSDK.dll, PlayCtrl.dll, HCGeneralCfgMgr.dll 等)都拷贝到项目的输出目录下(例如 bin\Debug\ 或 bin\Release\)。
- 注意平台匹配:SDK 通常分为 32 位和 64 位版本,你的 C# 项目目标平台必须与所使用的 SDK 版本一致(当前推荐使用 x64 平台)。
-
登录失败(错误码)
:
- 错误码 28:用户名或密码错误。
- 错误码 17:网络连接失败,请检查 IP 地址、端口号或网络防火墙设置。
- 错误码 7:设备不在线或当前用户权限不足。
-
车牌识别无回调
:
- 确认摄像头的车牌识别功能已在设备 Web 管理界面中启用并正确配置。
- 检查预览时传入的通道号 (
lChannel) 是否正确,部分设备通道号从 0 开始,部分从 1 开始。
- 尝试使用主码流 (
dwStreamType = 0) 进行预览,通常识别率更高。
总结
- 核心流程:C# 对接海思摄像头实现车牌识别的核心,是通过 P/Invoke 技术调用其 SDK 提供的本地函数,严格遵循「设备登录 → 启动预览 → 注册识别回调 → 解析结果 → 释放资源」这一流程。
- 关键注意事项:结构体定义必须精准匹配 SDK;动态库需与项目平台一致;回调函数中涉及 UI 更新需正确处理跨线程调用;务必在程序退出前释放所有 SDK 句柄。
- 调试技巧:善用 SDK 函数返回的错误码进行问题定位,优先排查网络连通性、设备配置、SDK 版本兼容性及平台匹配性等基础环节。将硬件对接与计算机视觉识别相结合,可以构建出强大的安防或智能交通应用。更多类似的软硬件集成与.NET开发实战,你可以在 云栈社区 找到丰富的讨论和资源。