How get contours between objects with OpenCV Watershed? - c++

I use OpenCV Watershed with my image:
#include "opencv2/opencv.hpp"
#include <string>
using namespace cv;
using namespace std;
class WatershedSegmenter{
private:
cv::Mat markers;
public:
void setMarkers(cv::Mat& markerImage)
{
markerImage.convertTo(markers, CV_32S);
}
cv::Mat process(cv::Mat &image)
{
cv::watershed(image, markers);
markers.convertTo(markers,CV_8U);
return markers;
}
};
int main(int argc, char* argv[])
{
cv::Mat image = cv::imread("d:\\projekty\\OpenCV\\trainData\\base01.jpg"); //http://i.imgur.com/sEWFHfY.jpg
cv::Mat blank(image.size(),CV_8U,cv::Scalar(0xFF));
cv::Mat dest;
imshow("originalimage", image);
// Create markers image
cv::Mat markers(image.size(),CV_8U,cv::Scalar(-1));
//Rect(topleftcornerX, topleftcornerY, width, height);
//top rectangle
markers(Rect(0,0,image.cols, 5)) = Scalar::all(1);
//bottom rectangle
markers(Rect(0,image.rows-5,image.cols, 5)) = Scalar::all(1);
//left rectangle
markers(Rect(0,0,5,image.rows)) = Scalar::all(1);
//right rectangle
markers(Rect(image.cols-5,0,5,image.rows)) = Scalar::all(1);
//centre rectangle
int centreW = image.cols/4;
int centreH = image.rows/4;
markers(Rect((image.cols/2)-(centreW/2),(image.rows/2)-(centreH/2), centreW, centreH)) = Scalar::all(2);
markers.convertTo(markers,CV_BGR2GRAY);
imshow("markers", markers);
//Create watershed segmentation object
WatershedSegmenter segmenter;
segmenter.setMarkers(markers);
cv::Mat wshedMask = segmenter.process(image);
cv::Mat mask;
convertScaleAbs(wshedMask, mask, 1, 0);
double thresh = threshold(mask, mask, 1, 255, THRESH_BINARY);
bitwise_and(image, image, dest, mask);
dest.convertTo(dest,CV_8U);
imshow("final_result", dest);
cv::waitKey(0);
return 0;
}
But this give me only individual mask. I also tried to create markers as two points - the result was only one mask. Is it possible with OpenCV to separate cells (objects) with contours as is in example http://biodynamics.ucsd.edu/ir/ ?
If not, is it possible create as result mask with values: 1 for first object, 2 - for second, .. 99 for 99 ?

after performing
cv::watershed(image, markers);
the markers image will be -1 at the boundaries of the regions, and will be 1 in the region corresponding to the seed that was labelled 1, and will be 2 in the region corresponding to the seed that was labelled 2, and so on. So you can do something like this:
cv::Mat region1 = markers==1;

I use the following approach for extracting objects countours after watershed segmentation. Watershed output is one markers image containing a segment code of each pixel. I create a binary mask image for each single object segment from the markers image. That can be done in one iteration over all pixels of the markers image. For the core "for loop", see opencv example https://github.com/Itseez/opencv/blob/master/samples/cpp/watershed.cpp. I have all the objects masks stored in a vector <Mat>. Then I run findContours on every such mask -> contour of each object. See http://docs.opencv.org/doc/tutorials/imgproc/shapedescriptors/find_contours/find_contours.html. You just don't need to use the edge detector Canny as the mask images are already binary.

Related

How to count all non-zero pixels within each polygon area efficiently?

I want to calculate numbers of all white pixels within every polygon area efficiently.
Given some processes:
// some codes for reading gray image
// cv::Mat gray = cv::imread("gray.jpg");
// given polygons
// vector< vector<cv::Point> > polygons;
cv::Mat cropped;
cv::Mat mask = cv::Mat::zeros(gray.size(), CV_8UC1);
cv::fillPoly(mask, polygons, cv::Scalar(255));
cv::bitwise_and(gray, gray, cropped, mask);
cv::Mat binary;
cv::threshold(cropped, binary, 20, 255, CV_THRESH_BINARY);
So until now, we can get a image with multiple polygon areas(say we have 3 areas) which have white( with value 255) pixels. Then after some operations we expect to get a vector like:
// some efficient operations
// ...
vector<int> pixelNums;
The size of pixelNums should be same with polygons which is 3 here. And if we print them we may get some outputs like(the values are basically depended on the pre-processes):
index: 0; value: 120
index: 1; value: 1389
index: 2; value: 0
Here is my thought. Counting every pixels within every polygon area with help of cv::countNonZero, but I need to call it within a loop which I don't think it's a efficient way, isn't it?
vector<int> pixelNums;
for(auto polygon : polygons)
{
vector< vector<cv::Point> > temp_polygons;
temp_polygons.push_back(polygon);
cv::Mat cropped;
cv::Mat mask = cv::Mat::zeros(gray.size(), CV_8UC1);
cv::fillPoly(mask, temp_polygons, cv::Scalar(255));
cv::bitwise_and(gray, gray, cropped, mask);
cv::Mat binary;
cv::threshold(cropped, binary, 20, 255, CV_THRESH_BINARY);
pixelNums.push_back(cv::countNonZero(binary));
}
If you have some better ways, please kindly answer this post. Here I say better way is consuming as little time as you can just in cpu environment.
There are some minor improvements that can be done, but all of them combined should provide a decent speedup.
Compute the threshold only once
Make most operations on smaller images, using the bounding box of your polygon to get the region of interest
Avoid unneeded copies in the for loop, use const auto&
Example code:
#include <vector>
#include <opencv2/opencv.hpp>
int main()
{
// Your image
cv::Mat1b gray = cv::imread("path/to/image", cv::IMREAD_GRAYSCALE);
// Your polygons
std::vector<std::vector<cv::Point>> polygons
{
{ {15,120}, {45,200}, {160,160}, {140, 60} },
{ {10,10}, {15,30}, {50,25}, {40, 15} },
// etc...
};
// Compute the threshold just once
cv::Mat1b thresholded = gray > 20;
std::vector<int> pixelNums;
for (const auto& polygon : polygons)
{
// Get bbox of polygon
cv::Rect bbox = cv::boundingRect(polygon);
// Make a new (small) mask
cv::Mat1b mask(bbox.height, bbox.width, uchar(0));
cv::fillPoly(mask, std::vector<std::vector<cv::Point>>{polygon}, cv::Scalar(255), 8, 0, -bbox.tl());
// Get crop
cv::Mat1b cropped = thresholded(bbox) & mask;
// Compute the number of white pixels only on the crop
pixelNums.push_back(cv::countNonZero(cropped));
}
return 0;
}

Is there a simple method to highlight the mask?

If I have mask like
And I have a image( the size is same to the mask) like
I want to hightlight the mask in the image. If I'm in other Language,I just
As you can see, the result image have a transparent red show the mask. I hope implement this in OpenCV. So I write this code
#include <opencv.hpp>
using namespace cv;
using namespace std;
int main() {
Mat srcImg = imread("image.jpg");
Mat mask = imread("mask.jpg", IMREAD_GRAYSCALE)>200;
for(int i=0;i<srcImg.rows;i++)
for(int j=0;j<srcImg.cols;j++)
if(mask.at<uchar>(i, j)==255)
circle(srcImg, Point(j,i), 3, Scalar(0, 0, 128,128));
imshow("image",srcImg);
waitKey();
return 0;
}
But as you see, I use a alpha value in Scalar, but it is not a transparent red.
Maybe this is due to the srcImg just have 3 channels. I have two question about this
How to hightlight the mask with a transparent red(even the image just have 3 channels)?
I have to draw circle pixel by pixel to do this thing?
#include<opencv2/core.hpp>
#include<opencv2/imgproc.hpp>
#include<opencv2/highgui.hpp>
using namespace cv;
int main(int argc, char** argv)
{
Mat srcImg = imread("image.png");
Mat mask = imread("mask.png", IMREAD_GRAYSCALE) > 200;
Mat red;
cvtColor(mask, red, COLOR_GRAY2BGR);
red = (red - Scalar(0, 0, 255)) / 2;
srcImg = srcImg - red;
imshow("image", srcImg);
waitKey();
return 0;
}
I've written this in python but you can easily port it to C++. Assuming that your source and mask images are CV_8UC3 images:
src = cv2.imread("source.png", -1)
mask = cv2.imread("mask.png", -1)
# convert mask to gray and then threshold it to convert it to binary
gray = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 40, 255, cv2.THRESH_BINARY)
# find contours of two major blobs present in the mask
im2,contours,hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
# draw the found contours on to source image
for contour in contours:
cv2.drawContours(src, contour, -1, (255,0,0), thickness = 1)
# split source to B,G,R channels
b,g,r = cv2.split(src)
# add a constant to R channel to highlight the selected area in reed
r = cv2.add(b, 30, dst = b, mask = binary, dtype = cv2.CV_8U)
# merge the channels back together
cv2.merge((b,g,r), src)

OpenCV filter2D inside non rectangular ROI

I have an image in which only a small non-rectangular portion is useful. I have a binary mask indicating the useful ROI.
How can I apply cv::filter2D in OpenCV to that ROI only, defined by the binary mask?
Edit
My pixels outside the ROI have a value of 0. The other have float values of around 300-500, so the problem with filter2D in the borders of the ROI have high value transitions.
It would also be acceptable to just set the pixel values outside the ROI as the nearest pixel inside the ROI, something similar to cv::BORDER_REPLICATE
May be like this. I think there is no problem near border mask
#include "opencv2/opencv.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc, char* argv[])
{
Mat m = imread("f:/lib/opencv/samples/data/lena.jpg", CV_LOAD_IMAGE_GRAYSCALE);
Mat mask=Mat::zeros(m.size(), CV_8UC1),maskBlur,mc;
// mask is a disk
circle(mask, Point(200, 200), 100, Scalar(255),-1);
Mat negMask;
// neg mask
bitwise_not(mask, negMask);
circle(mask, Point(200, 200), 100, Scalar(255), -1);
Mat md,mdBlur,mdint;
m.copyTo(md);
// All pixels outside mask set to 0
md.setTo(0, negMask);
imshow("mask image", md);
// Convert image to int
md.convertTo(mdint, CV_32S);
Size fxy(13, 13);
blur(mdint, mdBlur, fxy);
mdBlur.convertTo(mc, CV_8U);
imshow("Blur without mask", mc);
imwrite("blurwithoutmask.jpg",mc);
mask.convertTo(maskBlur, CV_32S);
// blur mask
blur(maskBlur, maskBlur, fxy);
Mat mskB;
mskB.setTo(1, negMask);
divide(mdBlur,maskBlur/255,mdBlur);
mdBlur.convertTo(mc, CV_8U);
imshow("Blur with mask", mc);
imwrite("blurwithmask.jpg",mc);
waitKey();
}

Drawing Rectangle around difference area

I have a question which i am unable to resolve. I am taking difference of two images using OpenCV. I am getting output in a seperate Mat. Difference method used is the AbsDiff method. Here is the code.
char imgName[15];
Mat img1 = imread(image_path1, COLOR_BGR2GRAY);
Mat img2 = imread(image_path2, COLOR_BGR2GRAY);
/*cvtColor(img1, img1, CV_BGR2GRAY);
cvtColor(img2, img2, CV_BGR2GRAY);*/
cv::Mat diffImage;
cv::absdiff(img2, img1, diffImage);
cv::Mat foregroundMask = cv::Mat::zeros(diffImage.rows, diffImage.cols, CV_8UC3);
float threshold = 30.0f;
float dist;
for(int j=0; j<diffImage.rows; ++j)
{
for(int i=0; i<diffImage.cols; ++i)
{
cv::Vec3b pix = diffImage.at<cv::Vec3b>(j,i);
dist = (pix[0]*pix[0] + pix[1]*pix[1] + pix[2]*pix[2]);
dist = sqrt(dist);
if(dist>threshold)
{
foregroundMask.at<unsigned char>(j,i) = 255;
}
}
}
sprintf(imgName,"D:/outputer/d.jpg");
imwrite(imgName, diffImage);
I want to bound the difference part in a rectangle. findContours is drawing too many contours. but i only need a particular portion. My diff image is
I want to draw a single rectangle around all the five dials.
Please point me to right direction.
Regards,
I would search for the highest value for i index giving a non black pixel; that's the right border.
The lowest non black i is the left border. Similar for j.
You can:
binarize the image with a threshold. Background will be 0.
Use findNonZero to retrieve all points that are not 0, i.e. all foreground points.
use boundingRect on the retrieved points.
Result:
Code:
#include <opencv2/opencv.hpp>
using namespace cv;
int main()
{
// Load image (grayscale)
Mat1b img = imread("path_to_image", IMREAD_GRAYSCALE);
// Binarize image
Mat1b bin = img > 70;
// Find non-black points
vector<Point> points;
findNonZero(bin, points);
// Get bounding rect
Rect box = boundingRect(points);
// Draw (in color)
Mat3b out;
cvtColor(img, out, COLOR_GRAY2BGR);
rectangle(out, box, Scalar(0,255,0), 3);
// Show
imshow("Result", out);
waitKey();
return 0;
}
Find contours, it will output a set of contours as std::vector<std::vector<cv::Point> let us call it contours:
std::vector<cv::Point> all_points;
size_t points_count{0};
for(const auto& contour:contours){
points_count+=contour.size();
all_points.reserve(all_points);
std::copy(contour.begin(), contour.end(),
std::back_inserter(all_points));
}
auto bounding_rectnagle=cv::boundingRect(all_points);

Detection of objects in nonuniform illumination in opencv C++

I am performing feature detection in a video/live stream/image using OpenCV C++. The lighting condition varies in different parts of the video, leading to some parts getting ignored while transforming the RGB images to binary images.
The lighting condition in a particular portion of the video also changes over the course of the video. I tried the 'Histogram equalization' function, but it didn't help.
I got a working solution in MATLAB in the following link:
http://in.mathworks.com/help/images/examples/correcting-nonuniform-illumination.html
However, most of the functions used in the above link aren't available in OpenCV.
Can you suggest the alternative of this MATLAB code in OpenCV C++?
OpenCV has the adaptive threshold paradigm available in the framework: http://docs.opencv.org/modules/imgproc/doc/miscellaneous_transformations.html#adaptivethreshold
The function prototype looks like:
void adaptiveThreshold(InputArray src, OutputArray dst,
double maxValue, int adaptiveMethod,
int thresholdType, int blockSize, double C);
The first two parameters are the input image and a place to store the output thresholded image. maxValue is the thresholded value assigned to an output pixel should it pass the criteria, adaptiveMethod is the method to use for adaptive thresholding, thresholdType is the type of thresholding you want to perform (more later), blockSize is the size of the windows to examine (more later), and C is a constant to subtract from each window. I've never really needed to use this and I usually set this to 0.
The default method for adaptiveThreshold is to analyze blockSize x blockSize windows and calculate the mean intensity within this window subtracted by C. If the centre of this window is above the mean intensity, this corresponding location in the output position of the output image is set to maxValue, else the same position is set to 0. This should combat the non-uniform illumination issue where instead of applying a global threshold to the image, you are performing the thresholding on local pixel neighbourhoods.
You can read the documentation on the other methods for the other parameters, but to get your started, you can do something like this:
// Include libraries
#include <cv.h>
#include <highgui.h>
// For convenience
using namespace cv;
// Example function to adaptive threshold an image
void threshold()
{
// Load in an image - Change "image.jpg" to whatever your image is called
Mat image;
image = imread("image.jpg", 1);
// Convert image to grayscale and show the image
// Wait for user key before continuing
Mat gray_image;
cvtColor(image, gray_image, CV_BGR2GRAY);
namedWindow("Gray image", CV_WINDOW_AUTOSIZE);
imshow("Gray image", gray_image);
waitKey(0);
// Adaptive threshold the image
int maxValue = 255;
int blockSize = 25;
int C = 0;
adaptiveThreshold(gray_image, gray_image, maxValue,
CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY,
blockSize, C);
// Show the thresholded image
// Wait for user key before continuing
namedWindow("Thresholded image", CV_WINDOW_AUTOSIZE);
imshow("Thresholded image", gray_image);
waitKey(0);
}
// Main function - Run the threshold function
int main( int argc, const char** argv )
{
threshold();
}
adaptiveThreshold should be your first choice.
But here I report the "translation" from Matlab to OpenCV, so you can easily port your code. As you see, most of the functions are available both in Matlab and OpenCV.
#include <opencv2\opencv.hpp>
using namespace cv;
int main()
{
// Step 1: Read Image
Mat1b img = imread("path_to_image", IMREAD_GRAYSCALE);
// Step 2: Use Morphological Opening to Estimate the Background
Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(15,15));
Mat1b background;
morphologyEx(img, background, MORPH_OPEN, kernel);
// Step 3: Subtract the Background Image from the Original Image
Mat1b img2;
absdiff(img, background, img2);
// Step 4: Increase the Image Contrast
// Don't needed it here, the equivalent would be cv::equalizeHist
// Step 5(1): Threshold the Image
Mat1b bw;
threshold(img2, bw, 50, 255, THRESH_BINARY);
// Step 6: Identify Objects in the Image
vector<vector<Point>> contours;
findContours(bw.clone(), contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);
for(int i=0; i<contours.size(); ++i)
{
// Step 5(2): bwareaopen
if(contours[i].size() > 50)
{
// Step 7: Examine One Object
Mat1b object(bw.size(), uchar(0));
drawContours(object, contours, i, Scalar(255), CV_FILLED);
imshow("Single Object", object);
waitKey();
}
}
return 0;
}