效果图:
参考了c++的高斯模糊的算法 高斯模糊的C++实现(Gaussian Blur),改成了Java版本的和ndk版本的,对比了下效果,Java的效率比较低,用时几十秒,ndk才不到1秒,毕竟安卓是Linux内核,c++跟接近底层。
原理
图像是颜色矩阵组成的,通过改变每个像素点的rgb的颜色值,可以改变图像。
图片模糊就是把像素点的rgb的值取周围像素点的加权平均值,使像素点失去其特性。
高斯模糊(英语:Gaussian Blur),也叫高斯平滑。
正态分布一维表达式:
二维是2个一维函数的积分:
这种计算方式时间复杂度大,这里试了下另一种高效的方法,对一维高斯函数分别在水平和垂直方向上继续2次计算。
下面分别贴出Java和dnk2个版本的核心代码。
- GsUtil.java工具类
/**
* @author DaQiang
* @time 2020/12/18 14:25
* @desc 高斯函数工具类
*/
public class GsUtil {
public static final float sigma = 1F;
public static final int r = 5;
/**
* 边界处理函数
* 之所以引入这个函数,是因为在高斯模糊中,每个点的新值都是由临近的矩形区域的平均值得出,
* 但是若矩形区域超出图像范围,则会产生黑边。为了消除这一影响,我们采用对称的方法,
* 将矩形的超出部分用内部的对应点代替。
*
* @param x
* @param i
* @param w
* @return
*/
private static int edge(int x, int i, int w) {
// x为中心位置, i为偏移量,w为宽度
int inx = x + i;
if (inx < 0 || inx >= w)
return x - i;
return inx;
}
// 一维高斯函数
private static double gaussFunc1D(int x) {
double A = 1 / (sigma * Math.sqrt(2 * 3.141592653));
double index = -1.0 * (x * x) / (2 * sigma * sigma);
return A * Math.exp(index);
}
// 获取线性的权值空间
private static double[] getKernal() {
double[] weight = new double[2 * r + 1];
double sum = 0;
// 获取权值空间weight[]
for (int i = 0; i < 2 * r + 1; i++) {
weight[i] = gaussFunc1D(i - r);
sum += weight[i];
}
// 归一化
for (int i = 0; i < 2 * r + 1; i++) {
weight[i] /= sum;
}
return weight;
}
//进行两个方向的高斯模糊获
// 获取最终的模糊图像
public static Bitmap getBlurImage(Bitmap originImg) {
int width = originImg.getWidth();
int height = originImg.getHeight();
// 原图为 originImg
// 第一个方向处理的图像为tmpImg
Bitmap tmpImg = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
// 经过第二次处理的最终结果newImg
Bitmap newImg = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
double[] weight = getKernal();
// 在横向进行一次相加
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
double red = 0, green = 0, blue = 0;
for (int i = -r; i <= r; i++) {
// 边界处理后的对应的权值矩阵实际值
int inx = edge(x, i, width);
int color = originImg.getPixel(inx, y);
red += Color.red(color) * weight[r + i];
green += Color.green(color) * weight[r + i];
blue += Color.blue(color) * weight[r + i];
}
int color = Color.rgb(Integer.parseInt(Math.round(red)+""), Integer.parseInt(Math.round(green)+""), Integer.parseInt(Math.round(blue)+""));
int srcColor = originImg.getPixel(x,y);
Log.d("GsUtil", "color:" + color+" srcColor: "+srcColor);
tmpImg.setPixel(x, y, color);
}
}
// 在纵方向对第一次的结果重新进行一次
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
double red = 0, green = 0, blue = 0;
for (int i = -r; i <= r; i++) {
int iny = edge(y, i, height);
int color = tmpImg.getPixel(x, iny);
red += Color.red(color) * weight[r + i];
green += Color.green(color) * weight[r + i];
blue += Color.blue(color) * weight[r + i];
}
int color = Color.rgb(Integer.parseInt(Math.round(red)+""), Integer.parseInt(Math.round(green)+""), Integer.parseInt(Math.round(blue)+""));
newImg.setPixel(x, y, color);
}
}
return newImg;
}
}
- native-lib.cpp
#include <jni.h>
#include <string>
#include<math.h>
#include <android/bitmap.h>
#include <stdlib.h>
#include <android/log.h>
#define TAG "native-lib" // 这个是自定义的LOG的标识
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型
float sigma = 3;
int r = 9;
int edge(int x, int i, int w) {
// x为中心位置, i为偏移量,w为宽度
int inx = x + i;
if (inx < 0 || inx >= w)
return x - i;
return inx;
}
double gaussFunc1D(int x) {
double A = 1 / (sigma * sqrt(2 * 3.141592653));
double index = -1.0 * (x * x) / (2 * sigma * sigma);
return A * exp(index);
}
void getKernal(double *weight) {
// double *weight = new double[2 * r + 1];
double sum = 0;
// 获取权值空间weight[]
for (int i = 0; i < 2 * r + 1; i++) {
weight[i] = gaussFunc1D(i - r);
sum += weight[i];
}
// 归一化
for (int i = 0; i < 2 * r + 1; i++) {
weight[i] /= sum;
}
}
extern "C"
JNIEXPORT jintArray JNICALL
Java_com_example_imagendk_ImageUtil_getImageGaoSiMohu(JNIEnv *env, jclass clazz, jintArray arr,
jint width, jint height) {
jint *source = (*env).GetIntArrayElements(arr, NULL);
int newSize = width * height;
double *weight = new double[2 * r + 1];
getKernal(weight);
int *temp = new int[newSize];
int *re = new int[newSize];
// 在横向进行一次相加
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
double red = 0, green = 0, blue = 0;
for (int i = -r; i <= r; i++) {
// 边界处理后的对应的权值矩阵实际值
int inx = edge(x, i, width);
int color = source[y * width + inx];
int tmpRed = (color >> 16) & 0xFF;
int g = (color >> 8) & 0xFF;
int b = color & 0xFF;
red += tmpRed * weight[r + i];
green += g * weight[r + i];
blue += b * weight[r + i];
}
int color = 0xff000000 | (int(red) << 16) | (int(green) << 8) | int(blue);
temp[y * width + x] = color;
}
}
// 在纵方向对第一次的结果重新进行一次
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
double red = 0, green = 0, blue = 0;
for (int i = -r; i <= r; i++) {
int iny = edge(y, i, height);
int color = temp[iny * width + x];
int tmpRed = (color >> 16) & 0xFF;
int g = (color >> 8) & 0xFF;
int b = color & 0xFF;
red += tmpRed * weight[r + i];
green += g * weight[r + i];
blue += b * weight[r + i];
}
int color = 0xff000000 | (int(red) << 16) | (int(green) << 8) | int(blue);
re[y * width + x] = color;
}
}
jintArray result = (*env).NewIntArray(newSize);
(*env).SetIntArrayRegion(result, 0, newSize, re);
(*env).ReleaseIntArrayElements(arr, source, 0);
(*env).ReleaseIntArrayElements(arr, temp, 0);
(*env).ReleaseIntArrayElements(arr, re, 0);
return result;
}