6
$\begingroup$

I'm struggling with a problem with image rotation. I'm using the idea of "Rotation by area mapping" in the following link: http://www.leptonica.com/rotation.html

My c# codes are as follows. The idea is first to find the dimension of the rotated image, then find the gray level of each pixel in the rotated image. To find the gray level for each pixel, the pixel coordinates are transformed back to the coordinates in the original image (called original pixel). The original pixel has fractional coordinate values, then the bilinear interpolation method in the link is used to obtain the gray level of the pixel in the rotated image. My codes did most of the job, except there are some clipping at the corners in the rotated image. I put the rotated images of a 16*16 image, we can clearly see the effect: The bottom line in the 45 degree rotated has no gray data; in the 90 degree rotated one, the vertical line in the left has no gray data; and in the 180 degree rotated one, both the vertical line in the left and the horizontal line on the top have no gray. I believe it's caused by the coordinate computation, and I could modify my code by subtracting one of the coordinates by 1 to achieve correct rotation by a certain angle, but it doesn't work for the other case. So I guess there must be something I ignored in my codes. Any suggestions and comments are appreciated!

public void RotateImageClockwise(double degree)
{
    Image<double> image = (Image<double>)(PreservedRawImage.Clone());      
        double sinAngle = Math.Sin(Math.PI * degree / 180.0);
        double cosAngle = Math.Cos(Math.PI * degree / 180.0);
        double newHeightRaw, newWidthRaw;

        ////Find the new dimensions of the rotated image.
        if (Math.Abs(degree) <= 90)
        {
            newHeightRaw = image.Width * sinAngle + image.Height * cosAngle;
            newWidthRaw = image.Width * cosAngle + image.Height * sinAngle;
        }
        else if (Math.Abs(degree) <= 180)
        {
            newHeightRaw = image.Width * Math.Sin(Math.PI * (180 - degree) / 180.0) + image.Height * Math.Sin(Math.PI * (degree - 90) / 180.0);
            newWidthRaw = image.Width * Math.Cos(Math.PI * (180 - degree) / 180.0) + image.Height * Math.Cos(Math.PI * (degree - 90) / 180.0);
        }
        else
            throw new Exception("Rotation angle is not right.");
        ////Done with finding the new dimensions

        double newWidthHalf = newWidthRaw / 2;
        double newHeightHalf = newHeightRaw / 2;

        int newWidth = (int)Math.Round(newWidthRaw);
        int newHeight = (int)Math.Round(newHeightRaw);
        Image<double> newImage = new Image<double>(newWidth, newHeight);
        byte[] bitmap = new byte[newWidth * newHeight];
        double newMax = double.MinValue, newMin = double.MaxValue;
        for (int x = 0; x < newHeight; x++)
        {
            for (int y = 0; y < newWidth; y++)
            {
                ////Transform the coordinates to be centered at the center of the image
                double xt = x - newHeightHalf;
                double yt = y - newWidthHalf;

                double originalX = xt * cosAngle - yt * sinAngle + image.Height / 2.0;
                double originalY = xt * sinAngle + yt * cosAngle + image.Width / 2.0;

                double fractionX = Math.Abs(originalX - (int)Math.Round(originalX));
                double fractionY = Math.Abs(originalY - (int)Math.Round(originalY));

                ////Find the neighbors of the pixel in the original image
                IList<Point<int>> neighbors = new List<Point<int>> { new Point<int>{x = (int)Math.Round(originalX), y = (int)Math.Round(originalY), weight = (1 - fractionX) * (1 - fractionY)}, 
                                                           new Point<int>{x = (int)Math.Round(originalX), y = (int)Math.Round(originalY) + 1, weight = fractionX * (1 - fractionY)},
                                                           new Point<int>{x = (int)Math.Round(originalX) + 1, y = (int)Math.Round(originalY), weight = (1 - fractionX) * fractionY},
                                                           new Point<int>{x = (int)Math.Round(originalX) + 1, y = (int)Math.Round(originalY) + 1, weight = fractionX * fractionY}
                                                         };
                bool hasData = false;
                double newData = 0;
                double weight = 0;
                foreach (var neighbor in neighbors)
                {
                    if (neighbor.x >= 0 && neighbor.x < image.Height
                        && neighbor.y >= 0 && neighbor.y < image.Width
                       )
                    {
                        hasData = true;
                        newData += neighbor.weight * image.Data[(neighbor.x) * image.Width + neighbor.y];
                        weight += neighbor.weight;
                    }
                }                    
                if (hasData && weight != 0)
                {
                    newData /= weight;
                    newImage.Data[x * newWidth + y] = newData;
                }
            }                
        }            
    } 

enter image description here

$\endgroup$
  • $\begingroup$ After you fix the main problem, if you want better image quality you should try bicubic interpolation instead of bilinear. blog.demofox.org/2015/08/15/… $\endgroup$ – Alan Wolfe Feb 20 '16 at 5:41
3
$\begingroup$

This is more of a long comment, with an image, than an answer.

Seems to me, at a quick glance, that you consider your pixels sample point to be at integer values. Instead in reality sample is located offset 0.5 pixels of this location.

enter image description here

Image 1: Is the pixel sample located at its integer coordinate? Or is it located offset by 0.5 pixels?

$\endgroup$
2
$\begingroup$

Thanks for the replies. According to joojaa's comments, I modified my codes as below,

public void RotateImageClockwise(double degree)
    {
        Image<double> image = (Image<double>)(PreservedRawImage.Clone());      
        double sinAngle = Math.Sin(Math.PI * degree / 180.0);
        double cosAngle = Math.Cos(Math.PI * degree / 180.0);
        double newHeightRaw, newWidthRaw;

        if (Math.Abs(degree) <= 90)
        {
            newHeightRaw = image.Width * Math.Sin(Math.PI * (Math.Abs(degree)) / 180.0) + image.Height * Math.Cos(Math.PI * Math.Abs(degree) / 180.0);
            newWidthRaw = image.Width * Math.Cos(Math.PI * Math.Abs(degree) / 180.0) + image.Height * Math.Sin(Math.PI * (Math.Abs(degree)) / 180.0);
        }
        else if (Math.Abs(degree) <= 180)
        {
            newHeightRaw = image.Width * Math.Sin(Math.PI * (180 - Math.Abs(degree)) / 180.0) + image.Height * Math.Sin(Math.PI * (Math.Abs(degree) - 90) / 180.0);
            newWidthRaw = image.Width * Math.Cos(Math.PI * (180 - Math.Abs(degree)) / 180.0) + image.Height * Math.Cos(Math.PI * (Math.Abs(degree) - 90) / 180.0);
        }

        int newWidth = (int)Math.Round(newWidthRaw);
        int newHeight = (int)Math.Round(newHeightRaw);
        Image<double> newImage = new Image<double>(newWidth, newHeight);
        for (int x = 0; x < newHeight; x++)
        {
            for (int y = 0; y < newWidth; y++)
            {
                double xt = x - (newHeightRaw - 1) / 2.0;
                double yt = y - (newWidthRaw - 1) / 2.0;

                double originalX = xt * cosAngle - yt * sinAngle + (image.Height - 1) / 2.0;
                double originalY = xt * sinAngle + yt * cosAngle + (image.Width - 1) / 2.0;

                double fractionX = Math.Abs(originalX - (int)Math.Round(originalX));
                double fractionY = Math.Abs(originalY - (int)Math.Round(originalY));

                IList<Point<int>> neighbors = new List<Point<int>> { new Point<int>{x = (int)Math.Round(originalX), y = (int)Math.Round(originalY), weight = (1 - fractionX) * (1 - fractionY)}, 
                                                           new Point<int>{x = (int)Math.Round(originalX), y = (int)Math.Round(originalY) + 1, weight = fractionX * (1 - fractionY)},
                                                           new Point<int>{x = (int)Math.Round(originalX) + 1, y = (int)Math.Round(originalY), weight = (1 - fractionX) * fractionY},
                                                           new Point<int>{x = (int)Math.Round(originalX) + 1, y = (int)Math.Round(originalY) + 1, weight = fractionX * fractionY}
                                                         };
                bool hasData = false;
                double newData = 0;
                double weight = 0;
                foreach (var neighbor in neighbors)
                {
                    if (neighbor.x >= 0 && neighbor.x < image.Height
                        && neighbor.y >= 0 && neighbor.y < image.Width
                        && (neighbor.x) * image.Width + neighbor.y < m_preservedImageValidDataBitmap.Length && m_preservedImageValidDataBitmap[(neighbor.x) * image.Width + neighbor.y] == 'T')
                    {
                        hasData = true;
                        newData += neighbor.weight * image.Data[(neighbor.x) * image.Width + neighbor.y];
                        weight += neighbor.weight;
                    }
                }                    
                if (hasData && weight != 0)
                {
                    newData /= weight;
                    newImage.Data[x * newWidth + y] = newData;                        
                }
            }                
        }
    } 

Now it's working for 90 and 180 degree rotations. But for 45 degree rotation, the bottom line is still blank, which makes me crazy. I'm guessing it's due to the computation inaccuracy when the new image width and height are computed, but I still can't figure it out.

enter image description here

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.