TransWikia.com

Recortar imagen eliminando espacios en blanco

Stack Overflow en español Asked by User1899289003 on December 18, 2021

estoy trabajando en una aplicación de escaneo, todo esto funciona bien pero un requerimiento es que si lo que se intenta escanear no ocupa todo el tamaño de la hoja se recorte en automático (el usuario no tiene acceso a manipular la imagen). Buscando en internet encontre esta respuesta a un problema similar al mio pero en mi caso no funciona. Esta es la imagen que me retorna el escaner:

introducir la descripción de la imagen aquí

Como se puede observar el objeto escaneado es pequeño pero se guarda en una hoja completa de tamaño carta, hay forma de que mediante un boton o manipulando el Bitmap que retorna el escaner se pueda extraer solo la tarjeta y eliminar todos los espacios en blanco?

Este es el codigo que estoy intentando (el resultado que me da es el mismo que la imagen de arriba):

public static System.Drawing.Image AforgeAutoCrop(Bitmap selectedImage)
{
    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 = 0; // 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(25).ApplyInPlace(rotatedImage);
        BlobCounter bc = new BlobCounter();
        bc.FilterBlobs = true;
        // bc.MinWidth = 500;
        //bc.MinHeight = 500;
        bc.ProcessImage(rotatedImage);
        Rectangle[] rects = bc.GetObjectsRectangles();

        if (rects.Length == 0)
        {
            // CAN'T CROP
        }
        else if (rects.Length == 1)
        {
            autoCropImage = autoCropImage.Clone(rects[0], autoCropImage.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 = autoCropImage.Clone(r2[0], autoCropImage.PixelFormat);
        }
        else
        {
            Console.WriteLine("Huh? on image ");
        }
    }
    catch (Exception ex)
    {
        //MessageBox.Show(ex.Message);
        //CAN'T CROP
    }

    return autoCropImage;
}

PD.1: El objeto escaneado puede variar su tamaño.

One Answer

Lo primero de todo para conseguir el propósito que buscas es incrementar el contraste de la imagen original de tal forma que lo casi blanco sea blanco. Para ello nos construimos la siguiente función:

private Bitmap AplicaFiltroContrasteBrillo(Image p_ImgSource, float p_Contraste, float p_Brillo)
{
    float brilloAjustado = p_Brillo - 1.0f;
    // Creamos una matriz para aplicar contraste y brillo
    // a una imagen dada.
    float[][] matrizContraste = {
           new float[] {p_Contraste, 0, 0, 0, 0}, // aplica a RED 
           new float[] {0, p_Contraste, 0, 0, 0}, // aplica a GREEN
           new float[] {0, 0, p_Contraste, 0, 0}, // aplica a BLUE
           new float[] {0, 0, 0, 1.0f, 0},        // canal alpha
           new float[] {brilloAjustado, brilloAjustado, brilloAjustado, 0, 1}};

    // Creamos los atributos en función de nuestra matriz
    // para aplicar a la imagen
    ColorMatrix cmx = new ColorMatrix(matrizContraste);
    ImageAttributes imgAttr = new ImageAttributes();
    imgAttr.SetColorMatrix(cmx);

    // Trasladamos la imagen a un bitmap ya con el efecto de contraste
    // y brillo aplicado para poder trabajar sobre eso
    Bitmap bmpCanvas = new Bitmap(p_ImgSource.Width, p_ImgSource.Height, p_ImgSource.PixelFormat);
    Graphics g = Graphics.FromImage(bmpCanvas);
    g.DrawImage(p_ImgSource, new Rectangle(Point.Empty, bmpCanvas.Size),
                0, 0,
                p_ImgSource.Width, p_ImgSource.Height,
                GraphicsUnit.Pixel, imgAttr);

    // Liberamos recursos
    g.Dispose();
    imgAttr.Dispose();

    // Retornamos la imagen con el filtro aplicado
    return bmpCanvas;
}

Ahora lo que precisamos es un algoritmo que nos recorra la imagen obtenida y nos calcule el rectángulo de recorte que andamos buscando. Seguro se podría optimizar y mejorar, pero para el asunto la he creado de la siguiente forma:

private Rectangle ObtenerBordes(Bitmap p_Bmp)
{
    int bordeIZQ = -1;
    int bordeDER = -1;
    int bordeSUP = -1;
    int bordeINF = -1;
    // Buscamos el borde SUPERIOR e INFERIOR
    for (int y = 0; y < p_Bmp.Height; y++)
    {
        for (int x = 0; x < p_Bmp.Width; x++)
        {
            if (bordeSUP == -1)
            {
                Color pixel = p_Bmp.GetPixel(x, y);
                if (pixel.Name != "ffffffff")
                    bordeSUP = y;
            }
            if (bordeINF == -1)
            {
                Color pixel = p_Bmp.GetPixel(x, p_Bmp.Height - y-1);
                if (pixel.Name !="ffffffff")
                    bordeINF = p_Bmp.Height - y - 1;
            }
        }
        if (bordeSUP != -1 && bordeINF != -1) break;
    }
    // Buscamos el borde DERECHO e IZQUIERDO
    for (int x = 0; x < p_Bmp.Width; x++)
    {
        for (int y = 0; y < p_Bmp.Height; y++)
        {
            if (bordeIZQ == -1)
            {
                Color pixel = p_Bmp.GetPixel(x, y);
                if (pixel.Name != "ffffffff")
                    bordeIZQ = x;
            }
            if (bordeDER == -1)
            {
                Color pixel = p_Bmp.GetPixel(p_Bmp.Width - x - 1, y);
                if (pixel.Name != "ffffffff")
                    bordeDER = p_Bmp.Width - x - 1;
            }
        }
        if (bordeIZQ != -1 && bordeDER != -1) break;
    }

    return new Rectangle(bordeIZQ, bordeSUP, bordeDER - bordeIZQ, bordeINF - bordeSUP);
}

Observa que lo único que hago es ir recorriendo la imagen hasta encontrar un pixel que sea distinto de BLANCO y entonces establecer el borde en esa coordenada.

Ya casi tenemos todo el trabajo hecho, basta con aglutinarlo en una función que será la que llamaremos directamente para obtener el recorte de la imagen:

private Image RecortarImage(Image p_ImgSource)
{
    // Aplicamos un filtro a la imagen y retornamos el
    // resultado a nuestro bitmap
    // He bajado el brillo a la mitad y aumentado a 6 el contraste
    // lo que me ha permitido obtener un resultado adecuado al menos
    // con tu imagen de prueba.
    Bitmap bmpCanvas = AplicaFiltroContrasteBrillo(p_ImgSource, 6f, 0.5f);

    // Llamamos a la siguiente función que le pasaremos el Bitmap que hemos
    // creado con la imagen y que ya tiene un contraste alto para hallar los bordes
    Rectangle imgRectRecorte = ObtenerBordes(bmpCanvas);

    // Una vez calculado el rectángulo de recorte creamos un Bitmap para copiar
    // desde la imagen original a dicho bitmap la porción de imagen correspondiente
    // según el rectángulo de recorte calculado.
    Bitmap bmpCROP = new Bitmap(imgRectRecorte.Width, imgRectRecorte.Height);
    Graphics gCrop = Graphics.FromImage(bmpCROP);
    gCrop.DrawImage(p_ImgSource, new Rectangle(Point.Empty, bmpCROP.Size),
                    imgRectRecorte, GraphicsUnit.Pixel);
    // Nos aseguramos de liberar recursos
    gCrop.Dispose();

    // Retornamos la imagen recortada
    return bmpCROP;
}

Answered by Antonio S.F. on December 18, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP