基于拼豆的图像颜色聚类算法:原理与实现
一、前言
拼豆(像素豆 / 融合豆)创作的核心需求是将任意图像简化为有限数量的 “色号”,既要保留图像核心视觉特征,又要去除杂色、降低颜色复杂度。常规的颜色量化算法(如 K-Means)无法兼顾 “颜色频率” 和 “视觉相似度”,而基于拼豆场景的图像颜色聚类算法,通过层次聚类 + 频率阈值筛选 + 聚类优化的组合策略,精准解决了这一问题 —— 该算法以 “颜色出现频率” 为重要性依据,结合视觉色差(ΔE)完成聚类,最终输出去杂色后的图像色号映射表。
本文将从算法核心思想、关键流程、核心实现三个维度,详细拆解这一算法的设计与落地思路。
二、算法核心思想
算法的核心逻辑是 “先聚类、再筛选、后优化”:
- 以 “颜色出现频率” 为权重,对图像中所有实际使用的色号做层次聚类,优先合并视觉上最相似(ΔE 最小)的颜色;
- 通过频率阈值 α 判断聚类的 “重要性”,过滤低频杂色聚类;
- 对重要聚类内部的低频色号进一步优化,将不重要聚类合并到最近的重要聚类;
- 最终生成 “原始色号→代表色号” 的映射表,完成图像颜色简化。
核心目标:保留高频且视觉特征显著的颜色,剔除低频杂色,同时保证颜色映射的视觉一致性。
三、算法详细流程
步骤 1:数据准备(频率基准构建)
数据准备的核心是量化每个颜色的 “存在感”,为后续重要性判断提供依据:
- 计算总像素块数:统计输入图像的总像素数(记为total_pixels),作为频率计算的基准;
- 筛选有效颜色:从 “颜色 - 次数统计” 结果中,过滤掉未使用的颜色(即counts[i] = 0的色号),仅保留实际出现在图像中的颜色(记为valid_colors,格式为 RGB 色号列表);
- 计算相对频率:对每个有效颜色,计算其相对频率(freq = counts[i] / total_pixels),反映该颜色在图像中的占比;
示例:若图像总像素 1000,某红色出现 200 次,则其相对频率为 0.2。
步骤2:层次聚类核心(视觉相似度聚合)
层次聚类是实现 “视觉相似颜色合并” 的核心,区别于 K-Means 需要预设聚类数,层次聚类可通过 “色差阈值” 自适应终止,更贴合拼豆场景的灵活需求:
2.1 初始化
将每个有效颜色视为独立聚类,每个聚类包含:
- 成员色号列表(初始仅自身);
- 总频率(初始为该颜色的相对频率);
- RGB 均值(初始为该颜色的 RGB 值)。
2.2 迭代合并
- 距离计算:采用 CIE76色差公式简化版(ΔE)计算聚类间的视觉距离,ΔE 越小表示颜色越相似;
- 优先队列迭代:将所有聚类对的 ΔE 存入优先队列(小顶堆),每次取出 ΔE 最小的聚类对进行合并;
- 终止条件:当优先队列中最小的 ΔE 超过预设阈值(默认 4.0,可根据拼豆色号精度调整)时,停止合并;
注:ΔE=4.0 是视觉上 “可感知但不明显” 的临界值,符合拼豆颜色简化的视觉要求。
2.3 距离更新(平均链接法)
合并后的新聚类与其他聚类的距离,采用平均链接法计算:
center.r = weighted_average(cluster_members.r, frequencies);
// ... 类似计算g, b
weighted_average计算聚类中所有成员频率加权平均中心,平均链接法能避免 “链式合并”,保证聚类的视觉一致性。
步骤 3:重要性决策点(频率阈值筛选)
聚类完成后,通过阈值 α(建议取值 0.03~0.05,即 3%~5%)判断聚类是否 “重要”:
- 判断规则:若聚类总频率 ≥ α → 重要聚类;否则 → 不重要聚类;
- 核心目的:过滤掉 “视觉相似但总占比极低” 的杂色聚类(如图像中的噪点、零星杂色)。
步骤4:聚类优化(杂色合并 + 代表色确定)
4.1 合并不重要聚类
对所有不重要聚类,计算其与每个重要聚类的 ΔE,将其合并到最近的重要聚类中,进一步简化颜色数量。
4.2 计算聚类中心(频率加权平均)
对每个重要聚类,计算频率加权的 RGB 中心值,保证高频颜色对聚类中心的主导作用:
RGBcenter=∑freqi∑(RGBi×freqi)
其中:
- RGBi 是聚类中第i个色号的 RGB 值;
- freqi 是该色号的相对频率。
4.3 确定代表色号
从输入的colorTable(拼豆可用色号表)中,寻找与RGBcenter ΔE 最小的色号作为该聚类的代表色号(保证输出色号是实际可用的拼豆色号)。
步骤 5:映射与输出
5.1 构建颜色映射表
生成 “原始色号 → 代表色号” 的映射字典:聚类中所有成员色号,均映射到该聚类的代表色号。
5.2 应用映射并统计效果
将映射表应用到原始图像,替换所有像素的色号,得到去杂色后的图像;
统计优化效果:
颜色减少率 = (原始颜色数 - 最终颜色数) / 原始颜色数 × 100%;
四、核心代码实现(C++)
以下是算法核心逻辑的 C++ 实现,依赖CIE76色差公式的简化版本计算 ΔE,使用优先队列存储簇间距离(非完整代码,代码使用Deepseek辅助生成):
// ============================================
// 主函数:removeRareColorsModular
// ============================================
std::vector<std::vector<std::string>> removeRareColorsModular(
const ColorMatcher& matcher,
const BlockProcessor& processor,
const std::vector<int>& counts,
double alpha,
double deltaEThreshold) {
const auto& colorTable = matcher.getColorTable();
// 步骤1: 准备数据
std::cout << "\n=== 步骤1: 数据准备 ===" << std::endl;
// 计算总像素数
int totalBlocks = 0;
for (int count : counts) totalBlocks += count;
// 获取使用的颜色索引和频率
std::vector<int> usedIndices;
std::vector<double> frequencies;
for (size_t i = 0; i < counts.size(); i++) {
if (counts[i] > 0) {
usedIndices.push_back(i);
frequencies.push_back(static_cast<double>(counts[i]) / totalBlocks);
}
}
std::cout << "总像素块: " << totalBlocks << std::endl;
std::cout << "使用颜色数: " << usedIndices.size() << " / " << counts.size() << std::endl;
std::cout << "频率阈值 α: " << alpha << std::endl;
std::cout << "ΔE阈值: " << deltaEThreshold << std::endl;
if (usedIndices.empty()) {
std::cerr << "错误: 没有使用的颜色" << std::endl;
return processor.getColorCodes();
}
// 步骤2: 预计算距离矩阵
std::cout << "\n=== 步骤2: 计算距离矩阵 ===" << std::endl;
auto distanceMatrix = computeDistanceMatrix(usedIndices, colorTable);
// 步骤3: 层次聚类
std::cout << "\n=== 步骤3: 层次聚类 ===" << std::endl;
Clusterer clusterer;
auto clusters = clusterer.performClustering(usedIndices, frequencies,
distanceMatrix, deltaEThreshold);
// 步骤4: 重要性评估
std::cout << "\n=== 步骤4: 重要性评估 ===" << std::endl;
ImportanceEvaluator evaluator;
evaluator.evaluateClusters(clusters, alpha);
evaluator.identifyLowFrequencyMembers(clusters, frequencies, alpha);
// 输出聚类信息
for (size_t i = 0; i < clusters.size(); i++) {
const auto& cluster = clusters[i];
std::cout << "聚类 " << i << ": 大小=" << cluster.colorIndices.size()
<< ", 总频率=" << cluster.totalFrequency
<< ", 重要=" << (cluster.isImportant ? "是" : "否")
<< ", 低频成员=" << cluster.lowFreqMembers.size() << std::endl;
}
// 步骤5: 聚类优化
std::cout << "\n=== 步骤5: 聚类优化 ===" << std::endl;
ClusterOptimizer optimizer;
optimizer.optimizeClusters(clusters, distanceMatrix, frequencies,
usedIndices, colorTable, alpha);
// 步骤6: 获取原始图像并应用映射
std::cout << "\n=== 步骤6: 颜色映射 ===" << std::endl;
auto originalImage = processor.getColorCodes();
ColorMapper mapper;
auto result = mapper.applyMapping(originalImage, clusters, usedIndices, colorTable);
// 步骤7: 统计结果
std::cout << "\n=== 步骤7: 结果统计 ===" << std::endl;
// 统计原始和最终的颜色数
int originalColorCount = 0;
for (int count : counts) {
if (count > 0) originalColorCount++;
}
// 统计最终使用的颜色数(不同的代表色数)
std::unordered_set<std::string> finalColors;
for (const auto& cluster : clusters) {
if (!cluster.colorIndices.empty()) {
finalColors.insert(colorTable[cluster.representativeIndex].name);
}
}
std::cout << "原始颜色数: " << originalColorCount << std::endl;
std::cout << "最终颜色数: " << finalColors.size() << std::endl;
std::cout << "颜色减少率: "
<< std::fixed << std::setprecision(1)
<< ((1.0 - static_cast<double>(finalColors.size()) / originalColorCount) * 100)
<< "%" << std::endl;
// 输出最终调色板
std::cout << "\n最终调色板 (" << finalColors.size() << " 种颜色):" << std::endl;
for (const auto& colorCode : finalColors) {
std::cout << " " << colorCode << std::endl;
}
return result;
}
五、算法关键说明
ΔE 的选择:CIE 是目前最贴合人眼视觉的色差计算公式,相比 RGB 欧式距离,能更准确反映颜色的视觉相似度;
平均链接法:避免了 “单链接法” 的链式聚类和 “全链接法” 的紧凑聚类,平衡了聚类的视觉一致性;
α 阈值的调整:α 越小,保留的颜色越多(杂色越少);α 越大,颜色简化越明显(可能丢失细节),建议根据拼豆色号数量调整(色号少则 α 大,色号多则 α 小);
代表色的选择:必须从拼豆色号表中选取,而非直接使用聚类中心(聚类中心可能不是实际可用的拼豆色号)。
六、结果与总结展望
结果
以下是三组像素图处理前后对比
图像1:
原始颜色数: 75
最终颜色数: 37
颜色减少率: 50.7%

图6.1 猫娘处理前

图6.1 猫娘处理后
图像2:
原始颜色数: 66
最终颜色数: 24
颜色减少率: 63.6%

图6.2 戴珍珠耳环少女处理前

图6.2 戴珍珠耳环少女处理后
图像3:
原始颜色数: 36
最终颜色数: 29
颜色减少率: 19.4%

图6.3 卡拉卡拉处理前

图6.3 卡拉卡拉处理后
总结
基于拼豆的图像颜色聚类算法通过 “层次聚类 + 频率筛选 + 优化映射” 的三步策略,完美适配拼豆创作的颜色简化需求:
- 层次聚类保证了视觉相似颜色的合并,避免颜色碎片化;
- 频率阈值 α 筛选掉低频杂色,聚焦核心颜色;
- 重要聚类优化和代表色映射,保证输出色号的实用性。
展望
在基于拼豆颜色聚类的像素画生成流程中,主体边缘是决定图像辨识度的核心区域 —— 边缘像素既要清晰勾勒轮廓,又要避免因全局聚类的 “平均化” 效应混入杂色,或因颜色过渡生硬破坏像素画的块感风格。针对这一痛点,后续优化可围绕 “边缘区域分离 - 专属聚类策略 - 颜色锚定约束 - 后处理提纯” 四步展开,实现边缘杂色的精准管控,同时强化轮廓表现力。
避免:
- 边缘像素被误归为杂色:边缘像素通常是主体与背景的过渡区,单像素占比低,全局频率统计中易被判定为 “低频杂色”,进而被合并到无关聚类,导致轮廓模糊。
- 边缘颜色过度融合:全局聚类的 ΔE 阈值是统一设定的,若为了简化非边缘区域降低阈值,会导致边缘的高对比度颜色被强行合并,丢失轮廓细节;若提高阈值,又会让非边缘区域产生过多杂色。
- 边缘无一致性约束:同一轮廓的边缘像素可能被分配到多个聚类,出现 “彩虹边” 式的杂色,破坏像素画的色块化美感。

10万+

被折叠的 条评论
为什么被折叠?



