ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

海康网络摄像机OSD字符叠加开发实战:基于设备网络SDK实现动态文字水印

2026/9/9 17:18:24 拓冰建站 浏览量
海康网络摄像机OSD字符叠加开发实战:基于设备网络SDK实现动态文字水印 简介面向海康网络摄像机二次开发者的OSD字符叠加例程基于官方SDK在视频画面中叠加文字水印与Logo图标解决监控画面自定义信息标注需求适合在BCB6.0环境下进行安防客户端功能扩展的工程师参考。压缩包共56个文件整体约11.61MB包含dll动态链接库、lib导入库、C源码、头文件、可执行demo及设备配置数据等SDK运行组件与示例工程齐全便于对照编译和运行。当前已有4012人学习/下载。资源内提供了叠加RGB565、RGB555、RGB24、RGB32等多种图像格式的像素级修改方法支持多OSD区域叠加通过查看源码和工程结构读者可快速掌握海康SDK的播放、预览与字符叠加调用流程并在此基础上扩展自己的监控客户端功能。 做监控项目的时间久了对“海康网络摄像机”这几个字的感情是又爱又恨。爱的是SDK功能确实全恨的是文档和结构体太绕不踩几个坑根本拿不到想要的效果。前阵子接了个仓储监控的活要求给几十台摄像头的实时画面叠加OSD文字水印显示库位编号、责任人和时间。现场同事第一反应是让我去Web后台一台台配置但那等于把自己绑死在运维上业务系统一旦改了库位还得派人去摄像头后台改。所以最后我还是决定用海康API接口SDK做统一管理。这篇文章就围绕这个项目中OSD叠加文字字符水印的完整实现来写从SDK接入、结构体理解到下发配置和排障经验全部是我实测过、可以直接套用的内容。如果你正准备做海康设备二次开发这一篇应该能帮你少走不少弯路。1. 先想清楚这个demo解决的是哪一类问题1.1 实际应用场景OSD是On-Screen Display的缩写在视频监控里最常见的用法就是把字符、时间、通道名直接烧录到视频流中和画面融为一体。这种叠加发生在摄像头编码固件内部不是播放器后期贴上去的所以无论是实时预览、本地存储还是平台转发水印一直都在。我这次项目里最典型的场景有三类。第一是库位标识仓库有十几个区每个区对应若干摄像头画面上需要固定显示“A-03-02”这样的库位号技防人员调回放时一看就知道是哪个位置。第二是责任人信息值班人员交接后负责人姓名要能跟随业务数据自动变更而不是每次手动去摄像头后台改。第三是告警联动提示当红外告警触发时业务系统希望画面上出现一行“ALARM: DOOR-OPEN”的提示文字方便事后核对录像和告警时间。这三个需求有一个共同点文字内容不是写死的要么来自业务数据库要么来自现场事件所以必须有程序化手段去动态控制。1.2 为什么不用页面配置和播放端叠加容易想到的替代方案有三个。第一是摄像头Web管理页手工配置OSD这个方案几乎不写代码单台设备调试也快缺点是完全没法批量管理几十台设备逐个改配置会让人崩溃而且业务系统无法动态变更文字。第二是在客户端播放器上叠加文字比如用Web插件、自绘控件在显示层画字实现简单、效果好看但水印只存在于播放窗口录像文件里根本看不到对安防溯源没有意义。第三就是本文要说的设备SDK方案在摄像头内部把OSD配置下发给设备让它在视频编码时直接加上字符预览和录像双生效还能循环下发控制多台设备。从工程角度讲第三种才是正规做法。前两种适合演示和临时调试不能作为正式项目交付方案。尤其需要注意很多刚接触海康二次开发的同学会把“播放端叠加”和“设备端OSD”混为一谈到了验收阶段发现录像里没有水印才回头整改工期就耽误了。2. 环境准备与SDK接入2.1 SDK下载与目录结构海康的设备网络SDK从官网服务支持板块能找到类型选“设备网络SDK”版本很多建议优先拿最新的稳定版。下载后是一个压缩包解压出来一般会有几个关键目录inc目录放的是头文件lib目录下面是运行库doc目录里有《设备网络SDK使用手册》demo目录则是官方示例代码。我自己习惯在工程里单独建一个HikSDK目录把inc和lib拷贝进去后续升级SDK时只替换这一个目录就行。这里要额外提醒一句网上下到的所谓“精简版SDK”、“绿色版SDK”不要乱用。海康的接口依赖很多基础组件少了某个DLL运行时才报错最折腾人。老老实实用官方包缺什么组件都能在doc目录里查到说明。2.2 开发工程配置以Windows加Visual Studio为例项目属性里做三步C/C常规附加包含目录填SDK的inc路径链接器常规附加库目录填SDK的lib路径链接器输入附加依赖项加上HCNetSDK.lib和PlayCtrl.lib。如果只是做OSD下发不涉及预览播放PlayCtrl.lib其实可以不加但实际项目中大多数情况还是要看到画面所以建议直接都引进来。Debug和Release模式都要检查一遍配置很多朋友在Debug下编译通过切Release就报一堆链接错误基本都是库目录配置遗漏。运行阶段还需要把HCNetSDK.dll、PlayCtrl.dll、hlog.dll、hpr.dll这一堆动态库放到程序输出目录。最简单的方式是把lib目录下的DLL全部复制过去避免缺文件。部署到客户机器时也照做我见过太多现场报“找不到HCNetSDK.dll”的情况提前把依赖收拾干净能省一半售后。2.3 初始化和登录写代码之前先把SDK的初始化、登录、注销这套标准流程理清楚。SDK使用前一定要调用NET_DVR_Init做初始化进程退出时调用NET_DVR_Cleanup释放资源。登录推荐用NET_DVR_Login_V40它同时返回设备信息和通道数等基础属性省掉很多额外的获取接口调用。#include HCNetSDK.h #include cstdio int main() { // 1. 初始化SDK NET_DVR_Init(); NET_DVR_SetConnectTime(3000, 2); NET_DVR_SetReconnect(10000, 1); // 2. 填写设备登录信息 NET_DVR_USER_LOGIN_INFO struLogin { 0 }; struLogin.bUseAsynLogin 0; strcpy(struLogin.sDeviceAddress, 192.168.1.64); struLogin.wPort 8000; strcpy(struLogin.sUserName, admin); strcpy(struLogin.sPassword, your_password); NET_DVR_DEVICEINFO_V40 struDevInfo { 0 }; LONG lUserID NET_DVR_Login_V40(struLogin, struDevInfo); if (lUserID 0) { printf(login failed, error%d\n, NET_DVR_GetLastError()); NET_DVR_Cleanup(); return -1; } printf(login ok, serial: %s\n, struDevInfo.struDeviceV30.sSerialNumber); // 此处放后面章节的业务代码 // 3. 注销并清理 NET_DVR_Logout(lUserID); NET_DVR_Cleanup(); return 0; }注意struLogin.sPassword字段是ANSI字符数组如果工程是UNICODE编码记得先转成ANSI再拷贝。SDK默认走ANSI这一点在后面处理中文OSD的时候还会再次踩到。3. 核心实现OSD叠加文字水印3.1 先把实时预览建立起来进入正题。要让OSD效果直观可见首先得把实时预览跑起来。用NET_DVR_CreateRealPlay_V40创建播放句柄hPlayWnd是窗口句柄如果程序是纯后台服务没有界面也可以传NULL预览照样能建立只是看不到画面。常见错误是把lChannel写成0实际上通道号从1开始通道0在某些设备上是虚拟通道不容易踩对。NET_DVR_PREVIEWINFO struPlay { 0 }; struPlay.hPlayWnd NULL; // 没有UI就传NULL struPlay.lChannel 1; // 通道从1开始 struPlay.dwStreamType 0; // 主码流 struPlay.dwLinkMode 0; // TCP方式 struPlay.bBlocked 1; // 阻塞式取流 LONG lRealHandle NET_DVR_CreateRealPlay_V40(lUserID, struPlay, NULL, NULL); if (lRealHandle 0) { printf(create realplay failed, error%d\n, NET_DVR_GetLastError()); NET_DVR_Logout(lUserID); NET_DVR_Cleanup(); return -1; }预览句柄建立之后设备端才会真正开始推流。OSD下发布置和取流通道是同一套会话体系把预览开起来再下发配置生效的成功率会高很多尤其是对部分老型号固件。3.2 OSD叠加核心结构体详解OSD叠加字符的核心是一个结构体NET_DVR_SHOWSTRING_V40。网上很多旧代码用的是老结构NET_DVR_SHOWSTRING所有字符串共享一套字号、颜色、对齐方式定制性弱。V40版本把每个字符串的信息拆到了NET_DVR_STRING_INFO结构里可以给每行单独设置字体大小、颜色和位置。字段说明wYear/byMonth/byDay/byHour/byMinute/bySecond/byMillisecond时间相关字段普通文字留0即可系统时间叠加由设备自己处理sString显示的字符串内容中文字符按GBK编码传入byStringSize字符大小0大字1小字byAlignment对齐位置0左上、1右上、2左下、3右下、4中央byFontColor字体颜色0白、1红、2黄、3蓝、4绿、5橙、6粉、7深蓝byStringFont字体类型0宋体、1黑体、2楷体、3仿宋byRes保留字段务必清零外层NET_DVR_SHOWSTRING_V40里dwStringNum表示本次要下发几个字符串dwStringInfoNum在V40里通常与dwStringNum保持一致struStringInfo数组最多8个元素。需要注意dwSize字段必须等于sizeof(NET_DVR_SHOWSTRING_V40)否则接口会返回参数错误。很多第一次用的人栽在这里明明逻辑都对就是返回失败其实就是结构体大小没填对。3.3 下发到设备并生效看代码。这个示例假设通道1叠加两行OSD第一行是库位号第二行是责任人。NET_DVR_SHOWSTRING_V40 struShow { 0 }; struShow.dwSize sizeof(NET_DVR_SHOWSTRING_V40); struShow.dwStringNum 2; struShow.dwStringInfoNum 2; // 第一行库位号红色、大字、左上角 strcpy(struShow.struStringInfo[0].sString, A-03-02); struShow.struStringInfo[0].byStringSize 0; struShow.struStringInfo[0].byAlignment 0; struShow.struStringInfo[0].byFontColor 1; struShow.struStringInfo[0].byStringFont 0; // 第二行责任人黄色、小字、左上角 strcpy(struShow.struStringInfo[1].sString, luru: zhangsan); struShow.struStringInfo[1].byStringSize 1; struShow.struStringInfo[1].byAlignment 0; struShow.struStringInfo[1].byFontColor 2; struShow.struStringInfo[1].byStringFont 1; // 通道为1命令码用NET_DVR_SET_SHOWSTRING_V40 if (!NET_DVR_SetDVRConfig(lUserID, NET_DVR_SET_SHOWSTRING_V40, 1, struShow, sizeof(struShow))) { printf(set OSD failed, error%d\n, NET_DVR_GetLastError()); }下发完成后不要马上关程序把预览窗口开在那里观察几秒确认OSD真的显示、位置没有压到关键监视区域再收工。这个配置是持久化到设备的注销登录后依然保留除非有人改回去。所以现场验收时要特别注意别把A区域的文字带到B区域的摄像头。3.4 动态刷新与批量管理思路这个项目真正的价值在于动态和批量。比如告警联动当上位机收到门磁信号后需要把告警文字推送到对应画面可以在回调线程里直接调NET_DVR_SetDVRConfig把sString改成新内容重新下发即可。但要注意控制频率建议不要低于5秒刷新一次。有些设备对OSD配置的写操作比较敏感频繁下发可能导致画面短暂花屏严重时卡死预览流。别问我怎么知道的项目上线时一位同事写了个2秒一次的定时器结果现场画面每隔几分钟就闪一下排查了很久。批量管理就更简单了。把登录和设置封装成一个函数循环遍历设备IP列表逐个登录下发最后统一退出。真正要花时间的是设备在线状态探测、失败重试和日志记录这些才是工程落地里容易被忽略的部分。另外建议把OSD内容做成配置项不要硬编码在程序里尤其是责任人名字、库位号这类会变的业务数据放到数据库或配置文件里系统重启后自动加载。4. 常见问题与排查实录4.1 登录和网络类问题错误现象可能原因解决办法登录返回error 7网络不通、IP或端口不对ping设备确认8000端口检查网线和VLAN隔离登录返回error 23用户名或密码错误设备启用非法登录锁定核对账号权限确认密码必要时登录Web后台解锁设置OSD返回error 17参数错误通常是结构体大小或命令码问题检查dwSize、命令码、通道号逐项打印核对设置返回error 29设备不支持该功能或固件版本过旧升级设备固件或改用兼容性更好的旧版结构体中文显示乱码传入字符串不是GBK编码把UTF-8字符串转成GBK后再下发登录失败是最常见的拦路虎我一般按“先网络、再密码、后安全策略”的顺序排查。先ping通再telnet测8000端口确认TCP能连上然后才去核对密码。海康设备有个恶心人的设置叫“非法登录锁定”密码试错几次后会把IP锁一段时间SDK报错和Web登录报错还不完全一样容易被误导。遇到这种情况先去Web后台把锁定解除再回头跑SDK。4.2 设置成功但不生效如果NET_DVR_SetDVRConfig返回成功画面却没有变化优先怀疑通道号配错。多通道设备尤其容易出问题每通道OSD是独立配置的你在通道1下发了两行字看的却是通道2的画面自然认为没生效。这不能用“看起来差不多”来判断要把通道号、设备型号、当前预览通道都打出来对比。另一个原因是设备固件的叠加总开关被占用了。比如有人用4200客户端或者海康vm软件管理过这台设备Web端手工配过文字SDK再下发时被Web端的配置覆盖或者互踩。实际项目里如果多个系统同时管理同一批设备必须约定配置来源以谁为准否则今天SDK覆盖Web明天Web覆盖SDK排查会非常痛苦。4.3 中文乱码与编码处理中文乱码是OSD开发里绕不开的坎。海康老版本SDK的ANSI接口对字符串的编码约定是GBK。你在Visual Studio的源文件里直接写中文如果源文件保存成UTF-8strcpy过去的就是UTF-8字节流设备端按GBK解析自然乱码。我建议统一走编码转换别依赖运气。Windows下可以用MultiByteToWideChar和WideCharToMultiByte做转换或者干脆把OSD内容统一从UTF-8配置转成GBK再调用SDK。新版SDK也提供了NET_DVR_SetSDKInitCfg可以设置全局编码类型但老设备兼容性有限最稳的做法还是程序里显式转成GBK。测试时多测几个含生僻字的名称比只测“张三”要靠谱。4.4 设备型号和固件差异海康的设备线很长不同型号、不同固件对OSD叠加的支持程度差异不小。同一套代码在A型号上完美运行换到B型号可能就返回不支持。遇到这种情况先查设备型号和固件版本再去官网确认该型号是否支持字符叠加功能。部分摄像机固件开启移动侦测或人脸抓拍后会把OSD区域强制挪动位置导致你配置的对齐方式看起来变了这是设备自身的策略SDK层面控制不了。另外现在有些项目会搭配海康vm软件做视觉定位那是另一套视觉平台和本文说的设备网络SDK不是一回事不要混用。如果视觉工位也要出图带水印通常是在vm流程里配置文字绘制模块而不是调摄像头的OSD接口。两边的实现路径完全不同选择前要先想清楚你到底需要的是设备端永久的OSD还是临时显示的文字标签。5. 复盘这套OSD方案值得注意的几个细节最后聊点个人体会。我在交付这个项目之后复盘过几次最深的感受是“设备端OSD虽然功能简单但它属于编码器层面的能力改错一次影响面不亚于动一次业务配置”。所以现在我做OSD下发前一定会先调用NET_DVR_GetDVRConfig把当前配置读出来在程序里比对后再覆盖下发。这样既能确认通道号对不对也能避免把现场已有配置无意中清掉。很多问题不是代码写不出来而是没做好提前校验。另一个经验是预留日志。每次设置OSD之后把设备IP、通道、下发内容、错误码和时间戳记到本地日志里线上回溯会轻松很多。尤其是批量管理几十台设备时光靠控制台打印根本看不出哪台设备失败了。如果你是在做一个长期维护的监控平台建议把OSD内容的来源从业务数据库读取而不是把值写在代码里。这样今天改库位、明天换责任人都只改数据不动程序。能做到这一步这个demo就不再是演示玩具而是一个可以被业务系统持续调用的标准能力。后面我还在计划把OSD内容做成可视化配置界面让现场实施人员不用改代码就能调整文字位置和颜色到时候再写一篇具体实现分享给大家。本文还有配套的精品资源点击获取