Halcon 在 C# 里的数据类型转换
2023 年 8 月 1 日 · 2 分钟读完
目录
把 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 的缩放与平移。
原文首发于 blog.csdn.net