pass the pointer of image buffer to OpenCV and change the saturation? - c++

I want to pass the pointer of my image buffer, change the saturation and see the result immediately. But the change is not applying in my buffer and it is not changing.
void changeSaturation(void* buffer,int width, int height)
{
Mat matObject(width, height, CV_8UC4, buffer);
m_matSource = matObject;
Mat newMat = m_matSource.clone();
// BGR to HSV
cvtColor(matSource, matSource, CV_BGR2HSV);
for(int i = 0; i < newMat.rows; ++i)
{
for(int j = 0; j < newMat.cols; ++j)
{
newMat.at<cv::Vec3b>(i, j)[1] = 255; //saturationValue;
}
}
// HSV back to BGR
cvtColor(newMat, m_matSource, CV_HSV2BGR); // here m_matSource->data change
}
How can I apply the change on my buffer?

I refactored your code when trying to reproduce your problem and in the process I fixed it. You cloned your source into newMat then changed the color space of your original image and then proceed to completely ignore your new modified image. Try this out:
void changeSaturation(Mat& image)
{
Mat result(image.rows, image.cols, image.type());
// BGR to HSV
cvtColor(image, result, CV_BGR2HSV);
for(int i = 0; i < result.rows; ++i)
{
for(int j = 0; j < result.cols; ++j)
result.at<cv::Vec3b>(i, j)[1] = 255; //saturationValue;
}
// HSV back to BGR
cvtColor(result, result, CV_HSV2BGR); // here m_matSource->data change
namedWindow("Original");
imshow("Original",image);
namedWindow("Duplicate");
imshow("Duplicate",result);
}
int main()
{
Mat image;
image = imread("C:/Users/Public/Pictures/Sample Pictures/Desert.jpg");
changeSaturation(image);
waitKey(0);
}
Edit
To modify the input image:
void changeSaturation(Mat& image)
{
// BGR to HSV
cvtColor(image, image, CV_BGR2HSV);
for(int i = 0; i < image.rows; ++i)
{
for(int j = 0; j < image.cols; ++j)
image.at<cv::Vec3b>(i, j)[1] = 255; //saturationValue;
}
// HSV back to BGR
cvtColor(image, image, CV_HSV2BGR); // here m_matSource->data change
}
Next Edit
This now has (almost) the original function signature:
void changeSaturation(uchar* buffer, int rows, int cols, int type)
{
Mat image(rows, cols, type, buffer);
Mat result;
// BGR to HSV
cvtColor(image, result, CV_BGR2HSV);
for(int i = 0; i < result.rows; ++i)
{
for(int j = 0; j < result.cols; ++j)
result.at<cv::Vec3b>(i, j)[1] = 255;
}
// HSV back to BGR
cvtColor(result, image, CV_HSV2BGR);
}
int main()
{
Mat image;
image = imread("C:/Users/Public/Pictures/Sample Pictures/Desert.jpg");
changeSaturation(image.data, image.rows, image.cols, image.type());
imshow("Original",image);
waitKey(0);
}

Your constructor Mat matObject(width, height, CV_8UC4, buffer); allocates matObject of size width and height at the location pointed by buffer.
In your function you are making changes to newMat, which is cloned from matObject. However, buffer doesn't point to newMat, it points to matObject and matObject is not changed by your function.

Related

How to simply get RGB values in OpenCV?

I would like to obtain the RGB values for any pixel I choose or loop. This is currently how I achieve it.
Vec3b color = img.at<Vec3b>(Point(i, j));
and for the loop
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
Vec3b color = img.at<Vec3b>(Point(i,j));
img.at<Vec3b>(Point(i, j)) = color;
}
}
But when I apply the Canny/cvtColor function it all messes up. An Unhandled exception at memory location pops up.
I ran more tests, and found that the loop just works fine with the height, but as for the width it works only up (1/3) of the actual width. Most likely to do with Vec3b.
One such solution suggested
unsigned char color = img.at<unsigned char>(Point(i,j));
or
Vec<uchar, 3> color = img2.at <uchar>(Point(i,j));
But in uchar cases, how can i obtain individual RGB from color and how to set color back to pixel?
FULL CODE :
int main() {
VideoCapture cap("Assets/test2.mp4");
if (!cap.isOpened()) {
std::cout << "Problem in reading" << std::endl;
return -1;
}
while (1) {
Mat frame;
cap >> frame;
if (frame.empty()) {
break;
}
frame = Imgfn(frame);
imshow("FRAME", frame);
char c = (char)waitKey(100);
if (c == 27) {
break;
}
}
cap.release();
destroyAllWindows();
return 0;
}
Mat Imgfn(Mat img) {
int width = img.size().width , height = img.size().height;
cvtColor(img, img, COLOR_BGR2GRAY);
GaussianBlur(img, img, Size(3,3),0,0);
Canny(img, img, 50, 150);
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
Vec3b color = img.at<Vec3b>(Point(i, j));
color[0] = 0; color[1] = 255; color[2] = 0;
img.at<Vec3b>(Point(i, j)) = color;
// so **Obtain, select, set** color
}
}
return img;
}
[About the mp4][1]
[1]: https://i.stack.imgur.com/9hOBV.png
EDIT - FOUND THE SOLUTION - from one of the answers suggested below. The problem is when cvtColor convert to gray is performed it brings down to just 1 channel from 3. Mat.channels() can be used to check. so a simple uchar would work fine. if 3 channels of RGB are required then one could simply created their own RGB to GRAY function
Currently, how do you choose the type such as { Vec3b, unsigned char, ... } ?
If you access pixels in a known Mat , you know the type at the time and you can use that. Just do it so.
Otherwise, if you are talking about a situation in which you don't know the type of pixels in Mat, you can use Mat::depth(), Mat::channels(), etc to determine the type.

Not able to display the Histogram properly from a function in c++

I want to write a function to display a Histogram in C++ Visual Studio, The code is displaying a function to calculate histogram and display it, but I'm getting the same histogram for all the images used. The code is written below, Kindly advise where i might need improvement. Thank You.
class Histogram {
public:
Mat calc_histogram(Mat src) {
Mat hist;
hist = Mat::zeros(256, 1, CV_32F);
src.convertTo(src, CV_32F);
double value{ 0 };
for (int i = 0; i < src.rows; i++)
{
for (int j = 0; j < src.cols; j++)
{
value = src.at<float>(i, j);
//Add 1 point to the bin pixel intensity, so giving total pixels at each intensities
hist.at<float>(value) = hist.at<float>(value) + 1;
}
}
return hist;
}
void plot_histogram(Mat histogram) {
Mat histogram_image(400, 512, CV_8UC3, Scalar(0, 0, 0)); //Image background to draw hist on &
//Scaling it 512 pixels for clearer image
Mat normalized_histogram;
normalize(histogram, normalized_histogram, 0, 400, NORM_MINMAX, -1, Mat());
for (int i{ 0 }; i < 256; i++)
{
rectangle(histogram_image, Point(2 * i, histogram_image.rows - normalized_histogram.at<float>(i)),
Point(2 * (i + 1), histogram_image.rows), Scalar(255, 0, 0));
}
//namedWindow("Histogtram", WINDOW_NORMAL);
imshow("Histogram", histogram_image);
}
};
int main() {
Mat img;
img = imread("Shiv_Mahadev.png");
Histogram H1;
Mat hist = H1.calc_histogram(img);
H1.plot_histogram(hist);
waitKey(0);
destroyAllWindows();
return 0;
}

Change RGB to GrayScale in c++

I am trying to convert RGB image to gray scale using average method. But the output that is get is different from the desired output. I'm taking the image and getting the rgb values. I perform average operation and store the averaged and another array of same size of the image. Finally i'm converting the array to Mat and displaying the image.
Input image:
Desired output:
My output:
int main()
{
Mat image;
image =imread("<image_path>");
int rows=image.rows;
int cols=image.cols;
int myArray[rows][cols];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
myArray[i][j] = 0;
}
}
uint8_t* pixelPtr = (uint8_t*)image.data;
int cn = image.channels();
Scalar_<uint8_t> bgrPixel;
for(int i = 0; i < rows; i++)
{
for(int j = 0; j < cols; j++)
{
bgrPixel.val[0] = pixelPtr[i*image.cols*cn + j*cn + 0]; // B
bgrPixel.val[1] = pixelPtr[i*image.cols*cn + j*cn + 1]; // G
bgrPixel.val[2] = pixelPtr[i*image.cols*cn + j*cn + 2]; // R
int average = (bgrPixel.val[0]+bgrPixel.val[1]+bgrPixel.val[2])/3;
myArray[i][j]=average;
}
}
Mat averaged_image(Size(rows, cols), CV_8UC3, myArray, Mat::AUTO_STEP);
imwrite("<path to save the image>",averaged_image);
imshow("averaged_image",averaged_image);
waitKey(0);
return 0;
}
When creating Mat averaged_image,
Mat averaged_image(Size(rows, cols), CV_8UC3, myArray, Mat::AUTO_STEP);
you need to use CV_32S not CV_8UC3 because your array element is not three chars, it's one 32-bit int.
You can also use the function cvtColor:
cv::Mat gray;
cv::cvtColor(image, gray, CV_BGR2GRAY);
Bonus: this function does correct weighting of the channels, because simple averaging may not be the right thing to do.

Opencv only process the parts of image

I want to make a negative transformation for the image which is a very simple program.
But when I run the program. I want to transform all of the pixels in the image, but only 1/3 parts of that are processed. I don't make sure where is wrong. all the code I followed the book. But the result is different.
I think there is something wrong about the columns, but when I change the value of I.cols in negativeImage function with the actual value of image. the output still keep the same. only 1/3 parts of image are processed. If I 3 times the I.cols all of the pixels in the iamge could be processed.
vector<uchar> getNegativeLUT() {
vector<uchar> LUT(256, 0);
for (int i = 0; i < 256; ++i)
LUT[i] = (uchar)(255 - i);
return LUT;
}
void negativeImage(Mat& I) {
vector<uchar> LUT = getNegativeLUT();
for (int i = 0; i < I.rows; ++i) {
for (int j = 0; j < I.cols; ++j) {
I.at<uchar>(i, j) = LUT[I.at<uchar>(i, j)];
//stack overflow
}
}
}
int main() {
Mat image = imread("1.png");
Mat processed_image2 = image.clone();
negativeImage(processed_image2);
printf("%d", image.cols);
imshow("Input Image", image);
imshow("Negative Image", processed_image2);
waitKey(0);
return 0;
}
Output Image
You need to put correct type with at<> operator. Your PNG image has to be converted to 8UC1 to then use uchar type to access each pixel. I suppose your image has 3 channels, so you only iterate over 1/3 of the image. Also, I suggest you to use ptr<> operator in rows loop and then access to pixel as an array.
Mat M;
cvtColor(I, M, CV_BGR2GRAY);
// M is CV_8UC1 type
for(int i = 0; i < M.rows; i++)
{
uchar* p = M.ptr<uchar>(i);
for(int j = 0; j < I.cols; j++)
{
p[j] = LUT[p[j]];
}
}
EDIT: you should use cv::LUT instead of doing it yourself.
cv::Mat lut(1, 256, CV_8UC1);
for( int i = 0; i < 256; ++i)
{
lut.at<uchar>(0,i) = uchar(255-i);
}
cv::LUT(M, lut, result);

OpenCV cvtColor modifies original image too

I have a function that takes an image, converts it to HSV, and sets V to 100. However, it appears to modify the original image too.
Mat hsvfilter(const Mat& img) {
Mat result;
cvtColor(img, result, CV_BGR2HSV);
for (int j = 0; j < img.rows; j++)
for (int i = 0; i < img.cols; i++)
result.at<Vec3d>(i, j)[2] = 100;
return result;
}
Here's how I call it:
Mat original = imread( "pic.png" );
Mat converted = hsvfilter(original);
namedWindow( "original", CV_WINDOW_AUTOSIZE );
imshow( "original", original );
namedWindow( "converted", CV_WINDOW_AUTOSIZE );
imshow( "converted", converted );
waitKey(0);
Both the original image and the converted images end up having strange black vertical bars. I believe my code has some issues with pointers or memory, but I can't quite figure out where. Any help would be appreciated.
EDIT: HERE'S THE FIXED CODE
Mat hsvfilter(const Mat& img) {
Mat result;
cvtColor(img, result, CV_BGR2HSV);
for (int j = 0; j < result.rows; j++) {
for (int i = 0; i < result.cols; i++) {
result.at<cv::Vec3b>(j, i)[2] = 100;
}
}
return result;
}
Your hsvFilter function should looks like this:
Mat hsvfilter(const Mat& img) {
Mat result;
cvtColor(img, result, CV_BGR2HSV);
for (int j = 0; j < result.rows; j++) //you are modyfying "result" object, not img
for (int i = 0; i < result.cols; i++) //same as above
result.at<Vec3d>(j, i)[2] = 100; //OpenCV uses (y,x) indexing
return result;
}
In this situation ther is no difference in using img.cols, img.rows / result.cols, result.rows, because size of both arrays (images) is the same, but generally don't forget about it :) The second comment doesn't need any more explanation.
Generally you code looks fine, in my opinion it should work. Did you try testing it without calling hsvFilter function (just display the original image)?
If you want to keep created windows for some time, use this code instead of waitKey(0);:
while(waitKey(100) != 'q')
{
//all imshow calls
}
Now, when you want to exit, just press 'q' (you need to have one of your app windows active).