图像处理与图像分析—图像统计特性的计算(纯C语言实现灰度值显示)

news2024/11/16 18:35:53

根据输入的灰度图像,分别计算图像的均值、方差等统计特征,并计算图像的直方图特征并以图形方式显示图像的直方图(用C或C++语言实现)。

学习将会依据教材图像处理与图像分析基础(C/C++)版内容展开

在上个笔记中,我们成功对bmp图像进行了读取,那么这次我们将会尝试计算图像的均值、方差等统计特征,并计算图像的直方图特征并以图形方式显示图像的直方图

首先分析一下我们需要的功能:

  • 能够读取图片
  • 分析图片每一个像素的灰度值
  • 把每一个像素的灰度值存入灰度数组
  • 用直方图的形式进行展示
  • 计算图像的均值,方差

那么上笔记的代码已经可以实现:图片的读取,分析灰度值,那么我们只需要再加上后面三个功能即可

首先我们还是先来分析书上的代码

算法【2-2】统计灰度直方图

void GetHistogram(BYTE* pGryImg, int width, int height, int* histogram)
{
	BYTE * pCur, * pEnd = pGryImg + width * height;
	//step.1 初始化
	//for(int g = 0;g < 256;g++) histogram[g] = 0;
	memset(histogram, 0, sizeof(int) * 256);
	//step.2 直方图统计
	for (pCur = pGryImg; pCur < pEnd;)histogram[*(pCur++)]++;
	//step.3 结束
	return;
}

还是先让我来浅浅的注释一下,看看能理解多少

//pGryImg: 指向灰度图像数据的指针
//width: 图像宽度
//height: 图像高度
//histogram: 存储直方图数据的数组
void GetHistogram(BYTE* pGryImg, int width, int height, int* histogram) {
    BYTE *pCur, *pEnd = pGryImg + width * height; // 定义指针变量并指向图像数据起始位置和结束位置

    // Step 1: 初始化直方图数组
    // 使用memset函数将直方图数组所有元素初始化为0
    memset(histogram, 0, sizeof(int) * 256);

    // Step 2: 直方图统计
    // 遍历灰度图像数组,对每个灰度值出现的次数进行统计
    for (pCur = pGryImg; pCur < pEnd;) {
        histogram[*(pCur++)]++; // 获取当前像素的灰度值并递增对应直方图计数
    }

    // Step 3: 函数结束
    return;
}

不是很难,可以试一下,首先还是写成C语言

void GetHistogram(uint8_t* pImg, int width, int height, int* histogram)
{
    uint8_t* pCur;
    uint8_t* pEnd = pImg + width * height;

    // 初始化直方图数组
    memset(histogram, 0, sizeof(int) * 256);

    // 直方图统计
    for (pCur = pImg; pCur < pEnd;)
    {
        histogram[*pCur]++;
        pCur++}

    // 函数结束
    return;
}

我们用之前写好的代码(图片的读入中的源代码)稍稍修改一下

还是使用这张图片

ME

传入参数少了一个histogram,我们需要创建一个histogram数组

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

void GetHistogram(uint8_t* pImg, int width, int height, int* histogram)
{
    uint8_t* pCur;
    uint8_t* pEnd = pImg + width * height;

    // 初始化直方图数组
    memset(histogram, 0, sizeof(int) * 256);

    // 直方图统计
    for (pCur = pImg; pCur < pEnd;)
    {
        histogram[*pCur]++;
        pCur++}

    // 函数结束
    return;
}

int main()
{
    int histogram[256];
    //建立一个数组
    const char* filename = "E:/code/IDP-Learning-Journey/images/ME.bmp";

    FILE* file = fopen(filename, "rb");

    uint8_t bmpHeader[54];
    size_t bytesRead = fread(bmpHeader, 1, 54, file);

    int width = *(int*)&bmpHeader[18];   // 宽度信息位于偏移量为 18 的位置
    int height = *(int*)&bmpHeader[22];  // 高度信息位于偏移量为 22 的位置

    uint8_t* imageData = (uint8_t*)malloc(width * height); 

    fseek(file, 54, SEEK_SET);  // 跳过 BMP 文件头

    bytesRead = fread(imageData, 1, width * height, file); 

    GetHistogram(imageData, width, height,histogram);
    
    	for (int i = 0; i < 256; i++)
	{
    	printf("histogram[%d]: %d\n", i, histogram[i]);
	}//打印出来看看结果是否成功

    fclose(file);
    free(imageData);

    return 0;
}

image-20240309230227278

成功实现预期功能,接下来查找如何显示为直方图

通过我的不懈努力,变成了这样

image-20240309230633643

很显然,还是没达到要求,这个时候只能网上查一下了

好像解决办法很少,但是还是查到了一个头文件,可以解决C语言生成图片的问题

#include <SDL.h>

SDL(Simple DirectMedia Layer)是一个跨平台的开源多媒体库,用于实现音频、视频、图形和输入设备的访问。SDL提供了一套简单而强大的接口,使得开发者能够在不同平台上编写高性能的多媒体应用程序,而不需要关注底层的细节。

我们用不到那么多功能,只需要能打开一个图形化页面就行


配置SDL

首先需要去GitHub上找到相应的包克隆到本地

Release 2.30.1 · libsdl-org/SDL (github.com)

image-20240310121243846

选择对应的版本,要区分mingw和MSVC编译器,后者也就是vs的编译器

下载到一个能找到的位置解压缩

image-20240310121528238

这里我演示一下如何配置vs2022的环境,如果是mingw,国内很多开源社区都有教程,就不过多去演示了,参考官方文档

step.1 点击项目中的属性
image-20240310121717504
step.2 修改VC++目录下的包含目录为解压目录下的include文件夹的地址

image-20240310121935263

step.3 修改库目录为解压目录下lib地址

image-20240310122117131

step.4 在链接器选择输入,修改附加依赖项为lib目录下lib后缀文件的名称

image-20240310122305329

step5 修改环境变量

首先需要添加lib目录下64位文件夹的地址

image-20240310122451516

接着是我实验发现,总是会报找不到SDL2.dll这个文件的错误,导致程序无法编译,我尝试把这个文件直接放在.c文件的目录下,可以解决问题,但是以后不想这么麻烦,所以我们把这个文件也添加系统变量

image-20240310122744885

问题解决,编译器可以编译,我们现在让chatgpt写一段小程序验证一下

image-20240310122943972

测试代码

#include <stdio.h>
#include <SDL.h>

int main() {
    SDL_Init(SDL_INIT_VIDEO);

    // 创建窗口
    SDL_Window* window = SDL_CreateWindow("SDL Example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, 0);

    // 创建渲染器
    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);

    // 设置绘制颜色为红色
    SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);

    // 清空屏幕
    SDL_RenderClear(renderer);

    // 创建一个矩形
    SDL_Rect rect = { 100, 100, 200, 150 };

    // 填充矩形
    SDL_RenderFillRect(renderer, &rect);

    // 更新屏幕
    SDL_RenderPresent(renderer);

    // 等待2秒
    SDL_Delay(2000);

    // 清理资源
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();

    return 0;
}

这样就配置成功了


SDL是如何绘图的

这个问题很重要,如果不搞清楚我们将无法绘制灰度值直方图

查了一下,分为以下几步

  • 初始化sdl

    这一步是为了指定要使用的SDL子系统,比如视频子系统、音频子系统、定时器子系统等。可以选择仅初始化所需的子系统,以提高性能和减少资源占用。*

SDL_Init(SDL_INIT_VIDEO);//我们使用的画图窗口用的是视频子系统
  • 创建窗口

    创建一个来展示我们直方图数据的窗口

SDL_Window* window = SDL_CreateWindow("Histogram", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 512, 512, SDL_WINDOW_SHOWN);//窗口名称为直方图,接下来两个参数是窗口在屏幕上的位置,分别在水平和垂直都居中显示,窗口的宽度和高度目前按时512和512,这个后面再调整,再后面一个参数的意思是创建窗口立即显示
  • 创建渲染器

    这一步不是很能理解,在我的理解里应该是类似于创建一个画板,可以在窗口里作图的功能

SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);//window为要在其上创建渲染器的窗口指针,-1:指定要使用的渲染器索引。通常将其设置为-1,以便SDL自动选择可用的索引。SDL_RENDERER_ACCELERATED:指定渲染器使用硬件加速,以提高性能。
  • 设置背景颜色

        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);//设置绘制颜色设置为黑色(RGB值为0, 0, 0,表示红、绿、蓝通道均为0,即黑色),同时将透明度设置为255(不透明)
    
        SDL_RenderClear(renderer);//然后通过SDL_RenderClear函数清空渲染器,实际上是在规定背景颜色。
    

    在使用SDL进行图形绘制时,首先通常会设置绘制颜色,然后使用SDL_RenderClear函数填充整个渲染目标(通常是窗口)的背景色。

  • 绘制直方图

    我尝试了使用for循环每次改变x坐标,y坐标为数组中的值,但是好像失败了

image-20240310130657225

先看代码

 int barWidth = 2;// 设置每个直方条的宽度

 for (int i = 0; i < size; i++) {

     SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);//设置白色

     // 计算直方条的位置和高度
     int barX = i * (barWidth + 1);
     int barY = histogram[i];

     
     SDL_Rect bar = { barX, barY, barWidth, (int)(histogram[i]) };
     SDL_RenderFillRect(renderer, &bar);
 }// 绘制

 SDL_RenderPresent(renderer); // 更新renderer

难道是因为,直方图太小了直接看不到吗,如果限定一个高度区间会不会好一点

我思路是这样的

  1. 找到灰度值的最大值
  2. 用规定的最大值去比上灰度值的最大值得到一个比例
  3. 这样所有的灰度值都乘上这个比例,就可以等比例缩放了

ok,用for循环找最大值和最小值

    int maxBarHeight = 0;
    int minBarHeight = 0;

    for (int i = 0; i < size; i++) 
    {
        if (histogram[i] > maxBarHeight) 
        {
            maxBarHeight = histogram[i];
        }
    }

    for (int i = 0; i < size; i++) 
    {
        if (histogram[i] < minBarHeight) 
        {
            minBarHeight = histogram[i];
        }
    }

这样子就有最大值和最小值了,我们的最大值设定为400,因为窗口为512,

宽度还为2,刚好填满窗口

int barWidth = 2;
double k = (double)(400) / maxBarHeight;//设比值为k,定义双精度浮点数400,防止整数相除默认算整数

for (int i = 0; i < size; i++) {
    
    SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);//设置绘制颜色为白色

    int barX = i * barWidth + 1;//每一个x坐标都为循环次数X宽度
    int barY = y - (int)(histogram[i] * k);//这里xy都为每一个柱子左上角点的坐标

    // 绘制直方条
    SDL_Rect bar = { barX, barY, barWidth, (int)(histogram[i] * k) };
    //传递参数为,直方图左上角点的坐标,直方图的宽度和高度
    SDL_RenderFillRect(renderer, &bar);
}

看一下效果

image-20240310133228536

对比一下Photoshop的直方图

image-20240310133811221

再调整一下,把窗口改为512X256,高度改为两百

image-20240310134311229

但是只能展示一瞬间,然后就会结束程序,查过资料之后发现可以添加以下代码来延长展示时间

    SDL_Event event;
    int quit = 0;
    while (!quit) {
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                quit = 1;
            }
        }
    }

再次测试可以正常展示,完成目标。

源代码:

#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <SDL.h>

void displayHistogram(const int* histogram, int size) {

    SDL_Init(SDL_INIT_VIDEO);

    SDL_Window* window = SDL_CreateWindow("Histogram", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 512, 256, SDL_WINDOW_SHOWN);

    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    if (renderer == NULL) {
        printf("Failed to create renderer: %s\n", SDL_GetError());
        return;
    }

    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);

    SDL_RenderClear(renderer);

    int maxBarHeight = 0;
    int minBarHeight = 0;

    for (int i = 0; i < size; i++) 
    {
        if (histogram[i] > maxBarHeight) 
        {
            maxBarHeight = histogram[i];
        }
    }

    for (int i = 0; i < size; i++) 
    {
        if (histogram[i] < minBarHeight) 
        {
            minBarHeight = histogram[i];
        }
    }


    int y = 250;


    int barWidth = 2;
    double k = (double)(200) / maxBarHeight;


    for (int i = 0; i < size; i++) {

        SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);

        int barX = i * barWidth + 1;
        int barY = y - (int)(histogram[i] * k);

        SDL_Rect bar = { barX, barY, barWidth, (int)(histogram[i] * k) };
        
        SDL_RenderFillRect(renderer, &bar);
    }

    SDL_RenderPresent(renderer);

    SDL_Event event;
    int quit = 0;
    while (!quit) {
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                quit = 1;
            }
        }
    }

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);

    SDL_Quit();
}

void GetHistogram(uint8_t* pImg, int width, int height, int* histogram)
{
    uint8_t* pCur;
    uint8_t* pEnd = pImg + width * height;

    // 初始化直方图数组
    memset(histogram, 0, sizeof(int) * 256);

    // 直方图统计
    for (pCur = pImg; pCur < pEnd;)
    {
        histogram[*pCur]++;
        pCur++;
    }

    // 函数结束
    return;
}

int main()
{
    int histogram[256];
    //建立一个数组
    const char* filename = "E:/code/IDP-Learning-Journey/images/ME.bmp";

    FILE* file = fopen(filename, "rb");

    uint8_t bmpHeader[54];
    size_t bytesRead = fread(bmpHeader, 1, 54, file);

    int width = *(int*)&bmpHeader[18];   // 宽度信息位于偏移量为 18 的位置
    int height = *(int*)&bmpHeader[22];  // 高度信息位于偏移量为 22 的位置

    uint8_t* imageData = (uint8_t*)malloc(width * height);

    fseek(file, 54, SEEK_SET);  // 跳过 BMP 文件头

    bytesRead = fread(imageData, 1, width * height, file);

    GetHistogram(imageData, width, height,histogram);

    displayHistogram(histogram, 256);

    fclose(file);
    free(imageData);

    return 0;
}

好的,我们目前还需要展示图像的均值和方差以及各部分信息,首先是均值

均值

没有想到特别好的办法,只能想到所有的灰度值相加,除以总的像素数

用C语言写一个函数,传入值为数组元素和宽度以及高度像素信息

double mean(const int* histogram, int width, int height)
{
    size_t sum = 0;
    size_t totalPixels = width * height;

    for (size_t i = 0; i < 256; i++)
    {     
        sum += i * histogram[i];
    }

    double a = (double)sum / totalPixels;

    return a;
}

其实这里没必要用size_t,是因为我之前思路有问题,一直内存溢出,数字不够存,改变思路后可以成功计算

image-20240310173812290

标准差

也同样没想到什么好的方法,只能想到用循环,对每个像素值与均值之差的平方求和,并除以像素总数减一

写一下代码

double Variance(const int* histogram, int width, int height,double mean)
{
    double variance = 0.0;
    
    for (size_t i = 0; i < width * height; i++) 
    {
        variance += pow(histogram[i] - mean, 2);
    }
    
    variance /= (width * height - 1);
    return variance
}

出现问题

for (size_t i = 0; i < width * height; i++)
{
    printf("%d\n", i);//检查发现循环次数无法达到1000000次以上
    variance += pow(histogram[i] - mean, 2);
}
原因是因为width * height数值太大,需要强制类型转换
不是上面的原因,因为灰度值图像中并没有这么多内存格子,我理解错了

这才是正确的,我真的是看了半个小时,取的数据不是图片所有的数据,而是直方图数据

image-20240310181201358

double StandardDeviation(const int* histogram, int width, int height, double mean)
{
    double variance = 0.0;
    int totalPixels = width * height;

    for (int i = 0; i < 256; i++)
    {
        variance += pow(i - mean, 2) * histogram[i];
    }
	//计算每一个色度值和均值的差的平方再乘上个数得到和最后开平方就可以得到标准差
    variance /= (totalPixels - 1);

    double standardDeviation = sqrt(variance);

    return standardDeviation;
}

image-20240310182432079

差的不是很多,我查了一下原因,应该是因为Photoshop统计的是rgb的标准差和均值,我们计算的是灰度值的标准差和均值

中间值

字如其名,我们只要找到最中间的像素即可

double Median(const int* histogram, int width, int height)
{
    int size = width * height; 
    int cumulativeSum = 0;
    int middleElement = (size + 1) / 2;

    for (int i = 0; i < 256; ++i)
    {
        cumulativeSum += histogram[i];
        if (cumulativeSum >= middleElement)
        {
            return i;
        }
    }

    return 0; 
}

真的是最顺利的一次

image-20240310182856703

明度和对比度

这个我们一起来看书上【程序 2-5】

void GetBrightContrast(int * histogram,double * bright,double * contrast)
{
    int g;
    double sum,num;//书上说图像很亮时,int有可能会溢出,所以我这里直接用double
    double fsum;
    
    //step.1 求亮度
    for(sum = num = 0, g = 0; g < 256 ; g++)
    {
        sum += histogram[g] * g;
        num += histogram[g];
    }
    * bright = sum * 1.0/num;
    
    //step.2 求对比度
    for(fsum = 0.0,g = 0;g < 256;g++)
    {
        fsum += histogram[g]*(g - *bright) * (g - *bright);
    }
    * contrast = sqrt(fsum/(num - 1));//即Std Dev
    
    //step.3 结束
    return;
}

好的观察后发现,明度就是均值,对比度就是标准差

我前面相当于白头脑风暴了

很容易理解我就不再注释了,看一下运行结果

image-20240310184356037

前面还是有点小问题,咱们先用后面的代码,那这样,我们就已经成功得到了图片的亮度,对比度,以及中间值,那么怎么写进直方图里呢

好的,我也没找到好的办法,最好的办法是用SDL的文字库,继续网上加

但是我在测试代码的时候一直提示没有这个库,后来我就想到了能不能直接加在标题上

于是我就创建了一串字符串用作标题,上面传入了计算的明度,对比度以及中间值的数据

代码如下

    char windowTitle[100];
    sprintf(windowTitle, "Histogram      Brightness: %.2f  Contrast: %.2f  Median: %.2f", a, b, c);
    SDL_Window* window = SDL_CreateWindow(windowTitle, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 512, 256, SDL_WINDOW_SHOWN);

详细的讲解在上面讲过,这里就不过多赘述了

我知道这个方法可能的确有点投机取巧了,后面也会去寻找解决办法,我觉得Qt虽然能解决,但是还是更想用C语言去自己敲一遍,这个找到解决办法了再来更新笔记

结果

不过我们看一下运行结果

image-20240310191200504

还是很不错的,对比一下Photoshop

image-20240310191246210

是很接近的,可能偏差的原因就是ps用的是rgb,我只用了单通道的灰度值

总体来看不是很难,但是细节很多,需要对C语言有深刻的了解,接下来我把源码放在下面

#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <SDL.h>


void displayHistogram(const int* histogram, int size,double a,double b,double c) 
{

    SDL_Init(SDL_INIT_VIDEO);

    char windowTitle[100];
    sprintf(windowTitle, "Histogram      Brightness: %.2f  Contrast: %.2f  Median: %.2f", a, b, c);
    SDL_Window* window = SDL_CreateWindow(windowTitle, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 512, 256, SDL_WINDOW_SHOWN);

    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    if (renderer == NULL) {
        printf("Failed to create renderer: %s\n", SDL_GetError());
        return;
    }

    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);

    SDL_RenderClear(renderer);

    int maxBarHeight = 0;
    int minBarHeight = 0;

    for (int i = 0; i < size; i++) 
    {
        if (histogram[i] > maxBarHeight) 
        {
            maxBarHeight = histogram[i];
        }
    }

    for (int i = 0; i < size; i++) 
    {
        if (histogram[i] < minBarHeight) 
        {
            minBarHeight = histogram[i];
        }
    }


    int y = 250;


    int barWidth = 2;
    double k = (double)(200) / maxBarHeight;


    for (int i = 0; i < size; i++) {

        SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);

        int barX = i * barWidth + 1;
        int barY = y - (int)(histogram[i] * k);

        SDL_Rect bar = { barX, barY, barWidth, (int)(histogram[i] * k) };
        
        SDL_RenderFillRect(renderer, &bar);
    }

    SDL_RenderPresent(renderer);

    SDL_Event event;
    int quit = 0;
    while (!quit) {
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                quit = 1;
            }
        }
    }

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);

    SDL_Quit();
}

void GetHistogram(uint8_t* pImg, int width, int height, int* histogram)
{
    uint8_t* pCur;
    uint8_t* pEnd = pImg + width * height;

    // 初始化直方图数组
    memset(histogram, 0, sizeof(int) * 256);

    // 直方图统计
    for (pCur = pImg; pCur < pEnd;)
    {
        histogram[*pCur]++;
        pCur++;
    }

    // 函数结束
    return;
}

double mean(const int* histogram, int width, int height)
{
    size_t sum = 0;
    size_t totalPixels = width * height;

    for (size_t i = 0; i < 256; i++)
    {
        sum += i * histogram[i]; // 将像素值乘以出现次数再累加
    }

    double a = (double)sum / totalPixels;

    return a;
}

double StandardDeviation(const int* histogram, int width, int height, double mean)
{
    double variance = 0.0;
    int totalPixels = width * height;

    for (int i = 0; i < 256; i++)
    {
        variance += pow(i - mean, 2) * histogram[i];
    }

    variance /= (totalPixels - 1);

    double standardDeviation = sqrt(variance);

    return standardDeviation;
}

double Median(const int* histogram, int width, int height)
{
    int size = width * height;
    int cumulativeSum = 0;
    int middleElement = (size + 1) / 2;

    for (int i = 0; i < 256; ++i)
    {
        cumulativeSum += histogram[i];
        if (cumulativeSum >= middleElement)
        {
            return i;
        }
    }

    return 0;
}

void GetBrightContrast(int * histogram,double * bright,double * contrast)
{
    int g;
    double sum,num;//书上说图像很亮时,int有可能会溢出,所以我这里直接用double
    double fsum;
    
    //step.1 求亮度
    for(sum = num = 0, g = 0; g < 256 ; g++)
    {
        sum += histogram[g] * g;
        num += histogram[g];
    }
    * bright = sum * 1.0/num;
    
    //step.2 求对比度
    for(fsum = 0.0,g = 0;g < 256;g++)
    {
        fsum += histogram[g]*(g - *bright) * (g - *bright);
    }
    * contrast = sqrt(fsum/(num - 1));//即Std Dev
    
    //step.3 结束
    return;
}

int main()
{
    int histogram[256];
    //建立一个数组
    const char* filename = "E:/code/IDP-Learning-Journey/images/ME.bmp";

    FILE* file = fopen(filename, "rb");

    uint8_t bmpHeader[54];
    size_t bytesRead = fread(bmpHeader, 1, 54, file);

    int width = *(int*)&bmpHeader[18];   // 宽度信息位于偏移量为 18 的位置
    int height = *(int*)&bmpHeader[22];  // 高度信息位于偏移量为 22 的位置

    uint8_t* imageData = (uint8_t*)malloc(width * height);

    fseek(file, 54, SEEK_SET);  // 跳过 BMP 文件头

    bytesRead = fread(imageData, 1, width * height, file);

    GetHistogram(imageData, width, height,histogram);

    double meanValue = 0;
    double StandardDeviationValue = 0;
    double median = 0;
    double bright, contrast;

    GetBrightContrast(histogram, &bright, &contrast);

    median = Median(histogram, width, height);

    //printf("中间值(Median):%f\n", median);
    //printf("亮度(Brightness): %lf\n", bright);
    //printf("对比度(Contrast): %lf\n", contrast);

    displayHistogram(histogram, 256, bright, contrast,median);

    fclose(file);
    free(imageData);

    return 0;
}

感谢您的阅读!

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

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

相关文章

hdml接口无信号,设备管理器报错:由于该设备有问题,Windows 已将其停止。 (代码 43)

今天&#xff0c;由于带电脑外出演示系统&#xff0c;回公司突然hdml接口无信号了&#xff0c;试了同事的电脑没问题&#xff0c;所以排除显示器和线束问题&#xff0c;最后找到设备管理器报错&#xff1a;由于该设备有问题&#xff0c;Windows 已将其停止。 (代码 43)以下是排…

学生时期学习资源同步-1 第一学期结业考试题6

原创作者&#xff1a;田超凡&#xff08;程序员田宝宝&#xff09; 版权所有&#xff0c;引用请注明原作者&#xff0c;严禁复制转载

AI短视频矩阵获客系统|罐头鱼AI视频批量混剪

AI短视频矩阵获客系统&#xff1a;智能管理&#xff0c;助力您的视频营销 如今&#xff0c;短视频已经成为企业宣传和推广的重要手段。为了满足用户对视频管理、发布和编辑的需求&#xff0c;《AI短视频矩阵获客系统》应运而生。Q:290615413这款智能化系统集成了多种功能&#…

Pandas DataFrame 写入 Excel 的三种场景及方法

一、引言 本文主要介绍如何将 pandas 的 DataFrame 数据写入 Excel 文件中&#xff0c;涉及三个不同的应用场景&#xff1a; 单个工作表写入&#xff1a;将单个 DataFrame 写入 Excel 表中&#xff1b;多个工作表写入&#xff1a;将多个 DataFrame 写入到同一个 Excel 表中的…

传统机器学习 基于TF_IDF的文本聚类实现

简介 使用sklearn基于TF_IDF算法&#xff0c;实现把文本变成向量。再使用sklearn的kmeans聚类算法进行文本聚类。 个人观点&#xff1a;这是比较古老的技术了&#xff0c;文本转向量的效果不如如今的 text2vec 文本转向量好。 而且sklearn 不支持GPU加速&#xff0c;处理大量…

交流回馈老化测试负载如何实现这些功能的

交流回馈老化测试负载是一种用于模拟电力系统中各种负载特性的装置&#xff0c;它可以对电力设备进行长时间的老化测试&#xff0c;以确保其在实际运行中的稳定性和可靠性。交流回馈老化测试负载主要通过以下几个方面实现这些功能&#xff1a; 1. 模拟负载特性&#xff1a;交流…

基于YOLOv8深度学习的野外火焰烟雾检测系统【python源码+Pyqt5界面+数据集+训练代码】深度学习实战、目标检测

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

Python 界面逻辑分离示例

本示例使用的发卡设备&#xff1a;https://item.taobao.com/item.htm?id615391857885&spma1z10.5-c.w4002-21818769070.11.6cc85700Robi3x 一、Python 安装PyQt5&#xff0c;运行 Qt Designer 新建窗体文件&#xff0c;在窗体中拖放控件 完成界面设计&#xff0c;保存为…

模拟信号隔离放大器导轨式直流信号转换器0-5V0-10V4-20mA0-75mV0-20mA负载能力0-85mA/0-165mA /0-200mA

概述 导轨安装DIN11HVI 系列模拟信号隔离放大器是一种将输入信号隔离放大、转换成按比例输出的直流信号混合集成厚模电路。产品广泛应用在电力、远程监控、仪器仪表、医疗设备、工业自控等需要直流信号隔离测控的行业。此系列产品内部采用了线性光电隔离技术相比电磁隔离具有更…

UI 设计师的工作职责是什么?需要掌握哪些技能?

什么是 UI 设计&#xff1f; UI 设计是设计用户界面的一门学科。用户界面是一个人与应用程序、网站或软件之间的联系。类似于平面设计和网页设计&#xff0c;UI 设计者是一种视觉设计师。 即时设计 - 可实时协作的专业 UI 设计工具即时设计是一款支持在线协作的专业级 UI 设计…

算法思想总结:二分查找算法

创作不易&#xff0c;感谢三连&#xff01;&#xff01; 一、二分查找算法思路总结 大家先看总结&#xff0c;然后再根据后面的题型去慢慢领悟 二、二分查找&#xff08;easy&#xff09; . - 力扣&#xff08;LeetCode&#xff09;二分查找 思路&#xff1a;&#xff08;模…

哪个牌子的大路灯对学生的视力好?一文带你了解大路灯

大路灯在如今市场中销量越来越高&#xff0c;与传统台灯相比&#xff0c;大路灯采用LED灯和专业的护眼技术&#xff0c;可以有效缓解用眼疲劳、帮助放松和舒适照明。但需要注意的是&#xff0c;目前市场中品牌类型较多&#xff0c;也有很多劣质的产品&#xff0c;比如网红或跨界…

吴恩达CNN之卷积初学习---二维卷积

1、卷积的实现 从左到右的矩阵可以看作&#xff1a;一幅图像、过滤器filter&#xff08;核&#xff09;、另一幅图像 编程中卷积的实现&#xff1a;支持卷积的深度学习框架都会有一些函数实现这个卷积运算 python&#xff1a;conv_forward函数 TensorFlow&#xff1a;tf.nn.…

ZStack Cloud云平台承载都江堰市人民医院核心业务

三甲医院都江堰市人民医院通过ZStack Cloud云平台对医院信息化架构进行了全面升级改造&#xff1a;一期通过ZStack Cloud云平台分布式存储&#xff0c;承载OA、短信、数字图书馆、餐厅消费系统、体检服务等一般业务&#xff1b;二期通过ZStack Cloud云平台承载医院HIS管理系统、…

18个惊艳的可视化大屏(第23辑):电子政务,一目了然如胸。

数据展示与监控&#xff1a; 可视化大屏可以将政务数据以图表、地图、仪表盘等形式展示出来&#xff0c;直观地呈现政务工作的进展、趋势和关键指标。通过大屏监控&#xff0c;政府部门可以实时了解各项指标的情况&#xff0c;及时发现问题并采取相应措施。 决策支持&#xff…

wifi的5G和3GPP的5G

wifi 5G 跑的是802.11的协议。 wifi的5G指的就是频率&#xff0c;例如wifi2.4G&#xff0c;其频段处于2.400GHz~2.4835GHz之间&#xff0c;wifi5G的频率范围为5.15GHz到5.875GHz&#xff0c;其中包括多个频道。 这里有个误区&#xff0c;并不是运行在5GHz频段的WI-FI就是5G …

鸿蒙:文本滑动选择器弹窗

根据指定的选择范围创建文本选择器&#xff0c;展示在弹窗上。 该组件从API Version 8开始支持 TextPickerDialog.show show(options?: TextPickerDialogOptions) 定义文本滑动选择器弹窗并弹出。 TextPickerDialogOptions参数&#xff1a; 参数名 参数类型 必填 参数…

第四百回 channel

文章目录 1. 知识回顾2. 示例代码3. 经验总结 我们在上一章回中介绍了MethodChannel的使用方法&#xff0c;本章回中将介绍EventChannel的使用方法.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 知识回顾 我们在前面章回中介绍了通道的概念和作用&#xff0c;并且提到了…

探索数据可视化:Matplotlib 高级绘图功能(四)

3D图形 线形图&散点图 import numpy as np import matplotlib.pyplot as pltfrom mpl_toolkits.mplot3d.axes3d import Axes3Dx np.linspace(0,60,300) y np.sin(x) z np.cos(x)fig plt.figure(figsize(9,6)) a3 Axes3D(fig) # 二维变成3D a3.plot(x,y,z)plt.figure…

SecureCRT出现乱码的解决方法

SecureCRT是一个商业终端连接工具&#xff0c;它支持多种自定义设置。默认设置下&#xff0c;通过SecureCRT连接SSH服务器可能出现中文乱码的情况。这是由于SecureCRT字符编码与服务器的字符编码不一致造成的。 当然解决这个问题也很简单&#xff0c;将SecureCRT字符编码设置成…