距离变换实施

时间:2016-04-10 13:57:16

标签: c# graphics computer-vision

我在C#中寻找一个带有位图实例输入的距离变换实现。

1 个答案:

答案 0 :(得分:0)

这是一个有效的解决方案,展开值因图像大小而异。它此时没有8bpp位图输出。

Sample-Output..

public class DistanceField
{
    public int Spread = 8;
    public int WhiteColor = Color.White.ToArgb();

    public DistanceField()
    {}

    /**
     * Caclulate the squared distance between two points
     * 
     * @param x1 The x coordinate of the first point
     * @param y1 The y coordiante of the first point
     * @param x2 The x coordinate of the second point
     * @param y2 The y coordinate of the second point
     * @return The squared distance between the two points
    */
    private int squareDist(int x1, int y1, int x2, int y2)
    {
        int dx = x1 - x2;
        int dy = y1 - y2;
        return dx * dx + dy * dy;
    }

    /**
     * Process the image into a distance field.
     * 
     * The input image should be binary (black/white), but if not, see {@link #isInside(int)}.
     *  
     * The returned image is a factor of {@code upscale} smaller than {@code inImage}.
     * Opaque pixels more than {@link #spread} away in the output image from white remain opaque;
     * transparent pixels more than {@link #spread} away in the output image from black remain transparent.
     * In between, we get a smooth transition from opaque to transparent, with an alpha value of 0.5
     * when we are exactly on the edge.
     * 
     * @param inImage the image to process. 
     * @return the distance field image
    */
    public Bitmap GenerateDistanceField(Bitmap inImage)
    {
        Bitmap outImage = new Bitmap(inImage.Width, inImage.Height, PixelFormat.Format32bppArgb);

        // Note: coordinates reversed to mimic storage of BufferedImage, for memory locality
        bool[,] bitmap = new bool[inImage.Height, inImage.Width];
        for(int y = 0; y < inImage.Height; ++y)
        {
            for(int x = 0; x < inImage.Width; ++x)
            {
                bitmap[y, x] = isInside(inImage.GetPixel(x, y).ToArgb());
            }
        }

        for (int y = 0; y < inImage.Height; ++y)
        {
            for (int x = 0; x < inImage.Width; ++x)
            {
                float signedDistance = findSignedDistance(x, y, ref bitmap);
                outImage.SetPixel(x, y, Color.FromArgb(distanceToRGB(signedDistance)));
            }
        }

       return outImage;
    }

    public Bitmap ConvertToGrayScale(Bitmap oldbmp)
    {
        using (var ms = new MemoryStream())
        {
            oldbmp.Save(ms, ImageFormat.Gif);
            ms.Position = 0;
            return (Bitmap)Image.FromStream(ms);
        }
    }

    /**
     * Returns {@code true} if the color is considered as the "inside" of the image,
     * {@code false} if considered "outside".
     * 
     * <p> Any color with one of its color channels at least 128
     * <em>and</em> its alpha channel at least 128 is considered "inside".
    */
    private bool isInside(int rgb)
    {
        return (rgb & 0x808080) != 0 && (rgb & 0x80000000) != 0;
    }


    /**
     * Returns the signed distance for a given point.
     * 
     * For points "inside", this is the distance to the closest "outside" pixel.
     * For points "outside", this is the <em>negative</em> distance to the closest "inside" pixel.
     * If no pixel of different color is found within a radius of {@code spread}, returns
     * the {@code -spread} or {@code spread}, respectively.
     * 
     * @param centerX the x coordinate of the center point 
     * @param centerY the y coordinate of the center point
     * @param bitmap the array representation of an image, {@code true} representing "inside"
     * @return the signed distance 
    */
    private float findSignedDistance(int centerX, int centerY, ref bool[,] bitmap)
    {
        int width = bitmap.GetLength(1);
        int height = bitmap.GetLength(0);
        bool baseVal = bitmap[centerY, centerX];

        int delta = this.Spread;
        int startX = Math.Max(0, centerX - delta);
        int endX = Math.Min(width - 1, centerX + delta);
        int startY = Math.Max(0, centerY - delta);
        int endY = Math.Min(height - 1, centerY + delta);

        int closestSquareDist = delta * delta;

        for (int y = startY; y <= endY; ++y)
        {
            for (int x = startX; x <= endX; ++x)
            {
                if (baseVal != bitmap[y, x])
                {
                    int sqDist = squareDist(centerX, centerY, x, y);
                    if (sqDist < closestSquareDist)
                    {
                        closestSquareDist = sqDist;
                    }
                }
            }
        }

        float closestDist = (float)Math.Sqrt(closestSquareDist);
        return (baseVal ? 1 : -1) * Math.Min(closestDist, this.Spread);
    }

    /**
     * For a distance as returned by {@link #findSignedDistance}, returns the corresponding "RGB" (really ARGB) color value.
     *  
     * @param signedDistance the signed distance of a pixel
     * @return an ARGB color value suitable for {@link BufferedImage#setRGB}.
     */
    private int distanceToRGB(float signedDistance)
    {
        float alpha = 0.5f + 0.5f * (signedDistance / this.Spread);
        alpha = Math.Min(1, Math.Max(0, alpha)); // compensate for rounding errors
        int alphaByte = (int)(alpha * 0xFF); // no unsigned byte in Java :(
        return (alphaByte << 24) | (this.WhiteColor & 0xFFFFFF);
    }
}