50% OFF!!!

Wednesday, October 6, 2010

C# | How to deskew an image

I found this code on CodeProject site:
http://www.codeproject.com/KB/graphics/Deskew_an_Image.aspx
By mackenb | 25 Apr 2006

The article describes an algorithm to calculate the skew angle of an image.

This is converted to C#: (tested and working)
The Code:
using System.Drawing;
using System.Drawing.Imaging;
using System;
using System.Diagnostics;


public class gmseDeskew
{
    // Representation of a line in the image.
    public class HougLine
    {
        //' Count of points in the line.
        public int Count;
        //' Index in Matrix.
        public int Index;
        //' The line is represented as all x,y that solve y*cos(alpha)-x*sin(alpha)=d
        public double Alpha;
        public double d;
    }

    // The Bitmap
    Bitmap cBmp;
    // The range of angles to search for lines
    double cAlphaStart = -20;
    double cAlphaStep = 0.2;
    int cSteps = 40 * 5;
    // Precalculation of sin and cos.
    double[] cSinA;
    double[] cCosA;
    // Range of d
    double cDMin;
    double cDStep = 1;
    int cDCount;
    // Count of points that fit in a line.
    int[] cHMatrix;

    // calculate the skew angle of the image cBmp

    public double GetSkewAngle()
    {
        HougLine[] hl;
        int i;
        double sum = 0;
        int count = 0;

        //' Hough Transformation
        Calc();
        //' Top 20 of the detected lines in the image.
        hl = GetTop(20);
        //' Average angle of the lines
        for (i = 0; i < 19; i++)      
        {          
             sum += hl[i].Alpha;
             count += 1;
         }
         return sum / count;
     }

     //    ' Calculate the Count lines in the image with most points.
    private HougLine[] GetTop(int Count)
     {
         HougLine[] hl;
         int j;
         HougLine tmp;
         int AlphaIndex, dIndex;
         hl = new HougLine[Count];
         for (int i = 0; i < Count; i++)
         {
             hl[i] = new HougLine();
         }
         for (int i = 0; i < cHMatrix.Length - 1; i++)
         {
             if (cHMatrix[i] > hl[Count - 1].Count)
            {
                hl[Count - 1].Count = cHMatrix[i];
                hl[Count - 1].Index = i;
                j = Count - 1;
                while (j > 0 && hl[j].Count > hl[j - 1].Count)
                {
                    tmp = hl[j];
                    hl[j] = hl[j - 1];
                    hl[j - 1] = tmp;
                    j -= 1;
                }
            }
        }
        for (int i = 0; i < Count; i++)
         {
             dIndex = hl[i].Index / cSteps;
             AlphaIndex = hl[i].Index - dIndex * cSteps;
             hl[i].Alpha = GetAlpha(AlphaIndex);
             hl[i].d = dIndex + cDMin;
         }
         return hl;
     }
     public void New(Bitmap bmp)
     {
         cBmp = bmp;
     }

     //    ' Hough Transforamtion:
     private void Calc()
     {
         int x;
         int y;
         int hMin = cBmp.Height / 4;
         int hMax = cBmp.Height * 3 / 4;
         Init();
         for (y = hMin; y < hMax; y++)
         {
             for (x = 1; x < cBmp.Width - 2; x++)
             {
                 //' Only lower edges are considered.
                 if (IsBlack(x, y) == true)
                 {
                     if (IsBlack(x, y + 1) == false)
                     {
                         Calc(x, y);
                     }
                 }
             }
         }
     }

     //    ' Calculate all lines through the point (x,y).
     private void Calc(int x, int y)
     {
         double d;
         int dIndex;
         int Index;
         for (int alpha = 0; alpha < cSteps - 1; alpha++)
         {
             d = y * cCosA[alpha] - x * cSinA[alpha];
             dIndex = (int)CalcDIndex(d);
             Index = dIndex * cSteps + alpha;

             try
             {
                 cHMatrix[Index] += 1;
             }
             catch (Exception ex)
             {
                 Debug.WriteLine(ex.ToString());
             }
         }
     }
     private double CalcDIndex(double d)
     {
         return Convert.ToInt32(d - cDMin);
     }
     private bool IsBlack(int x, int y)
     {
         Color c;
         double luminance;
         c = cBmp.GetPixel(x, y);
         luminance = (c.R * 0.299) + (c.G * 0.587) + (c.B * 0.114);
         return luminance < 140;
     }
     private void Init()
     {
         double angle;
         //' Precalculation of sin and cos.
         cSinA = new double[cSteps - 1];
         cCosA = new double[cSteps - 1];
         for (int i = 0; i < cSteps - 1; i++)
         {
             angle = GetAlpha(i) * Math.PI / 180.0;
             cSinA[i] = Math.Sin(angle);
             cCosA[i] = Math.Cos(angle);
         }
         //' Range of d
:         cDMin = -cBmp.Width;
         cDCount = (int)(2 * (cBmp.Width + cBmp.Height) / cDStep);
         cHMatrix = new int[cDCount * cSteps];
     }
     public double GetAlpha(int Index)
     {
         return cAlphaStart + Index * cAlphaStep;
     }     public static Bitmap RotateImage(Bitmap bmp, double angle)
     {
         Graphics g;
         Bitmap tmp = new Bitmap(bmp.Width, bmp.Height, PixelFormat.Format32bppRgb);
         tmp.SetResolution(bmp.HorizontalResolution, bmp.VerticalResolution);
         g = Graphics.FromImage(tmp);
         try       
         {
             g.FillRectangle(Brushes.White, 0, 0, bmp.Width, bmp.Height);
             g.RotateTransform((float)angle);
             g.DrawImage(bmp, 0, 0);
         }
         finally
         {
             g.Dispose();
         }
         return tmp;
     }
 }


enjoy...

Monday, October 4, 2010

Trim Image white-spaces (black&white) | C# Winforms

I searched for a simple code that preform "TRIMMING" to an image.
By "trimming" i mean, that if i have a LARGE image that contains information (painting/drawing...) only in small part of the image and all other is white-space (WHITE or BLACK pixels).

I needed to remove such whitespace and also to decrease image size.
Following three code examples:

Code #1 - Gets the NON-WHITE-SPACE bounds of an image
private Rectangle GetImageNonWhiteSpaceBounds(Bitmap p_img, bool p_isBlackWhitespace)
{
    Rectangle rect = new Rectangle();
    unsafe
    {
        BitmapData bdActual = null;
        try
        {
            int width = p_img.Width;
            int height = p_img.Height;

            bdActual = p_img.LockBits(new Rectangle(0, 0, width, height), System.Drawing.Imaging.ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
            Int32 i;
            Int32 pSize = 3;
            Byte whiteTrashold = (Byte)240;
            Byte blackTrashold = (Byte)10;
            Byte* sourceRow;

            Int32 minX = width;
            Int32 maxX = 0;
            Int32 minY = height;
            Int32 maxY = 0;
            bool isWhitepace;

            for (Int32 _y = 0; _y < height; ++_y)             {                 sourceRow = (Byte*)bdActual.Scan0 + (_y * bdActual.Stride);                 for (Int32 _x = 0; _x < width; ++_x)                 {                     i = _x * pSize;                     isWhitepace =                         (p_isBlackWhitespace && sourceRow[i] <= blackTrashold && sourceRow[i + 1] <= blackTrashold && sourceRow[i + 2] <= blackTrashold)                         ||                         (!p_isBlackWhitespace && sourceRow[i] >= whiteTrashold && sourceRow[i + 1] >= whiteTrashold && sourceRow[i + 2] >= whiteTrashold);

                    if (isWhitepace == false)
                    {
                        // NO whitespace!!!
                        minX = Math.Min(_x, minX);
                        maxX = Math.Max(_x, maxX);

                        minY = Math.Min(_y, minY);
                        maxY = Math.Max(_y, maxY);
                    }
                }
            }

            rect.X = minX;
            rect.Y = minY;
            rect.Width = maxX - minX;
            rect.Height = maxY - minY;
        }
        finally
        {
            if (bdActual != null)
            {
                p_img.UnlockBits(bdActual);
            }
        }
    }
    return rect;
}

Code #2 - Draw rectangle around the NON-WHITE-SPACE
private void DrawNonWhiteSpaceRectangle(Bitmap p_img, bool p_isBlackWhitespace, Color p_color)
{
    unsafe
    {
        BitmapData bdActual = null;
        try
        {
            int width = p_img.Width;
            int height = p_img.Height;

            bdActual = p_img.LockBits(new Rectangle(0, 0, width, height), System.Drawing.Imaging.ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
            Int32 i;
            Int32 pSize = 3;
            Byte whiteTrashold = (Byte)240;
            Byte blackTrashold = (Byte)10;
            Byte* sourceRow;

            Int32 minX = width;
            Int32 maxX = 0;
            Int32 minY = height;
            Int32 maxY = 0;
            bool isWhitepace;

            for (Int32 _y = 0; _y < height; ++_y)             {                 sourceRow = (Byte*)bdActual.Scan0 + (_y * bdActual.Stride);                 for (Int32 _x = 0; _x < width; ++_x)                 {                     i = _x * pSize;                     isWhitepace =                         (p_isBlackWhitespace && sourceRow[i] <= blackTrashold && sourceRow[i + 1] <= blackTrashold && sourceRow[i + 2] <= blackTrashold)                         ||                         (!p_isBlackWhitespace && sourceRow[i] >= whiteTrashold && sourceRow[i + 1] >= whiteTrashold && sourceRow[i + 2] >= whiteTrashold);

                    if (isWhitepace == false)
                    {
                        // NO whitespace!!!
                        minX = Math.Min(_x, minX);
                        maxX = Math.Max(_x, maxX);

                        minY = Math.Min(_y, minY);
                        maxY = Math.Max(_y, maxY);
                    }
                }
            }

            // draw rectagle:
            for (Int32 _y = minY; _y <= maxY; ++_y)             {                 sourceRow = (Byte*)bdActual.Scan0 + (_y * bdActual.Stride);                 i = minX * pSize;                 sourceRow[i] = (Byte)p_color.R;                 sourceRow[i + 1] = (Byte)p_color.G;                 sourceRow[i + 2] = (Byte)p_color.B;                 i = maxX * pSize;                 sourceRow[i] = (Byte)p_color.R;                 sourceRow[i + 1] = (Byte)p_color.G;                 sourceRow[i + 2] = (Byte)p_color.B;             }             Byte* sourceRowX1 = (Byte*)bdActual.Scan0 + (minY * bdActual.Stride);             Byte* sourceRowX2 = (Byte*)bdActual.Scan0 + (maxY * bdActual.Stride);             for (Int32 _x = minX; _x <= maxX; ++_x)             {                 i = _x * pSize;                 sourceRowX1[i] = (Byte)p_color.R;                 sourceRowX1[i + 1] = (Byte)p_color.G;                 sourceRowX1[i + 2] = (Byte)p_color.B;                 sourceRowX2[i] = (Byte)p_color.R;                 sourceRowX2[i + 1] = (Byte)p_color.G;                 sourceRowX2[i + 2] = (Byte)p_color.B;             }         }         finally         {             if (bdActual != null)             {                 p_img.UnlockBits(bdActual);             }         }     } }


Code #3 - Trim Image whitespaces
private Bitmap CropImageWhitespaces(Bitmap p_img, bool p_isBlackWhitespace)
{
    Rectangle rect = new Rectangle();
    unsafe
    {
        BitmapData bdActual = null;
        try
        {
            int width = p_img.Width;
            int height = p_img.Height;

            bdActual = p_img.LockBits(new Rectangle(0, 0, width, height), System.Drawing.Imaging.ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
            Int32 i;
            Int32 pSize = 3;
            Byte whiteTrashold = (Byte)240;
            Byte blackTrashold = (Byte)10;
            Byte* sourceRow;

            Int32 minX = width;
            Int32 maxX = 0;
            Int32 minY = height;
            Int32 maxY = 0;
            bool isWhitepace;

            for (Int32 _y = 0; _y < height; ++_y)             {                 sourceRow = (Byte*)bdActual.Scan0 + (_y * bdActual.Stride);                 for (Int32 _x = 0; _x < width; ++_x)                 {                     i = _x * pSize;                     isWhitepace =                         (p_isBlackWhitespace && sourceRow[i] <= blackTrashold && sourceRow[i + 1] <= blackTrashold && sourceRow[i + 2] <= blackTrashold)                         ||                         (!p_isBlackWhitespace && sourceRow[i] >= whiteTrashold && sourceRow[i + 1] >= whiteTrashold && sourceRow[i + 2] >= whiteTrashold);

                    if (isWhitepace == false)
                    {
                        // NO whitespace!!!
                        minX = Math.Min(_x, minX);
                        maxX = Math.Max(_x, maxX);

                        minY = Math.Min(_y, minY);
                        maxY = Math.Max(_y, maxY);
                    }
                }
            }

            rect.X = minX;
            rect.Y = minY;
            rect.Width = maxX - minX;
            rect.Height = maxY - minY;
        }
        finally
        {
            if (bdActual != null)
            {
                p_img.UnlockBits(bdActual);
            }
        }
    }

    return p_img.Clone(rect, p_img.PixelFormat);
}

I hope it will be helpful for you.
this code if fast and easy to use.