I have one gray scale image which is just the R channel of a photo, now I'm trying to write that R channel into a new image, which is an RGB image. Ideally, the new image would look just like the old image, but red.
What happens though is that in the new image, the old image appears three times squished next to each other.
Here you can see the gray scale image and the output image.
Here is my code, I think it's pretty straightforward:
Mat img_in = imread("in.png", CV_LOAD_IMAGE_GRAYSCALE);
Mat img_out = Mat::zeros(img_in.size(), CV_8UC3);
for (int i = 0; i < img_in.rows; i++)
{
for (int j = 0; j < img_in.cols; j++)
{
img_out.at<Vec3b>(i,j)[2] = img_in.at<Vec3b>(i,j)[2];
}
}
imwrite("test_img_in.png", img_in);
imwrite("test_img_out.png", img_out);
At first I thought it was some kind of indices mixup, but I've tried a lot of combinations, and it always multiplies the output image three times horizontally, never vertically.
Now my thought is that it comes from some OpenCV specification, like the CV_8UC3 type (I've tried others too), which I've chosen because I think it support RGB images. Unfortunately, I don't know too much about OpenCV itself, that's why I'm seeking help here.
PS: This is part of a whole bigger program which wants to generate a color image from three gray scale channel images, but I'm currently stuck on combining the aligned gray scale images, since this happens. The code I posted is isolated from the rest of the program and works like this on its own.
My OpenCV version is 2.4.11.
The problem is here:
img_out.at<Vec3b>(i,j)[2] = img_in.at<Vec3b>(i,j)[2];
As you said the input image is gray. So, just use:
img_out.at<Vec3b>(i,j)[2] = img_in.at<unsigned char>(i,j);
you will get the same result by loading your image as 3 channel and subtract Scalar(255,255,0)
#include <opencv2/opencv.hpp>
using namespace cv;
int main(int argc, char **argv)
{
Mat src = imread(argv[1]);
imshow("src", src );
src -= Scalar(255,255,0);
imshow("Red channel", src );
waitKey();
return 0;
}
Related
I'm attempting to read the pixels in an image and convert them into another format by iterating through the pixels.
After my conversion I only seem to be getting 1/3rd of the image and I'm certain it's because of the way I'm accessing the pixels using the .at() function.
I'm reading in the following image:
Mat image = imread("cameraman.jpg");
I then iterate through the images rows and columns:
for (int i = 0; i < image.rows; i++)
{
for (int j = 0; j < image.cols; j++)
{
placeGrayValue((double)image.at<uchar>(i, j));
}
}
Note: placedGrayValue() is just a placeholder here so that I can share only the code that is relevant.
The resulting image is only the first third of the image:
You're loading your image with cv::imread, which with default value (cv::IMREAD_COLOR) will load it as a 3 channel image of type CV_8UC3 (aka cv::Mat3b).
If your original image is grayscale, when loading as a 3 channel image you have the same intensity value for each channel.
So when you scan the image you should access pixels with .at<cv::Vec3b>(...).
If you want to copy only the first channel to the placeGrayValue matrix you should do it as:
placeGrayValue((double)image.at<cv::Vec3b>(i, j)[0]);
^^^^^^^^^ ^^^
3 channel first channel
If your input is not a grayscale image, then you shouldn't just copy the first channel, since the grayscale value is a linear combination of the three R,G,B channels.
So it's better to first convert to grayscale, and then copy:
cv::Mat grayscale;
cv::cvtColor(image, grayscale, cv::COLOR_BGR2GRAY);
...
placeGrayValue((double)grayscale.at<uchar>(i, j));
^^^^^
1 channel
Or you can load the image already as a grayscale image:
Mat grayscale = imread("cameraman.jpg", cv::IMREAD_GRAYSCALE);
At the end, you want to have placeGrayValue with the grayscale values as double.
You should not scan the image for this kind of easy operations. You can just:
cv::Mat placeGrayValue;
grayscale.convertTo(placeGrayValue, CV_64F);
^^^^^^
to double type
Summing up:
cv::Mat grayscale = cv::imread("cameraman.jpg", cv::IMREAD_GRAYSCALE);
cv::Mat placeGrayValue;
grayscale.convertTo(placeGrayValue, CV_64F);
I'll post what ended up working as an answer, though it makes little sense to me and I'd still like to understand why.
The image has 3 channels. When iterate through an image using a for loop and extract pixel data with (double)image.at<uchar>(i, j) it goes through each channel as if they were individual pixels.
The solution (at least with this grayscale image) is to iterate and multiply by 3. In other words, (double)image.at<uchar>(i*3, j) ended up giving me the full image.
I have a question for us. I'm a newbe of OpenCV and I need to understand if that lib can help me to reach my goals.
I need to use OpenCV to open a Tiff file (big Tiff file) and split it on two different file with a mask like that Mask, in the end the file 1 have pixel black and the file 2 have the negative - pixel white of the original image.
Any ideas or example for me?
Thank you all!
To read the file, you can use the function imread. This stores it in a cv::Mat object. Since your mask is black and white, I would read the mask-image as a grayscale using IMREAD_GRAYSCALE. This gives you each pixel with a value from 0-255. That should cover the first part of your question.
I have to admit I am having trouble understandig your question, but I expect you want to create two images. The first contains all the pixels where your mask has a black pixel. The second one contains an image where in the mask all the pixels are white.
You could look at this thread. Additionally I would like to give you the way that I would do it.
The problem you would run in to is that your .tiff-image has a different type than your chessboard. Tiff is probably CV_8UC3 and chessboard is probably CV_8UC1. But this should be easily solvable.
I think you would probably want to look at each individual pixel and leave the be if, at that same pixel of the chessboard, your color is white. Then if it is not, make that pixel from your original pixel black. I have not tested this, but it would look something like this.
for (int i = 0; i < originalImage.rows; i++) {
for (int j = 0; j < originalImage.cols; j++) {
if (chessboard.at<uchar>(Point(j, i)) != 255) {
originalImage.at<Vec3b>(Point(j, i)) = Scalar(0, 0, 0);
}
else {
// Do nothing.
}
}
}
Scalar is used, since the originalImage has three channels instead of one. I hope this helps!
Try this to create the mask:
cv::Mat tiff;
cv::Mat maskDark = tiff == 0; // comparison like '< 10' also works
cv::Mat maskDark = tiff == 255;
According to this post OCR: Difference between two frames, I now know how to find pixel differences between two images with OpenCV.
I would like to improve this solution and use it with high resolution images (from a video) with rich content. The example above is not applicable with big images because the process is to slow (too much differences found, the "findCountours method" fills the tab with 250k elements which takes a huge time to process).
My application uses a RLE decoder to decode the compressed frames of the video. Once the frame is decoded, I would like to compare the current frame with the previous one in order to store the differences between the two frames in a "Mat" tab for example.
The goal of all of this is to be able to perform an analysis on the different pixels and to check if there is any latin character. This allows me to reduce the amount of pixels to analyze and to save precious time.
If anyone has other ideas instead of this one to perform such operations, feel free to propose it please.
Thank you for your help.
EDIT 1:
Example of two high resolution images of a computer screen. These are for the moment the perfect example of what I'm trying to analyse. As we can see there is just a window as difference between the two big images and I would like to analyze just the new "Challenge" window for any character.
EDIT 2:
I'm trying to tune the algorithm depending on the data analyzed. Typically on the two following pictures I only get the green lines as differences and no text at all (which is what is the most interesting). I'm trying to understand better how things work for this.
1st image:
2nd image:
3rd image:
As you can see I only have those green lines and never the text (at the best I can have just ONE letter when decreasing the countours[i].size())
In addition to the post you mentioned, you need to:
When you binarize the mask, use a threshold higher then 0 to remove small differences.
Remove some noise. You can find all connected components, and remove smaller ones.
Find the area of the bigger connected components. You can use convexHull and fillConvexPoly to get the mask of the different objects on screen
Copy the second image to a new image, with the given mask.
The result will look like:
Code:
#include <opencv2/opencv.hpp>
#include <vector>
using namespace std;
using namespace cv;
int main()
{
Mat3b img1 = imread("path_to_image_1");
Mat3b img2 = imread("path_to_image_2");
Mat3b diff;
absdiff(img1, img2, diff);
// Split each channel
vector<Mat1b> masks;
split(diff, masks);
// Create a black mask
Mat1b mask(diff.rows, diff.cols, uchar(0));
// OR with each channel of the N channels mask
for (int i = 0; i < masks.size(); ++i)
{
mask |= masks[i];
}
// Binarize mask
mask = mask > 100;
// Results images
vector<Mat3b> difference_images;
// Remove small blobs
//Mat kernel = getStructuringElement(MORPH_RECT, Size(5,5));
//morphologyEx(mask, mask, MORPH_OPEN, kernel);
// Find connected components
vector<vector<Point>> contours;
findContours(mask.clone(), contours, CV_RETR_EXTERNAL, CHAIN_APPROX_NONE);
for (int i = 0; i < contours.size(); ++i)
{
if (contours[i].size() > 1000)
{
Mat1b mm(mask.rows, mask.cols, uchar(0));
vector<Point> hull;
convexHull(contours[i], hull);
fillConvexPoly(mm, hull, Scalar(255));
Mat3b difference_img(img2.rows, img2.cols, Vec3b(0,0,0));
img2.copyTo(difference_img, mm);
difference_images.push_back(difference_img.clone());
}
}
return 0;
}
I am trying to find an easy solution to implement the OCR algorithm from OPenCV. I am very new to Image Processing !
I am playing a video that is decoded with specific codec using RLE algorithm.
What I would like to do is that for each decoded frame, I would like to compare it with the previous one and store the pixels that have changed between the two frames.
Most of the existing solutions gives a difference between the two frames but I would like to just keep the new pixels that have changed and store it in a table and then be able to analyze every group of pixels that have changed instead of analyzing the whole image each time.
I planned to use the "blobs detection" algoritm mais I'm stuck before being able to implement it.
Today, I'm trying this:
char *prevFrame;
char *curFrame;
QVector DiffPixel<LONG>;
//for each frame
DiffPixel.push_back(curFrame-prevFrame);
I really want to have the "Only changed pixel result" solution. Could anyone give me some tips or correct me if I'm going to a wrong way ?
EDIT:
New question, what if there are multiple areas of changed pixels ? Will it be possible to have one table per blocs of changed pixels or will it be only one unique table ? Take the example below:
The best thing as a result would be to have 2 mat matrices. The first matrix with the first orange square and the second matrix with the second orange square. This way, it avoids having to "scan" almost the entire frame if we store the result in one matrix only with a resolution being almost the same as the full frame.
The main goal here is to minimize the area (aka the resolution) to analyze to find text.
After loading your images:
img1
img2
you can apply XOR operation to get the differences. The result has the same number of channels of the input images:
XOR
You can then create a binary mask OR-ing all channels:
mask
The you can copy the values of img2 that correspond to non-zero elements in the mask to a white image:
diff
UPDATE
If you have multiple areas where pixel changed, like this:
You'll find a difference mask (after binarization all non-zero pixels are set to 255) like:
You can then extract connected components and draw each connected component on a new black-initialized mask:
Then, as before, you can copy the values of img2 that correspond to non-zero elements in each mask to a white image.
The complete code for reference. Note that this is the code for the updated version of the answer. You can find the original code in the revision history.
#include <opencv2\opencv.hpp>
#include <vector>
using namespace cv;
using namespace std;
int main()
{
// Load the images
Mat img1 = imread("path_to_img1");
Mat img2 = imread("path_to_img2");
imshow("Img1", img1);
imshow("Img2", img2);
// Apply XOR operation, results in a N = img1.channels() image
Mat maskNch = (img1 ^ img2);
imshow("XOR", maskNch);
// Create a binary mask
// Split each channel
vector<Mat1b> masks;
split(maskNch, masks);
// Create a black mask
Mat1b mask(maskNch.rows, maskNch.cols, uchar(0));
// OR with each channel of the N channels mask
for (int i = 0; i < masks.size(); ++i)
{
mask |= masks[i];
}
// Binarize mask
mask = mask > 0;
imshow("Mask", mask);
// Find connected components
vector<vector<Point>> contours;
findContours(mask.clone(), contours, RETR_LIST, CHAIN_APPROX_SIMPLE);
for (int i = 0; i < contours.size(); ++i)
{
// Create a black mask
Mat1b mask_i(mask.rows, mask.cols, uchar(0));
// Draw the i-th connected component
drawContours(mask_i, contours, i, Scalar(255), CV_FILLED);
// Create a black image
Mat diff_i(img2.rows, img2.cols, img2.type());
diff_i.setTo(255);
// Copy into diff only different pixels
img2.copyTo(diff_i, mask_i);
imshow("Mask " + to_string(i), mask_i);
imshow("Diff " + to_string(i), diff_i);
}
waitKey();
return 0;
}
I'm trying to have a webcam take a picture of someone's face in BGR, convert the picture into HSV, and analyze these HSV values that will later be used in a skin detection algorithm. Unfortunately, the picture seems to be analyzed in BGR, even after I try to convert it using cvtColor().
I use the code below to test whether or not I'm using the right color space. Note the part where I try to set saturation and value to 0:
Mat faceROI = findFace(first); //basic Mat, region of interest for face (code not included)
Mat temp;
faceROI.convertTo(temp, CV_8UC3); //making sure this has right no. of channels and such
CvScalar s;
IplImage face_ipl = temp; //new header
IplImage* aNew = cvCreateImage(cvGetSize(&face_ipl), face_ipl.depth, 3);
cvCvtColor(&face_ipl, aNew, CV_BGR2HSV);
for(int x = 0; x < faceROI.cols; x++){
for (int y = 0; y < faceROI.rows; y++){
s = cvGet2D(aNew, x, y);
//vvvvvvvvvvv
s.val[1] = 0; //should be saturation
s.val[2] = 0; //should be value
//^^^^^^^^^^^
cvSet2D(aNew, x, y, s);
}
}
Mat again(aNew); //<--- is this where something is set back to BGR?
imshow("white", again);
cvReleaseImage(&aNew);
This produces a completely blue picture of my face, so it seems likes I'm editing the G and R channels of a BGR image, instead of the S and V channels of an HSV image. (I'd post the image, but this is my first post so I don't have enough reputation yet.)
Does anybody know why this is happening? Any and all thoughts are appreciated.
You're mixing up the C++ Mat style with the old C IplImage*, this makes it confusing to see what exactly is going on. Here is the code to turn inputImage into HSV:
Mat fullImageHSV;
cvtColor(inputImage, fullImageHSV, CV_BGR2HSV);
Be aware that the OpenCV HSV values are H from 0-180 while S and V are from 0-255 while other programs tend to use different values. ALso note that OpenCV is unable to show HSV images normally, this distorts the color because they are being interpreted as RGB.