Skew angle checker for scanned documents.

The class implements document's skew checking algorithm, which is based on Hough line transformation. The algorithm is based on searching for text base lines - black line of text bottoms' followed by white line below.

The routine supposes that a white-background document is provided with black letters. The algorithm is not supposed for any type of objects, but for document images with text.

The range of angles to detect is controlled by MaxSkewToDetect property.

The filter accepts 8 bpp grayscale images for processing.

Sample usage:

// create instance of skew checker DocumentSkewChecker skewChecker = new DocumentSkewChecker( ); // get documents skew angle double angle = skewChecker.GetSkewAngle( documentImage ); // create rotation filter RotateBilinear rotationFilter = new RotateBilinear( -angle ); rotationFilter.FillColor = Color.White; // rotate image applying the filter Bitmap rotatedImage = rotationFilter.Apply( documentImage );

Initial image:

Deskewed image:

コード例 #1
0
    protected void surfRansacBlendStraight(List <Bitmap> imgs)
    {
        MatrixH homography;

        List <SpeededUpRobustFeaturePoint[]> surfPoints = new List <SpeededUpRobustFeaturePoint[]>();
        //Calculate all the Surf Points
        SpeededUpRobustFeaturesDetector surf = new SpeededUpRobustFeaturesDetector();
        double lastAngle = 0;

        for (int i = 0; i < imgs.Count; i++)
        {
            //Grayscale to find the edges and adjust the normal to point up
            AForge.Imaging.Filters.GrayscaleBT709 grayscale = new AForge.Imaging.Filters.GrayscaleBT709();
            AForge.Imaging.DocumentSkewChecker    skew      = new AForge.Imaging.DocumentSkewChecker();

            double angle = skew.GetSkewAngle(grayscale.Apply(imgs[i]));

            //Less than 5 deg change in angle to account for wobble, ignore big shifts
            if (Math.Abs(angle - lastAngle) < 5)
            {
                AForge.Imaging.Filters.RotateBilinear rotate = new AForge.Imaging.Filters.RotateBilinear(angle);
                rotate.FillColor = Color.FromArgb(0, 255, 255, 255);
                imgs[i]          = rotate.Apply(imgs[i]);
                lastAngle        = angle;
            }
            showImage(imgs[i]);
            surfPoints.Add(surf.ProcessImage(imgs[i]).ToArray());
        }


        Bitmap final = imgs[0];

        for (int i = 1; i < imgs.Count; i++)
        {
            SpeededUpRobustFeaturePoint[] surfFinal = surf.ProcessImage(final).ToArray();

            //Correlate the Harris pts between imgs
            KNearestNeighborMatching matcher = new KNearestNeighborMatching(5);
            matcher.Threshold = 0.05;

            IntPoint[][] matches = matcher.Match(surfFinal, surfPoints[i]);

            //Create the homography matrix using RANSAC
            RansacHomographyEstimator ransac = new RansacHomographyEstimator(0.015, 1);
            homography = ransac.Estimate(matches[0], matches[1]);

            Blend blend = new Blend(homography, final);
            blend.Gradient = true;
            final          = blend.Apply(imgs[i]);
        }

        //Smooth/Sharpen if I wanted to
        AForge.Imaging.Filters.Sharpen filter = new AForge.Imaging.Filters.Sharpen();
        //AForge.Imaging.Filters.Gaussian filter = new AForge.Imaging.Filters.Guassian(5);
        //filter.ApplyInPlace(final);

        showImage(final);
    }
コード例 #2
0
ファイル: DeskewFilter.cs プロジェクト: SuperJMN/Glass
 public BitmapSource Apply(BitmapSource image)
 {
     var filter = new DocumentSkewChecker();
     var bitmap = image.ToBitmap();
     var grayscale = new Grayscale(0.2125, 0.7154, 0.0721).Apply(bitmap);
     var angle = filter.GetSkewAngle(grayscale);
     var rotationFilter = new RotateBilinear(-angle);
     return rotationFilter.Apply(grayscale).ToBitmapImage();
 }
コード例 #3
0
    protected void fastHarrisRansacBlendStraight(List <Bitmap> imgs)
    {
        List <IntPoint[]> harrisPoints = new List <IntPoint[]>();
        MatrixH           homography;

        //Calculate all the Harris Points
        HarrisCornersDetector harris = new HarrisCornersDetector(0.03f, 10000f);

        for (int i = 0; i < imgs.Count; i++)
        {
            harrisPoints.Add(harris.ProcessImage(imgs[i]).ToArray());
        }

        Bitmap final = imgs[0];

        for (int i = 1; i < imgs.Count; i++)
        {
            //Convert my frames to grayscale so I can find and adjust the normal vectors
            AForge.Imaging.Filters.GrayscaleBT709 grayscale = new AForge.Imaging.Filters.GrayscaleBT709();
            AForge.Imaging.DocumentSkewChecker    skew      = new AForge.Imaging.DocumentSkewChecker();

            double finalAngle = skew.GetSkewAngle(grayscale.Apply(final));
            double imgAngle   = skew.GetSkewAngle(grayscale.Apply(imgs[i]));

            //Less than 5% to account for human error with rotations and wobbles
            if (Math.Abs(finalAngle - imgAngle) < 5)
            {
                AForge.Imaging.Filters.RotateBilinear rotate = new AForge.Imaging.Filters.RotateBilinear(finalAngle - imgAngle);
                rotate.FillColor = Color.FromArgb(0, 255, 255, 255);
                imgs[i]          = rotate.Apply(imgs[i]);

                //Update harris
                harrisPoints[i] = harris.ProcessImage(imgs[i]).ToArray();
            }

            IntPoint[] harrisFinal = harris.ProcessImage(final).ToArray();

            //Correlate the Harris pts between imgs
            CorrelationMatching matcher = new CorrelationMatching(5, final, imgs[i]);
            IntPoint[][]        matches = matcher.Match(harrisFinal, harrisPoints[i]);

            //Create the homography matrix using ransac
            RansacHomographyEstimator ransac = new RansacHomographyEstimator(0.025, 0.99);
            homography = ransac.Estimate(matches[0], matches[1]);

            Blend blend = new Blend(homography, final);
            blend.Gradient = true;
            final          = blend.Apply(imgs[i]);
        }

        showImage(final);
    }
コード例 #4
0
        public IEnumerable<Bitmap> Apply(Bitmap bitmap)
        {
            // assuming scanned background is white we need to invert for the algo to work
            var copy = new Invert().Apply(bitmap);

            copy = EnsureGrayscale(copy);
            new Threshold { ThresholdValue = 25 }.ApplyInPlace(copy);
            new FillHoles().ApplyInPlace(copy);

            var blobCounter = new BlobCounter
            {
                // set filtering options
                FilterBlobs = true,
                MinWidth = 50,
                MinHeight = 50,
            };

            blobCounter.ProcessImage(copy);
            var blobs = blobCounter.GetObjectsInformation();

            if (blobs.Any())
            {
                var invertedOriginal = new Invert().Apply(bitmap);
                foreach (var blob in blobs)
                {
                    // use inverted source to ensure correct edge colors
                    blobCounter.ExtractBlobsImage(invertedOriginal, blob, false);
                    var blobImage = blob.Image.ToManagedImage();

                    // straighten
                    var angle = new DocumentSkewChecker().GetSkewAngle(EnsureGrayscale(blobImage));
                    var rotationFilter = new RotateBilinear(-angle) { FillColor = Color.Black };
                    blobImage = rotationFilter.Apply(blobImage);

                    // crop
                    blobImage = new ExtractBiggestBlob().Apply(blobImage);

                    new Invert().ApplyInPlace(blobImage);
                    yield return blobImage;
                }
            }
            else
            {
                yield return bitmap;
            }
        }
コード例 #5
0
ファイル: Canvas.cs プロジェクト: ondister/Recog
        private void RotateImage()
        {
            DocumentSkewChecker skewChecker = new DocumentSkewChecker();
            skewChecker.MaxSkewToDetect = 45;
            double angle = skewChecker.GetSkewAngle(_recogimg);

            //Максимальный угол, при котором идет поворот - 45 градусов, после этого изображение не поворачивается
            if (Math.Abs(angle) < 45)
            {
                RotateBilinear rotationFilter = new RotateBilinear(-angle, true);
                rotationFilter.FillColor = Color.Black;
                _recogimg = rotationFilter.Apply(_recogimg);
                _markersimg = rotationFilter.Apply(_markersimg);
            }
        }
コード例 #6
0
ファイル: ImageProcess.cs プロジェクト: linuxbank/PLOCR
        // 기울어짐 바로잡기
        public static Bitmap skew(Bitmap source)
        {
            // create grayscale filter (BT709)
            Grayscale filter = new Grayscale(0.2125, 0.7154, 0.0721);       // 8비트 grayscale 로 바꾸고
            // apply the filter
            Bitmap grayImage = filter.Apply(source);

            // create instance of skew checker
            DocumentSkewChecker skewChecker = new DocumentSkewChecker();        // 8비트 grayscale 로 넣어줘야 함
            //    // get documents skew angle
            double angle = skewChecker.GetSkewAngle(grayImage);  // 기울어진 각도를 얻고

            Bitmap tmp = source;
            // convert to 24 bits per pixel
            source = ImageProcess.Clone(tmp, PixelFormat.Format24bppRgb);       // 로테이션 전에 24비트로 바꿔주고
            // delete old image
            tmp.Dispose();

            // create rotation filter
            RotateBilinear rotationFilter = new RotateBilinear(-angle);
            rotationFilter.FillColor = Color.White;
            // rotate image applying the filter
            Bitmap rotatedImage = rotationFilter.Apply(source);  // 원래 이미지를 가져다가 각도만큼 돌리고(원래 이미지는 24비트로 넣어줘야함)

            return rotatedImage;
        }
コード例 #7
0
ファイル: AForgeImageUtil.cs プロジェクト: renyh1013/dp2
        public static System.Drawing.Image AforgeAutoCrop(Bitmap selectedImage,
            DetectBorderParam param)
        {
            // 一些参数的默认值
            if (param.MinObjectWidth == 0)
                param.MinObjectWidth = 500;
            if (param.MinObjectHeight == 0)
                param.MinObjectHeight = 500;

            Bitmap autoCropImage = null;
            try
            {

                autoCropImage = selectedImage;
                // create grayscale filter (BT709)
                Grayscale filter = new Grayscale(0.2125, 0.7154, 0.0721);
                Bitmap grayImage = filter.Apply(autoCropImage);
                // create instance of skew checker
                DocumentSkewChecker skewChecker = new DocumentSkewChecker();
                // get documents skew angle
                double angle = skewChecker.GetSkewAngle(grayImage);
                // create rotation filter
                RotateBilinear rotationFilter = new RotateBilinear(-angle);
                rotationFilter.FillColor = Color.White;
                // rotate image applying the filter
                Bitmap rotatedImage = rotationFilter.Apply(grayImage);
                new ContrastStretch().ApplyInPlace(rotatedImage);
                new Threshold(100).ApplyInPlace(rotatedImage);
                BlobCounter bc = new BlobCounter();
                bc.FilterBlobs = true;
                bc.MinWidth = param.MinObjectWidth; //  500;
                bc.MinHeight = param.MinObjectHeight;   // 500;
                bc.ProcessImage(rotatedImage);
                Rectangle[] rects = bc.GetObjectsRectangles();

                if (rects.Length == 0)
                {
                    System.Windows.Forms.MessageBox.Show("No rectangle found in image ");
                }
                else if (rects.Length == 1)
                {
                    autoCropImage = rotatedImage.Clone(rects[0], rotatedImage.PixelFormat);
                }
                else if (rects.Length > 1)
                {
                    // get largets rect
                    Console.WriteLine("Using largest rectangle found in image ");
                    var r2 = rects.OrderByDescending(r => r.Height * r.Width).ToList();
                    autoCropImage = rotatedImage.Clone(r2[1], rotatedImage.PixelFormat);
                }
                else
                {
                    Console.WriteLine("Huh? on image ");
                }
            }
            catch (Exception ex)
            {
                // MessageBox.Show(ex.Message);
                throw ex;
            }

            return autoCropImage;
        }
コード例 #8
0
ファイル: AForgeImageUtil.cs プロジェクト: renyh1013/dp2
        public static bool GetSkewParam(Bitmap selectedImage,
            DetectBorderParam param,
            out double angle,
            out Rectangle rect)
        {
            // 一些参数的默认值
            if (param.MinObjectWidth == 0)
                param.MinObjectWidth = 500;
            if (param.MinObjectHeight == 0)
                param.MinObjectHeight = 500;

            Bitmap autoCropImage = null;
            try
            {

                autoCropImage = selectedImage;

#if NO
                // create grayscale filter (BT709)
                Grayscale filter = new Grayscale(0.2125, 0.7154, 0.0721);
                Bitmap grayImage = filter.Apply(autoCropImage);
#endif
                Bitmap grayImage = selectedImage.Clone(new Rectangle(0, 0, selectedImage.Width, selectedImage.Height),
                System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
                // create instance of skew checker
                DocumentSkewChecker skewChecker = new DocumentSkewChecker();
                // get documents skew angle
                angle = skewChecker.GetSkewAngle(grayImage);
                // create rotation filter
                RotateBilinear rotationFilter = new RotateBilinear(-angle);
                rotationFilter.FillColor = Color.Black; // .White;
                rotationFilter.KeepSize = true;
                // rotate image applying the filter
                Bitmap rotatedImage = rotationFilter.Apply(grayImage);
                new ContrastStretch().ApplyInPlace(rotatedImage);
                new Threshold(100).ApplyInPlace(rotatedImage);
                BlobCounter bc = new BlobCounter();
                bc.FilterBlobs = true;
                bc.MinWidth = param.MinObjectWidth; //  500;
                bc.MinHeight = param.MinObjectHeight;   // 500;
#if NO
                bc.MinWidth = 500;// grayImage.Width / 10;  // 500
                bc.MinHeight = 500;// grayImage.Height / 10; // 500
#endif
                bc.ProcessImage(rotatedImage);

                Rectangle[] rects = bc.GetObjectsRectangles();

                if (rects.Length == 0)
                {
                    // System.Windows.Forms.MessageBox.Show("No rectangle found in image ");
                    rect = new Rectangle(0, 0, 0, 0);
                    return false;
                }
                else if (rects.Length == 1)
                {
                    rect = rects[0];
                    // autoCropImage = rotatedImage.Clone(rects[0], rotatedImage.PixelFormat);
                }
                else if (rects.Length > 1)
                {
                    // TODO: 应该把这些矩形合并在一起
                    Rectangle first = new Rectangle(0, 0, 0, 0);
                    int i = 0;
                    foreach (Rectangle one in rects)
                    {
                        Debug.WriteLine("one=" + one.ToString());
                        if (i == 0)
                            first = one;
                        else
                            first = Merge(first, one);
                        i++;
                    }
                    rect = first;
                    Debug.WriteLine("result=" + rect.ToString());
#if NO

                    // get largets rect
                    Console.WriteLine("Using largest rectangle found in image ");
                    var r2 = rects.OrderByDescending(r => r.Height * r.Width).ToList();
                    rect = r2[1];
                    // autoCropImage = rotatedImage.Clone(r2[1], rotatedImage.PixelFormat);
#endif
                }
                else
                {
                    // Console.WriteLine("Huh? on image ");
                    rect = new Rectangle(0, 0, 0, 0);
                    return false;
                }

#if NO
                Blob[] blobs = bc.GetObjectsInformation();
                foreach (var blob in blobs)
                {
                    List<IntPoint> edgePoints = blobCounter.GetBlobsEdgePoints(blob);
                    List<IntPoint> cornerPoints;

                    // use the shape checker to extract the corner points
                    if (shapeChecker.IsQuadrilateral(edgePoints, out cornerPoints))
                    {
                        // only do things if the corners form a rectangle
                        if (shapeChecker.CheckPolygonSubType(cornerPoints) == PolygonSubType.Rectangle)
                        {
                            // here i use the graphics class to draw an overlay, but you
                            // could also just use the cornerPoints list to calculate your
                            // x, y, width, height values.
                            List<Point> Points = new List<Point>();
                            foreach (var point in cornerPoints)
                            {
                                Points.Add(new Point(point.X, point.Y));
                            }

                            Graphics g = Graphics.FromImage(image);
                            g.DrawPolygon(new Pen(Color.Red, 5.0f), Points.ToArray());

                            image.Save("result.png");
                        }
                    }
                }
#endif

            }
            catch (Exception ex)
            {
                // MessageBox.Show(ex.Message);
                throw ex;
            }
            finally
            {
                if (autoCropImage != null)
                    autoCropImage.Dispose();
            }
            return true;
        }
コード例 #9
0
ファイル: Form1.cs プロジェクト: linuxbank/DocumentAnalysis
        // 기울기 보정
        private void button51_Click(object sender, EventArgs e)
        {
            ///////////// ini 객체 생성 시작 /////////////////////////////////////////////////////
            //현재 프로그램이 실행되고 있는정보 가져오기: 디버깅 모드라면 bin/debug/프로그램명.exe
            FileInfo exefileinfo = new FileInfo(@"C:\Program Files\PLOCR\PLOCR.exe");
            string pathini = exefileinfo.Directory.FullName.ToString();  //프로그램 실행되고 있는데 path 가져오기
            string fileName = @"\PLOCRconfig.ini";  // 환경설정 파일명
            string filePath = pathini + fileName;   //ini 파일 경로
            DocumentAnalysis.IniUtil ini = new DocumentAnalysis.IniUtil(filePath);   // 만들어 놓았던 iniUtil 객체 생성(생성자 인자로 파일경로 정보 넘겨줌)
            //////////// ini 객체 생성 끝 /////////////////////////////////////////////////////////

            Bitmap source = Global.source;

            // create grayscale filter (BT709)
            Grayscale filter = new Grayscale(0.2125, 0.7154, 0.0721);       // 8비트 grayscale 로 바꾸고
            // apply the filter
            Bitmap grayImage = filter.Apply(source);

            grayImage.Save(@"C:\\Program Files\\PLOCR\\그레이스케일.png");

            // create instance of skew checker
            DocumentSkewChecker skewChecker = new DocumentSkewChecker();        // 8비트 grayscale 로 넣어줘야 함
            //    // get documents skew angle
            double angle = skewChecker.GetSkewAngle(grayImage);  // 기울어진 각도를 얻고

            Bitmap tmp = source;
            // convert to 24 bits per pixel
            source = imageProcess.Clone(tmp, PixelFormat.Format24bppRgb);       // 로테이션 전에 24비트로 바꿔주고
            // delete old image
            tmp.Dispose();

            // create rotation filter
            RotateBilinear rotationFilter = new RotateBilinear(-angle);
            rotationFilter.FillColor = Color.White;
            // rotate image applying the filter
            Bitmap rotatedImage = rotationFilter.Apply(source);  // 원래 이미지를 가져다가 각도만큼 돌리고(원래 이미지는 24비트로 넣어줘야함)

            Global.source = rotatedImage;
            pictureBox1.Image = Global.source;
            pictureBox1.Invalidate();

            ini.SetIniValue("기울어짐 바로잡기", "바로잡기 예/아니오", "예");
        }
コード例 #10
0
ファイル: Preprocessing.cs プロジェクト: Algorithmix/Papyrus
 public static Bitmap Orient_Hough(Bitmap blob)
 {
     GC.Collect();
     Grayscale filter = new Grayscale(0.2125, 0.7154, 0.0721);
     Bitmap documentImage = filter.Apply(blob);
     //Bitmap documentImage =  AForge.Imaging.Image.Clone(blob, PixelFormat.Format16bppGrayScale); //force the jpgs
     // create instance of skew checker
     DocumentSkewChecker skewChecker = new DocumentSkewChecker();
     // get documents skew angle
     double angle = skewChecker.GetSkewAngle(documentImage);
     // rotate image applying the filter
     return RotateImg(blob, (int)-angle, Color.Transparent);
 }
コード例 #11
0
ファイル: ImageDoc.cs プロジェクト: eersonmez/iplab
        // Align scanned document
        private void documentAligningMenuItem_Click( object sender, EventArgs e )
        {
            try
            {
                // get grayscale image from current image
                Bitmap grayImage = ( image.PixelFormat == PixelFormat.Format8bppIndexed ) ?
                    AForge.Imaging.Image.Clone( image ) :
                    AForge.Imaging.Filters.Grayscale.CommonAlgorithms.BT709.Apply( image );
                // threshold it using adaptive Otsu thresholding
                OtsuThreshold threshold = new OtsuThreshold( );
                threshold.ApplyInPlace( grayImage );
                // get skew angle
                DocumentSkewChecker skewChecker = new DocumentSkewChecker( );
                double angle = skewChecker.GetSkewAngle( grayImage );

                if ( ( angle < -skewChecker.MaxSkewToDetect ) ||
                     ( angle > skewChecker.MaxSkewToDetect ) )
                {
                    throw new ApplicationException( );
                }

                // create rotation filter
                RotateBilinear rotationFilter = new RotateBilinear( -angle );
                rotationFilter.FillColor = Color.White;
                // rotate image applying the filter
                ApplyFilter( rotationFilter );
            }
            catch
            {
                MessageBox.Show( "Failed determining skew angle. Is it reallly a scanned document?", "Error",
                    MessageBoxButtons.OK, MessageBoxIcon.Error );
            }
        }