<走航车>基于OpenCV或亮度信息熵的图像噪声计算

走航车

这几天再接触一个走航车项目。我的主要任务是,负责对摄像头采集的图片进行处理,由于图片是tiff无损压缩格式,所以存在很多噪声。

目标任务:因为摄像头是那种普通摄像头,不能通过红外拍摄夜景图像。所以会产生存在大量噪点点图像,为此决定在设置摄像头拍摄时间的基础上(不拍摄夜晚的图像),决定分析图片的信息,排除掉那种高噪声的图像。

在这里插入图片描述

1.OpenCV方式

1.1代码

public class DeviceCameraUsbService {

    public void captureImage(String outputPath, String fileName) throws Exception {
        // 打开默认摄像头
        OpenCVFrameGrabber grabber = new OpenCVFrameGrabber(0);

        try{
            grabber.setImageWidth(1920); // 设置图像宽度
            grabber.setImageHeight(1080); // 设置图像高度
            grabber.start();

            try{
                Thread.sleep(3000);
            }catch (Exception e){}

            // 抓拍图像
            Frame frame = grabber.grab();
            if (frame == null) {
                throw new RuntimeException("DeviceCameraUsbService captureImage error: Frame not captured");
            }

            // 转换为Mat对象
            OpenCVFrameConverter.ToMat converter = new OpenCVFrameConverter.ToMat();
            Mat mat = converter.convert(frame);

            // 检测图像是否为噪声
            Mat mean = new Mat();
            Mat stdDev = new Mat();
            opencv_core.meanStdDev(mat, mean, stdDev);

            // 判断标准差是否过低
            boolean isLowStdDev = stdDev.createIndexer().getDouble(0) < 150.0;

            // 判断黑色像素比例是否过高
            int blackPixelCount = 0;
            int totalPixelCount = mat.rows() * mat.cols();
            UByteIndexer indexer = mat.createIndexer();

            for (int y = 0; y < mat.rows(); y++) {
                for (int x = 0; x < mat.cols(); x++) {
                    int r = indexer.get(y, x, 2);
                    int g = indexer.get(y, x, 1);
                    int b = indexer.get(y, x, 0);

                    if (r < 50 && g < 50 && b < 50) { // 这里的阈值可以根据实际情况调整
                        blackPixelCount++;
                    }
                }
            }

            double blackPixelRatio = (double) blackPixelCount / totalPixelCount;
            boolean isMostlyBlack = blackPixelRatio > 0.1; // 黑色像素比例阈值

            // 如果标准差过低且黑色像素比例过高,则认为图像为噪声,设置为全黑
            if (isLowStdDev && isMostlyBlack) {
                System.out.println("DeviceCameraUsbService captureImage detected noise with mostly black pixels, setting image to black.");
                mat = new Mat(mat.rows(), mat.cols(), opencv_core.CV_8UC3, new Scalar(0, 0, 0, 0));
            }

            // 确保frame的大小至少为1080x1080
            if (mat.cols() >= 1080 && mat.rows() >= 1080) {
                // 裁剪图像中心区域
                int width = mat.cols();
                int height = mat.rows();
                int cropSize = 1080;
                int x = (width - cropSize) / 2;
                int y = (height - cropSize) / 2;

                // 确保ROI在图像范围内
                if (x >= 0 && y >= 0 && x + cropSize <= width && y + cropSize <= height) {
                    Rect roi = new Rect(x, y, cropSize, cropSize);
                    Mat cropped = new Mat(mat, roi);

                    // 确保目录存在
                    File dir = new File(outputPath);
                    if (!dir.exists()) {
                        dir.mkdirs();
                    }

                    // 保存为TIFF格式
                    String outputFileName = outputPath + File.separator + fileName;
                    opencv_imgcodecs.imwrite(outputFileName, cropped);

                    System.out.println("DeviceCameraUsbService captureImage success:" + outputFileName);
                } else {
                    throw new RuntimeException("DeviceCameraUsbService captureImage error: ROI is out of image bounds");
                }
            } else {
                throw new RuntimeException("DeviceCameraUsbService captureImage error: Image size is too small for the desired crop size");
            }

        }catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        } finally {
            grabber.stop();
        }
    }


}

1.2核心代码

这段代码的功能是检测图像是否为噪声,并在检测到噪声时将图像设置为全黑。它主要通过两种方式来判断图像是否为噪声:计算图像的标准差和黑色像素的比例。下面是对代码的逐步解释:

  1. 计算图像的均值和标准差

    Mat mean = new Mat();
    Mat stdDev = new Mat();
    opencv_core.meanStdDev(mat, mean, stdDev);
    

    这里使用OpenCV的meanStdDev方法计算图像的均值和标准差,结果存储在meanstdDev对象中。

  2. 判断标准差是否过低

    boolean isLowStdDev = stdDev.createIndexer().getDouble(0) < 150.0;
    

    通过访问标准差对象stdDev的第一个值,判断标准差是否小于150.0。如果标准差较低,说明图像的颜色变化不大,可能是噪声图像。

  3. 计算黑色像素的比例

    int blackPixelCount = 0;
    int totalPixelCount = mat.rows() * mat.cols();
    UByteIndexer indexer = mat.createIndexer();
    
    for (int y = 0; y < mat.rows(); y++) {
        for (int x = 0; x < mat.cols(); x++) {
            int r = indexer.get(y, x, 2);
            int g = indexer.get(y, x, 1);
            int b = indexer.get(y, x, 0);
    
            if (r < 50 && g < 50 && b < 50) { // 这里的阈值可以根据实际情况调整
                blackPixelCount++;
            }
        }
    }
    

    遍历图像的每个像素,统计RGB值都小于50的黑色像素数,并计算黑色像素的比例。

  4. 判断黑色像素比例是否过高

    java
    double blackPixelRatio = (double) blackPixelCount / totalPixelCount;
    boolean isMostlyBlack = blackPixelRatio > 0.1; // 黑色像素比例阈值
    

    计算黑色像素在总像素中的比例,如果比例大于0.1,说明图像中黑色像素过多。

  5. 检测到噪声时将图像设置为全黑

    if (isLowStdDev && isMostlyBlack) {
        System.out.println("DeviceCameraUsbService captureImage detected noise with mostly black pixels, setting image to black.");
        mat = new Mat(mat.rows(), mat.cols(), opencv_core.CV_8UC3, new Scalar(0, 0, 0, 0));
    }
    

    如果标准差过低且黑色像素比例过高,则认为图像为噪声,并将图像设置为全黑。

总结:

  • 代码首先计算图像的标准差和均值。
  • 然后判断标准差是否过低。
  • 接着遍历图像所有像素,计算黑色像素的比例。
  • 最后,如果图像的标准差过低且黑色像素比例过高,则认为图像为噪声,并将图像设置为全黑。

缺点:采用这中方式大概可以排除掉90%的图像,但还是有部分图像漏掉。

2.亮度和信息熵

2.1代码方案

为了解决漏检的问题,我决定采用亮度+信息熵的方式实现。

因为java代码不便对图像进行处理,因此采用python代码实现。

import numpy as np
from PIL import Image
import os

# 定义亮度和信息熵的阈值(R、G、B各通道)
brightness_thresholds = {'R': 94.4, 'G': 127.9, 'B': 169.1}  # 亮度阈值
entropy_thresholds = {'R': 7.1, 'G': 6.7, 'B': 6.4}  # 信息熵阈值

# 指定输入和输出文件夹路径
input_folder = '/Users/gebaokang/Pictures/20240610-1/10'
output_folder = '/Users/gebaokang/Pictures/outputPath'

def calculate_entropy(data):
    # 计算数据的直方图
    hist = np.histogram(data, bins=256, range=(0, 256))[0]
    # 计算概率分布
    prob = hist / float(np.sum(hist))
    # 移除零概率以避免log(0)错误
    prob = prob[prob > 0]
    # 计算信息熵
    entropy = -np.sum(prob * np.log2(prob))
    return entropy

def calculate_rgb_entropy(image_path):
    # 打开图像并将其转换为RGB模式
    img = Image.open(image_path).convert('RGB')
    # 将图像转换为NumPy数组
    img_array = np.array(img)
    # 分离RGB通道
    red_channel = img_array[:, :, 0].flatten()
    green_channel = img_array[:, :, 1].flatten()
    blue_channel = img_array[:, :, 2].flatten()
    # 计算每个通道的信息熵
    red_entropy = calculate_entropy(red_channel)
    green_entropy = calculate_entropy(green_channel)
    blue_entropy = calculate_entropy(blue_channel)
    return red_entropy, green_entropy, blue_entropy

# 确保输出文件夹存在
if not os.path.exists(output_folder):
    os.makedirs(output_folder)

# 遍历指定文件夹下的所有TIFF图片文件
for filename in os.listdir(input_folder):
    if filename.endswith('.tiff') or filename.endswith('.tif'):
        # 读取TIFF图像
        img_path = os.path.join(input_folder, filename)
        img = Image.open(img_path)

        # 计算图像R、G、B通道的亮度值和信息熵
        img_array = np.array(img)
        r_channel = img_array[:, :, 0].flatten()
        g_channel = img_array[:, :, 1].flatten()
        b_channel = img_array[:, :, 2].flatten()
        brightness_r = np.mean(r_channel)
        brightness_g = np.mean(g_channel)
        brightness_b = np.mean(b_channel)
        entropy_r = calculate_entropy(r_channel)
        entropy_g = calculate_entropy(g_channel)
        entropy_b = calculate_entropy(b_channel)

        # 判断是否需要处理图像
        if not ((brightness_r < brightness_thresholds['R'] or
                 brightness_g < brightness_thresholds['G'] or
                 brightness_b < brightness_thresholds['B']) and
                (entropy_r < entropy_thresholds['R'] or
                 entropy_g < entropy_thresholds['G'] or
                 entropy_b < entropy_thresholds['B'])):
            # 图像满足条件,保存到输出文件夹
            img.save(os.path.join(output_folder, filename))

2.2代码含义

代码中涉及的两个重要算法的详细解释:亮度计算信息熵计算

  1. 亮度计算

亮度计算通过求平均值来实现。对于图像的每个通道(R、G、B),分别计算其像素值的平均值,作为该通道的亮度值。

# 获取图像的R、G、B通道数据
r_channel = img_array[:, :, 0].flatten()
g_channel = img_array[:, :, 1].flatten()
b_channel = img_array[:, :, 2].flatten()

# 计算R、G、B通道的亮度(即平均值)
brightness_r = np.mean(r_channel)
brightness_g = np.mean(g_channel)
brightness_b = np.mean(b_channel)
  1. 信息熵计算

信息熵用于度量数据的不确定性或随机性。在图像处理中,信息熵反映了图像中像素值的分布情况。以下是信息熵计算的步骤:

  1. 计算直方图:统计像素值的出现频率。
  2. 计算概率分布:将直方图转换为概率分布。
  3. 移除零概率:避免在计算对数时出现log(0)错误。
  4. 计算信息熵:根据概率分布计算信息熵。
def calculate_entropy(data):
    # 计算数据的直方图
    hist = np.histogram(data, bins=256, range=(0, 256))[0]
    # 计算概率分布
    prob = hist / float(np.sum(hist))
    # 移除零概率以避免log(0)错误
    prob = prob[prob > 0]
    # 计算信息熵
    entropy = -np.sum(prob * np.log2(prob))
    return entropy

对于每个通道(R、G、B),分别计算其信息熵:

# 计算每个通道的信息熵
entropy_r = calculate_entropy(r_channel)
entropy_g = calculate_entropy(g_channel)
entropy_b = calculate_entropy(b_channel)

判断条件

最后,通过亮度和信息熵的阈值来判断图像是否需要处理:

if not ((brightness_r < brightness_thresholds['R'] or
         brightness_g < brightness_thresholds['G'] or
         brightness_b < brightness_thresholds['B']) and
        (entropy_r < entropy_thresholds['R'] or
         entropy_g < entropy_thresholds['G'] or
         entropy_b < entropy_thresholds['B'])):
    # 图像满足条件,保存到输出文件夹
    img.save(os.path.join(output_folder, filename))

具体的判断逻辑是,如果图像的任何一个通道的亮度低于相应的阈值,并且任何一个通道的信息熵低于相应的阈值,则认为该图像不需要处理,否则将图像保存到输出文件夹。

重点:最重要的就是这里判断逻辑的设置,以及阈值设置。

阈值是通过计算所有高噪声图像的亮度、信息熵,最后进行设置。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值