☰
Android美颜相机毕业设计源码解析:从相机预览到磨皮滤镜贴纸全流程
2026/10/27 20:39:16 网站建设 项目流程

简介:这份资源是华南农业大学毕业设计项目,用Java实现一个类似美颜相机(美图秀秀)的Android应用,面向计算机相关专业做毕业设计或课程设计的学生,也适合想练手Android图像处理的开发者参考。压缩包共约2000个文件,以1216个xml布局与资源文件、378个json配置、352个java源码为主,另有少量sh脚本、properties配置和c语言文件,整体约19.43MB,目录结构完整,便于按模块查阅。项目围绕相机预览、滤镜处理与图像美化展开,从内容预览可见GPUImage滤镜框架、YUV解码等实现痕迹,能帮助读者理解美颜类App的相机采集、滤镜链与渲染流程。已有118人学习,可作为毕设选题的完整参考方案,用于对照源码梳理功能模块、学习滤镜与图像处理思路,并在此基础上做二次开发或答辩准备。

1. 从一份毕业设计源码说起:这个 Android 美颜相机到底能跑出什么

第一次拿到这份源码时,我其实没抱太大期待——毕业设计类的 Android 项目,十个里有八个是「能点开、能拍照、滤镜一贴就完事」的壳子。但把这份类似美颜相机(美图秀秀那类应用)的工程解压、导入、跑起来之后,我发现它的完成度比想象中高:相机预览、拍照、本地相册选图、磨皮美白、滤镜叠加、贴纸、保存到相册,这条主链路是通的,而且代码分层还算清楚,不是所有逻辑都堆在一个 Activity 里。对正在找 Java 毕业设计、课程设计参考的开发者来说,它的价值不在于算法多先进,而在于「一个完整的图像处理 App 该有哪些模块、这些模块怎么串起来」这件事被真实地写了一遍。下面我就按「它是什么 → 怎么跑起来 → 核心代码怎么写的 → 怎么改」的顺序,把这份工程拆给你看。

2. 工程结构与技术选型:一个相机 App 的骨架长什么样

2.1 目录分层与模块职责

导入 Android Studio 之后,先别急着点运行,花五分钟把app/src/main/java下的包结构看一遍,你对这个项目的理解会快很多。这类美颜相机工程的包名通常按功能切分,而不是按类型(把所有 Activity 塞一个包)堆。典型的目录长这样:

com.example.beautycamera ├── ui/ 界面层:主界面、相机页、编辑页、相册页 │ ├── MainActivity.java │ ├── CameraActivity.java │ ├── EditActivity.java │ └── GalleryActivity.java ├── camera/ 相机封装:预览、对焦、拍照回调 │ └── CameraHelper.java ├── image/ 图像处理核心:磨皮、美白、滤镜、贴纸合成 │ ├── BeautyFilter.java │ ├── FilterFactory.java │ └── StickerRenderer.java ├── adapter/ 列表适配器:滤镜缩略图、贴纸列表、相册网格 ├── bean/ 数据模型:图片项、滤镜项、贴纸项 ├── utils/ 工具类:Bitmap 转换、文件存储、权限 │ ├── BitmapUtils.java │ ├── FileUtils.java │ └── PermissionUtils.java └── widget/ 自定义控件:圆形按钮、滑动调节条

这个分层的关键在于image/包和ui/包是解耦的:界面层只负责拿参数、调方法、显示结果,真正的像素运算全在image/里。你后面要改滤镜、加效果,基本只动image/,不用碰界面。这是判断一份毕设源码值不值得二次开发的第一条标准——如果磨皮代码直接写在onClick里,那改起来就是灾难。

2.2 技术栈与依赖选择

工程是纯 Java 写的,没有上 Kotlin,这对课程设计场景反而友好,大部分学校的教学语言还是 Java。相机部分用的是 Android 原生的CameraAPI(android.hardware.Camera),而不是 CameraX 或 Camera2。这一点要有心理准备:原生CameraAPI 在 Android 5.0 之后就被标记为 deprecated,但在毕业设计里它依然是最省事的选择,因为 Camera2 的回调链复杂到能把人劝退,CameraX 又需要额外引入 Jetpack 依赖。图像处理没有用 OpenGL ES 做 GPU 加速,而是走Bitmap+Canvas+ 像素数组的 CPU 路线,好处是代码直观、每行都能看懂,坏处是处理大图会卡,这个后面避坑章节会细说。

依赖方面,build.gradle里通常只有基础的 support/androidx 库,可能加一个图片加载库(Glide 或 Picasso)用于相册缩略图。没有引入第三方美颜 SDK,所有效果都是自己写的——这正是它作为学习素材的价值所在。

// app/build.gradle 里常见的依赖配置 dependencies { implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' // 图片加载,用于相册网格缩略图 implementation 'com.github.bumptech.glide:glide:4.15.1' // 可能有的图片裁剪库,视具体工程而定 implementation 'androidx.recyclerview:recyclerview:1.3.0' }

这里要提醒一句:compileSdkVersion和targetSdkVersion如果写的是很老的版本(比如 28 以下),在新版 Android Studio 上会提示升级,但别急着无脑升到 34,因为高版本对存储权限、后台相机、FileProvider的要求都变严了,升完可能直接崩。稳妥做法是先按原配置跑通,再逐步升。

2.3 权限与清单配置

相机 App 绕不开权限。AndroidManifest.xml里至少要声明相机、存储读写、录音(如果录像)这几项:

<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-feature android:name="android.hardware.camera" android:required="true" />

注意WRITE_EXTERNAL_STORAGE加了maxSdkVersion="28",这是 Android 10 分区存储之后的常见写法:29 及以上用MediaStore写相册,不再需要这个权限。如果你的工程里没有这行,在 Android 11 真机上保存图片会失败,这是新手最容易翻车的地方之一。运行时权限的申请逻辑一般在MainActivity或PermissionUtils里,用ActivityCompat.requestPermissions走一遍即可。

3. 跑起来:从导入到拍出第一张美颜图的完整步骤

3.1 环境准备与工程导入

先把环境对齐,不然报错会让你怀疑人生。推荐配置:Android Studio(近两年的版本都行)、JDK 8 或 11(看build.gradle里的sourceCompatibility)、一台 Android 7.0 以上的真机。为什么强调真机?因为模拟器的虚拟摄像头拍出来是测试图案,你根本看不出磨皮和滤镜的效果,而且部分模拟器对CameraAPI 支持不全,预览直接黑屏。

导入步骤:File → Open选中解压后的工程根目录(不是app目录),等 Gradle Sync 完成。如果卡在下载依赖,检查settings.gradle里的仓库地址是不是google()和mavenCentral(),老工程可能还写着jcenter(),而 jcenter 已经停服,需要手动替换。

// settings.gradle 里仓库配置的修正 dependencyResolutionManagement { repositories { google() mavenCentral() // 替换掉已停服的 jcenter() } }

Sync 成功后如果报Manifest merger failed,多半是minSdkVersion和某个库冲突,把minSdkVersion提到 21 以上通常能解决。

3.2 首次运行与相机预览验证

点运行之前,先确认CameraActivity是启动页,或者在MainActivity里点「拍照」能跳过去。装到真机后第一次打开会弹权限框,全部允许。如果预览画面是黑的,按这个顺序排查:一,权限是否真的授予了(去系统设置里看);二,SurfaceView或TextureView的surfaceCreated回调有没有触发(打日志);三,Camera.open()有没有抛异常(被其他 App 占用相机也会失败)。

预览正常之后,点拍照,看图片有没有落到EditActivity。这一步是整条链路的第一个关键节点,通了说明相机封装和页面跳转没问题。常见现象是拍完闪退,日志里是NullPointerException在Bitmap相关代码——大概率是拍照回调里data为 null(用takePicture的PictureCallback时,如果传了null给ShutterCallback或RawCallback,某些机型行为不一致),这个在避坑章节展开。

3.3 编辑页效果验证与保存

进入编辑页后,依次验证磨皮、美白、滤镜、贴纸四个功能。磨皮和美白一般用滑动条调强度,滤镜是底部缩略图列表,贴纸是叠加在图上可拖动。每调一次,观察画面是否实时刷新——如果卡顿超过一秒,说明是 CPU 逐像素处理大图,属于预期内的性能问题,不是 bug。

最后点保存,去系统相册看图片在不在。保存失败是最常见的「最后一公里」问题,原因通常是:Android 10 以上没走MediaStore、FileProvider没配、或者路径写死到了/sdcard/根目录。下面这段是兼容性较好的保存写法:

// 保存 Bitmap 到系统相册,兼容 Android 10 分区存储 public static void saveToGallery(Context context, Bitmap bitmap, String name) { ContentValues values = new ContentValues(); values.put(MediaStore.Images.Media.DISPLAY_NAME, name + ".jpg"); values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // Android 10+ 走 MediaStore,无需 WRITE 权限 values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES); Uri uri = context.getContentResolver() .insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); try (OutputStream os = context.getContentResolver().openOutputStream(uri)) { bitmap.compress(Bitmap.CompressFormat.JPEG, 95, os); } catch (IOException e) { e.printStackTrace(); } } else { // 低版本走传统文件写入 + 相册扫描 File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); File file = new File(dir, name + ".jpg"); FileUtils.writeBitmap(file, bitmap); MediaScannerConnection.scanFile(context, new String[]{file.getAbsolutePath()}, null, null); } }

参数说明:bitmap是编辑后的结果图,name建议用时间戳避免重名,RELATIVE_PATH指定存到 Pictures 目录。compress的质量参数 95 是经验值,再高文件大、再低画质肉眼可见下降。这段代码直接抄进FileUtils就能用,是这份工程里最值得复用的工具方法之一。

4. 核心模块拆解:磨皮、滤镜、贴纸三段代码怎么读

4.1 磨皮与美白:像素级处理的入门实现

美颜的核心在BeautyFilter类。毕业设计级别的磨皮一般不会上双边滤波或导向滤波,而是用「均值模糊 + 原图混合」的简化方案:先对图像做一次模糊,再按强度把模糊图和原图按比例叠加,皮肤区域就被柔化了。美白则是整体提亮 + 轻微降低饱和度。看这段典型实现:

// 简化版磨皮:均值模糊后与原图按强度混合 public static Bitmap smooth(Bitmap src, int radius, float strength) { int w = src.getWidth(), h = src.getHeight(); int[] pixels = new int[w * h]; src.getPixels(pixels, 0, w, 0, 0, w, h); int[] blurred = boxBlur(pixels, w, h, radius); // 均值模糊 int[] out = new int[pixels.length]; for (int i = 0; i < pixels.length; i++) { int a = (pixels[i] >> 24) & 0xff; int r = mix((pixels[i] >> 16) & 0xff, (blurred[i] >> 16) & 0xff, strength); int g = mix((pixels[i] >> 8) & 0xff, (blurred[i] >> 8) & 0xff, strength); int b = mix(pixels[i] & 0xff, blurred[i] & 0xff, strength); out[i] = (a << 24) | (r << 16) | (g << 8) | b; } return Bitmap.createBitmap(out, w, h, Bitmap.Config.ARGB_8888); } // 按强度混合两个通道值,strength 0~1 private static int mix(int orig, int blur, float strength) { return (int) (orig * (1 - strength) + blur * strength); }

逻辑说明:boxBlur是均值模糊,radius控制模糊半径,半径越大皮肤越「糊」;strength是滑动条映射的 0~1 强度,0 是原图、1 是全模糊。mix做线性插值,这是所有混合类效果的基础公式,记住它,后面滤镜叠加也是同一套思路。参数上,radius一般取 3~8,太大五官边缘会糊成一团;strength默认给 0.5 左右比较自然。美白就是在out基础上对 RGB 各加一个偏移量并 clamp 到 255,代码结构一样,不重复贴。

4.2 滤镜工厂:用颜色矩阵统一管理多种风格

滤镜如果每个都写一遍像素循环,代码会爆炸。这份工程用的是颜色矩阵(ColorMatrix)方案,一个矩阵描述一种色调变换,Canvas.drawBitmap时套Paint.setColorFilter即可,既简洁又能实时预览。FilterFactory里维护一个滤镜列表:

// 用 ColorMatrix 定义滤镜,一个矩阵一种风格 public class FilterFactory { public static final float[] NONE = { 1,0,0,0,0, 0,1,0,0,0, 0,0,1,0,0, 0,0,0,1,0 }; // 暖色:红通道增益、蓝通道衰减 public static final float[] WARM = { 1.1f,0,0,0,10, 0,1.0f,0,0,0, 0,0,0.9f,0,-10, 0,0,0,1,0 }; // 黑白:三通道等权 public static final float[] MONO = { 0.299f,0.587f,0.114f,0,0, 0.299f,0.587f,0.114f,0,0, 0.299f,0.587f,0.114f,0,0, 0,0,0,1,0 }; public static Bitmap apply(Bitmap src, float[] matrix) { Bitmap out = Bitmap.createBitmap(src.getWidth(), src.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(out); Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setColorFilter(new ColorMatrixColorFilter(matrix)); canvas.drawBitmap(src, 0, 0, paint); return out; } }

逻辑说明:颜色矩阵是 4x5 的浮点数组,前四列是 RGBA 各通道的权重,第五列是偏移量。WARM里红通道乘 1.1、蓝通道乘 0.9 并减 10,整体就偏暖;MONO用亮度公式把三通道拉平,得到黑白。apply里用Canvas画一遍就完成变换,比逐像素快得多,因为底层有硬件加速。要加新滤镜,只需在工厂里加一个矩阵常量,再在缩略图列表里挂上去,扩展成本极低——这就是「工厂 + 矩阵」设计的价值。

4.3 贴纸渲染与手势拖动

贴纸模块在StickerRenderer里,核心是把贴纸 Bitmap 按用户手势的位移和缩放画到主图上。编辑页一般用一个自定义View承载,onTouchEvent里处理ACTION_MOVE更新坐标,ACTION_POINTER_DOWN处理双指缩放。最终合成时,把主图和所有贴纸按顺序drawBitmap到同一张画布:

// 合成主图与贴纸,stickers 里存了每张贴纸的 bitmap、坐标、缩放 public static Bitmap compose(Bitmap base, List<StickerItem> stickers) { Bitmap out = base.copy(Bitmap.Config.ARGB_8888, true); Canvas canvas = new Canvas(out); for (StickerItem s : stickers) { canvas.save(); // 以贴纸中心为轴缩放,避免缩放时位置漂移 canvas.scale(s.scale, s.scale, s.x + s.bitmap.getWidth() / 2f, s.y + s.bitmap.getHeight() / 2f); canvas.drawBitmap(s.bitmap, s.x, s.y, null); canvas.restore(); } return out; }

逻辑说明:save/restore保证每张贴纸的变换互不影响;scale的轴心参数是关键,如果直接用(0,0)做轴心,贴纸一放大就往右下角跑,这是新手写贴纸功能最常见的 bug。StickerItem里存x、y、scale三个字段,手势回调里只改这三个值再invalidate()重绘,合成时一次性画出来。参数上,scale建议限制在 0.3~3.0,防止缩没或缩爆。

5. 避坑与排查:真机上最容易翻车的五个点

5.1 拍照后闪退,日志指向 Bitmap 为空

现象:点拍照,界面卡一下然后闪退,Logcat 里是NullPointerException在Bitmap.createBitmap或copy附近。原因:Camera.takePicture的PictureCallback在某些机型上data可能为 null,尤其是同时传了ShutterCallback和RawCallback时,部分厂商 ROM 行为不一致。解决:在回调里先判空,为空则回退到从SurfaceView抓帧,或者干脆只传JPEG回调、其余传 null,并在onPictureTaken里加if (data == null) return;兜底。

5.2 预览画面拉伸变形

现象:相机预览里人脸被拉长或压扁。原因:SurfaceView的尺寸和相机预览尺寸(Camera.Parameters.getPreviewSize())不匹配,系统按控件比例硬拉伸。解决:在surfaceChanged里根据控件宽高比,从getSupportedPreviewSizes()里挑一个最接近的比例设置给相机,再调整控件布局参数。这一步不做,画面永远歪。

5.3 保存图片成功但相册里看不到

现象:代码没报错,文件也写了,但系统相册刷不出来。原因:Android 10 以下写文件后没有通知媒体库扫描;Android 10 以上没走MediaStore而是直接写路径,被分区存储拦截。解决:低版本用MediaScannerConnection.scanFile主动扫描,高版本按第 3.3 节的MediaStore写法插入。这是「文件存在但用户以为没保存」的经典误会。

5.4 大图处理 OOM

现象:选一张 4000x3000 的照片进编辑页,直接OutOfMemoryError。原因:Bitmap按 ARGB_8888 算,一张 1200 万像素图占约 48MB,加上模糊副本、输出副本,瞬间破百兆,超过默认堆上限。解决:进编辑页前先BitmapFactory.Options.inSampleSize降采样到屏幕尺寸的 2 倍以内;处理完及时recycle()中间 Bitmap;AndroidManifest里加android:largeHeap="true"作为兜底,但别把它当主要手段。

5.5 滤镜切换卡顿、滑动条掉帧

现象:拖磨皮强度条时画面一顿一顿。原因:每次onProgressChanged都全图重算,主线程被占满。解决:滑动过程中只做低分辨率预览(把图缩到 1/4 处理再放大显示),松手(onStopTrackingTouch)时再对原图做一次高质量处理;或者把处理放到子线程,用Handler回主线程刷新。这是性能优化的第一课,也是答辩时最容易被问到的点。

6. 二次开发:把这份毕设改成能拿得出手的作品

改造之前先想清楚方向。这份工程的骨架是「相机 + 编辑 + 保存」,往上加东西有两条路:一条是加功能(人脸检测、贴纸跟随、美妆),一条是提性能(GPU 加速、异步处理)。我一般建议先做后者,因为性能是地基,功能是装修,地基不稳装修越多越容易塌。

具体到代码,最划算的一处改造是把BeautyFilter的 CPU 逐像素循环换成RenderScript或 OpenGL 着色器。以 RenderScript 为例,把boxBlur换成ScriptIntrinsicBlur,磨皮耗时能从几百毫秒降到几十毫秒,代码量还更少:

// 用 RenderScript 内置模糊替代手写均值模糊,性能提升明显 private static Bitmap rsBlur(Context ctx, Bitmap src, float radius) { Bitmap out = Bitmap.createBitmap(src.getWidth(), src.getHeight(), Bitmap.Config.ARGB_8888); RenderScript rs = RenderScript.create(ctx); ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); Allocation in = Allocation.createFromBitmap(rs, src); Allocation o = Allocation.createFromBitmap(rs, out); blur.setRadius(Math.min(radius, 25f)); // 上限 25 blur.setInput(in); blur.forEach(o); o.copyTo(out); rs.destroy(); return out; }

注意setRadius上限是 25,超过会抛异常;rs.destroy()必须调,否则内存泄漏。改完用同一张图对比处理耗时,用System.currentTimeMillis()前后夹一下就能验证,别凭感觉说「快了」。

第二条改造路线是加人脸检测,让磨皮只作用于皮肤区域。用 Android 自带的FaceDetector(虽然老但零依赖)拿到人脸矩形,把矩形外区域的strength设为 0,磨皮就只糊脸不糊背景,效果立刻上一个档次。验证方法很直接:找一张背景有文字的照片,改之前文字被糊掉,改之后文字清晰、脸还是柔的,就说明区域控制生效了。

第三条是工程化:把image/包抽成独立 module,写几个单元测试固定住mix和颜色矩阵的输出值,以后改滤镜就不怕改坏。这一步对毕设答辩加分不多,但对「这份代码能不能继续用」意义很大。

血泪经验是:我见过太多人一上来就换 CameraX、上 OpenGL,结果相机预览调不通、着色器编译报错,最后连原本能跑的功能都回不去了。从那以后我每次动这类图像工程,都强制先跑通原版、打一个 git tag,再动第一行代码,改一处验证一处。希望这份拆解能帮你少走几步弯路,把这份源码真正跑成自己的东西。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询