/src/leptonica/src/morphapp.c
Line | Count | Source (jump to first uncovered line) |
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 | | /*! |
29 | | * \file morphapp.c |
30 | | * <pre> |
31 | | * |
32 | | * These are some useful and/or interesting composite |
33 | | * image processing operations, of the type that are often |
34 | | * useful in applications. Most are morphological in |
35 | | * nature. |
36 | | * |
37 | | * Extraction of boundary pixels |
38 | | * PIX *pixExtractBoundary() |
39 | | * |
40 | | * Selective morph sequence operation under mask |
41 | | * PIX *pixMorphSequenceMasked() |
42 | | * |
43 | | * Selective morph sequence operation on each component |
44 | | * PIX *pixMorphSequenceByComponent() |
45 | | * PIXA *pixaMorphSequenceByComponent() |
46 | | * |
47 | | * Selective morph sequence operation on each region |
48 | | * PIX *pixMorphSequenceByRegion() |
49 | | * PIXA *pixaMorphSequenceByRegion() |
50 | | * |
51 | | * Union and intersection of parallel composite operations |
52 | | * PIX *pixUnionOfMorphOps() |
53 | | * PIX *pixIntersectionOfMorphOps() |
54 | | * |
55 | | * Selective connected component filling |
56 | | * PIX *pixSelectiveConnCompFill() |
57 | | * |
58 | | * Removal of matched patterns |
59 | | * PIX *pixRemoveMatchedPattern() |
60 | | * |
61 | | * Display of matched patterns |
62 | | * PIX *pixDisplayMatchedPattern() |
63 | | * |
64 | | * Extension of pixa by iterative erosion or dilation (and by scaling) |
65 | | * PIXA *pixaExtendByMorph() |
66 | | * PIXA *pixaExtendByScaling() |
67 | | * |
68 | | * Iterative morphological seed filling (don't use for real work) |
69 | | * PIX *pixSeedfillMorph() |
70 | | * |
71 | | * Granulometry on binary images |
72 | | * NUMA *pixRunHistogramMorph() |
73 | | * |
74 | | * Composite operations on grayscale images |
75 | | * PIX *pixTophat() |
76 | | * PIX *pixHDome() |
77 | | * PIX *pixFastTophat() |
78 | | * PIX *pixMorphGradient() |
79 | | * |
80 | | * Centroid of component |
81 | | * PTA *pixaCentroids() |
82 | | * l_int32 pixCentroid() |
83 | | * </pre> |
84 | | */ |
85 | | |
86 | | #ifdef HAVE_CONFIG_H |
87 | | #include <config_auto.h> |
88 | | #endif /* HAVE_CONFIG_H */ |
89 | | |
90 | | #include "allheaders.h" |
91 | | #include "array_internal.h" |
92 | | |
93 | 0 | #define SWAP(x, y) {temp = (x); (x) = (y); (y) = temp;} |
94 | | |
95 | | /*-----------------------------------------------------------------* |
96 | | * Extraction of boundary pixels * |
97 | | *-----------------------------------------------------------------*/ |
98 | | /*! |
99 | | * \brief pixExtractBoundary() |
100 | | * |
101 | | * \param[in] pixs 1 bpp |
102 | | * \param[in] type 0 for background pixels; 1 for foreground pixels |
103 | | * \return pixd, or NULL on error |
104 | | * |
105 | | * <pre> |
106 | | * Notes: |
107 | | * (1) Extracts the fg or bg boundary pixels for each component. |
108 | | * Components are assumed to end at the boundary of pixs. |
109 | | * </pre> |
110 | | */ |
111 | | PIX * |
112 | | pixExtractBoundary(PIX *pixs, |
113 | | l_int32 type) |
114 | 0 | { |
115 | 0 | PIX *pixd; |
116 | |
|
117 | 0 | if (!pixs) |
118 | 0 | return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); |
119 | | |
120 | 0 | if (type == 0) |
121 | 0 | pixd = pixDilateBrick(NULL, pixs, 3, 3); |
122 | 0 | else |
123 | 0 | pixd = pixErodeBrick(NULL, pixs, 3, 3); |
124 | 0 | pixXor(pixd, pixd, pixs); |
125 | 0 | return pixd; |
126 | 0 | } |
127 | | |
128 | | |
129 | | /*-----------------------------------------------------------------* |
130 | | * Selective morph sequence operation under mask * |
131 | | *-----------------------------------------------------------------*/ |
132 | | /*! |
133 | | * \brief pixMorphSequenceMasked() |
134 | | * |
135 | | * \param[in] pixs 1 bpp |
136 | | * \param[in] pixm [optional] 1 bpp mask |
137 | | * \param[in] sequence string specifying sequence of operations |
138 | | * \param[in] dispsep horizontal separation in pixels between |
139 | | * successive displays; use zero to suppress display |
140 | | * \return pixd, or NULL on error |
141 | | * |
142 | | * <pre> |
143 | | * Notes: |
144 | | * (1) This applies the morph sequence to the image, but only allows |
145 | | * changes in pixs for pixels under the background of pixm. |
146 | | * (5) If pixm is NULL, this is just pixMorphSequence(). |
147 | | * </pre> |
148 | | */ |
149 | | PIX * |
150 | | pixMorphSequenceMasked(PIX *pixs, |
151 | | PIX *pixm, |
152 | | const char *sequence, |
153 | | l_int32 dispsep) |
154 | 0 | { |
155 | 0 | PIX *pixd; |
156 | |
|
157 | 0 | if (!pixs) |
158 | 0 | return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); |
159 | 0 | if (!sequence) |
160 | 0 | return (PIX *)ERROR_PTR("sequence not defined", __func__, NULL); |
161 | | |
162 | 0 | pixd = pixMorphSequence(pixs, sequence, dispsep); |
163 | 0 | pixCombineMasked(pixd, pixs, pixm); /* restore src pixels under mask fg */ |
164 | 0 | return pixd; |
165 | 0 | } |
166 | | |
167 | | |
168 | | /*-----------------------------------------------------------------* |
169 | | * Morph sequence operation on each component * |
170 | | *-----------------------------------------------------------------*/ |
171 | | /*! |
172 | | * \brief pixMorphSequenceByComponent() |
173 | | * |
174 | | * \param[in] pixs 1 bpp |
175 | | * \param[in] sequence string specifying sequence |
176 | | * \param[in] connectivity 4 or 8 |
177 | | * \param[in] minw min width to consider; use 0 or 1 for any width |
178 | | * \param[in] minh min height to consider; use 0 or 1 for any height |
179 | | * \param[out] pboxa [optional] return boxa of c.c. in pixs |
180 | | * \return pixd, or NULL on error |
181 | | * |
182 | | * <pre> |
183 | | * Notes: |
184 | | * (1) See pixMorphSequence() for composing operation sequences. |
185 | | * (2) This operates separately on each c.c. in the input pix. |
186 | | * (3) The dilation does NOT increase the c.c. size; it is clipped |
187 | | * to the size of the original c.c. This is necessary to |
188 | | * keep the c.c. independent after the operation. |
189 | | * (4) You can specify that the width and/or height must equal |
190 | | * or exceed a minimum size for the operation to take place. |
191 | | * (5) Use NULL for boxa to avoid returning the boxa. |
192 | | * </pre> |
193 | | */ |
194 | | PIX * |
195 | | pixMorphSequenceByComponent(PIX *pixs, |
196 | | const char *sequence, |
197 | | l_int32 connectivity, |
198 | | l_int32 minw, |
199 | | l_int32 minh, |
200 | | BOXA **pboxa) |
201 | 504 | { |
202 | 504 | l_int32 n, i, x, y, w, h; |
203 | 504 | BOXA *boxa; |
204 | 504 | PIX *pix, *pixd; |
205 | 504 | PIXA *pixas, *pixad; |
206 | | |
207 | 504 | if (!pixs) |
208 | 0 | return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); |
209 | 504 | if (!sequence) |
210 | 0 | return (PIX *)ERROR_PTR("sequence not defined", __func__, NULL); |
211 | | |
212 | 504 | if (minw <= 0) minw = 1; |
213 | 504 | if (minh <= 0) minh = 1; |
214 | | |
215 | | /* Get the c.c. */ |
216 | 504 | if ((boxa = pixConnComp(pixs, &pixas, connectivity)) == NULL) |
217 | 0 | return (PIX *)ERROR_PTR("boxa not made", __func__, NULL); |
218 | | |
219 | | /* Operate on each c.c. independently */ |
220 | 504 | pixad = pixaMorphSequenceByComponent(pixas, sequence, minw, minh); |
221 | 504 | pixaDestroy(&pixas); |
222 | 504 | boxaDestroy(&boxa); |
223 | 504 | if (!pixad) |
224 | 0 | return (PIX *)ERROR_PTR("pixad not made", __func__, NULL); |
225 | | |
226 | | /* Display the result out into pixd */ |
227 | 504 | pixd = pixCreateTemplate(pixs); |
228 | 504 | n = pixaGetCount(pixad); |
229 | 11.3k | for (i = 0; i < n; i++) { |
230 | 10.8k | pixaGetBoxGeometry(pixad, i, &x, &y, &w, &h); |
231 | 10.8k | pix = pixaGetPix(pixad, i, L_CLONE); |
232 | 10.8k | pixRasterop(pixd, x, y, w, h, PIX_PAINT, pix, 0, 0); |
233 | 10.8k | pixDestroy(&pix); |
234 | 10.8k | } |
235 | | |
236 | 504 | if (pboxa) |
237 | 0 | *pboxa = pixaGetBoxa(pixad, L_CLONE); |
238 | 504 | pixaDestroy(&pixad); |
239 | 504 | return pixd; |
240 | 504 | } |
241 | | |
242 | | |
243 | | /*! |
244 | | * \brief pixaMorphSequenceByComponent() |
245 | | * |
246 | | * \param[in] pixas of 1 bpp pix |
247 | | * \param[in] sequence string specifying sequence |
248 | | * \param[in] minw min width to consider; use 0 or 1 for any width |
249 | | * \param[in] minh min height to consider; use 0 or 1 for any height |
250 | | * \return pixad, or NULL on error |
251 | | * |
252 | | * <pre> |
253 | | * Notes: |
254 | | * (1) See pixMorphSequence() for composing operation sequences. |
255 | | * (2) This operates separately on each c.c. in the input pixa. |
256 | | * (3) You can specify that the width and/or height must equal |
257 | | * or exceed a minimum size for the operation to take place. |
258 | | * (4) The input pixa should have a boxa giving the locations |
259 | | * of the pix components. |
260 | | * </pre> |
261 | | */ |
262 | | PIXA * |
263 | | pixaMorphSequenceByComponent(PIXA *pixas, |
264 | | const char *sequence, |
265 | | l_int32 minw, |
266 | | l_int32 minh) |
267 | 504 | { |
268 | 504 | l_int32 n, i, w, h, d; |
269 | 504 | BOX *box; |
270 | 504 | PIX *pix1, *pix2; |
271 | 504 | PIXA *pixad; |
272 | | |
273 | 504 | if (!pixas) |
274 | 0 | return (PIXA *)ERROR_PTR("pixas not defined", __func__, NULL); |
275 | 504 | if ((n = pixaGetCount(pixas)) == 0) |
276 | 0 | return (PIXA *)ERROR_PTR("no pix in pixas", __func__, NULL); |
277 | 504 | if (n != pixaGetBoxaCount(pixas)) |
278 | 504 | L_WARNING("boxa size != n\n", __func__); |
279 | 504 | pixaGetPixDimensions(pixas, 0, NULL, NULL, &d); |
280 | 504 | if (d != 1) |
281 | 0 | return (PIXA *)ERROR_PTR("depth not 1 bpp", __func__, NULL); |
282 | | |
283 | 504 | if (!sequence) |
284 | 0 | return (PIXA *)ERROR_PTR("sequence not defined", __func__, NULL); |
285 | 504 | if (minw <= 0) minw = 1; |
286 | 504 | if (minh <= 0) minh = 1; |
287 | | |
288 | 504 | if ((pixad = pixaCreate(n)) == NULL) |
289 | 0 | return (PIXA *)ERROR_PTR("pixad not made", __func__, NULL); |
290 | 11.3k | for (i = 0; i < n; i++) { |
291 | 10.8k | pixaGetPixDimensions(pixas, i, &w, &h, NULL); |
292 | 10.8k | if (w >= minw && h >= minh) { |
293 | 10.8k | if ((pix1 = pixaGetPix(pixas, i, L_CLONE)) == NULL) { |
294 | 0 | pixaDestroy(&pixad); |
295 | 0 | return (PIXA *)ERROR_PTR("pix1 not found", __func__, NULL); |
296 | 0 | } |
297 | 10.8k | if ((pix2 = pixMorphCompSequence(pix1, sequence, 0)) == NULL) { |
298 | 0 | pixaDestroy(&pixad); |
299 | 0 | return (PIXA *)ERROR_PTR("pix2 not made", __func__, NULL); |
300 | 0 | } |
301 | 10.8k | pixaAddPix(pixad, pix2, L_INSERT); |
302 | 10.8k | box = pixaGetBox(pixas, i, L_COPY); |
303 | 10.8k | pixaAddBox(pixad, box, L_INSERT); |
304 | 10.8k | pixDestroy(&pix1); |
305 | 10.8k | } |
306 | 10.8k | } |
307 | | |
308 | 504 | return pixad; |
309 | 504 | } |
310 | | |
311 | | |
312 | | /*-----------------------------------------------------------------* |
313 | | * Morph sequence operation on each region * |
314 | | *-----------------------------------------------------------------*/ |
315 | | /*! |
316 | | * \brief pixMorphSequenceByRegion() |
317 | | * |
318 | | * \param[in] pixs 1 bpp |
319 | | * \param[in] pixm mask specifying regions |
320 | | * \param[in] sequence string specifying sequence |
321 | | * \param[in] connectivity 4 or 8, used on mask |
322 | | * \param[in] minw min width to consider; use 0 or 1 for any width |
323 | | * \param[in] minh min height to consider; use 0 or 1 for any height |
324 | | * \param[out] pboxa [optional] return boxa of c.c. in pixm |
325 | | * \return pixd, or NULL on error |
326 | | * |
327 | | * <pre> |
328 | | * Notes: |
329 | | * (1) See pixMorphCompSequence() for composing operation sequences. |
330 | | * (2) This operates separately on the region in pixs corresponding |
331 | | * to each c.c. in the mask pixm. It differs from |
332 | | * pixMorphSequenceByComponent() in that the latter does not have |
333 | | * a pixm (mask), but instead operates independently on each |
334 | | * component in pixs. |
335 | | * (3) Dilation will NOT increase the region size; the result |
336 | | * is clipped to the size of the mask region. This is necessary |
337 | | * to make regions independent after the operation. |
338 | | * (4) You can specify that the width and/or height of a region must |
339 | | * equal or exceed a minimum size for the operation to take place. |
340 | | * (5) Use NULL for %pboxa to avoid returning the boxa. |
341 | | * </pre> |
342 | | */ |
343 | | PIX * |
344 | | pixMorphSequenceByRegion(PIX *pixs, |
345 | | PIX *pixm, |
346 | | const char *sequence, |
347 | | l_int32 connectivity, |
348 | | l_int32 minw, |
349 | | l_int32 minh, |
350 | | BOXA **pboxa) |
351 | 0 | { |
352 | 0 | l_int32 n, i, x, y, w, h; |
353 | 0 | BOXA *boxa; |
354 | 0 | PIX *pix, *pixd; |
355 | 0 | PIXA *pixam, *pixad; |
356 | |
|
357 | 0 | if (pboxa) *pboxa = NULL; |
358 | 0 | if (!pixs) |
359 | 0 | return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); |
360 | 0 | if (!pixm) |
361 | 0 | return (PIX *)ERROR_PTR("pixm not defined", __func__, NULL); |
362 | 0 | if (pixGetDepth(pixs) != 1 || pixGetDepth(pixm) != 1) |
363 | 0 | return (PIX *)ERROR_PTR("pixs and pixm not both 1 bpp", __func__, NULL); |
364 | 0 | if (!sequence) |
365 | 0 | return (PIX *)ERROR_PTR("sequence not defined", __func__, NULL); |
366 | | |
367 | 0 | if (minw <= 0) minw = 1; |
368 | 0 | if (minh <= 0) minh = 1; |
369 | | |
370 | | /* Get the c.c. of the mask */ |
371 | 0 | if ((boxa = pixConnComp(pixm, &pixam, connectivity)) == NULL) |
372 | 0 | return (PIX *)ERROR_PTR("boxa not made", __func__, NULL); |
373 | | |
374 | | /* Operate on each region in pixs independently */ |
375 | 0 | pixad = pixaMorphSequenceByRegion(pixs, pixam, sequence, minw, minh); |
376 | 0 | pixaDestroy(&pixam); |
377 | 0 | boxaDestroy(&boxa); |
378 | 0 | if (!pixad) |
379 | 0 | return (PIX *)ERROR_PTR("pixad not made", __func__, NULL); |
380 | | |
381 | | /* Display the result out into pixd */ |
382 | 0 | pixd = pixCreateTemplate(pixs); |
383 | 0 | n = pixaGetCount(pixad); |
384 | 0 | for (i = 0; i < n; i++) { |
385 | 0 | pixaGetBoxGeometry(pixad, i, &x, &y, &w, &h); |
386 | 0 | pix = pixaGetPix(pixad, i, L_CLONE); |
387 | 0 | pixRasterop(pixd, x, y, w, h, PIX_PAINT, pix, 0, 0); |
388 | 0 | pixDestroy(&pix); |
389 | 0 | } |
390 | |
|
391 | 0 | if (pboxa) |
392 | 0 | *pboxa = pixaGetBoxa(pixad, L_CLONE); |
393 | 0 | pixaDestroy(&pixad); |
394 | 0 | return pixd; |
395 | 0 | } |
396 | | |
397 | | |
398 | | /*! |
399 | | * \brief pixaMorphSequenceByRegion() |
400 | | * |
401 | | * \param[in] pixs 1 bpp |
402 | | * \param[in] pixam of 1 bpp mask elements |
403 | | * \param[in] sequence string specifying sequence |
404 | | * \param[in] minw min width to consider; use 0 or 1 for any width |
405 | | * \param[in] minh min height to consider; use 0 or 1 for any height |
406 | | * \return pixad, or NULL on error |
407 | | * |
408 | | * <pre> |
409 | | * Notes: |
410 | | * (1) See pixMorphSequence() for composing operation sequences. |
411 | | * (2) This operates separately on each region in the input pixs |
412 | | * defined by the components in pixam. |
413 | | * (3) You can specify that the width and/or height of a mask |
414 | | * component must equal or exceed a minimum size for the |
415 | | * operation to take place. |
416 | | * (4) The input pixam should have a boxa giving the locations |
417 | | * of the regions in pixs. |
418 | | * </pre> |
419 | | */ |
420 | | PIXA * |
421 | | pixaMorphSequenceByRegion(PIX *pixs, |
422 | | PIXA *pixam, |
423 | | const char *sequence, |
424 | | l_int32 minw, |
425 | | l_int32 minh) |
426 | 0 | { |
427 | 0 | l_int32 n, i, w, h, same, maxd, fullpa, fullba; |
428 | 0 | BOX *box; |
429 | 0 | PIX *pix1, *pix2, *pix3; |
430 | 0 | PIXA *pixad; |
431 | |
|
432 | 0 | if (!pixs) |
433 | 0 | return (PIXA *)ERROR_PTR("pixs not defined", __func__, NULL); |
434 | 0 | if (pixGetDepth(pixs) != 1) |
435 | 0 | return (PIXA *)ERROR_PTR("pixs not 1 bpp", __func__, NULL); |
436 | 0 | if (!sequence) |
437 | 0 | return (PIXA *)ERROR_PTR("sequence not defined", __func__, NULL); |
438 | 0 | if (!pixam) |
439 | 0 | return (PIXA *)ERROR_PTR("pixam not defined", __func__, NULL); |
440 | 0 | pixaVerifyDepth(pixam, &same, &maxd); |
441 | 0 | if (maxd != 1) |
442 | 0 | return (PIXA *)ERROR_PTR("mask depth not 1 bpp", __func__, NULL); |
443 | 0 | pixaIsFull(pixam, &fullpa, &fullba); |
444 | 0 | if (!fullpa || !fullba) |
445 | 0 | return (PIXA *)ERROR_PTR("missing comps in pixam", __func__, NULL); |
446 | 0 | n = pixaGetCount(pixam); |
447 | 0 | if (minw <= 0) minw = 1; |
448 | 0 | if (minh <= 0) minh = 1; |
449 | |
|
450 | 0 | if ((pixad = pixaCreate(n)) == NULL) |
451 | 0 | return (PIXA *)ERROR_PTR("pixad not made", __func__, NULL); |
452 | | |
453 | | /* Use the rectangle to remove the appropriate part of pixs; |
454 | | * then AND with the mask component to get the actual fg |
455 | | * of pixs that is under the mask component. */ |
456 | 0 | for (i = 0; i < n; i++) { |
457 | 0 | pixaGetPixDimensions(pixam, i, &w, &h, NULL); |
458 | 0 | if (w >= minw && h >= minh) { |
459 | 0 | pix1 = pixaGetPix(pixam, i, L_CLONE); |
460 | 0 | box = pixaGetBox(pixam, i, L_COPY); |
461 | 0 | pix2 = pixClipRectangle(pixs, box, NULL); |
462 | 0 | pixAnd(pix2, pix2, pix1); |
463 | 0 | pix3 = pixMorphCompSequence(pix2, sequence, 0); |
464 | 0 | pixDestroy(&pix1); |
465 | 0 | pixDestroy(&pix2); |
466 | 0 | if (!pix3) { |
467 | 0 | boxDestroy(&box); |
468 | 0 | pixaDestroy(&pixad); |
469 | 0 | L_ERROR("pix3 not made in iter %d; aborting\n", __func__, i); |
470 | 0 | break; |
471 | 0 | } |
472 | 0 | pixaAddPix(pixad, pix3, L_INSERT); |
473 | 0 | pixaAddBox(pixad, box, L_INSERT); |
474 | 0 | } |
475 | 0 | } |
476 | |
|
477 | 0 | return pixad; |
478 | 0 | } |
479 | | |
480 | | |
481 | | /*-----------------------------------------------------------------* |
482 | | * Union and intersection of parallel composite operations * |
483 | | *-----------------------------------------------------------------*/ |
484 | | /*! |
485 | | * \brief pixUnionOfMorphOps() |
486 | | * |
487 | | * \param[in] pixs 1 bpp |
488 | | * \param[in] sela |
489 | | * \param[in] type L_MORPH_DILATE, etc. |
490 | | * \return pixd union of the specified morphological operation |
491 | | * on pixs for each Sel in the Sela, or NULL on error |
492 | | */ |
493 | | PIX * |
494 | | pixUnionOfMorphOps(PIX *pixs, |
495 | | SELA *sela, |
496 | | l_int32 type) |
497 | 0 | { |
498 | 0 | l_int32 n, i; |
499 | 0 | PIX *pixt, *pixd; |
500 | 0 | SEL *sel; |
501 | |
|
502 | 0 | if (!pixs || pixGetDepth(pixs) != 1) |
503 | 0 | return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL); |
504 | 0 | if (!sela) |
505 | 0 | return (PIX *)ERROR_PTR("sela not defined", __func__, NULL); |
506 | 0 | n = selaGetCount(sela); |
507 | 0 | if (n == 0) |
508 | 0 | return (PIX *)ERROR_PTR("no sels in sela", __func__, NULL); |
509 | 0 | if (type != L_MORPH_DILATE && type != L_MORPH_ERODE && |
510 | 0 | type != L_MORPH_OPEN && type != L_MORPH_CLOSE && |
511 | 0 | type != L_MORPH_HMT) |
512 | 0 | return (PIX *)ERROR_PTR("invalid type", __func__, NULL); |
513 | | |
514 | 0 | pixd = pixCreateTemplate(pixs); |
515 | 0 | for (i = 0; i < n; i++) { |
516 | 0 | sel = selaGetSel(sela, i); |
517 | 0 | if (type == L_MORPH_DILATE) |
518 | 0 | pixt = pixDilate(NULL, pixs, sel); |
519 | 0 | else if (type == L_MORPH_ERODE) |
520 | 0 | pixt = pixErode(NULL, pixs, sel); |
521 | 0 | else if (type == L_MORPH_OPEN) |
522 | 0 | pixt = pixOpen(NULL, pixs, sel); |
523 | 0 | else if (type == L_MORPH_CLOSE) |
524 | 0 | pixt = pixClose(NULL, pixs, sel); |
525 | 0 | else /* type == L_MORPH_HMT */ |
526 | 0 | pixt = pixHMT(NULL, pixs, sel); |
527 | 0 | pixOr(pixd, pixd, pixt); |
528 | 0 | pixDestroy(&pixt); |
529 | 0 | } |
530 | |
|
531 | 0 | return pixd; |
532 | 0 | } |
533 | | |
534 | | |
535 | | /*! |
536 | | * \brief pixIntersectionOfMorphOps() |
537 | | * |
538 | | * \param[in] pixs 1 bpp |
539 | | * \param[in] sela |
540 | | * \param[in] type L_MORPH_DILATE, etc. |
541 | | * \return pixd intersection of the specified morphological operation |
542 | | * on pixs for each Sel in the Sela, or NULL on error |
543 | | */ |
544 | | PIX * |
545 | | pixIntersectionOfMorphOps(PIX *pixs, |
546 | | SELA *sela, |
547 | | l_int32 type) |
548 | 0 | { |
549 | 0 | l_int32 n, i; |
550 | 0 | PIX *pixt, *pixd; |
551 | 0 | SEL *sel; |
552 | |
|
553 | 0 | if (!pixs || pixGetDepth(pixs) != 1) |
554 | 0 | return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL); |
555 | 0 | if (!sela) |
556 | 0 | return (PIX *)ERROR_PTR("sela not defined", __func__, NULL); |
557 | 0 | n = selaGetCount(sela); |
558 | 0 | if (n == 0) |
559 | 0 | return (PIX *)ERROR_PTR("no sels in sela", __func__, NULL); |
560 | 0 | if (type != L_MORPH_DILATE && type != L_MORPH_ERODE && |
561 | 0 | type != L_MORPH_OPEN && type != L_MORPH_CLOSE && |
562 | 0 | type != L_MORPH_HMT) |
563 | 0 | return (PIX *)ERROR_PTR("invalid type", __func__, NULL); |
564 | | |
565 | 0 | pixd = pixCreateTemplate(pixs); |
566 | 0 | pixSetAll(pixd); |
567 | 0 | for (i = 0; i < n; i++) { |
568 | 0 | sel = selaGetSel(sela, i); |
569 | 0 | if (type == L_MORPH_DILATE) |
570 | 0 | pixt = pixDilate(NULL, pixs, sel); |
571 | 0 | else if (type == L_MORPH_ERODE) |
572 | 0 | pixt = pixErode(NULL, pixs, sel); |
573 | 0 | else if (type == L_MORPH_OPEN) |
574 | 0 | pixt = pixOpen(NULL, pixs, sel); |
575 | 0 | else if (type == L_MORPH_CLOSE) |
576 | 0 | pixt = pixClose(NULL, pixs, sel); |
577 | 0 | else /* type == L_MORPH_HMT */ |
578 | 0 | pixt = pixHMT(NULL, pixs, sel); |
579 | 0 | pixAnd(pixd, pixd, pixt); |
580 | 0 | pixDestroy(&pixt); |
581 | 0 | } |
582 | |
|
583 | 0 | return pixd; |
584 | 0 | } |
585 | | |
586 | | |
587 | | |
588 | | /*-----------------------------------------------------------------* |
589 | | * Selective connected component filling * |
590 | | *-----------------------------------------------------------------*/ |
591 | | /*! |
592 | | * \brief pixSelectiveConnCompFill() |
593 | | * |
594 | | * \param[in] pixs 1 bpp |
595 | | * \param[in] connectivity 4 or 8 |
596 | | * \param[in] minw min width to consider; use 0 or 1 for any width |
597 | | * \param[in] minh min height to consider; use 0 or 1 for any height |
598 | | * \return pix with holes filled in selected c.c., or NULL on error |
599 | | */ |
600 | | PIX * |
601 | | pixSelectiveConnCompFill(PIX *pixs, |
602 | | l_int32 connectivity, |
603 | | l_int32 minw, |
604 | | l_int32 minh) |
605 | 0 | { |
606 | 0 | l_int32 n, i, x, y, w, h; |
607 | 0 | BOXA *boxa; |
608 | 0 | PIX *pix1, *pix2, *pixd; |
609 | 0 | PIXA *pixa; |
610 | |
|
611 | 0 | if (!pixs) |
612 | 0 | return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); |
613 | 0 | if (pixGetDepth(pixs) != 1) |
614 | 0 | return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, NULL); |
615 | 0 | if (minw <= 0) minw = 1; |
616 | 0 | if (minh <= 0) minh = 1; |
617 | |
|
618 | 0 | if ((boxa = pixConnComp(pixs, &pixa, connectivity)) == NULL) |
619 | 0 | return (PIX *)ERROR_PTR("boxa not made", __func__, NULL); |
620 | 0 | n = boxaGetCount(boxa); |
621 | 0 | pixd = pixCopy(NULL, pixs); |
622 | 0 | for (i = 0; i < n; i++) { |
623 | 0 | boxaGetBoxGeometry(boxa, i, &x, &y, &w, &h); |
624 | 0 | if (w >= minw && h >= minh) { |
625 | 0 | pix1 = pixaGetPix(pixa, i, L_CLONE); |
626 | 0 | if ((pix2 = pixHolesByFilling(pix1, 12 - connectivity)) == NULL) { |
627 | 0 | L_ERROR("pix2 not made in iter %d\n", __func__, i); |
628 | 0 | pixDestroy(&pix1); |
629 | 0 | continue; |
630 | 0 | } |
631 | 0 | pixRasterop(pixd, x, y, w, h, PIX_PAINT, pix2, 0, 0); |
632 | 0 | pixDestroy(&pix1); |
633 | 0 | pixDestroy(&pix2); |
634 | 0 | } |
635 | 0 | } |
636 | 0 | pixaDestroy(&pixa); |
637 | 0 | boxaDestroy(&boxa); |
638 | |
|
639 | 0 | return pixd; |
640 | 0 | } |
641 | | |
642 | | |
643 | | /*-----------------------------------------------------------------* |
644 | | * Removal of matched patterns * |
645 | | *-----------------------------------------------------------------*/ |
646 | | /*! |
647 | | * \brief pixRemoveMatchedPattern() |
648 | | * |
649 | | * \param[in] pixs input image, 1 bpp |
650 | | * \param[in] pixp pattern to be removed from image, 1 bpp |
651 | | * \param[in] pixe image after erosion by Sel that approximates pixp |
652 | | * \param[in] x0, y0 center of Sel |
653 | | * \param[in] dsize number of pixels on each side by which pixp is |
654 | | * dilated before being subtracted from pixs; |
655 | | * valid values are {0, 1, 2, 3, 4} |
656 | | * \return 0 if OK, 1 on error |
657 | | * |
658 | | * <pre> |
659 | | * Notes: |
660 | | * (1) This is in-place. |
661 | | * (2) You can use various functions in selgen to create a Sel |
662 | | * that is used to generate pixe from pixs. |
663 | | * (3) This function is applied after pixe has been computed. |
664 | | * It finds the centroid of each c.c., and subtracts |
665 | | * (the appropriately dilated version of) pixp, with the center |
666 | | * of the Sel used to align pixp with pixs. |
667 | | * </pre> |
668 | | */ |
669 | | l_ok |
670 | | pixRemoveMatchedPattern(PIX *pixs, |
671 | | PIX *pixp, |
672 | | PIX *pixe, |
673 | | l_int32 x0, |
674 | | l_int32 y0, |
675 | | l_int32 dsize) |
676 | 0 | { |
677 | 0 | l_int32 i, nc, x, y, w, h, xb, yb; |
678 | 0 | BOXA *boxa; |
679 | 0 | PIX *pix1, *pix2; |
680 | 0 | PIXA *pixa; |
681 | 0 | PTA *pta; |
682 | 0 | SEL *sel; |
683 | |
|
684 | 0 | if (!pixs) |
685 | 0 | return ERROR_INT("pixs not defined", __func__, 1); |
686 | 0 | if (!pixp) |
687 | 0 | return ERROR_INT("pixp not defined", __func__, 1); |
688 | 0 | if (!pixe) |
689 | 0 | return ERROR_INT("pixe not defined", __func__, 1); |
690 | 0 | if (pixGetDepth(pixs) != 1 || pixGetDepth(pixp) != 1 || |
691 | 0 | pixGetDepth(pixe) != 1) |
692 | 0 | return ERROR_INT("all input pix not 1 bpp", __func__, 1); |
693 | 0 | if (dsize < 0 || dsize > 4) |
694 | 0 | return ERROR_INT("dsize not in {0,1,2,3,4}", __func__, 1); |
695 | | |
696 | | /* Find the connected components and their centroids */ |
697 | 0 | boxa = pixConnComp(pixe, &pixa, 8); |
698 | 0 | if ((nc = boxaGetCount(boxa)) == 0) { |
699 | 0 | L_WARNING("no matched patterns\n", __func__); |
700 | 0 | boxaDestroy(&boxa); |
701 | 0 | pixaDestroy(&pixa); |
702 | 0 | return 0; |
703 | 0 | } |
704 | 0 | pta = pixaCentroids(pixa); |
705 | 0 | pixaDestroy(&pixa); |
706 | | |
707 | | /* Optionally dilate the pattern, first adding a border that |
708 | | * is large enough to accommodate the dilated pixels */ |
709 | 0 | sel = NULL; |
710 | 0 | if (dsize > 0) { |
711 | 0 | sel = selCreateBrick(2 * dsize + 1, 2 * dsize + 1, dsize, dsize, |
712 | 0 | SEL_HIT); |
713 | 0 | pix1 = pixAddBorder(pixp, dsize, 0); |
714 | 0 | pix2 = pixDilate(NULL, pix1, sel); |
715 | 0 | selDestroy(&sel); |
716 | 0 | pixDestroy(&pix1); |
717 | 0 | } else { |
718 | 0 | pix2 = pixClone(pixp); |
719 | 0 | } |
720 | | |
721 | | /* Subtract out each dilated pattern. The centroid of each |
722 | | * component is located at: |
723 | | * (box->x + x, box->y + y) |
724 | | * and the 'center' of the pattern used in making pixe is located at |
725 | | * (x0 + dsize, (y0 + dsize) |
726 | | * relative to the UL corner of the pattern. The center of the |
727 | | * pattern is placed at the center of the component. */ |
728 | 0 | pixGetDimensions(pix2, &w, &h, NULL); |
729 | 0 | for (i = 0; i < nc; i++) { |
730 | 0 | ptaGetIPt(pta, i, &x, &y); |
731 | 0 | boxaGetBoxGeometry(boxa, i, &xb, &yb, NULL, NULL); |
732 | 0 | pixRasterop(pixs, xb + x - x0 - dsize, yb + y - y0 - dsize, |
733 | 0 | w, h, PIX_DST & PIX_NOT(PIX_SRC), pix2, 0, 0); |
734 | 0 | } |
735 | |
|
736 | 0 | boxaDestroy(&boxa); |
737 | 0 | ptaDestroy(&pta); |
738 | 0 | pixDestroy(&pix2); |
739 | 0 | return 0; |
740 | 0 | } |
741 | | |
742 | | |
743 | | /*-----------------------------------------------------------------* |
744 | | * Display of matched patterns * |
745 | | *-----------------------------------------------------------------*/ |
746 | | /*! |
747 | | * \brief pixDisplayMatchedPattern() |
748 | | * |
749 | | * \param[in] pixs input image, 1 bpp |
750 | | * \param[in] pixp pattern to be removed from image, 1 bpp |
751 | | * \param[in] pixe image after erosion by Sel that approximates pixp |
752 | | * \param[in] x0, y0 center of Sel |
753 | | * \param[in] color to paint the matched patterns; 0xrrggbb00 |
754 | | * \param[in] scale reduction factor for output pixd |
755 | | * \param[in] nlevels if scale < 1.0, threshold to this number of levels |
756 | | * \return pixd 8 bpp, colormapped, or NULL on error |
757 | | * |
758 | | * <pre> |
759 | | * Notes: |
760 | | * (1) A 4 bpp colormapped image is generated. |
761 | | * (2) If scale <= 1.0, do scale to gray for the output, and threshold |
762 | | * to nlevels of gray. |
763 | | * (3) You can use various functions in selgen to create a Sel |
764 | | * that will generate pixe from pixs. |
765 | | * (4) This function is applied after pixe has been computed. |
766 | | * It finds the centroid of each c.c., and colors the output |
767 | | * pixels using pixp (appropriately aligned) as a stencil. |
768 | | * Alignment is done using the origin of the Sel and the |
769 | | * centroid of the eroded image to place the stencil pixp. |
770 | | * </pre> |
771 | | */ |
772 | | PIX * |
773 | | pixDisplayMatchedPattern(PIX *pixs, |
774 | | PIX *pixp, |
775 | | PIX *pixe, |
776 | | l_int32 x0, |
777 | | l_int32 y0, |
778 | | l_uint32 color, |
779 | | l_float32 scale, |
780 | | l_int32 nlevels) |
781 | 0 | { |
782 | 0 | l_int32 i, nc, xb, yb, x, y, xi, yi, rval, gval, bval; |
783 | 0 | BOXA *boxa; |
784 | 0 | PIX *pixd, *pixt, *pixps; |
785 | 0 | PIXA *pixa; |
786 | 0 | PTA *pta; |
787 | 0 | PIXCMAP *cmap; |
788 | |
|
789 | 0 | if (!pixs) |
790 | 0 | return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); |
791 | 0 | if (!pixp) |
792 | 0 | return (PIX *)ERROR_PTR("pixp not defined", __func__, NULL); |
793 | 0 | if (!pixe) |
794 | 0 | return (PIX *)ERROR_PTR("pixe not defined", __func__, NULL); |
795 | 0 | if (pixGetDepth(pixs) != 1 || pixGetDepth(pixp) != 1 || |
796 | 0 | pixGetDepth(pixe) != 1) |
797 | 0 | return (PIX *)ERROR_PTR("all input pix not 1 bpp", __func__, NULL); |
798 | 0 | if (scale > 1.0 || scale <= 0.0) { |
799 | 0 | L_WARNING("scale > 1.0 or < 0.0; setting to 1.0\n", __func__); |
800 | 0 | scale = 1.0; |
801 | 0 | } |
802 | | |
803 | | /* Find the connected components and their centroids */ |
804 | 0 | boxa = pixConnComp(pixe, &pixa, 8); |
805 | 0 | if ((nc = boxaGetCount(boxa)) == 0) { |
806 | 0 | L_WARNING("no matched patterns\n", __func__); |
807 | 0 | boxaDestroy(&boxa); |
808 | 0 | pixaDestroy(&pixa); |
809 | 0 | return 0; |
810 | 0 | } |
811 | 0 | pta = pixaCentroids(pixa); |
812 | |
|
813 | 0 | extractRGBValues(color, &rval, &gval, &bval); |
814 | 0 | if (scale == 1.0) { /* output 4 bpp at full resolution */ |
815 | 0 | pixd = pixConvert1To4(NULL, pixs, 0, 1); |
816 | 0 | cmap = pixcmapCreate(4); |
817 | 0 | pixcmapAddColor(cmap, 255, 255, 255); |
818 | 0 | pixcmapAddColor(cmap, 0, 0, 0); |
819 | 0 | pixSetColormap(pixd, cmap); |
820 | | |
821 | | /* Paint through pixp for each match location. The centroid of each |
822 | | * component in pixe is located at: |
823 | | * (box->x + x, box->y + y) |
824 | | * and the 'center' of the pattern used in making pixe is located at |
825 | | * (x0, y0) |
826 | | * relative to the UL corner of the pattern. The center of the |
827 | | * pattern is placed at the center of the component. */ |
828 | 0 | for (i = 0; i < nc; i++) { |
829 | 0 | ptaGetIPt(pta, i, &x, &y); |
830 | 0 | boxaGetBoxGeometry(boxa, i, &xb, &yb, NULL, NULL); |
831 | 0 | pixSetMaskedCmap(pixd, pixp, xb + x - x0, yb + y - y0, |
832 | 0 | rval, gval, bval); |
833 | 0 | } |
834 | 0 | } else { /* output 4 bpp downscaled */ |
835 | 0 | pixt = pixScaleToGray(pixs, scale); |
836 | 0 | pixd = pixThresholdTo4bpp(pixt, nlevels, 1); |
837 | 0 | pixps = pixScaleBySampling(pixp, scale, scale); |
838 | |
|
839 | 0 | for (i = 0; i < nc; i++) { |
840 | 0 | ptaGetIPt(pta, i, &x, &y); |
841 | 0 | boxaGetBoxGeometry(boxa, i, &xb, &yb, NULL, NULL); |
842 | 0 | xi = (l_int32)(scale * (xb + x - x0)); |
843 | 0 | yi = (l_int32)(scale * (yb + y - y0)); |
844 | 0 | pixSetMaskedCmap(pixd, pixps, xi, yi, rval, gval, bval); |
845 | 0 | } |
846 | 0 | pixDestroy(&pixt); |
847 | 0 | pixDestroy(&pixps); |
848 | 0 | } |
849 | |
|
850 | 0 | boxaDestroy(&boxa); |
851 | 0 | pixaDestroy(&pixa); |
852 | 0 | ptaDestroy(&pta); |
853 | 0 | return pixd; |
854 | 0 | } |
855 | | |
856 | | |
857 | | /*------------------------------------------------------------------------* |
858 | | * Extension of pixa by iterative erosion or dilation (and by scaling) * |
859 | | *------------------------------------------------------------------------*/ |
860 | | /*! |
861 | | * \brief pixaExtendByMorph() |
862 | | * |
863 | | * \param[in] pixas |
864 | | * \param[in] type L_MORPH_DILATE, L_MORPH_ERODE |
865 | | * \param[in] niters |
866 | | * \param[in] sel used for dilation, erosion; uses 2x2 if null |
867 | | * \param[in] include 1 to include a copy of the input pixas in pixad; |
868 | | * 0 to omit |
869 | | * \return pixad with derived pix, using all iterations, or NULL on error |
870 | | * |
871 | | * <pre> |
872 | | * Notes: |
873 | | * (1) This dilates or erodes every pix in %pixas, iteratively, |
874 | | * using the input Sel (or, if null, a 2x2 Sel by default), |
875 | | * and puts the results in %pixad. |
876 | | * (2) If %niters <= 0, this is a no-op; it returns a clone of pixas. |
877 | | * (3) If %include == 1, the output %pixad contains all the pix |
878 | | * in %pixas. Otherwise, it doesn't, but pixaJoin() can be |
879 | | * used later to join pixas with pixad. |
880 | | * </pre> |
881 | | */ |
882 | | PIXA * |
883 | | pixaExtendByMorph(PIXA *pixas, |
884 | | l_int32 type, |
885 | | l_int32 niters, |
886 | | SEL *sel, |
887 | | l_int32 include) |
888 | 0 | { |
889 | 0 | l_int32 maxdepth, i, j, n; |
890 | 0 | PIX *pix0, *pix1, *pix2; |
891 | 0 | SEL *selt; |
892 | 0 | PIXA *pixad; |
893 | |
|
894 | 0 | if (!pixas) |
895 | 0 | return (PIXA *)ERROR_PTR("pixas undefined", __func__, NULL); |
896 | 0 | if (niters <= 0) { |
897 | 0 | L_INFO("niters = %d; nothing to do\n", __func__, niters); |
898 | 0 | return pixaCopy(pixas, L_CLONE); |
899 | 0 | } |
900 | 0 | if (type != L_MORPH_DILATE && type != L_MORPH_ERODE) |
901 | 0 | return (PIXA *)ERROR_PTR("invalid type", __func__, NULL); |
902 | 0 | pixaGetDepthInfo(pixas, &maxdepth, NULL); |
903 | 0 | if (maxdepth > 1) |
904 | 0 | return (PIXA *)ERROR_PTR("some pix have bpp > 1", __func__, NULL); |
905 | | |
906 | 0 | if (!sel) |
907 | 0 | selt = selCreateBrick(2, 2, 0, 0, SEL_HIT); /* default */ |
908 | 0 | else |
909 | 0 | selt = selCopy(sel); |
910 | 0 | n = pixaGetCount(pixas); |
911 | 0 | pixad = pixaCreate(n * niters); |
912 | 0 | for (i = 0; i < n; i++) { |
913 | 0 | pix1 = pixaGetPix(pixas, i, L_CLONE); |
914 | 0 | if (include) pixaAddPix(pixad, pix1, L_COPY); |
915 | 0 | pix0 = pix1; /* need to keep the handle to destroy the clone */ |
916 | 0 | for (j = 0; j < niters; j++) { |
917 | 0 | if (type == L_MORPH_DILATE) { |
918 | 0 | pix2 = pixDilate(NULL, pix1, selt); |
919 | 0 | } else { /* L_MORPH_ERODE */ |
920 | 0 | pix2 = pixErode(NULL, pix1, selt); |
921 | 0 | } |
922 | 0 | pixaAddPix(pixad, pix2, L_INSERT); |
923 | 0 | pix1 = pix2; /* owned by pixad; do not destroy */ |
924 | 0 | } |
925 | 0 | pixDestroy(&pix0); |
926 | 0 | } |
927 | |
|
928 | 0 | selDestroy(&selt); |
929 | 0 | return pixad; |
930 | 0 | } |
931 | | |
932 | | |
933 | | /*! |
934 | | * \brief pixaExtendByScaling() |
935 | | * |
936 | | * \param[in] pixas |
937 | | * \param[in] nasc numa of scaling factors |
938 | | * \param[in] type L_HORIZ, L_VERT, L_BOTH_DIRECTIONS |
939 | | * \param[in] include 1 to include a copy of the input pixas in pixad; |
940 | | * 0 to omit |
941 | | * \return pixad with derived pix, using all scalings, or NULL on error |
942 | | * |
943 | | * <pre> |
944 | | * Notes: |
945 | | * (1) This scales every pix in %pixas by each factor in %nasc. |
946 | | * and puts the results in %pixad. |
947 | | * (2) If %include == 1, the output %pixad contains all the pix |
948 | | * in %pixas. Otherwise, it doesn't, but pixaJoin() can be |
949 | | * used later to join pixas with pixad. |
950 | | * </pre> |
951 | | */ |
952 | | PIXA * |
953 | | pixaExtendByScaling(PIXA *pixas, |
954 | | NUMA *nasc, |
955 | | l_int32 type, |
956 | | l_int32 include) |
957 | 0 | { |
958 | 0 | l_int32 i, j, n, nsc, w, h, scalew, scaleh; |
959 | 0 | l_float32 scalefact; |
960 | 0 | PIX *pix1, *pix2; |
961 | 0 | PIXA *pixad; |
962 | |
|
963 | 0 | if (!pixas) |
964 | 0 | return (PIXA *)ERROR_PTR("pixas undefined", __func__, NULL); |
965 | 0 | if (!nasc || numaGetCount(nasc) == 0) |
966 | 0 | return (PIXA *)ERROR_PTR("nasc undefined or empty", __func__, NULL); |
967 | 0 | if (type != L_HORIZ && type != L_VERT && type != L_BOTH_DIRECTIONS) |
968 | 0 | return (PIXA *)ERROR_PTR("invalid type", __func__, NULL); |
969 | | |
970 | 0 | n = pixaGetCount(pixas); |
971 | 0 | nsc = numaGetCount(nasc); |
972 | 0 | if ((pixad = pixaCreate(n * (nsc + 1))) == NULL) { |
973 | 0 | L_ERROR("pixad not made: n = %d, nsc = %d\n", __func__, n, nsc); |
974 | 0 | return NULL; |
975 | 0 | } |
976 | 0 | for (i = 0; i < n; i++) { |
977 | 0 | pix1 = pixaGetPix(pixas, i, L_CLONE); |
978 | 0 | if (include) pixaAddPix(pixad, pix1, L_COPY); |
979 | 0 | pixGetDimensions(pix1, &w, &h, NULL); |
980 | 0 | for (j = 0; j < nsc; j++) { |
981 | 0 | numaGetFValue(nasc, j, &scalefact); |
982 | 0 | scalew = w; |
983 | 0 | scaleh = h; |
984 | 0 | if (type == L_HORIZ || type == L_BOTH_DIRECTIONS) |
985 | 0 | scalew = w * scalefact; |
986 | 0 | if (type == L_VERT || type == L_BOTH_DIRECTIONS) |
987 | 0 | scaleh = h * scalefact; |
988 | 0 | pix2 = pixScaleToSize(pix1, scalew, scaleh); |
989 | 0 | pixaAddPix(pixad, pix2, L_INSERT); |
990 | 0 | } |
991 | 0 | pixDestroy(&pix1); |
992 | 0 | } |
993 | 0 | return pixad; |
994 | 0 | } |
995 | | |
996 | | |
997 | | /*-----------------------------------------------------------------* |
998 | | * Iterative morphological seed filling * |
999 | | *-----------------------------------------------------------------*/ |
1000 | | /*! |
1001 | | * \brief pixSeedfillMorph() |
1002 | | * |
1003 | | * \param[in] pixs seed |
1004 | | * \param[in] pixm mask |
1005 | | * \param[in] maxiters use 0 to go to completion |
1006 | | * \param[in] connectivity 4 or 8 |
1007 | | * \return pixd after filling into the mask or NULL on error |
1008 | | * |
1009 | | * <pre> |
1010 | | * Notes: |
1011 | | * (1) This is in general a very inefficient method for filling |
1012 | | * from a seed into a mask. Use it for a small number of iterations, |
1013 | | * but if you expect more than a few iterations, use |
1014 | | * pixSeedfillBinary(). |
1015 | | * (2) We use a 3x3 brick SEL for 8-cc filling and a 3x3 plus SEL for 4-cc. |
1016 | | * </pre> |
1017 | | */ |
1018 | | PIX * |
1019 | | pixSeedfillMorph(PIX *pixs, |
1020 | | PIX *pixm, |
1021 | | l_int32 maxiters, |
1022 | | l_int32 connectivity) |
1023 | 0 | { |
1024 | 0 | l_int32 same, i; |
1025 | 0 | PIX *pixt, *pixd, *temp; |
1026 | 0 | SEL *sel_3; |
1027 | |
|
1028 | 0 | if (!pixs || pixGetDepth(pixs) != 1) |
1029 | 0 | return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL); |
1030 | 0 | if (!pixm) |
1031 | 0 | return (PIX *)ERROR_PTR("mask pix not defined", __func__, NULL); |
1032 | 0 | if (connectivity != 4 && connectivity != 8) |
1033 | 0 | return (PIX *)ERROR_PTR("connectivity not in {4,8}", __func__, NULL); |
1034 | 0 | if (maxiters <= 0) maxiters = 1000; |
1035 | 0 | if (pixSizesEqual(pixs, pixm) == 0) |
1036 | 0 | return (PIX *)ERROR_PTR("pix sizes unequal", __func__, NULL); |
1037 | | |
1038 | 0 | if ((sel_3 = selCreateBrick(3, 3, 1, 1, SEL_HIT)) == NULL) |
1039 | 0 | return (PIX *)ERROR_PTR("sel_3 not made", __func__, NULL); |
1040 | 0 | if (connectivity == 4) { /* remove corner hits to make a '+' */ |
1041 | 0 | selSetElement(sel_3, 0, 0, SEL_DONT_CARE); |
1042 | 0 | selSetElement(sel_3, 2, 2, SEL_DONT_CARE); |
1043 | 0 | selSetElement(sel_3, 2, 0, SEL_DONT_CARE); |
1044 | 0 | selSetElement(sel_3, 0, 2, SEL_DONT_CARE); |
1045 | 0 | } |
1046 | |
|
1047 | 0 | pixt = pixCopy(NULL, pixs); |
1048 | 0 | pixd = pixCreateTemplate(pixs); |
1049 | 0 | for (i = 1; i <= maxiters; i++) { |
1050 | 0 | pixDilate(pixd, pixt, sel_3); |
1051 | 0 | pixAnd(pixd, pixd, pixm); |
1052 | 0 | pixEqual(pixd, pixt, &same); |
1053 | 0 | if (same || i == maxiters) |
1054 | 0 | break; |
1055 | 0 | else |
1056 | 0 | SWAP(pixt, pixd); |
1057 | 0 | } |
1058 | 0 | lept_stderr(" Num iters in binary reconstruction = %d\n", i); |
1059 | |
|
1060 | 0 | pixDestroy(&pixt); |
1061 | 0 | selDestroy(&sel_3); |
1062 | 0 | return pixd; |
1063 | 0 | } |
1064 | | |
1065 | | |
1066 | | /*-----------------------------------------------------------------* |
1067 | | * Granulometry on binary images * |
1068 | | *-----------------------------------------------------------------*/ |
1069 | | /*! |
1070 | | * \brief pixRunHistogramMorph() |
1071 | | * |
1072 | | * \param[in] pixs 1 bpp |
1073 | | * \param[in] runtype L_RUN_OFF, L_RUN_ON |
1074 | | * \param[in] direction L_HORIZ, L_VERT |
1075 | | * \param[in] maxsize size of largest runlength counted |
1076 | | * \return numa of run-lengths |
1077 | | */ |
1078 | | NUMA * |
1079 | | pixRunHistogramMorph(PIX *pixs, |
1080 | | l_int32 runtype, |
1081 | | l_int32 direction, |
1082 | | l_int32 maxsize) |
1083 | 0 | { |
1084 | 0 | l_int32 count, i, size; |
1085 | 0 | l_float32 val; |
1086 | 0 | NUMA *na, *nah; |
1087 | 0 | PIX *pix1, *pix2, *pix3; |
1088 | 0 | SEL *sel_2a; |
1089 | |
|
1090 | 0 | if (!pixs) |
1091 | 0 | return (NUMA *)ERROR_PTR("seed pix not defined", __func__, NULL); |
1092 | 0 | if (runtype != L_RUN_OFF && runtype != L_RUN_ON) |
1093 | 0 | return (NUMA *)ERROR_PTR("invalid run type", __func__, NULL); |
1094 | 0 | if (direction != L_HORIZ && direction != L_VERT) |
1095 | 0 | return (NUMA *)ERROR_PTR("direction not in {L_HORIZ, L_VERT}", |
1096 | 0 | __func__, NULL); |
1097 | 0 | if (pixGetDepth(pixs) != 1) |
1098 | 0 | return (NUMA *)ERROR_PTR("pixs must be binary", __func__, NULL); |
1099 | | |
1100 | 0 | if (direction == L_HORIZ) |
1101 | 0 | sel_2a = selCreateBrick(1, 2, 0, 0, SEL_HIT); |
1102 | 0 | else /* direction == L_VERT */ |
1103 | 0 | sel_2a = selCreateBrick(2, 1, 0, 0, SEL_HIT); |
1104 | 0 | if (!sel_2a) |
1105 | 0 | return (NUMA *)ERROR_PTR("sel_2a not made", __func__, NULL); |
1106 | | |
1107 | 0 | if (runtype == L_RUN_OFF) { |
1108 | 0 | if ((pix1 = pixCopy(NULL, pixs)) == NULL) { |
1109 | 0 | selDestroy(&sel_2a); |
1110 | 0 | return (NUMA *)ERROR_PTR("pix1 not made", __func__, NULL); |
1111 | 0 | } |
1112 | 0 | pixInvert(pix1, pix1); |
1113 | 0 | } else { /* runtype == L_RUN_ON */ |
1114 | 0 | pix1 = pixClone(pixs); |
1115 | 0 | } |
1116 | | |
1117 | | /* Get pixel counts at different stages of erosion */ |
1118 | 0 | na = numaCreate(0); |
1119 | 0 | pix2 = pixCreateTemplate(pixs); |
1120 | 0 | pix3 = pixCreateTemplate(pixs); |
1121 | 0 | pixCountPixels(pix1, &count, NULL); |
1122 | 0 | numaAddNumber(na, count); |
1123 | 0 | pixErode(pix2, pix1, sel_2a); |
1124 | 0 | pixCountPixels(pix2, &count, NULL); |
1125 | 0 | numaAddNumber(na, count); |
1126 | 0 | for (i = 0; i < maxsize / 2; i++) { |
1127 | 0 | pixErode(pix3, pix2, sel_2a); |
1128 | 0 | pixCountPixels(pix3, &count, NULL); |
1129 | 0 | numaAddNumber(na, count); |
1130 | 0 | pixErode(pix2, pix3, sel_2a); |
1131 | 0 | pixCountPixels(pix2, &count, NULL); |
1132 | 0 | numaAddNumber(na, count); |
1133 | 0 | } |
1134 | | |
1135 | | /* Compute length histogram */ |
1136 | 0 | size = numaGetCount(na); |
1137 | 0 | nah = numaCreate(size); |
1138 | 0 | numaAddNumber(nah, 0); /* number at length 0 */ |
1139 | 0 | for (i = 1; i < size - 1; i++) { |
1140 | 0 | val = na->array[i+1] - 2 * na->array[i] + na->array[i-1]; |
1141 | 0 | numaAddNumber(nah, val); |
1142 | 0 | } |
1143 | |
|
1144 | 0 | pixDestroy(&pix1); |
1145 | 0 | pixDestroy(&pix2); |
1146 | 0 | pixDestroy(&pix3); |
1147 | 0 | selDestroy(&sel_2a); |
1148 | 0 | numaDestroy(&na); |
1149 | 0 | return nah; |
1150 | 0 | } |
1151 | | |
1152 | | |
1153 | | /*-----------------------------------------------------------------* |
1154 | | * Composite operations on grayscale images * |
1155 | | *-----------------------------------------------------------------*/ |
1156 | | /*! |
1157 | | * \brief pixTophat() |
1158 | | * |
1159 | | * \param[in] pixs 1 bpp |
1160 | | * \param[in] hsize of Sel; must be odd; origin implicitly in center |
1161 | | * \param[in] vsize ditto |
1162 | | * \param[in] type L_TOPHAT_WHITE: image - opening |
1163 | | * L_TOPHAT_BLACK: closing - image |
1164 | | * \return pixd, or NULL on error |
1165 | | * |
1166 | | * <pre> |
1167 | | * Notes: |
1168 | | * (1) Sel is a brick with all elements being hits |
1169 | | * (2) If hsize = vsize = 1, returns an image with all 0 data. |
1170 | | * (3) The L_TOPHAT_WHITE flag emphasizes small bright regions, |
1171 | | * whereas the L_TOPHAT_BLACK flag emphasizes small dark regions. |
1172 | | * The L_TOPHAT_WHITE tophat can be accomplished by doing a |
1173 | | * L_TOPHAT_BLACK tophat on the inverse, or v.v. |
1174 | | * </pre> |
1175 | | */ |
1176 | | PIX * |
1177 | | pixTophat(PIX *pixs, |
1178 | | l_int32 hsize, |
1179 | | l_int32 vsize, |
1180 | | l_int32 type) |
1181 | 0 | { |
1182 | 0 | PIX *pixt, *pixd; |
1183 | |
|
1184 | 0 | if (!pixs) |
1185 | 0 | return (PIX *)ERROR_PTR("seed pix not defined", __func__, NULL); |
1186 | 0 | if (pixGetDepth(pixs) != 8) |
1187 | 0 | return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL); |
1188 | 0 | if (hsize < 1 || vsize < 1) |
1189 | 0 | return (PIX *)ERROR_PTR("hsize or vsize < 1", __func__, NULL); |
1190 | 0 | if ((hsize & 1) == 0 ) { |
1191 | 0 | L_WARNING("horiz sel size must be odd; increasing by 1\n", __func__); |
1192 | 0 | hsize++; |
1193 | 0 | } |
1194 | 0 | if ((vsize & 1) == 0 ) { |
1195 | 0 | L_WARNING("vert sel size must be odd; increasing by 1\n", __func__); |
1196 | 0 | vsize++; |
1197 | 0 | } |
1198 | 0 | if (type != L_TOPHAT_WHITE && type != L_TOPHAT_BLACK) |
1199 | 0 | return (PIX *)ERROR_PTR("type must be L_TOPHAT_BLACK or L_TOPHAT_WHITE", |
1200 | 0 | __func__, NULL); |
1201 | | |
1202 | 0 | if (hsize == 1 && vsize == 1) |
1203 | 0 | return pixCreateTemplate(pixs); |
1204 | | |
1205 | 0 | switch (type) |
1206 | 0 | { |
1207 | 0 | case L_TOPHAT_WHITE: |
1208 | 0 | if ((pixt = pixOpenGray(pixs, hsize, vsize)) == NULL) |
1209 | 0 | return (PIX *)ERROR_PTR("pixt not made", __func__, NULL); |
1210 | 0 | pixd = pixSubtractGray(NULL, pixs, pixt); |
1211 | 0 | pixDestroy(&pixt); |
1212 | 0 | break; |
1213 | 0 | case L_TOPHAT_BLACK: |
1214 | 0 | if ((pixd = pixCloseGray(pixs, hsize, vsize)) == NULL) |
1215 | 0 | return (PIX *)ERROR_PTR("pixd not made", __func__, NULL); |
1216 | 0 | pixSubtractGray(pixd, pixd, pixs); |
1217 | 0 | break; |
1218 | 0 | default: |
1219 | 0 | return (PIX *)ERROR_PTR("invalid type", __func__, NULL); |
1220 | 0 | } |
1221 | | |
1222 | 0 | return pixd; |
1223 | 0 | } |
1224 | | |
1225 | | |
1226 | | /*! |
1227 | | * \brief pixHDome() |
1228 | | * |
1229 | | * \param[in] pixs 8 bpp, filling mask |
1230 | | * \param[in] height of seed below the filling maskhdome; must be >= 0 |
1231 | | * \param[in] connectivity 4 or 8 |
1232 | | * \return pixd 8 bpp, or NULL on error |
1233 | | * |
1234 | | * <pre> |
1235 | | * Notes: |
1236 | | * (1) It is more efficient to use a connectivity of 4 for the fill. |
1237 | | * (2) This fills bumps to some level, and extracts the unfilled |
1238 | | * part of the bump. To extract the troughs of basins, first |
1239 | | * invert pixs and then apply pixHDome(). |
1240 | | * (3) It is useful to compare the HDome operation with the TopHat. |
1241 | | * The latter extracts peaks or valleys that have a width |
1242 | | * not exceeding the size of the structuring element used |
1243 | | * in the opening or closing, rsp. The height of the peak is |
1244 | | * irrelevant. By contrast, for the HDome, the gray seedfill |
1245 | | * is used to extract all peaks that have a height not exceeding |
1246 | | * a given value, regardless of their width! |
1247 | | * (4) Slightly more precisely, suppose you set 'height' = 40. |
1248 | | * Then all bumps in pixs with a height greater than or equal |
1249 | | * to 40 become, in pixd, bumps with a max value of exactly 40. |
1250 | | * All shorter bumps have a max value in pixd equal to the height |
1251 | | * of the bump. |
1252 | | * (5) The method: the filling mask, pixs, is the image whose peaks |
1253 | | * are to be extracted. The height of a peak is the distance |
1254 | | * between the top of the peak and the highest "leak" to the |
1255 | | * outside -- think of a sombrero, where the leak occurs |
1256 | | * at the highest point on the rim. |
1257 | | * (a) Generate a seed, pixd, by subtracting some value, p, from |
1258 | | * each pixel in the filling mask, pixs. The value p is |
1259 | | * the 'height' input to this function. |
1260 | | * (b) Fill in pixd starting with this seed, clipping by pixs, |
1261 | | * in the way described in seedfillGrayLow(). The filling |
1262 | | * stops before the peaks in pixs are filled. |
1263 | | * For peaks that have a height > p, pixd is filled to |
1264 | | * the level equal to the (top-of-the-peak - p). |
1265 | | * For peaks of height < p, the peak is left unfilled |
1266 | | * from its highest saddle point (the leak to the outside). |
1267 | | * (c) Subtract the filled seed (pixd) from the filling mask (pixs). |
1268 | | * Note that in this procedure, everything is done starting |
1269 | | * with the filling mask, pixs. |
1270 | | * (6) For segmentation, the resulting image, pixd, can be thresholded |
1271 | | * and used as a seed for another filling operation. |
1272 | | * </pre> |
1273 | | */ |
1274 | | PIX * |
1275 | | pixHDome(PIX *pixs, |
1276 | | l_int32 height, |
1277 | | l_int32 connectivity) |
1278 | 0 | { |
1279 | 0 | PIX *pixsd, *pixd; |
1280 | |
|
1281 | 0 | if (!pixs) |
1282 | 0 | return (PIX *)ERROR_PTR("src pix not defined", __func__, NULL); |
1283 | 0 | if (pixGetDepth(pixs) != 8) |
1284 | 0 | return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL); |
1285 | 0 | if (height < 0) |
1286 | 0 | return (PIX *)ERROR_PTR("height not >= 0", __func__, NULL); |
1287 | 0 | if (height == 0) |
1288 | 0 | return pixCreateTemplate(pixs); |
1289 | | |
1290 | 0 | if ((pixsd = pixCopy(NULL, pixs)) == NULL) |
1291 | 0 | return (PIX *)ERROR_PTR("pixsd not made", __func__, NULL); |
1292 | 0 | pixAddConstantGray(pixsd, -height); |
1293 | 0 | pixSeedfillGray(pixsd, pixs, connectivity); |
1294 | 0 | pixd = pixSubtractGray(NULL, pixs, pixsd); |
1295 | 0 | pixDestroy(&pixsd); |
1296 | 0 | return pixd; |
1297 | 0 | } |
1298 | | |
1299 | | |
1300 | | /*! |
1301 | | * \brief pixFastTophat() |
1302 | | * |
1303 | | * \param[in] pixs 8 bpp |
1304 | | * \param[in] xsize width of max/min op, smoothing; any integer >= 1 |
1305 | | * \param[in] ysize height of max/min op, smoothing; any integer >= 1 |
1306 | | * \param[in] type L_TOPHAT_WHITE: image - min |
1307 | | * L_TOPHAT_BLACK: max - image |
1308 | | * \return pixd, or NULL on error |
1309 | | * |
1310 | | * <pre> |
1311 | | * Notes: |
1312 | | * (1) Don't be fooled. This is NOT a tophat. It is a tophat-like |
1313 | | * operation, where the result is similar to what you'd get |
1314 | | * if you used an erosion instead of an opening, or a dilation |
1315 | | * instead of a closing. |
1316 | | * (2) Instead of opening or closing at full resolution, it does |
1317 | | * a fast downscale/minmax operation, then a quick small smoothing |
1318 | | * at low res, a replicative expansion of the "background" |
1319 | | * to full res, and finally a removal of the background level |
1320 | | * from the input image. The smoothing step may not be important. |
1321 | | * (3) It does not remove noise as well as a tophat, but it is |
1322 | | * 5 to 10 times faster. |
1323 | | * If you need the preciseness of the tophat, don't use this. |
1324 | | * (4) The L_TOPHAT_WHITE flag emphasizes small bright regions, |
1325 | | * whereas the L_TOPHAT_BLACK flag emphasizes small dark regions. |
1326 | | * </pre> |
1327 | | */ |
1328 | | PIX * |
1329 | | pixFastTophat(PIX *pixs, |
1330 | | l_int32 xsize, |
1331 | | l_int32 ysize, |
1332 | | l_int32 type) |
1333 | 0 | { |
1334 | 0 | PIX *pix1, *pix2, *pix3, *pixd; |
1335 | |
|
1336 | 0 | if (!pixs) |
1337 | 0 | return (PIX *)ERROR_PTR("seed pix not defined", __func__, NULL); |
1338 | 0 | if (pixGetDepth(pixs) != 8) |
1339 | 0 | return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL); |
1340 | 0 | if (xsize < 1 || ysize < 1) |
1341 | 0 | return (PIX *)ERROR_PTR("size < 1", __func__, NULL); |
1342 | 0 | if (type != L_TOPHAT_WHITE && type != L_TOPHAT_BLACK) |
1343 | 0 | return (PIX *)ERROR_PTR("type must be L_TOPHAT_BLACK or L_TOPHAT_WHITE", |
1344 | 0 | __func__, NULL); |
1345 | | |
1346 | 0 | if (xsize == 1 && ysize == 1) |
1347 | 0 | return pixCreateTemplate(pixs); |
1348 | | |
1349 | 0 | switch (type) |
1350 | 0 | { |
1351 | 0 | case L_TOPHAT_WHITE: |
1352 | 0 | if ((pix1 = pixScaleGrayMinMax(pixs, xsize, ysize, L_CHOOSE_MIN)) |
1353 | 0 | == NULL) |
1354 | 0 | return (PIX *)ERROR_PTR("pix1 not made", __func__, NULL); |
1355 | 0 | pix2 = pixBlockconv(pix1, 1, 1); /* small smoothing */ |
1356 | 0 | pix3 = pixScaleBySampling(pix2, xsize, ysize); |
1357 | 0 | pixd = pixSubtractGray(NULL, pixs, pix3); |
1358 | 0 | pixDestroy(&pix3); |
1359 | 0 | break; |
1360 | 0 | case L_TOPHAT_BLACK: |
1361 | 0 | if ((pix1 = pixScaleGrayMinMax(pixs, xsize, ysize, L_CHOOSE_MAX)) |
1362 | 0 | == NULL) |
1363 | 0 | return (PIX *)ERROR_PTR("pix1 not made", __func__, NULL); |
1364 | 0 | pix2 = pixBlockconv(pix1, 1, 1); /* small smoothing */ |
1365 | 0 | pixd = pixScaleBySampling(pix2, xsize, ysize); |
1366 | 0 | pixSubtractGray(pixd, pixd, pixs); |
1367 | 0 | break; |
1368 | 0 | default: |
1369 | 0 | return (PIX *)ERROR_PTR("invalid type", __func__, NULL); |
1370 | 0 | } |
1371 | | |
1372 | 0 | pixDestroy(&pix1); |
1373 | 0 | pixDestroy(&pix2); |
1374 | 0 | return pixd; |
1375 | 0 | } |
1376 | | |
1377 | | |
1378 | | /*! |
1379 | | * \brief pixMorphGradient() |
1380 | | * |
1381 | | * \param[in] pixs 8 bpp |
1382 | | * \param[in] hsize sel width; must be odd; origin implicitly in center |
1383 | | * \param[in] vsize sel height |
1384 | | * \param[in] smoothing half-width of convolution smoothing filter. |
1385 | | * The width is (2 * smoothing + 1, so 0 is no-op. |
1386 | | * \return pixd, or NULL on error |
1387 | | */ |
1388 | | PIX * |
1389 | | pixMorphGradient(PIX *pixs, |
1390 | | l_int32 hsize, |
1391 | | l_int32 vsize, |
1392 | | l_int32 smoothing) |
1393 | 0 | { |
1394 | 0 | PIX *pixg, *pixd; |
1395 | |
|
1396 | 0 | if (!pixs) |
1397 | 0 | return (PIX *)ERROR_PTR("seed pix not defined", __func__, NULL); |
1398 | 0 | if (pixGetDepth(pixs) != 8) |
1399 | 0 | return (PIX *)ERROR_PTR("pixs not 8 bpp", __func__, NULL); |
1400 | 0 | if (hsize < 1 || vsize < 1) |
1401 | 0 | return (PIX *)ERROR_PTR("hsize or vsize < 1", __func__, NULL); |
1402 | 0 | if ((hsize & 1) == 0 ) { |
1403 | 0 | L_WARNING("horiz sel size must be odd; increasing by 1\n", __func__); |
1404 | 0 | hsize++; |
1405 | 0 | } |
1406 | 0 | if ((vsize & 1) == 0 ) { |
1407 | 0 | L_WARNING("vert sel size must be odd; increasing by 1\n", __func__); |
1408 | 0 | vsize++; |
1409 | 0 | } |
1410 | | |
1411 | | /* Optionally smooth first to remove noise. |
1412 | | * If smoothing is 0, just get a copy */ |
1413 | 0 | pixg = pixBlockconvGray(pixs, NULL, smoothing, smoothing); |
1414 | | |
1415 | | /* This gives approximately the gradient of a transition */ |
1416 | 0 | pixd = pixDilateGray(pixg, hsize, vsize); |
1417 | 0 | pixSubtractGray(pixd, pixd, pixg); |
1418 | 0 | pixDestroy(&pixg); |
1419 | 0 | return pixd; |
1420 | 0 | } |
1421 | | |
1422 | | |
1423 | | /*-----------------------------------------------------------------* |
1424 | | * Centroid of component * |
1425 | | *-----------------------------------------------------------------*/ |
1426 | | /*! |
1427 | | * \brief pixaCentroids() |
1428 | | * |
1429 | | * \param[in] pixa of components; 1 or 8 bpp |
1430 | | * \return pta of centroids relative to the UL corner of |
1431 | | * each pix, or NULL on error |
1432 | | * |
1433 | | * <pre> |
1434 | | * Notes: |
1435 | | * (1) An error message is returned if any pix has something other |
1436 | | * than 1 bpp or 8 bpp depth, and the centroid from that pix |
1437 | | * is saved as (0, 0). |
1438 | | * </pre> |
1439 | | */ |
1440 | | PTA * |
1441 | | pixaCentroids(PIXA *pixa) |
1442 | 0 | { |
1443 | 0 | l_int32 i, n; |
1444 | 0 | l_int32 *centtab = NULL; |
1445 | 0 | l_int32 *sumtab = NULL; |
1446 | 0 | l_float32 x, y; |
1447 | 0 | PIX *pix; |
1448 | 0 | PTA *pta; |
1449 | |
|
1450 | 0 | if (!pixa) |
1451 | 0 | return (PTA *)ERROR_PTR("pixa not defined", __func__, NULL); |
1452 | 0 | if ((n = pixaGetCount(pixa)) == 0) |
1453 | 0 | return (PTA *)ERROR_PTR("no pix in pixa", __func__, NULL); |
1454 | | |
1455 | 0 | if ((pta = ptaCreate(n)) == NULL) |
1456 | 0 | return (PTA *)ERROR_PTR("pta not defined", __func__, NULL); |
1457 | 0 | centtab = makePixelCentroidTab8(); |
1458 | 0 | sumtab = makePixelSumTab8(); |
1459 | |
|
1460 | 0 | for (i = 0; i < n; i++) { |
1461 | 0 | pix = pixaGetPix(pixa, i, L_CLONE); |
1462 | 0 | if (pixCentroid(pix, centtab, sumtab, &x, &y) == 1) |
1463 | 0 | L_ERROR("centroid failure for pix %d\n", __func__, i); |
1464 | 0 | pixDestroy(&pix); |
1465 | 0 | ptaAddPt(pta, x, y); |
1466 | 0 | } |
1467 | |
|
1468 | 0 | LEPT_FREE(centtab); |
1469 | 0 | LEPT_FREE(sumtab); |
1470 | 0 | return pta; |
1471 | 0 | } |
1472 | | |
1473 | | |
1474 | | /*! |
1475 | | * \brief pixCentroid() |
1476 | | * |
1477 | | * \param[in] pix 1 or 8 bpp |
1478 | | * \param[in] centtab [optional] table for finding centroids; can be null |
1479 | | * \param[in] sumtab [optional] table for finding pixel sums; can be null |
1480 | | * \param[out] pxave x coordinate of centroid, relative to the UL corner |
1481 | | * of the pix |
1482 | | * \param[out] pyave y coordinate of centroid, relative to the UL corner |
1483 | | * of the pix |
1484 | | * \return 0 if OK, 1 on error |
1485 | | * |
1486 | | * <pre> |
1487 | | * Notes: |
1488 | | * (1) The sum and centroid tables are only used for 1 bpp. |
1489 | | * (2) Any table not passed in will be made internally and destroyed |
1490 | | * after use. |
1491 | | * </pre> |
1492 | | */ |
1493 | | l_ok |
1494 | | pixCentroid(PIX *pix, |
1495 | | l_int32 *centtab, |
1496 | | l_int32 *sumtab, |
1497 | | l_float32 *pxave, |
1498 | | l_float32 *pyave) |
1499 | 0 | { |
1500 | 0 | l_int32 w, h, d, i, j, wpl, pixsum, rowsum, val; |
1501 | 0 | l_float32 xsum, ysum; |
1502 | 0 | l_uint32 *data, *line; |
1503 | 0 | l_uint32 word; |
1504 | 0 | l_uint8 byte; |
1505 | 0 | l_int32 *ctab, *stab; |
1506 | |
|
1507 | 0 | if (!pxave || !pyave) |
1508 | 0 | return ERROR_INT("&pxave and &pyave not defined", __func__, 1); |
1509 | 0 | *pxave = *pyave = 0.0; |
1510 | 0 | if (!pix) |
1511 | 0 | return ERROR_INT("pix not defined", __func__, 1); |
1512 | 0 | pixGetDimensions(pix, &w, &h, &d); |
1513 | 0 | if (d != 1 && d != 8) |
1514 | 0 | return ERROR_INT("pix not 1 or 8 bpp", __func__, 1); |
1515 | | |
1516 | 0 | ctab = centtab; |
1517 | 0 | stab = sumtab; |
1518 | 0 | if (d == 1) { |
1519 | 0 | pixSetPadBits(pix, 0); |
1520 | 0 | if (!centtab) |
1521 | 0 | ctab = makePixelCentroidTab8(); |
1522 | 0 | if (!sumtab) |
1523 | 0 | stab = makePixelSumTab8(); |
1524 | 0 | } |
1525 | |
|
1526 | 0 | data = pixGetData(pix); |
1527 | 0 | wpl = pixGetWpl(pix); |
1528 | 0 | xsum = ysum = 0.0; |
1529 | 0 | pixsum = 0; |
1530 | 0 | if (d == 1) { |
1531 | 0 | for (i = 0; i < h; i++) { |
1532 | | /* The body of this loop computes the sum of the set |
1533 | | * (1) bits on this row, weighted by their distance |
1534 | | * from the left edge of pix, and accumulates that into |
1535 | | * xsum; it accumulates their distance from the top |
1536 | | * edge of pix into ysum, and their total count into |
1537 | | * pixsum. It's equivalent to |
1538 | | * for (j = 0; j < w; j++) { |
1539 | | * if (GET_DATA_BIT(line, j)) { |
1540 | | * xsum += j; |
1541 | | * ysum += i; |
1542 | | * pixsum++; |
1543 | | * } |
1544 | | * } |
1545 | | */ |
1546 | 0 | line = data + wpl * i; |
1547 | 0 | rowsum = 0; |
1548 | 0 | for (j = 0; j < wpl; j++) { |
1549 | 0 | word = line[j]; |
1550 | 0 | if (word) { |
1551 | 0 | byte = word & 0xff; |
1552 | 0 | rowsum += stab[byte]; |
1553 | 0 | xsum += ctab[byte] + (j * 32 + 24) * stab[byte]; |
1554 | 0 | byte = (word >> 8) & 0xff; |
1555 | 0 | rowsum += stab[byte]; |
1556 | 0 | xsum += ctab[byte] + (j * 32 + 16) * stab[byte]; |
1557 | 0 | byte = (word >> 16) & 0xff; |
1558 | 0 | rowsum += stab[byte]; |
1559 | 0 | xsum += ctab[byte] + (j * 32 + 8) * stab[byte]; |
1560 | 0 | byte = (word >> 24) & 0xff; |
1561 | 0 | rowsum += stab[byte]; |
1562 | 0 | xsum += ctab[byte] + j * 32 * stab[byte]; |
1563 | 0 | } |
1564 | 0 | } |
1565 | 0 | pixsum += rowsum; |
1566 | 0 | ysum += rowsum * i; |
1567 | 0 | } |
1568 | 0 | if (pixsum == 0) { |
1569 | 0 | L_WARNING("no ON pixels in pix\n", __func__); |
1570 | 0 | } else { |
1571 | 0 | *pxave = xsum / (l_float32)pixsum; |
1572 | 0 | *pyave = ysum / (l_float32)pixsum; |
1573 | 0 | } |
1574 | 0 | } else { /* d == 8 */ |
1575 | 0 | for (i = 0; i < h; i++) { |
1576 | 0 | line = data + wpl * i; |
1577 | 0 | for (j = 0; j < w; j++) { |
1578 | 0 | val = GET_DATA_BYTE(line, j); |
1579 | 0 | xsum += val * j; |
1580 | 0 | ysum += val * i; |
1581 | 0 | pixsum += val; |
1582 | 0 | } |
1583 | 0 | } |
1584 | 0 | if (pixsum == 0) { |
1585 | 0 | L_WARNING("all pixels are 0\n", __func__); |
1586 | 0 | } else { |
1587 | 0 | *pxave = xsum / (l_float32)pixsum; |
1588 | 0 | *pyave = ysum / (l_float32)pixsum; |
1589 | 0 | } |
1590 | 0 | } |
1591 | |
|
1592 | 0 | if (d == 1) { |
1593 | 0 | if (!centtab) LEPT_FREE(ctab); |
1594 | 0 | if (!sumtab) LEPT_FREE(stab); |
1595 | 0 | } |
1596 | 0 | return 0; |
1597 | 0 | } |