
国内安防行业长期存在场景碎片化、设备异构性强、系统烟囱化的问题:小区、商超、企业三类场景的监控系统往往独立建设,设备厂商SDK不统一,告警逻辑无法复用,运维成本居高不下。很多团队在做安防开发时,要么直接基于厂商Demo改代码,扩展性极差;要么过度追求通用化,最终陷入功能冗余、性能不足的困境。
本文基于实际项目经验,从架构设计、核心模块实现、工程化踩坑三个维度,完整讲解如何基于C# .NET 8搭建一套可复用、可扩展的智慧安防一体化监控系统,覆盖小区、商超、企业三大主流场景,所有核心模块均提供可落地的实现思路与关键代码。
一、前期准备:需求拆解与技术选型
1.1 三大场景的需求共性与差异
智慧安防的核心能力是通用的,但不同场景的业务重心完全不同。如果从一开始就按场景拆分开发,会造成大量重复工作;如果完全不做区分,又会导致功能冗余、配置复杂。
我们先对三类场景做能力拆解:
- 共性基础能力:设备管理、视频实时预览、录像存储与回放、告警推送、权限管理、日志审计。这部分是所有场景的底座,必须抽象成通用服务。
- 小区场景:侧重人车出入联动、周界入侵防范、单元门/电梯监控、访客登记、消防告警联动,核心是人员与车辆的通行安全。
- 商超场景:侧重客流统计、收银区异常行为检测、消防通道占用识别、夜间防盗、POS数据与视频叠加,核心是经营安全与损耗管控。
- 企业场景:侧重厂区周界防范、重点区域入侵检测、考勤门禁联动、危险品区域管控、消防与应急联动,核心是生产与人员安全。
基于这个拆解,我们的设计思路是:底层能力完全复用,业务逻辑通过策略模式和配置化实现差异化,避免为每个场景单独开发一套系统。
1.2 技术栈选型与依赖说明
整个系统采用.NET 8作为核心技术栈,兼顾桌面客户端、服务端与边缘端部署,具体选型如下:
- 服务端:ASP.NET Core Web API,提供设备管理、告警、存储、权限等REST接口;SignalR实现实时告警与状态推送。
- 客户端:WPF + MVVM模式,负责视频预览、设备控制、告警弹窗、大屏展示,是安防系统最常用的桌面交互形态。
- 设备接入:ONVIF协议对接通用网络摄像头;封装海康、大华等主流厂商SDK;MQTT对接门禁、道闸、传感器等物联网设备。
- 视频处理:FFmpeg.AutoGen实现RTSP拉流、硬解码、转码与录像;WPF WriteableBitmap实现低延迟渲染。
- 智能分析:ONNX Runtime加载YOLO系列模型,实现人形检测、区域入侵、离岗识别、物品遗留等算法,支持CPU/GPU推理。
- 数据存储:SQL Server存储设备、告警、用户等结构化数据;NVR/对象存储存放视频文件;时序数据库存储设备状态与统计数据。
选型的核心原则是稳定优先、性能可控、国产化适配,避免引入过度复杂的中间件,同时保证私有化部署的兼容性。
二、系统整体架构设计
整个系统采用分层架构设计,从下到上分为设备接入层、基础服务层、业务场景层、终端展示层,各层之间通过接口解耦,新增场景或设备时只需扩展对应层,无需修改核心框架。
各层的核心职责:
这种架构的优势在于,新增一个场景只需要在业务层开发对应的规则与流程,底层设备、视频、告警能力完全复用;新增一款设备只需要在接入层添加适配器,上层业务无感知。
三、核心模块的C#工程实现
3.1 异构设备统一接入框架
设备接入是安防系统最底层也最容易出问题的部分。不同厂商的SDK接口风格、线程模型、错误码、资源释放方式完全不同,如果业务层直接调用SDK,会导致代码严重耦合,换一个厂商就要重写一套逻辑。
我们的解决方案是抽象统一设备接口 + 适配器模式 + 工厂模式,将所有设备的共性能力抽象为ISecurityDevice接口,每个厂商或协议实现对应的适配器。
首先定义统一的设备接口:
/// <summary>
/// 安防设备统一接口
/// </summary>
public interface ISecurityDevice : IDisposable
{
string DeviceId { get; }
DeviceType DeviceType { get; }
bool IsConnected { get; }
// 连接与心跳
Task<bool> ConnectAsync(DeviceConnectInfo info);
Task DisconnectAsync();
Task<bool> KeepAliveAsync();
// 视频预览
Task<StreamInfo> StartPreviewAsync(int channel = 0, StreamType streamType = StreamType.Sub);
Task StopPreviewAsync(int channel = 0);
// PTZ控制
Task<bool> PtzControlAsync(PtzCommand command, int speed = 5);
// 事件订阅
event EventHandler<DeviceAlarmEventArgs> OnAlarm;
event EventHandler<DeviceStatusEventArgs> OnStatusChanged;
}
然后实现设备基类,封装通用的状态管理、心跳与异常处理,不同厂商的设备继承基类并实现具体逻辑:
/// <summary>
/// 海康设备适配器
/// </summary>
public class HikvisionDevice : DeviceBase
{
private IntPtr _loginHandle = IntPtr.Zero;
private readonly Dictionary<int, IntPtr> _previewHandles = new();
protected override Task<bool> OnConnectAsync(DeviceConnectInfo info)
{
// 调用海康SDK登录,初始化回调
CHCNetSDK.NET_DVR_Init();
var deviceInfo = new CHCNetSDK.NET_DVR_DEVICEINFO_V30();
_loginHandle = CHCNetSDK.NET_DVR_Login_V30(
info.Ip, info.Port, info.UserName, info.Password, ref deviceInfo);
if (_loginHandle == IntPtr.Zero)
{
var error = CHCNetSDK.NET_DVR_GetLastError();
throw new DeviceException($"海康设备登录失败,错误码:{error}");
}
// 注册异常与告警回调
CHCNetSDK.NET_DVR_SetExceptionCallBack_V30(0, IntPtr.Zero, ExceptionCallback, IntPtr.Zero);
return Task.FromResult(true);
}
// 预览、PTZ、事件回调等具体实现
// 注意:SDK回调必须在非UI线程处理,避免阻塞
}
最后通过设备工厂根据设备类型动态创建实例,业务层只依赖ISecurityDevice接口,完全感知不到底层是海康、大华还是ONVIF设备:
public static class DeviceFactory
{
public static ISecurityDevice CreateDevice(DeviceInfo deviceInfo)
{
return deviceInfo.Protocol switch
{
DeviceProtocol.Hikvision => new HikvisionDevice(deviceInfo.DeviceId),
DeviceProtocol.Dahua => new DahuaDevice(deviceInfo.DeviceId),
DeviceProtocol.Onvif => new OnvifDevice(deviceInfo.DeviceId),
_ => throw new NotSupportedException($"不支持的设备协议:{deviceInfo.Protocol}")
};
}
}
在此基础上,再实现设备心跳检测、离线重连、状态上报等通用能力,就可以支撑上百台异构设备的统一管理。
3.2 视频流解码与多屏预览
视频预览是安防系统的核心功能,也是性能瓶颈最集中的地方。我们采用FFmpeg拉流解码 + WPF WriteableBitmap渲染的方案,支持RTSP、RTMP等主流协议,同时支持DXVA2硬解码,大幅降低CPU占用。
3.2.1 FFmpeg拉流封装
使用FFmpeg.AutoGen封装拉流与解码逻辑,将RTSP流解码为YUV帧,再转换为RGB格式供WPF渲染。核心是要处理好非托管资源释放、线程安全与硬解码加速。
public unsafe class RtspVideoDecoder : IDisposable
{
private AVFormatContext* _formatCtx;
private AVCodecContext* _codecCtx;
private SwsContext* _swsCtx;
private int _videoStreamIndex = -1;
private readonly Thread _decodeThread;
private volatile bool _isRunning;
public event Action<VideoFrame> OnFrameDecoded;
public bool Start(string url, bool useHardwareAccel = true)
{
ffmpeg.avformat_network_init();
var formatCtx = ffmpeg.avformat_alloc_context();
// 设置RTSP传输方式为TCP,减少丢包
ffmpeg.av_opt_set(formatCtx->priv_data, "rtsp_transport", "tcp", 0);
ffmpeg.av_opt_set(formatCtx->priv_data, "stimeout", "5000000", 0);
var pFormatCtx = formatCtx;
if (ffmpeg.avformat_open_input(&pFormatCtx, url, null, null) != 0)
return false;
_formatCtx = pFormatCtx;
if (ffmpeg.avformat_find_stream_info(_formatCtx, null) < 0)
return false;
// 查找视频流并初始化解码器
_videoStreamIndex = ffmpeg.av_find_best_stream(
_formatCtx, AVMediaType.AVMEDIA_TYPE_VIDEO, -1, -1, null, 0);
var stream = _formatCtx->streams[_videoStreamIndex];
var codec = ffmpeg.avcodec_find_decoder(stream->codecpar->codec_id);
_codecCtx = ffmpeg.avcodec_alloc_context3(codec);
ffmpeg.avcodec_parameters_to_context(_codecCtx, stream->codecpar);
// 硬解码初始化
if (useHardwareAccel)
{
InitHardwareDecoder(AVHWDeviceType.AV_HWDEVICE_TYPE_DXVA2);
}
ffmpeg.avcodec_open2(_codecCtx, codec, null);
// 初始化格式转换上下文
_swsCtx = ffmpeg.sws_getContext(
_codecCtx->width, _codecCtx->height, _codecCtx->pix_fmt,
_codecCtx->width, _codecCtx->height, AVPixelFormat.AV_PIX_FMT_BGR24,
ffmpeg.SWS_BILINEAR, null, null, null);
// 启动解码线程
_isRunning = true;
_decodeThread = new Thread(DecodeLoop) { IsBackground = true };
_decodeThread.Start();
return true;
}
private void DecodeLoop()
{
var packet = ffmpeg.av_packet_alloc();
var frame = ffmpeg.av_frame_alloc();
var bgrFrame = ffmpeg.av_frame_alloc();
while (_isRunning)
{
if (ffmpeg.av_read_frame(_formatCtx, packet) < 0)
break;
if (packet->stream_index == _videoStreamIndex)
{
ffmpeg.avcodec_send_packet(_codecCtx, packet);
while (ffmpeg.avcodec_receive_frame(_codecCtx, frame) == 0)
{
// 转换为BGR格式
var buffer = Marshal.AllocHGlobal(_codecCtx->width * _codecCtx->height * 3);
var dstData = new byte_ptrArray8 { [0] = (byte*)buffer };
var dstLinesize = new int_array8 { [0] = _codecCtx->width * 3 };
ffmpeg.sws_scale(_swsCtx, frame->data, frame->linesize,
0, _codecCtx->height, dstData, dstLinesize);
OnFrameDecoded?.Invoke(new VideoFrame(
_codecCtx->width, _codecCtx->height, buffer, frame->pts));
}
}
ffmpeg.av_packet_unref(packet);
}
// 资源释放逻辑省略
}
}
3.2.2 WPF低延迟渲染
解码后的视频帧通过WriteableBitmap渲染到WPF界面,核心是要避免UI线程阻塞,同时控制帧率。我们采用后台线程写入后台缓冲区,再通过Dispatcher通知UI更新的方式,保证16路以上视频同时预览时界面不卡顿。
public class VideoRenderer : IDisposable
{
private readonly WriteableBitmap _bitmap;
private readonly object _lockObj = new();
public ImageSource ImageSource => _bitmap;
public VideoRenderer(int width, int height)
{
_bitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.Bgr24, null);
_bitmap.Freeze();
}
public void UpdateFrame(VideoFrame frame)
{
lock (_lockObj)
{
if (_bitmap.IsFrozen)
{
_bitmap.Freeze();
}
_bitmap.Dispatcher.InvokeAsync(() =>
{
_bitmap.Lock();
// 直接拷贝像素数据到后台缓冲区
Buffer.MemoryCopy(
frame.Buffer.ToPointer(),
_bitmap.BackBuffer.ToPointer(),
_bitmap.BackBufferStride * _bitmap.PixelHeight,
frame.Width * frame.Height * 3);
_bitmap.AddDirtyRect(new Int32Rect(0, 0, frame.Width, frame.Height));
_bitmap.Unlock();
}, DispatcherPriority.Render);
}
}
}
多屏预览则通过ItemsControl + 动态布局实现,支持1、4、9、16、32屏切换,每个窗口对应一个独立的解码实例与渲染器,通过线程池统一管理解码线程,避免线程过多导致系统卡顿。
3.3 智能分析与告警联动引擎
传统监控依赖人工值守,效率极低。我们通过ONNX Runtime集成YOLO检测模型,实现人形检测、区域入侵、离岗识别、物品遗留等智能分析能力,再配合告警规则引擎,实现自动告警与设备联动。
3.3.1 AI推理封装
使用Microsoft.ML.OnnxRuntime加载YOLO模型,对视频帧进行推理,输出检测结果。为了不影响视频预览,AI推理单独使用线程池,且支持按帧率抽帧检测,比如每秒检测2帧,平衡性能与准确率。
public class YoloDetector : IDisposable
{
private readonly InferenceSession _session;
private readonly int _inputSize = 640;
private readonly float _confidenceThreshold = 0.5f;
public YoloDetector(string modelPath, bool useGpu = false)
{
var options = new SessionOptions();
if (useGpu)
{
options.AppendExecutionProvider_CUDA(0);
}
options.OptimizationLevel = OptimizationLevel.All;
_session = new InferenceSession(modelPath, options);
}
public List<DetectionResult> Detect(VideoFrame frame)
{
// 图像预处理:缩放、归一化、转换为NCHW格式
var inputTensor = Preprocess(frame);
// 推理
var inputs = new List<NamedOnnxValue>
{
NamedOnnxValue.CreateFromTensor("images", inputTensor)
};
using var results = _session.Run(inputs);
var output = results.First().AsTensor<float>();
// 后处理:NMS非极大值抑制,过滤低置信度结果
return PostProcess(output, frame.Width, frame.Height);
}
private DenseTensor<float> Preprocess(VideoFrame frame)
{
// 缩放、Letterbox填充、归一化
// 实现细节省略,注意保持宽高比,避免变形
}
}
3.3.2 告警规则引擎
告警规则引擎是实现多场景差异化的核心。我们将告警规则抽象为"条件 + 动作"的组合,条件可以是AI检测结果、设备状态、时间范围、区域信息等,动作可以是弹窗、声音、设备控制、消息推送等。
/// <summary>
/// 告警规则接口
/// </summary>
public interface IAlarmRule
{
string RuleId { get; }
bool IsMatch(AlarmContext context);
List<IAlarmAction> Actions { get; }
}
/// <summary>
/// 区域入侵告警规则
/// </summary>
public class RegionIntrusionRule : IAlarmRule
{
public string RuleId { get; set; }
public List<Point> Region { get; set; }
public string TargetType { get; set; } = "person";
public int MinStaySeconds { get; set; } = 3;
public List<IAlarmAction> Actions { get; set; } = new();
public bool IsMatch(AlarmContext context)
{
// 判断检测目标是否在区域内,且停留时间超过阈值
var targets = context.Detections.Where(d => d.Label == TargetType);
return targets.Any(t => IsPointInPolygon(t.Center, Region)
&& t.StayTime >= TimeSpan.FromSeconds(MinStaySeconds));
}
}
告警触发后,通过SignalR实时推送到所有在线客户端,同时执行联动动作,比如打开灯光、触发声光报警器、锁定门禁、录像等。所有规则都可以在管理后台配置,无需修改代码,适配不同场景的需求。
3.4 多场景业务的可复用设计
小区、商超、企业的业务差异,本质是告警规则、联动逻辑、业务流程的差异。我们通过策略模式 + 配置化实现业务逻辑的灵活切换,核心框架完全复用。
以告警联动为例,小区场景下的周界入侵,联动动作是"声光报警 + 保安弹窗 + 灯光开启";商超场景下的收银区异常,联动动作是"店长弹窗 + 录像标记 + 收银数据叠加";企业场景下的重点区域入侵,联动动作是"门禁锁死 + 安保推送 + 自动录像"。
这些差异不需要修改核心代码,只需要为不同场景配置不同的规则与动作集合,系统启动时根据场景标识加载对应的配置即可。同时我们提供场景模板,用户可以基于模板快速配置,也可以自定义规则。
3.5 录像存储与事件回溯
录像存储支持三种策略:定时录像、移动侦测录像、告警录像,其中告警录像优先级最高,且会自动标记事件类型,方便后续检索。
存储层同时支持NVR对接与本地/对象存储,对外提供统一的检索与回放接口。检索支持按时间、通道、事件类型、告警级别等多维度查询,回放支持快进、慢放、拖拽、帧进等操作。
public interface IVideoStorageService
{
// 开始录像
Task<string> StartRecordAsync(string deviceId, int channel, RecordPlan plan);
// 停止录像
Task StopRecordAsync(string recordId);
// 按条件检索录像
Task<List<RecordFileInfo>> SearchRecordsAsync(RecordSearchCondition condition);
// 获取回放流
Task<Stream> GetPlaybackStreamAsync(string recordId, DateTime startTime);
}
四、工程化落地的关键踩坑与优化
安防系统的开发难点不在功能实现,而在稳定运行。实际项目中,多路视频并发、设备异构、网络波动、内存泄漏等问题,往往会导致系统卡顿、崩溃,甚至丢失录像。下面是几个最常见的坑与解决方案。
4.1 多路视频并发的性能瓶颈
问题:当客户端同时预览16路以上1080P视频时,CPU占用率飙升到90%以上,画面严重卡顿,甚至UI无响应。 根本原因:默认使用软解码,每路视频都占用一个CPU核心;WPF渲染在UI线程处理,多路视频同时更新时阻塞UI。 解决方案:
4.2 厂商SDK的内存泄漏与崩溃
问题:厂商SDK大多是非托管C++编写,回调线程、句柄管理、异常处理都不规范,长时间运行后内存持续上涨,甚至随机崩溃。 解决方案:
4.3 告警风暴与误报治理
问题:AI检测存在误报,加上移动侦测、传感器告警,短时间内会产生大量重复告警,导致用户忽略真正的危险。 解决方案:
4.4 网络抖动与断网续传
问题:摄像头与服务器之间网络不稳定时,视频经常中断,录像丢失,设备重连不及时。 解决方案:
4.5 WPF客户端的内存泄漏
问题:客户端长时间运行后内存持续上涨,切换页面、关闭视频窗口后内存不释放。 根本原因:FFmpeg非托管资源未释放、事件订阅未取消、WriteableBitmap引用未释放、MVVM绑定导致的内存泄漏。 解决方案:
4.6 跨场景部署的配置化适配
问题:不同项目的设备数量、功能需求、告警规则都不一样,每次部署都要修改代码、重新编译,维护成本极高。 解决方案:
五、部署架构与扩展方向
5.1 私有化部署方案
系统支持单机部署与分布式部署两种模式:
- 单机部署:适合小型项目,服务端、数据库、客户端都部署在一台工控机上,直接连接摄像头,成本低,部署简单。
- 分布式部署:适合中大型项目,服务端部署在服务器,视频流服务、智能分析服务可以独立部署,支持横向扩展,客户端分布在多个监控室。
对于有边缘计算需求的场景,还可以将智能分析服务部署在边缘网关,只上传告警结果,节省带宽。




