news 2026/9/26 2:37:57

TensorFlow CNN水果识别毕业设计源码:从环境搭建到模型评估全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TensorFlow CNN水果识别毕业设计源码:从环境搭建到模型评估全流程

简介:这份资源是面向计算机相关专业毕业设计学生与希望提升工程能力的开发者的一套TensorFlow卷积神经网络水果图像识别项目源码,难度定位中等,适合作为课程设计、期末项目或毕业设计参考。压缩包共1058个文件,约79.95MB,以872张jpg水果样本图片为主,另含模型训练产生的checkpoint、meta、index等权重文件,以及少量py源码、txt说明与zbak备份文件,覆盖数据预处理、网络架构设计、模型训练优化到预测部署的完整流程。项目代码采用模块化设计,注释详尽,结构遵循工程规范,便于理解算法逻辑并支持二次开发与功能扩展。文档包含环境配置说明与执行指南,可帮助读者快速部署运行、复现训练过程并掌握图像分类任务的实现细节。目前已有59人学习,适合需要完整深度学习项目实践参考的学习者。资源来源于网络分享,仅用于学习交流,请勿用于商业用途。

1. 从一份能跑通的 TensorFlow CNN 水果识别源码说起

很多计算机毕业设计的同学在选题阶段都会卡在同一个地方:想做深度学习方向,但不知道从哪里下手,网上找的源码要么跑不起来,要么环境依赖一堆报错,要么代码结构混乱到根本看不懂。这份基于 TensorFlow 的 CNN 水果图像识别系统,就是针对这个痛点整理出来的一套完整 Python 毕业设计源码。它的核心任务很明确:用卷积神经网络对水果图片做多分类识别,输入一张水果照片,输出它属于哪一类。整套代码基于 TensorFlow 和 Keras 构建,覆盖了数据预处理、模型搭建、训练、评估、预测的完整链路,适合正在做计算机毕业设计、想找一个能讲清楚原理又能实际跑通的深度学习项目的同学。如果你之前只跑过 MNIST 手写数字识别,想升级到一个有实际应用场景的项目,这份源码是一个比较合适的起点。

2. 环境搭建与数据准备:把 TensorFlow 和水果数据集跑起来

2.1 为什么选 TensorFlow 而不是 PyTorch

在动手之前,先把这个选型问题说清楚,因为毕业设计答辩时老师大概率会问。TensorFlow 和 PyTorch 目前是深度学习两大主流框架,2024 年以来 PyTorch 在学术圈的流行度确实更高,但 TensorFlow 在工程部署和毕业设计场景下仍有几个实际优势。第一,Keras 作为 TensorFlow 的高层 API,搭建 CNN 的代码量比 PyTorch 少很多,对于本科毕业设计的体量来说,代码可读性更好,答辩时也更容易讲清楚每一层在做什么。第二,TensorFlow 的 SavedModel 格式在导出和部署时比较省心,如果你后续想加一个 Flask 或 Django 的 Web 界面做演示,TensorFlow 的模型加载流程更直接。第三,网上 TensorFlow 的中文教程和毕业设计参考资料基数大,遇到问题更容易搜到解决方案。

当然,这不是说 PyTorch 不好。如果你后续想读研做科研,PyTorch 的生态更友好。但对于一个本科毕业设计项目,TensorFlow + Keras 的组合在开发效率和代码简洁度上更占优势。

2.2 环境安装的完整步骤

环境配置是新手翻车最多的地方。我一般建议用 Anaconda 来管理 Python 环境,避免和系统自带的 Python 版本冲突。以下是经过验证的安装流程:

# 创建独立的虚拟环境,Python 版本建议 3.8 到 3.10 conda create -n fruit_cnn python=3.9 # 激活环境 conda activate fruit_cnn # 安装 TensorFlow,CPU 版本足够毕业设计使用 pip install tensorflow==2.13.0 # 安装辅助库 pip install numpy matplotlib pillow scikit-learn flask

这里有几个参数需要说明。TensorFlow 版本选择 2.13.0 是因为这个版本在 Windows 和 Linux 上的兼容性都比较稳定,且自带 Keras 2.13,API 没有大的变动。Python 版本不要选 3.11 以上,部分 TensorFlow 版本对高版本 Python 支持不完善,容易出现安装成功但 import 报错的情况。如果你有 NVIDIA 显卡并且想用 GPU 加速训练,可以安装 tensorflow-gpu,但要注意 CUDA 和 cuDNN 的版本必须和 TensorFlow 版本严格对应,否则会出现找不到 GPU 的问题。对于水果识别这种规模的数据集,CPU 训练完全够用,一个 epoch 大概几十秒到几分钟,不建议在 GPU 环境上花太多时间折腾。

安装完成后,用以下代码验证环境是否正常:

import tensorflow as tf print(tf.__version__) print(tf.config.list_physical_devices('GPU'))

如果第一行能正常输出版本号,说明 TensorFlow 安装成功。第二行如果输出空列表,说明没有检测到 GPU,用 CPU 训练即可,不影响功能。

2.3 水果数据集的获取与目录结构

这份源码使用的数据集是常见的水果图像分类数据集,通常包含苹果、香蕉、橙子、葡萄、西瓜等类别,每类几百到上千张图片。数据集一般按以下目录结构组织:

fruits_dataset/ ├── train/ │ ├── apple/ │ ├── banana/ │ ├── orange/ │ └── ... ├── validation/ │ ├── apple/ │ ├── banana/ │ └── ... └── test/ ├── apple/ └── ...

如果你的数据集没有预先划分训练集和验证集,可以用以下脚本自动按 8:2 的比例拆分:

import os import shutil import random def split_dataset(source_dir, train_dir, val_dir, split_ratio=0.8): """ 将原始数据集按比例拆分为训练集和验证集 source_dir: 原始数据目录,每个子文件夹是一个类别 train_dir: 训练集输出目录 val_dir: 验证集输出目录 split_ratio: 训练集占比 """ if not os.path.exists(train_dir): os.makedirs(train_dir) if not os.path.exists(val_dir): os.makedirs(val_dir) for class_name in os.listdir(source_dir): class_path = os.path.join(source_dir, class_name) if not os.path.isdir(class_path): continue images = os.listdir(class_path) random.shuffle(images) split_point = int(len(images) * split_ratio) train_images = images[:split_point] val_images = images[split_point:] os.makedirs(os.path.join(train_dir, class_name), exist_ok=True) os.makedirs(os.path.join(val_dir, class_name), exist_ok=True) for img in train_images: shutil.copy(os.path.join(class_path, img), os.path.join(train_dir, class_name, img)) for img in val_images: shutil.copy(os.path.join(class_path, img), os.path.join(val_dir, class_name, img)) print("数据集拆分完成") split_dataset("fruits_dataset/raw", "fruits_dataset/train", "fruits_dataset/validation")

这段脚本的逻辑很直接:遍历每个类别文件夹,打乱图片顺序后按比例切分,分别复制到训练集和验证集目录。split_ratio参数控制训练集占比,一般设 0.8 即可。注意random.shuffle之前要确保random.seed固定,否则每次运行拆分结果不同,实验不可复现。常见做法是在脚本开头加一行random.seed(42)。

注意:数据集里如果有损坏的图片文件,训练时会在读取阶段报错。建议先用 PIL 批量检查一遍图片完整性,把打不开的文件提前清理掉。

3. CNN 模型搭建与训练:从卷积层到数据增强的完整实现

3.1 CNN 网络结构的设计思路

卷积神经网络的核心思想是通过卷积核在图像上滑动提取局部特征,再通过池化层降维,最后用全连接层做分类。对于水果识别这个任务,图像特征相对明确(颜色、形状、纹理),不需要特别深的网络。这份源码采用的是一种经典的四层卷积结构,每一层的设计都有明确目的。

第一层卷积用 32 个 3×3 的卷积核,主要捕捉边缘和颜色块等低级特征。第二层卷积用 64 个 3×3 卷积核,开始提取更复杂的纹理和形状组合。第三层和第四层分别用 128 个卷积核,进一步抽象出水果的判别性特征。每层卷积后面接一个最大池化层,把特征图尺寸逐步缩小,减少参数量。最后通过全局平均池化或 Flatten 展平,接入全连接层输出分类结果。

以下是模型定义的完整代码:

import tensorflow as tf from tensorflow.keras import layers, models def build_cnn_model(num_classes, input_shape=(224, 224, 3)): """ 构建 CNN 水果识别模型 num_classes: 水果类别数 input_shape: 输入图片尺寸 """ model = models.Sequential([ # 第一层卷积:提取低级特征 layers.Conv2D(32, (3, 3), activation='relu', padding='same', input_shape=input_shape), layers.MaxPooling2D((2, 2)), # 第二层卷积:提取纹理特征 layers.Conv2D(64, (3, 3), activation='relu', padding='same'), layers.MaxPooling2D((2, 2)), # 第三层卷积:提取形状特征 layers.Conv2D(128, (3, 3), activation='relu', padding='same'), layers.MaxPooling2D((2, 2)), # 第四层卷积:提取高层语义特征 layers.Conv2D(128, (3, 3), activation='relu', padding='same'), layers.MaxPooling2D((2, 2)), # 展平后接入全连接层 layers.Flatten(), layers.Dropout(0.5), layers.Dense(256, activation='relu'), layers.Dense(num_classes, activation='softmax') ]) return model model = build_cnn_model(num_classes=5) model.summary()

几个关键参数需要解释。padding='same'表示卷积后特征图尺寸不变,这样可以在不丢失边缘信息的前提下加深网络。Dropout(0.5)是防止过拟合的常用手段,在训练时随机丢弃一半的神经元连接,迫使网络学习更鲁棒的特征。最后一层用softmax激活函数,输出每个类别的概率分布,这是多分类任务的标准做法。model.summary()可以打印出每一层的输出尺寸和参数量,答辩时用来讲解网络结构非常直观。

3.2 数据增强与 ImageDataGenerator 的使用

水果数据集通常每类只有几百张图片,直接训练容易过拟合。数据增强通过对训练图片做随机旋转、平移、缩放、翻转等变换,在不增加实际图片数量的情况下扩充训练样本的多样性。Keras 提供了ImageDataGenerator来简化这个过程:

from tensorflow.keras.preprocessing.image import ImageDataGenerator # 训练集数据增强配置 train_datagen = ImageDataGenerator( rescale=1./255, # 像素值归一化到 0-1 rotation_range=30, # 随机旋转 ±30 度 width_shift_range=0.2, # 水平平移 ±20% height_shift_range=0.2, # 垂直平移 ±20% shear_range=0.2, # 剪切变换 zoom_range=0.2, # 随机缩放 horizontal_flip=True, # 水平翻转 fill_mode='nearest' # 填充方式 ) # 验证集只做归一化,不做增强 val_datagen = ImageDataGenerator(rescale=1./255) # 从目录加载数据 train_generator = train_datagen.flow_from_directory( 'fruits_dataset/train', target_size=(224, 224), batch_size=32, class_mode='categorical' ) val_generator = val_datagen.flow_from_directory( 'fruits_dataset/validation', target_size=(224, 224), batch_size=32, class_mode='categorical' )

rescale=1./255是把像素值从 0-255 映射到 0-1,这是图像任务的标准预处理步骤,不归一化会导致训练不稳定。rotation_range和zoom_range不宜设得太大,水果识别中旋转 30 度、缩放 20% 已经足够,设太大反而会让模型学到不真实的特征。flow_from_directory会自动根据子文件夹名称生成类别标签,所以目录结构必须严格按类别分文件夹。class_mode='categorical'表示输出 one-hot 编码的多分类标签,和模型最后一层的 softmax 对应。

3.3 模型编译、训练与回调配置

数据准备好之后,进入编译和训练阶段。编译时需要指定优化器、损失函数和评估指标:

from tensorflow.keras.optimizers import Adam from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping, ReduceLROnPlateau model.compile( optimizer=Adam(learning_rate=0.001), loss='categorical_crossentropy', metrics=['accuracy'] ) # 回调函数配置 callbacks = [ # 保存验证集上表现最好的模型 ModelCheckpoint('best_fruit_model.h5', monitor='val_accuracy', save_best_only=True, verbose=1), # 验证集损失不再下降时提前停止 EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True, verbose=1), # 学习率自动衰减 ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=5, min_lr=1e-6, verbose=1) ] # 开始训练 history = model.fit( train_generator, epochs=50, validation_data=val_generator, callbacks=callbacks )

Adam优化器的学习率设 0.001 是常规起点,如果训练损失震荡明显可以降到 0.0005。categorical_crossentropy是多分类任务的标准损失函数,和二分类的binary_crossentropy不要搞混。三个回调函数各有用途:ModelCheckpoint保证训练结束后拿到的是验证集上最好的模型而不是最后一个 epoch 的模型;EarlyStopping在验证损失连续 10 个 epoch 不下降时自动停止,避免浪费时间和过拟合;ReduceLROnPlateau在损失停滞时把学习率减半,帮助模型跳出局部最优。这三个回调组合使用是实践中比较稳妥的方案。

训练完成后,可以用以下代码绘制准确率和损失曲线,方便在毕业设计论文中展示:

import matplotlib.pyplot as plt fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4)) ax1.plot(history.history['accuracy'], label='Train Accuracy') ax1.plot(history.history['val_accuracy'], label='Val Accuracy') ax1.set_title('Accuracy Curve') ax1.legend() ax2.plot(history.history['loss'], label='Train Loss') ax2.plot(history.history['val_loss'], label='Val Loss') ax2.set_title('Loss Curve') ax2.legend() plt.savefig('training_curve.png') plt.show()

4. 模型评估与单张图片预测:验证准确率和推理接口

4.1 在测试集上评估模型表现

训练完成后不能只看训练曲线就下结论,需要在独立的测试集上评估模型的真实表现。以下代码加载保存的最优模型并在测试集上计算准确率:

from tensorflow.keras.models import load_model from tensorflow.keras.preprocessing.image import ImageDataGenerator # 加载训练好的模型 model = load_model('best_fruit_model.h5') # 测试集数据生成器 test_datagen = ImageDataGenerator(rescale=1./255) test_generator = test_datagen.flow_from_directory( 'fruits_dataset/test', target_size=(224, 224), batch_size=32, class_mode='categorical', shuffle=False ) # 评估 test_loss, test_acc = model.evaluate(test_generator) print(f"测试集准确率: {test_acc:.4f}") print(f"测试集损失: {test_loss:.4f}")

shuffle=False在测试集上很重要,因为后续如果要画混淆矩阵,需要预测结果和真实标签一一对应,打乱顺序会导致对应关系错位。测试集准确率一般会比验证集略低,如果低太多(超过 5 个百分点),说明模型对验证集过拟合了,需要检查数据划分是否合理。

4.2 单张图片预测与结果可视化

毕业设计答辩时,老师通常希望看到系统能对一张新图片给出预测结果。以下是一个完整的单张图片预测函数:

import numpy as np from tensorflow.keras.preprocessing import image def predict_fruit(img_path, model, class_indices): """ 对单张水果图片进行预测 img_path: 图片路径 model: 训练好的模型 class_indices: 类别索引字典 {'apple': 0, 'banana': 1, ...} """ # 加载并预处理图片 img = image.load_img(img_path, target_size=(224, 224)) img_array = image.img_to_array(img) img_array = img_array / 255.0 img_array = np.expand_dims(img_array, axis=0) # 预测 predictions = model.predict(img_array) predicted_class_idx = np.argmax(predictions[0]) confidence = predictions[0][predicted_class_idx] # 反查类别名称 idx_to_class = {v: k for k, v in class_indices.items()} predicted_class = idx_to_class[predicted_class_idx] print(f"预测类别: {predicted_class}") print(f"置信度: {confidence:.4f}") return predicted_class, confidence # 使用示例 class_indices = train_generator.class_indices predict_fruit('test_image.jpg', model, class_indices)

np.expand_dims(img_array, axis=0)这一步是把单张图片的维度从 (224, 224, 3) 扩展为 (1, 224, 224, 3),因为模型预测接口要求输入包含 batch 维度。class_indices是从train_generator中获取的类别到索引的映射字典,预测结果拿到的是索引,需要反查回类别名称。置信度低于 0.6 时建议提示用户图片可能不属于已训练的类别,避免给出误导性的结果。

4.3 混淆矩阵与分类报告

如果要在论文中展示更详细的评估结果,可以用 scikit-learn 生成混淆矩阵和分类报告:

from sklearn.metrics import confusion_matrix, classification_report import seaborn as sns # 获取测试集所有预测结果 test_generator.reset() predictions = model.predict(test_generator) y_pred = np.argmax(predictions, axis=1) y_true = test_generator.classes # 分类报告 class_names = list(test_generator.class_indices.keys()) print(classification_report(y_true, y_pred, target_names=class_names)) # 混淆矩阵 cm = confusion_matrix(y_true, y_pred) plt.figure(figsize=(8, 6)) sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=class_names, yticklabels=class_names) plt.xlabel('Predicted') plt.ylabel('True') plt.title('Confusion Matrix') plt.savefig('confusion_matrix.png') plt.show()

分类报告会输出每个类别的精确率、召回率和 F1 分数,这些指标比总体准确率更能反映模型在不同类别上的表现差异。混淆矩阵可以直观看到哪些类别容易被混淆,比如橙子和橘子如果外观相似,可能会互相误判,这时候就需要考虑增加这两类的训练样本或调整数据增强策略。

5. 避坑与常见问题排查

5.1 训练准确率很高但测试准确率很低

现象:训练集准确率到了 95% 以上,但测试集只有 60% 左右。原因:典型的过拟合,模型记住了训练样本的细节但没有学到泛化特征。解决:首先增加 Dropout 层的丢弃率或添加 L2 正则化;其次加大数据增强的强度,比如把 rotation_range 从 30 提到 45;最后检查训练集和测试集的分布是否一致,如果测试集里有训练集中没出现过的水果品种,模型自然识别不了。

5.2 训练过程中 loss 变成 NaN

现象:训练几个 epoch 后损失突然变成 NaN,模型输出全是相同值。原因:学习率过大导致梯度爆炸,或者数据中有异常值(比如像素值没有归一化)。解决:把学习率从 0.001 降到 0.0001 或更低;确认rescale=1./255是否生效;在编译模型时加clipnorm=1.0做梯度裁剪:Adam(learning_rate=0.001, clipnorm=1.0)。

5.3 图片读取报错 “cannot identify image file”

现象:训练时随机报错,提示某张图片无法识别。原因:数据集中混入了损坏的图片文件或非图片格式的文件(比如 .DS_Store、Thumbs.db)。解决:写一个批量检查脚本,用 PIL 尝试打开每张图片,把失败的文件打印出来并删除:

from PIL import Image import os def check_images(directory): bad_files = [] for root, dirs, files in os.walk(directory): for f in files: filepath = os.path.join(root, f) try: img = Image.open(filepath) img.verify() except Exception as e: bad_files.append(filepath) print(f"损坏文件: {filepath} - {e}") return bad_files bad = check_images('fruits_dataset') print(f"共发现 {len(bad)} 个损坏文件")

5.4 模型保存后加载报错

现象:load_model('best_fruit_model.h5')时报错,提示未知的层或自定义对象。原因:模型使用了自定义层或自定义损失函数,加载时没有通过custom_objects参数传入。解决:如果模型只用了 Keras 内置层,一般不会出现这个问题;如果确实用了自定义组件,加载时需要显式传入:load_model('model.h5', custom_objects={'my_layer': MyLayer})。另一个常见原因是 TensorFlow 版本不一致,训练时用的 2.13,加载时用了 2.15,建议固定版本。

5.5 预测时所有图片都输出同一个类别

现象:不管输入什么图片,模型都预测为同一类,置信度还很高。原因:类别样本严重不均衡,某一类占了绝大多数;或者数据生成器的class_indices和预测时的映射不一致。解决:检查每个类别的图片数量,如果差距超过 5:1,需要用class_weight参数做加权训练;确认预测时用的class_indices和训练时是同一个,不要手动重新定义。

6. 从源码到毕业设计论文:把实验数据变成可讲的故事

跑通代码只是第一步,毕业设计真正拉开差距的地方在于你能不能把实验过程讲成一个有逻辑的故事。我见过太多同学代码跑出来了,但论文里只贴了几张截图和一段“准确率达到了 92%”就没了,答辩时被老师追问“为什么选这个网络结构”“为什么用 224 而不是 128”“数据增强的参数怎么定的”就答不上来。

我的习惯是每做完一组实验就立刻记录三样东西:当前使用的超参数组合、训练集和验证集的准确率曲线、以及和上一组实验的对比结论。比如你第一次用学习率 0.001 训练了 50 个 epoch,验证集准确率停在 85%;第二次把学习率降到 0.0005,准确率提到了 89%。这个对比本身就是论文里“超参数调优”小节最好的素材。下面这张表是我建议在论文中呈现的实验记录格式:

实验编号学习率Batch Size数据增强验证集准确率备注
Exp-10.00132无82.3%基线
Exp-20.00132有89.7%加数据增强
Exp-30.000532有91.2%降学习率
Exp-40.000564有90.5%增大 batch

有了这张表,你在论文里就可以写:“基线模型在无数据增强时验证集准确率为 82.3%,引入旋转、平移、缩放等增强策略后提升至 89.7%,说明数据增强对小样本水果数据集有明显的正则化效果。进一步将学习率从 0.001 降至 0.0005 后,准确率继续提升至 91.2%,但将 batch size 增大到 64 后准确率略有下降,推测是因为 batch 增大导致梯度更新次数减少,模型收敛不充分。”这段话有数据、有对比、有分析,答辩时老师一听就知道你是真做过实验的。

另一个容易被忽略的点是错误案例分析。从混淆矩阵里挑出误判最多的那几组,把对应的图片拿出来看看到底是什么原因。比如我发现苹果和桃子经常互相误判,一看图片发现是那种红黄渐变色的苹果,颜色特征和桃子确实接近。这个分析写进论文里,比单纯说“准确率 92%”有说服力得多。从那以后我每次做完一个图像分类项目,都会强制走一遍“混淆矩阵 → 挑错误样本 → 看图找原因”的流程,这个习惯帮我省了很多答辩时的尴尬。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 2:36:37

LLMs 中的提示缓存:直觉、配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 2:36:31

App请求签名与加密码还原实战:从抓包识别到本地复现

简介:这份资源面向移动应用、小程序与网站开发者,聚焦数字签名与加密的实战代码整理,覆盖自如、小红书、蛋壳公寓、瑞幸咖啡等生活服务类App的签名与加密实现思路,适合需要研究接口安全、逆向分析或加固方案的中高级开发者参考。压…

作者头像 李华
网站建设 2026/9/26 2:36:07

数据库课程设计实战:员工考勤管理系统从需求到SQL实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 2:35:44

QT QTextEdit 自动滚动到底部怎么关?TaoToken 配置骨架与验证清单

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 2:34:48

SoLab AI逆向工作台:集成DEX/SO/Flutter的安卓逆向分析利器

很多做安卓安全研究、App合规检测、恶意代码分析的朋友,应该都有过这样的体会:拿到一个APK,第一件事就是用jadx打开看一眼Java层代码,再用IDA或者Ghidra去啃Native库,遇到Flutter应用更是头疼,Dart AOT编译…

作者头像 李华