{"id":1387,"date":"2021-07-19T10:56:42","date_gmt":"2021-07-19T02:56:42","guid":{"rendered":"https:\/\/badgameshow.com\/fly\/?p=1387"},"modified":"2021-07-23T11:20:54","modified_gmt":"2021-07-23T03:20:54","slug":"android-arcore-%e8%87%89%e9%83%a8%e5%81%b5%e6%b8%ac-%e6%b4%bb%e9%ab%94%e8%be%a8%e8%ad%98","status":"publish","type":"post","link":"https:\/\/badgameshow.com\/fly\/android-arcore-%e8%87%89%e9%83%a8%e5%81%b5%e6%b8%ac-%e6%b4%bb%e9%ab%94%e8%be%a8%e8%ad%98\/","title":{"rendered":"Android \u81c9\u90e8\u5075\u6e2c ArCore \u6d3b\u9ad4\u8fa8\u8b58"},"content":{"rendered":"<h1>Android \u81c9\u90e8\u5075\u6e2c ArCore \u6d3b\u9ad4\u8fa8\u8b58<\/h1>\n<h5>Android \u5c07 \u81c9\u90e8\u5075\u6e2c \u6574\u5408\u5230\u7cfb\u7d71,\u9069\u7528\u65bcAndroid\u5e73\u53f0, \u81c9\u90e8\u5075\u6e2c\u7528\u6709\u81c9\u90e8\u7279\u5fb5\u9ede\uff0cArCore\u5be6\u505a\u81c9\u90e8\u5075\u6e2c,\u53ef\u4ee5\u7528\u4f86\u5224\u5b9a\u662f\u5426\u70ba\u771f\u4eba,\u53ef\u4ee5\u9632\u6b62\u507d\u9020,\u81c9\u90e8\u5075\u6e2c\u662f\u4e00\u500b\u53ef\u4ee5\u5f88\u597d\u8fa8\u8a8d\u51fa\u771f\u5be6\u6027\u7684\u529f\u80fd,Android \u81c9\u90e8\u5075\u6e2c\u662f\u4f7f\u7528ArCore\u4f86\u5be6\u4f5c\u3002<\/h5>\n<hr \/>\n<h4>\u6587\u7ae0\u76ee\u9304<\/h4>\n<ol>\n<li><a href=\"#a\">\u5c0e\u5165\u57fa\u672c\u8a2d\u7f6e<\/a><\/li>\n<li><a href=\"#b\">\u5275\u5efaFaceArFragment<\/a><\/li>\n<li><a href=\"#c\">\u756b\u9762\u5e03\u5c40<\/a><\/li>\n<li><a href=\"#d\">\u5275\u5efa\u81c9\u90e8\u76f8\u95dc\u985e\u5225<\/a><\/li>\n<li><a href=\"#e\">\u7a0b\u5f0f\u78bc\u7bc4\u4f8b<\/a><\/li>\n<li><a href=\"#f\">\u6548\u679c\u5c55\u793a<\/a><\/li>\n<li><a href=\"#g\">Github<\/a><\/li>\n<\/ol>\n<hr \/>\n<p><a id=\"a\"><\/a><\/p>\n<h4>1.\u5c0e\u5165\u57fa\u672c\u8a2d\u7f6e<\/h4>\n<h5>Module<\/h5>\n<pre><code class=\"language-Gradle line-numbers\">plugins {\n    id 'com.google.ar.sceneform.plugin'\n    id 'kotlin-android-extensions'\n}\n\ndependencies {\n    implementation \"com.google.ar.sceneform.ux:sceneform-ux:1.17.1\"\n    implementation \"androidx.fragment:fragment-ktx:1.3.2\"\n}\n<\/code><\/pre>\n<h5>Project<\/h5>\n<pre><code class=\"language-Gradle line-numbers\">buildscript {\n    dependencies {\n         classpath 'com.google.ar.sceneform:plugin:1.17.1'\n    }\n}\n<\/code><\/pre>\n<h5>Manifest<\/h5>\n<pre><code class=\"language-XML line-numbers\">&lt;!--    \u7533\u8acb\u76f8\u6a5f\u6b0a\u9650--&gt;\n&lt;uses-permission android:name=\"android.permission.CAMERA\"\/&gt;\n\n&lt;!--    \u7533\u8acbAR--&gt;\n&lt;meta-data\n    android:name=\"com.google.ar.core\"\n    android:value=\"optional\" \/&gt;\n<\/code><\/pre>\n<p><a id=\"b\"><\/a><\/p>\n<h4>2.\u5275\u5efaFaceArFragment<\/h4>\n<pre><code class=\"language-Kotlin line-numbers\">class FaceArFragment : ArFragment() {\n    override fun getSessionConfiguration(session: Session?): Config {\n        val config = Config(session)\n        config.augmentedFaceMode = Config.AugmentedFaceMode.MESH3D\n        return config\n    }\n\n    override fun getSessionFeatures(): MutableSet&lt;Session.Feature&gt; {\n        return mutableSetOf(Session.Feature.FRONT_CAMERA)\n    }\n\n    override fun onCreateView(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n        savedInstanceState: Bundle?\n    ): View? {\n        val frameLayout =\n            super.onCreateView(inflater, container, savedInstanceState) as? FrameLayout\n        planeDiscoveryController.hide()\n        planeDiscoveryController.setInstructionView(null)\n        return frameLayout\n    }\n}\n<\/code><\/pre>\n<p><a id=\"c\"><\/a><\/p>\n<h4>3.\u756b\u9762\u5e03\u5c40<\/h4>\n<pre><code class=\"language-XML line-numbers\">&lt;?xml version=\"1.0\" encoding=\"utf-8\"?&gt;\n&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http:\/\/schemas.android.com\/apk\/res\/android\"\n    xmlns:tools=\"http:\/\/schemas.android.com\/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    xmlns:app=\"http:\/\/schemas.android.com\/apk\/res-auto\"&gt;\n\n    &lt;fragment\n        android:id=\"@+id\/face_fragment\"\n        android:name=\"com.example.arfacedemo.FaceArFragment\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_gravity=\"center\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" \/&gt;\n\n    &lt;LinearLayout\n        android:id=\"@+id\/linearLayout\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\"\n        app:layout_constraintBottom_toBottomOf=\"@+id\/face_fragment\"\n        app:layout_constraintEnd_toEndOf=\"@+id\/face_fragment\"\n        app:layout_constraintStart_toStartOf=\"@+id\/face_fragment\"\n        app:layout_constraintTop_toTopOf=\"parent\"&gt;\n\n        &lt;TextView\n            android:id=\"@+id\/face_text_info\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:gravity=\"center\"\n            android:paddingLeft=\"25dp\"\n            android:paddingTop=\"9dp\"\n            android:paddingRight=\"25dp\"\n            android:paddingBottom=\"9dp\"\n            android:textColor=\"#ffffff\"\n            android:textSize=\"20sp\" \/&gt;\n\n    &lt;\/LinearLayout&gt;\n\n&lt;\/androidx.constraintlayout.widget.ConstraintLayout&gt;\n<\/code><\/pre>\n<p><a id=\"d\"><\/a><\/p>\n<h4>4.\u5275\u5efa\u81c9\u90e8\u76f8\u95dc\u985e\u5225<\/h4>\n<p><a href=\"https:\/\/i.imgur.com\/ZX8moaR.png\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" src=\"https:\/\/i.imgur.com\/ZX8moaR.png\" width=\"50%\"\/><\/a><\/p>\n<h5>\u81c9\u90e8\u4f4d\u7f6e<\/h5>\n<pre><code class=\"language-Kotlin line-numbers\">import android.graphics.Bitmap\nimport android.os.Handler\nimport android.os.HandlerThread\nimport android.view.PixelCopy\nimport androidx.lifecycle.ViewModelProvider\nimport com.google.ar.core.AugmentedFace\nimport com.google.ar.core.AugmentedFace.RegionType\nimport com.google.ar.sceneform.ArSceneView\nimport com.google.ar.sceneform.FrameTime\nimport com.google.ar.sceneform.Node\nimport com.google.ar.sceneform.math.Quaternion\nimport com.google.ar.sceneform.math.Vector3\nimport com.google.ar.sceneform.ux.AugmentedFaceNode\nimport java.io.File\nimport java.io.FileOutputStream\n\nclass CustomFaceNode(\n    augmentedFace: AugmentedFace?,\n    private val mainActivity: MainActivity,\n    private val sceneView: ArSceneView,\n) : AugmentedFaceNode(augmentedFace) {\n\n\n    private var eyeNodeLeft: Node? = null\n    private var eyeNodeRight: Node? = null\n    private var lowerLipNode: Node? = null\n    private var upperLipNode: Node? = null\n    private var leftGggNode: Node? = null\n    private var rightGggNode: Node? = null\n    private var midGggNode: Node? = null\n    var turnRight = 0\n    var turnLeft = 0\n    var turnTop = 0\n    var turnBottom = 0\n\n\n    companion object {\n        enum class FaceRegion {\n            LEFT_EYE,\n            RIGHT_EYE,\n            LOWER_LIP,\n            UPPER_LIP,\n            LEFT_GGG,\n            RIGHT_GGG,\n            MID_GGG\n        }\n\n        var start = true\n    }\n\n    override fun onActivate() {\n        super.onActivate()\n        eyeNodeLeft = Node()\n        eyeNodeLeft?.setParent(this)\n\n        eyeNodeRight = Node()\n        eyeNodeRight?.setParent(this)\n\n        lowerLipNode = Node()\n        lowerLipNode?.setParent(this)\n\n        upperLipNode = Node()\n        upperLipNode?.setParent(this)\n\n        leftGggNode = Node()\n        leftGggNode?.setParent(this)\n\n        rightGggNode = Node()\n        rightGggNode?.setParent(this)\n\n        midGggNode = Node()\n        midGggNode?.setParent(this)\n    }\n\n    private fun getRegionPose(region: FaceRegion): Vector3? {\n        val buffer = augmentedFace?.meshVertices\n        if (buffer != null) {\n            return when (region) {\n                FaceRegion.LEFT_EYE -&gt;\n                    Vector3(buffer.get(374 * 3), buffer.get(374 * 3 + 1), buffer.get(374 * 3 + 2))\n                FaceRegion.RIGHT_EYE -&gt;\n                    Vector3(buffer.get(440 * 3), buffer.get(440 * 3 + 1), buffer.get(440 * 3 + 2))\n                FaceRegion.LOWER_LIP -&gt;\n                    Vector3(buffer.get(17 * 3), buffer.get(17 * 3 + 1), buffer.get(17 * 3 + 2))\n                FaceRegion.UPPER_LIP -&gt;\n                    Vector3(buffer.get(0 * 3), buffer.get(0 * 3 + 1), buffer.get(0 * 3 + 2))\n                FaceRegion.LEFT_GGG -&gt;\n                    Vector3(buffer.get(409 * 3), buffer.get(409 * 3 + 1), buffer.get(409 * 3 + 2))\n                FaceRegion.RIGHT_GGG -&gt;\n                    Vector3(buffer.get(185 * 3), buffer.get(185 * 3 + 1), buffer.get(185 * 3 + 2))\n                FaceRegion.MID_GGG -&gt;\n                    Vector3(buffer.get(14 * 3), buffer.get(14 * 3 + 1), buffer.get(14 * 3 + 2))\n            }\n        }\n        return null\n    }\n\n    override fun onUpdate(frameTime: FrameTime?) {\n        super.onUpdate(frameTime)\n        augmentedFace?.let { face -&gt;\n            getRegionPose(FaceRegion.LEFT_EYE)?.let {\n                eyeNodeLeft?.localPosition = Vector3(it.x, it.y - 0.035f, it.z + 0.015f)\n                eyeNodeLeft?.localScale = Vector3(0.055f, 0.055f, 0.055f)\n                eyeNodeLeft?.localRotation = Quaternion.axisAngle(Vector3(0.0f, 0.0f, 1.0f), -10f)\n            }\n\n            getRegionPose(FaceRegion.RIGHT_EYE)?.let {\n                eyeNodeRight?.localPosition = Vector3(it.x, it.y - 0.035f, it.z + 0.015f)\n                eyeNodeRight?.localScale = Vector3(0.055f, 0.055f, 0.055f)\n                eyeNodeRight?.localRotation = Quaternion.axisAngle(Vector3(0.0f, 0.0f, 1.0f), 10f)\n            }\n\n            getRegionPose(FaceRegion.UPPER_LIP)?.let {\n                upperLipNode?.localPosition = Vector3(it.x, it.y - 0.035f, it.z + 0.015f)\n                upperLipNode?.localScale = Vector3(0.04f, 0.04f, 0.04f)\n            }\n\n            getRegionPose(FaceRegion.LOWER_LIP)?.let {\n                lowerLipNode?.localPosition = Vector3(it.x, it.y - 0.035f, it.z + 0.015f)\n                lowerLipNode?.localScale = Vector3(0.04f, 0.04f, 0.04f)\n            }\n\n            getRegionPose(FaceRegion.LEFT_GGG)?.let {\n                leftGggNode?.localPosition = Vector3(it.x, it.y - 0.035f, it.z + 0.015f)\n                leftGggNode?.localScale = Vector3(0.04f, 0.04f, 0.04f)\n            }\n\n            getRegionPose(FaceRegion.RIGHT_GGG)?.let {\n                rightGggNode?.localPosition = Vector3(it.x, it.y - 0.035f, it.z + 0.015f)\n                rightGggNode?.localScale = Vector3(0.04f, 0.04f, 0.04f)\n            }\n\n            getRegionPose(FaceRegion.MID_GGG)?.let {\n                midGggNode?.localPosition = Vector3(it.x, it.y - 0.035f, it.z + 0.015f)\n                midGggNode?.localScale = Vector3(0.04f, 0.04f, 0.04f)\n            }\n\n            val faceCheckVM = ViewModelProvider(mainActivity).get(FaceCheckVM::class.java)\n\n\n            \/\/\u4f4e\u982d\n            if (face.getRegionPose(RegionType.FOREHEAD_LEFT).yAxis[2] &lt; -0.4 &amp;&amp; turnTop == 0) {\n                faceCheckVM.text.value = \"\u8acb\u9ede\u982d\"\n                ++turnTop\n            }\n\n            \/\/\u62ac\u982d\n            if (face.getRegionPose(RegionType.FOREHEAD_LEFT).yAxis[2] &gt; 0.01 &amp;&amp; turnBottom == 0) {\n                faceCheckVM.text.value = \"\u8acb\u62ac\u982d\"\n                ++turnBottom\n            }\n\n            \/\/\u9ede\u982d\u5224\u5b9a\u6210\u529f\n            if (turnTop &gt; 0 &amp;&amp; turnBottom &gt; 0) {\n                turnTop = -1\n                turnBottom = -1\n                faceCheckVM.text.value = \"\u9ede\u982d\u5224\u5b9a\u6210\u529f\"\n            }\n\n\n            \/\/\u5fae\u7b11\u76f8\u95dc\n            if ((leftGggNode!!.worldPosition.y - midGggNode!!.worldPosition.y) &gt; 0.006 &amp;&amp;\n                (rightGggNode!!.worldPosition.y - midGggNode!!.worldPosition.y) &gt; 0.006 &amp;&amp;\n                turnTop == -1 &amp;&amp;\n                turnBottom == -1\n            ) {\n                faceCheckVM.text.value = \"\u5fae\u7b11\u5224\u5b9a\u6210\u529f\"\n                takePicture()\n            }\n\n            \/\/\u6416\u982d\u76f8\u95dc\n            if (face.centerPose.xAxis[2] &gt; 0.4 &amp;&amp; turnRight == 0) {\n                ++turnRight\n                faceCheckVM.text.value = \"\u8acb\u8f49\u5411\u5de6\"\n            }\n\n            if (face.centerPose.xAxis[2] &lt; -0.4 &amp;&amp; turnLeft == 0) {\n                ++turnLeft\n                faceCheckVM.text.value = \"\u8acb\u8f49\u5411\u53f3\"\n            }\n\n            if (turnRight &gt; 0 &amp;&amp; turnLeft &gt; 0) {\n                turnRight = -1\n                turnLeft = -1\n                faceCheckVM.text.value = \"\u6416\u982d\u5224\u5b9a\u6210\u529f\"\n            }\n        }\n    }\n\n    private fun takePicture() {\n        if (start) {\n            val handlerThread = HandlerThread(\"PixelCopier\")\n            handlerThread.start()\n\n            val bitmap = Bitmap.createBitmap(\n                sceneView.width,\n                sceneView.height,\n                Bitmap.Config.ARGB_8888\n            )\n\n            PixelCopy.request(sceneView, bitmap, {\n                val name = \"${System.currentTimeMillis()}face.jpg\"\n                val file =\n                    File(mainActivity.getExternalFilesDir(\"face\")?.absolutePath, name)\n                val ops = FileOutputStream(file)\n                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, ops)\n            }, Handler(handlerThread.looper))\n            start = false\n        }\n    }\n}\n<\/code><\/pre>\n<h5>\u63d0\u793a\u8a0a\u606f<\/h5>\n<pre><code class=\"language-Kotlin line-numbers\">class FaceCheckVM : ViewModel() {\n    val text = MutableLiveData&lt;String&gt;().apply {\n        value = \"\u8acb\u9ede\u982d\"\n    }\n}\n<\/code><\/pre>\n<p><a id=\"e\"><\/a><\/p>\n<h4>5.\u7a0b\u5f0f\u78bc\u7bc4\u4f8b<\/h4>\n<pre><code class=\"language-Kotlin line-numbers\">class MainActivity : AppCompatActivity() {\n\n    lateinit var arFragment: FaceArFragment\n    var faceNodeMap = HashMap&lt;AugmentedFace, CustomFaceNode&gt;()\n    private val faceCheckVM by viewModels&lt;FaceCheckVM&gt;()\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_main)\n\n        faceCheckVM.text.observe(this, {\n            face_text_info.text = it\n        })\n\n        arFragment = face_fragment as FaceArFragment\n        val sceneView = arFragment.arSceneView\n        sceneView.cameraStreamRenderPriority = Renderable.RENDER_PRIORITY_FIRST\n        val scene = sceneView.scene\n\n        scene.addOnUpdateListener {\n            sceneView.session\n                ?.getAllTrackables(AugmentedFace::class.java)?.let {\n                    for (face in it) {\n                        if (!faceNodeMap.containsKey(face)) {\n                            val faceNode = CustomFaceNode(face, this, sceneView)\n                            faceNode.setParent(scene)\n                            faceNodeMap[face] = faceNode\n                        }\n                    }\n\n                    val iter = faceNodeMap.entries.iterator()\n                    while (iter.hasNext()) {\n                        val entry = iter.next()\n                        val face = entry.key\n                        if (face.trackingState == TrackingState.PAUSED) {\n                            val faceNode = entry.value\n                            faceNode.setParent(null)\n                            iter.remove()\n                        }\n                    }\n                }\n        }\n    }\n}\n<\/code><\/pre>\n<p><a id=\"f\"><\/a><\/p>\n<h4>6.\u6548\u679c\u5c55\u793a<\/h4>\n<p><a href=\"https:\/\/badgameshow.com\/fly\/wp-content\/uploads\/2021\/07\/Screenrecorder-2021-07-19-17-48-09-559.gif\"><img decoding=\"async\" src=\"https:\/\/badgameshow.com\/fly\/wp-content\/uploads\/2021\/07\/Screenrecorder-2021-07-19-17-48-09-559.gif\" width=\"30%\"\/><\/a><\/p>\n<p><a id=\"g\"><\/a><\/p>\n<h4>7.Github<\/h4>\n<p><a href=\"https:\/\/github.com\/MuHongWeiWei\/ArFaceDemo\" target=\"_blank\" rel=\"noopener\">Github<\/a><\/p>\n\n<div style=\"font-size: 0px; height: 0px; line-height: 0px; margin: 0; padding: 0; clear: both;\"><\/div>","protected":false},"excerpt":{"rendered":"<p>Android \u81c9\u90e8\u5075\u6e2c ArCore \u6d3b\u9ad4\u8fa8\u8b58 Android \u5c07 \u81c9\u90e8\u5075\u6e2c \u6574\u5408\u5230\u7cfb\u7d71,\u9069\u7528\u65bcAndroi &hellip; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"pgc_sgb_lightbox_settings":"","footnotes":""},"categories":[5],"tags":[193,192,15],"class_list":["post-1387","post","type-post","status-publish","format-standard","hentry","category-android","tag-arcore","tag-facedetection","tag-kotlin"],"_links":{"self":[{"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/posts\/1387","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/comments?post=1387"}],"version-history":[{"count":8,"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/posts\/1387\/revisions"}],"predecessor-version":[{"id":1439,"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/posts\/1387\/revisions\/1439"}],"wp:attachment":[{"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/media?parent=1387"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/categories?post=1387"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/tags?post=1387"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}