如何以非整数角度旋转2D阵列

时间:2014-01-14 18:50:33

标签: c# .net arrays vb.net rotation

应用

我正在分析在红外摄像机上成像的激光束。目标是进行实时测量,或者至少与相机一样快,每125毫秒刷新一次。

Beam detail

算法将对黄色虚线和它们之外的强度求和,然后找出比率。黄色虚线以与光束角度相同的角度旋转。光束的角度几乎不会是45度的倍数。

我从相机接收2D阵列形式的光束。它由一个大小为1360 x 1024的32位整数数组表示。我想旋转数组,使黄色虚线垂直于数组的边界,这将使求和算法更简单。

我尝试了什么:

我调查System.Drawing.Drawing2D.Matrix.Rotate(),正如在类似问题的答案中所建议的那样。但是,这适用于Graphics个对象。使用该方法我旋转了图像。但我还没有找到在2D阵列上执行类似操作的方法。

修改

该算法实际上找到w,使得线条包含光束能量的n%。我已经尝试遍历整个数组而没有旋转并使用线条检查边界(y = mx + b),但是,由于我对此操作进行了多次迭代,因此成本太高。同样,我在整个图像中有140万个值。

我想要旋转的原因是计算旋转阵列的每一行的总和(产生光束轮廓),然后找到产生w封闭能量的最窄n%

下图显示了旋转光束,轮廓(1D数组的总和,红色)和宽度(蓝色)。

Rotated beam with profile and width

3 个答案:

答案 0 :(得分:2)

我有一个有趣的想法,可能与你上面的有点不同。这是算法:

  • 为总和创建两个变量:betweenSumoutsideSum
  • 获取两条线的等式(y = mx+b
    • m = tan(( - 1)theta)其中theta是线w / x轴的角度。 b = y拦截
  • 浏览2D数组中的每个条目。
    • 如果条目的x和y坐标放在两个方程之间,则将强度添加到betweenSum。
    • 否则将强度添加到outsideSum。

这种方法有点不同,我不确定这是否适用于您的项目。提供一种方法可以帮助避免轮换(也许任何额外的开销)。

答案 1 :(得分:1)

我认为我从像素到物理坐标的转换是正确的(并且返回)。那你想要的是这个:

public struct ImageData
{
    /// <summary>
    /// Intensity map
    /// </summary>
    int[,] intensities;
    /// <summary>
    /// Pixel dimensios of image like 1360 × 1024
    /// </summary>
    Size pixel_size;

    /// <summary>
    /// Physical dimensions like 300μ × 260μ
    /// </summary>
    SizeF actual_size;

    /// <summary>
    /// Transforms pixel coordinates to actual dimensions. Assumes center of image is (0,0)
    /// </summary>
    /// <param name="pixel">The pixel coordinates (integer i,j)</param>
    /// <rereturns>The physical coordinates (float x,y)</rereturns>
    public PointF PixelToPoint(Point pixel)
    {
        return new PointF(
            actual_size.Width*((float)pixel.X/(pixel_size.Width-1)-0.5f),
            actual_size.Height*((float)pixel.Y/(pixel_size.Height-1)-0.5f));
    }
    /// <summary>
    /// Transforms actual dimensions to pixels. Assumes center of image is (0,0)
    /// </summary>
    /// <param name="point">The point coordines (float x,y)</param>
    /// <returns>The pixel coordinates (integer i,j)</returns>
    public Point PointToPixel(PointF point)
    {
        return new Point(
            (int)((pixel_size.Width-1)*(point.X/actual_size.Width+0.5f)),
            (int)((pixel_size.Height-1)*(point.Y/actual_size.Height+0.5f)));
    }
    /// <summary>
    /// Get the ratio of intensities included inside the strip (yellow lines). 
    /// Assume beam is located at the center.
    /// </summary>
    /// <param name="strip_width">The strip width in physical dimensions</param>
    /// <param name="strip_angle">The strip angle in degrees</param>
    /// <returns></returns>
    public float GetRatio(float strip_width, float strip_angle)
    {
        // Convert degrees to radians
        strip_angle*=(float)(Math.PI/180);
        // Cache sin() and cos()
        float cos=(float)Math.Cos(strip_angle), sin=(float)Math.Sin(strip_angle);
        //line through (xc,yc) with angle ψ is  (y-yc)*COS(ψ)-(x-xc)*SIN(ψ)=0
        //to offset the line by amount d, by add/subtract d from the equation above
        float inside=0, all=0;
        for(int i=0; i<pixel_size.Width; i++)
        {
            for(int j=0; j<pixel_size.Height; j++)
            {
                Point pixel=new Point(i, j);
                //find distance to strip center line
                PointF point=PixelToPoint(pixel);
                float t=-sin*point.X+cos*pixel.Y;
                if(Math.Abs(t)<=strip_width/2)
                {
                    inside+=intensities[i, j];
                }
                all += intensities[i,j];
            }
        }
        return inside/all;
    }
    public void RotateIntesities(float angle)
    {
        // Convert degrees to radians
        angle*=(float)(Math.PI/180);
        // Cache sin() and cos()
        float cos=(float)Math.Cos(angle), sin=(float)Math.Sin(angle);
        int[,] result=new int[pixel_size.Width, pixel_size.Height];
        for(int i=0; i<pixel_size.Width; i++)
        {
            for(int j=0; j<pixel_size.Height; j++)
            {
                Point pixel=new Point(i, j);
                PointF point=PixelToPoint(pixel);
                PointF point2=new PointF(
                    point.X*cos-point.Y*sin,
                    pixel.X*sin+point.Y*cos);
                Point pixel2=PointToPixel(point2);
                if(pixel2.X>=0&&pixel2.X<pixel_size.Width
                    &&pixel2.Y>=0&&pixel2.Y<pixel_size.Height)
                {
                    result[pixel2.X, pixel2.Y]+=intensities[i, j];
                }
            }
        }

        intensities=result;
    }
}

确保所有的积极性都是正的(以避免可能的1/0条件)。假设条带中心线穿过光束中心。

答案 2 :(得分:0)

以下是使用绘图变换旋转int[,]的一种方法。下面的演示代码需要一个带有

的新表单应用程序
  • timer1(启用,~100ms)
  • pictureBox1(1360x1024,挂钩油漆处理程序)

这将旋转的数据输出到rotArr并在〜40ms内完成操作(在我4岁的笔记本电脑上)。在这里编译x86似乎更快。如果您能够有效地管理其余部分并拥有一个合理的新系统,那么它可能足够快。要获得比此更好的速度,您可以使用以下方法覆盖默认插值模式:

 g.InterpolationMode = InterpolationMode.NearestNeighbor;

这会降低旋转图像的质量,但速度会更快。低插值质量可能会增加数据的不确定性 - 您必须对此进行测试。您可以测试的另一个标志是

g.SmoothingMode = SmoothingMode.AntiAlias;

这增加了计算时间(对我来说不多,但你的里程可能会有所不同),但可能会提高旋转图像的保真度(减少变换引入的伪像误差)。

这必须是C# - 你说你更喜欢VB,但是你必须在你的项目编译选项中设置“允许不安全的代码”,VB不支持不安全的代码。

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
using System.Drawing.Imaging;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        private int[,] imgArr = new int[1360, 1024];  //the 2D source from the camera
        private int[,] rotArr = new int[1360, 1024];  //the rotated output
        private float angle = 0;

        public Form1()
        {
            InitializeComponent();
            //Make an RGB stripe for testing
            for (int i = 0; i < 1360; i++)
            {
                for (int j = 0; j < 1024; j++)
                {
                    if (j < 333)
                    {
                        imgArr[i, j] = -65536; //0xFFFF0000 - red
                    }
                    else if (j < 666)
                    {
                        imgArr[i, j] = -16711936; //0xFF00FF00 - green
                    }
                    else
                    {
                        imgArr[i, j] = -16776961; //0xFF0000FF - blue
                    }                       
                }
            }          

        }

        private void pictureBox1_Paint(object sender, PaintEventArgs e)
        {
            Bitmap drawImg;
            // Copy array to a bitmap - fast!
            unsafe
            {
                fixed (int* intPtr = &imgArr[0, 0])
                {
                    //swap width and height due to [,] row layout
                    drawImg = new Bitmap(1024, 1360, 4096,
                                  PixelFormat.Format32bppArgb, 
                                  new IntPtr(intPtr));                        
                }
            }

            // transform 
            GraphicsPath gp = new GraphicsPath();
            gp.AddPolygon(new Point[]{new Point(0,0), 
                          new Point(drawImg.Width,0), 
                          new Point(0,drawImg.Height)});
            Matrix m = new Matrix();
            m.RotateAt(angle, new PointF((float)drawImg.Width/2, (float)drawImg.Height/2)); 
            gp.Transform(m);
            PointF[] pts = gp.PathPoints;

            //draw rotated image
            Bitmap rotImg = new Bitmap(1024, 1360);
            Graphics g = Graphics.FromImage(rotImg);

            // for speed...default is Bilinear
            //g.InterpolationMode = InterpolationMode.NearestNeighbor;

            // may improve accuracy - default is no AntiAliasing
            // slows calculation
            // g.SmoothingMode = SmoothingMode.AntiAlias;

            g.DrawImage(drawImg, pts);

            //copy array data out 
            BitmapData bData = rotImg.LockBits(
                                        new Rectangle(new Point(), rotImg.Size),
                                        ImageLockMode.ReadOnly,
                                        PixelFormat.Format32bppArgb);
            int byteCount = bData.Stride * (rotImg.Height);
            int[] flatArr = new int[byteCount / 4];
            //Do this in two steps - first to a flat array, then 
            //block copy to the [,] array
            Marshal.Copy(bData.Scan0, flatArr, 0, byteCount / 4);
            Buffer.BlockCopy(flatArr, 0, rotArr, 0, byteCount);

            // unlock the bitmap!!
            rotImg.UnlockBits(bData); 

            // for show... draw the rotated image to the picturebox
            // have to rotate 90deg due to [,] row layout
            rotImg.RotateFlip(RotateFlipType.Rotate90FlipNone);
            e.Graphics.DrawImage(rotImg, new Point(0, 0));

        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            // increment angle and paint
            angle += 1.0f;
            if (angle > 360.0f) { angle = 0; }
            pictureBox1.Invalidate();
        }

    }
 }