private List<Mask> DrawSegmentationOnYoloCanvas(SKBitmap bitmap, List<Segmentation> results)
{
List<Mask> maskList = new List<Mask>();
// 生成新的SKImage
var newImage = SKImage.FromBitmap(bitmap);
// 创建一个新的 Bitmap 来绘制所有轮廓
using (var surface = SKSurface.Create(new SKImageInfo(bitmap.Width, bitmap.Height)))
{
var canvas = surface.Canvas;
// 绘制 newImage 到画布
canvas.DrawImage(newImage, new SKRect(0, 0, bitmap.Width, bitmap.Height));
// 设置画笔
using (var paint = new SKPaint())
{
paint.Style = SKPaintStyle.Stroke;
paint.StrokeWidth = 1;
// 遍历所有结果并绘制轮廓
foreach (var result in results)
{
Pixel[] pixels = result.SegmentedPixels;
// 创建一个与原图相同大小的透明图层
Mat overlay = new Mat(new OpenCvSharp.Size(bitmap.Width, bitmap.Height), MatType.CV_8UC4, Scalar.All(0));
// 在透明图层上绘制掩码
foreach (var pixel in pixels)
{
paint.Color = new SKColor(1, 1, 1, 66);
// 绘制每个像素的轮廓到 SkiaSharp 的 canvas
canvas.DrawPoint(pixel.X, pixel.Y, paint);
overlay.Circle(new OpenCvSharp.Point(pixel.X, pixel.Y), 1, new Scalar(0, 0, 255, 127), -1);
}
// 用梯度算法计算梯度
Mat laplacian = overlay.CvtColor(ColorConversionCodes.BGRA2GRAY).Laplacian(overlay.Type(), 1, 5).ConvertScaleAbs();
// 从梯度图查找轮廓
var contours = laplacian.FindContoursAsArray(RetrievalModes.External, ContourApproximationModes.ApproxSimple);
laplacian.Dispose();
// 最小外接矩形的宽度和高度和最大内接圆的半径
float width = 0;
float height = 0;
float rectWidth = 0;
float rectHeight = 0;
float radius = 0;
RotatedRect minRect = new RotatedRect();
SKPoint centerMask = new SKPoint(0, 0);
// 画最大内接圆和最小外接矩形
if (contours.Length > 0)
{
// 计算最小外接矩形
minRect = Cv2.MinAreaRect(contours[0]);
// 绘制外接矩形到 SkiaSharp 的 canvas
Point2f[] rectPoints = minRect.Points();
foreach (var point in rectPoints)
{
paint.Color = SKColors.Red;
// 绘制矩形的边界
canvas.DrawLine(point.X, point.Y, rectPoints[(Array.IndexOf(rectPoints, point) + 1) % 4].X, rectPoints[(Array.IndexOf(rectPoints, point) + 1) % 4].Y, paint);
}
// 获取矩形的宽度和高度
width = minRect.Size.Width;
height = minRect.Size.Height;
// 如果宽度大于高度,交换值以确保 width 是较小的边,height 是较大的边
if (width > height)
{
float temp = width;
width = height;
height = temp;
}
if (null != skglControlColor.Tag)
{
// 从画布 Tag 属性中取出深度相机数据帧
var skgData = skglControlColor.Tag as SkgData;
if (skgData != null)
{
// 获取深度数据值
var point0 = pxToImgMatrix.MapPoint(new SKPoint(rectPoints[0].X, rectPoints[0].Y));
int depthValue0 = GetDepthValueAtXY(skgData, (int)point0.X, (int)point0.Y);
var point1 = pxToImgMatrix.MapPoint(new SKPoint(rectPoints[1].X, rectPoints[1].Y));
int depthValue1 = GetDepthValueAtXY(skgData, (int)point1.X, (int)point1.Y);
var point2 = pxToImgMatrix.MapPoint(new SKPoint(rectPoints[2].X, rectPoints[2].Y));
int depthValue2 = GetDepthValueAtXY(skgData, (int)point2.X, (int)point2.Y);
double widthD = CalculateDistance(point0, depthValue0, point1, depthValue1);
double heightD = CalculateDistance(point1, depthValue1, point2, depthValue2);
// 判断小的值为w,大的值为h
if (widthD < heightD)
{
rectWidth = (float)widthD;
rectHeight = (float)heightD;
}
else
{
rectWidth = (float)heightD;
rectHeight = (float)widthD;
}
}
else
{
LogError("深度数据为空,无法计算掩码的长宽。");
}
}
// 计算轮廓的质心(几何中心)
Moments moments = Cv2.Moments(contours[0]);
OpenCvSharp.Point2f center = new OpenCvSharp.Point2f(
(float)(moments.M10 / moments.M00),
(float)(moments.M01 / moments.M00)
);
// 计算最大内接圆的半径
float minDistance = float.MaxValue;
foreach (var point in contours[0])
{
// 确保 point 是 Point2f 类型
OpenCvSharp.Point2f contourPoint = new OpenCvSharp.Point2f(point.X, point.Y);
// 手动计算点到质心的距离
float distance = (float)Math.Sqrt(Math.Pow(contourPoint.X - center.X, 2) + Math.Pow(contourPoint.Y - center.Y, 2));
if (distance < minDistance)
{
minDistance = distance;
}
}
// 限制最大内接圆的半径,确保圆完全位于最小外接矩形内
float maxRadius = width / 2;
minDistance = Math.Min(minDistance, maxRadius);
// 绘制最大内接圆到 SkiaSharp 的 canvas
paint.Color = SKColors.LightBlue;
canvas.DrawCircle(center.X, center.Y, minDistance, paint);
if (null != skglControlColor.Tag)
{
// 从画布 Tag 属性中取出深度相机数据帧
var skgData = skglControlColor.Tag as SkgData;
if (skgData != null)
{
// 和外接矩形同理,计算圆的半径
var pointCenter = pxToImgMatrix.MapPoint(new SKPoint(center.X, center.Y));
int depthValueCenter = GetDepthValueAtXY(skgData, (int)pointCenter.X, (int)pointCenter.Y);
// 算一个圆心正上方的一个圆上坐标
var pointC = pxToImgMatrix.MapPoint(new SKPoint(center.X, center.Y + minDistance));
int depthValueC = GetDepthValueAtXY(skgData, (int)pointC.X, (int)pointC.Y);
// 计算圆的半径
radius = (float)CalculateDistance(pointCenter, depthValueCenter, pointC, depthValueC);
}
else
{
LogError("深度数据为空,无法计算掩码的圆半径。");
}
}
// 在圆心处画上 分数result.Confidence
paint.Color = SKColors.Blue;
canvas.DrawText($"{result.Confidence * 100 :F0}", center.X + 5, center.Y - 5, paint);
centerMask = new SKPoint(center.X, center.Y);
}
// 画掩码轮廓
if (contours.Length > 0)
{
// 绘制轮廓边缘
foreach (var contour in contours)
{
// 创建一个完整的封闭轮廓
OpenCvSharp.Point[] contourAll = new OpenCvSharp.Point[contour.Length + 1];
// 遍历轮廓点并添加到 contourAll
for (int i = 0; i < contour.Length; i++)
{
contourAll[i] = new OpenCvSharp.Point(contour[i].X, contour[i].Y);
}
// 闭合轮廓,添加最后一个点到第一个点的连接
contourAll[contour.Length] = new OpenCvSharp.Point(contour[0].X, contour[0].Y);
// 绘制封闭轮廓
paint.Color = SKColors.LightGray;
for (int i = 0; i < contourAll.Length - 1; i++)
{
canvas.DrawLine(new SKPoint(contourAll[i].X, contourAll[i].Y), new SKPoint(contourAll[i + 1].X, contourAll[i + 1].Y), paint);
}
RotatedRect maxInscribedRect = GetMaxInscribedRectangle(contourAll);
//绘制最大内接矩形
Point2f[] maxRectPoints = maxInscribedRect.Points();
for (int j = 0; j < 4; j++)
{
paint.Color = SKColors.Yellow;
canvas.DrawLine(
maxRectPoints[j].X, maxRectPoints[j].Y,
maxRectPoints[(j + 1) % 4].X, maxRectPoints[(j + 1) % 4].Y,
paint
);
}
}
}
// 获取圆心,判断圆半径是否满足14mm以上的要求 吸嘴的圆半径是14mm
if (radius > fixedTempRadius)
{
// 创建一个新的Mask对象
var mask = new Mask
{
Name = result.Label.Name,
Iou = result.Confidence,
Cx = centerMask.X,
Cy = centerMask.Y,
X = 0,
Y = 0,
Area = 0,
Deep = 0, // 深度信息可以根据需要设置
MaskImage = null, // 可以根据需要生成对应的Mask图像
BoxArea = 0,
RectCenterX = 0,
RectCenterY = 0,
RectWidth = rectWidth,
RectHeight = rectHeight,
RectAngle = 0,
RectPoints = null
};
var (widthRatio, heightRatio) = GetMaskAspectRatios(mask, fixedTemplateWidth, fixedTemplateHeight);
// 判断尺寸范围是否在预设范围内
if (widthRatio >= ratioMin && widthRatio <= ratioMax &&
heightRatio >= ratioMin && heightRatio <= ratioMax)
{
maskList.Add(mask);
}
else
{
// 在圆心画一个“x”
paint.Color = SKColors.Red;
canvas.DrawLine(centerMask.X - 5, centerMask.Y - 5, centerMask.X + 5, centerMask.Y + 5, paint);
canvas.DrawLine(centerMask.X - 5, centerMask.Y + 5, centerMask.X + 5, centerMask.Y - 5, paint);
}
}
else
{
// 在圆心画一个“x”
paint.Color = SKColors.Red;
canvas.DrawLine(centerMask.X - 5, centerMask.Y - 5, centerMask.X + 5, centerMask.Y + 5, paint);
canvas.DrawLine(centerMask.X - 5, centerMask.Y + 5, centerMask.X + 5, centerMask.Y - 5, paint);
}
}
}
// 获取绘制后的图像作为 SKImage
var outlinedImage = surface.Snapshot();
newImage = outlinedImage;
}
// 绘制检测框和置信度
var resultImage = newImage.Draw(results);
// 生成一个完整的画面,置入到skglControlYolo的Tag中,调用Invalidate()方法来刷新画面
skglControlYolo.Tag = newImage;
skglControlYolo.Invalidate();
return maskList;
}
// 使用中心扩散法获取最大内接矩形
public RotatedRect GetMaxInscribedRectangle(OpenCvSharp.Point[] contour)
{
// 获取轮廓的最小外接矩形
RotatedRect minRect = Cv2.MinAreaRect(contour);
// 初始化最大内接矩形
RotatedRect maxInscribedRect = minRect;
// 获取最小外接矩形的中心点、角度和尺寸
Point2f center = minRect.Center;
float angle = minRect.Angle;
Size2f size = minRect.Size;
// 确保最长边是 height,最短边是 width
float width = Math.Min(size.Width, size.Height);
float height = Math.Max(size.Width, size.Height);
// 检查宽边(最短边)的两个角点是否都在轮廓内
Point2f leftEdge = RotatePoint(new Point2f(center.X - width / 2, center.Y), center, angle);
Point2f rightEdge = RotatePoint(new Point2f(center.X + width / 2, center.Y), center, angle);
bool isWidthFixed = Cv2.PointPolygonTest(contour, leftEdge, false) >= 0 && Cv2.PointPolygonTest(contour, rightEdge, false) >= 0;
// 检查长边(最长边)的两个角点是否都在轮廓内
Point2f topEdge = RotatePoint(new Point2f(center.X, center.Y - height / 2), center, angle);
Point2f bottomEdge = RotatePoint(new Point2f(center.X, center.Y + height / 2), center, angle);
bool isHeightFixed = Cv2.PointPolygonTest(contour, topEdge, false) >= 0 && Cv2.PointPolygonTest(contour, bottomEdge, false) >= 0;
// 如果宽边不固定,调整宽度
if (!isWidthFixed)
{
while (true)
{
leftEdge = RotatePoint(new Point2f(center.X - width / 2, center.Y), center, angle);
rightEdge = RotatePoint(new Point2f(center.X + width / 2, center.Y), center, angle);
if (Cv2.PointPolygonTest(contour, leftEdge, false) >= 0 && Cv2.PointPolygonTest(contour, rightEdge, false) >= 0)
{
break; // 宽边已经在轮廓内
}
// 如果不在轮廓内,缩小宽度
width -= 0.5f;
if (width <= 0) break; // 防止宽度变为负数
}
}
// 如果长边不固定,调整高度
if (!isHeightFixed)
{
while (true)
{
topEdge = RotatePoint(new Point2f(center.X, center.Y - height / 2), center, angle);
bottomEdge = RotatePoint(new Point2f(center.X, center.Y + height / 2), center, angle);
if (Cv2.PointPolygonTest(contour, topEdge, false) >= 0 && Cv2.PointPolygonTest(contour, bottomEdge, false) >= 0)
{
break; // 长边已经在轮廓内
}
// 如果不在轮廓内,缩小高度
height -= 0.5f;
if (height <= 0) break; // 防止高度变为负数
}
}
// 返回最大内接矩形
return new RotatedRect(center, new Size2f(width, height), angle);
}
在我的代码中,我绘制了一个最小外接矩形,然后基于给出的点绘制出轮廓,
paint.Color = SKColors.LightGray;
for (int i = 0; i < contourAll.Length - 1; i++)
{
canvas.DrawLine(new SKPoint(contourAll[i].X, contourAll[i].Y), new SKPoint(contourAll[i + 1].X, contourAll[i + 1].Y), paint);
}
这个是我轮廓的代码。我想要获得这个轮廓的最大内接矩形。 RotatedRect maxInscribedRect = GetMaxInscribedRectangle(contourAll);
//绘制最大内接矩形
Point2f[] maxRectPoints = maxInscribedRect.Points();
for (int j = 0; j < 4; j++)
{
paint.Color = SKColors.Yellow;
canvas.DrawLine(
maxRectPoints[j].X, maxRectPoints[j].Y,
maxRectPoints[(j + 1) % 4].X, maxRectPoints[(j + 1) % 4].Y,
paint
);
}。但是我的代码中我绘制了这个最大内接矩形,效果不是很好。请你帮我查阅资料帮我修改,我看到网上很多使用python已经实现了,你帮我转成C#写入我的代码,像什么凸包什么的,或者你遍历每一个角度都可以。要保证是轮廓的最大内接矩形,然后就是分割出来的图形不论是什么角度都可以获得正确的最大内接矩形