C++ 高斯模糊 詳細解析與源代碼
圖片上的高斯模糊有種兩方式,一種是二維的遮罩一種是一維的,不過就算是一維的X方向做一次Y方向再做一次也是可以相等於二維的。
一維相對來說比較好做、比較好講,也可以稍微避免邊界的問題,這裡主題就已一維為主。
步驟
- 利用公式得出高斯矩陣
- 利用高斯矩陣做捲積
公式
參考一下維基百科的公式:維基百科高斯模糊
這個公式有兩個變數是可以決定的,r 與 sigma(那個圈圈)
// 高斯公式
float GauBlur::gau_meth(size_t r, float p) {
float two = 2;
float num = exp(-pow(r, two) / (two*pow(p, two)));
num /= sqrt(two*M_PI)*p;
return num;
}
sigma越大會越模糊,r 是指矩陣位置
在實際產生之前還要決定矩陣要設多大,這可以自己設定也可以用公式算
在實際產生之前還要決定矩陣要設多大,這可以自己設定也可以用公式算
// 計算矩陣長度
int mat_len = ((p-0.8) / 0.3+1.0) * 2.0;
比如說我設定成 sigma=1.6 那麼算出來需要長度為 7 的矩陣,當然你也可以自己決定要幾個,越多也會越模糊。
高斯矩陣
決定好矩陣長度之後你就可以利用公式算出來了
公式 r = | 3 | 2 | 1 | 0 | 1 | 2 | 3 |
---|---|---|---|---|---|---|---|
高斯矩陣 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
如此得到高斯矩陣。
得出之後還要把他們規一化,7個加起來總和是1
作法很簡單,讓矩陣個別除以他們的總和就可以了
得出之後還要把他們規一化,7個加起來總和是1
作法很簡單,讓矩陣個別除以他們的總和就可以了
寫成代碼像這個樣子
// 高斯矩陣
vector<float> GauBlur::gau_matrix(float p){
vector<float> gau_mat;
// 計算矩陣長度
int mat_len = ((p-0.8) / 0.3+1.0) * 2.0;
if (mat_len % 2 == 0) {++mat_len;}
// 一維高斯矩陣
gau_mat.resize(mat_len);
float sum=0;
for(int i=0, j=mat_len/2; j < mat_len; ++i, ++j) {
float temp;
if(i) {
temp = gau_meth(i, p);
gau_mat[j] = temp;
gau_mat[mat_len-j-1] = temp;
sum += temp += temp;
} else {
gau_mat[j]=gau_meth(i, p);
sum += gau_mat[j];
}
}
// 歸一化
for(auto&& i : gau_mat) { i /= sum; }
return gau_mat;
}
到這裡為止算出來的矩陣是
0 = 0.0441465
1 = 0.117223
2 = 0.210611
3 = 0.256038
4 = 0.210611
5 = 0.117223
6 = 0.0441465
1 = 0.117223
2 = 0.210611
3 = 0.256038
4 = 0.210611
5 = 0.117223
6 = 0.0441465
你也可以直接抄這個矩陣,直接拿去用
捲積
這裡需要引入一個名詞遮罩
假如說現在有9宮格,鍵盤上的,我的遮罩大小是 1x3 橫的,位置是5,那麼遮罩的矩陣是
假如說現在有9宮格,鍵盤上的,我的遮罩大小是 1x3 橫的,位置是5,那麼遮罩的矩陣是
4 5 6
依此類推,現在你讀入一張圖,剛剛的高斯矩陣長度多少遮罩就多少,需要你罩住長度的7的矩陣,位置從0跑到最後。
假如罩到邊邊,那麼缺的地方可以補邊緣或是鏡像另一邊開始算回來,都可以有很多方式,一般最簡單的建議補邊邊就好。
一樣是剛剛的例子舉例假如我選中鍵盤9宮格 7 那麼遮罩的矩陣是
一樣是剛剛的例子舉例假如我選中鍵盤9宮格 7 那麼遮罩的矩陣是
7 7 8
依此類推。
罩住之後就可以開始做捲積了,記得不要再同一個陣列做要寫到另一個陣列不然會被影響。然後加總的數值型態要是float或double不然出來圖會偏黑。
double sum = mask[0] gau[0] + mask[1] gau[1]…;
把它們各相乘最後再相加寫到新的陣列就可以完成了。
源代碼
// 高斯模糊
class GauBlur{
public:
static void raw2GauBlur(vector<unsigned char>& img_gau,
vector<unsigned char>& img_ori,
size_t width, size_t height, float p);
private:
static vector<float> gau_matrix(float p);
static float gau_meth(size_t r, float p);
};
// 高斯模糊
void GauBlur::raw2GauBlur(vector<unsigned char>& img_gau,
vector<unsigned char>& img_ori,
size_t width, size_t height, float p)
{
// 設定正確的大小
img_gau.resize(img_ori.size());
// 緩存
vector<double> img_gauX(img_ori.size());
// 高斯矩陣與半徑
vector<float> gau_mat = gau_matrix(p);
const int r = gau_mat.size()/2;
// 高斯模糊 X 軸
const size_t r = gau_mat.size() / 2;
for (unsigned j = 0; j < height; ++j) {
for (unsigned i = 0; i < width; ++i) {
double sum = 0;
for (unsigned k = 0; k < gau_mat.size(); ++k) {
int idx = i-r + k;
// idx超出邊緣處理
if (idx < 0) {
idx = 0;
} else if (idx >(int)(width-1)) {
idx = (width-1);
}
sum += img_ori[j*width + idx] * gau_mat[k];
}
img_gauX[j*width + i] = sum;
}
}
// 高斯模糊 Y 軸
for (unsigned j = 0; j < height; ++j) {
for (unsigned i = 0; i < width; ++i) {
double sum = 0;
for (unsigned k = 0; k < gau_mat.size(); ++k) {
int idx = j-r + k;
// idx超出邊緣處理
if (idx < 0) {
idx = 0;
} else if (idx > (int)(height-1)) {
idx = (height-1);
}
sum += img_gauX[idx*width + i] * gau_mat[k];
}
img_gau[j*width + i] = sum;
}
}
}
// 高斯公式
float GauBlur::gau_meth(size_t r, float p) {
float two = 2;
float num = exp(-pow(r, two) / (two*pow(p, two)));
num /= sqrt(two*M_PI)*p;
return num;
}
// 高斯矩陣
vector<float> GauBlur::gau_matrix(float p){
vector<float> gau_mat;
// 計算矩陣長度
int mat_len = ((p-0.8) / 0.3+1.0) * 2.0;
if (mat_len % 2 == 0) {++mat_len;}
// 一維高斯矩陣
gau_mat.resize(mat_len);
float sum=0;
for(int i=0, j=mat_len/2; j < mat_len; ++i, ++j) {
float temp;
if(i) {
temp = gau_meth(i, p);
gau_mat[j] = temp;
gau_mat[mat_len-j-1] = temp;
sum += temp += temp;
} else {
gau_mat[j]=gau_meth(i, p);
sum += gau_mat[j];
}
}
// 歸一化
for(auto&& i : gau_mat) { i /= sum; }
return gau_mat;
}
使用方式如下(須自己讀入一張灰階圖到 raw_img 陣列內)
vector<unsigned char> gau_img;
GauBlur::raw2GauBlur(gau_img, raw_img, height, width, p);
沒有留言:
張貼留言