Choose luminosity (exposure) from HDR image - c++

I'm currently stuck on a video projet from pictures.
Problem :
I'm extracting pictures from UE4, due to a bug, not all lights are taken into account during the rendering of the screenshot.
Output are HDR images. I want to get better brighteness because the exported picture are very dark, like the first exposure.
Using the "exposure bias" parameter in UE4 i'm able to real good luminosity of my scene, but cannot apply this parameter to the screenshot rendering :
Tries :
Using Tonemapper algorithm (speciafically cv::TonemapDrago) i'm able to get better image result :
The main problem, for my case, of the Tonemap Algorithm is because the global luminosity is changed depending of luminosity of areas : In the second image, the window add lots of light, so the algorithm low all the luminosity to adjust the mean.
In the video rendered, the light change is very brutal.
I've tried to change brightness and saturation without success.
I've modified the code of the TonemapDrago trying to use constants for some steps of the algorithm.
Question :
I would like to "choose the exposure time" from an HDR image. Tonemap is based on several exposure time of the same image, not interesting in my case.
Any other idea is welcome.
EDIT:
CV::Mat depth is 5, type is CV_32FC3
cout << mat.step give me 19200
Here are 2 samples i use to try solving my problem :
First Image
Image with light window
Edit 2 :
Cannot open .HDR picture with gimp, event with the "explosure blend" plugin.
I'm able to get great enough result using Photoshop. Any idea of the algorithm behind that ? Any of the 6 Tonemap algos by OpenCV allow to choose an exposure correction.
EDIT 3:
I've followed the algorithm explain in this tuto for openGL, which is giving this C+ code to me :
cv::Mat exposureTonemap (cv::Mat m, float gamma = 2.2, float exposure = 1)
{
// Exposure tone mapping
cv::Mat exp;
cv::exp( (-m) * exposure, exp );
cv::Mat mapped = cv::Vec3f(1.0) - exp;
// Gamma correction
cv::pow(exp, 1.0f / gamma, exp);
cv::imshow("exposure tonemap", exp );
cv::waitKey();
return exp;
}
Applying this algo on my .HDR picture i got very bright result even with a correction of 1 and 1 for gamma and exposure :
Reading the code, there is something wrong because 1 and 1 as argument should not modify the picture.
Fixed that, the answer is posted. thanks a lot to #user3896254 (Ge saw it too in comment)

Consider using Retinex. It uses single image for input and is included in GIMP, so is easy to toy around, besides you can get its source code (or roll your own, which is pretty simple anyway). Since you have renders instead of photos - there's no noise, and you theoretically are able to adjust the colours to your needs.
But as #mark-ransom has already said, you may have trouble recovering information from your rendered output. you said you have HDR images as render output, but I am not sure what do you mean. Is it a single RGB image? What is the colour depth of each channel? I have tried to apply retinex to your sample, but obviously it doesn't look good, because of compression, and limited range that was applied before saving. If your output has high range and is not compressed - you'll get better results.
EDIT: I have tried retinex on your input and it turned out not very good - the bright parts of image (lamps/windows) introduced ugly dark halos around them.
In this case simple tonemapping&gamma correction looks a lot better. Your code was almost fine, you just had a little typo:
instead of cv::pow(exp, 1.0f / gamma, exp); you should have had v::pow(mapped, 1.0f / gamma, exp);
I have messed around with your code, and noticed that this tonemapping seems to degrade color saturation. To overcome this I perform it only on V channel of HSV image. Compare results yourself (left - full space tonemapping, right - V only):
Note floor color, sky in window and yellowish light color that got preserved with this approach.
Here is full code for the sake of completeness:
#include <opencv2/opencv.hpp>
using namespace cv;
Mat1f exposureTonemap (Mat1f m, float gamma = 2.2, float exposure = 1) {
// Exposure tone mapping
Mat1f exp;
cv::exp( (-m) * exposure, exp );
Mat1f mapped = 1.0f - exp;
// Gamma correction
cv::pow(mapped, 1.0f / gamma, mapped);
return mapped;
}
Mat3f hsvExposureTonemap(Mat &a) {
Mat3f hsvComb;
cvtColor(a, hsvComb, COLOR_RGB2HSV);
Mat1f hsv[3];
split(hsvComb, hsv);
hsv[2] = exposureTonemap(hsv[2], 2.2, 10);
merge(hsv, 3, hsvComb);
Mat rgb;
cvtColor(hsvComb, rgb, COLOR_HSV2RGB);
return rgb;
}
int main() {
Mat a = imread("first.HDR", -1);
Mat b = imread("withwindow.HDR", -1);
imshow("a", hsvExposureTonemap(a));
imshow("b", hsvExposureTonemap(b));
waitKey();
return 0;
}

What kind of scene lighting are you currently using? It looks like you are using point lights where the lightbulbs would be, but they aren't bright enough. In your unrendered scene, the scene is going to be full brightness. In your rendered scene, you'll get darkness.
I would maybe recommend at least a minimal sky light so that you always have some light across your scene (unless you have areas of actual darkness)

cv::Mat exposureTonemap (cv::Mat m, float gamma = 2.2, float exposure = 1)
{
// Exposure tone mapping
cv::Mat exp;
cv::exp( (-m) * exposure, exp );
cv::Mat mapped = cv::Scalar(1.0f, 1.0f, 1.0f) - exp;
// Gamma correction
cv::pow(mapped, 1.0f / gamma, mapped);
/*
cv::imshow("exposure tonemap", mapped );
cv::waitKey();
*/
return mapped;
}
This algorithm is a Tonemapper trying to simulate exposure bias in an HDR
If you want to use it in openCv 3.0 don't forget to open with -1 as last argument of imread cv::Mat img = cv::imread("mypicture.HDR", -1);

Related

Algorithm, which can remove outliers, but do not blur other part of image

Is there any algorithm, which can remove outliers, but do not blur other part of image?
Only for example, when we use cv::StereoBM/SBGM or cv::gpu::StereoConstantSpaceBP from opencv, then we can have outliers, as shown in relevant question: opencv sgbm produces outliers on object edges Also, we can get large bursts of intensity (strong variations) in local area of image with similar colors:
And many other cases...
The simplest solution is using cv::medianBlur(), but it will smooth all image, not only outliers: Median filter example video
Is there any algorithm which smoothes only outliers, and It does not affect the rest of the image?
Is there anything better than this?
// get cv::Mat src_frame ...
int outliers_size = 10;
int outliers_intensive = 100;
int ksize = outliers_size*2 + 1; // smooth all outliers smaller than 11x11
cv::Mat smoothed;
cv::medianBlur( src_frame, smoothed, ksize );
cv::Mat diff;
cv::absdiff( src_frame, smoothed, diff );
cv::Mat mask = diff > Scalar( outliers_intensive );
smoothed.copyTo( src_frame, mask );
// we have smoothed only small outliers areas in src_frame
Perhaps you are looking for the bilateral filter?
OpenCV says:
we have explained some filters which main goal is to smooth an input
image. However, sometimes the filters do not only dissolve the noise,
but also smooth away the edges. To avoid this (at certain extent at
least), we can use a bilateral filter.
OpenCV has this built-in: http://docs.opencv.org/modules/imgproc/doc/filtering.html?highlight=bilateralfilter#bilateralfilter

Matching small grayscale images

I want to test whether two images match. Partial matches also interest me.
The problem is that the images suffer from strong noise. Another problem is that the images might be rotated with an unknown angle. The objects shown in the images will roughly always have the same scale!
The images show area scans from a top-shot perspective. "Lines" are mostly walls and other objects are mostly trees and different kinds of plants.
Another problem was, that the left image was very blurry and the right one's lines were very thin.
To compensate for this difference I used dilation. The resulting images are the ones I uploaded.
Although It can easily be seen that these images match almost perfectly I cannot convince my algorithm of this fact.
My first idea was a feature based matching, but the matches are horrible. It only worked for a rotation angle of -90°, 0° and 90°. Although most descriptors are rotation invariant (in past projects they really were), the rotation invariance seems to fail for this example.
My second idea was to split the images into several smaller segments and to use template matching. So I segmented the images and, again, for the human eye they are pretty easy to match. The goal of this step was to segment the different walls and trees/plants.
The upper row are parts of the left, and the lower are parts of the right image. After the segmentation the segments were dilated again.
As already mentioned: Template matching failed, as did contour based template matching and contour matching.
I think the dilation of the images was very important, because it was nearly impossible for the human eye to match the segments without dilation before the segmentation. Another dilation after the segmentation made this even less difficult.
Your first job should be to fix the orientation. I am not sure what is the best algorithm to do that but here is an approach I would use: fix one of the images and start rotating the other. For each rotation compute a histogram for the color intense on each of the rows/columns. Compute some distance between the resulting vectors(e.g. use cross product). Choose the rotation that results in smallest cross product. It may be good idea to combine this approach with hill climbing.
Once you have the images aligned in approximately the same direction, I believe matching should be easier. As the two images are supposed to be at the same scale, compute something analogous to the geometrical center for both images: compute weighted sum of all pixels - a completely white pixel would have a weight of 1, and a completely black - weight 0, the sum should be a vector of size 2(x and y coordinate). After that divide those values by the dimensions of the image and call this "geometrical center of the image". Overlay the two images in a way that the two centers coincide and then once more compute cross product for the difference between the images. I would say this should be their difference.
You can also try following methods to find rotation and similarity.
Use image moments to get the rotation as shown here.
Once you rotate the image, use cross-correlation to evaluate the similarity.
EDIT
I tried this with OpenCV and C++ for the two sample images. I'm posting the code and results below as it seems to work well at least for the given samples.
Here's the function to calculate the orientation vector using image moments:
Mat orientVec(Mat& im)
{
Moments m = moments(im);
double cov[4] = {m.mu20/m.m00, m.mu11/m.m00, m.mu11/m.m00, m.mu02/m.m00};
Mat covMat(2, 2, CV_64F, cov);
Mat evals, evecs;
eigen(covMat, evals, evecs);
return evecs.row(0);
}
Rotate and match sample images:
Mat im1 = imread(INPUT_FOLDER_PATH + string("WojUi.png"), 0);
Mat im2 = imread(INPUT_FOLDER_PATH + string("XbrsV.png"), 0);
// get the orientation vector
Mat v1 = orientVec(im1);
Mat v2 = orientVec(im2);
double angle = acos(v1.dot(v2))*180/CV_PI;
// rotate im2. try rotating with -angle and +angle. here using -angle
Mat rot = getRotationMatrix2D(Point(im2.cols/2, im2.rows/2), -angle, 1.0);
Mat im2Rot;
warpAffine(im2, im2Rot, rot, Size(im2.rows, im2.cols));
// add a border to rotated image
int borderSize = im1.rows > im2.cols ? im1.rows/2 + 1 : im1.cols/2 + 1;
Mat im2RotBorder;
copyMakeBorder(im2Rot, im2RotBorder, borderSize, borderSize, borderSize, borderSize,
BORDER_CONSTANT, Scalar(0, 0, 0));
// normalized cross-correlation
Mat& image = im2RotBorder;
Mat& templ = im1;
Mat nxcor;
matchTemplate(image, templ, nxcor, CV_TM_CCOEFF_NORMED);
// take the max
double max;
Point maxPt;
minMaxLoc(nxcor, NULL, &max, NULL, &maxPt);
// draw the match
Mat rgb;
cvtColor(image, rgb, CV_GRAY2BGR);
rectangle(rgb, maxPt, Point(maxPt.x+templ.cols-1, maxPt.y+templ.rows-1), Scalar(0, 255, 255), 2);
cout << "max: " << max << endl;
With -angle rotation in code, I get max = 0.758. Below is the rotated image in this case with the matching region.
Otherwise max = 0.293

Vignettation with white color in opencv

im working on a vignette filter in openCV and i tried the code in this question ( Creating vignette filter in opencv? ), and it works perfectly.
But now I'm trying to modify it to create a white vignetting filter and I can't find a way to turn it so that it shows white color vignette instead of black.
ADDITIONALY TO ANSWER
After modifying the code there are some points I'd like to make clear for any future programmers/developers or people interested in the problem.
What is said in the answer is basically to do a weighted addition of pixels. Simple addition can be easily done using openCV's AddWeighted. This can be use to do blending with any color, not just black or white. However this is not simple addition since we do not have the same blending level everyuwhere, but instead level of blending is given by the gradient;
pseudocode looks like:
pixel[][] originalImage; //3 channel image
pixel[][] result; //3 channel image
pixel[][] gradient; //1 channel image
pixel color; //pixel for color definition of color to blend with
generateGradient(gradient); //generates the gradient as one channel image
for( x from 0 to originalImage.cols )
{
for( y from 0 to originalImage.rows )
{
pixel blendLevel = gradient[x][y];
pixel pixelImage = originalImage[x][y];
pixel blendcolor = color;
//this operation is called weighted addition
//you have to multiply the whole pixel (every channel value of the pixel)
//by the blendLevel, not just one channel
result[x][y] = (blendLevel * pixelImage) + ( ( 1 - blendLevel ) * blendColor );
}
}
Say, you darken your colour fore by a factor of x. Then to blend it with a different colour back, you take x * fore + (1 - x) * back. I don't remember the exact OpenCV syntax; looking at your link, I would write something like this:
cv::Mat result = maskImage * img + (1.0 - maskImage) * white;
If you convert your image to the CIE Lab colour space (as in the vignette code), which would be a good idea, don't forget to do the same for white.

Best algorithm for video stabilization

I am creating a program to stabilize the video stream. At the moment, my program works based on the phase correlation algorithm. I'm calculating an offset between two images - base and current. Next I correct the current image according to the new coordinates. This program works, but the result is not satisfactory. The related links you may find that the treated video appears undesirable and shake the whole video is becoming worse.
Orininal video
Unshaked video
There is my current realisation:
Calculating offset between images:
Point2d calculate_offset_phase_optimized(Mat one, Mat& two) {
if(two.type() != CV_64F) {
cvtColor(two, two, CV_BGR2GRAY);
two.convertTo(two, CV_64F);
}
cvtColor(one, one, CV_BGR2GRAY);
one.convertTo(one, CV_64F);
return phaseCorrelate(one, two);
}
Shifting image according this coordinate:
void move_image_roi_alt(Mat& img, Mat& trans, const Point2d& offset) {
trans = Mat::zeros(img.size(), img.type());
img(
Rect(
_0(static_cast<int>(offset.x)),
_0(static_cast<int>(offset.y)),
img.cols-abs(static_cast<int>(offset.x)),
img.rows-abs(static_cast<int>(offset.y))
)
).copyTo(trans(
Rect(
_0ia(static_cast<int>(offset.x)),
_0ia(static_cast<int>(offset.y)),
img.cols-abs(static_cast<int>(offset.x)),
img.rows-abs(static_cast<int>(offset.y))
)
));
}
int _0(const int x) {
return x < 0 ? 0 : x;
}
int _0ia(const int x) {
return x < 0 ? abs(x) : 0;
}
I was looking through the document authors stabilizer YouTube and algorithm based on corner detection seemed attractive, but I'm not entirely clear how it works.
So my question is how to effectively solve this problem.
One of the conditions - the program will run on slower computers, so heavy algorithms may not be suitable.
Thanks!
P.S.
I apologize for any mistakes in the text - it is an automatic translation.
You can use image descriptors such as SIFT in each frame and calculate robust matches between the frames. Then you can calculate homography between the frames and use that to align them. Using sparse features can lead to faster implementation than using a dense correlation.
Alternately, if you know the camera parameters you can calculate 3D positions of the points and of the cameras and reproject the images onto a stable projection plane. In the result, you also get a sparse 3D reconstruction of the scene (somewhat imprecise, usually it needs to be optimized to be usable). This is what e.g. Autostitch would do, but it is quite difficult to implement, however.
Note that the camera parameters can also be calculated, but that is even more difficult.
OpenCV can do it for you in 3 lines of code (it is definitely shortest way, may be even the best):
t = estimateRigidTransform(newFrame, referenceFrame, 0); // 0 means not all transformations (5 of 6)
if(!t.empty()){
warpAffine(newFrame, stableFrame, t, Size(newFrame.cols, newFrame.rows)); // stableFrame should be stable now
}
You can turn off some kind of transformations by modifying matrix t, it can lead to more stable result. It is just core idea, then you can modify it in the way you want: change referenceFrame, smooth set of transformation parameters from matrix t etc.

How can I use OpenCV to find an arbitrarily transformed rectangle in a depth image?

I'm attempting to work with a depth sensor to add positional tracking to the Oculus Rift dev kit. However, I'm having trouble with the sequence of operations producing a usable result.
I'm starting with a 16 bit depth image, where the values sort of (but not really) correspond to millimeters. Undefined values in the image have already been set to 0.
First I'm eliminating everything outside a certain near and far distance by updating a mask image to exclude them.
cv::Mat result = cv::Mat::zeros(depthImage.size(), CV_8UC3);
cv::Mat depthMask;
depthImage.convertTo(depthMask, CV_8U);
for_each_pixel<DepthImagePixel, uint8_t>(depthImage, depthMask,
[&](DepthImagePixel & depthPixel, uint8_t & maskPixel){
if (!maskPixel) {
return;
}
static const uint16_t depthMax = 1200;
static const uint16_t depthMin = 200;
if (depthPixel < depthMin || depthPixel > depthMax) {
maskPixel = 0;
}
});
Next, since the feature I want is likely to be closer to the camera than the overall scene average, I update the mask again to exclude anything that isn't within a certain range of the median value:
const float depthAverage = cv::mean(depthImage, depthMask)[0];
const uint16_t depthMax = depthAverage * 1.0;
const uint16_t depthMin = depthAverage * 0.75;
for_each_pixel<DepthImagePixel, uint8_t>(depthImage, depthMask,
[&](DepthImagePixel & depthPixel, uint8_t & maskPixel){
if (!maskPixel) {
return;
}
if (depthPixel < depthMin || depthPixel > depthMax) {
maskPixel = 0;
}
});
Finally, I zero out everything that's not in the mask, and scale the remaining values to between 10 & 255 before converting the image format to 8 bit
cv::Mat outsideMask;
cv::bitwise_not(depthMask, outsideMask);
// Zero out outside the mask
cv::subtract(depthImage, depthImage, depthImage, outsideMask);
// Within the mask, normalize to the range + X
cv::subtract(depthImage, depthMin, depthImage, depthMask);
double minVal, maxVal;
minMaxLoc(depthImage, &minVal, &maxVal);
float range = depthMax - depthMin;
float scale = (((float)(UINT8_MAX - 10) / range));
depthImage *= scale;
cv::add(depthImage, 10, depthImage, depthMask);
depthImage.convertTo(depthImage, CV_8U);
The results looks like this:
I'm pretty happy with this section of the code, since it produces pretty clear visual features.
I'm then applying a couple of smoothing operations to get rid of the ridiculous amount of noise from the depth camera:
cv::medianBlur(depthImage, depthImage, 9);
cv::Mat blurred;
cv::bilateralFilter(depthImage, blurred, 5, 250, 250);
depthImage = blurred;
cv::Mat result = cv::Mat::zeros(depthImage.size(), CV_8UC3);
cv::insertChannel(depthImage, result, 0);
Again, the features look pretty clear visually, but I wonder if they couldn't be sharpened somehow:
Next I'm using canny for edge detection:
cv::Mat canny_output;
{
cv::Canny(depthImage, canny_output, 20, 80, 3, true);
cv::insertChannel(canny_output, result, 1);
}
The lines I'm looking for are there, but not well represented towards the corners:
Finally I'm using probabilistic Hough to identify lines:
std::vector<cv::Vec4i> lines;
cv::HoughLinesP(canny_output, lines, pixelRes, degreeRes * CV_PI / 180, hughThreshold, hughMinLength, hughMaxGap);
for (size_t i = 0; i < lines.size(); i++)
{
cv::Vec4i l = lines[i];
glm::vec2 a((l[0], l[1]));
glm::vec2 b((l[2], l[3]));
float length = glm::length(a - b);
cv::line(result, cv::Point(l[0], l[1]), cv::Point(l[2], l[3]), cv::Scalar(0, 0, 255), 3, CV_AA);
}
This results in this image
At this point I feel like I've gone off the rails, because I can't find a good set of parameters for Hough to produce a reasonable number of candidate lines in which to search for my shape, and I'm not sure if I should be fiddling with Hough or looking at improving the outputs of the prior steps.
Is there a good way of objectively validating my results at each stage, as opposed to just fiddling with the input values until I think it 'looks good'? Is there a better approach to finding the rectangle given the starting image (and given that it won't necessarily be oriented in a particular direction?
Very cool project!
Though, I feel like your approach does not use all the info that you could get from the depthmap (e.g. 3D points, normals, etc), which would help a lot.
The Point Cloud Library (PCL), which is a C++ library dedicated to the processing of RGB-D data, has a tutorial on plane segmentation using RANSAC which could inspire you. You might not want to use PCL in your program, due to the numerous dependencies, however as it is open-source, you can find the algorithm implementation on Github (PCL SAC segmentation). However, RANSAC might be slow and produce unwanted results depending on the scene.
You could also try to use the approach presented in "Real-Time Plane Segmentation
using RGB-D Cameras" by Holz, Holzer, Rusu and Behnke, 2011 (PDF), which suggests fast normal estimation using integral images followed by plane detection using clustering of normals.