Сглаживание зубчатой дорожки
На днях я участвовал в теме " Изображение / графика в форме" и сделал хакерскую попытку получить контур изображения, добавив Rectangle
итеративно к Area
, Это было очень медленно.
Этот пример вместо этого строит GeneralPath
и создает Area
от ВОП. Намного быстрее.
Изображение в верхнем левом углу является "исходным изображением". Два справа - различные этапы обработки схемы. У них обоих есть зазубренные края вокруг круга и вдоль наклонных сторон треугольника.
Я хотел бы получить форму, которая убрала или уменьшила эту неровность.
В 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)
Предполагая, что наш путь имеет показанные формы и перечисленные точки, я бы хотел отбросить точки "внутреннего угла" первого набора, сохраняя при этом "пару" внутренних углов (отрывок из изображения) для второй.
- Кто-нибудь может предложить какой-нибудь умный встроенный метод, чтобы сделать тяжелую работу этой работы?
- В противном случае, что было бы хорошим подходом для определения местоположения и природы (пара / один) внутренних углов? (Я думаю, я мог бы получить
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;
}
}
3 ответа
Это большая тема. Вам может пригодиться Depixelizing Pixel Art 1 от Johannes Kopf & Dani Lischinski: он читабелен, недавний, содержит краткое изложение предыдущей работы и подробно объясняет их подход.
Смотрите также слайды с аналогичным фоном и видео (!).
- Вот несколько скриншотов из документа "ближайший сосед" и "их техника".
Наиболее общая версия этой проблемы является одним из начальных этапов в большинстве конвейеров компьютерного зрения. Это называется сегментация изображений. Он разбивает изображение на области пикселей, которые считаются визуально идентичными. Эти области разделены "контурами" (см., Например, эту статью), которые составляют пути через изображение, проходящее вдоль границ пикселей.
Существует простой рекурсивный алгоритм для представления контуров в виде полилинии, определенной так, что ни одна точка в ней не отклоняется больше, чем на некоторое фиксированное значение (скажем, 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] )
Мысль, стоящая за этим, заключается в том, что у вас, похоже, есть похожие на мультфильм изображения, которые уже сегментированы. Поэтому, если вы кодируете простой поиск, который собирает краевые пиксели в цепочки, вы можете использовать алгоритм, приведенный выше, чтобы преобразовать их в цепочки отрезков, которые будут гладкими. Они даже могут быть нарисованы с помощью сглаживания.
Если вы уже знаете сегмент или ребро, попробуйте размыть с помощью гауссовского или среднего или одного из ваших собственных ядер и переместитесь к краю, который вы хотите сгладить. Это быстрое решение и может не подойти лучше всего на сложных изображениях, но для самостоятельного рисования, это хорошо.