海康 NVR 相机图像采集

一、网络拓扑与连接

1.1 网络环境示例

复制代码
主机 (192.168.1.100)
  │
  │  网线
  ▼
NVR LAN口 (192.168.1.10)            ← 同网段 192.168.1.x
  │
  │  NVR内部隔离
  ▼
NVR POE口 (内部网络)
  │
  ├── 通道1 (POE口1)
  ├── 通道2 (POE口2) → 相机 (192.168.2.10)   ← POE内部网段 192.168.2.x
  ├── 通道3 (POE口3)
  └── ...

1.2 NVR 网络结构说明

NVR 有两类网口,物理隔离

网口类型 用途 网段示例 特点
LAN口 连接外部网络(主机、交换机) 192.168.1.10 普通网口,对外通信
POE口 连接POE相机,供电+数据 192.168.2.x(默认) 专用口,供电48V,NVR内部管理

关键点: LAN口和POE口是两个独立的物理网络,NVR在中间做隔离转发。


二、常见疑问解答

疑问1:主机和相机不在同一网段,为什么还能采集图像?

答: 主机不需要直接访问相机。所有通信都通过 NVR 中转:

  • 主机 → NVR LAN口(如 192.168.1.10)→ NVR内部转发 → POE口上的相机
  • 主机只跟 NVR 的 LAN口通信,通过通道号区分不同相机
  • 相机在哪个网段无所谓,NVR负责内部路由

类比: NVR 就是一个视频代理网关,对外一个IP,对内管理所有相机。

疑问2:相机IP能改成其他网段吗?比如改成 192.168.1.20?

答: 可以改,但没有意义

  • POE口相机的网段是 NVR 内部管理的,可在 NVR Web 管理界面修改:配置 → 网络配置 → 基本配置 → POE网段设置
  • 即使改成与主机同网段,主机也无法直接访问
  • 因为 LAN口和POE口是 Layer 2 物理隔离,NVR 不会把 LAN口的数据转发到 POE口
  • 程序配置中 ip 字段永远写 NVR 的 LAN口 IP,与相机实际IP无关

疑问3:主机能不能接在 NVR 的 POE口上直接访问相机?

答: 不能,且有硬件损坏风险。

  • POE口带 48V 供电 ,直接接主机网卡可能烧坏网口
  • POE口之间没有路由转发,接上去设备之间也不能互访
  • POE口是给POE相机专用的,不是普通网口

疑问4:如果相机接在交换机上(不接NVR),能直连吗?

答: 可以。接入方式对比:

接入方式 主机能直连相机? 程序配置 devType 配置 IP
相机 → NVR POE口 ❌ 必须走NVR "nvr" NVR的IP
相机 → 同一交换机 ✅ 可以直连 "camera" 相机的IP
复制代码
# 直连方式拓扑
主机 (192.168.1.100) → 交换机 ← 相机 (192.168.1.20)
                           │
                      NVR LAN口 (192.168.1.10)

三、通过 C++ 海康 SDK 采集图像

3.1 采集原理

复制代码
┌─────────┐    SDK初始化     ┌─────────┐    内部转发    ┌─────────┐
│  主机    │ ──────────────→ │  NVR    │ ────────────→ │  相机    │
│ (C++程序)│ ←────────────── │ (代理)  │ ←──────────── │ (图像源) │
└─────────┘   返回图像数据    └─────────┘   取回图像     └─────────┘
              (端口8000)

核心流程:初始化 → 登录 → 预览 → 抓图

3.2 核心代码流程

步骤1:SDK初始化
cpp 复制代码
NET_DVR_Init();  // 初始化海康SDK,只需调用一次
步骤2:登录设备
cpp 复制代码
NET_DVR_USER_LOGIN_INFO loginInfo = {0};
loginInfo.bUseAsynLogin = 0;
strcpy(loginInfo.sDeviceAddress, "192.168.1.10");   // NVR的IP
loginInfo.wPort = 8000;                              // SDK端口
strcpy(loginInfo.sUserName, "admin");
strcpy(loginInfo.sPassword, "123456");

NET_DVR_DEVICEINFO_V40 deviceInfo = {0};
LONG userID = NET_DVR_Login_V40(&loginInfo, &deviceInfo);
// userID >= 0 表示登录成功
步骤3(NVR专用):获取数字通道起始号
cpp 复制代码
// 仅 devType == "nvr" 时需要
NET_DVR_IPPARACFG_V40 ipcfg;
DWORD bytesReturned = 0;
NET_DVR_GetDVRConfig(userID, NET_DVR_GET_IPPARACFG_V40, 0,
                     &ipcfg, sizeof(ipcfg), &bytesReturned);
// ipcfg.dwStartDChan 即数字通道起始号(通常为33)
步骤4:实时预览
cpp 复制代码
NET_DVR_PREVIEWINFO previewInfo;
previewInfo.hPlayWnd = (HWND)widget->winId();  // Qt窗口句柄,SDK直接渲染
previewInfo.lChannel = startDChan + channel - 1;  // 实际通道号
// 例:startDChan=33, channel=2 → lChannel=34
previewInfo.dwStreamType = 0;   // 0=主码流, 1=子码流
previewInfo.dwLinkMode = 0;     // 0=TCP, 1=UDP
previewInfo.bBlocked = 1;

LONG playHandle = NET_DVR_RealPlay_V40(userID, &previewInfo, callback, this);
// playHandle >= 0 表示预览成功,画面自动渲染到 hWnd

通道号计算:

场景 通道号计算 示例
直连相机(devType=camera) m_nStartChan + channel - 1 1+1-1 = 1
NVR连接(devType=nvr) m_dwStartDChan + channel - 1 33+2-1 = 34
步骤5:抓图(三种方式,自动降级)
cpp 复制代码
NET_DVR_JPEGPARA jpegPara = {0};
jpegPara.wPicSize = 0xFF;    // 当前分辨率
jpegPara.wPicQuality = 0;    // 最高质量

// 方式1:设备端抓图到文件(原图分辨率,推荐)
BOOL ret = NET_DVR_CaptureJPEGPicture(userID, channel, &jpegPara, "save.jpg");

// 方式2:设备端抓图到内存(原图分辨率)
char buf[10*1024*1024];
DWORD picSize = 0;
BOOL ret = NET_DVR_CaptureJPEGPicture_NEW(userID, channel, &jpegPara,
                                           buf, sizeof(buf), &picSize);
// 将 buf 前 picSize 字节写入文件即为JPEG图片

// 方式3:从预览流抓图(子码流分辨率,兜底方案)
char buf[10*1024*1024];
DWORD picSize = 0;
BOOL ret = NET_DVR_CapturePictureBlock_New(playHandle, buf, sizeof(buf), &picSize);

抓图方式优先级:

优先级 方式 分辨率 依赖
1 NET_DVR_CaptureJPEGPicture 原图 需登录,不需要预览
2 NET_DVR_CaptureJPEGPicture_NEW 原图 需登录,不需要预览
3 NET_DVR_CapturePictureBlock_New 子码流 需登录+预览

3.3 配置文件说明

json 复制代码
{
    "viewtype": "fill",
    "cameras": [
        {
            "id": "nvr_ch2",
            "name": "NVR通道2",
            "ip": "192.168.1.10",
            "port": 8000,
            "user": "admin",
            "password": "123456",
            "channel": 2,
            "devType": "nvr"
        }
    ]
}
字段 说明 NVR模式 直连相机模式
ip 设备IP NVR的LAN口IP 相机IP
port SDK端口 8000 8000
channel 通道号(整数) NVR通道号(1,2,3...) 固定填1
devType 设备类型 "nvr" "camera"

四、通过 RTSP 采集图像

4.1 RTSP 采集原理

复制代码
┌─────────┐   RTSP请求(端口554)  ┌─────────┐   内部转发   ┌─────────┐
│  主机    │ ──────────────────→ │  NVR    │ ──────────→ │  相机    │
│ (客户端) │ ←────────────────── │ (代理)  │ ←────────── │ (图像源) │
└─────────┘   返回视频流数据      └─────────┘   取回图像    └─────────┘
              (RTSP/RTP协议)

与 SDK 方式的区别:

  • SDK 走 8000端口 ,RTSP 走 554端口
  • SDK 需要海康专用 DLL,RTSP 只需要标准播放器/FFmpeg
  • 都是通过 NVR LAN口通信,NVR 内部转发到对应通道

4.2 RTSP 地址格式

复制代码
rtsp://用户名:密码@NVR_IP:554/Streaming/Channels/通道码

通道码规则:通道号 × 10 + 码流号

通道号 主码流(码流号1) 子码流(码流号2)
1 101 102
2 201 202
12 1201 1202

4.3 RTSP 地址示例

复制代码
# 通过 NVR 访问通道2
主码流: rtsp://admin:123456@192.168.1.10:554/Streaming/Channels/201
子码流: rtsp://admin:123456@192.168.1.10:554/Streaming/Channels/202

# 通过 NVR 访问通道12
主码流: rtsp://admin:123456@192.168.1.10:554/Streaming/Channels/1201
子码流: rtsp://admin:123456@192.168.1.10:554/Streaming/Channels/1202

# 直连相机(相机接交换机时)
主码流: rtsp://admin:123456@192.168.1.20:554/Streaming/Channels/101
子码流: rtsp://admin:123456@192.168.1.20:554/Streaming/Channels/102

4.4 RTSP 采集图像方式

方式1:用 FFmpeg 命令行抓图
bash 复制代码
# 抓取一帧保存为图片
ffmpeg -i "rtsp://admin:123456@192.168.1.10:554/Streaming/Channels/201" \
       -frames:v 1 output.jpg

# 连续抓图(每秒1帧)
ffmpeg -i "rtsp://admin:123456@192.168.1.10:554/Streaming/Channels/201" \
       -r 1 output_%04d.jpg
方式2:用 VLC 验证

打开 VLC → 媒体 → 打开网络串流 → 粘贴 RTSP 地址

方式3:C++ 代码集成 FFmpeg(需要开发)
cpp 复制代码
// 伪代码:通过 FFmpeg C API 从 RTSP 取流并保存图像
AVFormatContext* fmtCtx = nullptr;
avformat_open_input(&fmtCtx,
    "rtsp://admin:123456@192.168.1.10:554/Streaming/Channels/201",
    nullptr, nullptr);
avformat_find_stream_info(fmtCtx, nullptr);

// 找到视频流
int videoIdx = av_find_best_stream(fmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);

// 读取帧
AVPacket pkt;
while (av_read_frame(fmtCtx, &pkt) >= 0) {
    if (pkt.stream_index == videoIdx) {
        // 解码并保存为JPEG
        // ...
    }
    av_packet_unref(&pkt);
}

4.5 SDK 方式 vs RTSP 方式对比

对比项 海康 SDK RTSP
依赖库 HCNetSDK.dll/.so 等30+个库文件 FFmpeg(或OpenCV等)
跨平台 Windows + Linux(需对应平台SDK) 全平台(Windows/Linux/Mac)
设备兼容性 仅海康设备 几乎所有网络相机
预览渲染 SDK直接渲染到HWND 需自行解码渲染
抓图方式 SDK专用接口(原图) 解码视频帧(或触发RTSP快照)
抓图延迟 几乎无延迟 有明显延迟(见4.6节)
端口 8000 554

4.6 RTSP 延迟问题分析

实际测试结论

在同一网络环境下,分别用 SDK 和 RTSP 对同一 NVR 通道进行抓图:

采集方式 抓图一帧的耗时 延迟感受
SDK(NET_DVR_CaptureJPEGPicture) 约 0.2~0.5 秒 几乎即时
RTSP(FFmpeg 取流解码) 约 1~3 秒 明显卡顿/滞后
延迟原因分析

RTSP 取流存在多层延迟叠加:

复制代码
                    RTSP 延迟构成
─────────────────────────────────────────────
① 连接建立        RTSP DESCRIBE → SETUP → PLAY 握手    ~200ms
② 缓冲等待        等待下一个关键帧(I帧)才能解码          ~0.5~2s
③ 解码延迟        FFmpeg 解码 H.264/H.265                ~100ms
④ 缓冲策略        FFmpeg 默认缓冲队列                     ~200ms
─────────────────────────────────────────────
                  总延迟                                 ~1~3s

关键瓶颈在第②步: RTSP 传输的是 H.264/H.265 视频流,必须等到下一个 I 帧(关键帧)才能开始解码。相机的关键帧间隔通常为 1~2 秒,这就是 RTSP 抓图慢的主要原因。

为什么 SDK 没有延迟

SDK 抓图走的是完全不同的路径:

复制代码
SDK 抓图路径:
主机 ──SDK命令──→ NVR ──触发相机传感器直接拍照──→ 返回JPEG原图
                   │                                    │
                   │    不经过视频编码/解码流程            │
                   │    直接获取相机ISP输出的原始图像       │
                   ←──────── JPEG 二进制数据 ──────────────←
对比 SDK 抓图 RTSP 抓图
触发方式 发送命令,相机即时拍照 从已有视频流中取帧
图像来源 相机传感器直出 视频编码流解码
是否依赖关键帧 ❌ 不依赖 ✅ 必须等 I 帧
是否需要解码 ❌ 直接返回 JPEG ✅ 需要 H.264/H.265 解码
输出质量 原图分辨率,最高画质 取决于码流分辨率和画质
缓解 RTSP 延迟的方法(效果有限)
bash 复制代码
# 降低 FFmpeg 缓冲
ffmpeg -fflags nobuffer -flags low_delay -i "rtsp://..." -frames:v 1 output.jpg

# 使用 UDP 代替 TCP(减少握手开销)
ffmpeg -rtsp_transport udp -i "rtsp://..." -frames:v 1 output.jpg

# 设置超短超时
ffmpeg -stimeout 2000000 -i "rtsp://..." -frames:v 1 output.jpg

即使做了以上优化,RTSP 延迟仍然在 0.5~1.5 秒左右,无法达到 SDK 的毫秒级响应

结论
  • 对实时性要求高的场景(如工业采集):必须使用 SDK 方式
  • 对延迟不敏感的场景(如远程查看):RTSP 可用,且跨平台更方便
  • Linux 下需要低延迟采集:使用 Linux 版 SDK 封装 Python 接口(参见《C++封装Python采集接口技术文档》)

五、总结

推荐方案

场景 推荐方式
仅用海康设备、Windows平台 SDK方式(当前已实现)
Linux平台、需要低延迟采集 SDK方式(Linux版 + Python封装)
需要跨平台或兼容多品牌相机 RTSP方式(需集成FFmpeg,但有延迟)
远程查看、对延迟不敏感 RTSP方式即可

关键要点

  1. 主机通过 NVR 访问相机,不需要和相机同网段
  2. NVR LAN口和POE口物理隔离,不要把主机接到POE口上
  3. 通道号必须是整数,不能写 "D2" 这样的字符串
  4. devType 决定通道计算方式"nvr" 用数字通道起始号,"camera" 用模拟通道
  5. RTSP 地址通道码 = 通道号 × 10 + 码流号
  6. RTSP 存在明显延迟(1~3秒),SDK 抓图几乎即时,对实时性有要求的场景必须用 SDK
相关推荐
LabVIEW开发1 天前
LabVIEW 机器视觉 让 FDM 3D 打印缺陷检出率达到 100%
数码相机·labview·labview知识·labview功能·labview程序
小白不白1111 天前
Invoke的用法
开发语言·人工智能·数码相机·计算机视觉·c#
博图光电1 天前
梅卡曼德工业相机代理 | 专业工业视觉解决方案 - 助力智能制造
数码相机·制造
拓朗工控2 天前
视觉检测行业工控机选型指南:核心要素与避坑策略
人工智能·数码相机·视觉检测·工控机·工业电脑
小宋加油啊2 天前
对于工业相机的认识(对机械臂的,工业方面的也可以参考)
数码相机
暂未成功人士!2 天前
相机标定---张正友相机标定和手眼标定
数码相机·手眼标定·相机标定
大江东去浪淘尽千古风流人物3 天前
【VGGT】统一3D重建:单网络同时预测相机位姿、深度图、点云与3D轨迹的前馈Transformer架构深度解析
网络·数码相机·3d·transformer·slam·3d重建·cvpr2025
CG_MAGIC3 天前
摄像机与渲染输出:焦距、景深与Cycles/Eevee渲染设置
数码相机·3d·贴图·效果图·建模教程·渲云渲染
蝈蝈Tjguo3 天前
opencv 与摄影测量 相机坐标系的区别
人工智能·数码相机·opencv