← 全部文章 机器视觉

Halcon 在 C# 里的数据类型转换

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

目录
  1. 两套写法
  2. HObject 转其他图像类型
  3. HObject → HImage
  4. HObject → HRegion、HXLDCont
  5. HTuple 和普通类型
  6. HTuple → int、double、string
  7. int、double、string → HTuple
  8. HImage 和 Bitmap
  9. HImage → Bitmap(灰度)
  10. Bitmap → HImage(灰度)
  11. 延伸

把 HDevelop 导出的代码封装到 C# 项目里时,导出的代码全是 HObject 和 HTuple,外部接口却想用 HImage、HRegion、int、double、Bitmap。记录一下常用的转换。

两套写法

HALCON 在 C# 里有两种风格:

  • 过程式:HOperatorSet.Threshold(image, out HObject region, 128, 255),所有图像都是 HObject,所有控制参数都是 HTuple,就是 HDevelop 导出的样子;
  • 面向对象:HRegion region = image.Threshold(128.0, 255.0),图像是 HImage,区域是 HRegion,轮廓是 HXLDCont,算子变成对象的方法,返回值有明确类型。

HImage、HRegion 都继承自 HObject,所以它们可以直接传给 HOperatorSet,反过来要转换。

HObject 转其他图像类型

HObject → HImage

最简单的是用构造函数,不复制像素,只是包一层:

HImage image = new HImage(hObject);

原文的写法是取出像素指针再生成一张新图,会复制一份像素,适合需要和原对象脱钩的场合:

// 单通道(灰度)图
public static HImage HObject2HImage_Gray(HObject hObject)
{
    HOperatorSet.GetImagePointer1(hObject, out HTuple pointer, out HTuple type,
                                  out HTuple width, out HTuple height);
    var image = new HImage();
    image.GenImage1(type.S, width.I, height.I, pointer.IP); // GenImage1 会复制数据
    return image;
}


// 三通道(彩色)图
public static HImage HObject2HImage_Rgb(HObject hObject)
{
    HOperatorSet.GetImagePointer3(hObject, out HTuple r, out HTuple g, out HTuple b,
                                  out HTuple type, out HTuple width, out HTuple height);
    var image = new HImage();
    image.GenImage3(type.S, width.I, height.I, r.IP, g.IP, b.IP);
    return image;
}

原文的灰度版本在函数最后顺手 hObject.Dispose() 了。这样调用方传进来的对象在调用后就不能用了,容易踩坑。谁创建谁释放,转换函数里不要释放参数。

不确定是几通道时,先用 HOperatorSet.CountChannels(hObject, out HTuple channels) 判断。

HObject → HRegion、HXLDCont

HRegion region = new HRegion(hObject);
HXLDCont contour = new HXLDCont(hObject);

传进去的对象必须真的是区域或轮廓,类型不对会抛 HOperatorException。

HTuple 和普通类型

HTuple → int、double、string

HTuple t = ...;

int i       = t.I;      // 第一个元素,当作 int
double d    = t.D;      // 当作 double
string s    = t.S;      // 当作 string
long l      = t.L;      // 当作 long
object o    = t.O;      // 不关心类型时
int[] ia    = t.IArr;   // 整个元组
double[] da = t.DArr;
string[] sa = t.SArr;
long[] la   = t.LArr;
object[] oa = t.OArr;
int length  = t.Length;
int third   = t[2].I;   // 取某一项

类型要对上:元组里存的是 double(比如 area_center 返回的行列坐标),用 .I 取会抛异常。不确定时看 t.Type(HTupleType.INTEGER、DOUBLE、STRING、MIXED),或者先转换:t.TupleInt().I、t.TupleReal().D,也可以统一用 .D 再自己取整。

int、double、string → HTuple

HTuple 有从基本类型和数组的隐式转换,直接赋值就行:

HTuple a = 5;
HTuple b = 3.14;
HTuple c = "hello";
HTuple d = new[] { 1, 2, 3 };
HTuple e = new HTuple(1, 2.5, "mixed"); // 混合类型
HOperatorSet.Threshold(image, out HObject region, 128, 255); // 参数也会自动转换

HImage 和 Bitmap

界面上用 PictureBox 显示、或者把图交给别的库处理时,需要转成 Bitmap。

HImage → Bitmap(灰度)

public static Bitmap HImage2Bitmap_Gray(HImage image)
{
    IntPtr src = image.GetImagePointer1(out string type, out int width, out int height);
    var bmp = new Bitmap(width, height, PixelFormat.Format8bppIndexed);

    // 8 位图要设置灰度调色板,否则颜色是乱的
    ColorPalette palette = bmp.Palette;
    for (int i = 0; i < 256; i++) palette.Entries[i] = Color.FromArgb(i, i, i);
    bmp.Palette = palette;

    BitmapData data = bmp.LockBits(new Rectangle(0, 0, width, height),
                                   ImageLockMode.WriteOnly, bmp.PixelFormat);
    var row = new byte[width];
    for (int y = 0; y < height; y++)
    {
        // Bitmap 每行按 4 字节对齐(Stride),不能整块复制
        Marshal.Copy(src + y * width, row, 0, width);
        Marshal.Copy(row, 0, data.Scan0 + y * data.Stride, width);
    }
    bmp.UnlockBits(data);
    return bmp;
}

Stride 是最容易错的地方:Halcon 的像素是一行紧挨一行存的,Bitmap 每行的字节数(Stride)要补齐到 4 的倍数。宽度是 4 的倍数时两者一样,整块复制看不出问题;宽度是 1001 这种时,整块复制出来的图就是斜的。所以要逐行复制。

Bitmap → HImage(灰度)

public static HImage Bitmap2HImage_Gray(Bitmap bmp)
{
    BitmapData data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
                                   ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed);
    try
    {
        var image = new HImage();
        if (data.Stride == bmp.Width)
        {
            image.GenImage1("byte", bmp.Width, bmp.Height, data.Scan0);
        }
        else
        {
            // 行有补齐时,先拷成紧凑的数组
            var buffer = new byte[bmp.Width * bmp.Height];
            for (int y = 0; y < bmp.Height; y++)
                Marshal.Copy(data.Scan0 + y * data.Stride, buffer, y * bmp.Width, bmp.Width);
            GCHandle h = GCHandle.Alloc(buffer, GCHandleType.Pinned);
            try { image.GenImage1("byte", bmp.Width, bmp.Height, h.AddrOfPinnedObject()); }
            finally { h.Free(); }
        }
        return image;
    }
    finally
    {
        bmp.UnlockBits(data);
    }
}

彩色图是 24 位 BGR 交错排列,可以用 HImage.GenImageInterleaved,颜色格式填 "bgr",它带有行对齐参数,能直接处理 Stride,参数的具体含义查帮助里的 gen_image_interleaved。

从相机 SDK 拿到的原始缓冲区转 HImage,见 相机 Buffer 转图像。

延伸

  • 记得释放:HObject、HImage、HRegion 背后是 Halcon 的非托管内存,GC 不知道它们有多大,不会着急回收。循环里处理图像时,用完要 Dispose(),或者用 using;给 out 参数复用的 HObject,赋新值前也要先 Dispose 旧的,否则内存一直涨。原理见 .NET 的垃圾回收。
  • XL 版本:用 Halcon XL 时,像素指针和尺寸的类型不同,引用的也是 halcondotnetxl.dll,见 Halcon 入门。
  • 显示:转换好的图像在 HWindowControl 里显示和缩放,见 HWindow 的缩放与平移。

# Halcon # C#

原文首发于 blog.csdn.net