Optional standard gradient calculation in Sobel filter
Opened this issue · 0 comments
GoogleCodeExporter commented
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