C#高位深相机图像保存:12位16位图像保存方案,OpenCvSharp和Halcon实现

C#高位深相机图像保存:12位16位图像保存方案,OpenCvSharp和Halcon实现

8位存得好好的,12位16位Bitmap直接废了:高位深图像保存方案

前几篇聊图像存储,评论区有人说:"相机设置里有个Mono12,拍出来的图用Bitmap.Save存了,打开一看全黑,到底怎么回事?"

存成8位没问题,换12位就全黑了。问题出在System.Drawing.Bitmap压根不认识16位灰度。

后来换成OpenCvSharp和Halcon才搞定。

一、为什么需要高位深图像保存?

先搞清楚一个问题:8位图256个灰度级,不够用吗?

精密测量场景,8位确实不够。

一个锂电池极片表面的细微划痕,在16位图里灰度差异可能有30-50个灰度级,肉眼和算法都能轻松识别。但在8位图里,同样一条划痕,因为位深被压缩,灰度差异可能只剩下2-3个灰度级,几乎淹没在背景噪声里。

这不是夸张。16位图有65536个灰度级,8位图只有256个。把16位压成8位,相当于每256个灰度级合并成1个。那些微弱但关键的细节,就在这个合并过程中被抹掉了。

另一个典型场景是强光+暗区同时存在的图像:高光部分需要短曝光才能看清楚,暗区需要长曝光才能看清细节。折中曝光,两头都顾不上。但高位深图可以用一条"曝光时间"把整个动态范围吃下来,高光不溢出、暗区有细节,后期通过调整显示映射来查看不同区域。

高位深图像的三个核心价值:

① 保留更多细节,尤其是暗部。 12位图比8位图多16倍信息量,16位图比8位图多256倍信息量。相机感光能力越来越强,硬件能拍到的细节,存不下来就是浪费。

② 后期处理有余量。 8位图做一次亮度和对比度拉伸,直方图就出现断档(空洞)。16位图做完同样的处理,直方图依然连续。裁剪、旋转、缩放、滤波,8位图每处理一次就损失一层精度,16位图经得起折腾。

③ 跨平台兼容性。 医疗影像(DICOM)、遥感卫星、科研显微镜,这些领域的标准格式全是16位起步。工业检测的数据如果将来要对接这些系统,提前用高位深保存就省了后面重新采集的麻烦。

什么时候必须用高位深?

场景 是否需要高位深 原因
普通外观检测 不一定 肉眼判断,8位够用
精密尺寸测量 必须 微米级测量需要精细灰度梯度
深度学习训练 推荐 模型泛化需要更多信息,但很多模型训练时还是会归一化到0-1,信息保留在归一化环节
高动态范围场景(HDR合成) 必须 多张8位合成HDR,不如直接拍高位深
激光线扫描/3D点云 必须 高度信息本身就对应灰度值,精度要求极高
缺陷追溯/仲裁 推荐 高位深存档,随时调出复核,不受压缩损失影响

二、Bitmap为什么不行

日常用的8位图,每个像素0-255,一共256个灰度级。

12位图是0-4095,16位图是0-65535。信息量差了十几倍到几十倍。

Bitmap的问题在于:它只支持8位灰度(Format8bppIndexed)和24/32位彩色。你把12位数据塞进去,Bitmap会强行截断成8位------高4位直接扔掉,像素值从0-4095被硬压成0-255。有用的信息全丢了,这就是"存出来全黑"的原因。

三、OpenCvSharp方案:直接认16位

OpenCV的Mat原生支持CV_16UC1(16位无符号单通道)。从相机拿到数据后直接创建Mat,保存时指定PNG或TIFF就行。

csharp 复制代码
using OpenCvSharp;

// 假设相机回调给了IntPtr pData,指向12位或16位原始数据
void Save16BitImage(IntPtr pData, int width, int height, string filePath)
{
    // 直接包装成16位Mat,零拷贝
    using (Mat mat = new Mat(height, width, MatType.CV_16UC1, pData))
    {
        // 保存为PNG(无损,支持16位)
        Cv2.ImWrite(filePath + ".png", mat);
        
        // 或者保存为TIFF(工业标准)
        Cv2.ImWrite(filePath + ".tiff", mat);
    }
}

OpenCV的ImWrite会根据扩展名自动选择编码器。PNG和TIFF都支持16位无损存储。

如果相机给的是byte[]而不是IntPtr

csharp 复制代码
byte[] imageData = GetImageFromCamera();
// 16位图每两个byte组成一个像素(小端序)
ushort[] pixelData = new ushort[imageData.Length / 2];
Buffer.BlockCopy(imageData, 0, pixelData, 0, imageData.Length);

using (Mat mat = new Mat(height, width, MatType.CV_16UC1))
{
    // 逐行写入
    System.Runtime.InteropServices.Marshal.Copy(pixelData, 0, mat.Data, pixelData.Length);
    Cv2.ImWrite("output.tiff", mat);
}

注意Buffer.BlockCopy是按字节拷贝的,不需要担心大小端问题------它忠实地把每两个byte拼成一个ushort。

如果相机出的是12位(Mono12) :大部分相机SDK输出的12位数据是"左对齐"或"右对齐"存储在16位空间里的。直接按16位保存,高4位或者低4位是无效数据。建议先在MVS或pylon Viewer里确认一下相机的实际存储方式,再决定要不要做移位处理。

四、Halcon方案:WriteImage一把梭

Halcon的HImage原生支持各种位深,保存用WriteImage算子,格式选tiff就行。

csharp 复制代码
using HalconDotNet;

void Save16BitWithHalcon(IntPtr pData, int width, int height, string filePath)
{
    // 从指针创建HImage(16位灰度)
    HImage image = new HImage("uint2", width, height, pData);
    
    // 保存为TIFF(Halcon支持所有像素类型)
    HOperatorSet.WriteImage(image, "tiff", 0, filePath + ".tif");
    
    // 也可以存PNG
    // HOperatorSet.WriteImage(image, "png", 0, filePath + ".png");
    
    image.Dispose();
}

Halcon的WriteImage算子对TIFF格式支持所有HALCON像素类型,包括uint2(16位无符号)。BMP和JPEG只支持8位byte类型,16位图用这两个格式保存会报错或截断。

从相机SDK的HObject直接保存

如果项目里已经用Halcon的采集接口拿到了HObject

csharp 复制代码
HObject ho_Image;
// ... 从相机采集到ho_Image

// 直接保存,Halcon自动识别位深
HOperatorSet.WriteImage(ho_Image, "tiff", 0, "result.tif");

WriteImage的第二个参数"tiff"也可以带压缩选项,比如"tiff lzw"用LZW无损压缩减小文件大小。

五、两种方案对比

对比项 OpenCvSharp Halcon
16位图支持 ✅ CV_16UC1 ✅ uint2
12位处理 需手动移位 自动识别
代码量 中等
依赖 OpenCV库 Halcon运行时
成本 免费 商业授权
TIFF支持 ✅(更完善)
PNG 16位

OpenCvSharp适合已经用了OpenCV、不想额外装Halcon 的项目。Halcon适合已经在用Halcon做算法、顺便存图的项目。

如果项目里既没有OpenCV也没有Halcon,还有一个纯C#的选择:SixLabors.ImageSharp 。它原生支持Gray16类型,纯托管代码,跨平台:

csharp 复制代码
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;

// pixelData是ushort[]
using (var image = Image.LoadPixelData<Gray16>(pixelData, width, height))
{
    image.SaveAsPng("output.png");
}

六、格式怎么选

TIFF:工业检测的标准格式,完美支持8/10/12/16位,可选LZW无损压缩。Halcon和OpenCV都支持得很好。

PNG:也支持16位无损,文件比TIFF小,兼容性更好。但工业软件对PNG的16位支持不如TIFF普遍。

RAW:纯原始数据,不带任何文件头,文件最小,但需要额外保存宽高和位深信息。

建议:存原始数据用TIFF,给人看用PNG,算法训练用RAW+元数据

七、几个容易踩的坑

坑一:相机出的是12位,直接当16位存

12位数据通常左对齐或右对齐存放在16位空间里。直接存的话,有效数据只占一部分,另外4位是无效的。建议先在MVS/pylon Viewer里确认存储方式,必要时做移位归一化。

坑二:存成8位图丢了高位信息

Bitmap.Save默认会把16位截断成8位。用OpenCV或Halcon存,别用Bitmap。

坑三:TIFF存了16位,但看图软件打不开

有些看图软件只支持8位TIFF。用ImageJ或专用工业看图软件打开,或者同时生成一份8位预览图。

八、总结

12位16位图像保存,记住三件事:

  1. Bitmap.Save不能用,它会截断高位数据
  2. 用OpenCvSharp的CV_16UC1 直接包装指针保存,或者Halcon的WriteImage一把梭
  3. 格式选TIFF或PNG,别碰JPEG和BMP

把上面任意一段代码塞进你的采集回调里,12位16位的图就能原样保存下来,不丢一比特。

小提示:不确定相机出的12位数据是左对齐还是右对齐,先在MVS或pylon Viewer里存一张16位TIFF,用ImageJ打开看看直方图------如果直方图集中在低端或者高端,说明需要移位。如果均匀分布在0-4095之间,直接存就行。

相关推荐
zx_741484811 小时前
【计算机视觉入门】两大 OpenCV 案例:答题卡判分与图像全景拼接
人工智能·opencv·计算机视觉
樊小肆1 小时前
DeepSeeker-Code源码导读10-代码智能tsHost
人工智能·agent
小小龙学IT1 小时前
Qt Graphics View Framework(图形视图框架)深度解析:从 Scene/View/Item 到工业级 2D 场景
开发语言·数据库·qt
武子康1 小时前
262K 跑过,128K 却被脚本拦下:A6000 跑分里的三种“失败”不能混为一谈
人工智能·llm·agent
互联网志1 小时前
机器人物理AI操作系统问世 多形态设备协同训练重构智能作业模式
人工智能·重构·机器人
Zguigo1 小时前
【DL】神经网络学习目标|prediction|Loss|Gradient
人工智能·神经网络·学习
长江后浪博客1 小时前
RIP 颜色计算原理:RGB→CMYK 转换与陶瓷喷墨色彩管理
人工智能·rip·颜色管理·陶瓷喷墨
2601_966949651 小时前
Python 量化数据质量校验:如何确保股票历史日线数据不存在缺失交易日?
开发语言·人工智能·爬虫·python·量化策略·量化·quantdash
雪隐1 小时前
WPF + MVVM 实战系列03-MVVM 的边界之争,终于有人画了条“差不多”的线
c#