利用k-means演算法對灰度影象分割
本文主要利用k-means來對灰度影象進行分割。首先對k-means進行簡單的介紹,然後直接上程式碼。那麼什麼是k-means演算法?K-means演算法是硬聚類演算法,是典型的基於原型的目標函式聚類方法的代表,它是資料點到原型的某種距離作為優化的目標函式,利用函式求極值的方法得到迭代運算的調整規則。K-means演算法以歐式距離作為相似度測度,它是求對應某一初始聚類中心向量V最優分類,使得評價指標J最小。演算法採用誤差平方和準則函式作為聚類準則函式。這是百度百科的介紹,說了這麼多,其實作者也不知道百度百科在說啥,那就來看看基本的演算法步驟吧。基本的演算法步驟如下:
1. 從N個樣本隨機選取K個文字作為質心;
2. 對剩餘每個樣本測量其到每個質心的距離,並把它歸到最近的質心類
3. 重新計算已經得到的各個類的質心;
4. 迭代2、3步驟直至新的質心與原質心相等或小於指定的閾值,演算法結束。
這下演算法就比較清晰,下面直接貼程式碼,我的程式碼主要定義了point.h標頭檔案、kmeans.h標頭檔案和kmeans.cpp原始檔。下面是point.h檔案的程式碼。
#ifndef POINT_H
#define POINT_H
//point結構主要用來儲存影象中節點的橫座標,縱座標以及灰度值
struct point
{
int row;
int col;
double pixVal;
point(int row, int col, double pixVal) :row(row),col(col),pixVal(pixVal) {}
};
#endif
接下來,是keams.h標頭檔案:
#ifndef KMEANS_H
#define KMEANS_H
#include<opencv2\opencv.hpp>
#include<random>
#include<time.h>
#include<stdio.h>
#include<stdlib.h>
#include<list>
#include<iostream>
#include<math.h>
#include"point.h"
using namespace cv;
using namespace std;
class Kmeans{
private:
//儲存所有點
vector<point> points;
//儲存簇的中心點
vector<point> centers;
//儲存每個點到相應的簇
vector<point>* clusters;
//向量的維數
int dimension;
//簇的個數
int k;
public:
//建構函式
Kmeans(vector<point> points, vector<point> centers, int k, int dimension)
{
this->points = points;
this->centers = centers;
this->dimension = dimension;
this->k = k;
clusters = new vector<point>[k];
}
//解構函式
~Kmeans()
{
delete clusters;
}
//獲取簇
vector<point>* getClusters()
{
return this->clusters;
}
//計算兩個向量之間的歐式距離
double getDistanceBetweenTwoPoints(const point& point1, const point& point2)
{
double sum = 0;
//double tmp;
//for (int i = 0; i < dimension; i++)
//{
//tmp = pow(point1.pixVal - point2.pixVal,2);
//sum += tmp;
//}
sum = pow(point1.pixVal - point2.pixVal, 2);
return sqrt(sum);
}
//計算每個點到離它最近的簇中心點,結果儲存到vector中
vector<int> getClosetClusterCenterLabel()
{
double min;
int label;
vector<int> labels;
for (int i = 0; i < points.size(); i++)
{
label = 0;
min = getDistanceBetweenTwoPoints(points[i], centers[0]);
for (int j = 1; j < centers.size(); j++)
{
double tmp = getDistanceBetweenTwoPoints(points[i], centers[j]);
if (tmp < min)
{
min = tmp;
label = j;
}
}
labels.push_back(label);
}
return labels;
}
//將每個點放入它離的最近的中心點對應的簇中
void computeClusters(const vector<int>& labels)
{
for (int i = 0; i < k; i++)
{
clusters[i].clear();
}
for (int i = 0; i < labels.size(); i++)
{
int label = labels[i];
clusters[label].push_back(points[i]);
}
}
//重新計算所有簇的中心點的灰度值
void computeCenters()
{
centers.clear();
for (int i = 0; i < k; i++)
{
double sum = 0;
for (int j = 0; j < clusters[i].size(); j++)
{
sum += clusters[i][j].pixVal;
}
double meanVal = sum / clusters[i].size();
point cp(-1, -1, meanVal);
centers.push_back(cp);
}
}
//確定新的中心點後重新計算一次cost
double computeCost()
{
double sum = 0;
for (int i = 0; i < k; i++)
{
vector<point> tmpVec=clusters[i];
for (int j = 0; j < tmpVec.size(); j++)
{
sum += getDistanceBetweenTwoPoints(tmpVec[j], centers[i]);
}
}
return sum / points.size();
}
//迭代執行k-means演算法的步驟
void kmeans()
{
double oldCost, newCost;
vector<int> labels=getClosetClusterCenterLabel();
computeClusters(labels);
newCost = computeCost();
computeCenters();
labels = getClosetClusterCenterLabel();
computeClusters(labels);
oldCost = newCost;
newCost = computeCost();
while (oldCost != newCost)
{
oldCost = newCost;
computeCenters();
labels = getClosetClusterCenterLabel();
computeClusters(labels);
newCost = computeCost();
}
cout <<"Final Cost: "<< newCost << endl;
}
};
#endif
接下來,是測試的kmeans.cpp檔案:
#include "kmeans.h"
//圖片的存放位置
const String imageFolder = "F:\\";
//簇的個數(即k的大小,根據自己需要調整)
const int numOfCluster =4;
//最大畫素值
const int MAX_PIX_VALUE = 255;
//存放所有點
vector<point> points;
//存放所有簇中心
vector<point> centers;
//存放所有點顏色特徵(i,j)->i*rows+j
vector<double> pixVec;
//讀取影象
Mat readImage(String imageName)
{
String imageLoc = imageFolder + imageName;
Mat image=imread(imageLoc);
return image;
}
//初始化k-means聚類中心
void initializeCenters(const Mat& img)
{
srand((unsigned)time(NULL));
for (int i = 0; i < numOfCluster; i++)
{
int randomX = rand() % img.rows;
int randomY = rand() % img.cols;
uchar pixVal = img.at<uchar>(randomX, randomY);
point cp(randomX, randomY, (double)pixVal);
centers.push_back(cp);
}
}
//將影象中的所有點裝入points中
void initializePoints(const Mat& img)
{
for (int i = 0; i < img.rows; i++)
{
const uchar* data = img.ptr<uchar>(i);
for (int j = 0; j < img.cols; j++)
{
uchar pixVal = data[j];
point p(i,j, (double)pixVal);
points.push_back(p);
}
}
}
int main()
{
String imageName = "lena.jpg";
Mat img = readImage(imageName);
cvtColor(img, img, CV_RGB2GRAY);//轉化為灰度影象
namedWindow(imageName,WINDOW_NORMAL);
imshow(imageName, img);
waitKey(0);
int rows = img.rows;
int cols = img.cols;
initializeCenters(img);
initializePoints(img);
Kmeans* km=new Kmeans(points, centers, numOfCluster, 1);
cout << "---------------k-means start-------------" << endl;
km->kmeans();
cout << "---------------k-means end---------------" <<endl;
vector<point>* clusters = km->getClusters();
Mat res(img.rows,img.cols,img.type());
double div = MAX_PIX_VALUE / numOfCluster;
for (int i = 0; i < numOfCluster; i++)
{
vector<point> tmpVec = clusters[i];
for (int j = 0; j < tmpVec.size(); j++)
{
res.at<uchar>(tmpVec[j].row, tmpVec[j].col) = i*div;
}
}
namedWindow("kmeansResult",WINDOW_NORMAL);
imshow("kmeansResult", res);
waitKey(0);
imwrite("./segment_lena.jpg", res);
system("pause");
}
---------------------
作者:Rainbowzhouwen
來源:CSDN
原文:https://blog.csdn.net/u014612806/article/details/65442061
版權宣告:本文為博主原創文章,轉載請附上博文連結!