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

问题根源
保存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完全一致的坐标转换逻辑:
- 计算图像相对于控件的缩放比例(
scaleX/scaleY) - 获取平移偏移量(
translationX/translationY) - 调用
ScalePolygonPoints将客户端坐标转换为原始图像的实际坐标 - 使用转换后的坐标生成掩码并保存ROI
这样保存的ROI坐标和预览时的绿色线完全匹配,解决了缩放状态下的位置偏移问题。
内容的提问来源于stack exchange,提问作者Roshan Pillai
相关产品推荐
相关产品推荐

