likes
comments
collection
share

基于中值滤波针对椒盐图像去噪实战

作者站长头像
站长
· 阅读数 40

概述:

椒盐噪声(Salt-and-Pepper Noise)是一种常见的图像噪声类型,它随机地改变一些像素点的值。在椒盐噪声中,受影响的像素通常被设置为最小值(如黑色,类似于胡椒粒)或最大值(如白色,类似于盐粒),这就是其名称的由来。这种噪声可能是由图像传感器、传输错误、解码过程中的位错误等引起的。

椒盐噪声原理:

椒盐噪声的特点是图像中随机分布的黑白像素点,这些像素点的灰度值通常处于灰度范围的两端,即0和255(对于8位灰度图像)。由于这种噪声的随机性和极端值,它可以显著影响图像的视觉质量。

椒盐噪声处理方法:

椒盐噪声通常可以通过非线性滤波器来处理,这些滤波器能够有效地去除或减少噪声,同时保持图像的边缘和细节。常见的几种去噪滤波器包括:

  1. 中值滤波器(Median Filter):

    • 中值滤波是处理椒盐噪声非常有效的方法。它通过将每个像素的值替换为其邻域内像素值的中值来工作。这种方法可以有效地去除极端的像素值,而不会模糊图像边缘。
  2. 最小值滤波器(Min Filter):

    • 最小值滤波器将每个像素的值替换为其邻域内的最小值。它对于去除白色像素点(盐)特别有效,但可能会导致图像细节的丢失。
  3. 最大值滤波器(Max Filter):

    • 最大值滤波器与最小值滤波器相反,它将每个像素的值替换为其邻域内的最大值。它对于去除黑色像素点(椒)特别有效,但同样可能会导致图像细节的丢失。
  4. 开运算和闭运算(Morphological Opening and Closing):

    • 形态学开运算和闭运算可以用于去除小的噪声点。开运算能够去除小的白色噪声点,闭运算能够去除小的黑色噪声点。

哪个滤波去噪最好:

中值滤波器通常被认为是去除椒盐噪声最有效的方法,因为它在去噪的同时能够很好地保持图像的边缘和细节。它不依赖于像素的平均值,因此不会将噪声平滑到邻近的正确像素上。不过,选择最佳的滤波器也取决于具体的应用场景和图像特性,以及噪声的程度和类型。在某些情况下,可能需要结合使用多种滤波器或采用更高级的图像处理技术来获得最佳的去噪效果。

故此此篇着重介绍如何使用中值滤波进行图像去噪.

中值滤波概述

中值滤波是一种非线性数字滤波技术,常用于图像处理领域,特别是用于去除图像中的椒盐噪声(salt-and-pepper noise)。它的基本原理是用图像中某个像素周围邻域内的中值来代替该像素的值。

原理:

  1. 邻域选择:对于图像中的每个像素,选择一个邻域,通常是以当前像素为中心的正方形窗口。

  2. 排序:收集窗口内所有像素的灰度值,并将它们进行排序。

  3. 取中值:从排序后的列表中选择中间的值(如果列表长度为偶数,则通常取中间两个数的平均值)。

  4. 替换:将原始像素的值替换为上一步得到的中值。

  5. 边界处理:对于位于图像边缘的像素,其窗口可能会超出图像边界。在这种情况下,可以采用不同的策略处理,如忽略边界、复制边界、镜像边界等。

优点:

  • 有效去除椒盐噪声:中值滤波特别适合去除随机出现的、极端的噪声值,比如椒盐噪声。

  • 边缘保持:与平均滤波器相比,中值滤波在去噪的同时,能较好地保持图像边缘信息。

  • 简单易实现:中值滤波算法简单,容易在各种编程环境中实现。

缺点:

  • 细节丢失:虽然中值滤波能保持边缘,但在处理过程中,一些细节信息可能会丢失。

  • 计算量相对较大:排序操作相对耗时,特别是当窗口大小增大时,计算量会显著增加。

  • 不适合去除高斯噪声:对于高斯噪声等其他类型的噪声,中值滤波不如线性滤波器(如高斯滤波)有效。

  • 窗口大小选择:窗口大小的选择对结果有较大影响。窗口太小可能去噪不彻底,窗口太大又可能过度模糊图像。

中值滤波的基本步骤:

开始
为每个像素选择邻域
收集邻域内所有像素值
对邻域内的像素值进行排序
选择排序后的中间值
用中间值替换当前像素值
是否处理完所有像素?
结束

实现类:

源码:

import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.Arrays;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;
/**
 * @Author derek_smart
 * @Date 202/5/17 10:21
 * @Description 中值滤去噪
 * 使用了多线程技术来提高处理大型图像
 * 考虑到了边缘像素的处理
 * <p>
 */
public class MedianFilter {

    private static final int BLOCK_SIZE = 64; // Block size for processing
    private static final int KERNEL_SIZE = 3; // Kernel size for median filter (must be odd)
    private static final int EDGE_EXTENSION = KERNEL_SIZE / 2; // Edge extension for padding

    /**
     * 创建一个填充的图像(以处理边缘像素),将图像分割成多个块,并使用线程池并行处理每个块。最后,返回经过中值滤波的图像。
     * @param originalImage
     * @return
     * @throws InterruptedException
     */
    public static BufferedImage applyMedianFilter(BufferedImage originalImage) throws InterruptedException {
        int width = originalImage.getWidth();
        int height = originalImage.getHeight();

        // Create a padded image to handle the edges
        BufferedImage paddedImage = new BufferedImage(width + 2 * EDGE_EXTENSION, height + 2 * EDGE_EXTENSION, originalImage.getType());
        Graphics g = paddedImage.getGraphics();
        g.drawImage(originalImage, EDGE_EXTENSION, EDGE_EXTENSION, null);
        g.dispose();

        // Copy the edges to the padding
        copyEdgesToPadding(paddedImage, originalImage, EDGE_EXTENSION);

        BufferedImage filteredImage = new BufferedImage(width, height, originalImage.getType());
        ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

        // Split the image into blocks and process each block
        for (int i = 0; i < width; i += BLOCK_SIZE) {
            for (int j = 0; j < height; j += BLOCK_SIZE) {
                final int blockStartX = i;
                final int blockStartY = j;
                executor.submit(() -> processBlock(paddedImage, filteredImage, blockStartX, blockStartY, BLOCK_SIZE));
            }
        }

        executor.shutdown();
        executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);

        return filteredImage;
    }

    /**
     * 处理给定块的像素,应用中值滤波,并将结果写入`filteredImage`。
     * @param paddedImage  填充后的图像,用于处理边缘像素。
     * @param filteredImage 将存储过滤结果的图像。
     * @param blockStartX 当前块在未填充图像中的起始X坐标。
     * @param blockStartY当前块在未填充图像中的起始Y坐标。
     * @param blockSize 块的大小
     */
    private static void processBlock(BufferedImage paddedImage, BufferedImage filteredImage, int blockStartX, int blockStartY, int blockSize) {
        int paddedWidth = paddedImage.getWidth();
        int paddedHeight = paddedImage.getHeight();
        int blockWidth = Math.min(blockSize, filteredImage.getWidth() - blockStartX);
        int blockHeight = Math.min(blockSize, filteredImage.getHeight() - blockStartY);

        for (int x = blockStartX; x < blockStartX + blockWidth; x++) {
            for (int y = blockStartY; y < blockStartY + blockHeight; y++) {
                // Get the median value for each color channel
                int medianRed = findMedian(getNeighborhood(paddedImage, x + EDGE_EXTENSION, y + EDGE_EXTENSION, KERNEL_SIZE, ColorChannel.RED));
                int medianGreen = findMedian(getNeighborhood(paddedImage, x + EDGE_EXTENSION, y + EDGE_EXTENSION, KERNEL_SIZE, ColorChannel.GREEN));
                int medianBlue = findMedian(getNeighborhood(paddedImage, x + EDGE_EXTENSION, y + EDGE_EXTENSION, KERNEL_SIZE, ColorChannel.BLUE));

                // Set the pixel value of the filtered image
                Color newColor = new Color(medianRed, medianGreen, medianBlue);
                filteredImage.setRGB(x, y, newColor.getRGB());
            }
        }
    }

    /**
     *  获取指定像素周围的邻域内的颜色通道值。
     * @param image
     * @param x
     * @param y
     * @param kernelSize
     * @param channel
     * @return
     */
    private static int[] getNeighborhood(BufferedImage image, int x, int y, int kernelSize, ColorChannel channel) {
        int[] values = new int[kernelSize * kernelSize];
        int halfKernel = kernelSize / 2;
        int index = 0;
        for (int i = -halfKernel; i <= halfKernel; i++) {
            for (int j = -halfKernel; j <= halfKernel; j++) {
                Color color = new Color(image.getRGB(x + i, y + j));
                values[index++] = channel.extract(color);
            }
        }
        return values;
    }

    /**
     * 将原始图像的边缘复制到填充区域。
     * @param paddedImage 填充后的图像。
     * @param originalImage 原始图像。
     * @param edgeExtension 边缘扩展的大小。
     */
    private static void copyEdgesToPadding(BufferedImage paddedImage, BufferedImage originalImage, int edgeExtension) {
        int width = originalImage.getWidth();
        int height = originalImage.getHeight();
// Copy top and bottom edges
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < edgeExtension; y++) {
                paddedImage.setRGB(x + edgeExtension, y, originalImage.getRGB(x, 0));
                paddedImage.setRGB(x + edgeExtension, height + edgeExtension + y, originalImage.getRGB(x, height - 1));
            }
        }

        // Copy left and right edges
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < edgeExtension; x++) {
                paddedImage.setRGB(x, y + edgeExtension, originalImage.getRGB(0, y));
                paddedImage.setRGB(width + edgeExtension + x, y + edgeExtension, originalImage.getRGB(width - 1, y));
            }
        }

        // Copy corners
        for (int x = 0; x < edgeExtension; x++) {
            for (int y = 0; y < edgeExtension; y++) {
                // Top-left corner
                paddedImage.setRGB(x, y, originalImage.getRGB(0, 0));
                // Top-right corner
                paddedImage.setRGB(width + edgeExtension + x, y, originalImage.getRGB(width - 1, 0));
                // Bottom-left corner
                paddedImage.setRGB(x, height + edgeExtension + y, originalImage.getRGB(0, height - 1));
                // Bottom-right corner
                paddedImage.setRGB(width + edgeExtension + x, height + edgeExtension + y, originalImage.getRGB(width - 1, height - 1));
            }
        }
    }

    /**
     * 对数组进行排序并返回中值
     * @param values
     * @return
     */
    private static int findMedian(int[] values) {
        Arrays.sort(values);
        return values[values.length / 2];
    }

    /**
     * 从`Color`对象中提取指定颜色通道的值。
     */
    private enum ColorChannel {
        RED, GREEN, BLUE;

        public int extract(Color color) {
            switch (this) {
                case RED:
                    return color.getRed();
                case GREEN:
                    return color.getGreen();
                case BLUE:
                    return color.getBlue();
                default:
                    throw new IllegalArgumentException("Unknown color channel");
            }
        }
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        BufferedImage originalImage = ImageIO.read(new File("C:\my\6.jpg"));
        BufferedImage filteredImage = applyMedianFilter(originalImage);
        File outputFile = new File("C:\my\60.jpg");
        ImageIO.write(filteredImage, "jpg", outputFile);
    }
}

基于中值滤波针对椒盐图像去噪实战

流程图:

开始
加载原始图像
创建填充后的图像
复制原始图像边缘到填充区域
创建线程池
按块分割图像
为每个块创建处理任务
并行处理每个块
处理块内像素
获取邻域像素
计算每个颜色通道的中值
设置过滤后的像素值
所有块处理完毕?
关闭线程池
等待所有任务完成
保存过滤后的图像
结束

实现类总结和归纳:

MedianFilter类提供了一个静态方法applyMedianFilter,用于对图像应用中值滤波。该方法首先创建一个填充后的图像,以确保边缘像素也有完整的邻域。它使用copyEdgesToPadding方法将原始图像的边缘复制到填充区域。 然后,它将图像分割成多个块,并为每个块创建一个线程执行processBlock方法,该方法对块内的每个像素应用中值滤波。

为了提高性能,MedianFilter类利用了多线程处理,使用线程池来并行处理图像块。这种方法允许在现代多核处理器上更有效地利用CPU资源,并减少了同步开销。

该类还包含了一个辅助枚举ColorChannel,用于在处理颜色通道值时提供清晰的代码。getNeighborhood方法用于提取像素周围的邻域内的颜色通道值,而findMedian方法用于计算这些值的中值。

总的来说,MedianFilter类是一个高效的中值滤波器实现,它考虑到了边缘像素的处理,并且使用了多线程技术来提高处理大型图像的性能。

测试结果比对:

测试1: 基于中值滤波针对椒盐图像去噪实战 测试2:

基于中值滤波针对椒盐图像去噪实战 针对以上测试结果看出,中值滤波对于椒盐噪音图像具有良好去噪处理。

总结:

中值滤波是图像去噪的常用技术之一,尤其适用于去除椒盐噪声并保持边缘。然而,对于图像中的细节保持和计算效率,中值滤波可能不是最佳选择。 在实际应用中,可能需要根据具体问题来选择合适的滤波器类型和窗口大小。

转载自:https://juejin.cn/post/7374337248512409635
评论
请登录