C#-Helcon的HObject图像,OpenCV的Mat图像(代替IplImage类型图像),Bitmap图像三者之间的相互转化方法;HObject=>HImage(灰度/彩色);C#MAT图像与C++MAT图像的相互传输
全网图像转化源码C#版独一,为了弄清不同类型储存结构,我太难了!
一,认识HObject类型,HImage类型,Mat类型,Bitmap类型;(。。。没整理呢)
1. HObject类型
2. HImage类型
3. opencv之Mat数据类型,Mat类的定义,学习OpenCV2——Mat之通道的理解,Mat基本操作以及灰度图转化
data:Mat对象中的一个指针,指向内存中存放矩阵数据的一块内存 (uchar* data)
dims:Mat所代表的矩阵的维度,如 3 * 4 的矩阵为 2 维, 3 * 4 * 5 的为3维
channels:通道,矩阵中的每一个矩阵元素拥有的值的个数,比如说 3 * 4 矩阵中一共 12 个元素,如果每个元素有三个值,那么就说这个矩阵是 3 通道的,即 channels = 3。常见的是一张彩色图片有红、绿、蓝三个通道。
depth:深度,即每一个像素的位数(bits),在opencv的Mat.depth()中得到的是一个 0 – 6 的数字,分别代表不同的位数:enum { CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6 }; 可见 0和1都代表8位, 2和3都代表16位,4和5代表32位,6代表64位;
step:是一个数组,定义了矩阵的布局,具体见下面图片分析,另外注意 step1 (step / elemSize1),M.step[m-1] 总是等于 elemSize,M.step1(m-1)总是等于 channels;
elemSize : 矩阵中每一个元素的数据大小,如果Mat中的数据的数据类型是 CV_8U 那么 elemSize = 1,CV_8UC3 那么 elemSize = 3,CV_16UC2 那么 elemSize = 4;记住另外有个 elemSize1 表示的是矩阵中数据类型的大小,即 elemSize / channels 的大小
4. Bitmap类型(System.Drawing)
5.Image类型(System.Drawing)
6.ImageSource类型(System.Windows.Media)
二,不一定要转化(直接读文件的情况) 很简单,有点乱,整理出来再传
三,转化方法(避免 “存” “读”文件)
0.Bitmap=>ImageSource
///// <summary>
///// 文本转BitmapImage图 -Wpf工程中
//public System.Windows.Media.ImageSource BitmapToBitmapImage(System.Drawing.Bitmap bitmap)
//{
// MemoryStream stream = new MemoryStream();
// bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
// ImageBrush imageBrush = new ImageBrush();
// ImageSourceConverter imageSourceConverter = new ImageSourceConverter();
// return (ImageSource)imageSourceConverter.ConvertFrom(stream);
//}
1.HObject<=>Bitmap(还未验证,会验证的)
(1)把Halcon的“HObject图像”转换到Bitmap图像

1 /// <summary>
2 /// 把halcon图像转换到bitmap图像
3 /// </summary>
4 /// <param name="image">HObject对象</param>
5 /// <param name="res">Bitmap对象</param>
6 private Bitmap HObjectToBitmap(HObject hImage)
7 {
8 HTuple hpoint, type, width, height;
9 Bitmap res;
10 const int Alpha = 255;
11 HOperatorSet.GetImagePointer1(hImage, out hpoint, out type, out width, out height);
12
13
14 res = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
15 ColorPalette pal = res.Palette;
16 for (int i = 0; i <= 255; i++)
17 {
18 string insds = Color.FromArgb(Alpha, i, i, i).ToString();
19 }
20 res.Palette = pal;
21 Rectangle rect = new Rectangle(0, 0, width, height);
22 BitmapData bitmapData = res.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
23 int PixelSize = Bitmap.GetPixelFormatSize(bitmapData.PixelFormat) / 8;
24
25 if (width % 4 == 0)//4的倍数的时候,直接复制
26 CopyMemory(bitmapData.Scan0, hpoint, width * height * PixelSize);
27 else//根据高循环
28 {
29 for (int i = 0; i < height - 1; i++)
30 {
31 CopyMemory(bitmapData.Scan0, hpoint, width * PixelSize);
32 hpoint.I += width;
33 bitmapData.Scan0 += bitmapData.Stride;
34 }
35 }
36 res.UnlockBits(bitmapData);
37 return res;
38 }
(2)把Bitmap图像转换到Halcon的“HObject图像”

1 /// <summary>
2 /// 把bitmap图像转换到halcon图像
3 /// </summary>
4 /// <param name="bmp"></param>
5 /// <returns></returns>
6 public HObject BitmapToHObject(Bitmap bmp)
7 {
8 try
9 {
10
11 Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
12
13 System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed);
14 IntPtr pointer = bmpData.Scan0;
15
16 byte[] dataBlue = new byte[bmp.Width * bmp.Height];
17 unsafe
18 {
19 fixed (byte* ptrdata = dataBlue)
20 {
21 for (int i = 0; i < bmp.Height; i++)
22 {
23 CopyMemory((IntPtr)(ptrdata + bmp.Width * i), pointer + bmpData.Stride * i, bmp.Width);
24 }
25 HObject ho;
26 HOperatorSet.GenImage1(out ho, "byte", bmp.Width, bmp.Height, (int)ptrdata);
27 HImage himg = new HImage("byte", bmp.Width, bmp.Height, (IntPtr)ptrdata);
28
29 //HOperatorSet.DispImage(ho, hWindowControl1.HalconWindow);
30
31 bmp.UnlockBits(bmpData);
32 return ho;
33 }
34 }
35 }
36 catch (Exception exc)
37 {
38 return null;
39 }
40
41 }
2.Mat<=>Bitmap(还未写,会写的)
(1)把OpenCV的“Mat图像”转换到Bitmap图像
(2)把Bitmap图像转换到OpenCV的“Mat图像”
3.HObject<=>Mat
(1)把Halcon的“HObject图像”转换到OpenCV的“Mat图像”

1 /// <summary>
2 /// 把Halcon图像转换到OpenCV图像
3 /// </summary>
4 /// <param name="hImage">Halcon图像_HObject</param>
5 /// <returns>OpenCV图像_Mat</returns>
6 public Mat HImageToMat(HObject hImage)
7 {
8 try
9 {
10 Mat mImage; // 返回值
11 HTuple htChannels; // 通道
12 HTuple cType = null; // 类型
13 HTuple width, height; // 宽,高
14 width = height = 0;
15
16 htChannels = null;
17 HOperatorSet.CountChannels(hImage, out htChannels); // 获取通道
18
19 // 通道存在值
20 if (htChannels.Length == 0)
21 {
22 return null;
23 }
24 if (htChannels[0].I == 1) // 单通道
25 {
26 HTuple ptr; // HTuple_单通道值指针
27 HOperatorSet.GetImagePointer1(hImage, out ptr, out cType, out width, out height); // 单通道取值方法(图片,输出“单通道值指针”,输出“类型”,输出“宽”,输出“高”) // (ptr=2157902018096 cType=byte width=830 height=822)
28 mImage = new Mat(new OpenCvSharp.Size(width, height), MatType.CV_8UC1, new Scalar(0)); // 实例化mImage(大小,MatType.CV_8UC1,new Scalar(0))
29 int Width = width;
30
31 unsafe
32 {
33 //for (int i = 0; i < height; i++) // 循环赋值
34 //{
35 // IntPtr start = IntPtr.Add(mImage.Data, i * width); // Mat的单通道_Data地址+偏移(start=0x000001f66d4df300 Data=0x000001f66d4df300 i * 830)
36 // CopyMemory(start, new IntPtr((byte*)ptr.IP + width * i), width); // CopyMemory(要复制到的地址,复制源的地址,复制的长度)
37 //}
38 CopyMemory(mImage.Data, new IntPtr((byte*)ptr.IP), width*height);// CopyMemory(要复制到的地址,复制源的地址,复制的长度)
39 }
40 return mImage;
41 }
42 else if (htChannels[0].I == 3) // 三通道
43 {
44 HTuple ptrRed; // HTuple_R通道值指针
45 HTuple ptrGreen; // HTuple_G通道值指针
46 HTuple ptrBlue; // HTuple_B通道值指针
47
48 HOperatorSet.GetImagePointer3(hImage, out ptrRed, out ptrGreen, out ptrBlue, out cType, out width, out height); // 三通道取值方法(图片,输出“R通道值指针”,输出“G通道值指针”,输出“B通道值指针”,输出“类型”,输出“宽”,输出“高”)
49 Mat pImageRed = new Mat(new OpenCvSharp.Size(width, height), MatType.CV_8UC1); // Mat_R通道值指针(大小,MatType.CV_8UC1)
50 Mat pImageGreen = new Mat(new OpenCvSharp.Size(width, height), MatType.CV_8UC1); // Mat_G通道值指针(大小,MatType.CV_8UC1)
51 Mat pImageBlue = new Mat(new OpenCvSharp.Size(width, height), MatType.CV_8UC1); // Mat_B通道值指针(大小,MatType.CV_8UC1)
52 mImage = new Mat(new OpenCvSharp.Size(width, height), MatType.CV_8UC3, new Scalar(0, 0, 0)); // Mat_图片(大小,MatType.CV_8UC1,new Scalar(0, 0, 0))
53 unsafe
54 {
55 //for (int i = 0; i < height; i++)
56 //{
57 // //long step = mImage.Step();
58 // IntPtr startRed = IntPtr.Add(pImageRed.Data, i * width); // Mat的Red_Data地址+偏移(start=0x000001f66d4df300 Data=0x000001f66d4df300 i * 830)
59 // IntPtr startGreen = IntPtr.Add(pImageGreen.Data, i * width); // Mat的Green_Data地址+偏移(start=0x000001f66d4df300 Data=0x000001f66d4df300 i * 830)
60 // IntPtr startBlue = IntPtr.Add(pImageBlue.Data, i * width); // Mat的Blue_Data地址+偏移(start=0x000001f66d4df300 Data=0x000001f66d4df300 i * 830)
61 // CopyMemory(startRed, new IntPtr((byte*)ptrRed.IP + width * i), width); // CopyMemory(要复制到的地址,复制源的地址,复制的长度)_Red
62 // CopyMemory(startGreen, new IntPtr((byte*)ptrGreen.IP + width * i), width); // CopyMemory(要复制到的地址,复制源的地址,复制的长度)_Green
63 // CopyMemory(startBlue, new IntPtr((byte*)ptrBlue.IP + width * i), width); // CopyMemory(要复制到的地址,复制源的地址,复制的长度)_Blue
64 //}
65 CopyMemory(pImageRed.Data, new IntPtr((byte*)ptrRed.IP), width*height); // CopyMemory(要复制到的地址,复制源的地址,复制的长度)_Red
66 CopyMemory(pImageGreen.Data, new IntPtr((byte*)ptrGreen.IP), width * height); // CopyMemory(要复制到的地址,复制源的地址,复制的长度)_Green
67 CopyMemory(pImageBlue.Data, new IntPtr((byte*)ptrBlue.IP), width * height); // CopyMemory(要复制到的地址,复制源的地址,复制的长度)_Blue
68 }
69 Mat[] multi = new Mat[] { pImageBlue, pImageGreen, pImageRed }; // 存储rgb三通道
70 Cv2.Merge(multi, mImage); // rgb三通道合成一张图
71 pImageRed.Dispose(); // Mat_R通道值指针销毁
72 pImageGreen.Dispose(); // Mat_G通道值指针销毁
73 pImageBlue.Dispose(); // Mat_B通道值指针销毁
74 return mImage;
75 }
76 else
77 {
78 return null;
79 }
80 }
81 catch (Exception ex)
82 {
83 throw ex;
84 }
85 }
(2)把OpenCV的”Mat图像“转换到Halcon的“HObject图像”

1 /// <summary>
2 /// 把OpenCV图像转换到Halcon图像
3 /// </summary>
4 /// <param name="mImage">OpenCV图像_Mat</param>
5 /// <returns>Halcon图像_HObject</returns>
6 public HObject MatToHImage(Mat mImage)
7 {
8 try
9 {
10 HObject hImage;
11 int matChannels = 0; // 通道数
12 Type matType = null;
13 int width, height; // 宽,高
14 width = height = 0; // 宽,高初始化
15
16 // 获取通道数
17 matChannels = mImage.Channels();
18 if (matChannels == 0)
19 {
20 return null;
21 }
22 if (matChannels == 1) // 单通道
23 {
24 IntPtr ptr; // 灰度图通道
25 Mat[] mats = mImage.Split();
26
27 // 改自:Mat.GetImagePointer1(mImage, out ptr, out matType, out width, out height); // ptr=2157902018096 cType=byte width=830 height=822
28 ptr = mats[0].Data; // 取灰度图值
29 matType = mImage.GetType(); // byte
30 height = mImage.Rows; // 高
31 width = mImage.Cols; // 宽
32
33 // 改自:hImage = new HObject(new OpenCvSharp.Size(width, height), MatType.CV_8UC1, new Scalar(0));
34 byte[] dataGrayScaleImage = new byte[width * height]; //Mat dataGrayScaleImage = new Mat(new OpenCvSharp.Size(width, height), MatType.CV_8UC1);
35
36 unsafe
37 {
38 fixed (byte* ptrdata = dataGrayScaleImage)
39 {
40 #region 按行复制
41 //for (int i = 0; i < height; i++)
42 //{
43 // CopyMemory((IntPtr)(ptrdata + width * i), new IntPtr((long)ptr + width * i), width);
44 //}
45 #endregion
46
47 CopyMemory((IntPtr)ptrdata, new IntPtr((long)ptr), width * height);
48 HOperatorSet.GenImage1(out hImage, "byte", width, height, (IntPtr)ptrdata);
49
50 }
51 }
52 return hImage;
53 }
54 else if (matChannels == 3) // 三通道
55 {
56 IntPtr ptrRed; // R通道图
57 IntPtr ptrGreen; // G通道图
58 IntPtr ptrBlue; // B通道图
59 Mat[] mats =mImage.Split();
60
61 ptrRed = mats[0].Data; // 取R通道值
62 ptrGreen = mats[1].Data; // 取G通道值
63 ptrBlue = mats[2].Data; // 取B通道值
64 matType = mImage.GetType(); // 类型
65 height = mImage.Rows; // 高
66 width = mImage.Cols; // 宽
67
68 // 改自:hImage = new HObject(new OpenCvSharp.Size(width, height), MatType.CV_8UC1, new Scalar(0));
69 byte[] dataRed = new byte[width * height]; //Mat dataGrayScaleImage = new Mat(new OpenCvSharp.Size(width, height), MatType.CV_8UC1);
70 byte[] dataGreen = new byte[width * height];
71 byte[] dataBlue = new byte[width * height];
72
73 unsafe
74 {
75 fixed (byte* ptrdataRed = dataRed , ptrdataGreen = dataGreen, ptrdataBlue = dataBlue)
76 {
77 #region 按行复制
78 //HImage himg = new HImage("byte", width, height, (IntPtr)ptrdataRed);
79 //for (int i = 0; i < height; i++)
80 //{
81 // CopyMemory((IntPtr)(ptrdataRed + width * i), new IntPtr((long)ptrRed + width * i), width);
82 // CopyMemory((IntPtr)(ptrdataGreen + width * i), new IntPtr((long)ptrGreen + width * i), width);
83 // CopyMemory((IntPtr)(ptrdataBlue + width * i), new IntPtr((long)ptrBlue + width * i), width);
84 //}
85 #endregion
86
87 CopyMemory((IntPtr)ptrdataRed , new IntPtr((long)ptrRed ), width* height); // 复制R通道
88 CopyMemory((IntPtr)ptrdataGreen , new IntPtr((long)ptrGreen ), width* height); // 复制G通道
89 CopyMemory((IntPtr)ptrdataBlue , new IntPtr((long)ptrBlue ), width* height); // 复制B通道
90 HOperatorSet.GenImage3(out hImage, "byte", width, height, (IntPtr)ptrdataRed, (IntPtr)ptrdataGreen, (IntPtr)ptrdataBlue); // 合成
91 }
92 }
93 return hImage;
94 }
95 else
96 {
97 return null;
98 }
99 }
100 catch (Exception ex)
101 {
102 throw ex;
103 }
104
105 }
4.HObject=>HImage(灰度/彩色)
(1)灰度图像 HObject -> HImage

1 /// <summary>
2 /// 灰度图像 HObject -> HImage
3 /// </summary>
4 public HImage HObjectToHImage1(HObject hObj)
5 {
6 HImage image = new HImage();
7 HTuple type, width, height, pointer;
8 HOperatorSet.GetImagePointer1(hObj, out pointer, out type, out width, out height);
9 image.GenImage1(type, width, height, pointer);
10 return image;
11 }
(2)彩色图像 HObject -> HImage

1 /// <summary>
2 /// 彩色图像 HObject -> HImage
3 /// </summary>
4 public HImage HObjectToHImage3(HObject hObj)
5 {
6 HImage image = new HImage();
7 HTuple type, width, height, pointerRed, pointerGreen, pointerBlue;
8 HOperatorSet.GetImagePointer3(hObj, out pointerRed, out pointerGreen, out pointerBlue,
9 out type, out width, out height);
10 image.GenImage3(type, width, height, pointerRed, pointerGreen, pointerBlue);
11 return image;
12 }
5.灰度图HObject->彩图HObject

1 /// <summary>
2 /// 灰度图像 HObject -> 三通道HObject
3 /// </summary>
4 public HObject HObject1ToHObject3(HObject hObj)
5 {
6 HObject image = new HObject();
7 HTuple type, width, height, pointer;
8 HOperatorSet.GetImagePointer1(hObj, out pointer, out type, out width, out height);
9 HOperatorSet.GenImage3(out image, "byte", width, height, (IntPtr)pointer, (IntPtr)pointer, (IntPtr)pointer); // 合成
10 return image;
11 }
5.附录_全部代码:
(1)用到的NuGet包:OpenCvSharp3-AnyCPU 4.0.0.20181129
(2)全部代码:

1 using System.Runtime.InteropServices;
2 using System.Drawing.Imaging;
3 using HalconDotNet;
4 using System.Drawing;
5 using OpenCvSharp;
6 using System;
7
8 namespace Bll.SaveImage
9 {
10 public class HObjectBitmap
11 {
12 /// <summary>
13 /// 把source指针长度为size的数据复制到指针dest
14 /// </summary>
15 /// <param name="dest">要复制到的地址</param>
16 /// <param name="source">复制源的地址</param>
17 /// <param name="size">复制的长度</param>
18 /// <returns></returns>
19 [DllImport("kernel32.dll")]
20 public extern static long CopyMemory(IntPtr dest, IntPtr source, int size);
21
22 /// <summary>
23 /// 把halcon图像转换到bitmap图像
24 /// </summary>
25 /// <param name="image">HObject对象</param>
26 /// <param name="res">Bitmap对象</param>
27 private Bitmap HObjectToBitmap(HObject hImage)
28 {
29 HTuple hpoint, type, width, height;
30 Bitmap res;
31 const int Alpha = 255;
32 HOperatorSet.GetImagePointer1(hImage, out hpoint, out type, out width, out height);
33
34
35 res = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
36 ColorPalette pal = res.Palette;
37 for (int i = 0; i <= 255; i++)
38 {
39 string insds = Color.FromArgb(Alpha, i, i, i).ToString();
40 }
41 res.Palette = pal;
42 Rectangle rect = new Rectangle(0, 0, width, height);
43 BitmapData bitmapData = res.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
44 int PixelSize = Bitmap.GetPixelFormatSize(bitmapData.PixelFormat) / 8;
45
46 if (width % 4 == 0)//4的倍数的时候,直接复制
47 CopyMemory(bitmapData.Scan0, hpoint, width * height * PixelSize);
48 else//根据高循环
49 {
50 for (int i = 0; i < height - 1; i++)
51 {
52 CopyMemory(bitmapData.Scan0, hpoint, width * PixelSize);
53 hpoint.I += width;
54 bitmapData.Scan0 += bitmapData.Stride;
55 }
56 }
57 res.UnlockBits(bitmapData);
58 return res;
59 }
60
61 /// <summary>
62 /// 把bitmap图像转换到halcon图像
63 /// </summary>
64 /// <param name="bmp"></param>
65 /// <returns></returns>
66 public HObject BitmapToHObject(Bitmap bmp)
67 {
68 try
69 {
70
71 Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
72
73 System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed);
74 IntPtr pointer = bmpData.Scan0;
75
76 byte[] dataBlue = new byte[bmp.Width * bmp.Height];
77 unsafe
78 {
79 fixed (byte* ptrdata = dataBlue)
80 {
81 for (int i = 0; i < bmp.Height; i++)
82 {
83 CopyMemory((IntPtr)(ptrdata + bmp.Width * i), pointer + bmpData.Stride * i, bmp.Width);
84 }
85 HObject ho;
86 HOperatorSet.GenImage1(out ho, "byte", bmp.Width, bmp.Height, (int)ptrdata);
87 HImage himg = new HImage("byte", bmp.Width, bmp.Height, (IntPtr)ptrdata);
88
89 //HOperatorSet.DispImage(ho, hWindowControl1.HalconWindow);
90
91 bmp.UnlockBits(bmpData);
92 return ho;
93 }
94 }
95 }
96 catch (Exception exc)
97 {
98 return null;
99 }
100
101 }
102
103 /// <summary>
104 /// 把Halcon图像转换到OpenCV图像
105 /// </summary>
106 /// <param name="hImage">Halcon图像_HObject</param>
107 /// <returns>OpenCV图像_Mat</returns>
108 public Mat HImageToMat(HObject hImage)
109 {
110 try
111 {
112 Mat mImage; // 返回值
113 HTuple htChannels; // 通道
114 HTuple cType = null; // 类型
115 HTuple width, height; // 宽,高
116 width = height = 0;
117
118 htChannels = null;
119 HOperatorSet.CountChannels(hImage, out htChannels); // 获取通道
120
121 // 通道存在值
122 if (htChannels.Length == 0)
123 {
124 return null;
125 }
126 if (htChannels[0].I == 1) // 单通道
127 {
128 HTuple ptr; // HTuple_单通道值指针
129 HOperatorSet.GetImagePointer1(hImage, out ptr, out cType, out width, out height); // 单通道取值方法(图片,输出“单通道值指针”,输出“类型”,输出“宽”,输出“高”) // (ptr=2157902018096 cType=byte width=830 height=822)
130 mImage = new Mat(new OpenCvSharp.Size(width, height), MatType.CV_8UC1, new Scalar(0)); // 实例化mImage(大小,MatType.CV_8UC1,new Scalar(0))
131 int Width = width;
132
133 unsafe
134 {
135 //for (int i = 0; i < height; i++) // 循环赋值
136 //{
137 // IntPtr start = IntPtr.Add(mImage.Data, i * width); // Mat的单通道_Data地址+偏移(start=0x000001f66d4df300 Data=0x000001f66d4df300 i * 830)
138 // CopyMemory(start, new IntPtr((byte*)ptr.IP + width * i), width); // CopyMemory(要复制到的地址,复制源的地址,复制的长度)
139 //}
140 CopyMemory(mImage.Data, new IntPtr((byte*)ptr.IP), width*height);// CopyMemory(要复制到的地址,复制源的地址,复制的长度)
141 }
142 return mImage;
143 }
144 else if (htChannels[0].I == 3) // 三通道
145 {
146 HTuple ptrRed; // HTuple_R通道值指针
147 HTuple ptrGreen; // HTuple_G通道值指针
148 HTuple ptrBlue; // HTuple_B通道值指针
149
150 HOperatorSet.GetImagePointer3(hImage, out ptrRed, out ptrGreen, out ptrBlue, out cType, out width, out height); // 三通道取值方法(图片,输出“R通道值指针”,输出“G通道值指针”,输出“B通道值指针”,输出“类型”,输出“宽”,输出“高”)
151 Mat pImageRed = new Mat(new OpenCvSharp.Size(width, height), MatType.CV_8UC1); // Mat_R通道值指针(大小,MatType.CV_8UC1)
152 Mat pImageGreen = new Mat(new OpenCvSharp.Size(width, height), MatType.CV_8UC1); // Mat_G通道值指针(大小,MatType.CV_8UC1)
153 Mat pImageBlue = new Mat(new OpenCvSharp.Size(width, height), MatType.CV_8UC1); // Mat_B通道值指针(大小,MatType.CV_8UC1)
154 mImage = new Mat(new OpenCvSharp.Size(width, height), MatType.CV_8UC3, new Scalar(0, 0, 0)); // Mat_图片(大小,MatType.CV_8UC1,new Scalar(0, 0, 0))
155 unsafe
156 {
157 //for (int i = 0; i < height; i++)
158 //{
159 // //long step = mImage.Step();
160 // IntPtr startRed = IntPtr.Add(pImageRed.Data, i * width); // Mat的Red_Data地址+偏移(start=0x000001f66d4df300 Data=0x000001f66d4df300 i * 830)
161 // IntPtr startGreen = IntPtr.Add(pImageGreen.Data, i * width); // Mat的Green_Data地址+偏移(start=0x000001f66d4df300 Data=0x000001f66d4df300 i * 830)
162 // IntPtr startBlue = IntPtr.Add(pImageBlue.Data, i * width); // Mat的Blue_Data地址+偏移(start=0x000001f66d4df300 Data=0x000001f66d4df300 i * 830)
163 // CopyMemory(startRed, new IntPtr((byte*)ptrRed.IP + width * i), width); // CopyMemory(要复制到的地址,复制源的地址,复制的长度)_Red
164 // CopyMemory(startGreen, new IntPtr((byte*)ptrGreen.IP + width * i), width); // CopyMemory(要复制到的地址,复制源的地址,复制的长度)_Green
165 // CopyMemory(startBlue, new IntPtr((byte*)ptrBlue.IP + width * i), width); // CopyMemory(要复制到的地址,复制源的地址,复制的长度)_Blue
166 //}
167 CopyMemory(pImageRed.Data, new IntPtr((byte*)ptrRed.IP), width*height); // CopyMemory(要复制到的地址,复制源的地址,复制的长度)_Red
168 CopyMemory(pImageGreen.Data, new IntPtr((byte*)ptrGreen.IP), width * height); // CopyMemory(要复制到的地址,复制源的地址,复制的长度)_Green
169 CopyMemory(pImageBlue.Data, new IntPtr((byte*)ptrBlue.IP), width * height); // CopyMemory(要复制到的地址,复制源的地址,复制的长度)_Blue
170 }
171 Mat[] multi = new Mat[] { pImageBlue, pImageGreen, pImageRed }; // 存储rgb三通道
172 Cv2.Merge(multi, mImage); // rgb三通道合成一张图
173 pImageRed.Dispose(); // Mat_R通道值指针销毁
174 pImageGreen.Dispose(); // Mat_G通道值指针销毁
175 pImageBlue.Dispose(); // Mat_B通道值指针销毁
176 return mImage;
177 }
178 else
179 {
180 return null;
181 }
182 }
183 catch (Exception ex)
184 {
185 throw ex;
186 }
187 }
188
189 /// <summary>
190 /// 把OpenCV图像转换到Halcon图像
191 /// </summary>
192 /// <param name="mImage">OpenCV图像_Mat</param>
193 /// <returns>Halcon图像_HObject</returns>
194 public HObject MatToHImage(Mat mImage)
195 {
196 try
197 {
198 HObject hImage;
199 int matChannels = 0; // 通道数
200 Type matType = null;
201 int width, height; // 宽,高
202 width = height = 0; // 宽,高初始化
203
204 // 获取通道数
205 matChannels = mImage.Channels();
206 if (matChannels == 0)
207 {
208 return null;
209 }
210 if (matChannels == 1) // 单通道
211 {
212 IntPtr ptr; // 灰度图通道
213 Mat[] mats = mImage.Split();
214
215 // 改自:Mat.GetImagePointer1(mImage, out ptr, out matType, out width, out height); // ptr=2157902018096 cType=byte width=830 height=822
216 ptr = mats[0].Data; // 取灰度图值
217 matType = mImage.GetType(); // byte
218 height = mImage.Rows; // 高
219 width = mImage.Cols; // 宽
220
221 // 改自:hImage = new HObject(new OpenCvSharp.Size(width, height), MatType.CV_8UC1, new Scalar(0));
222 byte[] dataGrayScaleImage = new byte[width * height]; //Mat dataGrayScaleImage = new Mat(new OpenCvSharp.Size(width, height), MatType.CV_8UC1);
223
224 unsafe
225 {
226 fixed (byte* ptrdata = dataGrayScaleImage)
227 {
228 #region 按行复制
229 //for (int i = 0; i < height; i++)
230 //{
231 // CopyMemory((IntPtr)(ptrdata + width * i), new IntPtr((long)ptr + width * i), width);
232 //}
233 #endregion
234
235 CopyMemory((IntPtr)ptrdata, new IntPtr((long)ptr), width * height);
236 HOperatorSet.GenImage1(out hImage, "byte", width, height, (IntPtr)ptrdata);
237
238 }
239 }
240 return hImage;
241 }
242 else if (matChannels == 3) // 三通道
243 {
244 IntPtr ptrRed; // R通道图
245 IntPtr ptrGreen; // G通道图
246 IntPtr ptrBlue; // B通道图
247 Mat[] mats =mImage.Split();
248
249 ptrRed = mats[0].Data; // 取R通道值
250 ptrGreen = mats[1].Data; // 取G通道值
251 ptrBlue = mats[2].Data; // 取B通道值
252 matType = mImage.GetType(); // 类型
253 height = mImage.Rows; // 高
254 width = mImage.Cols; // 宽
255
256 // 改自:hImage = new HObject(new OpenCvSharp.Size(width, height), MatType.CV_8UC1, new Scalar(0));
257 byte[] dataRed = new byte[width * height]; //Mat dataGrayScaleImage = new Mat(new OpenCvSharp.Size(width, height), MatType.CV_8UC1);
258 byte[] dataGreen = new byte[width * height];
259 byte[] dataBlue = new byte[width * height];
260
261 unsafe
262 {
263 fixed (byte* ptrdataRed = dataRed , ptrdataGreen = dataGreen, ptrdataBlue = dataBlue)
264 {
265 #region 按行复制
266 //HImage himg = new HImage("byte", width, height, (IntPtr)ptrdataRed);
267 //for (int i = 0; i < height; i++)
268 //{
269 // CopyMemory((IntPtr)(ptrdataRed + width * i), new IntPtr((long)ptrRed + width * i), width);
270 // CopyMemory((IntPtr)(ptrdataGreen + width * i), new IntPtr((long)ptrGreen + width * i), width);
271 // CopyMemory((IntPtr)(ptrdataBlue + width * i), new IntPtr((long)ptrBlue + width * i), width);
272 //}
273 #endregion
274
275 CopyMemory((IntPtr)ptrdataRed , new IntPtr((long)ptrRed ), width* height); // 复制R通道
276 CopyMemory((IntPtr)ptrdataGreen , new IntPtr((long)ptrGreen ), width* height); // 复制G通道
277 CopyMemory((IntPtr)ptrdataBlue , new IntPtr((long)ptrBlue ), width* height); // 复制B通道
278 HOperatorSet.GenImage3(out hImage, "byte", width, height, (IntPtr)ptrdataRed, (IntPtr)ptrdataGreen, (IntPtr)ptrdataBlue); // 合成
279 }
280 }
281 return hImage;
282 }
283 else
284 {
285 return null;
286 }
287 }
288 catch (Exception ex)
289 {
290 throw ex;
291 }
292
293 }
294
295 /// <summary>
296 /// 灰度图像 HObject -> HImage
297 /// </summary>
298 public HImage HObjectToHImage1(HObject hObj)
299 {
300 HImage image = new HImage();
301 HTuple type, width, height, pointer;
302 HOperatorSet.GetImagePointer1(hObj, out pointer, out type, out width, out height);
303 image.GenImage1(type, width, height, pointer);
304 return image;
305 }
306
307 /// <summary>
308 /// 彩色图像 HObject -> HImage
309 /// </summary>
310 public HImage HObjectToHImage3(HObject hObj)
311 {
312 HImage image = new HImage();
313 HTuple type, width, height, pointerRed, pointerGreen, pointerBlue;
314 HOperatorSet.GetImagePointer3(hObj, out pointerRed, out pointerGreen, out pointerBlue,
315 out type, out width, out height);
316 image.GenImage3(type, width, height, pointerRed, pointerGreen, pointerBlue);
317 return image;
318 }
319 }
320 }
四,C#MAT图像与C++MAT图像的相互传输
C++:
dlltest.h

1 #pragma once
2 // dlltest.h 头文件,使用动态链接库时需要包含头文件
3
4 #ifdef __DLLEXPORT
5 #define __DLL_EXP _declspec(dllexport) // 导出函数 - 生成dll文件时使用
6 #else
7 #define __DLL_EXP _declspec(dllimport) // 导入函数 -使用dll是使用
8 #endif // __DLLEXPORT
9
10 // 判断是否是c++
11 #if defined(__cplusplus)||defined(c_plusplus)
12 extern "C"
13 {
14 #endif
15 __DLL_EXP int add(int a, int b);
16 __DLL_EXP int sub(int a, int b);
17
18 #if defined(__cplusplus)||defined(c_plusplus)
19 }
20 #endif
dlltest.cpp

1 #include "pch.h"
2 #include "dlltest.h"
3 #include<stdio.h>
4
5 int add(int a, int b)
6 {
7 return a + b;
8 }
9
10 int sub(int a, int b)
11 {
12 return a - b;
13 }
CppMatToCsMat.h

1 #pragma once
2 #include "pch.h"
3 #include<vector>
4 #include <opencv2/opencv.hpp> //头文件
5 using namespace cv; //包含cv命名空间
6 using namespace std;
7 // 头文件,使用动态链接库时需要包含头文件
8
9 #ifdef __DLLEXPORT
10 #define __DLL_EXP _declspec(dllexport) // 导出函数 - 生成dll文件时使用
11 #else
12 #define __DLL_EXP _declspec(dllimport) // 导入函数 -使用dll是使用
13 #endif // __DLLEXPORT
14
15 // 判断是否是c++
16 #if defined(__cplusplus)||defined(c_plusplus)
17 extern "C"
18 {
19 #endif
20 //__DLL_EXP void Getmat(uchar *data, size_t &size);
21 __DLL_EXP extern "C" _declspec(dllexport) void Getmat(uchar *data, size_t &size);
22 #if defined(__cplusplus)||defined(c_plusplus)
23 }
24 #endif
CppMatToCsMat.cpp

1 #include "pch.h"
2 #include "CppMatToCsMat.h"
3 #include<vector>
4 #include <opencv2/opencv.hpp> //头文件
5 using namespace cv; //包含cv命名空间
6 using namespace std;
7
8 //void Getmat(uchar *data, size_t &size)
9 extern "C" _declspec(dllexport) void Getmat(uchar *data, size_t &size)
10 {
11 vector<uchar>buf; // uchar数组
12 Mat img = imread("C:\\2.jpg"); // 读入图片
13 imencode(".bmp", img, buf); // 将Mat以BMP格式存入uchar的buf数组中
14 size = buf.size();
15 for (auto it = buf.begin(); it != buf.end(); it++) // 将buf拷贝到C#的byte[]中
16 {
17 *data = *it;
18 data++;
19 }
20 }
CsMatToCppMat.h

1 #pragma once
2 #include "pch.h"
3 #include<vector>
4 #include <opencv2/opencv.hpp> //头文件
5 using namespace cv; //包含cv命名空间
6 using namespace std;
7 // 头文件,使用动态链接库时需要包含头文件
8
9 #ifdef __DLLEXPORT
10 #define __DLL_EXP _declspec(dllexport) // 导出函数 - 生成dll文件时使用
11 #else
12 #define __DLL_EXP _declspec(dllimport) // 导入函数 -使用dll是使用
13 #endif // __DLLEXPORT
14
15 // 判断是否是c++
16 #if defined(__cplusplus)||defined(c_plusplus)
17 extern "C"
18 {
19 #endif
20 __DLL_EXP extern "C" _declspec(dllexport) const void Setmat(long* ImageBuffer, int imageWedth, int imageHeigth);
21 #if defined(__cplusplus)||defined(c_plusplus)
22 }
23 #endif
CsMatToCppMat.cpp

1 #include "pch.h"
2 #include "CsMatToCppMat.h"
3 #include<vector>
4 #include <opencv2/opencv.hpp> //头文件
5 using namespace cv; //包含cv命名空间
6 using namespace std;
7
8 // CsMat->CppMat
9 extern "C" _declspec(dllexport) const void Setmat(long* ImageBuffer, int imageWedth, int imageHeigth)
10 {
11 Mat compMat = Mat(imageHeigth, imageWedth, CV_8UC3, ImageBuffer);
12 // 在窗口中显示载入的图片
13 imshow("[载入的图片]", compMat);
14 // 等待6000 ms后窗口自动关闭
15 //waitKey(6000);
16
17 }
CsMatToCppMatToCsMat.h

1 #pragma once
2 #include "pch.h"
3 #include<vector>
4 #include <opencv2/opencv.hpp> //头文件
5 using namespace cv; //包含cv命名空间
6 using namespace std;
7 // 头文件,使用动态链接库时需要包含头文件
8
9 #ifdef __DLLEXPORT
10 #define __DLL_EXP _declspec(dllexport) // 导出函数 - 生成dll文件时使用
11 #else
12 #define __DLL_EXP _declspec(dllimport) // 导入函数 -使用dll是使用
13 #endif // __DLLEXPORT
14
15 // 判断是否是c++
16 #if defined(__cplusplus)||defined(c_plusplus)
17 extern "C"
18 {
19 #endif
20 __DLL_EXP extern "C" _declspec(dllexport) const void SetmatRuselt(long* ImageBuffer, int imageHeigth, int imageWidth, long* &imgdata, int &imgHeigth, int &imgWidth);
21 #if defined(__cplusplus)||defined(c_plusplus)
22 }
23 #endif
CsMatToCppMatToCsMat.cpp

1 #include "pch.h"
2 #include "CsMatToCppMatToCsMat.h"
3 #include<vector>
4 #include <opencv2/opencv.hpp> //头文件
5 using namespace cv; //包含cv命名空间
6 using namespace std;
7
8 // CsMat->CppMat->CsMat
9 extern "C" _declspec(dllexport) const void SetmatRuselt(long* ImageBuffer, int imageHeigth, int imageWidth, long* &imgdata,int &imgHeigth,int &imgWidth)
10 {
11 Mat compMat = Mat(imageHeigth, imageWidth, CV_8UC3, ImageBuffer);
12 //// 在窗口中显示载入的图片
13 //imshow("[载入的图片1]", compMat);
14 //// 等待6000 ms后窗口自动关闭
15 //waitKey(6000);
16
17 //返回值
18 imgdata = (long*)compMat.data;
19 imgHeigth = compMat.rows;
20 imgWidth = compMat.cols;
21
22 //Mat compMat2 = Mat(imageHeigth, imageWidth, CV_8UC3, imgdata);
23 //// 在窗口中显示载入的图片
24 //imshow("[载入的图片2]", compMat2);
25 //// 等待6000 ms后窗口自动关闭
26 //waitKey(6000);
27 }
CsMatToCppMatToCsMat新,解决“指向内存不对“的问题

//long* ImageBuffer, int imageWedth, int imageHeigth
extern "C" _declspec(dllexport) const char* ocrmain(long* ImageBuffer, int imageHeigth, int imageWidth) {
//获取mat
cv::Mat srcimg = cv::Mat(imageHeigth, imageWidth, CV_8UC3, ImageBuffer);
//返回值
....// img.data是处理完的新图片通道数据
memcpy(ImageBuffer, img.data, sizeof(unsigned char)*img.rows*img.cols*img.channels());
const char* test;
test = "1";
return test;
}
//返回值
memcpy(ImageBuffer, img.data, sizeof(unsigned char)*img.rows*img.cols*img.channels());
const char* test;
test = ocrstring.c_str();
return test;
C#:

1 using Bll.SaveImage;
2 using OpenCvSharp;
3 using System;
4 using System.Collections.Generic;
5 using System.ComponentModel;
6 using System.Data;
7 using System.Drawing;
8 using System.IO;
9 using System.Linq;
10 using System.Runtime.InteropServices;
11 using System.Text;
12 using System.Threading.Tasks;
13 using System.Windows.Forms;
14
15 namespace repo.素材
16 {
17 public partial class 调用dll : Form
18 {
19 /// <summary>
20 /// 把source指针长度为size的数据复制到指针dest
21 /// </summary>
22 /// <param name="dest">要复制到的地址</param>
23 /// <param name="source">复制源的地址</param>
24 /// <param name="size">复制的长度</param>
25 /// <returns></returns>
26 [DllImport("kernel32.dll")]
27 public extern static long CopyMemory(IntPtr dest, IntPtr source, int size);
28
29 HObjectMatBitmap hObjectMatBitmap21 = new HObjectMatBitmap();
30
31 // OpencvSharp的图片显示类
32 SharpWindows Imgwindow;
33 // 全局图片Mat类型
34 Mat rawimg;
35 public 调用dll()
36 {
37 InitializeComponent();
38 // 委托
39 Imgwindow = new SharpWindows(this.pic_ShowPic, "MainUIwindow");
40 }
41
42 // OCR接口
43 [DllImport("main.dll")]
44 //public extern static void ocrmainone();
45 public extern static IntPtr ocrmain(IntPtr ImageBuffer, int imageHeigth, int imageWidth);
46 private void button1_Click(object sender, EventArgs e)
47 {
48 //try
49 //{
50 var filename = OpenfileDlg();
51 if (filename != null && filename != "")
52 {
53 Mat img = Cv2.ImRead(filename);
54 // 图像数据,高度,宽度
55 IntPtr ptr1 = img.Data;
56 int imageHeigth = img.Height;
57 int imageWidth = img.Width;
58
59 IntPtr ocrStr =ocrmain(new IntPtr((long)ptr1), imageHeigth, imageWidth);
60 Mat lam = new Mat(imageHeigth, imageWidth, MatType.CV_8UC3, ptr1);
61 Cv2.ImShow("[载入的图片3]", lam);
62 Imgwindow.Showimg(lam);
63 string myString = Marshal.PtrToStringAnsi(ocrStr);
64
65 #region 转换为utf-8编码
66 byte[] bytes = Encoding.Default.GetBytes(myString);
67 string myStringr = Encoding.UTF8.GetString(bytes);
68 #endregion
69
70 textBox2.Text = myStringr;
71 }
72 //}
73 //catch (Exception ex)
74 //{
75 // throw (ex);
76 //}
77 }
78
79 #region 通过路径取文件方法
80 /// <summary>
81 /// 通过路径取文件方法
82 /// </summary>
83 /// <param name="Defaultpath"></param>
84 /// <returns></returns>
85 private static string OpenfileDlg(string Defaultpath = null)
86 {
87 OpenFileDialog ofd = new OpenFileDialog();
88 ofd.Title = "请选择要打开的文件";
89 //多选
90 ofd.Multiselect = true;
91 //初始目录
92 ofd.InitialDirectory = Defaultpath;
93 //设定文件类型
94 // ofd.Filter = "*.bmp | *.jpg";
95
96 ofd.ShowDialog();
97
98 //获得在打开文件对话框中选择的文件的路径
99 string path = ofd.FileName;
100 return path;
101 }
102 #endregion
103
104 // 求和
105 [DllImport("main.dll")]
106 public extern static int sum(int a, int b);
107 private void button3_Click(object sender, EventArgs e)
108 {
109 int i1 = 10, i2 = 5;
110 int sumruselt = sum(i1, i2);
111 txt1.Text = sumruselt.ToString();
112 }
113
114 [DllImport("Dlltext.dll")]
115 /// <summary>
116 /// Dlltext
117 /// </summary>
118 /// <param name="data"></param>
119 /// <param name=""> </param>
120 /// <param name=""> </param>
121 /// <returns></returns>
122 public extern static int add(int a, int b);
123 private void button4_Click(object sender, EventArgs e)
124 {
125 int iruselt = add(3, 5);
126 textBox1.Text = iruselt.ToString();
127 }
128
129 // CppMat->CsMat
130 [DllImport("Dlltext.dll")]
131 public static extern void Getmat(ref byte data, out ulong size);
132 private void button5_Click(object sender, EventArgs e)
133 {
134 //Mat Csmat;
135 //byte[] imgdata = new byte[width * height * 3]; // 存储图片的数组,必须大于等于图片分辨率
136 byte[] imgdata = new byte[3200 * 2400 * 3]; // 存储图片的数组,必须大于等于图片分辨率
137 ulong size = new ulong();
138 Getmat(ref imgdata[0], out size);
139 textBox1.Text = size.ToString();
140 pictureBox1.Image = Image.FromStream(new MemoryStream(imgdata, 0, (int)size));
141 }
142
143 // CsMat->CppMat
144 [DllImport("Dlltext.dll")]
145 public static extern void Setmat(IntPtr bytedata, int width, int height);
146 private void button6_Click(object sender, EventArgs e)
147 {
148 // 图像实际宽度,高度
149 int imgWidth, imgHeigth;
150 //try
151 //{
152 var filename = OpenfileDlg();
153 if (filename != null && filename != "")
154 {
155 // 读取Mat图像
156 Mat img = Cv2.ImRead(filename);
157
158 // 取高宽
159 imgWidth = img.Width;
160 imgHeigth = img.Height;
161 IntPtr ptr1 = img.Data;
162
163 //Mat lacalMat = new Mat(imgHeigth, imgWidth, MatType.CV_8UC3, ptr1);
164 //Cv2.ImShow("[载入的图片]", lacalMat);
165 Setmat(new IntPtr((long)ptr1), imgWidth, imgHeigth);
166 }
167 //}
168 //catch (Exception ex)
169 //{
170 // throw (ex);
171 //}
172 }
173
174 // CsMat->CppMat->CsMat
175 [DllImport("Dlltext.dll")]
176 public static extern void SetmatRuselt(IntPtr ImageBuffer, int imageHeigth, int imageWidth, out IntPtr imgdata, out int imgHeigth, out int imgWidth);
177 private void button7_Click(object sender, EventArgs e)
178 {
179 try
180 {
181 var filename = OpenfileDlg();
182 if (filename != null && filename != "")
183 {
184 // 读取Mat图像
185 Mat img = Cv2.ImRead(filename);
186
187 // 图像数据,高度,宽度
188 IntPtr ptr1 = img.Data;
189 int imageHeigth = img.Height;
190 int imageWidth = img.Width;
191
192 SetmatRuselt(new IntPtr((long)ptr1), imageHeigth, imageWidth, out IntPtr imgdata, out int imgHeigth, out int imgWidth);
193
194 Mat lacalMatr = new Mat(imgHeigth, imgWidth, MatType.CV_8UC3, imgdata);
195 Cv2.ImShow("[载入的图片3]", lacalMatr);
196 }
197 }
198 catch (Exception ex)
199 {
200 throw (ex);
201 }
202 }
203
204 private void button2_Click(object sender, EventArgs e)
205 {
206
207 }
208 // CsMat->CppMat->CsMat
209 [DllImport("main.dll")]
210 public static extern void SetmatRuselt1(IntPtr ImageBuffer, int imageHeigth, int imageWidth, out IntPtr imgdata, out int imgHeigth, out int imgWidth);
211 private void button8_Click(object sender, EventArgs e)
212 {
213 var filename = OpenfileDlg();
214 if (filename != null && filename != "")
215 {
216 // 读取Mat图像
217 Mat img = Cv2.ImRead(filename);
218
219 // 图像数据,高度,宽度
220 IntPtr ptr1 = img.Data;
221 int imageHeigth = img.Height;
222 int imageWidth = img.Width;
223
224 SetmatRuselt1(new IntPtr((long)ptr1), imageHeigth, imageWidth, out IntPtr imgdata, out int imgHeigth, out int imgWidth);
225
226 Mat lacalMatr = new Mat(imgHeigth, imgWidth, MatType.CV_8UC3, imgdata);
227 Cv2.ImShow("[载入的图片3]", lacalMatr);
228 }
229 }
230 }
231 }
本文来自博客园,作者:꧁执笔小白꧂,转载请注明原文链接:https://www.cnblogs.com/qq2806933146xiaobai/p/13299193.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏
· Manus爆火,是硬核还是营销?