37 #include <boost/format.hpp>
41 #include "DGtal/base/Common.h"
42 #include "DGtal/helpers/StdDefs.h"
43 #include "DGtal/io/readers/GenericReader.h"
44 #include "DGtal/io/writers/GenericWriter.h"
210 using namespace DGtal;
212 int main(
int argc,
char* argv[] )
220 string inpainting_mask;
234 string scv {
"0xff0000"};
239 stringstream ssDescr;
240 ssDescr <<
"Usage: " << argv[0] <<
" -i toto.pgm\n"
241 <<
"Computes a variant of the Ambrosio-Tortorelli reconstruction/segmentation of an input image."
242 <<
"It outputs 2 or 3 images (of basename given by option --output) giving the"
243 <<
" reconstructed image u, and other images superposing u and the discontinuities v."
247 <<
" | a.(u-g)^2 + v^2 |grad u|^2 + c.l.e^3.|Delta v|^2 + (c.l/e).(1-v)^2 "
251 <<
"Discretized as (u 2-form, v 0-form, L=A^t A laplacian, B edge-face bdy, M vertex-edge average)" << endl
252 <<
"E(u,v) = a(u-g)^t (u-g) + u^t B diag(M v)^2 B^t u + l e^3 v^t L^t L v + l/e (1-v)^t (1-v)" << endl
254 <<
"where c=1/(2sqrt(2)) is a constant that reflects the fact that the Gamma-limit of discontinuity length estimation term is 2.sqrt(2).L, for a length L of discontinuities."
255 <<
"Example: ./atv-u2-v0 -i ../Images/cerclesTriangle64b02.pgm -o tmp -a 0.05 -e 1 --lambda-1 0.1 --lambda-2 0.00001"
259 app.description(ssDescr.str());
262 app.add_option(
"-i,--input,1", f1,
"the input image PPM filename." )
264 ->check(CLI::ExistingFile);
265 app.add_option(
"--inpainting-mask,-m", inpainting_mask,
"the input inpainting mask filename." );
266 app.add_option(
"--output,-o", f2,
"the output image basename.",
true);
267 auto lambdaOpt = app.add_option(
"--lambda,-l",l,
"the parameter lambda.");
268 app.add_flag(
"--metric-average,-M",metric,
"use metric average to smooth L1-metric." );
270 app.add_option(
"--lambda-1,-1",l1,
"the initial parameter lambda (l1).",
true);
271 app.add_option(
"--lambda-2,-2",l2,
"the final parameter lambda (l2).",
true );
272 app.add_option(
"--lambda-ratio,-q",lr,
"the division ratio for lambda from l1 to l2.",
true);
273 app.add_option(
"--alpha,-a",a,
"the parameter alpha.",
true);
274 auto epsOpt = app.add_option(
"--epsilon,-e",
"the initial and final parameter epsilon of AT functional at the same time.");
276 app.add_option(
"--epsilon-1",e1,
"the initial parameter epsilon.",
true);
277 app.add_option(
"--epsilon-2",e2,
"the final parameter epsilon.",
true);
278 app.add_option(
"--epsilon-r",er,
"sets the ratio between two consecutive epsilon values of AT functional.",
true);
280 app.add_option(
"--nbiter,-n",nbiter,
"the maximum number of iterations.",
true );
281 auto snrOpt = app.add_option(
"--image-snr", isnr,
"the input image without deterioration if you wish to compute the SNR.");
282 app.add_option(
"--pixel-size,-p", pix_sz,
"the pixel size for outputing images (useful when one wants to see the discontinuities v on top of u).",
true);
283 app.add_option(
"--color-v,-c",scv,
"the color chosen for displaying the singularities v (e.g. red is 0xff0000).",
true );
284 app.add_option(
"--verbose,-v", verb,
"the verbose level (0: silent, 1: less silent, etc).",
true );
286 app.get_formatter()->column_width(40);
287 CLI11_PARSE(app, argc, argv);
291 Color color_v( (
unsigned int) std::stoul( scv,
nullptr, 16 ), 255 );
292 if ( lambdaOpt->count()) l1 = l2 = l;
293 if ( l2 > l1 ) l2 = l1;
294 if ( lr <= 1.0 ) lr = sqrt(2);
295 if ( epsOpt->count() > 0 ){
299 bool snr = snrOpt->count() > 0;
302 bool color_image = f1.size() > 4 && f1.compare( f1.size() - 4, 4,
".ppm" ) == 0;
303 bool grey_image = f1.size() > 4 && f1.compare( f1.size() - 4, 4,
".pgm" ) == 0;
304 if ( ! color_image && ! grey_image )
306 trace.error() <<
"Input image file must be either a PGM (grey-level) or a PPM (color) image with these extensions."
315 AT.setMetricAverage( metric );
318 typedef ImageContainerBySTLVector<Domain, Color> ColorImage;
319 typedef ImageContainerBySTLVector<Domain, unsigned char> GreyLevelImage;
323 trace.beginBlock(
"Reading PPM image");
324 ColorImage image = PPMReader<ColorImage>::importPPM( f1 );
326 trace.beginBlock(
"Building AT");
327 domain = image.domain();
328 K.init( domain.lowerBound(), domain.upperBound(),
true );
330 AT.addInput( image, [] ( Color c ) ->
double {
return ((
double) c.red()) / 255.0; } );
331 AT.addInput( image, [] ( Color c ) ->
double {
return ((
double) c.green()) / 255.0; } );
332 AT.addInput( image, [] ( Color c ) ->
double {
return ((
double) c.blue()) / 255.0; } );
335 else if ( grey_image )
337 trace.beginBlock(
"Reading PGM image");
338 GreyLevelImage image = PGMReader<GreyLevelImage>::importPGM( f1 );
340 trace.beginBlock(
"Building AT");
341 domain = image.domain();
342 K.init( domain.lowerBound(), domain.upperBound(),
true );
344 AT.addInput( image, [] (
unsigned char c ) {
return ((
double) c) / 255.0; } );
349 if ( snr && color_image )
351 trace.beginBlock(
"Reading ideal PPM image");
352 ColorImage image = PPMReader<ColorImage>::importPPM( isnr );
354 AT.addInput( image, [] ( Color c ) ->
double {
return ((
double) c.red()) / 255.0; }, true );
355 AT.addInput( image, [] ( Color c ) ->
double {
return ((
double) c.green()) / 255.0; }, true );
356 AT.addInput( image, [] ( Color c ) ->
double {
return ((
double) c.blue()) / 255.0; }, true );
358 else if ( snr && grey_image )
360 trace.beginBlock(
"Reading ideal PGM image");
361 GreyLevelImage image = PGMReader<GreyLevelImage>::importPGM( isnr );
363 AT.addInput( image, [] (
unsigned char c ) {
return ((
double) c) / 255.0; }, true );
368 Domain out_domain( pix_sz * domain.lowerBound(),
369 pix_sz * domain.upperBound() + Point::diagonal( pix_sz ) );
372 double g_snr = snr ? AT.computeSNR() : 0.0;
374 if ( inpainting_mask.size() > 0 )
376 string fm = inpainting_mask;
377 trace.beginBlock(
"Reading inpainting mask");
378 GreyLevelImage mask = GenericReader<GreyLevelImage>::import( fm );
380 Calculus::PrimalForm2 m( AT.calculus );
381 for ( Calculus::Index index = 0; index < m.myContainer.rows(); index++)
383 auto cell = m.getSCell( index );
384 double col = ((double) mask( K.sCoords( cell ) )) / 255.0;
385 m.myContainer( index ) = col > 0.0 ? 1.0 : 0.0;
388 AT.setUFromInputAndMask();
392 ossGM << boost::format(
"%s-g-mask.pgm") %f2;
393 GreyLevelImage image_mg( domain );
394 const Calculus::PrimalForm2 mg = functions::dec::diagonal( m ) * AT.getG( 0 );
395 functions::dec::form2ToGreyLevelImage
396 ( AT.calculus, mg, image_mg, 0.0, 1.0, 1 );
397 PGMWriter<GreyLevelImage>::exportPGM( ossGM.str(), image_mg );
399 else if ( color_image )
402 ossGM << boost::format(
"%s-g-mask.ppm") %f2;
403 ColorImage image_mg( domain );
404 const Calculus::PrimalForm2 mg0 = functions::dec::diagonal( m ) * AT.getG( 0 );
405 const Calculus::PrimalForm2 mg1 = functions::dec::diagonal( m ) * AT.getG( 1 );
406 const Calculus::PrimalForm2 mg2 = functions::dec::diagonal( m ) * AT.getG( 2 );
407 functions::dec::threeForms2ToRGBColorImage
408 ( AT.calculus, mg0, mg1, mg2, image_mg, 0.0, 1.0, 1 );
409 PPMWriter<ColorImage, functors::Identity >::exportPPM( ossGM.str(), image_mg );
415 trace.info() << AT << std::endl;
418 double cst = 1.0 / ( 2.0 * sqrt(2.0) );
421 trace.info() <<
"************ lambda = " << l1 <<
" **************" << endl;
422 AT.setLambda( cst * l1 );
423 for ( eps = e1; eps >= e2; eps /= er )
425 trace.info() <<
" ======= epsilon = " << eps <<
" ========" << endl;
426 AT.setEpsilon( eps );
429 trace.progressBar( n, nbiter );
433 n_v = AT.computeVariation();
434 }
while ( ( n_v > 0.0001 ) && ( ++n < nbiter ) );
435 trace.progressBar( n, nbiter );
436 trace.info() <<
"[#### last variation = " << n_v <<
" " << endl;
440 if ( verb > 0 ) trace.beginBlock(
"Writing u[0] as PGM image");
441 ostringstream ossU, ossV, ossW;
442 ossU << boost::format(
"%s-a%.5f-l%.7f-u.pgm") % f2 % a % l1;
443 ossV << boost::format(
"%s-a%.5f-l%.7f-u-v.pgm") % f2 % a % l1;
444 ossW << boost::format(
"%s-a%.5f-l%.7f-u-v.ppm") % f2 % a % l1;
445 const Calculus::PrimalForm2 u = AT.getU( 0 );
446 const Calculus::PrimalForm1 v = AT.M01 * AT.getV();
448 GreyLevelImage image_u( domain );
449 functions::dec::form2ToGreyLevelImage
450 ( AT.calculus, u, image_u, 0.0, 1.0, 1 );
451 PGMWriter<GreyLevelImage>::exportPGM( ossU.str(), image_u );
453 GreyLevelImage image_uv( out_domain );
454 functions::dec::form2ToGreyLevelImage
455 ( AT.calculus, u, image_uv, 0.0, 1.0, pix_sz );
456 functions::dec::primalForm1ToGreyLevelImage
457 ( AT.calculus, v, image_uv, 0.0, 1.0, pix_sz );
458 PGMWriter<GreyLevelImage>::exportPGM( ossV.str(), image_uv );
460 ColorImage cimage( out_domain );
461 functions::dec::threeForms2ToRGBColorImage
462 ( AT.calculus, u, u, u, cimage, 0.0, 1.0, pix_sz );
463 functions::dec::primalForm1ToRGBColorImage
464 ( AT.calculus, v, cimage, color_v, 0.0, 1.0, pix_sz );
465 PPMWriter<ColorImage, functors::Identity >::exportPPM( ossW.str(), cimage );
466 if ( verb > 0 ) trace.endBlock();
468 else if ( color_image )
470 if ( verb > 0 ) trace.beginBlock(
"Writing u[0,1,2] as PGM image");
471 ostringstream ossU, ossV;
472 ossU << boost::format(
"%s-a%.5f-l%.7f-u.ppm") % f2 % a % l1;
473 ossV << boost::format(
"%s-a%.5f-l%.7f-u-v.ppm") % f2 % a % l1;
474 const Calculus::PrimalForm2 u0 = AT.getU( 0 );
475 const Calculus::PrimalForm2 u1 = AT.getU( 1 );
476 const Calculus::PrimalForm2 u2 = AT.getU( 2 );
477 const Calculus::PrimalForm1 v = AT.M01 * AT.getV();
479 ColorImage image_u( domain );
480 functions::dec::threeForms2ToRGBColorImage
481 ( AT.calculus, u0, u1, u2, image_u, 0.0, 1.0, 1 );
482 PPMWriter<ColorImage, functors::Identity >::exportPPM( ossU.str(), image_u );
483 ColorImage image_uv( out_domain );
484 functions::dec::threeForms2ToRGBColorImage
485 ( AT.calculus, u0, u1, u2, image_uv, 0.0, 1.0, pix_sz );
486 functions::dec::primalForm1ToRGBColorImage
487 ( AT.calculus, v, image_uv, color_v, 0.0, 1.0, pix_sz );
488 PPMWriter<ColorImage, functors::Identity >::exportPPM( ossV.str(), image_uv );
489 if ( verb > 0 ) trace.endBlock();
494 double u_snr = AT.computeSNR();
495 trace.info() <<
"- SNR of u = " << u_snr <<
" SNR of g = " << g_snr << endl;
Aim: This class solves a variant of Ambrosio-Tortorelli functional in a plane for u a (vector of) 2-f...
DiscreteExteriorCalculus< 2, 2, LinearAlgebra > Calculus