SLIC superpixel segmentation and save the segmented superpixel blocks:
# https://github.com/LarkMi/SLIC/blob/main/SLIC.py import skimage from skimage.segmentation import slic,mark_boundaries from skimage import io import matplotlib.pyplot as plt from PIL import Image, ImageEnhance import numpy as np import cv2 # # np.set_printoptions(threshold=np.inf) path = 'C:\Users\Administrator\Desktop\SLIC\' img_name = 'test.png' img = io.imread(path + img_name,as_gray=True) #as_gray is grayscale reading, and the obtained value is normalized Segments = slic(img, n_segments=10, compactness=0.2,start_label = 1)# for SLIC segmentation out=mark_boundaries(img,segments) out = out*255 #io's grayscale reading is a normalized value, if you read a color picture, remove this line img3 = Image.fromarray(np.uint8(out)) img3. show() seg_img_name = 'seg.png' img3.save(path + '\' + seg_img_name)#Display and save the picture after adding the dividing line maxn = max(segments. reshape(int(segments. shape[0]*segments. shape[1]),)) for i in range(1,maxn + 1): a = np.array(segments == i) b = img * a w,h = [],[] for x in range(b. shape[0]): for y in range(b. shape[1]): if b[x][y] != 0: w.append(x) h.append(y) c = b[min(w):max(w),min(h):max(h)] c = c*255 d = c.reshape(c.shape[0],c.shape[1],1) e = np.concatenate((d,d),axis=2) e = np.concatenate((e,d),axis=2) img2 = Image.fromarray(np.uint8(e)) img2.save(path + '\' + str(i) + '.png') print('saved' + str(i) + 'picture') wid, hig = [], [] img = io.imread(path + '\' + seg_img_name) for i in range(1,maxn + 1): w,h = [],[] for x in range(segments. shape[0]): for y in range(segments. shape[1]): if segments[x][y] == i: w.append(x) h.append(y) font=cv2.FONT_HERSHEY_SIMPLEX#Use the default font #print((min(w),min(h))) img=cv2.putText(img,str(i),(h[int(len(h)/(2))],w[int(len(w)/2)]),font,1,(255,255,255) ,2)#Add text, 1.2 means font size, (0,40) is the initial position, (255,255,255) means color, 2 means thickness img = Image.fromarray(np.uint8(img)) img. show() img.save(path + '\' + seg_img_name + '_label.png')
Self-written superpixel segmentation code:
- Added the label attribute to the Cluster class to facilitate labeling the results of the k-means algorithm
- Added a border increase part to the SLICProcessor class method save_current_image, which can generate images similar to 3||4.png
- Added a new class method generate_result() with a user parameter K, which is the number of clusters of the set Kmeans algorithm, and selects the region to merge according to the number of clusters
- In the original code, it is impossible to read jpg and png images at the same time due to the different number of image channels, so it can be adapted with small changes.
# https://gitee.com/xu-qiyu/MyProject/tree/master/opencv/superpixel segmentation from copy import copy, deep copy import math from cv2 import CHAIN_APPROX_NONE, RETR_LIST, imshow, merge, findContours, waitKey from skimage import io, color import numpy as np from tqdm import trange from tqdm import tqdm from sklearn.cluster import KMeans class Cluster(object): cluster_index = 1 def __init__(self, h, w, l=0, a=0, b=0): self. update(h, w, l, a, b) self.pixels = [] self.no = self.cluster_index self.label = 0 Cluster.cluster_index += 1 def update(self, h, w, l, a, b): self.h = h self.w = w self.l = l self.a = a self.b = b def __str__(self): return "{},{}:{} {} {} ".format(self.h, self.w, self.l, self.a, self.b) def __repr__(self): return self.__str__() class SLICProcessor(object): @staticmethod def open_image(path): """ return: 3D array, row col [LAB] """ rgb = io.imread(path) if path[-3:] == 'png': lab_arr = color.rgb2lab(rgb[:, :, 0:3]) else: lab_arr = color.rgb2lab(rgb) return lab_arr @staticmethod def save_lab_image(path, lab_arr): """ Convert the array to RBG, then save the image :param path: :param lab_arr: :return: """ rgb_arr = color.lab2rgb(lab_arr) io.imsave(path, rgb_arr) def make_cluster(self, h, w): h = int(h) w = int(w) return Cluster(h, w, self.data[h][w][0], self.data[h][w][1], self.data[h][w][2]) def __init__(self, filename, K, M): self.K = K self.M = M self.data = self.open_image(filename) self.image_height = self.data.shape[0] self.image_width = self.data.shape[1] self.N = self.image_height * self.image_width self.S = int(math.sqrt(self.N / self.K)) self.clusters = [] self. label = {} self.dis = np.full((self.image_height, self.image_width), np.inf) def init_clusters(self): h = self.S // 2 w = self.S // 2 while h < self. image_height: while w < self. image_width: self. clusters. append(self. make_cluster(h, w)) w + = self.S w = self.S // 2 h + = self.S def get_gradient(self, h, w): if w + 1 >= self. image_width: w = self. image_width - 2 if h + 1 >= self. image_height: h = self. image_height - 2 gradient = self.data[h + 1][w + 1][0] - self.data[h][w][0] + \ self.data[h + 1][w + 1][1] - self.data[h][w][1] + \ self.data[h+1][w+1][2] - self.data[h][w][2] return gradient def move_clusters(self): for cluster in self. clusters: cluster_gradient = self. get_gradient(cluster. h, cluster. w) for dh in range(-1, 2): for dw in range(-1, 2): _h = cluster.h + dh _w = cluster.w + dw new_gradient = self. get_gradient(_h, _w) if new_gradient < cluster_gradient: cluster.update(_h, _w, self.data[_h][_w][0], self.data[_h][_w][1], self.data[_h][_w][2]) cluster_gradient = new_gradient def assignment(self): for cluster in tqdm(self.clusters): for h in range(cluster.h - 2 * self.S, cluster.h + 2 * self.S): if h < 0 or h >= self. image_height: continue for w in range(cluster.w - 2 * self.S, cluster.w + 2 * self.S): if w < 0 or w >= self. image_width: continue L, A, B = self. data[h][w] Dc = math. sqrt( math.pow(L - cluster.l, 2) + math.pow(A - cluster.a, 2) + math.pow(B - cluster.b, 2)) Ds = math. sqrt( math.pow(h - cluster.h, 2) + math.pow(w - cluster.w, 2)) D = math.sqrt(math.pow(Dc / self.M, 2) + math.pow(Ds / self.S, 2)) if D < self.dis[h][w]: if (h, w) not in self.label: self. label[(h, w)] = cluster cluster.pixels.append((h,w)) else: self. label[(h, w)]. pixels. remove((h, w)) self. label[(h, w)] = cluster cluster.pixels.append((h,w)) self.dis[h][w] = D a = 1 def update_cluster(self): for cluster in self. clusters: sum_h = sum_w = number = 0 for p in cluster.pixels: sum_h + = p[0] sum_w + = p[1] number + = 1 _h = int(sum_h / number) _w = int(sum_w / number) cluster.update(_h, _w, self.data[_h][_w][0], self.data[_h][_w][1], self.data[_h][_w][2]) def save_current_image(self, name): image_arr = np. copy(self. data) # imshow("r", image_arr) for cluster in self. clusters: for p in cluster.pixels: image_arr[p[0]][p[1]][0] = cluster.l image_arr[p[0]][p[1]][1] = cluster.a image_arr[p[0]][p[1]][2] = cluster.b image_arr[cluster.h][cluster.w][0] = 0 image_arr[cluster.h][cluster.w][1] = 0 image_arr[cluster.h][cluster.w][2] = 0 self.save_lab_image("1.png", image_arr) mask_r = np.ones((self.image_height, self.image_width), np.uint8)*0 for cluster in self. clusters: mask = np.ones((self.image_height, self.image_width), np.uint8)*0 for x in cluster.pixels: mask[x[0],x[1]] = 255 contours, _ = findContours(mask, RETR_LIST, CHAIN_APPROX_NONE) for contour in contours: for i in contour: mask_r[i[0][1], i[0][0]] = 255 for x in range(self. image_height): for y in range(self. image_width): if mask_r[x, y] == 255: image_arr[x, y] = [100, 0, 0] self.save_lab_image("2.png", image_arr) def iterate_10times(self): self.init_clusters() self. move_clusters() # for i in strange(10): self. assignment() self. update_cluster() # name = 'lenna_M{m}_K{k}_loop{loop}.png'.format(loop=0, m=self.M, k=self.K) self. save_current_image("123") def generate_result(self, K): clusters = deepcopy(self. clusters) temp_img = [[x.l, x.a, x.b] for x in clusters] kmeans = KMeans(n_clusters=K,random_state=3).fit(temp_img) for i in range(len(self. clusters)): self.clusters[i].label = kmeans.labels_[i] mask = np.ones((self.image_height, self.image_width)) img = merge([mask, mask, mask]) for cluster in self. clusters: for pixel in cluster.pixels: img[pixel[0], pixel[1]] = kmeans.cluster_centers_[cluster.label] for num in range(K): mask = np.ones((self.image_height, self.image_width), np.uint8)*0 for cluster in self. clusters: if cluster.label == num: for pixel in cluster.pixels: mask[pixel[0], pixel[1]] = 255 contours, _ = findContours(mask, RETR_LIST, CHAIN_APPROX_NONE) for contour in contours: # if len(contour)<200: continue#You can comment out this line to get the segmentation of small areas for i in contour: img[i[0][1], i[0][0]] = [100, 0, 0] self.data[i[0][1], i[0][0]] = [100, 0, 0] self.save_lab_image("3.png", img) self.save_lab_image("4.png", self.data) if __name__ == '__main__': p = SLICProcessor('5.png', 500, 40) p.iterate_10times() p. generate_result(4) waitKey()
slic function in python:
def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=0, spacing=None, multichannel=True, convert2lab=None, enforce_connectivity=True, min_size_factor=0.5, max_size_factor=3, slic_zero=False): """Segments image using k-means clustering in Color-(x,y,z) space. Parameters ---------- image : 2D, 3D or 4D ndarray Input image, which can be 2D or 3D, and grayscale or multichannel (see `multichannel` parameter). n_segments : int, optional The (approximate) number of labels in the segmented output image. compactness : float, optional Control the balance between color and space, about the height of the square, and the close relationship with the picture, it is best to determine the index level first, and then fine-tune Balances color proximity and space proximity. Higher values give more weight to space proximity, making superpixel shapes more square/cubic. In SLICO mode, this is the initial compactness. This parameter depends strongly on image contrast and on the shapes of objects in the image. We recommend exploring possible values on a log scale, e.g., 0.01, 0.1, 1, 10, 100, before Refining around a chosen value. max_iter : int, optional Maximum number of k-means iterations Maximum number of iterations of k-means. sigma : float or (3,) array-like of floats, optional Gaussian smoothing kernel width when preprocessing each dimension of the image. If given as a scalar value, the same value is applied to each dimension. 0 means Wearing is not smooth. If 'sigma' is scalar and manual voxel spacing is provided, it is automatically scaled (see Notes section). Width of Gaussian smoothing kernel for pre-processing for each dimension of the image. The same sigma is applied to each dimension in case of a scalar value. Zero means no smoothing. Note, that `sigma` is automatically scaled if it is scalar and a manual voxel spacing is provided (see Notes section). spacing : (3,) array-like of floats, optional Represents the voxel space along each dimension of the image. By default, slic assumes a homogeneous space (same voxel resolution along x, y, z axes) Rate), this parameter controls the weight of each axis distance in k-means clustering The voxel spacing along each image dimension. By default, `slic` assumes uniform spacing (same voxel resolution along z, y and x). This parameter controls the weights of the distances along z, y, and x during k-means clustering. multichannel : bool, optional Binary parameter indicating whether the last axis of the image represents multi-channel or another spatial dimension Whether the last axis of the image is to be interpreted as multiple channels or another spatial dimension. convert2lab : bool, optional Binary parameter, judging that the input needs to be transferred to the LAB color space before segmentation. Input must be RGB. When the multi-channel parameter is True, When the number of channels of the input image is 3, this parameter defaults to True Whether the input should be converted to Lab colorspace prior to segmentation. The input image *must* be RGB. Highly recommended. This option defaults to ``True`` when ``multichannel=True`` *and* ``image.shape[-1] == 3``. enforce_connectivity: bool, optional Binary parameter that controls whether the generated split blocks are connected or not Whether the generated segments are connected or not min_size_factor: float, optional The minimum segmentation block ratio to be deleted related to the number of segmentation targets, (probably less than the length * width * height / target number of segmentation results will be fused close) Proportion of the minimum segment size to be removed with respect to the supposed segment size ```depth*width*height/n_segments``` max_size_factor: float, optional Maximum Fusion Ratio Cap Proportion of the maximum connected segment size. A value of 3 works in most of the cases. slic_zero: bool, optional I don't know the so-called zero parameter Run SLIC-zero, the zero-parameter mode of SLIC. [2]_ returns ------- labels: 2D or 3D array Integer mask indicating segment labels. raises ------ ValueError If ``convert2lab`` is set to ``True`` but the last array dimension is not of length 3. notes ----- * If `sigma > 0`, the image is smoothed using a Gaussian kernel prior to segmentation. * If `sigma` is scalar and `spacing` is provided, the kernel width is divided along each dimension by the spacing. For example, if ``sigma=1`` and ``spacing=[5, 1, 1]``, the effective `sigma` is ``[0.2, 1, 1]``. This ensures sensible smoothing for anisotropic images. If there is a smoothing parameter sigma and a voxel space parameter spacing, then the spatial voxel parameter will have an equal impact on the smoothing parameter, such as 1/[5,1,1]=[0.2,1,1] * The image is rescaled to be in [0, 1] prior to processing. The image will be processed as a scalar between [0,1] before preprocessing * Images of shape (M, N, 3) are interpreted as 2D RGB images by default. To interpret them as 3D with the last dimension having length 3, use `multichannel=False`. (M, N, 3) images are 2-dimensional by default (RGB images). To be understood as 3-dimensional images, you need to set the multi-channel parameter = False References ---------- .. [1] Radhakrishna Achanta, Appu Shaji, Kevin Smith, Aurelien Lucchi, Pascal Fua, and Sabine Süsstrunk, SLIC Superpixels Compared to State-of-the-art Superpixel Methods, TPAMI, May 2012. .. [2] http://ivrg.epfl.ch/research/superpixels#SLICO Examples -------- >>> from skimage.segmentation import slice >>> from skimage.data import astronaut >>> img = astronaut() >>> segments = slic(img, n_segments=100, compactness=10) Increasing the compactness parameter yields more square regions: >>> segments = slic(img, n_segments=100, compactness=20) """ ############################################################################################################################## image = img_as_float(image) is_2d = False #2D grayscale image if image.ndim == 2: # 2D grayscale image image = image[np.newaxis, ..., np.newaxis] is_2d = True #Such as 2D RGB image elif image.ndim == 3 and multichannel: # Make 2D multichannel image 3D with depth = 1 image = image[np. newaxis, ...] is_2d = True # such as 3D map elif image.ndim == 3 and not multichannel: # Add channel as single last dimension image = image[..., np. newaxis] # Control the weight of each axis when clustering if spacing is None: spacing = np.ones(3) elif isinstance(spacing, (list, tuple)): spacing = np. array(spacing, dtype=np. double) # Gaussian smoothing if not isinstance(sigma, coll. Iterable): sigma = np. array([sigma, sigma, sigma], dtype=np. double) sigma /= spacing.astype(np.double)#possible voxel division elif isinstance(sigma, (list, tuple)): sigma = np. array(sigma, dtype=np. double) #Gaussian filter if (sigma > 0).any(): # add zero smoothing for multichannel dimension sigma = list(sigma) + [0] image = ndi.gaussian_filter(image, sigma) #Multi-channel RGB image and need to transfer to lab, it can be realized with rab2lab if multichannel and (convert2lab or convert2lab is None): if image.shape[-1] != 3 and convert2lab: raise ValueError("Lab colorspace conversion requires a RGB image.") elif image.shape[-1] == 3: image = rgb2lab(image) depth, height, width = image. shape[:3] # initialize cluster centroids for desired number of segments #In order to achieve the target number of split blocks, initialize the clustering center. #grid_* is equivalent to index #slices are blocks divided according to the target quantity, rounding is required grid_z, grid_y, grid_x = np.mgrid[:depth, :height, :width] slices = regular_grid(image. shape[:3], n_segments) step_z, step_y, step_x = [int(s.step if s.step is not None else 1) for s in slices] segments_z = grid_z[slices] segments_y = grid_y[slices] segments_x = grid_x[slices] segments_color = np.zeros(segments_z.shape + (image.shape[3],)) segments = np. concatenate([segments_z[..., np. newaxis], segments_y[..., np. newaxis], segments_x[..., np. newaxis], segments_color], axis=-1).reshape(-1, 3 + image.shape[3]) segments = np.ascontiguousarray(segments) # we do the scaling of ratio in the same way as in the SLIC paper # so the values have the same meaning step = float(max((step_z, step_y, step_x))) ratio = 1.0 / compactness #Let me go, the show operation that is not square when splitting image = np.ascontiguousarray(image * ratio) labels = _slic_cython(image, segments, step, max_iter, spacing, slic_zero) #Treat the too small and too small if enforce_connectivity: segment_size = depth * height * width / n_segments min_size = int(min_size_factor * segment_size) max_size = int(max_size_factor * segment_size) labels = _enforce_label_connectivity_cython(labels, min_size, max_size) if is_2d: labels = labels[0] return labels
Reference: csdn