ARKit1.5新增功能
- iOS 11.3 上的视频分辨率提升至 1080p,其余不变;
- 新增竖直平面识别
ARWorldTrackingConfiguration.PlaneDetection
; - 新增
ARPlaneAnchor
中粗略平面估计 - 新增
ARSession.setWorldOrigin
方法可以重新设置世界坐标原点; - 新增图片识别能力,示例代码见https://developer.apple.com/documentation/arkit/content_anchors/detecting_images_in_an_ar_experience?language=objc
-
ARsession
被打断后,可尝试恢复追踪.详见sessionShouldAttemptRelocalization
代理方法;
还有一个增强功能:
- 打了断点,再恢复后,
session
的VIO(视觉惯性里程计)也会继续工作了.原来放置在世界坐标系中的虚拟物体仍然是可见的.
1.图片识别
1.需要添加AR资源包如图:
2.AR资源包中图片需要设置物理尺寸,其要求如下:
这个功能使用起来其实非常简单,AR功能启动时设置要识别的图片,然后在回调方法中处理识别到的锚点就可以了.
代码如下
//
// HomeScanPage.swift
// 扫描卡片界面
import UIKit
import SceneKit
import ARKit
public class HomeScanPage: BaseViewController,ARSCNViewDelegate,ARSessionDelegate {
var arSCNView : ARSCNView!
var arWordTrackingConfiguration : ARWorldTrackingConfiguration!
// MARK: - 生命周期
public override func viewDidLoad() {
super.viewDidLoad()
initSubviews()
layoutPageSubviews()
setupParameter()
}
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
init() {
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - 界面初始化
/// 初始化UI
fileprivate func initSubviews() {
self.navigationController?.navigationBar.isHidden = true
self.view.backgroundColor = .white
self.arSCNView = ARSCNView.init(frame: CGRect(x: 0, y: 0, width: self.view.width, height: self.view.height))
self.arSCNView.session = self.arSession
self.arSCNView.delegate = self
self.arSCNView.backgroundColor = UIColor.clear
self.arSCNView.isPlaying = false
self.arSCNView.automaticallyUpdatesLighting = true
self.view.addSubview(self.arSCNView)
resetTrackingConfiguration()
self.view.addSubview(closeBtn)
// self.view.addSubview(bottomImgV)
self.view.addSubview(bottomLbl)
self.view.addSubview(scanBgImg)
self.view.addSubview(scanBgIpImg)
}
public override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.arSCNView.session.pause()
self.arSCNView = nil
}
/// 初始化布局
fileprivate func layoutPageSubviews() {
self.closeBtn.snp.makeConstraints { make in
make.width.height.equalTo(autoScaleW(24))
make.top.equalToSuperview().offset(autoScaleW(58.5))
make.left.equalToSuperview().offset(autoScaleW(25))
}
self.scanBgImg.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.bottom.equalToSuperview().offset(autoScaleW(-60))
make.width.equalTo(autoScaleW(220))
make.height.equalTo(autoScaleW(217))
}
self.scanBgIpImg.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.bottom.equalTo(self.scanBgImg.snp_top).offset(autoScaleW(-60))
make.width.equalTo(autoScaleW(250))
make.height.equalTo(autoScaleW(246))
}
// self.bottomImgV.snp.makeConstraints { make in
//
// make.width.height.equalTo(autoScaleW(24))
// make.centerX.equalToSuperview()
// make.bottom.equalToSuperview().offset(autoScaleW(-70))
// }
self.bottomLbl.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.width.equalTo(SCREEN_WIDTH)
make.height.equalTo(autoScaleW(20))
make.bottom.equalToSuperview().offset(autoScaleW(-40))
}
}
/// 初始化参数
fileprivate func setupParameter() {
}
// MARK: - 子控件
lazy var arSession: ARSession = {
let arsession = ARSession()
arsession.delegate = self
return arsession
}()
func resetTrackingConfiguration() {
guard let referenceImages = ARReferenceImage.referenceImages(inGroupNamed: "AR Resources", bundle: nil) else { return }
let configuration = ARWorldTrackingConfiguration()
configuration.detectionImages = referenceImages //设置识别度
let options: ARSession.RunOptions = [.resetTracking, .removeExistingAnchors]
self.arSCNView.session.run(configuration, options: options)
}
lazy var closeBtn: UIButton = {
let v = UIButton()
v.setImage(UIImage(named: "scan_close"), for: .normal)
v.setImage(UIImage(named: "scan_close"), for: .highlighted)
v.addTarget(self, action: #selector(closeBtnClick), for: .touchUpInside)
return v
}()
lazy var scanBgImg: UIImageView = {
let v = UIImageView()
v.image = UIImage(named: "scan_bg")
return v
}()
lazy var scanBgIpImg: UIImageView = {
let v = UIImageView()
v.image = UIImage(named: "scan_bg")
return v
}()
lazy var bottomImgV: UIImageView = {
let v = UIImageView()
v.image = UIImage(named: "scan_home")
return v
}()
lazy var bottomLbl: UILabel = {
let lbl = UILabel()
lbl.text = "请对准扫描卡片"
lbl.textAlignment = .center
lbl.font = .systemFont(ofSize: autoScaleW(14))
lbl.textColor = .white
return lbl
}()
public func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
DispatchQueue.main.async {
guard let imageAnchor = anchor as? ARImageAnchor, let imageName = imageAnchor.referenceImage.name
else { return }
if(imageName == "bb"){
print("识别出来了:\(imageName)")
let scene = SCNScene(named: "art.scnassets/小宙飞0105-2.dae")!
let shipNode = scene.rootNode.childNodes.first!
shipNode.position = SCNVector3(0, -2, 0)
// shipNode.transform = SCNMatrix4MakeScale(1, 1, 1)
shipNode.scale = SCNVector3Make(0.5, 0.5, 0.5)
// shipNode.eulerAngles = SCNVector3Make(-1.0, 0, 0)
node.addChildNode(shipNode)
}
}
}
public override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
// MARK: - action
extension HomeScanPage {
}
// MARK: - click
extension HomeScanPage {
@objc func closeBtnClick(){
self.navigationController?.popViewController(animated: true)
}
}
2.使用IOS应用程序扫描真实世界对象
在现实物品的识别不可以用平面图片,就像官方文档中说的
仅使用平面上的图像进行检测。如果要检测的图像位于非平面表面上,例如酒瓶上的标签,ARKit 可能根本无法检测到它,或者可能会在错误的位置创建图像锚点,
1.首先需要使用官方demo把真实物品的识别出来,并导出来(官方demo:https://developer.apple.com/documentation/arkit/content_anchors/scanning_and_detecting_3d_objects?language=objc)
扫描和定义ARKit可用于检测的参考对象的编程步骤非常简单。 (请参阅下面的“在AR会话中创建参考对象”。)但是,创建的参考对象的保真度以及您在自己的应用程序中检测参考对象的成功取决于扫描时与物体的物理交互。 在您的iOS设备上构建并运行此应用程序,以便通过一系列获取高质量扫描数据的步骤,从而生成可用于在自己的应用程序中进行检测的参考对象文件。
- 准备扫描。第一次运行时,应用程序会显示一个框,粗略估计任何真实世界物体在相机视图中居中显示的大小。将要扫描的对象放置在没有其他对象的表面上(如空的桌面)。然后移动设备,使对象显示在框中居中,然后点击下一步按钮。
- 定义边界框。在扫描之前,您需要告诉应用程序世界的哪个区域包含您要扫描的对象。拖动以3D方式移动框,或者按住框的一侧,然后拖动以调整其大小。 (或者,如果您不改变框,则可以在对象周围移动,并且应用程序将尝试自动在其周围放置一个框。)确保边界框仅包含要扫描的对象的功能(不是来自环境),然后点击扫描按钮。
- 扫描对象。四处移动以从不同角度查看对象。该应用程序会突出显示边界框的各个部分,以指示您何时扫描足以从相应方向识别对象。请务必扫描您希望您的应用的用户能够识别该对象的各个方面。扫描完成后,应用程序自动进入下一步,或者点击“停止”按钮手动继续。
- 调整原点。该应用程序显示x,y和z坐标轴线,显示对象的锚点或原点。拖动圆圈以将原点相对于对象移动。在这一步中,您还可以使用添加(+)按钮以USDZ格式加载3D模型。应用程序会在检测到真实世界对象时显示AR中出现的模型,并使用模型的大小来调整参照对象的比例。完成后点击测试按钮。
- 测试和导出。该应用程序现在创建了一个
ARReferenceObject
并重新配置了会话以检测它。在不同的环境和光照条件下,从不同角度观察真实世界的物体,以验证ARKit能够可靠地识别其位置和方向。点击导出按钮打开分享表以保存完成的.arobject
文件。例如,您可以使用AirDrop轻松将其发送到您的开发Mac,或将其发送到“文件”应用程序以将其保存到iCloud Drive。
注意:参考对象仅包含ARKit识别现实世界对象所需的空间特征信息,并不是该对象的可显示3D重建。
检测AR体验中的参考对象
您可以使用Xcode资产目录在应用程序中捆绑引用对象以用于检测:
- 打开项目的资产目录,然后使用添加按钮(+)添加新的AR资源组。
- 将.arobject文件从Finder拖到新创建的资源组中。
- 或者,对于每个参考对象,请使用检查员为自己的使用提供一个描述性名称。
注意:将想要在同一个会话中查找的所有对象放入资源组中,并使用单独的资源组来保存一组对象以在单独的会话中使用。 例如,博物馆应用程序可能会使用单独的会话(并因此分开资源组)来识别博物馆不同侧翼的显示。
要在AR会话中启用对象检测,请将要检测的引用对象加载为 ARReferenceObject
实例,将这些对象提供给ARWorldTrackingConfiguration
的 detectionObjects
属性,然后使用该配置运行ARSession:
let configuration = ARWorldTrackingConfiguration()
configuration.detectionObjects = ARReferenceObject.referenceObjects(inGroupNamed: "AR Resources", bundle: nil)
sceneView.session.run(configuration)
当ARKit检测到您的一个参考对象时,会话会自动将相应的 ARObjectAnchor
添加到其锚点列表中。 要响应正在识别的对象,请实施一个适当的 ARSessionDelegate
,ARSCNViewDelegate
或 ARSKViewDelegate
方法,以报告添加到会话的新锚点。 例如,在基于SceneKit的应用程序中,您可以实现 renderer:didAddNode:forAnchor:
向场景添加3D资产,自动匹配锚点的位置和方向
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
if let objectAnchor = anchor as? ARObjectAnchor {
node.addChildNode(self.model)
}
}
导出来的图片如图
2.代码如下
//
// HomeScanPage.swift
// LanzoARApp
// 扫描Ip机器人界面界面
import UIKit
import SceneKit
import ARKit
public class HomeScanIpPage: BaseViewController,ARSCNViewDelegate,ARSessionDelegate {
var arSCNView : ARSCNView!
var arWordTrackingConfiguration : ARWorldTrackingConfiguration!
// MARK: - 生命周期
public override func viewDidLoad() {
super.viewDidLoad()
initSubviews()
layoutPageSubviews()
setupParameter()
}
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
init() {
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - 界面初始化
/// 初始化UI
fileprivate func initSubviews() {
self.navigationController?.navigationBar.isHidden = true
self.view.backgroundColor = .white
self.arSCNView = ARSCNView.init(frame: CGRect(x: 0, y: 0, width: self.view.width, height: self.view.height))
self.arSCNView.session = self.arSession
self.arSCNView.delegate = self
self.arSCNView.backgroundColor = UIColor.clear
self.arSCNView.isPlaying = false
self.arSCNView.automaticallyUpdatesLighting = true
self.view.addSubview(self.arSCNView)
resetTrackingConfiguration()
self.view.addSubview(closeBtn)
self.view.addSubview(bottomImgV)
self.view.addSubview(bottomLbl)
self.view.addSubview(scanBgImg)
}
public override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.arSCNView.session.pause()
self.arSCNView = nil
}
/// 初始化布局
fileprivate func layoutPageSubviews() {
self.closeBtn.snp.makeConstraints { make in
make.width.height.equalTo(autoScaleW(24))
make.top.equalToSuperview().offset(autoScaleW(58.5))
make.left.equalToSuperview().offset(autoScaleW(25))
}
self.scanBgImg.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.centerY.equalToSuperview().offset(-autoScaleW(20))
make.width.equalTo(autoScaleW(325))
make.height.equalTo(autoScaleW(320))
}
self.bottomImgV.snp.makeConstraints { make in
make.width.height.equalTo(autoScaleW(24))
make.centerX.equalToSuperview()
make.bottom.equalToSuperview().offset(autoScaleW(-70))
}
self.bottomLbl.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.width.equalTo(SCREEN_WIDTH)
make.height.equalTo(autoScaleW(20))
make.bottom.equalToSuperview().offset(autoScaleW(-40))
}
}
/// 初始化参数
fileprivate func setupParameter() {
}
// MARK: - 子控件
lazy var arSession: ARSession = {
let arsession = ARSession()
arsession.delegate = self
return arsession
}()
func resetTrackingConfiguration() {
guard let referenceObject = ARReferenceObject.referenceObjects(inGroupNamed: "AR Resources", bundle: nil)
else { return }
let configuration = ARWorldTrackingConfiguration()
configuration.detectionObjects = referenceObject //设置识别度
let options: ARSession.RunOptions = [.resetTracking, .removeExistingAnchors]
self.arSCNView.session.run(configuration, options: options)
}
lazy var closeBtn: UIButton = {
let v = UIButton()
v.setImage(UIImage(named: "scan_close"), for: .normal)
v.setImage(UIImage(named: "scan_close"), for: .highlighted)
v.addTarget(self, action: #selector(closeBtnClick), for: .touchUpInside)
return v
}()
lazy var scanBgImg: UIImageView = {
let v = UIImageView()
v.image = UIImage(named: "scan_bg")
return v
}()
lazy var bottomImgV: UIImageView = {
let v = UIImageView()
v.image = UIImage(named: "scan_home")
return v
}()
lazy var bottomLbl: UILabel = {
let lbl = UILabel()
lbl.text = "请对准扫描机器人"
lbl.textAlignment = .center
lbl.font = .systemFont(ofSize: autoScaleW(14))
lbl.textColor = .white
return lbl
}()
public func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
DispatchQueue.main.async {
guard let objectAnchor = anchor as? ARObjectAnchor, let objectName = objectAnchor.referenceObject.name
else{
print("没有找到资源")
return
}
if(objectName == "landzo"){
print("识别出来了:\(objectName)")
let scal : Float = 0.06
let scene = SCNScene(named: "art.scnassets/C音乐_0004.dae")!
let shipNode = scene.rootNode.childNodes.first!
shipNode.position = SCNVector3(-3, -1.5, 0)
// shipNode.transform = SCNMatrix4MakeScale(1, 1, 1)
shipNode.scale = SCNVector3Make(scal, scal, scal)
shipNode.eulerAngles = SCNVector3Make(0, 1.6, 0)
// shipNode.camera?.zFar = 5000
node.addChildNode(shipNode)
}
}
}
public override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
// MARK: - action
extension HomeScanIpPage {
}
// MARK: - click
extension HomeScanIpPage {
@objc func closeBtnClick(){
self.navigationController?.popViewController(animated: true)
}
}