Make clang build as silent as possible
[fvs_assignment_project.git] / imagemanip.c
blob2273f16f60eb8e2c95a72ede0a375df2891c3bc0
1 /*########################################################################
3 * Copyright(C) 2002-2007. All Rights Reserved.
5 * Authors: Shivang Patel
6 * Jaap de Haan(jdh)
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
13 * version.
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
18 * for more details.
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
24 * 02111-1307, USA.
25 ########################################################################*/
27 #include <math.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
32 #include "imagemanip.h"
34 #ifndef min
35 #define min(a,b) (((a)<(b))?(a):(b))
36 #endif
38 /* {{{ Local stretching */
40 /* helper macro */
41 #define PIJKL p[i+k + (j+l)*nSizeX]
43 /* local stretch */
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;
49 FvsInt_t i, j, t, l;
50 FvsInt_t sum, denom;
51 FvsByte_t a = 0;
52 FvsInt_t k = 0;
53 FvsByte_t b = 255;
54 int hist[256];
55 FvsByte_t* p = ImageGetBuffer(image);
56 if (p==NULL)
57 return FvsMemory;
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++)
66 hist[PIJKL]++;
68 /* stretch locally */
69 for (k=0, sum=0; k <256; k++)
71 sum+=hist[k];
72 a = (FvsByte_t)k;
73 if (sum>tolerance) break;
76 for (k=255, sum=0; k >= 0; k--)
78 sum+=hist[k];
79 b = (FvsByte_t)k;
80 if (sum>tolerance) break;
83 denom = (FvsInt_t)(b-a);
84 if (denom!=0)
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);
100 return FvsOK;
102 /* }}} */
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
110 ** follows:
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:
122 ** i+w/2 j+w/2
123 ** --- ---
124 ** \ \
125 ** Nx(i,j) = -- -- 2 dx(u,v) dy(u,v)
126 ** / /
127 ** --- ---
128 ** u=i-w/2 v=j-w/2
130 ** i+w/2 j+w/2
131 ** --- ---
132 ** \ \
133 ** Ny(i,j) = -- -- dx²(u,v) - dy²(u,v)
134 ** / /
135 ** --- ---
136 ** u=i-w/2 v=j-w/2
138 ** 1 -1 / Nx(i,j) \
139 ** Theta(i,j) = - tan | ------- |
140 ** 2 \ Ny(i,j) /
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 | ----------- |
167 ** 2 \ Phi2_x(i,j) /
169 ** With this algorithm, a fairly smooth orientatin field estimate can be
170 ** obtained.
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));
185 FvsFloat_t nx, ny;
186 FvsInt_t val;
187 FvsInt_t i, j, x, y;
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)
196 nRet = FvsMemory;
197 else
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++)
210 val = x+y*w;
211 phix[val] = cos(theta[val]);
212 phiy[val] = sin(theta[val]);
214 /* build the low-pass filter */
215 nx = 0.0;
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 */
225 if (nx>1.0)
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++)
236 nx = 0.0;
237 ny = 0.0;
238 for (j = 0; j < fsize; j++)
239 for (i = 0; i < fsize; i++)
241 val = (x+i)+(j+y)*w;
242 nx += filter[j*fsize+i]*phix[val];
243 ny += filter[j*fsize+i]*phiy[val];
245 val = x+y*w;
246 phi2x[val] = nx;
247 phi2y[val] = ny;
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++)
257 val = x+y*w;
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);
268 return nRet;
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;
282 FvsFloat_t dx[s*s];
283 FvsFloat_t dy[s*s];
284 FvsFloat_t nx, ny;
285 FvsFloat_t* out;
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 */
297 if (nFilterSize>0)
299 theta = (FvsFloat_t*)malloc(w * h * sizeof(FvsFloat_t));
300 if (theta!=NULL)
301 memset(theta, 0, (w * h * sizeof(FvsFloat_t)));
304 /* detect any allocation error */
305 if (out==NULL || (nFilterSize>0 && theta==NULL))
306 nRet = FvsMemory;
307 else
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 */
326 nx = 0.0;
327 ny = 0.0;
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) */
335 if (nFilterSize>0)
336 theta[x+y*w] = atan2(nx, ny);
337 else
338 out[x+y*w] = atan2(nx, ny)*0.5;
340 if (nFilterSize>0)
341 nRet = FingerprintDirectionLowPass(theta, out, nFilterSize, w, h);
344 if (theta!=NULL) free(theta);
345 return nRet;
348 /* }}} */
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:
371 ** --- w-1
372 ** 1 \
373 ** X[k] = - -- G (u, v), k = 0, 1, ..., l-1
374 ** w /
375 ** --- d=0
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.
412 /* width */
413 #define BLOCK_W 16
414 #define BLOCK_W2 8
416 /* length */
417 #define BLOCK_L 32
418 #define BLOCK_L2 16
420 #define EPSILON 0.0001
421 #define LPSIZE 3
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);
435 FvsFloat_t* out;
436 FvsFloat_t* freq;
437 FvsFloat_t* orientation = FloatFieldGetBuffer(direction);
439 FvsInt_t x, y, u, v, d, k;
440 size_t size;
442 if (p==NULL)
443 return FvsMemory;
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);
450 if (freq==NULL)
451 return FvsMemory;
453 /* allocate memory for the output */
454 size = w*h*sizeof(FvsFloat_t);
455 out = (FvsFloat_t*)malloc(size);
456 if (out!=NULL)
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++)
483 Xsig[k] = 0.0;
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);
488 /* clipping */
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)];
493 Xsig[k] /= BLOCK_W;
496 /* Let T(i,j) be the avg number of pixels between 2 peaks */
497 /* find peaks in the x signature */
498 peak_cnt = 0;
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 */
521 peak_freq = 0.0;
522 if (peak_cnt>=2)
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)
531 out[x+y*w] = 0.0;
532 else if (peak_freq < 2.0)
533 out[x+y*w] = 0.0;
534 else
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];
547 else
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++)
558 k = x + y*w;
559 peak_freq = 0.0;
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;
565 free(out);
567 return nRet;
573 /* }}} */
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);
585 FvsByte_t* out;
586 FvsInt_t pitchout;
587 FvsInt_t pos, posout, x, y;
588 FvsFloat_t* freq = FloatFieldGetBuffer(frequency);
590 if (freq==NULL)
591 return FvsMemory;
593 /* TODO: add sanity checks for the direction and mask */
594 nRet = ImageSetSize(mask, w, h);
595 if (nRet==FvsOK)
596 nRet = ImageClear(mask);
597 out = ImageGetBuffer(mask);
598 if (out==NULL)
599 return FvsMemory;
600 if (nRet==FvsOK)
602 pitchout = ImageGetPitch(mask);
604 for (y = 0; y < h; y++)
605 for (x = 0; x < w; x++)
607 pos = x + y * w;
608 posout = x + y * pitchout;
609 out[posout] = 0;
610 if (freq[pos] >= freqmin && freq[pos] <= freqmax)
612 /* out[posout] = (uint8_t)(10.0/freq[pos]);*/
613 out[posout] = 255;
616 /* fill in the holes */
617 for (y = 0; y < 4; y++)
618 (void)ImageDilate(mask);
619 /* remove borders */
620 for (y = 0; y < 12; y++)
621 (void)ImageErode(mask);
623 return nRet;
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;
634 FvsByte_t* out;
636 nRet = ImageSetSize(mask, w, h);
637 if (nRet==FvsOK)
638 nRet = ImageClear(mask);
640 out = ImageGetBuffer(mask);
641 if (out==NULL)
642 return FvsMemory;
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;
652 return FvsOK;
656 /* }}} */
658 /* {{{ Thinning algorithms */
659 /* {{{ -> Thin: Using connectivity */
661 #undef P
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
670 Date :1998/02/23
672 Here is that thinning algorithm:
673 9 2 3
674 8 1 4
675 7 6 5
676 For each pixel(#1, above), we have 8 neighbors (#'s 2-9).
678 Step 1:
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)
683 d) 4*6*8=0
685 if these conditions hold, remove pixel 1.
686 Do this for the entire image.
688 Step 2
689 a) same as above
690 b) same as above
691 c) 2*6*8=0
692 d) 2*4*8=0
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
723 each iteration */
724 /* edit petertu: don't remove line endings - we shift that to another step*/
725 /* edit petertu: flood small circles */
726 c = 0;
730 n = 0;
731 for (y=1; y<h-1; y++)
732 for (x=1; x<w-1; x++)
734 if( p[P(x,y)]==0xFF)
736 t=0;
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
749 p[P(x,y)] = 0x80;
750 n++;
754 else //flood small circles so they'll be automatically removed
756 if(P2!=0 && P4!=0 && P6 != 0 && P8!=0)
757 P1 = 0xFF; //flood
760 for (y=1; y<h-1; y++)
761 for (x=1; x<w-1; x++)
763 if( p[P(x,y)]==0x80)
764 p[P(x,y)] = 0;
767 } while (n>0 && ++c < 5);
769 return FvsOK;
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++; \
779 if (n>=2 && n<=6)
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++; \
787 if (t==1)
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);
795 FvsInt_t x, y, n, t;
796 FvsBool_t changed = FvsTrue;
798 if (p==NULL)
799 return FvsMemory;
800 if (ImageGetFlag(image)!=FvsImageBinarized)
801 return FvsBadParameter;
803 while (changed==FvsTrue)
805 changed = FvsFalse;
806 for (y=1; y<h-1; y++)
807 for (x=1; x<w-1; x++)
809 if (p[P(x,y)]==0xFF)
811 STEP_A
813 STEP_B
816 c) 2*4*6=0 (ie either 2,4 ,or 6 is off)
817 d) 4*6*8=0
819 if (P2*P4*P6==0 && P4*P6*P8==0)
820 REMOVE_P;
827 for (y=1; y<h-1; y++)
828 for (x=1; x<w-1; x++)
829 if (p[P(x,y)]==0x80)
830 p[P(x,y)] = 0;
832 for (y=1; y<h-1; y++)
833 for (x=1; x<w-1; x++)
835 if (p[P(x,y)]==0xFF)
837 STEP_A
839 STEP_B
842 c) 2*6*8=0
843 d) 2*4*8=0
845 if (P2*P6*P8==0 && P2*P4*P8==0)
846 REMOVE_P;
853 for (y=1; y<h-1; y++)
854 for (x=1; x<w-1; x++)
855 if (p[P(x,y)]==0x80)
856 p[P(x,y)] = 0;
859 ImageRemoveSpurs(image);
861 return ImageSetFlag(image, FvsImageThinned);
866 /* redefine REMOVE_P */
867 #undef REMOVE_P
869 /* }}} */
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.
891 // 0 0 0 0 0
892 // 1 1 1 0
893 // 1 1 1 1
896 FvsInt_t x,y;
897 FvsBool_t changed = FvsTrue;
899 if (p==NULL)
900 return FvsMemory;
902 if (ImageGetFlag(image)!=FvsImageBinarized)
903 return FvsBadParameter;
905 while (changed==FvsTrue)
907 changed = FvsFalse;
908 for (y=1; y<h-1; y++)
909 for (x=1; x<w-1; x++)
911 if (p[P(x,y)]==0xFF)
914 // 0 0 0 0 1 1 1 1 1 0
915 // 1 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)
920 REMOVE_P;
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)
924 REMOVE_P;
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)
928 REMOVE_P;
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)
932 REMOVE_P;
935 // 0 0 0 0 1 1
936 // 1 1 0 0 1 1 0 1 1 1 1 0
937 // 1 1 0 0 0 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)
941 REMOVE_P;
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)
945 REMOVE_P;
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)
949 REMOVE_P;
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)
953 REMOVE_P;
958 ImageRemoveSpurs(image);
960 return ImageSetFlag(image, FvsImageThinned);
963 /* }}} */
964 /* }}} */