基于C++的八叉树颜色删减实验

article/2025/10/28 23:06:32

基于八叉树颜色删减实验

一、实验目的及要求

实现真彩色到256色的颜色转换算法

提供的代码:

main.cpp :提供了主控函数main ,八叉树类octTree 和八叉树节点结构octNode 。

代码的编译:

由于需要使用bmp的信息头和文件头结构,该结构在windows.h 文件中,建议使用 VisualStudio 系统编译,也可以自己定义相关变量在其它编译器里编译。

在VS里编译过程:打开VS,创建新的空项目,添加 main.cpp ,编译即可,运行命令:程序名 输入文件名 输出文件名,如“程序名 input.bmp output.bmp”

本程序只是简单的demo,未考虑太多的正确性检查,故输入文件应为24位的真彩色bmp格式文件。

测 试 文 件 可 以 用 附 带 的 test_in.bmp 文 件 , 同 时 给 出 了 几 个 减 色 样 例 供 对 比 :

  • test_ACDSee.bmp:ACDSee 软件的结果
  • test_ps.bmp:Photoshop 的结果
  • test_PaintBrush.bmp:windows 的画图软件的结果
  • test_out.bmp :上一届同学的结果

代码简介:

  1. 打开输入图像,读取文件头部和信息头部并填充输出图像的文件头部和信息头部;

  2. 依次读取输入图像的每一像素的颜色并插入八叉树中;

  3. 生成256色的调色板,并写出到文件中;

  4. 依次读取输入图像的每一个像素并在调色板中查找出与之最相近的颜色索引值。

  5. 输出256色图像

实验要求:

  1. 完成 octTree 类的成员函数 insertColor ,该函数的功能是往颜色八叉树中添加一个颜色;
    • 可以先把所有像素的颜色都加入八叉树,再进行颜色删减,也可以当八叉树中的颜色数超过256色后就进行删 减;
    • 删减的准则可以是最简单的删除最深层的叶子结点,或者可以尝试其它的原则;
  2. 完成 octTree 类的成员函数 generatePalette ,该函数的功能是从颜色八叉树生成256个调色板颜色;
  3. 完成 octTree 类的析构函数,释放分配的八叉树内存空间;
  4. 完成函数 selectClosestColor ,该函数的功能是从调色板中选出与给定颜色最接近的颜色;最简单的是按颜色空间的欧式距离最近来选择,可以尝试更好的方法;

二、实验原理

算法原理

要实现色彩转换,关键如何从真彩图像中选出256种颜色,又要使颜色的失真比较小。

八叉树颜色量化算法。算法基本思路是:将图像中使用的RGB颜色值分布到层状的八叉树中。八叉树的深度可达9层,即根节点层加上分别表示8位的R、G、B值的每一位的8层节点。较低的节点层对应于较不重要的RGB值的位(右边的位),因此,为了提高效率和节省内存,可以去掉最低部的2 ~ 3层,这样不会对结果有太大的影响。www.biyezuopin.vip

叶节点编码存储像素的个数和R、G、B颜色分量的值;而中间的节点组成了从最顶层到叶节点的路径。这是一种 高效的存储方式,既可以存储图像中出现的颜色和其出现的次数,也不会浪费内存来存储图像中不出现的颜色。算法 特点:效率高,效果好。

算法步骤

颜色量化步骤:

①、扫描图像的所有像素,将它们的数据累加到相应的节点中;遇到新的颜色则创建一个叶子节点,并将此像素的颜色数据存入其中。

②、如果叶子节点数目大于目标调色板所要的颜色数,就将部分叶子节点合并到父节点中,并将父节点转化为叶子节点,在其中存放颜色分量的累加值及其像素出现的次数。同时删除原来被合并的叶子节点。

③所有像素处理结束,遍历八叉树,将叶子节点的各颜色值求平均值作为节点的颜色分量值读出并存入目标调色板。

④再次遍历所有像素,通过每个像素的颜色值与调色板中选中的256色运算,求得一个最接近像素颜色值的调色板颜色,把该像素换相应的调色板颜色索引。

八叉树处理例子:节点RGB(109,204,170)如下图:

方法探索

算法的关键在于第三步的叶结点合并,超出256个颜色的叶结点很多,如何选择合并哪个?

基本算法

首先最简单的方式,根本不考虑顺序,就从底层向上合并,这是因为层数越深,这个结点表示的颜色越具体,代表的 像素点数越少,这样被合并后受到影响的像素点数就越少。

那么就可以用递归来实现这个过程了,从第7层开始向上合并,合并完第7层再合并第6层,直到颜色种类不超过256 个,代码如下,该函数实现合并八叉树的第depth 层结点:

void reduceNode(octNode* root, int depth, int* colorCount) { //在八叉树的第depth层减色if (root == NULL)return;if (*colorCount <= 256)return;if (root->depth < depth) { //深度不够则递归到达for (int i = 0; i < 8; i++) {reduceNode(root->child[i], depth, colorCount);}}else { //深度达到了depth层 开始减色for (int i = 0; i < 8; i++) {octNode* leaf = root->child[i];if (leaf != NULL) {root->rSum += leaf->rSum;root->gSum += leaf->gSum;root->bSum += leaf->bSum;(*colorCount) -= 1; //颜色种类数--delete root->child[i];root->child[i] = NULL;}}root->isLeaf = true; //父结点成为新的叶子(*colorCount) += 1;if (*colorCount <= 256) //减色完成return;}
}

整体调用如下,可以看到for 循环内部是从第7层开始向上合并的,直到颜色种数不超过256:

//根据现有的八叉树,选择256个颜色作为最终的调色板中的颜色
uint8 octTree::generatePalette(RGBQUAD* pal)
{int colorCount = getLeaftCount(root); //叶结点个数就是颜色种类数if (colorCount <= 256)return colorCount;for (int i = 7; i >= 0; i--) { //从深层向上减结点reduceNode(root, i, &colorCount);if (colorCount <= 256)break;}int index = 0;getPalette(root, pal, index); //下标i是传引用return colorCount;
}

改进

从深处开始合并是对的,但是相同深度的结点也有很多,有无先后顺序?

按照原来的思路,我们总想让像素数量较多的颜色不被合并,而合并像素数量较少的颜色。因为像素多的颜色被 修改后在图片上的体现会非常明显,而像素少的颜色被合并则无伤大雅,我们很难分辨出来。

于是我引入了数据结构记录0~7层的结点的指针:

vector<vector<octNode*>> allPtr(8, vector<octNode*>()); //第0层到第7层所有结点指针

并且改写合并函数,同样是合并第depth 层的结点,但是不像之前那样通过递归找到对应层,而是直接利用allPtr数组。

并且关键在于,合并第depth 层结点时,首先将这一层的结点**按照代表的像素个数从小到大排序,优先合并代表像素少的结点,**代码如下:

void newReduceNode(int depth, int* colorCount) //在八叉树的depth层减色
{if (*colorCount <= 256)return;sort(allPtr[depth].begin(), allPtr[depth].end(), compare);int size = allPtr[depth].size();octNode* leaf = NULL;for (int i = 0; i < size; i++) {for (int j = 0; j < 8; j++) {leaf = allPtr[depth][i]->child[j];if (leaf != NULL) {allPtr[depth][i]->rSum += leaf->rSum;allPtr[depth][i]->bSum += leaf->bSum;allPtr[depth][i]->gSum += leaf->gSum;--(*colorCount);delete leaf;allPtr[depth][i]->child[j] = NULL;}}allPtr[depth][i]->isLeaf = true;++(*colorCount);if (*colorCount <= 256)return;}
}

整体调用几乎没有改变,唯一改变就是调用的删结点函数:

uint8 octTree::generatePalette(RGBQUAD* pal)
{...for (int i = 7; i >= 0; i--) { //从深层向上减结点newReduceNode(root, i, &colorCount);if (colorCount <= 256)break;}...
}

代码的详细实现见main.cpp

三、实验结果

原图

减色后

改进前

这时并没有考虑优先合并代表像素少的结点,效果虽然还不错但是可以看到层次感比较明显,不够自然。 www.biyezuopin.vip

改进后

考虑了优先合并代表像素点较少的结点,比改进前的效果好多了,与原图的差异也几乎可以忽略。

四、实验小结

这个算法本身非常巧妙,比如将所有真色彩都用八叉树的结点表示,并且层数越深代表的RGB位越靠右,也就是越具体,所有像素都通过树进行归类,颜色和树形结构完美契合,令人赞叹。

感觉课上讲述这部分内容不是很清楚,我在完成实验时也参考了不少资料才理解,建议可以提供一些介绍的资料。

【参考文章】:

颜色量子化(K-Means聚合算法以及八叉树的运用)_TuGeLe的博客-CSDN博客

八叉树颜色压缩图解以及c++实现 - 知乎 (zhihu.com)


http://chatgpt.dhexx.cn/article/3G42ncUt.shtml

相关文章

八叉树 java_图像八叉树量化讲解 Java版本

这篇文章主要讲解八叉树算法的原理&#xff0c;并用java进行实现 1.算法原理 八叉树最早是在1988年&#xff0c;由 M. Gervautz 和 W. Purgathofer 提出&#xff0c;该算法的最大优点是效率高&#xff0c;占用内存少。在图像量化中的思路是&#xff0c;图像rgb通道的值均为8比特…

详解八叉树地图

个人博客&#xff1a;http://www.chenjianqu.com/ 原文链接&#xff1a;http://www.chenjianqu.com/show-102.html 八叉树地图 八叉树地图(OctoMap)就是一种灵活的、压缩的、又能随时更新的地图。八叉树示意图如下&#xff1a; 一个大立方体不断地均匀分成八块&#xff0c;直…

PCL可视化八叉树格网

1 原理 八叉树其实是一种特殊的由上至下的体素&#xff0c;其构造原理在这里不再进行赘述&#xff0c;详细的构造方式可参考博客&#xff1a;https://blog.csdn.net/qq_32867925/article/details/109094719 有时候为了将点云、构造的层次八叉树需要进行可视化&#xff0c;便于…

松散八叉树

1八叉树简述 1.1定义1.2数据1.3树的建立 1.3.1计算包围体的大小与中心点1.3.2判断物体所属的包围盒2松散八叉树 2.1松散八叉树的建立 八叉树简述 定义 八叉树是一种对三维世界进行场景管理的理想的空间数据结构。八叉树中根节点包含一个立方体包围盒。每个非叶子节点都拥有八…

八叉树学习

八叉树学习 八叉树结构八叉树的存储结构1. 规则八叉树&#xff1a;2.线性八叉树&#xff1a;3.一对八式八叉树 参考网站 八叉树结构 八叉树结构是由 Hunter 博士于1978年首次提出的一种数据模型。八叉树结构通过对三维空间的几何实体进行体元剖分&#xff0c;每个体元具有相同…

PCL中的八叉树

目录 &#xff08;1&#xff09;什么是八叉树 &#xff08;2&#xff09;PCL中八叉树的体素形成和PCL中基于八叉树的点云压缩 &#xff08;3&#xff09;基于八叉树的k邻域搜索、半径搜索和体素近邻搜索 &#xff08;4&#xff09;基于八叉树和基于 kd-tree 的k邻域搜索、半…

八叉树 java_java简单实现八叉树图像处理代码示例

一晃工作有段时间了&#xff0c;第一次写博客&#xff0c;有点不知道怎么写&#xff0c;大家将就着看吧&#xff0c;说的有什么不正确的也请大家指正。 最近工作中用到了一个图像压缩的功能。找了一些工具&#xff0c;没有太好的选择。最后选了一个叫jdeli的&#xff0c;奈何效…

八叉树

http://hi.baidu.com/onlywater/blog/item/905c5e162ed18f4021a4e9c1.html 一、八叉树基本原理&#xff1a; 用八叉树来表示三维形体&#xff0c;并研究这种表示下的各种操作以及应用&#xff0c;是进入80年代后开展起来的。这种方法&#xff0c;既可以看成是四叉树方法在三维…

Octree(八叉树)

1. 算法原理 八叉树&#xff08;Octree&#xff09;是一种用于描述三维空间的树状数据结构。八叉树的每个节点表示一个正方体的体积元素&#xff0c;每个节点有八个子节点&#xff0c;将八个子节点所表示的体积元素加在一起就等于父节点的体积。八叉树是四叉树在三维空间上的扩…

【PCL自学:ocTree】八叉树(octree)的原理及应用案例(点云压缩,搜索,空间变化)

PCL中八叉树&#xff08;octree&#xff09;的原理及应用案例 一、什么是八叉树ocTree&#xff1f;1.八叉树原理 二、八叉树应用案例1.点云压缩2.用八叉树进行空间划分和搜索操作3.无序点云数据的空间变化检测 一、什么是八叉树ocTree&#xff1f; 1.八叉树原理 上世纪80年代&…

十进制小数化为二进制小数的方法是什么_十进制转成二进制的两种方式

第一种&#xff1a;用2整除的方式。 用2整除十进制整数&#xff0c;得到一个商和余数&#xff1b;再用2去除商&#xff0c;又会得到一个商和余数&#xff0c;如此重复&#xff0c;直到商为小于1时为止&#xff0c;然后把先得到余数作为二进制数的低位有效位&#xff0c;后得到的…

Python 利用内置函数把二进制小数转换为十进制

如果需要把一个二进制整数转换为十进制整数&#xff0c;只需要简单的一行&#xff1a; int(1101,2) 但如果有一个二进制小数的话&#xff0c;就需要自己实现一个函数了。 不过&#xff0c;许多人是这样写的&#xff1a;(图片取自这里) 可是&#xff0c;由于python本身并不适…

[学习笔记] 二进制小数表示方法

文章目录 科学计数法二进制推广计算机中的小数EXCESS表示系统特殊情况举例&#xff08;float&#xff09;普通情况最大正实数普通情况最小负实数普通情况最小正实数特殊情况最大正实数 科学计数法 科学计数法想必大家都很熟悉了&#xff0c;往往通过如下形式表示一个实数&…

十进制小数化为二进制小数的方法是什么_二进制的转换

二进制是在计算机中常用的一种进制数&#xff0c;其数据用0和1两个数码来表示数据。我们人类常用的是十进制&#xff0c;那么二进制和十进制之间是有一个转换方法的。 二进制转换十进制 一个二进制数转换为十进制数&#xff0c;是比较简单的&#xff0c;其方法就是用每一个位置…

二进制小数的意义

回忆小学学的十进制小数的意义&#xff1a; 15.23这个小数&#xff0c;1是十位&#xff0c;5是个位&#xff0c;2是十分位&#xff0c;3是百分位。这个小数的意义为&#xff1a;&#xff0c;因为最低位为百分位&#xff0c;所以分母是100。 小数末尾加上0或去掉0&#xff0c;小…

浮点数(小数)在计算机中如何用二进制存储?

浮点数在计算机中如何用二进制存储&#xff1f; 前言 前面我有篇博文详解了二进制数&#xff0c;以及如何同二进制数表示整数。但是&#xff0c;计算机处理的不仅仅是整数&#xff0c;还有小数&#xff0c;同样小数在计算机也是用二进制进行存储的&#xff0c;但是&#xff0…

二进制小数的表示

二级制小数分为两大类&#xff1a;1、定点数&#xff1b;2、浮点数。 定点数 定点数&#xff1a; &#xff08;1&#xff09;小数点位置固定不变的数。 &#xff08;2&#xff09;定点数有定点整数和定点小数。 &#xff08;定点整数&#xff1a;小数部分为0&#xff1b;定点…

腾讯TDSQL全时态数据库系统论文入选VLDB

当地时间2019年8月26至30日&#xff0c;VLDB 2019会议在美国加利福尼亚召开&#xff0c;腾讯分布式数据库TDSQL与中国人民大学最新联合研究成果被VLDB 2019接收并将通过长文形式发表。VLDB是国际数据管理与数据库领域顶尖的学术会议之一&#xff0c;这是继去年腾讯TDSQL相似度计…

顶会VLDB‘22论文解读:CAE-ENSEMBLE算法

摘要&#xff1a;针对时间序列离群点检测问题&#xff0c;提出了基于CNN-AutoEncoder和集成学习的CAE-ENSEMBLE深度神经网络算法&#xff0c;并通过大量的实验证明CAE-ENSEMBLE算法能有效提高时间序列离群点检测的准确度与效率。 本文分享自华为云社区《VLDB22 CAE-ENSEMBLE论文…

【轨迹压缩】Trajectory Simplification: On Minimizing the Direction-based Error [2015] [VLDB]

一、一个动机 保护方向信息的方向保持轨迹简化&#xff08;DPTS&#xff09;已被证明表现良好&#xff0c;而现有关于 DPTS 的研究 要求用户指定一个容错&#xff0c;在某些情况下用户可能不知道如何正确设置&#xff08;例如&#xff0c;容错只能在未来某个时间知道&#xff…