图像处理(Image Processing) ---------- 图像旋转 (C#实现)

版权声明:此为个人学习与研究成果,若需转载请提前告知。 https://blog.csdn.net/weixin_35811044/article/details/84194234
  • 图像旋转需要一个圆心,通常以图像的中心點为圆心,图像旋转经过三个步骤:

  1. 从图像坐标系转换为以圆心为原点的直角坐标系。

  2. 通过旋转算法,将坐标旋转。

  3. 再将旋转后图像从直角坐标系转换回图像坐标系。

如图:

     

  • 旋转算法:

  1. 坐标系变换:

    1. 由图象坐标系换成直角坐标系:。(x , y 为直角坐标)
    2. 由直角坐标系换成图象坐标系:。(x , y 为图象坐标)
  2. 坐标旋转:

    1. Pixel到原点的距离为r与原点的夹角为  ,则Pixel坐标可以表示为:
    2. 当图象顺时针旋转\theta度,则Pixel坐标为:    , 如图:。           

                  即:

  • 旋转算法最终公式:

    1. 顺时针旋转:图像坐标 --> 直角坐标 -->  图像旋转  -->  图像坐标 。
    2. 逆运算:用于反向映射。
  • 两种旋转方式:

    1. 正向失真映射:当图像旋转时,原图Pixel的坐标经过旋转公式得到的新Pixel坐标通常不会是整数,而图像的Pixel坐标都是以整数记录的。所以,我们会采用四舍六入五凑偶的方式。但是,如果采用近似,那么就会出现多个Pixel旋转后都对应到同一个整数坐标,而有些整数坐标点却没有pixel对应,这样就会出现空洞(失真)。另外对应多个Pixel的点,该点Pixel值是按先后顺序采用覆盖的方式。
    2. 反向映射:为了避免出现空洞(失真),我们采用反向映射(其中正向映射也能采用相同方法避免空洞)。原理是对旋转后的图片,其整数坐标进行逆变换,可得到对应在原图中的坐标。通常得到的原图坐标也不会是整数,但是我们可以采用下面两种方法取得Pixel然后赋值给旋转后的图片。
    3. 如图:

       

      1. 最邻近插值:直接对原图中映射的非整数坐标进行四舍五入,得到的整数坐标,取Pixel值赋值给旋转图像中对应的点。
      2. 双线性插值:先说说线性插值。如下图:已知 (x0, y0) 、(x1, y1)、x,求 y。根据斜率相同:即可得:

                                                                                    

现在來看看什么是双线性插值法,说白了就是进行两次线性插值。如下图:(x,y) 为映射到原图的pixel点,是位于(0,0)、(1,0)、(0,1)、(1,1)中的点。f(0,0)、f(1,0)、f(0,1)、f(1,1)为各点的Pixel值,求 (x,y)的pixel值 f(x,y) 。根据线性插值,先进行x方向线性插值:

                          在(0,0)、(1,0)之间:f(x,0) = f(0,0)*(1-x)/1 + f(1,0)*(x-0) /1 ;

                          在(0,1)、(1,1)之间:f(x,1) = f(0,1)*(1-x)/1 + f(1,1)*(x-0) /1

                         得到  f(x,0)f(x,1) 后再进行 y 方向上的线性插值:f(x,y)  = f(x,0)*(1-y)/1 + f(x,1)*(y-0)/1

                         f(x,y)即按比例权重求得的Pixel值赋值回旋转图像中对应的点。

                                                            双线性插值

  • C#实现正向失真和反向双线性:        

C#存在Cos、Sin精度问题,即 Cos(Pi/2)、Sin(Pi) 等,不会等于0之类。目前没有想到很好的解决办法,有人知道的话请指教。 

 //正向失真旋转
public Bitmap rotateImageDis(Bitmap Image, double degree)
        {
            int Wo = Image.Width;
            int Lo = Image.Height;
            //采用向上取整,求旋转后图像大小。
            //double Wt = Math.Ceiling(Math.Abs(Wo * Math.Cos(Math.PI * (degree / 180))) + Math.Abs(Lo * Math.Sin(Math.PI * (degree / 180)))-0.00001);
            //double Lt = Math.Ceiling(Math.Abs(Wo * Math.Sin(Math.PI * (degree / 180))) + Math.Abs(Lo * Math.Cos(Math.PI * (degree / 180)))-0.00001);
            //采用四舍五入,求旋转后图像大小。
            double Wt = (int)(Math.Abs(Wo * Math.Cos(Math.PI * (degree / 180))) + Math.Abs(Lo * Math.Sin(Math.PI * (degree / 180))) + 0.5);
            double Lt = (int)(Math.Abs(Wo * Math.Sin(Math.PI * (degree / 180))) + Math.Abs(Lo * Math.Cos(Math.PI * (degree / 180))) + 0.5);
            Bitmap rotateImageData = new Bitmap((int)Wt, (int)Lt, PixelFormat.Format24bppRgb);

            //x、y即原图坐标,x1、y1即旋转后坐标。
            for (int y = 0; y < Lo; y++)
            {
                for (int x = 0; x < Wo; x++)
                {
                    int x1 = (int)Math.Round(x * Math.Cos(Math.PI * (degree / 180)) - y * Math.Sin(Math.PI * (degree / 180)) - (Wo - 1) / 2.0 * Math.Cos(Math.PI * (degree / 180)) + (Lo - 1) / 2.0 * Math.Sin(Math.PI * (degree / 180)) + (Wt - 1) / 2.0);
                    int y1 = (int)Math.Round(x * Math.Sin(Math.PI * (degree / 180)) + y * Math.Cos(Math.PI * (degree / 180)) - (Wo - 1) / 2.0 * Math.Sin(Math.PI * (degree / 180)) - (Lo - 1) / 2.0 * Math.Cos(Math.PI * (degree / 180)) + (Lt - 1) / 2.0);
                    rotateImageData.SetPixel(x1, y1, Image.GetPixel(x, y));
                }
            }
            return rotateImageData;
        }
        //反向双线性插值法
        public Bitmap rotareImageOri(Bitmap Image, double degree)
        {
            int Wo = Image.Width;
            int Lo = Image.Height;
            //采用向上取整,求旋转后图像大小。
            //double Wt = Math.Ceiling(Math.Abs(Wo * Math.Cos(Math.PI * (degree / 180))) + Math.Abs(Lo * Math.Sin(Math.PI * (degree / 180))) - 0.0001);
            //double Lt = Math.Ceiling(Math.Abs(Wo * Math.Sin(Math.PI * (degree / 180))) + Math.Abs(Lo * Math.Cos(Math.PI * (degree / 180))) - 0.0001);
            //采用四舍五入,求旋转后图像大小。
            double Wt = (int)(Math.Abs(Wo * Math.Cos(Math.PI * (degree / 180))) + Math.Abs(Lo * Math.Sin(Math.PI * (degree / 180))) + 0.5);
            double Lt = (int)(Math.Abs(Wo * Math.Sin(Math.PI * (degree / 180))) + Math.Abs(Lo * Math.Cos(Math.PI * (degree / 180))) + 0.5);
            Bitmap rotateImageData = new Bitmap((int)Wt, (int)Lt, PixelFormat.Format24bppRgb);

            //x1、y旋转后图像坐标,x、y原图坐标。
            for (int y1 = 0; y1 < Lt; y1++)
            {
                for (int x1 = 0; x1 < Wt; x1++)
                {
                    double x = x1 * Math.Cos(Math.PI * (degree / 180)) + y1 * Math.Sin(Math.PI * (degree / 180)) - (Wt - 1) / 2.0 * Math.Cos(Math.PI * (degree / 180)) - (Lt - 1) / 2.0 * Math.Sin(Math.PI * (degree / 180)) + (Wo - 1) / 2.0;
                    double y = -x1 * Math.Sin(Math.PI * (degree / 180)) + y1 * Math.Cos(Math.PI * (degree / 180)) + (Wt - 1) / 2.0 * Math.Sin(Math.PI * (degree / 180)) - (Lt - 1) / 2.0 * Math.Cos(Math.PI * (degree / 180)) + (Lo - 1) / 2.0;
                    if (-0.001 <= x & x <= (Wo - 1) & -0.001 <= y & y <= (Lo - 1))
                    {
                        Color RGB = new Color();

                        int a1 = (int)x;
                        int b1 = (int)y;
                        int a2 = (int)Math.Ceiling(x);
                        int b2 = (int)y;
                        int a3 = (int)x;
                        int b3 = (int)Math.Ceiling(y);
                        int a4 = (int)Math.Ceiling(x);
                        int b4 = (int)Math.Ceiling(y);

                        double xa13 = x - a1;
                        double xa24 = a2 - x;
                        double yb12 = y - b1;
                        double yb34 = b3 - y;

                        if (xa13 != 0 & xa24 != 0 & yb12 != 0 & yb34 != 0)
                        {//对应回原图是非整数坐标,双线性插值。
                            byte R1 = Image.GetPixel(a1, b1).R;
                            byte G1 = Image.GetPixel(a1, b1).G;
                            byte B1 = Image.GetPixel(a1, b1).B;
                            byte R2 = Image.GetPixel(a2, b2).R;
                            byte G2 = Image.GetPixel(a2, b2).G;
                            byte B2 = Image.GetPixel(a2, b2).B;
                            byte R3 = Image.GetPixel(a3, b3).R;
                            byte G3 = Image.GetPixel(a3, b3).G;
                            byte B3 = Image.GetPixel(a3, b3).B;
                            byte R4 = Image.GetPixel(a4, b4).R;
                            byte G4 = Image.GetPixel(a4, b4).G;
                            byte B4 = Image.GetPixel(a4, b4).B;

                            byte R = (byte)((R1 * xa24 + R2 * xa13) * yb34 + (R3 * xa24 + R4 * xa13) * yb12);
                            byte G = (byte)((G1 * xa24 + G2 * xa13) * yb34 + (G3 * xa24 + G4 * xa13) * yb12);
                            byte B = (byte)((B1 * xa24 + B2 * xa13) * yb34 + (B3 * xa24 + B4 * xa13) * yb12);

                            RGB = Color.FromArgb(R, G, B);
                        }
                        else
                        {//对应回原图是整数坐标,直接取Pixel。
                            RGB = Image.GetPixel(a1, b1);
                        }
                        rotateImageData.SetPixel(x1, y1, RGB);
                    }
                }
            }
            return rotateImageData;
        }

                                                                                                                                

仅为个人理解,如有不足,请指教。  https://blog.csdn.net/weixin_35811044

猜你喜欢

转载自blog.csdn.net/weixin_35811044/article/details/84194234