转载请保留原文链接: http://blog.csdn.net/u010593680/article/details/42400825
本项目的Git地址在:https://git.oschina.net/0-0Xuan/XPhoto
学习NDK的书本知识是比较简单的,但是把NDK知识用到实际的项目中就难多了,在实际开发中会遇到非常多的问题,并且因为NDK涉及的内容非常的多,许多问题可能会困扰很久,可以说,NDK开发入门比SDK开发入门难了很多倍,刚开始学习NDK知识的人,最好不要停留在书本上的demo,最好可以自己尝试开发一项较实际的内容,来检验自己在NDK知识上的掌握程度,也可以考虑模仿该软件的功能,再和源码参照、以及对比测试,
可以说目前NDK方面的开发资料是,很少的,而源码就跟少了,个人学习NDK过程中遇到的问题还是很多的,并且很难直接从网上获得解决方案。深知学习NDK的不易,特此发此源码,给有需要的朋友。
接下来还是说下项目吧!
如今微博、朋友圈、各种社交平台上充斥着各种照片,自拍或者各种生活记录也多以照片为其主要的手段,可见照片对生活的重要性,所以图片处理软件几乎成为人们的必备软件之一。
由于人们对图片处理的需求较大,我希望能开发一款在生活中能使用的手机图片处理软件,于是我选取了一个应用场景,即人们在各种场合拍照时,只希望照片显示的重点是自己关注的内容,而非其他事物,但照片难免包含其他不愿意让别人看到的、无关紧要的东西:比如个人隐私,路人等。
所以本应用软件诞生了,该应用软件是一款运行于android系统上的应用软件,其主要功能是让用户选取一个可调节大小的圆形区域,并模糊掉圆形区域外的内容。
设计分析思路
选取使用技术:
Java是andorid应用软件的主要开发语言,使用java可以非常高效的开发应用
NDK技术:由于手机上的内存较少,处理器运算能力较弱,而使用Java对内存的控制较弱,处理效率也不高,但图片资源将占据较大的内存空间,并且处理照片需要庞大的运算,所以使用NDK技术,NDK是google为android应用中使用C/C++语言开发所提供的便利工具集合,包括:-
从C / C++生成原生代码库所需要的工具和build files。
-
将一致的原生库嵌入可以在Android设备上部署的应用程序包文件(application packages files ,即.apk文件)中。
-
支持所有未来Android平台的一系列原生系统头文件和库。
关键技术分析:
根据功能:1、解析图片2、展示图片3、选取圆区域覆盖的图片,4、模糊图片像素,
1、不同格式的图片,主要指的是图片数据的存储方式不同,该软件目前选取的图片格式为JPEG格式,使用LibJPEG开源库解析和生成jpeg图片
2、 展示图片:
由于图片大小和比例和显示区域大小和比例难以完全像同,所以为了正常显示图片,并且节省内存,使用插值法选取图片内容来显示。
3、 选取圆区域覆盖的图片:
设圆心为x0,y0,半径为r,某一点的坐标为(x,y),则可得如下式子:
(x-x0)2+(y-y0)2 <= r2时, 该坐标为(x,y)是圆内的点
4、 模糊图片像素
模糊的算法有很多,并且不同算法有特定的应用场合,根据应用功能:选取了均值模糊和高斯模糊算法,均值模糊实现简单,但模糊后的图片连续性不强,显示效果不好,所以选取了高斯模糊算法
程序简略流程图
各模块(类)的功能及程序说明;
选择图片部分,调用手机其他程序以获取图片路径:
Uri originalUri = data.getData();
String[] proj = {MediaStore.Images.Media.DATA };
// 好像是android多媒体数据库的封装接口,具体的看Android文档
Cursorcursor = managedQuery(originalUri, proj, null, null,
null);
// 按我个人理解 这个是获得用户选择的图片的索引值
int column_index =cursor
.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
// 将光标移至开头 ,这个很重要,不小心很容易引起越界
cursor.moveToFirst();
// 最后根据索引值获取图片路径
String path =cursor.getString(column_index);
通过后缀名,初步判断是否是JPEG图片,如果后缀为.jpg则打开模糊设置界面:
if (path != null && path.endsWith(".jpg")) {
ImageShare.imgPath= path;
startActivity= CIRCLE_BLUZ;
startActivity(intent);
} else {
startActivity= 0;
ToastUtil.show(this,"请选择jpeg图片");
}
本地方法:
//展示原始图片,并获取生成缩略图
publicnative void showImg(Surface s, String imgPath);
//展示原图缩略图
public native void showCurImg(Surface s);
//模糊原始图片,并保存
public native void circleBluz(StringsavePath, int x, int y, int radius,
float sigma);
//展示模糊效果,主要是处理缩略图,并显示
public native void circleBluzShow(Surfaces, int x, int y, int radius,
float sigma);
//销毁所有本地数据
public native void destroyNativeAll();
关键模块说明
选取圆形区域,为了提高效率进行了优化
第一步:在圆的四周画一个半径为2r的正方形,正方形的面积为4r2,
第二步:在圆内画一个半径为√ ̄2的正方形,面积为2r2
第三步:判断在第一步的正方形中又不在第二步的正方形中的像素是否在圆内
优化效果为:减少了一半的像素(4r2,- 2r2)进行是否在圆内的判断
int powr2 = r * r;
//待检区域
int checkl, checkt, checkr, checkb;
checkl = x0 - r > 0 ? x0 - r : 0;
checkt = y0 - r > 0 ? y0 - r : 0;
checkr = x0 + r >= width ? width : x0+ r;
checkb = y0 + r >= height ? height :y0 + r;
//免检区域
int notcl, notct, notcr, notcb;
//sqrt(2)/2
float sqrt2half = 0.7071;
int nr = sqrt2half * r;
notcl = x0 - nr > 0 ? x0 - nr : 0;
notct = y0 - nr > 0 ? y0 - nr : 0;
notcr = x0 + nr >= width ? width - 1 :x0 + nr;
notcb = y0 + nr >= height ? height - 1: y0 + nr;
int kcenter = ksize / 2;
int i = 0, j = 0;
unsigned long sum;
//x方向一维高斯模糊
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++){
if (y >= checkt&& y <= checkb && x >= checkl && x <= checkr){
//判断是否在免检区域内
if (y >= notct&& y <= notcb && x >= notcl && x <= notcr) {
} else {
//判断是否在圆内
if(pow2minus(y, y0) + pow2minus(x, x0) <= powr2) {
//在圆内
} else { //不在圆内
//进行模糊处理,省略
}
}
} else {//一定不在园内的像素
//进行模糊处理,省略
}
}
}
}
}
接下来的是最重要的模糊模块:
模糊算法有很多 种,有最简单的均值模糊,这种模糊对细节的保留不够,图片模糊后显得很僵硬,不美观,不符合要求,就直接舍弃了,接下来选择了高斯模糊,这种算法的思想是离模糊中心像素越近的元素对该模糊后的像素影响越大,所以应占据更大的比重,越远的像素占据的比重越小
请先浏览:http://blog.csdn.net/zddblog/article/details/7450033说得很详细
这里主要 使用了以上博客的算法,并作了优化。用σ表示模糊程度,在大概3σ距离之外的像素都可以看作不起作用,这些像素的计算也就可以忽略。通常,图像处理程序只需要计算(6σ+1)*(6σ+1)的矩阵就可以保证相关像素影响。如下图是σ=0.3时获得的高斯模板矩阵,用这个矩阵来乘以中间像素周围的像素就可以获得中间像素的实际值。(如计算模糊后的像素(i,j),则其值为(i-1,j-1)*1.47169e -005+(i-1,j)*0.00380683+....+(i+1,j+1)*1.47169e -005)
//获得高斯核矩阵
void GaussianSmooth2D(double sigma, double ** & kernel, int& ksize) {
//确保sigma为正数
sigma = sigma > 0 ? sigma : 0;
//高斯核矩阵的大小为(6*sigma+1)*(6*sigma+1)
ksize = ceil(sigma * 3) * 2 + 1;
//计算高斯核矩阵
//double *kernel = new double[ksize*ksize];
kernel = (double **) malloc(sizeof(double*) * ksize);
for (int i = 0; i < ksize; i++) {
kernel[i] = (double *) malloc(sizeof(double) * ksize);
memset(kernel[i], 0, ksize);
}
double scale = -0.5 / (sigma * sigma); //-1/2sigma的平方
const double PI = 3.141592653;
double cons = -scale / PI;
double sum = 0.0;
for (int i = 0; i < ksize; i++) {
for (int j = 0; j < ksize; j++) {
int x = i - (ksize - 1) / 2;
int y = j - (ksize - 1) / 2;
kernel[i][j] = cons * exp(scale * (x * x + y * y));
sum += kernel[i][j];
}
}
//归一化
for (int i = 0; i < ksize; i++) {
for (int j = 0; j < ksize; j++) {
kernel[i][j] /= sum;
}
}
}
接下来将上面两处代数合并使用,对圆外的像素进行模糊
//输入两个图片模板的模糊函数
//圆型模糊图片
int circleFuzzyDouble(unsigned char ** img, unsigned char ** imgtmp, int width,
int height, int component, int x0, int y0, int r) {
LOGI("circleFuzzy ");
//高斯核矩阵
double ** kernel;
double sigma = 10.0;
int ksize;
GaussianSmooth2D(sigma, kernel, ksize);
LOGI("获得高斯矩阵 ");
//3处理函数
int powr2 = r * r;
//待检区域
int checkl, checkt, checkr, checkb;
checkl = x0 - r > 0 ? x0 - r : 0;
checkt = y0 - r > 0 ? y0 - r : 0;
checkr = x0 + r >= width ? width : x0 + r;
checkb = y0 + r >= height ? height : y0 + r;
//免检区域
int notcl, notct, notcr, notcb;
//sqrt(2)/2
float sqrt2half = 0.7071;
int nr = sqrt2half * r;
notcl = x0 - nr > 0 ? x0 - nr : 0;
notct = y0 - nr > 0 ? y0 - nr : 0;
notcr = x0 + nr >= width ? width - 1 : x0 + nr;
notcb = y0 + nr >= height ? height - 1 : y0 + nr;
//i控制行 不对边缘进行处理
for (int i = ksize; i < height - ksize; i++) {
//j控制列 不对边缘进行处理
for (int j = ksize; j < width - ksize; j++) {
//判断是否在待检查区域内
if (i >= checkt && i <= checkb && j >= checkl && j <= checkr) {
//判断是否在免检区域内
if (i >= notct && i <= notcb && j >= notcl && j <= notcr) {
} else {
//判断是否在圆内
if (pow2minus(i, y0) + pow2minus(j, x0) <= powr2) {
} else { //不在圆内 用高斯核矩阵进行模糊
double middle[3] = { 0, 0, 0 };
int mkl = j - ksize / 2;
int mkt = i - ksize / 2;
for (int k = 0; k < ksize; k++) {
for (int l = 0; l < ksize; l++) {
middle[0] += imgtmp[mkt + k][3 * (mkl + l)]
* kernel[k][l];
middle[1] += imgtmp[mkt + k][3 * (mkl + l) + 1]
* kernel[k][l];
middle[2] += imgtmp[mkt + k][3 * (mkl + l) + 2]
* kernel[k][l];
}
}
img[i][3 * j] = (unsigned char) middle[0];
img[i][3 * j + 1] = (unsigned char) middle[1];
img[i][3 * j + 2] = (unsigned char) middle[2];
}
}
} else { //绝对不在圆内,对其进行模糊处理
double middle[3] = { 0, 0, 0 };
int mkl = j - ksize / 2;
int mkt = i - ksize / 2;
for (int k = 0; k < ksize; k++) {
for (int l = 0; l < ksize; l++) {
middle[0] += imgtmp[mkt + k][3 * (mkl + l)]
* kernel[k][l];
middle[1] += imgtmp[mkt + k][3 * (mkl + l) + 1]
* kernel[k][l];
middle[2] += imgtmp[mkt + k][3 * (mkl + l) + 2]
* kernel[k][l];
}
}
img[i][3 * j] = (unsigned char) middle[0];
img[i][3 * j + 1] = (unsigned char) middle[1];
img[i][3 * j + 2] = (unsigned char) middle[2];
}
}
}
LOGI("circleFuzzy end ");
return 0;
}
以上代码是没有进行边缘的像素处理的,可以把四周的像素也进行进行部分高斯核矩阵的乘法运算,但必然有部分矩阵的元素没有对应的像素进行乘法,这时,必须对使用高斯模糊矩阵元素进行归一化,否则元素明显值变小,此处代码大家自己实现吧!
这样就能初步实现模糊的效果了,休息一下!!看下实现的效果吧
点击√显示效果
但是上面的算法边缘处理非常麻烦,运行耗时也非常大,为了进一步提升速度,可以将上面的二维高斯函数,分离成两次一维的高斯运算,一次对X方向上,另一次对Y方向上,上面的博客说得比较清楚了,进行这种变换,速度变为原来的2倍以上,且不用受边缘处理的烦恼。
//浮点型的一维高斯核矩阵
<span style="white-space:pre"> </span>double * kdouble = (double *) malloc(sizeof(double) * ksize);
double scale = -0.5 / (sigma * sigma);
const double PI = 3.141592653;
double cons = 1 / sqrt(-scale / PI);
double sum = 0;
int kcenter = ksize / 2;
int i = 0, j = 0;
for (i = 0; i < ksize; i++) {
int x = i - kcenter;
*(kdouble + i) = cons * exp(x * x * scale); //一维高斯函数
sum += *(kdouble + i);
}
//归一化,确保高斯权值在[0,1]之间
for (i = 0; i < ksize; i++) {
*(kdouble + i) /= sum;
}
接下来,我们在考虑运行环境,我们的应用运行在手机上,手机的处理器进行浮点运算的能力很弱,特别是该算法多次使用了double型变量,并且每个圆外像素均需要次数较大的浮点运算,如果我们能将浮点运算转化为整形运算,那么速度将大幅提升,而这是可以做到,并且比较简单:可以用先把double型的kernel放大一定倍数,并保存为unsigned long型,以后就和原来运算一般,最后将求得的结果在缩小一定倍数即可。
//获得一维unsigned long高斯 ,放大0xffffff
void GaussianKerneluLong(unsigned long * & kernel, int sigma, int& ksize) {
sigma = sigma > 0 ? sigma : -sigma;
//高斯核矩阵的大小为(6*sigma+1)*(6*sigma+1)
//ksize为奇数
unsigned long ratio = 0xffffff;
ksize = ceil(sigma * 3) * 2 + 1;
//浮点型的一维高斯核矩阵
double * kdouble = (double *) malloc(sizeof(double) * ksize);
//计算一维高斯核
kernel = (unsigned long *) malloc(sizeof(unsigned long) * ksize);
double scale = -0.5 / (sigma * sigma);
const double PI = 3.141592653;
double cons = 1 / sqrt(-scale / PI);
double sum = 0;
int kcenter = ksize / 2;
int i = 0, j = 0;
for (i = 0; i < ksize; i++) {
int x = i - kcenter;
*(kdouble + i) = cons * exp(x * x * scale); //一维高斯函数
sum += *(kdouble + i);
}
//归一化,确保高斯权值在[0,1]之间
for (i = 0; i < ksize; i++) {
*(kdouble + i) /= sum;
}
//将double型矩阵转化为long型(放大ratio倍)
for (i = 0; i < ksize; i++) {
kernel[i] = kdouble[i] * ratio;
}
//释放double型核矩阵
free(kdouble);
}
如此一来,优化效果明显,速度提升为原来的8~9倍
接下来继续优化的方向是使用Neon和多线程
使用多线程优化,可以将待模糊矩阵,分为手机处理器的核数个面积相等的矩阵,在进行模糊处理,速度能够提升约等于核心数倍
该部分优化将在后续更新,也欢迎大家给出更好的意见~ ~
感谢大家抽空看完这边博客,如果觉得对您有一些帮助,或者有一些疑问,可以在下方留言,欢迎大家交流讨论。