ARKit教程14_第十章:檢測占位符

前言

ARKit感興趣的同學(xué)昂秃,可以訂閱ARKit教程專題
源代碼地址在這里

正文

本章重點(diǎn)介紹如何檢測占位符并首先顯示一些位置標(biāo)記,以及之后的平面冬竟。

平面檢測與物體檢測

不要將平面檢測與物體檢測混淆;他們是兩個(gè)不同的東西贼穆。平面檢測內(nèi)置于ARKit中页畦,以幫助程序員將對象放入場景中改艇。

ARKit的平面檢測不是RazeAd所需的工具收班。相反,我們將使用Vision Framework來檢測有限形狀谒兄,然后我們將ARKit平面基于該形狀摔桦。

檢測矩形

iOS的一個(gè)更好的方面是不同框架之間的互操作性。在這種情況下承疲,我們正在利用Vision框架邻耕。視覺框架是一種圖像分析和計(jì)算機(jī)視覺框架,用于識(shí)別和分類現(xiàn)實(shí)世界的對象燕鸽。

我們將使用Vision框架檢測矩形兄世,然后使用VNDetectRectanglesRequest將該矩形轉(zhuǎn)換為ARKit對象,顧名思義啊研,它檢測矩形形狀碘饼。

Vision靜態(tài)檢測對象,因此我們必須為每個(gè)請求提供圖像悲伶。
現(xiàn)在,我們可以使用屏幕上的點(diǎn)按來觸發(fā)圖像分析住涉。

ARKit導(dǎo)入后再導(dǎo)入Vision框架:

import Vision

我們添加以下代碼:

// 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 
                  }
            } 
        } catch(let error) { 
    print( "An error occurred during rectangle detection: \(error)") 
    }
}  

上面的代碼作用如下:

  • 1: ARSession有一個(gè)currentFrame屬性麸锉,它是ARFrame的一個(gè)實(shí)例。幀是視頻源的單個(gè)捕獲舆声。 ARKit分析并將其與設(shè)備的運(yùn)動(dòng)感應(yīng)相結(jié)合花沉。與其他數(shù)據(jù)一起,它包含相機(jī)捕獲的圖像媳握。由于你需要框架碱屁,我們必須防止零幀,在這種情況下你跳過處理蛾找,只需提前返回娩脾。
  • 2: 圖像處理對CPU消耗比較大 - 我們需要使用后臺(tái)線程。
  • 3: 很快就會(huì)需要do/catch塊打毛。
  • 4: 我們創(chuàng)建一個(gè)矩形檢測請求柿赊,它將閉包作為唯一參數(shù)俩功。在完成圖像分析時(shí)調(diào)用閉包,提供剛創(chuàng)建的VNDetectRectanglesRequest實(shí)例和可選錯(cuò)誤碰声。結(jié)果可以作為請求參數(shù)的屬性進(jìn)行訪問诡蜓。
  • 5: results屬性將是VNRectangleObservation實(shí)例的數(shù)組,每個(gè)檢測到的矩形一個(gè)胰挑。在這里蔓罚,我們將使用compactMap()[Any]轉(zhuǎn)換為[VNRectangleObservation]。然后瞻颂,我們將結(jié)果數(shù)組存儲(chǔ)到結(jié)果變量中豺谈。
  • 6: 如果請求沒有產(chǎn)生任何結(jié)果,我們可以提前退出蘸朋,因?yàn)闆]有其他事可做核无。否則,如果結(jié)果數(shù)組包含至少一個(gè)檢測到的矩形藕坯,則選擇第一個(gè)团南。

請求將返回最多一個(gè)值,因?yàn)?strong>VNDetectRectanglesRequest的maximumObservations屬性默認(rèn)為1炼彪。

let result = results.first else ... block之后吐根,添加以下代碼:

// 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)
}

上面的代碼作用如下:

  • 1: 結(jié)果是VNRectangleObservation的一個(gè)實(shí)例,它有四個(gè)屬性辐马,用于標(biāo)識(shí)檢測到的矩形的四個(gè)頂點(diǎn)拷橘。

注意:Vision適用于2D圖像,因此它不了解3D世界喜爷。結(jié)果點(diǎn)始終是位圖圖像中的2D坐標(biāo)冗疮。

??我們將這四個(gè)坐標(biāo)轉(zhuǎn)換為一個(gè)地圖友好的數(shù)組,以便更容易按順序處理它們檩帐。

  • 2:還記得包含ARKit當(dāng)前處理的圖像的currentFrame變量嗎术幔?它公開了一個(gè)有用的hitTest()方法,用于通過將其投影到對象或3D世界中的錨點(diǎn)來消除2D圖像的點(diǎn)湃密。

    ?hitTest()返回一個(gè)ARHitTestResult列表诅挑,按距離排序,從最近到最遠(yuǎn)泛源,因此拔妥,再次,我們首先將其存儲(chǔ)到hitFeature變量中达箍。稍后會(huì)詳細(xì)介紹没龙。
  • 3:在它的屬性中锰瘸,我們只需要worldTransform桐款,這就是我們返回的內(nèi)容。
  • 4:現(xiàn)在坐標(biāo)包含一個(gè)4x4矩陣的數(shù)組,這些矩陣取自worldTransform屬性潮售。由于我們正在處理矩形想括,因此請確保正好有四個(gè)坐標(biāo)儡陨。如果在任何角落中將矩形的2D點(diǎn)更改為ARKit世界中的3D點(diǎn)的過程失敗司致,則可以提前退出。
  • 5: 由于代碼將添加和刪除UI元素肛鹏,因此我們需要返回主線程逸邦。
  • 6:如果已顯示先前的廣告牌,請將其刪除在扰。此方法尚未實(shí)現(xiàn)缕减。
  • 7: 最后,通過調(diào)用createBillboard()創(chuàng)建一個(gè)新的廣告牌芒珠,然后將前面步驟中找到的四個(gè)世界坐標(biāo)傳遞給它桥狡。此方法尚未實(shí)現(xiàn)。

currentFrame.hitTest(_:types :)將一個(gè)點(diǎn)投射到一個(gè)3D對象皱卓。 types參數(shù)確定它們是什么類型的對象裹芝,包括:

  • featurePoint:曲面的一個(gè)點(diǎn)部分,但沒有錨點(diǎn)娜汁。
  • estimatedHorizo??ntalPlane:搜索檢測到的水平曲面嫂易,但沒有相應(yīng)的錨點(diǎn)。
  • existingPlane:具有關(guān)聯(lián)錨點(diǎn)的平面掐禁,不考慮平面的大小怜械。
  • existingPlaneUsingExtent:具有關(guān)聯(lián)錨點(diǎn)的平面,與平面的大小相關(guān)傅事。

為了檢測表面上的矩形缕允,我們可能認(rèn)為最后三個(gè)中的任何一個(gè)都可能是一個(gè)不錯(cuò)的選擇。事實(shí)上蹭越,ARKit中的平面有一個(gè)對齊灼芭。由于我們希望檢測任何表面上的矩形 - 因此不限于水平或垂直 - 只留下一個(gè)選項(xiàng):featurePoint

還有一個(gè)缺失部分需要完成touchesBegan()實(shí)現(xiàn):使用VNDetectRectanglesRequest實(shí)例般又。

catch回調(diào)之前,我們添加下面的代碼:

// 1 
let handler = VNImageRequestHandler( cvPixelBuffer: currentFrame.capturedImage)

// 2 
try handler.perform([request])
  • 1: 執(zhí)行VNDetectRectanglesRequest的方法是創(chuàng)建一個(gè)負(fù)責(zé)執(zhí)行實(shí)際圖像處理的圖像請求處理程序巍佑。它通過cvPixelBuffer參數(shù)獲取要分析的圖像茴迁。
  • 2: 創(chuàng)建請求處理程序后,我們必須讓它發(fā)揮其魔力萤衰。所以我們調(diào)用它的perform方法堕义,傳遞一個(gè)只包含前面步驟中創(chuàng)建的請求的數(shù)組。

注意如何為單幀定義處理程序?qū)嵗捎糜谠谕粠蠄?zhí)行多個(gè)請求倦卖,例如文本識(shí)別洒擦,條形碼檢測等。如果需要執(zhí)行多個(gè)分析怕膛,則創(chuàng)建一個(gè)請求每個(gè)分析熟嫩,但只有一個(gè)處理程序。

創(chuàng)建廣告牌

現(xiàn)在我們有四個(gè)矩陣確定四個(gè)矩形頂點(diǎn)中每個(gè)矩形頂點(diǎn)的位置和方向褐捻。我們將使用這些來幫助定位廣告牌掸茅。

世界變換矩陣包含通過從相機(jī)向相反方向投影2D點(diǎn)及其方向而得到的交點(diǎn)。方向取決于從攝像機(jī)到矩形頂點(diǎn)的虛線之間的角度柠逞,如下圖中的紅線所示昧狮,矩形方向由垂直于平面的直線確定,如綠線所示板壮。

現(xiàn)在我們需要添加一個(gè)擴(kuò)展:

func createBillboard(
topLeft: matrix_float4x4, topRight: matrix_float4x4, bottomRight: matrix_float4x4, bottomLeft: matrix_float4x4) {
    // 1 
    let plane = RectangularPlane( topLeft: topLeft, topRight: topRight, bottomLeft: bottomLeft, bottomRight: bottomRight)
    // 2
    let anchor = ARAnchor(transform: plane.center)
    // 3 
    billboard = BillboardContainer(billboardAnchor: anchor, plane: plane)
    // 4 
    sceneView.session.add(anchor: anchor)
    print("New billboard created")
}

上面的代碼作用如下:

  • 1: 將四個(gè)矩陣存儲(chǔ)到名為RectangularPlane的數(shù)據(jù)容器中逗鸣,該容器還計(jì)算矩形大小及其中心。
  • 2: 為平面創(chuàng)建錨點(diǎn)绰精,位于矩形的中心撒璧。
  • 3: 將錨點(diǎn)和平面存儲(chǔ)到容器中,以便以后可以訪問它們茬底。
  • 4: 最后沪悲,將錨添加到ARKit會(huì)話。

BillboardContainer是一個(gè)實(shí)用程序數(shù)據(jù)結(jié)構(gòu)阱表,用于存儲(chǔ)有關(guān)在BillboardContainer.swift中實(shí)現(xiàn)的廣告牌的數(shù)據(jù)〉钊纾現(xiàn)在我們將擴(kuò)展BillboardContainer

我們需要添加的最后一段代碼是刪除廣告牌最爬。在createBillboard()之后立即添加以下函數(shù):

func removeBillboard() {
    // 1 
    if let anchor = billboard?.billboardAnchor {
    // 2 
    sceneView.session.remove(anchor: anchor) 
    // 3 
    billboard?.billboardNode?.removeFromParentNode() billboard = nil 
    }
}

上面代碼作用如下:

  • 1: 我們可以使用保護(hù)聲明對此進(jìn)行預(yù)先處理涉馁,以確保廣告牌屬性不為零。但它不需要爱致,因?yàn)槲覀冎恍枰獧z查錨是否有值烤送。
  • 2: 如果有錨,請將其從ARKit會(huì)話中刪除糠悯。
  • 3: 最后帮坚,刪除SceneKit節(jié)點(diǎn)。

下面的物體都是可以檢測到的矩形形狀:

  • 外部觸控板互艾。
  • A4紙或者白色的信紙试和。
  • 筆記本
  • 海報(bào)

重要的是它的顏色必須與它背后的表面形成鮮明對比,所以白色桌子上的白紙有可能識(shí)別不出來纫普。

Xcode啟動(dòng)應(yīng)用程序后阅悍,將iPhone的相機(jī)指向我們選擇檢測的對象并點(diǎn)按屏幕。

如果你沒有可以掃描的對象,那就對著屏幕掃描下面這個(gè)黑色的正方形吧:

如果你可以掃描到节视,那么控制臺(tái)會(huì)有如下提示:

如果Vision無法檢測到形狀拳锚,我們將在控制臺(tái)中看到如下消息:

[Vision] VNRequest produced no result

檢測到之后會(huì)有如下提示:

New billboard created

顯示地標(biāo)

我們可能希望在檢測點(diǎn)時(shí)顯示占位符。這可以幫助我們直觀地調(diào)試應(yīng)用程序寻行。

我們在ViewController.swift文件中的touchesBegan()方法中在self.createBillboard調(diào)用之后加入如下代碼:

for coordinate in coordinates {
    // 1 
    let box = SCNBox(width: 0.01, height: 0.01, length: 0.001, chamferRadius: 0.0)
    // 2
   let node = SCNNode(geometry: box)
    // 3 
    node.transform = SCNMatrix4(coordinate)
    // 4 
    self.sceneView.scene.rootNode.addChildNode(node)
}
  • 1: 首先霍掺,我們創(chuàng)建一個(gè)10×10×1 mm的小型SceneKit框。
  • 2: 然后寡痰,使用該框創(chuàng)建SceneKit節(jié)點(diǎn)抗楔。
  • 3: 接下來,在轉(zhuǎn)換為SCNMatrix4之后拦坠,為新節(jié)點(diǎn)設(shè)置轉(zhuǎn)換矩陣连躏。
  • 4: 最后,將節(jié)點(diǎn)添加到場景的根節(jié)點(diǎn)贞滨。

構(gòu)建并運(yùn)行并嘗試檢測矩形形狀;你會(huì)看到類似的東西:

注意每個(gè)矩形頂點(diǎn)的小白色矩形入热。當(dāng)完成視覺測試時(shí),可以注釋掉該代碼晓铆。

注意:我們還可以選擇不同的形狀勺良,大小,顏色骄噪,方向以及可能需要的任何內(nèi)容尚困,以使每個(gè)地標(biāo)在擁擠的場景中脫穎而出。

添加SceneKit節(jié)點(diǎn)

createBillboard中链蕊,我們創(chuàng)建了一個(gè)ARKit錨點(diǎn)并將其添加到ARKit會(huì)話中事甜。下一步是將ARKit錨轉(zhuǎn)換為SceneKit節(jié)點(diǎn)。

接下來我們需要完善ARSCNViewDelegate方法:

func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {

    // 1 
    guard let billboard = billboard else { return nil } 
    var node: SCNNode? = nil

    // 2 
    //DispatchQueue.main.sync { switch anchor { 
        // 3 
        case billboard.billboardAnchor:
            let billboardNode = addBillboardNode() 
            node = billboardNode 
        default:
             break 
            } 
        //}
   return node
}

當(dāng)手動(dòng)將新錨點(diǎn)添加到ARKit會(huì)話時(shí)滔韵,ARKit會(huì)調(diào)用此方法逻谦,讓我們有機(jī)會(huì)通過在方法結(jié)束時(shí)返回它來為新創(chuàng)建的錨點(diǎn)提供SceneKit節(jié)點(diǎn)。

上面的代碼作用如下:

  • 1: 驗(yàn)證是否有廣告牌陪蜻,否則退出邦马,返回nil
  • 2: 這是注釋掉的宴卖,以提醒我們通常不會(huì)在主線程中調(diào)用此方法滋将。我們我們有在這里進(jìn)行任何與UI相關(guān)的處理,因此任何線程都可以症昏。
  • 3: 在這里檢查錨點(diǎn)是否是廣告牌的錨點(diǎn)耕渴。如果是這樣,則調(diào)用addBillboardNode()齿兔,它返回一個(gè)SCNNode。然后將其設(shè)置為返回值。

createBillboard()后面分苇,我們添加如下代碼:

func addBillboardNode() -> SCNNode? { 
    guard let billboard = billboard else { return nil }
    // 1 
    let rectangle = SCNPlane(width: billboard.plane.width, height: billboard.plane.height)
    // 2 
    let rectangleNode = SCNNode(geometry: rectangle) 
    self.billboard?.billboardNode = rectangleNode
    return rectangleNode
}

上面的代碼作用如下:

  • 1: 使用之前在RectangularPlane結(jié)構(gòu)中計(jì)算的大小創(chuàng)建SCNPlane添诉。
  • 2: 創(chuàng)建一個(gè)SCNNode,將平面作為幾何體傳遞医寿。然后栏赴,將節(jié)點(diǎn)添加到廣告牌容器并返回它。

運(yùn)行程序靖秩,效果如下:

位置有一些偏差须眷,不過后面我們會(huì)做優(yōu)化的。

處理中斷

除了這個(gè)方向問題沟突,一切看起來都很棒花颗。但是,在將應(yīng)用程序置于后臺(tái)之前惠拭,有一個(gè)小問題可能會(huì)被忽視扩劝。比如下面這些:

  • 1: 運(yùn)行app
  • 2: 檢測矩形。
  • 3: ARKit顯示檢測到的平面后职辅,按Home鍵棒呛。
  • 4: 應(yīng)用程序進(jìn)入后臺(tái)后,請更改設(shè)備的方向域携。
  • 5: 恢復(fù)應(yīng)用程序簇秒。

無論新方向是什么,我們都可以在將應(yīng)用程序發(fā)送到后臺(tái)之前的同一屏幕位置找到該平面秀鞭。但是趋观,如果我們在應(yīng)用程序處于活動(dòng)狀態(tài)時(shí)更改方向,則該平面將移動(dòng)到其新位置气筋。

當(dāng)ARKit會(huì)話中斷時(shí)拆内,設(shè)備停止向ARKit饋送用于確定相對于當(dāng)前手機(jī)位置和方向的節(jié)點(diǎn)位置的硬件傳感器信息。

雖然可以使用后臺(tái)處理作為解決方法宠默,但這不是一個(gè)合理的解決方案 - 除非只想使用它幾秒鐘麸恍,例如當(dāng)用戶暫時(shí)被外部事件分心并且他們在幾個(gè)內(nèi)部返回應(yīng)用程序時(shí)秒。但是定期執(zhí)行此操作會(huì)對設(shè)備的資源造成巨大損失搀矫。

當(dāng)用戶恢復(fù)應(yīng)用程序時(shí)抹沪,他們必須重復(fù)平面檢測過程。

實(shí)施需要在會(huì)話中斷時(shí)刪除廣告牌瓤球。在ARSCNViewDelegate中有一個(gè)SceneKit委托方法:sessionWasInterrupted融欧。

此方法已包含在代碼中但它是空的。將這行代碼添加到其正文中:

removeBillboard()

這將刪除廣告牌卦羡,以便當(dāng)我們從后臺(tái)恢復(fù)應(yīng)用程序時(shí)噪馏,它將返回到相同的狀態(tài)麦到。

上一章 目錄 下一章
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市欠肾,隨后出現(xiàn)的幾起案子瓶颠,更是在濱河造成了極大的恐慌,老刑警劉巖刺桃,帶你破解...
    沈念sama閱讀 206,013評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件粹淋,死亡現(xiàn)場離奇詭異,居然都是意外死亡瑟慈,警方通過查閱死者的電腦和手機(jī)桃移,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來葛碧,“玉大人借杰,你說我怎么就攤上這事〈挡海” “怎么了第步?”我有些...
    開封第一講書人閱讀 152,370評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長缘琅。 經(jīng)常有香客問我粘都,道長,這世上最難降的妖魔是什么刷袍? 我笑而不...
    開封第一講書人閱讀 55,168評論 1 278
  • 正文 為了忘掉前任翩隧,我火速辦了婚禮,結(jié)果婚禮上呻纹,老公的妹妹穿的比我還像新娘堆生。我一直安慰自己,他們只是感情好雷酪,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評論 5 371
  • 文/花漫 我一把揭開白布淑仆。 她就那樣靜靜地躺著,像睡著了一般哥力。 火紅的嫁衣襯著肌膚如雪蔗怠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,954評論 1 283
  • 那天吩跋,我揣著相機(jī)與錄音寞射,去河邊找鬼。 笑死锌钮,一個(gè)胖子當(dāng)著我的面吹牛桥温,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播梁丘,決...
    沈念sama閱讀 38,271評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼侵浸,長吁一口氣:“原來是場噩夢啊……” “哼旺韭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起掏觉,我...
    開封第一講書人閱讀 36,916評論 0 259
  • 序言:老撾萬榮一對情侶失蹤茂翔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后履腋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,382評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡惭嚣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評論 2 323
  • 正文 我和宋清朗相戀三年遵湖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晚吞。...
    茶點(diǎn)故事閱讀 37,989評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡延旧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出槽地,到底是詐尸還是另有隱情迁沫,我是刑警寧澤,帶...
    沈念sama閱讀 33,624評論 4 322
  • 正文 年R本政府宣布捌蚊,位于F島的核電站集畅,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏缅糟。R本人自食惡果不足惜挺智,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望窗宦。 院中可真熱鬧赦颇,春花似錦、人聲如沸赴涵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽髓窜。三九已至扇苞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間纱烘,已是汗流浹背杨拐。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留擂啥,地道東北人哄陶。 一個(gè)月前我還...
    沈念sama閱讀 45,401評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像哺壶,于是被迫代替她去往敵國和親屋吨。 傳聞我的和親對象是個(gè)殘疾皇子蜒谤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評論 2 345