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);
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'm quite new to OpenCV and I'm now using version 3.4.1 with C++ implementation. I'm still exploring, so this question is not specific to a project, but is more of a "try to understand how it works". Please consider, with the same idea in mind, that I know that I'm somehow "reinventing the will" with this code, but I wrote this example to understand "HOW IT WORKS".
The idea is:
Read an RGB image
Make it binary
Find Connected areas
Colour each area differently
As an example I'm using a 5x5 pixel RGB image saved as BMP. The image is a white box with black pixels all around it's contour.
Up to the point where I get the ConnectedComponents matrix, named Mat::Labels, it all goes fine. If I print the Matrix I see exactly what I expect:
11111
10001
10001
10001
11111
Remember that I've inverted the threshold so it is correct to get 1 on the edges...
I then create a Mat with same size of Mat::Labels but 3 channels to colour it with RGB. This is named Mat::ColoredLabels.
Next step is to instanciate a pointer that runs through the Mat::Labels and for each position in the Mat::Labels where the value is 1 fill the corresponding Mat:.ColoredLabels position with a color.
HERE THINGS GOT VERY WRONG ! The pointer does not fetch the Mat::Labels row byt row as I would expect but follows some other order.
Questions:
Am I doing something wrong or it is "obvious" that the pointer fetching follows some "umpredictable" order ?
How could I set values of a Matrix (Mat::ColoredLabels) based on the values of another matrix (Mat::Labels) ?
.
#include "opencv2\highgui.hpp"
#include "opencv2\opencv.hpp"
#include <stdio.h>
using namespace cv;
int main(int argc, char *argv[]) {
char* FilePath = "";
Mat Img;
Mat ImgGray;
Mat ImgBinary;
Mat Labels;
uchar *P;
uchar *CP;
// Image acquisition
if (argc < 2) {
printf("Missing argument");
return -1;
}
FilePath = argv[1];
Img = imread(FilePath, CV_LOAD_IMAGE_COLOR);
if (Img.empty()) {
printf("Invalid image");
return -1;
}
// Convert to Gray...I know I could convert it right away while loading....
cvtColor(Img, ImgGray, CV_RGB2GRAY);
// Threshold (inverted) to obtain black background and white blobs-> it works
threshold(ImgGray, ImgBinary, 170, 255, CV_THRESH_BINARY_INV);
// Find Connected Components and put the 1/0 result in Mat::Labels
int BlobsNum = connectedComponents(ImgBinary, Labels, 8, CV_16U);
// Just to see what comes out with a 5x5 image. I get:
// 11111
// 10001
// 10001
// 10001
// 11111
std::cout << Labels << "\n";
// Prepare to fetch the Mat(s) with pointer to be fast
int nRows = Labels.rows;
int nCols = Labels.cols * Labels.channels();
if (Labels.isContinuous()) {
nCols *= nRows;
nRows = 1;
}
// Prepare a Mat as big as LAbels but with 3 channels to color different blobs
Mat ColoredLabels(Img.rows, Img.cols, CV_8UC3, cv::Scalar(127, 127, 127));
int ColoredLabelsNumChannels = ColoredLabels.channels();
// Fetch Mat::Labels and Mat::ColoredLabes with the same for cycle...
for (int i = 0; i < nRows; i++) {
// !!! HERE SOMETHING GOES WRONG !!!!
P = Labels.ptr<uchar>(i);
CP = ColoredLabels.ptr<uchar>(i);
for (int j = 0; j < nCols; j++) {
// The coloring operation does not work
if (P[j] > 0) {
CP[j*ColoredLabelsNumChannels] = 0;
CP[j*ColoredLabelsNumChannels + 1] = 0;
CP[j*ColoredLabelsNumChannels + 2] = 255;
}
}
}
std::cout << "\n" << ColoredLabels << "\n";
namedWindow("ColoredLabels", CV_WINDOW_NORMAL);
imshow("ColoredLabels", ColoredLabels);
waitKey(0);
printf("Execution completed succesfully");
return 0;
}
You used connectedComponents function with CV_16U parameter. This means that the single element of the image will consist of 16 bits (hence '16') and you have to interpret them as unsigned integer (hence 'U'). And since ptr returns a pointer, you have to dereference it to get the value.
Therefore you should access label image elements in the following way:
unsigned short val = *Labels.ptr<unsigned short>(i) // or uint16_t
unsigned short val = Labels.at<unsigned short>.at(y, x);
Regarding your second question, it is as simple as that, but of course you have to understand which type casts result in loss of precisions or overflows and which ones not.
mat0.at<int>(y, x) = mat1.at<int>(y, x); // both matrices have CV_32S types
mat2.at<int>(y, x) = mat3.at<char>(y,x); // CV_32S and CV_8S
// Implicit cast occurs. Possible information loss: assigning 32-bit integer values to 8-bit ints
// mat4.at<unsigned char>(y, x) = mat5.at<unsigned int>(y, x); // CV_8U and CV_32U
I want to detect a specific pixel value (let's say 128 in a unsigned 8 bit 1-channel image) in a cv::Mat image and replace the value of all the pixels with that specific value with another value (replacing each 128 with 120). Is there any efficient way of doing this? Or should I do the search and assertion operations pixel by pixel?
I started coding but could not completed. Here is the part of my code:
cv::Mat source;
unsigned oldValue = 128;
unsigned newValue = 120;
cv::Mat temp = (source == oldValue);
You can use setTo, using a mask:
Mat src;
// ... src is somehow initialized
int oldValue = 128;
int newValue = 120;
src.setTo(newValue, src == oldValue);
not sure whether it is more efficient than .setTo , but you could use a look-up-table (especially if you have multiple values you want to replace and you have to replace the same values in multiple images (e.g. in each image of a video stream)).
int main()
{
cv::Mat input = cv::imread("../inputData/Lenna.png");
cv::Mat gray;
cv::cvtColor(input,gray,CV_BGR2GRAY);
// prepare this once:
cv::Mat lookUpTable(1, 256, CV_8U);
uchar* p = lookUpTable.data;
for( int i = 0; i < 256; ++i)
{
p[i] = i;
}
// your modifications
p[128] = 120;
// now you can use LUT efficiently
cv::Mat result;
cv::LUT(gray, lookUpTable, result);
cv::imshow("result", result);
cv::imwrite("../outputData/LUT.png", result);
cv::waitKey(0);
return 0;
}
According to http://docs.opencv.org/doc/tutorials/core/how_to_scan_images/how_to_scan_images.html#the-core-function this is very efficient in special scenarios.
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!
I'd like to perform a color reduction via color depth scaling.
Like this example:
the first image is CGA resolution, the second is EGA, the third is HAM.
I'd like to do it with cv::LUT because i think it is the betterway to do it.
I can do with greyscale with this code:
Mat img = imread("test1.jpg", 0);
uchar* p;
Mat lookUpTable(1, 256, CV_8U);
p = lookUpTable.data;
for( int i = 0; i < 256; ++i)
p[i] = 16 * (i/16)
LUT(img, lookUpTable, reduced);
original:
color reduced:
but if i try to do it with color I get strange result..
with this code:
imgColor = imread("test1.jpg");
Mat reducedColor;
int n = 16;
for (int i=0; i<256; i++) {
uchar value = floor(i/n) * n;
cout << (int)value << endl;
lut.at<Vec3b>(i)[2]= (value >> 16) & 0xff;
lut.at<Vec3b>(i)[1]= (value >> 8) & 0xff;
lut.at<Vec3b>(i)[0]= value & 0xff;
}
LUT(imgColor, lut, reducedColor);
You'll probably have moved on by now, but the root of the problem is that you are doing a 16-bit shift to uchar value, which is just 8-bits long. Even an 8-bit shift in this case is too much, as you'll erase all the bits in the uchar. Then there is the fact that the cv::LUT documentation explicitly states that src must be an "input array of 8-bit elements", which clearly isn't the case in your code. The net result is that only the first channel of the color image (the Blue channel) is transformed by cv::LUT.
The best way to work around these limitations is to split color images across channels, transform each channel separately, and then merge the transformed channels into a new color image. See the code below:
/*
Calculates a table of 256 assignments with the given number of distinct values.
Values are taken at equal intervals from the ranges [0, 128) and [128, 256),
such that both 0 and 255 are always included in the range.
*/
cv::Mat lookupTable(int levels) {
int factor = 256 / levels;
cv::Mat table(1, 256, CV_8U);
uchar *p = table.data;
for(int i = 0; i < 128; ++i) {
p[i] = factor * (i / factor);
}
for(int i = 128; i < 256; ++i) {
p[i] = factor * (1 + (i / factor)) - 1;
}
return table;
}
/*
Truncates channel levels in the given image to the given number of
equally-spaced values.
Arguments:
image
Input multi-channel image. The specific color space is not
important, as long as all channels are encoded from 0 to 255.
levels
The number of distinct values for the channels of the output
image. Output values are drawn from the range [0, 255] from
the extremes inwards, resulting in a nearly equally-spaced scale
where the smallest and largest values are always 0 and 255.
Returns:
Multi-channel images with values truncated to the specified number of
distinct levels.
*/
cv::Mat colorReduce(const cv::Mat &image, int levels) {
cv::Mat table = lookupTable(levels);
std::vector<cv::Mat> c;
cv::split(image, c);
for (std::vector<cv::Mat>::iterator i = c.begin(), n = c.end(); i != n; ++i) {
cv::Mat &channel = *i;
cv::LUT(channel.clone(), table, channel);
}
cv::Mat reduced;
cv::merge(c, reduced);
return reduced;
}
Both i and n are integers, therefore i/n is an integer. Perhaps you want it converted to double ((double)i/n) before taking the floor and multiplying by n?