Сглаживание неровного пути

Я участвовал в теме Image/Graphic into a Shape, на днях и предпринял хакерскую попытку получить контур изображения, итеративно добавляя Rectangle к Area. Это было очень медленно.

В этом примере вместо этого создается GeneralPath и создается Area из GP. Намного быстрее.

Образцы изображений для обработки

Изображение в левом верхнем углу является «исходным изображением». Два справа — это различные этапы обработки контура. Оба они имеют зазубренные края по кругу и вдоль наклонных сторон треугольника.

Я хотел бы получить форму, в которой была бы удалена или уменьшена эта зубчатость.

В ASCII арт.

Дело 1:

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

Углы находятся по адресу:

  • (2,3) внутренний угол
  • (3,3)
  • (3,5) внутренний угол
  • (4,5)

Случай 2:

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

Углы находятся по адресу:

  • (4,2)
  • (2,2) внутренний угол
  • (2,5) внутренний угол
  • (4,5)

Предполагая, что на нашем пути были показаны формы и точки, указанные в списке, я хотел бы отбросить точки «внутреннего угла» первого набора, сохранив при этом «пару» внутренних углов (кусок изображения) для 2-й.


  • Может ли кто-нибудь предложить какой-нибудь умный встроенный метод для выполнения тяжелой работы?
  • В противном случае, что было бы хорошим подходом к определению местоположения и характера (пара/одиночка) внутренних углов? (Думаю, я мог бы получить PathIterator и построить новый GeneralPath, убрав отдельные внутренние углы - если бы я только мог понять, как их идентифицировать!).

Вот код для игры:

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;
    }
}

person Andrew Thompson    schedule 28.08.2011    source источник
comment
Хороший вопрос о сложной проблеме.   -  person trashgod    schedule 28.08.2011
comment
См. также Простое определение формы в изображении 1bpp.   -  person Andrew Thompson    schedule 09.11.2011
comment
@AndrewThompson Это может быть очень хорошо, как я его использую, но, похоже, это не нравится делать сегменты прямой линии ... Я использую этот код для игры JRPG (скорее проверка концепции) и для столкновения я использую области. При рисовании области это выглядит так: puu.sh/7wJA2.png. Если я добавлю пиксель под линией выглядит нормально, например: puu.sh/7wJGF.png. Насколько я понимаю этого кода довольно слаб, поэтому я не слишком уверен, в чем проблема. Какой-нибудь совет, который вы могли бы дать?   -  person Gareth Jones    schedule 16.03.2014
comment
Что бы вы могли посоветовать? Начните новый вопрос, дайте ссылку на этот.   -  person Andrew Thompson    schedule 16.03.2014
comment
@AndrewThompson Это было то, что я думал сделать :) Я сделаю это сейчас   -  person Gareth Jones    schedule 16.03.2014
comment
@AndrewThompson stackoverflow.com/questions/22432161/   -  person Gareth Jones    schedule 16.03.2014


Ответы (3)


Это большая тема. Вы можете найти депикселизацию пиксельной графики1< /sup> by Johannes Kopf & Dani Lischinski полезный: он удобочитаемый, свежий, содержит резюме предыдущей работы и подробно объясняет их подход.

См. также слайды с похожим фоном и видео(!).

  1. Вот несколько скриншотов из документа «ближайший сосед» против «их техники». ближайший соседих результат
person andrew cooke    schedule 28.08.2011
comment
Чтобы отпраздновать возвращение PDF, я захватил и добавил несколько «пробных» изображений из документа. Это далеко за пределами того, что я пытаюсь сделать, но, тем не менее, впечатляющий результат. - person Andrew Thompson; 31.12.2012
comment
мертвая ссылка - вот почему ответы только по ссылкам плохие. - person Gus; 18.10.2019

Наиболее общая версия этой проблемы — одна из начальных стадий в большинстве конвейеров компьютерного зрения. Это называется сегментация изображения. Он разбивает изображение на области пикселей, которые считаются визуально идентичными. Эти области разделены «контурами» (см., например, эту статью), которые представляют собой пути через изображение, бегущее по границам пикселей.

Существует простой рекурсивный алгоритм для представления контуров в виде полилинии, определенной таким образом, что ни одна точка в ней не отклоняется больше, чем на некоторую фиксированную величину (скажем, max_dev), которую вы можете выбрать. Обычно это от 1/2 до 2 пикселей.

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] )

Идея, стоящая за этим, заключается в том, что у вас, кажется, есть мультяшные изображения, которые уже сегментированы. Таким образом, если вы запрограммируете простой поиск, который собирает краевые пиксели в цепочки, вы можете использовать приведенный выше алгоритм, чтобы преобразовать их в цепочки линейных сегментов, которые будут гладкими. Их даже можно рисовать со сглаживанием.

person Gene    schedule 31.12.2012

Если вы уже знаете сегмент или край, попробуйте размыть с помощью Гаусса, среднего или одного из ваших собственных ядер и перейдите к краю, который вы хотите сгладить. Это быстрое решение, которое может не подходить для сложных изображений, но для самостоятельного рисования это хорошо.

person Pervez Alam    schedule 18.02.2014