【问题标题】:Detect passing of rectangle over yellow pixel检测矩形通过黄色像素
【发布时间】:2016-09-12 01:39:57
【问题描述】:

我有一个关于检测移动和可能旋转的矩形何时通过面板背景图像的黄色像素的最佳方法的查询。

我有一个接受图像和点的方法,如果该点是黄色像素的点,则返回 true。我的游戏功能需要这种颜色检测,如果汽车(玩家)驶过赛道的黄色边界,它会重置汽车(玩家)。该方法如下图所示:

private Boolean isYellow(Image image, Point point)
{
   Bitmap bitmap = new Bitmap(image);
   Color color = bitmap.GetPixel(point.X, point.Y);

   return (color.R > 220 && color.G > 220 && color.B < 200);
}

之前,为了检测玩家矩形是否超过黄色,我检查了矩形的位置,由对象的 X 和 Y 值提供。这样做的问题是该位置位于水平矩形的左上角,这意味着汽车几乎可以完全驶离赛道而不会发生检测。

我想通过检查矩形覆盖的所有点来解决这个问题。这并不像看起来那么简单,因为矩形可能会被旋转。我的绘图和移动逻辑如下所示:

public void draw(Graphics g)
{
   int dx = rectangle.X + (rectangle.Height / 2);
   int dy = rectangle.Y + (rectangle.Width / 2);

   g.ScaleTransform(xScale, yScale);
   g.TranslateTransform(dx, dy);
   g.RotateTransform((float) ((180 * angle) / Math.PI));
   g.TranslateTransform(-dx, -dy);
   g.DrawImage(image, rectangle.X, rectangle.Y);
   g.ResetTransform();
}

public void move(uRaceGame game, Panel panel)
{
   double cos = Math.Cos(angle), sin = Math.Sin(angle);
   int xLocation = 200;
   int yLocation = 200;

   xLocation = (int) Math.Floor(rectangle.X + (cos * game.moveDir * 60)); 
   yLocation = (int) Math.Floor(rectangle.Y + (sin * game.moveDir * 60)); 

   angle = (angle + (game.rotateDir * (Math.PI / 128))) % (Math.PI * 2);

   if (xLocation * xScale > panel.Width - (rectangle.Width * cos) || yLocation * yScale > panel.Height - (rectangle.Width * sin) - 5 || xLocation * xScale < 0 || yLocation * yScale < 5) return;

   rectangle.Location = new Point(xLocation, yLocation);
}

我尝试但未能创建一种方法来转换角的坐标并找出矩形的中间,但这不起作用,黄色检测会在非常模糊的地方触发:

public Point getCentre()
{
    int cX = (int) (rectangle.X + ((rectangle.Width / 2) / xScale)), cY = (int) (rectangle.Y + ((rectangle.Height / 2) / yScale));
    float tempX = (rectangle.X - cX), tempY = (rectangle.Y - cY);

    double rX = (tempX * Math.Cos(angle)) - (tempY * Math.Sin(angle));
    double rY = (tempX * Math.Sin(angle)) - (tempY * Math.Cos(angle));

    return new Point((int) ((rX + cX) * xScale), (int) ((rY + cY) * yScale));
}

我非常感谢有关如何解决此问题的任何建议。我包含了翻译和黄色检测代码,以防万一我的尝试距离很远,而其他人有更好的主意。

非常感谢。

【问题讨论】:

  • 为了速度,请使用 LockBits! - 要在矩形上循环,您需要在外部边界(顶部和底部角)上创建一个外部循环,然后是内部循环,通过将浮动斜率相加直到碰到一个角,然后减去来计算左右起点和终点。在汽车上改变方向时,您可以计算 2 个坡度。
  • 很高兴您发现这个答案很有用。你可能想看看更新的版本..
  • @TaW 我非常感谢您为这个答案付出的努力!非常感谢您的帮助。

标签: c# visual-studio graphics


【解决方案1】:

我想到了两种方法:

  • 您可以沿着汽车矩形的倾斜边创建循环
  • 或者,您可以将汽车复制到一个无底位图并正常循环。

这是第二种方法的示例。

它使用LockBits 方法检测Yellow 以及Bitmap 中的代码。

它通过从原始BackgroundImage 复制未旋转的位图来准备该位图。

这是结果,包括一个控件Panel,它显示了直到矩形:



这里是黄色查找功能。它使用 Lockbits 来提高速度:

using System.Runtime.InteropServices;
using System.Drawing.Imaging;

public bool testForYellowBitmap(Bitmap bmp)
{
    Size s1 = bmp.Size;
    PixelFormat fmt = new PixelFormat();
    fmt = bmp.PixelFormat;
    Rectangle rect = new Rectangle(0, 0, s1.Width, s1.Height);
    BitmapData bmp1Data = bmp.LockBits(rect, ImageLockMode.ReadOnly, fmt);
    byte bpp1 = 4;
    if (fmt == PixelFormat.Format24bppRgb) bpp1 = 3;
    else if (fmt == PixelFormat.Format32bppArgb) bpp1 = 4; else return false; // throw!!
    int size1 = bmp1Data.Stride * bmp1Data.Height;
    byte[] data1 = new byte[size1];
    System.Runtime.InteropServices.Marshal.Copy(bmp1Data.Scan0, data1, 0, size1);
    for (int y = 0; y < s1.Height; y++)
    {
        for (int x = 0; x < s1.Width; x++)
        {
            Color c1;
            int index1 = y * bmp1Data.Stride + x * bpp1;
            if (bpp1 == 4)
                c1 = Color.FromArgb(data1[index1 + 3], data1[index1 + 2],
                                    data1[index1 + 1], data1[index1 + 0]);
            else c1 = Color.FromArgb(255, data1[index1 + 2], 
                                          data1[index1 + 1], data1[index1 + 0]);
            if (c1.R > 220 && c1.G > 220 && c1.B < 200) 
               { bmp.UnlockBits(bmp1Data); return true; }
        }
    }
    bmp.UnlockBits(bmp1Data);
    return false;
}

我准备Bitmap 以在MouseMove 中进行比较。变量w, h, w2, h2 保存汽车尺寸的宽度、高度和一半。源位图位于drawPanel1.BackgroundImage。当前角度位于TrackBar tr_a.Value 中。为了进一步控制,我还将旋转的汽车矩形显示为白色。

private void drawPanel1_MouseMove(object sender, MouseEventArgs e)
{
    if (e.Button.HasFlag(MouseButtons.Left))
    {
        Size sz = drawPanel1.BackgroundImage.Size;
        Rectangle rectSrc = new Rectangle(e.X - w2, e.Y - h2, w, h);
        Rectangle rectTgt = new Rectangle(e.X - w, e.Y - h, 2 * w, 2 * h);

        using (Graphics g = drawPanel1.CreateGraphics())  // start optional
        {
            g.TranslateTransform(e.X, e.Y);
            g.RotateTransform(trb_a.Value);
            g.TranslateTransform(-e.X, -e.Y);
            drawPanel1.Refresh();
            g.DrawRectangle(Pens.White, rectSrc);
        }

        using (Graphics g = drawPanel2.CreateGraphics())
        {                                                      // end optional
            using (Bitmap bmp = new Bitmap(sz.Width, sz.Height))
            using (Graphics g2 = Graphics.FromImage(bmp))
            {
                g2.TranslateTransform(e.X, e.Y);
                g2.RotateTransform(-trb_a.Value);
                g2.TranslateTransform(-e.X, -e.Y);
                g2.DrawImage(drawPanel1.BackgroundImage, rectTgt, rectTgt, 
                             GraphicsUnit.Pixel);
                drawPanel2.Refresh();
                g.DrawImage(bmp, rectSrc, rectSrc, GraphicsUnit.Pixel);
                Text = testForYellowBitmap(bmp) ? "!!YELLOW!!" : "";
            }
        }
    }

第一种方法将使用类似的 LockBits 方法,但内部有循环沿着汽车矩形的旋转边,使用 floats 和循环变量来计算 x-坐标。这些数据应针对汽车尺寸或角度的每次变化进行准备。代码有点长,但也应该快一点。

第二种方法的优势在于,通过在Graphics 对象上使用ClippingRegion,可以检查任意形状,而第一种方法可以轻松修改为凹多边形,但不适用于弯曲形状。

这是第一个版本的检查代码的改编版本:

public bool testForYellowBitmapTilt(Bitmap bmp, List<int> leftPts, 
                                    List<int> rightPts, Point topLeft)
{
    Size s1 = bmp.Size;
    PixelFormat fmt = new PixelFormat();
    fmt = bmp.PixelFormat;
    Rectangle rect = new Rectangle(0, 0, s1.Width, s1.Height);
    BitmapData bmp1Data = bmp.LockBits(rect, ImageLockMode.ReadOnly, fmt);
    byte bpp1 = 4;
    if (fmt == PixelFormat.Format24bppRgb) bpp1 = 3;
    else if (fmt == PixelFormat.Format32bppArgb) bpp1 = 4; 
         else return false; // or throw!!
    if (leftPts.Count != rightPts.Count) return false; // or throw!!

    int size1 = bmp1Data.Stride * bmp1Data.Height;
    byte[] data1 = new byte[size1];
    System.Runtime.InteropServices.Marshal.Copy(bmp1Data.Scan0, data1, 0, size1);

    for (int y = 0; y < (leftPts.Count); y++)
    {
        for (int x = leftPts[y] + topLeft.X; x < rightPts[y] + topLeft.X; x++)
        {
            Color c1;

            int index1 = (y + topLeft.Y) * bmp1Data.Stride + x * bpp1;
            if (index1 > 0)
            {
                if (bpp1 == 4)
                    c1 = Color.FromArgb(data1[index1 + 3], data1[index1 + 2], 
                                        data1[index1 + 1], data1[index1 + 0]);
                else c1 = Color.FromArgb(255, data1[index1 + 2],
                                        data1[index1 + 1], data1[index1 + 0]);

                if (c1.R > 220 && c1.G > 220 && c1.B < 200) 
                   { bmp.UnlockBits(bmp1Data); return true; }
            }
        }
    }
    bmp.UnlockBits(bmp1Data);
    return false;
}

左右坐标存储在这里:

List<int> leftPts = new List<int>();
List<int> rightPts = new List<int>();
Point top = Point.Empty;


void getOuterPoints(List<PointF> corners, out List<int> leftPts, 
                    out List<int> rightPts, out Point top)
{
    leftPts = new List<int>();
    rightPts = new List<int>();

    PointF left = corners.Select(x => x).OrderBy(x => x.X).First();
    PointF right = corners.Select(x => x).OrderByDescending(x => x.X).First();
            top = Point.Round(corners.Select(x => x).OrderBy(x => x.Y).First());
    PointF bottom = corners.Select(x => x).OrderByDescending(x => x.Y).First();

    int w1 = -(int)(top.X - left.X);
    int w2 = -(int)(left.X - bottom.X );
    int h1 = (int)(left.Y - top.Y);
    int h2 = (int)(bottom.Y - left.Y);

    float d1 = 1f * w1 / h1;
    float d2 = 1f * w2 / h2;

    for (int y = 0; y < h1; y++) leftPts.Add( (int)(y * d1) );
    for (int y = 0; y < h2; y++) leftPts.Add( (int)(y * d2 + w1));

    for (int y = 0; y < h2; y++) rightPts.Add( (int)(y * d2));
    for (int y = 0; y < h1; y++) rightPts.Add(  (int)(y * d1 + w2));
}

您需要以List&lt;PointF&gt; 的形式以任意顺序输入四个角; top 可以是任何东西,它将在方法中设置。坐标是相对于汽车的,所以当汽车移动时它们不会改变..

【讨论】:

    猜你喜欢
    • 2011-11-12
    • 2014-03-15
    • 1970-01-01
    • 2021-03-12
    • 2014-04-30
    • 1970-01-01
    • 2012-02-29
    • 1970-01-01
    • 2016-09-19
    相关资源
    最近更新 更多