8000 Optional standard gradient calculation in Sobel filter · Issue #407 · culiniac/aforge · GitHub
[go: up one dir, main page]

Skip to content

Optional standard gradient calculation in Sobel filter #407

Open

Description

@GoogleCodeExporter
Instead

        Math.Sqrt( gx * gx + gy * gy )

, framework evaluates

        Math.Min( 255, Math.Abs(gx) + Math.Abs(gy) )

This produces differences in two ways

1.- Simpliffied expression is always a bit bigger. Differences are noticiable 
but not important.

gx + gy    <=>    Sqrt( (gx + gy)^2 )    <=>    Sqrt( gx^2 + 2*gx*gy + gy^2 )


2.- Evaluating "Min" before normalization, produces important differences in 
result.

We can see differences in this image that combines 4 results. Results has been 
inverted and thresholded (235) to clearly show differences  

* top-left: Min + Sum (current AForge implementation)
* top-right: Min + Sqrt
* bottom-left: Sum
* bottom-right: Sqrt

https://dl.dropboxusercontent.com/u/49778953/test_sobel_results.JPG

Differences between results clearly show that smoothing effect is lost when 
using "Min"

This is my test image
https://dl.dropboxusercontent.com/u/49778953/test_sobel.jpg


I suggest to introduce a new Property "StandardGradientCalculation" with 
default value = False. This way, behaviour will not change in production 
systems.


This is the function i've used. Note the use of 

float[,] gradients = new float[source.Height, source.Width];

, to avoid byte overflow


public unsafe void ProcessFilter(AForge.Imaging.UnmanagedImage source, 
AForge.Imaging.UnmanagedImage destination, Rectangle rect,
                                 bool takeMin, bool useSum)
{
    int[,] kernelX = {{-1, +0, +1},
                      {-2, +0, +2},
                      {-1, +0, +1}};

    int[,] kernelY = {{-1, -2, -1},
                      {+0, +0, +0},
                      {+1, +2, +1}};

    // 3x3 kernel => kernelX.GetUpperBound(0) = 2
    int kernelSize = kernelX.GetUpperBound(0) + 1;
    if (kernelSize % 2 == 0)
    {
        throw new ArgumentException("Size of kernel matrix must have odd");
    }
    int kernelRadius = (kernelSize - 1) / 2;
    byte srcBytesPerPixel = (byte)(System.Drawing.Bitmap.GetPixelFormatSize(source.PixelFormat) / 8);
    byte dstBytesPerPixel = (byte)(System.Drawing.Bitmap.GetPixelFormatSize(destination.PixelFormat) / 8);

    // processing start and stop X,Y positions                
    int startX = rect.Left + kernelRadius;
    int startY = rect.Top + kernelRadius;
    int stopX = startX + rect.Width - 2 * kernelRadius;
    int stopY = startY + rect.Height - 2 * kernelRadius;

    int dstStride = destination.Stride;
    int srcStride = source.Stride;

    byte* srcCurrent = (byte*)source.ImageData.ToPointer() + srcStride * startY + srcBytesPerPixel * startX;
    byte* dstCurrent = (byte*)destination.ImageData.ToPointer() + dstStride * startY + dstBytesPerPixel * startX;

    byte* src = null;
    byte* dst = null;

    // variables for gradient calculation
    float g = 0;
    float max = 0;
    float[,] gradients = new float[source.Height, source.Width];

    // for each line
    for (int y = startY; y < stopY; y++)
    {
        // for each pixel
        src = srcCurrent;
        dst = dstCurrent;
        for (int x = startX; x < stopX; x++)
        {
            //All cells for clarity: kernelX[0, 1], kernelX[1, 1] and kernelX[2, 1] are 0 and can be removed in expression
            int gx = kernelX[0, 0] * src[-srcStride - 1] + kernelX[0, 1] * src[-srcStride]  + kernelX[0, 2] * src[-srcStride + 1] +
                     kernelX[1, 0] * src[-1]             + kernelX[1, 1] * src[0]           + kernelX[1, 2] * src[1] +
                     kernelX[2, 0] * src[srcStride - 1]  + kernelX[2, 1] * src[srcStride]   + kernelX[2, 2] * src[srcStride + 1];

            //All cells for clarity: kernelY[1, 0], kernelY[1, 1] and kernelY[1, 2] are 0 and can be removed in expression
            int gy = kernelY[0, 0] * src[-srcStride - 1] + kernelY[0, 1] * src[-srcStride] + kernelY[0, 2] * src[-srcStride + 1] +
                     kernelY[1, 0] * src[-1]             + kernelY[1, 1] * src[0]          + kernelY[1, 2] * src[+1] +
                     kernelY[2, 0] * src[srcStride - 1]  + kernelY[2, 1] * src[srcStride]  + kernelY[2, 2] * src[srcStride + 1];

            if (useSum)
                g = Math.Abs(gy) + Math.Abs(gx);
            else
                g = (float)Math.Sqrt(gx * gx + gy * gy);

            if (takeMin)
                g = Math.Min(255, g);

            if (g > max)
                max = g;

            gradients[y, x] = g;
            src += srcBytesPerPixel;
            dst += dstBytesPerPixel;
        }
        srcCurrent += srcStride;
        dstCurrent += dstStride;
    }

    // make the second pass for intensity scaling and byte casting
    float factor = (float)255.0 / max;
    dstCurrent = (byte*)destination.ImageData.ToPointer() + dstStride * startY + dstBytesPerPixel * startX;
    // for each line
    for (int y = startY; y < stopY; y++)
    {
        // for each pixel
        dst = dstCurrent;
        for (int x = startX; x < stopX; x++)
        {
            *dst = (byte)(factor * gradients[y, x]);
            dst += dstBytesPerPixel;
        }
        dstCurrent += dstStride;
    }

    // draw black rectangle to remove those pixels, which were not processed
    // (this needs to be done for those cases, when filter is applied "in place" -
    // source image is modified instead of creating new copy)
    AForge.Imaging.Drawing.Rectangle(destination, rect, Color.Black);
}

Original issue reported on code.google.com by ricardoh...@gmail.com on 15 Jun 2015 at 2:08

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      0