Java模糊图像

10

我正在尝试给图片添加模糊效果

   int radius = 11;
    int size = radius * 2 + 1;
    float weight = 1.0f / (size * size);
    float[] data = new float[size * size];

    for (int i = 0; i < data.length; i++) {
        data[i] = weight;
    }

    Kernel kernel = new Kernel(size, size, data);
    ConvolveOp op = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
    //tbi is BufferedImage
    BufferedImage i = op.filter(tbi, null);

它将模糊图像,但不是所有部分。

在此输入图片描述

我错过了哪里,以便它可以完全模糊图像。没有任何路径。


如果您将int size = radius * 2 + 1;替换为实际值,并观察更改此值是否会改变模糊框,会发生什么? - npinti
3
请参考此文章中的“在边缘工作”部分,以获取解决方法。您需要根据内核大小增加图像的尺寸。 - halex
7个回答

11

标准的Java ConvolveOp只有两个选项:EDGE_ZERO_FILLEDGE_NO_OP。您需要的是JAI等效选项(ConvolveDescriptor),即EDGE_REFLECT(或EDGE_WRAP,如果您想要重复的图案)。

如果您不想使用JAI,可以自己实现,将图像复制到一个较大的图像中,拉伸或包裹边缘,应用卷积操作,然后切掉边缘(类似于@halex在评论部分发布的文章中描述的技术,但根据该文章,您也可以只留下边缘透明)。

为了简单起见,您可以直接使用我的实现称为ConvolveWithEdgeOp,它执行上述操作(BSD许可证)。
代码将类似于您最初的代码:
// ...kernel setup as before...
Kernel kernel = new Kernel(size, size, data);
BufferedImageOp op = new ConvolveWithEdgeOp(kernel, ConvolveOp.EDGE_REFLECT, null);

BufferedImage blurred = op.filter(original, null);

过滤器应该像其他BufferedImageOp一样工作,并且应该与任何BufferedImage一起使用。

我们能否从上述代码中仅获取模糊部分并将其复制到另一个缓冲图像中? - Prashant Thorat
@PrashantThorat 对不起,我不明白你的问题。我的代码应该适用于任何BufferedImage - Harald K

3
那是因为你在这一行中使用了ConvolveOp.EDGE_NO_OP
ConvolveOp op = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);

API文档中说明:

源图像边缘的像素将被复制到目标像素对应位置,而不做修改。

尝试使用EDGE_ZERO_FILL - 这会给你黑色边框。

你也可以尝试在模糊之后裁剪边缘。

不能处理边缘的原因与算法的工作方式有关。


使用 EDGE_ZERO_FILL 会在模糊部分周围产生黑色边框,这与 EDGE_NO_OP 的效果相同。 - halex
是的,我不想要那个黑色边框。 - Prashant Thorat

1
你无法模糊一个像素。这听起来很明显,但是当你思考时,什么是最小值呢?要模糊一个像素,你需要相邻的像素。
问题在于,在边缘和角落处,像素的相邻像素太少了 - 模糊算法使用的像素太少了。它没有“外部”图像中的像素可以用来模糊,因此它将保持原样。
解决方案要么是以某种方式扩展图片(您是否有更大的源图像可用?),要么在完成后割掉非模糊位。两者本质上是相同的。

0

我曾经在寻找模糊文档的能力时发现了这个问题,当 ConvolveOp 类无法满足您遇到的问题时。它执行高斯模糊,这是最自然的模糊效果... 希望它能帮到您。我从这个网页中检索到它:Java 图像处理...

/*
** Copyright 2005 Huxtable.com. All rights reserved.
*/

package com.jhlabs.image;

import java.awt.image.*;

/**
 * A filter which applies Gaussian blur to an image. This is a subclass of ConvolveFilter
 * which simply creates a kernel with a Gaussian distribution for blurring.
 * @author Jerry Huxtable
 */
public class GaussianFilter extends ConvolveFilter {

    static final long serialVersionUID = 5377089073023183684L;

    protected float radius;
    protected Kernel kernel;

    /**
     * Construct a Gaussian filter
     */
    public GaussianFilter() {
        this(2);
    }

    /**
     * Construct a Gaussian filter
     * @param radius blur radius in pixels
     */
    public GaussianFilter(float radius) {
        setRadius(radius);
    }

    /**
     * Set the radius of the kernel, and hence the amount of blur. The bigger the radius, the longer this filter will take.
     * @param radius the radius of the blur in pixels.
     */
    public void setRadius(float radius) {
        this.radius = radius;
        kernel = makeKernel(radius);
    }

    /**
     * Get the radius of the kernel.
     * @return the radius
     */
    public float getRadius() {
        return radius;
    }

    public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
        int width = src.getWidth();
        int height = src.getHeight();

        if ( dst == null )
            dst = createCompatibleDestImage( src, null );

        int[] inPixels = new int[width*height];
        int[] outPixels = new int[width*height];
        src.getRGB( 0, 0, width, height, inPixels, 0, width );

        convolveAndTranspose(kernel, inPixels, outPixels, width, height, alpha, CLAMP_EDGES);
        convolveAndTranspose(kernel, outPixels, inPixels, height, width, alpha, CLAMP_EDGES);

        dst.setRGB( 0, 0, width, height, inPixels, 0, width );
        return dst;
    }

    public static void convolveAndTranspose(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, boolean alpha, int edgeAction) {
        float[] matrix = kernel.getKernelData( null );
        int cols = kernel.getWidth();
        int cols2 = cols/2;

        for (int y = 0; y < height; y++) {
            int index = y;
            int ioffset = y*width;
            for (int x = 0; x < width; x++) {
                float r = 0, g = 0, b = 0, a = 0;
                int moffset = cols2;
                for (int col = -cols2; col <= cols2; col++) {
                    float f = matrix[moffset+col];

                    if (f != 0) {
                        int ix = x+col;
                        if ( ix < 0 ) {
                            if ( edgeAction == CLAMP_EDGES )
                                ix = 0;
                            else if ( edgeAction == WRAP_EDGES )
                                ix = (x+width) % width;
                        } else if ( ix >= width) {
                            if ( edgeAction == CLAMP_EDGES )
                                ix = width-1;
                            else if ( edgeAction == WRAP_EDGES )
                                ix = (x+width) % width;
                        }
                        int rgb = inPixels[ioffset+ix];
                        a += f * ((rgb >> 24) & 0xff);
                        r += f * ((rgb >> 16) & 0xff);
                        g += f * ((rgb >> 8) & 0xff);
                        b += f * (rgb & 0xff);
                    }
                }
                int ia = alpha ? PixelUtils.clamp((int)(a+0.5)) : 0xff;
                int ir = PixelUtils.clamp((int)(r+0.5));
                int ig = PixelUtils.clamp((int)(g+0.5));
                int ib = PixelUtils.clamp((int)(b+0.5));
                outPixels[index] = (ia << 24) | (ir << 16) | (ig << 8) | ib;
                index += height;
            }
        }
    }

    /**
     * Make a Gaussian blur kernel.
     */
    public static Kernel makeKernel(float radius) {
        int r = (int)Math.ceil(radius);
        int rows = r*2+1;
        float[] matrix = new float[rows];
        float sigma = radius/3;
        float sigma22 = 2*sigma*sigma;
        float sigmaPi2 = 2*ImageMath.PI*sigma;
        float sqrtSigmaPi2 = (float)Math.sqrt(sigmaPi2);
        float radius2 = radius*radius;
        float total = 0;
        int index = 0;
        for (int row = -r; row <= r; row++) {
            float distance = row*row;
            if (distance > radius2)
                matrix[index] = 0;
            else
                matrix[index] = (float)Math.exp(-(distance)/sigma22) / sqrtSigmaPi2;
            total += matrix[index];
            index++;
        }
        for (int i = 0; i < rows; i++)
            matrix[i] /= total;

        return new Kernel(rows, 1, matrix);
    }

    public String toString() {
        return "Blur/Gaussian Blur...";
    }
}

0
如果您的应用程序经常处理图片,您可能想考虑使用ImageJ API:它提供了相当多的图像处理任务功能,包括模糊处理。
他们的高斯模糊滤镜将模糊处理直到图片边缘,通过做出以下假设:

{...} 它假设图像外的像素值等于最近的边缘像素 {...}

即使您不想更改代码以使用ImageJ API,您仍然可以发现上述假设对解决问题有用。
有关更多信息,请查看API文档中的GaussianBlur滤镜: http://rsb.info.nih.gov/ij/developer/api/ij/plugin/filter/GaussianBlur.html

0

你可以使用OpenCV库来实现完整的图像模糊。


0

我总是做类似这样的事情:

public BufferedImage diagonalBlur(int range, int angle)
    {
        BufferedImage b = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = b.createGraphics();
        
        for(int x = 0; x < main_image.getWidth(); x++)
        {
            for(int y = 0; y < main_image.getHeight(); y++)
                
                int red[] = new int[range * 2], green[] = new int[range * 2], blue[] = new int[range * 2];
                int pixels[] = new int[range * 2];
                
                for(int i = 0; i < pixels.length; i++)
                {
                    pixels[i] = main_image.getRGB(clamp(x - clamp(range / 2, 0, range) + i, 0, main_image.getWidth() - 1), clamp(y - clamp(range / 2, 0, range) + (int)(i * Math.toRadians(angle)), 0, main_image.getHeight() - 1));
                    
                    red[i] = (pixels[i] >> 16) & 0xff;
                    green[i] = (pixels[i] >> 8) & 0xff;
                    blue[i] = (pixels[i]) & 0xff;
                }
                
                int red_t = 0, green_t = 0, blue_t = 0;
                
                for(int i = 0; i < pixels.length; i++)
                {
                    red_t += red[i];
                    green_t += green[i];
                    blue_t += blue[i];
                }
                
                int r = red_t / (range * 2);
                int gr = green_t / (range * 2);
                int bl = blue_t / (range * 2);
                
                //System.out.println(r + ", " + gr + ", " + bl);
                
                g.setColor(new Color(r, gr, bl));
                g.fillRect(x, y, 1, 1);
                
            }
        }
        g.dispose();
        
        return b;

然后,做一些类似于以下的事情:

public static void main(String a[])
{
  File f = new File("path");
  try{
    ImageIO.write(diagonalBlur(10, 69), "png", f);
  }
  catch(IOException e)
  {
    e.printStackTrace();
  }
}
}

这将把文件保存为BufferedImage。当然,您需要一个图像的引用。 在代码中,我使用了main_image,但那是我之前创建的变量:

public BufferedImage main_image;

我使用以下代码来启动它

try
{
  main_image = ImageIO.read(new File("path"));
}
catch(IOException e)
{
  e.printStackTrace();
}

在主方法中。 添加一些JFrame代码,使我能够做到这一点: 模糊 如果你想要高斯模糊,你只需要将redgreenbluepixels变量设置为二维数组,并在y轴上重复该过程。 希望我能帮到你。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接