You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1486 lines
58 KiB
1486 lines
58 KiB
// David Eberly, Geometric Tools, Redmond WA 98052
|
|
// Copyright (c) 1998-2021
|
|
// Distributed under the Boost Software License, Version 1.0.
|
|
// https://www.boost.org/LICENSE_1_0.txt
|
|
// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
|
|
// Version: 4.0.2019.08.13
|
|
|
|
#pragma once
|
|
|
|
#include <Mathematics/Image2.h>
|
|
#include <cmath>
|
|
#include <functional>
|
|
#include <limits>
|
|
|
|
// Image utilities for Image2<int> objects. TODO: Extend this to a template
|
|
// class to allow the pixel type to be int*_t and uint*_t for * in
|
|
// {8,16,32,64}.
|
|
//
|
|
// All but the Draw* functions are operations on binary images. Let the image
|
|
// have d0 columns and d1 rows. The input image must have zeros on its
|
|
// boundaries x = 0, x = d0-1, y = 0 and y = d1-1. The 0-valued pixels are
|
|
// considered to be background. The 1-valued pixels are considered to be
|
|
// foreground. In some of the operations, to save memory and time the input
|
|
// image is modified by the algorithms. If you need to preserve the input
|
|
// image, make a copy of it before calling these functions. Dilation and
|
|
// erosion functions do not have the requirement that the boundary pixels of
|
|
// the binary image inputs be zero.
|
|
|
|
namespace gte
|
|
{
|
|
class ImageUtility2
|
|
{
|
|
public:
|
|
// Compute the 4-connected components of a binary image. The input
|
|
// image is modified to avoid the cost of making a copy. On output,
|
|
// the image values are the labels for the components. The array
|
|
// components[k], k >= 1, contains the indices for the k-th component.
|
|
static void GetComponents4(Image2<int>& image,
|
|
std::vector<std::vector<size_t>>& components)
|
|
{
|
|
std::array<int, 4> neighbors;
|
|
image.GetNeighborhood(neighbors);
|
|
GetComponents(4, &neighbors[0], image, components);
|
|
}
|
|
|
|
// Compute the 8-connected components of a binary image. The input
|
|
// image is modified to avoid the cost of making a copy. On output,
|
|
// the image values are the labels for the components. The array
|
|
// components[k], k >= 1, contains the indices for the k-th component.
|
|
static void GetComponents8(Image2<int>& image,
|
|
std::vector<std::vector<size_t>>& components)
|
|
{
|
|
std::array<int, 8> neighbors;
|
|
image.GetNeighborhood(neighbors);
|
|
GetComponents(8, &neighbors[0], image, components);
|
|
}
|
|
|
|
// Compute a dilation with a structuring element consisting of the
|
|
// 4-connected neighbors of each pixel. The input image is binary
|
|
// with 0 for background and 1 for foreground. The output image must
|
|
// be an object different from the input image.
|
|
static void Dilate4(Image2<int> const& input, Image2<int>& output)
|
|
{
|
|
std::array<std::array<int, 2>, 4> neighbors;
|
|
input.GetNeighborhood(neighbors);
|
|
Dilate(input, 4, &neighbors[0], output);
|
|
}
|
|
|
|
// Compute a dilation with a structuring element consisting of the
|
|
// 8-connected neighbors of each pixel. The input image is binary
|
|
// with 0 for background and 1 for foreground. The output image must
|
|
// be an object different from the input image.
|
|
static void Dilate8(Image2<int> const& input, Image2<int>& output)
|
|
{
|
|
std::array<std::array<int, 2>, 8> neighbors;
|
|
input.GetNeighborhood(neighbors);
|
|
Dilate(input, 8, &neighbors[0], output);
|
|
}
|
|
|
|
// Compute a dilation with a structing element consisting of neighbors
|
|
// specified by offsets relative to the pixel. The input image is
|
|
// binary with 0 for background and 1 for foreground. The output
|
|
// image must be an object different from the input image.
|
|
static void Dilate(Image2<int> const& input, int numNeighbors,
|
|
std::array<int, 2> const* neighbors, Image2<int>& output)
|
|
{
|
|
LogAssert(&output != &input, "Input and output must be different.");
|
|
|
|
output = input;
|
|
|
|
// If the pixel at (x,y) is 1, then the pixels at (x+dx,y+dy) are
|
|
// set to 1 where (dx,dy) is in the 'neighbors' array. Boundary
|
|
// testing is used to avoid accessing out-of-range pixels.
|
|
int const dim0 = input.GetDimension(0);
|
|
int const dim1 = input.GetDimension(1);
|
|
for (int y = 0; y < dim1; ++y)
|
|
{
|
|
for (int x = 0; x < dim0; ++x)
|
|
{
|
|
if (input(x, y) == 1)
|
|
{
|
|
for (int j = 0; j < numNeighbors; ++j)
|
|
{
|
|
int xNbr = x + neighbors[j][0];
|
|
int yNbr = y + neighbors[j][1];
|
|
if (0 <= xNbr && xNbr < dim0 && 0 <= yNbr && yNbr < dim1)
|
|
{
|
|
output(xNbr, yNbr) = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compute an erosion with a structuring element consisting of the
|
|
// 4-connected neighbors of each pixel. The input image is binary
|
|
// with 0 for background and 1 for foreground. The output image must
|
|
// be an object different from the input image. If zeroExterior is
|
|
// true, the image exterior is assumed to be 0, so 1-valued boundary
|
|
// pixels are set to 0; otherwise, boundary pixels are set to 0 only
|
|
// when they have neighboring image pixels that are 0.
|
|
static void Erode4(Image2<int> const& input, bool zeroExterior, Image2<int>& output)
|
|
{
|
|
std::array<std::array<int, 2>, 4> neighbors;
|
|
input.GetNeighborhood(neighbors);
|
|
Erode(input, zeroExterior, 4, &neighbors[0], output);
|
|
}
|
|
|
|
// Compute an erosion with a structuring element consisting of the
|
|
// 8-connected neighbors of each pixel. The input image is binary
|
|
// with 0 for background and 1 for foreground. The output image must
|
|
// be an object different from the input image. If zeroExterior is
|
|
// true, the image exterior is assumed to be 0, so 1-valued boundary
|
|
// pixels are set to 0; otherwise, boundary pixels are set to 0 only
|
|
// when they have neighboring image pixels that are 0.
|
|
static void Erode8(Image2<int> const& input, bool zeroExterior, Image2<int>& output)
|
|
{
|
|
std::array<std::array<int, 2>, 8> neighbors;
|
|
input.GetNeighborhood(neighbors);
|
|
Erode(input, zeroExterior, 8, &neighbors[0], output);
|
|
}
|
|
|
|
// Compute an erosion with a structuring element consisting of
|
|
// neighbors specified by offsets relative to the pixel. The input
|
|
// image is binary with 0 for background and 1 for foreground. The
|
|
// output image must be an object different from the input image. If
|
|
// zeroExterior is true, the image exterior is assumed to be 0, so
|
|
// 1-valued boundary pixels are set to 0; otherwise, boundary pixels
|
|
// are set to 0 only when they have neighboring image pixels that
|
|
// are 0.
|
|
static void Erode(Image2<int> const& input, bool zeroExterior,
|
|
int numNeighbors, std::array<int, 2> const* neighbors, Image2<int>& output)
|
|
{
|
|
LogAssert(&output != &input, "Input and output must be different.");
|
|
|
|
output = input;
|
|
|
|
// If the pixel at (x,y) is 1, it is changed to 0 when at least
|
|
// one neighbor (x+dx,y+dy) is 0, where (dx,dy) is in the
|
|
// 'neighbors' array.
|
|
int const dim0 = input.GetDimension(0);
|
|
int const dim1 = input.GetDimension(1);
|
|
for (int y = 0; y < dim1; ++y)
|
|
{
|
|
for (int x = 0; x < dim0; ++x)
|
|
{
|
|
if (input(x, y) == 1)
|
|
{
|
|
for (int j = 0; j < numNeighbors; ++j)
|
|
{
|
|
int xNbr = x + neighbors[j][0];
|
|
int yNbr = y + neighbors[j][1];
|
|
if (0 <= xNbr && xNbr < dim0 && 0 <= yNbr && yNbr < dim1)
|
|
{
|
|
if (input(xNbr, yNbr) == 0)
|
|
{
|
|
output(x, y) = 0;
|
|
break;
|
|
}
|
|
}
|
|
else if (zeroExterior)
|
|
{
|
|
output(x, y) = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compute an opening with a structuring element consisting of the
|
|
// 4-connected neighbors of each pixel. The input image is binary
|
|
// with 0 for background and 1 for foreground. The output image must
|
|
// be an object different from the input image. If zeroExterior is
|
|
// true, the image exterior is assumed to consist of 0-valued pixels;
|
|
// otherwise, the image exterior is assumed to consist of 1-valued
|
|
// pixels.
|
|
static void Open4(Image2<int> const& input, bool zeroExterior, Image2<int>& output)
|
|
{
|
|
Image2<int> temp(input.GetDimension(0), input.GetDimension(1));
|
|
Erode4(input, zeroExterior, temp);
|
|
Dilate4(temp, output);
|
|
}
|
|
|
|
// Compute an opening with a structuring element consisting of the
|
|
// 8-connected neighbors of each pixel. The input image is binary
|
|
// with 0 for background and 1 for foreground. The output image must
|
|
// be an object different from the input image. If zeroExterior is
|
|
// true, the image exterior is assumed to consist of 0-valued pixels;
|
|
// otherwise, the image exterior is assumed to consist of 1-valued
|
|
// pixels.
|
|
static void Open8(Image2<int> const& input, bool zeroExterior, Image2<int>& output)
|
|
{
|
|
Image2<int> temp(input.GetDimension(0), input.GetDimension(1));
|
|
Erode8(input, zeroExterior, temp);
|
|
Dilate8(temp, output);
|
|
}
|
|
|
|
// Compute an opening with a structuring element consisting of
|
|
// neighbors specified by offsets relative to the pixel. The input
|
|
// image is binary with 0 for background and 1 for foreground. The
|
|
// output image must be an object different from the input image. If
|
|
// zeroExterior is true, the image exterior is assumed to consist of
|
|
// 0-valued pixels; otherwise, the image exterior is assumed to
|
|
// consist of 1-valued pixels.
|
|
static void Open(Image2<int> const& input, bool zeroExterior,
|
|
int numNeighbors, std::array<int, 2> const* neighbors, Image2<int>& output)
|
|
{
|
|
Image2<int> temp(input.GetDimension(0), input.GetDimension(1));
|
|
Erode(input, zeroExterior, numNeighbors, neighbors, temp);
|
|
Dilate(temp, numNeighbors, neighbors, output);
|
|
}
|
|
|
|
// Compute a closing with a structuring element consisting of the
|
|
// 4-connected neighbors of each pixel. The input image is binary
|
|
// with 0 for background and 1 for foreground. The output image must
|
|
// be an object different from the input image. If zeroExterior is
|
|
// true, the image exterior is assumed to consist of 0-valued pixels;
|
|
// otherwise, the image exterior is assumed to consist of 1-valued
|
|
// pixels.
|
|
static void Close4(Image2<int> const& input, bool zeroExterior, Image2<int>& output)
|
|
{
|
|
Image2<int> temp(input.GetDimension(0), input.GetDimension(1));
|
|
Dilate4(input, temp);
|
|
Erode4(temp, zeroExterior, output);
|
|
}
|
|
|
|
// Compute a closing with a structuring element consisting of the
|
|
// 8-connected neighbors of each pixel. The input image is binary
|
|
// with 0 for background and 1 for foreground. The output image must
|
|
// be an object different from the input image. If zeroExterior is
|
|
// true, the image exterior is assumed to consist of 0-valued pixels;
|
|
// otherwise, the image exterior is assumed to consist of 1-valued
|
|
// pixels.
|
|
static void Close8(Image2<int> const& input, bool zeroExterior, Image2<int>& output)
|
|
{
|
|
Image2<int> temp(input.GetDimension(0), input.GetDimension(1));
|
|
Dilate8(input, temp);
|
|
Erode8(temp, zeroExterior, output);
|
|
}
|
|
|
|
// Compute a closing with a structuring element consisting of
|
|
// neighbors specified by offsets relative to the pixel. The input
|
|
// image is binary with 0 for background and 1 for foreground. The
|
|
// output image must be an object different from the input image. If
|
|
// zeroExterior is true, the image exterior is assumed to consist of
|
|
// 0-valued pixels; otherwise, the image exterior is assumed to
|
|
// consist of 1-valued pixels.
|
|
static void Close(Image2<int> const& input, bool zeroExterior,
|
|
int numNeighbors, std::array<int, 2> const* neighbors, Image2<int>& output)
|
|
{
|
|
Image2<int> temp(input.GetDimension(0), input.GetDimension(1));
|
|
Dilate(input, numNeighbors, neighbors, temp);
|
|
Erode(temp, zeroExterior, numNeighbors, neighbors, output);
|
|
}
|
|
|
|
// Locate a pixel and walk around the edge of a component. The input
|
|
// (x,y) is where the search starts for a nonzero pixel. If (x,y) is
|
|
// outside the component, the walk is around the outside the
|
|
// component. If the component has a hole and (x,y) is inside that
|
|
// hole, the walk is around the boundary surrounding the hole. The
|
|
// function returns 'true' on a success walk. The return value is
|
|
// 'false' when no boundary was found from the starting (x,y).
|
|
static bool ExtractBoundary(int x, int y, Image2<int>& image, std::vector<size_t>& boundary)
|
|
{
|
|
// Find a first boundary pixel.
|
|
size_t const numPixels = image.GetNumPixels();
|
|
size_t i;
|
|
for (i = image.GetIndex(x, y); i < numPixels; ++i)
|
|
{
|
|
if (image[i])
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (i == numPixels)
|
|
{
|
|
// No boundary pixel found.
|
|
return false;
|
|
}
|
|
|
|
std::array<int, 8> const dx = { -1, 0, +1, +1, +1, 0, -1, -1 };
|
|
std::array<int, 8> const dy = { -1, -1, -1, 0, +1, +1, +1, 0 };
|
|
|
|
// Create a new point list that contains the first boundary point.
|
|
boundary.push_back(i);
|
|
|
|
// The direction from background 0 to boundary pixel 1 is
|
|
// (dx[7],dy[7]).
|
|
std::array<int, 2> coord = image.GetCoordinates(i);
|
|
int x0 = coord[0], y0 = coord[1];
|
|
int cx = x0, cy = y0;
|
|
int nx = x0 - 1, ny = y0, dir = 7;
|
|
|
|
// Traverse the boundary in clockwise order. Mark visited pixels
|
|
// as 2.
|
|
image(cx, cy) = 2;
|
|
bool notDone = true;
|
|
while (notDone)
|
|
{
|
|
int j, nbr;
|
|
for (j = 0, nbr = dir; j < 8; ++j, nbr = (nbr + 1) % 8)
|
|
{
|
|
nx = cx + dx[nbr];
|
|
ny = cy + dy[nbr];
|
|
if (image(nx, ny)) // next boundary pixel found
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (j == 8)
|
|
{
|
|
// (cx,cy) is isolated
|
|
notDone = false;
|
|
continue;
|
|
}
|
|
|
|
if (nx == x0 && ny == y0)
|
|
{
|
|
// boundary traversal completed
|
|
notDone = false;
|
|
continue;
|
|
}
|
|
|
|
// (nx,ny) is next boundary point, add point to list. Note
|
|
// that the index for the pixel is computed for the original
|
|
// image, not for the larger temporary image.
|
|
boundary.push_back(image.GetIndex(nx, ny));
|
|
|
|
// Mark visited pixels as 2.
|
|
image(nx, ny) = 2;
|
|
|
|
// Start search for next point.
|
|
cx = nx;
|
|
cy = ny;
|
|
dir = (j + 5 + dir) % 8;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Use a depth-first search for filling a 4-connected region. This is
|
|
// nonrecursive, simulated by using a heap-allocated "stack". The
|
|
// input (x,y) is the seed point that starts the fill.
|
|
template <typename PixelType>
|
|
static void FloodFill4(Image2<PixelType>& image, int x, int y,
|
|
PixelType foreColor, PixelType backColor)
|
|
{
|
|
// Test for a valid seed.
|
|
int const dim0 = image.GetDimension(0);
|
|
int const dim1 = image.GetDimension(1);
|
|
if (x < 0 || x >= dim0 || y < 0 || y >= dim1)
|
|
{
|
|
// The seed point is outside the image domain, so there is
|
|
// nothing to fill.
|
|
return;
|
|
}
|
|
|
|
// Allocate the maximum amount of space needed for the stack.
|
|
// An empty stack has top == -1.
|
|
size_t const numPixels = image.GetNumPixels();
|
|
std::vector<int> xStack(numPixels), yStack(numPixels);
|
|
|
|
// Push seed point onto stack if it has the background color. All
|
|
// points pushed onto stack have background color backColor.
|
|
int top = 0;
|
|
xStack[top] = x;
|
|
yStack[top] = y;
|
|
|
|
while (top >= 0) // stack is not empty
|
|
{
|
|
// Read top of stack. Do not pop since we need to return to
|
|
// this top value later to restart the fill in a different
|
|
// direction.
|
|
x = xStack[top];
|
|
y = yStack[top];
|
|
|
|
// Fill the pixel.
|
|
image(x, y) = foreColor;
|
|
|
|
int xp1 = x + 1;
|
|
if (xp1 < dim0 && image(xp1, y) == backColor)
|
|
{
|
|
// Push pixel with background color.
|
|
++top;
|
|
xStack[top] = xp1;
|
|
yStack[top] = y;
|
|
continue;
|
|
}
|
|
|
|
int xm1 = x - 1;
|
|
if (0 <= xm1 && image(xm1, y) == backColor)
|
|
{
|
|
// Push pixel with background color.
|
|
++top;
|
|
xStack[top] = xm1;
|
|
yStack[top] = y;
|
|
continue;
|
|
}
|
|
|
|
int yp1 = y + 1;
|
|
if (yp1 < dim1 && image(x, yp1) == backColor)
|
|
{
|
|
// Push pixel with background color.
|
|
++top;
|
|
xStack[top] = x;
|
|
yStack[top] = yp1;
|
|
continue;
|
|
}
|
|
|
|
int ym1 = y - 1;
|
|
if (0 <= ym1 && image(x, ym1) == backColor)
|
|
{
|
|
// Push pixel with background color.
|
|
++top;
|
|
xStack[top] = x;
|
|
yStack[top] = ym1;
|
|
continue;
|
|
}
|
|
|
|
// Done in all directions, pop and return to search other
|
|
// directions of predecessor.
|
|
--top;
|
|
}
|
|
}
|
|
|
|
// Compute the L1-distance transform of the binary image. The function
|
|
// returns the maximum distance and a point at which the maximum
|
|
// distance is attained.
|
|
static void GetL1Distance(Image2<int>& image, int& maxDistance, int& xMax, int& yMax)
|
|
{
|
|
int const dim0 = image.GetDimension(0);
|
|
int const dim1 = image.GetDimension(1);
|
|
int const dim0m1 = dim0 - 1;
|
|
int const dim1m1 = dim1 - 1;
|
|
|
|
// Use a grass-fire approach, computing distance from boundary to
|
|
// interior one pass at a time.
|
|
bool changeMade = true;
|
|
int distance;
|
|
for (distance = 1, xMax = 0, yMax = 0; changeMade; ++distance)
|
|
{
|
|
changeMade = false;
|
|
int distanceP1 = distance + 1;
|
|
for (int y = 1; y < dim1m1; ++y)
|
|
{
|
|
for (int x = 1; x < dim0m1; ++x)
|
|
{
|
|
if (image(x, y) == distance)
|
|
{
|
|
if (image(x - 1, y) >= distance
|
|
&& image(x + 1, y) >= distance
|
|
&& image(x, y - 1) >= distance
|
|
&& image(x, y + 1) >= distance)
|
|
{
|
|
image(x, y) = distanceP1;
|
|
xMax = x;
|
|
yMax = y;
|
|
changeMade = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
maxDistance = --distance;
|
|
}
|
|
|
|
// Compute the L2-distance transform of the binary image. The maximum
|
|
// distance should not be larger than 100, so you have to ensure this
|
|
// is the case for the input image. The function returns the maximum
|
|
// distance and a point at which the maximum distance is attained.
|
|
// Comments about the algorithm are in the source file.
|
|
static void GetL2Distance(Image2<int> const& image, float& maxDistance,
|
|
int& xMax, int& yMax, Image2<float>& transform)
|
|
{
|
|
// This program calculates the Euclidean distance transform of a
|
|
// binary input image. The adaptive algorithm is guaranteed to
|
|
// give exact distances for all distances < 100. The algorithm
|
|
// was provided John Gauch at University of Kansas. The following
|
|
// is a quote:
|
|
///
|
|
// The basic idea is similar to a EDT described recently in PAMI
|
|
// by Laymarie from McGill. By keeping the dx and dy offset to
|
|
// the nearest edge (feature) point in the image, we can search to
|
|
// see which dx dy is closest to a given point by examining a set
|
|
// of neighbors. The Laymarie method (and Borgfors) look at a
|
|
// fixed 3x3 or 5x5 neighborhood and call it a day. What we did
|
|
// was calculate (painfully) what neighborhoods you need to look
|
|
// at to guarentee that the exact distance is obtained. Thus,
|
|
// you will see in the code, that we L2Check the current distance
|
|
// and depending on what we have so far, we extend the search
|
|
// region. Since our algorithm for L2Checking the exactness of
|
|
// each neighborhood is on the order N^4, we have only gone to
|
|
// N=100. In theory, you could make this large enough to get all
|
|
// distances exact. We have implemented the algorithm to get all
|
|
// distances < 100 to be exact.
|
|
int const dim0 = image.GetDimension(0);
|
|
int const dim1 = image.GetDimension(1);
|
|
int const dim0m1 = dim0 - 1;
|
|
int const dim1m1 = dim1 - 1;
|
|
int x, y, distance;
|
|
|
|
// Create and initialize intermediate images.
|
|
Image2<int> xNear(dim0, dim1);
|
|
Image2<int> yNear(dim0, dim1);
|
|
Image2<int> dist(dim0, dim1);
|
|
for (y = 0; y < dim1; ++y)
|
|
{
|
|
for (x = 0; x < dim0; ++x)
|
|
{
|
|
if (image(x, y) != 0)
|
|
{
|
|
xNear(x, y) = 0;
|
|
yNear(x, y) = 0;
|
|
dist(x, y) = std::numeric_limits<int>::max();
|
|
}
|
|
else
|
|
{
|
|
xNear(x, y) = x;
|
|
yNear(x, y) = y;
|
|
dist(x, y) = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
int const K1 = 1;
|
|
int const K2 = 169; // 13^2
|
|
int const K3 = 961; // 31^2
|
|
int const K4 = 2401; // 49^2
|
|
int const K5 = 5184; // 72^2
|
|
|
|
// Pass in the ++ direction.
|
|
for (y = 0; y < dim1; ++y)
|
|
{
|
|
for (x = 0; x < dim0; ++x)
|
|
{
|
|
distance = dist(x, y);
|
|
if (distance > K1)
|
|
{
|
|
L2Check(x, y, -1, 0, xNear, yNear, dist);
|
|
L2Check(x, y, -1, -1, xNear, yNear, dist);
|
|
L2Check(x, y, 0, -1, xNear, yNear, dist);
|
|
}
|
|
if (distance > K2)
|
|
{
|
|
L2Check(x, y, -2, -1, xNear, yNear, dist);
|
|
L2Check(x, y, -1, -2, xNear, yNear, dist);
|
|
}
|
|
if (distance > K3)
|
|
{
|
|
L2Check(x, y, -3, -1, xNear, yNear, dist);
|
|
L2Check(x, y, -3, -2, xNear, yNear, dist);
|
|
L2Check(x, y, -2, -3, xNear, yNear, dist);
|
|
L2Check(x, y, -1, -3, xNear, yNear, dist);
|
|
}
|
|
if (distance > K4)
|
|
{
|
|
L2Check(x, y, -4, -1, xNear, yNear, dist);
|
|
L2Check(x, y, -4, -3, xNear, yNear, dist);
|
|
L2Check(x, y, -3, -4, xNear, yNear, dist);
|
|
L2Check(x, y, -1, -4, xNear, yNear, dist);
|
|
}
|
|
if (distance > K5)
|
|
{
|
|
L2Check(x, y, -5, -1, xNear, yNear, dist);
|
|
L2Check(x, y, -5, -2, xNear, yNear, dist);
|
|
L2Check(x, y, -5, -3, xNear, yNear, dist);
|
|
L2Check(x, y, -5, -4, xNear, yNear, dist);
|
|
L2Check(x, y, -4, -5, xNear, yNear, dist);
|
|
L2Check(x, y, -2, -5, xNear, yNear, dist);
|
|
L2Check(x, y, -3, -5, xNear, yNear, dist);
|
|
L2Check(x, y, -1, -5, xNear, yNear, dist);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pass in -- direction.
|
|
for (y = dim1m1; y >= 0; --y)
|
|
{
|
|
for (x = dim0m1; x >= 0; --x)
|
|
{
|
|
distance = dist(x, y);
|
|
if (distance > K1)
|
|
{
|
|
L2Check(x, y, 1, 0, xNear, yNear, dist);
|
|
L2Check(x, y, 1, 1, xNear, yNear, dist);
|
|
L2Check(x, y, 0, 1, xNear, yNear, dist);
|
|
}
|
|
if (distance > K2)
|
|
{
|
|
L2Check(x, y, 2, 1, xNear, yNear, dist);
|
|
L2Check(x, y, 1, 2, xNear, yNear, dist);
|
|
}
|
|
if (distance > K3)
|
|
{
|
|
L2Check(x, y, 3, 1, xNear, yNear, dist);
|
|
L2Check(x, y, 3, 2, xNear, yNear, dist);
|
|
L2Check(x, y, 2, 3, xNear, yNear, dist);
|
|
L2Check(x, y, 1, 3, xNear, yNear, dist);
|
|
}
|
|
if (distance > K4)
|
|
{
|
|
L2Check(x, y, 4, 1, xNear, yNear, dist);
|
|
L2Check(x, y, 4, 3, xNear, yNear, dist);
|
|
L2Check(x, y, 3, 4, xNear, yNear, dist);
|
|
L2Check(x, y, 1, 4, xNear, yNear, dist);
|
|
}
|
|
if (distance > K5)
|
|
{
|
|
L2Check(x, y, 5, 1, xNear, yNear, dist);
|
|
L2Check(x, y, 5, 2, xNear, yNear, dist);
|
|
L2Check(x, y, 5, 3, xNear, yNear, dist);
|
|
L2Check(x, y, 5, 4, xNear, yNear, dist);
|
|
L2Check(x, y, 4, 5, xNear, yNear, dist);
|
|
L2Check(x, y, 2, 5, xNear, yNear, dist);
|
|
L2Check(x, y, 3, 5, xNear, yNear, dist);
|
|
L2Check(x, y, 1, 5, xNear, yNear, dist);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pass in the +- direction.
|
|
for (y = dim1m1; y >= 0; --y)
|
|
{
|
|
for (x = 0; x < dim0; ++x)
|
|
{
|
|
distance = dist(x, y);
|
|
if (distance > K1)
|
|
{
|
|
L2Check(x, y, -1, 0, xNear, yNear, dist);
|
|
L2Check(x, y, -1, 1, xNear, yNear, dist);
|
|
L2Check(x, y, 0, 1, xNear, yNear, dist);
|
|
}
|
|
if (distance > K2)
|
|
{
|
|
L2Check(x, y, -2, 1, xNear, yNear, dist);
|
|
L2Check(x, y, -1, 2, xNear, yNear, dist);
|
|
}
|
|
if (distance > K3)
|
|
{
|
|
L2Check(x, y, -3, 1, xNear, yNear, dist);
|
|
L2Check(x, y, -3, 2, xNear, yNear, dist);
|
|
L2Check(x, y, -2, 3, xNear, yNear, dist);
|
|
L2Check(x, y, -1, 3, xNear, yNear, dist);
|
|
}
|
|
if (distance > K4)
|
|
{
|
|
L2Check(x, y, -4, 1, xNear, yNear, dist);
|
|
L2Check(x, y, -4, 3, xNear, yNear, dist);
|
|
L2Check(x, y, -3, 4, xNear, yNear, dist);
|
|
L2Check(x, y, -1, 4, xNear, yNear, dist);
|
|
}
|
|
if (distance > K5)
|
|
{
|
|
L2Check(x, y, -5, 1, xNear, yNear, dist);
|
|
L2Check(x, y, -5, 2, xNear, yNear, dist);
|
|
L2Check(x, y, -5, 3, xNear, yNear, dist);
|
|
L2Check(x, y, -5, 4, xNear, yNear, dist);
|
|
L2Check(x, y, -4, 5, xNear, yNear, dist);
|
|
L2Check(x, y, -2, 5, xNear, yNear, dist);
|
|
L2Check(x, y, -3, 5, xNear, yNear, dist);
|
|
L2Check(x, y, -1, 5, xNear, yNear, dist);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pass in the -+ direction.
|
|
for (y = 0; y < dim1; ++y)
|
|
{
|
|
for (x = dim0m1; x >= 0; --x)
|
|
{
|
|
distance = dist(x, y);
|
|
if (distance > K1)
|
|
{
|
|
L2Check(x, y, 1, 0, xNear, yNear, dist);
|
|
L2Check(x, y, 1, -1, xNear, yNear, dist);
|
|
L2Check(x, y, 0, -1, xNear, yNear, dist);
|
|
}
|
|
if (distance > K2)
|
|
{
|
|
L2Check(x, y, 2, -1, xNear, yNear, dist);
|
|
L2Check(x, y, 1, -2, xNear, yNear, dist);
|
|
}
|
|
if (distance > K3)
|
|
{
|
|
L2Check(x, y, 3, -1, xNear, yNear, dist);
|
|
L2Check(x, y, 3, -2, xNear, yNear, dist);
|
|
L2Check(x, y, 2, -3, xNear, yNear, dist);
|
|
L2Check(x, y, 1, -3, xNear, yNear, dist);
|
|
}
|
|
if (distance > K4)
|
|
{
|
|
L2Check(x, y, 4, -1, xNear, yNear, dist);
|
|
L2Check(x, y, 4, -3, xNear, yNear, dist);
|
|
L2Check(x, y, 3, -4, xNear, yNear, dist);
|
|
L2Check(x, y, 1, -4, xNear, yNear, dist);
|
|
}
|
|
if (distance > K5)
|
|
{
|
|
L2Check(x, y, 5, -1, xNear, yNear, dist);
|
|
L2Check(x, y, 5, -2, xNear, yNear, dist);
|
|
L2Check(x, y, 5, -3, xNear, yNear, dist);
|
|
L2Check(x, y, 5, -4, xNear, yNear, dist);
|
|
L2Check(x, y, 4, -5, xNear, yNear, dist);
|
|
L2Check(x, y, 2, -5, xNear, yNear, dist);
|
|
L2Check(x, y, 3, -5, xNear, yNear, dist);
|
|
L2Check(x, y, 1, -5, xNear, yNear, dist);
|
|
}
|
|
}
|
|
}
|
|
|
|
xMax = 0;
|
|
yMax = 0;
|
|
maxDistance = 0.0f;
|
|
for (y = 0; y < dim1; ++y)
|
|
{
|
|
for (x = 0; x < dim0; ++x)
|
|
{
|
|
float fdistance = std::sqrt((float)dist(x, y));
|
|
if (fdistance > maxDistance)
|
|
{
|
|
maxDistance = fdistance;
|
|
xMax = x;
|
|
yMax = y;
|
|
}
|
|
transform(x, y) = fdistance;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compute a skeleton of a binary image. Boundary pixels are trimmed
|
|
// from the object one layer at a time based on their adjacency to
|
|
// interior pixels. At each step the connectivity and cycles of the
|
|
// object are preserved. The skeleton overwrites the contents of the
|
|
// input image.
|
|
static void GetSkeleton(Image2<int>& image)
|
|
{
|
|
int const dim0 = image.GetDimension(0);
|
|
int const dim1 = image.GetDimension(1);
|
|
|
|
// Trim pixels, mark interior as 4.
|
|
bool notDone = true;
|
|
while (notDone)
|
|
{
|
|
if (MarkInterior(image, 4, Interior4))
|
|
{
|
|
// No interior pixels, trimmed set is at most 2-pixels
|
|
// thick.
|
|
notDone = false;
|
|
continue;
|
|
}
|
|
|
|
if (ClearInteriorAdjacent(image, 4))
|
|
{
|
|
// All remaining interior pixels are either articulation
|
|
// points or part of blobs whose boundary pixels are all
|
|
// articulation points. An example of the latter case is
|
|
// shown below. The background pixels are marked with '.'
|
|
// rather than '0' for readability. The interior pixels
|
|
// are marked with '4' and the boundary pixels are marked
|
|
// with '1'.
|
|
//
|
|
// .........
|
|
// .....1...
|
|
// ..1.1.1..
|
|
// .1.141...
|
|
// ..14441..
|
|
// ..1441.1.
|
|
// .1.11.1..
|
|
// ..1..1...
|
|
// .........
|
|
//
|
|
// This is a pathological problem where there are many
|
|
// small holes (0-pixel with north, south, west, and east
|
|
// neighbors all 1-pixels) that your application can try
|
|
// to avoid by an initial pass over the image to fill in
|
|
// such holes. Of course, you do have problems with
|
|
// checkerboard patterns...
|
|
notDone = false;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Trim pixels, mark interior as 3.
|
|
notDone = true;
|
|
while (notDone)
|
|
{
|
|
if (MarkInterior(image, 3, Interior3))
|
|
{
|
|
// No interior pixels, trimmed set is at most 2-pixels
|
|
// thick.
|
|
notDone = false;
|
|
continue;
|
|
}
|
|
|
|
if (ClearInteriorAdjacent(image, 3))
|
|
{
|
|
// All remaining 3-values can be safely removed since they
|
|
// are not articulation points and the removal will not
|
|
// cause new holes.
|
|
for (int y = 0; y < dim1; ++y)
|
|
{
|
|
for (int x = 0; x < dim0; ++x)
|
|
{
|
|
if (image(x, y) == 3 && !IsArticulation(image, x, y))
|
|
{
|
|
image(x, y) = 0;
|
|
}
|
|
}
|
|
}
|
|
notDone = false;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Trim pixels, mark interior as 2.
|
|
notDone = true;
|
|
while (notDone)
|
|
{
|
|
if (MarkInterior(image, 2, Interior2))
|
|
{
|
|
// No interior pixels, trimmed set is at most 1-pixel
|
|
// thick. Call it a skeleton.
|
|
notDone = false;
|
|
continue;
|
|
}
|
|
|
|
if (ClearInteriorAdjacent(image, 2))
|
|
{
|
|
// Removes 2-values that are not articulation points.
|
|
for (int y = 0; y < dim1; ++y)
|
|
{
|
|
for (int x = 0; x < dim0; ++x)
|
|
{
|
|
if (image(x, y) == 2 && !IsArticulation(image, x, y))
|
|
{
|
|
image(x, y) = 0;
|
|
}
|
|
}
|
|
}
|
|
notDone = false;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Make the skeleton a binary image.
|
|
size_t const numPixels = image.GetNumPixels();
|
|
for (size_t i = 0; i < numPixels; ++i)
|
|
{
|
|
if (image[i] != 0)
|
|
{
|
|
image[i] = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// In the remaining public member functions, the callback represents
|
|
// the action you want applied to each pixel as it is visited.
|
|
|
|
// Visit pixels in a (2*thick+1)x(2*thick+1) square centered at (x,y).
|
|
static void DrawThickPixel(int x, int y, int thick,
|
|
std::function<void(int, int)> const& callback)
|
|
{
|
|
for (int dy = -thick; dy <= thick; ++dy)
|
|
{
|
|
for (int dx = -thick; dx <= thick; ++dx)
|
|
{
|
|
callback(x + dx, y + dy);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Visit pixels using Bresenham's line drawing algorithm.
|
|
static void DrawLine(int x0, int y0, int x1, int y1,
|
|
std::function<void(int, int)> const& callback)
|
|
{
|
|
// Starting point of line.
|
|
int x = x0, y = y0;
|
|
|
|
// Direction of line.
|
|
int dx = x1 - x0, dy = y1 - y0;
|
|
|
|
// Increment or decrement depending on direction of line.
|
|
int sx = (dx > 0 ? 1 : (dx < 0 ? -1 : 0));
|
|
int sy = (dy > 0 ? 1 : (dy < 0 ? -1 : 0));
|
|
|
|
// Decision parameters for pixel selection.
|
|
if (dx < 0)
|
|
{
|
|
dx = -dx;
|
|
}
|
|
if (dy < 0)
|
|
{
|
|
dy = -dy;
|
|
}
|
|
int ax = 2 * dx, ay = 2 * dy;
|
|
int decX, decY;
|
|
|
|
// Determine largest direction component, single-step related
|
|
// variable.
|
|
int maxValue = dx, var = 0;
|
|
if (dy > maxValue)
|
|
{
|
|
var = 1;
|
|
}
|
|
|
|
// Traverse Bresenham line.
|
|
switch (var)
|
|
{
|
|
case 0: // Single-step in x-direction.
|
|
decY = ay - dx;
|
|
for (/**/; /**/; x += sx, decY += ay)
|
|
{
|
|
callback(x, y);
|
|
|
|
// Take Bresenham step.
|
|
if (x == x1)
|
|
{
|
|
break;
|
|
}
|
|
if (decY >= 0)
|
|
{
|
|
decY -= ax;
|
|
y += sy;
|
|
}
|
|
}
|
|
break;
|
|
case 1: // Single-step in y-direction.
|
|
decX = ax - dy;
|
|
for (/**/; /**/; y += sy, decX += ax)
|
|
{
|
|
callback(x, y);
|
|
|
|
// Take Bresenham step.
|
|
if (y == y1)
|
|
{
|
|
break;
|
|
}
|
|
if (decX >= 0)
|
|
{
|
|
decX -= ay;
|
|
x += sx;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Visit pixels using Bresenham's circle drawing algorithm. Set
|
|
// 'solid' to false for drawing only the circle. Set 'solid' to true
|
|
// to draw all pixels on and inside the circle.
|
|
static void DrawCircle(int xCenter, int yCenter, int radius, bool solid,
|
|
std::function<void(int, int)> const& callback)
|
|
{
|
|
int x, y, dec;
|
|
|
|
if (solid)
|
|
{
|
|
int xValue, yMin, yMax, i;
|
|
for (x = 0, y = radius, dec = 3 - 2 * radius; x <= y; ++x)
|
|
{
|
|
xValue = xCenter + x;
|
|
yMin = yCenter - y;
|
|
yMax = yCenter + y;
|
|
for (i = yMin; i <= yMax; ++i)
|
|
{
|
|
callback(xValue, i);
|
|
}
|
|
|
|
xValue = xCenter - x;
|
|
for (i = yMin; i <= yMax; ++i)
|
|
{
|
|
callback(xValue, i);
|
|
}
|
|
|
|
xValue = xCenter + y;
|
|
yMin = yCenter - x;
|
|
yMax = yCenter + x;
|
|
for (i = yMin; i <= yMax; ++i)
|
|
{
|
|
callback(xValue, i);
|
|
}
|
|
|
|
xValue = xCenter - y;
|
|
for (i = yMin; i <= yMax; ++i)
|
|
{
|
|
callback(xValue, i);
|
|
}
|
|
|
|
if (dec >= 0)
|
|
{
|
|
dec += -4 * (y--) + 4;
|
|
}
|
|
dec += 4 * x + 6;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (x = 0, y = radius, dec = 3 - 2 * radius; x <= y; ++x)
|
|
{
|
|
callback(xCenter + x, yCenter + y);
|
|
callback(xCenter + x, yCenter - y);
|
|
callback(xCenter - x, yCenter + y);
|
|
callback(xCenter - x, yCenter - y);
|
|
callback(xCenter + y, yCenter + x);
|
|
callback(xCenter + y, yCenter - x);
|
|
callback(xCenter - y, yCenter + x);
|
|
callback(xCenter - y, yCenter - x);
|
|
|
|
if (dec >= 0)
|
|
{
|
|
dec += -4 * (y--) + 4;
|
|
}
|
|
dec += 4 * x + 6;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Visit pixels in a rectangle of the specified dimensions. Set
|
|
// 'solid' to false for drawing only the rectangle. Set 'solid' to
|
|
// true to draw all pixels on and inside the rectangle.
|
|
static void DrawRectangle(int xMin, int yMin, int xMax, int yMax,
|
|
bool solid, std::function<void(int, int)> const& callback)
|
|
{
|
|
int x, y;
|
|
|
|
if (solid)
|
|
{
|
|
for (y = yMin; y <= yMax; ++y)
|
|
{
|
|
for (x = xMin; x <= xMax; ++x)
|
|
{
|
|
callback(x, y);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (x = xMin; x <= xMax; ++x)
|
|
{
|
|
callback(x, yMin);
|
|
callback(x, yMax);
|
|
}
|
|
for (y = yMin + 1; y <= yMax - 1; ++y)
|
|
{
|
|
callback(xMin, y);
|
|
callback(xMax, y);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Visit the pixels using Bresenham's algorithm for the axis-aligned
|
|
// ellipse ((x-xc)/a)^2 + ((y-yc)/b)^2 = 1, where xCenter is xc,
|
|
// yCenter is yc, xExtent is a, and yExtent is b.
|
|
static void DrawEllipse(int xCenter, int yCenter, int xExtent, int yExtent,
|
|
std::function<void(int, int)> const& callback)
|
|
{
|
|
int xExtSqr = xExtent * xExtent, yExtSqr = yExtent * yExtent;
|
|
int x, y, dec;
|
|
|
|
x = 0;
|
|
y = yExtent;
|
|
dec = 2 * yExtSqr + xExtSqr * (1 - 2 * yExtent);
|
|
for (/**/; yExtSqr * x <= xExtSqr * y; ++x)
|
|
{
|
|
callback(xCenter + x, yCenter + y);
|
|
callback(xCenter - x, yCenter + y);
|
|
callback(xCenter + x, yCenter - y);
|
|
callback(xCenter - x, yCenter - y);
|
|
|
|
if (dec >= 0)
|
|
{
|
|
dec += 4 * xExtSqr * (1 - y);
|
|
--y;
|
|
}
|
|
dec += yExtSqr * (4 * x + 6);
|
|
}
|
|
if (y == 0 && x < xExtent)
|
|
{
|
|
// The discretization caused us to reach the y-axis before the
|
|
// x-values reached the ellipse vertices. Draw a solid line
|
|
// along the x-axis to those vertices.
|
|
for (/**/; x <= xExtent; ++x)
|
|
{
|
|
callback(xCenter + x, yCenter);
|
|
callback(xCenter - x, yCenter);
|
|
}
|
|
return;
|
|
}
|
|
|
|
x = xExtent;
|
|
y = 0;
|
|
dec = 2 * xExtSqr + yExtSqr * (1 - 2 * xExtent);
|
|
for (/**/; xExtSqr * y <= yExtSqr * x; ++y)
|
|
{
|
|
callback(xCenter + x, yCenter + y);
|
|
callback(xCenter - x, yCenter + y);
|
|
callback(xCenter + x, yCenter - y);
|
|
callback(xCenter - x, yCenter - y);
|
|
|
|
if (dec >= 0)
|
|
{
|
|
dec += 4 * yExtSqr * (1 - x);
|
|
--x;
|
|
}
|
|
dec += xExtSqr * (4 * y + 6);
|
|
}
|
|
if (x == 0 && y < yExtent)
|
|
{
|
|
// The discretization caused us to reach the x-axis before the
|
|
// y-values reached the ellipse vertices. Draw a solid line
|
|
// along the y-axis to those vertices.
|
|
for (/**/; y <= yExtent; ++y)
|
|
{
|
|
callback(xCenter, yCenter + y);
|
|
callback(xCenter, yCenter - y);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Use a depth-first search for filling a 4-connected region. This is
|
|
// nonrecursive, simulated by using a heap-allocated "stack". The
|
|
// input (x,y) is the seed point that starts the fill. The x-value is
|
|
// in {0..xSize-1} and the y-value is in {0..ySize-1}.
|
|
template <typename PixelType>
|
|
static void DrawFloodFill4(int x, int y, int xSize, int ySize,
|
|
PixelType foreColor, PixelType backColor,
|
|
std::function<void(int, int, PixelType)> const& setCallback,
|
|
std::function<PixelType(int, int)> const& getCallback)
|
|
{
|
|
// Test for a valid seed.
|
|
if (x < 0 || x >= xSize || y < 0 || y >= ySize)
|
|
{
|
|
// The seed point is outside the image domain, so nothing to
|
|
// fill.
|
|
return;
|
|
}
|
|
|
|
// Allocate the maximum amount of space needed for the stack. An
|
|
// empty stack has top == -1.
|
|
int const numPixels = xSize * ySize;
|
|
std::vector<int> xStack(numPixels), yStack(numPixels);
|
|
|
|
// Push seed point onto stack if it has the background color. All
|
|
// points pushed onto stack have background color backColor.
|
|
int top = 0;
|
|
xStack[top] = x;
|
|
yStack[top] = y;
|
|
|
|
while (top >= 0) // stack is not empty
|
|
{
|
|
// Read top of stack. Do not pop since we need to return to
|
|
// this top value later to restart the fill in a different
|
|
// direction.
|
|
x = xStack[top];
|
|
y = yStack[top];
|
|
|
|
// Fill the pixel.
|
|
setCallback(x, y, foreColor);
|
|
|
|
int xp1 = x + 1;
|
|
if (xp1 < xSize && getCallback(xp1, y) == backColor)
|
|
{
|
|
// Push pixel with background color.
|
|
++top;
|
|
xStack[top] = xp1;
|
|
yStack[top] = y;
|
|
continue;
|
|
}
|
|
|
|
int xm1 = x - 1;
|
|
if (0 <= xm1 && getCallback(xm1, y) == backColor)
|
|
{
|
|
// Push pixel with background color.
|
|
++top;
|
|
xStack[top] = xm1;
|
|
yStack[top] = y;
|
|
continue;
|
|
}
|
|
|
|
int yp1 = y + 1;
|
|
if (yp1 < ySize && getCallback(x, yp1) == backColor)
|
|
{
|
|
// Push pixel with background color.
|
|
++top;
|
|
xStack[top] = x;
|
|
yStack[top] = yp1;
|
|
continue;
|
|
}
|
|
|
|
int ym1 = y - 1;
|
|
if (0 <= ym1 && getCallback(x, ym1) == backColor)
|
|
{
|
|
// Push pixel with background color.
|
|
++top;
|
|
xStack[top] = x;
|
|
yStack[top] = ym1;
|
|
continue;
|
|
}
|
|
|
|
// Done in all directions, pop and return to search other
|
|
// directions of the predecessor.
|
|
--top;
|
|
}
|
|
}
|
|
|
|
private:
|
|
// Connected component labeling using depth-first search.
|
|
static void GetComponents(int numNeighbors, int const* delta,
|
|
Image2<int>& image, std::vector<std::vector<size_t>>& components)
|
|
{
|
|
size_t const numPixels = image.GetNumPixels();
|
|
std::vector<int> numElements(numPixels);
|
|
std::vector<size_t> vstack(numPixels);
|
|
size_t i, numComponents = 0;
|
|
int label = 2;
|
|
for (i = 0; i < numPixels; ++i)
|
|
{
|
|
if (image[i] == 1)
|
|
{
|
|
int top = -1;
|
|
vstack[++top] = i;
|
|
|
|
int& count = numElements[numComponents + 1];
|
|
count = 0;
|
|
while (top >= 0)
|
|
{
|
|
size_t v = vstack[top];
|
|
image[v] = -1;
|
|
int j;
|
|
for (j = 0; j < numNeighbors; ++j)
|
|
{
|
|
size_t adj = v + delta[j];
|
|
if (image[adj] == 1)
|
|
{
|
|
vstack[++top] = adj;
|
|
break;
|
|
}
|
|
}
|
|
if (j == numNeighbors)
|
|
{
|
|
image[v] = label;
|
|
++count;
|
|
--top;
|
|
}
|
|
}
|
|
|
|
++numComponents;
|
|
++label;
|
|
}
|
|
}
|
|
|
|
if (numComponents > 0)
|
|
{
|
|
components.resize(numComponents + 1);
|
|
for (i = 1; i <= numComponents; ++i)
|
|
{
|
|
components[i].resize(numElements[i]);
|
|
numElements[i] = 0;
|
|
}
|
|
|
|
for (i = 0; i < numPixels; ++i)
|
|
{
|
|
int value = image[i];
|
|
if (value != 0)
|
|
{
|
|
// Labels started at 2 to support the depth-first
|
|
// search, so they need to be decremented for the
|
|
// correct labels.
|
|
image[i] = --value;
|
|
components[value][numElements[value]] = i;
|
|
++numElements[value];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Support for GetL2Distance.
|
|
static void L2Check(int x, int y, int dx, int dy, Image2<int>& xNear,
|
|
Image2<int>& yNear, Image2<int>& dist)
|
|
{
|
|
int const dim0 = dist.GetDimension(0);
|
|
int const dim1 = dist.GetDimension(1);
|
|
int xp = x + dx, yp = y + dy;
|
|
if (0 <= xp && xp < dim0 && 0 <= yp && yp < dim1)
|
|
{
|
|
if (dist(xp, yp) < dist(x, y))
|
|
{
|
|
int dx0 = xNear(xp, yp) - x;
|
|
int dy0 = yNear(xp, yp) - y;
|
|
int newDist = dx0 * dx0 + dy0 * dy0;
|
|
if (newDist < dist(x, y))
|
|
{
|
|
xNear(x, y) = xNear(xp, yp);
|
|
yNear(x, y) = yNear(xp, yp);
|
|
dist(x, y) = newDist;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Support for GetSkeleton.
|
|
static bool Interior2(Image2<int>& image, int x, int y)
|
|
{
|
|
bool b1 = (image(x, y - 1) != 0);
|
|
bool b3 = (image(x + 1, y) != 0);
|
|
bool b5 = (image(x, y + 1) != 0);
|
|
bool b7 = (image(x - 1, y) != 0);
|
|
return (b1 && b3) || (b3 && b5) || (b5 && b7) || (b7 && b1);
|
|
}
|
|
|
|
static bool Interior3(Image2<int>& image, int x, int y)
|
|
{
|
|
int numNeighbors = 0;
|
|
if (image(x - 1, y) != 0)
|
|
{
|
|
++numNeighbors;
|
|
}
|
|
if (image(x + 1, y) != 0)
|
|
{
|
|
++numNeighbors;
|
|
}
|
|
if (image(x, y - 1) != 0)
|
|
{
|
|
++numNeighbors;
|
|
}
|
|
if (image(x, y + 1) != 0)
|
|
{
|
|
++numNeighbors;
|
|
}
|
|
return numNeighbors == 3;
|
|
}
|
|
|
|
static bool Interior4(Image2<int>& image, int x, int y)
|
|
{
|
|
return image(x - 1, y) != 0
|
|
&& image(x + 1, y) != 0
|
|
&& image(x, y - 1) != 0
|
|
&& image(x, y + 1) != 0;
|
|
}
|
|
|
|
static bool MarkInterior(Image2<int>& image, int value,
|
|
bool (*function)(Image2<int>&, int, int))
|
|
{
|
|
int const dim0 = image.GetDimension(0);
|
|
int const dim1 = image.GetDimension(1);
|
|
bool noInterior = true;
|
|
for (int y = 0; y < dim1; ++y)
|
|
{
|
|
for (int x = 0; x < dim0; ++x)
|
|
{
|
|
if (image(x, y) > 0)
|
|
{
|
|
if (function(image, x, y))
|
|
{
|
|
image(x, y) = value;
|
|
noInterior = false;
|
|
}
|
|
else
|
|
{
|
|
image(x, y) = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return noInterior;
|
|
}
|
|
|
|
static bool IsArticulation(Image2<int>& image, int x, int y)
|
|
{
|
|
static std::array<int, 256> const articulation =
|
|
{
|
|
0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,
|
|
0,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0,
|
|
0,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0,
|
|
0,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0,
|
|
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
|
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
|
0,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0,
|
|
0,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0,
|
|
0,0,0,0,1,1,0,0,1,1,0,0,1,1,0,0,
|
|
1,1,1,1,1,1,1,1,1,1,0,0,1,1,0,0,
|
|
0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,1,1,0,0,1,1,0,0,1,1,0,0,
|
|
1,1,1,1,1,1,1,1,1,1,0,0,1,1,0,0,
|
|
0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0
|
|
};
|
|
|
|
// Converts 8 neighbors of pixel (x,y) to an 8-bit value,
|
|
// bit = 1 iff pixel is set.
|
|
int byteMask = 0;
|
|
if (image(x - 1, y - 1) != 0)
|
|
{
|
|
byteMask |= 0x01;
|
|
}
|
|
if (image(x, y - 1) != 0)
|
|
{
|
|
byteMask |= 0x02;
|
|
}
|
|
if (image(x + 1, y - 1) != 0)
|
|
{
|
|
byteMask |= 0x04;
|
|
}
|
|
if (image(x + 1, y) != 0)
|
|
{
|
|
byteMask |= 0x08;
|
|
}
|
|
if (image(x + 1, y + 1) != 0)
|
|
{
|
|
byteMask |= 0x10;
|
|
}
|
|
if (image(x, y + 1) != 0)
|
|
{
|
|
byteMask |= 0x20;
|
|
}
|
|
if (image(x - 1, y + 1) != 0)
|
|
{
|
|
byteMask |= 0x40;
|
|
}
|
|
if (image(x - 1, y) != 0)
|
|
{
|
|
byteMask |= 0x80;
|
|
}
|
|
|
|
return articulation[byteMask] == 1;
|
|
}
|
|
|
|
static bool ClearInteriorAdjacent(Image2<int>& image, int value)
|
|
{
|
|
int const dim0 = image.GetDimension(0);
|
|
int const dim1 = image.GetDimension(1);
|
|
bool noRemoval = true;
|
|
for (int y = 0; y < dim1; ++y)
|
|
{
|
|
for (int x = 0; x < dim0; ++x)
|
|
{
|
|
if (image(x, y) == 1)
|
|
{
|
|
bool interiorAdjacent =
|
|
image(x - 1, y - 1) == value ||
|
|
image(x, y - 1) == value ||
|
|
image(x + 1, y - 1) == value ||
|
|
image(x + 1, y) == value ||
|
|
image(x + 1, y + 1) == value ||
|
|
image(x, y + 1) == value ||
|
|
image(x - 1, y + 1) == value ||
|
|
image(x - 1, y) == value;
|
|
|
|
if (interiorAdjacent && !IsArticulation(image, x, y))
|
|
{
|
|
image(x, y) = 0;
|
|
noRemoval = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return noRemoval;
|
|
}
|
|
};
|
|
}
|
|
|