图像处理与图像分析—图像的读入(C语言)

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


什么是数字图像处理

一副图像可以定义为一个二维函数 f(x,y) ,其中 x 和 y 是空间(平面)坐标,任意一对空间坐标 (x,y) 处的幅度值 (x,y) 称为图像在该坐标点的强度或灰度。当 x,y 和灰度值 f 都是有限的离散量时,我们称该图像为数字图像。数字图像处理是指借助于数字计算机来处理数字图像。注意,数字图像由有限数量的元素组成,每个元素都有一定的位置和数值,这些元素称为像素

好,那么图像数字处理的第一步是什么?

一定是对于单个图片的读取,今天的主要目标也是读取一整个图片的完整信息

所用的语言为C语言,更加底层,能够帮助我们更好的理解

C语言编译器主要有Clang、GCC、WIN-TC、SUBLIME、MSVC、Turbo C等。

但是这里我说一下,我之前在编写C语言代码是所使用的都是GCC编译器,但是这一遍学习准备尝试一下MSVC这样一个微软的编译器,同时开始尝试使用VS2022来进行开发,之前使用Clion进行开发


我们先来看书上的【程序2-1】点运算的经典程序结构
void F1(BYTE* pimg, int width, int height)
{BYTE* pCur, * pEnd;pEnd = pImg + width * height;for (pCur = pImg; pCur < pEnd){*(pCur++) = f(*pCur);}return;
}

我先来分析一下这个代码:

//首先这串代码是一个名为F1的函数,输入参数是一个BYTE类型的指针变量,整型的宽度和高度
void F1(BYTE* pimg, int width, int height)
{BYTE* pCur, * pEnd;//定义了两个指针变量* pCur, * pEndpEnd = pImg + width * height;//表示将指针 pEnd 设置为指向图像数据末尾的位置。这里的计算方式是将指针 pImg 指向的内存地址加上图像的宽度乘以高度,从而得到图像数据的最后一个像素的下一个位置。for (pCur = pImg; pCur < pEnd)//pCur = pImg;:首先将指针变量 pCur 初始化为指向图像数据的起始位置,即指针 pImg 所指向的位置。//pCur < pEnd:这是循环的终止条件。只要 pCur 指针小于 pEnd 指针(即还未到达图像数据的末尾),就会继续执行循环。{*(pCur++) = f(*pCur);//首先会调用函数 f,并将当前指针 pCur 所指向的像素值作为参数传递给函数 f。函数 f 对该像素值进行处理,并返回处理后的结果。然后,将这个处理后的结果写回到当前指针 pCur 所指向的位置,并通过 pCur++ 操作使指针指向下一个像素位置。}return;
}

因为咱们用的是C语言哈,我写这些代码的时候已经发现报错了

原因是啥? —— C语言里面没有BYTE类型,所以只能用别的类型替代,BYTE我去查了一下,人如其名,一个字节的存储空间,那我这里就用uint8_t来代替这个指针类型

那C语言版本就呼之欲出了

void F1(uint8_t* pImg, int width, int height)
{uint8_t* pCur;uint8_t* pEnd;pEnd = pImg + width * height;for (pCur = pImg; pCur < pEnd; pCur++){*pCur = f(*pCur);}return;
}

其实uint8_t变量还是报错了,是因为没有引用头文件,引用#include <stdint.h>就完美解决

那接下来的问题,f函数是什么?—我们先来看下一个例子,再来探讨这个问题


【程序2-2】邻域运算的典型程序结构

void F2(BYTE* pOrgImg, int width, int height, BYTE* pResImg)
{BYTE* pCur, * pRes;int x, y;for (y = 0, pCur = pOrgImg, pRes = pResImg; y < height; y++){for (x = 0; x < width; x++, pCur++, pRes++){*pRes = f(pOrgImg, x, y);}}return;
}

我们还是先看一下这个cpp代码,先根据我的理解注释一下

//四个传入值,指针为BYTE型
//pOrgImg:这是指向原始图像数据的指针。原始图像数据包含了待处理的像素值。
//width:这是图像的宽度,表示图像的水平像素数。
//height:这是图像的高度,表示图像的垂直像素数。
//pResImg:这是指向结果图像数据的指针。结果图像数据将存储经过处理后的像素值。
void F2(BYTE* pOrgImg, int width, int height, BYTE* pResImg)
{BYTE* pCur, * pRes;//创建两个指针,通过指针 pCur 和 pRes 分别指向原始图像和结果图像中当前位置的像素。int x, y;for (y = 0, pCur = pOrgImg, pRes = pResImg; y < height; y++)//y = 0,从顶部开始处理,原始图像指针指向原始图像开始的位置,结果图像指针只想结果图像开始的部分//y一直递增,直到便利完所有的行数{for (x = 0; x < width; x++, pCur++, pRes++){//x循环来保证遍历完每一行的所有像素块//并且,每次原始图像指针和结果图像指针都往后递增来访问正确的操作像素*pRes = f(pOrgImg, x, y);//调用函数 f,并将原始图像数据指针 pOrgImg、当前位置的水平坐标 x 和垂直坐标 y 作为参数传递给 f 函数。f 函数会对该像素进行处理,并返回处理后的结果。}}return;
}

C语言版本顺手写一下

void F2(uint8_t* pOrgImg, int width, int height, uint8_t* pResImg)
{uint8_t* pCur;uint8_t* pRes;int x, y;for (y = 0, pCur = pOrgImg, pRes = pResImg; y < height; y++){for (x = 0; x < width; x++, pCur++, pRes++){*pRes = f(pOrgImg, x, y);}}return;
}

没有报错!


分析F1和F2

函数 F1
  • 参数
    • 接收三个参数:图像数据指针 pImg、图像宽度 width 和图像高度 height
  • 功能
    • 使用一个循环遍历图像中的每个像素。
    • 对每个像素调用函数 f 进行处理,并将处理结果直接更新到原始图像数据中。
函数 F2
  • 参数
    • 接收四个参数:原始图像数据指针 pOrgImg、图像宽度 width、图像高度 height 和结果图像数据指针 pResImg
  • 功能
    • 使用两个嵌套循环遍历原始图像的每个像素。
    • 对每个像素调用函数 f 进行处理,并将处理结果存储到结果图像数据中。
区别:
  1. 循环方式:函数 F1 使用单层循环来遍历像素,而函数 F2 使用嵌套循环进行遍历。
  2. 参数传递:函数 F1 直接操作原始图像数据,而函数 F2 在处理像素时需要额外传递像素的坐标信息 xy
  3. 结果存储:函数 F1 直接在原始图像数据中更新处理结果,而函数 F2 将处理结果存储到另一个结果图像数据中,保持了原始数据的不变性。
函数f

函数 f 被用作对图像数据进行处理的函数

接受一个像素值,处理后在返回一个像素值存入


过程实现

需要读取的图片

ME

本次要求是可以读取数据,那么我们实现的话F1和F2都可以针对像素进行操作

我们这次就通过F1来实现图像的读取

想了一下F1和F2其实都是针对于像素进行编辑操作,那我们的目标就是通过F1实现对于图片数据的读取,并打印在终端

void F1(uint8_t* pImg, int width, int height)
{uint8_t* pCur;uint8_t* pEnd;pEnd = pImg + width * height;for (pCur = pImg; pCur < pEnd; pCur++){//*pCur = f(*pCur);printf("%d",*pCur);}return;
}

C语言怎么去读图片呢,问了一下chatgpt

const char* filename = "path/to/your/image.jpg"; // 图片文件路径// 打开文件
FILE* file = fopen(filename, "rb");
if (file == NULL) {printf("无法打开文件:%s\n", filename);return 1;
}// 获取图像宽度和高度(根据图像格式进行解析)
int width = 0;  // 替换为实际的宽度值
int height = 0; // 替换为实际的高度值// 分配内存来存储图像数据
uint8_t* imageData = (uint8_t*)malloc(width * height);
if (imageData == NULL) {printf("内存分配失败\n");fclose(file);return 1;
}// 读取图像数据
size_t bytesRead = fread(imageData, 1, width * height, file);
if (bytesRead != width * height) {printf("读取图像数据失败\n");free(imageData);fclose(file);return 1;
}

可以参考一下,其中就发现了几个问题

高度和宽度又该如何去自动计算呢?

查了一下jpg格式的图片,其文件结构更为复杂,直接解析文件头部来获取图像的宽度和高度相对困难。JPEG 文件通常包含了大量的压缩数据和标记信息,因此需要专门的 JPEG 解码器来读取并解析这些数据。深度搜索后发现,可以通过开源的 JPEG 解码库来获得

有点难,咱们还是先写死程序

image-20240309175426145

原码:

#include <stdio.h>
#include <stdint.h>void F1(uint8_t* pImg, int width, int height)
{uint8_t* pCur;uint8_t* pEnd;pEnd = pImg + width * height;for (pCur = pImg; pCur < pEnd; pCur++){//*pCur = f(*pCur);printf("%d", *pCur);}return;
}int main()
{const char* filename = "C:/Users/25706/Pictures/Camera Roll/ME.jpg";FILE* file = fopen(filename, "rb");if (file == NULL) {printf("无法打开文件:%s\n", filename);return 1;}int width = 1107;  //通过Windows自带的画图工具看到图片尺寸为1107int height = 1107; uint8_t* imageData = (uint8_t*)malloc(width * height);//按需分配内存if (imageData == NULL) {printf("内存分配失败\n");fclose(file);return 1;}size_t bytesRead = fread(imageData, 1, width * height, file);if (bytesRead != width * height) {printf("读取图像数据失败\n");free(imageData);fclose(file);return 1;}F1(imageData, width, height);//调用F1来打印图片信息fclose(file);    // 关闭文件和释放内存free(imageData);return 0;
}

运行结果:

读取图像数据失败

后面又尝试了png、bmp等图片格式,全都失败了。

失败原因,图片不止这么大,bmp图片还包含别的信息

查了bmp文件格式

BMP文件由4部分组成:

  1. 位图文件头(bitmap-file header)
  2. 位图信息头(bitmap-informationheader)
  3. 颜色表(color table)
  4. 颜色点阵数据(bits data)

24位真彩色位图没有颜色表,所以只有1、2、4这三部分。

所以在 main 函数中,读取图像数据时需要考虑 BMP 图像文件头的大小(通常为 54 字节)。你需要跳过文件头,才能正确读取图像的 RGB 数据。可以通过 fseek 函数将文件指针移动到图像数据的起始位置。

重新修改

重新整理思路

第一步:打开文件

 const char* filename = "E:/code/IDP-Learning-Journey/images/ME.bmp";
//定义常变量指针变量指向文件地址FILE* file = fopen(filename, "rb");
//把未见打开为只读模式

第二部:处理图像数据

uint8_t bmpHeader[54];  
// 定义一个数组来存储 BMP 文件头信息,总共 54 字节
fread(bmpHeader, 1, 54, file);  
// 从文件中读取 54 字节的数据到 bmpHeader 数组中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);  
// 将文件指针移动到 54 字节的位置,跳过 BMP 文件头部分fread(imageData, 1, width * height, file);  
// 从文件中读取图像数据,每个像素点占用 1 个字节

*(int*)&bmpHeader[18] 这个表达式的含义是:

  • &bmpHeader[18] 取得 bmpHeader 数组中第 18 个元素的地址,也就是指向宽度信息的起始位置。
  • (int*) 表示将这个地址强制转换为指向整型数据的指针。
  • * 表示解引用这个指针,获取该地址上的值,即宽度信息。

第三步:调用函数,打印信息

F1(imageData, width, height);

函数在上面讲过了,可以回去看看

void F1(uint8_t* pImg, int width, int height)
{uint8_t* pCur;uint8_t* pEnd;pEnd = pImg + width * height;for (pCur = pImg; pCur < pEnd; pCur++){//*pCur = f(*pCur);printf("%d ", *pCur);  // 输出灰度值}return;
}

第四步:好程序员的良好习惯

fclose(file);
free(imageData);return 0;

成功!!!!!!!!!!!!!!

image-20240309183813597

真的太难了,本来以为不是很难的

原码必须记录:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>void F1(uint8_t* pImg, int width, int height)
{uint8_t* pCur;uint8_t* pEnd;pEnd = pImg + width * height;for (pCur = pImg; pCur < pEnd; pCur++){//*pCur = f(*pCur);printf("%d ", *pCur);  // 输出修改后的像素值}return;
}int main()
{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); F1(imageData, width, height);fclose(file);free(imageData);return 0;
}

灰度值和rgb有什么区别呢,查了一下,区别就是在读取像素点的时候所分配的字节个数

那么如果我们给图片的每个像素点分配三个字节

用F1中pCur的遍历图像数据中的每个像素点。pCur 指向当前正在处理的像素点的起始位置,通过 pCur 指针就可以逐个访问每个像素点的颜色数据。

试验了一下也是可以成功的

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>void F1(uint8_t* pImgRGB, int width, int height)
{uint8_t* pCur;uint8_t* pEnd;pEnd = pImgRGB + width * height * 3;  // 每个像素点占用 3 个字节(BGR)for (pCur = pImgRGB; pCur < pEnd; pCur += 3) {uint8_t blue = pCur[0];uint8_t green = pCur[1];uint8_t red = pCur[2];printf("R: %d, G: %d, B: %d ", red, green, blue);}
}int main()
{const char* filename = "E:/code/IDP-Learning-Journey/images/ME.bmp";FILE* file = fopen(filename, "rb");uint8_t bmpHeader[54];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 * 3);  // 每个像素点占用 3 个字节(BGR)fseek(file, 54, SEEK_SET);  // 跳过 BMP 文件头fread(imageData, 3, width * height, file);  // 每个像素点占用 3 个字节(BGR)F1(imageData, width, height);fclose(file);free(imageData);return 0;
}

image-20240309191106732


感谢您的观看!!!

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

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

相关文章

微信私信短剧机器人源码

本源码仅提供参考&#xff0c;有能力的继续开发 接口为api调用 云端同步 https://ys.110t.cn/api/ajax.php?actyingshilist 影视搜索 https://ys.110t.cn/api/ajax.php?actsearch&name剧名 每日更新 https://ys.110t.cn/api/ajax.php?actDaily 反馈接口 https://ys.11…

R语言复现:中国Charls数据库一篇现况调查论文的缺失数据填补方法

编者 在临床研究中&#xff0c;数据缺失是不可避免的&#xff0c;甚至没有缺失&#xff0c;数据的真实性都会受到质疑。 那我们该如何应对缺失的数据&#xff1f;放着不管&#xff1f;还是重新开始?不妨试着对缺失值进行填补&#xff0c;简单又高效。毕竟对于统计师来说&#…

NUC980开发板CAN开发笔记

一、内核开启CAN CAN 设置 NUC980 系列带有2个CAN(Controller Area Network), 可以分别独立设置。 请按以下的说明来使能CAN功能. 每个CAN可以单独的开关. CAN0有多组管脚可以选择, 需要一并设置。 使用者也可以设置CAN的唤醒功能。步骤如下&#xff1a; 进入 NUC980-linux-4.…

使用Tokeniser估算GPT和LLM服务的查询成本

将LLM集成到项目所花费的成本主要是我们通过API获取LLM返回结果的成本&#xff0c;而这些成本通常是根据处理的令牌数量计算的。我们如何预估我们的令牌数量呢&#xff1f;Tokeniser包可以有效地计算文本输入中的令牌来估算这些成本。本文将介绍如何使用Tokeniser有效地预测和管…

异步编程实战:使用C#实现FTP文件下载及超时控制

博客标题: 异步编程实战&#xff1a;使用C#实现FTP文件下载及超时控制 如果你的函数不是async&#xff0c;你仍然可以实现相同的超时功能&#xff0c;但你将不得不依赖更多的同步代码或使用.Result或.GetAwaiter().GetResult()来阻塞等待任务完成&#xff0c;这可能导致死锁的风…

verilog中的函数和for循环

在Verilog中&#xff0c;clogb2 的英文全称是 “ceiling&#xff08;天花板&#xff09; log base 2”&#xff0c;表示对输入参数取对数&#xff08;以2为底&#xff09;&#xff0c;并向上取整到最接近的整数值。这个函数通常用于计算内存地址宽度或状态数所需的位数12。 fun…

STM32---通用定时器(二)相关实验

写在前面&#xff1a;前面我们学习了基本定时器、通用定时器的相关理论部分&#xff0c;了解到通用定时器的结构框图&#xff0c;总共包含六大模块&#xff1a;时钟源、控制器、时基单元、输入捕获、公共部分以及输出捕获。对相关模块的使用也做详细的讲解。本节我们主要是对上…

Day33-计算机基础3

Day33-计算机基础3 1.根据TCP/IP进行Linux内核参数优化1.1 例1&#xff1a;调整访问服务端的【客户端】的动态端口范围 &#xff0c;LVS&#xff08;10-50万并发&#xff09;&#xff0c;NGINX负载&#xff0c;SQUID缓存服务,1.2 企业案例&#xff1a;DOS攻击的案例&#xff1a…

[备赛笔记]——5G大唐杯(5G考试等级考考试基础试题)

个人名片&#xff1a; &#x1f981;作者简介&#xff1a;学生 &#x1f42f;个人主页&#xff1a;妄北y &#x1f427;个人QQ&#xff1a;2061314755 &#x1f43b;个人邮箱&#xff1a;2061314755qq.com &#x1f989;个人WeChat&#xff1a;Vir2021GKBS &#x1f43c;本文由…

redis缓存满了的话会发生什么?

线上问题 未及时加监控&#xff0c;导致线上redis被逐出&#xff0c;业务有损 示例&#xff1a; 一个key临时存储在redis等缓存中&#xff0c;如果该key在一段时间内有很大作用 比如一次业务请求&#xff0c;上游服务写入一个value&#xff0c;时长1小时&#xff0c;下游服务…

Matlab|考虑源荷两侧不确定性的含风电电力系统低碳调度

目录 1 主要内容 目标函数&#xff1a; 约束条件&#xff1a; 程序亮点总结&#xff1a; 2 代码问题与程序测试 设备出力运行结果&#xff1a; 3 下载链接 1 主要内容 本程序是对《考虑源荷两侧不确定性的含风电电力系统低碳调度》的方法复现&#xff0c;主要实现了基…

【QT+QGIS跨平台编译】之七十六:【QGIS_Native+Qt跨平台编译】(一套代码、一套框架,跨平台编译)

文章目录 一、QGIS_Native介绍二、QGIS下载三、文件分析四、pro文件五、编译实践一、QGIS_Native介绍 QGIS_Native模块是QGIS软件的核心部分,提供了许多基本功能和核心组件,主要用于处理与底层操作系统的关系。 二、QGIS下载 QGIS网址: QGIS Source Download 三、文件分析…

Django学习笔记

Django学习笔记 一、Django整体流程跑通 1.1安装 pip install django //安装 import django //在python环境中导入django django.get_version() //获取版本号&#xff0c;如果能获取到&#xff0c;说明安装成功Django目录结构 Python310-Scripts\django-admi…

前端框架的发展历程

文章目录 前言 一、静态页面时代 二、JavaScript的兴起 三、jQuery的出现 四、前端框架的崛起 1.AngularJS 2.React 3.Vue.js 五、面向组件化的发展趋势 总结 前言 前端框架的发展史就是一个不断进化的过程&#xff0c;它的发展和进化一定程度…

力扣刷题Days14第二题--80删除数组中重复元素||(js)

目录 1&#xff0c;题目-中等 2&#xff0c;代码 双指针 3&#xff0c;学习与总结 思路学习与整理 1&#xff0c;题目-中等 给你一个有序数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使得出现次数超过两次的元素只出现两次 &#xff0c;返回删除后数组…

区块链和人工智能的关系以及经典案例

目录 1.区块链与人工智能的关系 2.应用案例&#xff1a;基于区块链的医疗数据共享平台 2.1背景 2.2方案 2.3优势 2.4挑战 区块链技术和人工智能&#xff08;AI&#xff09;是两种不同的技术&#xff0c;但它们之间存在着互补关系。区块链技术提供了一种安全、透明、去中心…

Android Studio下载gradle超时问题解决

方法一 1. 配置根目录的setting.gradle.kts文件 pluginManagement {repositories {maven { urluri ("https://www.jitpack.io")}maven { urluri ("https://maven.aliyun.com/repository/releases")}maven { urluri ("https://maven.aliyun.com/repos…

Open-Sora:开源 Sora 复现方案,成本降低 46%

Colossal-AI 开源了完整的 Sora 复现架构方案 Open-Sora&#xff0c;声称可降低 46% 复现成本&#xff0c;并将模型训练输入序列长度扩充至 819K patches。 演示站点&#xff1a; https://ai.uaai.cn UAAI 官方论坛&#xff1a; www.jingyuai.com京娱AI Sora 算法复现方案 在 …

springboot256基于springboot+vue的游戏交易系统

游戏交易系统设计与实现 摘 要 在如今社会上&#xff0c;关于信息上面的处理&#xff0c;没有任何一个企业或者个人会忽视&#xff0c;如何让信息急速传递&#xff0c;并且归档储存查询&#xff0c;采用之前的纸张记录模式已经不符合当前使用要求了。所以&#xff0c;对游戏交…

[LeetCode][LCR143]树的子结构判断——递归

题目 LCR 143. 子结构判断 给定两棵二叉树 tree1 和 tree2&#xff0c;判断 tree2 是否以 tree1 的某个节点为根的子树具有相同的结构和节点值。注意&#xff0c;空树不会是以 tree1 的某个节点为根的子树具有相同的结构和节点值。 示例&#xff1a; 输入&#xff1a;tree1 …