Mercurial > hgrepos > Python2 > PyMuPDF
comparison mupdf-source/thirdparty/leptonica/src/checkerboard.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 1:1d09e1dec1d9 | 2:b50eed0cc0ef |
|---|---|
| 1 /*====================================================================* | |
| 2 - Copyright (C) 2001 Leptonica. All rights reserved. | |
| 3 - | |
| 4 - Redistribution and use in source and binary forms, with or without | |
| 5 - modification, are permitted provided that the following conditions | |
| 6 - are met: | |
| 7 - 1. Redistributions of source code must retain the above copyright | |
| 8 - notice, this list of conditions and the following disclaimer. | |
| 9 - 2. Redistributions in binary form must reproduce the above | |
| 10 - copyright notice, this list of conditions and the following | |
| 11 - disclaimer in the documentation and/or other materials | |
| 12 - provided with the distribution. | |
| 13 - | |
| 14 - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 15 - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 16 - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 17 - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY | |
| 18 - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
| 19 - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
| 20 - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
| 21 - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | |
| 22 - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
| 23 - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
| 24 - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 25 *====================================================================*/ | |
| 26 | |
| 27 /* | |
| 28 * \file checkerboard.c | |
| 29 * <pre> | |
| 30 * | |
| 31 * Find the checker corners where 4 squares come together | |
| 32 * PIX *pixFindCheckerboardCorners() | |
| 33 * | |
| 34 * Generate the hit-miss sels | |
| 35 * static SELA *makeCheckerboardCornerSela() | |
| 36 * static PIXA *makeCheckerboardCornerPixa() | |
| 37 * | |
| 38 * The functions in this file locate the corners where four squares | |
| 39 * in a checkerboard come together. With a perfectly aligned checkerboard, | |
| 40 * the solution is trivial: take the union of two hit-miss transforms (HMTs), | |
| 41 * each having a simple diagonal structuring element (sel). The two | |
| 42 * sels can be generated from strings such as these, using | |
| 43 * selCreateFromString(): | |
| 44 * | |
| 45 * static const char *str1 = "o x" | |
| 46 * " " | |
| 47 * " " | |
| 48 * " C " | |
| 49 * " " | |
| 50 * " " | |
| 51 * "x o"; | |
| 52 * static const char *str2 = "x o" | |
| 53 * " " | |
| 54 * " " | |
| 55 * " C " | |
| 56 * " " | |
| 57 * " " | |
| 58 * "o x"; | |
| 59 * | |
| 60 * A more interesting problem is to consider the checkerboard viewed from | |
| 61 * some arbitrary angle and orientation from the normal. The method | |
| 62 * developed here works for a camera located within a cone with an opening | |
| 63 * half-angle of about 45 degrees, and with its axis along the normal | |
| 64 * to the checkerboard. | |
| 65 * | |
| 66 * See prog/checkerboard_reg.c for usage. | |
| 67 * | |
| 68 * </pre> | |
| 69 */ | |
| 70 | |
| 71 #ifdef HAVE_CONFIG_H | |
| 72 #include <config_auto.h> | |
| 73 #endif /* HAVE_CONFIG_H */ | |
| 74 | |
| 75 #include "allheaders.h" | |
| 76 | |
| 77 /* Static helpers */ | |
| 78 static SELA *makeCheckerboardCornerSela(l_int32 size, l_int32 dilation, | |
| 79 l_int32 nsels, PIXA *pixadb); | |
| 80 static PIXA *makeCheckerboardCornerPixa(l_int32 size, l_int32 dilation, | |
| 81 l_int32 nsels); | |
| 82 | |
| 83 static const char selnames[64] = "s_diag1 s_diag2 s_cross1 s_cross2"; | |
| 84 | |
| 85 /*! | |
| 86 * \brief pixFindCheckerboardCorner() | |
| 87 * | |
| 88 * \param[in] pixs of checkerboard | |
| 89 * \param[in] size size of HMT sel; >= 7, typ. 15; 0 for default | |
| 90 * \param[in] dilation size of hit and miss squares; typ. 1 or 3; max 5 | |
| 91 * \param[in] nsels number to use (either 2 or 4) | |
| 92 * \param[out] ppix_corners [optional] 1 bpp pix giving corner locations | |
| 93 * \param[out] ppta_corners [optional] pta giving corner locations | |
| 94 * \param[in] pixadb [optional] pass in pre-allocated | |
| 95 * \return 0 if OK, 1 on error | |
| 96 * | |
| 97 * <pre> | |
| 98 * Notes: | |
| 99 * (1) Use %nsels = 4 if the checkerboard may be rotated by more | |
| 100 * than 20 deg. | |
| 101 * (2) The values of %size and %dilation that can be used depend on | |
| 102 * the square sizes. Nominal values here are for squares of | |
| 103 * size 30 to 50. In general, because of the viewing angle | |
| 104 * of the camera, the "squares" will appear approximately | |
| 105 * as a rotated rectangle. | |
| 106 * (3) The outputs pix_corners and pta_corners are optional. | |
| 107 * </pre> | |
| 108 */ | |
| 109 l_ok | |
| 110 pixFindCheckerboardCorners(PIX *pixs, | |
| 111 l_int32 size, | |
| 112 l_int32 dilation, | |
| 113 l_int32 nsels, | |
| 114 PIX **ppix_corners, | |
| 115 PTA **ppta_corners, | |
| 116 PIXA *pixadb) | |
| 117 { | |
| 118 BOXA *boxa1; | |
| 119 PIX *pix1, *pix2, *pix3; | |
| 120 PTA *pta1; | |
| 121 SEL *sel; | |
| 122 SELA *sela; | |
| 123 | |
| 124 if (ppix_corners) *ppix_corners = NULL; | |
| 125 if (ppta_corners) *ppta_corners = NULL; | |
| 126 if (!pixs) | |
| 127 return ERROR_INT("pixs not defined", __func__, 1); | |
| 128 if (size <= 0) size = 7; | |
| 129 if (size < 7) | |
| 130 return ERROR_INT("size too small", __func__, 1); | |
| 131 if (dilation < 1 || dilation > 5) | |
| 132 return ERROR_INT("dilation not in [1 ...5]", __func__, 1); | |
| 133 if (nsels != 2 && nsels != 4) | |
| 134 return ERROR_INT("nsels not 2 or 4", __func__, 1); | |
| 135 | |
| 136 /* Generate the hit-miss sels for finding corners */ | |
| 137 sela = makeCheckerboardCornerSela(size, dilation, nsels, pixadb); | |
| 138 if (!sela) | |
| 139 return ERROR_INT("sela not made", __func__, 1); | |
| 140 if (pixadb) { | |
| 141 pix1 = selaDisplayInPix(sela, 15, 3, 15, 2); | |
| 142 pixaAddPix(pixadb, pix1, L_INSERT); | |
| 143 } | |
| 144 | |
| 145 /* Do the hit-miss transform to find corner locations */ | |
| 146 pix1 = pixUnionOfMorphOps(pixs, sela, L_MORPH_HMT); | |
| 147 if (pixadb) pixaAddPix(pixadb, pix1, L_CLONE); | |
| 148 selaDestroy(&sela); | |
| 149 | |
| 150 /* Remove large noise c.c. */ | |
| 151 pix2 = pixSelectBySize(pix1, size, size, 8, L_SELECT_IF_BOTH, | |
| 152 L_SELECT_IF_LTE, NULL); | |
| 153 if (pixadb) pixaAddPix(pixadb, pix2, L_CLONE); | |
| 154 | |
| 155 /* Thin remaining c.c. */ | |
| 156 pix3 = pixThinConnected(pix2, L_THIN_FG, 8, 0); | |
| 157 if (pixadb) pixaAddPix(pixadb, pix3, L_CLONE); | |
| 158 | |
| 159 /* Extract the location of the center of each component */ | |
| 160 boxa1 = pixConnCompBB(pix3, 8); | |
| 161 pta1 = boxaExtractCorners(boxa1, L_BOX_CENTER); | |
| 162 boxaDestroy(&boxa1); | |
| 163 pixDestroy(&pix1); | |
| 164 pixDestroy(&pix2); | |
| 165 if (pixadb) { /* show the result as colored plus signs on the input */ | |
| 166 sel = selMakePlusSign(15, 2); | |
| 167 pix1 = pixDisplaySelectedPixels(pixs, pix3, sel, 0xff000000); | |
| 168 pixaAddPix(pixadb, pix1, L_INSERT); | |
| 169 selDestroy(&sel); | |
| 170 } | |
| 171 | |
| 172 if (ppix_corners) | |
| 173 *ppix_corners = pix3; | |
| 174 else | |
| 175 pixDestroy(&pix3); | |
| 176 if (ppta_corners) | |
| 177 *ppta_corners = pta1; | |
| 178 else | |
| 179 ptaDestroy(&pta1); | |
| 180 return 0; | |
| 181 } | |
| 182 | |
| 183 | |
| 184 /*! | |
| 185 * \brief makeCheckerboardCornerSela() | |
| 186 * | |
| 187 * \param[in] size size of HMT sel; >= 7, typ. 15; 0 for default | |
| 188 * \param[in] dilation size of hit and miss squares; typ. 1 or 3; max 5 | |
| 189 * \param[in] nsels number to use (either 2 or 4) | |
| 190 * \param[in] pixadb [optional] pass in pre-allocated | |
| 191 * \return sela hit-miss sels for finding corners, or NULL on error | |
| 192 * | |
| 193 * <pre> | |
| 194 * Notes: | |
| 195 * (1) Use 4 sels if the checkerboard may be rotated by more than 20 deg. | |
| 196 * </pre> | |
| 197 */ | |
| 198 static SELA * | |
| 199 makeCheckerboardCornerSela(l_int32 size, | |
| 200 l_int32 dilation, | |
| 201 l_int32 nsels, | |
| 202 PIXA *pixadb) | |
| 203 { | |
| 204 PIX *pix1; | |
| 205 PIXA *pixa1; | |
| 206 SARRAY *sa; | |
| 207 SELA *sela; | |
| 208 | |
| 209 if (size <= 0) size = 7; | |
| 210 if (size < 7) | |
| 211 return (SELA *)ERROR_PTR("size too small", __func__, NULL); | |
| 212 if (dilation < 1 || dilation > 5) | |
| 213 return (SELA *)ERROR_PTR("dilation not in [1 ...5]", __func__, NULL); | |
| 214 if (nsels != 2 && nsels != 4) | |
| 215 return (SELA *)ERROR_PTR("nsels not 2 or 4", __func__, NULL); | |
| 216 | |
| 217 if ((pixa1 = makeCheckerboardCornerPixa(size, dilation, nsels)) == NULL) | |
| 218 return (SELA *)ERROR_PTR("pixa for sels not made", __func__, NULL); | |
| 219 if (pixadb) { | |
| 220 pix1 = pixaDisplayTiledInColumns(pixa1, 4, 8.0, 15, 2); | |
| 221 pixaAddPix(pixadb, pix1, L_INSERT); | |
| 222 } | |
| 223 sa = sarrayCreateWordsFromString(selnames); | |
| 224 sela = selaCreateFromColorPixa(pixa1, sa); | |
| 225 pixaDestroy(&pixa1); | |
| 226 sarrayDestroy(&sa); | |
| 227 if (!sela) | |
| 228 return (SELA *)ERROR_PTR("sela not made", __func__, NULL); | |
| 229 return sela; | |
| 230 } | |
| 231 | |
| 232 | |
| 233 /*! | |
| 234 * \brief makeCheckerboardCornerPixa() | |
| 235 * | |
| 236 * \param[in] size size of HMT sel; >= 7, typ. 15; 0 for default | |
| 237 * \param[in] dilation size of hit and miss squares; typ. 1 or 3; max 5 | |
| 238 * \param[in] nsels number to use (either 2 or 4) | |
| 239 * \return pixa representing hit-miss sels for finding corners, or NULL on error | |
| 240 * | |
| 241 * <pre> | |
| 242 * Notes: | |
| 243 * (1) Each pix can be used to generate a hit-miss sel, using the | |
| 244 * function selCreateFromColorPix(). See that function for the | |
| 245 * use of color and gray pixels to encode the hits, misses and | |
| 246 * center in the structuring element. | |
| 247 * </pre> | |
| 248 */ | |
| 249 static PIXA * | |
| 250 makeCheckerboardCornerPixa(l_int32 size, | |
| 251 l_int32 dilation, | |
| 252 l_int32 nsels) | |
| 253 { | |
| 254 PIX *pix1, *pix2, *pix3; | |
| 255 PIXA *pixa1; | |
| 256 | |
| 257 pixa1 = pixaCreate(4); | |
| 258 | |
| 259 /* Represent diagonal neg slope hits and pos slope misses */ | |
| 260 pix1 = pixCreate(size, size, 32); | |
| 261 pixSetAll(pix1); | |
| 262 pix2 = pixCreate(size, size, 1); /* slope -1 line (2 pixel) mask */ | |
| 263 pixSetPixel(pix2, 1, 1, 1); /* UL corner */ | |
| 264 pixSetPixel(pix2, size - 2, size - 2, 1); /* LR corner */ | |
| 265 if (dilation > 1) | |
| 266 pixDilateBrick(pix2, pix2, dilation, dilation); /* dilate each pixel */ | |
| 267 pixSetMasked(pix1, pix2, 0x00ff0000); /* green hit */ | |
| 268 pix3 = pixRotate90(pix2, 1); /* slope +1 line (2 pixel) mask */ | |
| 269 pixSetMasked(pix1, pix3, 0xff000000); /* red miss */ | |
| 270 pixSetRGBPixel(pix1, size / 2, size / 2, 128, 128, 128); /* gray center */ | |
| 271 pixaAddPix(pixa1, pix1, L_INSERT); | |
| 272 | |
| 273 /* Represent diagonal pos slope hits and neg slope misses */ | |
| 274 pix1 = pixCreate(size, size, 32); | |
| 275 pixSetAll(pix1); | |
| 276 pixSetMasked(pix1, pix2, 0xff000000); /* red hit */ | |
| 277 pixSetMasked(pix1, pix3, 0x00ff0000); /* green miss */ | |
| 278 pixSetRGBPixel(pix1, size / 2, size / 2, 128, 128, 128); /* gray center */ | |
| 279 pixaAddPix(pixa1, pix1, L_INSERT); | |
| 280 pixDestroy(&pix2); | |
| 281 pixDestroy(&pix3); | |
| 282 | |
| 283 if (nsels == 2) | |
| 284 return pixa1; | |
| 285 | |
| 286 /* Represent cross: vertical hits and horizontal misses */ | |
| 287 pix1 = pixCreate(size, size, 32); | |
| 288 pixSetAll(pix1); | |
| 289 pix2 = pixCreate(size, size, 1); /* vertical line (2 pixel) mask */ | |
| 290 pixSetPixel(pix2, size / 2, 1, 1); | |
| 291 pixSetPixel(pix2, size / 2, size - 2, 1); | |
| 292 if (dilation > 1) | |
| 293 pixDilateBrick(pix2, pix2, dilation, dilation); /* dilate each pixel */ | |
| 294 pixSetMasked(pix1, pix2, 0x00ff0000); /* green hit */ | |
| 295 pix3 = pixRotate90(pix2, 1); /* horizontal line (2 pixel) mask */ | |
| 296 pixSetMasked(pix1, pix3, 0xff000000); /* red miss */ | |
| 297 pixSetRGBPixel(pix1, size / 2, size / 2, 128, 128, 128); /* gray center */ | |
| 298 pixaAddPix(pixa1, pix1, L_INSERT); | |
| 299 | |
| 300 /* Represent cross: horizontal hits and vertical misses */ | |
| 301 pix1 = pixCreate(size, size, 32); | |
| 302 pixSetAll(pix1); | |
| 303 pixSetMasked(pix1, pix3, 0x00ff0000); /* green hit */ | |
| 304 pixSetMasked(pix1, pix2, 0xff000000); /* red miss */ | |
| 305 pixSetRGBPixel(pix1, size / 2, size / 2, 128, 128, 128); /* gray center */ | |
| 306 pixaAddPix(pixa1, pix1, L_INSERT); | |
| 307 pixDestroy(&pix2); | |
| 308 pixDestroy(&pix3); | |
| 309 | |
| 310 return pixa1; | |
| 311 } | |
| 312 |
