HalconRuntime.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. using System;
  2. using System.Drawing;
  3. using System.Drawing.Imaging;
  4. using System.Runtime.InteropServices;
  5. using System.Windows.Media.Imaging;
  6. using HalconDotNet;
  7. using TeamAAS.Camera.Images;
  8. namespace Plugins.Halcon
  9. {
  10. /// <summary>
  11. /// HALCON SDK 适配层(唯一允许出现 HalconDotNet 调用的地方)。
  12. ///
  13. /// 设计目的:把与 HALCON 强耦合的调用(图像互转、算子)全部收敛到这一层,插件/模型/视图只依赖本类的
  14. /// SDK 无关签名(object / GenImage / 基元类型),从而:
  15. /// - 更换/升级 HALCON 版本时只改这一处;
  16. /// - 插件类型本身不直接暴露 HObject,保证 PluginLoader 反射扫描(GetTypes)时不会强制加载 HALCON 原生库;
  17. /// - 与 Plugins.Vm 的 VmRuntime 隔离思路一致(区别:本机已装 HALCON 23.11,此处为真实实现而非骨架)。
  18. ///
  19. /// 环境:工程内 HalconSdk\ 放 halcondotnet.dll(托管,随生成复制到 EXE\win-x64\Plugins\);
  20. /// 运行期还需 HALCON 原生运行库(halcon.dll 等)+授权——由本机安装 HALCON 提供(HALCONROOT/PATH 已配)。
  21. /// </summary>
  22. public static class HalconRuntime
  23. {
  24. #region 图像互转:外部图像 → HObject
  25. /// <summary>
  26. /// 把上游传入的图像对象统一转换为 HALCON 的 <see cref="HObject"/> 图像。
  27. /// 支持:已是 HObject(原样返回)、<see cref="GenImage"/>(主流程标准图像)、
  28. /// <see cref="Bitmap"/>、<see cref="BitmapSource"/>。无法识别时抛异常(由插件 try/catch 兜住)。
  29. /// </summary>
  30. public static HObject ToHObject(object image)
  31. {
  32. if (image == null)
  33. throw new ArgumentNullException(nameof(image), "输入图像为 null");
  34. if (image is HObject ho)
  35. {
  36. if (!ho.IsInitialized())
  37. throw new ArgumentException("输入的 HObject 未初始化");
  38. return ho;
  39. }
  40. GenImage gi = image as GenImage;
  41. if (gi == null)
  42. {
  43. Bitmap bmp = image as Bitmap;
  44. if (bmp == null && image is BitmapSource bms)
  45. bmp = BitmapSourceToBitmap(bms);
  46. if (bmp != null)
  47. gi = GenImage.FromBitmap(bmp);
  48. }
  49. if (gi == null)
  50. throw new ArgumentException($"无法识别的图像类型:{image.GetType().Name}(支持 GenImage/Bitmap/BitmapSource/HObject)");
  51. return GenImageToHObject(gi);
  52. }
  53. /// <summary>
  54. /// GenImage(原始像素)→ HObject。灰度用 gen_image1,彩色拆三通道用 gen_image3;
  55. /// 生成后立即 copy_image 让 HALCON 拥有像素副本,再释放固定内存(gen_image1/3 只是包裹外部指针,不拷贝)。
  56. /// </summary>
  57. private static HObject GenImageToHObject(GenImage gi)
  58. {
  59. int w = gi.Width;
  60. int h = gi.Height;
  61. int stride = gi.ActualStride;
  62. byte[] src = gi.Data;
  63. switch (gi.Format)
  64. {
  65. case PixelType.Grey8:
  66. return GenMono(src, stride, w, h, 1, "byte");
  67. case PixelType.Grey16:
  68. case PixelType.Depth16:
  69. return GenMono(src, stride, w, h, 2, "uint2");
  70. case PixelType.RGB24:
  71. return GenColor(src, stride, w, h, 3, 0, 1, 2); // R,G,B
  72. case PixelType.BGR24:
  73. return GenColor(src, stride, w, h, 3, 2, 1, 0); // B,G,R → 传 R,G,B
  74. case PixelType.BGRA32:
  75. return GenColor(src, stride, w, h, 4, 2, 1, 0); // B,G,R,A → 传 R,G,B
  76. case PixelType.Depth16Conf8:
  77. case PixelType.Depth16Intensity8:
  78. default:
  79. // 其它复合格式:走标准 Bitmap 路径(GenImage.ToBitmap 会做伪彩/格式归一)
  80. using (var bmp = gi.ToBitmap())
  81. {
  82. var gi2 = GenImage.FromBitmap(bmp);
  83. if (gi2.Format == PixelType.Grey8)
  84. return GenMono(gi2.Data, gi2.ActualStride, gi2.Width, gi2.Height, 1, "byte");
  85. return GenColor(gi2.Data, gi2.ActualStride, gi2.Width, gi2.Height, 3, 0, 1, 2);
  86. }
  87. }
  88. }
  89. /// <summary>单通道图像生成(bpp=1→byte / bpp=2→uint2)。</summary>
  90. private static HObject GenMono(byte[] src, int stride, int w, int h, int bpp, string halconType)
  91. {
  92. byte[] tight = TightRows(src, stride, w, h, bpp);
  93. var gc = GCHandle.Alloc(tight, GCHandleType.Pinned);
  94. try
  95. {
  96. HOperatorSet.GenImage1(out HObject tmp, halconType, w, h, new HTuple(gc.AddrOfPinnedObject()));
  97. HOperatorSet.CopyImage(tmp, out HObject copy);
  98. tmp.Dispose();
  99. return copy;
  100. }
  101. finally { gc.Free(); }
  102. }
  103. /// <summary>三通道彩色图像生成:按通道下标拆分交织数据为 R/G/B 三个紧凑缓冲,再 gen_image3。</summary>
  104. private static HObject GenColor(byte[] src, int stride, int w, int h, int bpp, int rIdx, int gIdx, int bIdx)
  105. {
  106. int plane = w * h;
  107. var r = new byte[plane];
  108. var g = new byte[plane];
  109. var b = new byte[plane];
  110. for (int y = 0; y < h; y++)
  111. {
  112. int rowStart = y * stride;
  113. int oBase = y * w;
  114. for (int x = 0; x < w; x++)
  115. {
  116. int p = rowStart + x * bpp;
  117. int o = oBase + x;
  118. r[o] = src[p + rIdx];
  119. g[o] = src[p + gIdx];
  120. b[o] = src[p + bIdx];
  121. }
  122. }
  123. var gcr = GCHandle.Alloc(r, GCHandleType.Pinned);
  124. var gcg = GCHandle.Alloc(g, GCHandleType.Pinned);
  125. var gcb = GCHandle.Alloc(b, GCHandleType.Pinned);
  126. try
  127. {
  128. HOperatorSet.GenImage3(out HObject tmp, "byte", w, h,
  129. new HTuple(gcr.AddrOfPinnedObject()),
  130. new HTuple(gcg.AddrOfPinnedObject()),
  131. new HTuple(gcb.AddrOfPinnedObject()));
  132. HOperatorSet.CopyImage(tmp, out HObject copy);
  133. tmp.Dispose();
  134. return copy;
  135. }
  136. finally { gcr.Free(); gcg.Free(); gcb.Free(); }
  137. }
  138. /// <summary>按行拷贝出去掉行填充(stride)的紧凑像素缓冲;stride 已等于 width*bpp 时直接返回原数组。</summary>
  139. private static byte[] TightRows(byte[] src, int stride, int w, int h, int bpp)
  140. {
  141. int rowBytes = w * bpp;
  142. if (stride == rowBytes && src.Length >= rowBytes * h)
  143. return src;
  144. var dst = new byte[rowBytes * h];
  145. for (int y = 0; y < h; y++)
  146. {
  147. int from = y * stride;
  148. int to = y * rowBytes;
  149. int n = Math.Min(rowBytes, src.Length - from);
  150. if (n <= 0) break;
  151. Buffer.BlockCopy(src, from, dst, to, n);
  152. }
  153. return dst;
  154. }
  155. /// <summary>BitmapSource → Bitmap(WPF 图像兜底转换;主流程一般已是 GenImage,很少走到这里)。</summary>
  156. private static Bitmap BitmapSourceToBitmap(BitmapSource source)
  157. {
  158. var enc = new PngBitmapEncoder();
  159. enc.Frames.Add(BitmapFrame.Create(source));
  160. using (var ms = new System.IO.MemoryStream())
  161. {
  162. enc.Save(ms);
  163. ms.Position = 0;
  164. return new Bitmap(ms);
  165. }
  166. }
  167. #endregion
  168. #region 图像互转:HObject → GenImage(把处理结果传回主流程)
  169. /// <summary>
  170. /// HObject 图像 → <see cref="GenImage"/>(灰度→Grey8/Grey16;彩色→RGB24)。
  171. /// 传入的不是图像或无法解析时返回 null。
  172. /// </summary>
  173. public static GenImage ToGenImage(object hobj)
  174. {
  175. var img = hobj as HObject;
  176. if (img == null || !img.IsInitialized()) return null;
  177. try
  178. {
  179. HOperatorSet.GetImageSize(img, out HTuple wT, out HTuple hT);
  180. int w = wT.I;
  181. int h = hT.I;
  182. HOperatorSet.CountChannels(img, out HTuple chT);
  183. int channels = chT.I;
  184. if (channels <= 1)
  185. {
  186. HOperatorSet.GetImagePointer1(img, out HTuple ptr, out HTuple type, out HTuple pw, out HTuple ph);
  187. string t = type.S;
  188. int bpp = (t == "uint2" || t == "int2" || t == "int4") ? 2 : 1;
  189. var data = new byte[w * h * bpp];
  190. Marshal.Copy(ptr.IP, data, 0, data.Length);
  191. var pf = bpp == 1 ? PixelType.Grey8 : PixelType.Grey16;
  192. return new GenImage(w, h, pf, data);
  193. }
  194. else
  195. {
  196. HOperatorSet.GetImagePointer3(img, out HTuple pr, out HTuple pg, out HTuple pb,
  197. out HTuple type3, out HTuple pw3, out HTuple ph3);
  198. int plane = w * h;
  199. var r = new byte[plane];
  200. var g = new byte[plane];
  201. var b = new byte[plane];
  202. Marshal.Copy(pr.IP, r, 0, plane);
  203. Marshal.Copy(pg.IP, g, 0, plane);
  204. Marshal.Copy(pb.IP, b, 0, plane);
  205. var data = new byte[plane * 3];
  206. for (int i = 0; i < plane; i++)
  207. {
  208. data[i * 3] = r[i];
  209. data[i * 3 + 1] = g[i];
  210. data[i * 3 + 2] = b[i];
  211. }
  212. return new GenImage(w, h, PixelType.RGB24, data);
  213. }
  214. }
  215. catch
  216. {
  217. return null;
  218. }
  219. }
  220. #endregion
  221. #region 算子封装:图像处理
  222. /// <summary>阈值分割:灰度 ∈ [minGray,maxGray] 的像素成区域。返回单个(可能含多连通块的)区域。</summary>
  223. public static HObject Threshold(HObject image, double minGray, double maxGray)
  224. {
  225. HOperatorSet.Threshold(image, out HObject region, minGray, maxGray);
  226. return region;
  227. }
  228. /// <summary>连通域拆分:把一个区域拆成互不相连的多个独立区域。</summary>
  229. public static HObject Connection(HObject region)
  230. {
  231. HOperatorSet.Connection(region, out HObject connected);
  232. return connected;
  233. }
  234. /// <summary>对象数量(区域集中的区域个数 / 图像张数)。</summary>
  235. public static int CountObj(HObject obj)
  236. {
  237. if (obj == null || !obj.IsInitialized()) return 0;
  238. HOperatorSet.CountObj(obj, out HTuple number);
  239. return number.I;
  240. }
  241. /// <summary>图像尺寸。</summary>
  242. public static void GetImageSize(HObject image, out int width, out int height)
  243. {
  244. HOperatorSet.GetImageSize(image, out HTuple w, out HTuple h);
  245. width = w.I;
  246. height = h.I;
  247. }
  248. /// <summary>形态学-膨胀(圆形结构元)。</summary>
  249. public static HObject DilationCircle(HObject region, double radius)
  250. {
  251. HOperatorSet.DilationCircle(region, out HObject result, radius);
  252. return result;
  253. }
  254. /// <summary>形态学-腐蚀(圆形结构元)。</summary>
  255. public static HObject ErosionCircle(HObject region, double radius)
  256. {
  257. HOperatorSet.ErosionCircle(region, out HObject result, radius);
  258. return result;
  259. }
  260. /// <summary>形态学-开运算(先腐蚀后膨胀,去小噪点)。</summary>
  261. public static HObject OpeningCircle(HObject region, double radius)
  262. {
  263. HOperatorSet.OpeningCircle(region, out HObject result, radius);
  264. return result;
  265. }
  266. /// <summary>形态学-闭运算(先膨胀后腐蚀,填小孔洞)。</summary>
  267. public static HObject ClosingCircle(HObject region, double radius)
  268. {
  269. HOperatorSet.ClosingCircle(region, out HObject result, radius);
  270. return result;
  271. }
  272. /// <summary>均值滤波。</summary>
  273. public static HObject MeanImage(HObject image, double maskWidth, double maskHeight)
  274. {
  275. HOperatorSet.MeanImage(image, out HObject result, maskWidth, maskHeight);
  276. return result;
  277. }
  278. /// <summary>中值滤波(maskType: "circle"/"square";margin: "mirrored"/"continued")。</summary>
  279. public static HObject MedianImage(HObject image, string maskType, double radius, string margin)
  280. {
  281. HOperatorSet.MedianImage(image, out HObject result, maskType, radius, margin);
  282. return result;
  283. }
  284. /// <summary>高斯滤波(size 为奇数)。</summary>
  285. public static HObject GaussFilter(HObject image, double size)
  286. {
  287. HOperatorSet.GaussFilter(image, out HObject result, size);
  288. return result;
  289. }
  290. /// <summary>灰度线性变换:g' = g*mult + add(对比度/亮度调整)。</summary>
  291. public static HObject ScaleImage(HObject image, double mult, double add)
  292. {
  293. HOperatorSet.ScaleImage(image, out HObject result, mult, add);
  294. return result;
  295. }
  296. /// <summary>彩色转灰度(三通道图像→单通道)。已是单通道则原样拷贝返回。</summary>
  297. public static HObject RgbToGray(HObject image)
  298. {
  299. HOperatorSet.CountChannels(image, out HTuple ch);
  300. if (ch.I <= 1)
  301. {
  302. HOperatorSet.CopyImage(image, out HObject dup);
  303. return dup;
  304. }
  305. // rgb3_to_gray 需要三个「单通道」输入,先用 access_channel 拆出 R/G/B
  306. HOperatorSet.AccessChannel(image, out HObject cr, 1);
  307. HOperatorSet.AccessChannel(image, out HObject cg, 2);
  308. HOperatorSet.AccessChannel(image, out HObject cb, 3);
  309. try
  310. {
  311. HOperatorSet.Rgb3ToGray(cr, cg, cb, out HObject gray);
  312. return gray;
  313. }
  314. finally
  315. {
  316. cr.Dispose();
  317. cg.Dispose();
  318. cb.Dispose();
  319. }
  320. }
  321. #endregion
  322. #region 算子封装:几何测量 / 检测识别
  323. /// <summary>
  324. /// 区域特征:面积 + 重心(对区域集中的每个区域各返回一项)。
  325. /// </summary>
  326. public static void AreaCenter(HObject regions, out double[] area, out double[] row, out double[] col)
  327. {
  328. HOperatorSet.AreaCenter(regions, out HTuple a, out HTuple r, out HTuple c);
  329. area = SafeDoubleArr(a);
  330. row = SafeDoubleArr(r);
  331. col = SafeDoubleArr(c);
  332. }
  333. /// <summary>最小外接正矩形(行1,列1,行2,列2),取区域集的第一个区域。</summary>
  334. public static void SmallestRectangle1(HObject region, out double row1, out double col1, out double row2, out double col2)
  335. {
  336. HOperatorSet.SmallestRectangle1(region, out HTuple r1, out HTuple c1, out HTuple r2, out HTuple c2);
  337. row1 = First(r1);
  338. col1 = First(c1);
  339. row2 = First(r2);
  340. col2 = First(c2);
  341. }
  342. /// <summary>
  343. /// 形状筛选:按特征("area"/"roundness"/"compactness"/"rect2_features"…)与阈值区间保留符合条件的区域。
  344. /// operation: "and"/"or"。
  345. /// </summary>
  346. public static HObject SelectShape(HObject regions, string features, string operation, double min, double max)
  347. {
  348. HOperatorSet.SelectShape(regions, out HObject selected, features, operation, min, max);
  349. return selected;
  350. }
  351. #endregion
  352. #region 工具
  353. /// <summary>安全释放 HObject(吞掉异常,避免流程收尾时抛出)。</summary>
  354. public static void Dispose(HObject obj)
  355. {
  356. try { obj?.Dispose(); } catch { }
  357. }
  358. private static double First(HTuple t)
  359. {
  360. if (t == null || t.Length == 0) return 0d;
  361. return t.D;
  362. }
  363. private static double[] SafeDoubleArr(HTuple t)
  364. {
  365. if (t == null || t.Length == 0) return new double[0];
  366. try { return t.DArr; }
  367. catch { return new double[0]; }
  368. }
  369. #endregion
  370. }
  371. }