I'm a newbie in OpenCV. I'm learning the Segmentation by Watershed algorithm and I have a problem.
I have to convert the image color to grayscale for using Watershed.
When I use the BGR color space, no problem but with HSV, I'm not sure that the code below is correct.
Mat im = imread("./Image/118035.jpg", CV_LOAD_IMAGE_COLOR);
Mat imHSV;
cvtColor(im, imHSV, CV_BGR2HSV);
imshow("HSV", imHSV);
cvtColor(imHSV, imHSV, CV_BGR2GRAY);
imshow("HSV to gray", imHSV);
imshow("BGR", im);
cvtColor(im, im, CV_BGR2GRAY);
imshow("BGR to gray", im);
I think, after converting from BGR to HSV, Hue = Blue, Saturation = Green, Value = Red and I can use the operator BGR2GRAY for convert from HSV to grayscale.
The 2 output images are different. Can I convert HSV to grayscale like that?
//Is it similaire with color space LAB?
The conversion from HSV to gray is not necessary: you already have it. You can just select the V channel as your grayscale image by splitting the HSV image in 3 and taking the 3rd channel:
Mat im = imread("C:/local/opencv248/sources/samples/c/lena.jpg", CV_LOAD_IMAGE_COLOR);
Mat imHSV;
cvtColor(im, imHSV, CV_BGR2HSV);
imshow("HSV", imHSV);
//cvtColor(imHSV, imHSV, CV_BGR2GRAY);
Mat hsv_channels[3];
cv::split( imHSV, hsv_channels );
imshow("HSV to gray", hsv_channels[2]);
imshow("BGR", im);
cvtColor(im, im, CV_BGR2GRAY);
imshow("BGR to gray", im);
waitKey();
hsv1 = cv2.cvtColor(frame1, cv2.COLOR_BGR2HSV)
h, s, v1 = cv2.split(hsv1)
cv2.imshow("gray-image",v1)
in HSV color-space, V channel is defined as max(R, G, B) but in gray-scale, value is defined by mean(R, G, B).
in RGB2HSV conversion, we use these formulas for S and V channel:
V = max(R, G, B)
S = (max(R, G, B) - min(R, G, B)) / max(R, G, B)
so if S is zero, max(R, G, B) equals to min(R, G, B) and they are equal to mean(R, G, B). so if this criteria holds, V channel is equal to gray-scale value. other wise, they are different.
one way is to convert image to RGB and then convert it to GRAY. but if you look for a more straight way, you can use picture below:
HSV2RGB converion
and hence gray value is mean(R, G, B) you can calculate it as:
gray = m + (c + x) / 3
where you can compute m,c and x from formula in image.
Unfortunately I am unable to comment because of insufficient earned reputation.
Taking the 3rd channel alone will may give grey values you do not expect, as increasingly fully saturated colors,as an extreme example RGB 0,0,255 will appear as pure white once converted to grey scale by taking the value hsv channel.
This could certainly affect watershed (dependent on image content) as saturated red, greens and blues would not be differentiated in the V channel.
The obvious, converting to BGR then Grayscale could be a better option.
Related
I am a beginner in opencv. I am using opencv v2.1. I have converted an RGB image to HSV image. Now I want to obtain single channels Hue, Value and Saturation separately. What should I do? I have seen similar questions here but No-one answered that. Kindly help.
You can access the same way you were accessing for RGB image where 1st channel will be for H, 2nd channel for S and 3rd channel for V.
If you are using OpenCV 2.1, you must be using IplImage then, right?
like if your HSV image is
IplImage *src.
IplImage* h = cvCreateImage( cvGetSize(src), IPL_DEPTH_8U, 1 );
IplImage* s = cvCreateImage( cvGetSize(src), IPL_DEPTH_8U, 1 );
IplImage* v = cvCreateImage( cvGetSize(src), IPL_DEPTH_8U, 1 );
// Split image onto the color planes
cvSplit( src, h, s, v, NULL );
cvSplit function splits a multichannel array into several single channels. Correct me if I am wrong.
I would recommend using OpenCV 2.4. It has structs like cvMat which are very easy to handle just like 2D arrays.
EDIT:
If you are using Mat then you can separate the channels out easily.
Let's say your hsv mat is Mat img_hsv.
Then :
vector<Mat> hsv_planes;
split( img_hsv, hsv_planes );
hsv_planes[0] // H channel
hsv_planes[1] // S channel
hsv_planes[2] // V channel
See if you can work out with this.
Solution for Python:
import cv2
from matplotlib import pyplot as plt
# Read image in BGR
img_path = "test.jpg"
img = cv2.imread(img_path)
# Convert BGR to HSV and parse HSV
hsv_img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h, s, v = hsv_img[:, :, 0], hsv_img[:, :, 1], hsv_img[:, :, 2]
# Plot result images
plt.imshow("Original", cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.imshow("HSV", hsv_img)
plt.imshow("H", h)
plt.imshow("S", s)
plt.imshow("V", v)
plt.show()
Here it is for a Mat:
cv::Mat hsv_image = ...;
std::vector<cv::Mat> hsv_channels;
cv::split(hsv_image, hsv_channels);
cv::Mat h_image = hsv_channels[0];
cv::Mat s_image = hsv_channels[1];
cv::Mat v_image = hsv_channels[2];
Given a point on an image, I'd like to floodfill all points connected to that point - but onto a new image. A naive way to do this would be to floodfill the original image to a special magic colour value. Then, visit each pixel, and copy all pixels with this magic colour value to the new image. There must be a better way!
Why don't you use the second variant of cv::floodFill to create a mask?
int floodFill(InputOutputArray image, InputOutputArray mask, Point
seedPoint, Scalar newVal, Rect* rect=0, Scalar loDiff=Scalar(), Scalar
upDiff=Scalar(), int flags=4 )
Original image
cv::Mat img = cv::imread("squares.png");
First variant
cv::floodFill(img, cv::Point(150,150), cv::Scalar(255.0, 255.0, 255.0));
This is the img
Second variant
cv::Mat mask = cv::Mat::zeros(img.rows + 2, img.cols + 2, CV_8U);
cv::floodFill(img, mask, cv::Point(150,150), 255, 0, cv::Scalar(), cv::Scalar(),
4 + (255 << 8) + cv::FLOODFILL_MASK_ONLY);
This is the mask. img doesn't change
If you go with this though, note that:
Since the mask is larger than the filled image, a pixel (x,y) in image corresponds to the pixel (x+1, y+1) in the mask.
i'm using openNI for some project with kinect sensor. i'd like to color the users pixels given with the depth map. now i have pixels that goes from white to black, but i want from red to black. i've tried with alpha blending, but my result is only that i have pixels from pink to black because i add (with addWeight) red+white = pink.
this is my actual code:
layers = device.getDepth().clone();
cvtColor(layers, layers, CV_GRAY2BGR);
Mat red = Mat(240,320, CV_8UC3, Scalar(255,0,0));
Mat red_body; // = Mat::zeros(240,320, CV_8UC3);
red.copyTo(red_body, device.getUserMask());
addWeighted(red_body, 0.8, layers, 0.5, 0.0, layers);
where device.getDepth() returns a cv::Mat with depth map and device.getUserMask() returns a cv::Mat with user pixels (only white pixels)
some advice?
EDIT:
one more thing:
thanks to sammy answer i've done it. but actually i don't have values exactly from 0 to 255, but from (for example) 123-220.
i'm going to find minimum and maximum via a simple for loop (are there better way?), and how can i map my values from min-max to 0-255 ?
First, OpenCV's default color format is BGR not RGB. So, your code for creating the red image should be
Mat red = Mat(240,320, CV_8UC3, Scalar(0,0,255));
For red to black color map, you can use element wise multiplication instead of alpha blending
Mat out = red_body.mul(layers, 1.0/255);
You can find the min and max values of a matrix M using
double minVal, maxVal;
minMaxLoc(M, &minVal, &maxVal, 0, 0);
You can then subtract the minValue and scale with a factor
double factor = 255.0/(maxVal - minVal);
M = factor*(M -minValue)
Kinda clumsy and slow, but maybe split layers, copy red_body (make it a one channel Mat, not 3) to the red channel, merge them back into layers?
Get the same effect, but much faster (in place) with reshape:
layers = device.getDepth().clone();
cvtColor(layers, layers, CV_GRAY2BGR);
Mat red = Mat(240,320, CV_8UC1, Scalar(255)); // One channel
Mat red_body;
red.copyTo(red_body, device.getUserMask());
Mat flatLayer = layers.reshape(1,240*320); // presumed dimensions of layer
red_body.reshape(0,240*320).copyTo(flatLayer.col(0));
// layers now has the red from red_body
Since each pixel memory contains 8 bit for each component Blue,Green and Red. So how can I separate these components from Image or Image Matrix. As
int Blue = f(Image(X,y));// (x,y) = Coordinate of a pixel of Image
similarly, for red and green.
So what should be function f and 2D matrix Image;
Thanks in advance
First off, you must go through the basics of OpenCV and turn your attention towards other parts of image processing. What you ask for is pretty basic and assuming you will be using OpenCV 2.1 and higher,
cv::Mat img = Read the image off the disk or do something to fill the image.
To access the RGB values
img.at<cv::Vec3b>(x,y);
But would give the values in reverse that is BGR. So make sure you note this.
Basically a cv::Vec3b type that is accessed.
img.at<cv::Vec3b>(x,y)[0];//B
img.at<cv::Vec3b>(x,y)[1];//G
img.at<cv::Vec3b>(x,y)[2];//R
or
Vec3f pixel = img.at<Vec3f>(x, y);
int b = pixel[0];
int g = pixel[1];
int r = pixel[2];
Now onto splitting the image into RGB channels you can use the following
Now down to primitive C style of OpenCV (There C and C++ style supported)
You can use the cvSplit function
IplImage* rgb = cvLoatImage("C://MyImage.bmp");
//now create three single channel images for the channel separation
IplImage* r = cvCreateImage( cvGetSize(rgb), rgb->depth,1 );
IplImage* g = cvCreateImage( cvGetSize(rgb), rgb->depth,1 );
IplImage* b = cvCreateImage( cvGetSize(rgb), rgb->depth,1 );
cvSplit(rgb,b,g,r,NULL);
OpenCV 2 CookBook Is one of the best books on OpenCV. Will help you alot.
In order to add constant value to each pixel's saturation value, I do this in double loops. I wonder if there is any simpler and faster command achieving this.
Mat img(200, 300, CV_8UC1);
Mat saturated;
double saturation = 10;
double scale = 1;
// what it does here is dst = (uchar) ((double)src*scale+saturation);
img.convertTo(saturated, CV_8UC1, scale, saturation);
EDIT
If by saturation, you mean the S channel in a HSV image, you need to separe your image in three channels with split(), apply the saturation correction to the S channel, and then put them together with merge().
For the experiments I attempted, the alternative method of splitting hsv values, adjusting the individual channels and then doing a merge gave a better performance. Below is what worked for me many times faster as compared to looping through pixels:
(h, s, v) = cv2.split(imghsv)
s = s*satadj
s = np.clip(s,0,255)
imghsv = cv2.merge([h,s,v])
Note that I had converted the values to float32 during BGR2HSV transformation to avoid negative values during saturation transformation to due uint8 (default) overflow:
imghsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV).astype("float32")
And converted it back to default uint8 after my saturation adjustment:
imgrgb = cv2.cvtColor(imghsv.astype("uint8"), cv2.COLOR_HSV2BGR)