| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416 |
- 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
- {
- /// <summary>
- /// 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 已配)。
- /// </summary>
- public static class HalconRuntime
- {
- #region 图像互转:外部图像 → HObject
- /// <summary>
- /// 把上游传入的图像对象统一转换为 HALCON 的 <see cref="HObject"/> 图像。
- /// 支持:已是 HObject(原样返回)、<see cref="GenImage"/>(主流程标准图像)、
- /// <see cref="Bitmap"/>、<see cref="BitmapSource"/>。无法识别时抛异常(由插件 try/catch 兜住)。
- /// </summary>
- 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);
- }
- /// <summary>
- /// GenImage(原始像素)→ HObject。灰度用 gen_image1,彩色拆三通道用 gen_image3;
- /// 生成后立即 copy_image 让 HALCON 拥有像素副本,再释放固定内存(gen_image1/3 只是包裹外部指针,不拷贝)。
- /// </summary>
- 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);
- }
- }
- }
- /// <summary>单通道图像生成(bpp=1→byte / bpp=2→uint2)。</summary>
- 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(); }
- }
- /// <summary>三通道彩色图像生成:按通道下标拆分交织数据为 R/G/B 三个紧凑缓冲,再 gen_image3。</summary>
- 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(); }
- }
- /// <summary>按行拷贝出去掉行填充(stride)的紧凑像素缓冲;stride 已等于 width*bpp 时直接返回原数组。</summary>
- 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;
- }
- /// <summary>BitmapSource → Bitmap(WPF 图像兜底转换;主流程一般已是 GenImage,很少走到这里)。</summary>
- 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(把处理结果传回主流程)
- /// <summary>
- /// HObject 图像 → <see cref="GenImage"/>(灰度→Grey8/Grey16;彩色→RGB24)。
- /// 传入的不是图像或无法解析时返回 null。
- /// </summary>
- 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 算子封装:图像处理
- /// <summary>阈值分割:灰度 ∈ [minGray,maxGray] 的像素成区域。返回单个(可能含多连通块的)区域。</summary>
- public static HObject Threshold(HObject image, double minGray, double maxGray)
- {
- HOperatorSet.Threshold(image, out HObject region, minGray, maxGray);
- return region;
- }
- /// <summary>连通域拆分:把一个区域拆成互不相连的多个独立区域。</summary>
- public static HObject Connection(HObject region)
- {
- HOperatorSet.Connection(region, out HObject connected);
- return connected;
- }
- /// <summary>对象数量(区域集中的区域个数 / 图像张数)。</summary>
- public static int CountObj(HObject obj)
- {
- if (obj == null || !obj.IsInitialized()) return 0;
- HOperatorSet.CountObj(obj, out HTuple number);
- return number.I;
- }
- /// <summary>图像尺寸。</summary>
- 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;
- }
- /// <summary>形态学-膨胀(圆形结构元)。</summary>
- public static HObject DilationCircle(HObject region, double radius)
- {
- HOperatorSet.DilationCircle(region, out HObject result, radius);
- return result;
- }
- /// <summary>形态学-腐蚀(圆形结构元)。</summary>
- public static HObject ErosionCircle(HObject region, double radius)
- {
- HOperatorSet.ErosionCircle(region, out HObject result, radius);
- return result;
- }
- /// <summary>形态学-开运算(先腐蚀后膨胀,去小噪点)。</summary>
- public static HObject OpeningCircle(HObject region, double radius)
- {
- HOperatorSet.OpeningCircle(region, out HObject result, radius);
- return result;
- }
- /// <summary>形态学-闭运算(先膨胀后腐蚀,填小孔洞)。</summary>
- public static HObject ClosingCircle(HObject region, double radius)
- {
- HOperatorSet.ClosingCircle(region, out HObject result, radius);
- return result;
- }
- /// <summary>均值滤波。</summary>
- public static HObject MeanImage(HObject image, double maskWidth, double maskHeight)
- {
- HOperatorSet.MeanImage(image, out HObject result, maskWidth, maskHeight);
- return result;
- }
- /// <summary>中值滤波(maskType: "circle"/"square";margin: "mirrored"/"continued")。</summary>
- public static HObject MedianImage(HObject image, string maskType, double radius, string margin)
- {
- HOperatorSet.MedianImage(image, out HObject result, maskType, radius, margin);
- return result;
- }
- /// <summary>高斯滤波(size 为奇数)。</summary>
- public static HObject GaussFilter(HObject image, double size)
- {
- HOperatorSet.GaussFilter(image, out HObject result, size);
- return result;
- }
- /// <summary>灰度线性变换:g' = g*mult + add(对比度/亮度调整)。</summary>
- public static HObject ScaleImage(HObject image, double mult, double add)
- {
- HOperatorSet.ScaleImage(image, out HObject result, mult, add);
- return result;
- }
- /// <summary>彩色转灰度(三通道图像→单通道)。已是单通道则原样拷贝返回。</summary>
- 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 算子封装:几何测量 / 检测识别
- /// <summary>
- /// 区域特征:面积 + 重心(对区域集中的每个区域各返回一项)。
- /// </summary>
- 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);
- }
- /// <summary>最小外接正矩形(行1,列1,行2,列2),取区域集的第一个区域。</summary>
- 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);
- }
- /// <summary>
- /// 形状筛选:按特征("area"/"roundness"/"compactness"/"rect2_features"…)与阈值区间保留符合条件的区域。
- /// operation: "and"/"or"。
- /// </summary>
- 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 工具
- /// <summary>安全释放 HObject(吞掉异常,避免流程收尾时抛出)。</summary>
- 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
- }
- }
|