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 byte[,] ComputeOrientationMap(double[,] image, bool[,] mask, BlockMap blocks) { PointF[,] accumulated = ComputePixelwiseOrientation(image, mask, blocks); PointF[,] byBlock = AverageBlockOrientations(accumulated, blocks, mask); PointF[,] smooth = SmoothOutOrientationMap(byBlock, mask); return(ConvertOrientationVectorsToAngles(smooth, mask)); }
static bool[,] Binarize(double[,] input, double[,] baseline, bool[,] mask, BlockMap blocks) { var size = Point.SizeOf(input); var binarized = size.Allocate <bool>(); for (int blockY = 0; blockY < blocks.AllBlocks.Height; ++blockY) { for (int blockX = 0; blockX < blocks.AllBlocks.Width; ++blockX) { if (mask[blockY, blockX]) { Rectangle rect = blocks.BlockAreas[blockY, blockX]; for (int y = rect.Bottom; y < rect.Top; ++y) { for (int x = rect.Left; x < rect.Right; ++x) { if (input[y, x] - baseline[y, x] > 0) { binarized[y, x] = true; } } } } } } return(binarized); }
public static MutableTemplate Extract(DoubleMatrix raw, double dpi) { var template = new MutableTemplate(); // https://sourceafis.machinezoo.com/transparency/decoded-image FingerprintTransparency.Current.Log("decoded-image", raw); if (Math.Abs(dpi - 500) > Parameters.DpiTolerance) { raw = ScaleImage(raw, dpi); } // https://sourceafis.machinezoo.com/transparency/scaled-image FingerprintTransparency.Current.Log("scaled-image", raw); template.Size = raw.Size; var blocks = new BlockMap(raw.Width, raw.Height, Parameters.BlockSize); // https://sourceafis.machinezoo.com/transparency/blocks FingerprintTransparency.Current.Log("blocks", blocks); var histogram = Histogram(blocks, raw); var smoothHistogram = SmoothHistogram(blocks, histogram); var mask = Mask(blocks, histogram); var equalized = Equalize(blocks, raw, smoothHistogram, mask); var orientation = OrientationMap(equalized, mask, blocks); var smoothedLines = OrientedLines(Parameters.ParallelSmoothingResolution, Parameters.ParallelSmoothingRadius, Parameters.ParallelSmoothingStep); var smoothed = SmoothRidges(equalized, orientation, mask, blocks, 0, smoothedLines); // https://sourceafis.machinezoo.com/transparency/parallel-smoothing FingerprintTransparency.Current.Log("parallel-smoothing", smoothed); var orthogonalLines = OrientedLines(Parameters.OrthogonalSmoothingResolution, Parameters.OrthogonalSmoothingRadius, Parameters.OrthogonalSmoothingStep); var orthogonal = SmoothRidges(smoothed, orientation, mask, blocks, Math.PI, orthogonalLines); // https://sourceafis.machinezoo.com/transparency/orthogonal-smoothing FingerprintTransparency.Current.Log("orthogonal-smoothing", orthogonal); var binary = Binarize(smoothed, orthogonal, mask, blocks); var pixelMask = FillBlocks(mask, blocks); CleanupBinarized(binary, pixelMask); // https://sourceafis.machinezoo.com/transparency/pixel-mask FingerprintTransparency.Current.Log("pixel-mask", pixelMask); var inverted = Invert(binary, pixelMask); var innerMask = InnerMask(pixelMask); var ridges = new Skeleton(binary, SkeletonType.Ridges); var valleys = new Skeleton(inverted, SkeletonType.Valleys); template.Minutiae = new List <MutableMinutia>(); CollectMinutiae(template.Minutiae, ridges, MinutiaType.Ending); CollectMinutiae(template.Minutiae, valleys, MinutiaType.Bifurcation); // https://sourceafis.machinezoo.com/transparency/skeleton-minutiae FingerprintTransparency.Current.Log("skeleton-minutiae", template); MaskMinutiae(template.Minutiae, innerMask); // https://sourceafis.machinezoo.com/transparency/inner-minutiae FingerprintTransparency.Current.Log("inner-minutiae", template); RemoveMinutiaClouds(template.Minutiae); // https://sourceafis.machinezoo.com/transparency/removed-minutia-clouds FingerprintTransparency.Current.Log("removed-minutia-clouds", template); template.Minutiae = LimitTemplateSize(template.Minutiae); // https://sourceafis.machinezoo.com/transparency/top-minutiae FingerprintTransparency.Current.Log("top-minutiae", template); return(template); }
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)); }
public FingerprintTemplate(byte[,] image, int dpi = 500) { const int blockSize = 15; if (dpi != 500) { image = ScaleImage(image, dpi); } image = InvertInput(image); var blocks = new BlockMap(new Point(image.GetLength(1), image.GetLength(0)), blockSize); var histogram = ComputeHistogram(blocks, image); var smoothHistogram = ComputeSmoothedHistogram(blocks, histogram); var mask = ComputeMask(blocks, histogram); double[,] equalized = Equalize(blocks, image, smoothHistogram, mask); byte[,] orientation = ComputeOrientationMap(equalized, mask, blocks); double[,] smoothed = SmoothByOrientation(equalized, orientation, mask, blocks, 0, ConstructOrientedLines(step: 1.59)); double[,] orthogonal = SmoothByOrientation(smoothed, orientation, mask, blocks, Angle.PIB, ConstructOrientedLines(resolution: 11, radius: 4, step: 1.11)); var binary = Binarize(smoothed, orthogonal, mask, blocks); CleanupBinarized(binary); BitmapUtils.ShowImage(binary); var pixelMask = FillBlocks(mask, blocks); var innerMask = ComputeInnerMask(pixelMask); var inverted = Invert(binary, pixelMask); FingerprintSkeleton ridges = new FingerprintSkeleton(binary); FingerprintSkeleton valleys = new FingerprintSkeleton(inverted); CollectMinutiae(ridges, FingerprintMinutiaType.Ending); CollectMinutiae(valleys, FingerprintMinutiaType.Bifurcation); ApplyMask(innerMask); RemoveMinutiaClouds(); LimitTemplateSize(); ShuffleMinutiae(); BuildEdgeTable(); }
static bool[,] ComputeMask(BlockMap blocks, int[, ,] histogram) { byte[,] contrast = ComputeClippedContrast(blocks, histogram); var mask = ComputeAbsoluteContrast(contrast); MergeMask(mask, ComputeRelativeContrast(contrast, blocks)); MergeMask(mask, ApplyVotingFilter(mask, radius: 9, majority: 0.86, borderDist: 7)); MergeMask(mask, FilterBlockErrors(mask)); mask = InvertMask(mask); MergeMask(mask, FilterBlockErrors(mask)); MergeMask(mask, FilterBlockErrors(mask)); MergeMask(mask, ApplyVotingFilter(mask, radius: 7, borderDist: 4)); return(mask); }
static bool[,] FillBlocks(bool[,] mask, BlockMap blocks) { bool[,] pixelized = blocks.PixelCount.Allocate <bool>(); foreach (var block in blocks.AllBlocks) { if (block.Get(mask)) { foreach (var pixel in blocks.BlockAreas[block]) { pixel.Set(pixelized, true); } } } return(pixelized); }
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); }
static BooleanMatrix Mask(BlockMap blocks, HistogramCube histogram) { var contrast = ClipContrast(blocks, histogram); var mask = FilterAbsoluteContrast(contrast); mask.Merge(FilterRelativeContrast(contrast, blocks)); // https://sourceafis.machinezoo.com/transparency/combined-mask FingerprintTransparency.Current.Log("combined-mask", mask); mask.Merge(FilterBlockErrors(mask)); mask.Invert(); mask.Merge(FilterBlockErrors(mask)); mask.Merge(FilterBlockErrors(mask)); mask.Merge(Vote(mask, null, Parameters.MaskVoteRadius, Parameters.MaskVoteMajority, Parameters.MaskVoteBorderDistance)); // https://sourceafis.machinezoo.com/transparency/filtered-mask FingerprintTransparency.Current.Log("filtered-mask", mask); return(mask); }
static int[, ,] ComputeHistogram(BlockMap blocks, byte[,] image) { var histogram = new int[blocks.BlockCount.Y, blocks.BlockCount.X, 256]; foreach (var block in blocks.AllBlocks) { var area = blocks.BlockAreas[block]; for (int y = area.Bottom; y < area.Top; ++y) { for (int x = area.Left; x < area.Right; ++x) { ++histogram[block.Y, block.X, image[y, x]]; } } } return(histogram); }
static bool[,] ComputeRelativeContrast(byte[,] contrast, BlockMap blocks) { const int sampleSize = 168568; const double sampleFraction = 0.49; const double relativeLimit = 0.34; List <byte> sortedContrast = new List <byte>(); foreach (byte contrastItem in contrast) { sortedContrast.Add(contrastItem); } sortedContrast.Sort(); sortedContrast.Reverse(); int pixelsPerBlock = blocks.PixelCount.Area / blocks.AllBlocks.TotalArea; int sampleCount = Math.Min(sortedContrast.Count, sampleSize / pixelsPerBlock); int consideredBlocks = Math.Max(Convert.ToInt32(sampleCount * sampleFraction), 1); int averageContrast = 0; for (int i = 0; i < consideredBlocks; ++i) { averageContrast += sortedContrast[i]; } averageContrast /= consideredBlocks; byte limit = Convert.ToByte(averageContrast * relativeLimit); var result = blocks.BlockCount.Allocate <bool>(); for (int y = 0; y < blocks.BlockCount.Y; ++y) { for (int x = 0; x < blocks.BlockCount.X; ++x) { if (contrast[y, x] < limit) { result[y, x] = true; } } } return(result); }
static byte[,] ComputeClippedContrast(BlockMap blocks, int[, ,] histogram) { const double clipFraction = 0.08; byte[,] result = new byte[blocks.BlockCount.Y, blocks.BlockCount.X]; foreach (var block in blocks.AllBlocks) { int area = 0; for (int i = 0; i < 256; ++i) { area += histogram[block.Y, block.X, i]; } int clipLimit = Convert.ToInt32(area * clipFraction); int accumulator = 0; int lowerBound = 255; for (int i = 0; i < 256; ++i) { accumulator += histogram[block.Y, block.X, i]; if (accumulator > clipLimit) { lowerBound = i; break; } } accumulator = 0; int upperBound = 0; for (int i = 255; i >= 0; --i) { accumulator += histogram[block.Y, block.X, i]; if (accumulator > clipLimit) { upperBound = i; break; } } result[block.Y, block.X] = (byte)(upperBound - lowerBound); } return(result); }
static HistogramCube Histogram(BlockMap blocks, DoubleMatrix image) { var histogram = new HistogramCube(blocks.Primary.Blocks, Parameters.HistogramDepth); foreach (var block in blocks.Primary.Blocks.Iterate()) { var area = blocks.Primary.Block(block); for (int y = area.Top; y < area.Bottom; ++y) { for (int x = area.Left; x < area.Right; ++x) { int depth = (int)(image[x, y] * histogram.Bins); histogram.Increment(block, histogram.Constrain(depth)); } } } // https://sourceafis.machinezoo.com/transparency/histogram FingerprintTransparency.Current.Log("histogram", histogram); return(histogram); }
static int[, ,] ComputeSmoothedHistogram(BlockMap blocks, int[, ,] input) { var blocksAround = new Point[] { new Point(0, 0), new Point(-1, 0), new Point(0, -1), new Point(-1, -1) }; var output = new int[blocks.CornerCount.Y, blocks.CornerCount.X, 256]; foreach (var corner in blocks.AllCorners) { foreach (Point relative in blocksAround) { var block = corner + relative; if (blocks.AllBlocks.Contains(block)) { for (int i = 0; i < 256; ++i) { output[corner.Y, corner.X, i] += input[block.Y, block.X, i]; } } } } return(output); }
static PointF[,] AverageBlockOrientations(PointF[,] orientation, BlockMap blocks, bool[,] mask) { PointF[,] sums = new PointF[blocks.BlockCount.Y, blocks.BlockCount.X]; foreach (var block in blocks.AllBlocks) { if (block.Get(mask)) { PointF sum = new PointF(); Rectangle area = blocks.BlockAreas[block]; for (int y = area.Bottom; y < area.Top; ++y) { for (int x = area.Left; x < area.Right; ++x) { sum += orientation[y, x]; } } sums[block.Y, block.X] = sum; } } return(sums); }
static PointF[,] ComputePixelwiseOrientation(double[,] input, bool[,] mask, BlockMap blocks) { List <List <ConsideredOrientation> > neighbors = GetTestedOrientations(); PointF[,] orientation = new PointF[input.GetLength(0), input.GetLength(1)]; for (int blockY = 0; blockY < blocks.BlockCount.Y; ++blockY) { Range validMaskRange = GetMaskLineRange(mask, blockY); if (validMaskRange.Length > 0) { Range validXRange = new Range(blocks.BlockAreas[blockY, validMaskRange.Begin].Left, blocks.BlockAreas[blockY, validMaskRange.End - 1].Right); for (int y = blocks.BlockAreas[blockY, 0].Bottom; y < blocks.BlockAreas[blockY, 0].Top; ++y) { foreach (ConsideredOrientation neighbor in neighbors[y % neighbors.Count]) { int radius = Math.Max(Math.Abs(neighbor.CheckLocation.X), Math.Abs(neighbor.CheckLocation.Y)); if (y - radius >= 0 && y + radius < input.GetLength(0)) { Range xRange = new Range(Math.Max(radius, validXRange.Begin), Math.Min(input.GetLength(1) - radius, validXRange.End)); for (int x = xRange.Begin; x < xRange.End; ++x) { double before = input[y - neighbor.CheckLocation.Y, x - neighbor.CheckLocation.X]; double at = input[y, x]; double after = input[y + neighbor.CheckLocation.Y, x + neighbor.CheckLocation.X]; double strength = at - Math.Max(before, after); if (strength > 0) { orientation[y, x] = orientation[y, x] + strength * neighbor.OrientationVector; } } } } } } } return(orientation); }
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 HistogramCube SmoothHistogram(BlockMap blocks, HistogramCube input) { var blocksAround = new IntPoint[] { new IntPoint(0, 0), new IntPoint(-1, 0), new IntPoint(0, -1), new IntPoint(-1, -1) }; var output = new HistogramCube(blocks.Secondary.Blocks, input.Bins); foreach (var corner in blocks.Secondary.Blocks.Iterate()) { foreach (var relative in blocksAround) { var block = corner + relative; if (blocks.Primary.Blocks.Contains(block)) { for (int i = 0; i < input.Bins; ++i) { output.Add(corner, i, input[block, i]); } } } } // https://sourceafis.machinezoo.com/transparency/smoothed-histogram FingerprintTransparency.Current.Log("smoothed-histogram", output); return(output); }
static DoubleMatrix ClipContrast(BlockMap blocks, HistogramCube histogram) { var result = new DoubleMatrix(blocks.Primary.Blocks); foreach (var block in blocks.Primary.Blocks.Iterate()) { int volume = histogram.Sum(block); int clipLimit = Doubles.RoundToInt(volume * Parameters.ClippedContrast); int accumulator = 0; int lowerBound = histogram.Bins - 1; for (int i = 0; i < histogram.Bins; ++i) { accumulator += histogram[block, i]; if (accumulator > clipLimit) { lowerBound = i; break; } } accumulator = 0; int upperBound = 0; for (int i = histogram.Bins - 1; i >= 0; --i) { accumulator += histogram[block, i]; if (accumulator > clipLimit) { upperBound = i; break; } } result[block] = (upperBound - lowerBound) * (1.0 / (histogram.Bins - 1)); } // https://sourceafis.machinezoo.com/transparency/contrast FingerprintTransparency.Current.Log("contrast", result); return(result); }
static double[,] SmoothByOrientation(double[,] input, byte[,] orientation, bool[,] mask, BlockMap blocks, byte angle, Point[][] lines) { double[,] output = new double[input.GetLength(0), input.GetLength(1)]; foreach (var block in blocks.AllBlocks) { if (block.Get(mask)) { Point[] line = lines[Angle.Quantize(Angle.Add(orientation[block.Y, block.X], angle), lines.Length)]; foreach (Point linePoint in line) { Rectangle target = blocks.BlockAreas[block]; Rectangle source = target.GetShifted(linePoint); source.Clip(new Rectangle(blocks.PixelCount)); target = source.GetShifted(-linePoint); for (int y = target.Bottom; y < target.Top; ++y) { for (int x = target.Left; x < target.Right; ++x) { output[y, x] += input[y + linePoint.Y, x + linePoint.X]; } } } Rectangle blockArea = blocks.BlockAreas[block]; for (int y = blockArea.Bottom; y < blockArea.Top; ++y) { for (int x = blockArea.Left; x < blockArea.Right; ++x) { output[y, x] *= 1.0 / line.Length; } } } } return(output); }
static double[,] Equalize(BlockMap blocks, byte[,] image, int[, ,] histogram, bool[,] blockMask) { const double maxScaling = 3.99; const double minScaling = 0.25; const double rangeMin = -1; const double rangeMax = 1; const double rangeSize = rangeMax - rangeMin; const double widthMax = rangeSize / 256 * maxScaling; const double widthMin = rangeSize / 256 * minScaling; var limitedMin = new double[256]; var limitedMax = new double[256]; var toFloatTable = new double[256]; for (int i = 0; i < 256; ++i) { limitedMin[i] = Math.Max(i * widthMin + rangeMin, rangeMax - (255 - i) * widthMax); limitedMax[i] = Math.Min(i * widthMax + rangeMin, rangeMax - (255 - i) * widthMin); toFloatTable[i] = i / 255; } var cornerMapping = new double[blocks.CornerCount.Y, blocks.CornerCount.X, 256]; foreach (var corner in blocks.AllCorners) { if (corner.Get(blockMask, false) || new Point(corner.X - 1, corner.Y).Get(blockMask, false) || new Point(corner.X, corner.Y - 1).Get(blockMask, false) || new Point(corner.X - 1, corner.Y - 1).Get(blockMask, false)) { int area = 0; for (int i = 0; i < 256; ++i) { area += histogram[corner.Y, corner.X, i]; } double widthWeigth = rangeSize / area; double top = rangeMin; for (int i = 0; i < 256; ++i) { double width = histogram[corner.Y, corner.X, i] * widthWeigth; double equalized = top + toFloatTable[i] * width; top += width; double limited = equalized; if (limited < limitedMin[i]) { limited = limitedMin[i]; } if (limited > limitedMax[i]) { limited = limitedMax[i]; } cornerMapping[corner.Y, corner.X, i] = limited; } } } var result = new double[blocks.PixelCount.Y, blocks.PixelCount.X]; foreach (var block in blocks.AllBlocks) { if (block.Get(blockMask)) { var area = blocks.BlockAreas[block]; for (int y = area.Bottom; y < area.Top; ++y) { for (int x = area.Left; x < area.Right; ++x) { byte pixel = image[y, x]; double bottomLeft = cornerMapping[block.Y, block.X, pixel]; double bottomRight = cornerMapping[block.Y, block.X + 1, pixel]; double topLeft = cornerMapping[block.Y + 1, block.X, pixel]; double topRight = cornerMapping[block.Y + 1, block.X + 1, pixel]; var fraction = area.GetFraction(new Point(x, y)); result[y, x] = MathEx.Interpolate(topLeft, topRight, bottomLeft, bottomRight, fraction); } } } } return(result); }
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 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 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 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); }