← 全部文章 机器视觉

相机取图后转成视觉库的图像

2023 年 8 月 2 日 · 2 分钟读完

目录
  1. 大华相机
  2. 不用复制的情况
  3. 延伸

用相机厂家自己的 SDK 取图时(而不是用视觉库自带的采集接口),回调里拿到的只是一块像素数据,Halcon、VisionPro 这些视觉库不能直接用,需要转换成它们的图像类型。

不管哪家相机,2D 图像的关键信息都是这几个:

  • 像素数据:byte[] 或 IntPtr;
  • 宽、高;
  • 像素格式:Mono8(8 位灰度)、BGR8、BayerRG8 等;
  • 行填充:每行末尾有没有补齐的字节(有的 SDK 叫 PaddingX)。

大华相机

大华 SDK 回调里的图像数据:

public interface IGrabbedRawData
{
    byte[] Image { get; }        // 像素数据
    IntPtr Raw { get; }
    int ImageSize { get; }
    int Width { get; }           // 宽
    int Height { get; }          // 高
    long BlockID { get; }
    long TimeStamp { get; }
    uint ImagePaddingX { get; }  // 行填充
    uint ImagePaddingY { get; }
    GvspPixelFormatType PixelFmt { get; } // 像素格式
    IChunkData ChunkData { get; }

    IGrabbedRawData Clone();
    void Show(IntPtr pWnd);
    Bitmap ToBitmap(bool color);
}

我把需要的信息放进自己的类里,和具体相机解耦,然后再转 HImage:

public enum PixelFormatType { Mono8, BGR8 }

public class ImageData
{
    public int Width { get; set; }
    public int Height { get; set; }
    public byte[] Raw { get; set; }
    public PixelFormatType PixelFmt { get; set; }
    public long TimeStamp { get; set; }

    public unsafe HImage ToHImage()
    {
        var image = new HImage();
        fixed (byte* p = Raw)
        {
            switch (PixelFmt)
            {
                case PixelFormatType.Mono8:
                    image.GenImage1("byte", Width, Height, new IntPtr(p));
                    break;
                case PixelFormatType.BGR8:
                    image.GenImageInterleaved(new IntPtr(p), "bgr", Width, Height, 0, "byte",
                                              Width, Height, 0, 0, -1, 0);
                    break;
                default:
                    throw new NotSupportedException($"未支持的像素格式:{PixelFmt}");
            }
        }
        return image;
    }
}

原代码有个隐患:原文在 fixed 块里取到指针,出了 fixed 块之后才调用 GenImage1。fixed 只在块内固定数组,出了块之后 GC 随时可能把数组挪走,指针就指向了错误的内存。平时很难复现,偶尔会出现花图甚至崩溃。用指针的代码必须放在 fixed 块里面,像上面这样。

GenImage1 和 GenImageInterleaved 会把数据复制一份,所以返回后 Raw 被回收也没关系。

原文只写了大华 Mono8,彩色分支是补充的;GenImageInterleaved 各参数的含义建议对照 Halcon 帮助里的 gen_image_interleaved 再确认一遍。

不用复制的情况

如果 SDK 直接给了 IntPtr(非托管内存),就不需要 fixed:

image.GenImage1("byte", width, height, frame.Raw);

但要注意 SDK 的缓冲区什么时候被回收:很多 SDK 的回调返回后,这块内存就会被下一帧覆盖,所以必须在回调里完成转换(GenImage1 是复制,没问题),而不能把指针存起来以后再用。

延伸

  • 行填充:有的相机为了对齐,每行末尾会补几个字节,这时每行实际字节数 ≠ 宽度。直接 GenImage1 图像会变斜。要么在相机参数里关掉填充,要么先逐行拷成紧凑的数组,做法和 Bitmap 的 Stride 一样,见 Halcon 在 C# 里的数据类型转换。
  • Bayer 格式:彩色相机输出 BayerRG8 之类的原始格式时,每个像素只有一种颜色,要先用 GenImage1 生成单通道图,再用 Halcon 的 cfa_to_rgb 转成彩色。也可以在相机参数里直接设成 BGR8 输出,让相机去插值,代价是数据量变成 3 倍。
  • 转 VisionPro:灰度图用 CogImage8Grey,可以从 Bitmap 构造(new CogImage8Grey(bitmap)),也可以用 CogImage8Root 的 Initialize(width, height, pixelMemory, stride, disposable) 包装已有的内存,免去一次复制。
  • 海康相机:MVS SDK 回调里给的是 IntPtr pData 和 MV_FRAME_OUT_INFO_EX(里面有 nWidth、nHeight、enPixelType),转换方式和上面一样。
  • 取图线程:SDK 的回调在相机的线程里执行,回调里只做转换和入队,处理放到别的线程,否则处理慢了会丢帧。显示时更新界面要 Invoke。

# 相机 # Halcon # C#

原文首发于 blog.csdn.net