In openCV, how to replace an RGB ROI in image - opencv

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

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

Estimate white background

I have image with white uneven background (due to lighting). I'm trying to estimate background color and transform image into image with true white background. For this I estimated white color for each 15x15 pixels block based on its luminosity. So I've got the following map (on the right):
Now I want to interpolate color so it will be more smooth transition from 15x15 block to neighboring block, plus I want it to eliminate outliers (pink dots on left hand side). Could anyone suggest good technique/algorithm for this? (Ideally within OpenCV library, but not necessary)
Starting from this image:
You could find the text on the whiteboard as the parts of your images that have a high gradient, and apply a little dilation to deal with thick parts of the text. You'll get a mask that separates background from foreground pretty well:
Background:
Foreground:
You can then apply inpainting using the computed mask on the original image (you need OpenCV contrib module photo):
Just to show that this works independently of the text color, I tried on a different image:
Resulting in:
Code:
#include <opencv2/opencv.hpp>
#include <opencv2/photo.hpp>
using namespace cv;
void findText(const Mat3b& src, Mat1b& mask)
{
// Convert to grayscale
Mat1b gray;
cvtColor(src, gray, COLOR_BGR2GRAY);
// Compute gradient magnitude
Mat1f dx, dy, mag;
Sobel(gray, dx, CV_32F, 1, 0);
Sobel(gray, dy, CV_32F, 0, 1);
magnitude(dx, dy, mag);
// Remove low magnitude, keep only text
mask = mag > 10;
// Apply a dilation to deal with thick text
Mat1b K = getStructuringElement(MORPH_ELLIPSE, Size(3, 3));
dilate(mask, mask, K);
}
int main(int argc, const char * argv[])
{
Mat3b img = imread("path_to_image");
// Segment white
Mat1b mask;
findText(img, mask);
// Show intermediate images
Mat3b background = img.clone();
background.setTo(0, mask);
Mat3b foreground = img.clone();
foreground.setTo(0, ~mask);
// Apply inpainting
Mat3b inpainted;
inpaint(img, mask, inpainted, 21, CV_INPAINT_TELEA);
imshow("Original", img);
imshow("Foreground", foreground);
imshow("Background", background);
imshow("Inpainted", inpainted);
waitKey();
return 0;
}

How can I do image processing operations only in ROI part of original image directly?

Is that possible by using OpenCV to do some image processing operations only in ROI part of original image?
I search some articles on Internet. Most of codes look like this:
int main(int argc, char** argv) {
cv::Mat image;
image = cv::imread(argv[1], CV_LOAD_IMAGE_COLOR);
cv::Rect roi( 100, 100,200, 200);
//do some operations on roi
cv::waitKey(0);
return 0;
}
Actually, it created a new image called roi, and then do some operations in new created image. I want to do operations in original image directly. For example, I want to do gaussian blur, only blur the range of roi part in original image and do not blur other part of this image.
Because new created image roi has different informations with its information in original image. (like coordinates) I want to keep those information.
Is that possible to do this in OpenCV? If so, how to do it?
You can get the sub-image using one either a Rect or two Range (see OpenCV doc).
Mat3b img = imread("path_to_image");
img:
Rect r(100,100,200,200);
Mat3b roi3b(img(r));
As long as you don't change image type you can work on roi3b. All changes will be reflected in the original image img:
GaussianBlur(roi3b, roi3b, Size(), 10);
img after blur:
If you change type (e.g. from CV_8UC3 to CV_8UC1), you need to work on a deep copy, since a Mat can't have mixed types.
Mat1b roiGray;
cvtColor(roi3b, roiGray, COLOR_BGR2GRAY);
threshold(roiGray, roiGray, 200, 255, THRESH_BINARY);
You can always copy the results on the original image, taking care to correct the type:
Mat3b roiGray3b;
cvtColor(roiGray, roiGray3b, COLOR_GRAY2BGR);
roiGray3b.copyTo(roi3b);
img after threshold:
Full code for reference:
#include <opencv2\opencv.hpp>
using namespace cv;
int main(void)
{
Mat3b img = imread("path_to_image");
imshow("Original", img);
waitKey();
Rect r(100,100,200,200);
Mat3b roi3b(img(r));
GaussianBlur(roi3b, roi3b, Size(), 10);
imshow("After Blur", img);
waitKey();
Mat1b roiGray;
cvtColor(roi3b, roiGray, COLOR_BGR2GRAY);
threshold(roiGray, roiGray, 200, 255, THRESH_BINARY);
Mat3b roiGray3b;
cvtColor(roiGray, roiGray3b, COLOR_GRAY2BGR);
roiGray3b.copyTo(roi3b);
imshow("After Threshold", img);
waitKey();
return 0;
}
To blur the required region follow the following steps:
cv::Rect roi(x, y, w, h);
cv::GaussianBlur(image(roi), image(roi), Size(0, 0), 4);
Follow this link for more information http://docs.opencv.org/modules/core/doc/basic_structures.html#id6
Mat::operator()(Range rowRange, Range colRange)
Mat::operator()(const Rect& roi)
I have burred the region of interest and segmented the blurred region, you can perform image processing operation on the blurred region in an original image or you can perform on segmented region.
int main() {
Mat image;
image=imread("Light.jpg",1);
// image = cv::imread(argv[1], CV_LOAD_IMAGE_COLOR);
Rect roi( 100, 100,200, 200);
Mat blur;
GaussianBlur(image(roi), blur, Size(0, 0), 4);
imshow("blurred region",blur);
//do some operations on roi
imshow("aaaa",image);
waitKey(0);
return 0;
}

thresholding RGB image in OpenCV

I have a color image that I want to a threshold in OpenCV. What I would like is that if any of the RGB channels in under a certain value, to set the value in all the channels to zero (i.e. black).
So, I use the opencv threshold function as:
cv::Mat frame, thresholded
// read frame somewhere, it is a BGR image.
cv::threshold(frame, thresholded, 5, 255, cv::THRESH_BINARY);
So, what I thought this would do is that if any of the channels is less than 5, I thought it would set them to zero. However, it does not seem to work that way. For example, I see only the green channel come through for some of these regions, indicating not all channels are set to 0.
Is there a way to achieve this using OpenCV in a fast way?
It's possible to threshold a colored image using the function cv::inRange.
void inRange(InputArray src, InputArray lowerb, InputArray upperb, OutputArray dst)
For example, you can allow only values between (0, 125, 0) and (255, 200, 255), or any values for individual channels:
cv::Mat image = cv::imread("bird.jpg");
if (image.empty())
{
std::cout << "> This image is empty" << std::endl;
return 1;
}
cv::Mat output;
cv::inRange(image, cv::Scalar(0, 125, 0), cv::Scalar(255, 200, 255), output);
cv::imshow("output", output);
In short, you have to slipt your image in three images containing the three channels, threeshold them independantly and then merge them again.
Mat frame,thresholded;
vector<Mat> splited_frame;
//Read your frame
split(frame, splited_frame);
for (size_t i = 0; i < splited_frame.size(); i++)
threshold(splited_frame[i], splited_frame[i], 5, 255, cv::THRESH_BINARY);
merge(splited_frame,thresholded);
This code should do it.
Sorry, I read to fast.
Then, you should modify the code slightly after the for
thresholded = splited_frame[0].clone();
for(size_t i = 1; i < splited_frame.size(); i++) thresholded &= splited_frame[i];
frame &= thresholded;
You create a mask from the three thresholded images, then apply this mask to your input image.

OpenCV - Floodfill onto new Mat

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.

Resources