5
8

More than 1 year has passed since last update.

カメラで撮るだけで物の大きさがわかる

Jun-20-2023 13-20-24.gif

便利だと思いませんか?

(仕組み)AIで物体検出してARで大きさを測る

物体検出モデル(Yolov8)でカメラに映った物体を検出し、その結果をもとにARセンサーで距離や物体の大きさを測れます。
名称未設定2.jpg

方法

Yolov8を実行する

ARSessionのカメラフィードをYolov8にかけます。

lazy var yoloRequest: VNCoreMLRequest = {
    do {
        let model = try yolov8s().model
        let vnModel = try VNCoreMLModel(for: model)
        self.yoloRequest = VNCoreMLRequest(model: vnModel)
        self.yoloRequest.imageCropAndScaleOption = .scaleFit
    } catch let error {
        fatalError("mlmodel error.")
    }
}()


func session(_ session: ARSession, didUpdate frame: ARFrame) {
    let pixelBuffer = frame.capturedImage
    var ciImage = CIImage(cvPixelBuffer: pixelBuffer, options: [:])
    ciImage = ciImage.oriented(.right)
    let aspect =  sceneView.bounds.width / sceneView.bounds.height
    let estimateWidth = ciImage.extent.height * aspect          
    cropped = ciImage.cropped(to: CGRect(
        x: ciImage.extent.width / 2 - estimateWidth / 2,
        y: 0,
        width: estimateWidth,
        height: ciImage.extent.height
    )
    let handler = VNImageRequestHandler(ciImage: cropped, options: [:])
    do {
        try handler.perform([yoloRequest])
        guard let result = yoloRequest.results?.first as? VNRecognizedObjectObservation else { return }
        let topLeft = CGPoint(x: result.boundingBox.minX, y: 1 - result.boundingBox.maxY)
        let topRight = CGPoint(x: result.boundingBox.maxX, y: 1 - result.boundingBox.maxY)
        let bottomLeft = CGPoint(x: result.boundingBox.minX, y: 1 - result.boundingBox.minY)
        let bottomRight = CGPoint(x: result.boundingBox.maxX, y: 1 - result.boundingBox.minY)    
        let deNormalizedTopLeft = VNImagePointForNormalizedPoint(topLeft, Int(frameRect!.width), Int(frameRect!.height))
        let deNormalizedTopRight = VNImagePointForNormalizedPoint(topRight, Int(frameRect!.width), Int(frameRect!.height))
        let deNormalizedBottomLeft = VNImagePointForNormalizedPoint(bottomLeft, Int(frameRect!.width), Int(frameRect!.height))
        let deNormalizedBottomRight = VNImagePointForNormalizedPoint(bottomRight, Int(frameRect!.width), Int(frameRect!.height))

    } catch let error {
        print(error)
    }
}

iOS用に変換されたYolov8を以下からダウンロードし、xcodeプロジェクトにバンドルします。
https://github.com/john-rocky/CoreML-Models#yolov8

カスタムモデルを変換したい場合は以下の手順で変換できます。
https://qiita.com/john-rocky/items/0817bbd16667df183807

カメラフレームをクロップしています。
ARでディスプレイ上の物体位置を3次元空間に投影して大きさを測るので、
「ディスプレイに写っている映像」と「物体検出にかける画像」の領域を一致させるためです。
カメラのフレーム全体がディスプレイに写っているわけではないので。

それから、VNCoreMLRequestはデフォルトで前処理で画像を中央クロップするので、imageCropAndScaleOptionは.scaleFitにして、入力画像全体を物体検出にかけられるようにします。

検出したボックスを3次元に投影して距離を計測する

ディスプレイと対象物との距離を測り、その距離でYoloの検出結果を投影します。

デバイスと物体の距離を測る

let raycastQuery = sceneView.raycastQuery(from: cgPoint, allowing: .estimatedPlane, alignment: .any)
if let unwrappedRaycastQuery = raycastQuery {
            let raycastResults = sceneView.session.raycast(unwrappedRaycastQuery)
            guard let result = raycastResults.first else { return nil }
            let worldCoordinates = simd_float3(
                x: result.worldTransform.columns.3.x,
                y: result.worldTransform.columns.3.y,
                z: result.worldTransform.columns.3.z
            )

let devicePosition = simd_float3(x: transform.x, y: transform.y, z: transform.z)
let distanceToSurface = distance(devicePosition,surfaceCenter)

物体検出結果を物体の距離の平面に投影して幅と高さを測る

let infrontOfCamera = SCNVector3(x: 0, y: 0, z: -distanceToSurface)
guard let cameraNode = sceneView.pointOfView else { return }
let pointInWorld = cameraNode.convertPosition(infrontOfCamera, to: nil)
var screenPos = sceneView.projectPoint(pointInWorld)
screenPos.x = Float(deNormalizedTopLeft.x)
screenPos.y = Float(deNormalizedTopLeft.y)
let leftTopCoordinate = simd_float3(x: (screenPos.x, y: screenPos.y, z: screenPos.z)

var screenPos = sceneView.projectPoint(pointInWorld)
screenPos.x = Float(deNormalizedTopRight.x)
screenPos.y = Float(deNormalizedTopRight.y)
let rightTopCoordinate = simd_float3(x: (screenPos.x, y: screenPos.y, z: screenPos.z)

let objectWidth = distance(leftTopCoordinate,rightTopCoordinate)
5
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
8