"Fossies" - the Fresh Open Source Software Archive 
Member "pfstools-2.2.0/src/camera/pfsalign.cpp" (12 Aug 2021, 26550 Bytes) of package /linux/privat/pfstools-2.2.0.tgz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "pfsalign.cpp" see the
Fossies "Dox" file reference documentation and the latest
Fossies "Diffs" side-by-side code changes report:
2.1.0_vs_2.2.0.
1 /**
2 * @file pfsalign.cpp
3 * @brief Image stack alignment using feature matching
4 *
5 * The code relies on OpenCV SURF feature detector / descriptor. The ideas for
6 * prunning false matches are mostly based on the book:
7 *
8 * Lagani, R. (2011). OpenCV 2 Computer Vision Application Programming Cookbook.
9 * Packt Publishing.
10 *
11 *
12 * This file is a part of pfscalibration package.
13 * ----------------------------------------------------------------------
14 * Copyright (C) 2003-2013 Rafal Mantiuk and Grzegorz Krawczyk
15 *
16 * This library is free software; you can redistribute it and/or
17 * modify it under the terms of the GNU Lesser General Public
18 * License as published by the Free Software Foundation; either
19 * version 2.1 of the License, or (at your option) any later version.
20 *
21 * This library is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
24 * Lesser General Public License for more details.
25 *
26 * You should have received a copy of the GNU Lesser General Public
27 * License along with this library; if not, write to the Free Software
28 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 * ----------------------------------------------------------------------
30 *
31 * @author Rafal Mantiuk, <mantiuk@mpi-sb.mpg.de>
32 *
33 * $Id: pfsalign.cpp,v 1.11 2013/11/14 21:28:28 rafm Exp $
34 */
35
36 #include <iostream>
37 #include <sstream>
38 #include <string>
39 #include <algorithm>
40 #include <getopt.h>
41 #include <string.h>
42
43 // Because non-free OpenCV modules is not included in most of the distributions
44 // the current codes relies now on a less restrictive AKAZE feature detector
45 // Uncomment the line below to use SURF
46 //#define USE_SURF
47
48 #include <opencv2/core/core.hpp>
49 #include <opencv2/highgui/highgui.hpp>
50 #include <opencv2/features2d/features2d.hpp>
51 #include <opencv2/calib3d/calib3d.hpp>
52 #include <opencv2/imgproc/imgproc.hpp>
53 #include <libexif/exif-data.h>
54
55 #ifdef USE_SURF
56 #include <opencv2/nonfree/features2d.hpp>
57 #endif
58
59 #include <pfs.h>
60
61 #define PROG_NAME "pfsalign"
62 #define VERBOSE_STR if( verbose ) std::cerr << PROG_NAME ": "
63
64 bool verbose = false;
65
66 class QuietException
67 {
68 };
69
70 using namespace cv;
71 using namespace std;
72
73 void printHelp()
74 {
75 fprintf( stderr, PROG_NAME " [-r index|--reference index] [-c (min|max)|--crop (min|max)] [-d|--display-matches] [-f|--fail-no-match] -v -h\n"
76 "See man page for more information.\n" );
77 }
78
79 class Toc
80 {
81 double tickCount;
82 bool newLine;
83 const char *name;
84 public:
85 void tic( const char *name = NULL, bool newLine = false )
86 {
87 this->name = name;
88 this->newLine = newLine;
89 if( name != NULL )
90 name = "processing";
91 VERBOSE_STR << "Starting " << name << "...";
92 if( newLine )
93 VERBOSE_STR << std::endl;
94 tickCount = static_cast<double>( getTickCount() );
95 }
96
97 void toc( )
98 {
99 double duration = (static_cast<double>( getTickCount() ) - tickCount) / getTickFrequency();
100 if( newLine ) {
101 VERBOSE_STR << "Completed " << name << ". It took " << duration << " seconds." << std::endl;
102 } else
103 VERBOSE_STR << "completed. It took " << duration << " seconds." << std::endl;
104 }
105 };
106
107
108
109 void pruneNNDR( std::vector<std::vector<DMatch> > &matches, float ratio )
110 {
111 for( vector<vector<DMatch> >::iterator it = matches.begin(); it != matches.end(); it++ ) {
112 bool prune = false;
113 if( it->size() < 2 )
114 prune = true;
115 else {
116 float NNDR = (*it)[0].distance / (*it)[1].distance;
117 if( NNDR >= ratio )
118 prune = true;
119 }
120 if( prune )
121 it->clear();
122 }
123
124 }
125
126 void symmetryTest( const vector<vector<DMatch> > &matches_12, const vector<vector<DMatch> > &matches_21, vector<DMatch> &matches_sym )
127 {
128 for( vector< vector<DMatch> >::const_iterator it1 = matches_12.begin(); it1 != matches_12.end(); it1++ ) {
129 if( it1->size() == 0 )
130 continue;
131
132 for( vector< vector<DMatch> >::const_iterator it2 = matches_21.begin(); it2 != matches_21.end(); it2++ ) {
133 if( it2->size() == 0 )
134 continue;
135
136 if( (*it1)[0].queryIdx == (*it2)[0].trainIdx && (*it2)[0].trainIdx == (*it1)[0].queryIdx ) {
137 matches_sym.push_back( DMatch( (*it1)[0].queryIdx, (*it1)[0].trainIdx, (*it1)[0].distance ));
138 break;
139 }
140 }
141
142 }
143 }
144
145
146
147
148 /**
149 Match feature points in a pair image and find homography.
150 */
151 bool alignImagePair( const Mat &ref_img, const Mat &exp_img, Mat &homography, int sensitivity, bool display_matches )
152 {
153 Toc toc;
154 Toc toc_global;
155
156 toc_global.tic( "alignment of image pair", true );
157
158 homography = Mat::eye( 3, 3, CV_64F );
159 // cv::imshow( "Result", ref_img );
160 // cv::imshow( "Result2", exp_img );
161 // cv::waitKey(0);
162
163 // Ptr<FeatureDetector> detector;
164 // detector = new GoodFeaturesToTrackDetector();
165 // detector = new SurfFeatureDetector();
166
167 std::vector<KeyPoint> keypoints1, keypoints2;
168 Mat descr_ref, descr_exp;
169
170 #ifdef USE_SURF
171 Ptr<FeatureDetector> detector(new DynamicAdaptedFeatureDetector( new SurfAdjuster( (11-sensitivity) * 100.f, 2, 1000 ),
172 100, 1000, sensitivity/2+2 ));
173 toc.tic( "feature detection" );
174 detector->detect( ref_img, keypoints1 );
175 detector->detect( exp_img, keypoints2 );
176 toc.toc( );
177 #else
178 Ptr<AKAZE> akaze = AKAZE::create();
179 toc.tic( "feature detection and extraction" );
180 akaze->setThreshold( 0.005f );
181 akaze->detectAndCompute( ref_img, noArray(), keypoints1, descr_ref);
182 akaze->detectAndCompute( exp_img, noArray(), keypoints2, descr_exp);
183 toc.toc( );
184 VERBOSE_STR << "Detected " << keypoints1.size() << " keypoints in the reference image." << endl;
185 VERBOSE_STR << "Detected " << keypoints2.size() << " keypoints in the test image." << endl;
186 #endif
187
188 if( keypoints1.size() < 10 || keypoints2.size() < 10 ) {
189 cerr << PROG_NAME ": Could not detect a sufficient number of keypoints (found "
190 << keypoints1.size() << " and " << keypoints2.size() << " keypoints)" << endl;
191
192 if( display_matches ) {
193 Mat vis;
194 vector<char> inliners_c;
195 std::vector<cv::DMatch> matches_sym;
196 drawMatches( ref_img, keypoints1, exp_img, keypoints2, matches_sym, vis, Scalar(0,0,255), Scalar(0,255,255), inliners_c, DrawMatchesFlags::DRAW_RICH_KEYPOINTS );
197 cv::namedWindow( "Image pair matches" );
198 cv::imshow( "Image pair matches", vis );
199 cv::waitKey(0);
200 }
201
202 return false;
203 }
204
205 // SiftDescriptorExtractor surfDesc;
206 #ifdef USE_SURF
207 SurfDescriptorExtractor surfDesc;
208
209 toc.tic( "descriptor extraction" );
210 surfDesc.compute( ref_img, keypoints1, descr_ref );
211 surfDesc.compute( exp_img, keypoints2, descr_exp );
212 toc.toc( );
213 #endif
214
215 #ifdef USE_SURF
216 FlannBasedMatcher matcher;
217 // BruteForceMatcher< cv::L2<float> > matcher;
218 #else
219 BFMatcher matcher(NORM_HAMMING);
220 #endif
221 toc.tic( "matching" );
222 std::vector< std::vector<cv::DMatch> > matches_rt;
223 std::vector< std::vector<cv::DMatch> > matches_tr;
224 matcher.knnMatch( descr_ref, descr_exp, matches_rt, 2 );
225 matcher.knnMatch( descr_exp, descr_ref, matches_tr, 2 );
226
227 pruneNNDR( matches_rt, 1 );
228 pruneNNDR( matches_tr, 1 );
229
230 std::vector<cv::DMatch> matches_sym;
231 symmetryTest( matches_rt, matches_tr, matches_sym );
232 toc.toc( );
233
234 std::vector<cv::DMatch>::iterator it;
235 vector<Point3f> p1, p2;
236 Mat_<float> pp1(matches_sym.size(),2);
237 Mat_<float> pp2(matches_sym.size(),2);
238 int kk = 0;
239 for( it = matches_sym.begin(); it != matches_sym.end(); it++, kk++ ) {
240
241 const Point2f from = keypoints1[it->queryIdx].pt;
242 p1.push_back( Point3f( from.x, from.y, 1 ) );
243 pp1(kk,0) = from.x;
244 pp1(kk,1) = from.y;
245 // pp1(kk,2) = 1;
246 const Point2f to = keypoints2[it->trainIdx].pt;
247 p2.push_back( Point3f( to.x, to.y, 1 ) );
248 pp2(kk,0) = to.x;
249 pp2(kk,1) = to.y;
250 //pp2(kk,2) = 1;
251 // std::cout << "Matches: " << from << " -> " << to << std::endl;
252 }
253
254 if( matches_sym.size() < 9 ) {
255 cerr << PROG_NAME ": Not enough matches found between a pair of images (found " << matches_sym.size() << ")" << endl;
256 return false;
257 }
258
259 // Mat affine = estimateRigidTransform( e1, e2, false );
260
261 vector<uchar> inliners(matches_sym.size(), 0);
262 // affine = findHomography( pp1, pp2, inliners, CV_RANSAC, 1. );
263 homography = findHomography( pp2, pp1, RANSAC, 1., inliners );
264
265 // solve( pp1, pp2, affine, DECOMP_SVD );
266 // Mat affine = (Mat_<float>(2,3) << 1, 0, 0, 0, 1, 10);
267 int inliner_count = count( inliners.begin(), inliners.end(), 1 );
268
269 VERBOSE_STR << "Found " << matches_sym.size() << " matching features, reduced to " << inliner_count << " inliners." << endl;
270
271 if( display_matches ) {
272 Mat vis;
273 vector<char> inliners_c(matches_sym.size(), 0);
274 for( size_t i = 0; i < inliners.size(); i++ )
275 inliners_c[i] = (char)inliners[i];
276
277 drawMatches( ref_img, keypoints1, exp_img, keypoints2, matches_sym, vis, Scalar(0,0,255), Scalar(0,255,255), inliners_c, DrawMatchesFlags::DRAW_RICH_KEYPOINTS|DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS );
278
279 cv::namedWindow( "Image pair matches" );
280 cv::imshow( "Image pair matches", vis );
281
282 cv::waitKey(0);
283 }
284
285 if( inliner_count < 9 ) {
286 homography = Mat::eye( 3, 3, CV_64F );
287 cerr << PROG_NAME ": Not enough inliners to find a reliable transformation." << endl;
288 return false;
289 }
290 toc_global.toc();
291
292
293 return true;
294
295 }
296
297
298 /**
299 Convert floating point image to 8-bit image.
300 */
301 Mat convert_to_8bit( Mat &in, bool apply_gamma )
302 {
303 Mat out;
304 if( !apply_gamma ) {
305 in.convertTo( out, CV_8UC3, 255 );
306 } else {
307 out.create( in.rows, in.cols, CV_8UC3 );
308 for(int i = 0; i < in.rows; i++)
309 {
310 Vec3f *i_p = in.ptr<Vec3f>(i);
311 Vec3b *o_p = out.ptr<Vec3b>(i);
312 for(int j = 0; j < in.cols; j++) {
313 for( int cc=0; cc < 3; cc++ ) {
314 o_p[j][cc] = saturate_cast<uchar>(powf( i_p[j][cc], 1.f/2.2f ) * 255.f);
315 }
316 }
317 }
318
319
320 }
321
322
323 return out;
324 }
325
326
327 // Find maximum and positive minimum for an image
328 void find_min_max( Mat &in, float &minV, float &maxV )
329 {
330 minV = 1e10f;
331 maxV = -1e10f;
332 for(int i = 0; i < in.rows; i++)
333 {
334 Vec3f *i_p = in.ptr<Vec3f>(i);
335 for(int j = 0; j < in.cols; j++) {
336 for( int cc=0; cc < 3; cc++ ) {
337 float v = i_p[j][cc];
338 if( v > 0 ) {
339 if( v < minV )
340 minV = v;
341 if( v > maxV )
342 maxV = v;
343 }
344 }
345 }
346 }
347
348 }
349
350 Mat convert_and_log_scale( Mat &in, float min, float max, float mult, float gamma )
351 {
352 Mat out;
353 out.create( in.rows, in.cols, CV_8UC3 );
354
355 const float l_min = logf( min );
356 const float l_max = logf( max );
357 const float l_mult = logf( mult );
358
359 for(int i = 0; i < in.rows; i++)
360 {
361 Vec3f *i_p = in.ptr<Vec3f>(i);
362 Vec3b *o_p = out.ptr<Vec3b>(i);
363 for(int j = 0; j < in.cols; j++) {
364 for( int cc=0; cc < 3; cc++ ) {
365 if( i_p[j][cc] > 0.f )
366 o_p[j][cc] = saturate_cast<uchar>( ((gamma*(logf(i_p[j][cc]) - l_max) + l_mult) / (l_max-l_min) + 1.f) * 255.f);
367 else
368 o_p[j][cc] = 0;
369 }
370 }
371 }
372
373 return out;
374 }
375
376 /**
377 Convert a pair of floating point images to 8-bit.
378 */
379 void convert_to_8bit( Mat &in1, Mat &in2, Mat &out1, Mat &out2, float exp_mult1, float exp_mult2, bool is_ldr )
380 {
381 if( is_ldr && exp_mult1 == 1.f && exp_mult2 == 1.f ) {
382 in1.convertTo( out1, CV_8UC3, 255*exp_mult1);
383 in2.convertTo( out2, CV_8UC3, 255*exp_mult2);
384 } else {
385 float minV1, minV2, maxV1, maxV2;
386 find_min_max( in1, minV1, maxV1 );
387 find_min_max( in2, minV2, maxV2 );
388
389 minV1 *= exp_mult1;
390 maxV1 *= exp_mult1;
391 minV2 *= exp_mult2;
392 maxV2 *= exp_mult2;
393
394 float gamma = is_ldr ? 2.2f : 1.f;
395
396 minV1 = powf( minV1, gamma );
397 minV2 = powf( minV2, gamma );
398
399 float minV, maxV;
400 minV = std::min( minV1, minV2 );
401 maxV = std::max( maxV1, maxV2 );
402
403
404 if( (maxV/minV) > 10000.f ) {
405 // To avoid compressing contrast too much
406 minV = maxV / 10000.f;
407 }
408 if( (maxV/minV) < 100.f ) {
409 // To avoid streching contrast too much
410 minV = maxV / 100.f;
411 }
412
413 VERBOSE_STR << "Using log scaling (min val: " << minV1 << " max value: " << maxV1 << ")" << endl;
414
415
416 out1 = convert_and_log_scale( in1, minV, maxV1, exp_mult1, gamma );
417 out2 = convert_and_log_scale( in2, minV, maxV1, exp_mult2, gamma );
418 }
419 }
420
421 Point2f intersect_lines( Point2f p1, Point2f p2, Point2f p3, Point2f p4 )
422 {
423 float s = ((p4.x-p3.x)*(p3.y-p1.y) - (p3.x-p1.x)*(p4.y-p3.y)) / ( (p4.x-p3.x)*(p2.y-p1.y) - (p2.x-p1.x)*(p4.y-p3.y) );
424
425 Point2f p;
426 p.x = p1.x + s * (p2.x-p1.x);
427 p.y = p1.y + s * (p2.y-p1.y);
428
429 return p;
430 }
431
432 Rect auto_crop( const Size2f &size, const vector<Point2f> &rotated )
433 {
434
435 assert( rotated.size() == 4 ); // 4 corners of the polygon
436
437 Point2f centre;
438 for( int kk=0; kk < 4; kk++ )
439 centre += rotated[kk];
440
441 centre = centre * 0.25;
442
443 //cout << "Float: " << (rot_mat.type() == CV_32F) << endl;
444
445 float best_d = 1e10;
446 Point2d best_p;
447 for( int jj = 0; jj < 2; jj++ ) {
448 for( int ii=0; ii < 2; ii++ ) {
449 Point2f to;
450 if( jj == 0 )
451 to = centre - Point2f( size.width/2, size.height/2 );
452 else
453 to = centre - Point2f( -size.width/2, size.height/2 );
454
455 Point2f p1 = intersect_lines( centre, to, rotated[ii], rotated[ii+1] );
456 float d = powf(p1.x-centre.x,2)+powf(p1.y-centre.y,2);
457 if( d < best_d ) {
458 best_d = d;
459 best_p = p1;
460 }
461 }
462 }
463 Point2f ul( centre.x - fabs(best_p.x-centre.x), centre.y - fabs(best_p.y-centre.y) );
464 Point2f br( centre.x + fabs(best_p.x-centre.x), centre.y + fabs(best_p.y-centre.y) );
465
466 Rect crop( ul, br );
467
468 return crop;
469 }
470
471 struct FrameInfo {
472 Mat image;
473 std::string file_name;
474 pfs::Frame *frame;
475 Size size;
476
477 FrameInfo( Mat &image, const char *file_name, pfs::Frame *frame ) :
478 image( image ), file_name( file_name ), frame( frame )
479 {
480 size = Size( image.cols, image.rows );
481 }
482 };
483
484 void alignFrames(int argc, char *argv[])
485 {
486 pfs::DOMIO pfsio;
487
488 int reference_index = 0; // Index of the reference image
489 bool display_matches = false;
490 bool crop_min = false;
491 bool fail_on_no_match = false;
492 int sensitivity = 5;
493
494 static struct option cmdLineOptions[] = {
495 { "help", no_argument, NULL, 'h' },
496 { "verbose", no_argument, NULL, 'v' },
497 { "reference", required_argument, NULL, 'r' },
498 { "crop", required_argument, NULL, 'c' },
499 { "sensitivity", required_argument, NULL, 's' },
500 { "fail-no-match", no_argument, NULL, 'f' },
501 { "display-matches", no_argument, NULL, 'd' },
502 { NULL, 0, NULL, 0 }
503 };
504
505 int optionIndex = 0;
506 while( 1 ) {
507 int c = getopt_long (argc, argv, "r:c:s:fhvd", cmdLineOptions, &optionIndex);
508 if( c == -1 ) break;
509 switch( c ) {
510 case 'h':
511 printHelp();
512 throw QuietException();
513 case 'v':
514 verbose = true;
515 break;
516 case 'r':
517 reference_index = strtol( optarg, NULL, 10 ) - 1;
518 break;
519 case 'd':
520 display_matches = true;
521 break;
522 case 'c':
523 if( !strcasecmp( optarg, "min" ) ) {
524 crop_min = true;
525 } else if( !strcasecmp( optarg, "max" ) ) {
526 crop_min = false;
527 } else
528 throw pfs::Exception( "Unrecognized cropping mode" );
529 break;
530 case 'f':
531 fail_on_no_match = true;
532 break;
533 case 's':
534 sensitivity = strtol( optarg, NULL, 10 );
535 if( sensitivity <= 0 || sensitivity > 10 ) {
536 throw pfs::Exception( "Sensitivity parameter must be within 1-10 range.");
537 }
538 break;
539 case '?':
540 throw QuietException();
541 case ':':
542 throw QuietException();
543 }
544 }
545
546 vector<FrameInfo> frames;
547
548 if( crop_min ) {
549 VERBOSE_STR << "Cropping to the size that contains only overlaping pixels (min)" << endl;
550 } else {
551 VERBOSE_STR << "Cropping to the size that contains all pixels (max)" << endl;
552 }
553
554
555 // Load all images
556 // bool first_frame = true;
557 int frame_count = 0;
558 for( ; true; frame_count++ ) {
559
560 pfs::Frame *frame = pfsio.readFrame( stdin );
561 if( frame == NULL ) break; // No more frames
562
563 const char *f_name = frame->getTags()->getString("FILE_NAME");
564 if( f_name == NULL ) {
565 std::stringstream f_name_str;
566 f_name_str << "frame_" << frame_count;
567 f_name = f_name_str.str().c_str();
568 }
569 VERBOSE_STR << "Loading " << f_name << endl;
570
571 pfs::Channel *X, *Y, *Z;
572 frame->getXYZChannels( X, Y, Z );
573
574 if( X != NULL ) { // Color, XYZ
575 pfs::Channel* &R = X;
576 pfs::Channel* &G = Y;
577 pfs::Channel* &B = Z;
578 pfs::transformColorSpace( pfs::CS_XYZ, X, Y, Z, pfs::CS_RGB, R, G, B );
579
580 Mat img;
581 img.create( frame->getHeight(), frame->getWidth(), CV_32FC3 );
582
583 int in_index = 0;
584 for(int i = 0; i < img.rows; i++)
585 {
586 Vec3f *o_p = img.ptr<Vec3f>(i);
587 for(int j = 0; j < img.cols; j++) {
588 o_p[j][0] = (*B)(in_index);
589 o_p[j][1] = (*G)(in_index);
590 o_p[j][2] = (*R)(in_index);
591 in_index++;
592 }
593 }
594
595 frames.push_back( FrameInfo( img, f_name, frame ) );
596
597 } else
598 throw pfs::Exception( "Only color images are supported" );
599
600
601 // Remove all channels from the frame to save memory
602 pfs::ChannelIteratorPtr cit( frame->getChannelIterator() );
603 while( cit->hasNext() ) {
604 pfs::Channel *ch = cit->getNext();
605 frame->removeChannel( ch );
606 }
607
608 }
609
610 if( reference_index < 0 || (size_t)reference_index >= frames.size() )
611 throw pfs::Exception( "Reference image index out of range" );
612
613
614 vector<Mat> homoM;
615 vector<Size> imageSizes;
616 for( int ff=1; ff < (int)frames.size(); ff++ ) {
617 VERBOSE_STR << "Processing " << frames[ff-1].file_name << " and " << frames[ff].file_name << endl;
618
619 cv::Mat ref_img = frames[ff-1].image;
620 cv::Mat exp_img = frames[ff].image;
621
622 imageSizes.push_back( exp_img.size() );
623
624 bool is_ldr = false;
625 const char *lum_type = frames[ff].frame->getTags()->getString("LUMINANCE");
626 if( lum_type ) {
627 if( !strcmp( lum_type, "DISPLAY" ) )
628 is_ldr = true;
629 }
630
631 // Extract exposure values
632 float exp1 = 1.f, exp2 = 1.f;
633 const char* exp_str = frames[ff-1].frame->getTags()->getString("BV");
634 if( exp_str != NULL )
635 exp1 = powf(2.0f,strtof( exp_str, NULL ));
636 exp_str = frames[ff].frame->getTags()->getString("BV");
637 if( exp_str != NULL )
638 exp2 = powf(2.0f,strtof( exp_str, NULL ));
639
640 if( exp1 / exp2 != 1.f ) {
641 VERBOSE_STR << "Exposure pair: " << exp1 << ", " << exp2 << " (" << exp2/exp1 << " ratio)" << endl;
642 }
643
644 const float exp_mult1 = exp1 / std::min( exp1, exp2 );
645 const float exp_mult2 = exp2 / std::min( exp1, exp2 );
646
647 Mat ref_img_8b, exp_img_8b;
648
649 // Mat ref_img_8b = convert_to_8bit( ref_img, apply_gamma );
650 // Mat exp_img_8b = convert_to_8bit( exp_img, apply_gamma );
651 // convert_to_8bit( ref_img, exp_img, ref_img_8b, exp_img_8b, 1, 1, is_ldr );
652 convert_to_8bit( ref_img, exp_img, ref_img_8b, exp_img_8b, exp_mult1, exp_mult2, is_ldr );
653
654 Mat homography;
655 bool success = alignImagePair( ref_img_8b, exp_img_8b, homography, sensitivity, display_matches );
656 if( !success && fail_on_no_match )
657 throw pfs::Exception( "Stopping because could not find a match between image pair");
658
659 VERBOSE_STR << "Homography (for the image pair):" << endl << homography << endl;
660
661 homoM.push_back( homography );
662 }
663
664
665 // Chain all transformations and find the cropping box
666 vector<Mat> homoMC;
667 Rect_<double> pano_rect( 0, 0, frames[0].image.cols, frames[0].image.rows );
668 for( int kk = 0; kk < (int)frames.size(); kk++ )
669 {
670 Mat_<double> trans = Mat::eye(3,3,CV_64F);
671 for( int ll = min( kk, reference_index )+1; ll <= max( kk, reference_index); ll++ )
672 {
673 if( ll > 0 )
674 trans = trans*homoM[ll-1];
675 }
676 if( kk < reference_index )
677 trans = trans.inv();
678
679 homoMC.push_back( trans );
680
681 double data[4][3] = { { 0., 0., 1. }, { (double)frames[kk].size.width, 0., 1. }, { (double)frames[kk].size.width, (double)frames[kk].size.height, 1. }, { 0., (double)frames[kk].size.height, 1. } };
682 Mat corners( 4, 3, CV_64F, data );
683
684 Mat corners_trans = trans * corners.t();
685
686 // VERBOSE_STR << "Image: " << fileNames[kk] << endl;
687 // VERBOSE_STR << " Corners: " << trans * corners.t() << endl;
688
689 Mat p_nh; //( 4, 3, CV_32F );
690 Mat_<float> corners_f;
691 corners_trans.convertTo( corners_f, CV_32F );
692 convertPointsFromHomogeneous( corners_f.t(), p_nh );
693
694
695 if( crop_min ) {
696 vector<Point2f> dest_rect(4);
697 for( int ii=0; ii < 4; ii++ ) {
698 dest_rect[ii].x = p_nh.at<float>(ii,0);
699 dest_rect[ii].y = p_nh.at<float>(ii,1);
700 }
701 Rect_<float> img_rect = auto_crop( frames[kk].size, dest_rect );
702 Rect_<double> img_rect_d = img_rect;
703 pano_rect = pano_rect & img_rect_d;
704 } else {
705 Rect_<double> img_rect = boundingRect( p_nh );
706 pano_rect = pano_rect | img_rect;
707 }
708
709
710 // VERBOSE_STR << "Bounding rect: " << pano_rect.x << ", " << pano_rect.y << " - " << pano_rect.width << "x" << pano_rect.height << endl;
711 }
712
713
714 // Align
715 Size dest_size = Size( ceil( pano_rect.width ), ceil( pano_rect.height) );
716 // Mat pano_img( ceil( pano_rect.height), ceil( pano_rect.width ), CV_8UC3 );
717 // pano_img.setTo( Vec3b(255, 255, 255 ));
718 for( size_t ff=0; ff < frames.size(); ff++ ) {
719 VERBOSE_STR << "Warping: "<< frames[ff].file_name << endl;
720
721 Mat exp_img = frames[ff].image;
722
723 Mat_<double> translate = Mat::eye(3,3,CV_64F);
724 translate(0,2) = -pano_rect.x;
725 translate(1,2) = -pano_rect.y;
726 Mat trans = translate*homoMC[ff];
727 VERBOSE_STR << "Homography (to reference): " << trans << endl;
728
729 Mat res_img;
730 warpPerspective( exp_img, res_img, trans, dest_size, INTER_LINEAR );
731
732 pfs::Frame *alignedFrame = NULL;
733 alignedFrame = pfsio.createFrame( res_img.cols, res_img.rows );
734
735 pfs::Channel *X = alignedFrame->createChannel( "X" );
736 pfs::Channel *Y = alignedFrame->createChannel( "Y" );
737 pfs::Channel *Z = alignedFrame->createChannel( "Z" );
738
739 pfs::Channel* &R = X;
740 pfs::Channel* &G = Y;
741 pfs::Channel* &B = Z;
742
743 int out_index = 0;
744 for(int i = 0; i < res_img.rows; i++)
745 {
746 Vec3f *i_p = res_img.ptr<Vec3f>(i);
747 for(int j = 0; j < res_img.cols; j++) {
748 (*B)(out_index) = i_p[j][0];
749 (*G)(out_index) = i_p[j][1];
750 (*R)(out_index) = i_p[j][2];
751 out_index++;
752 }
753 }
754 pfs::transformColorSpace( pfs::CS_RGB, R, G, B, pfs::CS_XYZ, X, Y, Z );
755 pfs::copyTags( frames[ff].frame, alignedFrame );
756
757 pfsio.writeFrame( alignedFrame, stdout );
758
759 pfsio.freeFrame( alignedFrame );
760 pfsio.freeFrame( frames[ff].frame );
761
762
763 // TODO: Add alpha channel
764 /* Mat in_mask( exp_img.size(), CV_8U ), out_mask;
765 in_mask.setTo( Scalar( 255 ) );
766 warpPerspective( in_mask, out_mask, trans, pano_img.size(), INTER_LINEAR );
767 res_img.copyTo( pano_img, out_mask );
768
769 double data[4][3] = { { 0, 0, 1 }, { exp_img.cols, 0, 1 }, { exp_img.cols, exp_img.rows, 1 }, { 0, exp_img.rows, 1 } };
770 Mat corners( 4, 3, CV_64F, data );
771 Mat_<double> corners_trans = trans * corners.t();
772 Mat p_nh; //( 4, 3, CV_32F );
773 Mat_<float> corners_f;
774 corners_trans.convertTo( corners_f, CV_32F );
775 convertPointsFromHomogeneous( corners_f.t(), p_nh );*/
776 // Scalar borderColor = CV_RGB( 0, 0, 0 );
777 // line( pano_img, Point2f( p_nh(0,0), p_nh(0,1) ), Point2f( p_nh(1,0), p_nh(1,1) ), borderColor, 3 );
778 // line( pano_img, Point2f( p_nh(1,0), p_nh(1,1) ), Point2f( p_nh(2,0), p_nh(2,1) ), borderColor, 3 );
779 // line( pano_img, Point2f( p_nh(2,0), p_nh(2,1) ), Point2f( p_nh(3,0), p_nh(3,1) ), borderColor, 3 );
780 // line( pano_img, Point2f( p_nh(3,0), p_nh(3,1) ), Point2f( p_nh(0,0), p_nh(0,1) ), borderColor, 3 );
781 }
782
783 }
784
785 int main( int argc, char* argv[] )
786 {
787 try {
788 alignFrames( argc, argv );
789 }
790 catch( pfs::Exception ex ) {
791 fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() );
792 return EXIT_FAILURE;
793 }
794 catch( QuietException ex ) {
795 return EXIT_FAILURE;
796 }
797 return EXIT_SUCCESS;
798 }