ARKit 1.5更新 識別標記圖像并實現(xiàn)AVPlayer流媒體播放

AR系列的開篇塔鳍。本系列打算由上而下的學習AR的各種實現(xiàn)方式和原理。會涉及到音視頻拜隧、視頻照片濾鏡和OpenGLES相關的知識宿百。

讓我們來定義一個需求,AR識別固定標記洪添,識別后根據(jù)識別標記的位置計算旋轉(zhuǎn)垦页、平移和縮放矩陣確定播放器視口位置和大小,支持播放網(wǎng)絡視頻干奢。當然實現(xiàn)的方式有多種:

效果圖
  • 通過第三方 Vuforia可以實現(xiàn)AR識別功能痊焊,官方demo介紹了本地視頻播放的方法,但是不支持流媒體播放忿峻。流媒體播放視頻可以用AVPlayer加載URL宋光,并添加AVPlayerItemOutput 為AVPlayerItem的output。調(diào)用下面的API獲取視頻幀緩存炭菌。
/*!
@abstract檢索適合在指定的項目時間顯示的圖像罪佳,并將圖像標記為已獲取。
@discussion
完成后黑低,客戶端負責在返回的CVPixelBuffer上調(diào)用CVBufferRelease赘艳。
通常酌毡,您將調(diào)用此方法來響應CVDisplayLink回調(diào)或CADisplayLink委托調(diào)用,并且hasNewPixelBufferForItemTime:也返回YES蕾管。
從copyPixelBufferForItemTime:itemTimeForDisplay:中檢索的緩沖區(qū)引用本身可能為NULL枷踏。 為NULL時表明該CMTime沒有像素緩沖區(qū)需要顯示。
 */
- (nullable CVPixelBufferRef)copyPixelBufferForItemTime:(CMTime)itemTime itemTimeForDisplay:(nullable CMTime *)outItemTimeForDisplay CF_RETURNS_RETAINED;

Vuforia 類似的詳細實現(xiàn)之后專門開篇討論掰曾。

  • ARKit實現(xiàn)識別圖片旭蠕。這是iOS 11.3推出的新功能。具體API如下
/**
AR場景中要檢測的圖片
 @discussion 如果設置detectionImages旷坦,ARKit將嘗試檢測指定的圖像掏熬。當檢測到圖像時,
 會回調(diào) - (nullable SCNNode *)renderer:(id <SCNSceneRenderer>)renderer nodeForAnchor:(ARAnchor *)anchor方法秒梅。anchor  是ARImageAnchor旗芬。
 */
@property (nonatomic, copy, nullable, readwrite) NSSet<ARReferenceImage *> *detectionImages API_AVAILABLE(ios(11.3));

本篇主要討論ARKit實現(xiàn)方案。

先看幾個11.3 新特性

  • iOS 11.3 新特性之一 ARPlaneDetectionVertical 這是一個NS_OPTIONS位移枚舉捆蜀。用于檢測場景中的垂直平面疮丛。

  • iOS 11.3 新特性之二: ARReferenceImage API_AVAILABLE(ios(11.3))
    ARReferenceImage 是識別圖像的模型類,提供了三個創(chuàng)建方法辆它。init和new都設置為不可用誊薄。

// CGImageRef生成ARReferenceImage識別模型,注意physicalWidth的單位是:米 (meters)
- (instancetype)initWithCGImage:(CGImageRef)image orientation:(CGImagePropertyOrientation)orientation physicalWidth:(CGFloat)physicalWidth NS_SWIFT_NAME(init(_:orientation:physicalWidth:));
// 從CVPixelBufferRef生成ARReferenceImage識別模型
- (instancetype)initWithPixelBuffer:(CVPixelBufferRef)pixelBuffer orientation:(CGImagePropertyOrientation)orientation physicalWidth:(CGFloat)physicalWidth NS_SWIFT_NAME(init(_:orientation:physicalWidth:));

// 從指定的圖片包里加載一套識別圖锰茉,返回一個ARReferenceImage識別模型 的NSSet暇屋。
+ (nullable NSSet<ARReferenceImage *> *)referenceImagesInGroupNamed:(NSString *)name bundle:(nullable NSBundle *)bundle;

/** Unavailable */ 
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
  • iOS 11.3 新特性之三: ARWorldTrackingConfiguration的新屬性detectionImages 。把我們上面創(chuàng)建的ARReferenceImage識別模型組賦值給detectionImages洞辣。
@property (nonatomic, copy, nullable, readwrite) NSSet<ARReferenceImage *> *detectionImages API_AVAILABLE(ios(11.3));

- iOS 11.3 新特性之四: SCNMaterial對象的contents可以直接添加AVPlayer對象作為紋理源。(iOS 11.3之前可以添加SpriteKit scene add child SKVideoNode  (SKVideoNode may creat with AVPlayer))
@property(nonatomic, retain, nullable) id contents;

   SCNMaterial *materialVideoFrame = [SCNMaterial material];
   materialVideoFrame.diffuse.contents = self.playerManger.player;

根據(jù)上訴四條新功能昙衅,可以實現(xiàn)AR視頻播放扬霜。下面上代碼:

  • 配置 ARWorldTrackingConfiguration 添加識別圖, 并runWithConfiguration而涉。
- (void)resetTracking
{
    /**
     ARSessionRunOptions 也是一個NS_OPTION著瓶。
     ARSessionRunOptionResetTracking 每次調(diào)用重新配置識別。
     ARSessionRunOptionRemoveExistingAnchors 重新配置時刪除之前存在的Anchors
     
     如果不配置options:默認保留現(xiàn)有的Anchors
     */
    [self.sceneView.session runWithConfiguration:self.arConfig options:ARSessionRunOptionResetTracking | ARSessionRunOptionRemoveExistingAnchors];
}
- (ARWorldTrackingConfiguration *)arConfig
{
    if (!_arConfig) {
        _arConfig = [[ARWorldTrackingConfiguration alloc] init];

        //iOS 11.3 新特性之一: ARPlaneDetectionVertical API_AVAILABLE(ios(11.3)) = (1 << 1)
        /** Plane detection determines vertical planes in the scene. */

        if (@available(iOS 11.3, *)) {
// 由于我們識別的是圖像平面啼县,這里不設置其他平面的識別材原。
//            _arConfig.planeDetection = ARPlaneDetectionHorizontal | ARPlaneDetectionVertical;

            NSString *resourcePath = [[NSBundle mainBundle] resourcePath];
            NSString *filePath = [resourcePath stringByAppendingPathComponent:@"L3DWY888_800x600@2x.jpg"];
            _deImage = [UIImage imageWithContentsOfFile:filePath];
            
            //iOS 11.3 新特性之二: ARReferenceImage API_AVAILABLE(ios(11.3))

            ARReferenceImage *referenceDetectedImg = [[ARReferenceImage alloc] initWithCGImage:_deImage.CGImage orientation:1 physicalWidth:0.1];
//  iOS 11.3 新特性之三: ARWorldTrackingConfiguration的新屬性
            [_arConfig setDetectionImages:[NSSet setWithObject:referenceDetectedImg]];
        }
    }
    return _arConfig;
}
  • 識別成功返回錨點,添加node季眷,矩陣變換余蟹。
#pragma mark - ARSCNViewDelegate
// 重寫以根據(jù)anchor創(chuàng)建添加到當前session中的node。識別水平垂直平面或圖像返回的錨點子刮。
- (SCNNode *)renderer:(id<SCNSceneRenderer>)renderer nodeForAnchor:(ARAnchor *)anchor {
    // 當返回ARImageAnchor時威酒,說明識別到圖片了窑睁。
    if (![anchor isMemberOfClass:[ARImageAnchor class]]) {
        return [SCNNode node];
    }
// 這里配置我們播放視頻相關的node。
// 自定義SCNGeometry(SCNGeometrySource葵孤, SCNGeometryElement) 設置視頻node
    DKVideoPlane *videoGeometry = [DKVideoPlane planeWithType:DKVideoPlaneHorizontal width:0.1 length:0.07];
    videoGeometry.materials = @[self.materials[DKARPlayerMaterialTypeVideo]];

    _videoNode = [SCNNode nodeWithGeometry:videoGeometry];
    //worldTransform 和 transform 的區(qū)別:worldTransform相對于根節(jié)點的旋轉(zhuǎn)平移縮放矩陣担钮,transform和position同時設置,共同作用與node的變換尤仍。
    SCNMatrix4 transM = SCNMatrix4FromMat4(anchor.transform);
    _videoNode.worldTransform = transM;
}

- (NSMutableArray<SCNMaterial *> *)materials
{
    if (!_materials) {
        _materials = [NSMutableArray array];
      
        SCNMaterial *materialVideoFrame = [SCNMaterial material];
        /**
         SKScene *ss = [SKScene sceneWithSize:CGSizeMake(100, 100)];
         SKVideoNode *vn = [SKVideoNode videoNodeWithAVPlayer:self.playerManger.player];
         [ss addChild:vn];
         materialVideoFrame.diffuse.contents = ss;
         */
        
        // iOS 11.3 新特性之四: SCNMaterial對象的contents可以直接添加AVPlayer對象作為紋理源箫津。
        materialVideoFrame.diffuse.contents = self.playerManger.player;
        [_materials insertObject:materialVideoFrame atIndex:DKARPlayerMaterialTypeVideo];
    }
    return _materials;
}

  • 加上簡單的播放邏輯,點擊播放暫停宰啦,快進快退seek
- (void)setupRecognizers {
    // Single tap will insert a new piece of geometry into the scene
    UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onPlayerVideo:)];
    tapGestureRecognizer.numberOfTapsRequired = 1;
    [self.sceneView addGestureRecognizer:tapGestureRecognizer];
    
    UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panDirection:)];
    [panRecognizer setMaximumNumberOfTouches:1];
    [panRecognizer setDelaysTouchesBegan:YES];
    [panRecognizer setDelaysTouchesEnded:YES];
    [panRecognizer setCancelsTouchesInView:YES];
    [self.sceneView addGestureRecognizer:panRecognizer];
}

// 添加手勢控制播放暫停
- (void)onPlayerVideo:(UITapGestureRecognizer *)sender {
    
    if (sender.state == UIGestureRecognizerStateEnded) {
        
        if ([self.playerManger getStatus] == PLAYING) {
            [self pause];
        }else{
            [self play];
        }
    }
}
// 添加手勢控制播放進度
- (void)panDirection:(UIPanGestureRecognizer *)pan {
    CGPoint veloctyPoint = [pan velocityInView:self.sceneView];
    
    // 判斷是垂直移動還是水平移動
    switch (pan.state) {
        case UIGestureRecognizerStateBegan: { // 開始移動
            // 使用絕對值來判斷移動的方向
            CGFloat x = fabs(veloctyPoint.x);
            CGFloat y = fabs(veloctyPoint.y);
            if (x > y) { // 水平移動
                self.panDirection = DKARPanDirectionHorizontalMoved;
                CMTime time       = self.playerManger.player.currentTime;
                self.sumTime      = time.value/time.timescale;
            }
            break;
        }
        case UIGestureRecognizerStateChanged: {
            switch (self.panDirection) {
                case DKARPanDirectionHorizontalMoved:{
                    [self horizontalMoved:veloctyPoint.x];
                    break;
                }
                default:
                    break;
            }
            break;
        }
        case UIGestureRecognizerStateEnded: {
            switch (self.panDirection) {
                case DKARPanDirectionHorizontalMoved:{
                    [self.playerManger seekTo:self.sumTime];
                    self.sumTime = 0;
                    break;
                }
                default:
                    break;
            }
            break;
        }
        default:
            break;
    }
}

  • 基本完成開篇提出的需求苏遥。

開發(fā)過程中需要注意的點:

  • 需要對紋理坐標系和頂點坐標系有清晰的理解。
  • 注意node的SCNGeometry繪制方向绑莺。

待完善的地方:

  • hitTest: 不能擊中已添加的ARImageAnchor暖眼,識別平面添加的anchor可以正確擊中。接下來打算自定義點擊區(qū)域計算方法纺裁。

  • SCNText沒有正確顯示到scene中

有不明白的歡迎討論诫肠。本篇demo

識別標記圖:

// 要識別的圖片name。也可以定義圖片包欺缘。
#define TrackImage @"aaa.jpg"
識別標記圖
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末栋豫,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子谚殊,更是在濱河造成了極大的恐慌丧鸯,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嫩絮,死亡現(xiàn)場離奇詭異丛肢,居然都是意外死亡,警方通過查閱死者的電腦和手機剿干,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門蜂怎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人置尔,你說我怎么就攤上這事杠步。” “怎么了榜轿?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵幽歼,是天一觀的道長。 經(jīng)常有香客問我谬盐,道長甸私,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任飞傀,我火速辦了婚禮颠蕴,結(jié)果婚禮上泣刹,老公的妹妹穿的比我還像新娘。我一直安慰自己犀被,他們只是感情好椅您,可當我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著寡键,像睡著了一般掀泳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上西轩,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天员舵,我揣著相機與錄音,去河邊找鬼藕畔。 笑死马僻,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的注服。 我是一名探鬼主播韭邓,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼溶弟!你這毒婦竟也來了女淑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤辜御,失蹤者是張志新(化名)和其女友劉穎鸭你,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體擒权,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡袱巨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了碳抄。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片愉老。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖纳鼎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情裳凸,我是刑警寧澤贱鄙,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站姨谷,受9級特大地震影響逗宁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜梦湘,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一瞎颗、第九天 我趴在偏房一處隱蔽的房頂上張望件甥。 院中可真熱鬧,春花似錦哼拔、人聲如沸引有。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽譬正。三九已至,卻和暖如春檬姥,著一層夾襖步出監(jiān)牢的瞬間曾我,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工健民, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留抒巢,地道東北人。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓秉犹,卻偏偏與公主長得像蛉谜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子凤优,可洞房花燭夜當晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容