C++基于OpenCV K-means提取图像主色时如何计算各颜色簇占比
OpenCV k-Means主色提取的簇占比计算方案
cv::kmeans接口返回的labels矩阵已经存储了每一个输入像素对应的簇编号,所有占比计算不需要额外新增图像遍历逻辑,完全可以基于现有流程的输出直接统计。
具体实现流程
- 簇像素计数:初始化长度等于聚类簇数k的计数数组,逐行遍历
labels矩阵,每读取到一个像素的所属簇ID,就给对应ID的计数项累加,最终得到每个簇包含的总像素数。 - 占比换算:单个簇的像素数除以输入的总像素数(即
labels矩阵的总行数,等于原始图像的宽×高),乘以100即可得到该簇的像素占比百分比。 - 适配现有排序逻辑:你现有代码需要把聚类中心按亮度降序排序时,不要直接对聚类中心矩阵做原地排序——这种操作会打乱中心和原始簇索引的映射关系,最终导致占比和对应颜色错位。正确的做法是建立一个和原始簇索引一一对应的索引数组,对这个索引数组按中心亮度排序,后续取中心颜色、取对应簇占比都通过这个排好序的索引访问,就能保证两者一一对应。
可直接嵌入现有流程的代码片段
// 以下代码可直接对接你已有的kmeans实现,不需要改动原有主色提取的核心流程 // 现有代码已完成的逻辑:读取detect.bmp、reshape为单行浮点矩阵、调用kmeans得到labels和centers // 其中 labels 为单列int型矩阵,每一行对应一个像素的所属簇ID // 其中 centers 为k行3列的float型矩阵,每一行对应一个簇的中心颜色,通道顺序为B/G/R const int k = 10; // 你已设置的聚类簇数 cv::Mat labels, centers; // ... 原有kmeans调用逻辑保持不变 ... // 1. 统计每个簇的像素总数 std::vector<int> cluster_pixel_cnt(k, 0); for (int row_idx = 0; row_idx < labels.rows; row_idx++) { int cluster_id = labels.at<int>(row_idx, 0); cluster_pixel_cnt[cluster_id]++; } const int total_pixels = labels.rows; // 图像总像素数 // 2. 生成按亮度降序的簇索引,避免排序后颜色和占比错位 std::vector<int> sorted_idxs(k); for (int i = 0; i < k; i++) sorted_idxs[i] = i; std::sort(sorted_idxs.begin(), sorted_idxs.end(), [¢ers](int a, int b) { // 按人眼感知的亮度公式计算:L = 0.299*R + 0.587*G + 0.114*B,从高到低排序 auto luminance_a = centers.at<float>(a, 2)*0.299 + centers.at<float>(a,1)*0.587 + centers.at<float>(a,0)*0.114; auto luminance_b = centers.at<float>(b, 2)*0.299 + centers.at<float>(b,1)*0.587 + centers.at<float>(b,0)*0.114; return luminance_a > luminance_b; }); // 3. 生成目标格式的展示内容,取前6个高亮度主色 for (int show_idx = 0; show_idx < 6; show_idx++) { int real_cluster_id = sorted_idxs[show_idx]; // 读取对应簇的RGB值(OpenCV默认BGR顺序,读取后直接按R/G/B顺序拼接) float b = centers.at<float>(real_cluster_id, 0); float g = centers.at<float>(real_cluster_id, 1); float r = centers.at<float>(real_cluster_id, 2); // 计算占比,保留1位小数 double percent = 100.0 * cluster_pixel_cnt[real_cluster_id] / total_pixels; // 拼接为你需要的 "[R G B] 占比百分比" 格式 QString content = QString("[%1 %2 %3] 占比%4%") .arg(r, 0, 'f', 0) .arg(g, 0, 'f', 0) .arg(b, 0, 'f', 0) .arg(percent, 0, 'f', 1); // 直接将content文本传入现有QLabel控件展示即可 }
实现注意事项
- 注意OpenCV默认的图像通道顺序是BGR,输出RGB值时不要把B、R通道搞混,否则会出现显示的颜色和标注的RGB值不匹配的问题。
- 占比统计的准确性完全依赖
labels矩阵的完整性,确保调用cv::kmeans时传入的像素数据和生成labels的数据完全一致,不要在kmeans调用前后修改输入矩阵的尺寸或内容。 - 统计占比不需要重新遍历聚类后生成的效果图像,直接基于kmeans返回的标签计算即可,性能开销几乎可以忽略。
- 如果之前的亮度排序是直接拿单通道值做比较,可替换为人眼感知加权的亮度公式,排序结果会更符合日常视觉认知。
内容的提问来源于stack exchange,提问作者sachin
相关产品推荐
相关产品推荐

