知行合一Mean-shift实现篇

前言

度过了理论篇,相信这篇你读起来会轻松很多。本文尝试使用mean shift算法对图像进行降噪。如果你对这个算法还不清楚建议阅读我写的上一篇文章。

环境和依赖

  1. mac或者linux系统(win平台没有测试)
  2. g++ 和 cmake
  3. openCV

cmake:这里有一个不错的传送门更多的内容请百度.
openCV 的安装和下载请看传送门更多其他平台内容请百度。

直接看代码:

主文件设置成了test.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#include <highgui.h>
#include <cv.h>
#include <iostream>
#include <stdlib.h>
#include <math.h>
using namespace cv;
using namespace std;
//获取某个点的收敛距离
int getNewZ(Vec3f v1);
//获取两个点间的距离
float getDistance(Vec3f v1,Vec3f v2);
// 启点 终点 =》 终点减起点
Vec3f getNewVec(Vec3f v1,Vec3f v2);
//计算两个向量的和
Vec3f vecAdd(Vec3f v1,Vec3f v2);
//获取一个点的平局向量
Vec3f getargvec(Vec3f v);
//多维球体半径 实验结果这个数字越大 图像越平滑
float h = 10.0;
//最终 收敛条件
float m = 0.1;
Mat src,dist;
// 这里面的参数是图片地址
int main(int argc,char ** argv) {
src = imread(argv[1],CV_LOAD_IMAGE_GRAYSCALE);//其实这里的取灰度图不是特别有必要了这里图片已经是Vec3b的三维量了。
cvNamedWindow("Image_show",1);
imshow("Image_show",src);
dist = src.clone();
//现在应该使用mean shirft算法对这个图片进行降噪
//对数据结构的测试代码
cout << dist.channels() << endl;
cout << dist.rows << endl << dist.cols << endl;
//128X128
for(int i=0;i < dist.rows;i++){
//uchar* data = src.ptr<uchar>(i);
for(int j=0;j < dist.cols;j++){
Vec3f point = Vec3f((float)i,(float)j,(float)dist.at<uchar>(i,j));
int z = getNewZ(point);
//cout << z<<":"<< (int)dist.at<uchar>(i,j) << endl;
dist.at<uchar>(i,j) = z;
// //todo 这里使用算法对dist进行改造
}
}
// cout<< dist.channels()<<endl;
//把新图片打印出来
cvNamedWindow("new_Image",1);
imshow("new_Image",dist);
//保证程序不关闭
waitKey(0);
return 0;
}
float getDistance(Vec3f v1,Vec3f v2){
float sum = pow(v1[0]-v2[0],2)+pow(v1[1]-v2[1],2)+pow(v1[2]-v2[2],2);
//cout << sqrt(sum) <<endl;
return sqrt(sum);
}
// 启点 终点 =》 终点减起点
Vec3f getNewVec(Vec3f v1,Vec3f v2){
return Vec3f(v2[0]-v1[0],v2[1]-v1[1],v2[2]-v1[2]);
}
//计算两个向量的和
Vec3f vecAdd(Vec3f v1,Vec3f v2){
return Vec3f(v1[0]+v2[0],v1[1]+v2[1],v1[2]+v2[2]);
}
int getNewZ(Vec3f v1){
//根据 src h 确定 出循环查询的范围
//计算范围内点的 “平局向量的值”
Vec3f argV,newV;
argV = getargvec(v1);
//使用m判断收敛条件
newV = vecAdd(v1,argV);
if(getDistance(argV,Vec3f(0,0,0)) <= m){
return (int)newV[2];
}
//产生新的 递归
return getNewZ(newV);
}
Vec3f getargvec(Vec3f v){
//通过v 判断循环范围 floor 向下取整 ceil 向上取整
int x_start,x_end,y_start,y_end;
x_start = v[0]-h > 0 ? ceil(v[0]-h) : 0;
x_end = v[0]+h < src.rows ? floor(v[0]+h) : src.rows;
y_start = v[1]-h > 0 ? ceil(v[1]-h) : 0;
y_end = v[1]+h < src.cols ? floor(v[1]+h) : src.cols;
//循环时使用 src 原始数据
Vec3f tmp = v;
int k = 0;
v = getNewVec(v,v);
for(int i=x_start;i<x_end;i++){
for(int j=y_start;j<y_end;j++){
//现在判断这个点是不是在范围内 然后累加求和
Vec3f end_point = Vec3f(float(i),float(j),(float)src.at<uchar>(i,j));
Vec3f tmp2 = getNewVec(tmp,end_point);
if(getDistance(Vec3f(0,0,0),tmp2) <= h){
//todo 这里的平均向量计算有问题
k++;
v = vecAdd(v,tmp2);
}
}
}
//偏移向量进行 衰减
if(k!=0){
v = Vec3f(v[0]/(float)k,v[1]/(float)k,v[2]/(float)k);
}
return v;
}

为了方便我先把cmake的配置文件先送上。一会在对代码进行解释。CMakeLists.txt如下:

1
2
3
4
5
6
7
8
9
cmake_minimum_required(VERSION 2.8)
PROJECT(Test)
FIND_PACKAGE( OpenCV REQUIRED )
INCLUDE_DIRECTORIES(
${ShowImage_SOURCE_DIR}
)
ADD_EXECUTABLE(Test test.cpp)
TARGET_LINK_LIBRARIES (Test ${OpenCV_LIBS} )

代码解释

要是看过之前理论篇的同学,不难理解,我要实现的的核函数对每个点的加权是一样的,没有越近就越大越远就越小。然后也没有使用自适应的步长。
我的大体思路是使用mean shift算法计算每个素点的收敛的值作为这个点新的灰度。这里面没有什么复杂的逻辑,所以很好理解。这里圆内判断条件,我进行了一定的优化使用正方形代替了圆进行计算。减少了循环的次数。

实验结果

结果图

这是我写的源代码git的传送门。欢迎学习的目的使用。:)

我将一直迷惑和无知,我是黄油香蕉君,再见。

给作者买杯咖啡吧。喵~