How to add Scalar to Mat by mask? - opencv

How to add Scalar to Mat only where mask>0?
this code don't work as expected, area where mask>0 is img.value+scalar but where mask=0 us 0, but I expected img.value.
add(image,Scalar(0,0,80),dst, mask);
code that work as I expect is
Mat dst;
image.copyTo(dst,mask);
add(dst,Scalar(0,0,80),dst, mask);
dst.copyTo(image,mask);
dst= image;
but it's not very clear, is there any simpler variant?

Since your dst image is uninitialized, the values outside the mask are set to 0.
You get the expected behavior if you use as destination an initialized matrix. It can be your source matrix;
#include <opencv2\opencv.hpp>
using namespace cv;
int main()
{
// Initial image
Mat3b image(10, 10, Vec3b(0,2,0));
// Mask
Mat1b mask(10,10, uchar(0));
rectangle(mask, Rect(0,0,3,4), Scalar(255), CV_FILLED);
add(image, Scalar(0, 0, 3), image, mask);
return 0;
}
Or if you need the source matrix to remain unchanged, you can simply clone the source image to the destination image before the add, like:
#include <opencv2\opencv.hpp>
using namespace cv;
int main()
{
// Initial image
Mat3b image(10, 10, Vec3b(0,2,0));
// Mask
Mat1b mask(10,10, uchar(0));
rectangle(mask, Rect(0,0,3,4), Scalar(255), CV_FILLED);
Mat3b dst = image.clone();
add(image, Scalar(0, 0, 3), dst, mask);
return 0;
}

Related

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

Why doesn't OpenCV findContour method always find closed outer contour?

I have two images from the same camera position. The difference between them is that one was taken with orthographic and the other was taken with perspective projection.
Here is the two image:
When I run the findContour OpenCV method on them the result is the follwing:
Why OpenCV doesn't find a closed outer contour curve for the perspective one?
I tried both CV_RETR_TREE and CV_RETR_EXTERNAL flags with the combination of CV_CHAIN_APPROX_SIMPLE and CV_CHAIN_APPROX_NONE flags.
Here is the documentation and sample code (which I am using) for the findContour method.
Actually I can't reproduce your problem. Try with this code:
#include <opencv2\opencv.hpp>
#include <vector>
using namespace std;
using namespace cv;
int main()
{
RNG rng(1234);
Mat3b img = imread("path_to_image");
Mat1b gray;
cvtColor(img, gray, COLOR_BGR2GRAY);
Mat1b bw = ~gray;
vector<vector<Point>> contours;
findContours(bw, contours, RETR_LIST, CHAIN_APPROX_SIMPLE);
for (int i = 0; i < contours.size(); ++i)
{
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
drawContours(img, contours, i, color, 2);
}
imshow("Result", img);
waitKey();
return 0;
}
Result:

OpenCV displaying 2 images adjacently in the same window

I am trying to display 2 images horizontally adjacent to each other in the same window using OpenCV.
I have tried using adjustROI function for this.Image 1 has 1088 pixels width and 2208 pixels height while Image 2 has 1280 pixels width and 2208 pixels height.Please suggest what could be wrong in the code below.All I am getting is an image of size Image2 with content from Image2 as well.
Mat img_matches=Mat(2208,2368,imgorig.type());//set size as combination of img1 and img2
img_matches.adjustROI(0,0,0,-1280);
imgorig.copyTo(img_matches);
img_matches.adjustROI(0,0,1088,1280);
imgorig2.copyTo(img_matches);
EDIT: Here's how I'd do what you want to do:
Mat left(img_matches, Rect(0, 0, 1088, 2208)); // Copy constructor
imgorig.copyTo(left);
Mat right(img_matches, Rect(1088, 0, 1280, 2208)); // Copy constructor
imgorig2.copyTo(right);
The copy constructors create a copy of the Mat header that points to the ROI defined by each of the Rects.
Full code:
#include <cv.h>
#include <highgui.h>
using namespace cv;
int
main(int argc, char **argv)
{
Mat im1 = imread(argv[1]);
Mat im2 = imread(argv[2]);
Size sz1 = im1.size();
Size sz2 = im2.size();
Mat im3(sz1.height, sz1.width+sz2.width, CV_8UC3);
Mat left(im3, Rect(0, 0, sz1.width, sz1.height));
im1.copyTo(left);
Mat right(im3, Rect(sz1.width, 0, sz2.width, sz2.height));
im2.copyTo(right);
imshow("im3", im3);
waitKey(0);
return 0;
}
Compiles with:
g++ foo.cpp -o foo.out `pkg-config --cflags --libs opencv`
EDIT2:
Here's how it looks with adjustROI:
#include <cv.h>
#include <highgui.h>
using namespace cv;
int
main(int argc, char **argv)
{
Mat im1 = imread(argv[1]);
Mat im2 = imread(argv[2]);
Size sz1 = im1.size();
Size sz2 = im2.size();
Mat im3(sz1.height, sz1.width+sz2.width, CV_8UC3);
// Move right boundary to the left.
im3.adjustROI(0, 0, 0, -sz2.width);
im1.copyTo(im3);
// Move the left boundary to the right, right boundary to the right.
im3.adjustROI(0, 0, -sz1.width, sz2.width);
im2.copyTo(im3);
// restore original ROI.
im3.adjustROI(0, 0, sz1.width, 0);
imshow("im3", im3);
waitKey(0);
return 0;
}
You have to keep track of what the current ROI is, and the syntax for moving the ROI around can be a little un-intuitive. The result is the same as the first block of code.
As the height (rows of Mat) of the images are same, function hconcat maybe used to horizontally concatenate two images (Mat) and thus can be used to display them side-by-side in the same window. OpenCV doc.
It works with both grayscale and color images. The number of color channels in the source matrices must be same.
Mat im1, im2; // source images im1 and im2
Mat newImage;
hconcat(im1, im2, newImage); // <---- place image side by side
imshow("Display side by side", newImage);
waitKey(0);
For the sake of completeness, vconcat can be similarly used for vertical concatenation.
Here's a solution inspired in #misha's answer.
#include <cv.h>
#include <highgui.h>
using namespace cv;
int
main(int argc, char **argv)
{
Mat im1 = imread(argv[1]);
Mat im2 = imread(argv[2]);
Size sz1 = im1.size();
Size sz2 = im2.size();
Mat im3(sz1.height, sz1.width+sz2.width, CV_8UC3);
im1.copyTo(im3(Rect(0, 0, sz1.width, sz1.height)));
im2.copyTo(im3(Rect(sz1.width, 0, sz2.width, sz2.height)));
imshow("im3", im3);
waitKey(0);
return 0;
}
Instead of using the copy constructor, this solution uses Mat::operator()(const Rect& roi). While both solutions are O(1), this solution seems cleaner.

OpenCV distance transform outputting an image that looks exactly like the input image

I am doing some detection work using OpenCV, and I need to use the distance transform. Except the distance transform function in opencv gives me an image that is exactly the same as the image I use as source. Anyone know what I am doing wrong? Here is the portion of my code:
cvSetData(depthImage, m_rgbWk, depthImage->widthStep);
//gotten openCV image in "depthImage"
IplImage *single_channel_depthImage = cvCreateImage(cvSize(320, 240), 8, 1);
cvSplit(depthImage, single_channel_depthImage, NULL, NULL, NULL);
//smoothing
IplImage *smoothed_image = cvCreateImage(cvSize(320, 240), 8, 1);
cvSmooth(single_channel_depthImage, smoothed_image, CV_MEDIAN, 9, 9, 0, 0);
//do canny edge detector
IplImage *edges_image = cvCreateImage(cvSize(320, 240), 8, 1);
cvCanny(smoothed_image, edges_image, 100, 200);
//invert values
IplImage *inverted_edges_image = cvCreateImage(cvSize(320, 240), 8, 1);
cvNot(edges_image, inverted_edges_image);
//calculate the distance transform
IplImage *distance_image = cvCreateImage(cvSize(320, 240), IPL_DEPTH_32F, 1);
cvZero(distance_image);
cvDistTransform(inverted_edges_image, distance_image, CV_DIST_L2, CV_DIST_MASK_PRECISE, NULL, NULL);
In a nutshell, I grad the image from the kinect, turn it into a one channel image, smooth it, run the canny edge detector, invert the values, and then I do the distance transform. But the transformed image looks exactly the same as the input image. What's wrong?
Thanks!
I believe the key here is that they look the same. Here is a small program I wrote to show the difference:
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
Mat before = imread("qrcode.png", 0);
Mat dist;
distanceTransform(before, dist, CV_DIST_L2, 3);
imshow("before", before);
imshow("non-normalized", dist);
normalize(dist, dist, 0.0, 1.0, NORM_MINMAX);
imshow("normalized", dist);
waitKey();
return 0;
}
In the non-normalized image, you see this:
which doesn't really look like it changed anything, but the distance steps are very small compared to the overall range of values [0, 255] (due to imshow converting the image from 32-bit float to 8-bits for display), we can't see the differences, so let's normalize it...
Now we get this:
The values themselves should be correct, but when displayed you will need to normalize the image to see the difference.
EDIT :
Here is a small 10x10 sample from the upper-left corner of the dist matrix show that the values are in fact different:
[10.954346, 10.540054, 10.125763, 9.7114716, 9.2971802, 8.8828888, 8.4685974, 8.054306, 7.6400146, 7.6400146;
10.540054, 9.5850525, 9.1707611, 8.7564697, 8.3421783, 7.927887, 7.5135956, 7.0993042, 6.6850128, 6.6850128;
10.125763, 9.1707611, 8.2157593, 7.8014679, 7.3871765, 6.9728851, 6.5585938, 6.1443024, 5.730011, 5.730011;
9.7114716, 8.7564697, 7.8014679, 6.8464661, 6.4321747, 6.0178833, 5.6035919, 5.1893005, 4.7750092, 4.7750092;
9.2971802, 8.3421783, 7.3871765, 6.4321747, 5.4771729, 5.0628815, 4.6485901, 4.2342987, 3.8200073, 3.8200073;
8.8828888, 7.927887, 6.9728851, 6.0178833, 5.0628815, 4.1078796, 3.6935883, 3.2792969, 2.8650055, 2.8650055;
8.4685974, 7.5135956, 6.5585938, 5.6035919, 4.6485901, 3.6935883, 2.7385864, 2.324295, 1.9100037, 1.9100037;
8.054306, 7.0993042, 6.1443024, 5.1893005, 4.2342987, 3.2792969, 2.324295, 1.3692932, 0.95500183, 0.95500183;
7.6400146, 6.6850128, 5.730011, 4.7750092, 3.8200073, 2.8650055, 1.9100037, 0.95500183, 0, 0;
7.6400146, 6.6850128, 5.730011, 4.7750092, 3.8200073, 2.8650055, 1.9100037, 0.95500183, 0, 0]
I just figured this one out.
The OpenCV distanceTransform
Calculates the distance to the closest zero pixel for each pixel of
the source image.
and so it expects your edges image to be negative.
All you need to do is to negate your edges image:
edges = 255 - edges;
You can print this values using this code before normalize function:
for(int x=0; x<10;x++)
{
cout<<endl;
for(int y=0; y<10;y++)
cout<<std::setw(10)<<dist.at<float>(x, y);
}
Mat formats
Input: CV_8U
Dist: CV_32F
Normalized: CV_8U
normalize(Mat_dist, Mat_norm, 0, 255, NORM_MINMAX, CV_8U);
If you want to visualize the result, you need to scale the normalization to 0 ... 255 and not to 0 ... 1 or everything will seem black. Using imshow(); on a scaled to 0 ... 1 image will work but may cause problmes in the next processing steps. Al least it did in my case.

Resources