/* Major update 11/99 to clean up scaling, spot finding and other steps and add multiple rectification iterations. Distorted spots in wide angle views should be easier to find in partially recified images. 12/99 Added a pre-rectification option for better initial spot finding. Program doesn't breath hard even on 120 deg images. -- hpm */ /* To Do List 11/99 $ Make local maximum test neighborhood proportional to spot size $ Include a "don't care" ring between spot and background in spot template $ Tighten range for adjacent spot location in spot linking code (Skip unlinked spots in spot-coordinate calculations!!!! - fixed) $ Replace rectification S->P polynomial by inverse of P->S polynomial to improve extrapolation properties. (Substituted atan basis function that excellently models lens instead) $ Expand horizontal/vertical spot linking to diagonals $ Fix redistort display $ Investigate why red curve, green inverse curve overlap colors cut out $ save first calibration, but replace whenever #points grows by more than 4 even if error rises $ stop iterations if function coeffs become too large $ save rectified pixel coords when calculating original coords $ Replace spotpixelcoord from simple bilinear to plane fit on distance-weighted rectified pixel coordinates (in later iterations, simply use prior rectification) ** All above replaced by adding pixel center values to scatter-minimization search $ Display center crosses on rectified pixel location * Make spot size range in iteration 0 proportional to tan(FOV/2) * Eliminate unnecessary display images with Gaudy=0 switch */ /* flatfish.c last updated 8/10/1999 mcm - scale pixels amounts by image width */ /* First version completed 4/96 at DB Berlin. -- hpm */ /* Radial distortion finder and rectifier for solid state cameras with wide angle lenses. Created 5/95 Hans Moravec Carnegie Mellon University Pittsburgh, PA 15213 In calibration phase, the program assumes the camera is perpendicularly aimed at a square, horizontally oriented, grid of round dark spots, with an additional spot at a half grid position near the center: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *_* * * * * * * * * * * * * * * * * * * * * * * *^* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * (the center "spot" made of an underbar paired with a hat is supposed to look like all the others, represented by stars, and all spots should be black circular disks: excuse the limitations of ascii graphics). The program was developed with a test pattern made of a magnetic white board 4 feet high and six feet wide, covered with a 16 x 24 + 1 grid of dark magnetic disks 1.5 inches in diameter at 3 inch spacing. The object of the calibration was a stereo bar of three Sony XC-75 black and white cameras with 90 degree wide angle lenses exhibiting strong fish eye distortion, with a radial image compression factor between the center and the edges of at least 2:1. The cameras were mounted parallel at 15 cm spacing, and connected to the R, G and B channels of a color digitizer. The camera bar was placed so the lens iris rings were 22.5 inches in front of the calibration pattern. This filled each camera's field of view with a majority of the pattern. The center spot was approximately centered in the "green" camera's image, to the left of center in the "blue" image and to the right in the "red" image. The program finds the spots, in pixel (distorted) and spot grid (ideal) coordinates, and finds a least-squares best fit distortion center, assumed to indicate lens axis, a digitizer aspect ratio, and a distortion correcting function that maps distorted to ideal radius from that center. In our setup the result RMS deviation for all (approximately 300/image) spots is better than 0.5 pixel. In our setup, we note that the lens center between calibrations jitters only a few hundredths of a pixel. As of 11/95 the program could rectify images from the cameras using the distortion polynomial into ones with ideal projective geometry. The program is not complete, as of 3/96. Bugs and missing: When the distorion causes a corner spot to appear in the calibration image whose horizontal and vertical neighbors are off the edge of the image, the program often links it with a diagonal neighbor, giving it the wrong ideal coordinates. Tests need to be added to handle this situation. (fixed 3/15/96 - hpm). The center spot is intended to relate the three cameras, in a not yet implemented later calibration step, which produces a full geometric camera calibration. (completed 4/24/96 - hpm) */ /* The Great Aspect Ratio Saga Concerning aspect ratio of Sony XC-75 camera module in conjunction with our K2T digitizer: the camera specs say the camera pixels are 8.4 um horizontal by 9.8 um vertical. Horizontal clock rate is 14.318 MHz. Vertical drive frequency is 15.734 kHz +- 1%. The camera has 768 horizontal by 494 vertical pixels. Sensing area is 1/2 inch wide. (Optical blank 43 elements on each horizontal line?) EIA system. Chip size is 7.95 (h) by 6.45 (v) mm. 525 lines. --- Turns out that the resolution of AQDOTFREQ in the k2t package is limited - I think it's 80ns and the 12.2 MHz is really 12.256 which is as close as we can get to perfectly square... Bill Ross 12.256 makes the horizontal pixel size 9.813, vs. 9.8 um vertical, an error of only 0.135 percent, about 0.86 pixels total in the horizontal direction, if you use the vertical as the reference. That's good, it means that there is no effect on things like template matches, where the template spans only a fraction of the image, and thus has a maximum aspect stretch much less than a pixel. In numeric computations involving pixel coordinates, I can now multiply in the aspect ratio of 1.001352107, and have complete peace of mind. A camera solver would never have been able to come up with an estimate that accurate. hpm 9/95 Hah!!!! Minimizing over aspect ratio with the distortion-finding code gives a repeatable aspect ratio of 1.020 +/- .0006. Using that found aspect ratio reduces the rms error of the best fit curve of spot index radius to pixel radius to about 0.5 pixel, compared to much worse 2.5 pixels using the calculated aspect ratio. Another good intention leads to hell! */ static int letterdefs[340] ={ 00000000000, 00000000000, /* null */ 00010101010, 05234100000, /* 1 downarrow */ 00000003244, 04444320000, /* 2 alpha */ 00000003442, 07442744040, /* 3 beta */ 00000001024, 04200000000, /* 4 or */ 00000000076, 00200000000, /* 5 not */ 00000001420, 03420140000, /* 6 epsilon */ 00000007624, 02424240000, /* 7 pi */ 00000404020, 01024420000, /* 8 lambda */ 07020202000, 01412141214, /* tab */ 04040407000, 01610141000, /* lf */ 00004121010, 01010502000, /* vt */ 00010107610, 01076000000, /* ff */ 03440403400, 03422342222, /* cr */ 00000002452, 05224000000, /* 16 infinity */ 00030040236, 04242340000, /* 17 partial */ 00000364040, 04036000000, /* 20 subset */ 00000740202, 00274000000, /* 21 superset */ 00000344242, 04200000000, /* 22 intersection */ 00000424242, 03400000000, /* 23 union */ 00042427642, 02424100000, /* 24 forall */ 00076020236, 00202760000, /* 25 thereexists */ 00000346652, 06634000000, /* 26 circlestar */ 00010047604, 01020762010, /* 27 doublearrow */ 00000000000, 00000000076, /* underbar */ 00000100476, 00410000000, /* 31 rightarrow */ 03254000000, 00000000000, /* 32 tilde */ 00002047610, 07620400000, /* 33 not equal */ 00004102010, 00400340000, /* 34 lt or eq */ 00020100410, 02000340000, /* 35 gt or eq */ 00000760076, 00076000000, /* 36 equivalence */ 00000004224, 01000000000, /* 37 or */ 00000000000, 00000000000, /* space */ 00010101010, 01000100000, /* ! */ 02424240000, 00000000000, /* " "*/ 00000247624, 02476240000, /* # */ 01034525034, 01252341000, /* $ */ 00076620410, 02046460000, /* % */ 00020505020, 05244320000, /* & */ 03030600000, 00000000000, /* ' */ 00002041010, 01004020000, /* ( */ 00040201010, 01020400000, /* ) */ 00010523410, 03452100000, /* * */ 00000101076, 01010000000, /* + */ 00000000000, 00030306000, /* , */ 00000000076, 00000000000, /* - */ 00000000000, 00030300000, /* . */ 00000020410, 02040000000, /* / */ 00034424652, 06242340000, /* 0 */ 00010301010, 01010340000, /* 1 */ 00034420204, 01020760000, /* 2 */ 00034420214, 00242340000, /* 3 */ 00004142444, 07604040000, /* 4 */ 00076407402, 00242340000, /* 5 */ 00014204074, 04242340000, /* 6 */ 00076020404, 01010100000, /* 7 */ 00034424234, 04242340000, /* 8 */ 00034424236, 00204300000, /* 9 */ 00000003030, 00030300000, /* : */ 00000003030, 00030306000, /* ; */ 00000041020, 01004000000, /* < */ 00000007600, 07600000000, /* = */ 00000201004, 01020000000, /* > */ 00034420410, 01000100000, /* ? */ 00034425652, 05640340000, /* @ */ 00034424276, 04242420000, /* A */ 00074424274, 04242740000, /* B */ 00034424040, 04042340000, /* C */ 00074222222, 02222740000, /* D */ 00076404074, 04040760000, /* E */ 00076404074, 04040400000, /* F */ 00034424040, 04642340000, /* G */ 00042424276, 04242420000, /* H */ 00034101010, 01010340000, /* I */ 00002020202, 00242340000, /* J */ 00042445060, 05044420000, /* K */ 00040404040, 04040760000, /* L */ 00042665242, 04242420000, /* M */ 00042426252, 04642420000, /* N */ 00034424242, 04242340000, /* O */ 00074424274, 04040400000, /* P */ 00034424242, 05244320000, /* Q */ 00074424274, 05044420000, /* R */ 00034424034, 00242340000, /* S */ 00076101010, 01010100000, /* T */ 00042424242, 04242340000, /* U */ 00042424242, 02424100000, /* V */ 00042424242, 05266420000, /* W */ 00042422410, 02442420000, /* X */ 00042422410, 01010100000, /* Y */ 00076020476, 02040760000, /* Z */ 01610101010, 01010101600, /* [ */ 00000402010, 00402000000, /* \ */ 07010101010, 01010107000, /* ] */ 00010345210, 01010100000, /* 136 uparrow */ 00000102076, 02010000000, /* 137 leftarrow */ 01414060000, 00000000000, /* ` */ 00000003402, 03642360000, /* a */ 00040407442, 04242740000, /* b */ 00000003442, 04040360000, /* c */ 00002023642, 04242360000, /* d */ 00000003442, 07440340000, /* e */ 00014222070, 02020200000, /* f */ 00000003442, 04242360234, /* g */ 00040407442, 04242420000, /* h */ 00000100010, 01010100000, /* i */ 00000020002, 00202024234, /* j */ 00040404244, 07044420000, /* k */ 00010101010, 01010100000, /* l */ 00000006452, 05252520000, /* m */ 00000005462, 04242420000, /* n */ 00000003442, 04242340000, /* o */ 00000007442, 04242744040, /* p */ 00000003442, 04242360202, /* q */ 00000005462, 04040400000, /* r */ 00000003640, 03402740000, /* s */ 00010107610, 01010060000, /* t */ 00000004242, 04242340000, /* u */ 00000004242, 04224100000, /* v */ 00000004242, 05252240000, /* w */ 00000004224, 01024420000, /* x */ 00000004242, 04224102040, /* y */ 00000007604, 03420760000, /* z */ 00204040410, 00404040200, /* { */ 01010101010, 01010101010, /* | */ 04020202010, 02020204000, /* alt (quad) */ 00010102442, 02410100000, /* ~ */ 07777777777, 07777777777, /* */ 00036424236, 01222420000, /* */ 00000003642, 03622420000, /* */ 00034420276, 00242340000, /* */ 00000003442, 01642160000, /* */ 00042424236, 00202020000, /* */ 00000004242, 03602020000, /* */ 00044525272, 05252440000, /* */ 00000004452, 07252440000, /* */ 00052525252, 05252760000, /* */ 00000005252, 05252760000, /* */ 00052525252, 05252760600, /* */ 00000005252, 05252760600, /* */ 00042424242, 04242760600, /* */ 00000004242, 04242760600, /* */ 00076222222, 04242420000, /* */ 00000007622, 02242420000, /* */ 00000006060, 03422340000, /* */ 00042424272, 04646720000, /* */ 00000004242, 07246720000, /* */ 00042424652, 06242420000, /* */ 00000004246, 05262420000, /* */ 03400424246, 05262420000, /* */ 00034004246, 05262420000, /* */ 00076424040, 04040400000, /* */ 00000007642, 04040400000, /* */ 00076222222, 04242764200, /* */ 00000007622, 02242764200, /* */ 00000004040, 07442740000, /* */ 00052525234, 05252520000, /* */ 00000005252, 03452520000, /* */ 00074404074, 04242740000, /* */ 00000003440, 07442740000, /* */ 00034103442, 03410340000, /* */ 00000101034, 04234101000, /* */ 00074020276, 00202760000, /* */ 00000007402, 07602740000, /* */ 02476404074, 04040760000, /* */ 00024003442, 07440340000, /* */ 00000007442, 07442740000, /* */ 00076242424, 02424240000, /* */ 00000004242, 07642420000, /* */ 00000004450, 06050440000 /* */ }; #define CompareCenters 1 /* whether to check out old Spot->Pixel converters */ #define IDline "Calibration File Version Number: 3.0" #include #include #include #include #include #include "ppm.h" #include clock_t timeA; /* often-needed time variable */ #define PGM_ASSIGN(p,v) (p = (v)) /* for symmetry with PPM_ASSIGN */ int SPOTSACROSS = 25; /* approximate number of horizontal spot spacings across image */ int SPOTRADIUS = 15; int SPOTRADIUSMIN = 5; /* minimum radius of spot to consider */ int SPOTRADIUSMAX = 18; /* maximum radius of spot to consider */ int DISTORPOLYDEG = 6; /* number of coefficients of radial distortion function */ #define PhMax 1000 /* Maximum picture height */ #define PwMax 2000 /* Maximum picture width */ #define Pad 100 /* Picture padding border, pixels */ #define Sad 50 /* How far beyond boundaries to allow spot centers */ #define Paspect 1.000 /* Width/height ratio of pixels */ char PreRectify = 1; /* 1 to blindly pre-rectify image using atan assumption */ int IdentityMap = 0; /* 1 to leave picture unchanged in current rectification */ int Iteration = 0; /* Each rectification iteration uses last to better find spots */ int BasisSet = 0 ; /* Which basis functions to use in least squares fit */ double FitRad = 1.0; /* Radius in spot spacings of neighborhood for SpotToPixelSimple */ int weirdcount = 0, clashcount = 0; /* in spot linking, count of aberrant local link coordinates and number of attempted spot coord labellings inconsistent with previous labellings */ gray **picin; /* raw incoming picture */ pixel **picout; /* outgoing display picture */ #define PICOUT_(I,J) picout[(I)+Pad][(J)+Pad] pixel **curves; /* color curve fit display picture */ #define CURVES(i,j) curves[Curveswid-1-(i)][j] int Curveswid; /* size of Curves image */ gray **graphout; /* outgoing plot picture */ int Ph, /* Picture height, pixels (576 or 480 typical) */ Pw; /* Picture width, pixels (768 or 640 typical) */ unsigned char **picpad; /* working image, with border padding */ /* IMGid specifies the destination image for Text and perhaps other graphic routines */ #define IMGpicin 0 /* specifies picin as graphic destination */ #define IMGpicout 1 /* specifies picout as graphic destination */ #define IMGgraphout 2 /* specifies graphout as graphic destination */ #define IMGpicpad 3 /* specifies picpad as graphic destination */ #define IMGcurves 4 /* specifies curves as graphic destination */ int IMGid = IMGpicout; #ifndef PI #define PI 3.14159265358979323 /* Pies are round */ #endif float **flP; /* raster spotness value */ unsigned char **chP; /* raster spotness radius */ int MaxNspots = 2000; /* maximum number of spots expected + safety margin */ int Nspots = 0; /* number of spots found */ int NspotsLinked = 0; /* number of spots linked into grid */ int MaxHood = 12; /* neighborhood size for adjacent spot candidates */ #define Adjacencies 8 /* immediate neighbors: horizontal, vertical are 4 and maybe 2 diagonals make 8 */ int Inb[8] = {1,-1,0,0,1,-1,1,-1}, Jnb[8] = {0,0,1,-1,1,-1,-1,1}; /* displacement of the eight adjacent spots, in I and J spot coords */ double *Ispots, *Jspots; /* stores spot's pixel coordinates */ double *Vspots, *Rspots; /* spotness value, radius lists */ double *IIspots, *IJspots, *JIspots, *JJspots; /* spot local coordinate system */ double *Itemp, *Jtemp, *Ktemp, *Ltemp; /* working storage for spot least-square fits */ /* IIspots and JJspots also used later to store spot's spot coordinates IJspots and JIspots to store spot's rectified pixel coordinates */ int *Aspots; /* spot coordinate status: -1 untouched, 0 inited, 1 done 2 linked */ float **Dspots; int **DPspots, **DPerror; int Cspots, Pspots; /* Pattern and image center spots */ float *Dsp; int *DPsp; /* Arrays for spot distance sort */ double SpotSpacing, FOVrad; float Yaim = 0.0, Xaim = -2.62, Distance = 8.5, FOV = 90.0; /* Daimler Benz old camera calibration setup Yaim = 0.0, Xaim = 0.0, Distance = 40.33, FOV = 30.0; */ /* CMU red camera Yaim = 0.0, Xaim = -2.62, Distance = 8.5, FOV = 90.0; */ #define CalibPNMax 100 /* Scatter polynomial maximum size */ double CalibPi=240.0, CalibPj=320.0, CalibAS = 1.0; /* Calibrated pixel image center and aspect ratio */ double CalibSi = 0.0, CalibSj = 0.0; /* Calibrated spot image center */ /* Set up distortion polynomial for identity */ int CalibPN = 1; /* Distortion function degree */ double CalibP[CalibPNMax] = {1.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0, 0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0}; /* Spot->Pixel radius scatter plot function */ double CalibS[CalibPNMax] = {1.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0, 0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0}; /* Pixel->Spot radius scatter plot function */ double CalibAN = 0.0; /* Angle of the gid image from horizontal (vertical) */ /* Map_Spot_Pixel_Setup set versions of distortion parameters */ int CalibPN_s; double CalibP_s[CalibPNMax], CalibS_s[CalibPNMax]; double SpotSpacing_s, Yaim_s, Xaim_s; double CalibSj_s, CalibSi_s; double CalibAN_s, CalibAS_s, rootCalibAS_s; double CalibPj_s, CalibPi_s; int Ph_s, Pw_s; double Poly[CalibPNMax]; /* Snapshot of "fitbasis" function */ double isc, jsc; /* Image axis center */ double ipc, jpc, res, aspect, rootaspect; double bestisc, bestjsc, bestipc, bestjpc; double bestfit; /* RMS scatter from most recent minimization */ double bestaspect, Bestbestfit; int dpass; /* indexed color map, reflecting some old conventions */ #define x 255 #define y 128 unsigned char cmap[15][3] = {{0,0,0},{x,0,0},{0,x,0},{0,0,x},{0,x,x}, {x,0,x},{x,x,0},{x,x,x},{x,y,0},{x,0,y}, {y,x,0},{y,0,x},{0,x,y},{0,y,x},{y,y,y}}; #undef x #undef y /* spot pixels, from center out, maximally balanced */ #define SPX 5000 /* maximum length of prototype spot pixel list */ #define ROX 100 /* maximum spot prototype radius */ int spi[SPX], spj[SPX]; /* i and j coords of the pixels */ int sprpos[ROX]; /* starting index for each whole pixel ring */ int spn = 0; /* actual number of pixels in list */ int sprmax = 0; /* maximum spot radius in list */ #define IMGNMAX 200 /* maximum length of calibration image file names */ char img[2*IMGNMAX]; int imgn; int imgl; /* calibration image name */ char imgname[2*IMGNMAX]; /* extra copy of name to annotate displays */ FILE *logfile; char slog[500]; /* logfile, slog: file and string for run log output */ void plog(void); void plog(void) /* write out message in log file and stdout */ { fprintf(logfile,"%s",slog); printf("%s",slog); } void llog(char *s); void llog(char *s) /* write fixed string in log file */ { fprintf(logfile,s); printf(s); } void Text(int kolor, int ip, int jp, char* txt); void Text(int kolor, int ip, int jp, char* txt) /* insert text txt into image at ip jp into IMGid specified image */ { char *tc; int i, j, l, ic, jc, jl, icp, jcp; tc=txt; ic = ip; jl = jc = jp; while ((i = *tc) != '\0') { if (i == '\n') { jc = jl; ic += 11; } else if (i == '\b') jc -= 7; else if (i == '\v') ic -= 11; else if (i == ' ') jc += 7; else { icp = ic - 9; for (l=0; l<=1; ++l) for (j=0; j<30; ++j) { if ((j % 6) == 0) { icp++; jcp = jc; } if (((1 << (29-j)) & letterdefs[2*i+l]) != 0) switch (IMGid) { case IMGpicout: if ( jcp >= -Pad && jcp < Pw+Pad && icp >= -Pad && icp < Ph+Pad ) PPM_ASSIGN(PICOUT_(icp,jcp),cmap[kolor-1][0], cmap[kolor-1][1],cmap[kolor-1][2]); break; case IMGpicin: if ( jcp >= 0 && jcp < Pw && icp >= 0 && icp < Ph ) PGM_ASSIGN(picin[icp][jcp],cmap[kolor-1][1]); break; case IMGgraphout: if ( jcp >= 0 && jcp < Pw && icp >= 0 && icp < Ph ) PGM_ASSIGN(graphout[icp][jcp],cmap[kolor-1][1]); break; case IMGpicpad: if ( jcp >= -Pad && jcp < Pw+Pad && icp >= -Pad && icp < Ph+Pad ) picpad[icp][jcp] = cmap[kolor-1][1]; break; case IMGcurves: if ( jcp >= 0 && jcp < Curveswid && icp >= 0 && icp < Curveswid ) PPM_ASSIGN(curves[icp][jcp],cmap[kolor-1][0], cmap[kolor-1][1],cmap[kolor-1][2]); break; default: {}; } jcp++; } jc += 7; } ++tc; } } void WriteCalibFile(char *CalibFilename); void WriteCalibFile(char *CalibFilename) /* Write out calibration file */ { FILE *fp; clock_t time1; int i; assert(fp = fopen(CalibFilename,"w")); /* write out the calibration setup, Ph, Pw, Xaim, Yaim, Distance, FOV, The function degree+1, the distortion center i and j pixel coordinates, the aspect ratio, the distortion center i and j spot coordinates, the image rotation angle, the Spot->Pixel function coefficients, the Pixel->Spot function coefficients, space for fine displacement and rotation adjustments */ time1 = time(NULL); fprintf(fp,IDline "\n" "Creation Time: %s\n" "Image Height (pixels): %d\nImage Width (pixels): %d\n" "Aimed At (spot coords): Y=%.6g X=%.6g\n" "Distance to Calibration Grid (spot spacings): %.6g\n" "Horizontal Field Of View (degrees): %.6g\n", ctime(&time1), Ph, Pw, Yaim, Xaim, Distance, FOV); fprintf(fp,"Function Degree: %d\n" "Distortion Center (pixels): Y=%.6g X=%.6g\n" "Aspect Ratio: %.6g\n" "Distortion Center (spot coords): Y=%.6g X=%.6g\n" "Rotation (degrees): %.6g\n\n" "Spot->Pixel Function Coefficients:\n", CalibPN,CalibPi, CalibPj,CalibAS,CalibSi,CalibSj,CalibAN*180./PI); for (i=0; iSpot Function Coefficients:\n"); for (i=0; iS lens distortion */ { c = Distance; v = x*FOVrad/Pw; v = c*pow(v,(double) 2*i+1); /* series expansion for tan(v) is made of odd powers of v */ } else if (BasisSet==2) /* for S->P lens distortion */ { c = Pw/FOVrad; v = x/Distance; /* projective scale, from calibration setup */ v = c*pow(((i&1)==0)?atan(v):v,(double) (1+(i>>1))); /* Powers of atan(v) and v v models simple projective geometry. atan(v) is appropriate for distortion that gives uniform light intensity across image. Function is ready for either, with higher order adjustments. */ } else v = pow(x,(double) i); /* generic */ return(v); } void Map_Spot_Pixel_Setup(void); void Map_Spot_Pixel_Setup(void) /* setu up to transform spot coords [i,j] into pixel coords [*ii,*jj] and vice versa using last calculated distortion setup */ { int i; CalibPN_s = CalibPN; CalibPi_s = CalibPi; CalibPj_s = CalibPj; CalibAS_s = CalibAS; CalibSi_s = CalibSi; CalibSj_s = CalibSj; CalibAN_s = CalibAN; Pw_s = Pw; Ph_s = Ph; Xaim_s = Xaim; Yaim_s = Yaim; SpotSpacing_s = SpotSpacing; for (i=0; i< CalibPN_s; i++) CalibS_s[i] = CalibS[i]; for (i=0; i< CalibPN_s; i++) CalibP_s[i] = CalibP[i]; rootCalibAS_s = sqrt(CalibAS_s); sprintf(slog," Rectified image spot spacing %.4g pixels\n",SpotSpacing_s); plog(); sprintf(slog," Rectification parameters:\n"); plog(); sprintf(slog," Ph %d Pw %d Yaim %.4g Xaim %.4g Distance %.4g FOV %.4g\n", Ph_s, Pw_s, Yaim_s, Xaim_s, Distance, FOV); plog(); sprintf(slog," CalibPi %.4g CalibPj %.4g CalibAS %.4g " "CalibSi %.4g CalibSj %.4g CalibAN(d) %.4g\n", CalibPi,CalibPj,CalibAS,CalibSi,CalibSj,CalibAN_s*180.0/PI); plog(); llog(" S->P Function CalibP:"); for (i=0; iS Function CalibS:"); for (i=0; i0) /* see if identity transform really is */ { is = *ii - i; if (is<0) is = -is; js = *jj - j; if (js<0) js = -js; if (is>0.001 || js>0.001) IdentityMap++; *ii = i; *jj = j; } } void Map_Pixel_to_Spot(double ii, double jj, double *i, double *j); void Map_Pixel_to_Spot(double ii, double jj, double *i, double *j) /* transform pixel coords [ii,jj] into spot coords [*i,*j] using last calculated distortion setup */ { double is, js, rs, rp, ang, sang, cang, ip, jp; /* subtract pixel optical axis from source pixel */ ip = ii - CalibPi_s; jp = jj - CalibPj_s; ip *= rootCalibAS_s; jp /= rootCalibAS_s; rp = sqrt(ip*ip+jp*jp); /* transform pixel radius to spot radius */ rs = Spot_Radius_from_Pixel(rp); /* project spot radius back onto to pixel radius direction, rotated by grid angle from horizontal */ ang = atan2(ip,jp); sang = sin(ang+CalibAN_s); cang = cos(ang+CalibAN_s); is = rs*sang; js = rs*cang; /* add in axis spot center */ is += CalibSi_s; js += CalibSj_s; /* rectified spot image coordinates for this source pixel */ *i = (is - Yaim_s)*SpotSpacing_s + (Ph_s-1)/2.0; *j = (js - Xaim_s)*SpotSpacing_s + (Pw_s-1)/2.0; if (IdentityMap>0) /* see if identity transform really is */ { is = *i - ii; if (is<0) is = -is; js = *j - jj; if (js<0) js = -js; if (is>0.001 || js>0.001) IdentityMap++; /* count up violations */ *i = ii; *j = jj; } } void ScatterGraph(char *name); void ScatterGraph(char *name) /* draw basic scatter graph of Pixel versus Spot radii */ { double RpMax = 0.0, RsMax = 0.0; /* max radii, for scaling plots */ int k, ii, jj, i, j; FILE *fp; /* clear scatter graph */ for (i=0; iRpMax) RpMax = Itemp[k]; if (Jtemp[k]>RsMax) RsMax = Jtemp[k]; }; /* draw spot/pixel radius scatter points */ for (k=0; k0 && ii0 && jjS and S->P functions */ { int rmax, i, j, l, ii, jj; double k; FILE *fp; rmax = sqrt(Pw*Pw+Ph*Ph)/2.0+1; Curveswid = 1.4*rmax; assert(curves = ppm_allocarray(Curveswid,Curveswid)); for (i=0; iP curve in red */ { j = floor(k+0.5); i = floor(Pixel_Radius_from_Spot(k/SpotSpacing)+0.5); if (j>=0 && j=0 && iS curve in green */ { i = k+0.5; j = floor(SpotSpacing*Spot_Radius_from_Pixel(k)+0.5); if (j>=0 && j=0 && i=0 && i+ii=0 && j+jjsan[kout]) /* eliminate mostly unknown areas */ { kk = k>1?k-1:k; dn = sn[kk]; dv = sv[kk]; dq = sq[kk]; /* dot averages: if possible omit outermost pixels for distortion tolerance */ on = sn[kout]-sn[k]; ov = sv[kout]-sv[k]; oq = sq[kout]-sq[k]; /* outer ring averages */ dmn = dv/dn; dvr = (dq - dv*dmn); omn = ov/on; ovr = (oq - ov*omn); dotq = omn-dmn - sqrt(4.0*dvr/(dn-1.0)+2.0*ovr/(on-1.0)); if (dotq>dotbest) { dotbest = dotq; *rbest = k;}; } }; return(dotbest*(*rbest)); /* bias answer towards largest spots */ } void paintspots(int lor, int hir); void paintspots(int lor, int hir) /* calculate "spotness" across image */ { int i,j,l; flP = (float **) padmat(Ph,Pw,Pad,Pad,sizeof(float)); chP = (unsigned char **) padmat(Ph,Pw,Pad,Pad,sizeof(char)); for (i= -Sad; i0.0) { di = i; dj = j; Ispots[Nspots] = di; Jspots[Nspots] = dj; Vspots[Nspots] = flP[i][j]; Rspots[Nspots] = chP[i][j]; Nspots++; assert(Nspotsszmax) szmax=Rspots[i]; }; llog(" Spot maxima histogram: \n "); for (i=szmin; i<=szmax; i++) { sprintf(slog," %d(%d)",i,sizes[i]); plog(); }; llog("\n"); } llog("\n* Linking neighboring spots into grid *\n\n"); llog(" Creating and propagating spot-local coordinate systems\n"); /* scan spot list to find neighbors of each spot */ if (Nspots < Hood) Hood = Nspots; for (i = 0; i 1.4 || ddj/ddi > 1.4) weirdcount++; for (j=0; j=0) for (k=0; k<2; k++) axpr[j][k] = axbs[j][k]; for (j=0; j=0 && axid[1]>=0) { IIspots[i] = (axpr[0][0]-axpr[1][0])/4.0 + IIspots[i]/2.0; IJspots[i] = (axpr[0][1]-axpr[1][1])/4.0 + IJspots[i]/2.0; }; if (axid[2]>=0 && axid[3]>=0) { JIspots[i] = (axpr[2][0]-axpr[3][0])/4.0 + JIspots[i]/2.0; JJspots[i] = (axpr[2][1]-axpr[3][1])/4.0 + JJspots[i]/2.0; }; for (j=0; j=0) { if (Aspots[k]<0) /* an uninitialized spot */ { Aspots[k] = 0; IIspots[k] = IIspots[i]; IJspots[k] = IJspots[i]; JIspots[k] = JIspots[i]; JJspots[k] = JJspots[i]; } else if (Aspots[k]==0) /* already has initial coordinates */ { /* average in this spot's coordinates */ IIspots[k] = (IIspots[i] + IIspots[k])/2; IJspots[k] = (IJspots[i] + IJspots[k])/2; JIspots[k] = (JIspots[i] + JIspots[k])/2; JJspots[k] = (JJspots[i] + JJspots[k])/2; }; }; for (j=0; j0); llog("\n"); if (weirdcount>0) { sprintf(slog," %d spots got weird labelling axis ratios !!!\n",weirdcount); plog(); }; llog(" Assigning ideal coordinates to spots\n"); /* label spots with ideal coordinates */ /* for (i=0; i=0 && Aspots[k]>=1) /* only do validly linked spots! */ { di = IIspots[i] + Inb[j-1]; dj = JJspots[i] + Jnb[j-1]; if (Aspots[k]==2) { if (IIspots[k] != di || JJspots[k] != dj) { clashcount++; /* note labelling clash */ Aspots[k] = -1; /* trouble - unlink clashing spot */ } } else { Aspots[k]=2; ndone++; IIspots[k] = di; JJspots[k] = dj; }; }; }; sprintf(slog," %d",ndone); plog(); } while (ndone>0); llog("\n"); if (clashcount>0) { sprintf(slog," %d spot coordinate labels clashed !!!\n",clashcount); plog(); }; /* calculate ideal coords for pattern center spot */ IIspots[Cspots]=JJspots[Cspots]=0; for (j=1; j<=4; j++) { IIspots[Cspots] += IIspots[DPspots[Cspots][j]]; JJspots[Cspots] += JJspots[DPspots[Cspots][j]]; }; IIspots[Cspots] /= 4.0; JJspots[Cspots] /= 4.0; sprintf(slog," Pattern center spot, at Centroidmost spot + [%.3g,%.3g]\n", IIspots[Cspots],JJspots[Cspots]); plog(); /* shift spot coordinates so pattern center spot is at (0,0) */ di = IIspots[Cspots]; dj = JJspots[Cspots]; for (i=0; ip01?p00:p01) < 0) i = p10>p11?p10:p11; if (i<0) return; *ipos = Ispots[i]; *jpos = Jspots[i]; return; }; is -= is0; js -= js0; /* fractional position within bounding box */ /* Bilinear interpolation of spot pixel coordinates */ *ipos = (Ispots[p00]*(1.0-is) + Ispots[p10]*is)*(1.0-js) + (Ispots[p01]*(1.0-is) + Ispots[p11]*is)*js ; *jpos = (Jspots[p00]*(1.0-is) + Jspots[p10]*is)*(1.0-js) + (Jspots[p01]*(1.0-is) + Jspots[p11]*is)*js ; } #endif #define NN 1000 /* cc doesn't allow variables in declarations. Use NN as a substitute for n in declarations */ int SimulSolve(double a[], int n); int SimulSolve(double a[], int n) /* solve n simultaneous linear equations */ /* a is an [n * (n+1)] array with rhs constants in rightmost column. Solved values are returned in a[0:n-1]. Rest of a turns into a mess. */ { int i, ii, j, jj, k, it; double t, tt; int perm[NN]; assert(NN>n); /* initialize pivot permutation */ perm[0]=0; for (i=1; itt) {it = perm[ii]; perm[ii] = perm[i]; perm[i] = it; tt=t;}; } i = perm[ii]; if ((t=a[i+ii]) == 0.0) return(0); /* singular */ if (t != 1.0) { t = 1.0/t; /* eliminate variable perm[ii] */ for (k=ii+1; k<=n; k++) a[i+k] *= t; } for (jj=ii+1; jj0; ii--) /* backsolve */ { i = perm[ii]; if ((t=a[i+n]) != 0.0) for (jj=0; jj z (weighting w), and return its root mean square error */ { double Sz, Sxz, Syz, S1, Sx, Sy, Sxx, Sxy, Syy; double E[12]; int i; Sz = Sxz = Syz = 0; S1 = Sx = Sy = Sxx = Sxy = Syy = 0; for (i=0; i rp, and return its root mean square error */ { /* double S[2*DEGG-1], R[DEGG], A[DEGG*(DEGG+1)]; */ double *S, *R, *A; int i, j, k; double v, x, xp, sum; /* assert(DEGG>deg); */ assert(S = (double *) calloc(2*deg-1,sizeof(double))); assert(R = (double *) calloc(deg,sizeof(double))); assert(A = (double *) calloc(deg*(deg+1),sizeof(double))); for (k=0; k<2*deg-1; k++) S[k]=0; for (k=0; k=0; j--) xp = xp*x + A[j]; xp -= rp[i]; sum += xp*xp; /* *w[i] if weighted sum */ }; free(A); free(R); free(S); return (sqrt(sum/n)); /* /Sum(w[i]), not n, if weighted sum */ } double fitbasis(int deg, int n, double r[], double rp[], double coeff[]); double fitbasis(int deg, int n, double r[], double rp[], double coeff[]) /* least-squares fit weighted basis functions to n points r -> rp, and return its root mean square error */ { /* double S[DEGG+1], A[DEGG*(DEGG+1)]; */ double *S, *A; int i, j, k, l; double xp, sum; /* assert(DEGG>deg); */ assert(S = (double *) calloc(deg+1,sizeof(double))); assert(A = (double *) calloc(deg*(deg+1),sizeof(double))); k = 0; /* initialize sums in matrix */ for (i = 0; i10?4e-44:exp(-r*r); /* gaussian in pixel radius */ k++; }; ipx = ipixel; jpx = jpixel; /* in case pixel and spot arguments are same variables */ /* fit plane for I spot coordinate */ k = 0; for (i =0; i10?4e-44:exp(-r*r); /* gaussian in spot radius */ k++; }; isp = ispot; jsp = jspot; /* in case pixel and spot arguments are same variables */ /* fit plane for I pixel coordinate */ k = 0; for (i =0; i0) { if (flP[i][j]>flmax) flmax=flP[i][j]; if (chP[i][j]>szmax) szmax=chP[i][j]; sizes[chP[i][j]]++; }; if (hir>szmax) hir=szmax; if (lor>hir) lor=hir; llog(" Spot operator spot size histogram:\n "); l=0; while (sizes[l]==0 && l<100) l++; for (i=l; i<=szmax; i++) { sprintf(slog," %d(%d)",i,sizes[i]); plog(); }; llog("\n"); for (i=-Pad; i0) { k = chP[i][j]; if (khir) k = hir; l = sqrt(sqrt(flP[i][j]/flmax))*255.0; PPM_ASSIGN(PICOUT_(i,j),255-l,l,l); }; } void stroke(int kolor, int is, int js, int id, int jd); void stroke(int kolor, int is, int js, int id, int jd) /* draw a line from is,js to id,jd */ { int i,j,k,l; l = sqrt((double) ((is-id)*(is-id)+(js-jd)*(js-jd))) + 1; for (k=0; k<=l; k++) { i = (k*id + (l-k)*is)/l; j = (k*jd + (l-k)*js)/l; if (j >= -Pad && j < Pw+Pad) if (i >= -Pad && i= -Pad && jp+i < Pw+Pad) { if (ip-i >= -Pad && ip-i < Ph+Pad) PPM_ASSIGN(PICOUT_(ip-i,jp+i),cmap[kolor-1][0], cmap[kolor-1][1],cmap[kolor-1][2]); if (ip+i >= -Pad && ip+i < Ph+Pad) PPM_ASSIGN(PICOUT_(ip+i,jp+i),cmap[kolor-1][0], cmap[kolor-1][1],cmap[kolor-1][2]); }; } void starburst(int kolor, int ip, int jp, int w); void starburst(int kolor, int ip, int jp, int w) /* draw a * of size w at ip,jp */ { int i, k, ki, kj; double di, dj; for (i = 1; i < 16; i+=2) { di = sin(2*PI*i/16); dj = cos(2*PI*i/16); for (k=0; k= -Pad && ki < Ph+Pad && kj >= -Pad && kj < Pw+Pad) PPM_ASSIGN(PICOUT_(ki,kj),cmap[kolor-1][0], cmap[kolor-1][1],cmap[kolor-1][2]); }; }; } void ringdraw(int kolor, int ip, int jp, int r); void ringdraw(int kolor, int ip, int jp, int r) /* draw a ring overlay around ip,jp */ { int i,j,k; for (k=sprpos[r]; k= -Pad && i < Ph+Pad && j >= -Pad && j < Pw+Pad) { if (cmap[kolor-1][0]) PPM_PUTR(PICOUT_(i,j),cmap[kolor-1][0]); if (cmap[kolor-1][1]) PPM_PUTG(PICOUT_(i,j),cmap[kolor-1][1]); if (cmap[kolor-1][2]) PPM_PUTB(PICOUT_(i,j),cmap[kolor-1][2]); }; }; } void RectifyImage(void); void RectifyImage(void) /* Rectify display image using Map_Spot_to_Pixel*/ { double is, js; int i, j, k, ii, jj, errc; double di, dj; FILE *fp; Map_Spot_Pixel_Setup(); errc = 0; plottransforms(); /* draw S->P and P->S radial correction curves */ /* Make grid spacing SpotSpacing pixels between spot centers */ for (i=-Pad; iPh-1 || jj<0 || jj>Pw-1) { errc++; PPM_ASSIGN(PICOUT_(i,j),128,128,128); } else PPM_ASSIGN(PICOUT_(i,j),picin[ii][jj],picin[ii][jj],picin[ii][jj]); }; sprintf(slog," Display rectify: %d pixels from beyond the original image edge\n",errc); plog(); /* Overlay a grid with SpotSpacing-sized cells */ for (k=-1; k<=1; k+=2) { for (is=0; is>1),128+(k>>1),(k>>1)); }; assert(fp = fopen(strcat(img,".rect.ppm"),"w")); /* and write out display */ IMGid = IMGpicout; Text(1,Ph+Pad-20,(Pw+2*Pad)/8-Pad,img); /* annotate display */ { char temp[500]; timeA = time(NULL); sprintf(temp,"flatfish %s %.4g %.4g %.4g %.4g %d %s", imgname,Yaim,Xaim,Distance,FOV,SPOTSACROSS,ctime(&timeA) ); Text(1,Ph+Pad-5,(Pw+2*Pad)/8-Pad,temp); }; Text(1,Ph+Pad-20,Pw+Pad-80,PreRectify?"PRE-RECT":"ORIGINAL"); img[imgl] = 0; ppm_writeppm(fp,picout,Pw+Pad+Pad,Ph+Pad+Pad,255,0); fclose(fp); /* Rectify image into working array */ errc = 0; for (i=-Pad; iPh-1 || jj<0 || jj>Pw-1) { errc++; picpad[i][j] = 128; } else picpad[i][j] = picin[ii][jj]; } sprintf(slog, " Working image rectify: %d pixels from beyond original image edge\n" ,errc); plog(); if (IdentityMap > 1) { sprintf(slog," !!!!! Identity transform failed %d times !!!!!\n", IdentityMap/2); plog(); }; /* Redistort image using Map_Pixel_to_Spot */ errc = 0; for (i=-Pad; iPh+Pad-1 || jj<-Pad || jj>Pw+Pad-1) { errc++; PPM_PUTB(PICOUT_(i,j),128); } else { PPM_PUTB(PICOUT_(i,j),PPM_GETG(PICOUT_(ii,jj))); }; }; sprintf(slog," Redistort: %d pixels from beyond the padded image edge\n",errc); plog(); for (i=-Pad; i=0 && i=0 && j.pgm " " \n" "spot.pgm: an aligned image of a calibration spot grid\n" "Y,Xaim: down,right spot coords of camera aim, center spot = (0,0)\n" "Distance: spot spacings between lens center and the spot grid\n" "FOV: Degrees horizontal field of view of rectified image\n" "SpotsAcross: number of spot spacings across image (0=automatic)\n" "PreRectify: precorrect assumed distortion? (defaults to Y)\n" ); printf("\n\nflatfish "); fflush(stdout); fgets(line,300,stdin); sscanf(line,"%s%g%g%g%g%d%c\n", imgname,&Yaim,&Xaim,&Distance,&FOV,&SPOTSACROSS,&PreRectifyChar); printf("\n\n"); } else { strcpy(imgname,argv[1]); if (argc > 2) sscanf(argv[2],"%g",&Yaim); if (argc > 3) sscanf(argv[3],"%g",&Xaim); if (argc > 4) sscanf(argv[4],"%g",&Distance); if (argc > 5) sscanf(argv[5],"%g",&FOV); if (argc > 6) sscanf(argv[6],"%d",&SPOTSACROSS); if (argc > 7) sscanf(argv[7],"%c",&PreRectifyChar); }; /* set up file name extension system for trace files */ strcpy(img,imgname); imgl = strlen(img); assert(imgl5) if (img[imgl-4]=='.') imgl -= 4; imgn = imgl; img[imgl] = 0; pgm_init(&argc, argv); ppm_init(&argc, argv); /* Read in raw spot image */ fp = fopen(imgname,"r"); if (fp == NULL) {printf("No %s file!!\n",imgname); exit(1); }; picin = pgm_readpgm(fp, &cols, &rows, &maxval ); fclose(fp); Pw = cols; Ph = rows; /* check to see if picture will fit */ assert(Ph10 || Yaim<-10 || Xaim>15 || Xaim<-15 || Distance<3 || Distance>50 || FOV < 10 || FOV > 170) { printf("(Yaim %.3g Xaim %.3g Distance %.3g FOV %.3g) seem unreasonable\n", Yaim, Xaim, Distance, FOV); exit(1); }; FOVrad = FOV*PI/180.0; /* FOV in radians */ /* Desired spot spacing calculated from user FOV request and Distance */ SpotSpacing = Pw/(Distance*2.0*tan(FOVrad/2.0)); /* estimate number of spots across image from SpotSpacing and picture width */ if (SPOTSACROSS<=0) SPOTSACROSS = Pw/SpotSpacing+1; PreRectify = (PreRectifyChar == 'Y' || PreRectifyChar =='y'); logfile = fopen(strcat(img,".log"),"w"); /* open log file */ img[imgl] = 0; /* reset file name to base */ llog("\n"); j = strlen(imgname); /* pretty print file name */ for (i=0; i>1),128+(k>>1),(k>>1)); else PPM_ASSIGN(PICOUT_(i,j),k,k,k); }; IMGid = IMGpicout; Text(1,Ph+Pad-20,(Pw+2*Pad)/4-Pad,img); /* annotate display */ { char temp[500]; timeA = time(NULL); sprintf(temp,"flatfish %s %.4g %.4g %.4g %.4g %d %s", imgname,Yaim,Xaim,Distance,FOV,SPOTSACROSS,ctime(&timeA) ); Text(1,Ph+Pad-5,(Pw+2*Pad)/4-Pad,temp); }; Text(1,Ph+Pad-20,Pw+Pad-80,PreRectify?"PRE-RECT":"ORIGINAL"); /*****************************************************\ fit dot templates - find spots with spot operator \*****************************************************/ sprintf(slog,"\n* Applying spot operator at every pixel," " radius range %d to %d *\n\n", SPOTRADIUSMIN,SPOTRADIUSMAX); plog(); time1 = clock() ; /* time it too - this is the expensive part */ paintspots(SPOTRADIUSMIN, SPOTRADIUSMAX); /* apply spot operator */ time2 = clock() ; sprintf(slog," Spot finder time: %g s\n", (time2 - time1)/((double) CLOCKS_PER_SEC)); plog(); showspots(SPOTRADIUSMIN, SPOTRADIUSMAX); /* overlay the results of the spot operator */ assert(fp = fopen(strcat(img,".spots.ppm"),"w")); /* and write out display */ img[imgl] = 0; ppm_writeppm(fp,picout,Pw+Pad+Pad,Ph+Pad+Pad,255,0); fclose(fp); /************************************************************\ link up found spots (identified by local max of spot operator) into their natural grid + center spot \************************************************************/ llog("\n* Finding local maxima of spot operator *\n\n"); for (i= -Sad; i0.0) { rmax = (chP[i][j]*3)/2+1; /* radius of local max test */ for (k=1; k= -Sad) if (ii= -Sad) if (jj=0) { stroke(j,Ispots[i],Jspots[i], (Ispots[k]+Ispots[i]+1)/2,(Jspots[k]+Jspots[i]+1)/2); }; }; assert(fp = fopen(strcat(img,".links.ppm"),"w")); /* and write out display */ img[imgl] = 0; ppm_writeppm(fp,picout,Pw+Pad+Pad,Ph+Pad+Pad,255,0); fclose(fp); if (NspotsLinked < 2*CalibPN+1) /* can't go on without enough data! */ { sprintf(slog, "\n!! Too few spots (%d) linked for degree %d fit - quitting!!\n", NspotsLinked,CalibPN); plog(); llog("\n"); img[imgn]=0; llog("Calibration file is: "); llog(strcat(img,".calib")); llog("\n\n"); fclose(logfile); /* close log file */ exit(0); }; /***********************************************************\ fit distortion curve, and find center to minimize scatter \***********************************************************/ llog("\n* Determining optical center and radial distortion *\n\n"); Map_Spot_Pixel_Setup(); /* done with using old transform now: can muck with the parameters */ /* replace rectified pixel coords with raw coords */ for (k=0; kDISTORPOLYDEG) CalibPN = DISTORPOLYDEG; else CalibPN = Iteration+2; assert(CalibPN < CalibPNMax); /* choose initial center, in spot coords, at pixel center of image */ CalibPi = (Ph-1.0)/2.0; CalibPj = (Pw-1.0)/2.0; PixelToSpotSimple(CalibPi, CalibPj, &CalibSi, &CalibSj); CalibAS = 1.0; /* and initial aspect ratio */ bestfit = 1e10; /* Any answer is better than none! */ bestisc = CalibSi; bestjsc = CalibSj; bestaspect = CalibAS; bestipc = CalibPi; bestjpc = CalibPj; for (dpass = -1; dpass <= 0; dpass++) /* first guess, then fit */ { int scan; /* extent of search */ double reslo, reshi; /* search for a better image center */ scan = dpass?0:1; /* 1st pass: use initial guess */ reslo = 4; reshi = dpass?reslo:1e-6; /* 2nd pass: optimize */ for (res = reslo; res >= reshi; res /= 1.414) { int l; for (l = -scan; l<=scan; l+=1) for (i = -scan; i<=scan; i+=1) for (j=-scan; j<=scan; j+=1) for (ii = -scan; ii<=scan; ii+=1) for (jj=-scan; jj<=scan; jj+=1) { /* search for spot center, pixel center, aspect simultaneously */ rootaspect = sqrt(aspect = bestaspect + l*res/10.0); isc = bestisc + i*res/8.0; jsc = bestjsc + j*res/8.0; ipc = bestipc + ii*res*SpotSpacing/8.0; jpc = bestjpc + jj*res*SpotSpacing/8.0; di = TryCenter(isc,jsc,ipc,jpc,aspect); if (di %.4g (%.4g) pixel rms error\n", bestfit,bestfit*NspotsLinked/(NspotsLinked-CalibPN)); plog(); llog(" S->P Function: "); for (i=0; iS Function: "); for (i=0; i0) for (i=1; i 2 || CalibP[i] < -2 || CalibS[i] > 4 || CalibS[i] < -4) { llog("\n Polynomial coefficient too large\n" " Probably nearing singularity\n" " Quitting while ahead!\n"); llog("\n"); img[imgn] = 0; llog("Calibration file is: "); llog(strcat(img,".calib")); llog("\n\n"); fclose(logfile); /* close log file */ exit(0); }; /* At this point, [Ispots,Jspots] holds pixel coordinates of spots [IIspots,JJspots] holds spot coordinates of spots Itemp holds pixel radius from optical axis for each spot Jtemp holds spot radius from optical axis for each spot */ /*****************************************************************\ Determine angle from plumb of radially corrected spot grid \*****************************************************************/ llog("\n* Determining angle of grid from horizontal (vertical) *\n\n"); { double x, y, Sx, Sy, Sxx, Syy, Sxy, A, B; double rratio; double Cmin, Cmax, Cur, t; int pass; /* clear scatter graph */ for (i=0; iCmax) Cmax = t; }; llog(pass?" Cols":" Rows"); sprintf(slog," [%g:%g] ",Cmin,Cmax); plog(); for (Cur = Cmin; Cur <= Cmax; Cur += 0.5) { int n; n = 0; Sx = Sy = 0; for (k=0; k3) /* if row/column has enough members, add in its sums */ { sprintf(slog," %d",n); plog(); Sx /= n; Sy /= n; for (k=0; k=0 && i < Ph && j>=0 && jPI/4) CalibAN -= PI/2; while (CalibAN<-PI/4) CalibAN += PI/2; sprintf(slog," Grid angle = %.4g degrees\n",CalibAN*180/PI); plog(); }; /* Write out all calibration parameters into .calib file */ llog("\n* Writing out calibration file *"); WriteCalibFile(strcat(img,".calib")); img[imgl] = 0; /* If best calibration so far, write it out under plain .calib name also */ if (bestfit*NspotsLinked/(NspotsLinked-CalibPN)NspotsLast+4) { char imgcpy[2*IMGNMAX]; llog(" (best so far)"); strcpy(imgcpy,img); imgcpy[imgn] = 0; WriteCalibFile(strcat(imgcpy,".calib")); Bestbestfit = bestfit*NspotsLinked/(NspotsLinked-CalibPN); }; llog("\n"); llog("\n* Rectifying image *\n\n"); IdentityMap = 0; /* Now using calculated rectifications for sure */ RectifyImage(); Iteration++; IterateMore = Iteration<3 || (Iteration<5 && NspotsLinked>NspotsLast); NspotsLast = NspotsLinked; } while (IterateMore); if (Iteration>=5) llog(" \n Enough iterations, already!\n"); else llog("\n Spot count didn't grow: we're done!\n"); llog("\n"); img[imgn]=0; llog("Calibration file is: "); llog(strcat(img,".calib")); llog("\n\n"); fclose(logfile); /* close log file */ return(0); }