I have a video stream coming from a 180 degree fisheye camera. I want to do some image-processing to convert the fisheye view into a normal view.
After some research and lots of read articles I found this paper.
They describe an algorithm (and some formulas) to solve this problem.
I used tried to implement this method in a Matlab. Unfortunately it doesn't work, and I failed to make it work. The "corrected" image looks exactly like the original photograph and there's no any removal of distortion and secondly I am just receiving top left side of the image, not the complete image but changing the value of 'K' to 1.9 gives mw the whole image, but its exactly the same image.
Input image:
Result:
When the value of K is 1.15 as mentioned in the article
When the value of K is 1.9
Here is my code:
image = imread('image2.png');
[Cx, Cy, channel] = size(image);
k = 1.5;
f = (Cx * Cy)/3;
opw = fix(f * tan(asin(sin(atan((Cx/2)/f)) * k)));
oph = fix(f * tan(asin(sin(atan((Cy/2)/f)) * k)));
image_new = zeros(opw, oph,channel);
for i = 1: opw
for j = 1: oph
[theta,rho] = cart2pol(i,j);
R = f * tan(asin(sin(atan(rho/f)) * k));
r = f * tan(asin(sin(atan(R/f))/k));
X = ceil(r * cos(theta));
Y = ceil(r * sin(theta));
for k = 1: 3
image_new(i,j,k) = image(X,Y,k);
end
end
end
image_new = uint8(image_new);
warning('off', 'Images:initSize:adjustingMag');
imshow(image_new);
This is what solved my problem.
input:
strength as floating point >= 0. 0 = no change, high numbers equal stronger correction.
zoom as floating point >= 1. (1 = no change in zoom)
algorithm:
set halfWidth = imageWidth / 2
set halfHeight = imageHeight / 2
if strength = 0 then strength = 0.00001
set correctionRadius = squareroot(imageWidth ^ 2 + imageHeight ^ 2) / strength
for each pixel (x,y) in destinationImage
set newX = x - halfWidth
set newY = y - halfHeight
set distance = squareroot(newX ^ 2 + newY ^ 2)
set r = distance / correctionRadius
if r = 0 then
set theta = 1
else
set theta = arctangent(r) / r
set sourceX = halfWidth + theta * newX * zoom
set sourceY = halfHeight + theta * newY * zoom
set color of pixel (x, y) to color of source image pixel at (sourceX, sourceY)
Related
I am trying to calculate two objects circumscribed radius and inscribed radius using the code below. I used for the inscribedRadius parameter from this script and for the circumscribed radius I used this function
I do not understand why I get for the box shape that the inscribed radius is bigger than the circumscribed radius. Any idea what is wrong? And how to fix it?
Image
Code:
clc;
clear;
RGB = imcomplement(imread('https://i.stack.imgur.com/8WLAt.jpg'));
I = rgb2gray(RGB);
bw = imbinarize(I);
bw = imfill(bw,'holes');
imshow(bw)
hold on;
[B,L] = bwboundaries(bw,'noholes');
stats = regionprops(L,'Centroid','MajorAxisLength');
for i = 1 : numel(stats)
b = B{i};
c = stats(i).Centroid;
y = b(:,1);
x = b(:,2);
plot( b(:,2),b(:,1),'Color','red','linewidth',2);
text(c(1),c(2),num2str(i),'Color','red');
xMin = min(x);
xMax = max(x);
yMin = min(y);
yMax = max(y);
scalingFactor = 1000 / min([xMax-xMin, yMax-yMin]);
x2s = (x - xMin) * scalingFactor + 1;
y2s = (y - yMin) * scalingFactor + 1;
mask = poly2mask(x2s, y2s, ceil(max(y2s)), ceil(max(x2s)));
edtImage = bwdist(~mask);
inscribedRadius = max(edtImage(:));
[yCenter, xCenter] = find(edtImage == inscribedRadius);
xCenter = (xCenter - 1)/ scalingFactor + xMin;
yCenter = (yCenter - 1)/ scalingFactor + yMin;
inscribedRadius = inscribedRadius / scalingFactor
[circumscribedCenter,circumscribedRadius] = minboundcircle(x,y); % from https://www.mathworks.com/matlabcentral/fileexchange/34767-a-suite-of-minimal-bounding-objects?focused=3820656&tab=function
circumscribedRadius
end
The results are:
Object 1: inscribedRadius = 264, cumscribedRadius = 186.6762
Object 2: inscribedRadius = 130.4079, circumscribedRadius = 132.3831
The values of object 1 (box) are wrong as the inscribedRadius can not be bigger than the cumscribedRadius. They are fine for object 2 (circle)
If you look at the mask image, you'll notice that the square shape is drawn touching the right and bottom edges of the image. There's background only along the left and top of the shape. The distance transform bwdist(~mask) subsequently computes the distance to the background for each pixel within the shape, but since there's background only to the left and top, the pixel at the bottom right of the shape has a distance of 1000, rather than 1. The distance transform is supposed to have a maximum in the middle, at a point equidistant to at least the three nearest shape edge points.
The solution is simple: poly2mask must create an image that is one pixel wider and taller:
mask = poly2mask(x2s, y2s, ceil(max(y2s)) + 1, ceil(max(x2s)) + 1);
^^^ ^^^
With this change, the computed inscribedRadius for the square is 132, as expected.
Here is a Octave/Matlab code for generating a Mobius strip.
u = linspace(0,2*pi,100);
v = linspace(-1.0,1.0,100);
[u,v] = meshgrid(u,v);
x = (1+v.*cos(u/2)).*cos(u);
y = (1+v.*cos(u/2)).*sin(u);
z = v.*sin(u/2);
plot3(x,y,z)
The output is as follow.
In this, strip, I need the edge coordinates (XYZ). How I can get the XYZ coordinates of the edge?
plot3(x([1 end],:).',y([1 end],:).',z([1 end],:).', "b")
I could do this using python as follows. See a related post here
bLength=1.6
numPoints=10
radius = bLength*numPoints / (2 * np.pi)
theta = np.linspace(0,2*np.pi,numPoints,endpoint=False)
dtheta=theta[1]-theta[0]
x0,y0=(radius * np.cos(theta)), (radius * np.sin(theta))
x1,y1=(radius * np.cos(theta+dtheta/2)) , (radius * np.sin(theta+dtheta/2))
cons0=np.ones(x0.shape)*0
cons1=np.ones(x1.shape)*2
np.savetxt('cooRing00.csv',np.c_[x0,y0,cons0],delimiter=' ',fmt='%10f')
np.savetxt('cooRing01.csv',np.c_[x1,y1,cons1],delimiter=' ',fmt='%10f')
EDIT: This is the code I am using that generates an edge detected looking image:
cookiesImage = rgb2gray(imread('Cookies.png'));
width = 45;
height = 45;
KMAX = pi / 2;
f = sqrt(2);
delta = pi / 3;
output = zeros(size(cookiesImage, 1), size(cookiesImage, 2), 8);
for i = 0 : 7
wavelets = GaborWavelet(width, height, KMAX, f, i, 2, delta);
figure(1);
subplot(1, 8, i + 1), imshow(real(wavelets), []);
output(:, :, i + 1) = imfilter(cookiesImage, wavelets, 'symmetric');
end
display = sum(abs(output).^2, 3).^0.5;
display = display./max(display(:));
figure(2); imshow(display);
function GWKernel = GaborWavelet (width, height, KMAX, f, u , v, delta)
delta2 = delta * delta;
kv = KMAX / (f^v);
thetaU = (u * pi) / 8;
kuv = kv * exp (1i * thetaU);
kuv2 = abs(kuv)^2;
GWKernel = zeros (height, width);
for y = -height/ 2 + 1 : height / 2
for x = -width / 2 + 1 : width / 2
GWKernel(y + height / 2, x + width / 2) = (kuv2 / delta2) * exp(-0.5 * kuv2 * (x * x + y * y) / delta2) * (exp(1i * (real(kuv) * y + imag (kuv) * x )) - exp (-0.5 * delta2));
end
end
This is the function that I am using for the Wavelets and this is how I am trying to apply them but all I am getting is an edge detected looking image, rather than one as in this link.
Running your code, I see the following wavelets being generated:
These look a lot like rotated second derivatives. This is only the real (even) component of the Gabor filter kernels. The imaginary (odd) counterparts look like first derivatives.
This is why you feel like your result is like an edge-detected image. It sort of is.
Try increasing the size of the filter (not the footprint widthxheight, but the delta that determines the size of the envelope). This will make it so that you see a larger portion of the sinusoid waves that form the Gabor kernel.
Next, the result image you show is the sum of the square magnitude of the individual Gabor filters. Try displaying the real or imaginary components of one of the filter results, you'll see it looks more like you'd expect:
imshow(real(output(:,:,3)),[])
I'm not familiar with this parametrization of the Gabor kernel, but note that it has a Gaussian envelope. Therefore, the footprint (width, height) of the kernel can be adjusted to the size of this Gaussian (which seems to use delta as the sigma). I typically recommend using a kernel footprint of 2*ceil(3*sigma)+1 for the Gaussian kernel. The same applies here:
width = 2*ceil(3*delta)+1;
height = width;
This will speed up computations, as you see in the image above your kernels have lots of near-zero values in them, it is possible to crop them to a smaller size without affecting the output.
The GaborWavelet function can also be simplified a lot using vectorization:
function GWKernel = GaborWavelet (width, height, KMAX, f, u , v, delta)
delta2 = delta * delta;
kv = KMAX / (f^v);
thetaU = (u * pi) / 8;
kuv = kv * exp (1i * thetaU);
kuv2 = abs(kuv)^2;
x = -width/2 + 1 : width/2;
[x,y] = meshgrid(x,x);
GWKernel = (kuv2 / delta2) * exp(-0.5 * kuv2 * (x .* x + y .* y) / delta2) ...
.* (exp(1i * (real(kuv) * y + imag (kuv) * x )) - exp (-0.5 * delta2));
Based on this question, I can confirm that horizontal patterns can be imposed onto a matrix (which in this case is an image), by multiplying it with a modulation signal created with this:
vModulationSignal = 1 + (0.5 * cos(2 * pi * (signalFreq / numRows) * [0:(numRows - 1)].'));
It would also be great if someone could explain to why the above modulation signal works.
Now I want to create diagonal patterns such as :
And criss-cross (checkered) patterns such as this:
using a similar vModulationSignal
Code Excerpt where the modulation signal is created
numRows = size(mInputImage, 1);
numCols = size(mInputImage, 2);
signalFreq = floor(numRows / 1.25);
vModulationSignal = 1 + (0.5 * cos(2 * pi * (signalFreq / numRows) * [0:(numRows - 1)].'));
mOutputImage = bsxfun(#times, mInputImage, vModulationSignal);
Code Excerpt where I'm trying to create the criss cross signal
numRows = size(mInputImage, 1);
numCols = size(mInputImage, 2);
signalFreq1 = floor(numRows / 1.25);
signalFreq2 = floor(numCols / 1.25);
vModulationSignal1 = 1 + (0.5 * cos(2 * pi * (signalFreq / numRows) * [0:(numRows - 1)].'));
vModulationSignal2 = 1 + (0.5 * cos(2 * pi * (signalFreq / numRows) * [0:(numRows - 1)].'));
mOutputImage = bsxfun(#times, mInputImage, vModulationSignal);
figure();
imshow(mOutputImage);
For horizontal, vertical, diagonal stripes:
fx = 1 / 20; % 1 / period in x direction
fy = 1 / 20; % 1 / period in y direction
Nx = 200; % image dimension in x direction
Ny = 200; % image dimension in y direction
[xi, yi] = ndgrid(1 : Nx, 1 : Ny);
mask = sin(2 * pi * (fx * xi + fy * yi)) > 0; % for binary mask
mask = (sin(2 * pi * (fx * xi + fy * yi)) + 1) / 2; % for gradual [0,1] mask
imagesc(mask); % only if you want to see it
just choose fx and fy accordingly (set fy=0 for horizontal stripes, fx=0 for vertical stripes and fx,fy equal for diagonal stripes). Btw. the period of the stripes (in pixels) is exactly
period_in_pixel = 1 / sqrt(fx^2 + fy^2);
For checkerboard patterns:
f = 1 / 20; % 1 / period
Nx = 200;
Ny = 200;
[xi, yi] = ndgrid(1 : Nx, 1 : Ny);
mask = sin(2 * pi * f * xi) .* sin(2 * pi * f * yi) > 0; % for binary mask
mask = (sin(2 * pi * f * xi) .* sin(2 * pi * f * yi) + 1) / 2; % for more gradual mask
imagesc(mask);
Here the number of black and white squares per x, y direction is:
number_squares_x = 2 * f * Nx
number_squares_y = 2 * f * Ny
And if you know the size of your image and the number of squares that you want, you can use this to calculate the parameter f.
Multiplying the mask with the image:
Now that is easy. The mask is a logical (white = true, black = false). Now you only have to decide which part you want to keep (the white or the black part).
Multiply your image with the mask
masked_image = original_image .* mask;
to keep the white areas in the mask and
masked_image = original_image .* ~mask;
for the opposite.
This is actually an extension of Trilarion's answer that gives better control on stripes appearance:
function out = diagStripes( outSize, stripeAngle, stripeDistance, stripeThickness )
stripeAngle = wrapTo2Pi(-stripeAngle+pi/2);
if (stripeAngle == pi/2) || (stripeAngle == 3*pi/2)
f = #(fx, fy, xi, yi) cos(2 * pi * (fy * yi)); % vertical stripes
elseif (stripeAngle == 0)||(stripeAngle == pi)
f = #(fx, fy, xi, yi) cos(2 * pi * (fx * xi)); % horizontal stripes
else
f = #(fx, fy, xi, yi) cos(2 * pi * (fx * xi + fy * yi)); % diagonal stripes
end
if numel(outSize) == 1
outSize = [outSize outSize];
end;
fx = cos(stripeAngle) / stripeDistance; % period in x direction
fy = sin(stripeAngle) / stripeDistance; % period in y direction
Nx = outSize(2); % image dimension in x direction
Ny = outSize(1); % image dimension in y direction
[yi, xi] = ndgrid((1 : Ny)-Ny/2, (1 : Nx)-Nx/2);
mask = (f(fx, fy, xi, yi)+1)/2; % for gradual [0,1] mask
out = mask < (cos(pi*stripeThickness)+1)/2; % for binary mask
end
outSize is a two or one element vector that gives the dimensions of output image in pixels, stripeAngle gives the slope of stripes in radians, stripeDistance is the distance between centers of stripes in pixels and stripeDistance is a float value in [0 .. 1] that gives the percent of coverage of (black) stripes in (white) background.
There're also answers to the other question for generating customized checkerboard patterns.
I'm plotting a square image, but since my camera views out of a circular construction, I want the image to look circular as well. So to do this, I just wanted to create a mask for the image (basically create a matrix, and multiply my data by the mask, so if I want to retain my image I am multiplying by one, and if I want that part of the image to go to black, I multiply by 0).
I'm not sure the best way to make a matrix that will represent a circular opening though. I just want every element within the circle to be a "1" and every element outside the circle to be a "0" so I can color my image accordingly. I was thinking of doing a for loop, but I was hoping there was a faster way to do it. So...all I need is:
A matrix that is 1280x720
I need a circle that has a diameter of 720, centered in the middle of the 1280x720 matrix (what I mean by this is all elements corresponding to being within the circle have a "1" and all other elements have a "0"
My attempt
mask = zeros(1280,720)
for i = 1:1280
for j = 1:720
if i + j > 640 && i + j < 1360
mask(i,j) = 1;
end
end
end
Well the above obviously doesn't work, I need to look at it a little better to form a better equation for determing when to add a 1 =P but ideally I would like to not use a for loop
Thanks, let me know if anything is unclear!
#kol 's answer looks correct. You can do this with vectorized code using the meshgrid function.
width = 1280;
height = 720;
radius = 360;
centerW = width/2;
centerH = height/2;
[W,H] = meshgrid(1:width,1:height);
mask = ((W-centerW).^2 + (H-centerH).^2) < radius^2;
Here is a possible solution:
width = 160;
height = 120;
mask = zeros(width, height);
center_x = width / 2;
center_y = height / 2;
radius = min(width, height) / 2;
radius2 = radius ^ 2;
for i = 1 : width
for j = 1 : height
dx = i - center_x;
dy = j - center_y;
dx2 = dx ^ 2;
dy2 = dy ^ 2;
mask(i, j) = dx2 + dy2 <= radius2;
end;
end;
picture = randn(width, height); % test image :)
masked_image = picture .* mask;
imagesc(masked_image);