static BooleanMatrix FilterRelativeContrast(DoubleMatrix contrast, BlockMap blocks) { var sortedContrast = new List <double>(); foreach (var block in contrast.Size.Iterate()) { sortedContrast.Add(contrast[block]); } sortedContrast.Sort(); sortedContrast.Reverse(); int pixelsPerBlock = blocks.Pixels.Area / blocks.Primary.Blocks.Area; int sampleCount = Math.Min(sortedContrast.Count, Parameters.RelativeContrastSample / pixelsPerBlock); int consideredBlocks = Math.Max(Doubles.RoundToInt(sampleCount * Parameters.RelativeContrastPercentile), 1); double averageContrast = 0; for (int i = 0; i < consideredBlocks; ++i) { averageContrast += sortedContrast[i]; } averageContrast /= consideredBlocks; var limit = averageContrast * Parameters.MinRelativeContrast; var result = new BooleanMatrix(blocks.Primary.Blocks); foreach (var block in blocks.Primary.Blocks.Iterate()) { if (contrast[block] < limit) { result[block] = true; } } // https://sourceafis.machinezoo.com/transparency/relative-contrast-mask FingerprintTransparency.Current.Log("relative-contrast-mask", result); return(result); }
static DoublePointMatrix SmoothOrientation(DoublePointMatrix orientation, BooleanMatrix mask) { var size = mask.Size; var smoothed = new DoublePointMatrix(size); foreach (var block in size.Iterate()) { if (mask[block]) { var neighbors = IntRect.Around(block, Parameters.OrientationSmoothingRadius).Intersect(new IntRect(size)); for (int ny = neighbors.Top; ny < neighbors.Bottom; ++ny) { for (int nx = neighbors.Left; nx < neighbors.Right; ++nx) { if (mask[nx, ny]) { smoothed.Add(block, orientation[nx, ny]); } } } } } // https://sourceafis.machinezoo.com/transparency/smoothed-orientation FingerprintTransparency.Current.Log("smoothed-orientation", smoothed); return(smoothed); }
static BooleanMatrix InnerMask(BooleanMatrix outer) { var size = outer.Size; var inner = new BooleanMatrix(size); for (int y = 1; y < size.Y - 1; ++y) { for (int x = 1; x < size.X - 1; ++x) { inner[x, y] = outer[x, y]; } } if (Parameters.InnerMaskBorderDistance >= 1) { inner = ShrinkMask(inner, 1); } int total = 1; for (int step = 1; total + step <= Parameters.InnerMaskBorderDistance; step *= 2) { inner = ShrinkMask(inner, step); total += step; } if (total < Parameters.InnerMaskBorderDistance) { inner = ShrinkMask(inner, Parameters.InnerMaskBorderDistance - total); } // https://sourceafis.machinezoo.com/transparency/inner-mask FingerprintTransparency.Current.Log("inner-mask", inner); return(inner); }
static BooleanMatrix Binarize(DoubleMatrix input, DoubleMatrix baseline, BooleanMatrix mask, BlockMap blocks) { var size = input.Size; var binarized = new BooleanMatrix(size); foreach (var block in blocks.Primary.Blocks.Iterate()) { if (mask[block]) { var rect = blocks.Primary.Block(block); for (int y = rect.Top; y < rect.Bottom; ++y) { for (int x = rect.Left; x < rect.Right; ++x) { if (input[x, y] - baseline[x, y] > 0) { binarized[x, y] = true; } } } } } // https://sourceafis.machinezoo.com/transparency/binarized-image FingerprintTransparency.Current.Log("binarized-image", binarized); return(binarized); }
static IntRange MaskRange(BooleanMatrix mask, int y) { int first = -1; int last = -1; for (int x = 0; x < mask.Width; ++x) { if (mask[x, y]) { last = x; if (first < 0) { first = x; } } } if (first >= 0) { return(new IntRange(first, last + 1)); } else { return(IntRange.Zero); } }
static void MaskMinutiae(List <MutableMinutia> minutiae, BooleanMatrix mask) { minutiae.RemoveAll(minutia => { var arrow = (-Parameters.MaskDisplacement * DoubleAngle.ToVector(minutia.Direction)).Round(); return(!mask.Get(minutia.Position + arrow, false)); }); }
public BooleanMatrix(BooleanMatrix other) : this(other.Size) { for (int i = 0; i < Cells.Length; ++i) { Cells[i] = other.Cells[i]; } }
static DoubleMatrix OrientationMap(DoubleMatrix image, BooleanMatrix mask, BlockMap blocks) { var accumulated = PixelwiseOrientation(image, mask, blocks); var byBlock = BlockOrientations(accumulated, blocks, mask); var smooth = SmoothOrientation(byBlock, mask); return(OrientationAngles(smooth, mask)); }
BooleanMatrix Thin(BooleanMatrix input) { var neighborhoodTypes = NeighborhoodTypes(); var mutable = new BooleanMatrix(Size); for (int y = 1; y < Size.Y - 1; ++y) { for (int x = 1; x < Size.X - 1; ++x) { mutable[x, y] = input[x, y]; } } var thinned = new BooleanMatrix(Size); bool removedAnything = true; for (int i = 0; i < Parameters.ThinningIterations && removedAnything; ++i) { removedAnything = false; for (int evenY = 0; evenY < 2; ++evenY) { for (int evenX = 0; evenX < 2; ++evenX) { for (int y = 1 + evenY; y < Size.Y - 1; y += 2) { for (int x = 1 + evenX; x < Size.X - 1; x += 2) { if (mutable[x, y] && !thinned[x, y] && !(mutable[x, y - 1] && mutable[x, y + 1] && mutable[x - 1, y] && mutable[x + 1, y])) { uint neighbors = (mutable[x + 1, y + 1] ? 128u : 0u) | (mutable[x, y + 1] ? 64u : 0u) | (mutable[x - 1, y + 1] ? 32u : 0u) | (mutable[x + 1, y] ? 16u : 0u) | (mutable[x - 1, y] ? 8u : 0u) | (mutable[x + 1, y - 1] ? 4u : 0u) | (mutable[x, y - 1] ? 2u : 0u) | (mutable[x - 1, y - 1] ? 1u : 0u); if (neighborhoodTypes[neighbors] == NeighborhoodType.Removable || neighborhoodTypes[neighbors] == NeighborhoodType.Ending && IsFalseEnding(mutable, new IntPoint(x, y))) { removedAnything = true; mutable[x, y] = false; } else { thinned[x, y] = true; } } } } } } } // https://sourceafis.machinezoo.com/transparency/thinned-skeleton FingerprintTransparency.Current.Log(Prefix(Type) + "thinned-skeleton", thinned); return(thinned); }
public void Merge(BooleanMatrix other) { if (other.Width != Width || other.Height != Height) { throw new ArgumentException(); } for (int i = 0; i < Cells.Length; ++i) { Cells[i] |= other.Cells[i]; } }
static bool IsRidgeOverlapping(IntPoint[] line, BooleanMatrix shadow) { for (int i = Parameters.ToleratedGapOverlap; i < line.Length - Parameters.ToleratedGapOverlap; ++i) { if (shadow[line[i]]) { return(true); } } return(false); }
static BooleanMatrix ShrinkMask(BooleanMatrix mask, int amount) { var size = mask.Size; var shrunk = new BooleanMatrix(size); for (int y = amount; y < size.Y - amount; ++y) { for (int x = amount; x < size.X - amount; ++x) { shrunk[x, y] = mask[x, y - amount] && mask[x, y + amount] && mask[x - amount, y] && mask[x + amount, y]; } } return(shrunk); }
static DoubleMatrix OrientationAngles(DoublePointMatrix vectors, BooleanMatrix mask) { var size = mask.Size; var angles = new DoubleMatrix(size); foreach (var block in size.Iterate()) { if (mask[block]) { angles[block] = DoubleAngle.Atan(vectors[block]); } } return(angles); }
static BooleanMatrix Invert(BooleanMatrix binary, BooleanMatrix mask) { var size = binary.Size; var inverted = new BooleanMatrix(size); for (int y = 0; y < size.Y; ++y) { for (int x = 0; x < size.X; ++x) { inverted[x, y] = !binary[x, y] && mask[x, y]; } } return(inverted); }
static void AddGapRidge(BooleanMatrix shadow, Gap gap, IntPoint[] line) { var ridge = new SkeletonRidge(); foreach (var point in line) { ridge.Points.Add(point); } ridge.Start = gap.End1; ridge.End = gap.End2; foreach (var point in line) { shadow[point] = true; } }
static BooleanMatrix FilterAbsoluteContrast(DoubleMatrix contrast) { var result = new BooleanMatrix(contrast.Size); foreach (var block in contrast.Size.Iterate()) { if (contrast[block] < Parameters.MinAbsoluteContrast) { result[block] = true; } } // https://sourceafis.machinezoo.com/transparency/absolute-contrast-mask FingerprintTransparency.Current.Log("absolute-contrast-mask", result); return(result); }
static BooleanMatrix FillBlocks(BooleanMatrix mask, BlockMap blocks) { var pixelized = new BooleanMatrix(blocks.Pixels); foreach (var block in blocks.Primary.Blocks.Iterate()) { if (mask[block]) { foreach (var pixel in blocks.Primary.Block(block).Iterate()) { pixelized[pixel] = true; } } } return(pixelized); }
public Skeleton(BooleanMatrix binary, SkeletonType type) { Type = type; // https://sourceafis.machinezoo.com/transparency/binarized-skeleton FingerprintTransparency.Current.Log(Prefix(Type) + "binarized-skeleton", binary); Size = binary.Size; var thinned = Thin(binary); var minutiaPoints = FindMinutiae(thinned); var linking = LinkNeighboringMinutiae(minutiaPoints); var minutiaMap = MinutiaCenters(linking); TraceRidges(thinned, minutiaMap); FixLinkingGaps(); // https://sourceafis.machinezoo.com/transparency/traced-skeleton FingerprintTransparency.Current.LogSkeleton("traced-skeleton", this); Filter(); }
BooleanMatrix Shadow() { var shadow = new BooleanMatrix(Size); foreach (var minutia in Minutiae) { shadow[minutia.Position] = true; foreach (var ridge in minutia.Ridges) { if (ridge.Start.Position.Y <= ridge.End.Position.Y) { foreach (var point in ridge.Points) { shadow[point] = true; } } } } return(shadow); }
static bool IsFalseEnding(BooleanMatrix binary, IntPoint ending) { foreach (var relativeNeighbor in IntPoint.CornerNeighbors) { var neighbor = ending + relativeNeighbor; if (binary[neighbor]) { int count = 0; foreach (var relative2 in IntPoint.CornerNeighbors) { if (binary.Get(neighbor + relative2, false)) { ++count; } } return(count > 2); } } return(false); }
static void CleanupBinarized(BooleanMatrix binary, BooleanMatrix mask) { var size = binary.Size; var inverted = new BooleanMatrix(binary); inverted.Invert(); var islands = Vote(inverted, mask, Parameters.BinarizedVoteRadius, Parameters.BinarizedVoteMajority, Parameters.BinarizedVoteBorderDistance); var holes = Vote(binary, mask, Parameters.BinarizedVoteRadius, Parameters.BinarizedVoteMajority, Parameters.BinarizedVoteBorderDistance); for (int y = 0; y < size.Y; ++y) { for (int x = 0; x < size.X; ++x) { binary[x, y] = binary[x, y] && !islands[x, y] || holes[x, y]; } } RemoveCrosses(binary); // https://sourceafis.machinezoo.com/transparency/filtered-binary-image FingerprintTransparency.Current.Log("filtered-binary-image", binary); }
static DoublePointMatrix PixelwiseOrientation(DoubleMatrix input, BooleanMatrix mask, BlockMap blocks) { var neighbors = PlanOrientations(); var orientation = new DoublePointMatrix(input.Size); for (int blockY = 0; blockY < blocks.Primary.Blocks.Y; ++blockY) { var maskRange = MaskRange(mask, blockY); if (maskRange.Length > 0) { var validXRange = new IntRange( blocks.Primary.Block(maskRange.Start, blockY).Left, blocks.Primary.Block(maskRange.End - 1, blockY).Right); for (int y = blocks.Primary.Block(0, blockY).Top; y < blocks.Primary.Block(0, blockY).Bottom; ++y) { foreach (var neighbor in neighbors[y % neighbors.Length]) { int radius = Math.Max(Math.Abs(neighbor.Offset.X), Math.Abs(neighbor.Offset.Y)); if (y - radius >= 0 && y + radius < input.Height) { var xRange = new IntRange(Math.Max(radius, validXRange.Start), Math.Min(input.Width - radius, validXRange.End)); for (int x = xRange.Start; x < xRange.End; ++x) { double before = input[x - neighbor.Offset.X, y - neighbor.Offset.Y]; double at = input[x, y]; double after = input[x + neighbor.Offset.X, y + neighbor.Offset.Y]; double strength = at - Math.Max(before, after); if (strength > 0) { orientation.Add(x, y, strength * neighbor.Orientation); } } } } } } } // https://sourceafis.machinezoo.com/transparency/pixelwise-orientation FingerprintTransparency.Current.Log("pixelwise-orientation", orientation); return(orientation); }
static void TraceRidges(BooleanMatrix thinned, Dictionary <IntPoint, SkeletonMinutia> minutiaePoints) { var leads = new Dictionary <IntPoint, SkeletonRidge>(); foreach (var minutiaPoint in minutiaePoints.Keys) { foreach (var startRelative in IntPoint.CornerNeighbors) { var start = minutiaPoint + startRelative; if (thinned.Get(start, false) && !minutiaePoints.ContainsKey(start) && !leads.ContainsKey(start)) { var ridge = new SkeletonRidge(); ridge.Points.Add(minutiaPoint); ridge.Points.Add(start); var previous = minutiaPoint; var current = start; do { var next = IntPoint.Zero; foreach (var nextRelative in IntPoint.CornerNeighbors) { next = current + nextRelative; if (thinned.Get(next, false) && next != previous) { break; } } previous = current; current = next; ridge.Points.Add(current); } while (!minutiaePoints.ContainsKey(current)); var end = current; ridge.Start = minutiaePoints[minutiaPoint]; ridge.End = minutiaePoints[end]; leads[ridge.Points[1]] = ridge; leads[ridge.Reversed.Points[1]] = ridge; } } } }
List <IntPoint> FindMinutiae(BooleanMatrix thinned) { var result = new List <IntPoint>(); foreach (var at in Size.Iterate()) { if (thinned[at]) { int count = 0; foreach (var relative in IntPoint.CornerNeighbors) { if (thinned.Get(at + relative, false)) { ++count; } } if (count == 1 || count > 2) { result.Add(at); } } } return(result); }
static void RemoveCrosses(BooleanMatrix input) { var size = input.Size; bool any = true; while (any) { any = false; for (int y = 0; y < size.Y - 1; ++y) { for (int x = 0; x < size.X - 1; ++x) { if (input[x, y] && input[x + 1, y + 1] && !input[x, y + 1] && !input[x + 1, y] || input[x, y + 1] && input[x + 1, y] && !input[x, y] && !input[x + 1, y + 1]) { input[x, y] = false; input[x, y + 1] = false; input[x + 1, y] = false; input[x + 1, y + 1] = false; any = true; } } } } }
static BooleanMatrix Vote(BooleanMatrix input, BooleanMatrix mask, int radius, double majority, int borderDistance) { var size = input.Size; var rect = new IntRect(borderDistance, borderDistance, size.X - 2 * borderDistance, size.Y - 2 * borderDistance); int[] thresholds = new int[Integers.Sq(2 * radius + 1) + 1]; for (int i = 0; i < thresholds.Length; ++i) { thresholds[i] = (int)Math.Ceiling(majority * i); } var counts = new IntMatrix(size); var output = new BooleanMatrix(size); for (int y = rect.Top; y < rect.Bottom; ++y) { int superTop = y - radius - 1; int superBottom = y + radius; int yMin = Math.Max(0, y - radius); int yMax = Math.Min(size.Y - 1, y + radius); int yRange = yMax - yMin + 1; for (int x = rect.Left; x < rect.Right; ++x) { if (mask == null || mask[x, y]) { int left = x > 0 ? counts[x - 1, y] : 0; int top = y > 0 ? counts[x, y - 1] : 0; int diagonal = x > 0 && y > 0 ? counts[x - 1, y - 1] : 0; int xMin = Math.Max(0, x - radius); int xMax = Math.Min(size.X - 1, x + radius); int ones; if (left > 0 && top > 0 && diagonal > 0) { ones = top + left - diagonal - 1; int superLeft = x - radius - 1; int superRight = x + radius; if (superLeft >= 0 && superTop >= 0 && input[superLeft, superTop]) { ++ones; } if (superLeft >= 0 && superBottom < size.Y && input[superLeft, superBottom]) { --ones; } if (superRight < size.X && superTop >= 0 && input[superRight, superTop]) { --ones; } if (superRight < size.X && superBottom < size.Y && input[superRight, superBottom]) { ++ones; } } else { ones = 0; for (int ny = yMin; ny <= yMax; ++ny) { for (int nx = xMin; nx <= xMax; ++nx) { if (input[nx, ny]) { ++ones; } } } } counts[x, y] = ones + 1; if (ones >= thresholds[yRange * (xMax - xMin + 1)]) { output[x, y] = true; } } } } return(output); }
static BooleanMatrix FilterBlockErrors(BooleanMatrix input) { return(Vote(input, null, Parameters.BlockErrorsVoteRadius, Parameters.BlockErrorsVoteMajority, Parameters.BlockErrorsVoteBorderDistance)); }
static DoubleMatrix Equalize(BlockMap blocks, DoubleMatrix image, HistogramCube histogram, BooleanMatrix blockMask) { const double rangeMin = -1; const double rangeMax = 1; const double rangeSize = rangeMax - rangeMin; double widthMax = rangeSize / histogram.Bins * Parameters.MaxEqualizationScaling; double widthMin = rangeSize / histogram.Bins * Parameters.MinEqualizationScaling; var limitedMin = new double[histogram.Bins]; var limitedMax = new double[histogram.Bins]; var dequantized = new double[histogram.Bins]; for (int i = 0; i < histogram.Bins; ++i) { limitedMin[i] = Math.Max(i * widthMin + rangeMin, rangeMax - (histogram.Bins - 1 - i) * widthMax); limitedMax[i] = Math.Min(i * widthMax + rangeMin, rangeMax - (histogram.Bins - 1 - i) * widthMin); dequantized[i] = i / (double)(histogram.Bins - 1); } var mappings = new Dictionary <IntPoint, double[]>(); foreach (var corner in blocks.Secondary.Blocks.Iterate()) { double[] mapping = new double[histogram.Bins]; mappings[corner] = mapping; if (blockMask.Get(corner, false) || blockMask.Get(corner.X - 1, corner.Y, false) || blockMask.Get(corner.X, corner.Y - 1, false) || blockMask.Get(corner.X - 1, corner.Y - 1, false)) { double step = rangeSize / histogram.Sum(corner); double top = rangeMin; for (int i = 0; i < histogram.Bins; ++i) { double band = histogram[corner, i] * step; double equalized = top + dequantized[i] * band; top += band; if (equalized < limitedMin[i]) { equalized = limitedMin[i]; } if (equalized > limitedMax[i]) { equalized = limitedMax[i]; } mapping[i] = equalized; } } } var result = new DoubleMatrix(blocks.Pixels); foreach (var block in blocks.Primary.Blocks.Iterate()) { var area = blocks.Primary.Block(block); if (blockMask[block]) { var topleft = mappings[block]; var topright = mappings[new IntPoint(block.X + 1, block.Y)]; var bottomleft = mappings[new IntPoint(block.X, block.Y + 1)]; var bottomright = mappings[new IntPoint(block.X + 1, block.Y + 1)]; for (int y = area.Top; y < area.Bottom; ++y) { for (int x = area.Left; x < area.Right; ++x) { int depth = histogram.Constrain((int)(image[x, y] * histogram.Bins)); double rx = (x - area.X + 0.5) / area.Width; double ry = (y - area.Y + 0.5) / area.Height; result[x, y] = Doubles.Interpolate(bottomleft[depth], bottomright[depth], topleft[depth], topright[depth], rx, ry); } } } else { for (int y = area.Top; y < area.Bottom; ++y) { for (int x = area.Left; x < area.Right; ++x) { result[x, y] = -1; } } } } // https://sourceafis.machinezoo.com/transparency/equalized-image FingerprintTransparency.Current.Log("equalized-image", result); return(result); }
static DoublePointMatrix BlockOrientations(DoublePointMatrix orientation, BlockMap blocks, BooleanMatrix mask) { var sums = new DoublePointMatrix(blocks.Primary.Blocks); foreach (var block in blocks.Primary.Blocks.Iterate()) { if (mask[block]) { var area = blocks.Primary.Block(block); for (int y = area.Top; y < area.Bottom; ++y) { for (int x = area.Left; x < area.Right; ++x) { sums.Add(block, orientation[x, y]); } } } } // https://sourceafis.machinezoo.com/transparency/block-orientation FingerprintTransparency.Current.Log("block-orientation", sums); return(sums); }
static DoubleMatrix SmoothRidges(DoubleMatrix input, DoubleMatrix orientation, BooleanMatrix mask, BlockMap blocks, double angle, IntPoint[][] lines) { var output = new DoubleMatrix(input.Size); foreach (var block in blocks.Primary.Blocks.Iterate()) { if (mask[block]) { var line = lines[DoubleAngle.Quantize(DoubleAngle.Add(orientation[block], angle), lines.Length)]; foreach (var linePoint in line) { var target = blocks.Primary.Block(block); var source = target.Move(linePoint).Intersect(new IntRect(blocks.Pixels)); target = source.Move(-linePoint); for (int y = target.Top; y < target.Bottom; ++y) { for (int x = target.Left; x < target.Right; ++x) { output.Add(x, y, input[x + linePoint.X, y + linePoint.Y]); } } } var blockArea = blocks.Primary.Block(block); for (int y = blockArea.Top; y < blockArea.Bottom; ++y) { for (int x = blockArea.Left; x < blockArea.Right; ++x) { output.Multiply(x, y, 1.0 / line.Length); } } } } return(output); }