16-《ARKit by Tutorials》讀書筆記3:交互操做

說明

ARKit系列文章目錄swift

本文是Ray Wenderlich上《ARKit by Tutorials》的讀書筆記,主要講內容概要和讀後感 session

ARKit by Tutorials中講到了圖像識別觸發AR場景交互的一種特殊方法:利用Vision Framework來識別一些物體,而後在上面展現一些圖片或動畫.框架

還有利用地理定位和iBeacon觸發AR交互的方法.async

爲何用Vision

可能你會以爲奇怪:爲何不用ARKit自帶的圖片檢測功能? 只要把參考圖片的素材放好,設置好物理尺寸,ARKit就能夠檢測到圖片,在WWDC2018上ARKit 2更是增長了圖片追蹤功能,效果很是好,識別率高,追蹤穩定.post

那是由於,ARKit目前自帶的圖片檢測和追蹤功能,有幾點要求不太好知足:測試

  • 圖片不能過於類似;
  • 圖片的色彩直方圖分佈要均勻(不能是黑白的);
  • 圖片不能有大片相同顏色的區域;
  • 圖片的物理尺寸必須是已知且準確的.

好比下面的圖片就不知足要求,雖然也能檢測到,但追蹤效果會差不少. 動畫

更麻煩的是:二維碼.ui

  • 雖然不一樣內容的二維碼圖片自己並不相同,可是仍然太類似了,尤爲是文本很長的時候;
  • 二維碼通常是黑白的;
  • 二維碼中有大片相同顏色的區域;
  • 二維碼的尺寸每每是不一樣的;

Vision框架能夠識別的內容就不少,能夠識別矩形,二維碼等等.咱們能夠把它們兩個結合起來使用,達到神奇的效果.spa

識別任意矩形

Vision框架的使用自己並不難,在AR項目中,寫個touchesBegan()方法,在其中寫上:3d

// 1
guard let currentFrame = sceneView.session.currentFrame else {
  return 
}
// 2
DispatchQueue.global(qos: .background).async {
  // 3
  do {
    // 4
    let request = VNDetectRectanglesRequest {(request, error) in
                                 
      // Access the first result in the array,
      // after converting to an array
      // of VNRectangleObservation
      // 5
      guard
        let results = request.results?.compactMap({ $0 as? VNRectangleObservation }),
        // 6 
        let result = results.first else {
          print ("[Vision] VNRequest produced no result")
          return
      }
      // 獲得識別結果,稍後在這裏添加處理代碼.
    }


let handler = VNImageRequestHandler(cvPixelBuffer: currentFrame.capturedImage)

try handler.perform([request])

  } catch(let error) {
    print("An error occurred during rectangle detection: \(error)")
  }
}
複製代碼

能夠看到,在上面第6步以後,已經獲得了識別出的矩形的結果,繼續經過hitTest方法,根據二維的屏幕座標上矩形的四個角的位置(二維座標),找到三維空間裏矩形的四個角的位置(三維座標).

// 1
let coordinates: [matrix_float4x4] = [
  result.topLeft, 
  result.topRight, 
  result.bottomRight, 
  result.bottomLeft
].compactMap {
  // 2
  guard let hitFeature = currentFrame.hitTest($0, types: .featurePoint).first else { return nil }

  // 3
  return hitFeature.worldTransform
}

// 4
guard coordinates.count == 4 else { return }

// 5
DispatchQueue.main.async {
  // 6
  self.removeBillboard()

  let (topLeft, topRight, bottomRight, bottomLeft) = (coordinates[0], coordinates[1], 
      coordinates[2], coordinates[3])

  // 7
  self.createBillboard(topLeft: topLeft, topRight: topRight,
    bottomRight: bottomRight, bottomLeft: bottomLeft)
}
複製代碼

利用hitTest方法獲得了四個featurePoint,後建立一個三維的平面Billboard.

三維平面的位置由錨點決定,錨點位置則由4個點的中心點肯定:

let anchor = ARAnchor(transform: plane.center)
sceneView.session.add(anchor: anchor)
複製代碼

同時還能夠建立四個SCNBox來標識矩形的四個角,效果以下

座標系的處理

但這樣建立出的平面有個問題,朝向不正確

這是由於,咱們是根據4個點來建立的平面,這4個點是在世界座標下的點,自己只有位置座標,沒有旋轉和縮放信息,打印 print(coordinates[0])結果以下:

simd_float4x4([
  [1.0, 0.0, 0.0, 0.0)], 
  [0.0, 1.0, 0.0, 0.0)], 
  [0.0, 0.0, 1.0, 0.0)],
  [-0.0293431, -0.238044, -0.290515, 1.0)]
])
複製代碼

用這樣的4個點去建立平面,過程以下:

func addBillboardNode() -> SCNNode? {
  guard let billboard = billboard else { return nil }
  
  // 1 寬和高是從4個點的位置計算出來的
  let rectangle = SCNPlane(width: billboard.plane.width,
    height: billboard.plane.height)
  
  // 2 沒法獲得transform信息,不能正確顯示方向,而SCNPlane的默認方向是在x-y平面上,也就是垂直於地面(沿y軸方向),與手機的初始化方向平行(x-y平面方向平行)
  let rectangleNode = SCNNode(geometry: rectangle)
  self.billboard?.billboardNode = rectangleNode

  return rectangleNode
}
複製代碼

這裏就能看出問題:建立平面只利用了4個點的寬高信息,朝向信息沒有設置使用了默認方向.

書中給出了一種處理方式:更改ARKit配置項ARConfiguration中的worldAlignment屬性.這個屬性有三個值:

  • gravity:座標系的y軸是與重力方向平行的,座標原點及x-z軸則是設備初始化時的位置和朝向.也就是默認設置項
  • gravityAndHeading:y軸與重力方向平行,而x軸指向東,z軸指向南.和現實世界保持一致.
  • camera:座標系始終跟隨攝像機(也就是手機)的位置和朝向,伴隨移動.

若是咱們採用第三種配置,那麼建立出的平面是平行於x-y平面的,即平行於手機屏幕的.可是因爲正常狀況下,識別過程當中手機是正對着要識別對象的,因此獲得的結果就是幾乎是正確的.

configuration.worldAlignment = .camera
複製代碼

我的認爲:這種作法很扯蛋,根本沒有解決問題,只是當用戶垂直於矩形進行識別時,效果較好(遠遠算不上完美)而已.
我認爲能夠這樣解決,歡迎你們討論:

  1. 利用4個點,計算所在平面的法線A,法線A的方向就取指向攝像機(手機)方向爲正.考慮到4個點可能不共面(畢竟立體幾何中3點肯定一個平面),能夠用排列組合的方式輪流取3個點求法向量,總共求出4個法向量再求平均值作爲法線A;(求平面的法向量能夠用兩條邊向量的叉乘)
  2. 將建立出的平面的法線B對準剛纔計算出的法線A.如何對準呢?變換矩陣是什麼? 這就是一個數學問題:已知初始法線B,和目標法線A,求變換矩陣;能夠藉助四元數進行求解,或者直接設置四元數來處理旋轉;
  3. 將建立出的平面SCNNode的矩陣屬性設置爲求出的變換矩陣就能夠了;
// 能夠先求出從法線B到法線A的四元數
extension simd_quatf {
    /// A quaternion whose action rotates the vector `from` onto the vector `to`.
    public init(from: float3, to: float3)
}
// 從四元數中獲得變換矩陣或直接使用四元數
extension simd_float4x4 {
    /// Construct a 4x4 matrix from `quaternion`.
    public init(_ quaternion: simd_quatf)
}
複製代碼

Vision能實現的其它功能

除了矩形以外,Vision還能識別出其它物體:

  • Horizon:VNDetectHorizonRequest類能夠獲得畫面的水平角度.
  • Faces:VNDetectFaceRectanglesRequest類能夠實現人臉識別;
  • Text:VNDetectTextRectanglesRequest類能夠識別文本和區域;
  • Rectangleobject追蹤:VNTrackRectangleRequestVNTrackObjectRequest類能夠追蹤識別出的物體.

例如,上面的例子想改爲識別二維碼,並在二維碼上顯示圖片或視頻,只須要更改Vision部分的代碼就好了:

let request = VNDetectBarcodesRequest { (request, error) in
  // Access the first result in the array,
  // after converting to an array
  // of VNBarcodeObservation
  guard let results = request.results?.compactMap({
    $0 as? VNBarcodeObservation }),
    let result = results.first else {
      print ("[Vision] VNRequest produced no result")
      return
    }

  ...
}
複製代碼

效果以下:

後續還能夠在識別出二維碼的內容後,在上面展現圖片,打開網頁或播放視頻等

地理定位相關

除了Vision識別來觸發場景外,還講到了利用地理定位和iBeacon來觸發AR場景,其實核心代碼很是簡單,若是你作過地圖開發或iBeacon開發的話,就知道其實就是下面幾個代理方法:

// MARK: - LocationManagerDelegate
extension AdViewController: LocationManagerDelegate {
  // MARK: Location
  func locationManager(_ locationManager: LocationManager, didEnterRegionId regionId: String) {
  }

  func locationManager(_ locationManager: LocationManager, didExitRegionId regionId: String) {
  }

  // MARK: Beacons
  func locationManager(_ locationManager: LocationManager, didRangeBeacon beacon: CLBeacon) {
  }

  func locationManager(_ locationManager: LocationManager, didLeaveBeacon beacon: CLBeacon) {
  }
}
複製代碼

具體業務邏輯沒有什麼太大的難點,再也不贅述了.

須要注意的是,提到了地理定位的測試方法:

  • .gpx文件來作虛擬定位測試;
  • 用lightblue等藍牙軟件來模擬iBeacon定位;

第三部分讀書筆記結束!

相關文章
相關標籤/搜索