37 #include <boost/format.hpp>
39 #include <boost/program_options/options_description.hpp>
40 #include <boost/program_options/parsers.hpp>
41 #include <boost/program_options/variables_map.hpp>
44 #include "DGtal/base/Common.h"
45 #include "DGtal/helpers/StdDefs.h"
46 #include "DGtal/io/readers/GenericReader.h"
47 #include "DGtal/io/writers/GenericWriter.h"
178 using namespace DGtal;
180 int main(
int argc,
char* argv[] )
185 namespace po = boost::program_options;
186 po::options_description general_opt(
"Allowed options are: ");
187 general_opt.add_options()
188 (
"help,h",
"display this message")
189 (
"input,i", po::value<string>(),
"the input image PPM filename." )
190 (
"inpainting-mask,m", po::value<string>(),
"the input inpainting mask filename." )
191 (
"output,o", po::value<string>()->default_value(
"AT" ),
"the output image basename." )
192 (
"lambda,l", po::value<double>(),
"the parameter lambda." )
193 (
"lambda-1,1", po::value<double>()->default_value( 0.3125 ),
"the initial parameter lambda (l1)." )
194 (
"lambda-2,2", po::value<double>()->default_value( 0.0005 ),
"the final parameter lambda (l2)." )
195 (
"lambda-ratio,q", po::value<double>()->default_value( sqrt(2) ),
"the division ratio for lambda from l1 to l2." )
196 (
"alpha,a", po::value<double>()->default_value( 1.0 ),
"the parameter alpha." )
197 (
"epsilon,e", po::value<double>(),
"the initial and final parameter epsilon of AT functional at the same time." )
198 (
"epsilon-1", po::value<double>()->default_value( 2.0 ),
"the initial parameter epsilon." )
199 (
"epsilon-2", po::value<double>()->default_value( 0.25 ),
"the final parameter epsilon." )
200 (
"epsilon-r", po::value<double>()->default_value( 2.0 ),
"sets the ratio between two consecutive epsilon values of AT functional." )
201 (
"nbiter,n", po::value<int>()->default_value( 10 ),
"the maximum number of iterations." )
202 (
"image-snr", po::value<string>(),
"the input image without deterioration if you wish to compute the SNR." )
203 (
"pixel-size,p", po::value<int>()->default_value( 1 ),
"the pixel size for outputing images (useful when one wants to see the discontinuities v on top of u)." )
204 (
"color-v,c", po::value<string>()->default_value(
"0xff0000" ),
"the color chosen for displaying the singularities v (e.g. red is 0xff0000)." )
205 (
"verbose,v", po::value<int>()->default_value( 0 ),
"the verbose level (0: silent, 1: less silent, etc)." )
209 po::variables_map vm;
211 po::store(po::parse_command_line(argc, argv, general_opt), vm);
212 }
catch (
const exception& ex ) {
214 cerr <<
"Error checking program options: "<< ex.what()<< endl;
217 if ( ! parseOK || vm.count(
"help") || !vm.count(
"input") )
219 cerr <<
"Usage: " << argv[0] <<
" -i toto.pgm\n"
220 <<
"Computes the Ambrosio-Tortorelli reconstruction/segmentation of an input image."
221 <<
"It outputs 2 or 3 images (of basename given by option --output) giving the"
222 <<
" reconstructed image u, and other images superposing u and the discontinuities v."
226 <<
" | a.(u-g)^2 + v^2 |grad u|^2 + le.|grad v|^2 + (l/4e).(1-v)^2 "
230 <<
"Discretized as (u 0-form, v 1-form, A vertex-edge bdry, B edge-face bdy)" << endl
231 <<
"E(u,v) = a(u-g)^t (u-g) + u^t A^t diag(v)^2 A^t u + l e v^t (A A^t + B^t B) v + l/(4e) (1-v)^t (1-v)" << endl
233 << general_opt <<
"\n"
234 <<
"Example: ./at-u0-v1 -i ../Images/cerclesTriangle64b02.pgm -o tmp -a 0.05 -e 1 --lambda-1 0.1 --lambda-2 0.00001 -g"
238 string f1 = vm[
"input" ].as<
string>();
239 string f2 = vm[
"output" ].as<
string>();
240 double l1 = vm[
"lambda-1" ].as<
double>();
241 double l2 = vm[
"lambda-2" ].as<
double>();
242 double lr = vm[
"lambda-ratio" ].as<
double>();
243 if ( vm.count(
"lambda" ) ) l1 = l2 = vm[
"lambda" ].as<double>();
244 if ( l2 > l1 ) l2 = l1;
245 if ( lr <= 1.0 ) lr = sqrt(2);
246 double a = vm[
"alpha" ].as<
double>();
247 double e1 = vm[
"epsilon-1" ].as<
double>();
248 double e2 = vm[
"epsilon-2" ].as<
double>();
249 if ( vm.count(
"epsilon" ) )
250 e1 = e2 = vm[
"epsilon" ].as<double>();
251 double er = vm[
"epsilon-r" ].as<
double>();
252 int verb = vm[
"verbose" ].as<
int>();
253 int nbiter = vm[
"nbiter" ].as<
int>();
254 int pix_sz = vm[
"pixel-size" ].as<
int>();
255 string scv = vm[
"color-v" ].as<
string>();
256 bool snr = vm.count(
"image-snr" );
257 string isnr= snr ? vm[
"image-snr" ].as<
string>() :
"";
258 Color color_v( (
unsigned int) std::stoul( scv,
nullptr, 16 ), 255 );
260 bool color_image = f1.size() > 4 && f1.compare( f1.size() - 4, 4,
".ppm" ) == 0;
261 bool grey_image = f1.size() > 4 && f1.compare( f1.size() - 4, 4,
".pgm" ) == 0;
262 if ( ! color_image && ! grey_image )
264 trace.error() <<
"Input image file must be either a PGM (grey-level) or a PPM (color) image with these extensions."
274 typedef ImageContainerBySTLVector<Domain, Color> ColorImage;
275 typedef ImageContainerBySTLVector<Domain, unsigned char> GreyLevelImage;
279 trace.beginBlock(
"Reading PPM image");
280 ColorImage image = PPMReader<ColorImage>::importPPM( f1 );
282 trace.beginBlock(
"Building AT");
283 domain = image.domain();
284 K.init( domain.lowerBound(), domain.upperBound() - Point::diagonal( 1 ), true );
286 AT.addInput( image, [] ( Color c ) ->
double {
return ((
double) c.red()) / 255.0; } );
287 AT.addInput( image, [] ( Color c ) ->
double {
return ((
double) c.green()) / 255.0; } );
288 AT.addInput( image, [] ( Color c ) ->
double {
return ((
double) c.blue()) / 255.0; } );
291 else if ( grey_image )
293 trace.beginBlock(
"Reading PGM image");
294 GreyLevelImage image = PGMReader<GreyLevelImage>::importPGM( f1 );
296 trace.beginBlock(
"Building AT");
297 domain = image.domain();
298 K.init( domain.lowerBound(), domain.upperBound() - Point::diagonal( 1 ), true );
300 AT.addInput( image, [] (
unsigned char c ) {
return ((
double) c) / 255.0; } );
305 if ( snr && color_image )
307 trace.beginBlock(
"Reading ideal PPM image");
308 ColorImage image = PPMReader<ColorImage>::importPPM( isnr );
310 AT.addInput( image, [] ( Color c ) ->
double {
return ((
double) c.red()) / 255.0; }, true );
311 AT.addInput( image, [] ( Color c ) ->
double {
return ((
double) c.green()) / 255.0; }, true );
312 AT.addInput( image, [] ( Color c ) ->
double {
return ((
double) c.blue()) / 255.0; }, true );
314 else if ( snr && grey_image )
316 trace.beginBlock(
"Reading ideal PGM image");
317 GreyLevelImage image = PGMReader<GreyLevelImage>::importPGM( isnr );
319 AT.addInput( image, [] (
unsigned char c ) {
return ((
double) c) / 255.0; }, true );
324 Domain out_domain( pix_sz * domain.lowerBound(),
325 pix_sz * domain.upperBound() + Point::diagonal( pix_sz - 1) );
328 double g_snr = snr ? AT.computeSNR() : 0.0;
330 if ( vm.count(
"inpainting-mask" ) )
332 string fm = vm[
"inpainting-mask" ].as<
string>();
333 trace.beginBlock(
"Reading inpainting mask");
334 GreyLevelImage mask = GenericReader<GreyLevelImage>::import( fm );
336 Calculus::PrimalForm0 m( AT.calculus );
337 for ( Calculus::Index index = 0; index < m.myContainer.rows(); index++)
339 auto cell = m.getSCell( index );
340 double col = ((double) mask( K.sCoords( cell ) )) / 255.0;
341 m.myContainer( index ) = col > 0.0 ? 1.0 : 0.0;
347 ossGM << boost::format(
"%s-g-mask.pgm") %f2;
348 GreyLevelImage image_mg( domain );
349 Calculus::DualForm2 mg = AT.primal_h0 * functions::dec::diagonal( m ) * AT.getG( 0 );
350 functions::dec::form2ToGreyLevelImage
351 ( AT.calculus, mg, image_mg, 0.0, 1.0, 1 );
352 PGMWriter<GreyLevelImage>::exportPGM( ossGM.str(), image_mg );
354 else if ( color_image )
357 ossGM << boost::format(
"%s-g-mask.ppm") %f2;
358 ColorImage image_mg( domain );
359 Calculus::DualForm2 mg0 = AT.primal_h0 * functions::dec::diagonal( m ) * AT.getG( 0 );
360 Calculus::DualForm2 mg1 = AT.primal_h0 * functions::dec::diagonal( m ) * AT.getG( 1 );
361 Calculus::DualForm2 mg2 = AT.primal_h0 * functions::dec::diagonal( m ) * AT.getG( 2 );
362 functions::dec::threeForms2ToRGBColorImage
363 ( AT.calculus, mg0, mg1, mg2, image_mg, 0.0, 1.0, 1 );
364 PPMWriter<ColorImage, functors::Identity >::exportPPM( ossGM.str(), image_mg );
370 trace.info() << AT << std::endl;
375 trace.info() <<
"************ lambda = " << l1 <<
" **************" << endl;
377 for ( eps = e1; eps >= e2; eps /= er )
379 trace.info() <<
" ======= epsilon = " << eps <<
" ========" << endl;
380 AT.setEpsilon( eps );
383 trace.progressBar( n, nbiter );
387 n_v = AT.computeVariation();
388 }
while ( ( n_v > 0.0001 ) && ( ++n < nbiter ) );
389 trace.progressBar( n, nbiter );
390 trace.info() <<
"[#### last variation = " << n_v <<
" " << endl;
394 if ( verb > 0 ) trace.beginBlock(
"Writing u[0] as PGM image");
395 ostringstream ossU, ossV, ossW;
396 ossU << boost::format(
"%s-a%.5f-l%.7f-u.pgm") % f2 % a % l1;
397 ossV << boost::format(
"%s-a%.5f-l%.7f-u-v.pgm") % f2 % a % l1;
398 ossW << boost::format(
"%s-a%.5f-l%.7f-u-v.ppm") % f2 % a % l1;
399 Calculus::DualForm2 u = AT.primal_h0 * AT.getU( 0 );
400 Calculus::DualForm1 v = AT.primal_h1 * AT.getV();
402 GreyLevelImage image_u( domain );
403 functions::dec::form2ToGreyLevelImage
404 ( AT.calculus, u, image_u, 0.0, 1.0, 1 );
405 PGMWriter<GreyLevelImage>::exportPGM( ossU.str(), image_u );
407 GreyLevelImage image_uv( out_domain );
408 functions::dec::form2ToGreyLevelImage
409 ( AT.calculus, u, image_uv, 0.0, 1.0, pix_sz );
410 functions::dec::dualForm1ToGreyLevelImage
411 ( AT.calculus, v, image_uv, 0.0, 1.0, pix_sz );
412 PGMWriter<GreyLevelImage>::exportPGM( ossV.str(), image_uv );
414 ColorImage cimage( out_domain );
415 functions::dec::threeForms2ToRGBColorImage
416 ( AT.calculus, u, u, u, cimage, 0.0, 1.0, pix_sz );
417 functions::dec::dualForm1ToRGBColorImage
418 ( AT.calculus, v, cimage, color_v, 0.0, 1.0, pix_sz );
419 PPMWriter<ColorImage, functors::Identity >::exportPPM( ossW.str(), cimage );
420 if ( verb > 0 ) trace.endBlock();
422 else if ( color_image )
424 if ( verb > 0 ) trace.beginBlock(
"Writing u[0,1,2] as PGM image");
425 ostringstream ossU, ossV;
426 ossU << boost::format(
"%s-a%.5f-l%.7f-u.ppm") % f2 % a % l1;
427 ossV << boost::format(
"%s-a%.5f-l%.7f-u-v.ppm") % f2 % a % l1;
428 Calculus::DualForm2 u0 = AT.primal_h0 * AT.getU( 0 );
429 Calculus::DualForm2 u1 = AT.primal_h0 * AT.getU( 1 );
430 Calculus::DualForm2 u2 = AT.primal_h0 * AT.getU( 2 );
431 Calculus::DualForm1 v = AT.primal_h1 * AT.getV();
433 ColorImage image_u( domain );
434 functions::dec::threeForms2ToRGBColorImage
435 ( AT.calculus, u0, u1, u2, image_u, 0.0, 1.0, 1 );
436 PPMWriter<ColorImage, functors::Identity >::exportPPM( ossU.str(), image_u );
437 ColorImage image_uv( out_domain );
438 functions::dec::threeForms2ToRGBColorImage
439 ( AT.calculus, u0, u1, u2, image_uv, 0.0, 1.0, pix_sz );
440 functions::dec::dualForm1ToRGBColorImage
441 ( AT.calculus, v, image_uv, color_v, 0.0, 1.0, pix_sz );
442 PPMWriter<ColorImage, functors::Identity >::exportPPM( ossV.str(), image_uv );
443 if ( verb > 0 ) trace.endBlock();
448 double u_snr = AT.computeSNR();
449 trace.info() <<
"- SNR of u = " << u_snr <<
" SNR of g = " << g_snr << endl;
DiscreteExteriorCalculus< 2, 2, LinearAlgebra > Calculus
Aim: This class solves Ambrosio-Tortorelli functional in a plane for u a (vector of) 0-form(s) and v ...