convert bgr to hsv in opencv, C++ - c++

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.

Related

Convert grayscale image to single color in OpenCV

I'm wondering if there is a way to convert a grayscale image to one color image? Like if I have an image in grayscale and I want to convert it to shades of blue instead? Is that possible in OpenCV?
Thank you very much!
According to the opencv community answer, you should of creating a 3-channel image by yourself.
Mat empty_image = Mat::zeros(src.rows, src.cols, CV_8UC1);//initial empty layer
Mat result_blue(src.rows, src.cols, CV_8UC3); //initial blue result
Mat in1[] = { ***GRAYINPUT***, empty_image, empty_image }; //construct 3 layer Matrix
int from_to1[] = { 0,0, 1,1, 2,2 };
mixChannels( in1, 3, &result_blue, 1, from_to1, 3 ); //combine image
After that, you can get your blue channel image. Normally, the blue channel of an colour image in opencv is the first layer (cuz they put 3 channels as BGR).
By the way, if you wanna use the copy each pixel method, you can initial an empty image
Mat result_blue(src.rows, src.cols, CV_8UC3); //blue result
for (int i =0; i<src.rows; i++)
for (int j=0; j<src.cols; j++){
Vec3b temp = result_blue.at<Vec3b>(Point(i,j));//get each pixel
temp[0] = gray.at<uchar>(i,j); //give value to blue channel
result_blue.at<Vec3b>(Point(x,y)) = temp; //copy back to img
}
However, it will take longer as there are two loops!
A gray scale image is usually just one dimensional. Usually what I do if I want to pass in a gray scale image into a function that accepts RGB (3-dimensional), I replicate the the matrix 3 times to create a MxNx3 matrix. If you wish to only use the Blue channel, just concat MxN of zeros in the 1st dimension and the 2nd dimension while putting the original gray scale values in the 3rd dimension.
To accomplish this you would essentially just need to iterate over all the pixels in the grayscale image and map the values over to a color range. Here is pseudo-code:
grayImage:imageObject;
tintedImage:imageObject;
//Define your color tint here
myColorR:Int = 115;
myColorG:Int = 186;
myColorB:Int = 241;
for(int i=0; i<imagePixelArray.length; i++){
float pixelBrightness = grayImage.getPixelValueAt(i)/255;
int pixelColorR = myColorR*pixelBrightness;
int pixelColorG = myColorG*pixelBrightness;
int pixelColorB = myColorB*pixelBrightness;
tintedImage.setPixelColorAt(i, pixelColorR, pixelColorG, pixelColorB);
}
Hope that helps!

OpenCV Object Detection (HOGDescriptor) on iOS

I'm trying to get the people detector provided by the OpenCV library running. So far I get decent performance on my iPhone 6 but the detection is super bad and almost never correct and I'm not really sure why this is since you can find example videos using the same default HOG descriptor with way better detection.
Here is the code:
- (void)processImage:(Mat&)image {
cv::Mat cvImg, result;
cvtColor(image, cvImg, COLOR_BGR2HSV);
cv::vector<cv::Rect> found, found_filtered;
hog.detectMultiScale(cvImg, found, 0, cv::Size(4,4), cv::Size(8,8), 1.5, 0);
size_t i;
for (i=0; i < found.size(); i++) {
cv::Rect r = found[i];
rectangle(image, r.tl(), r.br(), Scalar(0,255,0), 2);
}
}
The video input comes from the iPhone camera itself and "processImage:" is called for every frame. For the HOGDescriptor I use the default people detector:
_hog.setSVMDetector(cv::HOGDescriptor::getDefaultPeopleDetector());
I appreciate any help. :)
I'm new to openCV, so take this with a grain of salt:
The line cvtColor(image, cvImg, COLOR_BGR2HSV); converts the image from the BGR color space to the HSV color space. Essentially, it changes each pixel from being represented by how much blue, green, and red it has, to being represented by the components hue (color), saturation (how much color) and value (how bright). Clearly, the hogDescriptor acts on a BGR image, not an HSV image. You need to pass it a type CV_8UC3 image: An image with 3 channels per pixel (C3), ex. BGR, and an 8bit unsigned number for each channel (8U), This part is less important. What are you passing into the method processImage()? It should be one of those types. If not, you need to know the type and convert it to CV_8UC3 using the cvtColor() method

Image is multiplied three times in OpenCV, what causes this?

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

OpenCV keep background transparent during warpAffine

I create a Bird-View-Image with the warpPerspective()-function like this:
warpPerspective(frame, result, H, result.size(), CV_WARP_INVERSE_MAP, BORDER_TRANSPARENT);
The result looks very good and also the border is transparent:
Bird-View-Image
Now I want to put this image on top of another image "out". I try doing this with the function warpAffine like this:
warpAffine(result, out, M, out.size(), CV_INTER_LINEAR, BORDER_TRANSPARENT);
I also converted "out" to a four channel image with alpha channel according to a question which was already asked on stackoverflow:
Convert Image
This is the code: cvtColor(out, out, CV_BGR2BGRA);
I expected to see the chessboard but not the gray background. But in fact, my result looks like this:
Result Image
What am I doing wrong? Do I forget something to do? Is there another way to solve my problem? Any help is appreciated :)
Thanks!
Best regards
DamBedEi
I hope there is a better way, but here it is something you could do:
Do warpaffine normally (without the transparency thing)
Find the contour that encloses the image warped
Use this contour for creating a mask (white values inside the image warped, blacks in the borders)
Use this mask for copy the image warped into the other image
Sample code:
// load images
cv::Mat image2 = cv::imread("lena.png");
cv::Mat image = cv::imread("IKnowOpencv.jpg");
cv::resize(image, image, image2.size());
// perform warp perspective
std::vector<cv::Point2f> prev;
prev.push_back(cv::Point2f(-30,-60));
prev.push_back(cv::Point2f(image.cols+50,-50));
prev.push_back(cv::Point2f(image.cols+100,image.rows+50));
prev.push_back(cv::Point2f(-50,image.rows+50 ));
std::vector<cv::Point2f> post;
post.push_back(cv::Point2f(0,0));
post.push_back(cv::Point2f(image.cols-1,0));
post.push_back(cv::Point2f(image.cols-1,image.rows-1));
post.push_back(cv::Point2f(0,image.rows-1));
cv::Mat homography = cv::findHomography(prev, post);
cv::Mat imageWarped;
cv::warpPerspective(image, imageWarped, homography, image.size());
// find external contour and create mask
std::vector<std::vector<cv::Point> > contours;
cv::Mat imageWarpedCloned = imageWarped.clone(); // clone the image because findContours will modify it
cv::cvtColor(imageWarpedCloned, imageWarpedCloned, CV_BGR2GRAY); //only if the image is BGR
cv::findContours (imageWarpedCloned, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
// create mask
cv::Mat mask = cv::Mat::zeros(image.size(), CV_8U);
cv::drawContours(mask, contours, 0, cv::Scalar(255), -1);
// copy warped image into image2 using the mask
cv::erode(mask, mask, cv::Mat()); // for avoid artefacts
imageWarped.copyTo(image2, mask); // copy the image using the mask
//show images
cv::imshow("imageWarpedCloned", imageWarpedCloned);
cv::imshow("warped", imageWarped);
cv::imshow("image2", image2);
cv::waitKey();
One of the easiest ways to approach this (not necessarily the most efficient) is to warp the image twice, but set the OpenCV constant boundary value to different values each time (i.e. zero the first time and 255 the second time). These constant values should be chosen towards the minimum and maximum values in the image.
Then it is easy to find a binary mask where the two warp values are close to equal.
More importantly, you can also create a transparency effect through simple algebra like the following:
new_image = np.float32((warp_const_255 - warp_const_0) *
preferred_bkg_img) / 255.0 + np.float32(warp_const_0)
The main reason I prefer this method is that openCV seems to interpolate smoothly down (or up) to the constant value at the image edges. A fully binary mask will pick up these dark or light fringe areas as artifacts. The above method acts more like true transparency and blends properly with the preferred background.
Here's a small test program that warps with transparent "border", then copies the warped image to a solid background.
int main()
{
cv::Mat input = cv::imread("../inputData/Lenna.png");
cv::Mat transparentInput, transparentWarped;
cv::cvtColor(input, transparentInput, CV_BGR2BGRA);
//transparentInput = input.clone();
// create sample transformation mat
cv::Mat M = cv::Mat::eye(2,3, CV_64FC1);
// as a sample, just scale down and translate a little:
M.at<double>(0,0) = 0.3;
M.at<double>(0,2) = 100;
M.at<double>(1,1) = 0.3;
M.at<double>(1,2) = 100;
// warp to same size with transparent border:
cv::warpAffine(transparentInput, transparentWarped, M, transparentInput.size(), CV_INTER_LINEAR, cv::BORDER_TRANSPARENT);
// NOW: merge image with background, here I use the original image as background:
cv::Mat background = input;
// create output buffer with same size as input
cv::Mat outputImage = input.clone();
for(int j=0; j<transparentWarped.rows; ++j)
for(int i=0; i<transparentWarped.cols; ++i)
{
cv::Scalar pixWarped = transparentWarped.at<cv::Vec4b>(j,i);
cv::Scalar pixBackground = background.at<cv::Vec3b>(j,i);
float transparency = pixWarped[3] / 255.0f; // pixel value: 0 (0.0f) = fully transparent, 255 (1.0f) = fully solid
outputImage.at<cv::Vec3b>(j,i)[0] = transparency * pixWarped[0] + (1.0f-transparency)*pixBackground[0];
outputImage.at<cv::Vec3b>(j,i)[1] = transparency * pixWarped[1] + (1.0f-transparency)*pixBackground[1];
outputImage.at<cv::Vec3b>(j,i)[2] = transparency * pixWarped[2] + (1.0f-transparency)*pixBackground[2];
}
cv::imshow("warped", outputImage);
cv::imshow("input", input);
cv::imwrite("../outputData/TransparentWarped.png", outputImage);
cv::waitKey(0);
return 0;
}
I use this as input:
and get this output:
which looks like ALPHA channel isn't set to ZERO by warpAffine but to something like 205...
But in general this is the way I would do it (unoptimized)

Using Mat::at(i,j) in opencv for a 2-D Mat object

I am using Ubuntu 12.04 and OpenCV 2
I have written the following code :
IplImage* img =0;
img = cvLoadImage("nature.jpg");
if(img != 0)
{
Mat Img_mat(img);
std::vector<Mat> RGB;
split(Img_mat, RGB);
int data = (RGB[0]).at<int>(i,j)); /*Where i, j are inside the bounds of the matrix size .. i have checked this*/
}
The problem is I am getting negative values and very large values in the data variable. I think I have made some mistake somewhere. Can you please point it out.
I have been reading the documentation (I have not finished it fully.. it is quite large. ) But from what I have read, this should work. But it isnt. What is going wrong here?
Img_mat is a 3 channeled image. Each channel consists of pixel values uchar in data type.
So with split(Img_mat, BGR) the Img_mat is split into 3 planes of blue, green and red which are collectively stored in a vector BGR. So BGR[0] is the first (blue) plane with uchar data type pixels...hence it will be
int dataB = (int)BGR[0].at<uchar>(i,j);
int dataG = (int)BGR[1].at<uchar>(i,j);
so on...
You have to specify the correct type for cv::Mat::at(i,j). You are accessing the pixel as int, while it should be a vector of uchar. Your code should look something like this:
IplImage* img = 0;
img = cvLoadImage("nature.jpg");
if(img != 0)
{
Mat Img_mat(img);
std::vector<Mat> BGR;
split(Img_mat, BGR);
Vec3b data = BGR[0].at<Vec3b>(i,j);
// data[0] -> blue
// data[1] -> green
// data[2] -> red
}
Why are you loading an IplImage first? You are mixing the C and C++ interfaces.
Loading a cv::Mat with imread directly would be more straight-forward.
This way you can also specify the type and use the according type in your at call.