Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
150 lines (123 sloc) 5.87 KB

⬅️

Can you rotate a meme?

Hey!

Image rotation is the very basic operation in image (pre)processing. We are so often using this procedure, that I even asked myself, do I really know how to rotate an image without built-in function? That's how it all started.
The first and the most important math technique to refresh to accomplish this task is Polar Coordinate system. Usually, since image is matrix, we usually work with it in Cartesian coordinate system (translations, flipping, etc). However, rotation operation is much easier in polar coordinate system, where radius and ANGLE are already how we can represent any point (pixel). Here is a brief result:

Rotation

traintest

1. Code

Here is the code. The important part is commented out:

%% 1-D Vector of original image size
x1 = zeros([size(A,1)*size(A,2) 1]);
x2 = zeros([size(A,1)*size(A,2) 1]);

% Empty template of zeros
C = uint8(zeros([size(A,1) size(A,2)]));

% Specified angle
deg = 45;

% Midpoint (axis of rotation)
midx = ceil((size(C,1)+1)/2);
midy = ceil((size(C,2)+1)/2);

% Main loop
m = 1;
for i = 1:size(A,1)
    i1 = i-midx;
    for j = 1:size(A,2)
        % Converting Cartesian to polar from central point
        [t,r] = cart2pol(i1,j-midy);
        % Converting from radians to degree with new added value
        t1 = radtodeg(t)+deg;
        % Converting back degrees to radians
        t = degtorad(t1);
        % Convertin back polar to Cartesian
        [x,y] = pol2cart(t,r);
        % Floats to integers replacing with new values
        x1(m) = round(x+midx);
        x2(m) = round(y+midy);
        m = m+1;
    end
end

x1 and x2 represent values of Cartesian system coordinates. Sometimes, their value may go negative. This happens when rotated pixels are out of bounds. We can prevent it by shifting pixels to the exact absolute value of that negative:

% Checking for negative values (Out of image size range)
min_x1 = min(x1);
min_x2 = min(x2);

%% Shifting them
if min_x1 <= 0
    x1 = abs(min_x1) + x1 + 1;
end
if min_x2 <= 0
    x2 = abs(min_x2) + x2 + 1;
end

n=1;

%% Putting old pixels to zero template
for i = 1:size(A,1)
    for j = 1:size(A,2)
        C(x1(n),x2(n)) = A(i,j);
        n = n+1;
    end
end

Output = C;

Done. But let's inspect the image:

Black dots (empty pixels)

traintest

We can notice here of black empty dots. These mean that due to rotation, size rotated image is different from original image, and so, there are extra pixels at the places. If we do not assign any value to these pixels, they will appear as black (0). So, how do we manage them? There are several techniques, actually, such as nearest neighbour, bilinear, bicubic interpolation. We will consider first and second in this post.
In essence, nearest neighbour methods simply reads values of the nearest pixel and puts it into the one, which is empty. Here is how it works in terms of code:

% Fill pixels with nearest neighbour value
for i = 2:size(C,1)-1
    for j = 2:size(C,2)-1
        % 3-3 Neighbourhood
        temp = C(i-1:i+1,j-1:j+1);
        % If center point is zero and neighbourhood sum is not zero
        if(temp(5)==0 && sum(temp(:))~=0)
            % Find non-zero values
            pt = find(temp~=0);
            % Sort them according to distance
            [val, ind] = sort(abs(pt-5));
            % Replace central point with new nearest neighbour value
            Output(i,j) = temp(pt(ind(1)));
        end
    end
end

figure()
imshow(Output)

Let's see the zoomed result:

Nearest neighbour result (Zoomed)

traintest

Good. No black dots anymore. However, the edge parts in the image are rough. In original image, they are more smooth. So, this is the drawback of nearest neighbour technique. Since, it does not care about neighbourhood information in general, and takes only nearest neighbour value, such effects (roughness) can take place in the result. So, let's inspect the bilinear interpolation.
Bilinear interpolation reads whole 3x3 neighbourhood around the empty pixels and calculates weighted average, where weights are distances from center point. These values are combined to get weighted average value. This value is then replaced to the empty pixel:

% Fill pixels with bilinear interpolation
for i = 2:size(C,1)-1 
    for j = 2:size(C,2)-1 
        % 3-3 Neighbourhood
        temp = C(i-1:i+1,j-1:j+1);
        % If center point is zero and neighbourhood sum is not zero
        if(temp(5)==0 && sum(temp(:))~=0)
            % Pixel values of neighbors
            ns = [temp(1:4),temp(6:9)];
            % Weight values (distances) of neighbours
            ws = [sqrt(2) 1 sqrt(2) 1 1 sqrt(2) 1 sqrt(2)];
            % Weighted average
            wal = round(mean(double(ns).*ws));
            % Replacing central pixel with weighted average value
            Output(i,j) = wal;
        end
    end
end

figure
imshow(Output);

Bilinear interpolation (Zoomed)

traintest

Now it gets smoother. We can go even more, if we use bicubic interpolation. The difference between bilinear and bicubic interpolation is in the size of considered neigbourhood. For example, if bilinear uses 3x3 neighbourhood, bicubic considers 4x4 neighbourhood. That's it. We can rotate a meme without MATLAB's imrotate and OpenCV's cv2.rotate().

-Have fun!

You can’t perform that action at this time.