当前位置:首页 > 技术 > 正文内容

基于累积分布函数的图像直方图匹配

访客 技术 2026年6月3日 1

引言

在图像处理中,直方图均衡化是一种常用的增强技术,它通过重新分布像素值来使图像的灰度级均匀分布。然而,在某些特定场景下,如需要将一幅图像的灰度分布特性迁移到另一幅图像时,直方图均衡化可能并非最佳选择。此时,直方图匹配(Histogram Matching)技术便应运而生。该方法通过将源图像的直方图转换为目标图像的直方图,实现图像的灰度特性匹配。本文将详细阐述直方图匹配的理论基础、实现步骤及代码示例。

理论基础

直方图匹配的核心思想是利用累积分布函数(Cumulative Distribution Function, CDF)进行映射。其基本原理与直方图均衡化类似,都需要计算图像的概率密度函数(Probability Density Function, PDF)和累积分布函数(CDF)。

设源图像的灰度级为 r,其CDF记为 T(r)。设目标图像的灰度级为 z,其CDF记为 G(z)

直方图匹配的目标是找到一个变换函数 H,使得经过变换后的源图像灰度值 s 满足 s = H(r),并且变换后图像的CDF与目标图像的CDF (G(z)) 匹配。即 T(s) = G(z)

通过令 s = T(r)G(z) = s,我们可以推导出变换函数 H。具体来说,对于源图像中的每一个灰度级 r,我们计算其CDF值 T(r)。然后,在目标图像的CDF G(z) 中寻找与 T(r) 最接近的累积概率值,对应的灰度级 z 即为变换后的灰度值。这个过程可以通过查找表(Lookup Table, LUT)来实现。

实现步骤

1. 直方图统计

首先,需要计算源图像和目标图像各自的直方图。直方图统计是计算每个灰度级出现的频率。


std::vector<int> calculateHistogram(const cv::Mat& image) {
    CV_Assert(image.channels() == 1); // 确保是单通道图像
    std::vector<int> histogram(256, 0);
    for (int row = 0; row < image.rows; ++row) {
        const uchar* pixel_ptr = image.ptr<uchar>(row);
        for (int col = 0; col < image.cols; ++col) {
            histogram[pixel_ptr[col]]++;
        }
    }
    return histogram;
}
    

2. 累积分布函数(CDF)计算

根据直方图,计算出图像的CDF。CDF表示小于或等于当前灰度级的像素所占的比例。


std::vector<float> calculateCDF(const cv::Mat& image) {
    CV_Assert(image.channels() == 1);
    
    std::vector<int> hist = calculateHistogram(image);
    std::vector<float> cdf(256, 0.0f);
    long long totalPixels = image.rows * image.cols;
    
    cdf[0] = static_cast<float>(hist[0]) / totalPixels;
    for (int i = 1; i < 256; ++i) {
        cdf[i] = cdf[i - 1] + static_cast<float>(hist[i]) / totalPixels;
    }
    return cdf;
}
    

3. 构建直方图映射表(LUT)

利用源图像和目标图像的CDF,构建一个映射表。对于源图像的每个灰度级 i,在目标图像的CDF中查找与 srcCdf[i] 最接近的值,并记录对应的目标灰度级。


std::vector<uchar> buildLookupTable(const std::vector<float>& srcCdf, const std::vector<float>& dstCdf) {
    std::vector<uchar> lut(256);
    int dstIndex = 0;
    for (int srcGrayLevel = 0; srcGrayLevel < 256; ++srcGrayLevel) {
        // 寻找目标CDF中第一个大于或等于源CDF的值
        while (dstIndex < 256 && dstCdf[dstIndex] < srcCdf[srcGrayLevel]) {
            dstIndex++;
        }
        
        if (dstIndex == 0) { // 如果源CDF很小,直接映射到0
            lut[srcGrayLevel] = 0;
        } else if (dstIndex == 256) { // 如果源CDF很大,映射到255
            lut[srcGrayLevel] = 255;
        } else {
            // 线性插值
            float prevDstCdf = dstCdf[dstIndex - 1];
            float currDstCdf = dstCdf[dstIndex];
            
            if (std::abs(currDstCdf - prevDstCdf) < 1e-8f) { // 避免除零错误
                lut[srcGrayLevel] = static_cast<uchar>(dstIndex - 1);
            } else {
                float interpolatedGray = static_cast<float>(dstIndex - 1) + 
                                        (srcCdf[srcGrayLevel] - prevDstCdf) / (currDstCdf - prevDstCdf);
                lut[srcGrayLevel] = static_cast<uchar>(cvRound(interpolatedGray));
            }
        }
    }
    return lut;
}
    

4. 应用映射表进行直方图匹配

最后,使用构建好的LUT对源图像进行像素值转换,生成直方图匹配后的图像。


cv::Mat performHistogramMatching(const cv::Mat& sourceImage, const cv::Mat& targetImage) {
    CV_Assert((sourceImage.channels() == 1) && (targetImage.channels() == 1));

    std::vector<float> srcCdf = calculateCDF(sourceImage);
    std::vector<float> dstCdf = calculateCDF(targetImage);

    std::vector<uchar> lut = buildLookupTable(srcCdf, dstCdf);

    cv::Mat matchedImage = sourceImage.clone();
    for (int row = 0; row < sourceImage.rows; ++row) {
        uchar* pixel_ptr = matchedImage.ptr<uchar>(row);
        for (int col = 0; col < sourceImage.cols; ++col) {
            pixel_ptr[col] = lut[pixel_ptr[col]];
        }
    }
    return matchedImage;
}
    

多通道图像处理

对于彩色图像(如RGB),直接对每个通道(R, G, B)分别进行直方图匹配可能会导致图像失真,因为通道间的相关性被破坏。一种更稳妥的方法是选择一个代表亮度的通道进行匹配,例如在Lab颜色空间中对L通道进行匹配,或在HSV颜色空间中对V通道进行匹配。匹配后再将结果转换回RGB格式。

以下示例展示了如何使用Lab颜色空间的L通道进行直方图匹配:


int main() {
    cv::Mat sourceRGB = cv::imread("source_image.jpg");
    cv::Mat targetRGB = cv::imread("target_image.jpg");

    if (sourceRGB.empty() || targetRGB.empty()) {
        // 处理图像加载失败
        return -1;
    }

    // 转换为Lab颜色空间
    cv::Mat sourceLab, targetLab;
    cv::cvtColor(sourceRGB, sourceLab, cv::COLOR_BGR2Lab);
    cv::cvtColor(targetRGB, targetLab, cv::COLOR_BGR2Lab);

    // 分离Lab通道
    std::vector<cv::Mat> sourceLabChannels, targetLabChannels;
    cv::split(sourceLab, sourceLabChannels);
    cv::split(targetLab, targetLabChannels);

    // 对L通道进行直方图匹配
    cv::Mat matchedLChannel = performHistogramMatching(sourceLabChannels[0], targetLabChannels[0]);

    // 替换L通道
    sourceLabChannels[0] = matchedLChannel;
    cv::Mat mergedLab;
    cv::merge(sourceLabChannels, mergedLab);

    // 转换回RGB
    cv::Mat resultRGB;
    cv::cvtColor(mergedLab, resultRGB, cv::COLOR_Lab2BGR);

    cv::imshow("Original Source", sourceRGB);
    cv::imshow("Target Image", targetRGB);
    cv::imshow("Histogram Matched Result", resultRGB);
    cv::waitKey(0);
    cv::destroyAllWindows();

    return 0;
}
    
标签: 图像处理

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。