
简介这是基于Android Studio开发的花卉识别系统完整源码项目面向有一定Android基础或对移动端图像识别感兴趣的开发者也可作为课程设计与毕业设计选题参考。项目采用Java实现工程结构规范涵盖AndroidManifest配置、XML布局、图片资源、核心Java逻辑及Gradle构建配置压缩包仅6.84MB共63个文件其中包含6个Java源码、17个XML文件、15张PNG图片以及Gradle脚本、依赖JAR、properties配置和README说明从工程搭建到识别流程都清晰可循。系统针对图像处理算法与用户交互界面进行了优化能够在移动设备上较流畅地完成花卉图像识别任务体现了移动计算与植物学领域结合的典型应用。目前已有49人学习下载对初学者来说既可借助完整源码梳理Android应用的图像识别实现思路也可直接导入Android Studio进行二次开发与功能拓展压缩包内的备份文件还保留了配置快照便于对比与纠错。资源来自网络分享仅用于学习交流。1. 移动端识别一朵花先从工程结构判断识别方案拿到一套“基于 Android Studio 的花卉识别系统源码”我先看它的libs目录而不是 Activity。源码里放着android-smart-image-view-1.0.0.jar说明它没有引入 TensorFlow Lite 或 MNN 这类深度学习推理库识别方案大概率是传统图像特征颜色直方图、边缘统计或者感知哈希。实际读下来主流程是拍照或选图、压缩、取像素、与内置样本比对、返回最接近的花名。这种方式的好处是 APK 小、逻辑透明、代码容易读懂坏处是识别率受光照和背景干扰影响大。对打算研究 Android 图像处理的开发者来说它比调用云 API 更接近原理也方便改造成自己的离线识别工具。2. Gradle 配置与源码目录让 Android Studio 工程先跑起来2.1 从 build.gradle 看依赖与版本策略用 Android Studio 打开这个工程时第一件触发的事就是 Gradle Sync。项目里gradle/wrapper目录保存着 Wrapper 版本而build.gradle决定编译依赖。以同类型项目的app/build.gradle为例结构大致是下面这个样子代码里的版本号只作讲解用要以你手里源码的真实值为准// app/build.gradle 典型配置 apply plugin: com.android.application android { compileSdkVersion 28 defaultConfig { applicationId com.example.flower minSdkVersion 19 targetSdkVersion 28 versionCode 1 versionName 1.0 } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile(proguard-android.txt), proguard-rules.pro } } } dependencies { implementation fileTree(dir: libs, include: [*.jar]) implementation com.android.support:appcompat-v7:28.0.0 }这里的compileSdkVersion是编译时使用的 API Level它不需要手机系统达到这个数值只是告诉编译器可以调用哪些接口。minSdkVersion 19表示最低支持 Android 4.4targetSdkVersion影响系统兼容模式比如运行时权限策略。implementation fileTree会把libs目录下所有.jar文件加入编译路径源码里那个android-smart-image-view-1.0.0.jar就是这样被引入的。Gradle 版本是经常出问题的环节。打开工程前先检查gradle-wrapper.properties里的distributionUrl是否和当前 Android Studio 内置版本接近。老项目用 Gradle 4.x新 Studio 自带 8.x直接 Sync 大概率报错。常见做法是改distributionUrl同时把项目根目录build.gradle里的com.android.tools.build:gradle版本同步调整。对于单模块项目升级成本并不高。2.2 app/src/main 下的职责划分梳理源码时我习惯先列一个目录映射表知道每个目录负责什么再读代码。这个项目的app/src/main下大概包含这些内容路径/文件职责java/com/example/flower/MainActivity.java识别入口处理拍照、相册选择、结果展示java/com/example/flower/ImageHelper.java图像缩放、像素读取、颜色直方图计算java/com/example/flower/FlowerMatcher.java保存样本特征执行距离比对并返回花名res/layout/activity_main.xml布局预览图、识别按钮、结果文本res/drawable/默认图、按钮背景等资源libs/android-smart-image-view-1.0.0.jar第三方图片异步加载库可以看到识别逻辑被拆成了几个类而不是全堆在 Activity 里。这个分层带来的好处是以后想把颜色直方图替换成感知哈希只需要改ImageHelper和FlowerMatcher。MainActivity只负责获取图像和展示结果不用关心特征怎么算。源码根目录还有几个容易让人误会的文件。settings.gradle、.gitignore、gradlew是工程骨架必须保留带有.zbak后缀的比如gradle-wrapper.properties.zbak、settings.gradle.zbak是别人还原环境时留下的备份。release/output.json只是构建输出记录不影响运行。导入工程时如果遇到“SDK location not found”或“Failed to find target with hash string”打开 Android SDK Manager 安装对应 Platform或在local.properties里指定 SDK 路径。2.3 老工程遇到 AGP 版本不兼容怎么办检索热词里有一条 “android studio build 出现tag number over 30 is not supported”这是老工程配新 AGP 的典型问题。低版本图片库在资源 ID 上用了高位 tag新 AAPT 会限制资源 tag 数。解决路线有两个升级第三方库或关闭 Android Gradle Plugin 的严格资源检查。对于这个花卉识别项目只用了一个较老的 SmartImageView它没有复杂资源引用一般不会触发该问题。万一遇到优先升级 SmartImageView 或换用 Glide不要依赖已被新版本移除的android.enableAapt2开关。注意Android Gradle Plugin 7.0 以后移除了很多旧版开关排错时应先看当前 AGP 版本对应的官方迁移说明再决定改代码还是改配置。3. 图像识别主链路拍照、缩放、直方图与相似度匹配3.1 识别入口与图像获取花卉识别从一张照片开始。MainActivity里的入口通常是点击“选择照片”按钮跳转到系统相册或相机返回的数据在onActivityResult里接收。核心代码如下private static final int REQUEST_PICK_IMAGE 100; Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode REQUEST_PICK_IMAGE resultCode RESULT_OK data ! null) { Uri imageUri data.getData(); try { Bitmap original MediaStore.Images.Media.getBitmap( getContentResolver(), imageUri); String result recognize(original); resultView.setText(识别结果 result); } catch (IOException e) { Log.e(TAG, 读取图片失败, e); } } }这段代码的问题很典型MediaStore.Images.Media.getBitmap()会直接解码原图。现代手机一张照片少说 3000x4000 像素ARGB_8888 格式下内存占用接近 48MB很可能触发 OOM。所以拿到Bitmap后不能直接做特征提取必须先进压缩流程。recognize方法内部会调用图像缩放和特征提取我一般会把这段逻辑封装成单独方法方便放到异步线程执行。3.2 图像压缩与 Bitmap 内存管理压缩的目的不是减小文件体积而是减少参与计算的像素数量。颜色直方图只需要足够的颜色分布信息3000 像素宽的照片和 256 像素宽的照片在统计结果上没有本质差异但计算量差距是上百倍。推荐等比缩放把最长边限制到 256 或 512 像素private Bitmap scaleBitmap(Bitmap src, int maxSize) { int w src.getWidth(); int h src.getHeight(); float ratio Math.min((float) maxSize / w, (float) maxSize / h); if (ratio 1f) { w (int) (w * ratio); h (int) (h * ratio); src Bitmap.createScaledBitmap(src, w, h, true); } return src; }ratio取maxSize / w和maxSize / h的较小值保证缩放后图像不会变形也不会有一侧超出限制。如果原图已经小于maxSizeratio大于等于 1此时不缩放。注意createScaledBitmap()即使缩放比例相同也可能返回新对象如果原图不再使用最好在原图使用完后调用recycle()提前释放。Android 8.0 以后系统会自动管理一部分原生内存recycle()主要用来主动释放不是必须。处理完缩略图后用bitmap.getPixels()把位图转换成int[]数组方便逐像素读取 RGB。这里stride参数直接传宽度即可避免某些设备上出现像素排列偏移。3.3 颜色直方图特征提取与相似度计算这个项目适合采用颜色直方图作为特征。直方图把图像的颜色分布压缩成一组数字忽略空间位置因此对花瓣旋转、小范围裁剪不敏感。实现时先统计 RGB 三个通道的分布再拼成一个一维数组public int[] extractHistogram(Bitmap bitmap, int bins) { int[] histogram new int[bins * 3]; int w bitmap.getWidth(); int h bitmap.getHeight(); int[] pixels new int[w * h]; bitmap.getPixels(pixels, 0, w, 0, 0, w, h); for (int i 0; i pixels.length; i) { int pixel pixels[i]; int r (pixel 16) 0xFF; int g (pixel 8) 0xFF; int b pixel 0xFF; histogram[r * bins / 256]; histogram[bins (g * bins / 256)]; histogram[bins * 2 (b * bins / 256)]; } return histogram; }bins表示每个颜色通道分成多少桶常见值为 8 或 16。bins16时直方图数组长度是 48信息量适中bins太小会丢失区分度太大会让距离值对噪声敏感。为了减少光照影响后续可以转成 HSV 色域再提取 H 通道直方图但这会增加代码复杂度。入门版里 RGB 直方图已经能处理背景干净的单朵花照片。提取完特征下一步是距离计算。常见做法是欧氏距离public double euclideanDistance(int[] a, int[] b) { double sum 0; for (int i 0; i a.length; i) { double diff a[i] - b[i]; sum diff * diff; } return Math.sqrt(sum); }直方图是离散计数直接相减没有归一化距离会随像素总数变化。因为所有待识别图像都缩放到接近相同的尺寸像素总数差异不大不归一化也能得到相对稳定的排序。如果你把maxSize改成可配置参数建议对直方图做归一化即每个桶值除以总像素数让距离比较不受图片尺寸影响。匹配时遍历所有样本选出距离最小的一项public String match(int[] feature) { String bestName 未知; double bestDistance Double.MAX_VALUE; for (FlowerSample sample : sampleList) { double d euclideanDistance(feature, sample.feature); if (d bestDistance) { bestDistance d; bestName sample.name; } } return bestName; }这里的FlowerSample对象在 App 启动时预加载内部保存花名和提前计算好的特征数组。为了调试可以把bestDistance也返回用来判断当前识别结果是否可信。特征提取的关键参数可以根据实际场景调整参数推荐值影响maxSize256越大特征越精细计算越慢bins16直方图桶数影响区分度缩放比例保持原始宽高比避免花形扭曲4. 界面交互与图片展示异步任务和 SmartImageView 的配合4.1 用 AsyncTask 把识别放到后台线程识别流程包含 Bitmap 解码、缩放、遍历样本这些操作在低端机型上耗时几百毫秒放在主线程会造成卡顿和 ANR。最传统的写法是 AsyncTask虽然已被标记为 deprecated但读老源码时你大概率会遇到它理解它依然有价值private class RecognizeTask extends AsyncTaskBitmap, Void, RecognitionResult { Override protected RecognitionResult doInBackground(Bitmap... params) { Bitmap original params[0]; Bitmap scaled scaleBitmap(original, 256); int[] feature extractHistogram(scaled, 16); return matcher.matchWithDistance(feature); } Override protected void onPreExecute() { progressBar.setVisibility(View.VISIBLE); } Override protected void onPostExecute(RecognitionResult result) { progressBar.setVisibility(View.GONE); resultView.setText(result.name 距离: result.distance); } }doInBackground里不能直接更新 UIonPostExecute会被系统切回主线程所以只在这里操作TextView或ListView。这套机制适合短耗时任务但 Activity 重建后AsyncTask 持有的旧 Activity 引用会泄漏。现在的替代方案是ViewModel LiveData或者在onStop里取消任务。如果只是学原理理解“后台计算、主线程显示”的分工就够了。4.2 用 SmartImageView 加载图片并复用缓存libs/android-smart-image-view-1.0.0.jar是 SmartImageView 库用法比 Glide 简单适合这个项目的轻量需求。布局里直接声明com.loopj.android.image.SmartImageView android:idid/smartImage android:layout_widthmatch_parent android:layout_height240dp android:scaleTypecenterCrop /在 Activity 里加载本地照片用setImageURI加载样本图用setImageUrlSmartImageView imageView findViewById(R.id.smartImage); imageView.setImageURI(imageUri); // 加载 assets 下的样本图 imageView.setImageUrl(file:///android_asset/sample_rose.jpg, R.drawable.ic_loading, R.drawable.ic_error);SmartImageView 内部有一套简单的异步加载和缓存逻辑图片会先在非 UI 线程解码再回到主线程设置到ImageView。需要注意当列表或界面复用ImageView时旧图片可能在新图加载完成前被显示造成闪烁。解决办法是加载前调用imageView.setImageResource(R.drawable.ic_loading)清掉旧图或者在切换时用 tag 做校验。4.3 运行时的几个高频问题现象可能原因排查/解决选图后闪退大 Bitmap 导致 OOM在getBitmap后立即缩放不要持有原始引用识别结果总是“未知”样本库没有这种花或特征差异过大打印bestDistance调大maxSize重新统计按钮点击几次就卡顿多个 AsyncTask 并发识别时置灰按钮或使用串行线程池SmartImageView 加载慢没有触发异步加载或缓存失效确认 jar 包已打入 APKlibs目录被 build.gradle 正确引用遇到闪退先看 Logcat 中的OutOfMemoryError这是花卉识别项目最常见的崩溃。Android Studio Profiler 可以观察内存波动如果看到锯齿状上升并跌回基线说明缩放逻辑起到作用如果持续上升不回落就要检查哪里把 Bitmap 引用长期保存了。4.4 结果页的距离转置信度识别结果不应该只显示花名建议把匹配距离一起显示出来。距离越小表示和样本越接近但不同图片尺寸会影响距离绝对值所以我会做一个简单转换double confidence 1.0 - Math.min(bestDistance / (feature.length * 255.0), 1.0);feature.length是直方图长度feature.length * 255是理论最大距离的粗略估算这样得到的confidence被限制在 0 到 1 之间。0.7以上可以认为是可靠结果0.5到0.7显示“有一定可能”小于0.5则提示“请重新拍摄正对花朵的照片”。这个阈值对不同样本库需要重新标定因为颜色直方图的距离分布高度依赖样本差异。5. 把固定样本改成 assets 资源扩展花卉识别库的落地方案源码自带的样本可能只有十几种花特征在代码里硬编码想加一种花就要重新编译。更可维护的做法是让 App 运行时从assets目录读取样本图片和名称。先在assets下创建flowers目录放样本图配套一个flowers.csv玫瑰,flowers/rose.jpg 菊花,flowers/chrysanthemum.jpg 郁金香,flowers/tulip.jpg启动时解析 CSV 并生成特征库ListFlowerSample samples new ArrayList(); BufferedReader reader new BufferedReader( new InputStreamReader(getAssets().open(flowers.csv))); String line; while ((line reader.readLine()) ! null) { String[] parts line.split(,); InputStream is getAssets().open(parts[1]); Bitmap bmp BitmapFactory.decodeStream(is); is.close(); Bitmap scaled scaleBitmap(bmp, 256); samples.add(new FlowerSample(parts[0], extractHistogram(scaled, 16))); }这种方式把“训练数据”变成了纯资源文件以后扩充花卉种类只需要向assets/flowers里增加图片再在flowers.csv里补一行。注意第一次启动时预加载所有图片并计算直方图几十张样本的耗时在几百毫秒内放在启动页的异步任务里比较合适。如果想把启动速度再压一压可以把计算好的特征序列化写入本地文件第二次启动直接读取数组跳过 Bitmap 解码和直方图统计。进一步优化可以在 CSV 里为同一种花配置多张不同光照、不同背景的样本。匹配时取多张样本的最小距离能明显提升稳定性。这套“资源文件驱动识别库”的思路也适用于货架商品检测、停车位状态判断等场景核心是把业务数据和逻辑代码解耦。最后一个小技巧每次新增样本后在FlowerMatcher里打印一份“不同花种平均距离”的报告用来检验新图片有没有和既有品种混淆如果两个花种的距离均值相差低于阈值就说明样本选得太像了换一张拍摄角度差异更大的图再试。本文还有配套的精品资源点击获取