It turned out to be trickier than I thought....why???
Rotation Matrix = [cos(t) sin(t); -sin(t) cos(t)]. Rotation Matrix tells where the new position of current pixel.
Although this method can be used for rotating image. Couple of problems
1) The pixel may be out of boundaries of the image
2) Sub-optimal results (pixellation)
A better approach is to think about the problem in reverse. For example, what would be the pixels in input image which contribute to the a pixel in output image. To find the answer, we have to invert rotation matrix.
The rotation matrix might give fractional pixels. For example (1,2) in output pixel might be equivalent to
(100.1, 20.1) in input image. One option would be to find nearest neighbour. The other option would be to use interpolation techniques such as
bilinear interpolation etc.,
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <iostream>
#include <iomanip>
#include <cassert>
using namespace cv;
enum interp
{
nearest,
bilinear
};
Mat rotateImage(const Mat source, double angle,int border=20);
cv::Mat imageRotate(const cv::Mat source, float angle, interp interp_type= nearest);
inline float bilinear_interp(cv::Mat source, const float& row, const float& col);
int main()
{
//Read Image from a file
const string foldername = "C:/Users/kvemishe/Documents/Vision/coursera/images/standard_test_images";
const string filename = "/lena_color_256.tif";
string fullname = foldername+filename;
Mat image = imread(foldername+filename,0);
if (!image.data)
{
std::cout <<"Error reading file \n";
getchar();
exit(0);
}
imshow("Raw Image from file", image);
const int angle = 15;
int vangle = abs(angle)%90;
float t = vangle * 3.14/ 180;
int x = image.rows; int y = image.cols;
float xborder = 0.5*( (cos(t)-1) * x + y * sin(t));
float yborder = 0.5*( (cos(t)-1) * y + x * sin(t));
std::cout << "xborder is " << xborder << std::endl;
std::cout << "yborder is " << yborder << std::endl;
int border = std::max(xborder,yborder);
/*
Mat rimage = rotateImage(image,angle,0);
Mat rimage2 = rotateImage(image,angle,border);
imshow("Rotated Image without border",rimage);
imshow("Rotated Image with border",rimage2);
*/
Mat rim = imageRotate(image,angle);
imshow("No Interp", rim);
Mat rim_interp = imageRotate(image,angle, bilinear);
imshow("Interp", rim_interp);
imwrite("rawimage.jpg", image);
imwrite("RotatedImage_nointerp.jpg", rim);
imwrite("RotatedImage_bilinearinterp.jpg", rim_interp);
waitKey(0);
}
//Rotate using OpenCV
Mat rotateImage(const Mat source, double angle,int border, enum interp)
{
Mat bordered_source;
int top,bottom,left,right;
top=bottom=left=right=border;
copyMakeBorder( source, bordered_source, top, bottom, left, right, BORDER_CONSTANT,cv::Scalar() );
Point2f src_center(bordered_source.cols/2.0F, bordered_source.rows/2.0F);
Mat rot_mat = getRotationMatrix2D(src_center, angle, 1.0);
Mat dst;
warpAffine(bordered_source, dst, rot_mat, bordered_source.size());
return dst;
}
//Rotate using an algorithm
cv::Mat imageRotate(const cv::Mat source, float angle, interp interp_type)
{
assert(source.rows >0 && source.cols >0);
//Create Output image space
int nRows = source.rows; int nCols = source.cols;
cv::Mat dst(nRows, nCols,source.type(),cv::Scalar());
int ci, cj; //pixel location w.r.t center
float t, si, sj; //source pixels required for destination pixel
//Convert angle from degrees to radians
t = angle /180 * 3.1412;
for (int i=0; i < nRows ; i++)
{
for (int j=0; j < nCols;j++)
{
//Bring the co-ordinate system to center of image as we will perform rotation w.r.t center
ci = nRows/2 -i; cj= nCols/2-j;
//Get the pixels from source image for corresponding rotation
si = cos(t)* ci + sin(t) * cj;
sj = -sin(t) * ci + cos(t) * cj;
//Convert the source pixel location to original co-ordinate system
si = nRows/2-si; sj = nCols/2-sj;
//Some source pixels might be out of the image, check if they are with in image borders
if (si >0 && ceil(si) <nRows && sj >0 && ceil(sj) <nCols)
{
switch(interp_type)
{
case nearest:
dst.at<uchar>(i,j) = source.at<uchar>(si,sj); //using nearest neighbor
break;
case bilinear:
dst.at<uchar>(i,j) = bilinear_interp(source,si,sj);
break;
}
}
}
}
return dst;
}
inline float bilinear_interp(cv::Mat source, const float& row, const float& col)
{
int loRow = floor(row);
int hiRow = ceil(row);
int loCol = floor(col);
int hiCol = ceil(col);
//Perform Interpolation for column point along two rows
//Row1 Interpolation
float row1val = (hiCol - col) *source.at<uchar>(loRow,loCol) + (col - loCol) *source.at<uchar>(loRow,hiCol);
//Row2 Interpolation
float row2val = (hiCol - col) *source.at<uchar>(hiRow,loCol) + (col - loCol) *source.at<uchar>(hiRow,hiCol);
//Perform final interpolation for required row
float finalval = (hiRow - row)* row1val + (row-loRow) * row2val;
return finalval;
}