计算机视觉算法面试准备:从图像基础到3D几何全指南

发布时间:2026/9/17 19:32:04
计算机视觉算法面试准备:从图像基础到3D几何全指南 简介这是由求职者整理的计算机视觉算法面试复习PDF笔记按照面试常见考点梳理了数据结构与算法、深度学习和CNN并延伸到Linux与网络知识。数据结构部分覆盖稳定/不稳定排序、回溯法与分支定界法、多种搜索策略及哈希冲突处理深度学习部分总结了加速收敛手段、Batch Normalization原理、感受野和feature map计算、Xavier与He初始化方法CNN部分整理了其适合图像应用的原因、不适用场景以及模型效果不佳时可检查的损失函数、batch size、激活函数、学习率、优化器等角度。文档还补充了Linux查看文件权限、shell配置、进程状态、自旋锁等命令与概念以及TCP断开时客户端状态变化等网络基础知识。资源为1个PDF文件大小294KB共700人浏览学习适合正在准备计算机视觉或深度学习算法面试的读者作为考前快速回顾和查漏补缺的提纲。1. 计算机视觉算法面试准备先把“算法”拆成三张卷子“计算机视觉算法面试准备”这个标题真正要准备的远不只是LeetCode手撕。和纯后端面试不同视觉岗位要考的是三张卷子图像信号与几何算法的原理卷、深度网络结构推导与训练细节的模型卷以及现场白板写代码的工程卷。第一张卷子常被新人忽略却在五年以上经验的问题里反复出现因为透视几何、对极约束、特征匹配这些内容靠背模型真没用。第二张卷子既考记忆又考推导比如为什么BatchNorm要放在ReLU之前、ResNet的恒等映射解决的是什么。第三张卷子是筛人的硬门槛常见题目包括NMS、IoU计算、卷积前向、以及目标匹配时的匈牙利算法要求十分钟内写出边界正确、复杂度最优的版本。适合把这份准备当成“考研 竞赛 日常开发”三重复习来做的是准备社招或校招视觉岗位的算法工程师、科研人员以及想要转行视觉方向的工程同学。这篇内容不会覆盖所有深度学习模型而是按面试里出现频率从高到低拆解成可执行的准备路径。2. 高频图像处理与特征点算法第一轮筛选的送分题2.1 卷积、边缘检测与降噪必须能手写实现面试官喜欢从最基础的图像处理问题切入因为这是评估代码功底和信号处理基础的快速方式。第一类问题是“写一个3x3的高斯模糊”第二类追问是“高斯核里σ与半径的关系”第三类是“Sobel算子为什么能检测边缘”。前两年还允许用OpenCV函数调用现在社招面试通常要求现场实现通用卷积函数再验证输出尺寸和边界处理。以下是一个最朴素的二维卷积实现用Python写清楚步长、填充和边界策略import numpy as np def conv2d(img, kernel, stride1, padding0): # img: [H, W], kernel: [Kh, Kw] if padding 0: img np.pad(img, padding, modeedge) H, W img.shape Kh, Kw kernel.shape out_h (H - Kh) // stride 1 out_w (W - Kw) // stride 1 out np.zeros((out_h, out_w)) for i in range(0, out_h * stride, stride): for j in range(0, out_w * stride, stride): out[i // stride, j // stride] np.sum(img[i:iKh, j:jKw] * kernel) return out这段代码的逻辑很简单先做padding再按stride滑动窗口每次做逐元素相乘后求和。面试时被问到的坑有三个。第一是边界效应用edge模式比补零在降噪任务中更稳因为不会人为制造暗边第二是输出尺寸公式(H - Kh 2P) / stride 1如果无法整除多数框架选择向下取整但面试官期望你主动说明第三是性能问题三层for循环显然太慢后续可以用im2col或者FFT优化但面试中更看重实现正确性。2.2 特征点算法SIFT与HOG的面试问法图像处理基本面过了之后面试官会直接跳到特征点算法。这里几乎不让你手写完整SIFT但会问“SIFT为什么对大尺度变化鲁棒”或者“捕获关键任务中的核心思想”。2.2.1 SIFT的高斯差分金字塔SIFT的关键在尺度空间构造用高斯金字塔的相邻层做差分DoG再在三维邻域里找极值点。面试中要说清楚两点高斯模糊的σ逐层加倍DoG响应能近似归一化拉普拉斯算子因此对尺度变化稳定。如果被追问“为什么用DoG”可以回答因为LoG的离散近似需要双重滤波DoG用两个相邻尺度相减计算量更小且结论相似。2.2.2 HOG在行人检测中的应用HOG的核心是局部梯度方向直方图。很多面试者能背出block和cell的尺寸却说不清为什么对光照变化鲁棒。原因是每个cell的直方图会做归一化而且梯度本身对亮度整体偏移不敏感因为像素梯度计算的是差分值。如果被追问“HOG和CNN特征的区别”可以点出HOG是手工设计方向不变的局部特征CNN则能学习更高级的语义模式。这些基础算法在面试里的权重不高但答不好会直接影响后面题目。准备时建议把每个算法拆成“一句话原理 关键参数 与后续任务的关系”三点比如SIFT的关键参数是尺度数、每层模糊σ的初始值以及与RANSAC匹配结合时的阈值。2.3 透视几何基础单应矩阵与相机投影近年面试对透视几何的考察频率明显提升尤其是做过三维重建或SLAM方向的岗位。最基础的问题是“单应矩阵描述什么运动”其次是“内参矩阵与外参矩阵各包含什么”。2.3.1 单应矩阵的适用场景单应矩阵描述的是同一平面在两个视图之间的映射关系适合用于二维码识别、文档拍照矫正、特征点匹配后的对齐。面试题常常这样出现给你两张同一平面拍摄的照片求它们之间的变换矩阵。解法是找到至少4对匹配点用直接线性变换DLT解齐次方程再用RANSAC去除误匹配。import cv2 import numpy as np # pts_src, pts_dst 是特征匹配后的对应点至少4对 H, _ cv2.findHomography(pts_src, pts_dst, cv2.RANSAC, 5.0) transformed cv2.warpPerspective(img, H, (width, height))参数5.0是RANSAC的误差阈值单位是像素表示重投影误差小于5个像素才认为是内点。这个值根据场景变化特征点数量充足时建议调小到3.0否则内点太少。如果两张图之间发生了纯旋转或所有特征点都落在同一个平面上单应矩阵依然有效但要注意此时它无法处理视差。2.3.2 内参矩阵与外参矩阵另一个高频题是相机模型。内参矩阵K包含焦距fx, fy和主点cx, cy再加上畸变系数。外参矩阵R|t把世界坐标转到相机坐标。面试官可能让你推导从世界点到像素点的完整变换[u,v,1]^T K[R|t][X,Y,Z,1]^T。这里要清楚最后一步除以Z是透视除法把归一化坐标转到图像平面。为什么这些内容在“算法面试”里重要因为很多视觉任务的核心就是几何反推。比如自动驾驶的SGBM深度估计就是通过双目视差和标定好的内外参反算深度。从三张卷子的角度看这张卷子考察的是“算法工程师是否理解图像是怎么形成的”如果只懂深度学习这部分会明显露怯。3. 深度学习网络中的手撕算法与训练细节模型卷的夺分点3.1 手动实现卷积层前向传播且给出复杂度分析Networks是视觉算法面试的重头戏但面试官普遍不考你怎么调torch.nn.Conv2d而是让你写一个不依赖框架的卷积前向更进一步是计算浮点数计算量。常见的难度递进是普通卷积 → 分组卷积 → 深度可分离卷积。以下是一个带多重循环的卷积前向先确保思路正确def conv2d_forward(x, weight, bias, stride1, padding0): # x: [N, C_in, H_in, W_in] # weight: [C_out, C_in, kH, kW] N, C_in, H_in, W_in x.shape C_out, _, kH, kW weight.shape if padding 0: x np.pad(x, ((0,0), (0,0), (padding,padding), (padding,padding)), modeconstant) H_out (H_in 2*padding - kH) // stride 1 W_out (W_in 2*padding - kW) // stride 1 out np.zeros((N, C_out, H_out, W_out)) for n in range(N): for co in range(C_out): for ci in range(C_in): for i in range(H_out): for j in range(W_out): h_start i * stride w_start j * stride out[n, co, i, j] np.sum(x[n, ci, h_start:h_startkH, w_start:w_startkW] * weight[co, ci]) return out如果说一个3x3卷积的输入特征图是HxWxC那么单次乘加次数是9C_outH_out*W_out。这个复杂度的计算在面试中常常被问到比如“1x1卷积的参数量是多少”回答是C_out * C_in因为空间维度是1x1没有跨像素聚合。另一个必考参数是感受野三层3x3卷积和一层7x7卷积的感受野都是7但参数量分别是27C^2和49C^2所以三层小卷积的参数量更少中间层同时提供更多非线性。3.2 BatchNorm位置剖析与训练推理差异在视觉模型的面试里BatchNorm几乎是必问的。面试官会抛出这几个问题为什么BN可以加速收敛训练和推理时bn的均值方差分别怎么用BN应该放在激活函数之前还是之后对于第三个问题实践中最常见的是Conv → BN → ReLU的顺序尤其是ResNet和DenseNet的官方实现。原因是BN需要统计每个通道的分布而ReLU之后会产生大量零值导致均值方差失真。如果把BN放在ReLU之后等价于对稀疏特征做标准化效果往往更差。但个别任务上把BN放在激活之后也能工作面试时说出这个理由并对比实际表现即可。训练时的BN按当前batch计算均值方差然后使用滑动平均累加全局统计量。推理时直接用保存好的全局均值和方差不需要再依赖batch大小。这个差异在高分辨率图像推理、batch size设为1时会暴露出来很多人把模型切到eval时发现输出变了就是因为忘记切换BN模式。3.3 损失函数与优化器的参数表模型卷除了网络结构还会考察损失函数的设计。面试官可能给一个具体场景比如“遮挡严重的目标检测应该用什么损失变体”回答是CIoU或GIoU因为普通IoU损失在重叠为零时梯度消失GIoU通过最小包络矩形构造可导数项。类似这样的问题很多我整理一个高频参数表用于集中复习算法模块常见面试参数需要记住的数值或范围SGDmomentum0.9Adambetas(0.9, 0.999)学习率warmup线性/warmup steps3~5个epoch约总步数的5%数据增强RandomResizedCrop0.08~1.0 缩放范围权值初始化He init均匀分布 sigmasqrt(2/n_in)非极大值抑制IoU阈值0.3~0.5一般0.45锚框anchor ratios常用[0.5,1,2]这部分在面试中的问法比较固定一定要能说出参数背后的直觉不能只会背数值。3.4 用最小代码实现一个可训练的ResNet类残差块题目要求“写一个残差块的前向过程不用考虑反向传播。”这是一个很好的进阶题因为它能区分真正写过网络代码和只会调库的人。import torch.nn as nn class BasicBlock(nn.Module): def __init__(self, in_planes, planes, stride1): super().__init__() self.conv1 nn.Conv2d(in_planes, planes, 3, stride, 1, biasFalse) self.bn1 nn.BatchNorm2d(planes) self.conv2 nn.Conv2d(planes, planes, 3, 1, 1, biasFalse) self.bn2 nn.BatchNorm2d(planes) self.shortcut nn.Sequential() if stride ! 1 or in_planes ! planes: self.shortcut nn.Sequential( nn.Conv2d(in_planes, planes, 1, stride, biasFalse), nn.BatchNorm2d(planes) ) def forward(self, x): out torch.relu(self.bn1(self.conv1(x))) out self.bn2(self.conv2(out)) out self.shortcut(x) return torch.relu(out)这个代码里有几个细节要注意。第一个是biasFalse因为后面接BNBN有可学习的β参数如果卷积层再带bias会冗余所以经典ResNet实现都置为False。第二个是shortcut的设计只有stride1且通道数不变时才是恒等映射否则用1x1卷积做变换。第三个是关键命题“恒等映射在反传时如何影响梯度”恒等路径的梯度为1所以原始梯度可以直接流过shortcut避免深层网络的梯度消失。听到这里面试官大概率会追问“为什么ResNet比VGG好训练”。答案是网络经过shortcut具有类似集成多路径的特征每次只需要学习残差部分优化目标从学习完整映射降级为学习残差难度降低。这部分回答如果能提到“残差结构缓解了优化瓶颈”就比只回答“解决了梯度消失”要高级。4. 目标检测与多目标跟踪里的确定性算法面试中的硬核手撕4.1 非极大值抑制的向量化实现与参数陷阱计算机视觉算法面试中NMS是出现频率最高的手撕题目。逻辑不复杂按类别置信度排序选中置信度最高的框移除与其IoU超过阈值的框重复到没有候选框为止。下面这个向量化实现是面试中推荐写出的版本def nms(boxes, scores, iou_threshold): order scores.argsort()[::-1] keep [] while order.size 0: i order[0] keep.append(i) if order.size 1: break xx1 np.maximum(boxes[i, 0], boxes[order[1:], 0]) yy1 np.maximum(boxes[i, 1], boxes[order[1:], 1]) xx2 np.minimum(boxes[i, 2], boxes[order[1:], 2]) yy2 np.minimum(boxes[i, 3], boxes[order[1:], 3]) w np.maximum(0.0, xx2 - xx1) h np.maximum(0.0, yy2 - yy1) inter w * h area_i (boxes[i,2] - boxes[i,0]) * (boxes[i,3] - boxes[i,1]) area_o (boxes[order[1:],2] - boxes[order[1:],0]) * (boxes[order[1:],3] - boxes[order[1:],1]) iou inter / (area_i area_o - inter) order order[1:][iou iou_threshold] return keep参数iou_threshold在检测任务里通常取0.5某些更严格的经验值是0.3。阈值越低抑制越强能去除更多重叠框但也可能把相邻的两个同类别目标误杀。要注意的是代码里每次用order[1:]切片这里隐藏了一个O(N^2)的复杂度面试时可以补充一种实现预计算所有框两两之间的IoU再用排序后的顺序进行抑制。实际上成熟框架里常用torchvision.ops.nms但手撕代码面试的重点不是性能而是让你演示对边界条件的理解空的boxes输入IoU的分母为零两个框都是面积为0的退化情况浮点误差导致的比较问题建议用epsilon修正。4.2 匈牙利算法在视觉任务中的匹配应用学习了目标检测后匹配问题贯穿了多目标跟踪、ReID、结构光三维重建和医学分割等多个方向。比较出名的算法是匈牙利算法它解决的是一个二分图最大权匹配问题核心复杂度是O(N^3)在目标数量小于几百个时非常高效。面试并不要求完全手写原始实现但会更进一步要看“是否真的用过”。一个常见案例是用它做目标关联对上一帧的跟踪框和当前帧的检测框用IoU或者外观特征计算代价矩阵再调用匈牙利算法找出全局最优匹配。下面代码是用scipy实现的示例from scipy.optimize import linear_sum_assignment import numpy as np # cost_matrix[i][j] 表示检测框i与跟踪框j之间的代价 cost_matrix np.array([ [0.1, 0.9, 0.8], [0.7, 0.2, 0.6], [0.5, 0.4, 0.3] ]) row_ind, col_ind linear_sum_assignment(cost_matrix) for r, c in zip(row_ind, col_ind): print(f检测框{r} - 跟踪框{c}代价{cost_matrix[r][c]:.2f})linear_sum_assignment默认求最小化代价如果使用相似度矩阵则需要取负号。还有一个容易被忽略的坑代价矩阵必须是完整的方阵或矩形处理不了“检测框多于跟踪框”的不平衡情况。解法是扩展一个虚拟列虚拟代价设为阈值大于该阈值的匹配视为拒绝。这个细节决定了多目标跟踪里“如何处理新出现的目标”面试官听到这里通常都会感兴趣。多目标跟踪领域经典的DeepSORT算法就是结合了IoU代价和外观特征的级联匹配。面试里会问“为什么要用级联而不是直接全局匹配”因为跟踪目标很可能被短暂遮挡越近的帧置信度越高应该优先匹配最近帧。这种工程判断往往是面试官用来区分“知道函数”和“理解场景”的关键题。4.3 从NMS到匈牙利算法的复杂度推理能力面试中另一类有意思的题目是“给出一个O(N^3)的算法如何优化到O(N^2)”。匈牙利算法的标准实现基于增广路径但实际优化的包大多用了Jonker-Volgenant算法它对稀疏矩阵支持更好。准备面试时要习惯先写朴素实现再讨论优化而不是直接贴最优解法这样才能向面试官展示思路。可以通过一个小例子对比匹配算法时间复杂度适用条件贪心算法O(N log N)单帧小目标不需要最优匹配匈牙利算法O(N^3)稠密矩阵需要全局最优Jonker-VolgenantO(N^3)但常数更小对目标数量500时推荐最小费用最大流O(f * E log V)带约束的匹配自定义代价较强不过面试问到这些不是真的让你在十分钟里写出来而是确认你对算法复杂度的边界有概念。5. 3D视觉与几何算法的原理与代码进阶岗位的必考区5.1 双目视差估计SGBM的关键参数与真实效果做过自动驾驶或机器人的人大概率会被问到立体匹配算法。传统的SGBMSemi-Global Block Matching在计算机视觉中是一个经典算法它结合了局部匹配和全局能量优化的思想。面试题一般从“怎么定义匹配代价”开始到“深度精度受什么因素影响”结束。用OpenCV调用非常简单import cv2 import numpy as np def compute_depth_with_sgbm(left_img, right_img): left cv2.cvtColor(left_img, cv2.COLOR_BGR2GRAY) right cv2.cvtColor(right_img, cv2.COLOR_BGR2GRAY) stereo cv2.StereoSGBM_create( minDisparity0, numDisparities64, blockSize11, P18 * 3 * blockSize ** 2, P232 * 3 * blockSize ** 2, disp12MaxDiff1, uniquenessRatio10, speckleWindowSize100, speckleRange32 ) disparity stereo.compute(left, right).astype(np.float32) / 16.0 depth np.zeros_like(disparity) valid disparity 0 depth[valid] (focal_length * baseline) / disparity[valid] return depth这里参数numDisparities必须是16的倍数它决定搜索范围数值越大计算越慢同时能覆盖更近的物体。blockSize是匹配窗口大小越大对噪声越鲁棒但会丢失边缘细节。P1和P2是能量函数里的平滑惩罚项P2数值越大图像中背景和前景的过渡越平滑但过大会把物体的边缘也抹掉。实际调参时可以先从blockSize11和numDisparities64开始再用disp12MaxDiff检查左右视差一致性过滤掉遮挡区域的错误匹配。SGBM在神经网络深度估计出来之后仍是工业界常用的老将原因在于它的确定性行为和低部署成本。5.2 对极几何与本质矩阵恢复相机的相对运动3D视觉面试的另一大主题是双视图几何。问题通常是“已知两幅图像中的匹配点如何估计相机运动”这是SLAM中视觉里程计的基础。标准做法是计算本质矩阵E再由E分解出旋转矩阵R和平移向量t。import cv2 import numpy as np def estimate_pose(pts1, pts2, K): # 归一化坐标 pts1_norm cv2.undistortPoints(pts1, K, None) pts2_norm cv2.undistortPoints(pts2, K, None) E, mask cv2.findEssentialMat( pts1_norm, pts2_norm, focal1.0, pp(0., 0.), methodcv2.RANSAC, prob0.999, threshold1.0 ) _, R, t, _ cv2.recoverPose(E, pts1_norm, pts2_norm, maskmask) return R, tcv2.undistortPoints传入内参矩阵K得到归一化平面坐标这一步很关键。E矩阵的自由度是5包含3个旋转和3个平移分量但因为尺度不确定所以是5个自由度。recoverPose返回的t是单位向量表示相对位移的方向而不是真实距离尺度模糊是单目视觉的本质问题。面试时被追问“为什么单目恢复不出绝对尺度”时要说出这是透视投影造成的一个物体平行移动两倍距离投影像素完全不变。这部分内容对纯2D检测工程师来说可能稍偏但在涉及三维重建、AR、自动驾驶的项目中几乎必考。如果准备时间有限可以优先掌握E矩阵的估计过程和RANSAC参数不用深入研究BA优化算法。5.3 从算法到面试场景经典题目重现下面是两道我在不同公司遇到过的真题题目给定已知内参的双目图如何计算某个像素的深度 回答通过立体匹配得到视差d深度Z f * b / d其中f是焦距像素单位b是基线d是同一特征点在左右图上x坐标的差。题目双目匹配中为什么极线约束能让搜索从二维降为一维 回答因为双目相机经过校正后左右图像的对极线是水平的同一点在右图中的匹配位置必然在对应的扫描行上因此只需要沿水平方向搜索极大减少了匹配计算量。这类问题考察的是算法背后的空间推理能力而非背公式。准备时可以自己动手画一画小孔成像模型把相机标定、畸变矫正、立体校正和视差计算串成一条线比刷十道模板题更有效。6. 面试现场的手撕算法进阶技巧三个能拉开差距的细节最后一章直接给三个能拉开差距的进阶技巧它们都具备通用性无论面试官出什么题目都能用上。第一个技巧是从“用for循环写”升级到“用矩阵运算写”同时主动说出优化理由。比如手写NMS时很多人会按顺序逐个比较面试官不会说错但你能补充一句“向量化实现可以利用局部性减少Python循环开销但要注意边界判断避免越界”就能明显加分。同样的逻辑在卷积、SIFT特征匹配、计算IoU时都适用。拿出一分钟画出计算图让面试官看到你在思考数据形状和索引关系。第二个技巧是养成先讨论边界条件的习惯。写代码前先问输入可不可能为空、尺寸不一致、分母为零的情况。例如计算IoU时若两个框不相交交集宽高为负必须用max(0, ...)钳制计算视差时若左右图完全没有匹配视差值不应当被当作有效数据。这个习惯在面试官眼中代表工程经验的沉淀远比在十分钟内多写几十行代码更值钱。第三个技巧是背一个“复杂度对照表”在脑子里比如匈牙利算法O(N^3)、SGBM的复杂度约O(WHD)且D是视差搜索范围、单应矩阵DLT算法是O(N)循环但代价是构建8*8线性方程。当面试官问某个算法的复杂度时直接给出上下界和常数因子说明你理解算法内部实现而不是背结论。这里写一个像素级的分段搜索区示例展示如何在可控时间内实现复杂度边界判断def estimate_self_complexity(points_count, dim3): # 假设做SVD分解复杂度为O(N*dim^2)常数因子低 if points_count 1000: return 可行实时性有保障 elif points_count 10000: return 建议用RANSAC预过滤降低有效点数 else: return 优先考虑断点或边缘化优化或者换用线性算法这段代码的三段逻辑体现了算法工程师的工程感不是所有问题都用O(N^3)暴力解决可以根据数据量选择离线预处理或在线降密度。面试中主动提到这类选择能让你从“会写代码”的评价提升到“有架构头脑”。除了技巧还有一种实用的准备方式把要手撕的代码控制在30行左右用思路分析当作开场白。面试官出的题通常不会难到无解真正难的是在有限时间内把思路表达清楚同时覆盖异常输入。准备时自己录一遍解题过程看看是不是从“读题”一下子跳到“写代码”如果是请改成先说出“我打算用什么数据结构、状态转移是什么、返回量是什么”再动笔。这样做之后面试表现通常会有明显提升。最后提醒一个具体的复习方法把所有手撕题整理成“最小数据测试集”在本地跑一遍。例如卷积题用2x2输入配上3x3核NMS题用两个重叠程度不同的框测试阈值边界匈牙利算法用3x3矩阵测试虚拟行逻辑。数据测试集比重复刷题更能检验真实掌握程度面试前当天看一遍自己的测试集胜过再翻十页笔记。本文还有配套的精品资源点击获取