tags: Watershed algorithm Image segmentation opencv python
table of Contents
0 principle 1 For example 1) Binarization 2) Remove all white noise in the image 3) Extract the area that is definitely a coin 4) Obtain the boundary area
5) Marked area 6) Implement the watershed algorithm
In geography, a watershed is a ridge that distinguishes drainage areas by different water systems. A catchment basin is a geographical area where water is discharged into a river or reservoir. The watershed transform applies these concepts to grayscale image processing to solve many image segmentation problems.
Understanding the watershed transform requires that we consider the grayscale image as a topological surface, and the value of f(x, y) in the surface is interpreted as height. For example, we can visualize the simple image in (a) below as the three-dimensional surface in (b) below. If rainwater falls on the surface, the rain will obviously flow into the two catchment basins. Rainwater that just landed on the watershed ridge will flow equally into the two catchment basins. The watershed transform will find the catchment basin and ridgeline in the grayscale image. In solving the problem of image segmentation, the key concept is to change the starting image into another image. In the transformed image, the catchment basin is the object or region we want to identify.

OpenCV uses a mask-based watershed algorithm, in which we set up those valley points to meet, and those that don't. This is an interactive image segmentation. We have to do is give us the known objects marked with different labels. If an area is definitely a foreground or object, mark it with a color (or gray value) label. If an area is definitely not an object but the background is marked with another color label. The rest of the area that cannot be determined to be foreground or background is marked with 0. This is our label. Then implement the watershed algorithm. Each time we fill, our label is updated. When two different colored labels meet, we build the dam until all the peaks are submerged. Finally we get the boundary object (dam) with a value of -1.
First look at the following example, and then explain the meaning inside:
We start by finding an approximate estimate of the coin. We can use Otsu's binarization.
import numpy as np
import cv2
from matplotlib import pyplot as plt
src = cv2.imread('test27.jpg')
img = src.copy()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(
gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)

This requires the use of open operations in morphology. In order to remove small holes in the object we need to use a morphological closing operation. So we now know that the area near the center of the object is definitely the foreground, and the area far from the center of the object is definitely the background. The area that cannot be determined is the boundary between coins.
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
Open operation can refer to:
a. When there is no contact between the coins
Corrosion operations remove edge pixels. The rest is definitely a coin.
b. The coins are in contact with each other
The distance transform plus the appropriate threshold. Next we have to find an area that is definitely not a coin. This is the need for expansion operations. Swelling extends the boundaries of the object into the background. So because the boundary areas are processed, we can know that those areas are definitely foreground, and those are definitely the background.
#
sure_bg = cv2.dilate(opening, kernel, iterations=3)
#
dist_transform = cv2.distanceTransform(opening, 1, 5)
ret, sure_fg = cv2.threshold(dist_transform, 0.7*dist_transform.max(), 255, 0)
Expansion operation can refer to:

Distance transformation function:
cv2.distanceTransform(src, distanceType, maskSize)
DIST_L1 = 1, //!< distance = |x1-x2| + |y1-y2|
DIST_L2 = 2, //!< the simple euclidean distance
DIST_C = 3, //!< distance = max(|x1-x2|,|y1-y2|)
DIST_L12 = 4, //!< L1-L2 metric: distance = 2(sqrt(1+x*x/2) - 1))
DIST_FAIR = 5, //!< distance = c^2(|x|/c-log(1+|x|/c)), c = 1.3998
DIST_WELSCH = 6, //!< distance = c^2/2(1-exp(-(x/c)^2)), c = 2.9846
DIST_HUBER = 7 //!< distance = |x|<c ? x^2/2 : c(|x|-c/2), c=1.345
DIST_MASK_3 = 3, //!< mask=3
DIST_MASK_5 = 5, //!< mask=5
DIST_MASK_PRECISE = 0 //!< mask=0

The rest of the area is that we don't know how to distinguish. This is what the watershed algorithm does. These areas are usually the junction of the foreground and the background (or the junction of the two foregrounds). We call it the border.
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg, sure_fg)

Now I know that those are the backgrounds that are coins. Then we can create a label (an array of the same size as the original image, the data type is in32) and mark the area. Use a different positive integer mark for the area we have identified (whether foreground or background) and a 0 mark for areas we don't know. We can do this using the function cv2.connectedComponents() . It marks the background as 0, and other objects use a positive integer token starting at 1. However, we know that if the background marker is 0, then the watershed algorithm will treat it as an unknown region. So we want to mark them with different integers. The area that is indeterminate (the unknown is defined in the result of the function cv2.connectedComponents output is unknown) is marked as 0.
#
ret, markers1 = cv2.connectedComponents(sure_fg)
# Make sure the background is 1 is not 0
markers = markers1 + 1
# Unknown area marked as 0
markers[unknown == 255] = 0
The result is represented using a JET color map. The dark blue area is an unknown area. The area of the coin must be marked with a different color. The rest of the area is the background marked in light blue.

Where the connectedComponents() function:
cv2.connectedComponents(image, labels, connectivity, ltype)
The label image will be modified and the marker in the border area will change to -1
markers3 = cv2.watershed(img, markers)
img[markers3 == -1] = [0, 0, 255]

The watershed algorithm function:
cv2.watershed(img, markers)
Finally all the procedures are as follows:
import numpy as np
import cv2
from matplotlib import pyplot as plt
src = cv2.imread('test27.jpg')
img = src.copy()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(
gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
# Eliminate noise
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
#
sure_bg = cv2.dilate(opening, kernel, iterations=3)
#
dist_transform = cv2.distanceTransform(opening, 1, 5)
ret, sure_fg = cv2.threshold(dist_transform, 0.7*dist_transform.max(), 255, 0)
#Get an unknown area
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg, sure_fg)
#
ret, markers1 = cv2.connectedComponents(sure_fg)
# Make sure the background is 1 is not 0
markers = markers1 + 1
# Unknown area marked as 0
markers[unknown == 255] = 0
markers3 = cv2.watershed(img, markers)
img[markers3 == -1] = [0, 0, 255]
plt.subplot(241), plt.imshow(cv2.cvtColor(src, cv2.COLOR_BGR2RGB)),
plt.title('Original'), plt.axis('off')
plt.subplot(242), plt.imshow(thresh, cmap='gray'),
plt.title('Threshold'), plt.axis('off')
plt.subplot(243), plt.imshow(sure_bg, cmap='gray'),
plt.title('Dilate'), plt.axis('off')
plt.subplot(244), plt.imshow(dist_transform, cmap='gray'),
plt.title('Dist Transform'), plt.axis('off')
plt.subplot(245), plt.imshow(sure_fg, cmap='gray'),
plt.title('Threshold'), plt.axis('off')
plt.subplot(246), plt.imshow(unknown, cmap='gray'),
plt.title('Unknow'), plt.axis('off')
plt.subplot(247), plt.imshow(np.abs(markers), cmap='jet'),
plt.title('Markers'), plt.axis('off')
plt.subplot(248), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)),
plt.title('Result'), plt.axis('off')
plt.show()
The results are as follows:

Article catalog 1 principle 2 algorithm improvement 3 API 4 instance 1 principle The split segmentation method is a segmentation method based on the topological theory, the basic idea is to view the i...
theory Any gray-scale image can be regarded as a terrain surface, where high intensity represents mountains and hills, while low intensity represents valleys. Fill each isolated valley (local minimum)...
Watershed algorithm for image segmentation: The watershed segmentation method is a mathematical morphology segmentation method based on topological theory. The basic idea is to regard the image as a g...
This time we look at image segmentation, which is also an important part of OpenCV. Image segmentation is a process of dividing an image into several disjoint small local areas according to certain pr...
1 principle Any gray image can be seen as a topological plane, a region with high grayscale value can be seen as a mountain peak, a region with a low grayscale value can be seen as a valley. We are fi...
Watershed algorithm implementation (C++, opencv) 1. Function: It is usually used to segment images, mainly to achieveSimilarity between adjacent pixelsAs an important reference basis,Pixels with simil...
The following sample code has these main steps: Load the image and turn it to grayscale, set a threshold, and divide the image into black and white. The noise data is removed by a morphologyEx() trans...
C++: void distanceTransform(InputArray src, OutputArray dst, int distanceType, int maskSize) Detailed parameters: InputArray src: the input image, usu...
Principles and Applications OpenCV- watershed image segmentation algorithm Image segmentation is in accordance with certain principles, it will process an image is divided into several disjoint small ...