自动驾驶数据闭环与工程化 (上)

来源:焉知智能汽车
2022-08-16
1270

前言

 

关于自动驾驶的软件架构,我之前有几篇文章《智能驾驶域控制器的软件架构及实现》,《中间件与SOA》。



这篇文章的重点不在于具体的软件架构,而在于软件架构的工程化落地。因为再好的架构,也需要变成真正可以稳定运行的产品才能体现其价值。而自动驾驶相关的软件系统关联了非常多的技术领域,每个技术领域有专门的知识体系,需要专门的人才,需要解决特定的问题。而完整的自动驾驶系统是需要所有相关的技术一起协同工作的,任何一个领域在技术或工程上的短板,都将影响整个系统的可用性、可靠性、安全性。


同时在研发组织上,也需要让不同领域的技术团队能够独立的开发、独立测试验证,最后还能集成在一起。那么如何拆得开、拆得好,还能整合起来,就是工程落地过程中至关重要的顶层设计问题。


第1章叙述闭环的抽象概念以及研发闭环的界定。阐述如何依据最小变量原理来拆解研发闭环,使得复杂系统的研发及集成过程可控。


第2章叙述产品概念上的闭环思路。自动驾驶作为跨多个技术领域的复杂产品,很难一步到位解决所有问题。本章从产品角度出发,提出纵横交叉的产品演进路线,使得自动驾驶系统工程化道路上的每一步的难度降低,也让整个系统能在早期就开始逐步集成,提高整体工程化成功的可能性。

 

第3章是本文的重点。目的是阐述数据驱动闭环的基本原理以及方法论。

 

这一章中,我们先给出数据概念模型及其分析,重点阐明语义层级的概念。然后我假设一个AEB 的技术方案,作为我们讨论的功能范围的锚定点。基于概念模型,分析这个技术方案涉及算法的语义层级分解。


我们再给出智能驾驶系统集成的一般原理,分别阐述“算法串接、执行平台、数据规模”三个集成纬度的各自概念和相互组合关系。并将这个一般原理应用到 AEB 方案的系统集成上,给出一个参考的 AEB 功能的系统集成路径。这个集成路径上涉及了大量的工具链,我们重点分析了其中的仿真测试工具链,并对不同的仿真和测试的手段做了对比,分析其各自的用途和侧重点。至此,用于数据驱动闭环的技术基本设施叙述完毕。在此基础上,我们论述了如何基于这些技术设施,利用大量数据从算法开发和测试验证两个方面驱动AEB产品完成工程化落地。虽然 AEB 功能实际对量产开发并不需要把所有上述过方法论严格实施。但是这个方法论是迈向更高阶功能的基础。我们以 Level 2.5 和 Level 4 为例阐明该方法论的进一步应用。


最后,我们分析了什么是自动驾驶的核心技术:“算法能力”和“工程化能力”缺一不可。目前自动驾驶技术在实际应用中在这两方面都遇到了很大程度的困难,于是采取了很多手段规避这些困难,从而衍生出各种弱化版本的自动驾驶场景。本章对这些场景在算法能力和工程化难度两个纬度做了分析和比较。一方面可以了解产业图景背后的技术原因,一方面也可以直观的理解建立工程化能力的难度及重要性。

 

本文为个人产品与研发经验的总结,不代表任何企业或组织的观点,仅仅分享出来供行业同仁参考。受个人水平与视野限制,文中难免有疏漏谬误之处,欢迎业内同仁指正,共同探讨。

 

01

闭环概念及研发闭环

 

我们每天都在跟闭环打交道,你的手指点击手机屏幕,手机系统将你选择的内容显示出来给你,这是一个交互闭环,在你使用手机的过程中,这个闭环会持续进行。算法分析你在某些短视频的停留时间,推断你的兴趣,就推送你可能喜欢的短视频,形成一个闭环。这个闭环持续运转,最终算法就会对你的兴趣点抓的死死的。

 

其实我们在产品设计、营销、研发等所有环节都会经常提到闭环。

 

产品设计研发、发布给用户、收集反馈进一步改进设计,形成闭环。

 

举行营销活动、发布广告、收集广告效果数据并分析,根据数据改进营销方式和广告设计、优选广告投放渠道,也形成了闭环。

 

深度学习算法训练过程中每一次梯度下降的迭代、损失函数计算偏离、反向传播后计算下一次梯度下降也是一个闭环。

 

敏捷开发中每一次两周的迭代,是一次 PDCA(plan-do-check-adjust)的闭环,每一次 PI (Program Increment) 包含了多次迭代,PI本身是一个更大范围的 PDCA闭环。

 

闭环概念广泛存在,我们来梳理其一般化的概念,来更好的理解和应用。

 

1.1.闭环的抽象概念

 

学术上,严谨的“闭环”概念是在控制论中出现的。较为抽象的定义是:当我们要准确控制一个系统的行为时,我们根据系统的输出来校正对它的输入,以达到较为准确的控制精度。因为系统的输出会导入到输入端的计算,形成一个不断往复的循环,称之为闭环(Closed Loop)。

 

图1 控制系统.png

图 1控制系统

 

通俗的例子,我们要把鼠标移到屏幕中的某一个目标位置,我们手在移动鼠标的过程中,眼睛观察当前位置与目标位置,大脑计算两者之间的偏差,控制手中鼠标指针移动的方向和速度。移动过程的前一段会快一些,到目标位置附近再做精细调节。眼睛观察到到的新的鼠标偏离会导入到大脑中用来计算手部移动的距离。不断循环这个闭环过程,只到鼠标到达目标位置。看起来很简单,但却是一个非常通用的过程。自动驾驶的车辆控制算法、远程导弹的制导都是类似的过程。

 

我们从这些例子中抽象出组成闭环的几个关键概念:闭环标的、偏离定义、反馈回路。

 

闭环标的

 

每一个“闭环”都有其“标的”。“标的”可以理解为这个闭环说需要达成的目标。也就是这个闭环的作用所在。比如“控制车辆按照规划的轨迹行使”,“正确识别图像中的人”。 


对与一个测试闭环,具体要测试的目标物就是这个闭环的标的。


对于一个研发过程的闭环,研发的目标就是闭环的标的。

 

偏离定义

 

每一个闭环都有一个从输入到输出的正向路径,输出结果与期望达到的结果直接有一个偏离,这个偏离需要能够被明确的定义。闭环多次迭代的目的就是要缩小这个偏离。

 

图2 闭环的属性.png

图 2 闭环的属性

 

反馈回路

 

反馈回路是根据正向路径的输出来修正输入的机制。

 

概念的抽象

 

熟悉控制理论和深度学习算法的人对这些概念会非常熟悉,比如在深度学习算法里关于这这些概念会被称作“损失函数”、“反向传播”之类的术语。本文的目的不是讲述具体的算法原理,而是自动驾驶系统研发的工程化。这个工程化的过程是由一系列闭环组成的。


这些闭环有的是某一个具体技术研发的过程,有的是测试的方式,有的是具体某个算法执行时的原理。但只要是某个特定过程形成了一个闭环,就都可以归纳出上述抽象概念,也能演绎出抽象概念在具体上下文语境中的具体体现。

 

1.2.研发闭环

 

如前文所述,“闭环”是一个很通用的概念,可以出现在很多领域,尤其在自动驾驶系统的研发和运行过程中处处可见。为了更准确的描述本文的话题,我们先澄清容易误解的概念。这里先区分“研发闭环”(Develop Closed Loop)与“运行时闭环”(Runtime Closed Loop)。

 

1.2.1.研发闭环与运行时闭环

 

我们用一个具体的例子来说明。


自动驾驶功能中比较基础的功能是“车道居中”,即在有车道线的情况下,保持车辆沿车道中心线行使。这个功能的关键技术是两项:车道线识别的视觉感知算法和横向车辆控制算法。

 

图 3 车道保持功能示意.png

图 3 车道保持功能示意

 

其“运行时闭环”就是:车辆行使过程中,摄像头采集前方道路图像,识别出车道线并转换成合适的坐标系,估算当前车辆在车道中的位置,控制算法接收车道线信息、车辆位置信息、车辆自身的速度、方向等信息,控制方向盘的角度或扭矩。整个闭环周而复始的运转,保持车辆居中。

 

这个运行时闭环的标的是“保持车辆居中”,闭环的偏离定义为车辆方向与车道中线方向的偏离,反馈回路是根据偏离计算出的横向控制指令再控制车辆状态发生变化。

 

而“研发闭环”是开发这个“运行时闭环”的研发、测试和集成过程。我们可以为上述运行时闭环设计两个独立的研发闭环,一个用于开发和测试感知算法,从视觉图像中计算车道线,并根据通过其它手段获(如仿真系统)得的车道线真值验证计算是否正确,这个研发闭环反复迭代,持续改进算法;另一个研发闭环之间获取车道线的真值,只是开发和测试横向控制算法。这两个研发闭环可以独立进行,最后集成在一起。

 

原型系统 vs 量产开发

 

如果只是要做一个原型系统,其实并不复杂。可以使用一个200万像素的前视摄像头(内置ISP)并做好相机内外参的标定,一台工控机(x86带 GPU), 一台带线控的车辆。车道线识别的卷积神经网络有很多论文和开源算法可供参考,单纯的横向控制做到 Demo 级别也相对比较容易。

 

但是如果是需要一个能安装到上万台车辆上的量产系统,事情就不那么简单了,可以想到的难点至少有:

·  摄像头的ISP性能在不同环境下都能输出较好的画质

·  车道线识别算法的准确度,尤其是在车道线不清晰、雨雪天气下

·  车道线识别算法对车道弯曲程度的识别是否准确

·  横向控制算法在不同车速情况下的稳定性

·  横向控制算法在不同弯道情况下的稳定性

·  从获取图像到控制指令被执行的延迟是否足够小,并且足够稳定

 

当整套系统移植到车载嵌入式平台后还有其它的工程难题:

·  要选择合适的能够执行深度学习算法的嵌入式处理器

·  深度学习的算法模型转换到目标嵌入式处理器,会不会缺失算子,会不会有精度损失,会不会执行延迟过长

·  深度学习算法要对特定的嵌入式平台做优化,是否需要减支,能否定点化,如何充分利用内存带宽,如何提高 Cache 命中率等等

·  支持视觉算法运行的软件架构如何设计,不同模块之间如何通讯

视觉算法运行的OS往往是个软实时系统,车辆控制算法往往运行在OS硬实时系统上,视觉算法极端情况下丢帧,控制算法如何容错

·  ECU 跟整车系统的电源管理、网络管理系统、诊断系统的对接

·  ECU 的更新升级,包括调试模式、工厂模式、OTA模式等等

 

这还没有算上硬件设计和测试的复杂性,硬件上还要考虑散热、抗震、电磁兼容性、耐久性等一系列问题。所以实现一套能量产的系统远比开发一个原型系统要复杂得多。开发原型系统只需要几个主要的工程师把算法跑通,就可以控制车辆在道路上进行演示。但这距离工程化量产还差得很远。

 

原型系统可以暂时忽略上述各种难点问题,在一个非常理想的工况上直接展示核心功能。原型系统的研发可以只使用一个大的涵盖所有环节的研发闭环,内置 ISP的摄像头,工控机上运行视觉算法可控制算法,最理想的道路环境,直接在车上进行调试。

 

研发闭环的拆解

 

但是对于工程化的量产开发,不可能把上述所有难点都在一个研发闭环内解决。而且不同的难点之间跨越的技术领域差距非常大。都是深度学习算法相关,但是用英伟达的GPU做算法训练跟在特定嵌入式平台做算法移植和优化,几乎是两个完全不同的专业。

 

所以在研发时需要把上述各自技术难点拆分在不同的研发闭环中进行。让每一项关键技术点能分别在一个研发闭环中进行独立的开发和优化。每个研发闭环的标的关注于尽可能少的技术点。

 

每个研发闭环独立演进、测试。最后集成在一起,构成完整的系统。一旦在整个系统运行中出现的问题,要能回溯到该问题所在的独立闭环中去复现、解决、再测试。然后再进行集成后的验证。这就是工程化。

 

工程化是对复杂系统按照合理逻辑进行分解,分解到合适规模的团队能够进行规范化分析、设计和研发,最后还能集成为完整系统的过程。最后体现出来,其重要特征是过程可重复、结果可预期、问题可追溯,流程自动化。

 

这边文章重点在是研发过程的工程化,所以是以研发闭环为基点进行讨论,每个研发闭环的标的往往是整体自动驾驶运行时闭环的某一个环节。还有可能某一个研发闭环,在运行时可能是开环 (Open Loop) 。

 

1.2.2. 最少变量原理

 

你可能看到过这么一个故事: 在一个大工厂,有一台大机器,突然有一天出了问题,停工不能生产了,请了好多修理工都未能修好,老板最后找到了最有名的机械师,他这边敲敲,那边敲敲,最后确定了一个位置,选准了一个角度,敲了一锤子,机器正常运转起来。然后开价“要一万元”。理由是:“抡一锤子只值一元,但在那里敲,什么角度,用多大力就值9999元,这就是技术的价值。”

 

且不论故事的真假,这个故事说明了一个现象,人们遇到问题的时候,希望能有一个技术牛人一锤子下去就解决问题,哪怕要为此付出一定代价。

 

我也遇到过很多次,自动驾驶的研发团队的二十多个人围着一辆车,不同技术部门、不同工种的人轮番上去,你试试、我试试,就是解决不了问题。团队的Leader 肯定也跟前文故事中的老板一样,希望能有这么一个牛人,上去就能指出来问题在哪里,应该如何整改。

 

但是故事之所以是故事,正因为它只是一个美好的愿望。

 

相比与这个故事,我更青睐另一个事实。A、B两地的电话线路断了,维修人员先到A、B的中点C,检测AC和BC是否能通电话。如果AC通畅而BC不通,则继续到BC的中点完成类似的检测。我们清楚的知道,无论AB距离多长,这个方法都能让我们以对数函数的形式快速收敛到故障点附近。

 

第二个故事完美的符合了工程化的特征:过程可重复、结果可预期、问题可追溯。我们知道一定能在某个有限时间内发现问题并解决问题。很多时候发现问题比解决问题难得多。而第一个故事更多是看运气了,能否有这么一个牛人、这个牛人能花多长时间解决问题,这次解决了,下一次能否解决,完全都是不可预期的。

 

事实上,当一个闭环系统中的可变因素超过达到两个及以上的时候,你就很难确定是哪个可变因素导致的问题。

 

当我们分析一个可变量时,我们应该尽可能隔离其它可变量的影响。所以拆解研发闭环时,我们希望每一个小闭环针对的只是整体问题域的一个单一可变量。就像故事二中,我们测试AC段时,不会受到BC段的影响。这往往就要求我们能够对其他可变量进行准确的控制甚至隔离,体现在具体应用中就是提供其它可变量的数据模拟(Mock)。自动驾驶研发中往往把这个称为各种形式的在环仿真。

 

1.2.3. 研发闭环之间的组成关系

 

一般我们对自动驾驶数据闭环的理解就是:驾驶专业采集车或者通过量产车辆采集数据,用来改进各种算法、发现并解决驾驶场景中出现的各种问题,改进自动驾驶系统,形成自动驾驶能力的闭环迭代。

 

这个理解本身并没有问题,但是这么复杂的系统,如果没有一个合适的层级分解,直接就整个复杂系统的进行讨论,那就是在讨论一个玄学问题,而不是一个工程问题。

 

一个大的研发闭环是由一系列相互关联的小闭环组成特定。有的是层级关系,大闭环内有小闭环,不同小闭环之间还有相互衔接的形式。我们来看具体的例子。

 

图 4 车辆控制算法开发闭环演进.png

图 4 车辆控制算法开发闭环演进

 

上图演示了车辆控制算法开发时4个可能的闭环(A,B,C,D)。

 

一般车辆控制算法做原型开发时会直接使用“闭环B”,在这个闭环中,控制算法是使用Model Based Development 模式开发,常用的是使用MATLAB+Simulink构建算法模型,模型生成的C代码会编译运行在一个快速原型设备(如:dSpace AutoBox),快速原型设备与车辆总线相连,通过车辆线控系统控制车辆运行。

 

旁注:关于Model Based Development。 这是一种低代码的开发方式。MATLAB 是数学分析软件,基本上对数学计算有密集需求的专业(如应用数学、自动控制等),从大学开始就是使用MATLAB内置的简便的脚本语言进行数学计算和显示。Simulink 作为 MATLAB的插件存在,提供可视化的编程环境。内置了各种现成的函数实现,使用者只需要将这些模块可视化的装配起来就可以实现自己的算法思想。基本上控制算法工程师从学校学习到实际工作都是这样进行的。这样他们可以专注于将控制理论应用到实际场景,而不必花太多时间在 C 语言编程上。

大多数精通C语言的车载嵌入式工程师,解决的是怎么跟操作系统、跟车辆控制器硬件打交道,对控制理论并不熟悉。毕竟每个领域都不简单,不是每个人都有足够的精力掌握跨领域的知识。

使用 MATLAB + Simulink ,就可以把控制算法工程师的建立的可视化算法(控制模型)自动转换成高质量的 C代码。这样就弥补了两个领域的鸿沟,让两个领域的专家可以专注自己擅长的工作。

Simulink 的可视化开发,本质上跟儿童编程语言 Scratch 的工作模式没啥区别,只不过提供了更专业的函数库和更丰富灵活的表达方式。如果有好事者,完全也可以为 Scratch 编写一个C代码生成器。

 

旁注:关于快速原型设备。使用 MATLAB + Simulink 编写的可视化模型最终是要转换为 C 代码再编译执行的。因为自动控制系统往往都是运行在嵌入式实时计算平台,所以这些 C 代码需要运行在一个 RTOS 系统上,通过各种现场总线与其它设备进行交互,这是嵌入式开发的领域,这就又要求学自动控制的学生也要精通嵌入式开发,精通两个领域难度就大了,毕竟时间只有这么多。

快速原型设备就是想解决这个问题。这个设备内置了自定义的标准嵌入式硬件,以及配套的实时操作系统。同时提供将 C 代码编译并运行在这个设备上的工具链。设备定型后,其输入输出能力也就确定了。既然控制算法工程师是使用 Simulink 进行工作,那这个设备的开发商也就很贴心的将对设备输入输出进行控制的能力,设计成在Simulink 上可以进行拖拽布局的模块。控制算法工程师就在Simulink 上使用这些模块与自己的算法部分进行对接。这样只需要全程在 Simulink 上工作就能够完成实际的控制动作了。而从模型转换为 C代码,编译、部署到这个标准嵌入式硬件的过程都由设备的开放商提供的工具自动完成。模型开发者不需要关心,只需要专注自己控制领域的技术就行。

量产的嵌入式设备是一般为特定项目专门设计,功能够用、性能满足的情况下,成本越低越好,更不可能去开发配套的 Simulink 模块。而快速原型设备可以堆砌很多接口,提供高性能的硬件,方便的开发工具链,目的是适用于尽可能广泛的项目,当然价格也贵。不过只需要小批量用于开发,成本就不是重要因素,方便才是最重要的。

有了这样的设备就解决了开发前期的问题,节省时间,让控制算法工程师可以不依赖其他工程师的工作而快速投入前期的原型开发,所以叫快速原型设备。

 

但是“闭环B”是需要实际的车辆的,当车辆未就绪或者资源不够时,我们可以先采用“闭环A”,这个闭环中我们使用软件仿真工具代替车辆,仿真软件可以配置模拟车辆(ego car)的动力学参数,给出ego car 周围环境的真值出道路环境的真值。这样控制算法开发人员可以脱离车辆,先在桌面进行开发,而且感知结果使用的是真值,相当于减少了可变量。

 

“闭环C”与“闭环A”的差别在于去掉了快速原型设备,使用一块MCU开发板来运行控制算法模型生成的 C 代码。这可以用来验证控制算法模型转换成 C 代码后是否正确。这个MCU应该就是我们量产时打算使用的芯片,但是量产硬件的开发还需要时间,我们可以使用厂家提供的开发板来快速搭建试验环境。可能导致不正确的原因至少有:

 

·  Simulink模型转换成 C 代码时有错误,可能需要对模型进行调整

 

·  MCU性能比快速原型设备差,出现之前(闭环A)没有发现的问题

 

·  模型生成的 C 语言代码在往 RTOS 集成时出现的 Bug. 这是将来在量产开发中会出现的问题,可以提前在这个阶段发现。

 

“闭环D”与 “闭环C”相比,用实车替换了仿真,相当于增加了真实的车辆动力学因素进闭环。

 

下表这四个闭环各自的标的,同时列出每个闭环的可变量与不可变量(真值)。


微信截图_20220816154048.png


我们可以看到, A 到 B 、A 到 C 两个过程,是分别增加一个不同的可变量(我们假设Smart Senor 给出的感知结果是可信赖的)。这两个可变量又一起出现在 D 中。

 

然而 闭环 D 还远不是终点,这4个关联到闭环主要是测试车辆控制算法。Smart Sensor 还需要替换成自研的感知算法,MCU开发板还要替换成量产的硬件。所以这四个关联闭环还只是更大范围闭环的一部份,需要被集成在更高一个层级的闭环中。

 

通过这个例子,我们可以看到一个研发闭环的几个特征。

 

研发闭环的独立性

 

每个研发闭环应该是可以独立运转的。能否独立运转也是我们在设计一个研发闭环时必须考虑的边界条件。在独立运转的前提下来我们尽量给闭环赋予最少的可变量,这样就可以专注于测试这个可变量本身的正确性。

 

然而为了形成闭环,某些数据或过程又是必需的。比如上面例子中的闭环A,我们需要要有一个模拟车以及车周围的环境信息。解决方法就是提供模拟数据。简单的模拟数据可以自己编造,这也是软件测试过程中的常用方法。自动驾驶系统的模拟数据比较复杂,往往需要通过仿真工具来实现。

 

所以,仿真工具的目的是为了给独立运转的闭环提供模拟数据(Mock Data)。不同目的的闭环,对仿真工具提供的模拟数据的要求是不一样的。比如上面的闭环A,只关心车辆动力学模型是否准确,以及车周围环境的真值数据,置于渲染的是否美观逼真,对这个闭环意义不大。

 

研发闭环的衔接与演进

 

我们在一个研发闭环中放入最少的可变量,是为了在测试这个可变量的时候隔离其它可变量的影响。但是我们的最终目标是要将所有的可变量集成到一个最大的闭环并能正常工作。

 

这个增加可变量的过程是逐步累积的,最好一次增加一个,每增加一个可变量,就形成一个新的闭环,正如上文例子中 “A演进到B”、“A演进到C”以及“B+C = D”的过程。这个逐步做加法的过程,相当闭环之间的衔接或者是原始闭环的演进。闭环演进的步子越小,就越容易发现问题出现在哪一步。

 

这个做加法的顺序反过来做减法,就恰恰是我们在出现故障时定位问题的方法。

 

我们能不能跳过ABC,直接到 D ,当然也是可以做到的。但是问题在于这么多可变量在一起集成,没有任何的前置验证,最终闭环D能正确运转的可能性很低。体现出来就是项目延期,不知道什么时候能完成。而且出了故障,没有测试环境可以用于隔离某个可变量进行诊断。

 

ABC三个闭环的存在,实际上也为闭环D提供了故障检测的技术手段。用数学的说法,ABC三个闭环的成功概率是闭环D成功的先验概率,有了这个先验概率,根据概率论的贝叶斯定律,我们是可以算出闭环D的成功概率的。

 

没有ABC闭环,闭环D的成功完全是靠运气了。还是那句话,我们需要的是工程上的确定性,不到万不得已不需要牛人力挽狂澜。我们要的是让一群普通人一开始就走在堂堂皇皇的王者之道上,而不是寄希望于某个多智近妖的人在混乱中“受任于败军之际,奉命于危难之间”,工程化不指望奇迹!

 

研发闭环的层级

 

研发闭环不仅有顺序的演进,还是有层级的。最大的一层闭环就是本节开头说的:驾驶专业采集车或者通过量产车辆采集数据,用来改进各种算法、发现并解决驾驶场景中出现的各种问题,改进自动驾驶系统,形成自动驾驶能力的闭环迭代。这个大闭环是由内层的多级子闭环组成的。

 

自动驾驶技术在工程上涉及很多完全不同的专业技术,感知算法与控制算法是完全不同的技术方向,感知算法中不同传感器的算法原理也完全不同,前融合与后融合的技术路线也是差异巨大。还有操作系统、中间件跟前面这些算法也没什么关系。

这些不同领域的技术完全可以在各自独立的闭环中进行开发和验证,最后一起装配成最大的闭环。根据各自技术领域的特点,各子闭环内部还能继续嵌套更小的闭环。单独一层的闭环甚至可以衍生出独立的产品,可以自己研发,也可以采购成熟的现有产品。

 

对闭环的层级分解实质上是划定出了组成完整自动驾驶系统各个子产品的可能边界。这个子产品边界的划分,可以让不同的团队并行的独立完成自己的开发活动,也可以通过采购成熟产品来加快整个系统的研发与建设。

 


(上)








版权声明

 本文作者为萧猛。本文的电子版本允许任何不以盈利为目的的使用、复制和再分发。要求在

分发过程中保留此版权声明的全文。否则视为对本文著作权的侵犯,将会承担相应的法律责任。以盈利为目的的使用请与本人联系。


收藏
点赞
2000