漫谈 Clustering (番外篇): Vector Quantization

介绍Vector Quantization(VQ)的基本原理及其在图像压缩中的应用,通过K-means聚类算法减少图像颜色数量以实现数据压缩。

转:http://blog.pluskid.org/?p=57

本文是“漫谈 Clustering 系列”中的第 3 篇,参见本系列的其他文章

stallman

在接下去说其他的聚类算法之前,让我们先插进来说一说一个有点跑题的东西:Vector Quantization 。这项技术广泛地用在信号处理以及数据压缩等领域。事实上,在 JPEG 和 MPEG-4 等多媒体压缩格式里都有 VQ 这一步。

Vector Quantization 这个名字听起来有些玄乎,其实它本身并没有这么高深。大家都知道,模拟信号是连续的值,而计算机只能处理离散的数字信号,在将模拟信号转换为数字信号的时候,我们可以用区间内的某一个值去代替着一个区间,比如,[0, 1) 上的所有值变为 0 ,[1, 2) 上的所有值变成 1 ,如此类推。其这就是一个 VQ 的过程。一个比较正式一点的定义是:VQ 是将一个向量空间中的点用其中的一个有限子集来进行编码的过程。

一个典型的例子就是图像的编码。最简单的情况,考虑一个灰度图片,0 为黑色,1 为白色,每个像素的值为 [0, 1] 上的一个实数。现在要把它编码为 256 阶的灰阶图片,一个最简单的做法就是将每一个像素值 x 映射为一个整数 floor(x*255) 。当然,原始的数据空间也并不以一定要是连续的。比如,你现在想要把压缩这个图片,每个像素只使用 4 bit (而不是原来的 8 bit)来存储,因此,要将原来的 [0, 255] 区间上的整数值用 [0, 15] 上的整数值来进行编码,一个简单的映射方案是 x*15/255 。

VQ 2

VQ 2

不过这样的映射方案颇有些 Naive ,虽然能减少颜色数量起到压缩的效果,但是如果原来的颜色并不是均匀分布的,那么的出来的图片质量可能并不是很好。例如,如果一个 256 阶灰阶图片完全由 0 和 13 两种颜色组成,那么通过上面的映射就会得到一个全黑的图片,因为两个颜色全都被映射到 0 了。一个更好的做法是结合聚类来选取代表性的点。

实际做法就是:将每个像素点当作一个数据,跑一下 K-means ,得到 k 个 centroids ,然后用这些 centroids 的像素值来代替对应的 cluster 里的所有点的像素值。对于彩色图片来说,也可以用同样的方法来做,例如 RGB 三色的图片,每一个像素被当作是一个 3 维向量空间中的点。

用本文开头那张 Rechard Stallman 大神的照片来做一下实验好了,VQ 2、VQ 10 和 VQ 100 三张图片分别显示聚类数目为 2 、10 和 100 时得到的结果,可以看到 VQ 100 已经和原图非常接近了。把原来的许多颜色值用 centroids 代替之后,总的颜色数量减少了,重复的颜色增加了,这种冗余正是压缩算法最喜欢的。考虑一种最简单的压缩办法:单独存储(比如 100 个)centroids 的颜色信息,然后每个像素点存储 centroid 的索引而不是颜色信息值,如果一个 RGB 颜色值需要 24 bits 来存放的话,每个(128 以内的)索引值只需要 7 bits 来存放,这样就起到了压缩的效果。

VQ 100

VQ 100

VQ 10

VQ 10

实现代码很简单,直接使用了 SciPy 提供的 kmeans 和 vq 函数,图像读写用了 Python Image Library :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/python
 
from scipy.cluster.vq import kmeans, vq
from numpy import array, reshape, zeros
from mltk import image
 
vqclst = [2, 10, 100, 256]
 
data = image.read('example.jpg')
(height, width, channel) = data.shape
 
data = reshape(data, (height*width, channel))
for k in vqclst:
    print 'Generating vq-%d...' % k
    (centroids, distor) = kmeans(data, k)
    (code, distor) = vq(data, centroids)
    print 'distor: %.6f' % distor.sum()
    im_vq = centroids[code, :]
    image.write('result-%d.jpg' % k, reshape(im_vq,
        (height, width, channel)))

当然,Vector Quantization 并不一定要用 K-means 来做,各种能用的聚类方法都可以用,只是 K-means 通常是最简单的,而且通常都够用了。

<think> 根据用户提供的引用内容,特别是引用[1]和引用[2],我们可以了解到`std::vector<pcl::PointIndices> cluster_indices`在点云库(PCL)中的用途和基本用法。 ### 定义 `std::vector<pcl::PointIndices> cluster_indices`是一个标准模板库(STL)的向量容器,用于存储多个`pcl::PointIndices`对象。每个`pcl::PointIndices`对象代表一个点云聚类的索引集合。具体来说: - `pcl::PointIndices`是一个PCL中定义的结构体,包含一个整数索引的向量(`std::vector<int> indices`),用于存储属于同一个聚类的点在原始点云中的索引位置。 - 因此,`cluster_indices`向量中的每个元素(如`cluster_indices[0]`)对应一个检测到的聚类,其`indices`成员包含了该聚类中所有点的索引。 ### 使用方法 以下是`cluster_indices`的典型使用步骤(基于引用[2]): 1. **创建对象**: ```cpp std::vector<pcl::PointIndices> cluster_indices; ``` 2. **配置聚类提取参数**(以欧几里得聚类为例): ```cpp pcl::EuclideanClusterExtraction<pcl::PointXYZ> ec; ec.setClusterTolerance(0.02); // 设置聚类距离阈值(单位:米) ec.setMinClusterSize(100); // 最小聚类点数 ec.setMaxClusterSize(25000); // 最大聚类点数 ec.setSearchMethod(tree); // 设置搜索方法(如KdTree) ec.setInputCloud(cloud_filtered); // 输入滤波后的点云 ``` 3. **执行聚类提取**: ```cpp ec.extract(cluster_indices); // 结果存储在cluster_indices中 ``` 4. **遍历聚类并处理**: 使用迭代器遍历每个聚类,并根据索引提取对应的点云数据(引用[1]): ```cpp int cluster_id = 0; for (auto it = cluster_indices.begin(); it != cluster_indices.end(); ++it) { pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_cluster(new pcl::PointCloud<pcl::PointXYZ>); // 遍历当前聚类的所有点索引 for (const auto& idx : it->indices) { cloud_cluster->push_back((*cloud_filtered)[idx]); // 将点添加到新点云 } // 可选:保存或处理cloud_cluster cluster_id++; } ``` ### 关键点说明 - **索引的作用**:`cluster_indices`存储的是**索引**而非实际点数据,因此处理大规模点云时可节省内存(引用[3]提到分块处理策略应对内存溢出)。 - **与关键点提取的区别**:引用[4]中提到的关键点提取(如SIFT、Harris)通常生成的是点的位置,而聚类提取生成的是点集的索引集合。 ### 注意事项 - 输入点云`cloud_filtered`必须是`pcl::PointCloud`的共享指针(如`pcl::PointCloud<pcl::PointXYZ>::Ptr`)。 - 若点云数据量极大,需结合分块处理(引用[3])避免内存溢出。 §§相关问题§§ 1. 如何优化欧几里得聚类提取的速度? 2. `pcl::PointIndices`结构体的详细定义是什么? 3. 如何处理聚类后的点云数据(如保存、可视化)? </think> ### `std::vector<pcl::PointIndices> cluster_indices` 详解 #### 1. **定义与用途** - **核心作用**:存储点云聚类结果的索引集合,每个 `pcl::PointIndices` 对象代表一个独立聚类。 - **数据结构**: ```cpp std::vector<pcl::PointIndices> cluster_indices; ``` - `pcl::PointIndices` 是 PCL 中的结构体,包含成员 `std::vector<int> indices`,存储**原始点云索引**。 - `cluster_indices` 作为向量容器,保存多个聚类结果(如 `cluster_indices[0]` 是第一个聚类的索引集合)[^2]。 #### 2. **典型使用流程** ##### (1) 聚类提取(以欧几里得聚类为例) ```cpp // 创建 KdTree 搜索对象 pcl::search::KdTree<PointT>::Ptr tree(new pcl::search::KdTree<PointT>); tree->setInputCloud(cloud_filtered); // 配置聚类参数 pcl::EuclideanClusterExtraction<PointT> ec; ec.setClusterTolerance(0.02); // 聚类距离阈值 (单位:) ec.setMinClusterSize(100); // 最小点数 ec.setMaxClusterSize(25000); // 最大点数 ec.setSearchMethod(tree); // 设置搜索方法 ec.setInputCloud(cloud_filtered); // 输入点云 // 执行聚类并将结果存入 cluster_indices std::vector<pcl::PointIndices> cluster_indices; ec.extract(cluster_indices); // 聚类索引输出至此[^2] ``` ##### (2) 遍历聚类结果 ```cpp int cluster_id = 0; for (const auto& cluster : cluster_indices) { pcl::PointCloud<PointT>::Ptr cloud_cluster(new pcl::PointCloud<PointT>); // 通过索引提取当前聚类的点 for (const auto& idx : cluster.indices) { cloud_cluster->push_back((*cloud_filtered)[idx]); // 索引指向原始点云 } // 后续操作(如保存/处理聚类点云) std::cout << "Cluster " << cluster_id++ << " has " << cloud_cluster->size() << " points." << std::endl; } ``` #### 3. **关键特性** 1. **内存高效**: - 存储的是**索引而非点云数据副本**,适合大规模点云处理[^3]。 - 结合分块策略可避免内存溢出: ```cpp pcl::PointCloud<PointT>::Ptr cloud_chunk(new pcl::PointCloud<PointT>); pcl::copyPointCloud(*full_cloud, chunk_indices, *cloud_chunk); // 分块处理[^3] ``` 2. **与关键点提取的关联**: - 聚类常基于关键点(如 Harris、SIFT)进行: ```cpp pcl::SIFTKeypoint<PointT, pcl::PointWithScale> sift; sift.setScales(min_scale, n_octaves, n_scales_per_octave); // 设置关键点尺度[^4] ``` - 关键点索引可作为聚类输入。 #### 4. **注意事项** - **索引有效性**:确保索引对应的点云未被释放或修改。 - **参数调优**: - `setClusterTolerance()` 影响聚类粒度(值越大聚类越少)。 - `setMin/MaxClusterSize()` 过滤噪声或过小聚类[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值