diff options
Diffstat (limited to 'editor/pnmstitch.c')
-rw-r--r-- | editor/pnmstitch.c | 2408 |
1 files changed, 2408 insertions, 0 deletions
diff --git a/editor/pnmstitch.c b/editor/pnmstitch.c new file mode 100644 index 00000000..61f02a04 --- /dev/null +++ b/editor/pnmstitch.c @@ -0,0 +1,2408 @@ +/* + * Copyright (c) 2002 Mark Salyzyn + * All rights reserved. + * + * TERMS AND CONDITIONS OF USE + * + * Redistribution and use in source form, with or without modification, are + * permitted provided that redistributions of source code must retain the + * above copyright notice, this list of conditions and the following + * disclaimer. + * + * This software is provided `as is' by Mark Salyzyn and any express or implied + * warranties, including, but not limited to, the implied warranties of + * merchantability and fitness for a particular purpose, are disclaimed. In no + * event shall Mark Salyzyn be liable for any direct, indirect, incidental, + * special, exemplary or consequential damages (including, but not limited to, + * procurement of substitute goods or services; loss of use, data, or profits; + * or business interruptions) however caused and on any theory of liability, + * whether in contract, strict liability, or tort (including negligence or + * otherwise) arising in any way out of the use of this software, even if + * advised of the possibility of such damage. + * + * Any restrictions or encumberances added to this source code or derivitives, + * is prohibited. + * + * Name: pnmstitch.c + * Description: Automated panoramic stitcher. + * Many digital Cameras have a panorama mode where they hold on to + * the right hand side of an image, shifted to the left hand side of their + * view screen for subsequent pictures, facilitating the manual stitching + * up of a panoramic shot. These same cameras are shipped with software + * to manually or automatically stitch images together into the composite + * image. However, these programs are dedicated for a specific OS. + * In the pnmstitch program, it analyzes the match between the images, + * generates a transform, processes the transform on the images, and + * blends the overlapping regions. In addition, there is an output filter + * to process automatic cropping of the resultant image. + * The stitching software here works by constraining the right + * hand side of the right hand image as `fixed' per-se, after offset + * evaluation and only the left hand side of the right hand image is + * mangled. Thus, the algorithm is optimized for stitching a right hand + * image to the right hand half (half being a loose term) of the left hand + * image. + * Author: Mark Salyzyn <mark@bohica.net> June 2002 + * Version: 0.0.4 + * + * Modifications: 0.0.4 July 31 2002 Mark Salyzyn <mark@bohica.net> + * & Bryan Henderson <bryanh@giraffe-data.com> + * - FreeBSD port. + * - merge changes to incorporate into netpbm tree. + * Modifications: 0.0.3 July 27 2002 Mark Salyzyn <mark@bohica.net> + * & "George M. Sipe" <geo@sipe.org> + * - Deal with subtle differences between BSD and GNU getopt + * facilitating the Linux port. + * Modifications: 0.0.2 July 25 2002 Mark Salyzyn <mark@bohica.net> + * - RotateSliver needs to use higher resolution match. + * - RotateCrop code interfered with StraightThrough code + * resulting in an incorrect pnm image output. + * Modifications: 0.0.1 July 18 2002 Mark Salyzyn <mark@bohica.net> + * - Added BiLinearSliver, RotateSliver and HorizontalCrop + * + * ToDo: + * - Split this into multiple files ... nah, keep it in one to + * keep it from polluting the netpbm tree. + * - Add and refine the videorbits algorithm. One piece of public + * domain software that is pnm aware is called videorbits. It + * needs considerably more overlap than the Digital Cameras set + * up (of course, anyone can to a panorama with more overlap + * with our without the feature) as it uses what is called video + * flow to generate the match. It is designed more for a series + * of images from a video camera. videorbits has three programs, + * one generates the transform, the next processes the + * transform, and the final blends the images together much as + * this one piece program does. + * - speedups in matching algorithm + * - refinement in accuracy of matching algorithm + * - Add RotateCrop filter algorithm (in-memory copy of image, + * detect least loss horizontal crop on a rotated image). + * - pnmstitch should be generalized to handle transformation + * occuring on the left image, currently it blends assuming + * that there is no transformation effects on the left image. + * - user selectable blending algorithms? + */ + +#define _BSD_SOURCE 1 /* Make sure strdup() is in string.h */ +#define _XOPEN_SOURCE 500 /* Make sure strdup() is in string.h */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <limits.h> +#include <math.h> + +#include "pm_c_util.h" +#include "shhopt.h" +#include "nstring.h" +#include "mallocvar.h" +#include "pam.h" + +/* + * Structures + */ + +/* + * Image structure + */ +typedef struct { + const char * name; /* File Name */ + struct pam pam; /* netpbm image description */ + tuple ** tuple; /* in-memory copy of image */ +} Image; + +/* + * Output class + * The following methods and data allocations are used for output filter. + */ +typedef struct output { + /* name */ + const char * Name; + /* methods */ + bool (* Alloc)(struct output * me, const char * file, + unsigned int width, unsigned int height, + struct pam * prototype); + void (* DeAlloc)(struct output * me); + tuple *(* Row)(struct output * me, unsigned row); + void (* FlushRow)(struct output * me, unsigned row); + void (* FlushImage)(struct output * me); + /* data */ + Image * image; + void * extra; +} Output; + +extern Output OutputMethods[]; + +/* + * Stitching class + * The following methods and data allocations are used for operations + * surrounding stitching of an image. + */ +typedef struct stitcher { + /* name */ + const char * Name; + /* methods */ + bool (* Alloc)(struct stitcher *me); + void (* DeAlloc)(struct stitcher *me); + void (* Constrain)(struct stitcher *me, int x, int y, + int width, int height); + /* Set transformation parameter constraints. This affects the + function of a future 'Match' method execution. + */ + bool (* Match)(struct stitcher *me, Image * Left, Image * Right); + /* Determine the transformation parameters for the stitching. + I.e. determine the parameters that affect future invocations + of the transformation methods below. You must execute a + 'Match' before executing any of the transformation methods. + */ + /*----------------------------------------------------------------------- + The transformation methods answer the question, "Which pixel in the left + image and which pixel in the right image contribute to the pixel at + Column X, Column Y of the output? + + If there is no pixel in the left image that contributes to the output + pixel in question, the methods return column or row numbers outside + the bounds of the left image (possibly negative). Likewise for the + right image. + */ + float (* XLeft)(struct stitcher *me, int x, int y); + /* column number of the pixel from the left image */ + float (* YLeft)(struct stitcher *me, int x, int y); + /* row number of the pixel from the left image */ + float (* XRight)(struct stitcher *me, int x, int y); + /* column number of the pixel from the right image */ + float (* YRight)(struct stitcher *me, int x, int y); + /* row number of the pixel from the left image */ + /*----------------------------------------------------------------------*/ + /* Output methods */ + void (* Output)(struct stitcher *me, FILE * fp); + /* private data */ + int x, y, width, height; + /* For a Linear Sliver stitcher, 'x' and 'y' are simply the offset you + add to an output location to get the location in the right image of the + pixel that corresponds to that output pixel. + */ + float * parms; +} Stitcher; + +extern Stitcher StitcherMethods[]; + +/* + * Prototypes + */ +static int pnmstitch(const char * const left, + const char * const right, + const char * const out, + int const x, + int const y, + int const width, + int const height, + const char * const stitcher, + const char * const filter); + +struct cmdlineInfo { + /* + * All the information the user supplied in the command line, + * in a form easy for the program to use. + */ + const char * leftFilespec; /* '-' if stdin */ + const char * rightFilespec; /* '-' if stdin */ + const char * outputFilespec; /* '-' if stdout */ + const char * stitcher; + const char * filter; + int width; + int height; + int xrightpos; + int yrightpos; + unsigned int verbose; +}; + +static char minus[] = "-"; + +static void +parseCommandLine ( int argc, char ** argv, + struct cmdlineInfo *cmdlineP ) +{ +/*---------------------------------------------------------------------------- + parse program command line described in Unix standard form by argc + and argv. Return the information in the options as *cmdlineP. + + If command line is internally inconsistent (invalid options, etc.), + issue error message to stderr and abort program. + + Note that the strings we return are stored in the storage that + was passed to us as the argv array. We also trash *argv. +-----------------------------------------------------------------------------*/ + optEntry *option_def = malloc( 100*sizeof( optEntry ) ); + /* Instructions to optParseOptions3 on how to parse our options. + */ + optStruct3 opt; + + unsigned int option_def_index; + + char *outputOpt; + unsigned int widthSpec, heightSpec, outputSpec, + xrightposSpec, yrightposSpec, stitcherSpec, filterSpec; + + option_def_index = 0; /* incremented by OPTENT3 */ + OPTENT3(0, "width", OPT_UINT, &cmdlineP->width, + &widthSpec, 0); + OPTENT3(0, "height", OPT_UINT, &cmdlineP->height, + &heightSpec, 0); + OPTENT3(0, "verbose", OPT_FLAG, NULL, + &cmdlineP->verbose, 0 ); + OPTENT3(0, "output", OPT_STRING, &outputOpt, + &outputSpec, 0); + OPTENT3(0, "xrightpos", OPT_UINT, &cmdlineP->xrightpos, + &xrightposSpec, 0); + OPTENT3(0, "yrightpos", OPT_UINT, &cmdlineP->yrightpos, + &yrightposSpec, 0); + OPTENT3(0, "stitcher", OPT_STRING, &cmdlineP->stitcher, + &stitcherSpec, 0); + OPTENT3(0, "filter", OPT_STRING, &cmdlineP->filter, + &filterSpec, 0); + + opt.opt_table = option_def; + opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ + opt.allowNegNum = FALSE; /* We have no parms that are negative numbers */ + + optParseOptions3( &argc, argv, opt, sizeof(opt), 0); + /* Uses and sets argc, argv, and some of *cmdlineP and others. */ + + if (!widthSpec) { + cmdlineP->width = INT_MAX; + } + if (!heightSpec) { + cmdlineP->height = INT_MAX; + } + if (!xrightposSpec) { + cmdlineP->xrightpos = INT_MAX; + } + if (!yrightposSpec) { + cmdlineP->yrightpos = INT_MAX; + } + if (!stitcherSpec) { + cmdlineP->stitcher = "BiLinearSliver"; + } + if (!filterSpec) { + cmdlineP->filter = "StraightThrough"; + } + + if (argc-1 > 3) { + pm_error("Program takes at most three arguments: left, right, and " + "output file specifications. You specified %d", argc-1); + /* NOTREACHED */ + } else { + if (argc-1 == 0) { + cmdlineP->leftFilespec = minus; + cmdlineP->rightFilespec = minus; + } else if (argc-1 == 1) { + cmdlineP->leftFilespec = minus; + cmdlineP->rightFilespec = argv[1]; + } else { + cmdlineP->leftFilespec = argv[1]; + cmdlineP->rightFilespec = argv[2]; + } + if (argc-1 == 3 && outputSpec) { + pm_error("You cannot specify --output and also name the " + "output file with the 3rd argument."); + /* NOTREACHED */ + } else if (argc-1 == 3) { + cmdlineP->outputFilespec = argv[3]; + } else if (outputSpec) { + cmdlineP->outputFilespec = outputOpt; + } else { + cmdlineP->outputFilespec = minus; + } + } +} /* parseCommandLine() - end */ + +static int verbose; + +/* + * Parse the command line, call pnmstitch to perform work. + */ +int +main (int argc, char **argv) +{ + struct cmdlineInfo cmdline; + + pnm_init(&argc, argv); + + parseCommandLine(argc, argv, &cmdline); + + verbose = cmdline.verbose; + + return pnmstitch (cmdline.leftFilespec, + cmdline.rightFilespec, + cmdline.outputFilespec, + cmdline.xrightpos, + cmdline.yrightpos, + cmdline.width, + cmdline.height, + cmdline.stitcher, + cmdline.filter); +} /* main() - end */ + + + +/* + * allocate a clear image structure. + */ +static Image * +allocate_image(void) +{ + Image * retVal; + + MALLOCVAR(retVal); + + if (retVal != NULL) + memset (retVal, 0, (unsigned)sizeof(Image)); + + return retVal; +} + + + +/* + * free an image structure. + */ +static void +free_image(Image * image) +{ + if (image->name) { + strfree(image->name); + image->name = NULL; + } + if (image->tuple) { + pnm_freepamarray(image->tuple, &image->pam); + image->tuple = NULL; + } + if (image->pam.file) { + fclose (image->pam.file); + image->pam.file = NULL; + } + free (image); +} + + + +static void +openWithPossibleExtension(const char * const baseName, + FILE ** const ifPP, + const char ** const filenameP) { + + /* list of possible extensions for input file */ + const char * const extlist[] = { + "", ".pnm", ".pam", ".pgm", ".pbm", ".ppm" + }; + + FILE * ifP; + unsigned int extIndex; + + ifP = NULL; /* initial value -- no file opened yet */ + + for (extIndex = 0; extIndex < ARRAY_SIZE(extlist) && !ifP; ++extIndex) { + + const char * trialName; + + asprintfN(&trialName, "%s%s", baseName, extlist[extIndex]); + + ifP = fopen(trialName, "rb"); + + if (ifP) + *filenameP = trialName; + else + strfree(trialName); + } + if (!ifP) + pm_error ("Failed to open input file named '%s' " + "or '%s' with one of various common extensions.", + baseName, baseName); + + *ifPP = ifP; +} + + + +/* + * Create an image object for the PNM/PAM image in the file name 'name' + * This includes reading the entire image. + */ +static Image * +readinit(const char * const name) +{ + Image * const image = allocate_image(); + Image * retVal; + + if (image == NULL) + retVal = NULL; + else { + FILE * ifP; + + if (strcmp(name,minus) == 0) { + ifP = stdin; + image->name = strdup("<stdin>"); + } else { + openWithPossibleExtension(name, &ifP, &image->name); + } + image->tuple = pnm_readpam(ifP, &(image->pam), + PAM_STRUCT_SIZE(tuple_type)); + fclose (ifP); + image->pam.file = NULL; + + if (image->tuple == NULL) { + free_image(image); + retVal = NULL; + } else + retVal = image; + } + return retVal; +} /* readinit() - end */ + +/* + * Prepare an image to be output. + * Too bad we can't help them and add a .pnm on the filename, + * since this would be `bad'. on readinit we can check if the .pnm + * needs to be added ... + */ +static bool +writeinit(Image * image) +{ + if (strcmp(image->name,minus) == 0) { + image->pam.file = stdout; + strfree(image->name); + image->name = strdup("<stdout>"); + } else { + image->pam.file = pm_openw(image->name); + } + return TRUE; +} /* writeinit() - end */ + +/* + * Compare a subimage to an image. + * The most time consuming actions surround this subroutine. + * Return the magnitude of the difference between the specified region + * in image 'left' and the specified region in image 'right'. + * The magnitude is defined as the sum of the squares of the differences + * between intensities of each of the 3 colors over all the pixels. + * + * The region in the left image is the rectangle with top left corner at + * Column 'lx', Row 'ly', with dimensions 'width' columns by 'height' + * rows. The region in the right image is a rectangle the same dimensions + * with upper left corner at Column 'rx', Row 'ry'. + * + * Caller must ensure that the regions indicated are entirely within the + * their respective images. + */ +static unsigned long +regionDifference(Image * left, + int lx, + int ly, + Image * right, + int rx, + int ry, + int width, + int height) +{ + unsigned long total; + unsigned row; + + total = 0; /* initial value */ + + for (row = 0; row < height; ++row) { + unsigned column; + + for (column = 0; column < width; ++column) { + unsigned plane; + + for (plane = 0; plane < left->pam.depth; ++plane) { + sample const leftSample = left->tuple[row][column][plane]; + sample const rightSample = right->tuple[row][column][plane]; + total += SQR(leftSample - rightSample); + } + } + } + return total; +} + + + +/* + * Generate a recent sorted histogram of best matches. + */ +typedef struct { + unsigned long total; + int x; + int y; +} Best; +/* Arbitrary, except 9 points surrounding a hot spot plus one layer more */ +#define NUM_BEST 9 + +/* + * Allocate the Best structure. + */ +static Best * +allocate_best(void) +{ + Best * retVal; + + MALLOCARRAY(retVal, NUM_BEST); + + if (retVal != NULL) { + unsigned int i; + for (i = 0; i < NUM_BEST; ++i) { + retVal[i].total = ULONG_MAX; + retVal[i].x = INT_MAX; + retVal[i].y = INT_MAX; + } + } + return retVal; +} + +/* + * Free the Best structure + */ +#define free_best(best) free(best) + +/* + * Placement helper for the Best structure. + */ +static void +update_best(Best * best, unsigned long total, int x, int y) +{ + int i; + if (best[NUM_BEST-1].total <= total) { + return; + } + for (i = NUM_BEST - 1; i > 0; --i) { + if (best[i-1].total < total) { + break; + } + best[i] = best[i-1]; + } + best[i].total = total; + best[i].x = x; + best[i].y = y; +} + +/* + * Print helper for the Best structure. + */ +static void +pr_best(Best * const best) +{ + int i; + if (best != (Best *)NULL) + for (i = 0; i < NUM_BEST; ++i) { + fprintf (stderr, " (%d,%d)%lu", + best[i].x, best[i].y, best[i].total); + } +} /* pr_best() - end */ + +static void * +findObject(const char * const name, void * start, unsigned size) +{ + char ** object; + char ** best; + unsigned length; + + if (name == (char *)NULL) { + return start; + } + for (length = 0, best = (char **)NULL, object = (char **)start; + *object != (char *)NULL; + object = (char **)(((char *)object) + size)) { + const char * np = name; + char * op = *object; + unsigned matched = 0; + + /* case insensitive match */ + while ((*np != '\0') && (*op != '\0') + && ((*np == *op) || (tolower(*np) == tolower(*op)))) { + ++np; + ++op; + ++matched; + } + if ((*np == '\0') && (*op == '\0')) { + break; + } + if ((matched >= length) && (*np == '\0')) { + if (matched == length) { + best = (char **)NULL; + } else { + best = object; + } + length = matched; + } + } + if (*object == (char *)NULL) { + object = best; + } + if (object == (char **)NULL) { + fprintf (stderr, + "Unknown driver \"%s\". available drivers are:\n", name); + for (object = (char **)start; + *object != (char *)NULL; + object = (char **)(((char *)object) + size)) { + fprintf (stderr, "\t%s%s\n", *object, + (object == (char **)start) ? " (default)" : ""); + } + } + return object; +} + +/* + * The general wrapper for both the Output and the Stitcher algorithms. + */ + +/* Determine the mask corners for both Left and Right images */ +static void +determineMaskCorners(Stitcher * Stitch, + Image * Left, + Image * Right, + int xp[], + int yp[]) +{ + int i; + + xp[0] = xp[1] = xp[4] = xp[5] = 0; + yp[0] = yp[2] = yp[4] = yp[6] = 0; + xp[2] = xp[3] = Left->pam.width; + yp[1] = yp[3] = Left->pam.height; + xp[6] = xp[7] = Right->pam.width; + yp[5] = yp[7] = Right->pam.height; + for (i = 0; i < 8; ++i) { + int x, y, xx, yy, count = 65536; /* max iterations */ + float (*X)(Stitcher *me, int x, int y); + float (*Y)(Stitcher *me, int x, int y); + + if (i < 4) { + X = Stitch->XLeft; + Y = Stitch->YLeft; + } else { + X = Stitch->XRight; + Y = Stitch->YRight; + } + x = xp[i]; + y = yp[i]; + /* will not work if rotated 90o or if gain > 10 */ + do { + xx = ((*X)(Stitch, xp[i], yp[i]) + 0.5) - x; + if (xx < 0) { + if (xx > -100) { + ++xp[i]; + } else { + xp[i] -= xx / 10; + } + } else if (xx > 0) { + if (xx < 100) { + --xp[i]; + } else { + xp[i] -= xx / 10; + } + } + yy = ((*Y)(Stitch, xp[i], yp[i]) + 0.5) - y; + if (yy < 0) { + if (yy > -100) { + ++yp[i]; + } else { + yp[i] -= yy / 10; + } + } else if (yy > 0) { + if (yy < 100) { + --yp[i]; + } else { + yp[i] -= yy / 10; + } + } + } while (((xx != 0) || (yy != 0)) && (--count != 0)); + } + if (verbose) { + (*(Stitch->Output))(Stitch, stderr); + if (verbose > 2) { + static char quotes[] = "'\0\0\"\0\0'\"\0\"\""; + fprintf (stderr, " Left:"); + for (i = 0; i < 8; ++i) { + if (i == 4) { + fprintf (stderr, "\n Right:"); + } + fprintf (stderr, " x%s,y%s=%d,%d", + "es[(i%4)*3], "es[(i%4)*3], + xp[i], yp[i]); + } + } + } +} /* determineMaskCorners() - end */ + +static void +calculateXyWidthHeight(int xp[], + int yp[], + int * const xP, + int * const yP, + int * const widthP, + int * const heightP) +{ + int x, y, width, height, i; + + /* Calculate generic x,y left top corner, and the width and height */ + x = xp[0]; + y = yp[0]; + width = height = 0; + for (i = 1; i < 8; ++i) { + if (xp[i] < x) { + width += x - xp[i]; + x = xp[i]; + } else if ((x + width) < xp[i]) { + width = xp[i] - x; + } + if (yp[i] < y) { + height += y - yp[i]; + y = yp[i]; + } else if ((y + height) < yp[i]) { + height = yp[i] - y; + } + } + *xP = x; *yP = y; + *widthP = width; *heightP = height; +} /* calculateXyWidthHeight() - end */ + +static void +printPlan(int xp[], int yp[], Image * Left, Image * Right) +{ + /* Calculate Left image transformed bounds */ + int X, Y, W, H, i; + + X = xp[0]; + Y = yp[0]; + W = H = 0; + for (i = 1; i < 4; ++i) { + if (xp[i] < X) { + W += X - xp[i]; + X = xp[i]; + } else if ((X + W) < xp[i]) { + W = xp[i] - X; + } + if (yp[i] < Y) { + H += Y - yp[i]; + Y = yp[i]; + } else if ((Y + H) < yp[i]) { + H = yp[i] - Y; + } + } + fprintf (stderr, + "%s[%u,%u=>%d,%d](%d,%d)", + Left->name, Left->pam.width, Left->pam.height, + W, H, X, Y); + X = xp[i]; + Y = yp[i]; + W = H = 0; + for (++i; i < 8; ++i) { + if (xp[i] < X) { + W += X - xp[i]; + X = xp[i]; + } else if ((X + W) < xp[i]) { + W = xp[i] - X; + } + if (yp[i] < Y) { + H += Y - yp[i]; + Y = yp[i]; + } else if ((Y + H) < yp[i]) { + H = yp[i] - Y; + } + } + fprintf (stderr, + "+%s[%u,%u=>%d,%d](%d,%d)", + Right->name, Right->pam.width, Right->pam.height, + W, H, X, Y); +} /* printPlan() - end */ + + + +static void +stitchOnePixel(Image * const Left, + Image * const Right, + struct pam const outpam, + int const row, + int const column, + int const y, + int const right_row, + int const right_column, + unsigned * const firstRightP, + tuple const outPixel) { + + unsigned plane; + + for (plane = 0; plane < outpam.depth; ++plane) { + sample leftPixel, rightPixel; + /* Left `mix' is easy to find */ + leftPixel = (column < Left->pam.width) + ? (y < 0) + ? ((row < -y) || (row >= (Left->pam.height - y))) + ? 0 + : Left->tuple[row + y][column][plane] + : (row < Left->pam.height) + ? Left->tuple[row][column][plane] + : 0 + : 0; + rightPixel = 0; + if (right_column >= 0) { + rightPixel = Right->tuple[right_row][right_column][plane]; + if ((rightPixel > 0) && (*firstRightP == 0)) + *firstRightP = column; + } + if (leftPixel == 0) { + leftPixel = rightPixel; + } else if ((*firstRightP <= column) + && (column < Left->pam.width) + && (rightPixel > 0)) { + /* blend 7/8 over half of stitch */ + int const w = Left->pam.width - *firstRightP; + if (column < (*firstRightP + w/2)) { + int const v = (w * 4) / 7; + leftPixel = (sample)( + ((leftPixel + * (unsigned long)(*firstRightP + v - column)) + + (rightPixel + * (unsigned long)(column - *firstRightP))) + / (unsigned long)v); + } else { + int const v = w * 4; + leftPixel = (sample)( + ((leftPixel + * (unsigned long)(Left->pam.width - column)) + + (rightPixel + * (unsigned long)(column - Left->pam.width + v))) + / (unsigned long)v); + } + } + outPixel[plane] = leftPixel; + } +} + + + +static void +stitchOneRow(Image * const Left, + Image * const Right, + Output * const Out, + Stitcher * const Stitch, + int const row, + int const y) { + + /* + * We scale the overlap of the left and right images, we need to + * discover and hold on to the left edge of the right image to + * determine the rate at which we blend. Most (7/8) of the blending + * occurs in the first half of the overlap to reduce the occurences + * of blending artifacts. If there is no overlap, the image present + * has no blending activity, this is determined by the black + * background and is not through an alpha layer to help reduce + * storage needs. The algorithm below is complicated most by + * the blending determinations, overlapping a left untransformed + * image with a right transformed image with a black background is + * all that remains. + */ + /* + * Normalize transformation against origin, the + * transformation algorithm was in reference to the right + * hand side of the left hand image before. + */ + tuple * const Row = (*(Out->Row))(Out,row); + + unsigned column, firstRight; + + firstRight = 0; /* initial value */ + + for (column = 0; column < Out->image->pam.width; ++column) { + int right_row, right_column; + + right_row = -1; + right_column = (*(Stitch->XRight))(Stitch, column, + (y < 0) ? (row + y) : row) + 0.5; + if ((0 <= right_column) + && (right_column < Right->pam.width)) { + right_row = (*(Stitch->YRight))(Stitch, column, + (y < 0) ? (row + y) : row) + 0.5; + if ((right_row < 0) + || (Right->pam.height <= right_row)) { + right_column = -1; + right_row = -1; + } + } else + right_column = -1; + + /* Create the pixel at column 'column' of row 'row' of the + output 'Out': Row[column]. + */ + stitchOnePixel(Left, Right, Out->image->pam, row, column, y, + right_row, right_column, &firstRight, Row[column]); + } +} + + + +static void +stitchit(Image * const Left, + Image * const Right, + const char * const outfilename, + const char * const filter, + Stitcher * const Stitch, + int * const retvalP) { + + Output * const Out = findObject(filter, &OutputMethods[0], + sizeof(OutputMethods[0])); + unsigned row; + int xp[8], yp[8], x, y, width, height; + + if ((Out == (Output *)NULL) || (Out->Name == (char *)NULL)) + *retvalP = -2; + else { + if (verbose) + fprintf (stderr, "Selected %s output filter algorithm\n", + Out->Name); + + /* Determine the mask corners for both Left and Right images */ + determineMaskCorners(Stitch, Left, Right, xp, yp); + + /* Output the combined images */ + + /* Calculate generic x,y left top corner, and the width and height */ + calculateXyWidthHeight(xp, yp, &x, &y, &width, &height); + + if (verbose) + printPlan(xp, yp, Left, Right); + + if (!(*(Out->Alloc))(Out, outfilename, width, height, &Left->pam)) + *retvalP = -9; + else { + if (verbose) { + fprintf (stderr, + "=%s[%u,%u=>%d,%d](%d,%d)\n", + Out->image->name, Out->image->pam.width, + Out->image->pam.height, width, height, x, y); + } + for (row = 0; row < Out->image->pam.height; row++) { + /* Generate row number 'row' of the output image 'Out' */ + stitchOneRow(Left, Right, Out, Stitch, row, y); + (*(Out->FlushRow))(Out,row); + } + (*(Out->FlushImage))(Out); + (*(Out->DeAlloc))(Out); + + *retvalP = 0; + } + } +} + + + +static int +pnmstitch(const char * const leftfilename, + const char * const rightfilename, + const char * const outfilename, + int const reqx, + int const reqy, + int const reqWidth, + int const reqHeight, + const char * const stitcher, + const char * const filter) +{ + Stitcher * const Stitch = findObject(stitcher, &StitcherMethods[0], + sizeof(StitcherMethods[0])); + Image * Left; + Image * Right; + int retval; + + if ((Stitch == (Stitcher *)NULL) || (Stitch->Name == (char *)NULL)) + retval = -1; + else { + if (verbose) + fprintf (stderr, "Selected %s stitcher algorithm\n", + Stitch->Name); + + /* Left hand image read into memory */ + Left = readinit(leftfilename); + if (Left == NULL) + retval = -3; + else { + /* Right hand image read into memory */ + Right = readinit(rightfilename); + if (Right == NULL) + retval = -4; + else { + if (Left->pam.depth != Right->pam.depth) { + fprintf(stderr, "Images should have matching depth. " + "The left image has depth %d, " + "while the right has depth %d.", + Left->pam.depth, Right->pam.depth); + retval = -5; + } else if (Left->pam.maxval != Right->pam.maxval) { + fprintf (stderr, + "Images should have matching maxval. " + "The left image has maxval %u, " + "while the right has maxval %u.", + (unsigned)Left->pam.maxval, + (unsigned)Right->pam.maxval); + retval = -6; + } else if ((*(Stitch->Alloc))(Stitch) == FALSE) + retval = -7; + else { + (*(Stitch->Constrain))(Stitch, reqx, reqy, + reqWidth, reqHeight); + + if ((*(Stitch->Match))(Stitch, Left, Right) == FALSE) + retval = -8; + else + stitchit(Left, Right, outfilename, filter, Stitch, + &retval); + } + free_image(Right); + } + free_image(Left); + } + } + return retval; +} + + + +/* Output Methods */ + +/* Helper methods */ + +static void +OutputDeAlloc(Output * me) +{ + if (me->image != (Image *)NULL) { + /* Free up resources */ + free_image (me->image); + me->image = (Image *)NULL; + } + if (me->extra != (void *)NULL) { + free (me->extra); + me->extra = (void *)NULL; + } +} /* OutputDeAlloc() - end */ + +static bool +OutputAlloc(Output * const me, + const char * const file, + unsigned int const width, + unsigned int const height, + struct pam * const prototype) +{ + /* Output the combined images */ + me->extra = (void *)NULL; + me->image = allocate_image(); + if (me->image == (Image *)NULL) { + return FALSE; + } + me->image->pam = *prototype; + me->image->pam.width = width; + me->image->pam.height = height; + /* Give the output a name */ + me->image->name = strdup(file); + /* Initialize output arrays */ + if (writeinit(me->image) == FALSE) { + OutputDeAlloc(me); + return FALSE; + } + return TRUE; +} /* OutputAlloc() - end */ + +/* StraightThrough output method */ + +static void +StraightThroughDeAlloc(Output * me) +{ + /* Trick the proper freeing of resouces on the Output Image */ + me->image->pam.height = 1; + OutputDeAlloc(me); +} /* StraightThroughDeAlloc() - end */ + +static bool +StraightThroughAlloc(Output * const me, + const char * const file, + unsigned int const width, + unsigned int const height, + struct pam * const prototype) +{ + if (OutputAlloc(me, file, width, height, prototype) == FALSE) { + StraightThroughDeAlloc(me); + } + /* Trick the proper allocation of resouces on the Output Image */ + me->image->pam.height = 1; + me->image->tuple = pnm_allocpamarray(&me->image->pam); + if (me->image->tuple == (tuple **)NULL) { + StraightThroughDeAlloc(me); + return FALSE; + } + me->image->pam.height = height; + pnm_writepaminit(&me->image->pam); + return TRUE; +} /* StraightThroughAlloc() - end */ + +static tuple * +StraightThroughRow(Output * me, unsigned row) +{ + UNREFERENCED_PARAMETER(row); + return me->image->tuple[0]; +} /* StraightThroughRow() - end */ + +static void +StraightThroughFlushRow(Output * me, unsigned row) +{ + UNREFERENCED_PARAMETER(row); + if (me->image != (Image *)NULL) { + pnm_writepamrow(&me->image->pam, me->image->tuple[0]); + } +} /* StraightThroughFlushRow() - end */ + +static void +StraightThroughFlushImage(Output * me) +{ + UNREFERENCED_PARAMETER(me); +} /* StraightThroughFlushImage() - end */ + +/* Horizontal Crop output method */ + +#define HorizontalCropDeAlloc StraightThroughDeAlloc + +typedef struct { + int state; + int lostInSpace; +} HorizontalCropExtra; + +static bool +HorizontalCropAlloc(Output * const me, + const char * const file, + unsigned int const width, + unsigned int const height, + struct pam * const prototype) +{ + unsigned long pos; + + if (StraightThroughAlloc(me, file, width, height, prototype) == FALSE) { + return FALSE; + } + me->extra = (void *)malloc(sizeof(HorizontalCropExtra)); + if (me->extra == (void *)NULL) { + HorizontalCropDeAlloc(me); + return FALSE; + } + memset (me->extra, 0, sizeof(HorizontalCropExtra)); + /* Test if we can seek, important since we rewrite the header */ + pos = ftell(me->image->pam.file); + if ((fseek(me->image->pam.file, 1L, SEEK_SET) != 0) + || (ftell(me->image->pam.file) != 1L)) { + fprintf (stderr, "%s needs to output to a seekable entity\n", + me->Name); + } + (void)fseek(me->image->pam.file, pos, SEEK_SET); + return TRUE; +} /* HorizontalCropAlloc() - end */ + +#define HorizontalCropRow StraightThroughRow + +static void +HorizontalCropFlushRow(Output * me, unsigned row) +{ + unsigned column; + unsigned threshold; +# define HorizontalCropThreshold 4 + UNREFERENCED_PARAMETER(row); + + if (me->image == (Image *)NULL) { + return; + } + if (((HorizontalCropExtra *)(me->extra))->state == 2) { + ((HorizontalCropExtra *)(me->extra))->lostInSpace++; + return; + } + /* Any pitch black pixels? */ + threshold = HorizontalCropThreshold; + for (column = 0; column < me->image->pam.width; ++column) { + unsigned plane = 0; + while (me->image->tuple[0][column][plane] == (sample)0) { + if (++plane >= me->image->pam.depth) { + if (--threshold == 0) { + if (((HorizontalCropExtra *)(me->extra))->state == 1) { + ((HorizontalCropExtra *)(me->extra))->state = 2; + } + ((HorizontalCropExtra *)(me->extra))->lostInSpace++; + return; + } + } + } + if (plane < me->image->pam.depth) { + threshold = HorizontalCropThreshold; + } + } + ((HorizontalCropExtra *)(me->extra))->state = 1; + pnm_writepamrow(&me->image->pam, me->image->tuple[0]); +} /* HorizontalCropFlushRow() - end */ + +static void +HorizontalCropFlushImage(Output * me) +{ + me->image->pam.height -= ((HorizontalCropExtra *)(me->extra))->lostInSpace; + if (verbose) { + fprintf (stderr, "%s has set image size to %d x %d\n", + me->Name, me->image->pam.width, me->image->pam.height); + } + if (fseek(me->image->pam.file, 0L, SEEK_SET) == 0) { + pnm_writepaminit(&me->image->pam); + } else { + fprintf (stderr, + "%s failed to seek to beginning to rewrite the header\n", + me->Name); + } +} /* HorizontalCropFlushImage() - end */ + +/* Rotate Crop output method */ + +#define RotateCropDeAlloc OutputDeAlloc + +static bool +RotateCropAlloc(Output * const me, + const char * const file, + unsigned int const width, + unsigned int const height, + struct pam * const prototype) +{ + if (OutputAlloc(me, file, width, height, prototype) == FALSE) { + RotateCropDeAlloc(me); + } + me->image->tuple = pnm_allocpamarray(&me->image->pam); + if (me->image->tuple == (tuple **)NULL) { + RotateCropDeAlloc(me); + return FALSE; + } + return TRUE; +} /* RotateCropAlloc() - end */ + +static tuple * +RotateCropRow(Output * me, unsigned row) +{ + return me->image->tuple[row]; +} /* RotateCropRow() - end */ + +static void +RotateCropFlushRow(Output * me, unsigned row) +{ + UNREFERENCED_PARAMETER(me); + UNREFERENCED_PARAMETER(row); +} /* RotateCropFlushRow() - end */ + +/* + * Algorithm under construction. + * + */ +static void +RotateCropFlushImage(Output * me) +{ + /* Cop Out for now ... */ + pnm_writepam(&me->image->pam, me->image->tuple); +} /* RotateCropFlushImage() - end */ + +/* Output Method Table */ + +Output OutputMethods[] = { + { "StraightThrough", StraightThroughAlloc, StraightThroughDeAlloc, + StraightThroughRow, StraightThroughFlushRow, + StraightThroughFlushImage }, + { "HorizontalCrop", HorizontalCropAlloc, HorizontalCropDeAlloc, + HorizontalCropRow, HorizontalCropFlushRow, HorizontalCropFlushImage }, + { "RotateCrop (unimplemented)", RotateCropAlloc, RotateCropDeAlloc, + RotateCropRow, RotateCropFlushRow, RotateCropFlushImage }, + { (char *)NULL } +}; + +/* Stitcher Methods */ + +/* These names are for the 8 parameters of a stitch, in any of the 3 + methods this program presently implements. Each is a subscript in + the parms[] array for the Stitcher object that represents a linear + stitching method. + + There are also other sets of names for the 8 parameters, such as + Rotate_a. I don't know why. Maybe historical. +*/ + +#define Sliver_A 0 +#define Sliver_B 1 +#define Sliver_C 2 +#define Sliver_D 3 +#define Sliver_xp 4 +#define Sliver_yp 5 +#define Sliver_xpp 6 +#define Sliver_ypp 7 + +/* Linear Stitcher Methods */ + +static void +LinearDeAlloc(Stitcher * me) +{ + if (me->parms != (float *)NULL) { + free (me->parms); + me->parms = (float *)NULL; + } +} /* LinearDeAlloc() - end */ + +static bool +LinearAlloc(Stitcher * me) +{ + bool retval; + + MALLOCARRAY(me->parms, 8); + if (me->parms == NULL) + retval = FALSE; + else { + /* Constraints unset */ + me->x = INT_MAX; + me->y = INT_MAX; + me->width = INT_MAX; + me->height = INT_MAX; + /* Unity transform matrix */ + me->parms[Sliver_A] = 1.0; + me->parms[Sliver_B] = 0.0; + me->parms[Sliver_C] = 0.0; + me->parms[Sliver_D] = 0.0; + me->parms[Sliver_xp] = 0.0; + me->parms[Sliver_yp] = 1.0; + me->parms[Sliver_xpp] = 0.0; + me->parms[Sliver_ypp] = 0.0; + retval = TRUE; + } + return retval; +} + + + +static void +LinearConstrain(Stitcher * me, int x, int y, int width, int height) +{ + me->x = x; + me->y = y; + me->width = width; + me->height = height; +} /* LinearConstrain() - end */ + +/* + * First pass is to find an approximate match. To do so, we take a + * width sliver of the left hand side of the right image and compare + * the sample to the left hand image. Accuracy is honored over speed. + * The image overlap is expected between 7/16 to 1/16 in the horizontal + * position, and a minumum of 5/8 in the vertical dimension. + * + * Blind alleys: + * - reduced resolution can match in totally wrong regions, + * as such it can not be used to improve the speed by + * getting close, then fine tuning at full resolution. + * - vector (color) average of sample matched to running + * vector average on left image in an attempt to improve + * positional accuracy of a reduced resolution image + * produced even more artifacts. + * - A complete boxed sliver did not find a minima, as for + * too large or too small of a square sample. heuristics + * show that it works between 1/128 to 1/16 of the total + * image dimension. Smaller, of course, improves speed, + * but has the possibility of less accuracy. + * + * Transformation parameters + * x=x.+a + * y=y'+b + * Where x,y represents the original point, and x.,y. + * represents the transformed point. Thus: + * + * Transformed image: + * ((x'+x")/2,(y'+y"-H)/2) ((x'+x"+2W)/2,(y'+y"-H)/2) + * ((x'+x")/2,(y'+y"+H)/2) ((x'+x"+2W)/2,(y'+y"+H)/2 + * + * Corresponding to Original (dot) image: + * (0,0) (Right->pam.width,0) + * (0,Right->pam.height) (Right->pam.width,Right->pam.height) + * + * Our matching data points are centered on x.=width/2, and + * scan for transformation results with a variety of y. values: + * x=a*width/2+by.+c*width*y./2+d + * y=e*width/2+fy.+g*width*y./2+h + * we set: + * A=b+c*width/2 + * B=a*width/2+d + * C=f+g*width/2 + * D=e*width/2+h + * thus simplifying to: + * x=Ay.+B + * y=Cy.+D + * adding in a weighting factor of w, the error equation is: + * 2 2 2 + * E(A,B,C,D)=w * ((Ay.+B-x) + (Cy.+D-y)) + * thus + * 2 + * dE(A)=2wy.(Ay.+B-x) => 0=A{wy.y. + B{wy. - {wy.x + * 2 + * dE(B)=2w(Ay.+B-x) => 0=A{wy. + B{w - {wx + * A=({wy.x{w-{wx{wy.)/({wy.y.{w-{wy.{wy.) + * B=({wx-A{wy.)/{w + * and + * 2 + * dE(C)=2wy.(Cy.+D-y) => 0=C{wy.y. + D{wy. - {wy.y + * 2 + * dE(D)=2w(Cy.+D-y) => 0=C{wy. + D{w - {wy + * C=({wy.y{w-{wy{wy.)/({wy.y.{w-{wy.{wy.) + * D=({wy-C{wy.)/{w + * requiring us to collect: + * {wy.x=sumydotx + * {wx=sumx + * {wy.=sumydot + * {wy.y.=sumydotydot + * {w=sum + * {wy.y=sumydoty + * {wy=sumy + * Once we have A, B, C and D, we can calculate the x',y' and x",y" + * values as follows (based on geometric interpolation from the above + * constraints): + * x'=AH/2+B-AHW/(2W-width) + * y'=CH/2+D-CHW/(2W-width) + * x"=AH/2+B+AHW/(2W-width) + * y"=CH/2+D+CHW/(2W-width) + * These two points can be used either in the Linear or the BiLinear to + * establish a transform. + */ + +#define IMAGE_PORTION 64 +#define SKIP_SLIVER 1 + +/* Following global variables are for use by SliverMatch() */ +static unsigned long starPeriod; + /* The number of events between printing of a "*" progress + indicator. + */ +static unsigned long starCount; + /* The number of events until the next * progress indicator needs to be + printed. + */ + +static void +starEvent() { + + if (--starCount == 0) { + starCount = starPeriod; + fprintf (stderr, "*"); + } +} + + +static void +starInit(unsigned long const period) { + starPeriod = period; + starCount = period; +} + + +static void +starResetPeriod(unsigned long const period) { + starPeriod = period; + if (starCount > period) + starCount = period; +} + +static void +findBestMatches(Image * const Left, + Image * const Right, + int const x, + int const y, + int const width, + int const height, + int const offY, + unsigned const Xmin, + unsigned const Xmax, + int const Ymin, + int const Ymax, + Best best[NUM_BEST]) { +/*---------------------------------------------------------------------------- + Compare the rectangle 'width' columns by 'height' rows with upper + left corner at Column 'x', Row y+offY in image 'Right' to a bunch of + rectangles of the same size in image 'Left' and generate a list of the + rectangles in 'Left' that best match the one in 'Right'. + + The specific rectangles in 'Left' we examine are those with upper left + corner (X,Y+offY) where X is in [Xmin, Xmax) and Y is in [Ymin, Ymax). + + We return the ordered list of best matches as best[]. + + Caller must ensure that each of the rectangles in question is fully + contained with its respective image. +-----------------------------------------------------------------------------*/ + unsigned X, Y; + /* Exhaustively find the best match */ + for (X = Xmin; X < Xmax; ++X) { + int const widthOfOverlap = X - Left->pam.width; + for (Y = Ymin; Y < Ymax; ++Y) { + unsigned long difference = regionDifference( + Left, X, Y + offY, + Right, x, y + offY, + width, height); + update_best(best, difference, widthOfOverlap, Y + offY); + starEvent(); + } + } +} + + + +static void +allocate_best_array(Best *** const bestP, unsigned const bestSize) { + + Best ** best; + unsigned int i; + + MALLOCARRAY(best, bestSize); + if (best == NULL) + pm_error("No memory for Best array"); + + for (i = 0; i < bestSize; ++i) + best[i] = allocate_best(); + *bestP = best; +} + + + +static void determineXYRange(Stitcher * const me, + Image * const Left, + Image * const Right, + unsigned * const XminP, + unsigned * const XmaxP, + int * const YminP, + int * const YmaxP) { + + if (me->x == INT_MAX) { + *XmaxP = Left->pam.width - me->width; + /* I can't bring myself to go half way */ + *XminP = Left->pam.width - (7 * Right->pam.width / 16); + } else { + *XminP = me->x; + *XmaxP = me->x + 1; + } + if (me->y == INT_MAX) { + /* Middle 1/4 */ + *YminP = Left->pam.height * 3 / 8; + *YmaxP = Left->pam.height - (*YminP) - me->height; + } else { + *YminP = me->y; + *YmaxP = me->y + 1; + } + if (verbose) + pm_message("Test %d<x<%d %d<y<%d", *XminP, *XmaxP, *YminP, *YmaxP); +} + + + +/* + * Find the weighted best line fit using the left hand margin of the + * right hand image. + */ +static bool +SliverMatch(Stitcher * me, Image * Left, Image * Right, + unsigned image_portion, unsigned skip_sliver) +{ + /* up/down 3/10, make sure has an odd number of members */ + unsigned const bestSize = + 1 + 2 * ((image_portion * 3) / (10 * skip_sliver)); + Best ** best; /* malloc'ed array of Best * */ + float sumydotx, sumx, sumydot, sum; + float sumydoty, sumy, sumydotydot; + int yDiff; + unsigned X, Xmin, Xmax, num, xmin, xmax; + int x, y, Y, Ymin, Ymax, in, ymin, ymax; + + /* Harry Sticks Geeses */ + if (me->width == INT_MAX) { + me->width = Right->pam.width / image_portion; + } + if ((me->width > (Right->pam.width/2)) + || (me->width > (Left->pam.width/2))) { + pm_error ("stitch sample too wide %d\n", me->width); + /* NOTREACHED */ + } + if (me->height == INT_MAX) { + me->height = Right->pam.height / image_portion; + } + if ((me->height > Right->pam.height) + || (me->height > Left->pam.height)) { + pm_error ("stitch sample too high %d\n", me->height); + /* NOTREACHED */ + } + yDiff = (Right->pam.height * skip_sliver) / image_portion; + starInit((unsigned long)-1L); + + allocate_best_array(&best, bestSize); + + determineXYRange(me, Left, Right, &Xmin, &Xmax, &Ymin, &Ymax); + + /* Find the best */ + if ((verbose == 1) || (verbose == 2)) { + fprintf (stderr, "%79s|\r|", ""); + starInit((unsigned long) + ( + (unsigned long)(Xmax - Xmin) + * (unsigned long)(Ymax - Ymin) + ) * (unsigned long)bestSize + / 78L); + } + + /* A point in the middle of the right image */ + x = 0; + y = (Right->pam.height - me->height) / 2; + /* + * Exhaustively search for the best match, improvements + * in the algorithm here, if any, would give us the best + * bang for the buck when it comes to improving performance. + */ + /* + * First pass through the right hand images to determine + * which are good candidate (top 90 percentile) for content of + * features that we may have a chance of testing with. + */ + { + float minf, maxf; + float * features; + + MALLOCARRAY(features, bestSize); + minf = maxf = 0.0; + for (in = 0; in < bestSize; ++in) { + int const offY = yDiff * (in - (bestSize/2)); + float SUM[3], SUMSQ[3]; + int plane; + for (plane = 0; plane < MIN(Right->pam.depth,3); ++plane) { + SUM[plane] = SUMSQ[plane] = 0.0; + } + for (X = x; X < (x + me->width); ++X) { + for (Y = y + offY; Y < (y + offY + me->height); ++Y) { + for (plane = 0; + plane < MIN(Right->pam.depth,3); + ++plane) { + sample point = Right->tuple[Y][X][plane]; + SUM[plane] += point; + SUMSQ[plane] += point * point; + } + } + } + /* How many features */ + features[in] = 0.0; + for (plane = 0; plane < MIN(Right->pam.depth,3); ++plane) { + features[in] += SUMSQ[plane] - + (SUM[plane]*SUM[plane]/(float)(me->width*me->height)); + } + if ((minf == 0.0) || (features[in] < minf)) { + minf = features[in]; + } + if ((maxf == 0.0) || (features[in] > maxf)) { + maxf = features[in]; + } + } + /* Select 90% in the contrast range */ + minf = (minf + maxf) / 10; + for (in = 0; in < bestSize; ++in) { + if (features[in] < minf) { + free_best(best[in]); + best[in] = (Best *)NULL; + } + } + } + /* Loop through the constraints to find the best match */ + sumydotx=sumx=sumydot=sumydotydot=sum=sumydoty=sumy=0.0; + xmin = UINT_MAX; + xmax = 0; + ymin = INT_MAX; + ymax = INT_MIN; + in = num = 0; + for (;;) { + float w; + int offY = yDiff * (in - (bestSize/2)); + /* See if this one to be skipped because of too few features */ + if (best[in] == (Best *)NULL) { + if (in > (bestSize/2)) { + in = bestSize - in; + } else if (in < (bestSize/2)) { + in = (bestSize-1) - in; + } else { + break; + } + if ((verbose == 1) || (verbose == 2)) + for (X = Xmin; X < Xmax; ++X) { + for (Y = Ymin; Y < Ymax; ++Y) + starEvent(); + } + continue; + } + findBestMatches(Left, Right, x, y, me->width, me->height, + offY, Xmin, Xmax, Ymin, Ymax, + best[in]); + /* slop (noise in NUM_BEST) */ + { + float SUMx, SUMxx, SUMy, SUMyy, SUMw; + unsigned i; + SUMx = SUMxx = SUMy = SUMyy = SUMw = 0.0; + for (i = 0; i < NUM_BEST; ++i) { + /* best[in][i] describes the ith closest region in the right + image to the region in the left image whose top corner is + at (x, y+offY). + */ + float const w2 = (best[in][i].total > 0) + ? (1.0 / (float)best[in][i].total) + : 1.0; + SUMx += w2 * best[in][i].x; + SUMy += w2 * best[in][i].y; + SUMxx += w2 * (best[in][i].x * best[in][i].x); + SUMyy += w2 * (best[in][i].y * best[in][i].y); + SUMw += w2; + } + /* Find our weighted error */ + w = SUMw + / ((SUMxx - (SUMx*SUMx)/SUMw) + (SUMyy - (SUMy*SUMy)/SUMw)); + } + /* magnify slop */ + w *= w; + Y = y + offY; + sumy += w * best[in][0].y; + sumx += w * best[in][0].x; + sum += w; + sumydot += w * Y; + sumydotydot += w * Y * Y; + sumydoty += w * Y * best[in][0].y; + sumydotx += w * Y * best[in][0].x; + /* Calculate the best fit line for these matches */ + me->parms[Sliver_C] = ((sumydotydot * sum) + - (sumydot * sumydot)); + if (me->parms[Sliver_C] == 0.0) { + me->parms[Sliver_A] = 0.0; + } else { + me->parms[Sliver_A] = ((sumydotx * sum) + - (sumx * sumydot)) + / me->parms[Sliver_C]; + me->parms[Sliver_C] = ((sumydoty * sum) + - (sumy * sumydot)) + / me->parms[Sliver_C]; + } + if (sum == 0.0) { + me->parms[Sliver_B] = me->parms[Sliver_D] = 0; + } else { + me->parms[Sliver_B] = (sumx + - (me->parms[Sliver_A] * sumydot)) + / sum; + me->parms[Sliver_D] = (sumy + - (me->parms[Sliver_C] * sumydot)) + / sum; + } + if (verbose > 2) { + fprintf (stderr, "%.4g*(%d,%d)@(%d,%d)\n", + w, best[in][0].x + Left->pam.width, best[in][0].y, + me->width / 2, Y); + } + /* Record history of limits */ + if ((best[in][0].x + Left->pam.width) < xmin) { + xmin = best[in][0].x + Left->pam.width; + } + if (xmax < (best[in][0].x + Left->pam.width)) { + xmax = best[in][0].x + Left->pam.width; + } + if ((best[in][0].y - offY) < ymin) { + ymin = best[in][0].y - offY; + } + if (ymax < (best[in][0].y - offY)) { + ymax = best[in][0].y - offY; + } + /* Lets restrict the search a bit now */ + if (++num > 1) { + if (me->x == INT_MAX) { + int newXmin, newXmax, hold; + /* Use the formula to determine the bounds */ + newXmin = (int)(me->parms[Sliver_B] + 0.5) + + Left->pam.width; + newXmax = (int)((me->parms[Sliver_A] + * (float)Left->pam.height) + + me->parms[Sliver_B] + 0.5) + + Left->pam.width; + if (newXmax < newXmin) { + hold = newXmin; + newXmin = newXmax; + newXmax = hold; + } + /* Trust little ... */ + hold = (3 * newXmin - newXmax) / 2; + newXmax = (3 * newXmax - newXmin) / 2; + newXmin = hold; + /* Don't go inside history */ + if (xmin < newXmin) { + newXmin = xmin + Left->pam.width; + } + if (newXmax < xmax) { + newXmax = xmax + Left->pam.width; + } + /* If it is `wacky' drop it */ + if ((newXmax - Xmax) < (Right->pam.width / 3)) { + /* Now upgrade new minimum and maximum */ + hold = Xmin; + if ((Xmin < newXmin) && (newXmin < Xmax)) { + hold = newXmin; + } + if ((Xmin < newXmax) && (newXmax < Xmax)) { + Xmax = newXmax; + } + Xmin = hold; + } + } + if (me->y == INT_MAX) { + int newYmin, newYmax, hold; + float tmp; + /* Use the formula to determine the bounds */ + newYmin = tmp = me->parms[Sliver_D] + + ((float)(Left->pam.height + 1)) + / 2; + newYmax = (int)((me->parms[Sliver_C] + * (float)Left->pam.height) + + tmp) - Left->pam.height; + if (newYmax < newYmin) { + hold = newYmin; + newYmin = newYmax; + newYmax = hold; + } + /* Trust little ... */ + hold = (3 * newYmin - newYmax) / 2; + newYmax = (3 * newYmax - newYmin) / 2; + newYmin = hold; + /* Don't go inside history */ + if (ymin < newYmin) { + newYmin = ymin; + } + if (newYmax < ymax) { + newYmax = ymax; + } + /* Now upgrade new minimum and maximum */ + hold = Ymin; + if ((Ymin < newYmin) && (newYmin < Ymax)) { + hold = newYmin; + } + if ((Ymin < newYmax) && (newYmax < Ymax)) { + Ymax = newYmax; + } + Ymin = hold; + } + if ((verbose == 1) || (verbose == 2)) { + starResetPeriod((unsigned long)( + ((unsigned long)(Xmax - Xmin) + * (unsigned long)(Ymax - Ymin)) + * (unsigned long)bestSize + / 78L)); + } + } + if (in > (bestSize/2)) { + in = bestSize - in; + } else if (in < (bestSize/2)) { + in = (bestSize-1) - in; + } else { + break; + } + } + if ((verbose == 1) || (verbose == 2)) { + fprintf (stderr, "\n"); + } + if (verbose > 2) { + fprintf (stderr, "Up "); + pr_best(best[bestSize-1]); + fprintf (stderr, "\nMid "); + pr_best(best[bestSize/2]); + fprintf (stderr, "\nDown"); + pr_best(best[0]); + fprintf (stderr, "\n"); + } + + if (verbose) { + if (verbose > 1) { + fprintf (stderr, + "[y=%.4g [x=%.4g [=%.4g [y.=%.4g " + "[y.y.=%.4g [y.y=%.4g [y.x=%.4g\n", + sumy, sumx, sum, sumydot, sumydotydot, + sumydoty, sumydotx); + } + fprintf (stderr, "x=%.4gY%+.4g\ny=%.4gY%+.4g\n", + me->parms[Sliver_A], me->parms[Sliver_B], + me->parms[Sliver_C], me->parms[Sliver_D]); + } + /* + * Free up resources + */ + for (in = 0; in < bestSize; ++in) { + if (best[in] != (Best *)NULL) { + free_best (best[in]); + best[in] = (Best *)NULL; + } + } + free(best); + + /* Calculate x',y' and x",y" from best fit line formula */ + sum = (float)(Right->pam.width * Right->pam.height) + / (float)(2 * Right->pam.width - me->width); + me->parms[Sliver_xpp] = me->parms[Sliver_A] * sum; + me->parms[Sliver_ypp] = me->parms[Sliver_C] * sum; + sumx = me->parms[Sliver_A] * (Right->pam.height / 2) + + me->parms[Sliver_B]; + sumx += Left->pam.width; + sumy = me->parms[Sliver_C] * (Right->pam.height / 2) + + me->parms[Sliver_D]; + me->parms[Sliver_xp] = sumx - me->parms[Sliver_xpp]; + me->parms[Sliver_yp] = sumy - me->parms[Sliver_ypp]; + me->parms[Sliver_xpp] += sumx; + me->parms[Sliver_ypp] += sumy; + if (verbose > 1) { + fprintf (stderr, "x',y'=%.4g,%.4g x\",y\"=%.4g,%.4g\n", + me->parms[Sliver_xp], me->parms[Sliver_yp], + me->parms[Sliver_xpp], me->parms[Sliver_ypp]); + } + return TRUE; +} /* SliverMatch() - end */ + +/* These are not used. Perhaps they are forerunners of the more + expressive Sliver_A, etc. names. +*/ +#define Linear_a 0 +#define Linear_b 1 +#define Linear_c 2 +#define Linear_d 3 +#define Linear_e 4 +#define Linear_f 5 +#define Linear_g 6 +#define Linear_h 7 + +static bool +LinearMatch(Stitcher * me, Image * Left, Image * Right) +{ + if (SliverMatch(me, Left, Right, IMAGE_PORTION, SKIP_SLIVER * 8) + == FALSE) { + return FALSE; + } + + me->x = - (me->parms[Sliver_xp] + me->parms[Sliver_xpp] + 1) / 2; + me->y = - (me->parms[Sliver_yp] + me->parms[Sliver_ypp] + + (1 - Left->pam.height)) / 2; + + if (verbose) + pm_message("LinearMatch translation parameters are (%d,%d)", + me->x, me->y); + + return TRUE; +} /* LinearMatch() - end */ + +/* + * Transformation parameters + * left x' = x + * left y' = y + * right x' = x + me->x + * right y' = y + me->y + */ +static float +LinearXLeft(Stitcher * me, int x, int y) +{ + UNREFERENCED_PARAMETER(y); + return x; +} /* LinearXLeft() - end */ + +static float +LinearYLeft(Stitcher * me, int x, int y) +{ + UNREFERENCED_PARAMETER(x); + return y; +} /* LinearYLeft() - end */ + +static float +LinearXRight(Stitcher * me, int x, int y) +{ + UNREFERENCED_PARAMETER(y); + return (x + me->x); +} /* LinearXRight() - end */ + +static float +LinearYRight(Stitcher * me, int x, int y) +{ + UNREFERENCED_PARAMETER(x); + return (y + me->y); +} /* LinearYRight() - end */ + +static void +LinearOutput(Stitcher * me, FILE * fp) +{ + fprintf (fp, "x'=x%+d\ny'=y%+d\n", me->x, me->y); +} /* LinearOutput() - end */ + +/* BiLinear Stitcher Methods */ + +static void +BiLinearDeAlloc(Stitcher * me) +{ + LinearDeAlloc(me); +} + + + +static bool +BiLinearAlloc(Stitcher * me) +{ + return LinearAlloc(me); +} + + + +static void +BiLinearConstrain(Stitcher * me, int x, int y, int width, int height) +{ + LinearConstrain(me, x, y, width, height); + if (x != INT_MAX) { + me->parms[3] -= x; + } + if (y != INT_MAX) { + me->parms[7] -= y; + } +} /* BiLinearConstrain() - end */ + +/* + * First pass is to find an approximate match. To do so, we take a + * width sliver of the left hand side of the right image and compare + * the sample to the left hand image. Accuracy is honored over speed. + * The image overlap is expected between 7/16 to 1/16 in the horizontal + * position, and a minumum of 5/8 in the vertical dimension. + * + * Blind alleys: + * - Tried a simpler constraint for right side to be `back' + * to image, twisted too much sometimes: + * . . . + * W=aW+bH+cWH+d + * H=eW+fH+gWH+h + * W=aW+d + * 0=eW+h + * Solve the equations resulted in: + * a = W/(W-x") - cy" + * b = -Wc + * c = W/((x"-W)(y'-y")) + * d = (1-a)W + * e = y'(y"x'+W-Hx'-x"y")/(x'y"x"-Wx'y"-Wy'x"-WWy'+x'y'x"-Wx'y'-W) + * f = 1 - Wg + * g = (e + (H-y")/(x"-W))/y" + * h = -We + * Results left here for historical reasons. + * + * Transformation parameters + * x=ax.+by.+cx.y.+d + * y=ex.+fy.+gx.y.+h + * Where x,y represents the original point, and x.,y. + * represents the transformed point. Thus: + * + * Transformed image: + * (x',y') (x'",y'") + * (x",y") (x"",y"") + * + * Corresponding to Original (dot) image: + * (0,0) (Right->pam.width,0) + * (0,Right->pam.height) (Right->pam.width,Right->pam.height) + * + * Define: + * H=Right->pam.height + * W=Right->pam.width + * Given that I want a flat presentation that both reduces the distortion + * necessary on an image, reduces the cropping losses, and flattens out the + * spherical or orbit distortions; it was chosen to constrain the right side + * in the middle horizontal, and pivot the left side in that middle (hopefully + * minimally) and to allow the image only vertical and horizontal location + * placement. Rotating the entire image could increase cropping losses + * especially if the focus was not down the center of the image on a + * graduated field causing the distortion to accumulate in subsequent + * images. Trapezoidal would cause the distortion to accumulate in subsequent + * images as well, resetting to `square' gradually towards the right would + * allow the next image to restart a match placing the distortions mainly + * in the stitching zone where averaging and the slight expectation of + * artifacts would minimize the effects. These constraints can be explained + * mathematically as the following: + * x'" + x"" - 2W = x' + x" + * y'" + y"" = y' + y" + * x'" = x"" + * y'" + H = y"" + * resulting in the right side of the image being completely explained by the + * placement of the left hand side: + * x'"=(x'+x"+2W)/2 + * y'"=(y'+y"-H)/2 + * x""=(x'+x"+2W)/2 + * y""=(y'+y"+H)/2 + * + * Describing the `X' polygon using geometry and ratios: + * X=A(y-(y'+y")/2)(x-(x'+x"+2W)/2) + (x - (x'+x")/2)) + * A=2(x"-x')/((y'-y")(x'-x"-2W)) + * a=2(y'(x'-x")-W(y'-y"))/((y'-y")(x'-x"-2W)) + * b=(x'-x")(x'+x"+2W)/((y'-y")(x'-x"-2W)) + * c=2(x"-x')/((y'-y")(x'-x"-2W)) + * d=(2W(x"y'-x'y")+y'(x"x"-x'x'))/((y'-y")(x'-x"-2W)) + * + * Describing the `Y' polygon using geometry and ratios (note use of X rather + * than x, this has the effect of linearalizing the polygon). + * Y=((y'-y"+H)/W(y'-y"))(y-(y'+y")/2)(X-HW/(y'-y"+H)) + H/2 + * e=(y'+y")(y'-y"+H)/2W(y"-y') + * f=H/(y"-y') + * g=(y'-y"+H)/W(y'-y") + * h=Hy'/(y'-y") + * + * FYI: Reverse transform using the same formula style is: + * a=(x"-x'+2W)/2W + * b=(x"-x')/H + * c=(x'-x")/WH + * d=x' + * e=(y"-y'-H)/2W + * f=(y"-y')/H + * g=(y'-y"+H)/WH + * h=y' + */ + +#define BiLinear_a 0 +#define BiLinear_b 1 +#define BiLinear_c 2 +#define BiLinear_d 3 +#define BiLinear_e 4 +#define BiLinear_f 5 +#define BiLinear_g 6 +#define BiLinear_h 7 + +static bool +BiLinearMatch(Stitcher * me, Image * Left, Image * Right) +{ + float xp, yp, xpp, ypp; + + if (SliverMatch(me, Left, Right, IMAGE_PORTION, SKIP_SLIVER) == FALSE) { + return FALSE; + } + /* If too wacky, flatten out */ + xp = me->parms[Sliver_xp]; + yp = me->parms[Sliver_yp]; + xpp = me->parms[Sliver_xpp]; + ypp = me->parms[Sliver_ypp]; + if ((me->parms[Sliver_A] < -0.3) + || (0.3 < me->parms[Sliver_A])) { + xp = xpp = (xp + xpp) / 2; + } + if ((me->parms[Sliver_C] < 0.6) + || (1.5 < me->parms[Sliver_D])) { + yp = (yp + ypp - (float)Right->pam.height) / 2; + ypp = yp + Right->pam.height; + } + + /* + * Calculate any necessary transformations on the + * right image to improve the stitching match. We have Done a + * weighted best fit line on the points we have collected + * thus far, now translate this to the constrained + * transformation equations. + */ + /* a = y"-y' */ + me->parms[BiLinear_a] = ypp-yp; + /* c = x'-x" */ + me->parms[BiLinear_c] = xp-xpp; + /* d = (y"-y')(x"-x'+2W) = (y'-y")(x'-x"-2W) */ + me->parms[BiLinear_d] = me->parms[BiLinear_a] + * ((float) + (2*Right->pam.width)-me->parms[BiLinear_c]); + /* a = 2(y'(x'-x")+W(y"-y'))/((y'-y")(x'-x"-2W)) */ + me->parms[BiLinear_a] = 2*(yp*me->parms[BiLinear_c] + + me->parms[BiLinear_a]*(float)(Right->pam.width)) + / me->parms[BiLinear_d]; + /* b = (x'-x")(x'+x"+2W)/((y'-y")(x'-x"-2W)) */ + me->parms[BiLinear_b] = me->parms[BiLinear_c] + * (xp+xpp+(float)(2*Right->pam.width)) + / me->parms[BiLinear_d]; + /* c = -2(x'-x")/((y'-y")(x'-x"-2W)) */ + me->parms[BiLinear_c]*= -2/me->parms[BiLinear_d]; + /* d = (2W(x"y'-x'y")+y'(x"x"-x'x'))/((y'-y")(x'-x"-2W)) */ + me->parms[BiLinear_d] = ((xpp*yp-xp*ypp)*(float)(2*Right->pam.width) + + yp*(xpp*xpp-xp*xp)) + / me->parms[BiLinear_d]; + + /* f = y"-y' */ + me->parms[BiLinear_f] = ypp-yp; + /* g = (y"-y'-H)/W(y"-y') */ + me->parms[BiLinear_g] = (me->parms[BiLinear_f]-(float)Right->pam.height) + / me->parms[BiLinear_f] + / (float)Right->pam.width; + /* e = (y'+y")(y'-y"+H)/2W(y"-y') = -g(y'+y")/2 */ + me->parms[BiLinear_e] = (yp+ypp)*me->parms[BiLinear_g]/-2; + /* f=H/(y"-y') */ + me->parms[BiLinear_f] = ((float)Right->pam.height) + / me->parms[BiLinear_f]; + /* h = Hy'/(y'-y") = -fy' */ + me->parms[BiLinear_h] = -yp*me->parms[BiLinear_f]; + + return TRUE; +} /* BiLinearMatch() - end */ + +/* + * Transformation parameters + * x`=x + * y`=y + */ +#define BiLinearXLeft LinearXLeft +#define BiLinearYLeft LinearYLeft + +/* + * Transformation parameters + * x`=ax+by+cxy+d + * y`=ex`+fy+gx`y+h + */ +static float +BiLinearXRight(Stitcher * me, int x, int y) +{ + return (me->parms[BiLinear_a] * x) + (me->parms[BiLinear_b] * y) + + (me->parms[BiLinear_c] * (x * y)) + me->parms[BiLinear_d]; +} /* BiLinearXRight() - end */ + +static float +BiLinearYRight(Stitcher * me, int x, int y) +{ + /* A little trick I learned from a biker */ + float X = BiLinearXRight(me, x, y); + return (me->parms[BiLinear_e] * X) + (me->parms[BiLinear_f] * y) + + (me->parms[BiLinear_g] * (X * y)) + me->parms[BiLinear_h]; +} /* BiLinearYRight() - end */ + +static void +BiLinearOutput(Stitcher * me, FILE * fp) +{ + fprintf (fp, + "x'=%.6gx%+.6gy%+.6gxy%+.6g\ny'=%.6gx'%+.6gy%+.6gx'y%+.6g\n", + me->parms[BiLinear_a], me->parms[BiLinear_b], me->parms[BiLinear_c], + me->parms[BiLinear_d], me->parms[BiLinear_e], me->parms[BiLinear_f], + me->parms[BiLinear_g], me->parms[BiLinear_h]); +} /* BiLinearOutput() - end */ + +/* Rotate Stitcher Methods */ + +#define RotateDeAlloc BiLinearDeAlloc + +#define RotateAlloc BiLinearAlloc + +#define RotateConstrain BiLinearConstrain + +/* + * First pass is to utilize the SliverMatch. + * + * Transformation parameters + * x=ax.+by.+d + * y=ex.+fy.+h + * Where x,y represents the original point, and x.,y. + * represents the transformed point. Thus: + * + * Transformed image: + * (x',y') (x'",y'") + * (x",y") (x"",y"") + * + * Corresponding to Original (dot) image: + * (0,0) (Right->pam.width,0) + * (0,Right->pam.height) (Right->pam.width,Right->pam.height) + * + * Define: + * H=Right->pam.height + * W=Right->pam.width + * + */ + +#define Rotate_a 0 +#define Rotate_b 1 +#define Rotate_c 2 +#define Rotate_d 3 +#define Rotate_e 4 +#define Rotate_f 5 +#define Rotate_g 6 +#define Rotate_h 7 + +static bool +RotateMatch(Stitcher * me, Image * Left, Image * Right) +{ + float xp, yp, xpp, ypp; + + if (SliverMatch(me, Left, Right, IMAGE_PORTION, SKIP_SLIVER) == FALSE) { + return FALSE; + } + xp = me->parms[Sliver_xp]; + yp = me->parms[Sliver_yp]; + xpp = me->parms[Sliver_xpp]; + ypp = me->parms[Sliver_ypp]; + + me->parms[Rotate_c] = (xp - xpp); + me->parms[Rotate_c]*= me->parms[Rotate_c]; + me->parms[Rotate_g] = (yp - ypp); + me->parms[Rotate_g]*= me->parms[Rotate_g]; + me->parms[Rotate_a] = me->parms[Rotate_f] = sqrt(me->parms[Rotate_g] + / (me->parms[Rotate_c] + + me->parms[Rotate_g])); + me->parms[Rotate_b] = me->parms[Rotate_e] = sqrt(me->parms[Rotate_c] + / (me->parms[Rotate_c] + + me->parms[Rotate_g])); + if (xp < xpp) { + me->parms[Rotate_b] = -me->parms[Rotate_b]; + } else { + me->parms[Rotate_e] = -me->parms[Rotate_e]; + } + /* negative (for reverse transform below) xp & yp set for unity gain */ + xp = ((me->parms[Rotate_b] * (float)Right->pam.height) + xp + xpp) / -2; + yp = ((me->parms[Rotate_f] * (float)Right->pam.height) - yp - ypp) / 2; + me->parms[Rotate_d] = xp * me->parms[Rotate_a] + yp * me->parms[Rotate_b]; + me->parms[Rotate_h] = xp * me->parms[Rotate_e] + yp * me->parms[Rotate_f]; + return TRUE; +} /* RotateMatch() - end */ + +/* + * Transformation parameters + * x`=x + * y`=y + */ +#define RotateXLeft BiLinearXLeft +#define RotateYLeft BiLinearYLeft + +/* + * Transformation parameters + * x`=ax+by+d + * y`=ex+fy+h + */ + +static float +RotateXRight(Stitcher * me, int x, int y) +{ + return (me->parms[Rotate_a] * x) + (me->parms[Rotate_b] * y) + + me->parms[Rotate_d]; +} /* RotateXRight() - end */ + +static float +RotateYRight(Stitcher * me, int x, int y) +{ + return (me->parms[Rotate_e] * x) + (me->parms[Rotate_f] * y) + + me->parms[Rotate_h]; +} /* RotateYRight() - end */ + +static void +RotateOutput(Stitcher * me, FILE * fp) +{ + fprintf (fp, + "x'=%.6gx%+.6gy%+.6g\ny'=%.6gx%+.6gy%+.6g\n", + me->parms[Rotate_a], me->parms[Rotate_b], me->parms[Rotate_d], + me->parms[Rotate_e], me->parms[Rotate_f], me->parms[Rotate_h]); +} /* RotateOutput() - end */ + +/* Stitcher Method Table */ + +Stitcher StitcherMethods[] = { + { "RotateSliver", RotateAlloc, RotateDeAlloc, RotateConstrain, + RotateMatch, RotateXLeft, RotateYLeft, RotateXRight, + RotateYRight, RotateOutput }, + { "BiLinearSliver", BiLinearAlloc, BiLinearDeAlloc, BiLinearConstrain, + BiLinearMatch, BiLinearXLeft, BiLinearYLeft, BiLinearXRight, + BiLinearYRight, BiLinearOutput }, + { "LinearSliver", LinearAlloc, LinearDeAlloc, LinearConstrain, + LinearMatch, LinearXLeft, LinearYLeft, LinearXRight, + LinearYRight, LinearOutput }, + { (char *)NULL } +}; |