当前位置:   article > 正文

关于将Pytorch模型部署到安卓移动端方法总结_pytorch android

pytorch android

一、Android Studio环境配置

1.安装包下载问题解决

在Android Studio官网下载编译工具时,会出现无法下载的问题,可右键复制下载链接IDMan中进行下载。

2.安装

安装过程中,需要将Android Virtual Device勾选,否则无法使用虚拟机

安装启动后,会提示没有SDK,设置代码,直接选择cancel键。

完后,会有专门的SKD组件的安装,但是会有unavailable不可安装的情况出现,可通过创建项目后配置gradle后便可以安装了。

二、项目创建

软件安装后可能出现打不开的情况,可选择以管理员身份启动即可解决问题。

选择New Project

选择喜欢的界面样式即可。

使用语言、SDK根据自行需求进行选择就行。

Build configuration language建议选择Kotlin DSL(build.gradle.kts)[Recommended],否则会出现缺少gradle文件的情况。

创建完后会出现如下项目目录,并不会直接出现app的文件夹,需要手动配置gradle。

按照如下目录gradle/wrapper/gradle-wrapper.properties修改distributionUrl为本地地址。(根据原先的地址下载对应的压缩包)

  1. #Wed May 01 21:02:04 CST 2024
  2. distributionBase=GRADLE_USER_HOME
  3. distributionPath=wrapper/dists
  4. distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
  5. zipStoreBase=GRADLE_USER_HOME
  6. zipStorePath=wrapper/dists
  7. 更变为
  8. #Wed May 01 21:02:04 CST 2024
  9. distributionBase=GRADLE_USER_HOME
  10. distributionPath=wrapper/dists
  11. # 对应的gradle-8.4-bin.zip本地地址即可
  12. distributionUrl=file:///D://Android//gradle-8.4-bin.zip
  13. zipStoreBase=GRADLE_USER_HOME
  14. zipStorePath=wrapper/dists

在settings.gradle.kts更换阿里源(直接复制粘贴即可)

  1. pluginManagement {
  2. repositories {
  3. maven { url=uri ("https://www.jitpack.io")}
  4. maven { url=uri ("https://maven.aliyun.com/repository/releases")}
  5. maven { url=uri ("https://maven.aliyun.com/repository/google")}
  6. maven { url=uri ("https://maven.aliyun.com/repository/central")}
  7. maven { url=uri ("https://maven.aliyun.com/repository/gradle-plugin")}
  8. maven { url=uri ("https://maven.aliyun.com/repository/public")}
  9. google()
  10. mavenCentral()
  11. gradlePluginPortal()
  12. }
  13. }
  14. dependencyResolutionManagement {
  15. repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
  16. repositories {
  17. maven { url=uri ("https://www.jitpack.io")}
  18. maven { url=uri ("https://maven.aliyun.com/repository/releases")}
  19. maven { url=uri ("https://maven.aliyun.com/repository/google")}
  20. maven { url=uri ("https://maven.aliyun.com/repository/central")}
  21. maven { url=uri ("https://maven.aliyun.com/repository/gradle-plugin")}
  22. maven { url=uri ("https://maven.aliyun.com/repository/public")}
  23. google()
  24. mavenCentral()
  25. }
  26. }
  27. rootProject.name = "Helloword"
  28. include(":app")

在build.gradle.kts中点击sync now即可自动配置,稍等即可便可变成app文件夹的形式。

选择Project,变成全部文件的形式。

初始新建项目即刻完成。

三、训练模型权重转化

需将训练好的.pth文件转化为.pt文件

  1. """
  2. 该程序使用的是resnet32网络,用到其他网络可自行更改
  3. 保存的权重字典目录如下所示。
  4. ckpt = {
  5. 'weight': model.state_dict(),
  6. 'epoch': epoch,
  7. 'cfg': opt.model,
  8. 'index': name
  9. }
  10. """
  11. from models.resnet_cifar import resnet32 # 确保引用你的正确模型架构
  12. import torch
  13. import torch.nn as nn
  14. # 假设你的ResNet定义在resnet.py文件中
  15. model = resnet32()
  16. num_ftrs = model.fc.in_features
  17. model.fc = nn.Linear(num_ftrs, 100) # 修改这里的100为你的类别数
  18. # 加载权重
  19. checkpoint = torch.load('modelleader_best.pth', map_location=torch.device('cpu'))
  20. model.load_state_dict(checkpoint['weight'], strict=False) # 使用strict=False可以忽略不匹配的键
  21. model.eval()
  22. # 将模型转换为TorchScript
  23. example_input = torch.rand(1, 3, 32, 32) # 修改这里以匹配你的模型输入尺寸
  24. traced_script_module = torch.jit.trace(model, example_input)
  25. traced_script_module.save("model.pt")

四、Pytorch项目搭建工作

在如下目录下创建assets文件,将转化好的模型放在里面即可,切记不可直接创建文件夹,会出现找不到模型问题。

在com/example/myapplication下创建了两个类cifarClassed,MainActivity。

MainActivity类
  1. package com.example.myapplication;
  2. import android.content.Context;
  3. import android.content.Intent;
  4. import android.content.pm.PackageManager;
  5. import android.graphics.Bitmap;
  6. import android.graphics.BitmapFactory;
  7. import android.os.Bundle;
  8. import android.provider.MediaStore;
  9. import android.util.Log;
  10. import android.view.View;
  11. import android.widget.Button;
  12. import android.widget.ImageView;
  13. import android.widget.TextView;
  14. import androidx.annotation.NonNull;
  15. import androidx.appcompat.app.AppCompatActivity;
  16. import androidx.core.app.ActivityCompat;
  17. import androidx.core.content.ContextCompat;
  18. import androidx.core.content.FileProvider;
  19. import org.pytorch.IValue;
  20. import org.pytorch.Module;
  21. import org.pytorch.Tensor;
  22. import org.pytorch.torchvision.TensorImageUtils;
  23. import java.io.File;
  24. import java.io.FileOutputStream;
  25. import java.io.IOException;
  26. import java.io.InputStream;
  27. import java.io.OutputStream;
  28. public class MainActivity extends AppCompatActivity {
  29. private static final int PERMISSION_REQUEST_CODE = 101;
  30. private static final int REQUEST_IMAGE_CAPTURE = 1;
  31. private static final int REQUEST_IMAGE_SELECT = 2;
  32. private ImageView imageView;
  33. private TextView textView;
  34. private Module module;
  35. @Override
  36. protected void onCreate(Bundle savedInstanceState) {
  37. super.onCreate(savedInstanceState);
  38. setContentView(R.layout.activity_main);
  39. // 检查相机权限
  40. if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
  41. ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.CAMERA}, PERMISSION_REQUEST_CODE);
  42. }
  43. imageView = findViewById(R.id.image);
  44. textView = findViewById(R.id.text);
  45. ImageView logoImageView = findViewById(R.id.logo);
  46. logoImageView.setImageResource(R.drawable.logo);
  47. Button takePhotoButton = findViewById(R.id.button_take_photo);
  48. Button selectImageButton = findViewById(R.id.button_select_image);
  49. takePhotoButton.setOnClickListener(v -> dispatchTakePictureIntent());
  50. selectImageButton.setOnClickListener(v -> dispatchGalleryIntent());
  51. try {
  52. module = Module.load(assetFilePath(this, "model.pt"));
  53. } catch (IOException e) {
  54. Log.e("PytorchHelloWorld", "Error reading assets", e);
  55. finish();
  56. }
  57. }
  58. @Override
  59. public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
  60. super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  61. if (requestCode == PERMISSION_REQUEST_CODE) {
  62. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
  63. // 权限被授予
  64. Log.d("Permissions", "Camera permission granted");
  65. } else {
  66. // 权限被拒绝
  67. Log.d("Permissions", "Camera permission denied");
  68. }
  69. }
  70. }
  71. private void dispatchTakePictureIntent() {
  72. Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
  73. if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
  74. startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
  75. }
  76. }
  77. private void dispatchGalleryIntent() {
  78. Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
  79. startActivityForResult(intent, REQUEST_IMAGE_SELECT);
  80. }
  81. @Override
  82. protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  83. super.onActivityResult(requestCode, resultCode, data);
  84. if (resultCode == RESULT_OK && (requestCode == REQUEST_IMAGE_CAPTURE || requestCode == REQUEST_IMAGE_SELECT)) {
  85. Bitmap imageBitmap = null;
  86. if (requestCode == REQUEST_IMAGE_CAPTURE) {
  87. Bundle extras = data.getExtras();
  88. imageBitmap = (Bitmap) extras.get("data");
  89. } else if (requestCode == REQUEST_IMAGE_SELECT) {
  90. try {
  91. imageBitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), data.getData());
  92. } catch (IOException e) {
  93. e.printStackTrace();
  94. }
  95. }
  96. imageView.setImageBitmap(imageBitmap);
  97. classifyImage(imageBitmap);
  98. }
  99. }
  100. // private void classifyImage(Bitmap bitmap) {
  101. // Tensor inputTensor = TensorImageUtils.bitmapToFloat32Tensor(bitmap,
  102. // TensorImageUtils.TORCHVISION_NORM_MEAN_RGB, TensorImageUtils.TORCHVISION_NORM_STD_RGB);
  103. // Tensor outputTensor = module.forward(IValue.from(inputTensor)).toTensor();
  104. // float[] scores = outputTensor.getDataAsFloatArray();
  105. // float maxScore = -Float.MAX_VALUE;
  106. // int maxScoreIdx = -1;
  107. // for (int i = 0; i < scores.length; i++) {
  108. // if (scores[i] > maxScore) {
  109. // maxScore = scores[i];
  110. // maxScoreIdx = i;
  111. // }
  112. // }
  113. // textView.setText("推理结果:" + CifarClassed.IMAGENET_CLASSES[maxScoreIdx]);
  114. // textView.setVisibility(View.VISIBLE); // 设置 TextView 可见
  115. // }
  116. // private void classifyImage(Bitmap bitmap) {
  117. // // 调整图像大小为 32x32 像素
  118. // Bitmap resizedBitmap = resizeBitmap(bitmap, 32, 32);
  119. //
  120. // // 将调整大小后的图像转换为 PyTorch Tensor
  121. // Tensor inputTensor = TensorImageUtils.bitmapToFloat32Tensor(resizedBitmap,
  122. // new float[]{0.485f, 0.456f, 0.406f}, // 均值 Mean
  123. // new float[]{0.229f, 0.224f, 0.225f}); // 标准差 Std
  124. //
  125. // // 推理
  126. // Tensor outputTensor = module.forward(IValue.from(inputTensor)).toTensor();
  127. // float[] scores = outputTensor.getDataAsFloatArray();
  128. // float maxScore = -Float.MAX_VALUE;
  129. // int maxScoreIdx = -1;
  130. // for (int i = 0; i < scores.length; i++) {
  131. // if (scores[i] > maxScore) {
  132. // maxScore = scores[i];
  133. // maxScoreIdx = i;
  134. // }
  135. // }
  136. // textView.setText("推理结果:" + CifarClassed.IMAGENET_CLASSES[maxScoreIdx]);
  137. // textView.setVisibility(View.VISIBLE); // 设置 TextView 可见
  138. // }
  139. //
  140. private float[] softmax(float[] scores) {
  141. float max = Float.NEGATIVE_INFINITY;
  142. for (float score : scores) {
  143. if (score > max) max = score;
  144. }
  145. float sum = 0.0f;
  146. float[] exps = new float[scores.length];
  147. for (int i = 0; i < scores.length; i++) {
  148. exps[i] = (float) Math.exp(scores[i] - max); // 减去最大值防止指数爆炸
  149. sum += exps[i];
  150. }
  151. for (int i = 0; i < exps.length; i++) {
  152. exps[i] /= sum; // 归一化
  153. }
  154. return exps;
  155. }
  156. // 图像分类方法
  157. private void classifyImage(Bitmap bitmap) {
  158. // 调整图像大小为 32x32 像素
  159. Bitmap resizedBitmap = resizeBitmap(bitmap, 32, 32);
  160. // 将调整大小后的图像转换为 PyTorch Tensor
  161. Tensor inputTensor = TensorImageUtils.bitmapToFloat32Tensor(resizedBitmap,
  162. new float[]{0.485f, 0.456f, 0.406f}, // 使用训练时相同的均值 Mean
  163. new float[]{0.229f, 0.224f, 0.225f}); // 使用训练时相同的标准差 Std
  164. // 推理
  165. Tensor outputTensor = module.forward(IValue.from(inputTensor)).toTensor();
  166. float[] scores = outputTensor.getDataAsFloatArray();
  167. // 应用自定义的 Softmax 函数获取概率分布
  168. float[] probabilities = softmax(scores);
  169. float maxScore = -Float.MAX_VALUE;
  170. int maxScoreIdx = -1;
  171. for (int i = 0; i < probabilities.length; i++) {
  172. if (probabilities[i] > maxScore) {
  173. maxScore = probabilities[i];
  174. maxScoreIdx = i;
  175. }
  176. }
  177. // 更新 UI 必须在主线程中完成
  178. final int maxIndex = maxScoreIdx;
  179. final float finalMaxScore = maxScore;
  180. runOnUiThread(new Runnable() {
  181. @Override
  182. public void run() {
  183. textView.setText("推理结果:" + CifarClassed.IMAGENET_CLASSES[maxIndex] + " (" + String.format("%.2f%%", finalMaxScore * 100) + ")");
  184. textView.setVisibility(View.VISIBLE); // 设置 TextView 可见
  185. }
  186. });
  187. }
  188. ///
  189. //
  190. // 方法来调整 Bitmap 的大小
  191. private Bitmap resizeBitmap(Bitmap originalBitmap, int targetWidth, int targetHeight) {
  192. return Bitmap.createScaledBitmap(originalBitmap, targetWidth, targetHeight, false);
  193. }
  194. public static String assetFilePath(Context context, String assetName) throws IOException {
  195. File file = new File(context.getFilesDir(), assetName);
  196. if (file.exists() && file.length() > 0) {
  197. return file.getAbsolutePath();
  198. }
  199. try (InputStream is = context.getAssets().open(assetName)) {
  200. try (OutputStream os = new FileOutputStream(file)) {
  201. byte[] buffer = new byte[4 * 1024];
  202. int read;
  203. while ((read = is.read(buffer)) != -1) {
  204. os.write(buffer, 0, read);
  205. }
  206. os.flush();
  207. }
  208. return file.getAbsolutePath();
  209. }
  210. }
  211. }

CifarClassed类
  1. package com.example.myapplication;
  2. public class CifarClassed {
  3. public static String[] IMAGENET_CLASSES = new String[]{
  4. "apple", "aquarium_fish", "baby", "bear", "beaver", "bed", "bee", "beetle",
  5. "bicycle", "bottle", "bowl", "boy", "bridge", "bus", "butterfly", "camel",
  6. "can", "castle", "caterpillar", "cattle", "chair", "chimpanzee", "clock",
  7. "cloud", "cockroach", "couch", "crab", "crocodile", "cup", "dinosaur",
  8. "dolphin", "elephant", "flatfish", "forest", "fox", "girl", "hamster", "house",
  9. "kangaroo", "keyboard", "lamp", "lawn_mower", "leopard", "lion", "lizard",
  10. "lobster", "man", "maple_tree", "motorcycle", "mountain", "mouse", "mushroom",
  11. "oak_tree", "orange", "orchid", "otter", "palm_tree", "pear", "pickup_truck",
  12. "pine_tree", "plain", "plate", "poppy", "porcupine", "possum", "rabbit", "raccoon",
  13. "ray", "road", "rocket", "rose", "sea", "seal", "shark", "shrew", "skunk",
  14. "skyscraper", "snail", "snake", "spider", "squirrel", "streetcar", "sunflower",
  15. "sweet_pepper", "table", "tank", "telephone", "television", "tiger", "tractor",
  16. "train", "trout", "tulip", "turtle", "wardrobe", "whale", "willow_tree", "wolf",
  17. "woman", "worm"
  18. };
  19. }

页面布局存放在MyApplication\app\src\main\res\layout\activity_main.xml文件中。

  1. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. tools:context=".MainActivity"
  6. android:background="#F0F0F0">
  7. <LinearLayout
  8. android:layout_width="match_parent"
  9. android:layout_height="wrap_content"
  10. android:layout_gravity="center_vertical"
  11. android:orientation="vertical"
  12. android:gravity="center">
  13. <ImageView
  14. android:id="@+id/image"
  15. android:layout_width="200dp"
  16. android:layout_height="200dp"
  17. android:scaleType="centerCrop"
  18. android:elevation="2dp" />
  19. <!-- 推理结果显示在图片与按钮之间的空白区域 -->
  20. <TextView
  21. android:id="@+id/text"
  22. android:layout_width="wrap_content"
  23. android:layout_height="wrap_content"
  24. android:textSize="24sp"
  25. android:textColor="#FFF"
  26. android:gravity="center"
  27. android:layout_marginTop="16dp"
  28. android:layout_marginBottom="16dp"
  29. android:visibility="gone" /> <!-- 初始状态隐藏 -->
  30. </LinearLayout>
  31. <!-- 按钮位于屏幕底部 -->
  32. <LinearLayout
  33. android:layout_width="match_parent"
  34. android:layout_height="wrap_content"
  35. android:orientation="horizontal"
  36. android:layout_gravity="bottom"
  37. android:elevation="4dp">
  38. <Button
  39. android:id="@+id/button_take_photo"
  40. android:layout_width="0dp"
  41. android:layout_height="wrap_content"
  42. android:layout_weight="1"
  43. android:text="拍照"
  44. android:backgroundTint="#FF6200EE"
  45. android:textColor="#FFFFFF"
  46. android:layout_margin="8dp"
  47. android:elevation="2dp"
  48. android:stateListAnimator="@null"/>
  49. <Button
  50. android:id="@+id/button_select_image"
  51. android:layout_width="0dp"
  52. android:layout_height="wrap_content"
  53. android:layout_weight="1"
  54. android:text="选择图片"
  55. android:backgroundTint="#FF018786"
  56. android:textColor="#FFFFFF"
  57. android:layout_margin="8dp"
  58. android:elevation="2dp"
  59. android:stateListAnimator="@null"/>
  60. </LinearLayout>
  61. <!-- 调整商标为小圆形并放置在顶部中间 -->
  62. <!-- 调整商标为小圆形并放置在顶部中间使用 CircleImageView -->
  63. <de.hdodenhof.circleimageview.CircleImageView
  64. android:id="@+id/logo"
  65. android:src="@drawable/logo"
  66. android:layout_width="50dp"
  67. android:layout_height="50dp"
  68. android:layout_gravity="top|center_horizontal"
  69. android:layout_marginTop="16dp"
  70. android:elevation="5dp"/>
  71. </FrameLayout>

在MyApplication\app\src\main\res\drawable\circle_shape.xml(自行创建)

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <shape xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:shape="oval">
  4. <solid android:color="#FFFFFF"/> <!-- 修改颜色以匹配你的需求 -->
  5. <size
  6. android:width="50dp"
  7. android:height="50dp"/> <!-- 定义圆的尺寸,确保它与 ImageView 的尺寸相匹配 -->
  8. </shape>

在MyApplication\app\src\main\res\drawable\rounded_background(自行创建)

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <shape xmlns:android="http://schemas.android.com/apk/res/android">
  3. <solid android:color="#FFFFFF"/> <!-- 背景色,半透明黑 -->
  4. <corners android:radius="10dp"/> <!-- 圆角的大小 -->
  5. </shape>

在MyApplication\app\src\main\AndroidManifest.xml添加相机与读取照片的权限。

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:tools="http://schemas.android.com/tools">
  4. <uses-feature android:name="android.hardware.camera" android:required="true"/>
  5. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  6. <uses-permission android:name="android.permission.CAMERA" />
  7. <application
  8. android:allowBackup="true"
  9. android:dataExtractionRules="@xml/data_extraction_rules"
  10. android:fullBackupContent="@xml/backup_rules"
  11. android:icon="@mipmap/ic_launcher"
  12. android:label="@string/app_name"
  13. android:roundIcon="@mipmap/ic_launcher_round"
  14. android:supportsRtl="true"
  15. android:theme="@style/Theme.MyApplication"
  16. tools:targetApi="31">
  17. <activity
  18. android:name=".MainActivity"
  19. android:exported="true">
  20. <intent-filter>
  21. <action android:name="android.intent.action.MAIN" />
  22. <category android:name="android.intent.category.LAUNCHER" />
  23. </intent-filter>
  24. </activity>
  25. </application>
  26. </manifest>

app级别build.gradle.kts(MyApplication\app\build.gradle.kts)配置如下。

  1. plugins {
  2. alias(libs.plugins.androidApplication)
  3. }
  4. android {
  5. namespace = "com.example.myapplication"
  6. compileSdk = 34
  7. sourceSets {
  8. getByName("main") {
  9. jniLibs.srcDir("libs")
  10. }
  11. }
  12. packaging {
  13. resources.excludes.add("META-INF/*")
  14. }
  15. defaultConfig {
  16. applicationId = "com.example.myapplication"
  17. minSdk = 24
  18. targetSdk = 34
  19. versionCode = 1
  20. versionName = "1.0"
  21. testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
  22. }
  23. buildTypes {
  24. release {
  25. isMinifyEnabled = false
  26. proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
  27. }
  28. }
  29. compileOptions {
  30. sourceCompatibility = JavaVersion.VERSION_1_8
  31. targetCompatibility = JavaVersion.VERSION_1_8
  32. }
  33. }
  34. dependencies {
  35. // 使用 alias 来指定库,确保 libs.aliases.gradle 中已经定义了这些别名
  36. implementation(libs.appcompat)
  37. implementation(libs.material)
  38. implementation(libs.activity)
  39. implementation(libs.constraintlayout)
  40. testImplementation(libs.junit)
  41. androidTestImplementation(libs.ext.junit)
  42. androidTestImplementation(libs.espresso.core)
  43. implementation("org.pytorch:pytorch_android:1.12.1")
  44. implementation("org.pytorch:pytorch_android_torchvision:1.12.1")
  45. implementation("com.google.android.exoplayer:exoplayer:2.14.1")
  46. implementation("androidx.localbroadcastmanager:localbroadcastmanager:1.0.0")
  47. implementation("androidx.activity:activity:1.2.0")
  48. implementation("androidx.fragment:fragment:1.3.0")
  49. implementation("de.hdodenhof:circleimageview:3.1.0")
  50. }

这段可解决如下bug。

  1. packaging {
  2. resources.excludes.add("META-INF/*")
  3. }
Caused by: com.android.builder.merge.DuplicateRelativeFileException: 2 files found with path ‘META-INF/androidx.core_core.version’.

手动添加非常麻烦,因为不止一个文件冲突!!!

完成以上步骤再按下Sync Now完成依赖的配置工作,需在编译器中自行选择虚拟设备。

完成后即可在MainActivity.java文件启动项目。

五、APK安装包导出

 点击create创建即可,便可得到apk文件。

六、效果图

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/767349
推荐阅读
相关标签
  

闽ICP备14008679号