Alisando un camino irregular

Estaba participando en el hilo Imagen / gráfico en forma el otro día e hice un intento hack para obtener el contorno de una imagen agregando un Rectangle iterativamente a un Area. Eso fue muy lento.

En cambio, este ejemplo construye un GeneralPath y crea el Area del GP. Mucho mas rápido.

Imágenes de muestra para procesar

La imagen de la esquina superior izquierda es la 'imagen de origen'. Los dos de la derecha son varias etapas del procesamiento del esquema. Ambos tienen bordes dentados alrededor del círculo y a lo largo de los lados inclinados del triángulo.

Me gustaría obtener una forma que tenga esa irregularidad eliminada o reducida.

En ASCII art.

Caso 1:

  1234
1 **
2 **
3 ***
4 ***
5 ****
6 ****

Las esquinas están en:

  • (2,3) esquina interior
  • (3,3)
  • (3,5) esquina interior
  • (4,5)

Caso 2:

  1234
1 ****
2 ****
3 **
4 **
5 ****
6 ****

Las esquinas están en:

  • (4,2)
  • (2,2) esquina interior
  • (2,5) esquina interior
  • (4,5)

Suponiendo que nuestro camino tenía las formas mostradas y los puntos enumerados, me gustaría eliminar los puntos de la 'esquina interna' del primer conjunto, mientras retengo el 'par' de esquinas internas (un mordisco de la imagen) para el 2do.


  • ¿Alguien puede sugerir algún método incorporado inteligente para hacer el trabajo pesado de este trabajo?
  • De no ser así, ¿cuál sería un buen enfoque para identificar la ubicación y la naturaleza (pareja / individual) de las esquinas interiores? (Supongo que podría conseguir un PathIterator y construir una nueva GeneralPath dejando caer las esquinas interiores singulares, ¡si tan solo pudiera averiguar cómo identificarlas!).

Aquí está el código para jugar:

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;

/* Gain the outline of an image for further processing. */
class ImageOutline {

    private BufferedImage image;

    private TwoToneImageFilter twoToneFilter;
    private BufferedImage imageTwoTone;
    private JLabel labelTwoTone;

    private BufferedImage imageOutline;
    private Area areaOutline = null;
    private JLabel labelOutline;

    private JLabel targetColor;
    private JSlider tolerance;

    private JProgressBar progress;
    private SwingWorker sw;

    public ImageOutline(BufferedImage image) {
        this.image = image;
        imageTwoTone = new BufferedImage(
            image.getWidth(),
            image.getHeight(),
            BufferedImage.TYPE_INT_RGB);
    }

    public void drawOutline() {
        if (areaOutline!=null) {
            Graphics2D g = imageOutline.createGraphics();
            g.setColor(Color.WHITE);
            g.fillRect(0,0,imageOutline.getWidth(),imageOutline.getHeight());

            g.setColor(Color.RED);
            g.setClip(areaOutline);
            g.fillRect(0,0,imageOutline.getWidth(),imageOutline.getHeight());
            g.setColor(Color.BLACK);
            g.setClip(null);
            g.draw(areaOutline);

            g.dispose();
        }
    }

    public Area getOutline(Color target, BufferedImage bi) {
        // construct the GeneralPath
        GeneralPath gp = new GeneralPath();

        boolean cont = false;
        int targetRGB = target.getRGB();
        for (int xx=0; xx<bi.getWidth(); xx++) {
            for (int yy=0; yy<bi.getHeight(); yy++) {
                if (bi.getRGB(xx,yy)==targetRGB) {
                    if (cont) {
                        gp.lineTo(xx,yy);
                        gp.lineTo(xx,yy+1);
                        gp.lineTo(xx+1,yy+1);
                        gp.lineTo(xx+1,yy);
                        gp.lineTo(xx,yy);
                    } else {
                        gp.moveTo(xx,yy);
                    }
                    cont = true;
                } else {
                    cont = false;
                }
            }
            cont = false;
        }
        gp.closePath();

        // construct the Area from the GP & return it
        return new Area(gp);
    }

    public JPanel getGui() {
        JPanel images = new JPanel(new GridLayout(2,2,2,2));
        JPanel  gui = new JPanel(new BorderLayout(3,3));

        JPanel originalImage =  new JPanel(new BorderLayout(2,2));
        final JLabel originalLabel = new JLabel(new ImageIcon(image));
        targetColor = new JLabel("Target Color");
        targetColor.setForeground(Color.RED);
        targetColor.setBackground(Color.WHITE);
        targetColor.setBorder(new LineBorder(Color.BLACK));
        targetColor.setOpaque(true);

        JPanel controls = new JPanel(new BorderLayout());
        controls.add(targetColor, BorderLayout.WEST);
        originalLabel.addMouseListener( new MouseAdapter() {
            @Override
            public void mouseEntered(MouseEvent me) {
                originalLabel.setCursor(
                    Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
            }

            @Override
            public void mouseExited(MouseEvent me) {
                originalLabel.setCursor(Cursor.getDefaultCursor());
            }

            @Override
            public void mouseClicked(MouseEvent me) {
                int x = me.getX();
                int y = me.getY();

                Color c = new Color( image.getRGB(x,y) );
                targetColor.setBackground( c );

                updateImages();
            }
        });
        originalImage.add(originalLabel);

        tolerance = new JSlider(
            JSlider.HORIZONTAL,
            0,
            255,
            104
            );
        tolerance.addChangeListener( new ChangeListener() {
            public void stateChanged(ChangeEvent ce) {
                updateImages();
            }
        });
        controls.add(tolerance, BorderLayout.CENTER);
        gui.add(controls,BorderLayout.NORTH);

        images.add(originalImage);

        labelTwoTone = new JLabel(new ImageIcon(imageTwoTone));

        images.add(labelTwoTone);

        images.add(new JLabel("Smoothed Outline"));

        imageOutline = new BufferedImage(
            image.getWidth(),
            image.getHeight(),
            BufferedImage.TYPE_INT_RGB
            );

        labelOutline = new JLabel(new ImageIcon(imageOutline));
        images.add(labelOutline);

        updateImages();

        progress = new JProgressBar();

        gui.add(images, BorderLayout.CENTER);
        gui.add(progress, BorderLayout.SOUTH);

        return gui;
    }

    private void updateImages() {
        if (sw!=null) {
            sw.cancel(true);
        }
        sw = new SwingWorker() {
            @Override
            public String doInBackground() {
                progress.setIndeterminate(true);
                adjustTwoToneImage();
                labelTwoTone.repaint();
                areaOutline = getOutline(Color.BLACK, imageTwoTone);

                drawOutline();

                return "";
            }

            @Override
            protected void done() {
                labelOutline.repaint();
                progress.setIndeterminate(false);
            }
        };
        sw.execute();
    }

    public void adjustTwoToneImage() {
        twoToneFilter = new TwoToneImageFilter(
            targetColor.getBackground(),
            tolerance.getValue());

        Graphics2D g = imageTwoTone.createGraphics();
        g.drawImage(image, twoToneFilter, 0, 0);

        g.dispose();
    }

    public static void main(String[] args) throws Exception {
        int size = 150;
        final BufferedImage outline =
            new BufferedImage(size,size,BufferedImage.TYPE_INT_RGB);
        Graphics2D g = outline.createGraphics();
        g.setColor(Color.WHITE);
        g.fillRect(0,0,size,size);
        g.setRenderingHint(
            RenderingHints.KEY_DITHERING,
            RenderingHints.VALUE_DITHER_ENABLE);
        g.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);

        Polygon p = new Polygon();
        p.addPoint(size/2, size/10);
        p.addPoint(size-10, size-10);
        p.addPoint(10, size-10);
        Area a = new Area(p);

        Rectangle r = new Rectangle(size/4, 8*size/10, size/2, 2*size/10);
        a.subtract(new Area(r));

        int radius = size/10;
        Ellipse2D.Double c = new Ellipse2D.Double(
            (size/2)-radius,
            (size/2)-radius,
            2*radius,
            2*radius
            );
        a.subtract(new Area(c));

        g.setColor(Color.BLACK);
        g.fill(a);

        ImageOutline io = new ImageOutline(outline);

        JFrame f = new JFrame("Image Outline");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(io.getGui());
        f.pack();
        f.setResizable(false);
        f.setLocationByPlatform(true);
        f.setVisible(true);
    }
}

class TwoToneImageFilter implements BufferedImageOp {

    Color target;
    int tolerance;

    TwoToneImageFilter(Color target, int tolerance) {
        this.target = target;
        this.tolerance = tolerance;
    }

    private boolean isIncluded(Color pixel) {
        int rT = target.getRed();
        int gT = target.getGreen();
        int bT = target.getBlue();
        int rP = pixel.getRed();
        int gP = pixel.getGreen();
        int bP = pixel.getBlue();
        return(
            (rP-tolerance<=rT) && (rT<=rP+tolerance) &&
            (gP-tolerance<=gT) && (gT<=gP+tolerance) &&
            (bP-tolerance<=bT) && (bT<=bP+tolerance) );
    }

    public BufferedImage createCompatibleDestImage(
        BufferedImage src,
        ColorModel destCM) {
        BufferedImage bi = new BufferedImage(
            src.getWidth(),
            src.getHeight(),
            BufferedImage.TYPE_INT_RGB);
        return bi;
    }

    public BufferedImage filter(
        BufferedImage src,
        BufferedImage dest) {

        if (dest==null) {
            dest = createCompatibleDestImage(src, null);
        }

        for (int x=0; x<src.getWidth(); x++) {
            for (int y=0; y<src.getHeight(); y++) {
                Color pixel = new Color(src.getRGB(x,y));
                Color write = Color.BLACK;
                if (isIncluded(pixel)) {
                    write = Color.WHITE;
                }
                dest.setRGB(x,y,write.getRGB());
            }
        }

        return dest;
    }

    public Rectangle2D getBounds2D(BufferedImage src) {
        return new Rectangle2D.Double(0, 0, src.getWidth(), src.getHeight());
    }

    public Point2D getPoint2D(
        Point2D srcPt,
        Point2D dstPt) {
        // no co-ord translation
        return srcPt;
    }

    public RenderingHints getRenderingHints() {
        return null;
    }
}

preguntado el 28 de agosto de 11 a las 00:08

Buena pregunta sobre un problema difícil. -

@AndrewThompson Esto podría ser muy bueno para la forma en que lo estoy usando, pero parece que no le gusta hacer segmentos de línea recta ... Estoy usando este código para un juego JRPG (más una prueba de concepto) y para colisiones Estoy usando áreas. Al dibujar el área, este es el aspecto que tiene: puu.sh/7wJA2.png .Si agrego un píxel debajo de la línea, parece estar bien, así: puu.sh/7wJGF.png Mi comprensión de este código es bastante débil, por lo que no estoy muy seguro de dónde radica el problema. ¿Algún consejo que puedas ofrecer? -

"¿Algún consejo que puedas ofrecer?" Inicie una nueva pregunta, enlace a esta. -

@AndrewThompson Eso era algo que estaba pensando en hacer :) Lo haré ahora -

3 Respuestas

Este es un gran tema. Podrías encontrar Pixel Art depixelizante1 por Johannes Kopf & Dani Lischinski útil: es legible, reciente, incluye un resumen de trabajos anteriores y explica su enfoque en detalle.

Véase también diapositivas que cubren un fondo similar y video(!).

  1. Aquí hay algunas capturas de pantalla del documento de 'vecino más cercano' frente a 'su técnica'. Vecino más cercano su resultado

Respondido el 31 de diciembre de 12 a las 06:12

Para celebrar el regreso del PDF, capturé y agregué algunas imágenes 'de prueba' del documento. Va mucho más allá de lo que estoy intentando, pero, sin embargo, una impresionante resultado. - Andrew Thompson

enlace muerto: esta es la razón por la que las respuestas de solo enlace son malas. - Gus

La versión más general de este problema es una de las etapas iniciales en la mayoría de las canalizaciones de visión por computadora. Se llama segmentación de imágenes. Divide una imagen en regiones de píxeles que se consideran visualmente idénticas. Estas regiones están separadas por "contornos" (ver por ejemplo este artículo), que equivalen a rutas a través de la imagen que se extienden a lo largo de los límites de píxeles.

Existe un algoritmo recursivo simple para representar contornos como una polilínea definida de tal manera que ningún punto en ella se desvía más que una cantidad fija (digamos max_dev) puedes elegir. Normalmente es de 1/2 a 2 píxeles.

function getPolyline(points [p0, p1, p2... pn] in a contour, max_dev) {
  if n <= 1 (there are only one or two pixels), return the whole contour
  Let pi, 0 <= i <= n, be the point farthest from the line segment p0<->pn
  if distance(pi, p0<->pn) < max_dev 
    return [ p0 -> pn ]
  else
    return concat(getPolyline [ p0, ..., pi ],  getPolyline [ pi, ..., pn] )

La idea detrás de esto es que parece tener imágenes de dibujos animados que ya están segmentadas. Entonces, si codifica una búsqueda simple que ensambla los píxeles del borde en cadenas, puede usar el algoritmo anterior para convertirlos en cadenas de segmentos de línea que serán suaves. Incluso se pueden dibujar con suavizado.

Respondido el 31 de diciembre de 12 a las 07:12

Si ya conoce el segmento o el borde, intente difuminar con gaussiano o promedio o uno de su propio núcleo y muévase al borde que desea suavizar. Esta es una solución rápida y puede que no se adapte mejor a imágenes complejas, pero es bueno para el autodibujado.

Respondido 18 Feb 14, 16:02

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.