OpenCV  3.0.0-rc1
Open Source Computer Vision
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
Image Segmentation with Distance Transform and Watershed Algorithm

Goal

In this tutorial you will learn how to:

Theory

Code

This tutorial code's is shown lines below. You can also download it from here.

1 
7 #include <opencv2/opencv.hpp>
8 #include <iostream>
9 
10 using namespace std;
11 using namespace cv;
12 
13 int main(int, char** argv)
14 {
16  // Load the image
17  Mat src = imread(argv[1]);
18 
19  // Check if everything was fine
20  if (!src.data)
21  return -1;
22 
23  // Show source image
24  imshow("Source Image", src);
26 
28  // Change the background from white to black, since that will help later to extract
29  // better results during the use of Distance Transform
30  for( int x = 0; x < src.rows; x++ ) {
31  for( int y = 0; y < src.cols; y++ ) {
32  if ( src.at<Vec3b>(x, y) == Vec3b(255,255,255) ) {
33  src.at<Vec3b>(x, y)[0] = 0;
34  src.at<Vec3b>(x, y)[1] = 0;
35  src.at<Vec3b>(x, y)[2] = 0;
36  }
37  }
38  }
39 
40  // Show output image
41  imshow("Black Background Image", src);
43 
45  // Create a kernel that we will use for accuting/sharpening our image
46  Mat kernel = (Mat_<float>(3,3) <<
47  1, 1, 1,
48  1, -8, 1,
49  1, 1, 1); // an approximation of second derivative, a quite strong kernel
50 
51  // do the laplacian filtering as it is
52  // well, we need to convert everything in something more deeper then CV_8U
53  // because the kernel has some negative values,
54  // and we can expect in general to have a Laplacian image with negative values
55  // BUT a 8bits unsigned int (the one we are working with) can contain values from 0 to 255
56  // so the possible negative number will be truncated
57  Mat imgLaplacian;
58  Mat sharp = src; // copy source image to another temporary one
59  filter2D(sharp, imgLaplacian, CV_32F, kernel);
60  src.convertTo(sharp, CV_32F);
61  Mat imgResult = sharp - imgLaplacian;
62 
63  // convert back to 8bits gray scale
64  imgResult.convertTo(imgResult, CV_8UC3);
65  imgLaplacian.convertTo(imgLaplacian, CV_8UC3);
66 
67  // imshow( "Laplace Filtered Image", imgLaplacian );
68  imshow( "New Sharped Image", imgResult );
70 
71  src = imgResult; // copy back
72 
74  // Create binary image from source image
75  Mat bw;
76  cvtColor(src, bw, CV_BGR2GRAY);
77  threshold(bw, bw, 40, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
78  imshow("Binary Image", bw);
80 
82  // Perform the distance transform algorithm
83  Mat dist;
84  distanceTransform(bw, dist, CV_DIST_L2, 3);
85 
86  // Normalize the distance image for range = {0.0, 1.0}
87  // so we can visualize and threshold it
88  normalize(dist, dist, 0, 1., NORM_MINMAX);
89  imshow("Distance Transform Image", dist);
91 
93  // Threshold to obtain the peaks
94  // This will be the markers for the foreground objects
95  threshold(dist, dist, .4, 1., CV_THRESH_BINARY);
96 
97  // Dilate a bit the dist image
98  Mat kernel1 = Mat::ones(3, 3, CV_8UC1);
99  dilate(dist, dist, kernel1);
100  imshow("Peaks", dist);
102 
104  // Create the CV_8U version of the distance image
105  // It is needed for findContours()
106  Mat dist_8u;
107  dist.convertTo(dist_8u, CV_8U);
108 
109  // Find total markers
110  vector<vector<Point> > contours;
112 
113  // Create the marker image for the watershed algorithm
114  Mat markers = Mat::zeros(dist.size(), CV_32SC1);
115 
116  // Draw the foreground markers
117  for (size_t i = 0; i < contours.size(); i++)
118  drawContours(markers, contours, static_cast<int>(i), Scalar::all(static_cast<int>(i)+1), -1);
119 
120  // Draw the background marker
121  circle(markers, Point(5,5), 3, CV_RGB(255,255,255), -1);
122  imshow("Markers", markers*10000);
124 
126  // Perform the watershed algorithm
127  watershed(src, markers);
128 
129  Mat mark = Mat::zeros(markers.size(), CV_8UC1);
130  markers.convertTo(mark, CV_8UC1);
131  bitwise_not(mark, mark);
132 // imshow("Markers_v2", mark); // uncomment this if you want to see how the mark
133  // image looks like at that point
134 
135  // Generate random colors
136  vector<Vec3b> colors;
137  for (size_t i = 0; i < contours.size(); i++)
138  {
139  int b = theRNG().uniform(0, 255);
140  int g = theRNG().uniform(0, 255);
141  int r = theRNG().uniform(0, 255);
142 
143  colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
144  }
145 
146  // Create the result image
147  Mat dst = Mat::zeros(markers.size(), CV_8UC3);
148 
149  // Fill labeled objects with random colors
150  for (int i = 0; i < markers.rows; i++)
151  {
152  for (int j = 0; j < markers.cols; j++)
153  {
154  int index = markers.at<int>(i,j);
155  if (index > 0 && index <= static_cast<int>(contours.size()))
156  dst.at<Vec3b>(i,j) = colors[index-1];
157  else
158  dst.at<Vec3b>(i,j) = Vec3b(0,0,0);
159  }
160  }
161 
162  // Visualize the final image
163  imshow("Final Result", dst);
165 
166  waitKey(0);
167  return 0;
168 }
Vec< uchar, 3 > Vec3b
Definition: matx.hpp:358
#define CV_32SC1
Definition: cvdef.h:140
#define CV_8U
Definition: cvdef.h:101
void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0)
Converts an image from one color space to another.
int rows
the number of rows and columns or (-1, -1) when the matrix has more than 2 dimensions ...
Definition: mat.hpp:1865
Mat imread(const String &filename, int flags=IMREAD_COLOR)
Loads an image from a file.
Definition: types_c.h:121
void convertTo(OutputArray m, int rtype, double alpha=1, double beta=0) const
Converts an array to another data type with optional scaling.
uchar * data
pointer to the data
Definition: mat.hpp:1867
void distanceTransform(InputArray src, OutputArray dst, OutputArray labels, int distanceType, int maskSize, int labelType=DIST_LABEL_CCOMP)
Calculates the distance to the closest zero pixel for each pixel of the source image.
void imshow(const String &winname, InputArray mat)
Displays an image in the specified window.
#define CV_RGB(r, g, b)
Definition: imgproc_c.h:985
Definition: types_c.h:458
#define CV_8UC1
Definition: cvdef.h:116
Definition: types_c.h:446
Definition: types_c.h:571
Definition: gr_skig.hpp:77
void drawContours(InputOutputArray image, InputArrayOfArrays contours, int contourIdx, const Scalar &color, int thickness=1, int lineType=LINE_8, InputArray hierarchy=noArray(), int maxLevel=INT_MAX, Point offset=Point())
Draws contours outlines or filled contours.
void filter2D(InputArray src, OutputArray dst, int ddepth, InputArray kernel, Point anchor=Point(-1,-1), double delta=0, int borderType=BORDER_DEFAULT)
Convolves an image with the kernel.
void watershed(InputArray image, InputOutputArray markers)
Performs a marker-based image segmentation using the watershed algorithm.
Template class for short numerical vectors, a partial case of Matx.
Definition: matx.hpp:300
void dilate(InputArray src, OutputArray dst, InputArray kernel, Point anchor=Point(-1,-1), int iterations=1, int borderType=BORDER_CONSTANT, const Scalar &borderValue=morphologyDefaultBorderValue())
Dilates an image by using a specific structuring element.
int cols
Definition: mat.hpp:1865
MatSize size
Definition: mat.hpp:1882
RNG & theRNG()
Returns the default random number generator.
unsigned char uchar
Definition: defs.h:284
static Vec< _Tp, cn > normalize(const Vec< _Tp, cn > &v)
int uniform(int a, int b)
returns uniformly distributed integer random number from [a,b) range
void bitwise_not(InputArray src, OutputArray dst, InputArray mask=noArray())
Inverts every bit of an array.
Definition: types_c.h:559
for i
Definition: modelConvert.m:63
#define CV_8UC3
Definition: cvdef.h:118
double threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type)
Applies a fixed-level threshold to each array element.
Definition: gr_skig.hpp:62
flag
Definition: base.hpp:176
void findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset=Point())
Finds contours in a binary image.
#define CV_32F
Definition: cvdef.h:106
int main(int argc, const char *argv[])
Definition: facerec_demo.cpp:67
n-dimensional dense array class
Definition: mat.hpp:726
_Tp & at(int i0=0)
Returns a reference to the specified array element.
Definition: types_c.h:577
Point2i Point
Definition: types.hpp:181
int waitKey(int delay=0)
Waits for a pressed key.

Explanation / Result

  1. Load the source image and check if it is loaded without any problem, then show it:
    // Load the image
    Mat src = imread(argv[1]);
    // Check if everything was fine
    if (!src.data)
    return -1;
    // Show source image
    imshow("Source Image", src);
    source.jpeg
  2. Then if we have an image with white background, it is good to tranform it black. This will help us to desciminate the foreground objects easier when we will apply the Distance Transform:
    // Change the background from white to black, since that will help later to extract
    // better results during the use of Distance Transform
    for( int x = 0; x < src.rows; x++ ) {
    for( int y = 0; y < src.cols; y++ ) {
    if ( src.at<Vec3b>(x, y) == Vec3b(255,255,255) ) {
    src.at<Vec3b>(x, y)[0] = 0;
    src.at<Vec3b>(x, y)[1] = 0;
    src.at<Vec3b>(x, y)[2] = 0;
    }
    }
    }
    // Show output image
    imshow("Black Background Image", src);
    black_bg.jpeg
  3. Afterwards we will sharp our image in order to acute the edges of the foreground objects. We will apply a laplacian filter with a quite strong filter (an approximation of second derivative):
    // Create a kernel that we will use for accuting/sharpening our image
    Mat kernel = (Mat_<float>(3,3) <<
    1, 1, 1,
    1, -8, 1,
    1, 1, 1); // an approximation of second derivative, a quite strong kernel
    // do the laplacian filtering as it is
    // well, we need to convert everything in something more deeper then CV_8U
    // because the kernel has some negative values,
    // and we can expect in general to have a Laplacian image with negative values
    // BUT a 8bits unsigned int (the one we are working with) can contain values from 0 to 255
    // so the possible negative number will be truncated
    Mat imgLaplacian;
    Mat sharp = src; // copy source image to another temporary one
    filter2D(sharp, imgLaplacian, CV_32F, kernel);
    src.convertTo(sharp, CV_32F);
    Mat imgResult = sharp - imgLaplacian;
    // convert back to 8bits gray scale
    imgResult.convertTo(imgResult, CV_8UC3);
    imgLaplacian.convertTo(imgLaplacian, CV_8UC3);
    // imshow( "Laplace Filtered Image", imgLaplacian );
    imshow( "New Sharped Image", imgResult );
    laplace.jpeg
    sharp.jpeg
  4. Now we tranfrom our new sharped source image to a grayscale and a binary one, respectively:
    // Create binary image from source image
    Mat bw;
    cvtColor(src, bw, CV_BGR2GRAY);
    imshow("Binary Image", bw);
    bin.jpeg
  5. We are ready now to apply the Distance Tranform on the binary image. Moreover, we normalize the output image in order to be able visualize and threshold the result:
    // Perform the distance transform algorithm
    Mat dist;
    // Normalize the distance image for range = {0.0, 1.0}
    // so we can visualize and threshold it
    normalize(dist, dist, 0, 1., NORM_MINMAX);
    imshow("Distance Transform Image", dist);
    dist_transf.jpeg
  6. We threshold the dist image and then perform some morphology operation (i.e. dilation) in order to extract the peaks from the above image:
    // Threshold to obtain the peaks
    // This will be the markers for the foreground objects
    threshold(dist, dist, .4, 1., CV_THRESH_BINARY);
    // Dilate a bit the dist image
    Mat kernel1 = Mat::ones(3, 3, CV_8UC1);
    dilate(dist, dist, kernel1);
    imshow("Peaks", dist);
    peaks.jpeg
  7. From each blob then we create a seed/marker for the watershed algorithm with the help of the cv::findContours function:
    // Create the CV_8U version of the distance image
    // It is needed for findContours()
    Mat dist_8u;
    dist.convertTo(dist_8u, CV_8U);
    // Find total markers
    vector<vector<Point> > contours;
    // Create the marker image for the watershed algorithm
    Mat markers = Mat::zeros(dist.size(), CV_32SC1);
    // Draw the foreground markers
    for (size_t i = 0; i < contours.size(); i++)
    drawContours(markers, contours, static_cast<int>(i), Scalar::all(static_cast<int>(i)+1), -1);
    // Draw the background marker
    circle(markers, Point(5,5), 3, CV_RGB(255,255,255), -1);
    imshow("Markers", markers*10000);
    markers.jpeg
  8. Finally, we can apply the watershed algorithm, and visualize the result:
    // Perform the watershed algorithm
    watershed(src, markers);
    Mat mark = Mat::zeros(markers.size(), CV_8UC1);
    markers.convertTo(mark, CV_8UC1);
    bitwise_not(mark, mark);
    // imshow("Markers_v2", mark); // uncomment this if you want to see how the mark
    // image looks like at that point
    // Generate random colors
    vector<Vec3b> colors;
    for (size_t i = 0; i < contours.size(); i++)
    {
    int b = theRNG().uniform(0, 255);
    int g = theRNG().uniform(0, 255);
    int r = theRNG().uniform(0, 255);
    colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
    }
    // Create the result image
    Mat dst = Mat::zeros(markers.size(), CV_8UC3);
    // Fill labeled objects with random colors
    for (int i = 0; i < markers.rows; i++)
    {
    for (int j = 0; j < markers.cols; j++)
    {
    int index = markers.at<int>(i,j);
    if (index > 0 && index <= static_cast<int>(contours.size()))
    dst.at<Vec3b>(i,j) = colors[index-1];
    else
    dst.at<Vec3b>(i,j) = Vec3b(0,0,0);
    }
    }
    // Visualize the final image
    imshow("Final Result", dst);
    final.jpeg