/*====================================================================* - Copyright (C) 2001 Leptonica. All rights reserved. - This software is distributed in the hope that it will be - useful, but with NO WARRANTY OF ANY KIND. - No author or distributor accepts responsibility to anyone for the - consequences of using this software, or for whether it serves any - particular purpose or works at all, unless he or she says so in - writing. Everyone is granted permission to copy, modify and - redistribute this source code, for commercial or non-commercial - purposes, with the following restrictions: (1) the origin of this - source code must not be misrepresented; (2) modified versions must - be plainly marked as such; and (3) this notice may not be removed - or altered from any source or modified source distribution. *====================================================================*/ /* * classapp.c * * Top-level jb2 correlation and rank-hausdorff * * l_int32 jbCorrelation() * l_int32 jbRankHausdorff() * * Extract and classify words in textline order * * JBCLASSER *jbWordsInTextlines() * l_int32 pixGetWordsInTextlines() * l_int32 pixGetWordBoxesInTextlines() * * Use word bounding boxes to compare page images * * NUMAA *boxaExtractSortedPattern() * l_int32 numaaCompareImagesByBoxes() * static l_int32 testLineAlignmentX() * static l_int32 countAlignedMatches() * static void printRowIndices() */ #include #include #include #include "allheaders.h" static const l_int32 JB_WORDS_MIN_WIDTH = 5; /* pixels */ static const l_int32 JB_WORDS_MIN_HEIGHT = 3; /* pixels */ /* MSVC can't handle arrays dimensioned by static const integers */ #define L_BUF_SIZE 512 /* Static comparison functions */ static l_int32 testLineAlignmentX(NUMA *na1, NUMA *na2, l_int32 shiftx, l_int32 delx, l_int32 nperline); static l_int32 countAlignedMatches(NUMA *nai1, NUMA *nai2, NUMA *nasx, NUMA *nasy, l_int32 n1, l_int32 n2, l_int32 delx, l_int32 dely, l_int32 nreq, l_int32 *psame, l_int32 debugflag); static void printRowIndices(l_int32 *index1, l_int32 n1, l_int32 *index2, l_int32 n2); /*------------------------------------------------------------------* * Top-level jb2 correlation and rank-hausdorff * *------------------------------------------------------------------*/ /*! * jbCorrelation() * * Input: dirin (directory of input images) * thresh (typically ~0.8) * weight (typically ~0.6) * components (JB_CONN_COMPS, JB_CHARACTERS, JB_WORDS) * rootname (for output files) * firstpage (0-based) * npages (use 0 for all pages in dirin) * renderflag (1 to render from templates; 0 to skip) * Return: 0 if OK, 1 on error * * Notes: * (1) See prog/jbcorrelation for generating more output (e.g., * for debugging) */ l_int32 jbCorrelation(const char *dirin, l_float32 thresh, l_float32 weight, l_int32 components, const char *rootname, l_int32 firstpage, l_int32 npages, l_int32 renderflag) { char filename[L_BUF_SIZE]; l_int32 nfiles, i, numpages; JBDATA *data; JBCLASSER *classer; PIX *pix; PIXA *pixa; SARRAY *safiles; PROCNAME("jbCorrelation"); if (!dirin) return ERROR_INT("dirin not defined", procName, 1); if (!rootname) return ERROR_INT("rootname not defined", procName, 1); if (components != JB_CONN_COMPS && components != JB_CHARACTERS && components != JB_WORDS) return ERROR_INT("components invalid", procName, 1); safiles = getSortedPathnamesInDirectory(dirin, NULL, firstpage, npages); nfiles = sarrayGetCount(safiles); /* Classify components */ classer = jbCorrelationInit(components, 0, 0, thresh, weight); jbAddPages(classer, safiles); /* Save data */ data = jbDataSave(classer); jbDataWrite(rootname, data); /* Optionally, render pages using class templates */ if (renderflag) { pixa = jbDataRender(data, FALSE); numpages = pixaGetCount(pixa); if (numpages != nfiles) fprintf(stderr, "numpages = %d, nfiles = %d, not equal!\n", numpages, nfiles); for (i = 0; i < numpages; i++) { pix = pixaGetPix(pixa, i, L_CLONE); snprintf(filename, L_BUF_SIZE, "%s.%05d", rootname, i); fprintf(stderr, "filename: %s\n", filename); pixWrite(filename, pix, IFF_PNG); pixDestroy(&pix); } pixaDestroy(&pixa); } sarrayDestroy(&safiles); jbClasserDestroy(&classer); jbDataDestroy(&data); return 0; } /*! * jbRankHaus() * * Input: dirin (directory of input images) * size (of Sel used for dilation; typ. 2) * rank (rank value of match; typ. 0.97) * components (JB_CONN_COMPS, JB_CHARACTERS, JB_WORDS) * rootname (for output files) * firstpage (0-based) * npages (use 0 for all pages in dirin) * renderflag (1 to render from templates; 0 to skip) * Return: 0 if OK, 1 on error * * Notes: * (1) See prog/jbrankhaus for generating more output (e.g., * for debugging) */ l_int32 jbRankHaus(const char *dirin, l_int32 size, l_float32 rank, l_int32 components, const char *rootname, l_int32 firstpage, l_int32 npages, l_int32 renderflag) { char filename[L_BUF_SIZE]; l_int32 nfiles, i, numpages; JBDATA *data; JBCLASSER *classer; PIX *pix; PIXA *pixa; SARRAY *safiles; PROCNAME("jbRankHaus"); if (!dirin) return ERROR_INT("dirin not defined", procName, 1); if (!rootname) return ERROR_INT("rootname not defined", procName, 1); if (components != JB_CONN_COMPS && components != JB_CHARACTERS && components != JB_WORDS) return ERROR_INT("components invalid", procName, 1); safiles = getSortedPathnamesInDirectory(dirin, NULL, firstpage, npages); nfiles = sarrayGetCount(safiles); /* Classify components */ classer = jbRankHausInit(components, 0, 0, size, rank); jbAddPages(classer, safiles); /* Save data */ data = jbDataSave(classer); jbDataWrite(rootname, data); /* Optionally, render pages using class templates */ if (renderflag) { pixa = jbDataRender(data, FALSE); numpages = pixaGetCount(pixa); if (numpages != nfiles) fprintf(stderr, "numpages = %d, nfiles = %d, not equal!\n", numpages, nfiles); for (i = 0; i < numpages; i++) { pix = pixaGetPix(pixa, i, L_CLONE); snprintf(filename, L_BUF_SIZE, "%s.%05d", rootname, i); fprintf(stderr, "filename: %s\n", filename); pixWrite(filename, pix, IFF_PNG); pixDestroy(&pix); } pixaDestroy(&pixa); } sarrayDestroy(&safiles); jbClasserDestroy(&classer); jbDataDestroy(&data); return 0; } /*------------------------------------------------------------------* * Extract and classify words in textline order * *------------------------------------------------------------------*/ /*! * jbWordsInTextlines() * * Input: dirin (directory of input pages) * reduction (1 for full res; 2 for half-res) * maxwidth (of word mask components, to be kept) * maxheight (of word mask components, to be kept) * thresh (on correlation; 0.80 is reasonable) * weight (for handling thick text; 0.6 is reasonable) * natl ( numa with textline index for each component) * firstpage (0-based) * npages (use 0 for all pages in dirin) * Return: classer (for the set of pages) * * Notes: * (1) This is a high-level function. See prog/jbwords for example * of usage. * (2) Typically, words can be found reasonably well at a resolution * of about 150 ppi. For highest accuracy, you should use 300 ppi. * Assuming that the input images are 300 ppi, use reduction = 1 * for finding words at full res, and reduction = 2 for finding * them at 150 ppi. */ JBCLASSER * jbWordsInTextlines(const char *dirin, l_int32 reduction, l_int32 maxwidth, l_int32 maxheight, l_float32 thresh, l_float32 weight, NUMA **pnatl, l_int32 firstpage, l_int32 npages) { char *fname; l_int32 nfiles, i, w, h; BOXA *boxa; JBCLASSER *classer; NUMA *nai, *natl; PIX *pix; PIXA *pixa; SARRAY *safiles; PROCNAME("jbWordsInTextlines"); if (!pnatl) return (JBCLASSER *)ERROR_PTR("&natl not defined", procName, NULL); *pnatl = NULL; if (!dirin) return (JBCLASSER *)ERROR_PTR("dirin not defined", procName, NULL); if (reduction != 1 && reduction != 2) return (JBCLASSER *)ERROR_PTR("reduction not in {1,2}", procName, NULL); safiles = getSortedPathnamesInDirectory(dirin, NULL, firstpage, npages); nfiles = sarrayGetCount(safiles); /* Classify components */ classer = jbCorrelationInit(JB_WORDS, maxwidth, maxheight, thresh, weight); classer->safiles = sarrayCopy(safiles); natl = numaCreate(0); *pnatl = natl; for (i = 0; i < nfiles; i++) { fname = sarrayGetString(safiles, i, 0); if ((pix = pixRead(fname)) == NULL) { L_WARNING_INT("image file %d not read", procName, i); continue; } pixGetDimensions(pix, &w, &h, NULL); if (reduction == 1) { classer->w = w; classer->h = h; } else { /* reduction == 2 */ classer->w = w / 2; classer->h = h / 2; } pixGetWordsInTextlines(pix, reduction, JB_WORDS_MIN_WIDTH, JB_WORDS_MIN_HEIGHT, maxwidth, maxheight, &boxa, &pixa, &nai); jbAddPageComponents(classer, pix, boxa, pixa); numaJoin(natl, nai, 0, 0); pixDestroy(&pix); numaDestroy(&nai); boxaDestroy(&boxa); pixaDestroy(&pixa); } sarrayDestroy(&safiles); return classer; } /*! * pixGetWordsInTextlines() * * Input: pixs (1 bpp, 300 ppi) * reduction (1 for full res; 2 for half-res) * minwidth, minheight (of saved components; smaller are discarded) * maxwidth, maxheight (of saved components; larger are discarded) * &boxad ( word boxes sorted in textline line order) * &pixad ( word images sorted in textline line order) * &naindex ( index of textline for each word) * Return: 0 if OK, 1 on error * * Notes: * (1) The input should be at a resolution of about 300 ppi. * The word masks can be computed at either 150 ppi or 300 ppi. * For the former, set reduction = 2. * (2) The four size constraints on saved components are all * used at 2x reduction. * (3) The result are word images (and their b.b.), extracted in * textline order, all at 2x reduction, and with a numa giving * the textline index for each word. * (4) The pixa and boxa interfaces should make this type of * application simple to put together. The steps are: * - generate first estimate of word masks * - get b.b. of these, and remove the small and big ones * - extract pixa of the word mask from these boxes * - extract pixa of the actual word images, using word masks * - sort actual word images in textline order (2d) * - flatten them to a pixa (1d), saving the textline index * for each pix * (5) In an actual application, it may be desirable to pre-filter * the input image to remove large components, to extract * single columns of text, and to deskew them. */ l_int32 pixGetWordsInTextlines(PIX *pixs, l_int32 reduction, l_int32 minwidth, l_int32 minheight, l_int32 maxwidth, l_int32 maxheight, BOXA **pboxad, PIXA **ppixad, NUMA **pnai) { l_int32 maxsize; BOXA *boxa1, *boxa2, *boxa3, *boxad; BOXAA *baa; NUMA *nai; NUMAA *naa; PIXA *pixa1, *pixa2, *pixad; PIX *pixt1, *pixt2; PIXAA *paa; PROCNAME("pixGetWordsInTextlines"); if (!pboxad || !ppixad || !pnai) return ERROR_INT("&boxad, &pixad, &nai not all defined", procName, 1); *pboxad = NULL; *ppixad = NULL; *pnai = NULL; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (reduction != 1 && reduction != 2) return ERROR_INT("reduction not in {1,2}", procName, 1); if (reduction == 1) { pixt1 = pixClone(pixs); maxsize = 14; } else { /* reduction == 2 */ pixt1 = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0); maxsize = 7; } /* First estimate of the word masks */ pixt2 = pixWordMaskByDilation(pixt1, maxsize, NULL); /* Get the bounding boxes of the words. First remove the * small ones, which can be due to punctuation that was * not joined to a word. Then remove the large ones, which are * also not likely to be words. Here, pixa1 contains * the masks over each word. */ boxa1 = pixConnComp(pixt2, NULL, 8); boxa2 = boxaSelectBySize(boxa1, minwidth, minheight, L_SELECT_IF_BOTH, L_SELECT_IF_GTE, NULL); boxa3 = boxaSelectBySize(boxa2, maxwidth, maxheight, L_SELECT_IF_BOTH, L_SELECT_IF_LTE, NULL); pixa1 = pixaCreateFromBoxa(pixt2, boxa3, NULL); /* Generate a pixa of the actual word images, not the mask images. */ pixa2 = pixaClipToPix(pixa1, pixt1); /* Sort the bounding boxes of these words, saving the * index mapping that will allow us to sort the pixa identically. */ baa = boxaSort2d(boxa3, &naa, -1, -1, 4); paa = pixaSort2dByIndex(pixa2, naa, L_CLONE); /* Flatten the word pixa */ pixad = pixaaFlattenToPixa(paa, &nai, L_CLONE); boxad = pixaGetBoxa(pixad, L_COPY); *pnai = nai; *pboxad = boxad; *ppixad = pixad; pixDestroy(&pixt1); pixDestroy(&pixt2); pixaDestroy(&pixa1); pixaDestroy(&pixa2); boxaDestroy(&boxa1); boxaDestroy(&boxa2); boxaDestroy(&boxa3); boxaaDestroy(&baa); pixaaDestroy(&paa); numaaDestroy(&naa); return 0; } /*! * pixGetWordBoxesInTextlines() * * Input: pixs (1 bpp, 300 ppi) * reduction (1 for full res; 2 for half-res) * minwidth, minheight (of saved components; smaller are discarded) * maxwidth, maxheight (of saved components; larger are discarded) * &boxad ( word boxes sorted in textline line order) * &naindex ( index of textline for each word) * Return: 0 if OK, 1 on error * * Notes: * (1) The input should be at a resolution of about 300 ppi. * The word masks can be computed at either 150 ppi or 300 ppi. * For the former, set reduction = 2. * (2) In an actual application, it may be desirable to pre-filter * the input image to remove large components, to extract * single columns of text, and to deskew them. * (3) This is a special version that just finds the word boxes * in line order, with a numa giving the textline index for * each word. See pixGetWordsInTextlines() for more details. */ l_int32 pixGetWordBoxesInTextlines(PIX *pixs, l_int32 reduction, l_int32 minwidth, l_int32 minheight, l_int32 maxwidth, l_int32 maxheight, BOXA **pboxad, NUMA **pnai) { l_int32 maxsize; BOXA *boxa1, *boxa2, *boxa3, *boxad; BOXAA *baa; NUMA *nai; PIX *pixt1, *pixt2; PROCNAME("pixGetWordBoxesInTextlines"); if (!pboxad || !pnai) return ERROR_INT("&boxad and &nai not both defined", procName, 1); *pboxad = NULL; *pnai = NULL; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (reduction != 1 && reduction != 2) return ERROR_INT("reduction not in {1,2}", procName, 1); if (reduction == 1) { pixt1 = pixClone(pixs); maxsize = 14; } else { /* reduction == 2 */ pixt1 = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0); maxsize = 7; } /* First estimate of the word masks */ pixt2 = pixWordMaskByDilation(pixt1, maxsize, NULL); /* Get the bounding boxes of the words, and remove the * small ones, which can be due to punctuation that was * not joined to a word, and the large ones, which are * also not likely to be words. */ boxa1 = pixConnComp(pixt2, NULL, 8); boxa2 = boxaSelectBySize(boxa1, minwidth, minheight, L_SELECT_IF_BOTH, L_SELECT_IF_GTE, NULL); boxa3 = boxaSelectBySize(boxa2, maxwidth, maxheight, L_SELECT_IF_BOTH, L_SELECT_IF_LTE, NULL); /* 2D sort the bounding boxes of these words. */ baa = boxaSort2d(boxa3, NULL, 3, -5, 5); /* Flatten the boxaa, saving the boxa index for each box */ boxad = boxaaFlattenToBoxa(baa, &nai, L_CLONE); *pnai = nai; *pboxad = boxad; pixDestroy(&pixt1); pixDestroy(&pixt2); boxaDestroy(&boxa1); boxaDestroy(&boxa2); boxaDestroy(&boxa3); boxaaDestroy(&baa); return 0; } /*------------------------------------------------------------------* * Use word bounding boxes to compare page images * *------------------------------------------------------------------*/ /*! * boxaExtractSortedPattern() * * Input: boxa (typ. of word bounding boxes, in textline order) * numa (index of textline for each box in boxa) * Return: naa (numaa, where each numa represents one textline), * or null on error * * Notes: * (1) The input is expected to come from pixGetWordBoxesInTextlines(). * (2) Each numa in the output consists of an average y coordinate * of the first box in the textline, followed by pairs of * x coordinates representing the left and right edges of each * of the boxes in the textline. */ NUMAA * boxaExtractSortedPattern(BOXA *boxa, NUMA *na) { l_int32 index, nbox, row, prevrow, x, y, w, h; BOX *box; NUMA *nad; NUMAA *naa; PROCNAME("boxaExtractSortedPattern"); if (!boxa) return (NUMAA *)ERROR_PTR("boxa not defined", procName, NULL); if (!na) return (NUMAA *)ERROR_PTR("na not defined", procName, NULL); naa = numaaCreate(0); nbox = boxaGetCount(boxa); if (nbox == 0) return naa; prevrow = -1; for (index = 0; index < nbox; index++) { box = boxaGetBox(boxa, index, L_CLONE); numaGetIValue(na, index, &row); if (row > prevrow) { if (index > 0) numaaAddNuma(naa, nad, L_INSERT); nad = numaCreate(0); prevrow = row; boxGetGeometry(box, NULL, &y, NULL, &h); numaAddNumber(nad, y + h / 2); } boxGetGeometry(box, &x, NULL, &w, NULL); numaAddNumber(nad, x); numaAddNumber(nad, x + w - 1); boxDestroy(&box); } numaaAddNuma(naa, nad, L_INSERT); return naa; } /*! * numaaCompareImagesByBoxes() * * Input: naa1 (for image 1, formatted by boxaExtractSortedPattern()) * naa2 (ditto; for image 2) * nperline (number of box regions to be used in each textline) * nreq (number of complete row matches required) * maxshiftx (max allowed x shift between two patterns, in pixels) * maxshifty (max allowed y shift between two patterns, in pixels) * delx (max allowed difference in x data, after alignment) * dely (max allowed difference in y data, after alignment) * &same ( 1 if @nreq row matches are found; 0 otherwise) * debugflag (1 for debug output) * Return: 0 if OK, 1 on error * * Notes: * (1) Each input numaa describes a set of sorted bounding boxes * (sorted by textline and, within each textline, from * left to right) in the images from which they are derived. * See boxaExtractSortedPattern() for a description of the data * format in each of the input numaa. * (2) This function does an alignment between the input * descriptions of bounding boxes for two images. The * input parameter @nperline specifies the number of boxes * to consider in each line when testing for a match, and * @nreq is the required number of lines that must be well-aligned * to get a match. * (3) Testing by alignment has 3 steps: * (a) Generating the location of word bounding boxes from the * images (prior to calling this function). * (b) Listing all possible pairs of aligned rows, based on * tolerances in horizontal and vertical positions of * the boxes. Specifically, all pairs of rows are enumerated * whose first @nperline boxes can be brought into close * alignment, based on the delx parameter for boxes in the * line and within the overall the @maxshiftx and @maxshifty * constraints. * (c) Each pair, starting with the first, is used to search * for a set of @nreq - 1 other pairs that can all be aligned * with a difference in global translation of not more * than (@delx, @dely). */ l_int32 numaaCompareImagesByBoxes(NUMAA *naa1, NUMAA *naa2, l_int32 nperline, l_int32 nreq, l_int32 maxshiftx, l_int32 maxshifty, l_int32 delx, l_int32 dely, l_int32 *psame, l_int32 debugflag) { l_int32 n1, n2, i, j, nbox, y1, y2, xl1, xl2; l_int32 shiftx, shifty, match; l_int32 *line1, *line2; /* indicator for sufficient boxes in a line */ l_int32 *yloc1, *yloc2; /* arrays of y value for first box in a line */ l_int32 *xleft1, *xleft2; /* arrays of x value for left side of first box */ NUMA *na1, *na2, *nai1, *nai2, *nasx, *nasy; PROCNAME("numaaCompareImagesByBoxes"); if (!psame) return ERROR_INT("&same not defined", procName, 1); *psame = 0; if (!naa1) return ERROR_INT("naa1 not defined", procName, 1); if (!naa2) return ERROR_INT("naa2 not defined", procName, 1); if (nperline < 1) return ERROR_INT("nperline < 1", procName, 1); if (nreq < 1) return ERROR_INT("nreq < 1", procName, 1); n1 = numaaGetCount(naa1); n2 = numaaGetCount(naa2); if (n1 < nreq || n2 < nreq) return 0; /* Find the lines in naa1 and naa2 with sufficient boxes. * Also, find the y-values for each of the lines, and the * LH x-values of the first box in each line. */ line1 = (l_int32 *)CALLOC(n1, sizeof(l_int32)); line2 = (l_int32 *)CALLOC(n2, sizeof(l_int32)); yloc1 = (l_int32 *)CALLOC(n1, sizeof(l_int32)); yloc2 = (l_int32 *)CALLOC(n2, sizeof(l_int32)); xleft1 = (l_int32 *)CALLOC(n1, sizeof(l_int32)); xleft2 = (l_int32 *)CALLOC(n2, sizeof(l_int32)); for (i = 0; i < n1; i++) { na1 = numaaGetNuma(naa1, i, L_CLONE); numaGetIValue(na1, 0, yloc1 + i); numaGetIValue(na1, 1, xleft1 + i); nbox = (numaGetCount(na1) - 1) / 2; if (nbox >= nperline) line1[i] = 1; numaDestroy(&na1); } for (i = 0; i < n2; i++) { na2 = numaaGetNuma(naa2, i, L_CLONE); numaGetIValue(na2, 0, yloc2 + i); numaGetIValue(na2, 1, xleft2 + i); nbox = (numaGetCount(na2) - 1) / 2; if (nbox >= nperline) line2[i] = 1; numaDestroy(&na2); } /* Enumerate all possible line matches. A 'possible' line * match is one where the x and y shifts for the first box * in each line are within the maxshiftx and maxshifty * constraints, and the left and right sides of the remaining * (nperline - 1) successive boxes are within delx of each other. * The result is a set of four numas giving parameters of * each set of matching lines. */ nai1 = numaCreate(0); /* line index 1 of match */ nai2 = numaCreate(0); /* line index 2 of match */ nasx = numaCreate(0); /* shiftx for match */ nasy = numaCreate(0); /* shifty for match */ for (i = 0; i < n1; i++) { if (line1[i] == 0) continue; y1 = yloc1[i]; xl1 = xleft1[i]; na1 = numaaGetNuma(naa1, i, L_CLONE); for (j = 0; j < n2; j++) { if (line2[j] == 0) continue; y2 = yloc2[j]; if (L_ABS(y1 - y2) > maxshifty) continue; xl2 = xleft2[j]; if (L_ABS(xl1 - xl2) > maxshiftx) continue; shiftx = xl1 - xl2; /* shift to add to x2 values */ shifty = y1 - y2; /* shift to add to y2 values */ na2 = numaaGetNuma(naa2, j, L_CLONE); /* Now check if 'nperline' boxes in the two lines match */ match = testLineAlignmentX(na1, na2, shiftx, delx, nperline); if (match) { numaAddNumber(nai1, i); numaAddNumber(nai2, j); numaAddNumber(nasx, shiftx); numaAddNumber(nasy, shifty); } numaDestroy(&na2); } numaDestroy(&na1); } /* Determine if there are a sufficient number of mutually * aligned matches. Mutually aligned matches place an additional * constraint on the 'possible' matches, where the relative * shifts must not exceed the (delx, dely) distances. */ countAlignedMatches(nai1, nai2, nasx, nasy, n1, n2, delx, dely, nreq, psame, debugflag); FREE(line1); FREE(line2); FREE(yloc1); FREE(yloc2); FREE(xleft1); FREE(xleft2); numaDestroy(&nai1); numaDestroy(&nai2); numaDestroy(&nasx); numaDestroy(&nasy); return 0; } static l_int32 testLineAlignmentX(NUMA *na1, NUMA *na2, l_int32 shiftx, l_int32 delx, l_int32 nperline) { l_int32 i, xl1, xr1, xl2, xr2, diffl, diffr; PROCNAME("testLineAlignmentX"); if (!na1) return ERROR_INT("na1 not defined", procName, 1); if (!na2) return ERROR_INT("na2 not defined", procName, 1); for (i = 0; i < nperline; i++) { numaGetIValue(na1, i + 1, &xl1); numaGetIValue(na1, i + 2, &xr1); numaGetIValue(na2, i + 1, &xl2); numaGetIValue(na2, i + 2, &xr2); diffl = L_ABS(xl1 - xl2 - shiftx); diffr = L_ABS(xr1 - xr2 - shiftx); if (diffl > delx || diffr > delx) return 0; } return 1; } /* * countAlignedMatches() * Input: nai1, nai2 (numas of row pairs for matches) * nasx, nasy (numas of x and y shifts for the matches) * n1, n2 (number of rows in images 1 and 2) * delx, dely (allowed difference in shifts of the match, * compared to the reference match) * nreq (number of required aligned matches) * &same ( 1 if @nreq row matches are found; 0 otherwise) * Return: 0 if OK, 1 on error * * Notes: * (1) This takes 4 input arrays giving parameters of all the * line matches. It looks for the maximum set of aligned * matches (matches with approximately the same overall shifts) * that do not use rows from either image more than once. */ static l_int32 countAlignedMatches(NUMA *nai1, NUMA *nai2, NUMA *nasx, NUMA *nasy, l_int32 n1, l_int32 n2, l_int32 delx, l_int32 dely, l_int32 nreq, l_int32 *psame, l_int32 debugflag) { l_int32 i, j, nm, shiftx, shifty, nmatch, diffx, diffy; l_int32 *ia1, *ia2, *iasx, *iasy, *index1, *index2; PROCNAME("countAlignedMatches"); if (!nai1 || !nai2 || !nasx || !nasy) return ERROR_INT("4 input numas not defined", procName, 1); if (!psame) return ERROR_INT("&same not defined", procName, 1); *psame = 0; /* Check for sufficient aligned matches, doing a double iteration * over the set of raw matches. The row index arrays * are used to verify that the same rows in either image * are not used in more than one match. Whenever there * is a match that is properly aligned, those rows are * marked in the index arrays. */ nm = numaGetCount(nai1); /* number of matches */ if (nm < nreq) return 0; ia1 = numaGetIArray(nai1); ia2 = numaGetIArray(nai2); iasx = numaGetIArray(nasx); iasy = numaGetIArray(nasy); index1 = (l_int32 *)CALLOC(n1, sizeof(l_int32)); /* keep track of rows */ index2 = (l_int32 *)CALLOC(n2, sizeof(l_int32)); for (i = 0; i < nm; i++) { if (*psame == 1) break; /* Reset row index arrays */ memset(index1, 0, 4 * n1); memset(index2, 0, 4 * n2); nmatch = 1; index1[ia1[i]] = nmatch; /* mark these rows as taken */ index2[ia2[i]] = nmatch; shiftx = iasx[i]; /* reference shift between two rows */ shifty = iasy[i]; /* ditto */ if (nreq == 1) { *psame = 1; break; } for (j = 0; j < nm; j++) { if (j == i) continue; /* Rows must both be different from any previously seen */ if (index1[ia1[j]] > 0 || index2[ia2[j]] > 0) continue; /* Check the shift for this match */ diffx = L_ABS(shiftx - iasx[j]); diffy = L_ABS(shifty - iasy[j]); if (diffx > delx || diffy > dely) continue; /* We have a match */ nmatch++; index1[ia1[j]] = nmatch; /* mark the rows */ index2[ia2[j]] = nmatch; if (nmatch >= nreq) { *psame = 1; if (debugflag) printRowIndices(index1, n1, index2, n2); break; } } } FREE(ia1); FREE(ia2); FREE(iasx); FREE(iasy); FREE(index1); FREE(index2); return 0; } static void printRowIndices(l_int32 *index1, l_int32 n1, l_int32 *index2, l_int32 n2) { l_int32 i; fprintf(stderr, "Index1: "); for (i = 0; i < n1; i++) { if (i && (i % 20 == 0)) fprintf(stderr, "\n "); fprintf(stderr, "%3d", index1[i]); } fprintf(stderr, "\n"); fprintf(stderr, "Index2: "); for (i = 0; i < n2; i++) { if (i && (i % 20 == 0)) fprintf(stderr, "\n "); fprintf(stderr, "%3d", index2[i]); } fprintf(stderr, "\n"); return; }