I am new to OGRE library. I have a human model in OGRE, I get the projection of the model in 'orginalImage' variable. I would like to perform some image processing using openCV. So I am trying to achieve OGRE::Image to cv::Mat conversion.
Ogre::Image orginalImage = get2dProjection();
//This is an attempt to convert the image
cv::Mat destinationImage(orginalImage.getHeight(), orginalImage.getWidth(), CV_8UC3, orginalImage.getData());
imwrite("out.png", destinationImage);
I get following error:
realloc(): invalid pointer: 0x00007f9e2ca13840 ***
On the similar note, I tried following as my second attempt
cv::Mat cvtImgOGRE2MAT(Ogre::Image imgIn) {
//Result image intialisation:
int imgHeight = imgIn.getHeight();
int imgWidth = imgIn.getWidth();
cv::Mat imgOut(imgHeight, imgWidth, CV_32FC1);
Ogre::ColourValue color;
float gray;
cout << "Converting " << endl;
for(int r = 0; r < imgHeight; r++){
for(int c = 0; c < imgWidth; c++){
color = imgIn.getColourAt(r,c,0);
gray = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
imgOut.at<float>(r,c) = gray;
}
}
return imgOut;
}
I get same error when I do one of the following:
imshow("asdfasd", imgOut);
imwrite("asdfasd.png", imgOut);
unfortunately I have no experience with OGRE, so I can just talk about OpenCV and what I've seen in Ogre documentation and poster's comments.
The first thing to mention is that the Ogre image' PixelFormat is PF_BYTE_RGBA (from comments) which is (according to OGRE documentation) a 4 byte pixel format, so the cv::Mat type should be CV_8UC4 if image data should be given by pointer. In addition, openCV best supports BGR images, so a color conversion might be best to save/display.
please try:
Ogre::Image orginalImage = get2dProjection();
//This is an attempt to convert the image
cv::Mat destinationImage(orginalImage.getHeight(), orginalImage.getWidth(), CV_8UC4, orginalImage.getData());
cv::Mat resultBGR;
cv::cvtColor(destinationImage, resultBGR, CV_RGBA2BGR);
imwrite("out.png", resultBGR);
in your second example I wondered what is wrong there, until I saw color = imgIn.getColourAt(r,c,0); which might be wrong since most image APIs use .getPixel(x,y) so I confirmed that this is the same for OGRE. Please try this:
cv::Mat cvtImgOGRE2MAT(Ogre::Image imgIn)
{
//Result image intialisation:
int imgHeight = imgIn.getHeight();
int imgWidth = imgIn.getWidth();
cv::Mat imgOut(imgHeight, imgWidth, CV_32FC1);
Ogre::ColourValue color;
float gray;
cout << "Converting " << endl;
for(int r = 0; r < imgHeight; r++)
{
for(int c = 0; c < imgWidth; c++)
{
// next line was changed
color = imgIn.getColourAt(c,r,0);
gray = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
// this access is right
imgOut.at<float>(r,c) = gray;
}
}
return imgOut;
// depending of what you want to do with the image, "float" Mat type assumes what image intensity values are within 0..1 (displaying) or 0..255 (imwrite)
}
if you still get realloc errors, can you please try to find the exact line of code where it happens?
One thing I didnt consider yet is the real memory layout of OGRE images. It might be possible that they use some kind of aligned memory, where each pixel-row is aligned to have a memory size as a multiple of 4 or 16 or sth. (which might be more efficient, e.g. to use SSE instructions or sth.) If that is the case, you can't use the first method but you would have to change it to cv::Mat destinationImage(orginalImage.getHeight(), orginalImage.getWidth(), CV_8UC4, orginalImage.getData(), STEPSIZE); where STEPSIZE is the number of BYTES for each pixel ROW! But the second version should work then!
Related
I'm using OpenCV (v 2.4.9.1, Ubuntu 16.04) to do a resize and crop on an image, the original image is a JPEG file with dimensions 640x480.
cv::Mat _aspect_preserving_resize(const cv::Mat& image, int target_width)
{
cv::Mat output;
int min_dim = ( image.cols >= image.rows ) ? image.rows : image.cols;
float scale = ( ( float ) target_width ) / min_dim;
cv::resize( image, output, cv::Size(int(image.cols*scale), int(image.rows*scale)));
return output;
}
cv::Mat _center_crop(cv::Mat& image, cv::Size& input_size)
{
cv::Rect myROI(int(image.cols/2-input_size.width/2), int(image.rows/2-input_size.height/2), input_size.width, input_size.height);
cv::Mat croppedImage = image(myROI);
return croppedImage;
}
int min_input_size = int(input_size.height * 1.14);
cv::Mat image = cv::imread("power-dril/47105738371_72f83eeb37_z.jpg");
cv::Mat output = _aspect_preserving_resize(image, min_input_size);
cv::Mat result = _center_crop(output, input_size);
After this I display the images, and it looks perfect - as I would expect it to be:
The problem comes when I stream this image, where I notice that the size (in elements) of the cropped image is only a third of what I would expect. It looks as if there is only one cannel on the resultant crop. It should have had 224*224*3=150528, but I'm getting only 50176 when I'm doing
std::cout << cropped_image.total() << " " << cropped_image.type() << endl;
>>> 50176 16
Any idea what's wrong here? The type of the resulting cv::Mat looks okay, and also visually it looks ok, so how there is only one channel?
Thanks in advance.
Basic Structures — OpenCV 2.4.13.7 documentation says:
Mat::total
Returns the total number of array elements.
C++: size_t Mat::total() const
The method returns the number of array elements (a number of pixels if
the array represents an image).
Therefore, the return value is the number of pixels 224*224=50176 and your expected value is wrong.
My terminology was wrong, as pointed by #MikeCAT, and it seems that the issue should be solved in the serialization logic. I went with a solution along the lines of this one:
Convert Mat to Array/Vector in OpenCV
My original code didn't check the channels() function.
if (curr_img.isContinuous()) {
int totalsz = curr_img.dataend-curr_img.datastart;
array.assign(curr_img.datastart, curr_img.datastart + totalsz);
} else {
int rowsz = CV_ELEM_SIZE(curr_img.type()) * curr_img.cols;
for (int i = 0; i < curr_img.rows; ++i) {
array.insert(array.end(), curr_img.ptr<uint8_t>(i), curr_img.ptr<uint8_t>(i) + rowsz);
}
}
I start out with this image:
for which I want to color in the lane markings directly in front of the vehicle (yes this is for a Udacity online class, but they want me to do this in python, but I'd rather do it in C++)
Finding the right markers is easy:
This works for coloring the markers:
cv::MatIterator_<cv::Vec3b> output_pix_it = output.begin<cv::Vec3b>();
cv::MatIterator_<cv::Vec3b> output_end = output.end<cv::Vec3b>();
cv::MatIterator_<cv::Vec3b> mask_pix_it = lane_markers.begin<cv::Vec3b>();
//auto t1 = std::chrono::high_resolution_clock::now();
while (output_pix_it != output_end)
{
if((*mask_pix_it)[0] == 255)
{
(*output_pix_it)[0] = 0;
(*output_pix_it)[1] = 0;
(*output_pix_it)[2] = 255;
}
++output_pix_it;
++mask_pix_it;
}
correctly producing
however I was a little surprised that it seemed to be kind of slow, taking 1-2 ms (on a core i7-7700HQ w/ 16gb ram, compiled with -O3) for the image which is 960 x 540
Following "the efficient way" here: https://docs.opencv.org/2.4/doc/tutorials/core/how_to_scan_images/how_to_scan_images.html#howtoscanimagesopencv
I came up with:
unsigned char *o; // pointer to first element in output Mat
unsigned char *m; //pointer to first element in mask Mat
o = output.data;
m = lane_markers.data;
size_t pixel_elements = output.rows * output.cols * output.channels();
for( size_t i=0; i < pixel_elements; i+=3 )
{
if(m[i] == 255)
{
o[i] = 0;
o[i+1] = 0;
o[i+2] = 255;
}
}
which is about 3x faster....but doesn't produce the correct results:
All cv::Mat objects are of type 8UC3 type (standard BGR pixel format).
As far as I can tell the underlying data of the Mat objects should be an array of unsigned chars of the length pixel width * pixel height * num channels. But it seems like I'm missing something. isContinuous() is true for both the output and mask matrices. I'm using openCV 3.4.4 on Ubuntu 18.04. What am I missing?
Typical way of setting a masked area of a Mat to a specific value is to use Mat::setTo function:
cv::Mat mask;
cv::cvtColor(lane_markers, mask, cv::COLOR_BGR2GRAY); //mask Mat has to be 8UC1
output.setTo(cv::Scalar(0, 0, 255), mask);
I have 2 Mat, both have same size. Then I split those 2 Mat to process the BGR value of each Mat (in my case, it's actually BGRA). Then I want to add each of BGR values from those 2 Mat and store it in blue, green, red variable.
I successfully adding 2 Mat without using pixel iteration, like this :
//below operation works
blue = img1_split1[0] + img2_split[0];
green = img1_split[1] + img2_split[1];
red = img1_split[2] + img2_split[2];
But I'm facing Exception Unhandled if I use pixel iteration and add 2 pixel values from each Mat, like this :
Unhandled exception at 0x00007FF89AA69129 in VideoStitcher.exe:
Microsoft C++ exception: cv::Exception at memory location
0x000000ADA5CFD020.
Here's my code :
Mat img1, img2;
img1 = imread("some_image.jpg"); //this is just for example
img2 = imread("some_image.jpg");
vector<Mat> img1_split, img2_split;
split(img1, img1_split);
split(img2, img2_split);
Mat blue, green, red;
for (int i = 0; i < img1.rows; i++)
{
for (int j = 0; j < img1.cols; j++)
{ //below operation doesn't work
blue.at<uchar>(i, j) = img1_split1[0].at<uchar>(i, j) + img2_split[0].at<uchar>(i, j);
green.at<uchar>(i, j) = img1_split[1].at<uchar>(i, j) + img2_split[1].at<uchar>(i, j);
red.at<uchar>(i, j) = img1_split[2].at<uchar>(i, j) + img2_split[2].at<uchar>(i, j);
}
}
Can somebody point out the mistake that I made? Thanks
cv::Mat works like a shared smart pointer, so when one creates it with the default constructor (i.e. cv::Mat x;), it does not have any data or type and its .data member points nowhere.
You need to pass at least rows and cols and type for it to be able to allocate the image data, for example, in your case it will be like:
Mat blue(img1.rows, img1.cols, CV_8UC1), green(img1.rows, img1.cols, CV_8UC1), red(img1.rows, img1.cols, CV_8UC1);
I used CV_8UC1, since they are one channel images once you split them.
When you set pixels like this, you have to be careful with underflow and overflow... there is a nice function for that, it is call saturate_cast.
Someone gave me this function:
Mat tan_triggs_preprocessing(InputArray src, float alpha = 1, float gamma = 10.0,
float tau = 1000.0, int sigma1 = 2) {
Mat X = src.getMat();
Mat I, tmp, tmp2;
double meanI;
X.convertTo(X, CV_32FC1);
pow(X, gamma, I);
meanI = 0.0;
pow(abs(I), alpha, tmp);
meanI = mean(tmp).val[0];
I = I / pow(meanI, 1.0/alpha);
meanI = 0.0;
pow(min(abs(I), tau), alpha, tmp2);
meanI = mean(tmp2).val[0];
I = I / pow(meanI, 1.0/alpha);
for(int r = 0; r < I.rows; r++) {
for(int c = 0; c < I.cols; c++) {
I.at<float>(r,c) = tanh(I.at<float>(r,c) / tau);
}
}
I = tau * I;
return I;
}
The function takes an input as a gray scale image or CV_8UC1 type, and it outputs a matrix of CV_32FC1 type. All I know is the function makes the input image lighter, increases its contrast. When I show the image using imshow function, I can see the output of tan_triggs_preprocessing very clearly, and actually the output lighter, more contrast compares to the source image. But the problem is when I save it as image format (JPG for example) using imwrite function, it's totally black. I can't see anything.
I checked the value of elements in the output, and I saw that their values are between [0.06.., 2.3...]. Here are my questions, hopefully you can help me, thank you so much.
Can I write an CV_32FC1 as image file format?
Why is the file written by imwrite above totally black?
I also looked for min and max value in the output, so I can normalize it in to 256 bins for CV_8UC1, but it doesn't work, even when I use imshow or imwrite.
How can I convert it to CV_8UC1 or write it as image file format? I used convertTo but it doesn't work as well.
Thank a lot.
imwrite/imread can only handle 8/16/24/32bit integral data, not floats (if you don't count Ilm/exr)
you probably want :
Mat gray_in = ...
Mat gray_out;
cv::normalize( tan_triggs_preprocessing(gray_in), gray_out, 0, 255, NORM_MINMAX, CV_8UC1);
(admittedly hard to spot, but it's even in the small print of bytefish's code ;)
also, please look at alternatives to that, like equalizehist and CLAHE
I am relatively new to C++ and coding in general and have run into a problem when attempting to convert an image to a floating point image. I am attempting to do this to eliminate round off errors with calculating the mean and standard deviation of pixel intensity for images as it starts to effect data quite substantially. My code is below.
Mat img = imread("Cells2.tif");
cv::namedWindow("stuff", CV_WINDOW_NORMAL);
cv::imshow("stuff",img);
CvMat cvmat = img;
Mat dst = cvCreateImage(cvGetSize(&cvmat),IPL_DEPTH_32F,1);
cvConvertScale(&cvmat,&dst);
cvScale(&dst,&dst,1.0/255);
cvNamedWindow("Test",CV_WINDOW_NORMAL);
cvShowImage("Test",&dst);
And I am running into this error
OpenCV Error: Bad argument (Array should be CvMat or IplImage) in an unknown function, file ......\modules\core\src\array.cpp, line 1238
I've looked everywhere and everyone was saying to convert img to CvMat which I attempted above.
When I did that as above code shows I get
OpenCV Error: Bad argument (Unknown array type) in unknown function, file ......\modules\core\src\matrix.cpp line 697
Thanks for your help in advance.
Just use C++ OpenCV interface instead of C interface and use convertTo function to convert between data types.
Mat img = imread("Cells2.tif");
cv::imshow("source",img);
Mat dst; // destination image
// check if we have RGB or grayscale image
if (img.channels() == 3) {
// convert 3-channel (RGB) 8-bit uchar image to 32 bit float
src.convertTo(dst, CV_32FC3);
}
else if (img.channels() == 1) {
// convert 1-chanel (grayscale) 8-bit uchar image to 32 bit float
img1.convertTo(dst, CV_32FC1);
}
// display output, note that to display dst image correctly
// we have to divide each element of dst by 255 to keep
// the pixel values in the range [0,1].
cv::imshow("output",dst/255);
waitKey();
Second part of the question To calculate the mean of all elements in dst
cv::Salar avg_pixel;
double avg;
// note that Scalar is a vector.
// If your image is RGB, Scalar will contain 3 values,
// representing color values for each channel.
avg_pixel = cv::mean(dst);
if (dst.channels() == 3) {
//if 3 channels
avg = (avg_pixel[0] + avg_pixel[1] + avg_pixel[2]) / 3;
}
if(dst.channels() == 1) {
avg = avg_pixel[0];
}
cout << "average element of m: " << avg << endl;
Here is my code for calculating the average in C++ OpenCV.
int NumPixels = img.total();
double avg;
double c;
for(int y = 0; y <= img.cols; y++)
for(int x = 0; x <= dst.rows; x++)
c+=img.at<uchar>(x,y);
avg = c/NumPixels;
cout << "Avg Value\n" << 255*avg;
For MATLAB I just load the image and take Q = mean(img(:)); which returns 1776.23
And for the return of 1612.36 I used cv:Scalar z = mean(dst);