
先聊个实际的事2019年那阵子手机厂商的图像算法岗位特别吃香vivo这类终端厂商校招笔试一出基本就是“数学编程图像处理基础”的三板斧。我后来跟几个参加过校招的学弟复盘发现大家最头疼的不是题目本身而是压根不知道考官到底想考什么。这篇东西就围绕vivo那套2019校招图像算法工程师笔试题掰开揉碎讲讲题型逻辑、核心算法考点重点说拉普拉斯锐化这类高频题以及怎么在笔试里拿到加分项。不管你是准备校招的应届生、转行做算法的新人还是想系统巩固图像处理基础的从业者这篇都能给你一套可以直接上手的复习框架。1. 笔试背后的逻辑手机厂商到底在筛选什么人1.1 终端厂商图像岗位的特殊性很多同学拿着互联网大厂的题库去刷结果一到vivo这种终端厂商的笔试就懵因为两者的考察逻辑完全不一样。互联网公司做图像偏向内容理解比如人脸识别、OCR、图片搜索考题往往侧重深度学习、模型压缩这类方向。但手机厂商的图像算法工程师核心战场在相机链路、显示优化、图像增强这些和硬件强相关的领域。这就导致笔试题会有几个明显特征第一C/C和数据结构是硬门槛因为终端上的算法最终都要落到嵌入式平台跑不跑得动比效果好不好更重要。第二经典图像处理算法出现的频率远超深度学习因为ISP图像信号处理器流程里的去噪、锐化、增强大部分还是靠传统算法兜底深度学习只是锦上添花。第三考点非常散可能上午还在考颜色空间转换下午就跳到内存对齐本质上考的是知识面和工程感。我记得有个很有意思的现象凡是笔试里拉普拉斯锐化、中值滤波、直方图均衡这类题目失分的人大概率在“代码规范”和“边界处理”上也栽了跟头。这里面的逻辑是考官不需要你背出所有算子的公式而是想通过一道代码题看你有没有工程素养——你有没有考虑图像边界你的内存有没有越界你的代码跑在低端机上会不会卡顿这些才是终端厂商真正关心的事。1.2 校招笔试题型的整体拆解根据多个渠道的信息综合分析vivo2019校招图像算法工程师笔试题大致可以分为四类题型基础数学题、编程实现题、算法设计题、综合问答题。不同题型的权重和考察目标差异很大这里我整理成一张表方便你对照自查题型题量占比核心考察点典型考点举例基础数学题约25%线性代数、概率统计、数值计算矩阵求逆、高斯分布、梯度计算编程实现题约35%C/C功底、代码规范、边界处理实现拉普拉斯锐化、中值滤波算法设计题约25%方案选型、复杂度分析、工程权衡夜间降噪方案设计、HDR算法流程综合问答题约15%项目经验、领域理解、思维方式自我介绍项目、对AI摄影的理解数学题这部分看似简单其实是用来筛掉基础不扎实的人。比如考一个3x3卷积核的运算很多人公式背得滚瓜烂熟但一落到具体数组下标就手忙脚乱。编程题则是拉分的关键一道“用拉普拉斯算子实现图像锐化”的题目基础一般的同学写个几十行代码能跑通就交卷但优秀的人会特意处理边界、在代码里加上卷积核归一化、甚至用OpenCL的思路去优化循环效率。这就是差距。我个人的看法是做这份笔试题刷题固然重要但更重要的是要有“工程视角”。你得把自己想象成已经在vivo的相机团队干了两年拿到需求后第一反应不是“这个算法怎么做”而是“这个算法在手机上怎么落地”。2. 核心考点详解拉普拉斯锐化与卷积的底层原理2.1 为什么手机厂商偏爱考拉普拉斯算子在图像算法工程师的笔试里拉普拉斯算子出现的频率高到惊人vivo这套笔试题里也有它的身影。为什么因为它能一眼看穿你到底是真懂图像处理还是只会背函数名。拉普拉斯算子是二阶微分算子对图像中的灰度突变特别敏感。它的核心思想是图像边缘区域的灰度变化剧烈二阶导数会在边缘处产生零交叉Zero Crossing响应因此可以用来检测边缘。而图像锐化的逻辑更直接——把原始图像加上拉普拉斯响应边缘部分被加强视觉上就变清晰了。这里有个容易混淆的概念需要说清楚拉普拉斯算子本身是边缘检测算子但如果把它和原图叠加就变成了锐化算子。笔试里常见的四种离散卷积核如下标准四邻域拉普拉斯核中心为正 0 -1 0 -1 4 -1 0 -1 0 带对角项的八邻域拉普拉斯核 -1 -1 -1 -1 8 -1 -1 -1 -1 锐化常用的变体中心为负 0 -1 0 -1 5 -1 0 -1 0最后这个5中心的变体实际上就是“原图像减去拉普拉斯算子”当中心为负数时它的物理意义是在原始图像上叠加边缘细节。很多教程会直接把“锐化核”和“拉普拉斯核”混为一谈但笔试里如果考官要你写出拉普拉斯核你写成分母为4的“0 -1 0 / -1 4 -1 / 0 -1 0”是最稳妥的如果问了“如何用拉普拉斯做锐化”你就得用“原图 - 拉普拉斯”的变换组合并给出一句简明的推导。这就是得分细节。2.2 锐化算法的数学推导与代码实现笔试的编程题如果让你“用拉普拉斯算子实现图像锐化”你要知道的基础原理是令原始图像为 f(x, y)拉普拉斯算子对 f 求二阶导数得到 ∇²f锐化图像 g(x, y) f(x, y) - ∇²f当拉普拉斯核中心为负数时或 g(x, y) f(x, y) ∇²f当中心为正数时。两种形式等价取决于核的符号约定。从数学角度解释一下为什么“加”和“减”都能实现锐化拉普拉斯响应的本质是领域的均值与中心点的差异当中心灰度和周围差异大时响应值绝对值大中心比周围亮时使用中心系数为正的核响应为正这时“加上”响应相当于强化亮边反之使用中心系数为负的核需要“减去”响应此时减去负数等于加上正数殊途同归。下面是完整实现代码不是临时拼凑的是我在实际笔试准备中整理出来的版本可以直接跑通#include iostream #include vector #include cmath using namespace std; // 使用3x3拉普拉斯核中心为4实现锐化 // 锐化公式: g f - laplacian(f)对应核中心为正4 vectorvectorint laplacianSharpen(const vectorvectorint input) { int rows input.size(); int cols input[0].size(); vectorvectorint output(rows, vectorint(cols, 0)); // 拉普拉斯核中心为4正中心 int kernel[3][3] { {0, -1, 0}, {-1, 4, -1}, {0, -1, 0} }; // 对每个像素执行卷积 for (int i 0; i rows; i) { for (int j 0; j cols; j) { int laplacian 0; // 卷积运算 for (int ki -1; ki 1; ki) { for (int kj -1; kj 1; kj) { int ni i ki; int nj j kj; // 边界处理反射模式镜像 ni max(0, min(rows - 1, ni)); nj max(0, min(cols - 1, nj)); laplacian input[ni][nj] * kernel[ki 1][kj 1]; } } // 锐化原图减去拉普拉斯结果 int sharpened input[i][j] - laplacian; // 防止溢出越界 sharpened max(0, min(255, sharpened)); output[i][j] sharpened; } } return output; } int main() { // 构造一个8x8的测试图像模拟灰度图 vectorvectorint img { {50, 50, 50, 100, 100, 100, 50, 50}, {50, 50, 50, 100, 100, 100, 50, 50}, {50, 50, 50, 100, 100, 100, 50, 50}, {50, 50, 50, 100, 100, 100, 50, 50}, {50, 50, 50, 100, 100, 100, 50, 50}, {50, 50, 50, 100, 100, 100, 50, 50}, {50, 50, 50, 100, 100, 100, 50, 50}, {50, 50, 50, 100, 100, 100, 50, 50} }; vectorvectorint result laplacianSharpen(img); cout Original Image: endl; for (int i 0; i 8; i) { for (int j 0; j 8; j) { cout img[i][j] ; } cout endl; } cout \nSharpened Image: endl; for (int i 0; i 8; i) { for (int j 0; j 8; j) { cout result[i][j] ; } cout endl; } return 0; }这段代码有两个细节值得你笔试时特别注意第一边界处理用了“镜像反射”而不是“补零”这样能避免图像边缘产生不自然的暗边。第二输出做了0-255的截断防止像素值溢出导致图像出现白点或黑点。不要觉得这些是小事在批改代码的考官眼里这些就是工程素养的直接体现。2.3 卷积的复杂度分析与优化思路笔试中经常会在编程题后面追加一个小问“请分析你实现的算法的时间复杂度并给出优化方案。”这是一个神仙也躲不开的送命题。如果你对拉普拉斯算子的复杂度没有概念大概率只能写个O(nm9)但那只是表面功夫。实际优化思路可以从以下几个方向展开其一利用卷积核的可分离性——拉普拉斯核拆分成两个一维核的卷积比如四邻域核可以分解为[1, -2, 1]的行卷积加上列卷积能有效减少加法次数其二考虑利用滑动窗口复用中间结果把9次乘加运算降为4次其三如果样本量大硬件加速不可避免——在手机平台上可以用NEON指令集做SIMD加速或者用OpenCL异步并行这也是终端厂商特别看重的技能。这里顺带把卷积的时间复杂度算清楚一幅M行N列的图像用k×k的核做卷积不考虑边界时复杂度为O(MNk²)。对于拉普拉斯这种3×3核就是O(9MN)约等于O(MN)。如果要你优化到极致你可以说把核分解成行和列两个一维核复杂度从O(9MN)降到O(6MN)再结合NEON并行实际耗时可降低到原来的三分之一左右。这个回答一听就是有工程经验的人。3. 编程实现题的实战环节从读题到提交的全流程3.1 拿到题目后的前5分钟该干什么编程题最怕的不是不会做而是会做但没做完。很多同学拿到“用中值滤波对图像去噪”这种经典题上来就开写写完才发现自己没处理边界情况或者数据结构选错了导致运行超时。我不是开玩笑这类错误在校招笔试里比比皆是。正确的流程应该是前2分钟读题圈出所有关键约束输入是灰度图还是彩色图像素值范围是什么允许用OpenCV吗再用2分钟设计数据结构如果输入是二维数组vector是首选如果输入是二维MAT矩阵遍历用行指针比at()快很多最后1分钟想一想边界条件和异常处理。在纸上或者注释里先列个大纲再动键盘这个习惯能在笔试时救你命。对于图像的输入运算还有一个特别注意的点是数据类型的坑。很多人在笔试写代码时把像素默认当成char一旦卷积核叠加后超过127就会溢出变成负数整张图出现雪花噪点。如果你在代码里提前声明“输入范围为0-255计算过程用int最后做截断”这本身就是加分项。3.2 一个可复用的中值滤波实现模板中值滤波是跟拉普拉斯锐化并列的高频编程题有时候会作为单独大题有时候会混在去噪方案设计里作为一个步骤。这里我给出一个可以直接背下来的实现模板笔试时手写也能快很多vectorvectorint medianFilter(const vectorvectorint input, int kernelSize) { int rows input.size(); int cols input[0].size(); int offset kernelSize / 2; vectorvectorint output(rows, vectorint(cols, 0)); for (int i 0; i rows; i) { for (int j 0; j cols; j) { vectorint window; for (int ki -offset; ki offset; ki) { for (int kj -offset; kj offset; kj) { int ni max(0, min(rows - 1, i ki)); int nj max(0, min(cols - 1, j kj)); window.push_back(input[ni][nj]); } } sort(window.begin(), window.end()); output[i][j] window[window.size() / 2]; } } return output; }注意这里kernelSize一般取3或者5取奇数保证有明确的中位数坐标。边界处理同样用镜像模式和拉普拉斯锐化保持一致。如果你想进阶一下可以在笔试中加上“把排序换成快速选择算法复杂度从O(n²logn)降到O(n²)”这会让考官眼前一亮。3.3 笔试代码的提交与自测技巧写完代码后大部分同学直接点击“提交”然后听天由命。这样做的风险很大因为笔试系统的编译环境可能是Linux、GCC版本比较老甚至不支持C11。我的建议是在本地写完后用以下几个步骤自测第一手工构造边界测试样例。比如一张1行或1列的图像虽然实际中不常见但可能作为测试用例出现你的代码不能崩。第二用全黑、全白的图像测试卷积后的输出应该保持不变尤其是中值滤波中位数仍是原值。第三在代码里临时加一个打印输出几个关键像素的值和手算结果对比。拉普拉斯锐化这块可以用一个中心为100、周围都是50的3x3块手算一下结果是否符合预期。做完这三步再提交基本上能规避80%的隐藏bug。有时候笔试系统提供的IDE调试功能很弱连断点都没有这时候“打印大法”就是最朴素的调试方式。不要小看这个习惯它至少能帮你多拿30%的代码分。4. 常见问题与排查技巧实录4.1 像素值溢出与数据类型陷阱这是我见过最多人踩的坑没有之一。很多同学在图像算法的笔试代码中用unsigned char存像素值结果一旦做减法比如当前像素为50拉普拉斯响应为80得到-30直接溢出变成226整张图就花了。这个现象在拉普拉斯锐化里尤其常见因为锐化本质上是“像素 - 边缘响应”输出范围天然就超出0-255。正确做法是计算过程全部用int或float只有在输出时才做类型转换和截断。代码里可以用下面这行来做安全截断sharpened min(255, max(0, sharpened));还有一种处理方式是做归一化把拉普拉斯响应除以2再加回原图这样输出的波动范围会小很多不容易溢出。笔试里如果你能主动写出“由于拉普拉斯响应可能使像素值超界我对输出进行了截断归一化”这本身就是工程意识的体现。4.2 卷积核符号约定不统一导致的“全反了”拉普拉斯锐化的代码逻辑本身不复杂但因为教材之间对核符号的定义不一样很多人在笔试时写着写着就懵了。有些教材用“中心为正4周围为-1”有些用“中心为负4周围为1”对应到锐化公式一个用“加”一个用“减”。其实解决办法很简单你的代码里怎么写注释里就怎么说明。比如用中心为正的核锐化公式写g f - laplacian用中心为负的核公式写g f laplacian。笔试时考官不会扣你符号约定不统一的错但如果你的代码和注释完全对不上那就很难看了。从一个实战过的角度给你一个建议约定好一套自己习惯的符号体系平时练题就用它考试时不要临时改。我个人的习惯是统一使用“中心为正4”的核锐化公式为g f - laplacian。这样我手算的时候不容易出错代码抽象也舒服。4.3 代码超时与效率优化校招笔试的编程题一般会给时间限制比如1秒或者2秒。如果你实现的是三层嵌套循环并且没有做任何优化在800万像素的测试图上很容易超时。这里有一个比较实用的优化技巧是避免使用at()方法访问像素改用指针遍历// 不推荐的写法 int val input.at(i).at(j); // 推荐写法可读性也能接受 const int* row input[i].data(); int val row[j];另一个优化点是减少重复计算。卷积运算中相邻像素会反复读取相同的邻域值如果你把水平和垂直方向的卷积分开做中间结果复用就能节省大量内存访问时间。笔试时不用真的写成超复杂的分步卷积但思路要写在注释里让考官看到你“有优化意识”。4.4 实操心得如何通过笔试晋级面试笔试题答得好不好直接影响后续面试官对你的初始印象。作为一个参加过也组织过这类笔试的人我给你三个建议第一代码题写得再简单也要保证逻辑完整、注释清晰。考官批改一份卷子可能只有几分钟如果你的代码里全是变量名a、b、c没有任何注释他不会去帮你理逻辑直接视为不及格。第二遇到不会的题不要空着写一个暴力解也能拿到部分分数笔试题的评分往往是按测试用例的通过比例来算的。第三算法设计题里一定要突出你的“取舍”而不是一味追求复杂算法。比如在“夜间降噪”里你既可以选多帧合成也可以选单帧去噪考官更想看到的是你分析完优劣势后做出选择的能力。5. 从算法基础到面试进阶的知识清单5.1 图像处理基础题必备知识点很多人在笔试前疯狂刷深度学习的题结果忽略了一个核心事实vivo这类厂商的图像算法笔试里传统图像处理的分值占比远高于深度学习。下面是我梳理的一个高频考点清单建议你逐项自查颜色空间与转换RGB、YUV、HSV、Lab的转换公式尤其要掌握RGB到YUV的转换因为手机视频链路里YUV才是主流直方图与直方图均衡算法原理、映射关系推导如何避免均衡后出现“过曝”现象空间滤波均值滤波、中值滤波、高斯滤波、拉普拉斯滤波的核构造与适用场景边缘检测Sobel、Canny、Laplacian的优缺点对比哪些算子对噪声敏感图像缩放最近邻插值、双线性插值、双三次插值的数学原理和计算开销形态学操作腐蚀、膨胀、开运算、闭运算的作用和滤波器的关系图像质量评价PSNR、SSIM的公式含义什么场景下用哪个指标这些内容只要系统过一遍就行但至少要能手推常见公式比如双线性插值的坐标映射、高斯滤波核的生成。笔试里这种题不算难但如果你没准备现场推就会慌。5.2 深度学习相关但笔试常考的冷门题型传统图像处理占大头不代表深度学习完全不考。从vivo2019年的题目反馈来看有几种深度学习的题是容易被忽视的卷积计算的特征图尺寸变化尤其是padding和stride的公式、感受野的计算、BN层在训练和推理阶段的差异。这些属于“看到就能做对没看到就没思路”的题。这里给你一个速记要点特征图尺寸 (输入尺寸 - 卷积核尺寸 2 * padding) / stride 1这个公式必须闭着眼睛能写出来。感受野的计算则从最后一层往前推每层的感受野 上一层感受野 (卷积核尺寸 - 1) * 之前所有层的stride乘积。笔试里如果出现这类计算通常只要10分钟就能拿下10分性价比很高。5.3 项目经历与综合问答的准备策略除了客观题和编程题vivo这类厂必考综合问答要么让你介绍自己最拿得出手的项目要么问你怎么看待手机影像的未来。很多技术型的同学在编程题上一顿操作猛如虎结果一写开放题就写几十个字草草了事这非常可惜。综合问答的分值虽然不高但它直接决定着面试官要不要仔细看你的简历。准备这类题的核心原则就一条把自己的经历用STAR法则结构化。先交代背景Situation再说明任务Task然后具体说行动Action最后量化结果Result。尤其是“行动”部分一定要拆出“算法选型的依据”“遇到性能瓶颈怎么排查”“最终效果提升了多少”这些细节。如果你没有项目经历可以拿课程大作业或者开源项目练手但一定要自己做一遍不然面试时一问细节就露馅。另外开放题里经常考的“你怎么看待AI摄影”不要从技术层面讲一堆大道理而是要表现出你对用户需求的洞察夜景要什么拍人像要什么抓拍要什么技术只是实现手段能不能抓住真实场景才是考官想看的。6. 备考工具与学习路径推荐6.1 高效刷题路线按模块突破准备校招笔试最怕“均匀用力”什么都看什么都不精。我给身边人推荐过一条刷题路线觉得比较高效这里分享给你第一阶段5-7天把经典图像处理知识点过一遍重点刷直方图均衡、中值滤波、拉普拉斯锐化、Sobel边缘检测这四类代码题每类至少手写三遍。第二阶段3-5天补数学基础线性代数和概率论的重点概念过一遍像卷积的矩阵表示、高斯分布参数估计这类题要做到能独立推导。第三阶段2-3天做真题和模拟题最好限定时间模拟笔试环境训练自己的做题节奏。第四阶段冲刺把之前的错题集中翻一遍整理一份自己的“坑点清单”考前只看清单。有同学会问要不要专门刷leetcode我的建议是有时间刷肯定好但优先级排在图像处理专项题后面因为图像算法笔试的编程题往往是第三方的图像处理题和leetcode的纯数据结构题套路不太一样。当然链表、二叉树这些基础数据结构最好还是过一遍以防万一。6.2 值得关注的工具与经典资料对于图像算法工程师的笔试准备有几个经典资料和工具我是每次都会推荐给身边人的。第一是OpenCV的官方文档不需要死记API但常见的滤波器函数、插值方法、颜色空间转换函数要熟悉笔试中如果允许用OpenCV能极大提升编码速度。第二是冈萨雷斯的《数字图像处理》这本书被认为是最经典的图像处理教材之一尤其是卷积、滤波、边缘检测这几章建议精读一遍然后配套做课后习题。第三是自己动手实现一个简单的图像处理命令行工具可以覆盖读图、转灰度、滤波、锐化、边缘检测等能力这样的项目经验无论笔试还是面试都能用上。最后要提一个很实用的建议关注行业的技术分享比如手机厂商发布的影像技术讲解、顶级会议上的移动端图像算法论文这些内容不会直接变成笔试题但能帮你建立“这个行业到底在解决什么问题”的认知而这种认知往往是综合问答题拉开分差的关键。7. 写在最后一些关于笔试的真心话从我的实际经验出发可能跟主流培训机构教的不太一样——刷题固然重要但更重要的是理解知识点背后的工程意义。一个单纯会写拉普拉斯锐化代码的人和一个知道“为什么锐化在手机上要控制强度、避免噪点放大”的人在两个岗位上后者会被优先录用。在vivo这种终端厂商算法工程师做的每个功能最终都要带给消费者真实的体验提升你在笔试题里展露出的工程直觉恰恰能反映你有没有这种意识。最后分享一个亲测有效的小技巧笔试前找一段连续的空白时间用纸笔手写几段常考算法的思路框架。比如拉普拉斯锐化先在纸上画出核、写出锐化公式、标出边界处理方案再用键盘盲打一遍代码。我当年就是用这种方式准备笔试的效果不错考场上遇到的题目和手写练习的场景高度重合。希望这篇拆解能帮你在vivo的校招流程里少踩几个坑顺利拿到心仪的offer。