interpolation for smooth downscale of image in OpenCV - c++

I noticed that of the two methods below for scaling an image N halfs that the first produced a more smooth image, looking more appealing to the eye.
while (lod-- > Payload->MaxZoom)
{
cv::resize(img, img, cv::Size(), 0.5, 0.5, cv::INTER_LINEAR);
}
vs
double scale = 1.0 / (1<< (lod - Payload->MaxZoom));
cv::resize(img, img, cv::Size(), scale, scale, cv::INTER_LINEAR);
I am interested in knowing if there is a interpolation that would produce similar result as the first resize but not having to loop over it N times.
Any mathematical insight into why doing the resize in multiply steps can result in a better result is also interesting.
The latter method above gives a very pixelated result (for N=5) where the first is very smooth (it makes sense since its the average of 4 pixel over N steps)

This happens because OpenCV's implementation of linear interpolation is rather simplistic.
A simple implementation of linear interpolation takes the values of four pixels closest to the interpolated point and interpolates between them. This is all right for upscaling, but for downscaling, this will ignore the values of many pixels - if there are N pixels in the output image, then it depends on at most 4N pixels of the input. This cannot give good results when the product of scaling factors is lower than 0.25.
The correct thing to do is to consider all input pixels that correspond to an output pixel after the transformation, and compute an average over them (or more generally, compute a convolution with a suitable resampling filter).
OpenCV seems to have an interpolation mode called cv::INTER_AREA, which should do the thing you want.

Related

Can I balance an extremely bright picture in python? This picture is a result of thousands of pictures stitched together to form a panorama

My aim is to stitch 1-2 thousand images together. I find the key points in all the images, then I find the matches between them. Next, I find the homography between the two images. I also take into account the current homography and all the previous homographies. Finally, I warp the images based on combined homography. (My code is written in python 2.7)
The issue I am facing is that when I overlay the warped images, they become extremely bright. The reason is that most of the area between two consecutive images is common/overalapping. So, when I overlay them, the intensities of the common areas increase by a factor of 2 and as more and more images are overalid the moew bright the values become and eventually I get a matrix where all the pixels have the value of 255.
Can I do something to adjust the brightness after every image I overlay?
I am combining/overlaying the images via open cv function named cv.addWeighted()
dst = cv.addWeighted( src1, alpha, src2, beta, gamma)
here, I am taking alpha and beta = 1
dst = cv.addWeighted( image1, 1, image2, 1, 0)
I also tried decreasing the value of alpha and beta but here a problem comes that, when around 100 images have been overlaid, the first ones start to vanish probably because the intensity of those images became zero after being multiplied by 0.5 at every iteration. The function looked as follows. Here, I also set the gamma as 5:
dst = cv.addWeighted( image1, 0.5, image2, 0.5, 5)
Can someone please help how can I solve the problem of images getting extremely bright (when aplha = beta = 1) or images vanishing after a certain point (when alpha and beta are both around 0.5).
This is the code where I am overlaying the images:
for i in range(0, len(allWarpedImages)):
for j in range(1, len(allWarpedImages[i])):
allWarpedImages[i][0] = cv2.addWeighted(allWarpedImages[i][0], 1, allWarpedImages[i][j], 1, 0)
images.append(allWarpedImages[i][0])
cv2.imwrite('/root/Desktop/thesis' + 'final.png', images[0])
When you stitch two images, the pixel values of overlapping part do not just add up. Ideally, two matching pixels should have the same value (a spot in the first image should also has the same value in the second image), so you simply keep one value.
In reality, two matching pixels may have slightly different pixel value, you may simply average them out. Better still, you adjust their exposure level to match each other before stitching.
For many images to be stitched together, you will need to adjust all of their exposure level to match. To equalize their exposure level is a rather big topic, please read about "histogram equalization" if you are not familiar with it yet.
Also, it is very possible that there is high contrast across that many images, so you may need to make your stitched image an HDR (high dynamic range) image, to prevent pixel value overflow/underflow.

Resize a kernel

For learning purposes I am implementing a blur function. I have it working but I want to resize my kernel to achieve a more blurred affect.
If I scale up my kernel will that indeed create a more blurred affect? And how can I resize my kernel?
I have tried to resize the kernel using resize but that results in a white image.
// create blur kernel
float kdata[] = { 0.0625f, 0.125f, 0.0625f, 0.125f, 0.25f, 0.125f, 0.0625f, 0.125f, 0.0625f };
Mat kernel(3, 3, CV_32F, kdata);
// resize kernel to 9x9 to create a more blurred effect
resize(kernel, kernel, {9,9});
// output is white, whats going wrong?
filter2D(src, output, -1, kernel);
Going a bit to the basics, a kernel is a matrix that is convoluted with your image.
The operation of convolution picks a pixel in each matrix, overlaps the kernel at the kernel's anchor point (usually the middle), and you sum all the values in the image weighted by the values in the kernel.
For example, imagine you had the kernel:
1 0 -1
0 0 0
-1 0 1
(only for demonstration purposes - the values are random)
With the anchor point at the center. Then, filter2D would take all the pixels in the image and overlap the kernel. At each pixel, it would add the upper left and the lower right pixels and subtract the upper right and the lower left pixels, as indicated by the weights in the kernel.
Now, to achieve a greater blur, you need to have a bigger kernel. You cannot simply resize the kernel - the resize function is to change the size of the images. For the kernel, you need to compute the values of the bigger kernel - keep in mind that the kernel is a matrix with special values, not an image.
What a kernel for Gaussian blur does is to have the values carefully chosen (according to a Gaussian distribution) such that the center pixel (the initial value) has the biggest contribution to the final pixel, but also the surrounding pixels get added, with lesser and lesser weights. The contribution of the surrounding pixels, their weights, are tuned by the sigma parameter of the Gaussian. This parameter indicates how fast the gaussian's value drop.
In the end, you need to calculate the values for your kernel, considering the sigma and the size of the kernel you want. This is done either manually (pen and paper), or use a calculator such as this one: http://dev.theomader.com/gaussian-kernel-calculator/.

Thresholding an image in OpenCV using another image matrix of pixel standard deviations as threshold values

I have two videos, one of a background and one of that same background with a person sitting in the frame. I generated two images from the video of just the background: the mean image of the background video (by accumulating the frames and dividing by the number of frames) and an image of standard deviations from the mean per pixel, taken over the frames. In other words, I have two images representing the Gaussian distribution of the background video. Now, I want to threshold an image, not using one fixed threshold value for all pixels, but using the standard deviations from the image (a different threshold per pixel). However, as far as I understand, OpenCV's threshold() function only allows for one fixed threshold. Are there functions I'm missing, or is there a workaround?
A cv::Mat provides methodology to accomplish this.
The setTo() methods takes an optional InputArray as mask.
Assuming the following:
std is your standard deviations cv::Mat, in is the cv::Mat you want to threshold and thresh is the factor for your standard deviations.
Using these values the custom thresholding could be done like this:
// Computes threshold values based on input image + std dev
cv::Mat mask = in +/- (std * thresh);
// Set in.at<>(x,y) to 0 if value is lower than mask.at<>(x,y)
in.setTo(0, in < mask);
The in < mask expression creates a new MatExpr object, a matrix which is 0 at every pixel where the predicate is false, 255 otherwise.
This is a handy way to implement custom thresholding.

blur detection in Images taken with different cameras

I want to detect blurred images using Laplacian Operator. This is the code I am using:
bool checkforblur(Mat img)
{
bool is_blur = 0;
Mat gray,laplacianImage;
Scalar mean, stddev, mean1, stddev1;
double variance1,variance2,threshold;
cvtColor(img, gray, CV_BGR2GRAY);
Laplacian(gray, laplacianImage, CV_64F);
meanStdDev(laplacianImage, mean, stddev, Mat());
meanStdDev(gray, mean1, stddev1, Mat());
variance1 = stddev.val[0]*stddev.val[0];
variance2 = stddev1.val[0]*stddev1.val[0];
double ratio= variance1/variance2;
threshold = 90;
cout<<"Variance is:"<<ratio<<"\n"<<"Threshold Used:"
<<threshold<<endl;
if (ratio <= threshold){is_blur=1;}
return is_blur;
}
This code takes an image as input and returns 1 or 0 based on whether the image is blurred or not.
As suggested I edited the code to check for ratio instead of variance of the laplacian image alone.
But still the threshold varies for images taken with different cameras.
Is the code scene dependent?
How should I change it?
Example:
For the above image the variance is 62.9 So it detects that the image is blurred.
For the above image the variance is 235, Hence it is detecting wrongly as not blurred.
The Laplacian operator is linear, so that its amplitude varies with the amplitude of the signal. Thus the response will be higher for images with a stronger contrast.
You might have a better behavior by normalizing the values, for instance using the ratio of the variance of the Laplacian over the variance of the signal itself, or over the variance of the gradient magnitude.
I also advise you to experiment using sharp images that you progressively blur with a wider and wider gaussian, and to look at the plots of "measured blurriness" versus the know bluriness.
As suggested above, you should normalize this ratio. Basically, if you divide your variance by the mean value you will get the normalized gray level variance, which I think is what you are looking for.
That said, there is an excellent thread on blur detection which I would recommend - full of good info and code examples.

Cement Effect - Artistic Effect

I wish to give an effect to images, where the resultant image would appear as if it is painted on a rough cemented background, and the cemented background customizes itself near the edges to highlight them... Please help me in writing an algorithm to generate such an effect.
The first image is the original image
and the second image is the output im looking for.
please note the edges are detected and the mask changes near the edges to indicate the edges clearly
You need to read up on Bump Mapping. There are plenty of bump mapping algorithms.
The basic algorithm is:
for each pixel
Look up the position on the bump map texture that corresponds to the position on the bumped image.
Calculate the surface normal of the bump map
Add the surface normal from step 2 to the geometric surface normal (in case of an image it's a vector pointing up) so that the normal points in a new direction.
Calculate the interaction of the new 'bumpy' surface with lights in the scene using, for example, Phong shading -- light placement is up to you, and decides where will the shadows lie.
Finally, here's a plain C implementation for 2D images.
Starting with
1) the input image as R, G, B, and
2) a texture image, grayscale.
The images are likely in bytes, 0 to 255. Divide it by 255.0 so we have them as being from 0.0 to 1.0. This makes the math easier. For performance, you wouldn't actually do this but instead use clever fixed-point math, an implementation matter I leave to you.
First, to get the edge effects between different colored areas, add or subtract some fraction of the R, G, and B channels to the texture image:
texture_mod = texture - 0.2*R - 0.3*B
You could get fancier with with nonlinear forumulas, e.g. thresholding the R, G and B channels, or computing some mathematical expression involving them. This is always fun to experiment with; I'm not sure what would work best to recreate your example.
Next, compute an embossed version of texture_mod to create the lighting effect. This is the difference of the texture slid up and right one pixel (or however much you like), and the same texture slid. This give the 3D lighting effect.
emboss = shift(texture_mod, 1,1) - shift(texture_mod, -1, -1)
(Should you use texture_mod or the original texture data in this formula? Experiment and see.)
Here's the power step. Convert the input image to HSV space. (LAB or other colorspaces may work better, or not - experiment and see.) Note that in your desired final image, the cracks between the "mesas" are darker, so we will use the original texture_mod and the emboss difference to alter the V channel, with coefficients to control the strength of the effect:
Vmod = V * ( 1.0 + C_depth * texture_mod + C_light * emboss)
Both C_depth and C_light should be between 0 and 1, probably smaller fractions like 0.2 to 0.5 or so. You will need a fudge factor to keep Vmod from overflowing or clamping at its maximum - divide by (1+C_depth+C_light). Some clamping at the bright end may help the highlights look brighter. As always experiment and see...
As fine point, you could also modify the Saturation channel in some way, perhaps decreasing it where texture_mod is lower.
Finally, convert (H, S, Vmod) back to RGB color space.
If memory is tight or performance critical, you could skip the HSV conversion, and apply the Vmod formula instead to the individual R,G, B channels, but this will cause shifts in hue and saturation. It's a tradeoff between speed and good looks.
This is called bump mapping. It is used to give a non flat appearance to a surface.