I am interested in many kind of computer algorithms, including algorithms for computer graphics, digital image processing and 3D-graphics.
In one of my projects I needed to have "fish eye lens" like effect, and tried to find easy equation or ready function from the internet. To my surprise, there were not complete functions available, just theories and images how it can be achieved.
Here is a brief tutorial and loop how to create fish eye -effect to any image. The function is written in Java, but as you can see, it is easily rewritten with any programming language.
If you find this article and function useful and you will use it in your own projects, please refer where the source came. In any case, leave a comment. Questions and requests are welcome!
What is fish eye effect?
Fish eye effect is a mirror effect which happens when the image is drawn on to a surface of a sphere or a hemisphere. The surface does not need to be a perfect sphere, it can also be paraboloid or have other kind of curve as a surface.
Common effect is, that the image is "zoomed" in the center and "shrink" at the edges. The image is usually round (because we are dealing with spheres) but the image can also be clipped to be square.
Mathematics
What we need is a function which maps any pixel from the image to a surface of a sphere.
In this fish eye effect, we are not using equations for a sphere, but an equation for unit circle:
x^2 + y^2 = 1
Solving y
y^2 = 1 - x^2
and then finally
y = sqrt(1 - x^2)
Because we are limiting all our values between 0.0 and 1.0, we can create values for nice arc when x goes from 0.0 to 1.0.
For example, in the image we have marked x-position 0.75, and we can calculate y-position substituting x in y = sqrt(1 - x^2), which gives us y = 0.66.
For our purpose, we will flip the curve with subtracting the result from 1.0:
d = 1 - sqrt(1 - x^2)
This curve will give us values for the difference of the distance of the source pixel and destination pixel, from the center of the image, when mapping from plane to sphere.
The new distance is then the original distance added with the difference:
r' = r + d (r' > r)
Polar coordinates
The normal screen coordinates (two dimensional Cartesian coordinates, simply x- and y-axis) are difficult when dealing with trigonometric functions, because the input values are typically between -1 to 1 and the angles are in radians from 0 to 2*PI.
Everything becomes more easier when you change the screen coordinates (x,y) to polar coordinates (r, theta). This is done with simple functions:
From Cartesian to polar:
r = sqrt(y^2 + x^2)
theta = atan2(y, x)
From polar to Cartesian:
x = r cos(theta)
y = r sin(theta)
y = r sin(theta)
In polar coordinates, any point in the image can be accessed with the angle (theta) and distance from center (r).
Algorithm
What we need:
- the distance from center of the source image to any pixel (x,y) in the same image
- the position (x', y') where the pixel belongs in the fish eye image
In the following pseudo code, we are using polar coordinates. The key part in the whole process is to use polar coordinates and this fact: The actual pixel translation from 2D-image to sphere surface is done with manipulating the distance from center (r).
Pseudo code:
for each pixel (x,y)
normalize (x,y) to (nx, ny) to be in range [-1,1]
calculate distance from (nx, ny) to center (0,0)
convert (nx,ny) to polar coordinates
calculate new distance from center on the sphere surface
The new distance is r' = r + (1 - sqrt(1 -r^2)) / 2
translate (nx, ny) back to screen coordinates (x',y')
The new distance is r' = r + (1 - sqrt(1 -r^2)) / 2
translate (nx, ny) back to screen coordinates (x',y')
The image above shows how Points P1 and P2 will be displaced to P1' and P2', so that the angle remains the same. In other words, the point is displaced along the original line towards the edge of the unit circle. The distance from the center tells us how big is the displacement. In the center and near the center the displacement value is zero or almost zero. Near the edges displacement value grows towards value of 1.0.
The image above gives the idea behind the displacement value. For every pixel in the result image plane, there is a pixel from source image plane. In here, the source image plane is stretched and curved. It may be easier to understand, if you think a one single line instead of the plane. Along that line, the pixels are almost normal at the center, but towards the ends, the pixels are fetched further and further away from the center.
Most important thing
The most important thing is to understand, that for each result pixel, there is a source pixel, on the same line going through the center, but with a greater distance.
The source
public static int[] fisheye(int[] srcpixels, double w, double h) {
/*
* Fish eye effect
* tejopa, 2012-04-29
* http://popscan.blogspot.com
* http://www.eemeli.de
*/
// create the result data
int[] dstpixels = new int[(int)(w*h)];
// for each row
for (int y=0;y<h;y++) {
// normalize y coordinate to -1 ... 1
double ny = ((2*y)/h)-1;
// pre calculate ny*ny
double ny2 = ny*ny;
// for each column
for (int x=0;x<w;x++) {
// normalize x coordinate to -1 ... 1
double nx = ((2*x)/w)-1;
// pre calculate nx*nx
double nx2 = nx*nx;
// calculate distance from center (0,0)
// this will include circle or ellipse shape portion
// of the image, depending on image dimensions
// you can experiment with images with different dimensions
double r = Math.sqrt(nx2+ny2);
// discard pixels outside from circle!
if (0.0<=r&&r<=1.0) {
double nr = Math.sqrt(1.0-r*r);
// new distance is between 0 ... 1
nr = (r + (1.0-nr)) / 2.0;
// discard radius greater than 1.0
if (nr<=1.0) {
// calculate the angle for polar coordinates
double theta = Math.atan2(ny,nx);
// calculate new x position with new distance in same angle
double nxn = nr*Math.cos(theta);
// calculate new y position with new distance in same angle
double nyn = nr*Math.sin(theta);
// map from -1 ... 1 to image coordinates
int x2 = (int)(((nxn+1)*w)/2.0);
// map from -1 ... 1 to image coordinates
int y2 = (int)(((nyn+1)*h)/2.0);
// find (x2,y2) position from source pixels
int srcpos = (int)(y2*w+x2);
// make sure that position stays within arrays
if (srcpos>=0 & srcpos < w*h) {
// get new pixel (x2,y2) and put it to target array at (x,y)
dstpixels[(int)(y*w+x)] = srcpixels[srcpos];
}
}
}
}
}
//return result pixels
return dstpixels;
}
Example pictures
Original |
After applying fish eye function |