当前位置: 首页 > news >正文

digit_eye开发记录(3): C语言读取MNIST数据集

在前两篇,我们解读了 MNIST 数据集的 IDX 文件格式,并分别用 C++ 和 Python 做了 读取 MNIST 数据集的实现。 基于 C++ 的代码稍长,基于 Python 的代码则明显更短,然而它们的共同特点是:依赖了外部库:

  • 基于 C++ 的实现: 依赖了 OpenCV
  • 基于 Python 的实现: 依赖了 Numpy

基于 C++ 的实现,有哪些问题

为了配置 OpenCV,无论是手动下载 OpenCV 预编译包 + 自行写 CMake 配置; 还是安装 vcpkg 后,从 vcpkg 安装 OpenCV + 自行写 CMake 配置,都略微麻烦:

  • vcpkg install opencv 会在本地源码编译 opencv,耗时几十分钟

即便配置完毕,还会看到关于 cmake minimum version 的提示:
在这里插入图片描述
读取 MNIST 数据集这个任务的规模很小,不用 vcpkg、不用 OpenCV,完全可以做到的。更进一步,还可以拿掉 C++ 的 std::vectorstd::stringstd::fstream. 那么为啥不用 C 语言实现?完全可以。

基于 Python 的实现,有哪些问题

Pure Python 的性能堪忧,调用 Numpy 库性能确实不错,但 Numpy 是 C/C++ 实现,这性能其实和 Python 本身无关。

如果为了让代码短小,那么基于 numpy 的实现也仍显啰嗦:tensorflow/pytorch/keras/sklearn 等开源库,早就提供了 mnist 的读取的实现,安静的做一个调用者,也挺快乐的,不是吗?

基于 C 语言的实现 - 可视化怎么做?

1. 基于 ImageWatch 的自定义图像格式可视化

基于 C++ 的实现, 用了 OpenCV 是为了图像可视化,是为了验证图像和标签是否配对。抛开 OpenCV,在 Windows 下可以使用 Visual Studio 中的 ImageWatch 插件,自行扩展一下,可以得到可视化。

先看一下效果:左侧是meta信息,表明是 DE_GrayImage 类型的数据结构,大小是28x28,元素是 UINT8 类型,通道是1个;右图则是 ImageWatch 可视化的结果
在这里插入图片描述

ImageWatch 还提供了常见图像操作,如阈值化,@thread(image, 128) 后可视化为:
在这里插入图片描述
又或者,旋转90度:@rot90(image):
在这里插入图片描述
其他更多操作,可以在 ImageWatch文档 找到:
在这里插入图片描述
我们回到如何显示上述的 DE_GrayImage 类型的问题上:首先在C代码中定义:

typedef struct DE_GrayImage
{unsigned int width;unsigned int height;unsigned char* data;
} DE_GrayImage;

然后创建文件 C:\Users\zz\Documents\Visual Studio 2022\Visualizers\DE_GrayImage.natvis, 内容如下:

<?xml version="1.0" encoding="utf-8"?> 
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> <UIVisualizer ServiceId="{A452AFEA-3DF6-46BB-9177-C0B08F318025}" Id="1"  MenuName="Add to Image Watch"/> <Type Name="DE_GrayImage"> <UIVisualizer ServiceId="{A452AFEA-3DF6-46BB-9177-C0B08F318025}" Id="1" /> </Type> <Type Name="DE_GrayImage"> <Expand> <Synthetic Name="[type]"> <DisplayString>UINT8</DisplayString> </Synthetic><Item Name="[channels]">1</Item> <Item Name="[width]">width</Item> <Item Name="[height]">height</Item> <Item Name="[data]">data</Item> <Item Name="[stride]">width</Item> </Expand> </Type>   
</AutoVisualizer>

简单解释下:

  • [type], [channels], [width], [height], [data], [stride] 是 ImageWatch 插件规定我们在编写 .natvis 文件来可视化图像时,需要填写的字段
  • <Item Name="[channels]">1</Item> 是为 channels 硬编码一个数值
  • <Synthetic Name="[type]" 则是指定数据类型

保存 .natvis 文件后,重新执行 Visual Studio 里的调试会话,就可以查看 DE_GrayImage 类型的图像的可视化了。嗯, ImageWatch 挺强大的。

不过, ImageWatch 也有不足

第一个不足:当 ImageWatch 查看的表达式本身非法时,并没有什么提示。

例如 dataset->images[0], 在 print_sample 函数内,ImageWatch 能正常显示图像内容,因为此时 dataset->images[0] 是合法的表达式
在这里插入图片描述
而当调用堆栈回到 main 函数, dataset->images[0] 不再是合法表达式, ImageWatch 直接显示为 invalid:
在这里插入图片描述
而仔细检查了代码后,发现此时 dataset 类型是 DataSet 而非 DataSet* 后,改为使用 dataset. Images[0] ,就能正常显示:
在这里插入图片描述

第二个不足: @mem(address, type, channels, width, height, stride) 并不能把一块内存当作图像显示

在这里插入图片描述

2. 化繁为简,在控制台显示图像

void print_sample(const DataSet* dataset, int index)
{DE_GrayImage* image = &dataset->images[index];printf("label: %d\n", (int)dataset->labels[index]);for (int i=0; i<28; i++){for (int j=0; j<28; j++){for (int k=0; k<3;k++)printf("%c", image->data[i * 28 + j] > 128 ? '#' : ' ');}printf("\n");}
}

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

完整代码

对于 MNIST 数据的读取,由于我们已经很熟悉它的格式,这里直接给出 C 风格的文件读取写法.

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>long get_filesize(FILE* fp)
{fseek(fp, 0, SEEK_END);long filesize = ftell(fp);fseek(fp, 0, SEEK_SET);return filesize;
}typedef enum Endian {ENDIAN_LSB = 0,ENDIAN_MSB = 1
} Endian;int read_int_from_4_bytes(unsigned char* buf, Endian endian)
{int x = 0;int c[2][4] = {{ (1 << 0),  (1 << 8), (1 << 16), (1 << 24) },{ (1 << 24), (1 << 16), (1 << 8), (1 << 0) }};for (int i=0; i<4; i++)x += buf[i] * c[endian][i];return x;
}typedef struct DE_GrayImage
{unsigned int width;unsigned int height;unsigned char* data;
} DE_GrayImage;typedef struct DataSet
{DE_GrayImage* images;uint8_t* labels;uint8_t* image_buf;uint8_t* label_buf;int num_images;int num_labels;
} DataSet;void destroy_dataset(DataSet* dataset)
{if (dataset){free(dataset->image_buf);dataset->image_buf = NULL;free(dataset->label_buf);dataset->labels = NULL;free(dataset->images);dataset->images = NULL;}
}void load_labels(DataSet* dataset, const char* filename)
{FILE* fin = fopen(filename, "rb");long filesize = get_filesize(fin);unsigned char* buf = (unsigned char*)malloc(filesize + 1);if (buf == NULL)exit(1);buf[filesize] = '\0';dataset->label_buf = buf;fread((void*)buf, filesize, 1, fin);fclose(fin);dataset->num_labels = read_int_from_4_bytes(buf + 4, ENDIAN_MSB);dataset->labels = buf + 8;
}void load_images(DataSet* dataset, const char* filename)
{FILE* fin = fopen(filename, "rb");long filesize = get_filesize(fin);unsigned char* buf = (unsigned char*)malloc(filesize + 1);if (buf == NULL)exit(1);dataset->image_buf = buf;buf[filesize] = '\0';fread((void*)buf, filesize, 1, fin);fclose(fin);uint8_t magic[4] = { buf[0], buf[1], buf[2], buf[3] };int num_images = read_int_from_4_bytes(buf + 4, ENDIAN_MSB);int rows = read_int_from_4_bytes(buf + 8, ENDIAN_MSB);int cols = read_int_from_4_bytes(buf + 12, ENDIAN_MSB);DE_GrayImage* images = (DE_GrayImage*)malloc(sizeof(DE_GrayImage) * num_images);if (images == NULL) exit(1);dataset->images = images;for (int i=0; i<num_images; i++){images[i].height = rows;images[i].width = cols;images[i].data = buf + 16 + i * rows * cols;}
}void print_sample(const DataSet* dataset, int index)
{DE_GrayImage* image = &dataset->images[index];printf("label: %d\n", (int)dataset->labels[index]);for (int i=0; i<28; i++){for (int j=0; j<28; j++){for (int k=0; k<3;k++)printf("%c", image->data[i * 28 + j] > 128 ? '#' : ' ');}printf("\n");}
}int main()
{DataSet dataset;load_images(&dataset, "C:/work/digit_eye/data/train-images.idx3-ubyte");load_labels(&dataset, "C:/work/digit_eye/data/train-labels.idx1-ubyte");print_sample(&dataset, 0);print_sample(&dataset, 233);print_sample(&dataset, 666);printf("wait\n");destroy_dataset(&dataset);return 0;
}

总结

这一篇尝试了以最少依赖的方式,实现 MNIST 数据集的读取,假定了读者已经熟悉 MNIST 数据集格式。 使用 C 语言而非 C++,在图像可视化方面去掉了对于 OpenCV 的依赖,探索了使用 ImageWatch 插件、 在控制台输出这两种方式;在文件读取方面使用 C标准库的 fopen, fread, ftell 等 API 替代了 C++ 的 std::fstream

References

  • https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2015/debugger/image-watch/image-watch-reference?view=vs-2015#pixel-formats

相关文章:

digit_eye开发记录(3): C语言读取MNIST数据集

在前两篇&#xff0c;我们解读了 MNIST 数据集的 IDX 文件格式&#xff0c;并分别用 C 和 Python 做了 读取 MNIST 数据集的实现。 基于 C 的代码稍长&#xff0c;基于 Python 的代码则明显更短&#xff0c;然而它们的共同特点是&#xff1a;依赖了外部库&#xff1a; 基于 C …...

【linux】(23)对象存储服务-MinIo

MinIO 是一个高性能的对象存储服务&#xff0c;兼容 Amazon S3 API。 Docker安装MinIo 前提条件 确保您的系统已经安装了 Docker。如果还没有安装 Docker&#xff0c;可以参考 Docker 官方文档进行安装。 1. 拉取 MinIO Docker 镜像 首先&#xff0c;从 Docker Hub 拉取 Mi…...

如何使用Python解析从淘宝API接口获取到的JSON数据?

基本的 JSON 解析 当从淘宝 API 接口获取到数据后&#xff08;假设数据存储在变量response_data中&#xff09;&#xff0c;首先要判断数据类型是否为 JSON。如果是&#xff0c;就可以使用 Python 内置的json模块进行解析。示例代码如下&#xff1a; import json # 假设respon…...

C# 2024年Visual Studio实用插件集合

在2024年&#xff0c;Visual Studio作为.NET开发者的首选IDE&#xff0c;其插件生态不断壮大&#xff0c;为开发者提供了更高效、便捷的开发体验。本文将介绍一些实用的Visual Studio插件&#xff0c;特别是针对C#开发者&#xff0c;帮助提升开发效率和代码质量。 1. GitHub C…...

Matlab Simulink HDL Coder开发流程(一)— 创建HDL兼容的Simulink模型

创建HDL兼容的Simulink模型 一、使用Balnk DUT模板二、从HDL Coder库中选择模块三、为DUT开发算法/功能四、为设计创建Testbench五、仿真验证设计功能六、Simulink模型生成HDL代码 这个例子说明了如何创建一个用于生成HDL代码的Simulink模型。要创建兼容HDL代码生成的MATLAB算法…...

详解Qt pdf 之QPdfSelection 选择文本类

文章目录 QPdfSelection 类详解前言 详细说明公共函数说明1. 构造函数2. text3. boundingRect4. isEmpty5. startPage6. endPage 使用场景示例代码代码说明总结 QPdfSelection 类详解 前言 QPdfSelection 是 Qt PDF 模块中的一个类&#xff0c;用于表示在 PDF 文档中被选中的…...

docker中redis查看key、删除key

查看docker启动的进程 docker ps这个命令会列出所有正在运行的容器&#xff0c;包括容器的 ID、镜像名称、创建时间、状态、端口映射和名称 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 1a2b3c4d5e6…...

【MySQL — 数据库基础】MySQL的安装与配置 & 数据库简单介绍

数据库基础 本节目标 掌握关系型数据库&#xff0c;数据库的作用掌握在Windows和Linux系统下安装MySQL数据库了解客户端工具的基本使用和SQL分类了解MySQL架构和存储引擎 1. 数据库的安装与配置 1.1 确认MYSQL版本 处理无法在 cmd 中使用 mysql 命令的情况&a…...

ehr系统建设方案,人力资源功能模块主要分为哪些,hrm平台实际案例源码,springboot人力资源系统,vue,JAVA语言hr系统(源码)

eHR人力资源管理系统&#xff1a;功能强大的人力资源管理工具 随着企业规模的不断扩大和业务需求的多样化&#xff0c;传统的人力资源管理模式已无法满足现代企业的需求。eHR人力资源管理系统作为一种先进的管理工具&#xff0c;能够为企业提供高效、准确、实时的人力资源管理。…...

【解决安全扫描漏洞】---- 检测到目标站点存在 JavaScript 框架库漏洞

1. 漏洞结果 JavaScript 框架或库是一组能轻松生成跨浏览器兼容的 JavaScript 代码的工具和函数。如果网站使用了存在漏洞的 JavaScript 框架或库&#xff0c;攻击者就可以利用此漏洞来劫持用户浏览器&#xff0c;进行挂马、XSS、Cookie劫持等攻击。 1.1 漏洞扫描截图 1.2 具体…...

flink学习(12)——checkPoint

如何设置checkPoint package com.bigdata.day06;/** * 1、需要三句话 * 2、设置完checkPoint后若程序出现异常&#xff0c;会一直重启 * 3、此时是自动进行checkPoint保存 * 4、注意&#xff1a;此时如果有checkpoint ,是不会出现异常的&#xff0c;需要将checkpoint的代码关…...

【iOS】《Effective Objective-C 2.0》阅读笔记(一)

文章目录 前言了解OC语言的起源在类的头文件中尽量少引入其他头文件多用字面量语法&#xff0c;少用与之等价的方法字面量数值字面量数组字面量字典 多用类型常量&#xff0c;少用#define预处理指令用枚举法表示状态、选项、状态码 总结 前言 最近开始阅读一些iOS开发的相关书籍…...

LVS 负载均衡面试题及参考答案

目录 什么是 LVS 负载均衡?它的主要作用是什么? 为什么要使用 LVS 进行负载均衡? LVS 有哪些组成部分? 简述 LVS 的架构。 LVS 中有哪两种典型的架构?请简要说明它们的特点。 LVS 的工作原理是怎样的?简述 LVS 的工作原理。 解释 LVS 中的虚拟服务器(VS)概念。 …...

北京科博会 天云数据CEO雷涛谈人工智能技术服务数字资产建设

7月13日&#xff0c;第二十六届中国北京国际科技产业博览会(简称北京科博会)在国家会议中心开幕。本届科博会年度主题为“实施创新驱动发展战略 增强高质量发展动能”。会上&#xff0c;天云数据CEO雷涛发表《人工智能技术服务数字资产建设》主题演讲。 近期非常引人注目的事件…...

【Python运维】容器管理新手入门:使用Python的docker-py库实现Docker容器管理与监控

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 随着容器技术的广泛应用,Docker已经成为开发和运维中的标准工具之一。使用Python语言管理Docker容器,不仅可以自动化繁琐的容器操作,还能…...

小程序解决大问题-物流系统磁盘爆满问题处理

晚上七点&#xff0c;煤矿调运的物流调度系统突然磁盘报名导致服务崩溃。系统用的是微服务&#xff0c;没有详细操作说明&#xff0c;也不敢动&#xff0c;运煤车辆排起了长队&#xff0c;只能联系厂家处理。好在经过30多分钟的处理&#xff0c;服务终于启动&#xff0c;系统运…...

计算机网络基础篇

TCP/IP网络模型 TCP/IP网络模型的作用就是给数据包进行层层封装&#xff0c;帮助数据包能够正确的找到对应的设备接受数据。 一个URL所经历的全部过程 URL所经历的全部过程&#xff1a; HTTP -> DNS ->协议栈-TCP->IP->MAC->网卡->交换机->路由器->服…...

32 从前序与中序遍历序列构造二叉树

32 从前序与中序遍历序列构造二叉树 32.1 从前序与中序遍历序列构造二叉树解决方案 class Solution { public:TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {return buildTreeHelper(preorder, inorder, 0, 0, inorder.size() - 1)…...

D82【python 接口自动化学习】- pytest基础用法

day82 pytest初体验 学习日期&#xff1a;20241128 学习目标&#xff1a;pytest基础用法 -- pytest初体验 学习笔记&#xff1a; 文件命名规范 py测试文件必须以test_开头&#xff08;或_test结尾&#xff09;测试方法必须以test开头测试类必须以Test开头&#xff0c;并且…...

在开发环境中,前端(手机端),后端(电脑端),那么应该如何设置iisExpress

首先&#xff0c;要想手机端应用能成功请求后端&#xff0c;两个设备至少需在同一个局域网内&#xff0c;且IP地址互通&#xff1b; 因为ajax是http(s)://IP地址端口号的方式请求&#xff0c;但是iisExpress默认是localhost如何解决&#xff0c;并没有IP地址&#xff0c;所以手…...

Google Calendar智能安排深度拆解(Gemini原生集成技术白皮书级解析)

更多请点击&#xff1a; https://intelliparadigm.com 第一章&#xff1a;Gemini Google Calendar智能安排技术全景概览 Gemini 与 Google Calendar 的深度集成标志着日程管理进入语义理解驱动的新阶段。该能力并非简单调用 API&#xff0c;而是依托 Gemini 模型对自然语言指…...

从服务器到手机:手把手教你修改游戏客户端IP,让私服在手机上跑起来

移动游戏私服客户端IP修改实战指南 当你在服务器上成功部署了游戏私服后&#xff0c;最令人沮丧的莫过于发现手机上的官方客户端无法连接到你的私人服务器。这个看似简单的"最后一公里"问题&#xff0c;往往成为许多私服搭建者的拦路虎。本文将彻底解决这个痛点&…...

阴阳师御魂自动刷脚本:5分钟快速上手的智能挂机指南

阴阳师御魂自动刷脚本&#xff1a;5分钟快速上手的智能挂机指南 【免费下载链接】yysScript 阴阳师脚本 支持御魂副本 双开 项目地址: https://gitcode.com/gh_mirrors/yy/yysScript 还在为重复刷御魂副本而感到疲惫吗&#xff1f;yysScript智能挂机脚本是专为《阴阳师》…...

HS2-HF Patch:一站式解决HoneySelect2汉化、去和谐与MOD管理难题

HS2-HF Patch&#xff1a;一站式解决HoneySelect2汉化、去和谐与MOD管理难题 【免费下载链接】HS2-HF_Patch Automatically translate, uncensor and update HoneySelect2! 项目地址: https://gitcode.com/gh_mirrors/hs/HS2-HF_Patch 如果你正在玩HoneySelect2这款游戏…...

2000-2025年《中国县域统计年鉴》pdf+excel版(附赠面板数据)

资源介绍《中国县域统计年鉴》2000-2025一、数据介绍《中国县域统计年鉴》是一部全面反映我国县域社会经济发展状况的资料性年鉴&#xff0c;从2014年开始分为《中国县域统计年鉴&#xff08;县市卷&#xff09;》和《中国县域统计年鉴&#xff08;乡镇卷&#xff09;》两卷。数…...

马斯克解散 xAI、接纳 Anthropic:亡羊补牢的无奈,与一场被 AGI 神话带偏的豪赌

马斯克解散 xAI、接纳 Anthropic&#xff1a;亡羊补牢的无奈&#xff0c;与一场被 AGI 神话带偏的豪赌 2026 年 5 月 6 日&#xff0c;两件事同时发生&#xff1a; 一、Anthropic 宣布获得 xAI Colossus 1 集群的全部算力——22 万张英伟达 GPU&#xff0c;300 兆瓦电力容量。 …...

Davinci vs. 其他BI工具怎么选?从私有化部署和二次开发角度深度对比

Davinci vs. 主流BI工具技术选型指南&#xff1a;私有化部署与二次开发实战解析 当企业数据量突破TB级时&#xff0c;我们技术团队曾面临一个关键抉择&#xff1a;是继续支付每年六位数的商业BI服务费&#xff0c;还是转向可深度定制的开源方案&#xff1f;这个决策不仅关乎成本…...

2026最权威的六大降AI率助手实际效果

Ai论文网站排名&#xff08;开题报告、文献综述、降aigc率、降重综合对比&#xff09; TOP1. 千笔AI TOP2. aipasspaper TOP3. 清北论文 TOP4. 豆包 TOP5. kimi TOP6. deepseek 在把学术成果提交到知网平台以前&#xff0c;针对借助生成式AI辅助而产出的内容去进行合规化…...

LIO-SAM源码逐行解析:从因子图构建到多传感器融合实战

1. LIO-SAM技术架构解析 LIO-SAM&#xff08;Lidar Inertial Odometry via Smoothing and Mapping&#xff09;是Tixiao Shan博士在LeGO-LOAM基础上开发的激光-惯性紧耦合SLAM系统。它的核心创新点在于采用因子图优化框架&#xff0c;将IMU预积分、激光里程计、GPS和闭环检测四…...

别再为Modbus RTU超时头疼了!STM32CubeMX+FreeModbus从站移植,搞定串口与定时器配置的黄金法则

STM32CubeMXFreeModbus从站移植实战&#xff1a;破解RTU超时难题的工程化思维 当你在深夜调试Modbus RTU从站设备&#xff0c;串口调试助手反复弹出"Timeout"错误提示时&#xff0c;那种挫败感每个嵌入式工程师都深有体会。超时问题就像幽灵般难以捉摸——代码编译通…...