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 } }