I'm trying to get a pixel from a Mat object. To test I try to draw a diagonal line on a square and expect to get a perfect line crossing from the top left to the down right vertex.
for (int i =0; i<500; i++){
//I just hard-coded the width (or height) to make the problem more obvious
(image2.at<int>(i, i)) = 0xffffff;
//Draw a white dot at pixels that have equal x and y position.
}
The result, however, is not as expected.
Here is a diagonal line drawn on a color picture.
Here is it on a grayscale picture.
Anyone sees the problem?
The problem is that you are trying to access each pixel as int (32 bit per pixel image), while your image is a 3-channel unsigned char (24 bit per pixel image) or a 1-channel unsigned char (8 bit per pixel image) for the grayscale one.
You can try to access each pixel like this for the grayscale one
for (int i =0; i<image2.width; i++){
image2.at<unsigned char>(i, i) = 255;
}
or like this for the color one
for (int i =0; i<image2.width; i++){
image2.at<Vec3b>(i, i)[0] = 255;
image2.at<Vec3b>(i, i)[1] = 255;
image2.at<Vec3b>(i, i)[2] = 255;
}
(image2.at<int>(i, i)) = 0xffffff;
It looks like your color image is 24bit, but your addressing pixels in terms of int which seems to be 32 bit.
Related
I am trying to make an image that is completely black except for a white rectangle at the centre of the image. However, on my first attempt, I got a weird result so I changed my code to nail down the problem.
So with for loops, I tried to set all the horizontal pixels at the centre to white to draw a white line across the image. Below is my code.
//--Block Mask--//
block_mask = cv::Mat::zeros(image_height, image_width, CV_8UC3);
int img_height = block_mask.rows;
int img_width = block_mask.cols;
for (int row = (img_height / 2); row < ((img_height / 2) + 1); row++)
{
for (int column = 0; column < img_width; column++)
{
block_mask.at<uchar>(row, column) = 255;
}
}
cv::namedWindow("Block Mask", CV_WINDOW_AUTOSIZE);
cv::imshow("Block Mask", block_mask);
img_height = 1080
img_width = 1920
image_height and image_width are defined from another image.
With this code I expected to see a white line drawn across the entire image, however, the white line extends only part way across the image. See the image below.
To troubleshoot I made a variable to count the iterations of the inner for loop and it counted up to 1920 as I expected it to. This leaves me wondering if it is something to do with the image being displayed? When simply setting individual pixels (not in loops) to white past where the line comes to, no results can be seen either.
I am at a loss as to what is going on here so any help, or perhaps a better way of achieving this, would be greatly appreciated.
Solved: The image block_mask is a three channel BGR image as it was created with the type CV_8UC3. However, when setting the pixel values to white the type uchar was used. Moreover, this was set to a signal integer type of value 255.
To properly set the colour of each pixel all three channels must be set. This can be achieved using a cv::Vec3b type variable that contains values for each channel and can be individually set. This can be done by:
cv::Vec3b new_pixel_colour;
new_pixel_colour[0] = 255; //Blue channel
new_pixel_colour[1] = 255; //Green channel
new_pixel_colour[2] = 255; //Red channel
From here, pixels can be assigned with this variable to change their colour, making sure to change the type in the .at operator to cv::Vec3b also. The corrected code is below.
//--Block Mask--//
block_mask = cv::Mat::zeros(image_height, image_width, CV_8UC3);
cv::Vec3b new_pixel_colour;
new_pixel_colour[0] = 255; //Blue channel
new_pixel_colour[1] = 255; //Green channel
new_pixel_colour[2] = 255; //Red channel
int img_height = block_mask.rows;
int img_width = block_mask.cols;
for (int row = (img_height / 2); row < ((img_height / 2) + 1); row++)
{
for (int column = 0; column < img_width; column++)
{
block_mask.at<cv::Vec3b>(row, column) = new_pixel_colour;
}
}
cv::namedWindow("Block Mask", CV_WINDOW_AUTOSIZE);
cv::imshow("Block Mask", block_mask);
An alternative solution for drawing is using the in-buit drawing functions of OpenCV. Specifically, for drawing a rectangle the OpenCV function cv::rectangle() can be used. A tutorial on basic drawing in OpenCV can be found here: https://docs.opencv.org/master/d3/d96/tutorial_basic_geometric_drawing.html
I've got a problem iterating through coordinates of a OpenCV Mat:
cv::Mat picture = cv::Mat(depth.rows, depth.cols, CV_32F);
for (int y = 0; y < depth.rows; ++y)
{
for (int x = 0; x < depth.cols; ++x)
{
float depthValue = (float) depth.at<float>(y,x);
picture.at<float>(y, x) = depthValue;
}
}
cv::namedWindow("picture", cv::WINDOW_AUTOSIZE);
cv::imshow("picture", picture);
cv::waitKey(0);
Resulting pictures:
before (depth)
after (picture)
It looks like it's
1. scaled and
2. stopped at about a third of the width. Any ideas?
Looks like your depth image have 3 channels.
All channels values are the same for BW image (B=G=R), so you have BGRBGRBGR instead of GrayGrayGray, and you trying to access it as it is 1 channel, that is why image is stretched 3 times horizontally.
Try to cv::cvtColor(depth,depth,COLOR_BGR2GRAY) before running loop.
Your iteration code is right.
What's wrong instead is the cv::Mat depth type assumption.
As suggested, this could be CV_U8C3 according to the distorsion.
To get the pixel value of such a CV_8UC3 matrix you can use :
cv::Vec3i depthValue = depth.at<cv::Vec3i>(y,x);
Then do whatever you want with this scalar.
For example, if your depth is of type CV_8UC3 with distance encoded in the two first bytes (MSB first) you can get the distance with :
float distance = depthValue[0] * 255 + depthValue[1];
enter image description here
jpg
I want to check the 2nd image to see if the pixel is white, if it is white i should change it into a black pixel, and also i should be able to change the pixel of the same spot in the 2nd image to the 1st image to black or white..
Example:
img at the cooridnate (100,100) the pixel is white from the 2nd image and i should be able to change it into black. Then the 1st img at the same cooridnate (100,100) the pixel would be black and i should be able to change it into white. to reduce the noise.
The below code shows you how to find a point in an image, see if it i white, and change it to black if it is.
Scalar colourInSecondImage = img2.at<uchar>(y,x);
if(colourInSecondImage .val[0]==255 && colourInSecondImage .val[1]==255 && colourInSecondImage .val[2]==255)
{
// Then your point is a white point
img2.at<uchar>(y,x) = Scalar(0,0,0);
}
I'm a little confused by your question, it seems to be that you then want to access the same point in another image and set that to black? Or the same colour? either way you would use the same method as in the code above. change change im2 to img1
This is how you can loop through all your pixel values and manipulate them
for(int r = 0; r < image.rows; r++) {
for(int c = 0; c < image.cols; c++) {
// if pixel is white
if(image.at<uchar>(r,c) == 255) {
image.at<uchar>(r,c) = 0;
}
}
}
//// split channels
split(image,spl);
imshow("spl1",spl[0]);//b
imshow("spl2",spl[1]);//g
imshow("spl3",spl[2]);//r
I know Mat data save each pixel data(RGB data in a column) as a unsigned char. And texture data in unity3d is byte[]. I try to do the following thing, but the result is not right, the image capture from the camera displayed in the hwind is not right.
Mat src_frame;//this is every frame that captured from the current camera
D3DLOCKED_RECT d3d_rect;
byte *pSrc = src_frame.data;
byte *pDest = (byte *)d3d_rect.pBits;
int stride = d3d_rect.Pitch;
int pixel_w_size = src_frame.cols;
for (unsigned long i = 0; i < src_frame.rows; i++){
memcpy(pDest, pSrc, pixel_w_size);
pDest += stride;
pSrc += pixel_w_size;
}
Only gray image shows, I don't know why this happens. Does the Mat data sequence is different from the texture data? Their data both contain the RGB information.
pixel_w_size should be src_frame.cols multiplied with the bytes per pixel (3 in case of RGB) or even better use elemSize().
So use:
int pixel_w_size = src_frame.cols * src_frame.elemSize();
I've wrote a code which detects squares (white) in realtime and draws a frame around it. Each side of length l of the squares is divided in 7 parts. Then I draw a line of length h=l/7 at each of the six points evolving from the deviation perpendicular to the side of the triangle (blue). The corners are marked in red. It then looks something like this:
For the drawing of the blue lines and circles I have a 3 Channel (CV_8UC3) matrix drawing, which is zero everywhere except at the positions of the red, blue and white lines. Then what I do to lay this matrix over my webcam image is using the addWeighted function of opencv.
addWeighted( drawing, 1, webcam_img, 1, 0.0, dst); (Description for addWeighted here).
But then, as you can see I get the effect that the colors for my dashes and circles are wrong outside the black area (probably also not correct inside the black area, but better there). It makes totally sense why it happens, as it just adds the matrices with a weight.
I'd like to have the matrix drawing with the correct colors over my image. Problem is, I don't no how to fix it. I somehow need a mask drawing_mask where my dashes are, sort of, superimposed to my camera image. In Matlab something like dst=webcam_img; dst(drawing>0)=drawing(drawing>0);
Anyone an idea how to do this in C++?
1. Custom version
I would write it explicitly:
const int cols = drawing.cols;
const int rows = drawing.rows;
for (int j = 0; j < rows; j++) {
const uint8_t* p_draw = drawing.ptr(j); //Take a pointer to j-th row of the image to be drawn
uint8_t* p_dest = webcam_img.ptr(j); //Take a pointer to j-th row of the destination image
for (int i = 0; i < cols; i++) {
//Check all three channels BGR
if(p_draw[0] | p_draw[1] | p_draw[2]) { //Using binary OR should ease the optimization work for the compiler
p_dest[0] = p_draw[0]; //If the pixel is not zero,
p_dest[1] = p_draw[1]; //copy it (overwrite) in the destination image
p_dest[2] = p_draw[2];
}
p_dest += 3; //Move to the next pixel
p_draw += 3;
}
}
Of course you can move this code in a function with arguments (const cv::Mat& drawing, cv::Mat& webcam_img).
2. OpenCV "purist" version
But the pure OpenCV way would be the following:
cv::Mat mask;
//Create a single channel image where each pixel != 0 if it is colored in your "drawing" image
cv::cvtColor(drawing, mask, CV_BGR2GRAY);
//Copy to destination image only pixels that are != 0 in the mask
drawing.copyTo(webcam_img, mask);
Less efficient (the color conversion to create the mask is somehow expensive), but certainly more compact. Small note: It won't work if you have one very dark color, like (0,0,1) that in grayscale will be converted to 0.
Also note that it might be less expensive to redraw the same overlays (lines, circles) in your destination image, basically calling the same draw operations that you made to create your drawing image.