-
Notifications
You must be signed in to change notification settings - Fork 0
/
image_sementation.cc
321 lines (252 loc) · 10.8 KB
/
image_sementation.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
// igraph includes
#include <igraph.h>
// opencv includes
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
// TODO
// - Use colored images for graph generation (Done)
// - Implement superpixels procedure
// - Do a shitload of tests
/// Show program usage
void usage(const char *program_name)
{
std::cerr << "Usage: " << program_name << " <image name> "
"<neighbourhood radius> <pixel similarity constant> "
"[reduce: 1..3 (default: 1)]" << std::endl;
}
int main(int argc, char *argv[])
{
// Step 0: process CLI arguments
std::cerr << "ArgsParse... ";
char *image_name; // input: image file name
int nhood_radius; // input: pixel area scan radius
float similarity_const; // input: pixel similarity threshold
int imread_flags; // possible input: image downscaling factor
int arg_error = 0; // arguments parse error flag
if (argc < 4) {
arg_error = 1;
} else {
// Required
image_name = argv[1];
nhood_radius = atoi(argv[2]);
similarity_const = atof(argv[3]);
// Parse downscaling factor, if provided
int reduction_idx = argc > 4 ? atoi(argv[4]) : 1;
if (nhood_radius && similarity_const && reduction_idx > 0 && reduction_idx <= 4) {
const int imread_possible_flags[] = {
cv::IMREAD_COLOR,
cv::IMREAD_REDUCED_COLOR_2,
cv::IMREAD_REDUCED_COLOR_4,
cv::IMREAD_REDUCED_COLOR_8,
};
// Reduction = 1 means no downscaling at all
imread_flags = imread_possible_flags[reduction_idx - 1];
} else {
arg_error = 1;
}
}
// Failure: TODO
if (arg_error) {
usage(argv[0]);
return EXIT_FAILURE;
}
std::cerr << "Done." << std::endl;
// Step 0: end
// Step 1: retrieve the image
std::cerr << "Reading image... ";
// Image to be processed, possibly downscaled
cv::Mat image = cv::imread(image_name, imread_flags);
// Failure: TODO
if (image.empty()) {
std::cerr << "Failure on image read." << std::endl;
return EXIT_FAILURE;
}
// Normalized copy
cv::Mat image_f; // image copy, normalized
image.convertTo(image_f, CV_32F, 1.0f/255.0f);
// Failure: TODO
if (image_f.empty()) {
std::cerr << "Failure on image convert." << std::endl;
return EXIT_FAILURE;
}
std::cerr << "Done. Image is " << image.cols << " by " << image.rows << ", ";
std::cerr << (image.channels() > 1 ? "colored." : "grayscale.") << std::endl;
// Ste 1: end
// Step 2.1: build graph from image
std::cerr << "Building graph... ";
igraph_t graph; // graph object
// Failure: TODO
if (igraph_empty(&graph, image.rows*image.cols, IGRAPH_UNDIRECTED)) {
std::cerr << "Failure on graph allocation." << std::endl;
return EXIT_FAILURE;
}
igraph_vector_t edges; // graph edge array
// Failure: TODO
if (igraph_vector_init(&edges, 0)) {
std::cerr << "Failure on edge array allocation." << std::endl;
return EXIT_FAILURE;
}
int nch = image_f.channels();
// Relate pixels that satisfy the weight function W(i,j) = 1 − |Ii−Ij| ≥ t
// -> Since our values are normalized, 1 is replaced with 255 (max intensity)
// and t is chosen to be 254 (arbitrary measure, has to be studied better)
for (int irow = 0; irow < image_f.rows; irow++) {
float *scanline = image_f.ptr<float>(irow);
for (int icol = 0; icol < image_f.cols; icol++) {
float bvalue = scanline[icol*nch+0];
float gvalue = scanline[icol*nch+1];
float rvalue = scanline[icol*nch+2];
//std::cerr << " Pixel " << irow*image_f.cols+icol << " has value " << int(value) << " and ";
// Define neighborhood around the pixel
int irow_n_min = std::max(0, irow-nhood_radius);
int icol_n_min = std::max(0, icol-nhood_radius);
int irow_n_max = std::min(image_f.rows, irow+nhood_radius);
int icol_n_max = std::min(image_f.cols, icol+nhood_radius);
//std::cerr << (irow_n_max-irow_n_min)*(icol_n_max-icol_n_min) << " neighbours." << std::endl;
// Look for similar pixels in this neighboorhood
for (int irow_n = irow_n_min; irow_n < irow_n_max; irow_n++) {
float *neighbour_scanline = image_f.ptr<float>(irow_n);
for (int icol_n = icol_n_min; icol_n < icol_n_max; icol_n++) {
float neighbour_bvalue = neighbour_scanline[icol_n*nch+0];
float neighbour_gvalue = neighbour_scanline[icol_n*nch+1];
float neighbour_rvalue = neighbour_scanline[icol_n*nch+2];
// distance function
float similarity = 1.0f - sqrtf((bvalue-neighbour_bvalue)*(bvalue-neighbour_bvalue)
+(gvalue-neighbour_gvalue)*(gvalue-neighbour_gvalue)
+(rvalue-neighbour_rvalue)*(rvalue-neighbour_rvalue))
/sqrtf(3.0f);
// If the pixels have similarity_const greater than a certain measure, link them
if (similarity >= similarity_const) {
igraph_vector_push_back(&edges, irow*image_f.cols+icol);
igraph_vector_push_back(&edges, irow_n*image_f.cols+icol_n);
}
}
}
}
}
// Add edges as batch (gotta go fast)
if (igraph_add_edges(&graph, &edges, NULL)) {
std::cerr << "Failure adding edges to graph." << std::endl;
std::cerr << "Are all of your vertex IDs valid?" << std::endl;
std::cerr << "Does your edge vector have an odd length?" << std::endl;
return EXIT_FAILURE;
}
if (igraph_simplify(&graph, 1, 1, NULL)) {
std::cerr << "Failure simplifying graph." << std::endl;
std::cerr << "You're probably out of memory." << std::endl;
return EXIT_FAILURE;
}
std::cerr << "Done. Graph has " << igraph_ecount(&graph) << " edges." << std::endl;
// Step 2.1: end
//#if 0
// Step 2.2: write graph to a file
std::cerr << "Writing to file... ";
{
using std::ofstream;
ofstream graph_file; // output graph to this file
graph_file.open("image_to_graph.out", ofstream::out|ofstream::trunc);
// Downsampled image dims outputted as header
graph_file << image.rows << " " << image.cols << std::endl;
// Graph is outputted to file as an edgelist
for (igraph_integer_t iedge = 0; iedge < igraph_ecount(&graph); iedge++) {
igraph_integer_t node1; // connected node 1
igraph_integer_t node2; // connected node 2
igraph_edge(&graph, iedge, &node1, &node2);
graph_file << node1 << " " << node2 << std::endl;
}
graph_file.close();
}
std::cerr << "Done." << std::endl;
// Step 2.2: end
//#endif
// Step 2.3: evaluate communities and separate segments
std::cerr << "Evaluating communities... ";
igraph_matrix_t merges; // Merge steps array
igraph_matrix_init(&merges, 0, 0);
igraph_vector_t modularity; // Graph mudularity array
igraph_vector_init(&modularity, 0);
igraph_vector_t membership; // Community membership arrays
igraph_vector_init(&membership, 0);
// Apply fastgreedy algorithm
igraph_community_fastgreedy(&graph, /*weights*/ NULL, &merges, &modularity,
/*membership vector*/ NULL);
std::cerr << "Done. ";
// Membership vector
size_t imod = igraph_vector_which_max(&modularity); // max modularity index
igraph_community_to_membership(&merges, igraph_vcount(&graph), imod, &membership, 0);
#if 0
igraph_reindex_membership(&membership, NULL);
std::vector<int> comm_counts{(int)igraph_vector_max(&membership), 0};
for (int icomm_count = 0; icomm_count < igraph_vector_size(&membership); icomm_count++)
comm_counts[VECTOR(membership)[icomm_count]]++;
std::sort(comm_counts.rbegin(), comm_counts.rend());
int smol_community;
for (int icomm = 0; icomm < comm_counts.size(); icomm++) {
if (comm_counts[icomm] <= 10) {
smol_community = icomm;
break;
}
}
std::cerr << std::endl << "Index of first `1' is " << smol_community << std::endl;
for (int ipixel = smol_community; ipixel < igraph_vector_size(&membership); ipixel++) {
if (VECTOR(membership)[ipixel] > smol_community)
VECTOR(membership)[ipixel] = smol_community;
}
#endif
std::cerr << "Max modularity is " << VECTOR(modularity)[imod]
<< " with " << (int)igraph_vector_max(&membership)
<< " communities." << std::endl;
// Paint segments
std::cerr << "Painting segments... ";
cv::Mat image_s; // image copy, to be painted
cv::cvtColor(image, image_s, cv::COLOR_BGR2GRAY);
// Failure: TODO
if (image_s.empty()) {
std::cerr << "Failure on image color to gray (cvtColor)." << std::endl;
return EXIT_FAILURE;
}
float max_seg_val = igraph_vector_max(&membership); // max segment value, for normalization
for (long ipixel = 0; ipixel < image_s.rows*image_s.cols; ipixel++)
image_s.data[ipixel] = (uchar)(VECTOR(membership)[ipixel]*255.0f/max_seg_val);
//image_s.data[ipixel] = (uchar)(VECTOR(membership)[ipixel]);
cv::equalizeHist(image_s, image_s);
std::cerr << "Done." << std::endl;
// Step 2.3: end
// Step 3: show segmentation results
cv::Mat colored_segments;
cv::applyColorMap(image_s, colored_segments, cv::COLORMAP_RAINBOW);
cv::namedWindow("Output", cv::WINDOW_NORMAL);
cv::namedWindow("Original", cv::WINDOW_NORMAL);
cv::resizeWindow("Output", image.cols*5, image.rows*5);
cv::resizeWindow("Original", image_s.cols*5, image_s.rows*5);
cv::imshow("Original", image);
cv::imshow("Output", colored_segments);
while((cv::waitKey() & 0xEFFFFF) != 27);
// Write to file
std::stringstream output_name;
output_name << image_name << "." << nhood_radius << "_" << similarity_const
<< "_" << imread_flags << ".png";
cv::imwrite(output_name.str(), colored_segments);
// Step 3: end
// Step 4: cleanup & goodbye
std::cerr << "Cleaning up memory... ";
// Community allocation stuff
igraph_vector_destroy(&membership);
igraph_vector_destroy(&modularity);
igraph_matrix_destroy(&merges);
// Graph structure stuff
igraph_vector_destroy(&edges);
igraph_destroy(&graph);
// Images
image_s.release();
image_f.release();
image.release();
std::cerr << "Done." << std::endl;
// Step 4: end
return EXIT_SUCCESS;
}