从0开始 yolov5可以用灰度图像进行训练和检测吗

news2024/10/5 14:17:20

yolov5可以用灰度图像进行训练吗,从0开始yolov5灰度图训练和检测


文章目录

  • yolov5可以用灰度图像进行训练吗,从0开始yolov5灰度图训练和检测
    • @[toc]
    • 1 预演
            • 【表1-1 模型结构截取】
    • 2 修改源码使可以灰度训练
      • 2.1 修改读取图片模式
      • 2.2 修改源码传参中的通道数
      • 2.3 运行train.py
      • 2.4 修改utils/general.py中的源码
      • 2.5 修改dataloaders.py中所有cv2读取图片的flags
      • 2.6 此时运行 ’train.py‘ 文件可正常epoch跑起来。
    • 3 模型测试
      • 3.1 RGB图训练的模型测试
        • 训练对比
        • 准确率的对比
        • val推理时间的对比
      • 3.2 GRAY图训练的模型检测测试
        • 训练对比
        • 准确率的对比
        • val推理时间的对比
      • 3.3 RGB和GRAY训练测试对比
            • 【表3-1 RGB和GRAY模型结论对比】
    • 4 结论
    • 5 测试RGB转Gray时间
    • 附1 未修改任何源码,直接使用灰度图像+yolov6_6.2训练
    • 附2 训练时数据集标签如下

1 预演

通常我们采用RGB图像做目标检测,考虑多路视频流同时做目标检测时,消耗显卡算力和推理时间较多。现通过实验对比对灰度图(GRAY)和RGB图做训练和测试对比。

本次使用yolov5_6.2尝试灰度图做目标检测的训练的目标检测的测试。

直接将彩色图通过opencv算法转换为灰度图,运行’python train.py’ 训练。

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

在train.py运行的时候,无报错,但是打印的模型结构的前两行如下所示:

【表1-1 模型结构截取】
fromnparamsmodulearguments
0-113520models.common.Conv[3, 32, 6, 2, 2]
1-1118560models.common.Conv[32, 64, 3, 2]

​ 通过上图打印可知模型的输入是固定3通道的,那么意味着即使训练的数据集是灰度图像,在读取图像时仍会对图像进行处理。

观察源码,在utils/dataloaders.py文件第248行

245        else:
246            # Read image
247            self.count += 1
248            img0 = cv2.imread(path)  # BGR
249            assert img0 is not None, f'Image Not Found {path}'
250            s = f'image {self.count}/{self.nf} {path}: '

​ 如下opencv的imread方法简介

img = cv2.imread(path, flag)   	# flag: the way in which img should be read
								# flag: default value is cv2.IMREAD_COLOR
# cv2.IMREAD_COLOR(1):    BGR
# cv2.IMREAD_GRAYSCALE(0): GRAY
# cv2.IMREAD_UNCHANGED(-1):

​ opencv在默认情况下,'cv2.imread(path)'会读取3个通道(BGR)的图像,如果是灰度图,会将图层复制三次(BGR缺省,BGR),因此读出来的图片是三通道。

2 修改源码使可以灰度训练

源码改变的位置,后加注释’Hlj2308’。

2.1 修改读取图片模式

在utils/dataloaders.py文件第248行,原

img0 = cv2.imread(path)  # BGR

改为

img0 = cv2.imread(path, 0)  # Hlj2308_GRAY

2.2 修改源码传参中的通道数

2.2.1 在train.py文件第122行、130行中的参数 ch=3 改为 ch=1。

model = Model(cfg or ckpt['model'].yaml, ch=1, nc=nc, anchors=hyp.get('anchors')).to(device)  # Hlj2308
model = Model(cfg, ch=1, nc=nc, anchors=hyp.get('anchors')).to(device)  # Hlj2308 create

2.2.2 在models/yolo.py文件第151行ch=3改为 ch=1。

149 class DetectionModel(BaseModel):
150     # YOLOv5 detection model
151     def __init__(self, cfg='yolov5s.yaml', ch=1, nc=None, anchors=None):  
152         super().__init__()
153         if isinstance(cfg, dict):
154             self.yaml = cfg  # model dict

2.3 运行train.py

在运行 train.py 的过程中,反正就是一路报错,一路改错调试。最终实现可正常训练和测试的目的。

【报错1】

File "....packages\torch\nn\modules\conv.py", line 442, in _conv_forward
    return F.conv2d(input, weight, bias, self.stride,
RuntimeError: Given groups=1, weight of size [32, 1, 6, 6], expected input[8, 3, 640, 640] to have 1 channels, but got 3 channels instead

数据读入的通道数不对,应该是 1 通道,但是这里读入的是 3 通道。

可能是PIL模块’Image.open()'打开图像时的’mode’问题。修改models/common.py文件中 612 行

im, f = Image.open(requests.get(im, stream=True).raw if str(im).startswith('http') else im), im

后面添加如下两行代码

if im.mode != 'L':
    im = im.convert('L')

仍存在【报错1】

2.4 修改utils/general.py中的源码

修改utils/general.py中的源码1031-1032行如下

def imread(path, flags=cv2.IMREAD_COLOR):
    return cv2.imdecode(np.fromfile(path, np.uint8), flags)

改为

def imread(path, flags=cv2.IMREAD_GRAYSCALE):
    return cv2.imdecode(np.fromfile(path, np.uint8), cv2.IMREAD_GRAYSCALE)

仍存在【报错1】

2.5 修改dataloaders.py中所有cv2读取图片的flags

在utils/dataloaders.py文件第677、691、881、1042、1119、1122、1125行,原

cv2.imread(f)

改为如下,可通过Ctr+F搜索替换

cv2.imread(f, 0)

此时,运行train.py,【报错1】将不存在

【报错2】如下

File "..../yolov5_6.2_gray/utils/dataloaders.py", line 706, in load_mosaic
    img4 = np.full((s * 2, s * 2, img.shape[2]), 114, dtype=np.uint8)  # base image with 4 tiles
IndexError: tuple index out of range

解决:原因是img.shape[2]索引不存在,因为图片是单通道的,所以只有二维,而非三维数组。这里将源码\utils\dataloaders.py的706行源码

img4 = np.full((s * 2, s * 2, img.shape[2]), 114, dtype=np.uint8)  

改为

img4 = np.full((s * 2, s * 2, img.shape[2] if len(img.shape)==3 else 1), 114, dtype=np.uint8) 

【报错3】如下,是由于【报错2】的修改方式不对

File "..../yolov5_6.2_gray/utils/dataloaders.py", line 721, in load_mosaic
    img4[y1a:y2a, x1a:x2a] = img[y1b:y2b, x1b:x2b]  # img4[ymin:ymax, xmin:xmax]
ValueError: could not broadcast input array from shape (360,640) into shape (360,640,1)

解决:打印报错对应 img4 和 img 的 shape,有’img4.shape, img.shape = (1280, 1280, 1) (360, 640)',显然是图片尺寸不匹配导致的。将【报错2】重新修改为如下

img4 = np.full((s * 2, s * 2), 114, dtype=np.uint8)  # base image with 4 tiles

【报错4】如下

File "D:\yolov5train\yolov5_6.2_grayTrain\utils\dataloaders.py", line 642, in __getitem__
    augment_hsv(img, hgain=hyp['hsv_h'], sgain=hyp['hsv_s'], vgain=hyp['hsv_v'])
  File "D:\yolov5train\yolov5_6.2_grayTrain\utils\augmentations.py", line 69, in augment_hsv
    hue, sat, val = cv2.split(cv2.cvtColor(im, cv2.COLOR_BGR2HSV))
cv2.error: OpenCV(4.2.0) c:\projects\opencv-python\opencv\modules\imgproc\src\color.simd_helpers.hpp:92: error: (-2:Unspecified error) in function '__cdecl cv::impl::`anonymous-namespace'::CvtHelper<struct cv::impl::`anonymous namespace'::Set<3,4,-1>,struct cv::impl::A0x3b52564f::Set<3,-1,-1>,struct cv::impl::A0x3b52564f::Set<0,5,-1>,2>::CvtHelper(const class cv::_InputArray &,const class cv::_OutputArray &,int)'
> Invalid number of channels in input image:
>     'VScn::contains(scn)'
> where
>     'scn' is 1

解决:上述问题是由于hsv通道拆分导致的,此处不需要,将\utils\dataloaders.py中的 line 642对应代码注释掉,如下

# augment_hsv(img, hgain=hyp['hsv_h'], sgain=hyp['hsv_s'], vgain=hyp['hsv_v'])

【报错5】如下

File "D:\yolov5train\yolov5_6.2_grayTrain\utils\dataloaders.py", line 665, in __getitem__
    img = img.transpose((2, 0, 1))[::-1]  # HWC to CHW, BGR to RGB
ValueError: axes don't match array

解决:上述问题是猜测是由于BGR to RGB转换导致的,将\utils\dataloaders.py的665行源码

img = img.transpose((2, 0, 1))[::-1]  # HWC to CHW, BGR to RGB

改为如下是不对的

img = img.transpose((2, 0, 1))  # HWC to CHW

opencv通过HWC加载图片,而Pytorch需要CHW,故需要将图片通过 'transpose((2, 0, 1)) ’ 转为CHW。但此时需要进行 ‘transpose’ 操作的 'img’的尺寸是(640, 640),无法直接进行 ‘transpose’ 操作。故改为如下【报错5】可避免。

img = img.reshape(1, img.shape[0], img.shape[1])

2.6 此时运行 ’train.py‘ 文件可正常epoch跑起来。

​ 此时未涉及修改模型cfg文件。其中train.py中的参数设置如下

'--weights', type=str, default='',
'--cfg', type=str, default='./models/yolov5s.yaml',
'--data', type=str, default= './data/my_yolo5.yaml',
'--epochs', type=int, default=50
'--batch-size', type=int, default=8,
'--imgsz', '--img', '--img-size', type=int, default= 640,

'./models/yolov5s.yaml’参数包含如下

# Parameters
nc: 11  # number of classes
depth_multiple: 0.33  # model depth multiple
width_multiple: 0.50  # layer channel multiple

'./data/my_yolo5.yaml’参数包含如下

train: ../datasTrain4_LQTest_gray/images/train/
val: ../datasTrain4_LQTest_gray/images/val/

# number of classes
nc: 11

# class names
names: [ 'pedes', 'car', 'bus', 'truck', 'bike', 'elec', 'tricycle','coni', 'warm', 'tralight', 'speVeh']

3 模型测试

​ 本次 RGB 和 GRAY 均采用yolov5_6.2无预训练weight训练50epochs。数据集是同一组图像的RGB和GRAY格式,标签文件一模一样。–> 因为数据集较少,2491(train)、360(val),且没有使用预训练模型,epoch=50,所以P、R、mAP相对较低。

3.1 RGB图训练的模型测试

训练对比

​ 一个epoch的train的时间在 4:24-4:34 不等,一个epoch的val的时间在 0:13-0:15 不等。

准确率的对比

​ trian结束,all:P(0.869) R(0.653) mAP(0.717)@.5 mAP(0.48)@.5:.95

val: all:P(0.712) R(0.651) mAP(0.71)@.5 mAP(0.512)@.5:.95

val推理时间的对比

Speed: 0.3ms pre-process, 7.1ms inference, 1.8ms NMS per image at shape (8, 3, 640, 640)

3.2 GRAY图训练的模型检测测试

训练对比

​ 一个epoch的train的时间在 2:48-2:54 不等,一个epoch的val的时间在 0:08-0:09 不等。

准确率的对比

​ trian结束,all:P(0.905) R(0.617) mAP(0.705)@.5 mAP(0.464)@.5:.95

val: all:P(0.728) R(0.619) mAP(0.696)@.5 mAP(0.497)@.5:.95

val推理时间的对比

Speed: 0.1ms pre-process, 4ms inference, 2.3ms NMS per image at shape (8, 1, 640, 640)

上述运行val.py过程中,

【报错6】如下

...
File "D:/yolov5train/yolov5_6.2_grayTrain/val.py", line 169, in run
    model.warmup(imgsz=(1 if pt else batch_size, 3, imgsz, imgsz))  # warmup
...
File "D:\AppData\anaconda3.8\envs\yolov5t\lib\site-packages\torch\nn\modules\conv.py", line 442, in _conv_forward
    return F.conv2d(input, weight, bias, self.stride,
RuntimeError: Given groups=1, weight of size [32, 1, 6, 6], expected input[1, 3, 640, 640] to have 1 channels, but got 3 channels instead

解决:将val.py源码中169行代码

model.warmup(imgsz=(1 if pt else batch_size, 3, imgsz, imgsz))  # warmup

改为如下,【报错6】得以解决。

model.warmup(imgsz=(1 if pt else batch_size, 1, imgsz, imgsz))  # warmup

3.3 RGB和GRAY训练测试对比

​ 本次 RGB 和 GRAY 均采用yolov5_6.2无预训练weight训练50epochs。数据集是同一组图像的RGB和GRAY格式,标签文件一模一样。 因为数据集分配:2491(train)、360(val)。对比结果如下:

【表3-1 RGB和GRAY模型结论对比】

在这里插入图片描述

4 结论

综合表3-1可得结论如下:

(1)通过采用RGB和GRAY对图像的对比,采用灰度图像训练和推理的时间确有减少。我们关注的推理时间从7.1降到4.1,减少了3 / 7.1 = 42%的时间。

(2)测试的P、R、mAP@.5值存在差异,但50轮epochs的差异并不大。由于在无预训练模型的状态下,训练50轮。

5 测试RGB转Gray时间

​ 通过 ‘detect.py’ 测试。将模型传入的参数 ch=3 改为 1 。 ‘–source’ 参数取文件夹中均为 RGB 的图像。发现直接检测完会按照gray格式保存在了 ‘runs/val’ 目录下。原来是自己在 ‘utils/dataloaders.py’ 源码中 ‘class LoadImages:’ 类里的方法 ‘def next(self):’ 中,已经改为了 ‘img0 = cv2.imread(path, 0)’ 。于是,自己测了一下 ‘cv2.imread()’ 方法的耗时,如下:

读取RGB图的耗时(s): 0.02003192901611328
读取灰度图的耗时(s): 0.011004924774169922

​ 其实,‘cv2.imread()’ 方法的耗时对比是完全没有必要的,我们通常通过rtsp流或者相机SDK取流,拿到的摄相机数据流为yuv格式,而y通道的数据就是gray数据。反而要获取RGB数据需要yuv转RGB公式的计算。

​ 有必要研究一下,① 目标检测在部署的时候,是怎么来读取相机中的视频的。② 相机SDK给出来的是什么样的数据形式。③ 目标检测的前处理当中,是对rgb图做处理吗,还是视频流过来的其它格式(或形式的数据),是否需要转为rgb的格式后再做其它的前处理操作。考虑包含相机的编解码操作在内的原理。

【上段落中提到的有必要研究的3点,有没有好一点的资源推荐呢,感谢~】

如下为图像所有像素点的 R、G、B 分量与 Y、U、V 分量之间相互转换的公式。
{ Y = 0.299 ∗ R + 0.587 ∗ G + . 0114 ∗ B U = − 0.147 ∗ R − 0.289 ∗ G + 0.436 ∗ B V = 0.615 ∗ R − 0.515 ∗ G − 0.100 ∗ B \begin{cases} Y = 0.299*R + 0.587*G + .0114*B\\ U = -0.147*R -0.289*G + 0.436*B\\ V = 0.615*R -0.515*G -0.100*B \end{cases} Y=0.299R+0.587G+.0114BU=0.147R0.289G+0.436BV=0.615R0.515G0.100B

{ R = Y + 1.14 ∗ V G = Y − 0.39 ∗ U − 0.58 ∗ V B = Y + 2.03 ∗ U \begin{cases} R = Y + 1.14*V\\ G = Y - 0.39*U - 0.58*V\\ B = Y + 2.03*U \end{cases} R=Y+1.14VG=Y0.39U0.58VB=Y+2.03U


附1 未修改任何源码,直接使用灰度图像+yolov6_6.2训练

模型参数如图1

在这里插入图片描述

图1 也能跑起来,根据理解,也是3通道跑的训练,并且三个通道都是GRAY通道的值。按照yuv理解的话,y通道认为是灰度通道。即相当于3个通道都是y对应的灰度的值。

附2 训练时数据集标签如下

表1 目标标签详情

IDlabel描述备注
0pedes行人(骑着平衡车 平板车也划到此类)
1car轿车(包括SUV、 MPV(皮卡)、 VAN(面包车))
2bus大巴车、公交车
3truck卡车、货车
4bike自行车
5elec摩托车(电摩托车)
6tricycle三轮车(电三轮车、油三轮车)
7coni锥桶
8warm警示柱
9tralight交通信号灯
10speVeh紧急或特殊车辆 (救护车、消防车、工程车辆如吊车挖掘机渣土车等

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/975139.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java八股文面试[数据库]——BufferPool

Buffer Pool是MYSQL数据库中的一个重要的内存组件&#xff0c;介于外部系统和存储引擎之间的一个缓存区&#xff0c;针数据库的增删改查这些操作都是针对这个内存数据结构中的缓存数据执行的,在操作数据之前&#xff0c;都会将数据从磁盘加载到Buffer Pool中&#xff0c;操作完…

亚马逊店铺如何快速出单?

对于一个新开的亚马逊店铺而言&#xff0c;首先要做的就是想办法让自己的店铺快速出单&#xff0c;只有有订单了&#xff0c;才能够稳住局势&#xff0c;才能够让自己亚马逊店铺在市场上有立足的资本。 不过对于一个亚马逊店铺而言&#xff0c;要想做到快速出单是很难的&#…

一文读懂GPU显卡的10个重要参数

在当今的高性能计算机世界中&#xff0c;GPU显卡的性能至关重要。这一领域的快速发展&#xff0c;使得图形渲染、游戏体验、视频编辑等高性能计算任务变得更加高效和流畅。正因如此&#xff0c;选择一款合适的GPU显卡变得越来越重要。在挑选GPU显卡时&#xff0c;了解其关键参数…

uni-app:实现右侧弹窗

效果&#xff1a; 代码&#xff1a; <template><view class"container"><button click"showModal true">点击按钮</button><view class"modal-overlay" v-if"showModal" click"closeModal">…

没有软件怎么管理固定资产

在当今数字化的世界中&#xff0c;我们已经习惯了使用各种软件来管理我们的日常生活和工作。然而&#xff0c;当我们面临一个看似简单的问题——如何管理固定资产时&#xff0c;我们可能会感到困惑。那么&#xff0c;如果没有软件&#xff0c;我们该如何进行资产管理呢&#xf…

QT C++ 基于TCP通信的网络聊天室

一、基本原理及流程 1&#xff09;知识回顾&#xff08;C语言中的TCP流程&#xff09; 2&#xff09;QT中的服务器端/客户端的操作流程 二、代码实现 1&#xff09;服务器 .ui .pro 在pro文件中添加network库 .h #ifndef WIDGET_H #define WIDGET_H#include <QWidget>…

Netty—FuturePromise

Netty—Future&Promise 一、JDK原生 Future二、Netty包下的 Future三、Promise1、使用Promise同步获取结果2、使用Promise异步获取结果.3、使用Promise同步获取异常 - sync & get4、使用Promise同步获取异常 - await5、使用Promise异步获取异常 在异步处理时&#xff0…

uniapp - 倒计时组件-优化循环时间倒计时

使用定时器的规避方法 为了避免定时器误差导致倒计时计算错误&#xff0c;可以采用一些规避方法&#xff0c;比如将倒计时被中断时的剩余时间记录下来&#xff0c;重新开启定时器时再将这个剩余时间加到新的计算中。同时&#xff0c;为了避免定时器延迟&#xff0c;可以在每次执…

Golang 新手经常踩的坑

1、 Golang 新手经常踩的坑 1.1 前言 Go 是一门简单有趣的编程语言&#xff0c;与其他语言一样&#xff0c;在使用时不免会遇到很多坑&#xff0c;不过它们大多不是 Go 本身的设计缺陷。如果你刚从其他语言转到 Go&#xff0c;那这篇文章里的坑多半会踩到。 如果花时间学习官…

风向变了!智能汽车何以「降本」

随着软件定义汽车的概念逐步落地&#xff0c;以及底盘、动力、座舱、智驾、车身等不同域&#xff08;分布式或者混合式&#xff09;的功能更新迭代和融合&#xff0c;汽车行业正在意识到&#xff1a;底层硬件架构重构的迫切性。 事实上&#xff0c;早在2016年&#xff0c;作为传…

客户端发现pod并与之通信

客户端发现pod并与之通信 pod需要一种寻找其他pod的方法来使用其他pod提供的服务&#xff0c;不像在没有Kubernetes的世界&#xff0c;系统管理员要在用户端配置文件中明确指出服务的精确IP地址 或者主机名来配置每个客户端应用&#xff0c;但同样的方法在Kubernetes中不适用 …

富硒虫草肉丸系列新品上市—虫草可以“享”着吃

2023年9月4日&#xff0c;鸿祥食品(汕尾市)有限公司探索研发的富硒虫草肉丸全系列新品惊喜亮相&#xff0c;为消费者带来全新的滋补体验。五款以富硒虫草为主要原料&#xff0c;分别以猪肉、鲜虾、牛肉、墨鱼以及牛筋为新食品原料加工而成的虫草类健康新品--“享着丸”系列&…

yolov5运行过程遇到的小问题(随时更新)

1.关于git的问题 解决办法&#xff1a;插入下面代码 import os os.environ["GIT_PYTHON_REFRESH"] "quiet"2.页面太小无法完成操作 解决办法: 如果不好使再考虑降低Batch_Size大小或者调整虚拟内存可用硬盘空间大小&#xff01;&#xff08;调整虚拟内存…

公信力不是儿戏:政府与非营利组织如何利用爱校对提升信息质量

公信力是政府和非营利组织成功的基础&#xff0c;而这种公信力大多来源于对外发布的信息的准确性和可靠性。在这个方面&#xff0c;“爱校对”展现了它的强大能力和实用性。以下我们将具体探讨这两种组织如何通过使用爱校对来提升他们的信息质量。 政府&#xff1a;公开与透明&…

【strapi系列】strapi在登录时调用api/auth/local获取token接口一直报401、403、400错误的问题解决

文章目录 问题描述解决403 forbidden问题解决401 (Unauthorized) error问题调用认证接口需用注意的事项&#xff0c;解决400问题 问题描述 strapi在调用api/auth/local登录接口时&#xff0c;一直报403 forbidden 或 401 (Unauthorized) error问题。 这个接口的作用其实就是使…

02-Linux-IO多路复用之select、poll和epoll详解

前言&#xff1a; 在linux系统中&#xff0c;实际上所有的 I/O 设备都被抽象为了文件这个概念&#xff0c;一切皆文件&#xff0c;磁盘、网络数据、终端&#xff0c;甚至进程间通信工具管道 pipe 等都被当做文件对待。 在了解多路复用 select、poll、epoll 实现之前&#xff…

手写Mybatis:第19章-二级缓存

文章目录 一、目标&#xff1a;二级缓存二、设计&#xff1a;二级缓存三、实现&#xff1a;二级缓存3.1 工程结构3.2 二级缓存类图3.3 二级缓存队列3.3.1 FIFI缓存策略3.3.2 事务缓存3.3.3 事务管理3.3.4 修改一级缓存 3.4 缓存执行器3.4.1 执行器接口3.4.2 执行器抽象基类3.4.…

STM32CUBEMX_创建时间片轮询架构的软件框架

STM32CUBEMX_创建时间片轮询架构的软件框架 说明&#xff1a; 1、这种架构避免在更新STM32CUBEMX配置后把用户代码清除掉 2、利用这种时间片的架构可以使得代码架构清晰易于维护 创建步骤&#xff1a; 1、使用STM32CUBEMX创建基础工程 2、新建用户代码目录 3、构建基础的代码框…

uniapp制作——交友盲盒

在小程序端可以有很多好玩的小玩意&#xff0c;目前网上比较有趣的就是有一个交友盲盒&#xff0c;能抽出和找出对象的一个有趣的小程序&#xff0c;所以今天给大家带来用uniapp搭建的交友盲盒&#xff0c;大家再根据自己的情况去搭建自己的后端和数据库来完成自己的一个小项目…

实现Android APK瘦身99.99%

摘要&#xff1a; 如何瘦身是 APK 的重要优化技术。APK 在安装和更新时都需要经过网络下载到设备&#xff0c;APK 越小&#xff0c;用户体验越好。本文作者通过对 APK 内在机制的详细解析&#xff0c;给出了对 APK 各组成成分的优化方法及技术&#xff0c;并实现了一个基本 APK…