You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Cyotek ImageBox中正确绘制多边形ROI(解决缩放偏移问题)

问题解决:Cyotek ImageBox缩放状态下ROI保存位置偏移

问题现象

使用Cyotek ImageBox绘制多边形ROI以提取弧形文本用于OCR,仅在图像100%比例时能正确保存ROI;缩放状态下绘制的ROI(红色)和实际保存的区域(绿色)位置不匹配。

Test Image

问题根源

保存ROI时直接使用了控件客户端坐标(polygonPoints),未将其转换为原始图像的实际坐标;而绘制预览时的DrawPolygonOnImage方法已经做了缩放和平移转换,导致保存逻辑和预览逻辑不一致。

修复方案

修改imageBox1_MouseUp事件中的坐标转换逻辑,复用缩放和平移计算逻辑,将客户端坐标转换为原始图像坐标后再生成掩码。

修改后的完整代码

private Rectangle GetBoundingRectangle(List<Point> points)
{
    int minX = int.MaxValue;
    int minY = int.MaxValue;
    int maxX = int.MinValue;
    int maxY = int.MinValue;

    foreach (Point point in points)
    {
        if (point.X < minX)
            minX = point.X;
        if (point.Y < minY)
            minY = point.Y;
        if (point.X > maxX)
            maxX = point.X;
        if (point.Y > maxY)
            maxY = point.Y;
    }

    int width = maxX - minX + 1;
    int height = maxY - minY + 1;

    return new Rectangle(minX, minY, width, height);
}

private List<Point> polygonPoints = new List<Point>();
private bool isDrawing = false;
private Image<Bgr, byte> frame1;

private void imageBox1_MouseUp(object sender, MouseEventArgs e)
{
    if (isDrawing)
    {
        isDrawing = false;

        if (polygonPoints.Count > 1)
        {
            // 关键修复:将客户端坐标转换为原始图像坐标
            float scaleX = (float)frame1.Width / imageBox1.ClientSize.Width;
            float scaleY = (float)frame1.Height / imageBox1.ClientSize.Height;
            int translationX = -imageBox1.HorizontalScroll.Value;
            int translationY = -imageBox1.VerticalScroll.Value;
            Point[] scaledPolygonPoints = ScalePolygonPoints(polygonPoints.ToArray(), scaleX, scaleY, translationX, translationY);

            using (VectorOfPoint polygonVector = new VectorOfPoint(scaledPolygonPoints))
            {
                using (UMat roiMask = new UMat(frame1.Height, frame1.Width, DepthType.Cv8U, 1))
                {
                    CvInvoke.FillPoly(roiMask, polygonVector, new MCvScalar(255, 255, 255));

                    Mat roiMat = new Mat();
                    roiMask.CopyTo(roiMat);

                    Mat roiBgr = new Mat();
                    CvInvoke.BitwiseAnd(frame1, frame1, roiBgr, roiMat);

                    string outputPath = "extracted_roi.png";
                    roiBgr.Save(outputPath);
                    MessageBox.Show($"Extracted ROI saved to {outputPath}", "ROI Saved", MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
            }
        }

        polygonPoints.Clear();
        imageBox1.Refresh();
    }
}

private void imageBox1_MouseMove(object sender, MouseEventArgs e)
{
    if (isDrawing)
    {
        polygonPoints.Add(e.Location);
        DrawPolygonOnImage();
        imageBox1.Refresh();
    }
}

private void imageBox1_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
    {
        polygonPoints.Clear();
        polygonPoints.Add(e.Location);
        isDrawing = true;
    }
}

private void imageBox1_Paint(object sender, PaintEventArgs e)
{
    if (polygonPoints.Count > 1)
    {
        using (var pen = new System.Drawing.Pen(System.Drawing.Color.Red, 2))
        {
            e.Graphics.DrawPolygon(pen, polygonPoints.ToArray()); // 红色绘制线
        }
    }
}

private Point[] PointFToPoint(Point[] points)
{
    Point[] result = new Point[points.Length];
    for (int i = 0; i < points.Length; i++)
    {
        result[i] = new Point((int)Math.Round((double)points[i].X), (int)Math.Round((double)points[i].Y));
    }
    return result;
}

private void DrawPolygonOnImage()
{
    if (frame1 != null)
    {
        using (Mat tempImage = frame1.Clone().Mat)
        {
            Point[] polygonPointF = polygonPoints.ToArray();
            float scaleX = (float)tempImage.Width / imageBox1.ClientSize.Width;
            float scaleY = (float)tempImage.Height / imageBox1.ClientSize.Height;

            int translationX = -imageBox1.HorizontalScroll.Value;
            int translationY = -imageBox1.VerticalScroll.Value;

            Point[] scaledPolygonPoints = ScalePolygonPoints(polygonPointF, scaleX, scaleY, translationX, translationY);

            using (VectorOfPoint polygonVector = new VectorOfPoint(scaledPolygonPoints))
            {
                CvInvoke.Polylines(tempImage, polygonVector, true, new MCvScalar(0, 255, 0), 2);
            }

            imageBox1.Image = tempImage.ToBitmap();
        }
    }
}

private Point[] ScalePolygonPoints(Point[] points, float scaleX, float scaleY, int translationX, int translationY)
{
    Point[] scaledPoints = new Point[points.Length];
    for (int i = 0; i < points.Length; i++)
    {
        int scaledX = (int)Math.Round((points[i].X + translationX) * scaleX);
        int scaledY = (int)Math.Round((points[i].Y + translationY) * scaleY);
        scaledPoints[i] = new Point(scaledX, scaledY);
    }
    return scaledPoints;
}

修复说明

在imageBox1_MouseUp中,添加了和DrawPolygonOnImage完全一致的坐标转换逻辑:

  1. 计算图像相对于控件的缩放比例(scaleX/scaleY)
  2. 获取平移偏移量(translationX/translationY)
  3. 调用ScalePolygonPoints将客户端坐标转换为原始图像的实际坐标
  4. 使用转换后的坐标生成掩码并保存ROI

这样保存的ROI坐标和预览时的绿色线完全匹配,解决了缩放状态下的位置偏移问题。

内容的提问来源于stack exchange,提问作者Roshan Pillai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 08:55:55