2019-2022·相机算法集成·总结概述

此前三年,我的工作内容主要是Android相机底层的算法集成。现在总算是离职了,换了一个方向工作,是时候回忆回忆我做了些什么、学到些什么。虽然级别很低,也没有竞业协议之类,但是出于道德还是需要注意信息保密。该总结涉及的框架不涉及细节、也不涉及具体的工作内容,仅大致了解即可。本文所有内容仅代表个人观点和看法,与其他任何集体或个体无关。

未来一段时间的工作方向和相机系统无关,没有去继续追求热门赛道,但是大方向还是有软件设计和C++等,也不算偏离得很远吧。

前言

在我前段时间的面试过程中发现,不少人对算法集成的工作可能比较陌生。算法集成可能会和SDK工程师有点像,但是我们不仅仅需要大致了解算法,还需要了解集成平台(系统)。了解算法是因为软件需要根据算法需求做一些输入数据之类的调整,以此达到尽可能好的效果或者性能。又比如我们主要在高通平台工作,那么就需要对高通平台的相机软件流程比较了解以便可以得到更好的性能以及效果,如果换到MTK平台,那么就需要重新熟悉MTK的系统。

一般来说,一个相机功能从开发到落地,参与的工程人员会有:算法、软件、调试、测评、测试、驱动。算法顾名思义,就是某个相机功能中涉及到的算法,比如超级夜景、HDR等等,当然不是所有功能都需要复杂算法参与,有些也可以直接硬件完成,一个功能也不仅仅一个单一算法就可以完成。算法集成扮演的就是其中的软件角色,因为算法是和平台“无关”的,算法提供出来是不能直接在手机上使用的,这时候就需要算法集成来将算法落地在手机上。调试角色一般叫做tuning,他们会负责一些3A(AE、AWB、AF)或者其他ISP模块的调试工作。测评和测试的区别在于,测评一般会有主观测评或客观测评,主观测评会由一些摄影师组成,由他们来评判一张照片的视觉效果(有主观因素也有一些客观数据),以便给算法、软件、调试指明优化方向。测试一般会测试系统功能、性能等,主要评判功能是否准备好以及是否存在稳定性、性能等问题。驱动在我们的工作中接触比较少,因为他们主要是在相机bring up的阶段中参与的,比如负责点亮sensor之类,相机功能的开始落地则是在bring up之后。

以下再简单总结各个角色:

  1. 算法:提供如超级夜景、HDR等功能的核心算法;
  2. 集成:将算法在对应平台落地;
  3. 调试:调整图片3A、ISP模块参数;
  4. 测评:评价成像效果;
  5. 测试:评价功能性能;
  6. 驱动:点亮sensor;

以上一般会有类似这样的关系:

https://bu.dusays.com/2022/09/17/6325bc2d71b40.jpg
角色关系

为了方便后续的阅读,再补充一些前提:

  1. 部分情况也需要平台商参与,因为部分代码是闭源的;
  2. 本总结主要针对拍照功能;

Android相机底层结构

我们不讨论什么HAL、Framework的框架,现在按照我的语言和认知来看看更小的部分,以此来了解Android相机拍照是怎么大致工作的。

在我看来,主要部分是:app、graph、feature、pipeline、node。(还有session、stage等概念,但是不影响对整体的认知,便不叙述了。)

app就是我们使用的相机app,把它当做一个客户即可,他负责发送指令和接收结果(实际不止这些),并且app和上述几个概念是隔离开的。从graph开始则是底层概念(当然app到graph之间不是这么简单,它们之间至少还有一层,会有各种调度逻辑之类的,不过现在可以省略他们,因为这对整体认知没有太大影响),可以把它当做就是描述整个数据流的,整个数据流要当做是一个单向链表,数据从哪来到哪去。graph下面则有feature的概念,feature可以当做是整个数据流程中相对独立的一小段,我认为feature是一个分类的概念。feature再下面的就是pipeline,数据实际上是在pipeline这个概念上流动的,feature相当于是一个pipeline的管理器,负责选择当前使用哪条pipeline。如上所述,feature是一些数据流小段的分类,同一个类别的功能可能有不同的数据流动过程,那么这些不同的流动过程就可以是不同的pipeline,但是他们又是完成相同或类似的功能,所以可以用一个feature来管理和调度他们。在pipeline之下,就是node的概念,node就是节点的意思,是数据处理的节点,可以认为是最基本的单位。

以上,从上到下,我们可以有以下的结构图:

https://bu.dusays.com/2022/09/17/632571cd8feee.png
从上往下

举个例子来说,比如我们执行了某个拍照功能,其中数据流是从node1 ~ node9,每个node执行了不同的功能。现在再将数据流分成一些小段,比如node1 ~ node3组成pipeline1,node4 ~ node5组成pipeline2,node6 ~ node9组成pipeline3,现在可以认为不同的pipeline又完成了不同的任务,同一条pipeline里面的几个node连接一起共同完成的这条pipeline的某个功能。又因为feature是pipeline的调度器,因此用不同的feature管理不同功能的pipeline,但是以上每个feature暂时只管理一条pipeline。feature之上则是graph,可以认为graph是最大的分类,对应的就是用户功能(比如夜景、HDR等等),用户通过app界面调用不同的功能则可能对应底层不同的graph。

那么,从下往上看,现在又有如下结构:

https://bu.dusays.com/2022/09/17/632571cca185f.png
从下往上

上面一段描述的是sensor出图后,数据的流动过程。我们在app上点击拍照到生成jpeg照片的数据流是怎样的?如下:

  1. 点击拍照按钮,生成一个request(包含一些参数信息等以及待填充的一块buffer);
  2. request流转到底层,根据其中包含的拍照模式等信息选择对应的graph;
  3. request送到对应的graph,先在数据流上从高级向低级流动,给每个feature、pipeline、node配置对应的参数(因为还没有图像数据,这时候仅做一些初始化或者配置工作);
  4. request从高级到低级一直走到最终的node层的最后一个node(一般是sensor node),开始通知sensor出图(现在有真正的图像buffer了);
  5. buffer从sensor出来经过ISP模块(也看做一个node),做一些硬件去噪、去坏点、颜色校正之类;
  6. buffer按照顺序经过数据流上的其他node,有些node可能会修改buffer,比如提亮、去噪、融合之类;(还记得前文说的吗?node是最基础的单位,数据实际上是在node上流动的,那么node之间怎么调度?那就是靠更高级的概念,比如以此是pipeline、feature、graph等。)
  7. buffer解码成app下发的格式,比如yuv;
  8. buffer填充到app下发的“待填充”buffer中;
  9. app显示buffer或者送去encode成jpeg保存;

以上是一次拍照的大致流程,但是实际情况也不是这么简单的。

比如“倒着”走的配置过程、feature/pipeline/node的初始化过程、出图过程、ISP处理过程、buffer分发过程、buffer合并过程、buffer encode成jpeg的通路过程、整个数据通路(比如A可以到B,A也可以通过C再到B,如何设计是需要思考的)等等,都是可以有很多工作和优化的。不过一次拍照的大致流程还是这样的。

算法集成

算法集成是我的本职工作。

算法集成和SDK工程师的区别可能就在于(总是拿这两者比较是因为我认为两者很相似),SDK更了解算法,算法集成则更了解平台,比如上述《app上点击拍照到生成jpeg照片的数据流》的几个过程中,算法集成工程师需要参与2~9的所有过程。

集成的一般开发过程和其他职业可能没有太大区别:确认需求、预研、上项目、debug、发布。

如果细分,一般可能的工作有:确认需求、在旧平台预研、告知算法平台可以提供的能力、获取SDK、和算法交涉(接口、性能、效果、协助测试)、在旧平台测试、和tuning交涉(效果)、和平台商交涉(bug、提问、需求)、新平台落地、代码移植、debug、发布等。(可以参考上文的关系图。)

因为集成是比较底层(叶子结点)的划分,所以每一个具体功能就当做我们的一个项目,比如前置夜景功能、xxx供应商的后置夜景功能、xxx供应商的HDR功能、xxx供应商的人脸检查功能等等。一般这个层次的集成项目可以由一人到多人完成,而且我所了解到的基本是一人完成。

对一个集成项目我们需要做什么?

首先是看是不是在已有的数据通路上工作。在上文中,我也详细介绍了什么是数据通路,按照我的总结,算法集成的核心工作就是设计和编写数据通路。

如果是数据通路以及满足集成需求,那么集成工作就会简单很多,一般就只需要在node层面工作就可以了,而且一般也只涉及到一个node。

如果数据通路不满足需求,那么就需要重新设计数据通路。首先会和核心算法确认好算法的输入输出结构,包括一些环境参数、图像格式等。确认好算法的输入输出需求后,需要和sensor、芯片厂商确认硬件输出能力,以便决定在哪个硬件端口输出或者输入数据。算法和硬件确认好后,可以设计第一版的数据通路,一般先从feature层面开始设计,因为对app来说,确定某个功能之后,graph就是确定的,因此可以认为没有选择的余地,graph层面需要做的,就是事先想好这个功能大概会有那些feature参与,然后将这些feature拼接成graph即可,可操作空间不太大。feature层面设计我认为是比较抽象的,一般来说,需要描述feature的输入输出端口,决定如何调用pipeline,但是并不关心更底层的数据流动,只关心feature这一层面的数据流动。feature内部数据如何流动,是pipeline层面关心的,完成feature的基本设计之后,需要设计pipeline的结构,主要内容就是node之间是如何链接的,有串联、并联之类的链接方式,完成某些功能可能有不同的连接方式或者工作层面(意思是,有些功能可以不需要pipeline参与),具体怎么做,除了按照算法、硬件、tuning等需求外,其余部分我认为是比较经验化的东西。pipeline设计完就是node的编写工作了,node是模板化的,主要职责是负责调用算法SDK或者处理图像,主要操作性在于代码结构和逻辑的优化。

以上就是新设计一条数据通路的大致工作。当然并不是每个feature、pipeline、node都需要重新编写,很多时候是考虑复用的。比如新增某个功能,可能只需要设计一个新的feature,一个新的pipeline,一两个新的node就行了,其余部分,复用已经写好的就行了。

我这三年在工作技术上最大的收获,就莫过于这种分层设计的思想了。到现在为止,我依然认为还有很多可以学习的地方,不只是看懂代码说怎么怎么分层就好了,我还缺少一点让我豁然开朗的契机。为什么我认为我的最大收获是这种分层思想?因为有两个相悖观点:

  1. 软件问题,中间加一层就好了
  2. 添加中间层只会让问题越来越复杂

对我来说,除了graph目前认为有点冗余外,feature、pipeline、node的设计就平衡了上述1、2的优点和缺点。是如何考虑到这样分层的,这是我需要长期思考的问题。其中更细节的,如node工厂的设计、调度器的设计等,我则认为是比较通俗的想法了。

(写到这里,思考上述分层设计,我想到的一个点是:分层和复用,又分层又复用,是不是可以降低相对复杂度了。)

以上,是集成开发的基本工作。更多的,还有数据通路的优化。现在补充一点预备知识:

  1. app和底层是两个不同的进程。(完整的相机功能是多进程参与)

那么针对数据通路的优化,可以考虑:

  1. 数据通路全部放在底层?
  2. 数据通路拆成两部分,一部分底层完成,一部分app进程完成?
  3. 数据通路拆成多部分,底层完成某些部分,app进程完成某些部分?
  4. 在这条数据通路上,底层和app的数据交互方式是一对一还是多对一?
  5. 数据一定要按部就班的沿着数据通路流动吗?
  6. 底层通路间或者同一条通路中各个节点的通信方式?
  7. 不同通路的性能、功耗问题?
  8. 其他

集成开发除了完成功能集成工作,也需要考虑功能的性能、功耗等优化问题。因此所涉及的部分就比较广了。

除了技术上的问题,因为需要接触不同职责的同事比较多,交流、交际能力也算得上是算法集成的一个比较重要的技能。因为需要给出“合理”的理由拒绝一些需求,或者给出一些“合理”的理由给其他同事发送一些需求。

从事相机算法集成的成长是什么?

  1. 可以了解某些soc平台,比如高通平台或者MTK平台
  2. 能够了解到硬件部分,比如sensor出图方式、ISP处理流程、DSP调用方式等
  3. 可以了解到相机整个通路和运作原理,包括APP到sensor的整个流程
  4. 可以了解到Android底层的一些原理,比如binder通信
  5. 可以更熟悉Linux平台
  6. 可以了解部分库的编译、链接原理
  7. 可以接触到一些项目管理、编译工具链的编写的过程(makefile、repo)
  8. 可以了解到一些图像处理过程,比如Demosaic算法、DRC、gamma、AWB统计原理等
  9. 可以提高代码能力,主要原因是,代码量不算少,项目周期也比较长,因此有时间来优化代码
  10. 可以提高大型工程的工程能力,算法集成设计的层面比较多,接触的代码量是比较大的
  11. 可以提高社交能力,因为会需要和产品、测试、算法battle

不足之处有什么?

  1. 杂而不精,正因为接触面很多,所以比较难对某一个方面有很深的耕耘,目前接触到的同事(基本在1~7年),没有一个是对某方面可以做到“倒背如流”的,都是“会”而不是“不仅会,还知道为什么,还知道还可以怎么做”的层次
  2. 技术不纯粹,因为接触的人多、交涉多,算法集成不是纯粹的技术流(当然对有些人来说这是优点,但是对我来说是缺点)
  3. 挑战性不高,类似于第一点缺点,因为没有某方面的深入,算法集成是很容易上手的
  4. 技术不前沿,虽然相机技术是前沿的,但是相机算法集成的技术并不新,反而很老套;我认为,只需要C with class阶段的知识就可以完成大部分工作,当然,如果要做得更好,那么也是需要广泛涉猎的,比如数据结构与算法(一些循环结构的优化、数组的优化、读写优化等)、设计模式(框架、node层面)、数字图像处理方面的内容;不过话说回来,上述技能树也是属于传统技能的
  5. 上限低,和技术不前沿有点类似,另一部分原因是,尽管算法集成的上限取决于soc、相机模组的上限,但是算法集成属于业务部门,并不承担研究任务(不会给研究资源),所以算法集成的上限变成了soc、相机模组等平台的下限,基本就是拿来能用就行了的状态
  6. 所接触的代码,很多闭源

当然,以上说的优点、缺点仅仅是个人体验, 并不代表所有类似的岗位,我认为这些体验是和个人规划、公司管理相关的。

工具开发

除了项目开发,我或者其他同事也有提议过一些其他的项目。比如看图软件、数据分析平台等。

我们开发平台主要是Ubuntu,该系统上目前是没有一款方便的看大部分Raw格式的看图软件的。我大概在2020年2月份意识到了这个问题,认为我们亟需一款方便的看图软件,不然每次还需要Windows电脑来看图。

最开始开发的是一个格式转换脚本,但是如果将脚本发布出去便十分用户不友好,遂考虑需要加上图形界面。因为该需求是个人想法,所以未占用项目时间,算作是业余时间开发。再确认需要图形界面后,便约上了大学的一位好友一起开发,由他帮助我完成了第一个带图形界面版本的UI部分。

第一个版本之后便由个人开发。修改了界面样式、参考pipeline的设计来设计了解码链、也有诸多软件上的加速优化,比如Demosaic加速、buffer池优化等。

因为最初想法是当做个人作品发布,在第一次发版之前也未占用项目时间。大约历时七个月,基本功能完成,可用之后,便以“集成组”的名义在软件大组发布了(自己YY太多了,如果以个人名义发布,又会当心被别人说闲话),之后就一直被当做“集成组”的项目,然后由我一直维护。我之后的一些遭遇也可能由此开始了吧~~~ (之后写点其他的项目,就被人阴阳我又在憋大招,或者被说,我自私,就想着做新东西 :-< )

先不管别人如何评价我,能发布一个软件我依然还是很高兴的,该看图软件也得到了大组和其他部门同事的好评,我认为这是我这三年来最大的收获之一吧,我认为是高于“青年工程师”等称号给予我的荣誉的。

在看图软件开发期间,有同事提出了另外一个需求,数据分析,这是算作团队的项目了。最初目的是用来可视化手机上一些sensor的数据(比如ois、gyro等)。

我最初想法是做成一个web应用,但是leader期望是桌面应用,只安排我一个人做。在考虑复杂度后,我还是决定以web应用发布。细节不表。该应用制作时间大概在6个月以上,之后一直由我维护,并且扩展接口供其他项目组使用。令我高兴的是,也有不少项目组接入了这个应用,并且用户人次也不少。

除了以上项目,也有一些其他自制小项目,比如测试工具,但是因为发布测试版本后没有得到其他同事的任何有效反馈,该项目便只有零星几个人在使用了。

未来工作中,我还是会期望继续保持对这些技术的热爱吧。通过这三年的经历(集成和工具开发),还是得学会一些社会现实。比如:

  1. 适当高调。因为刚工作太低调了,已经设定了这种人设,做很多事情,都考虑和别人分享成果,尽管他们可能什么都没做(只是同事,不是leader),这很容易导致自己受伤,因为别人会不断压低你的下限,它们会像蛀虫一样一直汲取
  2. 要让别人知道你做了什么,不要以为别人知道,因为他们可以假装不知道
  3. 学会表达。类似第一点,不会表达的人就会被当作老实人,老实人是只能被欺负的,除非他死了或者它死了。学会表达不一定要说的天花乱坠,但是要学会敢于把自己的想法说出来
  4. 不要把自己表现得像老实人(我在网络上可能才算正常人吧,现实工作中像一头牛,基本谁都能来抽我两鞭子,让我替他耕地)。摒弃老实人人设不止是学会表达,还要学会拒绝,以及偶尔的生气,这样才能让别人知道你的底线,而不是让别人毫无底线的压低你的底线
  5. 分清楚工作和个人,公司(表示公司以及公司里面的人)对个人是没有感情的,所以也不需要对公司有过多感情

大概就想到这些吧。也期望在未来的工作中,我能摆正自己的心态,心里要能更抗揍才好!


声明:本文采取 CC BY-NC-ND 4.0 协议,如与本站其他共享协议冲突,则以该声明为准。