OpenCV - Floodfill onto new Mat - image-processing

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.

Related

openCV inRange masking

I'm using Opencv 3.0 to get only the colored objects in an image. Therefore i create and use a mask.
#include <opencv2\opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
namedWindow("Display", CV_WINDOW_AUTOSIZE);
namedWindow("Orignial", CV_WINDOW_AUTOSIZE);
namedWindow("Mask", CV_WINDOW_AUTOSIZE);
// First load your image
Mat mSrc = imread("IMG_0005_AUSZUG2.png", CV_LOAD_IMAGE_COLOR);
Mat mGray = Mat::zeros(mSrc.size(), mSrc.type());
cvtColor(mSrc, mGray, CV_BGR2GRAY);
// define your mask
Mat mask = Mat::zeros(mSrc.size(), mSrc.type());
// define destination image
Mat dstImg = Mat::zeros(mSrc.size(), mSrc.type());
//finding mask
inRange(mSrc, Scalar(90, 90, 90), Scalar(180, 180, 180), mask);
// combination of mask and Source image
dilate(mask, mask, Mat(), Point(-1, -1));
bitwise_not(mask, mask);
//cvtColor(mask, mask, CV_GRAY2BGR);
mSrc.copyTo(dstImg, mask);
//bitwise_and(mSrc, mSrc, dstImg, mask);
imshow("Mask", mask);
imshow("Orignial", mSrc);
imshow("Display", dstImg);
waitKey(0);
return 0;
}
As you can see the result image is not the intended one. Only the colored objects should stay, because they have a white background in the mask, but it seems that the result image is a combination of source and mask.
Anybody know how to fix this ?
Source:
Mask:
Result:
To understand your requirement- you have an image with some coloured objects in it, in a white background, and you essentially want an result image containing the same coloured objects in a black background instead.
If that's the case, inRange will not help because you've essentially kept the threshold between grey values 90 and 180, so your code will discard dark objects as well.
To ensure that you obtain a mask that is black only in the white background regions, I would suggest using the threshold function instead, as shown:
//finding mask
//inRange(mSrc, Scalar(90, 90, 90), Scalar(180, 180, 180), mask);
threshold(mGray, mask, 220, 255, THRESH_BINARY_INV);
This function will ensure that any pixel value in your greyscale image above 220 will be set to 0 in the binary mask.
To superimpose the binary mask over the source image, you should use the subtract method, as shown:
cvtColor(mask,mask,CV_GRAY2BGR);//change thresh to a 3 channel image
Mat mResult = Mat::zeros(mSrc.size(), mSrc.type());
subtract(mask,mSrc,mResult);
subtract(mask,mResult,mResult);

background detection having font with same color as background

I have image as following (so, this is white figure on red background. This figure have two thin red lines inside it)
and I want to receive following image (remove red background but not two red lines inside figure)
I was trying convexHull from OpenCV, but, obviously that approach works only on convex figures. My feeling that convolution may help here, but have no real idea yet.
Dilate and Erode should work for your example:
Mat image = imread("image1.jpg");
int erosion_size = 5;
int dilation_size = 6;
int threshold_value = 200;
Mat mask;
cvtColor( image, mask, CV_BGR2GRAY );
//BINARY THRESHOLDING
threshold( mask, mask, threshold_value, 255, 0);
Mat erosion_element = getStructuringElement(MORPH_RECT, Size( 2*erosion_size + 1, 2*erosion_size+1 ), Point( erosion_size, erosion_size ) );
Mat dilation_element = getStructuringElement(MORPH_RECT, Size( 2*dilation_size + 1, 2*dilation_size+1 ), Point( dilation_size, dilation_size ) );
dilate(mask, mask, erosion_element);
erode(mask, mask, dilation_element);
Mat target;
image.copyTo(target, mask);
imshow("hello",target);
waitKey();
OutPut:
suggestions: :)
just floodfill()
convexHull does what the name says, but has a companion, convexitydefects
he-he, it looks like convolution with circle having diameter slightly bigger (8 pixels, for example) than line thickness works!
so, algorithm will looks as following:
convolve with circle having diameter slightly bigger than line
thickness
normalize convolution, you are interested in values
greater than 0.95-0.97
for each point on convolution function
(with values greater than 0.95-0.97) you should zero all
neighborhoods which are in range R=diameter/2

OpenCV floodfill with mask

The documentation for OpenCV's floodfill function states:
The function uses and updates the mask, so you take responsibility of
initializing the mask content. Flood-filling cannot go across non-zero
pixels in the mask. For example, an edge detector output can be used
as a mask to stop filling at edges. It is possible to use the same
mask in multiple calls to the function to make sure the filled area
does not overlap.
How does the function update the mask? Does it set all the pixels within the floodfill to some non-zero value?
All zero-valued pixels in the same connected component as the seed point of the mask are replaced by the value you specify. This value must be added to the flags parameter, left-shifted by 8 bits:
uchar fillValue = 128;
cv::floodFill(img, mask, seed, cv::Scalar(255) ,0, cv::Scalar(), cv::Scalar(), 4 | cv::FLOODFILL_MASK_ONLY | (fillValue << 8));
A simple, but perhaps enlightening example follows. Creating an image like so:
//Create simple input image
cv::Point seed(4,4);
cv::Mat img = cv::Mat::zeros(100,100,CV_8UC1);
cv::circle(img, seed, 20, cv::Scalar(128),3);
Results in this image:
Then, creating a mask and flood-filling it:
//Create a mask from edges in the original image
cv::Mat mask;
cv::Canny(img, mask, 100, 200);
cv::copyMakeBorder(mask, mask, 1, 1, 1, 1, cv::BORDER_REPLICATE);
//Fill mask with value 128
uchar fillValue = 128;
cv::floodFill(img, mask, seed, cv::Scalar(255) ,0, cv::Scalar(), cv::Scalar(), 4 | cv::FLOODFILL_MASK_ONLY | (fillValue << 8));
Gives this result:
The white pixels in the mask are the result of edge detection, while the grey pixels are the result of the flood-fill.
UPDATE:
In response to the comment, flag value 4 specifies the pixel neighborhood with which to compare the color value difference. From the documentation:
Lower bits contain a connectivity value, 4 (default) or 8, used within the function. Connectivity determines which neighbors of a pixel are considered.
When the cv::FLOODFILL_MASK_ONLY flag is not passed, both the image and the mask are updated, but the flood filling will stop at at any nonzero mask values.
And a python version
im = cv2.imread("seagull.jpg")
h,w,chn = im.shape
seed = (w/2,h/2)
mask = np.zeros((h+2,w+2),np.uint8)
floodflags = 4
floodflags |= cv2.FLOODFILL_MASK_ONLY
floodflags |= (255 << 8)
num,im,mask,rect = cv2.floodFill(im, mask, seed, (255,0,0), (10,)*3, (10,)*3, floodflags)
cv2.imwrite("seagull_flood.png", mask)
(Seagull image from Wikimedia: https://commons.wikimedia.org/wiki/Commons:Quality_images#/media/File:Gull_portrait_ca_usa.jpg)
Result:
Per Aurelius' answer, the mask needs to be zeroed.
Checking comment in the source, it stated that
Since this is both an input and output parameter, you must take responsibility
of initializing it. Flood-filling cannot go across non-zero pixels in the input mask.
The mask will impact the result so need to be zeroed before use:
cv::Mat mask;
mask = cv::Mat::zeros(img.rows + 2, img.cols + 2, CV_8UC1);

In openCV, how to replace an RGB ROI in image

I have an RGB large-image, and an RGB small-image.
What is the fastest way to replace a region in the larger image with the smaller one?
Can I define a multi-channel ROI and then use copyTo? Or must I split each image to channels, replace the ROI and then recombine them again to one?
Yes. A multi channel ROI and copyTo will work. Something like:
int main(int argc,char** argv[])
{
cv::Mat src = cv::imread("c:/src.jpg");
//create a canvas with 10 pixels extra in each dim. Set all pixels to yellow.
cv::Mat canvas(src.rows + 20, src.cols + 20, CV_8UC3, cv::Scalar(0, 255, 255));
//create an ROI that will map to the location we want to copy the image into
cv::Rect roi(10, 10, src.cols, src.rows);
//initialize the ROI in the canvas. canvasROI now points to the location we want to copy to.
cv::Mat canvasROI(canvas(roi));
//perform the copy.
src.copyTo(canvasROI);
cv::namedWindow("original", 256);
cv::namedWindow("canvas", 256);
cv::imshow("original", src);
cv::imshow("canvas", canvas);
cv::waitKey();
}

color a grayscale image with opencv

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

Resources