OpenCV (3) - image segmentation

Table of contents

1. Image Segmentation

2. Fixed threshold method - histogram bimodal method

3. Automatic threshold method

3.1 Adaptive threshold method

3.2 Iterative Threshold Segmentation

3.3 Otsu method

4. Edge detection

4.1 The concept of image gradient

4.2 The concept of template convolution and gradient map

4.3 Gradient operator

4.4 Canny edge detection algorithm

5. Connected region analysis

5.1 Overview of Connected Regions

5.2 Two-Pass Algorithm

6. Region growing algorithm

6.1 Summary of region growing

6.2 Principle of region growing

7. Watershed algorithm

7.1 Summary of Watershed Algorithm

7.2 Watershed algorithm


1. Image Segmentation

  • Image segmentation refers to the process of dividing an image into several regions with similar properties, mainly including threshold-based, region-based, edge-based, cluster-based, graph theory-based and deep learning-based image segmentation methods.
  • Image segmentation is divided into semantic segmentation and instance segmentation.

  • The principle of segmentation is to make the divided subgraphs maintain the maximum similarity inside , and keep the similarity between subgraphs to the minimum .
  • Divide G = (V,E) into two subsets A, B such that:

 

2. Fixed threshold method - histogram bimodal method

Bimodal method: It is a typical global single threshold segmentation method.

Basic idea: Assuming that there are obvious targets and backgrounds in the image , its gray level histogram has a bimodal distribution . When the gray level histogram has bimodal characteristics, the gray level corresponding to the valley between the two peaks is selected as the threshold .

Function : cv2.threshold(src, thresh, maxval, type)

Parameter Description:

  • Parameter 1: original image
  • Parameter 2: Threshold to classify pixel values
  • Parameter 3: When the pixel value is higher (lower) than the threshold, the new pixel value that should be assigned
  • Parameter 4: The fourth parameter is the threshold method
  • Return value: The function has two return values, one is retVal (threshold), and the other is the image after thresholding.

 

#=================================固定阈值======================================#
# flag = 0
flag = 1
if flag == 1:
    # 灰度图读入
    img = cv2.imread('./image/thresh.png', 0)
    threshold = 127
    # 阈值分割
    ret, th = cv2.threshold(img, threshold, 255, cv2.THRESH_BINARY)
    print(ret)

    cv2.imshow('thresh', th)
    cv2.imshow('img',img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

# flag = 0
flag = 1
if flag == 1:
    #opencv读取图像
    img = cv2.imread('./image/person.png',0)
    #5种阈值法图像分割
    ret, thresh1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
    ret, thresh2 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
    ret, thresh3 = cv2.threshold(img, 127, 255,cv2.THRESH_TRUNC)
    ret, thresh4 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO)
    ret, thresh5 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV)

    images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]
    #使用for循环进行遍历,matplotlib进行显示
    for i in range(6):
        plt.subplot(2,3, i+1)
        plt.imshow(images[i],cmap='gray')
        plt.xticks([])
        plt.yticks([])

    plt.suptitle('fixed threshold')
    plt.show()

3. Automatic threshold method

3.1 Adaptive threshold method

For simple images, the fixed threshold method can be well segmented, but for images with many elements and uneven light and dark changes, the fixed threshold method cannot be well segmented.

Function : cv2.adaptiveThreshold()

Parameter description :

  • Parameter 1: the original image to be processed
  • Parameter 2: The maximum threshold, generally 255
  • Parameter 3: Calculation method of small area threshold
    • ADAPTIVE_THRESH_MEAN_C: Take the mean value in a small area
    • ADAPTIVE_THRESH_GAUSSIAN_C: Weighted summation in a small area, the weight is a Gaussian kernel
  • Parameter 4: Threshold mode (5 types, as introduced in the previous section)
  • Parameter 5: The area of ​​the small area, such as 11 is a small block of 11×11
  • Parameter 6: The final threshold is equal to the threshold calculated by the small area and then subtracted from this value

Specific : Adaptive threshold will take a small part of the picture to calculate the threshold each time , so that the thresholds in different areas of the picture are different, and it is suitable for pictures with uneven distribution of light and dark.

#=================================自适应阈值======================================#
# flag = 0
flag = 1
if flag == 1:
    img = cv2.imread('./image/paper2.png', 0)

    # 固定阈值
    ret, th1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
    # 自适应阈值
    th2 = cv2.adaptiveThreshold(
        img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY,11, 4)
    th3 = cv2.adaptiveThreshold(
        img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 4)
    #全局阈值,均值自适应,高斯加权自适应对比
    titles = ['Original', 'Global(v = 127)', 'Adaptive Mean', 'Adaptive Gaussian']
    images = [img, th1, th2, th3]
    for i in range(4):
        plt.subplot(2, 2, i + 1)
        plt.imshow(images[i], cmap='gray')
        plt.title(titles[i], fontsize=8)
        plt.xticks([])
        plt.yticks([])
    plt.show()

3.2 Iterative Threshold Segmentation

step:

  • Find the maximum gray value and the minimum gray value of the image , which are recorded as ZMAX and ZMIN respectively, and set the initial threshold tT0=(ZMAX+ZMIN)/2
  • According to the threshold TK, the image is divided into foreground and background, and the average gray value ZO and ZB of the two are calculated respectively.
  • Find the new threshold TK+1=(ZO+ZB)/2
  • If TK==TK+1, the result is the threshold; otherwise, go to 2 and iteratively calculate
  • Fixed Threshold Segmentation Using Calculated Threshold
#=================================自适应阈值——迭代法======================================#
# flag = 0
flag = 1
if flag == 1:
    def best_thresh(img):
        img_array = np.array(img).astype(np.float32)#转化成数组
        I=img_array
        zmax=np.max(I)
        zmin=np.min(I)
        tk=(zmax+zmin)/2#设置初始阈值
        #根据阈值将图像进行分割为前景和背景,分别求出两者的平均灰度zo和zb
        b=1
        m,n=I.shape;
        while b==0:
            ifg=0
            ibg=0
            fnum=0
            bnum=0
            for i in range(1,m):
                 for j in range(1,n):
                    tmp=I(i,j)
                    if tmp>=tk:
                        ifg=ifg+1
                        fnum=fnum+int(tmp)#前景像素的个数以及像素值的总和
                    else:
                        ibg=ibg+1
                        bnum=bnum+int(tmp)#背景像素的个数以及像素值的总和
            #计算前景和背景的平均值
            zo=int(fnum/ifg)
            zb=int(bnum/ibg)
            if tk==int((zo+zb)/2):
                b=0
            else:
                tk=int((zo+zb)/2)
        return tk

    img = cv2.imread("./image/bird.png")
    img_1 = cv2.imread("./image/bird.png")
    img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
    gray = cv2.cvtColor(img,cv2.COLOR_RGB2GRAY)
    img = cv2.resize(gray,(200,200))#大小
    yvzhi=best_thresh(img)
    ret1, th1 = cv2.threshold(img, yvzhi, 255, cv2.THRESH_BINARY)
    print(ret1)
    plt.imshow(img_1,cmap=cm.gray)
    plt.show()
    plt.imshow(th1,cmap=cm.gray)
    plt.show()

 

3.3 Otsu method

Otsu method:

  • Maximum between-class variance is an adaptive method based on a global threshold .
  • Grayscale feature: The image is divided into foreground and background. When the optimal threshold is taken, the difference between the two parts should be the largest, and the standard for measuring the difference is the largest between-class variance.
  • The histogram has an image with two peaks, and the T obtained by the Otsu method is approximately equal to the valley between the two peaks.

Symbol Description:

  • T: Segmentation threshold for the foreground and background of the image (x, y) .
  • w1: The proportion of pixels belonging to the foreground to the entire image , and its average gray level is μ1.
  • w2: The proportion of the number of background pixels to the entire image , and its average grayscale μ2.
  • μ: The overall average gray level of the image .
  • g: Between-class variance .
  • N1: Suppose the size of the image is M×N, and the number of pixels whose gray value is less than the threshold T in the image .
  • N2: The number of pixels whose grayscale is greater than the threshold T.

 

#=================================自适应阈值——大津阈值法======================================#
# flag = 0
flag = 1
if flag == 1:
    img = cv2.imread('./image/noisy.png', 0)
    # 固定阈值法
    ret1, th1 = cv2.threshold(img, 100, 255, cv2.THRESH_BINARY)
    # Otsu阈值法
    ret2, th2 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

    # 先进行高斯滤波,再使用Otsu阈值法
    blur = cv2.GaussianBlur(img, (5, 5), 0)
    ret3, th3 = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
    images = [img, 0, th1, img, 0, th2, blur, 0, th3]
    titles = ['Original', 'Histogram', 'Global(v=100)',
              'Original', 'Histogram', "Otsu's",
              'Gaussian filtered Image', 'Histogram', "Otsu's"]

    for i in range(3):
        # 绘制原图
        plt.subplot(3, 3, i * 3 + 1)
        plt.imshow(images[i * 3], 'gray')
        plt.title(titles[i * 3], fontsize=8)
        plt.xticks([]), plt.yticks([])

        # 绘制直方图plt.hist, ravel函数将数组降成一维
        plt.subplot(3, 3, i * 3 + 2)
        plt.hist(images[i * 3].ravel(), 256)
        plt.title(titles[i * 3 + 1], fontsize=8)
        plt.xticks([]), plt.yticks([])

        # 绘制阈值图
        plt.subplot(3, 3, i * 3 + 3)
        plt.imshow(images[i * 3 + 2], 'gray')
        plt.title(titles[i * 3 + 2], fontsize=8)
        plt.xticks([]), plt.yticks([])
    plt.show()

#Otsu源码
import numpy as np
 
def OTSU_enhance(img_gray, th_begin=0, th_end=256, th_step=1):
    #"must input a gary_img"
    assert img_gray.ndim == 2
 
    max_g = 0
    suitable_th = 0
    for threshold in range(th_begin, th_end, th_step):
        bin_img = img_gray > threshold
        bin_img_inv = img_gray <= threshold
        fore_pix = np.sum(bin_img)
        back_pix = np.sum(bin_img_inv)
        if 0 == fore_pix:
            break
        if 0 == back_pix:
            continue
 
        w0 = float(fore_pix) / img_gray.size
        u0 = float(np.sum(img_gray * bin_img)) / fore_pix
        w1 = float(back_pix) / img_gray.size
        u1 = float(np.sum(img_gray * bin_img_inv)) / back_pix
        # intra-class variance
        g = w0 * w1 * (u0 - u1) * (u0 - u1)
        if g > max_g:
            max_g = g
            suitable_th = threshold
    return suitable_th
img = cv2.imread('noisy.png', 0)
thresh = OTSU_enhance(img)
ret1, th1 = cv2.threshold(img, thresh, 255, cv2.THRESH_BINARY)
ret2, th2 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
a = plt.imshow(th1,cmap=cm.gray)
plt.show(a)
b = plt.imshow(th2,cmap=cm.gray)
plt.show(b)

4. Edge detection

4.1 The concept of image gradient

Image gradient : Gradient is a vector, and the gradient direction points to the direction where the function changes the fastest. The size is its modulus and the maximum rate of change. For a binary function z=f(x,y), it is at point (x,y ) is the gradient of grad(x,y) or

 

The magnitude and orientation of this gradient vector are: 

The image gradient is the measure of the grayscale change in the image , and the process of calculating the image gradient is the derivation process of a two-dimensional discrete function . The edge is actually a collection of points on the image whose gray level changes rapidly .

The figure below shows the mathematical expression of a grayscale image. The grayscale value of a pixel (x,y) is f(x,y), which has eight neighborhoods

The gradient of the image at point (x,y) is:

That is, gx corresponds to the horizontal direction of the image, that is, gx corresponds to the vertical direction of the image.

 4.2 The concept of template convolution and gradient map

(1) Template convolution:

To understand the generation of gradient maps, you must first understand the process of template convolution. Template convolution is a way of template operation. The steps are as follows:

  • Walk the template in the input image, and coincide the center of the template with a pixel position in the image.
  • Multiply each coefficient on the template with the gray level of each corresponding pixel under the template.
  • Add all the products (in order to maintain the grayscale range, the result is often divided by the sum of the template coefficients, and if the template sum of the gradient operator is 0, there is no need to divide).
  • Assign the above operation result (the response output of the template) to the pixel corresponding to the center position of the template in the output image.

 

(2) Gradient map:  The generation of the gradient map is the same as the template convolution. The difference is that to generate the gradient map, it is also necessary to calculate the magnitude of the gradient at the point (x, y) after the template convolution is completed, and use the magnitude as a pixel. value, that's all.

Note: The gray value of each pixel on the gradient map is the magnitude of the gradient vector.

A template is required to generate a gradient map, and the image on the right shows the simplest templates in the horizontal and vertical directions.

Horizontal direction :

Vertical direction :

 

4.3 Gradient operator

Gradient operator : The gradient operator is a first-order derivative operator, which is a combination of corresponding templates in the horizontal G(x) and vertical G(y) directions, and also has diagonal directions.

Common first-order operators : Roberts intersection operator, Prewitt operator, Sobel operator

(1) Roberts crossover operator

The essence of the Roberts crossover operator is a gradient operator in the diagonal direction , and the corresponding gradients in the horizontal and vertical directions are: 

Advantages : edge localization calibration, suitable for images with obvious edges and less noise

Disadvantages :

  • The grayscale changes in the horizontal and vertical directions are not described , and only the diagonal direction is focused, which is easy to cause omissions .
  • Poor robustness . Since the point itself participates in the gradient calculation, it cannot effectively suppress the interference of noise .

(2) Prewitt operator

The Prewitt operator is a typical 3×3 template. The center of the template corresponds to the original image coordinates (x, y) that require the gradient, and the gray value of the pixel in the 8 fields corresponding to (x, y) is shown in the following table:

After convolution through the horizontal template M(x) of the Prewitt operator, the corresponding horizontal gradient is:

 Through the vertical template M(y) convolution of the Prewitt operator, the corresponding vertical direction gradient is:

The gray value of the output gradient map at (x, y) is:

The Prewitt operator introduces an operation similar to local average , which has a smoothing effect on the noise, and can suppress the noise better than the Roberts operator

(3) Sobel operator

The Sobel operator is actually a Prewitt operator with added weight coefficients . The center of the template corresponds to the original image coordinates that require gradients. The corresponding pixel gray values ​​​​in the 8 fields are shown in the following table:

 After convolution with the horizontal template M_{x} of the Sobel operator, the corresponding horizontal gradient is:

After convolution through the vertical template M(y) of the Sobel operator, the corresponding vertical gradient is:

The gray value of the output gradient map at (x, y) is:

The Sobel operator introduces an operation similar to the local weighted average, and the positioning ratio of the edge is better than that of the Prewitt operator. 

Sobel operator

Function : dst = cv2.Sobel(src, ddepth, dx, dy, ksize)

Parameter description :

  • Parameter 1: The image to be processed
  • Parameter 2: The depth of the image, -1 means that the same depth as the original image is used. The depth of the target image must be greater than or equal to the depth of the original image
  • Parameters 3, 4: dx and dy represent the order of derivation, 0 means that there is no derivation in this direction, generally 0, 1, 2
  • Parameter 5: ksize is the size of the Sobel operator, which must be 1,3,5,7.

#=================================Sobel======================================#
# flag = 0
flag = 1
if flag == 1:
    img = cv2.imread('image/girl2.png',0)
    sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=5)
    sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=5)
    

    plt.subplot(1,3,1),plt.imshow(img,cmap = 'gray')
    plt.title('Original'), plt.xticks([]), plt.yticks([])
    plt.subplot(1,3,2),plt.imshow(sobelx,cmap = 'gray')
    plt.title('Sobel X'), plt.xticks([]), plt.yticks([])
    plt.subplot(1,3,3),plt.imshow(sobely,cmap = 'gray')
    plt.title('Sobel Y'), plt.xticks([]), plt.yticks([])
    plt.show()

 4.4  Canny edge detection algorithm

The Canny operator is a method of smoothing first and then calculating derivatives . John Canny studied the characteristics required by the optimal edge detection method, and gave three indicators for evaluating the performance of edge detection:

  • A good signal-to-noise ratio means that the probability of judging a non-edge point as an edge point is low, and the probability of judging an edge point as a non-edge point is low
  • High positioning performance , that is, the detected edge point should be in the center of the actual edge as much as possible
  • There is only a unique response to a single edge , i.e. the probability of a single edge generating multiple responses is low, and false response edges should be maximally suppressed

Function : cv2.Canny(image, th1, th2, Size)

Parameter description :

  • image: original image
  • th1: Threshold 1
  • th2: Threshold 2
  • Size: optional parameter, the size of the Sobel operator

steps :

  • Convert a color image to a grayscale image (read in as a grayscale single-channel image)
  • Gaussian blur (denoise) the image
  • Calculate the image gradient, and calculate the image edge amplitude and angle according to the gradient
  • Non-maximum suppression along the gradient direction (edge ​​refinement)
  • Dual Threshold Edge Connection Processing
  • Binary image output result

#=================================canny======================================#
# flag = 0
flag = 1
if flag == 1:
    #以灰度图形式读入图像
    img = cv2.imread('image/canny.png')
    v1 = cv2.Canny(img, 80, 150,(3,3))
    v2 = cv2.Canny(img, 50, 100,(5,5))

    #np.vstack():在竖直方向上堆叠
    #np.hstack():在水平方向上平铺堆叠
    ret = np.hstack((v1, v2))
    cv2.imshow('img', ret)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

5. Connected region analysis

5.1 Overview of Connected Regions

Connected region generally refers to the image region composed of foreground pixels with the same pixel value and adjacent positions in the image . Connected region analysis refers to finding and marking each connected region in the image. Connected region analysis is a commonly used and basic method in many application fields of CV and image analysis and processing.

For example: OCR is the segmentation and extraction of Chinese characters (license plate recognition, text recognition, subtitle recognition, etc.), the segmentation and extraction of moving foreground objects in visual tracking (pedestrian intrusion detection, legacy object detection, vision-based vehicle detection and tracking, etc.), Medical image processing (target area of ​​interest extraction), etc.

The connected region analysis method can be used in application scenarios where foreground objects need to be extracted for subsequent processing. Usually, the object of connected region analysis and processing is a binarized image .

In an image, the smallest unit is a pixel, and there are adjacent pixels around each pixel. There are two common adjacency relationships: 4-adjacency and 8-adjacency.

If A is connected to B, and B is connected to C, then A and C are connected. From a visual point of view, the points connected to each other form a region, and the points that are not connected form different regions. Such a collection of all points connected to each other is called a connected region.

4 adjacency, there are 3 connected regions

8 adjacency, there are 2 connected regions

 

5.2 Two-Pass Algorithm

Two-pass scanning method (Two-Pass), as its name suggests, refers to finding and marking all the connected regions existing in the image by scanning the image twice.

First scan :

  • Start traversing the pixels from the upper left corner, find the point where the first pixel is 255, label = 1
  • When the left and upper neighbors of the pixel are invalid values, set a new label value for the pixel, label++
  • When the pixel's left neighbor or upper neighbor pixel has a valid value, assign the label of the valid value pixel to the label value of the pixel
  • When both the left neighbor pixel and the upper neighbor pixel of the pixel are valid values, select the smaller label value and assign it to the pixel label value

 Second scan :

  • Update the label of each point to the smallest label in its set

 

import cv2
import numpy as np

# 4邻域的连通域和 8邻域的连通域
# [row, col]
NEIGHBOR_HOODS_4 = True
OFFSETS_4 = [[0, -1], [-1, 0], [0, 0], [1, 0], [0, 1]]

NEIGHBOR_HOODS_8 = False
OFFSETS_8 = [[-1, -1], [0, -1], [1, -1],
             [-1,  0], [0,  0], [1,  0],
             [-1,  1], [0,  1], [1,  1]]
#第二遍扫描
def reorganize(binary_img: np.array):
    index_map = []
    points = []
    index = -1
    rows, cols = binary_img.shape
    for row in range(rows):
        for col in range(cols):
            var = binary_img[row][col]
            if var < 0.5:
                continue
            if var in index_map:
                index = index_map.index(var)
                num = index + 1
            else:
                index = len(index_map)
                num = index + 1
                index_map.append(var)
                points.append([])
            binary_img[row][col] = num
            points[index].append([row, col])
    #print(binary_img)
    #print(points)
    return binary_img, points

#四领域或八领域判断
def neighbor_value(binary_img: np.array, offsets, reverse=False):
    rows, cols = binary_img.shape
    label_idx = 0
    rows_ = [0, rows, 1] if reverse == False else [rows-1, -1, -1]
    cols_ = [0, cols, 1] if reverse == False else [cols-1, -1, -1]
    for row in range(rows_[0], rows_[1], rows_[2]):
        for col in range(cols_[0], cols_[1], cols_[2]):
            label = 256
            if binary_img[row][col] < 0.5:
                continue
            for offset in offsets:
                neighbor_row = min(max(0, row+offset[0]), rows-1)
                neighbor_col = min(max(0, col+offset[1]), cols-1)
                neighbor_val = binary_img[neighbor_row, neighbor_col]
                if neighbor_val < 0.5:
                    continue
                label = neighbor_val if neighbor_val < label else label
            if label == 255:
                label_idx += 1
                label = label_idx
            binary_img[row][col] = label
    print('第一遍扫描:',binary_img)
    print('开始第二遍...')
    return binary_img

# binary_img: bg-0, object-255; int
#第一遍扫描
def Two_Pass(binary_img: np.array, neighbor_hoods):
    if neighbor_hoods == NEIGHBOR_HOODS_4:
        offsets = OFFSETS_4
    elif neighbor_hoods == NEIGHBOR_HOODS_8:
        offsets = OFFSETS_8
    else:
        raise ValueError

    binary_img = neighbor_value(binary_img, offsets, False)

    return binary_img

if __name__ == "__main__":
    #创建四行七列的矩阵
    binary_img = np.zeros((4, 7), dtype=np.int16)
    #指定点设置为255
    index = [[0, 2], [0, 5],
            [1, 0], [1, 1], [1, 2], [1, 4], [1, 5], [1, 6],
            [2, 2], [2, 5],
            [3, 1], [3, 2], [3, 4],[3,5], [3, 6]]
    for i in index:
        binary_img[i[0], i[1]] = np.int16(255)

    print("原始二值图像")
    print(binary_img)

    #print("Two_Pass")
    #调用Two Pass算法,计算第一遍扫面的结果
    binary_img = Two_Pass(binary_img, NEIGHBOR_HOODS_4)
    #print(binary_img)
    #计算第一遍扫面的结果
    binary_img, points = reorganize(binary_img)
    print(binary_img)
    #print(points)

6. Region growing algorithm

6.1 Summary of region growing

Region growing is an image segmentation method for sequential region segmentation . Region growing refers to starting from a certain pixel and gradually adding adjacent pixels according to certain criteria. When certain conditions are met, the region growing terminates.

How well a region grows depends on

  • Selection of initial point (seed point)
  • growth criteria
  • Termination condition

Region growing is to start from one or some pixel points, and finally get the whole region, and then realize the extraction of the target.

6.2 Principle of region growing

Basic idea : gather pixels with similar properties to form a region

steps :

  • (1) Scan the image sequentially to find the first pixel that has not yet been assigned, and set the pixel to be (x0, y0).
  • (2) With (x0, y0) as the center, consider the 4 neighborhood pixels (x, y) of (x0, y0). If (x0, y0) satisfies the growth criterion, combine (x, y) with (x0, y0) Merge (within the same region), while pushing (x,y) onto the stack.
  • (3) Take a pixel from the stack and return to step 2 as (x0, y0).
  • (4) When the stack is empty, return to step 1.
  • (5) Repeat steps 1-4 until every point in the image has an attribution.
  • (6) Growth ends.
# -*- coding:utf-8 -*-
import cv2
import numpy as np

####################################################################################



#######################################################################################
class Point(object):
    def __init__(self , x , y):
        self.x = x
        self.y = y
    def getX(self):
        return self.x
    def getY(self):
        return self.y
connects = [ Point(-1, -1), Point(0, -1), Point(1, -1), Point(1, 0),
            Point(1, 1), Point(0, 1), Point(-1, 1), Point(-1, 0)]
#####################################################################################
#计算两个点间的欧式距离
def get_dist(seed_location1,seed_location2):
    l1 = im[seed_location1.x , seed_location1.y]
    l2 = im[seed_location2.x , seed_location2.y]
    count = np.sqrt(np.sum(np.square(l1-l2)))
    return count

#import Image
im = cv2.imread('image/222.jpg')
cv2.imshow('src' , im)
cv2.waitKey(0)
cv2.destroyAllWindows()
im_shape = im.shape
height = im_shape[0]
width = im_shape[1]

print( 'the shape of image :', im_shape)

#标记,判断种子是否已经生长
img_mark = np.zeros([height , width])
cv2.imshow('img_mark' , img_mark)
cv2.waitKey(0)
cv2.destroyAllWindows()

# 建立空的图像数组,作为一类
img_re = im.copy()
for i in range(height):
    for j in range(width):
        img_re[i, j][0] = 0
        img_re[i, j][1] = 0
        img_re[i, j][2] = 0
cv2.imshow('img_re' , img_re)
cv2.waitKey(0)
cv2.destroyAllWindows()
#取一点作为种子点
seed_list = []
seed_list.append(Point(15, 15))
T = 7#阈值
class_k = 1#类别
#生长一个类
while (len(seed_list) > 0):
    seed_tmp = seed_list[0]
    #将以生长的点从一个类的种子点列表中删除
    seed_list.pop(0)


    img_mark[seed_tmp.x, seed_tmp.y] = class_k

    # 遍历8邻域
    for i in range(8):
        tmpX = seed_tmp.x + connects[i].x
        tmpY = seed_tmp.y + connects[i].y

        if (tmpX < 0 or tmpY < 0 or tmpX >= height or tmpY >= width):
            continue
        dist = get_dist(seed_tmp, Point(tmpX, tmpY))
        #在种子集合中满足条件的点进行生长
        if (dist < T and img_mark[tmpX, tmpY] == 0):
            img_re[tmpX, tmpY][0] = im[tmpX, tmpY][0]
            img_re[tmpX, tmpY][1] = im[tmpX, tmpY][1]
            img_re[tmpX, tmpY][2] = im[tmpX, tmpY][2]
            img_mark[tmpX, tmpY] = class_k
            seed_list.append(Point(tmpX, tmpY))


########################################################################################

#输出图像
cv2.imshow('OUTIMAGE' , img_re)
cv2.waitKey(0)
cv2.destroyAllWindows()

7. Watershed algorithm

7.1 Summary of Watershed Algorithm

Arbitrary grayscale images can be thought of as geological surfaces, with peaks at high brightness and valleys at low brightness.

Give each isolated valley (local minimum) a different color of water (label). When the water rises, according to the surrounding peaks (gradient), different valleys, that is, different colors, will start to merge. To avoid valley merging, you need Create a watershed where the water is to merge until all the peaks are submerged, the created watershed is the dividing boundary line, this is the principle of the watershed. 

7.2 Watershed algorithm

steps :

  • load original image
  • Threshold segmentation, which divides the image into black and white parts
  • Perform an open operation on the image, that is, first corrode and then expand
  • The result of the opening operation is expanded to obtain most of the background area
  • Obtain the foreground area through the distance transform Distance Transform
  • The background area sure_bg and the foreground area sure_fg are subtracted to obtain an overlapping area with both foreground and background
  • connected region processing
  • Finally using the watershed algorithm
# import cv2
"""
完成分水岭算法步骤:
1、加载原始图像
2、阈值分割,将图像分割为黑白两个部分
3、对图像进行开运算,即先腐蚀在膨胀
4、对开运算的结果再进行 膨胀,得到大部分是背景的区域
5、通过距离变换 Distance Transform 获取前景区域
6、背景区域sure_bg 和前景区域sure_fg相减,得到即有前景又有背景的重合区域
7、连通区域处理
8、最后使用分水岭算法
"""

import cv2
import numpy as np


# Step1. 加载图像
img = cv2.imread('image/yezi.jpg')
cv2.imshow("img", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Step2.阈值分割,将图像分为黑白两部分
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
# cv2.imshow("thresh", thresh)

# Step3. 对图像进行“开运算”,先腐蚀再膨胀
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
# cv2.imshow("opening", opening)

# Step4. 对“开运算”的结果进行膨胀,得到大部分都是背景的区域
sure_bg = cv2.dilate(opening, kernel, iterations=3)
cv2.imshow("sure_bg", sure_bg)
cv2.waitKey(0)
cv2.destroyAllWindows()

# Step5.通过distanceTransform获取前景区域
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)  # DIST_L1 DIST_C只能 对应掩膜为3    DIST_L2 可以为3或者5
cv2.imshow("dist_transform", dist_transform)
cv2.waitKey(0)
cv2.destroyAllWindows()
print(dist_transform.max())
ret, sure_fg = cv2.threshold(dist_transform, 0.1 * dist_transform.max(), 255, 0)


# Step6. sure_bg与sure_fg相减,得到既有前景又有背景的重合区域   #此区域和轮廓区域的关系未知 
sure_fg = np.uint8(sure_fg)
unknow = cv2.subtract(sure_bg, sure_fg)
cv2.imshow("unknow", unknow)
cv2.waitKey(0)
cv2.destroyAllWindows()

# Step7. 连通区域处理
ret, markers = cv2.connectedComponents(sure_fg,connectivity=8) #对连通区域进行标号  序号为 0 - N-1 
#print(markers)
print(ret)
markers = markers + 1           #OpenCV 分水岭算法对物体做的标注必须都 大于1 ,背景为标号 为0  因此对所有markers 加1  变成了  1  -  N
#去掉属于背景区域的部分(即让其变为0,成为背景)
# 此语句的Python语法 类似于if ,“unknow==255” 返回的是图像矩阵的真值表。
markers[unknow==255] = 0   

# Step8.分水岭算法
markers = cv2.watershed(img, markers)  #分水岭算法后,所有轮廓的像素点被标注为  -1 
#print(markers)

img[markers == -1] = [0, 0, 255]   # 标注为-1 的像素点标 红
cv2.imshow("dst", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

Open CV code data set
Click on the card to follow the official account [Sister takes you to play AI]
Reply "open CV" to receive

Guess you like

Origin blog.csdn.net/m0_45447650/article/details/124398438