using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Windows.Media.Imaging;
using HalconDotNet;
using TeamAAS.Camera.Images;
namespace Plugins.Halcon
{
///
/// HALCON SDK 适配层(唯一允许出现 HalconDotNet 调用的地方)。
///
/// 设计目的:把与 HALCON 强耦合的调用(图像互转、算子)全部收敛到这一层,插件/模型/视图只依赖本类的
/// SDK 无关签名(object / GenImage / 基元类型),从而:
/// - 更换/升级 HALCON 版本时只改这一处;
/// - 插件类型本身不直接暴露 HObject,保证 PluginLoader 反射扫描(GetTypes)时不会强制加载 HALCON 原生库;
/// - 与 Plugins.Vm 的 VmRuntime 隔离思路一致(区别:本机已装 HALCON 23.11,此处为真实实现而非骨架)。
///
/// 环境:工程内 HalconSdk\ 放 halcondotnet.dll(托管,随生成复制到 EXE\win-x64\Plugins\);
/// 运行期还需 HALCON 原生运行库(halcon.dll 等)+授权——由本机安装 HALCON 提供(HALCONROOT/PATH 已配)。
///
public static class HalconRuntime
{
#region 图像互转:外部图像 → HObject
///
/// 把上游传入的图像对象统一转换为 HALCON 的 图像。
/// 支持:已是 HObject(原样返回)、(主流程标准图像)、
/// 、。无法识别时抛异常(由插件 try/catch 兜住)。
///
public static HObject ToHObject(object image)
{
if (image == null)
throw new ArgumentNullException(nameof(image), "输入图像为 null");
if (image is HObject ho)
{
if (!ho.IsInitialized())
throw new ArgumentException("输入的 HObject 未初始化");
return ho;
}
GenImage gi = image as GenImage;
if (gi == null)
{
Bitmap bmp = image as Bitmap;
if (bmp == null && image is BitmapSource bms)
bmp = BitmapSourceToBitmap(bms);
if (bmp != null)
gi = GenImage.FromBitmap(bmp);
}
if (gi == null)
throw new ArgumentException($"无法识别的图像类型:{image.GetType().Name}(支持 GenImage/Bitmap/BitmapSource/HObject)");
return GenImageToHObject(gi);
}
///
/// GenImage(原始像素)→ HObject。灰度用 gen_image1,彩色拆三通道用 gen_image3;
/// 生成后立即 copy_image 让 HALCON 拥有像素副本,再释放固定内存(gen_image1/3 只是包裹外部指针,不拷贝)。
///
private static HObject GenImageToHObject(GenImage gi)
{
int w = gi.Width;
int h = gi.Height;
int stride = gi.ActualStride;
byte[] src = gi.Data;
switch (gi.Format)
{
case PixelType.Grey8:
return GenMono(src, stride, w, h, 1, "byte");
case PixelType.Grey16:
case PixelType.Depth16:
return GenMono(src, stride, w, h, 2, "uint2");
case PixelType.RGB24:
return GenColor(src, stride, w, h, 3, 0, 1, 2); // R,G,B
case PixelType.BGR24:
return GenColor(src, stride, w, h, 3, 2, 1, 0); // B,G,R → 传 R,G,B
case PixelType.BGRA32:
return GenColor(src, stride, w, h, 4, 2, 1, 0); // B,G,R,A → 传 R,G,B
case PixelType.Depth16Conf8:
case PixelType.Depth16Intensity8:
default:
// 其它复合格式:走标准 Bitmap 路径(GenImage.ToBitmap 会做伪彩/格式归一)
using (var bmp = gi.ToBitmap())
{
var gi2 = GenImage.FromBitmap(bmp);
if (gi2.Format == PixelType.Grey8)
return GenMono(gi2.Data, gi2.ActualStride, gi2.Width, gi2.Height, 1, "byte");
return GenColor(gi2.Data, gi2.ActualStride, gi2.Width, gi2.Height, 3, 0, 1, 2);
}
}
}
/// 单通道图像生成(bpp=1→byte / bpp=2→uint2)。
private static HObject GenMono(byte[] src, int stride, int w, int h, int bpp, string halconType)
{
byte[] tight = TightRows(src, stride, w, h, bpp);
var gc = GCHandle.Alloc(tight, GCHandleType.Pinned);
try
{
HOperatorSet.GenImage1(out HObject tmp, halconType, w, h, new HTuple(gc.AddrOfPinnedObject()));
HOperatorSet.CopyImage(tmp, out HObject copy);
tmp.Dispose();
return copy;
}
finally { gc.Free(); }
}
/// 三通道彩色图像生成:按通道下标拆分交织数据为 R/G/B 三个紧凑缓冲,再 gen_image3。
private static HObject GenColor(byte[] src, int stride, int w, int h, int bpp, int rIdx, int gIdx, int bIdx)
{
int plane = w * h;
var r = new byte[plane];
var g = new byte[plane];
var b = new byte[plane];
for (int y = 0; y < h; y++)
{
int rowStart = y * stride;
int oBase = y * w;
for (int x = 0; x < w; x++)
{
int p = rowStart + x * bpp;
int o = oBase + x;
r[o] = src[p + rIdx];
g[o] = src[p + gIdx];
b[o] = src[p + bIdx];
}
}
var gcr = GCHandle.Alloc(r, GCHandleType.Pinned);
var gcg = GCHandle.Alloc(g, GCHandleType.Pinned);
var gcb = GCHandle.Alloc(b, GCHandleType.Pinned);
try
{
HOperatorSet.GenImage3(out HObject tmp, "byte", w, h,
new HTuple(gcr.AddrOfPinnedObject()),
new HTuple(gcg.AddrOfPinnedObject()),
new HTuple(gcb.AddrOfPinnedObject()));
HOperatorSet.CopyImage(tmp, out HObject copy);
tmp.Dispose();
return copy;
}
finally { gcr.Free(); gcg.Free(); gcb.Free(); }
}
/// 按行拷贝出去掉行填充(stride)的紧凑像素缓冲;stride 已等于 width*bpp 时直接返回原数组。
private static byte[] TightRows(byte[] src, int stride, int w, int h, int bpp)
{
int rowBytes = w * bpp;
if (stride == rowBytes && src.Length >= rowBytes * h)
return src;
var dst = new byte[rowBytes * h];
for (int y = 0; y < h; y++)
{
int from = y * stride;
int to = y * rowBytes;
int n = Math.Min(rowBytes, src.Length - from);
if (n <= 0) break;
Buffer.BlockCopy(src, from, dst, to, n);
}
return dst;
}
/// BitmapSource → Bitmap(WPF 图像兜底转换;主流程一般已是 GenImage,很少走到这里)。
private static Bitmap BitmapSourceToBitmap(BitmapSource source)
{
var enc = new PngBitmapEncoder();
enc.Frames.Add(BitmapFrame.Create(source));
using (var ms = new System.IO.MemoryStream())
{
enc.Save(ms);
ms.Position = 0;
return new Bitmap(ms);
}
}
#endregion
#region 图像互转:HObject → GenImage(把处理结果传回主流程)
///
/// HObject 图像 → (灰度→Grey8/Grey16;彩色→RGB24)。
/// 传入的不是图像或无法解析时返回 null。
///
public static GenImage ToGenImage(object hobj)
{
var img = hobj as HObject;
if (img == null || !img.IsInitialized()) return null;
try
{
HOperatorSet.GetImageSize(img, out HTuple wT, out HTuple hT);
int w = wT.I;
int h = hT.I;
HOperatorSet.CountChannels(img, out HTuple chT);
int channels = chT.I;
if (channels <= 1)
{
HOperatorSet.GetImagePointer1(img, out HTuple ptr, out HTuple type, out HTuple pw, out HTuple ph);
string t = type.S;
int bpp = (t == "uint2" || t == "int2" || t == "int4") ? 2 : 1;
var data = new byte[w * h * bpp];
Marshal.Copy(ptr.IP, data, 0, data.Length);
var pf = bpp == 1 ? PixelType.Grey8 : PixelType.Grey16;
return new GenImage(w, h, pf, data);
}
else
{
HOperatorSet.GetImagePointer3(img, out HTuple pr, out HTuple pg, out HTuple pb,
out HTuple type3, out HTuple pw3, out HTuple ph3);
int plane = w * h;
var r = new byte[plane];
var g = new byte[plane];
var b = new byte[plane];
Marshal.Copy(pr.IP, r, 0, plane);
Marshal.Copy(pg.IP, g, 0, plane);
Marshal.Copy(pb.IP, b, 0, plane);
var data = new byte[plane * 3];
for (int i = 0; i < plane; i++)
{
data[i * 3] = r[i];
data[i * 3 + 1] = g[i];
data[i * 3 + 2] = b[i];
}
return new GenImage(w, h, PixelType.RGB24, data);
}
}
catch
{
return null;
}
}
#endregion
#region 算子封装:图像处理
/// 阈值分割:灰度 ∈ [minGray,maxGray] 的像素成区域。返回单个(可能含多连通块的)区域。
public static HObject Threshold(HObject image, double minGray, double maxGray)
{
HOperatorSet.Threshold(image, out HObject region, minGray, maxGray);
return region;
}
/// 连通域拆分:把一个区域拆成互不相连的多个独立区域。
public static HObject Connection(HObject region)
{
HOperatorSet.Connection(region, out HObject connected);
return connected;
}
/// 对象数量(区域集中的区域个数 / 图像张数)。
public static int CountObj(HObject obj)
{
if (obj == null || !obj.IsInitialized()) return 0;
HOperatorSet.CountObj(obj, out HTuple number);
return number.I;
}
/// 图像尺寸。
public static void GetImageSize(HObject image, out int width, out int height)
{
HOperatorSet.GetImageSize(image, out HTuple w, out HTuple h);
width = w.I;
height = h.I;
}
/// 形态学-膨胀(圆形结构元)。
public static HObject DilationCircle(HObject region, double radius)
{
HOperatorSet.DilationCircle(region, out HObject result, radius);
return result;
}
/// 形态学-腐蚀(圆形结构元)。
public static HObject ErosionCircle(HObject region, double radius)
{
HOperatorSet.ErosionCircle(region, out HObject result, radius);
return result;
}
/// 形态学-开运算(先腐蚀后膨胀,去小噪点)。
public static HObject OpeningCircle(HObject region, double radius)
{
HOperatorSet.OpeningCircle(region, out HObject result, radius);
return result;
}
/// 形态学-闭运算(先膨胀后腐蚀,填小孔洞)。
public static HObject ClosingCircle(HObject region, double radius)
{
HOperatorSet.ClosingCircle(region, out HObject result, radius);
return result;
}
/// 均值滤波。
public static HObject MeanImage(HObject image, double maskWidth, double maskHeight)
{
HOperatorSet.MeanImage(image, out HObject result, maskWidth, maskHeight);
return result;
}
/// 中值滤波(maskType: "circle"/"square";margin: "mirrored"/"continued")。
public static HObject MedianImage(HObject image, string maskType, double radius, string margin)
{
HOperatorSet.MedianImage(image, out HObject result, maskType, radius, margin);
return result;
}
/// 高斯滤波(size 为奇数)。
public static HObject GaussFilter(HObject image, double size)
{
HOperatorSet.GaussFilter(image, out HObject result, size);
return result;
}
/// 灰度线性变换:g' = g*mult + add(对比度/亮度调整)。
public static HObject ScaleImage(HObject image, double mult, double add)
{
HOperatorSet.ScaleImage(image, out HObject result, mult, add);
return result;
}
/// 彩色转灰度(三通道图像→单通道)。已是单通道则原样拷贝返回。
public static HObject RgbToGray(HObject image)
{
HOperatorSet.CountChannels(image, out HTuple ch);
if (ch.I <= 1)
{
HOperatorSet.CopyImage(image, out HObject dup);
return dup;
}
// rgb3_to_gray 需要三个「单通道」输入,先用 access_channel 拆出 R/G/B
HOperatorSet.AccessChannel(image, out HObject cr, 1);
HOperatorSet.AccessChannel(image, out HObject cg, 2);
HOperatorSet.AccessChannel(image, out HObject cb, 3);
try
{
HOperatorSet.Rgb3ToGray(cr, cg, cb, out HObject gray);
return gray;
}
finally
{
cr.Dispose();
cg.Dispose();
cb.Dispose();
}
}
#endregion
#region 算子封装:几何测量 / 检测识别
///
/// 区域特征:面积 + 重心(对区域集中的每个区域各返回一项)。
///
public static void AreaCenter(HObject regions, out double[] area, out double[] row, out double[] col)
{
HOperatorSet.AreaCenter(regions, out HTuple a, out HTuple r, out HTuple c);
area = SafeDoubleArr(a);
row = SafeDoubleArr(r);
col = SafeDoubleArr(c);
}
/// 最小外接正矩形(行1,列1,行2,列2),取区域集的第一个区域。
public static void SmallestRectangle1(HObject region, out double row1, out double col1, out double row2, out double col2)
{
HOperatorSet.SmallestRectangle1(region, out HTuple r1, out HTuple c1, out HTuple r2, out HTuple c2);
row1 = First(r1);
col1 = First(c1);
row2 = First(r2);
col2 = First(c2);
}
///
/// 形状筛选:按特征("area"/"roundness"/"compactness"/"rect2_features"…)与阈值区间保留符合条件的区域。
/// operation: "and"/"or"。
///
public static HObject SelectShape(HObject regions, string features, string operation, double min, double max)
{
HOperatorSet.SelectShape(regions, out HObject selected, features, operation, min, max);
return selected;
}
#endregion
#region 工具
/// 安全释放 HObject(吞掉异常,避免流程收尾时抛出)。
public static void Dispose(HObject obj)
{
try { obj?.Dispose(); } catch { }
}
private static double First(HTuple t)
{
if (t == null || t.Length == 0) return 0d;
return t.D;
}
private static double[] SafeDoubleArr(HTuple t)
{
if (t == null || t.Length == 0) return new double[0];
try { return t.DArr; }
catch { return new double[0]; }
}
#endregion
}
}