├── dog.png
├── dog_segmentation.png
├── README.md
├── test_slic.cpp
├── slic.h
└── slic.cpp
/dog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PSMM/SLIC-Superpixels/HEAD/dog.png
--------------------------------------------------------------------------------
/dog_segmentation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PSMM/SLIC-Superpixels/HEAD/dog_segmentation.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SLIC Superpixel Implementation
2 | This repository contains an implementation of the SLIC Superpixel algorithm by Achanta et al. (PAMI'12, vol. 34, num. 11, pp. 2274-2282). The C++ implementation is created to work with the strutures of OpenCV.
3 |
4 | ## Exemplary result
5 | The images below shows an example of an over-segmentation using 400 superpixels and a weight factor of 40.
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test_slic.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * test_slic.cpp.
3 | *
4 | * Written by: Pascal Mettes.
5 | *
6 | * This file creates an over-segmentation of a provided image based on the SLIC
7 | * superpixel algorithm, as implemented in slic.h and slic.cpp.
8 | */
9 |
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | using namespace std;
17 |
18 | #include "slic.h"
19 |
20 | int main(int argc, char *argv[]) {
21 | /* Load the image and convert to Lab colour space. */
22 | IplImage *image = cvLoadImage(argv[1], 1);
23 | IplImage *lab_image = cvCloneImage(image);
24 | cvCvtColor(image, lab_image, CV_BGR2Lab);
25 |
26 | /* Yield the number of superpixels and weight-factors from the user. */
27 | int w = image->width, h = image->height;
28 | int nr_superpixels = atoi(argv[2]);
29 | int nc = atoi(argv[3]);
30 |
31 | double step = sqrt((w * h) / (double) nr_superpixels);
32 |
33 | /* Perform the SLIC superpixel algorithm. */
34 | Slic slic;
35 | slic.generate_superpixels(lab_image, step, nc);
36 | slic.create_connectivity(lab_image);
37 |
38 | /* Display the contours and show the result. */
39 | slic.display_contours(image, CV_RGB(255,0,0));
40 | cvShowImage("result", image);
41 | cvWaitKey(0);
42 | cvSaveImage(argv[4], image);
43 | }
44 |
--------------------------------------------------------------------------------
/slic.h:
--------------------------------------------------------------------------------
1 | #ifndef SLIC_H
2 | #define SLIC_H
3 |
4 | /* slic.h.
5 | *
6 | * Written by: Pascal Mettes.
7 | *
8 | * This file contains the class elements of the class Slic. This class is an
9 | * implementation of the SLIC Superpixel algorithm by Achanta et al. [PAMI'12,
10 | * vol. 34, num. 11, pp. 2274-2282].
11 | *
12 | * This implementation is created for the specific purpose of creating
13 | * over-segmentations in an OpenCV-based environment.
14 | */
15 |
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 | #include
22 | using namespace std;
23 |
24 | /* 2d matrices are handled by 2d vectors. */
25 | #define vec2dd vector >
26 | #define vec2di vector >
27 | #define vec2db vector >
28 | /* The number of iterations run by the clustering algorithm. */
29 | #define NR_ITERATIONS 10
30 |
31 | /*
32 | * class Slic.
33 | *
34 | * In this class, an over-segmentation is created of an image, provided by the
35 | * step-size (distance between initial cluster locations) and the colour
36 | * distance parameter.
37 | */
38 | class Slic {
39 | private:
40 | /* The cluster assignments and distance values for each pixel. */
41 | vec2di clusters;
42 | vec2dd distances;
43 |
44 | /* The LAB and xy values of the centers. */
45 | vec2dd centers;
46 | /* The number of occurences of each center. */
47 | vector center_counts;
48 |
49 | /* The step size per cluster, and the colour (nc) and distance (ns)
50 | * parameters. */
51 | int step, nc, ns;
52 |
53 | /* Compute the distance between a center and an individual pixel. */
54 | double compute_dist(int ci, CvPoint pixel, CvScalar colour);
55 | /* Find the pixel with the lowest gradient in a 3x3 surrounding. */
56 | CvPoint find_local_minimum(IplImage *image, CvPoint center);
57 |
58 | /* Remove and initialize the 2d vectors. */
59 | void clear_data();
60 | void init_data(IplImage *image);
61 |
62 | public:
63 | /* Class constructors and deconstructors. */
64 | Slic();
65 | ~Slic();
66 |
67 | /* Generate an over-segmentation for an image. */
68 | void generate_superpixels(IplImage *image, int step, int nc);
69 | /* Enforce connectivity for an image. */
70 | void create_connectivity(IplImage *image);
71 |
72 | /* Draw functions. Resp. displayal of the centers and the contours. */
73 | void display_center_grid(IplImage *image, CvScalar colour);
74 | void display_contours(IplImage *image, CvScalar colour);
75 | void colour_with_cluster_means(IplImage *image);
76 | };
77 |
78 | #endif
79 |
--------------------------------------------------------------------------------
/slic.cpp:
--------------------------------------------------------------------------------
1 | #include "slic.h"
2 |
3 | /*
4 | * Constructor. Nothing is done here.
5 | */
6 | Slic::Slic() {
7 |
8 | }
9 |
10 | /*
11 | * Destructor. Clear any present data.
12 | */
13 | Slic::~Slic() {
14 | clear_data();
15 | }
16 |
17 | /*
18 | * Clear the data as saved by the algorithm.
19 | *
20 | * Input : -
21 | * Output: -
22 | */
23 | void Slic::clear_data() {
24 | clusters.clear();
25 | distances.clear();
26 | centers.clear();
27 | center_counts.clear();
28 | }
29 |
30 | /*
31 | * Initialize the cluster centers and initial values of the pixel-wise cluster
32 | * assignment and distance values.
33 | *
34 | * Input : The image (IplImage*).
35 | * Output: -
36 | */
37 | void Slic::init_data(IplImage *image) {
38 | /* Initialize the cluster and distance matrices. */
39 | for (int i = 0; i < image->width; i++) {
40 | vector cr;
41 | vector dr;
42 | for (int j = 0; j < image->height; j++) {
43 | cr.push_back(-1);
44 | dr.push_back(FLT_MAX);
45 | }
46 | clusters.push_back(cr);
47 | distances.push_back(dr);
48 | }
49 |
50 | /* Initialize the centers and counters. */
51 | for (int i = step; i < image->width - step/2; i += step) {
52 | for (int j = step; j < image->height - step/2; j += step) {
53 | vector center;
54 | /* Find the local minimum (gradient-wise). */
55 | CvPoint nc = find_local_minimum(image, cvPoint(i,j));
56 | CvScalar colour = cvGet2D(image, nc.y, nc.x);
57 |
58 | /* Generate the center vector. */
59 | center.push_back(colour.val[0]);
60 | center.push_back(colour.val[1]);
61 | center.push_back(colour.val[2]);
62 | center.push_back(nc.x);
63 | center.push_back(nc.y);
64 |
65 | /* Append to vector of centers. */
66 | centers.push_back(center);
67 | center_counts.push_back(0);
68 | }
69 | }
70 | }
71 |
72 | /*
73 | * Compute the distance between a cluster center and an individual pixel.
74 | *
75 | * Input : The cluster index (int), the pixel (CvPoint), and the Lab values of
76 | * the pixel (CvScalar).
77 | * Output: The distance (double).
78 | */
79 | double Slic::compute_dist(int ci, CvPoint pixel, CvScalar colour) {
80 | double dc = sqrt(pow(centers[ci][0] - colour.val[0], 2) + pow(centers[ci][1]
81 | - colour.val[1], 2) + pow(centers[ci][2] - colour.val[2], 2));
82 | double ds = sqrt(pow(centers[ci][3] - pixel.x, 2) + pow(centers[ci][4] - pixel.y, 2));
83 |
84 | return sqrt(pow(dc / nc, 2) + pow(ds / ns, 2));
85 |
86 | //double w = 1.0 / (pow(ns / nc, 2));
87 | //return sqrt(dc) + sqrt(ds * w);
88 | }
89 |
90 | /*
91 | * Find a local gradient minimum of a pixel in a 3x3 neighbourhood. This
92 | * method is called upon initialization of the cluster centers.
93 | *
94 | * Input : The image (IplImage*) and the pixel center (CvPoint).
95 | * Output: The local gradient minimum (CvPoint).
96 | */
97 | CvPoint Slic::find_local_minimum(IplImage *image, CvPoint center) {
98 | double min_grad = FLT_MAX;
99 | CvPoint loc_min = cvPoint(center.x, center.y);
100 |
101 | for (int i = center.x-1; i < center.x+2; i++) {
102 | for (int j = center.y-1; j < center.y+2; j++) {
103 | CvScalar c1 = cvGet2D(image, j+1, i);
104 | CvScalar c2 = cvGet2D(image, j, i+1);
105 | CvScalar c3 = cvGet2D(image, j, i);
106 | /* Convert colour values to grayscale values. */
107 | double i1 = c1.val[0];
108 | double i2 = c2.val[0];
109 | double i3 = c3.val[0];
110 | /*double i1 = c1.val[0] * 0.11 + c1.val[1] * 0.59 + c1.val[2] * 0.3;
111 | double i2 = c2.val[0] * 0.11 + c2.val[1] * 0.59 + c2.val[2] * 0.3;
112 | double i3 = c3.val[0] * 0.11 + c3.val[1] * 0.59 + c3.val[2] * 0.3;*/
113 |
114 | /* Compute horizontal and vertical gradients and keep track of the
115 | minimum. */
116 | if (sqrt(pow(i1 - i3, 2)) + sqrt(pow(i2 - i3,2)) < min_grad) {
117 | min_grad = fabs(i1 - i3) + fabs(i2 - i3);
118 | loc_min.x = i;
119 | loc_min.y = j;
120 | }
121 | }
122 | }
123 |
124 | return loc_min;
125 | }
126 |
127 | /*
128 | * Compute the over-segmentation based on the step-size and relative weighting
129 | * of the pixel and colour values.
130 | *
131 | * Input : The Lab image (IplImage*), the stepsize (int), and the weight (int).
132 | * Output: -
133 | */
134 | void Slic::generate_superpixels(IplImage *image, int step, int nc) {
135 | this->step = step;
136 | this->nc = nc;
137 | this->ns = step;
138 |
139 | /* Clear previous data (if any), and re-initialize it. */
140 | clear_data();
141 | init_data(image);
142 |
143 | /* Run EM for 10 iterations (as prescribed by the algorithm). */
144 | for (int i = 0; i < NR_ITERATIONS; i++) {
145 | /* Reset distance values. */
146 | for (int j = 0; j < image->width; j++) {
147 | for (int k = 0;k < image->height; k++) {
148 | distances[j][k] = FLT_MAX;
149 | }
150 | }
151 |
152 | for (int j = 0; j < (int) centers.size(); j++) {
153 | /* Only compare to pixels in a 2 x step by 2 x step region. */
154 | for (int k = centers[j][3] - step; k < centers[j][3] + step; k++) {
155 | for (int l = centers[j][4] - step; l < centers[j][4] + step; l++) {
156 |
157 | if (k >= 0 && k < image->width && l >= 0 && l < image->height) {
158 | CvScalar colour = cvGet2D(image, l, k);
159 | double d = compute_dist(j, cvPoint(k,l), colour);
160 |
161 | /* Update cluster allocation if the cluster minimizes the
162 | distance. */
163 | if (d < distances[k][l]) {
164 | distances[k][l] = d;
165 | clusters[k][l] = j;
166 | }
167 | }
168 | }
169 | }
170 | }
171 |
172 | /* Clear the center values. */
173 | for (int j = 0; j < (int) centers.size(); j++) {
174 | centers[j][0] = centers[j][1] = centers[j][2] = centers[j][3] = centers[j][4] = 0;
175 | center_counts[j] = 0;
176 | }
177 |
178 | /* Compute the new cluster centers. */
179 | for (int j = 0; j < image->width; j++) {
180 | for (int k = 0; k < image->height; k++) {
181 | int c_id = clusters[j][k];
182 |
183 | if (c_id != -1) {
184 | CvScalar colour = cvGet2D(image, k, j);
185 |
186 | centers[c_id][0] += colour.val[0];
187 | centers[c_id][1] += colour.val[1];
188 | centers[c_id][2] += colour.val[2];
189 | centers[c_id][3] += j;
190 | centers[c_id][4] += k;
191 |
192 | center_counts[c_id] += 1;
193 | }
194 | }
195 | }
196 |
197 | /* Normalize the clusters. */
198 | for (int j = 0; j < (int) centers.size(); j++) {
199 | centers[j][0] /= center_counts[j];
200 | centers[j][1] /= center_counts[j];
201 | centers[j][2] /= center_counts[j];
202 | centers[j][3] /= center_counts[j];
203 | centers[j][4] /= center_counts[j];
204 | }
205 | }
206 | }
207 |
208 | /*
209 | * Enforce connectivity of the superpixels. This part is not actively discussed
210 | * in the paper, but forms an active part of the implementation of the authors
211 | * of the paper.
212 | *
213 | * Input : The image (IplImage*).
214 | * Output: -
215 | */
216 | void Slic::create_connectivity(IplImage *image) {
217 | int label = 0, adjlabel = 0;
218 | const int lims = (image->width * image->height) / ((int)centers.size());
219 |
220 | const int dx4[4] = {-1, 0, 1, 0};
221 | const int dy4[4] = { 0, -1, 0, 1};
222 |
223 | /* Initialize the new cluster matrix. */
224 | vec2di new_clusters;
225 | for (int i = 0; i < image->width; i++) {
226 | vector nc;
227 | for (int j = 0; j < image->height; j++) {
228 | nc.push_back(-1);
229 | }
230 | new_clusters.push_back(nc);
231 | }
232 |
233 | for (int i = 0; i < image->width; i++) {
234 | for (int j = 0; j < image->height; j++) {
235 | if (new_clusters[i][j] == -1) {
236 | vector elements;
237 | elements.push_back(cvPoint(i, j));
238 |
239 | /* Find an adjacent label, for possible use later. */
240 | for (int k = 0; k < 4; k++) {
241 | int x = elements[0].x + dx4[k], y = elements[0].y + dy4[k];
242 |
243 | if (x >= 0 && x < image->width && y >= 0 && y < image->height) {
244 | if (new_clusters[x][y] >= 0) {
245 | adjlabel = new_clusters[x][y];
246 | }
247 | }
248 | }
249 |
250 | int count = 1;
251 | for (int c = 0; c < count; c++) {
252 | for (int k = 0; k < 4; k++) {
253 | int x = elements[c].x + dx4[k], y = elements[c].y + dy4[k];
254 |
255 | if (x >= 0 && x < image->width && y >= 0 && y < image->height) {
256 | if (new_clusters[x][y] == -1 && clusters[i][j] == clusters[x][y]) {
257 | elements.push_back(cvPoint(x, y));
258 | new_clusters[x][y] = label;
259 | count += 1;
260 | }
261 | }
262 | }
263 | }
264 |
265 | /* Use the earlier found adjacent label if a segment size is
266 | smaller than a limit. */
267 | if (count <= lims >> 2) {
268 | for (int c = 0; c < count; c++) {
269 | new_clusters[elements[c].x][elements[c].y] = adjlabel;
270 | }
271 | label -= 1;
272 | }
273 | label += 1;
274 | }
275 | }
276 | }
277 | }
278 |
279 | /*
280 | * Display the cluster centers.
281 | *
282 | * Input : The image to display upon (IplImage*) and the colour (CvScalar).
283 | * Output: -
284 | */
285 | void Slic::display_center_grid(IplImage *image, CvScalar colour) {
286 | for (int i = 0; i < (int) centers.size(); i++) {
287 | cvCircle(image, cvPoint(centers[i][3], centers[i][4]), 2, colour, 2);
288 | }
289 | }
290 |
291 | /*
292 | * Display a single pixel wide contour around the clusters.
293 | *
294 | * Input : The target image (IplImage*) and contour colour (CvScalar).
295 | * Output: -
296 | */
297 | void Slic::display_contours(IplImage *image, CvScalar colour) {
298 | const int dx8[8] = {-1, -1, 0, 1, 1, 1, 0, -1};
299 | const int dy8[8] = { 0, -1, -1, -1, 0, 1, 1, 1};
300 |
301 | /* Initialize the contour vector and the matrix detailing whether a pixel
302 | * is already taken to be a contour. */
303 | vector contours;
304 | vec2db istaken;
305 | for (int i = 0; i < image->width; i++) {
306 | vector nb;
307 | for (int j = 0; j < image->height; j++) {
308 | nb.push_back(false);
309 | }
310 | istaken.push_back(nb);
311 | }
312 |
313 | /* Go through all the pixels. */
314 | for (int i = 0; i < image->width; i++) {
315 | for (int j = 0; j < image->height; j++) {
316 | int nr_p = 0;
317 |
318 | /* Compare the pixel to its 8 neighbours. */
319 | for (int k = 0; k < 8; k++) {
320 | int x = i + dx8[k], y = j + dy8[k];
321 |
322 | if (x >= 0 && x < image->width && y >= 0 && y < image->height) {
323 | if (istaken[x][y] == false && clusters[i][j] != clusters[x][y]) {
324 | nr_p += 1;
325 | }
326 | }
327 | }
328 |
329 | /* Add the pixel to the contour list if desired. */
330 | if (nr_p >= 2) {
331 | contours.push_back(cvPoint(i,j));
332 | istaken[i][j] = true;
333 | }
334 | }
335 | }
336 |
337 | /* Draw the contour pixels. */
338 | for (int i = 0; i < (int)contours.size(); i++) {
339 | cvSet2D(image, contours[i].y, contours[i].x, colour);
340 | }
341 | }
342 |
343 | /*
344 | * Give the pixels of each cluster the same colour values. The specified colour
345 | * is the mean RGB colour per cluster.
346 | *
347 | * Input : The target image (IplImage*).
348 | * Output: -
349 | */
350 | void Slic::colour_with_cluster_means(IplImage *image) {
351 | vector colours(centers.size());
352 |
353 | /* Gather the colour values per cluster. */
354 | for (int i = 0; i < image->width; i++) {
355 | for (int j = 0; j < image->height; j++) {
356 | int index = clusters[i][j];
357 | CvScalar colour = cvGet2D(image, j, i);
358 |
359 | colours[index].val[0] += colour.val[0];
360 | colours[index].val[1] += colour.val[1];
361 | colours[index].val[2] += colour.val[2];
362 | }
363 | }
364 |
365 | /* Divide by the number of pixels per cluster to get the mean colour. */
366 | for (int i = 0; i < (int)colours.size(); i++) {
367 | colours[i].val[0] /= center_counts[i];
368 | colours[i].val[1] /= center_counts[i];
369 | colours[i].val[2] /= center_counts[i];
370 | }
371 |
372 | /* Fill in. */
373 | for (int i = 0; i < image->width; i++) {
374 | for (int j = 0; j < image->height; j++) {
375 | CvScalar ncolour = colours[clusters[i][j]];
376 | cvSet2D(image, j, i, ncolour);
377 | }
378 | }
379 | }
380 |
--------------------------------------------------------------------------------