[89c355]: bin / match-n-shift Maximize Restore History

Download this file

match-n-shift    254 lines (206 with data), 7.8 kB

#!/usr/bin/perl

use strict;
use warnings;
use Getopt::Long;
use Pod::Usage;
use Panotools::Script;
use Panotools::Photos;
use Text::ParseWords;

my $path_oto;
my $path_csv;
my $pix_max = 1600;
my $points = 25;
my $noransac = 0;
my $refine = 0;
my $dostacks = 0;
my $linkstacks = 0;
my $projection = 0;
my $deg_fov;
my $crop_s = undef;
my $align = 0;
my $clean = 0;
my $help = 0;

GetOptions ('o|output=s' => \$path_oto,
            'i|input=s' => \$path_csv,
            's|size=i' => \$pix_max,
            'p|points=i' => \$points,
            'n|noransac' => \$noransac,
            'r|refine' => \$refine,
            'b|stacks' => \$dostacks,
            'l|linkstacks' => \$linkstacks,
            'f|projection=i' => \$projection,
            'v|fov=s' => \$deg_fov,
            'k|selection=s' => \$crop_s,
            'a|align' => \$align,
            'c|clean' => \$clean,
            'h|help' => \$help);

# code assumes images are sorted
my @path_photos = sort @ARGV;

# if csv file is specified, parse and replace list of photos
my @csv;
if ($path_csv)
{
    open (CSV, '<'. $path_csv);
    my @lines = (<CSV>);
    chomp @lines;
    close CSV;
    @csv = map {[parse_line ('\s*,\s*', 0, $_)]} @lines;
    @path_photos = map {$_->[0]} @csv;
}

pod2usage (-verbose => 2) if $help;
pod2usage (2) unless (scalar @path_photos > 1);

my $photos_all = new Panotools::Photos (@path_photos);

$deg_fov = 50 unless defined $deg_fov;
# decimal separator workaround
$deg_fov =~ s/,.*//;

my $oto = new Panotools::Script;

my @AverageRGB = $photos_all->AverageRGB;

my $sum_Eev;
for my $id (0 .. scalar @path_photos -1)
{
    my $image = new Panotools::Script::Line::Image;
    my $photo = $photos_all->[$id];
    %{$image} = (
        n => '"'. $photo->{path} .'"',
        w => $photo->{exif}->{ImageWidth},
        h => $photo->{exif}->{ImageHeight},
        r => $photos_all->Rotation ($id), p => 0.0, y => 0.0,
        a => 0.0, b => 0.0, c => 0.0, d => 0.0, e => 0.0,
        Ra => 0, Rb => 0, Rc => 0, Rd => 0, Re => 0,
        Va => 1, Vb => 0, Vc => 0, Vd => 0, Vx => 0, Vy => 0,
        f => $projection,
        Eev => $photos_all->Eev ($id),
        v => ($photos_all->FOV ($id) || $deg_fov)
    );
    $image->{n} = '"'. $image->Path .'"';
    $image->{S} = $crop_s if defined $crop_s;
    $sum_Eev += $photos_all->Eev ($id);
    $image->{Er} = $photo->{exif}->{RedBalance} / $AverageRGB[0] if $photo->{exif}->{RedBalance};
    $image->{Eb} = $photo->{exif}->{BlueBalance} / $AverageRGB[2] if $photo->{exif}->{BlueBalance};
    $image->{y} = $csv[$id]->[1] if (defined $csv[$id] and defined $csv[$id]->[1]);
    $image->{p} = $csv[$id]->[2] if (defined $csv[$id] and defined $csv[$id]->[2]);
    $image->{r} = $csv[$id]->[3] if (defined $csv[$id] and defined $csv[$id]->[3]);

    if ($photos_all->Bracketed)
    {
        $image->{j} = int ($id / scalar @{$photos_all->Speeds});
    }
    push @{$oto->Image}, $image;

    my $imagemetadata = new Panotools::Script::Line::ImageMetadata;
    $imagemetadata->{cropFactor} = $photo->{exif}->{ScaleFactor35efl} || 1;
    $imagemetadata->{autoCenterCrop} = 0 if defined $crop_s;
    push @{$oto->ImageMetadata}, $imagemetadata;

    unless ($id == 0)
    {
        $oto->Variable->{$id}->{r} = 1;
        $oto->Variable->{$id}->{p} = 1;
        $oto->Variable->{$id}->{y} = 1;
    }
}

#TODO calculate fisheye field of view from EXIF FoV

$oto->Panorama->{E} = sprintf ('%.3f', $sum_Eev / scalar @{$photos_all});

$oto->Panorama->{w} = $oto->OptimalWidth (0.7);
$oto->Panorama->{h} = $oto->Panorama->{w} / 2;

$oto->UnifyLenses;

$oto->LinkStacks if $linkstacks;

$oto->Option->{cpgenSize} = $pix_max;
$oto->Option->{cpgenNumber} = $points;
$oto->Option->{cpgenRansac} = 'true';
$oto->Option->{cpgenRansac} = 'false' if $noransac;
$oto->Option->{cpgenRefine} = 'false';
$oto->Option->{cpgenRefine} = 'true' if $refine;

if ($photos_all->Bracketed)
{
    $oto->Option->{outputLDRBlended} = 'false';
    $oto->Option->{outputLDRExposureBlended} = 'true';
    $oto->Option->{outputLDRExposureLayersFused} = 'false';
}
elsif ($photos_all->Layered)
{
    $oto->Option->{outputLDRBlended} = 'false';
    $oto->Option->{outputLDRExposureBlended} = 'false';
    $oto->Option->{outputLDRExposureLayersFused} = 'true';
}
else
{
    $oto->Option->{outputLDRBlended} = 'true';
    $oto->Option->{outputLDRExposureBlended} = 'false';
    $oto->Option->{outputLDRExposureLayersFused} = 'false';
}

$path_oto = $photos_all->Stub .'.pto' unless defined $path_oto;
if ($align)
{
    my $path_nopoints = $path_oto;
    $path_nopoints =~ s/\.[[:alnum:]]+$//;
    $path_nopoints .= '.pointless.pto';
    $oto->Write ($path_nopoints);
    system ('ptoanchor', '--output', $path_oto, $path_nopoints);
}
else
{
    $oto->Write ($path_oto);
}

exit 0;

__END__

=head1 NAME

match-n-shift - generate a Hugin .pto project from a list of photos

=head1 SYNOPSIS

match-n-shift [options] --output project.pto image1 image2 [...]

 Options:
  -o | --output name    Filename of created panorama project
  -i | --input name     Filename of CSV file specifying input images (optional, see below)
  -f | --projection     Panotools style input projection number. Use
                          0 for rectilinear (default), 2 for circular fisheye and
                          3 for full-frame fisheye images.  Note, this has to be
                          specified for fisheye lenses as this can't be determined
                          from EXIF metadata
  -v | --fov            Horizontal field of view in degrees, this value is
                          only used if FoV can't be determined from EXIF
                          metadata, defaults to 50
  -k | --selection      Crop selection boundary, eg -459,2459,-57,2861
  -l | --linkstacks     Hard link positions of stacked photos.
  -a | --align          Generate control points (default no).
  -h | --help           Outputs help documentation.

 Deprecated options (have no effect when using cpfind):
  -s | --size number    Downsize images until width and height is
                          smaller than number, default 1600
  -p | --points number  Number of generated control points between,
                          each pair, default: 25
  -n | --noransac       No ransac detection, useful for fisheye images
  -r | --refine         Refine the found control points using the
                          original images, delete unrefinable.

=head1 DESCRIPTION

B<match-n-shift> takes a list of image files and creates a hugin compatible
project file (optionally containing control points linking the images together,
though there are better ways of doing this).

As much information as possible is extracted from photo EXIF data.  So photo
orientation, exposure, angle of view and white balance will all be initialised
if possible.

An optional input file can be chosen with the --input option, this can be a
simple list of photo filenames, or full paths, one per line:

  DSC_0001.JPG
  DSC_0002.JPG
  DSC_0003.JPG
  DSC_0004.JPG

..or a CSV file specifying "filename, yaw, pitch, roll" (in degrees):

  DSC_0001.JPG,0,20,90
  DSC_0002.JPG,90,-20,90
  DSC_0003.JPG,180,20,90
  DSC_0004.JPG,-180,-20,90

If the roll field is ommitted then it will be set to zero or any value
obtainable from EXIF data, similarly if the pitch field is ommitted then pitch
will be set to zero.  So this should also work:

  DSC_0001.JPG,0
  DSC_0002.JPG,90
  DSC_0003.JPG,180
  DSC_0004.JPG,-180

=head1 LICENSE

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 2
of the License, or (at your option) any later version.

=head1 SEE ALSO

L<http://hugin.sourceforge.net/>

=head1 AUTHOR

Bruno Postle - February 2008.

=cut

=begin perl