
简介这份资源是面向C#开发者与机器视觉入门者的RealSense D435图像流获取示例工程针对在Visual Studio 2019环境下调用Intel RealSense SDK 2.0、初始化Pipeline并读取彩色或深度帧这一常见需求提供了可直接运行的参考实现适合具备一定C#基础、希望快速上手3D深度相机开发的工程师与学习者。压缩包共66个文件约13.79MB以cs源码、dll动态库、json配置、exe可执行文件与pdb调试符号为主另含png截图、txt说明及sln解决方案等工程文件结构完整、开箱即用。目前已有1294人学习下载。通过该Demo读者可掌握配置彩色流与深度流、等待帧集合并将ColorFrame转换为位图保存的完整流程并以此为起点扩展实时图像处理、运动检测或3D重建等功能是理解RealSense SDK C#绑定的实用参考。1. 从一行标题到能跑通的图像流C# RealSense Demo 到底在做什么很多人第一次拿到 RealSense 深度相机插上 USB 看到设备管理器里多出一个摄像头就以为跟普通网络摄像头一样OpenCV里VideoCapture(0)一开就能出图。结果跑起来要么黑屏要么只出彩色不出深度要么帧率低得离谱。这个 Demo 要解决的就是这件事在 C# 环境里用官方 SDK 把 RealSense 的彩色流、深度流稳定地取出来并且能对齐、能保存、能继续往上做检测或测量。它适合两类人一类是做工业视觉、机器人、三维重建的 C# 开发者另一类是手里有 RealSense 但一直卡在“能识别、不能取流”这一步的工程师。核心不是调 API而是理解流配置、帧同步和内存管理这三件事否则你拿到的只是一堆对不上的图。2. 选型先定死为什么是 librealsense 的 C# 封装而不是 OpenCV 直连2.1 RealSense 在 Windows 下的两条取流路径RealSense 在 Windows 上能走的路其实就两条。第一条是把它当 UVC 摄像头用 OpenCV 的VideoCapture直接读。这条路胜在简单彩色流基本能出但深度流经常拿不到因为深度数据不是标准 UVC 格式需要 SDK 做解析和校准。第二条是走 Intel 官方的librealsense2C# 侧用librealsense的 .NET 封装。这条路多一层依赖但深度、红外、IMU、帧元数据都能拿到而且能控制曝光、激光发射功率这些底层参数。我一般会直接选第二条。原因很实际你做 RealSense 的 Demo大概率不是只为了看彩色画面而是要用深度。用 OpenCV 直连后面想拿深度图还得再切回 SDK等于白写一遍。选librealsense的 C# 封装前期多配一个 NuGet 包后面省掉大量返工。2.2 环境准备SDK、运行时和 NuGet 包三件套在动手写代码之前先把三样东西装齐。缺一个都会在运行时报DllNotFoundException或者Could not load file or assembly。第一步装librealsense2的 Windows SDK。去官方发布页下载对应版本安装时勾选“Add to PATH”。装完在命令行敲realsense-viewer能弹出官方查看器就说明 SDK 和驱动都正常。这一步很关键很多人跳过查看器直接写代码结果分不清是驱动问题还是代码问题。第二步确认 USB 控制器带宽。RealSense 的深度流和彩色流同时开对 USB 带宽要求不低。常见做法是插在主板原生 USB 3.0 口上不要走前面板或者 Hub。如果查看器里帧率上不去先换口再查线。第三步在 C# 项目里加 NuGet 包。常见的是librealsense的 .NET 绑定包名和版本以你实际能搜到的为准。加完之后项目目标框架建议用net6.0-windows或更高AnyCPU有时候会在 32 位下找不到 DLL改成x64更稳。# 验证 SDK 是否在 PATH 里能输出版本号说明环境变量没问题 realsense-viewer --version这条命令不是必须的但它能帮你快速判断 SDK 是否装到了系统路径。如果提示找不到命令就去安装目录把bin目录手动加到 PATH重启终端再试。2.3 最小取流代码先让彩色流跑起来环境齐了之后不要一上来就彩色加深度一起开。先写一个只取彩色流的最小版本确认管线能通再往上加东西。using Intel.RealSense; using System; class Program { static void Main(string[] args) { // 创建管线管线是 RealSense 取流的核心对象 using (var pipe new Pipeline()) { // 配置彩色流640x48030帧BGR8 格式方便后续转 Bitmap var cfg new Config(); cfg.EnableStream(Stream.Color, 640, 480, Format.Bgr8, 30); // 启动管线这一步会触发设备初始化 var profile pipe.Start(cfg); // 取 30 帧前几帧往往还没稳定丢掉 for (int i 0; i 30; i) { using (var frames pipe.WaitForFrames()) { var colorFrame frames.ColorFrame; if (colorFrame null) continue; // 打印帧号和分辨率确认数据在动 Console.WriteLine($Frame {colorFrame.FrameNumber}, ${colorFrame.Width}x{colorFrame.Height}); } } pipe.Stop(); } } }这段代码的逻辑很直白Pipeline负责管理设备Config负责声明你要哪些流WaitForFrames是阻塞式取帧。参数上640x480和30帧是 RealSense 上最稳的组合先不要改。Format.Bgr8是为了后面直接转Bitmap如果你用Format.Rgb8转的时候要交换通道容易出颜色偏蓝的问题。跑通之后你会看到控制台连续打印帧号。如果卡在WaitForFrames不动八成是流配置和设备实际支持的能力不匹配换1280x720或者降到15帧再试。3. 把深度流加进来对齐、同步和内存释放3.1 深度流和彩色流的配置差异彩色流跑通之后加深度流就是多一行EnableStream的事但参数不能照抄。深度流的格式通常是Z16也就是 16 位无符号整数单位是毫米。分辨率上深度流常见的是640x480和848x480帧率跟彩色流保持一致否则后面做对齐会很麻烦。var cfg new Config(); cfg.EnableStream(Stream.Color, 640, 480, Format.Bgr8, 30); cfg.EnableStream(Stream.Depth, 640, 480, Format.Z16, 30);这里有个细节彩色和深度的分辨率最好设成一样。如果彩色用1280x720深度用640x480对齐时 SDK 会做缩放边缘会出现无效区域。我一般会统一成640x480先保证对齐质量再考虑提分辨率。3.2 用 Align 做深度到彩色的对齐RealSense 的深度相机和彩色相机在物理上是分开的两个镜头有视差。你直接拿深度图的像素坐标去彩色图上找对应点会偏。Align就是干这个的把深度图重投影到彩色相机的视角下让两张图的像素一一对应。// 创建对齐对象把深度对齐到彩色 var align new Align(Stream.Color); using (var frames pipe.WaitForFrames()) { // 处理后的帧深度已经对齐到彩色视角 using (var aligned align.Process(frames)) { var colorFrame aligned.ColorFrame; var depthFrame aligned.DepthFrame; if (colorFrame null || depthFrame null) continue; // 取中心点的深度值单位毫米 int cx depthFrame.Width / 2; int cy depthFrame.Height / 2; ushort depth depthFrame.GetDistance(cx, cy); Console.WriteLine($中心深度: {depth} mm); } }Align的构造参数是目标流这里传Stream.Color意思是把深度对齐到彩色。反过来也可以但对大多数应用来说彩色图更直观所以对齐到彩色更常用。GetDistance返回的是ushort单位毫米0 表示无效值通常是超出量程或者被遮挡。3.3 帧对象的释放别让内存泄漏拖垮长时间运行RealSense 的帧对象持有非托管内存using或者手动Dispose必须做。我见过一个 Demo 跑十分钟内存涨到几个 G就是因为WaitForFrames返回的frames没释放。using (var frames pipe.WaitForFrames()) using (var aligned align.Process(frames)) { // 在这里处理帧 // 离开 using 块时aligned 和 frames 都会自动释放 }注意align.Process返回的是新的帧对象它和原始frames是两份内存。两个都要释放。如果你把aligned存到成员变量里跨帧使用那就要自己控制生命周期不能依赖using。提示长时间运行的取流程序建议每处理 100 帧调用一次GC.Collect观察内存曲线如果持续上涨先查帧对象释放再查 Bitmap 转换有没有泄漏。4. 避坑排查RealSense 在 C# 里最容易翻车的 5 个点4.1 现象程序启动报 DllNotFoundException原因通常是librealsense2.dll不在输出目录或者 PATH 里没有 SDK 的bin目录。C# 的 NuGet 包有时候只带了托管层非托管 DLL 要自己拷。解决把 SDK 安装目录下的realsense2.dll和相关依赖拷到bin\Debug\net6.0-windows下或者把 SDK 的bin目录加到系统 PATH 后重启 Visual Studio。用dumpbin /dependents可以查 DLL 的依赖链缺哪个补哪个。4.2 现象彩色流能出深度流一直是 0 或者全黑原因一般是深度流没使能或者激光发射器被关掉了。有些场景下 SDK 默认关闭激光深度图就是全 0。解决在Config里确认EnableStream(Stream.Depth, ...)已经调用。如果还是全 0用realsense-viewer看深度图是否正常。查看器正常而代码不正常检查是不是在Pipeline启动后又改了Sensor的选项。激光功率可以通过sensor.Options里的LaserPower调整但不要一上来就拉满先确认默认值。4.3 现象帧率标称 30实际只有 5 到 10原因多半是 USB 带宽不够或者同时开了太多流。彩色加深度加红外全开USB 3.0 也可能吃紧。解决先降到640x48015确认帧率能上去再逐步加。如果必须高帧率关掉不用的流比如红外。另外WaitForFrames是阻塞的如果你在处理帧的时候做了耗时操作比如存盘或者跑推理帧率也会被拖下来。常见做法是取流和处理分线程用队列缓冲。4.4 现象对齐后的深度图边缘有大片黑色原因是对齐过程中深度相机的视场和彩色相机不完全重合边缘没有对应像素SDK 填 0。解决这是正常现象不是 bug。如果应用不能接受可以在后处理里裁掉边缘或者用HoleFilling滤波器补洞。HoleFilling在librealsense里有现成的后处理块加到管线里就行但会增加延迟。4.5 现象长时间运行后程序卡死或者崩溃原因通常是帧对象没释放非托管内存耗尽或者多线程访问了同一个Pipeline。解决确保每个frames和aligned都在using里。如果用了多线程Pipeline不是线程安全的取流只能在一个线程里做处理可以另开线程。崩溃时看 Windows 事件查看器里的异常模块如果是realsense2.dll基本就是内存或者生命周期问题。5. 从 Demo 到可用保存帧、转 Bitmap 和三个调参习惯5.1 把彩色帧转成 Bitmap 并保存Demo 跑通之后下一步通常是把帧存下来做数据集或者做后续处理。RealSense 的彩色帧是连续内存转Bitmap可以用Bitmap的构造函数直接映射。using System.Drawing; using System.Drawing.Imaging; using System.Runtime.InteropServices; Bitmap ToBitmap(VideoFrame frame) { int w frame.Width; int h frame.Height; // Bgr8 格式每像素 3 字节 var bmp new Bitmap(w, h, PixelFormat.Format24bppRgb); var data bmp.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb); // 把帧数据拷到 Bitmap 的扫描线 for (int y 0; y h; y) { IntPtr src IntPtr.Add(frame.Data, y * frame.Stride); IntPtr dst IntPtr.Add(data.Scan0, y * data.Stride); // 每行拷贝 w*3 字节 Buffer.MemoryCopy(src.ToPointer(), dst.ToPointer(), data.Stride, w * 3); } bmp.UnlockBits(data); return bmp; }这里的关键是frame.Stride和data.Stride可能不一样所以不能一次性整块拷贝要按行拷。Buffer.MemoryCopy的第三个参数是目标缓冲区大小第四个是实际拷贝字节数。拷完记得UnlockBits否则 Bitmap 会一直锁着。保存的时候用bmp.Save(frame.png, ImageFormat.Png)PNG 无损适合做数据集。如果要批量存注意磁盘 IO 会成为瓶颈建议单独开一个写盘线程。5.2 深度图可视化把 Z16 映射成灰度图深度图直接存 Z16 的原始数据很多看图软件打不开。常见做法是映射成 8 位灰度图近处亮、远处暗。Bitmap DepthToGray(DepthFrame frame, ushort min 200, ushort max 2000) { int w frame.Width; int h frame.Height; var bmp new Bitmap(w, h, PixelFormat.Format8bppIndexed); // 设置灰度调色板 var pal bmp.Palette; for (int i 0; i 256; i) pal.Entries[i] Color.FromArgb(i, i, i); bmp.Palette pal; var data bmp.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); for (int y 0; y h; y) { for (int x 0; x w; x) { ushort d frame.GetDistance(x, y); // 超出范围或者无效值置 0 byte v (d min || d max) ? (byte)0 : (byte)(255 - (d - min) * 255 / (max - min)); Marshal.WriteByte(data.Scan0, y * data.Stride x, v); } } bmp.UnlockBits(data); return bmp; }min和max要根据你的实际场景调。室内近距离用200到2000毫米室外或者远距离要放大。映射公式里用255 -是为了让近处更亮符合直觉。如果场景里无效值很多可以先跑一遍HoleFilling再映射。5.3 三个我每次都会调的参数第一个是LaserPower。默认值在室内够用但如果你测的是低反射率物体比如黑色橡胶深度图会缺一大片。适当提高激光功率能改善但不要超过设备上限否则近距离会过曝。第二个是Exposure。彩色相机的自动曝光在光线变化快的场景下会来回跳导致帧间亮度不一致。做数据集的时候我一般关掉自动曝光手动设一个值保证每帧一致。第三个是Align的目标流。默认对齐到彩色但如果你做的是纯深度测量对齐到深度反而更省算力。这个没有绝对对错看你的下游任务。5.4 一个我踩过的坑别在回调里做重活RealSense 的 C# 封装支持回调方式取帧但回调是在 SDK 的内部线程上执行的。我早期图省事在回调里直接做Bitmap转换和存盘结果帧率掉到个位数还偶尔死锁。后来改成回调里只把帧数据拷到队列另开线程处理帧率立刻回到 30。这个习惯我保持到现在取流线程只做取流和浅拷贝所有耗时操作放到消费线程。队列要有上限满了就丢旧帧保证实时性。如果你也在做 RealSense 的 C# Demo建议从第一天就这么分后面加推理或者网络传输都不用重构。希望帮到你。本文还有配套的精品资源点击获取