🚀 优质资源分享 🚀
学习路线指引(点击解锁) | 知识定位 | 人群定位 |
---|---|---|
🧡 Python实战微信订餐小程序 🧡 | 进阶级 | 本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。 |
💛Python量化交易实战💛 | 入门级 | 手把手带你打造一个易扩展、更安全、效率更高的量化交易系统 |
目录 |
回到顶部## 0 引言
大多数分割算法都基于图像灰度值的两个基本性质之一:不连续性和相似性。第一类方法根据灰度的突变(如边缘)将图像分割为多个区域:首先寻找边缘线段,然后将这些线段连接为边界的方法来识别区域。第二类方法根据一组预定义的准则把一幅图像分割为多个区域。本节讨论两种相关的区域分割方法:(1)在数据中寻找聚类的经典方法,它与亮度和颜色等变量有关;(2)用聚类从图像中提取“超像素”的现代方法。
回到顶部## 1 使用k均值聚类的区域分割
1.1 原理
**聚类方法的思想****是将样本集合按照其特征的相似性划分为若干类别,使同一类别样本的特征具有较高的相似性,不同类别样本的特征具有较大的差异性。**令{z1, z2, z3 …, zn}是样本集合,在图像分割中,样本向量z的每个分量表示一个数值像素属性。例如,分割只基于灰度尺度时,z是一个表示像素灰度的标量。分割的如果是RGB彩色图像,z通常是一个三维向量,这个三维向量的每个分量是RGB三通道的灰度值。k均值聚类的目的就是将样本集合划分为k个满足如下最优准则的不相交的聚类集合C={C1, C2, …, Ck}:
式中,mi是集合Ci中样本的均值向量(或质心),||z-mi||项是Ci中的一个样本到均值mi的欧式距离。换言之,这个公式说,我们感兴趣的是找到集合C={C1, C2, …, Ck},集合中的每个点到该集合的均值的距离之和是最小的。
基于聚类的区域分割,就是基于图像的灰度、颜色、纹理、形状等特征,用聚类算法把图像分成若干类别或区域,使每个点到聚类中心的均值最小。k 均值(k-means)是一种无监督聚类算法。基于 k 均值聚类算法的区域分割,算法步骤为:
(1)初始化算法:规定一组初始均值
(2)将样本分配给聚类:对所有的像素点,计算像素到每个聚类中心的距离,将像素分类到距离最小的一个聚类中;
(3)更新聚类中心:根据分类结果计算出新的聚类中心;
(4)完备性验证:计算当前步骤和前几步中平均向量之间的差的欧几里得范数。计算残差E,即k个范数之和。若E≤T,其中T是一个规定的非负阈值,则停止。否则,返回步骤2。
1.2 cv::kmeans函数
OpenCV提供了函数cv::kmeans来实现 k-means 聚类算法。函数cv::kmeans不仅可以基于灰度、颜色对图像进行区域分割,也可以基于样本的其它特征如纹理、形状进行聚类。
double cv::kmeans(InputArray data, //用于聚类的数据,类型为 CV\_32F
int K, //设定的聚类数量
InputOutputArray bestLabels, //输出整数数组,用于存储每个样本的聚类类别索引
TermCriteria criteria, //算法终止条件:即最大迭代次数或所需精度
int attempts, //用于指定使用不同初始标记执行算法的次数
int flags, //初始化聚类中心的方法:0=随机初始化 1=kmeans++方法初始化 2=第一次用用户指定的标签初始化,后面attempts-1都用随机或版随机的初始化
OutputArray centers = noArray() //聚类中心的输出矩阵,每个聚类中心占一行
)
示例 图像分割之k均值聚类
| 123456789101112131415 | Mat src = imread(``"./14.tif"``, 0);``Mat dataPixels = src.reshape(0, 1);``//可以是一列,每一行表示一个样本;或者一行,一列是一个样本;样本的分量数为通道数``dataPixels.convertTo(dataPixels, CV_32FC1);``//输入需要是32位浮点型``int
numCluster = 3;``Mat labels;``Mat centers;``kmeans(dataPixels, numCluster, labels, TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 10, 0.1), 3, KMEANS_PP_CENTERS, centers);``Mat dst = Mat::zeros(src.size(), CV_8UC1);``float``* pdata = dataPixels.ptr<``float``>(0);``for
(``int
i = 0; i < src.rows * src.cols; i++) {``int
k = labels.ptr<``int``>(i)[0];``//每个像素对应的标签k,即属于集合k``pdata[i] = centers.ptr<``float``>(k)[0];``//用集合中心替换该像素``}``dataPixels.convertTo(dataPixels, CV_8UC1);``dst = dataPixels.reshape(0, src.rows);
|
1.3 cv::kmeans源码
当图片非常大时,对图像进行简单的计算操作,耗时就会变得非常大,常用的加速方法如OpenMp,TBB,OpenCL和CUDA。使用cuda时,图片在cpu和gpu之间的传输时间就达到上百ms,不适合本来就是ms级的计算。在kmeans源码中使用**parallel_for_**并行计算各个样本到聚类中心的距离。这边写一个简单的例子,了解下parallel_for_的用法。
示例 利用并行计算加速图片旋转
| 1234567891011121314151617181920212223242526272829303132333435363738 | // 方法1:并行 将该方法写成一个类,继承ParallelLoopBody,然后重写(),利用parallel_for_可以开启并行``class
trans :``public
ParallelLoopBody {``public``:``trans(``const
uchar* _src, uchar*_dst,
int
_dims,
int
_istep):src(_src), dst(_dst), dims(_dims), istep(_istep) {}``void
operator()(``const
Range& range)
const
//重载操作符()``{``for
(``int
n = range.start; n < range.end; ++n)``{``for
(``int
i = 0; i < dims; i++)``dst[i * istep + (istep - 1 - n)] = src[n * dims + i];``}``}``private``:``const
uchar* src;``uchar* dst;``int
dims;``int
istep;``};``// 方法2 for循环`