OpenCV Stereo rectification from manually created matrices - c++

I am currently working on a 3d reconstruction of X-Ray images, and therefore I need to stereo-rectify images of two views before I can match some features with help of the epilines. I am using OpenCV 2.4 with C++.
For this purpose I got a set of pairs of X-Ray images (cone beam X-ray images, no real cameras with distortion parameters or a real focal length), one from the anteroposterior view (directly looking at the chest), and one from the lateral view (looking at the chest from the side). I know some parameters like a virtual focal length (equal for both views) that I can use, and the images have got a resolution of 512x512px, hence the camera projection at the images is at (255,255) for both views. Also i know that the cameras are perpendicular. From this information I developed a rotation matrix R and a translation vector t (both verified with help of a 3d plot in Matlab).
Problem: R and t are actually enough for a stereo rectification in OpenCV, but the resulting images after rectification are black. Googling led me to a bug in stereoRectify, but I doubt that it is the bug as I can run the OpenCV stereoRectification example which does work. When trying a stereoRectification in Matlab I can at least see some distorted rectification results.
Here is my C++ code:
float camera_matrix_ap_data[] = {1207*2.0, 0.0, 255.0,
0.0, 1207*2, 255.0,
0.0, 0.0, 1.0};
cv::Mat camera_matrix_ap(3, 3, CV_64F, camera_matrix_ap_data);
float camera_matrix_lat_data[] = {1207*2, 0.0, 255.0,
0.0, 1207*2, 255.0,
0.0, 0.0, 1.0};
cv::Mat camera_matrix_lat(3, 3, CV_64F, camera_matrix_lat_data);
///
/// #brief the distortion matrices
///
cv::Mat distortion_ap(4, 1, CV_64F, 0.0);
cv::Mat distortion_lat(4, 1, CV_64F, 0.0);
///
/// #brief Translation and Rotation matrices
///
float R_data[] = {0.0, 0.0, 1.0,
0.0, 1.0, 0.0,
-1.0, 0.0, 0.0};
float T_data[] = {-(1207.0*2 + 255), 0.0, 1207.0*2 + 255};
cv::Mat R(3, 3, CV_64F, R_data);
cv::Mat T(3, 1, CV_64F, T_data);
for (int i=1; i<=20; i++) {
std::stringstream filenameAP_tmp;
std::stringstream filenameLAT_tmp;
filenameAP_tmp << "imageAP"<< i <<".jpg";
filenameAP = filenameAP_tmp.str();
filenameLAT_tmp << "imageLAT"<< i <<".jpg";
filenameLAT = filenameLAT_tmp.str();
rectimg_ap = cv::imread(filenameAP);
rectimg_lat = cv::imread(filenameLAT);
// Yes, these images are grayscale
/// Experimental
/// Stereo rectify both images
cv::Mat R1(3, 3, CV_64F);
cv::Mat R2(3, 3, CV_64F);
cv::Mat P1(3, 4, CV_64F);
cv::Mat P2(3, 4, CV_64F);
cv::Mat Q(4, 4, CV_64F);
cv::Rect validRoi[2];
// buggy?
cv::stereoRectify(camera_matrix_ap, distortion_ap, camera_matrix_lat, distortion_lat, rectimg_ap.size(), R, T, R1, R2, P1, P2, Q, CALIB_ZERO_DISPARITY, 1, rectimg_ap.size(), &validRoi[0], &validRoi[1] );
// Maps for AP View
cv::Mat map1x(rectimg_ap.size(), CV_32FC1, 255.0);
cv::Mat map2x(rectimg_ap.size(), CV_32FC1, 255.0);
// Maps for LAT View
cv::Mat map1y(rectimg_ap.size(), CV_32FC1, 255.0);
cv::Mat map2y(rectimg_ap.size(), CV_32FC1, 255.0);
cv::initUndistortRectifyMap(camera_matrix_ap, distortion_ap, R1, P1, rectimg_ap.size(), CV_32FC1, map1x, map1y);
cv::initUndistortRectifyMap(camera_matrix_lat, distortion_lat, R2, P2, rectimg_lat.size(), CV_32FC1, map2x, map2y);
cv::Mat tmp1, tmp2;
cv::remap(rectimg_ap, tmp1, map1x, map1y, INTER_LINEAR);
cv::remap(rectimg_lat, tmp2, map2x, map2y, INTER_LINEAR);
//findHomography(rectimg_ap, rectimg_lat, CV_RANSAC);
}
So I am wondering what is wrong with this code or my matrices, as the rectification images after remap are completely black. Is there a difference concerning the coordinate system axes between OpenCV and Matlab? As I read, in OpenCV the z-axis points to the image plane, and it was the same for Matlab.
I'd be glad if someone could help me, I am stuck with this problem for weeks now. Thank you very much!

Try changing the "float" variable types to "double". The CV_64F corresponds to a double, and not to a float, since it is 8 bytes (= 64bits). I tried your code with my own matrix values, and that did the trick.

Related

How to use the merge function properly?

I've used the cv::merge() function at the end of the following code, but it throws an unhandled exception when the compiler reaches to the cv::merge() function call.
I've tried both cv::Mat[] array and vector of cv::Mat as inputs, but it is still throws the C++ exception.
The purpose of the code is to extract the red channel of an underwater image, and apply some new values in order to enhance color distribution according to equation 8 of this reference (Color Correction Based on CFA and Enhancement Based on Retinex With Dense Pixels for Underwater Images).
It only works with cv::merge(planes, 1, image2); which returns one page of planes in image2. It must merge three planes in planes into image2 to give a color image not a gray.
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace cv;
using namespace std;
int main()
{
//read an image
Mat image = imread("9554.png", 1);
//check for existence of data
if (!image.data)
{ printf("no image data.\n"); return -1; }
//planes is a vector for holding rgb channels separately
//std::vector<Mat> planes;
Mat planes[3];
//split the image into channels
//planes[2] is the red channel
split(image, planes);
// converting planes from uchar to double
planes[0].convertTo(planes[0], CV_64FC1);
planes[1].convertTo(planes[1], CV_64FC1);
planes[2].convertTo(planes[2], CV_64FC1);
// defining coefficients of green and blue channel for blending
double a = 0.05, b = 0.95;
//sum_im stores pixelwise sum of Red, Green and Blue planes
Mat imBlendNormal_B_G, sum_im;
//converting to double
imBlendNormal_B_G.convertTo(imBlendNormal_B_G, CV_64FC1);
sum_im.convertTo(sum_im, CV_64FC1);
//blending green and blue planes with a and b coefficients
// and 0.0 offset(or gamma)
addWeighted(planes[1], a, planes[0], b, 0.0, imBlendNormal_B_G);
// sum of red, green and blue pixel in two addWeighted calls
addWeighted(planes[2], 1.0, planes[1], 1.0, 0.0, sum_im);
addWeighted(planes[0], 1.0, sum_im, 1.0, 0.0, sum_im);
//dividing blended green and blue image to total RGB sum
divide(imBlendNormal_B_G, sum_im, imBlendNormal_B_G);
//defining average kernel 3x3
Mat avg3x3_kernel = (Mat_<double>(3, 3) << 1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0);
//defining matrices for storing 3x3 average of blue and green planes
Mat blueAverage, greenAverage;
// converting to double type
blueAverage.convertTo(blueAverage, CV_64FC1);
greenAverage.convertTo(greenAverage, CV_64FC1);
// taking 3x3 average
filter2D(planes[0], blueAverage, planes[0].depth(), avg3x3_kernel);
filter2D(planes[1], greenAverage, planes[1].depth(), avg3x3_kernel);
//imBlendAverage_B_G_R: for blending of averaged green and blue channels
Mat imBlendAverage_B_G_R;
//convert to double
imBlendAverage_B_G_R.convertTo(imBlendAverage_B_G_R, CV_64FC1);
//blend averaged green and blue with a and b coeffs
addWeighted(greenAverage, a, blueAverage, b, 0.0, imBlendAverage_B_G_R);
//differentiate red values
addWeighted(imBlendAverage_B_G_R, 1.0, planes[2], -1.0, 0.0, imBlendAverage_B_G_R);
//CompensationTermRed: storing finally compensated red channel intensities
Mat CompensationTermRed;
//coverting to double
CompensationTermRed.convertTo(CompensationTermRed, CV_64FC1);
//multiplication term
CompensationTermRed = imBlendAverage_B_G_R.mul(imBlendNormal_B_G);
//final add term
addWeighted(CompensationTermRed, 1.0, planes[2], 1.0, 0.0, CompensationTermRed);
//convert to uchar
Mat CompensationTermRed_uint8;
CompensationTermRed.convertTo(CompensationTermRed_uint8, CV_8UC1);
//imshow("CompensationTermRed_uint8", CompensationTermRed_uint8);
// assign new red channel values to planes[2]
planes[2] = CompensationTermRed_uint8;
Mat image2 = image;
cv::merge(planes, 1, image2);
image2.convertTo(image2, CV_8UC3);
imshow("merge",image2);
waitKey(0);
return 0;
}
Debugging your code, namely inspecting planes right before the cv::merge call, reveals that planes[0] and planes[1] are of type FLOAT64, whereas planes[2] is of type UINT8. From the documentation on cv::merge:
Parameters
mv input array of matrices to be merged; all the matrices in mv must have the same size and the same depth.
Therefore, you get the exception – and that's also the reason, why cv::merge(planes, 1, image2) works nevertheless.
To fix that issue, just get rid of that part:
//convert to uchar
Mat CompensationTermRed_uint8;
CompensationTermRed.convertTo(CompensationTermRed_uint8, CV_8UC1);
and replace
planes[2] = CompensationTermRed_uint8;
with
planes[2] = CompensationTermRed;
Then, your code should work as expected.
Here is the corrected version of the code that works fine and merge() function throws no unhandled C++ exception. (thanks to comments from HansHirse)
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace cv;
using namespace std;
int main()
{
//read an image
Mat image = imread("9554.png", 1);
//check for existance of data
if (!image.data)
{ printf("no image data.\n"); return -1; }
//planes is a vector for holding rgb channels separately
Mat planes[3];
//split the image into channels
//planes[2] is the red channel
split(image, planes);
// converting planes from uchar to double
planes[0].convertTo(planes[0], CV_64FC1);
planes[1].convertTo(planes[1], CV_64FC1);
planes[2].convertTo(planes[2], CV_64FC1);
// defining coefficients of green and blue channel for blending
double a = 0.05, b = 0.95;
//sum_im stores pixelwise sum of Red, Green and Blue planes
Mat imBlendNormal_B_G, sum_im;
//converting to double
imBlendNormal_B_G.convertTo(imBlendNormal_B_G, CV_64FC1);
sum_im.convertTo(sum_im, CV_64FC1);
//blending green and blue planes with a and b coefficients
// and 0.0 offset(or gamma)
addWeighted(planes[1], a, planes[0], b, 0.0, imBlendNormal_B_G);
// sum of red, green and blue pixel in two addWeighted calls
addWeighted(planes[2], 1.0, planes[1], 1.0, 0.0, sum_im);
addWeighted(planes[0], 1.0, sum_im, 1.0, 0.0, sum_im);
//dividing blended green and blue image to total RGB sum
divide(imBlendNormal_B_G, sum_im, imBlendNormal_B_G);
//defining average kernel 3x3
Mat avg3x3_kernel = (Mat_<double>(3, 3) << 1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0);
//defining matrices for storing 3x3 average of blue and green planes
Mat blueAverage, greenAverage;
// converting to double type
blueAverage.convertTo(blueAverage, CV_64FC1);
greenAverage.convertTo(greenAverage, CV_64FC1);
// taking 3x3 average
filter2D(planes[0], blueAverage, planes[0].depth(), avg3x3_kernel);
filter2D(planes[1], greenAverage, planes[1].depth(), avg3x3_kernel);
//imBlendAverage_B_G_R: for blending of averaged green and blue channels
Mat imBlendAverage_B_G_R;
//convert to double
imBlendAverage_B_G_R.convertTo(imBlendAverage_B_G_R, CV_64FC1);
//blend averaged green and blue with a and b coeffs
addWeighted(greenAverage, a, blueAverage, b, 0.0, imBlendAverage_B_G_R);
//differentiate red values
addWeighted(imBlendAverage_B_G_R, 1.0, planes[2], -1.0, 0.0, imBlendAverage_B_G_R);
//CompensationTermRed: storing finally compensated red channel intensities
Mat CompensationTermRed;
//coverting to double
CompensationTermRed.convertTo(CompensationTermRed, CV_64FC1);
//multiplication term
CompensationTermRed = imBlendAverage_B_G_R.mul(imBlendNormal_B_G);
//final add term
addWeighted(CompensationTermRed, 1.0, planes[2], 1.0, 0.0, CompensationTermRed);
// assign new red channel values to planes[2]
planes[2] = CompensationTermRed;
Mat image2;
cv::merge(planes, 3, image2);
image2.convertTo(image2, CV_8UC3);
imshow("merge",image2);
waitKey(0);
return 0;
}

I want to add gaussian noise to colour image where the standard deviation of gaussian noise were varied from 0.2 to 2 at 0.2 intervals in openCV

I have tried to separate the colour channels and add noise to each of them and merge them. And succeed to do so. But I want to separate the Y Cr Cb components and then add noise to only Y. Finally merge the components but could not. My code for colour channels:
for (int i=0 ; i<3 ; i++){
Mat noise = Mat(channel[i].size(), CV_64F);
normalize(channel[i], result[i], 0.0, 1.0, CV_MINMAX, CV_64F);
randn(noise, 0, .2);
result[i] += noise;
normalize(result[i], result[i], 0.0, 1.0, CV_MINMAX, CV_64F);
result[i].convertTo(result[i], CV_32F, 255, 0);
}
merge(result, 3, input);
imwrite("/Users/hossainmdshakhawat/developer/subimg/Noisy/merge.jpg", input);

How to Compute the Structure Tensor of an Image using OpenCV

I am trying to implement an application that uses images to search for similar images in a large image database. I am developing an image descriptor to use for this search and I would like to combine color information with some gradient information. I have seen structure tensors used in this domain to find the main gradient direction in images or sub-images.
I would like to take an image, divide it into grid of sub-images, for example, 4x4 grid (in total 16 sub-images) and then find the leading gradient direction of each cell. To find the leading gradient direction I want to see if computing the structure tensor for each cell can give good representation of the image gradient and lead to improved image matching. Is this a good idea or a bad idea? The idea was to get a feature vector similar to the idea in section 3.2 in this paper http://cybertron.cg.tu-berlin.de/eitz/pdf/2009_sbim.pdf
Dividing the image into sub-images(cells) is trivial and with opencv I can compute the partial derivatives using the Sobel function.
Mat dx, dy;
Sobel(im, dx, CV_32F, 1, 0, 3, 1, 0, BORDER_DEFAULT);
Sobel(im, dy, CV_32F, 0, 1, 3, 1, 0, BORDER_DEFAULT);
Computing dx^2, dy^2 and dxy should not be a problem, but I am not sure how I can compute the structure tensor matrix and use the tensor matrix to find the main gradient direction for an image or sub-image. How can I implement this with OpenCV?
EDIT
Okay, this is what I have done.
Mat _im; // Image to compute main gradient direction for.
cvtColor(im, _im, CV_BGR2GRAY);
GaussianBlur(_im, _im, Size(3, 3), 0, 0, BORDER_DEFAULT); //Blur the image to remove unnecessary details.
GaussianBlur(_im, _im, Size(5, 5), 0, 0, BORDER_DEFAULT);
GaussianBlur(_im, _im, Size(7, 7), 0, 0, BORDER_DEFAULT);
// Calculate image derivatives
Mat dx2, dy2, dxy;
Sobel(_im, dx2, CV_32F, 2, 0, 3, 1, 0, BORDER_DEFAULT);
Sobel(_im, dy2, CV_32F, 0, 2, 3, 1, 0, BORDER_DEFAULT);
Sobel(_im, dxy, CV_32F, 1, 1, 3, 1, 0, BORDER_DEFAULT);
Mat t(2, 2, CV_32F); // tensor matrix
// Insert values to the tensor matrix.
t.at<float>(0, 0) = sum(dx2)[0];
t.at<float>(0, 1) = sum(dxy)[0];
t.at<float>(1, 0) = sum(dxy)[0];
t.at<float>(1, 1) = sum(dy2)[0];
// eigen decomposition to get the main gradient direction.
Mat eigVal, eigVec;
eigen(t, eigVal, eigVec);
// This should compute the angle of the gradient direction based on the first eigenvector.
float* eVec1 = eigVec.ptr<float>(0);
float* eVec2 = eigVec.ptr<float>(1);
cout << fastAtan2(eVec1[0], eVec1[1]) << endl;
cout << fastAtan2(eVec2[0], eVec2[1]) << endl;
Is this approach correct?
Using this image the application outputs 44.9905, 135.01.
This gives 0, 90.
When I use a part of a real image I get 342.743, 72.7425, which I find odd. I expected to get an angle along the color change (90ish).
After testing I am not sure if my implementation is correct, so any feedback or comments on this are welcomed.
I believe your problem is that you are computing second order derivatives instead of squaring first order derivatives. It should be something like this instead:
// Calculate image derivatives
Mat dx, dy;
Mat dx2, dy2, dxy;
Sobel(_im, dx, CV_32F, 1, 0);
Sobel(_im, dy, CV_32F, 0, 1);
multiply(dx, dx, dx2);
multiply(dy, dy, dy2);
multiply(dx, dy, dxy);
P.S.
Oh, by the way, there is no need to do Gaussian blurring over and over again. Just use a bigger kernel and blur once.
D.S.

How to find correspondence of 3d points and 2d points

I have a set of 3d points in world coordinates and respective correspondences with 2d points in an image. I want to find a matrix that gives me the transformation between these set of points. How can I do this in OpenCV?
cv::solvePnP() is what you are looking for, it finds an object pose from 3D-2D point correspondences and results a rotation vector (rvec), that, together with translation vector (tvec), brings points from the model coordinate system to the camera coordinate system.
you can use solvePnP for this:
// camMatrix based on img size
int max_d = std::max(img.rows,img.cols);
Mat camMatrix = (Mat_<double>(3,3) <<
max_d, 0, img.cols/2.0,
0, max_d, img.rows/2.0,
0, 0, 1.0);
// 2d -> 3d correspondence
vector<Point2d> pts2d = ...
vector<Point3d> pts3d = ...
Mat rvec,tvec;
solvePnP(pts3d, pts2d, camMatrix, Mat(1,4,CV_64F,0.0), rvec, tvec, false, SOLVEPNP_EPNP);
// get 3d rot mat
Mat rotM(3, 3, CV_64F);
Rodrigues(rvec, rotM);
// push tvec to transposed Mat
Mat rotMT = rotM.t();
rotMT.push_back(tvec.reshape(1, 1));
// transpose back, and multiply
return camMatrix * rotMT.t();

How to correct the image according to the camera's matrix with the distortion coefficients?

I'm not sure the the unit of the camera's matrix(mm, etc.):
camera matrix ={0.0074209246, 0.0, 0.026450, 0.0, 0.0074209246, -0.0560390.0, 0.0, 1.0}
matrix distortion= {-4.179306e-005,-4.179306e-005,,2.008752e-005,-2.959854e-005}
undistort(image, imageUndistorted, cameraMatrix, distCoeffs);
Where image is the captured image, imageUndistorted is a mat to store the undistorted image. The units are pixels.