原理
超像素概念是2003年Xiaofeng Ren提出和發展起來的圖像分割技術,是指具有相似紋理、顏色、亮度等特徵的相鄰像素構成的有一定視覺意義的不規則像素塊。它利用像素之間特徵的相似性將像素分組,用少量的超像素代替大量的像素來表達圖片特徵,很大程度上降低了圖像後處理的複雜度,所以通常作爲分割算法的預處理步驟。
常見的超像素分割方法包括: Graph-based 、NCut 、Turbopixel 、 Quick-shift 、 Graph-cut a、Graph-cut b 以及 SLIC 。
其中,SLIC(simple linear iterativeclustering),即 簡單線性迭代聚類 。 它是2010年提出的一種思想簡單、實現方便的算法,將彩色圖像轉化爲CIELAB顏色空間和XY座標下的5維特徵向量,然後對5維特徵向量構造距離度量標準,對圖像像素進行局部聚類的過程。
SLIC主要優點如下:
- 生成的超像素如同細胞一般緊湊整齊,鄰域特徵比較容易表達。這樣基於像素的方法可以比較容易的改造爲基於超像素的方法。
- 不僅可以分割彩色圖,也可以兼容分割灰度圖。
- 需要設置的參數非常少,默認情況下只需要設置一個預分割的超像素的數量。
- 相比其他的超像素分割方法,SLIC在運行速度、生成超像素的緊湊度、輪廓保持方面都比較理想。
效果圖
經過觀察發現,在迭代至第10輪後,分割效果基本不再發生變化。
原圖
K=64 時
第1輪迭代,效果圖:
第20輪迭代,效果圖:
K=128 時
第1輪迭代,效果圖:
第20輪迭代,效果圖:
K=256 時
第1輪迭代,效果圖:
第20輪迭代,效果圖:
K=1024 時
第1輪迭代,效果圖:
第20輪迭代,效果圖:
實現代碼
代碼是我上網找來的,稍微改動了一丟丟。
原代碼出處:SLIC算法分割超像素原理及Python實現
import math from skimage import io, color import numpy as np from tqdm import trange 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.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) 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 """ rgb_arr = color.lab2rgb(lab_arr) io.imsave(path, rgb_arr) def make_cluster(self, h, 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[w + 1][h + 1][0] - self.data[w][h][0] + \ self.data[w + 1][h + 1][1] - self.data[w][h][1] + \ self.data[w + 1][h + 1][2] - self.data[w][h][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 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 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 = sum_h / number _w = 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) 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(name, image_arr) def iterate_10times(self): self.init_clusters() self.move_clusters() for i in trange(20): self.assignment() self.update_cluster() name = 'Elegent_Girl_M{m}_K{k}_loop{loop}.jpg'.format(loop=i, m=self.M, k=self.K) self.save_current_image(name) if __name__ == '__main__': for k in [64, 128, 256, 1024]: p = SLICProcessor('800.jpg', k, 30) p.iterate_10times()
打印結果:
0%| | 0/20 [00:00<?, ?it/s]/home/user/anaconda2/lib/python2.7/site-packages/skimage/util/dtype.py:111: UserWarning: Possible precision loss when converting from float64 to uint8 "%s to %s" % (dtypeobj_in, dtypeobj)) 100%|##########| 20/20 [32:36<00:00, 97.83s/it] 100%|##########| 20/20 [24:37<00:00, 73.88s/it] 100%|##########| 20/20 [21:30<00:00, 64.55s/it] 100%|##########| 20/20 [18:49<00:00, 56.46s/it] Process finished with exit code 0