DHDHSoftware/DH.Commons/Helper/OpenCVEngineTool.cs
2025-03-18 14:20:11 +08:00

725 lines
30 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using HalconDotNet;
using OpenCvSharp;
using OpenCvSharp.Extensions;
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace DH.Commons.Enums
{
public class OpenCVEngineTool : IDisposable
{
public void Dispose()
{
throw new NotImplementedException();
}
}
public static class OpenCVHelper
{
[DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)]
public static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);
public static byte[] MatToBytes(this Mat image)
{
if (image != null && image.Data != IntPtr.Zero)
{
int size = (int)(image.Total() * image.ElemSize());
byte[] bytes = new byte[size];
Marshal.Copy(image.Data, bytes, 0, size);
return bytes;
}
else
{
return null;
}
}
/// <summary>
/// 获取水平拼接图片
/// </summary>
/// <param name="map1"></param>
/// <param name="map2"></param>
/// <returns></returns>
public static Bitmap GetHConcatImage(Bitmap map1, Bitmap map2)
{
var mat1 = map1.ToMat();
var mat2 = map2.ToMat();
//var maxChannel = Math.Max(mat1.Channels(), mat2.Channels());
//var maxType = Math.Max(mat1.Type(), mat2.Type());
//转通道数
mat1 = mat1.CvtColor(ColorConversionCodes.GRAY2BGRA);
//转位深
mat1.ConvertTo(mat1, mat2.Type());
Mat resMat = new Mat(mat1.Height, mat1.Width, mat2.Type(), new Scalar(0));
Cv2.HConcat(mat1, mat2, resMat);
mat1.Dispose();
mat2.Dispose();
return resMat.ToBitmap();
}
public static Mat To3Channels(this Mat img)
{
if (img == null)
return null;
Mat resMat = new Mat(img.Rows, img.Cols, MatType.CV_8UC3);
Mat[] channels = new Mat[3]; ;
for (int i = 0; i < 3; i++)
{
channels[i] = img;
}
Cv2.Merge(channels, resMat);
img.Dispose();
img = null;
return resMat;
}
/// <summary>
/// 把OpenCV图像转换到Halcon图像
/// </summary>
/// <param name="mImage">OpenCV图像_Mat</param>
/// <returns>Halcon图像_HObject</returns>
public static HObject MatToHImage(Mat mImage)
{
try
{
HObject hImage;
int matChannels = 0; // 通道数
Type matType = null;
uint width, height; // 宽,高
width = height = 0; // 宽,高初始化
// 获取通道数
matChannels = mImage.Channels();
if (matChannels == 0)
{
return null;
}
if (matChannels == 1) // 单通道
//if (true) // 单通道
{
IntPtr ptr; // 灰度图通道
Mat[] mats = mImage.Split();
// 改自Mat.GetImagePointer1(mImage, out ptr, out matType, out width, out height); // ptr=2157902018096 cType=byte width=830 height=822
ptr = mats[0].Data; // 取灰度图值
matType = mImage.GetType(); // byte
height = (uint)mImage.Rows; // 高
width = (uint)mImage.Cols; // 宽
// 改自hImage = new HObject(new OpenCvSharp.Size(width, height), MatType.CV_8UC1, newScalar(0));
byte[] dataGrayScaleImage = new byte[width * height]; //Mat dataGrayScaleImage = new Mat(new OpenCvSharp.Size(width, height), MatType.CV_8UC1);
unsafe
{
fixed (byte* ptrdata = dataGrayScaleImage)
{
#region
//for (int i = 0; i < height; i++)
//{
// CopyMemory((IntPtr)(ptrdata + width * i), new IntPtr((long)ptr + width *i), width);
//}
#endregion
CopyMemory((IntPtr)ptrdata, new IntPtr((long)ptr), width * height);
HOperatorSet.GenImage1(out hImage, "byte", width, height, (IntPtr)ptrdata);
}
}
return hImage;
}
else if (matChannels == 3) // 三通道
{
IntPtr ptrRed; // R通道图
IntPtr ptrGreen; // G通道图
IntPtr ptrBlue; // B通道图
Mat[] mats = mImage.Split();
ptrRed = mats[0].Data; // 取R通道值
ptrGreen = mats[1].Data; // 取G通道值
ptrBlue = mats[2].Data; // 取B通道值
matType = mImage.GetType(); // 类型
height = (uint)mImage.Rows; // 高
width = (uint)mImage.Cols; // 宽
// 改自hImage = new HObject(new OpenCvSharp.Size(width, height), MatType.CV_8UC1, new Scalar(0));
byte[] dataRed = new byte[width * height]; //Mat dataGrayScaleImage = new Mat(new OpenCvSharp.Size(width, height), MatType.CV_8UC1);
byte[] dataGreen = new byte[width * height];
byte[] dataBlue = new byte[width * height];
unsafe
{
fixed (byte* ptrdataRed = dataRed, ptrdataGreen = dataGreen, ptrdataBlue = dataBlue)
{
#region
//HImage himg = new HImage("byte", width, height, (IntPtr)ptrdataRed);
//for (int i = 0; i < height; i++)
//{
// CopyMemory((IntPtr)(ptrdataRed + width * i), new IntPtr((long)ptrRed +width * i), width);
// CopyMemory((IntPtr)(ptrdataGreen + width * i), new IntPtr((long)ptrGreen+ width * i), width);
// CopyMemory((IntPtr)(ptrdataBlue + width * i), new IntPtr((long)ptrBlue +width * i), width);
//}
#endregion
CopyMemory((IntPtr)ptrdataRed, new IntPtr((long)ptrRed), width* height); // 复制R通道
CopyMemory((IntPtr)ptrdataGreen, new IntPtr((long)ptrGreen), width * height); // 复制G通道
CopyMemory((IntPtr)ptrdataBlue, new IntPtr((long)ptrBlue), width * height); // 复制B通道
HOperatorSet.GenImage3(out hImage, "byte", width, height, (IntPtr)ptrdataRed, (IntPtr)ptrdataGreen, (IntPtr)ptrdataBlue); // 合成
}
}
return hImage;
}
else
{
return null;
}
}
catch (Exception ex)
{
throw ex;
}
}
public static Mat HImageToMat(this HObject hobj)
{
try
{
Mat mImage;
HTuple htChannels;
HTuple cType = null;
HTuple width, height;
width = height = 0;
HOperatorSet.CountChannels(hobj, out htChannels);
if (htChannels.Length == 0)
{
return null;
}
if (htChannels[0].I == 1)
{
HTuple ptr;
HOperatorSet.GetImagePointer1(hobj, out ptr, out cType, out width, out height);
mImage = new Mat(height, width, MatType.CV_8UC1, new Scalar(0));
unsafe
{
CopyMemory(mImage.Data, new IntPtr((byte*)ptr.IP), (uint)(width * height));// CopyMemory要复制到的地址复制源的地址复制的长度
}
return mImage;
}
else if (htChannels[0].I == 3)
{
HTuple ptrRed;
HTuple ptrGreen;
HTuple ptrBlue;
HOperatorSet.GetImagePointer3(hobj, out ptrRed, out ptrGreen, out ptrBlue, out cType, out width, out height);
Mat pImageRed = new Mat(new OpenCvSharp.Size(width, height), MatType.CV_8UC1);
Mat pImageGreen = new Mat(new OpenCvSharp.Size(width, height), MatType.CV_8UC1);
Mat pImageBlue = new Mat(new OpenCvSharp.Size(width, height), MatType.CV_8UC1);
mImage = new Mat(new OpenCvSharp.Size(width, height), MatType.CV_8UC3, new Scalar(0, 0, 0));
unsafe
{
CopyMemory(pImageRed.Data, new IntPtr((byte*)ptrRed.IP), (uint)(width * height)); // CopyMemory要复制到的地址复制源的地址复制的长度_Red
CopyMemory(pImageGreen.Data, new IntPtr((byte*)ptrGreen.IP), (uint)(width * height)); // CopyMemory要复制到的地址复制源的地址复制的长度_Green
CopyMemory(pImageBlue.Data, new IntPtr((byte*)ptrBlue.IP), (uint)(width * height)); // CopyMemory要复制到的地址复制源的地址复制的长度_Blue
}
Mat[] multi = new Mat[] { pImageBlue, pImageGreen, pImageRed };
Cv2.Merge(multi, mImage);
pImageRed.Dispose();
pImageRed = null;
pImageGreen.Dispose();
pImageGreen = null;
pImageBlue.Dispose();
pImageBlue = null;
return mImage;
}
else
{
return null;
}
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// 从内存流中指定位置,读取数据
/// </summary>
/// <param name="curStream"></param>
/// <param name="startPosition"></param>
/// <param name="length"></param>
/// <returns></returns>
public static int ReadData(MemoryStream curStream, int startPosition, int length)
{
int result = -1;
byte[] tempData = new byte[length];
curStream.Position = startPosition;
curStream.Read(tempData, 0, length);
result = BitConverter.ToInt32(tempData, 0);
return result;
}
/// <summary>
/// 使用byte[]数据,生成三通道 BMP 位图
/// </summary>
/// <param name="originalImageData"></param>
/// <param name="originalWidth"></param>
/// <param name="originalHeight"></param>
/// <returns></returns>
public static Bitmap CreateColorBitmap(byte[] originalImageData, int originalWidth, int originalHeight, byte[] color_map)
{
// 指定8位格式即256色
Bitmap resultBitmap = new Bitmap(originalWidth, originalHeight, System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
// 将该位图存入内存中
MemoryStream curImageStream = new MemoryStream();
resultBitmap.Save(curImageStream, System.Drawing.Imaging.ImageFormat.Bmp);
curImageStream.Flush();
// 由于位图数据需要DWORD对齐4byte倍数计算需要补位的个数
int curPadNum = ((originalWidth * 8 + 31) / 32 * 4) - originalWidth;
// 最终生成的位图数据大小
int bitmapDataSize = ((originalWidth * 8 + 31) / 32 * 4) * originalHeight;
// 数据部分相对文件开始偏移,具体可以参考位图文件格式
int dataOffset = ReadData(curImageStream, 10, 4);
// 改变调色板因为默认的调色板是32位彩色的需要修改为256色的调色板
int paletteStart = 54;
int paletteEnd = dataOffset;
int color = 0;
for (int i = paletteStart; i < paletteEnd; i += 4)
{
byte[] tempColor = new byte[4];
tempColor[0] = (byte)color;
tempColor[1] = (byte)color;
tempColor[2] = (byte)color;
tempColor[3] = (byte)0;
color++;
curImageStream.Position = i;
curImageStream.Write(tempColor, 0, 4);
}
// 最终生成的位图数据,以及大小,高度没有变,宽度需要调整
byte[] destImageData = new byte[bitmapDataSize];
int destWidth = originalWidth + curPadNum;
// 生成最终的位图数据,注意的是,位图数据 从左到右,从下到上,所以需要颠倒
for (int originalRowIndex = originalHeight - 1; originalRowIndex >= 0; originalRowIndex--)
{
int destRowIndex = originalHeight - originalRowIndex - 1;
for (int dataIndex = 0; dataIndex < originalWidth; dataIndex++)
{
// 同时还要注意新的位图数据的宽度已经变化destWidth否则会产生错位
destImageData[destRowIndex * destWidth + dataIndex] = originalImageData[originalRowIndex * originalWidth + dataIndex];
}
}
// 将流的Position移到数据段
curImageStream.Position = dataOffset;
// 将新位图数据写入内存中
curImageStream.Write(destImageData, 0, bitmapDataSize);
curImageStream.Flush();
// 将内存中的位图写入Bitmap对象
resultBitmap = new Bitmap(curImageStream);
resultBitmap = Convert8to24(resultBitmap, color_map); // 转为3通道图像
return resultBitmap;
}
// 实现单通道到多通道
public static Bitmap Convert8to24(this Bitmap bmp, byte[] color_map)
{
Rectangle rect = new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bitmapData = bmp.LockBits(rect, ImageLockMode.ReadOnly, bmp.PixelFormat);
//计算实际8位图容量
int size8 = bitmapData.Stride * bmp.Height;
byte[] grayValues = new byte[size8];
//// 申请目标位图的变量,并将其内存区域锁定
Bitmap TempBmp = new Bitmap(bmp.Width, bmp.Height, PixelFormat.Format24bppRgb);
BitmapData TempBmpData = TempBmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
//// 获取图像参数以及设置24位图信息
int stride = TempBmpData.Stride; // 扫描线的宽度
int offset = stride - TempBmp.Width; // 显示宽度与扫描线宽度的间隙
IntPtr iptr = TempBmpData.Scan0; // 获取bmpData的内存起始位置
int scanBytes = stride * TempBmp.Height;// 用stride宽度表示这是内存区域的大小
// 下面把原始的显示大小字节数组转换为内存中实际存放的字节数组
byte[] pixelValues = new byte[scanBytes]; //为目标数组分配内存
System.Runtime.InteropServices.Marshal.Copy(bitmapData.Scan0, grayValues, 0, size8);
for (int i = 0; i < bmp.Height; i++)
{
for (int j = 0; j < bitmapData.Stride; j++)
{
if (j >= bmp.Width)
continue;
int indexSrc = i * bitmapData.Stride + j;
int realIndex = i * TempBmpData.Stride + j * 3;
// color_id就是预测出来的结果
int color_id = (int)grayValues[indexSrc] % 256;
if (color_id == 0) // 分割中类别1对应值1而背景往往为0因此这里就将背景置为[0, 0, 0]
{
// 空白
pixelValues[realIndex] = 0;
pixelValues[realIndex + 1] = 0;
pixelValues[realIndex + 2] = 0;
}
else
{
// 替换为color_map中的颜色值
pixelValues[realIndex] = color_map[color_id * 3];
pixelValues[realIndex + 1] = color_map[color_id * 3 + 1];
pixelValues[realIndex + 2] = color_map[color_id * 3 + 2];
}
}
}
//Parallel.For(0, width * height, i =>
// {
// int index = (i + 1) % width + widthIn4 * ((i + 1) / width) - 1;
// showBitmapBuffer[index * 6] = bitmapBuffer[index * 6] = data[i * 2];
// showBitmapBuffer[index * 6 + 1] = bitmapBuffer[index * 6 + 1] = data[i * 2 + 1];
// });
// 用Marshal的Copy方法将刚才得到的内存字节数组复制到BitmapData中
Marshal.Copy(pixelValues, 0, iptr, scanBytes);
TempBmp.UnlockBits(TempBmpData); // 解锁内存区域
bmp.UnlockBits(bitmapData);
return TempBmp;
}
// 获取伪彩色图的RGB值 -- 同时也是适用于检测框分类颜色
public static byte[] GetColorMap(int num_classes = 256)
{
num_classes += 1;
byte[] color_map = new byte[num_classes * 3];
for (int i = 0; i < num_classes; i++)
{
int j = 0;
int lab = i;
while (lab != 0)
{
color_map[i * 3] |= (byte)(((lab >> 0) & 1) << (7 - j));
color_map[i * 3 + 1] |= (byte)(((lab >> 1) & 1) << (7 - j));
color_map[i * 3 + 2] |= (byte)(((lab >> 2) & 1) << (7 - j));
j += 1;
lab >>= 3;
}
}
// 去掉底色
color_map = color_map.Skip(3).ToArray();
return color_map;
}
// GetGrayMap
public static byte[] GetGrayMap(int num_classes = 256)
{
byte[] color_map = new byte[num_classes];
for (int i = 0; i < num_classes; i++)
{
if (i <= 100)
color_map[i] = 0;
if (i > 100 && i <= 200)
color_map[i] = 100;
if (i > 200)
color_map[i] = 255;
}
return color_map;
}
/// <summary>
/// 像素点阵转换为bitmap 二值化图
/// </summary>
/// <param name="rawValues">byte[]数组</param>
/// <param name="width">图片的宽度</param>
/// <param name="height">图片的高度</param>
/// <returns>bitmap图片</returns>
public static Bitmap CreateBinaryBitmap(byte[] rawValues, int width, int height)
{
Bitmap bmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
BitmapData bmpData = bmp.LockBits(new System.Drawing.Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
//获取图像参数
int stride = bmpData.Stride; // 扫描线的宽度
int offset = stride - width; // 显示宽度与扫描线宽度的间隙
IntPtr iptr = bmpData.Scan0; // 获取bmpData的内存起始位置
int scanBytes = stride * height;// 用stride宽度表示这是内存区域的大小
//下面把原始的显示大小字节数组转换为内存中实际存放的字节数组
int posScan = 0, posReal = 0;// 分别设置两个位置指针,指向源数组和目标数组
byte[] pixelValues = new byte[scanBytes]; //为目标数组分配内存
for (int x = 0; x < height; x++)
{
//下面的循环节是模拟行扫描
for (int y = 0; y < width; y++)
{
pixelValues[posScan++] = rawValues[posReal++] == 0 ? (byte)0 : (byte)255;
}
posScan += offset; //行扫描结束,要将目标位置指针移过那段“间隙”
}
//用Marshal的Copy方法将刚才得到的内存字节数组复制到BitmapData中
Marshal.Copy(pixelValues, 0, iptr, scanBytes);
bmp.UnlockBits(bmpData); // 解锁内存区域
//下面的代码是为了修改生成位图的索引表,从伪彩修改为灰度
ColorPalette tempPalette;
using (Bitmap tempBmp = new Bitmap(1, 1, System.Drawing.Imaging.PixelFormat.Format8bppIndexed))
{
tempPalette = tempBmp.Palette;
}
for (int i = 0; i < 256; i++)
{
tempPalette.Entries[i] = System.Drawing.Color.FromArgb(i, i, i);
}
bmp.Palette = tempPalette;
return bmp;
}
/// <summary>
/// 像素点阵转换为bitmap灰度图
/// </summary>
/// <param name="rawValues">byte[]数组</param>
/// <param name="width">图片的宽度</param>
/// <param name="height">图片的高度</param>
/// <returns>bitmap图片</returns>
public static Bitmap CreateGrayBitmap(byte[] rawValues, int width, int height)
{
Bitmap bmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
BitmapData bmpData = bmp.LockBits(new System.Drawing.Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
//获取图像参数
int stride = bmpData.Stride; // 扫描线的宽度
int offset = stride - width; // 显示宽度与扫描线宽度的间隙
IntPtr iptr = bmpData.Scan0; // 获取bmpData的内存起始位置
int scanBytes = stride * height;// 用stride宽度表示这是内存区域的大小
//下面把原始的显示大小字节数组转换为内存中实际存放的字节数组
int posScan = 0, posReal = 0;// 分别设置两个位置指针,指向源数组和目标数组
byte[] pixelValues = new byte[scanBytes]; //为目标数组分配内存
for (int x = 0; x < height; x++)
{
//下面的循环节是模拟行扫描
for (int y = 0; y < width; y++)
{
pixelValues[posScan++] = rawValues[posReal++];
}
posScan += offset; //行扫描结束,要将目标位置指针移过那段“间隙”
}
//用Marshal的Copy方法将刚才得到的内存字节数组复制到BitmapData中
Marshal.Copy(pixelValues, 0, iptr, scanBytes);
bmp.UnlockBits(bmpData); // 解锁内存区域
//下面的代码是为了修改生成位图的索引表,从伪彩修改为灰度
ColorPalette tempPalette;
using (Bitmap tempBmp = new Bitmap(1, 1, System.Drawing.Imaging.PixelFormat.Format8bppIndexed))
{
tempPalette = tempBmp.Palette;
}
for (int i = 0; i < 256; i++)
{
tempPalette.Entries[i] = System.Drawing.Color.FromArgb(i, i, i);
}
bmp.Palette = tempPalette;
return bmp;
}
//分别基于像素GetPixel和SetPixel、基于内存、基于指针这三种方法增强图片对比度
// 第一种方法:像素提取法 速度慢 基于像素400-600ms
public static Bitmap MethodBaseOnPixel(Bitmap bitmap, int degree)
{
Color curColor;
int grayR, grayG, grayB;
double Deg = (100.0 + degree) / 100.0;
for (int i = 0; i < bitmap.Width; i++)
{
for (int j = 0; j < bitmap.Height; j++)
{
curColor = bitmap.GetPixel(i, j);
grayR = Convert.ToInt32((((curColor.R / 255.0 - 0.5) * Deg + 0.5)) * 255);
grayG = Convert.ToInt32((((curColor.G / 255.0 - 0.5) * Deg + 0.5)) * 255);
grayB = Convert.ToInt32((((curColor.B / 255.0 - 0.5) * Deg + 0.5)) * 255);
if (grayR < 0)
grayR = 0;
else if (grayR > 255)
grayR = 255;
if (grayB < 0)
grayB = 0;
else if (grayB > 255)
grayB = 255;
if (grayG < 0)
grayG = 0;
else if (grayG > 255)
grayG = 255;
bitmap.SetPixel(i, j, Color.FromArgb(grayR, grayG, grayB));
}
}
return bitmap;
}
// 第二种方法:基于内存 17-18ms
public static unsafe Bitmap MethodBaseOnMemory(Bitmap bitmap, int degree)
{
if (bitmap == null)
{
return null;
}
double Deg = (100.0 + degree) / 100.0;
int width = bitmap.Width;
int height = bitmap.Height;
int length = height * 3 * width;
byte[] RGB = new byte[length];
BitmapData data = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
System.IntPtr Scan0 = data.Scan0;
System.Runtime.InteropServices.Marshal.Copy(Scan0, RGB, 0, length);
double gray = 0;
for (int i = 0; i < RGB.Length; i += 3)
{
for (int j = 0; j < 3; j++)
{
gray = (((RGB[i + j] / 255.0 - 0.5) * Deg + 0.5)) * 255.0;
if (gray > 255)
gray = 255;
if (gray < 0)
gray = 0;
RGB[i + j] = (byte)gray;
}
}
System.Runtime.InteropServices.Marshal.Copy(RGB, 0, Scan0, length);// 此处Copy是之前Copy的逆操作
bitmap.UnlockBits(data);
return bitmap;
}
//第三种方法:基于指针 20-23ms
public static unsafe Bitmap MethodBaseOnPtr(Bitmap b, int degree)
{
if (b == null)
{
return null;
}
try
{
double num = 0.0;
double num2 = (100.0 + degree) / 100.0;
num2 *= num2;
int width = b.Width;
int height = b.Height;
BitmapData bitmapdata = b.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
byte* numPtr = (byte*)bitmapdata.Scan0;
int offset = bitmapdata.Stride - (width * 3);
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
for (int k = 0; k < 3; k++)
{
num = ((((((double)numPtr[k]) / 255.0) - 0.5) * num2) + 0.5) * 255.0;
if (num < 0.0)
{
num = 0.0;
}
if (num > 255.0)
{
num = 255.0;
}
numPtr[k] = (byte)num;
}
numPtr += 3;
}
numPtr += offset;
}
b.UnlockBits(bitmapdata);
return b;
}
catch
{
return b;
}
}
public static double GetRoiArae(Mat src, Rect rect)
{
//初始面积为0
double Area = 0;
//获取感兴趣区域
src = src[rect];
//转为单通道
Mat gray = src.CvtColor(ColorConversionCodes.BGR2GRAY);
//二值化
Mat binary = gray.Threshold(0, 255, ThresholdTypes.Otsu | ThresholdTypes.Binary);
//求轮廓 不连通会有多个闭合区域
OpenCvSharp.Point[][] contours;
HierarchyIndex[] hierarchy;
Cv2.FindContours(binary, out contours, out hierarchy, RetrievalModes.List, ContourApproximationModes.ApproxSimple);
for (int i = 0; i < contours.Count(); i++)
{
//所有面积相加
Area += Cv2.ContourArea(contours[i], false);
}
return Area;
}
}
}