1 /*########################################################################
3 * Copyright(C) 2002-2007. All Rights Reserved.
5 * Authors: Shivang Patel
8 * Changes: jdh -> Added support for ImageMagick that enables
9 * to export files to more than 40 formats.
10 * This is free software; you can redistribute it and/or modify it under
11 * the terms of the GNU General Public License as published by the Free
12 * Software Foundation; either version 2, or (at your option) any later
15 * This is distributed in the hope that it will be useful, but WITHOUT
16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
20 * You should have received a copy of the GNU General Public License with
21 * the fvs source package as the
22 * file COPYING. If not, write to the Free Software Foundation, Inc.,
23 * 59 Temple Place - Suite 330, Boston, MA
25 ########################################################################*/
32 #include "imagemanip.h"
35 #define min(a,b) (((a)<(b))?(a):(b))
38 /* {{{ Local stretching */
41 #define PIJKL p[i+k + (j+l)*nSizeX]
44 FvsError_t
ImageLocalStretch(FvsImage_t image
, const FvsInt_t size
, const FvsInt_t tolerance
)
46 /* define a bunch of variables */
47 int nSizeX
= ImageGetWidth(image
) - size
+ 1;
48 int nSizeY
= ImageGetHeight(image
) - size
+ 1;
55 FvsByte_t
* p
= ImageGetBuffer(image
);
58 for (j
=0; j
< nSizeY
; j
+=size
)
60 for (i
=0; i
< nSizeX
; i
+=size
)
62 /* compute local histogram */
63 memset(hist
, 0, 256*sizeof(int));
64 for (l
= 0; l
<size
; l
++)
65 for (k
= 0; k
<size
; k
++)
69 for (k
=0, sum
=0; k
<256; k
++)
73 if (sum
>tolerance
) break;
76 for (k
=255, sum
=0; k
>= 0; k
--)
80 if (sum
>tolerance
) break;
83 denom
= (FvsInt_t
)(b
-a
);
86 for (l
= 0; l
<size
; l
++)
88 for (k
= 0; k
<size
; k
++)
90 if (PIJKL
<a
) PIJKL
= a
;
91 if (PIJKL
>b
) PIJKL
= b
;
92 t
= (FvsInt_t
)((((PIJKL
)-a
)*255)/denom
);
93 PIJKL
= (FvsByte_t
)(t
);
103 /* {{{ Fingerprint orientation field */
105 #define P(x,y) ((int32_t)p[(x)+(y)*pitch])
108 ** In this step, we estimate the ridge orientation field.
109 ** Given a normalized image G, the main steps of the algorithm are as
112 ** 1 - Divide G into blocks of w x w - (15 x 15)
114 ** 2 - Compute the gradients dx(i,j) and dy(i,j) at each pixel (i,j),
115 ** depending on the computational requirement, the gradient operator
116 ** may vary from the single Sobel operator to the more complex Marr-
117 ** Hildreth operator.
119 ** 3 - Estimate the local orientation of each block centered at pixel
120 ** (i,j), using the following operations:
125 ** Nx(i,j) = -- -- 2 dx(u,v) dy(u,v)
133 ** Ny(i,j) = -- -- dx²(u,v) - dy²(u,v)
139 ** Theta(i,j) = - tan | ------- |
142 ** where Theta(i,j) is the least square estimate of the local ridge
143 ** orientation at the block centered at pixel (i,j). Mathematically,
144 ** it represents the direction that is orthogonal to the dominant
145 ** direction of the Fourier spectrum of the w x w window.
147 ** 4 - Due to the presence of noise, corrupted ridge and furrow structures,
148 ** minutiae, etc. in the input image,the estimated local ridge
149 ** orientation may not always be a correct estimate. Since local ridge
150 ** orientation varies slowly in a local neighbourhood, where no
151 ** singular point appears, a low pass filter can be used to modify the
152 ** incorrect local ridge orientation. In order to perform the low-pass
153 ** filtering, the orientation image needs to be converted into a
154 ** continuous vector field, which is defined as follows:
155 ** Phi_x(i,j) = cos( 2 x theta(i,j) )
156 ** Phi_y(i,j) = sin( 2 x theta(i,j) )
157 ** With the resulting vector field, the low-pass filtering can then
158 ** be performed with a convolution as follows:
159 ** Phi2_x(i,j) = (W @ Phi_x) (i,j)
160 ** Phi2_y(i,j) = (W @ Phi_y) (i,j)
161 ** where W is a 2D low-pass filter.
163 ** 5 - Compute the local ridge orientation at (i,j) with
165 ** 1 -1 / Phi2_y(i,j) \
166 ** O(i,j) = - tan | ----------- |
169 ** With this algorithm, a fairly smooth orientatin field estimate can be
174 static FvsError_t
FingerprintDirectionLowPass(FvsFloat_t
* theta
, FvsFloat_t
* out
,
175 FvsInt_t nFilterSize
, FvsInt_t w
, FvsInt_t h
)
177 FvsError_t nRet
= FvsOK
;
178 FvsFloat_t
* filter
= NULL
;
179 FvsFloat_t
* phix
= NULL
;
180 FvsFloat_t
* phiy
= NULL
;
181 FvsFloat_t
* phi2x
= NULL
;
182 FvsFloat_t
* phi2y
= NULL
;
183 FvsInt_t fsize
= nFilterSize
*2+1;
184 size_t nbytes
= (size_t)(w
*h
*sizeof(FvsFloat_t
));
189 filter
= (FvsFloat_t
*)malloc((size_t)fsize
*fsize
*sizeof(FvsFloat_t
));
190 phix
= (FvsFloat_t
*)malloc(nbytes
);
191 phiy
= (FvsFloat_t
*)malloc(nbytes
);
192 phi2x
= (FvsFloat_t
*)malloc(nbytes
);
193 phi2y
= (FvsFloat_t
*)malloc(nbytes
);
195 if (filter
==NULL
|| phi2x
==NULL
|| phi2y
==NULL
|| phix
==NULL
|| phiy
==NULL
)
199 /* reset all fields to 0 */
200 memset(filter
, 0, (size_t)fsize
*fsize
*sizeof(FvsFloat_t
));
201 memset(phix
, 0, nbytes
);
202 memset(phiy
, 0, nbytes
);
203 memset(phi2x
, 0, nbytes
);
204 memset(phi2y
, 0, nbytes
);
206 /* 4 - Compute a continuous field from theta */
207 for (y
= 0; y
< h
; y
++)
208 for (x
= 0; x
< w
; x
++)
211 phix
[val
] = cos(theta
[val
]);
212 phiy
[val
] = sin(theta
[val
]);
214 /* build the low-pass filter */
216 for (j
= 0; j
< fsize
; j
++)
217 for (i
= 0; i
< fsize
; i
++)
219 filter
[j
*fsize
+i
] = 1.0;
221 filter[j*fsize+i] = (FvsFloat_t)(fsize - (abs(nFilterSize-i)+abs(nFilterSize-j)));
223 nx
+= filter
[j
*fsize
+i
]; /* sum of coefficients */
227 for (j
= 0; j
< fsize
; j
++)
228 for (i
= 0; i
< fsize
; i
++)
229 /* normalize the result */
230 filter
[j
*fsize
+i
] /= nx
;
232 /* low-pass on the result arrays getting phi2 */
233 for (y
= 0; y
< h
-fsize
; y
++)
234 for (x
= 0; x
< w
-fsize
; x
++)
238 for (j
= 0; j
< fsize
; j
++)
239 for (i
= 0; i
< fsize
; i
++)
242 nx
+= filter
[j
*fsize
+i
]*phix
[val
];
243 ny
+= filter
[j
*fsize
+i
]*phiy
[val
];
249 /* we do not need phix, phiy anymore, delete them */
250 if (phix
!=NULL
) { free(phix
); phix
=NULL
; }
251 if (phiy
!=NULL
) { free(phiy
); phiy
=NULL
; }
253 /* 5 - local ridge orientation -> theta */
254 for (y
= 0; y
< h
-fsize
; y
++)
255 for (x
= 0; x
< w
-fsize
; x
++)
258 out
[val
] = atan2(phi2y
[val
], phi2x
[val
])*0.5;
262 if (phix
!=NULL
) free(phix
);
263 if (phiy
!=NULL
) free(phiy
);
264 if (phi2x
!=NULL
) free(phi2x
);
265 if (phi2y
!=NULL
) free(phi2y
);
266 if (filter
!=NULL
)free(filter
);
272 FvsError_t
FingerprintGetDirection(const FvsImage_t image
, FvsFloatField_t field
,
273 const FvsInt_t nBlockSize
, const FvsInt_t nFilterSize
)
275 /* width & height of the input image */
276 FvsInt_t w
= ImageGetWidth (image
);
277 FvsInt_t h
= ImageGetHeight(image
);
278 FvsInt_t pitch
= ImageGetPitch (image
);
279 FvsByte_t
* p
= ImageGetBuffer(image
);
280 FvsInt_t i
, j
, u
, v
, x
, y
;
281 FvsInt_t s
= nBlockSize
*2+1;
286 FvsFloat_t
* theta
= NULL
;
287 FvsError_t nRet
= FvsOK
;
289 /* (re-)allocate the output image */
290 nRet
= FloatFieldSetSize(field
, w
, h
);
291 if (nRet
!=FvsOK
) return nRet
;
292 nRet
= FloatFieldClear(field
);
293 if (nRet
!=FvsOK
) return nRet
;
294 out
= FloatFieldGetBuffer(field
);
296 /* allocate memory for the orientation values */
299 theta
= (FvsFloat_t
*)malloc(w
* h
* sizeof(FvsFloat_t
));
301 memset(theta
, 0, (w
* h
* sizeof(FvsFloat_t
)));
304 /* detect any allocation error */
305 if (out
==NULL
|| (nFilterSize
>0 && theta
==NULL
))
309 /* 1 - divide the image in blocks */
310 for (y
= nBlockSize
+1; y
< h
-nBlockSize
-1; y
++)
311 for (x
= nBlockSize
+1; x
< w
-nBlockSize
-1; x
++)
313 /* 2 - for the block centered at x,y compute the gradient */
314 for (j
= 0; j
< s
; j
++)
315 for (i
= 0; i
< s
; i
++)
317 dx
[i
*s
+j
] = (FvsFloat_t
)
318 (P(x
+i
-nBlockSize
, y
+j
-nBlockSize
) -
319 P(x
+i
-nBlockSize
-1, y
+j
-nBlockSize
));
320 dy
[i
*s
+j
] = (FvsFloat_t
)
321 (P(x
+i
-nBlockSize
, y
+j
-nBlockSize
) -
322 P(x
+i
-nBlockSize
, y
+j
-nBlockSize
-1));
325 /* 3 - compute orientation */
328 for (v
= 0; v
< s
; v
++)
329 for (u
= 0; u
< s
; u
++)
331 nx
+= 2 * dx
[u
*s
+v
] * dy
[u
*s
+v
];
332 ny
+= dx
[u
*s
+v
]*dx
[u
*s
+v
] - dy
[u
*s
+v
]*dy
[u
*s
+v
];
334 /* compute angle (-pi/2 .. pi/2) */
336 theta
[x
+y
*w
] = atan2(nx
, ny
);
338 out
[x
+y
*w
] = atan2(nx
, ny
)*0.5;
341 nRet
= FingerprintDirectionLowPass(theta
, out
, nFilterSize
, w
, h
);
344 if (theta
!=NULL
) free(theta
);
349 /* {{{ Fingerprint frequency field */
352 ** In this step, we estimate the ridge frequency. In a local neighbour-
353 ** hood where no minutiae and singular points appear, the gray levels
354 ** along ridges and furrows can be modelled as a sinusoidal-shaped wave
355 ** along a direction normal to the local ridge orientation. Therefore,
356 ** local ridge frequency is another intrinsic property of a fingerprint
357 ** image. Let G be a normalized image G, and O be the orientation image
358 ** (computed at step B). Then the steps involved in local ridge
359 ** frequency estimation are as follows:
361 ** 1 - Divide G into blocks of w x w - (16 x 16)
363 ** 2 - For each block centered at pixel (i,j), compute an oriented
364 ** window of size l x w (32 x 16) that is defined in the ridge
365 ** coordinates system.
367 ** 3 - For each block centered at pixel (i,j), compute the x-signature
368 ** X[0], X[1], ... X[l-1] of the ridges and furrows within the
369 ** oriented window where:
373 ** X[k] = - -- G (u, v), k = 0, 1, ..., l-1
377 ** u = i + (d - w/2).cos O(i,j) + (k - l/2).sin O(i,j)
379 ** v = j + (d - w/2).sin O(i,j) - (k - l/2).cos O(i,j)
381 ** If no minutiae and singular points appear in the oriented window,
382 ** the x-signature forms a discrete sinusoidal-shape wave, which has
383 ** the same frequency as that of the ridges and furrows in the
384 ** oriented window. Therefore, the frequency of ridges and furrows
385 ** can be estimated from the x-signature. Let T(i,j) be the average
386 ** number of pixels between two consecutive peaks in the x-signature,
387 ** then the frequency, OHM(i,j) is computed as OHM(i,j) = 1 / T(i,j).
389 ** If no consecutive peaks can be detected from the x-signature, then
390 ** the frequency is assigned a value of -1, to differenciate it from
391 ** the valid frequency values.
393 ** 4 - For a fingerprint image scanned at a fixed resolution, the value
394 ** of the frequency of the ridges and furrows in a local neighbour-
395 ** hood lies in a certain range. For a 500 dpi image, this range is
396 ** [1/3, 1/25]. Therefore, if the estimated value of the frequency
397 ** is out of this range, then the frequency is assigned a value of
398 ** -1 to indicate that an valid frequency cannot be obtained.
400 ** 5 - The blocks in which minutiae and/or singular points appear and/or
401 ** ridges and furrows are corrupted do not form a well defined
402 ** sinusoidal-shaped wave. The frequency value for these blocks need
403 ** to be interpolated from the frequency of the neighbouring blocks
404 ** which have a well-defined frequency. (Ex: Gaussian kernel mean 0,
405 ** and variance 9 and of size 7).
407 ** 6 - Inter-ridges distance change slowly in a local neighbourhood. A
408 ** low-pass filter can be used to remove the outliers.
420 #define EPSILON 0.0001
423 #define LPFACTOR (1.0/((LPSIZE*2+1)*(LPSIZE*2+1)))
426 FvsError_t
FingerprintGetFrequency(const FvsImage_t image
, const FvsFloatField_t direction
,
427 FvsFloatField_t frequency
)
429 /* width & height of the input image */
430 FvsError_t nRet
= FvsOK
;
431 FvsInt_t w
= ImageGetWidth (image
);
432 FvsInt_t h
= ImageGetHeight(image
);
433 FvsInt_t pitchi
= ImageGetPitch (image
);
434 FvsByte_t
* p
= ImageGetBuffer(image
);
437 FvsFloat_t
* orientation
= FloatFieldGetBuffer(direction
);
439 FvsInt_t x
, y
, u
, v
, d
, k
;
445 /* (re-)allocate the output image */
446 nRet
= FloatFieldSetSize(frequency
, w
, h
);
447 if (nRet
!=FvsOK
) return nRet
;
448 (void)FloatFieldClear(frequency
);
449 freq
= FloatFieldGetBuffer(frequency
);
453 /* allocate memory for the output */
454 size
= w
*h
*sizeof(FvsFloat_t
);
455 out
= (FvsFloat_t
*)malloc(size
);
458 FvsFloat_t dir
= 0.0;
459 FvsFloat_t cosdir
= 0.0;
460 FvsFloat_t sindir
= 0.0;
462 FvsInt_t peak_pos
[BLOCK_L
]; /* peak positions */
463 FvsInt_t peak_cnt
; /* peak count */
464 FvsFloat_t peak_freq
; /* peak frequence */
465 FvsFloat_t Xsig
[BLOCK_L
]; /* x signature */
466 FvsFloat_t pmin
, pmax
;
468 memset(out
, 0, size
);
469 memset(freq
, 0, size
);
471 /* 1 - Divide G into blocks of BLOCK_W x BLOCK_W - (16 x 16) */
472 for (y
= BLOCK_L2
; y
< h
-BLOCK_L2
; y
++)
473 for (x
= BLOCK_L2
; x
< w
-BLOCK_L2
; x
++)
475 /* 2 - oriented window of size l x w (32 x 16) in the ridge dir */
476 dir
= orientation
[(x
+BLOCK_W2
) + (y
+BLOCK_W2
)*w
];
477 cosdir
= -sin(dir
); /* ever > 0 */
478 sindir
= cos(dir
); /* -1 ... 1 */
480 /* 3 - compute the x-signature X[0], X[1], ... X[l-1] */
481 for (k
= 0; k
< BLOCK_L
; k
++)
484 for (d
= 0; d
< BLOCK_W
; d
++)
486 u
= (FvsInt_t
)(x
+ (d
-BLOCK_W2
)*cosdir
+ (k
-BLOCK_L2
)*sindir
);
487 v
= (FvsInt_t
)(y
+ (d
-BLOCK_W2
)*sindir
- (k
-BLOCK_L2
)*cosdir
);
489 if (u
<0) u
= 0; else if (u
>w
-1) u
= w
-1;
490 if (v
<0) v
= 0; else if (v
>h
-1) v
= h
-1;
491 Xsig
[k
] += p
[u
+ (v
*pitchi
)];
496 /* Let T(i,j) be the avg number of pixels between 2 peaks */
497 /* find peaks in the x signature */
499 /* test if the max - min or peak to peak value too small is,
500 then we ignore this point */
501 pmax
= pmin
= Xsig
[0];
502 for (k
= 1; k
< BLOCK_L
; k
++)
504 if (pmin
>Xsig
[k
]) pmin
= Xsig
[k
];
505 if (pmax
<Xsig
[k
]) pmax
= Xsig
[k
];
508 //comment by petertu:
509 //The value of pmax - pmin seems to be strongly dependent on the image's variance
510 //In case of bad results try a smaller threshhold
511 if ((pmax
- pmin
)>64.0)
512 // if ((pmax - pmin)>16.0)
514 for (k
= 1; k
< BLOCK_L
-1; k
++)
515 if ((Xsig
[k
-1] < Xsig
[k
]) && (Xsig
[k
] >= Xsig
[k
+1]))
517 peak_pos
[peak_cnt
++] = k
;
520 /* compute mean value */
524 for (k
= 0; k
< peak_cnt
-1; k
++)
525 peak_freq
+= peak_pos
[k
+1]-peak_pos
[k
];
526 peak_freq
/= peak_cnt
-1;
528 /* 4 - must lie in a certain range [1/25-1/3] */
529 /* changed to range [1/30-1/2] */
530 if (peak_freq
> 30.0)
532 else if (peak_freq
< 2.0)
535 out
[x
+y
*w
] = 1.0/peak_freq
;
537 /* 5 - interpolated ridge period for the unknown points */
538 for (y
= BLOCK_L2
; y
< h
-BLOCK_L2
; y
++)
539 for (x
= BLOCK_L2
; x
< w
-BLOCK_L2
; x
++)
541 if (out
[x
+y
*w
]<EPSILON
)
543 if (out
[x
+(y
-1)*w
]>EPSILON
)
545 out
[x
+(y
*w
)] = out
[x
+(y
-1)*w
];
549 if (out
[x
-1+(y
*w
)]>EPSILON
)
550 out
[x
+(y
*w
)] = out
[x
-1+(y
*w
)];
554 /* 6 - Inter-ridges distance change slowly in a local neighbourhood */
555 for (y
= BLOCK_L2
; y
< h
-BLOCK_L2
; y
++)
556 for (x
= BLOCK_L2
; x
< w
-BLOCK_L2
; x
++)
560 for ( v
= -LPSIZE
; v
<= LPSIZE
; v
++)
561 for ( u
= -LPSIZE
; u
<= LPSIZE
; u
++)
562 peak_freq
+= out
[(x
+u
)+(y
+v
)*w
];
563 freq
[k
] = peak_freq
*LPFACTOR
;
574 /* {{{ Fingerprint mask */
576 FvsError_t
FingerprintGetMask(const FvsImage_t image
, const FvsFloatField_t frequency
, FvsImage_t mask
)
578 FvsError_t nRet
= FvsOK
;
579 FvsFloat_t freqmin
= 1.0 / 25;
580 FvsFloat_t freqmax
= 1.0 / 3;
582 /* width & height of the input image */
583 FvsInt_t w
= ImageGetWidth (image
);
584 FvsInt_t h
= ImageGetHeight(image
);
587 FvsInt_t pos
, posout
, x
, y
;
588 FvsFloat_t
* freq
= FloatFieldGetBuffer(frequency
);
593 /* TODO: add sanity checks for the direction and mask */
594 nRet
= ImageSetSize(mask
, w
, h
);
596 nRet
= ImageClear(mask
);
597 out
= ImageGetBuffer(mask
);
602 pitchout
= ImageGetPitch(mask
);
604 for (y
= 0; y
< h
; y
++)
605 for (x
= 0; x
< w
; x
++)
608 posout
= x
+ y
* pitchout
;
610 if (freq
[pos
] >= freqmin
&& freq
[pos
] <= freqmax
)
612 /* out[posout] = (uint8_t)(10.0/freq[pos]);*/
616 /* fill in the holes */
617 for (y
= 0; y
< 4; y
++)
618 (void)ImageDilate(mask
);
620 for (y
= 0; y
< 12; y
++)
621 (void)ImageErode(mask
);
626 //written by petertu:
627 //creates a mask with a fixed size border
628 FvsError_t
FingerprintGetFixedMask(const FvsImage_t image
, FvsImage_t mask
, const FvsInt_t border
)
630 FvsError_t nRet
= FvsOK
;
631 FvsInt_t w
= ImageGetWidth (image
);
632 FvsInt_t h
= ImageGetHeight(image
);
633 FvsInt_t x
, y
, pitchout
;
636 nRet
= ImageSetSize(mask
, w
, h
);
638 nRet
= ImageClear(mask
);
640 out
= ImageGetBuffer(mask
);
644 pitchout
= ImageGetPitch(mask
);
645 memset(out
, 0, (size_t)w
*h
*sizeof(FvsByte_t
));
647 for (y
= border
; y
< h
-border
; y
++)
648 for (x
= border
; x
< w
-border
; x
++)
650 out
[x
+ y
* pitchout
] = 255;
658 /* {{{ Thinning algorithms */
659 /* {{{ -> Thin: Using connectivity */
662 #define P(x,y) ((x)+(y)*pitch)
663 #define REMOVE_P { p[P(x,y)]=0x80; changed = FvsTrue; }
667 From : Nadeem Ahmed (umahmed@cc.umanitoba.ca)
668 Subject : Thinning Algorithm
669 Newsgroup: borland.public.cppbuilder.graphics
672 Here is that thinning algorithm:
676 For each pixel(#1, above), we have 8 neighbors (#'s 2-9).
679 a) Make sure pixel 1, has 2 to 6 (inclusive) neighbors
680 b) starting from 2, go clockwise until 9, and count the
681 number of 0 to 1 transitions. This should be equal to 1.
682 c) 2*4*6=0 (ie either 2,4 ,or 6 is off)
685 if these conditions hold, remove pixel 1.
686 Do this for the entire image.
694 if these hold remove pixel 1.
696 /* defines to facilitate reading */
697 #define P1 p[P(x ,y )]
698 #define P2 p[P(x ,y-1)]
699 #define P3 p[P(x+1,y-1)]
700 #define P4 p[P(x+1,y )]
701 #define P5 p[P(x+1,y+1)]
702 #define P6 p[P(x ,y+1)]
703 #define P7 p[P(x-1,y+1)]
704 #define P8 p[P(x-1,y )]
705 #define P9 p[P(x-1,y-1)]
708 FvsError_t
ImageRemoveSpurs(FvsImage_t image
)
710 FvsInt_t w
= ImageGetWidth(image
);
711 FvsInt_t h
= ImageGetHeight(image
);
712 FvsInt_t pitch
= ImageGetPitch(image
);
713 FvsByte_t
* p
= ImageGetBuffer(image
);
714 FvsInt_t x
, y
, n
, t
, c
;
716 /* Thanks to Tony Xu for contributing this section that improves the quality
717 of the binarized image by getting rid of the extra pixels (spurs)
719 jdh: improvment by using 2 steps, the previous algorithm had the bad
720 habbit to remove completely some lines oriented in a special way... */
722 /* edit petertu: only allow one pixel in each neighbourhood to be altered
724 /* edit petertu: don't remove line endings - we shift that to another step*/
725 /* edit petertu: flood small circles */
731 for (y
=1; y
<h
-1; y
++)
732 for (x
=1; x
<w
-1; x
++)
737 if (P3
==0 && P2
!=0 && P4
==0) t
++;
738 if (P5
==0 && P4
!=0 && P6
==0) t
++;
739 if (P7
==0 && P6
!=0 && P8
==0) t
++;
740 if (P9
==0 && P8
!=0 && P2
==0) t
++;
741 if (P3
!=0 && P4
==0) t
++;
742 if (P5
!=0 && P6
==0) t
++;
743 if (P7
!=0 && P8
==0) t
++;
744 if (P9
!=0 && P2
==0) t
++;
745 if (t
==1 && P2
!=0x80 && P3
!=0x80 && P4
!=0x80 && P5
!=0x80 && P6
!=0x80 && P7
!=0x80 && P8
!=0x80 && P9
!=0x80)
747 if(P2
+P3
+P4
+P5
+P6
+P7
+P8
+P9
!= 255) //line ending
754 else //flood small circles so they'll be automatically removed
756 if(P2
!=0 && P4
!=0 && P6
!= 0 && P8
!=0)
760 for (y
=1; y
<h
-1; y
++)
761 for (x
=1; x
<w
-1; x
++)
767 } while (n
>0 && ++c
< 5);
775 /* a) Make sure it has 2 to 6 neighbours */
776 #define STEP_A n = 0; /* number of neighbours */ \
777 if (P2!=0) n++; if (P3!=0) n++; if (P4!=0) n++; if (P5!=0) n++; \
778 if (P6!=0) n++; if (P7!=0) n++; if (P8!=0) n++; if (P9!=0) n++; \
781 /* b) count number 0 to 1 transsitions */
782 #define STEP_B t = 0; /* number of transitions */ \
783 if (P9==0 && P2!=0) t++; if (P2==0 && P3!=0) t++; \
784 if (P3==0 && P4!=0) t++; if (P4==0 && P5!=0) t++; \
785 if (P5==0 && P6!=0) t++; if (P6==0 && P7!=0) t++; \
786 if (P7==0 && P8!=0) t++; if (P8==0 && P9!=0) t++; \
789 FvsError_t
ImageThinConnectivity(FvsImage_t image
)
791 FvsInt_t w
= ImageGetWidth(image
);
792 FvsInt_t h
= ImageGetHeight(image
);
793 FvsInt_t pitch
= ImageGetPitch(image
);
794 FvsByte_t
* p
= ImageGetBuffer(image
);
796 FvsBool_t changed
= FvsTrue
;
800 if (ImageGetFlag(image
)!=FvsImageBinarized
)
801 return FvsBadParameter
;
803 while (changed
==FvsTrue
)
806 for (y
=1; y
<h
-1; y
++)
807 for (x
=1; x
<w
-1; x
++)
816 c) 2*4*6=0 (ie either 2,4 ,or 6 is off)
819 if (P2
*P4
*P6
==0 && P4
*P6
*P8
==0)
827 for (y
=1; y
<h
-1; y
++)
828 for (x
=1; x
<w
-1; x
++)
832 for (y
=1; y
<h
-1; y
++)
833 for (x
=1; x
<w
-1; x
++)
845 if (P2
*P6
*P8
==0 && P2
*P4
*P8
==0)
853 for (y
=1; y
<h
-1; y
++)
854 for (x
=1; x
<w
-1; x
++)
859 ImageRemoveSpurs(image
);
861 return ImageSetFlag(image
, FvsImageThinned
);
866 /* redefine REMOVE_P */
870 /* {{{ -> Thin: Hit and miss */
872 #define REMOVE_P { p[P(x,y)]=0x00; changed = FvsTrue; }
875 // jdh: Second thinning algorithm based on a Hit and Miss transformation
877 FvsError_t
ImageThinHitMiss(FvsImage_t image
)
879 FvsInt_t w
= ImageGetWidth(image
);
880 FvsInt_t h
= ImageGetHeight(image
);
881 FvsInt_t pitch
= ImageGetPitch(image
);
882 FvsByte_t
* p
= ImageGetBuffer(image
);
885 // Hit and Miss structuring elements for thinning
887 // this algo has the disadvantage to produce spurious lines resulting
888 // from the skeletonization. These may be eliminated by another algorithm.
889 // postprocessing is then still needed afterwards.
897 FvsBool_t changed
= FvsTrue
;
902 if (ImageGetFlag(image
)!=FvsImageBinarized
)
903 return FvsBadParameter
;
905 while (changed
==FvsTrue
)
908 for (y
=1; y
<h
-1; y
++)
909 for (x
=1; x
<w
-1; x
++)
914 // 0 0 0 0 1 1 1 1 1 0
916 // 1 1 1 0 1 0 0 0 1 0
918 if (p
[P(x
-1,y
-1)]==0 && p
[P(x
,y
-1)]==0 && p
[P(x
+1,y
-1)]==0 &&
919 p
[P(x
-1,y
+1)]!=0 && p
[P(x
,y
+1)]!=0 && p
[P(x
+1,y
+1)]!=0)
922 if (p
[P(x
-1,y
-1)]!=0 && p
[P(x
,y
-1)]!=0 && p
[P(x
+1,y
-1)]!=0 &&
923 p
[P(x
-1,y
+1)]==0 && p
[P(x
,y
+1)]==0 && p
[P(x
+1,y
+1)]==0)
926 if (p
[P(x
-1,y
-1)]==0 && p
[P(x
-1,y
)]==0 && p
[P(x
-1,y
+1)]==0 &&
927 p
[P(x
+1,y
-1)]!=0 && p
[P(x
+1,y
)]!=0 && p
[P(x
+1,y
+1)]!=0)
930 if (p
[P(x
-1,y
-1)]!=0 && p
[P(x
-1,y
)]!=0 && p
[P(x
-1,y
+1)]!=0 &&
931 p
[P(x
+1,y
-1)]==0 && p
[P(x
+1,y
)]==0 && p
[P(x
+1,y
+1)]==0)
936 // 1 1 0 0 1 1 0 1 1 1 1 0
939 if (p
[P(x
,y
-1)]==0 && p
[P(x
+1,y
-1)]==0 && p
[P(x
+1,y
)]==0 &&
940 p
[P(x
-1,y
)]!=0 && p
[P(x
,y
+1)]!=0)
943 if (p
[P(x
-1,y
-1)]==0 && p
[P(x
,y
-1)]==0 && p
[P(x
-1,y
)]==0 &&
944 p
[P(x
+1,y
)]!=0 && p
[P(x
,y
+1)]!=0)
947 if (p
[P(x
-1,y
+1)]==0 && p
[P(x
-1,y
)]==0 && p
[P(x
,y
+1)]==0 &&
948 p
[P(x
+1,y
)]!=0 && p
[P(x
,y
-1)]!=0)
951 if (p
[P(x
+1,y
+1)]==0 && p
[P(x
+1,y
)]==0 && p
[P(x
,y
+1)]==0 &&
952 p
[P(x
-1,y
)]!=0 && p
[P(x
,y
-1)]!=0)
958 ImageRemoveSpurs(image
);
960 return ImageSetFlag(image
, FvsImageThinned
);