前几天我参与了帖子Image/Graphic into a Shape,并试图通过向Rectangle
迭代添加Area
来获取图片的轮廓。那很慢。
此示例构建GeneralPath
并从GP创建Area
。快多了。
左上角的图像是“源图像”。右边的两个是处理轮廓的各个阶段。它们都有圆形边缘和三角形倾斜边缘的锯齿状边缘。
我希望获得一种能够消除或减少锯齿状的形状。
1234
1 **
2 **
3 ***
4 ***
5 ****
6 ****
角落位于:
1234
1 ****
2 ****
3 **
4 **
5 ****
6 ****
角落位于:
假设我们的路径有所示的形状和列出的点,我想放下第一组的“内角”点,同时保留“一对”内角(咬出图像) )为第二个。
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;
}
}
答案 0 :(得分:20)
这是一个很大的主题。您可能会发现Depixelizing Pixel Art1 by Johannes Kopf & Dani Lischinski很有用:它可读,最新,包含以前工作的摘要,并详细解释了它们的方法。
另请参阅slides covering similar background和video(!)。
答案 1 :(得分:2)
此问题的最常见版本是大多数计算机视觉管道的初始阶段之一。它被称为图像分割。它将图像分成视觉上相同的像素区域。这些区域由“轮廓”分隔(例如参见this article),这相当于沿像素边界运行的图像的路径。
有一种简单的递归算法可以将轮廓表示为一条定义的折线,使得它中的任何一点都不会超过您选择的某个固定量(比如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] )
这背后的想法是你似乎有类似卡通的图像已经被分割。因此,如果您编写一个将边缘像素组合成链的简单搜索,您可以使用上面的算法将它们转换为平滑的线段链。它们甚至可以用抗锯齿绘制。
答案 2 :(得分:0)
如果您已经知道段或边缘尝试使用高斯或平均值或您自己的内核之一模糊并移动到您想要平滑的边缘。 这是一个快速的解决方案,可能不适合复杂的图像,但对于自绘,它很好。