用手机拍摄纸质文档、名片或白板内容时,几乎总会遇到一个问题:照片里的文档不是矩形,而是因为拍摄角度变成了梯形或不规则四边形。要让内容恢复成端正的平面视图,就需要做透视校正。iOS平台上的Core Image框架提供了完善的透视变换能力,配合矩形特征检测,可以在几行代码内完成从原图到校正后图像的转换。本文从原理到代码,完整梳理四点透视变换、梯形校正与文档边缘对齐的实现路径。

透视变换的基本原理与Core Image实现思路
透视变换在数学上是一个单应性变换,它描述了同一个平面在不同视角下的投影关系。只要知道原图中一个四边形的四个顶点,以及目标矩形的四个顶点,就能求解出一个3x3的单应矩阵,把原图中的像素按这个矩阵映射到目标平面上。对拍摄文档来说,文档的物理边界是一个矩形,照片里呈现的四边形就是这个矩形经过透视投影后的结果,所以只要找到这四个角的坐标,就能反推出正视图。
Core Image把这套数学封装成了几个现成的滤镜。CIPerspectiveCorrection是最常用的一个,它接收输入图像以及原图四边形的四个角点坐标,输出校正后的图像。与之配套的还有CIPerspectiveTransform,它只做坐标变换不裁剪,适合需要保留更多背景的场景。理解了这一点,整个处理流程就非常清晰:先拿到图像,再找到文档的四个角点,最后交给滤镜完成校正。
下面是使用CIPerspectiveCorrection的最小示例代码:
import CoreImage
import CoreImage.CIFilterBuiltins
func correctPerspective(image: CIImage,
topLeft: CGPoint,
topRight: CGPoint,
bottomLeft: CGPoint,
bottomRight: CGPoint) -> CIImage? {
let filter = CIFilter.perspectiveCorrection()
filter.inputImage = image
filter.topLeft = topLeft
filter.topRight = topRight
filter.bottomLeft = bottomLeft
filter.bottomRight = bottomRight
return filter.outputImage
}需要注意的是,Core Image使用的坐标原点在图像左下角,x轴向右、y轴向上,这与UIKit中左上角为原点的坐标系是相反的。如果角点是从Vision或UIKit层的视图坐标拿到的,必须做一次y轴翻转,否则校正结果会出现上下颠倒或角点错位的问题。这是实际开发中最常见的踩坑点之一。
用CIRectangleFeature自动检测文档四个角点
手动指定四个角点只适合调试或用户交互框选的场景,真正的扫描类应用应该自动检测文档边缘。Core Image自带了 CIDetector 矩形检测能力,通过CIDetectorTypeRectangle类型可以返回图像中最显著的矩形特征,也就是CIRectangleFeature对象。这个对象包含topLeft、topRight、bottomLeft、bottomRight四个属性,正好对应透视滤镜需要的输入。
检测时可以设置一些参数来提升准确性。CIDetectorAccuracy设为High会花更多时间但结果更准,CIDetectorAspectRatio可以限定候选矩形的宽高比范围,比如扫描A4文档时设为接近0.707能过滤掉大量误检。下面的代码演示了完整的检测与校正组合:
import CoreImage
func autoDetectAndCorrect(_ input: CIImage) -> CIImage? {
let options = [
CIDetectorAccuracy: CIDetectorAccuracyHigh,
CIDetectorAspectRatio: 0.707
]
guard let detector = CIDetector(ofType: CIDetectorTypeRectangle,
context: nil,
options: options),
let feature = detector.features(in: input).first
as? CIRectangleFeature else {
return nil
}
let filter = CIFilter.perspectiveCorrection()
filter.inputImage = input
if let tl = feature.topLeft, let tr = feature.topRight,
let bl = feature.bottomLeft, let br = feature.bottomRight {
filter.topLeft = tl
filter.topRight = tr
filter.bottomLeft = bl
filter.bottomRight = br
}
return filter.outputImage
}这个方案在文档与背景对比明显、光照均匀的场景下效果很好,识别速度也能满足实时预览的需求。但当文档颜色与背景接近、边缘有阴影干扰,或者画面中出现多个矩形时,检测结果可能不稳定。此时可以考虑升级到Vision框架的VNDetectRectanglesRequest,它的检测精度和鲁棒性更好,只是拿到坐标后同样要注意Vision与Core Image之间的坐标转换:Vision返回的归一化坐标以左下角为原点,乘以图像宽高即可得到像素坐标,与Core Image直接兼容。
梯形校正的细节处理与输出优化
四点坐标到位之后,CIPerspectiveCorrection会自动把原图四边形拉伸成标准矩形并裁剪输出。默认情况下,滤镜输出的尺寸等于四点构成的边界矩形在目标空间的尺寸,如果原图四边形面积很大而文档本身偏小,输出图像可能会包含模糊放大的内容。可以通过设置extent或者在变换后用CIFilter.croped()控制输出区域,保证成图大小合理。
实际产品中通常还需要叠加几步增强处理。第一步是锐化和对比度增强,透视拉伸会让文字边缘变软,接一个CIColorControls提高对比度,再用CISharpenLuminance锐化亮度通道,文字可读性会明显提升。第二步是色彩处理,扫描文档常做成黑白的增强扫描效果,可以用CIColorClamp配合阈值处理,把接近白色的背景压成纯白、文字压成深色。第三步是方向修正,如果用户横着拍摄,还需要根据EXIF方向信息或设备姿态旋转图像。
import CoreImage
import CoreImage.CIFilterBuiltins
func enhanceDocument(_ image: CIImage) -> CIImage {
// 提高对比度并适当调整饱和度
let colorCtrl = CIFilter.colorControls()
colorCtrl.inputImage = image
colorCtrl.contrast = 0.2
colorCtrl.saturation = 0.3
colorCtrl.brightness = 0.05
// 对亮度做锐化,改善透视拉伸带来的文字模糊
let sharpen = CIFilter.sharpenLuminance()
sharpen.inputImage = colorCtrl.outputImage
sharpen.sharpness = 0.8
return sharpen.outputImage ?? image
}最后谈谈整体架构上的建议。如果应用需要实时预览校正效果,建议把AVCaptureSession采集到的视频帧直接转成CIImage送入检测与校正管线,并在CIContext上用Metal渲染,全程停留在GPU可以保证流畅度。如果是单张拍照后处理,流程可以更宽松,先让用户确认或拖动四个角点,再执行高精度校正。无论哪种方式,都建议在检测失败时回退到手动框选,让用户拖动四个角点完成校正,这是扫描类应用保证可用性的关键兜底设计。掌握Core Image这套透视处理组合后,文档扫描、名片识别、白板内容数字化等场景都可以低成本落地。
Core Image透视校正文档边缘检测修改时间:2026-09-01 14:09:35