简体   繁体   English

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

[英]How to rotate a 2D array at non integral angle

Application: 应用:

I am doing analysis of a laser beam which is imaged on an IR camera. 我正在分析在红外摄像机上成像的激光束。 The goal is the have a real-time measurement, or at least as fast as the camera, which refreshes about once every 125 ms. 目标是进行实时测量,或至少与摄像机一样快,每125毫秒刷新一次。

光束细节

The algorithm will sum the intensities between the yellow dashed lines, and outside of them, then find the ratio. 该算法将对黄色虚线之间以及它们之间的强度求和,然后找到比率。 The yellow dashed lines are rotated at the same angle as the beam's angle. 黄色虚线以与光束角度相同的角度旋转。 The beam's angle is almost never a multiple of 45 degrees . 光束的角度几乎永远不会是45度的倍数

I receive the beam in the form of a 2D array from the camera. 我从相机收到2D阵列形式的光束。 It is represented by a 32-bit Integer array of size 1360 x 1024. I want to rotate the array so the yellow dashed lines are perpendicular to the bounds of the array, which would make the summing algorithm simpler. 它由大小为1360 x 1024的32位Integer数组表示。我想旋转数组,以便黄色虚线垂直于数组的边界,这将使求和算法更简单。

What I've tried: 我尝试过的

I looked into System.Drawing.Drawing2D.Matrix.Rotate() , as has been suggested in answers to similar questions. 我研究了System.Drawing.Drawing2D.Matrix.Rotate() ,就像在回答类似问题时所建议的那样。 However, this applies to Graphics objects. 但是,这适用于Graphics对象。 Using that method I have rotated images. 使用该方法,我旋转了图像。 But I haven't found a way to perform a similar operation on a 2D array. 但是我还没有找到在2D数组上执行类似操作的方法。

Edit 编辑

The algorithm actually finds w such that the lines encompass n% of the beam's energy. 该算法实际上找到w ,以使线包含光束能量的n% I have tried traversing the entire array without rotation and checking the boundaries using the lines (y = mx + b), however, since I do many iterations of this operation, it is too costly. 我尝试遍历整个数组而不旋转,并使用线(y = mx + b)检查边界,但是,由于我对该操作进行了多次迭代,因此成本太高。 Again, I have 1.4 million values in the entire image. 同样,我在整个图像中有140万个值。

The reason I want to rotate is to calculate sums of each row of the rotated array (resulting in a beam profile), then find the narrowest w which produces n% enclosed energy. 我要旋转的原因是要计算旋转阵列的每一行的总和(得出光束轮廓),然后找到产生n%封闭能量的最窄w

The following image shows the rotated beam, the profile (1D array of sums, in red), and the width (blue). 下图显示了旋转的光束,轮廓(一维求和数组,红色)和宽度(蓝色)。

具有轮廓和宽度的旋转光束

I have an interesting idea that may be a bit different than what you have above. 我有一个有趣的想法,可能与您上面所说的有所不同。 Here is the algorithm: 这是算法:

  • Create two variables for the sums: betweenSum and outsideSum 为总和创建两个变量: betweenSumoutsideSum
  • Get the equation for both lines ( y = mx+b ) 获得两条线的方程( y = mx+b
    • m = tan((-1)theta) where theta is the angle of the line w/ x axis. m = tan((-1)theta)其中,theta是线与x轴的夹角。 b = y intercept b = y截距
  • Go through each entry in the 2D array. 浏览2D数组中的每个条目。
    • if the entry's x and y coordinates put in between both equations, then add the intensity to the betweenSum. 如果条目的x和y坐标都放在两个方程式之间,则将强度添加到betweenSum中。
    • else add intensity to the outsideSum. 否则增加强度outsideSum。

This approach is quite a bit different, and I'm not sure if this is something that is practical for your project. 这种方法有很多不同,我不确定这是否适合您的项目。 Just offering an approach that may help avoid doing rotation (and maybe any extra overhead). 仅提供一种可能有助于避免轮换的方法(可能还有任何额外的开销)。

I think I have the conversion from pixel to physical coordinates correct (and back). 我认为我已经从像素到物理坐标的转换正确了(然后又回来了)。 Then what you want is this: 然后您想要的是:

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;
    }
}

make sure the intesities are all positive (to avoid possible 1/0 condition). 确保完整性均为正(以避免可能的1/0条件)。 The strip center line is assumed to pass through center of beam. 假定带材中心线穿过光束中心。

Here is one way to use the drawing transforms to rotate an int[,] . 这是一种使用绘图变换旋转int[,] The demo code below needs a new forms application with 下面的演示代码需要一个具有以下内容的新表单应用程序:

  • timer1 (Enabled, ~100ms) timer1(启用,〜100ms)
  • pictureBox1 (1360x1024, hook up paint handler) pictureBox1(1360x1024,连接绘画处理程序)

This outputs the rotated data to rotArr and completes the operation in ~40ms (on my 4 year-old laptop). rotArr旋转后的数据输出到rotArr并在约40毫秒内完成操作(在我4岁的笔记本电脑上)。 Compiling for x86 seems to be faster here. 在这里为x86编译似乎更快。 It may be just quick enough if you can manage the rest efficiently and have a reasonably new system to work with. 如果您可以有效地管理其余部分并拥有一个可以使用的合理新系统,则可能足够快。 To get better speeds than this you can override the default interpolation mode using : 要获得比此更好的速度,您可以使用以下命令覆盖默认的插值模式:

 g.InterpolationMode = InterpolationMode.NearestNeighbor;

this reduces the quality of the rotated image but it can be faster. 这会降低旋转图像的质量,但速度会更快。 A low interpolation quality may add uncertainty to your data - you'll have to test this. 低插值质量可能会增加数据的不确定性-您必须对此进行测试。 Another flag you can test is 您可以测试的另一个标志是

g.SmoothingMode = SmoothingMode.AntiAlias;

This adds time to the calculation (not much for me, but your mileage may vary), but may improve the fidelity of the rotated image (reduce artifact errors introduced by the transform). 这会增加计算时间(对我来说不算多,但是您的里程可能会有所不同),但可能会提高旋转图像的保真度(减少由转换引起的伪影误差)。

This has to be C# - you said you'd prefer VB but you have to set "Allow unsafe code" in your project compile options for this work and VB does not support unsafe code. 这必须是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();
        }

    }
 }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM