我知道,这意味着我存在:计算机视觉深度学习的回顾(第2部分)

我们继续理解现代魔术(计算机视觉)。 第2部分并不意味着您需要先阅读第1部分,第2部分意味着现在一切都变得很重要-我们想了解视觉中神经网络的全部功能。 检测,跟踪,分割,姿势评估,动作识别...最时尚,最酷的体系结构,数百个层次和数十个绝妙的主意已经在切切等待着您!



在上个系列中


让我提醒您,在第一部分中,我们熟悉了卷积神经网络及其可视化,以及图像分类和构建其有效表示(嵌入)的任务。 我们甚至讨论了人脸识别和重新识别的任务。

即使在上一篇文章中,我们也讨论了不同类型的架构(是的, 我生产一个月的平板电脑),而Google在这里并没有浪费时间:他们发布了又一种非常快速和准确的EfficientNet架构。 他们使用NAS和特殊的Composite Scaling程序创建了它。 查看这篇文章 ,这是值得的。


同时,一些研究人员制作了动画面孔,在电影中寻找接吻 ,我们将处理更多紧迫的问题。

人们在这里说:“图像识别”。 但是什么是“识别”? 什么是“理解(场景)”? 我认为,对这些问题的答案取决于我们到底想“识别”什么,以及我们到底想“理解”什么。 如果我们建立了人工智能,它将像人类一样有效(甚至更好)地从视觉流中提取有关世界的信息,那么我们就需要从任务和需求出发。 从历史上看,现代的“识别”和“场景理解”可以分为几个特定的​​任务:分类,检测,跟踪,姿势和面部识别,分割,视频上的动作识别以及文本图像描述。 本文将重点介绍列表中的前两个任务(ups,第三部分的破坏者),因此当前的计划是这样的:

  1. 如果可以找到我:对象检测
  2. 脸部侦测:未被发现-不是小偷
  3. 许多字母:文本检测(和识别)
  4. 视频和跟踪:在单个流中

让我们摇滚,超级巨星!

如果可以找到我:对象检测



因此,这项任务听起来很简单-给了一张图片,您需要在上面找到预定义类的对象(人,书,苹果,自流诺曼·诺曼·巴塞特-格里芬等)。 为了在神经网络的帮助下解决这个问题,我们以张量和机器学习的形式提出它。

我们记得彩色图片是张量(H,W,3)(如果我们不记得,那就是第1部分 )。 以前,我们只知道如何对整个图片进行分类,但是现在我们的目标是预测图片中感兴趣的对象(像素坐标)及其类的位置。

这里的关键思想是立即解决两个问题-分类和回归。 我们使用神经网络对坐标进行回归并对其内部的对象进行分类。

分类? 回归?
让我提醒您,我们正在谈论机器学习的任务。 在分类问题中, 标签充当对象的真实标签的质量,并且我们预测对象的类。 在回归问题中, 实数实数 ,我们可以预测数字(例如:体重,身高,薪水,在下一系列《权力的游戏》中死亡的角色数量...)。 更详细地讲- 欢迎参加DLSchool的第三届演讲(FPMI MIPT)

但是,一般来讲,对象的坐标可以以不同的方式形式化,在DL中,主要有三种方式: 检测 (对象 ), 姿势评估 (对象的关键点)和分割 (对象的“蒙版”)。 现在让我们谈谈精确预测边界框 ,点和分割的内容。


基本上,检测数据集用以下格式的框标记:“每个图片中每个对象的左上角和右下角的坐标”(此格式也称为左上角,右下角 ),并且大多数神经网络方法会预测这些坐标。


关于检测问题中的数据集和指标
设置任务后,最好查看可用于培训的数据以及用于衡量质量的指标。 这是我在深度学习学院第13堂讲座的前半部分(在x2.0上最多)所谈论的话题。

在深入研究神经网络的类型之前,让我们一起思考如何解决检测图像中任何东西的问题。 可能是,如果我们想在图片中找到某个对象,则可以大致知道它的外观以及应该在图像中占据什么区域(尽管它可以更改)。

从头开始进行检测
天真的和最简单的方法是简单地进行“模板搜索”算法:让图片为100x100像素,而我们正在寻找一个足球。 设一个20x20像素的球形图案。 以这个模板为例,我们将像遍历整个图片的卷积一样遍历它,计算逐像素差异。 这就是模板匹配的工作方式(通常使用某种类型的相关性来代替逐像素差异)。

如果没有模板,但是有一个神经网络分类器,那么我们可以这样做:我们将在图片中经过一个固定大小的窗口,并预测图片当前区域的类别。 然后,我们只说对象最可能的区域是分类器自信地回答的区域。 因此,我们可以解决以下问题:对象在外观上看起来有所不同(因为它经过训练可以在非常多样的样本上进行分类)。

但随后出现一个问题-图片中的对象具有不同的大小。 相同的足球可以在图片的整个高度/宽度内,也可以在目标上很远,仅占1000个像素中的10-20像素。我想编写“蛮力”算法:我们只是循环浏览窗口的大小。 假设我们有100x200像素,那么我们将转到2x2、2X3、3x2、2x4、4x2、3x3窗口...,3x4、4x3 ... ,执行(100-W_window)*(200-H_window)分类操作,这需要很多时间。 恐怕我们不会等到这种算法起作用。

当然,您可以根据对象选择最具特色的窗口,但是它也可以工作很长时间,而且如果速度很快,就不太可能准确-在实际应用中,图像中对象的大小会发生疯狂的变化。

此外,我有时会依赖于2019年1月对检测区域进行的新审查 (图片也会来自此)。 如果您想快速了解检测中的DL,那么这只是必须阅读的内容。

有关使用CNN进行检测和定位的第一篇文章之一是Overfeat 。 作者声称,他们首先使用神经网络在ImageNet上进行检测,从而重新构造了问题并改变了损耗。 顺便说一下,这种方法几乎是端到端的(以下是Overfeat方案)。


下一个重要的体系结构是FAIR的研究人员于2014年发明的基于区域的卷积神经网络RCNN )。 其本质是,它首先预测了很多所谓的“感兴趣区域”(RoI),在其中可能存在对象(使用“选择性搜索”算法),然后将其分类并使用CNN来优化框的坐标。


没错,这样的管道会使整个系统变慢,因为我们通过神经网络运行了每个区域(我们确实进行了数千次前移)。 一年后,同一个FAIR Ross Girshick将RCNN升级为Fast-RCNN 。 这里的想法是交换选择性搜索和网络预测:首先,将整个图片传递给经过预先训练的神经网络,然后我们在骨干网发布的特征图上预测感兴趣的区域(例如,使用相同的选择性搜索,但是还有其他算法 )。 它仍然相当慢,比实时要慢得多(就目前而言,我们假设每张图片的实时时间少于40毫秒)。


速度受CNN的影响最大,但不受盒生成算法本身的影响,因此决定将其替换为第二个神经网络-区域提议网络( RPN ),该网络将被训练以预测物体感兴趣的区域。 这就是Faster-RCNN的出现方式 (是的,很明显他们很久没有想到这个名称了)。 方案:


然后, R-FCN的形式有了另一个改进,我们将不做详细讨论,但是我想提到Mask-RCNN 。 Mask-RCNN是一个独特的神经网络,它是第一个同时解决检测和实例分割问题的神经网络-它预测边界框内对象的精确遮罩(轮廓)。 她的想法实际上很简单-有两个分支:用于检测和分段,您需要同时为这两项任务训练网络。 最主要的是要标记数据。 Mask-RCNN本身与Faster-RCNN非常相似:主干是相同的,但最后有两个“头” (通常称为神经网络的最后一层 )来完成两个不同的任务。


这些就是所谓的两阶段 (或基于区域的 )方法。 与之并行,在DL检测- 一阶段法中开发了类似物。 这些包括神经网络,例如:单发检测器(SSD),一次只看一次(YOLO),深度监督对象检测器(DSOD),感受野块网络(RFBNet)以及许多其他神经网络(请参见下面的地图,来自此存储库 )。


与两阶段方法不同,一阶段方法不使用单独的算法来生成框,而只是为卷积神经网络生成的每个特征图预测几个框坐标。 YOLO的行为类似,SSD稍有不同,但思想是相同的:1x1卷积可从接收到的特征图预测深度上的许多数字,但是我们预先同意它的含义。



例如,我们根据特征图预测大小为13x13x256的特征图为13x13x(4 *(5 + 80))个数字,在深度上我们预测4个盒子的85个数字:序列中的前4个数字始终是盒子的坐标,第5个-对拳击的信心和80个数字-每个类别(分类)的概率。 这是必要的,以便随后将必要的数字提交给必要的损失并正确地训练神经网络。


我想提请注意以下事实:检测器的工作质量取决于提取特征(即主干神经网络 )的神经网络的质量 。 通常,此角色是由我在上一篇文章中提到过的一种架构(ResNet,SENet等)扮演的,但有时作者会提出自己的最佳架构(例如YOLOv3中的Darknet-53)或修改(例如, 功能金字塔池) (FPN))。

同样,我注意到我们同时对网络进行分类和回归训练。 在社区中,这称为多任务损失:多个任务(具有某些系数)的损失总和显示为一个损失。

领先的多任务损失新闻
Machines Can See 2019上,其中一位演讲者同时使用多任务丢失功能完成了7个任务,卡尔 。 事实证明,最初将某些任务设置为彼此抵消,并获得了“冲突”,这使得网络无法像单独训练每个任务那样学习得更好。 结论:如果您使用的是多任务丢失,请确保这些相同的多任务不会与语句冲突(例如,预测对象的边界及其内部分段可能会相互干扰,因为这些东西可能依赖于网络内部的不同符号)。 作者通过为每个任务添加单独的“挤压和激励”块来规避此问题。

最近,出现了2019年的文章,其中作者使用基于点的盒子预测在检测任务中声明了更高的速度/准确性比。 我正在谈论文章“以点为对象”“ CornerNet-Lite”ExtremeNet是CornerNet的修改。 看起来现在可以将它们称为使用神经网络进行检测的SOTA(但这并不准确)。


如果突然我对探测器的解释仍然显得混乱而难以理解,那么在我们的视频中,我会慢慢地讨论它。 也许您应该首先看到它。

下面我给出了用于检测的神经网络表,其中包含代码链接和每个网络芯片的简要说明。 我试图只收集那些真正重要的网络(至少是它们的想法),以便对当今的对象检测有一个好主意:

神经网络检测器(两阶段)
年份文章关键思想代号
2013-2014神经网络感兴趣区域的生成以及其中的类的神经网络预测咖啡
2015年快速rcnn首先将图片通过网络,然后生成感兴趣的区域咖啡
2016年Faster-rcnn使用RPN生成感兴趣的区域火炬
2016年流式网络全卷积方法而不是生成感兴趣区域咖啡
2017年遮罩RoI-Align可同时解决两个任务的两个“负责人”TF凯拉斯
2019年推理通过构造对象的语义关系图来提高RCNN的质量---



神经网络检测器(一级)
年份文章关键思想代号
2013-2014夸大其词首批神经网络检测器之一C ++(带有其他语言的包装器)
2015年固态硬盘现在在许多应用中使用的非常灵活的一阶段方法火炬
2015年尤洛与SSD类似的想法正在并行发展,并且同样受欢迎(有新版本)C ++
2016年YOLOv2(又名YOLO9000)YOLO的许多改进火炬
2017年YOLOv3YOLOv2的许多改进火炬
2017-2018DSOD深度监管理念和DenseNet理念咖啡
2017-2018射频网根据人类视觉系统( RF块)的结构巧妙地选择卷积滤波器火炬



神经网络检测器(其他)
年份文章关键思想代号
2018年视网膜网解决特殊的焦点损失问题凯拉斯
2014-2015SPP模块,可让您有效处理不同尺寸的图像凯拉斯
2016-2017FPN具有特征金字塔,可以更好地检测不同大小的物体张量流
2019年NAS-FPN通过神经架构搜索找到最佳的FPN张量流
2019年代达罗斯如何通过对抗攻击破坏探测器---



神经网络检测器(基于点)
年份文章关键思想代号
2019年中心网一种新的检测方法,可以快速有效地解决同时发现点,盒子和3D盒子的问题火炬
2019年角落网基于对角点的盒子预测火炬
2019年CornerNet-Lite加速角网火炬
2019年至尊网预测对象的“极端”点(几何上精确的边界)火炬


为了了解每种架构的速度/质量之间的关系,您可以查看此评论或其更流行的版本

架构很好,但检测主要是一项实际任务。 “没有一百个网络,但是至少有一个正在运行”-这是我的信息。 上表中有指向代码的链接,但是我个人很少遇到直接从存储库启动检测器的情况(至少是为了进一步部署到生产环境)。 通常,为此使用一个库,例如TensorFlow对象检测API(请参阅本课程实际部分 )或来自中大研究人员 。 我提请您注意另一个超级表(您喜欢它们,对吗?):

用于运行检测模型的库
职称作者内容描述实施的神经网络构架
侦探Facebook AI研究具有各种模型代码的Facebook存储库,用于检测和评估姿势所有地区Caffe2
TF对象检测APITensorFlow团队许多模型可供使用(已给出权重)所有基于区域的SSD和SSD(具有不同的主干)张量流
暗流特里乌即用型YOLO和YOLOv2实现YOLOv3以外的所有YOLO类型(经过修改)张量流
毫米波检测开设MMLab(香港中文大学)PyTorch上的大量检测器, 请参阅他们的文章除YOLO系列外,几乎所有型号火炬
暗网(已修改)亚历克斯YOLOv3的便捷实现,对原始存储库进行了许多改进YOLOv3C ++


通常,您只需要检测一个类别,但是特定且高度可变的对象。 例如,检测照片中的所有面孔(用于进一步验证/对人计数),检测整个人(用于重新识别/计数/跟踪)或检测场景中的文本(用于OCR /翻译照片中的单词)。 通常,此处的“常规”检测方法在一定程度上会起作用,但是这些子任务中的每一个都有其自己的技巧来提高质量。

脸部侦测:未被发现-不是小偷



由于面部通常只占据图像的一小部分,因此此处会出现一些特殊性。 另外,人们并不总是看着相机,通常只有从侧面才能看到面部。 最早的人脸识别方法之一是2001年发明的基于Haar级联的著名的Viola-Jones检测器。


那时的神经网络还不流行,它们的视野还不那么强,但是,良好的旧手工方法就可以了。 几种类型的特殊滤镜已被积极使用,这有助于从图像及其符号中提取面部区域,然后将这些符号提交给AdaBoost分类器。 顺便说一句,这种方法确实可以正常工作,现在它足够快并且可以使用OpenCV开箱即 。 该检测器的缺点是只能看到在摄像机前面展开的面部。 一个人只需要转一下就可以了,这破坏了检测的稳定性。

对于更复杂的情况,可以使用dlib 。 这是C ++-一个在其中实现许多视觉算法(包括用于面部检测)的库。

在人脸检测的神经网络方法中, 多任务级联CNN(MTCNN)MatLabTensorFlow )尤其重要。 通常,它现在已被积极使用(在同一facenet中 )。


MTCNN的想法是顺序使用三个神经网络(因此称为“级联” )来预测面部的位置及其奇异点。 在这种情况下,脸上恰好有5个特殊点:左眼,右眼,嘴唇的左边缘,嘴唇和鼻子的右边缘。来自级联的第一个神经网络(P-Net)用于生成人脸的潜在区域。第二(R-Net)-改善接收框的坐标。第三个(O-Net)神经网络再次回归盒子的坐标,此外,还预测了面部的5个关键点。该网络是多任务的,因为解决了三个任务:框点的回归,每个框的人脸/非人脸分类以及人脸点的回归。此外,MTCNN实时完成所有操作,即每个图像所需的时间少于40毫秒。


如何,您仍然不自己阅读ArXiv的文章?
在这种情况下,如果您已经在卷积网络中有了一些背景知识,那么我建议您尝试阅读有关MTCNN原始文章本文仅占5页,但列出了理解该方法所需的所有信息。尝试一下,它会被延迟:)

在现代技术中,可以注意到双镜头面部检测器(DSFD)FaceBoxesFaceBoxes具有在CPU(!)上快速启动的能力,而DSFD具有最佳质量(于2019年4月发布)。DSFD比MTCNN更为复杂,因为在网络内部使用了一个用于改善特征的特殊模块(具有扩展的卷积),其处理的两个分支以及特殊类型的损耗。顺便说一下,通过膨胀的卷积,我们将在下一部分有关分割的文章中多次遇到。以下是DSFD的示例(令人印象深刻,不是吗?)。


要学习如何识别人脸,请不要忘记阅读本系列上一篇文章,我其中进行了简要介绍。

许多字母:文本检测(和识别)



注意上面的照片。很容易看到,如果您预测平行于坐标轴的边界框(就像我们之前所做的那样),结果将是非常差的质量。例如,如果我们要将这些框提交给识别神经网络的输入,这非常关键,该输入将根据图片预测文本

在这种情况下,通常会预测旋转的边界框,或者如果文本是弯曲的,则甚至将文本限制为多边形而不是矩形(以下示例)。旋转箱子的预测例如由EAST检测器处理



EAST检测器的思想不是预测盒子角的坐标,而是预测以下三件事:

  1. 文本分数图(在每个像素中查找文本的可能性)
  2. 每个盒子的旋转角度
  3. 每个像素到矩形边框的距离

因此,这比检测更让人联想到分段(突出显示文本掩码)的任务。arxiv文章的解释性图片



文本识别(及其检测)的任务非常流行,因此有类似的东西:TextBoxes ++Caffe)和SegLinks,但是我认为EAST是最简单且负担得起的。

在检测到文本之后,我想立即将其提供给另一个神经网络以识别它并产生一个字符串。在这里,您会注意到模态的一个有趣变化-从图像到文本。您完全不必担心这一点,因为一切仅取决于网络体系结构,在最后一层上准确预测的内容以及所使用的损耗类型。例如,MORANPyTorch 代码)和ASTERTensorFlow代码) )完全应付任务。



它们没有什么超自然的东西,但是可以非常熟练地同时使用非常根本不同类型的神经网络:CNN和RNN。第一个用于从图片中提取特征,第二个用于生成文本。有关MORAN的示例的更多信息:下面是其识别网络的体系结构。


但是,尽管EAST旋转了框,但可识别的网络仍会收到矩形图片,这意味着其中的文本可能会占据所有空间。为了使识别器更容易从图片中直接预测文本,您可以按某种方式进行转换。

我们可以将仿射变换应用于输入图像以拉伸/旋转文本。这可以使用空间变换网络(STN)来实现,因为它可以独立学习这种变换并且可以轻松地集成到其他神经网络中(顺便说一下,您可以对任何图片进行对齐,而不仅仅是对文本进行对齐)。以下是STN之前/之后的示例。


在这里详细讨论STN是没有意义的,因为在Habré上有一篇很棒的文章(感谢作者,这张照片是从那里拍摄的)和PyTorch代码

但是MORAN(用于文本识别的同一个神经网络)甚至更智能-它不仅限于仿射变换族,而且可以预测输入图像每个像素的x和y 位移图,从而实现任何可改善识别网络的学习的变换。这种方法称为“ 校正”,即使用辅助神经网络(整流器校正图片以下是亲和力转换后和校正后图像的比较:


但是,除了“模块化”地进行文本识别的方法(检测网络->识别网络)外,还有一种端到端的体系结构:输入是图片,输出是检测并且在其中识别文本。 所有这一切都是一个单一的管道,可以同时学习两个任务。 在这个方向上,存在着令人印象深刻的工作,即使用统一网络( FOTS进行快速定向文本点选PyTorch 代码 ),作者还注意到端到端方法的速度是“检测+识别”的两倍。 下面是FOTS神经网络图,RoiRotate块扮演着特殊的角色,因此有可能从网络“投射梯度”以在神经网络上进行识别以进行检测(这实际上比看起来复杂得多)。


顺便说一下,每年都举行ICDAR会议,并安排了几场比赛以识别各种图像中的文本。

当前检测中的问题


在我看来,现在检测中的主要问题不是检测器模型的质量,而是数据:它们通常很长且标记起来很昂贵,尤其是在需要检测许多类的情况下(顺便说一下,有一个解决 500个类的示例 )。 因此,现在有许多作品致力于“综合”生成最合理的数据并“免费”获得标记。 以下是 从Nvidia 毕业的文凭的照片,该 文章专门处理合成数据的生成。


但仍然很高兴,现在我们可以确定在图片中的位置。 例如,如果我们要计算框架上的物体数量,则足以检测到该物体并给出箱子数量。 在检测人员方面,通常的YOLO效果也很好,只是主要的是提交大量数据。 相同的Darkflow也是合适的,并且在几乎所有主要检测数据集中都可以找到“人类”类。 因此,如果我们要使用摄像头来计算一天中路过的人数或某人在一家商店中拿走的商品数量,则只需检测并给出数量即可...


停下 但是,如果我们要从相机中检测每个图像中的人物,则可以在一帧或两帧中计算他们的人数-不再,因为我们无法确切地说出哪个人在哪里。 我们需要一种算法,使我们能够计算视频流中的唯一身份用户。 它可能是一种重新识别算法,但在视频和检测方面,不使用跟踪算法是一个罪过。

视频和跟踪:在单个流中


到目前为止,我们仅谈论图片中的任务,但是最有趣的事情发生在视频上。 为了解决对动作的相同识别,我们不仅需要使用所谓的空间成分,还需要使用时间成分,因为视频是时间上的图像序列。


跟踪是图像检测的模拟,但用于视频。 也就是说,我们要教导网络预测的不是图片中的装箱,而是时间上的小轨迹(本质上是一系列箱子)。 以下是显示“尾巴”的图像示例-视频中这些人的踪迹。


让我们考虑一下如何解决跟踪问题。 假设有一个视频及其第1帧和第2帧。 让我们考虑到目前为止,只有一个物体-我们跟踪一个球。 在第1帧,我们可以使用检测器进行检测。 在第二个上,我们还可以检测到一个球,如果单独存在,那么一切都很好:我们说前一帧的拳击与第2帧的拳击相同。 您还可以继续查看pyimagesearch视觉课程 gif下方的其余帧。


顺便说一句,为了节省时间,我们无法在第二帧中启动神经网络,而只是从第一帧中“切出”球框,并在第二帧或逐像素中寻找完全相同的相关性相关跟踪器利用这种方法,如果我们处理诸如“在空荡荡的房间中在摄像机前面跟踪一个球”这样的简单情况,它们就会被认为是简单的或差不多可靠的。 此任务也称为可视对象跟踪 。 以下是一个人的示例的相关跟踪器的工作示例。


但是,如果有多个检测/人,则需要能够匹配第1帧和第2帧中的框。 我想到的第一个想法是尝试将盒子与具有最大交叉区域( IoU )的盒子匹配。 的确,在多次检测重叠的情况下,这样的跟踪器将变得不稳定,因此您需要使用更多信息。


使用IoU的方法仅依赖于检测的“几何”符号 ,也就是说,它只是尝试通过帧上的接近度来比较它们。 但是我们可以使用整个图像(在这种情况下甚至是两个),并且可以利用以下事实:在这些检测中有“视觉”符号 。 另外,我们还有每个人的检测历史,这使我们能够根据速度和运动方向更准确地预测他的下一个位置,这可以有条件地称为“物理”标志


2016年, Simple Online and Realtime Traker(SORT)Python代码 )发布了首批完全可靠且能够应对困难情况的实时跟踪器之一。 SORT不使用任何视觉信号和神经网络,而仅估计每个框上每个框的许多参数:当前速度(分别为x和y)和大小(高度和宽度)。 框的长宽比始终是从该框的首次检测中得出的。 此外,使用卡尔曼滤波器预测速度(在信号处理领域中通常情况下它们很好并且很轻),通过IoU建立盒子的相交矩阵,并通过匈牙利算法分配检测结果。

如果在您看来数学已经变得很多,那么在本文中,所有内容都将以一种易于使用的方式进行解释(这是中等:)。

早在2017年,SORT的一种修改就以DeepSORTTensorFlow的代码 )的形式发布了。 DeepSORT已经开始使用神经网络来提取视觉符号,并使用它们来解决冲突。 跟踪的质量不断提高-它被认为是当今最好的在线跟踪器之一。


跟踪领域的确在积极发展:有具有暹罗神经网络的 跟踪器和具有RNN的跟踪器 。 请随时注意,因为在任何一天,甚至可能会出现(甚至已经出现)更加准确,快速的架构。 顺便说一句,在PapersWithCode上关注此类内容非常方便,始终有指向文章和代码的链接(如果有)。

后记




我们真的经历了很多,学到了很多。 但是计算机视觉是一个极其广阔的领域,我是一个非常固执的人。 这就是为什么我们会在本周期的第三篇文章中看到您(这是最后一篇?谁知道...),在那儿我们将更详细地讨论分段,姿势评估,视频动作识别以及使用神经网络从图像生成描述。

PS:我要特别感谢Vadim Gorbachev在编写本文和上一篇文章时提出的宝贵建议和意见。

Source: https://habr.com/ru/post/zh-CN458190/


All Articles