OpenCV replacing specific pixel values with another value - c++

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.

Related

Apply Mask in OpenCV

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);

Make 32x32 sections on an image in C++ OpenCV?

I want to take a gray scaled image and divide it into 32x32 sections. Each section will contain pixels and based their intensity and volume, they would be considered a 1 or a 0.
My thought is that I would name the sections like "(x,y)". For example:
Section(1,1) contains this many pixels that are within this range of intensity so this is a 1.
Does that make sense? I tried looking for the answer to this question but dividing up the image into overlaying sections doesn't seem to yield any results in the OpenCV community. Keep in mind I don't want to change the way the image looks, just divide it up into a 32x32 table with (x,y) being a "section" of the picture.
Yes you can do that. Here is the code. It is rough around the edges, but it does what you request. See comments in the code for explanations.
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
struct BradleysImage
{
int rows;
int cols;
cv::Mat data;
int intensity_threshold;
int count_threshold;
cv::Mat buff = cv::Mat(32, 32, CV_8UC1);
// When we call the operator with arguments y and x, we check
// the region(y,x). We then count the number of pixels within
// that region that are greater than some threshold. If the
// count is greater than desired number, we return 255, else 0.
int operator()(int y, int x) const
{
int j = y*32;
int i = x*32;
auto window = cv::Rect(i, j, 32, 32);
// threshold window contents
cv::threshold(data(window), buff, intensity_threshold, 1, CV_THRESH_BINARY);
int num_over_threshold = cv::countNonZero(buff);
return num_over_threshold > count_threshold ? 255 : 0;
}
};
int main() {
// Input image
cv::Mat img = cv::imread("walken.jpg", CV_8UC1);
// I resize it so that I get dimensions divisible
// by 32 and get better looking result
cv::Mat resized;
cv::resize(img, resized, cv::Size(3200, 3200));
BradleysImage b; // I had no idea how to name this so I used your nick
b.rows = resized.rows / 32;
b.cols = resized.cols / 32;
b.data = resized;
b.intensity_threshold = 128; // just some threshold
b.count_threshold = 512;
cv::Mat result(b.rows -1, b.cols-1, CV_8UC1);
for(int y = 0; y < result.rows; ++y)
for(int x = 0; x < result.cols; ++x)
result.at<uint8_t>(y, x) = b(y, x);
imwrite("walken.png", result);
return 0;
}
I used Christopher Walken's image from Wikipedia and obtained this result:

C++ OpenCV: Iterate through pixels in a Mat which is ROI of another Mat

I have a very large Mat which is actually a ROI of another Mat (obtained by otherMat(cv::Rect(x,y,w,h))). I want to go through all the pixels of the Mat, do some pixelwise computation and write the result to another Mat by using a pointer.
Going through all pixels, including the ones outside the ROI is working fine so far, but I am wondering what the fastest way of skipping the pixels outside the ROI is. I want to have as few cache misses as possible and also I don't want to have an inefficient branch prediction. What would be the best way to go about this?
Edit: I am not interested in getting a submatrix for a specifitc region of interest. I am interested in iterating through the pixel by pointer in an maximally efficient way without accessing data outside the submatrix' region.
Just use a submatrix:
cv::Mat largeMat
cv::Rect roi(yourROI);
cv::Mat submatrix = largeMat(roi);
// now iterate over all the pixels of submatrix
you will have cache misses at the end of each row
Here's the actual code example which shows, that the pixels outside of the submat are skipped (you'll get an additional cache miss at the end of each row but that should be all).
int main(int argc, char* argv[])
{
cv::Mat input = cv::imread("C:/StackOverflow/Input/Lenna.png");
cv::Rect roi(128, 128, 256, 256);
cv::Mat submat = input(roi);
cv::MatIterator_<cv::Vec3b> it; // = src_it.begin<cv::Vec3b>();
for (it = submat.begin<cv::Vec3b>(); it != submat.end<cv::Vec3b>(); ++it)
{
(*it)[0] = 0;
(*it)[1] = 0;
}
cv::imshow("input", input);
cv::imwrite("C:/StackOverflow/Output/submatIter.png", input);
cv::waitKey(0);
return 0;
}
giving this result:
If you want it a little faster you can use row-Pointers: http://docs.opencv.org/2.4/doc/tutorials/core/how_to_scan_images/how_to_scan_images.html
Please mind, that in the link they compared debug mode runtime speed, that's why the random access is so slow. In release mode it should be as fast (or maybe faster) than the iterator verson.
But here's the row-Ptr version (which spares to compute the row-offset on each pixel access) which gives the same result and should be the fastest way (if openCV's LUT function can't be used for your task):
int main(int argc, char* argv[])
{
cv::Mat input = cv::imread("C:/StackOverflow/Input/Lenna.png");
cv::Rect roi(128, 128, 256, 256);
cv::Mat submat = input(roi);
cv::Vec3b * currentRow;
for (int j = 0; j < submat.rows; ++j)
{
currentRow = submat.ptr<cv::Vec3b>(j);
for (int i = 0; i < submat.cols; ++i)
{
currentRow[i][0] = 0;
currentRow[i][1] = 0;
}
}
cv::imshow("input", input);
cv::imwrite("C:/StackOverflow/Output/submatIter.png", input);
cv::waitKey(0);
return 0;
}
As the OtherMat is a subset of Original mat and you want to do operation over the original mat but only inside the otherMat Region
//As otherMat(cv::Rect(x,y,w,h)));
for(int j=x;j<x+w;j++)
{
for (int i=y;i<y+h;i++)
{
original.at<uchar>(j,i) = 255;
}
}

Mat Image creation from raw buffer data

I have float x, float y, float z values of an image. I want to construct a 16 bit png depth image by copying the z values. The image I am getting as a result has some invalid points. Below is my code.
uint16_t* depthValues = new uint16_t[size];
auto sampleVector(DepthPoints);
for (unsigned int i = 0; i < sampleVector.size(); i++)
{
depthValues[i] = (sampleVector.at(i).z) * 65536;
}
Mat newDepthImage = cv::Mat(var.height, var.width, CV_16UC1,depthValues);
imwrite(Location, CImage);
Can someone tell me, if I can copy the float values into an unsigned char array to create the image?
Is that why my image has invalid points?
auto sampleVector(DepthPoints);
const int size = sampleVector.size();
float* depthValues = new float[size];
for (unsigned int i = 0; i < sampleVector.size(); i++)
{
depthValues[i] = (sampleVector.at(i).z);
}
Mat depthImageOne, depthImageTwo;
Mat depthImageNew = cv::Mat(var.height, var.width, CV_32FC1,depthValues);
normalize(newDepthImageNew, depthImageOne, 1, 0, NORM_MINMAX, CV_32FC1);
depthImageOne.convertTo(depthImageTwo, CV_16UC1, 65536.0,0.0);
imwrite("path", depthImageTwo);
Normalization might cause lose of depth information. I have used normalization for visualization of the images. To preserve the depth information, I used the below code.
Mat depthImageNew = cv::Mat(var.height, var.width, CV_32FC1,depthValues);
depthImageOne.convertTo(depthImageTwo, CV_16UC1, 1000.0,0.0);

Making an array of Mat type objects. The output window shows the same frame

Following is my code. It has an array of Mat type objects. And I add the Mat made inside the for loop as imgArr[index] = img. But when I output all the frames to see the animation on the window, it just shows the last frame and shows the same frame.
namedWindow( "Display window", WINDOW_AUTOSIZE );// Create a window for display.
int numFrames = endFrame - startFrame; // Total number of frames
Mat imgArr[100];
for(long int FrameNumber = startFrame; FrameNumber < endFrame; FrameNumber++){
fp.seekg( BytesPerFrame*(FrameNumber), std::ios::beg);
char buffer[BytesPerImage];
fp.read(buffer, BytesPerImage);
short image[512*512];
short min = 20000, max=1000;
for ( int i = 0; i < BytesPerImage; i=i+2 )
{
int a;
a = floor(i/2)+1;
// take first character
image[a] = (static_cast<unsigned int>(static_cast<unsigned char>(buffer[i+1]))*256+static_cast<unsigned int>(static_cast<unsigned char>(buffer[i])));
if(image[a] < min){
min = image[a];
}
if(image[a] > max){
max = image[a];
}
}
// Processing the image
Mat img(512, 512, CV_16S, image);
img -= (min);
img *= (32767/max); // (330000/2500);
img *= ((max/min)/2) + 2; // 16;
imgArr[FrameNumber-startFrame] = img;
}
for(int i = 0; i<numFrames; i++){
imshow( "Display window", imgArr[i]); // Show our image inside it.
waitKey(50);
}
There are a number of things you are not getting right with your code. I will try to list them:
namedWindow( "Display window", WINDOW_AUTOSIZE );// Create a window for display.
int numFrames = endFrame - startFrame; // Total number of frames
Mat imgArr[100];
First problem: What if your number of frames numFrames is bigger than 100? This would be safer:
std::vector<Mat> imgVector;
imgVector.reserve(numFrames);
And then at each new frame you push_back an image. Let's continue.
for(long int FrameNumber = startFrame; FrameNumber < endFrame; FrameNumber++){
fp.seekg( BytesPerFrame*(FrameNumber), std::ios::beg); //Hmmm, when did you compute BytesPerFrame?
char buffer[BytesPerImage]; //This is actually not C++, you probably even got a warning
You should replace char buffer[BytesPerImage] with char* buffer = new char[BytesPerImage];. You should also preallocate this intermediate buffer before the loop, so that you have to allocate it only once and use it many times. Then, after the loop, you deallocate it: delete[] buffer;.
fp.read(buffer, BytesPerImage); //This seems fine
short image[512*512]; //What's this?
What is 512? I can understand looking at your code later, but you should define somewhere something like:
const int MYWIDTH = 512;
const int MYHEIGHT = 512;
const int BYTES_PER_IMAGE = MYWIDTH * MYHEIGHT * 2; //Maybe also the 2 should be a constant named `BYTES_PER_PIXEL`
Also, in this case let's allocate dynamically your data with short* image = new short[MYWIDTH*MYHEIGHT];. However, this is not going to work properly: unfortunately if you construct a Mat from an external buffer then deallocation won't be managed automatically. It's better to proceed the other way around: create your Mat and then use it as your buffer. It will look like this:
Mat img(MYHEIGHT, MYWIDTH, CV_16S); //
short* image = static_cast<short*> img.ptr();
One problem for further operations is that there might be "padding bytes". It's unlikely for a 512x512 image, but who knows. Please assert that the following will be true (See doc):
(img.cols == img.step1() )
Then:
short min = 20000, max=1000;
Why not max=0? Also, min could be initialized to 32767, or, more elegantly, to std::numeric_limits<short>::max() (#include <limits.h>)
for ( int i = 0; i < BytesPerImage; i=i+2 )
{
int a;
a = floor(i/2)+1;
// take first character
image[a] = (static_cast<unsigned int>(static_cast<unsigned char>(buffer[i+1]))*256+static_cast<unsigned int>(static_cast<unsigned char>(buffer[i])));
if(image[a] < min){
min = image[a];
}
if(image[a] > max){
max = image[a];
}
}
What I understand is: your input buffer is a 16bit image represented in big endian (the most significant byte comes before the least significant). The problems I see:
what if the most significant byte is more than 127? Then your output value will overflow, as 128*256=32768 > 32767.
floor(i/2). floor is not necessary: when you divide an integer value, it always return you the integer part of the result. Also, given the definition of your for loop, i is always even (you increment by 2) so the floor operation is two times unnecessary.
int a; a = floor(i/2)+1; Remove the +1: think to the 0 indexed pixel and you'll immediately see you are assigning the value to the wrong pixel. With the last pixel you will actually have a segmentation fault. Your instruction becomes: const int a = i/2; (Ehi, how simple! :) )
image[a] = [...];: Some of the cast you are doing are effectively necessary, especially the cast to unsigned char. I wonder, though, why don't you read buffer as buffer of unsigned char in the first place. All the unsigned int conversion could be omitted, as you don't need it for the least significant byte, while using the integer value 256 will already promote the pixel data
A cameo: the min and max update function could be written as:
min = std::min(min,image[a]);
max = std::max(max,image[a]);
Let's proceed:
// Processing the image
Mat img(512, 512, CV_16S, image); //Already done, now remove
The Mat creation has already been taken care of.
img -= (min);
img *= (32767/max); // (330000/2500);
Ok, this has an easier equivalent using opencv library, we'll speak about it later. One problem here: this time you should really use a float for your division
img *= (float(32767)/max);
By the way, I think in this case you wanted max-min at the denominator:
img *= (float(32767)/(max-min));
The following I don't understand:
img *= ((max/min)/2) + 2; // 16;
Looking further,
imgArr[FrameNumber-startFrame] = img;
given the change I suggested above (std::vector of images), this becomes:
imgVector.push_back(img);
Finished!
One final note: it looks to me that what you are trying to do can be obtained with cv::normalize. You could do:
cv::normalize(img, img, 0, 32767, NORM_MINMAX, CV_16UC1);
if you construct a cv::Mat like this:
short pixels[512*512];
Mat img(512, 512, CV_16S, pixels);
the pointer to the pixels will be invalid, once you leave the scope.
either clone your Mat :
imgArr[FrameNumber-startFrame] = img.clone();
or pre-allocate it, and read into existing pixels:
Mat img(512, 512, CV_16S);
fp.read(img.data, BytesPerImage);
also: please replace the whole for ( int i = 0; i < BytesPerImage; i=i+2 ) (horror!!) loop with:
double minVal,maxVal;
minMaxLoc(img, &minVal, &maxVal, 0,0);