-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathimageProcessing.cu
More file actions
316 lines (267 loc) · 11.3 KB
/
imageProcessing.cu
File metadata and controls
316 lines (267 loc) · 11.3 KB
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
/*
LICENCE NOTICE
imageProcessing | A program for image denoising experiments.
Copyright (C) 2021 Matt Hague
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <iostream>
#include <stdio.h>
#include <math.h>
#include <curand.h>
#include <curand_kernel.h>
#include "imageHandling.cuh"
#include "imageOperations.cuh"
#include "imageAlgebra.cuh"
#include "imageFilters.cuh"
#include "imageVariationalMethods.cuh"
#define GAUSSIAN_NOISE_MODE 1
#define SANDP_NOISE_MODE 2
#define MIN_PROCESSING_MODE 0
#define GRAYSCALE_MODE 1
#define SVD_MODE 2
#define FOURIER_LOW_PASS_MODE 3
#define MEDIAN_MODE 4
#define DIFFUSION_MODE 5
#define TOTAL_VARIATION_MODE 6
#define MAX_PROCESSING_MODE 6
#define DEFAULT_NUM_ITERATIONS 1 // SET THIS TO WHATEVER YOU LIKE
#define NO_ALPHA true // SET THIS FALSE IF YOU WANT TO PROCESS AN ALPHA CHANNEL
void printUsage(char *programName) {
printf("Usage:\n");
printf("\t%s <input_file> <output_file> <noise_mode> <processing_mode> <noise_rate> <OPTION:lambda1> <OPTION:num_iterations>\n\n",
programName);
}
void printModes() {
printf("Noise modes:\n");
printf("\t1: Additive white gaussian - has standard deviation of <noise_rate>.\n");
printf("\t2: Salt and pepper - has frequency of <noise_rate>/256.\n\n");
printf("Processing modes:\n");
printf("\t0: NULL - Does nothing.\n");
printf("\t1: Grayscale - An intermediate mode for debugging. No options.\n");
printf("\t2: SVD - Performs a singular value decomposition approximation, keeping the first <lambda1> fraction of singular values.\n");
printf("\t3: Fourier Low Pass - Performs a radial low pass filter on the 2d transform of the image, using parameter <lambda1> for radius. Increasing the radius means that the image will be represented by more frequencies.\n");
printf("\t4: Median - Performs a 3x3 sliding window median filter on the image, using parameter <lambda1> for the number of filter passes. <lambda1> should be a positive integer.\n");
printf("\t5: Diffusion - Performs a variational diffusion on the image, using parameter <lambda1> for the diffusive regularization parameter. <lambda1> should be a positive float.\n");
printf("\t6: Total Variation - Performs a total variation minimization on the image, using parameter <lambda1> for the regularization parameter. <lambda1> should be a positive float.\n\n");
}
int parseArgs(int argc, char *argv[], FILE **inputFile, FILE **outputFile, int *noiseMode, int *processingMode,
double *noiseRate,
double *lambda1, int* num_iters) {
// basic parsing
if (argc < 6) {
printf("Error: invalid number of arguments.\n\n");
printUsage(argv[0]);
printModes();
return 1;
}
*inputFile = fopen(argv[1], "r");
if (*inputFile == NULL) {
printf("Error: could not open %s\n\n", argv[1]);
perror("FATAL");
return 1;
}
*outputFile = fopen(argv[2], "w");
if (*outputFile == NULL) {
printf("Error: could not open %s\n\n", argv[2]);
perror("FATAL");
return 1;
}
*noiseMode = atoi(argv[3]);
if (*noiseMode != GAUSSIAN_NOISE_MODE && *noiseMode != SANDP_NOISE_MODE) {
printf("Error: Invalid noise mode\n\n");
printUsage(argv[0]);
printModes();
return 1;
}
*processingMode = atoi(argv[4]);
if (*processingMode < MIN_PROCESSING_MODE || *processingMode > MAX_PROCESSING_MODE) {
printf("Error: Invalid processing mode\n\n");
printUsage(argv[0]);
printModes();
return 1;
}
*noiseRate = atof(argv[5]);
if (*noiseRate < 0.0 || *noiseRate > 255.0) {
printf("Error: <noise_rate> must be in range [0.0 ... 255.0]\n\n");
printUsage(argv[0]);
return 1;
}
// process optional arguments
if (*processingMode == SVD_MODE || *processingMode == FOURIER_LOW_PASS_MODE) {
if (argc < 7) {
printf("Error: must specify <lambda1>\n\n");
printUsage(argv[0]);
return 1;
}
*lambda1 = atof(argv[6]);
if (*lambda1 < 0.0 || *lambda1 > 1.0) {
printf("Error: <lambda1> must be in range [0.0 ... 1.0]\n\n");
printUsage(argv[0]);
return 1;
}
}
if(*processingMode == MEDIAN_MODE) {
if (argc < 7) {
printf("Error: must specify <lambda1>\n\n");
printUsage(argv[0]);
return 1;
}
*lambda1 = atoi(argv[6]);
if(*lambda1 < 1) {
printf("Error: <lambda1> must be a positive integer\n\n");
printUsage(argv[0]);
return 1;
}
}
if(*processingMode == DIFFUSION_MODE || *processingMode == TOTAL_VARIATION_MODE) {
if (argc < 7) {
printf("Error: must specify <lambda1>\n\n");
printUsage(argv[0]);
return 1;
}
*lambda1 = atof(argv[6]);
if(*lambda1 < 0.0) {
printf("Error: <lambda1> must be a positive float\n\n");
printUsage(argv[0]);
return 1;
}
}
if(argc >= 8) {
*num_iters = atoi(argv[7]);
if(*num_iters < 1) {
printf("Error: <num_iterations> cannot be less than 1");
printUsage(argv[0]);
return 1;
}
}
return 0;
}
void
processImage(double **inputPixelArrays, double **outputPixelArrays, int width, int height, int depth,
int noiseMode, int processingMode,
double noiseRate, double lambda1, int numIterations) {
// image similarity measures
double averageMSE = 0.0;
double averageSSIM = 0.0;
// set up the RNG state
curandState *rngState;
cudaMalloc(&rngState, width * height * sizeof(*rngState));
imageOperations::initRNG(rngState, width * height, time(0));
for (int iteration = 0; iteration < numIterations; iteration++) {
// copy true input pixel data to the output array
for (int i = 0; i < depth; i++) {
cudaMemcpy(outputPixelArrays[i], inputPixelArrays[i], width * height * sizeof(double),
cudaMemcpyHostToHost);
}
// add artificial noise to the image
switch (noiseMode) {
case GAUSSIAN_NOISE_MODE:
imageOperations::addGaussianNoise(outputPixelArrays, rngState, width, height, depth, noiseRate);
break;
case SANDP_NOISE_MODE:
imageOperations::addSANDPNoise(outputPixelArrays, rngState, width, height, depth, noiseRate);
break;
default:
break;
}
// additional variables used in some processing modes
int k;
// denoise the image
switch (processingMode) {
case GRAYSCALE_MODE:
// basic grayscale conversion
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i++) {
double avg = outputPixelArrays[0][j * width + i] + outputPixelArrays[1][j * width + i] +
outputPixelArrays[2][j * width + i] / 3;
outputPixelArrays[0][j * width + i] = avg;
outputPixelArrays[1][j * width + i] = avg;
outputPixelArrays[2][j * width + i] = avg;
}
}
break;
case SVD_MODE:
k = ceil(min(width, height) * lambda1);
imageAlgebra::kSVD(outputPixelArrays, width, height, depth, k);
break;
case FOURIER_LOW_PASS_MODE:
imageFilters::fourierLowPass(outputPixelArrays, width, height, depth, lambda1);
break;
case MEDIAN_MODE:
k = (int) lambda1;
imageFilters::median(outputPixelArrays, width, height, depth, k);
break;
case DIFFUSION_MODE:
imageVariationalMethods::diffusion(outputPixelArrays, width, height, depth, 0.001, lambda1, 250);
break;
case TOTAL_VARIATION_MODE:
imageVariationalMethods::total(outputPixelArrays, width, height, depth, 0.1, lambda1, 250);
break;
default:
break;
}
// accumulate statistics to get average
double mse = imageOperations::computeMSE(inputPixelArrays, outputPixelArrays, width, height, depth);
double ssim = imageOperations::computeSSIM(inputPixelArrays, outputPixelArrays, width, height, depth);
averageMSE += mse / numIterations;
averageSSIM += ssim / numIterations;
}
// numerically stable way to get PSNR
double averagePSNR = imageOperations::computePSNR(averageMSE);
// display final statistics
printf("Number of trials: %d\n", numIterations);
printf("Average MSE: %f\n", averageMSE); // mean squared error
printf("Average PSNR: %f dB\n", averagePSNR); // peak signal-to-noise ratio
printf("Average SSIM: %f\n", averageSSIM); // structure similarity index measurement
// free the RNG state
cudaFree(rngState);
}
int main(int argc, char *argv[]) {
// parse arguments
FILE *inputFile;
FILE *outputFile;
int noiseMode;
int processingMode;
double noiseRate;
double lambda1;
int numIterations = DEFAULT_NUM_ITERATIONS;
int err = parseArgs(argc, argv, &inputFile, &outputFile, &noiseMode, &processingMode, &noiseRate, &lambda1, &numIterations);
if (err) {
return 1;
}
// load image
int width; // image width
int height; // image height
int depth; // number of color planes [e.g. 4 for RGBA]
imageHandling::getImageDimensions(inputFile, &width, &height, &depth);
double **inputPixelArrays; // pointers to arrays that hold pixel data
double **outputPixelArrays;
inputPixelArrays = imageHandling::allocateImage(depth, width, height);
outputPixelArrays = imageHandling::allocateImage(depth, width, height);
imageHandling::loadImage(inputFile, inputPixelArrays, width, height, depth);
// set effective depth
int effectiveDepth = depth;
if (NO_ALPHA && (effectiveDepth == 4)) {
effectiveDepth -= 1;
cudaMemcpy(outputPixelArrays[depth - 1], inputPixelArrays[depth - 1], width * height * sizeof(double),
cudaMemcpyHostToHost);
}
// process the image according to user parameters
processImage(inputPixelArrays, outputPixelArrays, width, height, effectiveDepth, noiseMode, processingMode,
noiseRate, lambda1, numIterations);
// save output and free memory
imageHandling::saveImage(outputFile, outputPixelArrays, width, height, depth);
imageHandling::freeImage(inputPixelArrays, depth);
imageHandling::freeImage(outputPixelArrays, depth);
cudaDeviceReset();
return 0;
}