置かれた書類を検知して真っ直ぐにする機能を作ります
置かれた書類を自動検知して真っ直ぐにしたい
置かれた書類をカメラで撮影すると、斜めになることがあります。
それを自動で真っ直ぐに直してくれる機能があればいいですよね。
で、iOSではVisionKitというフレームワークですでに実現できます。
もっというなら、iPhoneのデフォルトカメラにもOCR付きのドキュメントスキャナが付いています。すごい。
既存のものを組み合わせてあえて再発明。
画像変形ライブラリと、Visionの四角形検出を組み合わせれば、ドキュメントスキャナっぽいものが再発明できそうです。
ライブラリのコアを見つける
上記の画像変形ライブラリのコアは、画像の4点を受け取って、整形された四角形の画像を返すFunctionです。
CIPerspectiveCorrectionというフィルターで4点から整形された画像を作っていることがわかります。
private func getCroppedImage(image: CIImage, topL: CGPoint, topR: CGPoint, botL: CGPoint, botR: CGPoint) -> CIImage {
let rectCoords = NSMutableDictionary(capacity: 4)
rectCoords["inputTopLeft"] = topL.toVector(image: image)
rectCoords["inputTopRight"] = topR.toVector(image: image)
rectCoords["inputBottomLeft"] = botL.toVector(image: image)
rectCoords["inputBottomRight"] = botR.toVector(image: image)
guard let coords = rectCoords as? [String : Any] else {
return image
}
return image.applyingFilter("CIPerspectiveCorrection", parameters: coords)
}
extension CGPoint {
func toVector(image: CIImage) -> CIVector {
return CIVector(x: x, y: image.extent.height-y)
}
}
このFunctionに、Visionで検知した四角形の座標を与えれば、自動検知による切り抜きができそうです。
private func getDocumentImage(image:UIImage) -> UIImage? {
let ciImage = CIImage(image: image)!
let request = VNDetectRectanglesRequest()
let handler = VNImageRequestHandler(ciImage: ciImage, options: [:])
try! handler.perform([request])
guard let result = request.results?.first else { return nil }
let topLeft = CGPoint(x: result.topLeft.x, y: 1-result.topLeft.y)
let topRight = CGPoint(x: result.topRight.x, y: 1-result.topRight.y)
let bottomLeft = CGPoint(x: result.bottomLeft.x, y: 1-result.bottomLeft.y)
let bottomRight = CGPoint(x: result.bottomRight.x, y: 1-result.bottomRight.y)
let deNormalizedTopLeft = VNImagePointForNormalizedPoint(topLeft, Int(ciImage.extent.width), Int(ciImage.extent.height))
let deNormalizedTopRight = VNImagePointForNormalizedPoint(topRight, Int(ciImage.extent.width), Int(ciImage.extent.height))
let deNormalizedBottomLeft = VNImagePointForNormalizedPoint(bottomLeft, Int(ciImage.extent.width), Int(ciImage.extent.height))
let deNormalizedBottomRight = VNImagePointForNormalizedPoint(bottomRight, Int(ciImage.extent.width), Int(ciImage.extent.height))
let croppedImage = getCroppedImage(image: ciImage, topL: deNormalizedTopLeft, topR: deNormalizedTopRight, botL: deNormalizedBottomLeft, botR: deNormalizedBottomRight)
let safeCGImage = context.createCGImage(croppedImage, from: croppedImage.extent)
let croppedUIImage = UIImage(cgImage: safeCGImage!)
return croppedUIImage
}
できました。
ライブラリのコードを見てみると発見がある
ライブラリのコードを見てみたり、組み合わせてみたりすると、発見があるかも。
コードGitHubにアップロードしました。
SemanticImageという画像フィルター集にも入れました。
🐣
フリーランスエンジニアです。
お仕事のご相談こちらまで
rockyshikoku@gmail.com
Core MLやARKitを使ったアプリを作っています。
機械学習/AR関連の情報を発信しています。