scratch lenet(3): 直方图均衡化的C语言实现

news2024/11/15 19:43:57

文章目录

    • 1. 目的
    • 2. 原理
    • 3. 实现
      • 3.1 获得直方图 `int hist[256]`
      • 3.2 获得累积分布 `int cdf[256]`
      • 3.3 均衡化公式
      • 3.4 遍历原图,逐点均衡化,得到结果
    • 4. 完整代码和结果
      • 4.1 例子1
      • 4.2 例子2
      • 4.3 例子3
      • 4.4 完整代码
    • 5. References

1. 目的

用 C 语言实现直方图均衡化。

没有调用第三方依赖库, 没有使用 C++。图像加载和保存时使用的 .pgm 这一格式。

2. 原理

首先得到单通道灰度图的直方图这一统计量 int hist[256], 然后得到累积分布函数 cdf (cumulated distribution function), 也就是统计量 int cdf[256], 最后根据均衡化公式遍历原始图像每个像素得到均衡化后的图像像素,得到最终结果。

3. 实现

3.1 获得直方图 int hist[256]

获得直方图的过程不在赘述, 见 scratch lenet(2): C语言实现图像直方图的计算。

3.2 获得累积分布 int cdf[256]

获得 cdf 的过程很简单, 遍历 hist 每个元素并累加即可。由于后续的均衡化公式需要用到 cdf 的最小、最大值, 这里也一并统计算出:

    int cdf[256] = { 0 };
    cdf[0] = hist[0];
    bool found_min = false;
    int min_cdf = -1;
    for (int i = 1; i < 256; i++)
    {
        cdf[i] = hist[i] + cdf[i-1];
        if (!found_min && cdf[i] > 0)
        {
            min_cdf = cdf[i];
            found_min = true;
        }
    }
    int max_cdf = cdf[255];

3.3 均衡化公式

对于原始像素取值为 v 的情况, 均衡化后的值为 h(v), 公式为:
h ( v ) = r o u n d ( cdf ( v ) − cdf ( m i n ) cdf ( m a x ) − cdf ( m i n ) ∗ ( L − 1 ) ) h(v) = round(\frac{\text{cdf}(v) - \text{cdf}(min)}{\text{cdf}(max) - \text{cdf}(min)} * (L-1)) h(v)=round(cdf(max)cdf(min)cdf(v)cdf(min)(L1))

对于图像任务来说, 通常 L = 256 L=256 L=256. 容易写出对应的 C 实现

int equalize(uchar v, int min_cdf, int max_cdf, int cdf[256])
{
    int up = cdf[v] - min_cdf;
    int down = max_cdf - min_cdf;
    float frac = up * 255.f / down;
    int equalized_v = (int)(frac + 0.5);
    return equalized_v;
}

3.4 遍历原图,逐点均衡化,得到结果

    uchar* equalized_image = (uchar*)malloc(height * width);
    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            int idx = i * width + j;
            uchar v = image[idx];

            uchar equalized_v = equalize(v, min_cdf, max_cdf, cdf);

            equalized_image[idx] = equalized_v;
        }
    }

4. 完整代码和结果

4.1 例子1

原图:

在这里插入图片描述

原图和直方图 vs 均衡化后的图像:
在这里插入图片描述

原图的直方图 vs 直方图均衡化后的直方图
在这里插入图片描述

4.2 例子2

原图: 很模糊, 是 28x28 大小的手写数字 3 执行了 5x5 kernel 为全1的卷积计算后的结果:

在这里插入图片描述

直方图均衡化后的结果图:
在这里插入图片描述

原图的直方图 vs 结果图的直方图:
在这里插入图片描述

4.3 例子3

原图:
在这里插入图片描述

直方图均衡化之后的结果图:
在这里插入图片描述

原图的直方图 vs 结果图的直方图:
在这里插入图片描述

4.4 完整代码

// Author: Zhuo Zhang <imzhuo@foxmail.com>
// Homepage: https://github.com/zchrissirhcz
// Date: 2023.06.17

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>

typedef unsigned char uchar;

typedef struct GrayImage
{
    int width;
    int height;
    uchar* data;
} GrayImage;

GrayImage read_pgm_image(const char* filepath)
{
    FILE* fin = fopen(filepath, "rb");
    char magic[3];
    int width, height;
    int nscan = fscanf(fin, "%2s\n%d %d\n255\n", magic, &width, &height);
    uchar* image = NULL;
    if (nscan == 3 && magic[0] == 'P' && magic[1] == '5')
    {
        image = (uchar*)malloc(width * height);
        fread(image, width * height, 1, fin);
    }
    else
    {
        printf("Error: failed to read pgm file %s\n", filepath);
    }
    fclose(fin);

    GrayImage gray;
    gray.width = width;
    gray.height = height;
    gray.data = image;
    return gray;
}

void write_pgm_image(uchar* image, int width, int height, const char* filename)
{
    FILE* fout = fopen(filename, "wb");
    fprintf(fout, "P5\n%d %d\n255\n", width, height);
    fwrite(image, width * height, 1, fout);
    fclose(fout);
}

void* memset(void* s, int c, size_t n)
{
    char x = c & 0xff;
    char* p = (char*)s;
    for (size_t i = 0; i < n; i++)
    {
        p[i] = x;
    }
    return s;
}

void* memcpy(void *dest, const void *src, size_t n)
{
    char* dp = (char*)dest;
    char* sp = (char*)src;
    for (size_t i = 0; i < n; i++)
    {
        *dp = *sp;
        dp++;
        sp++;
    }
    return dest;
}

int equalize(uchar v, int min_cdf, int max_cdf, int cdf[256])
{
    int up = cdf[v] - min_cdf;
    int down = max_cdf - min_cdf;
    float frac = up * 255.f / down;
    int equalized_v = (int)(frac + 0.5);
    return equalized_v;
}

void calculate_histogram(uchar* image, int width, int height, int hist[256])
{
    memset(hist, 0, 256 * sizeof(int));
    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            int idx = i * width + j;
            uchar v = image[idx];
            hist[v]++;
        }
    }
}

GrayImage create_gray_image_from_histogram(int hist[256])
{
    // draw histogram as image
    int max_hist = 0;
    for (int i = 0; i < 256; i++)
    {
        if (hist[i] > max_hist)
        {
            max_hist = hist[i];
        }
    }
    int hist_height = max_hist;
    int hist_width = 256;
    uchar* image = (uchar*)malloc(hist_height * hist_width);
    for (int i = 0; i < hist_height; i++)
    {
        int inv_i = hist_height - 1 - i;
        for (int j = 0; j < hist_width; j++)
        {
            int idx = i * hist_width + j;
            if (inv_i > hist[j])
            {
                image[idx] = 255;
            }
            else
            {
                image[idx] = 0;
            }
        }
    }

    GrayImage hist_image;
    hist_image.width = hist_width;
    hist_image.height = hist_height;
    hist_image.data = image;
    return hist_image;
}

uchar* equalize_histogram(uchar* image, int width, int height, int hist[256])
{
    // get CDF
    int cdf[256] = { 0 };
    cdf[0] = hist[0];
    bool found_min = false;
    int min_cdf = -1;
    for (int i = 1; i < 256; i++)
    {
        cdf[i] = hist[i] + cdf[i-1];
        if (!found_min && cdf[i] > 0)
        {
            min_cdf = cdf[i];
            found_min = true;
        }
    }
    int max_cdf = cdf[255];

    // calculate equalized histogram
    uchar* equalized_image = (uchar*)malloc(height * width);
    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            int idx = i * width + j;
            uchar v = image[idx];

            uchar equalized_v = equalize(v, min_cdf, max_cdf, cdf);
           
            equalized_image[idx] = equalized_v;
        }
    }

    return equalized_image;
}


void test_histogram()
{
    GrayImage gray = read_pgm_image("/home/zz/work/lenet_c/2.pgm");
    uchar* image = gray.data;

    int width = gray.width;
    int height = gray.height;
 
    int hist[256] = { 0 };
    calculate_histogram(image, width, height, hist);
 
    calculate_histogram(image, width, height, hist);
    GrayImage hist_image = create_gray_image_from_histogram(hist);
    write_pgm_image(hist_image.data, hist_image.width, hist_image.height, "res_histogram.pgm");
    free(hist_image.data);
    uchar* equalized_image = equalize_histogram(image, width, height, hist);
    write_pgm_image(equalized_image, width, height, "res_equalized_image.pgm");
    
    int eq_hist[256] = { 0 };
    calculate_histogram(equalized_image, width, height, eq_hist);
    GrayImage eq_hist_image = create_gray_image_from_histogram(eq_hist);
    write_pgm_image(eq_hist_image.data, eq_hist_image.width, eq_hist_image.height, "res_eq_histogram.pgm");
    free(eq_hist_image.data);

    free(equalized_image);
    free(gray.data);
}

int main(int argc, char** argv)
{
    test_histogram();
    return 0;
}

5. References

  1. 直方图均衡化(HE)
  2. scratch lenet(2): C语言实现图像直方图的计算

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/657272.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

低价618背后,看见品牌营销的「产业新洪流」

如今消费者对于低价与品质的兼得需求&#xff0c;正倒逼一个全新的产业经济模式出现&#xff0c;而企业恰是最直接承载者。只有具备真正“低价”的能力模型&#xff0c;企业才能参与到下一轮的产业经济&#xff0c;甚至是社会经济的发展浪潮中。 作者|皮爷 出品|产业家 成本不…

Elasticsearch设置密码

Elasticsearch设置密码 概述ES开启认证配置密码访问开启安全认证的EScurl浏览器直接访问Kibana 配置 es认证直接配置用户名密码到 kibana.yml以kibana密钥的形式使用命令行启动参数形式指定用户名密码 使用kibana 查看es用户 概述 ES默认没有开启安全组件&#xff0c;如果我们…

简单的Dubbo实验环境搭建

Dubbo-api中定义的UserQueryFacade接口可以发布在私服上&#xff0c;这样子dubbo-consumer和dubbo-provider就可以以maven依赖的形式导入使用。dubbo-provider需要提供接口的实现类&#xff0c;dubbo-consumer需要订阅该实现类&#xff0c;他们的元数据都通过zk进行记录。 许多…

Three.js学习项目--3D抗美援朝数据可视化

文章目录 部分场景体验地址操作说明 视频我做了哪些&#xff08;功能&#xff09;局限源代码地址部分逻辑按需渲染模型加载动画控制器模型纹理条件切换模型加载同时请求部分纹理 生成进度条模型缩放小动画 部分场景 体验地址 https://kmyc.hongbin.xyz/ 操作说明 视频 操作说…

LeetCode——查询后矩阵的和

目录 1、题目 2、题目解读 3、代码 1、题目 2718. 查询后矩阵的和 - 力扣&#xff08;Leetcode&#xff09; 给你一个整数 n 和一个下标从 0 开始的 二维数组 queries &#xff0c;其中 queries[i] [typei, indexi, vali] 。 一开始&#xff0c;给你一个下标从 0 开始的…

数学建模常用模型(一):灰色预测法

数学建模常用模型&#xff08;一&#xff09;&#xff1a;灰色预测法 灰色预测法是一种用于处理少量数据、数据质量较差或者缺乏历史数据的预测方法。它适用于一些非线性、非平稳的系统&#xff0c;尤其在短期预测和趋势分析方面有着广泛的应用。灰色预测法作为一种强大的数学…

基于STM32+OneNet设计的物联网智慧路灯

一、前言 近年来,构筑智慧城市、推动城镇发展被国家列入重要工作范畴。发布的《超级智慧城市报告》显示,全球已启动或在建的智慧城市有1000多个,中国在建500个,远超排名第二的欧洲(90个)。从在建智慧城市的分布来看,我国已初步形成环渤海、长三角、珠三角、中西部四大智…

FreeRTOS 任务优先级 【杂记】

FreeRTOS任务优先级 FreeRTOS任务优先级&#xff1a;任务优先级数值越小&#xff0c;任务优先级越低。 1、 FreeRTOS 中任务的最高优先级是通过 FreeRTOSConfig.h 文件中的configMAX_PRIORITIES 进行配置的&#xff0c;用户实际可以使用的优先级范围是 0 到 configMAX_PRIORIT…

python 第七章 字典dict {}

系列文章目录 第一章 初识python 第二章 变量 第三章 基础语句 第四章 字符串str 第五章 列表list [] 第六章 元组tuple ( ) 文章目录 字典的应用场景创建字典的语法字典常见操作增改删查 字典的循环遍历遍历字典的key遍历字典的value遍历字典的元素遍历字典的键值对&#xff0…

【新款DVR、NVR、直播、录播机单芯片解决方案】

新款DVR、NVR、直播、录播机单芯片解决方案 一、 22AP80或SS522V100是入门级DVR解决方案&#xff0c;能做到4路1080p30fps编码 2路 1080p30fps解码 多路图像分析方法智能算法&#xff1b;可以平替Hi3520DV510 二、 22AP10或SS524V100&#xff0c;这是一款中端的DVR芯片&#…

java语言中方法的多态

文章目录 前言一、多态是什么&#xff1f;二、使用步骤 1.实操展示2.注意事项总结 前言 自然界中&#xff0c;生物是多种形态的&#xff0c;繁殖这一行为也是多样的&#xff0c;细菌是裂殖&#xff0c;禽类是卵生&#xff0c;哺乳动物是胎生......java语言中的一个创建的方法&a…

Nike登录的acw_sc__v2参数逆向详细思路分析(非常简单,建议入手)含AST解混淆代码

分析目录 前言一、分析三、总结四、番外1.AST解混淆 前言 最近周末闲着无事&#xff0c;看了一下Nike的登录&#xff0c;发现连环境都不用补acw_sc__v2这个参数&#xff0c;分享出来给大家趣味性娱乐一下 一、分析 打开F12抓包看看登录 老样子复制curl给抓到Postman里面去…

Qt多线程编程之线程池

QThreadPool与QRunnable 线程的创建及销毁需要与系统交互&#xff0c;会产生很大的开销。若需要频繁的创建线程建议使用线程池&#xff0c;有线程池维护一定数量的线程&#xff0c;当需要进行多线程运算时将运算函数传递给线程池即可。线程池会根据可用线程进行任务安排。 QT…

Android studio自动登录和记住密码的实现

Android studio自动登录和记住密码的实现 文章目录 Android studio自动登录和记住密码的实现前言一、效果二、设计思路三、知识点介绍1. SharedPreferenced2. checkButton就不介绍了 四、自动登录及记住密码实现总结与补充 前言 大家好&#xff0c;我是oy&#xff0c;今天介绍…

浅层神经网络

目录 1、神经网络表示 2、计算神经网络的输出 3、多个样本的向量化 4、激活函数 5、激活函数的导数 6、神经网络的梯度下降法 1、神经网络表示 输入层&#xff1a;有输入特征&#x1d465;1、&#x1d465;2、&#x1d465;3隐藏层&#xff1a;四个结点&#xff0c;表示你…

验证性实验 - 逻辑回归

练习2&#xff1a;逻辑回归 介绍 在本练习中&#xff0c;您将实现逻辑回归并将其应用于两个不同的数据集。还将通过将正则化加入训练算法&#xff0c;来提高算法的鲁棒性&#xff0c;并用更复杂的情形来测试模型算法。 在开始练习前&#xff0c;需要下载如下的文件进行数据上…

前端Vue非常简单实用商品分类展示组件 侧边商品分类组件

前端vue非常简单实用商品分类展示组件 侧边商品分类组件 &#xff0c; 下载完整代码请访问uni-app插件市场址:https://ext.dcloud.net.cn/plugin?id13084 效果图如下&#xff1a; #### 使用方法 使用方法 <!-- flist:第一级数组 slist&#xff1a;第二级数组 tlist&…

JS 介绍 Babel 的使用及 presets plugins 的概念

一、Babel 是什么 Bebal 可以帮助我们将新 JS 语法编译为可执行且兼容旧浏览器版本的一款编译工具。 举个例子&#xff0c;ES6&#xff08;编译前&#xff09;&#xff1a; const fn () > {};ES5&#xff08;编译后&#xff09;&#xff1a; var fn function() {}二、B…

NLP实战:使用Word2vec实现文本分类

目录 一、数据预处理 1、加载数据 2. 构建词典 3.生成数据批次和迭代器 二、模型构建 1.搭建模型 2.初始化模型 3.定义训练与评估函数 三、训练模型 1. 拆分数据集并运行模型 2. 测试指定数据 &#x1f368; 本文为[&#x1f517;365天深度学习训练营]内部限免文章&…

设计模式篇---单例模式

文章目录 概念结构与实现优缺点 概念 单例模式是结构最简单的设计模式&#xff0c;通过单例模式可以保证在整个系统中的一个类只有一个实例&#xff0c;从而节约系统资源。举个例子&#xff0c;比如windows电脑下的任务管理器只能打开一个&#xff0c;这个就是单例模式&#x…