97 个 OpenCV 实例(十七):图片读写,FileStorage 与 JPEG 采样

上一篇把粘连的水果分开了。这一篇离开 imgproc,进入第三阶段「图像读写与视频 I/O」------先把「读进来、写出去」这条链路走通。官方 samples 里三个例子对号入座:imagelist_creator 把一堆图片路径存进 yaml,imagelist_reader 再把它读出来逐张显示,imgcodecs_jpeg 则在内存里把同一张图按不同采样因子压缩、看失真。这一课还挖到一个彩蛋:OpenCV 4.x 的 lena.jpg 已经不是经典 Lena 了。

一、效果先行

先看 JPEG 采样因子。上排是 5 张一模一样的原图,下排是同一张图按 5 种色度采样因子压缩后的结果------从 444 到 411,圆环边缘的彩色晕染越来越明显:

再看图片列表。imagelist_creator 把 4 张图写进一个 yaml,imagelist_reader 读出来逐张灰度显示,按一下键翻一张:

两张图记住两件事:JPEG 靠「牺牲色度」换体积;FileStorage 靠「序列化」统一管理一堆图片路径。

二、FileStorage:把图片列表写进文件

批量处理几百上千张图时,最笨也最稳的办法是先把它们的路径存成一个清单,程序按清单逐张读。OpenCV 自带一套序列化框架 FileStorage,能把数据写成 YAML 或 XML。imagelist_creator 只做一件事------把命令行传进来的图片路径存进文件:

cpp 复制代码
FileStorage fs(outputname, FileStorage::WRITE);   // 打开文件,写模式
fs << "images" << "[";                             // 开一个叫 images 的序列
for (int i = 2; i < ac; i++)
    fs << string(av[i]);                           // 逐条压入路径
fs << "]";                                         // 闭合序列

有个细节很贴心:它先 imread(outputname) 试探一下输出名,如果这个文件本身是一张图片(!m.empty()),立刻报错退出。为什么?防止用户手滑,把输出清单写成一张已有图片的名字,把原图覆盖掉。这个防覆盖检查,正是工程师该有的谨慎。

imagelist_reader 负责把清单读回来。核心是拿到顶层节点、确认它是序列,再逐条取出:

cpp 复制代码
FileStorage fs(filename, FileStorage::READ);
FileNode n = fs.getFirstTopLevelNode();            // 顶层节点
if (n.type() != FileNode::SEQ) return false;       // 必须是序列
for (FileNodeIterator it = n.begin(); it != n.end(); ++it)
    l.push_back((string)*it);                      // 逐条取出路径

一个写、一个读,合起来就是完整的「图片清单」工作流。工业上做批量质检、数据集管理,第一步永远是这么个清单文件。

三、图片编解码四兄弟

读图写图有四个 API,两两对应,很多人分不清 imwrite 和 imencode 的区别:

API 方向 介质 典型场景
imread 文件 → Mat 磁盘 加载图片
imwrite Mat → 文件 磁盘 保存图片
imencode Mat → 内存 buffer 内存 压缩成字节流(发网络、写数据库)
imdecode 内存 buffer → Mat 内存 从字节流解码(收网络、读数据库)

关键区别:imencode 和 imdecode 全程在内存里完成编解码,不落盘。相机抓到一帧、要压缩后通过网络发出去,走的就是 imencode;接收方拿到字节流,用 imdecode 还原成 Mat。imgcodecs_jpeg 这个例子,正是用这俩 API 演示内存编解码:

cpp 复制代码
vector<int> param;
param.push_back(IMWRITE_JPEG_SAMPLING_FACTOR);     // 指定参数名
param.push_back(config[i].sampling_factor);        // 411/420/422/440/444
vector<uint8_t> jpeg;
imencode(".jpg", img, jpeg, param);                // 压缩到内存
Mat lossy_img = imdecode(Mat(jpeg), -1);           // 从内存解码还原

四、JPEG 采样因子:色度抽样

JPEG 压缩的核心洞察是:人眼对亮度敏感、对色度不敏感。所以它把图像拆成亮度分量和色度分量,亮度全采样,色度可以降采样------这就是「色度抽样」。采样因子控制色度被抽掉多少:

采样因子 比例 含义
444 4:4:4 色度不降采样,质量最高
422 4:2:2 水平色度减半,折中
420 4:2:0 水平垂直都减半,相机/网络默认
440 4:4:0 垂直色度减半,较少用
411 4:1:1 色度压得最狠,体积最小

官方测试图故意用一张粉紫色同心圆------高频的彩色边缘,最能暴露色度抽样的失真。从 444 到 411,圆环边缘的彩色晕染越来越重。而对灰度图,这参数几乎没影响,因为灰度图本来就没有色度。

五、实测

三个实例跑下来,两个是纯命令行、一个要弹窗。imagelist_creator 一行命令生成清单,4 张图的绝对路径整整齐齐写进 yaml;imgcodecs_jpeg 无参数直接吐出 800×320 的对比图。只有 imagelist_reader 需要 GUI 逐张显示,我用 Xvfb 虚拟屏加 xdotool 模拟按键翻页,4 张灰度图全部截了下来。

这里挖到一个彩蛋:OpenCV 4.x 的 lena.jpg 已经不是经典 Lena 了。那张 1972 年《花花公子》插图的扫描图,因为版权争议被 OpenCV 换掉,现在 reader 第一张显示的是个戴针织帽的人像。测试图也讲究版权------这大概是工业工程里最容易被忽略的一课。

六、踩坑记录

现象 正确姿势
读图失败不报错 imread 读不存在的文件返回空 Mat,不抛异常 用 empty() 判断,别依赖 try-catch
清单结构不对 reader 读不出、返回失败 顶层节点必须是序列(SEQ),用 << "[" ... "]" 写出来
相对路径失效 换目录跑 reader 找不到图 清单里存绝对路径
采样因子无效 灰度图看不出差别 色度抽样只对彩色图有意义

七、AI 与 LLM Wiki:这一课沉淀了什么

LLM Wiki 从 26 页涨到 27 页,这一课新增「图片读写与JPEG采样」概念页,沉淀了 FileStorage 序列化的写读范式、imread/imwrite/imencode/imdecode 四兄弟的区分,以及 JPEG 色度抽样的 5 档采样因子对照。进度表上 imagelist_creator、imagelist_reader、imgcodecs_jpeg 三个勾选完成,从 32 个变成 35 个,进度 36%。

这一课最值得进知识库的,是那个防覆盖设计------imread 先试探输出名、是图片就拒绝覆盖。它不是算法,却比算法更值钱,因为它体现的是「写代码时多想一步用户会怎么误操作」。把这类工程细节攒进知识库,比单纯记 API 有用得多。

写在最后

97 个实例,今天完成第 35 个,进度 36%,第三阶段「图像读写与视频 I/O」开了个头。这一站把「读进来、写出去」的链路铺平了:FileStorage 管清单,四兄弟管编解码,采样因子管体积。下一篇继续这条链路,上摄像头------从打开摄像头、调分辨率帧率,到写视频文件,把视频采集摸一遍。

本文示例代码均出自 OpenCV 官方 samples,遵循 Apache 2.0 协议。

相关推荐
Forerror202619 分钟前
API网关怎么选?MAI Gateway vs 开源方案全面对比
人工智能·ai工具·maigateway·企业ai网关·企业级大模型网关
Likeadust22 分钟前
一键创建,说开就开:私有化音视频系统EasyDSS视频会议的入会、协作与AI纪要全流程
人工智能·音视频·easydss
小陈phd22 分钟前
电商客服智能体状态机编排:基于 LangGraph 的双轨意图识别与分级降级工程实践
人工智能
code_pgf23 分钟前
总体目标与系统架构
人工智能·机器人
科技风向标go25 分钟前
奥维云网×洛图科技:2026监控品牌“功能延伸”vs垂直品牌“底层设计”——品牌选型与产业趋势
网络·人工智能·科技·户外安防
小王20425 分钟前
Day 33:目标跟踪基础 — SORT与DeepSORT
人工智能·计算机视觉·目标跟踪
您^_^26 分钟前
DeepSeek-Harness 升级排障完全指南:三类本地残留逐个拆解
人工智能·windows·个人开发·deepseekharness·deepseekv4pro
旖旎夜光33 分钟前
【AI入门】大模型介绍全解析:从模型、LLM 到提示词与嵌入
人工智能·笔记·python·学习·ai编程
牧羊人.33334 分钟前
计算机视觉基础 第13章 |背景建模与运动目标检测
图像处理·人工智能·目标检测·计算机视觉·目标跟踪