From 66f0abbd26de4ad8afd5f8f56be70230a789634b Mon Sep 17 00:00:00 2001 From: terric Date: Thu, 16 Nov 2023 01:23:16 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8A=AC=E6=89=8B=E5=8A=A8=E4=BD=9C+=E5=88=87?= =?UTF-8?q?=E6=8D=A2mediapipe=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MediaPipePoseEstimator.cs | 1 - Assets/Resources/UI/MainUI.prefab | 473 ++++++++++++------ .../PoseCheck/EstimateModel/CVEstimator.cs | 8 +- .../PoseCheck/EstimateModel/Estimator.cs | 37 +- .../EstimateModel/MediaPipeEstimator.cs | 135 ++++- .../EstimateModel/OpenPoseEsimater.cs | 39 +- Assets/Scripts/PoseCheck/HandsUp.cs | 21 +- .../Scripts/PoseCheck/MotionCaptureManager.cs | 10 +- Assets/Scripts/YogaConfig.cs | 2 + Assets/Scripts/YogaDataLoader.cs | 15 +- Assets/UI/Img/Steering wheel.png | Bin 0 -> 8514 bytes Assets/UI/Img/Steering wheel.png.meta | 124 +++++ Assets/UI/Img/seat.png | Bin 0 -> 6401 bytes Assets/UI/Img/seat.png.meta | 124 +++++ 14 files changed, 787 insertions(+), 202 deletions(-) create mode 100644 Assets/UI/Img/Steering wheel.png create mode 100644 Assets/UI/Img/Steering wheel.png.meta create mode 100644 Assets/UI/Img/seat.png create mode 100644 Assets/UI/Img/seat.png.meta diff --git a/Assets/OpenCVForUnity/Examples/MainModules/dnn/PoseEstimationMediaPipeExample/MediaPipePoseEstimator.cs b/Assets/OpenCVForUnity/Examples/MainModules/dnn/PoseEstimationMediaPipeExample/MediaPipePoseEstimator.cs index 0e78c38..7357e10 100644 --- a/Assets/OpenCVForUnity/Examples/MainModules/dnn/PoseEstimationMediaPipeExample/MediaPipePoseEstimator.cs +++ b/Assets/OpenCVForUnity/Examples/MainModules/dnn/PoseEstimationMediaPipeExample/MediaPipePoseEstimator.cs @@ -462,7 +462,6 @@ namespace OpenCVForUnityExample.DnnModel float[] landmarks_presence = new float[(39 - auxiliary_points_num)]; results_col4_199_39x5.colRange(new OpenCVRange(4, 5)).get(0, 0, landmarks_presence); - Mat results_col199_316_39x3 = results.rowRange(new OpenCVRange(199, 316 - (3 * auxiliary_points_num))).reshape(1, 39 - auxiliary_points_num); float[] landmarks_world = new float[(39 - auxiliary_points_num) * 3]; results_col199_316_39x3.get(0, 0, landmarks_world); diff --git a/Assets/Resources/UI/MainUI.prefab b/Assets/Resources/UI/MainUI.prefab index 8599a0c..665cc18 100644 --- a/Assets/Resources/UI/MainUI.prefab +++ b/Assets/Resources/UI/MainUI.prefab @@ -1,5 +1,155 @@ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: +--- !u!1 &812840956832231530 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1958927731802958260} + - component: {fileID: 2260945507531342483} + - component: {fileID: 3436322692073427523} + m_Layer: 5 + m_Name: Image + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1958927731802958260 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 812840956832231530} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 2576201504434113081} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: -100, y: -100} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &2260945507531342483 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 812840956832231530} + m_CullTransparentMesh: 1 +--- !u!114 &3436322692073427523 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 812840956832231530} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.49019608, g: 0.49019608, b: 0.49019608, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: 9b4580a483f26ac4388ec72c4c479a83, type: 3} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &4637189147528760871 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4138242365235796459} + - component: {fileID: 7693179399692606510} + - component: {fileID: 3913808783048664634} + m_Layer: 5 + m_Name: Image + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &4138242365235796459 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4637189147528760871} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 7867255813535008431} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: -100, y: -100} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &7693179399692606510 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4637189147528760871} + m_CullTransparentMesh: 1 +--- !u!114 &3913808783048664634 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4637189147528760871} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.49019608, g: 0.49019608, b: 0.49019608, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: c218c4357d09a2f4e94961d8b04540c6, type: 3} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 --- !u!1 &4975108645149662689 GameObject: m_ObjectHideFlags: 0 @@ -87,6 +237,81 @@ CanvasGroup: m_Interactable: 1 m_BlocksRaycasts: 1 m_IgnoreParentGroups: 0 +--- !u!1 &5012355027590094061 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8940443638627649355} + - component: {fileID: 5822777235898107403} + - component: {fileID: 5023932294307215150} + m_Layer: 5 + m_Name: Mask + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 0 +--- !u!224 &8940443638627649355 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5012355027590094061} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 2576201504434113081} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &5822777235898107403 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5012355027590094061} + m_CullTransparentMesh: 1 +--- !u!114 &5023932294307215150 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5012355027590094061} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: fc3cc6afdc9d6f645bad60ad7309189e, type: 3} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 --- !u!1 &5611881615064894930 GameObject: m_ObjectHideFlags: 0 @@ -100,6 +325,7 @@ GameObject: - component: {fileID: 7069011303126796865} - component: {fileID: 4329244061302087914} - component: {fileID: 5061662039744333050} + - component: {fileID: 25324508393644868} m_Layer: 5 m_Name: Driver m_TagString: Untagged @@ -120,12 +346,14 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1449208219289806508} + - {fileID: 4138242365235796459} + - {fileID: 173739580069041147} m_Father: {fileID: 1888392902869825718} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: -450, y: 0} - m_SizeDelta: {x: 700, y: 500} + m_AnchoredPosition: {x: -300, y: 0} + m_SizeDelta: {x: 256, y: 256} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &2054363549373963548 CanvasRenderer: @@ -155,7 +383,7 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_Sprite: {fileID: 21300000, guid: f3df4d9369dcdc840b85c95d3e1176f4, type: 3} + m_Sprite: {fileID: 21300000, guid: 404ba9da9833aa0429c62a90c92f0e33, type: 3} m_Type: 0 m_PreserveAspect: 0 m_FillCenter: 1 @@ -231,6 +459,19 @@ Animator: m_AllowConstantClipSamplingOptimization: 1 m_KeepAnimatorStateOnDisable: 0 m_WriteDefaultValuesOnDisable: 0 +--- !u!114 &25324508393644868 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5611881615064894930} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 31a19414c41e5ae4aae2af33fee712f6, type: 3} + m_Name: + m_EditorClassIdentifier: + m_ShowMaskGraphic: 1 --- !u!1 &6036208431044648634 GameObject: m_ObjectHideFlags: 0 @@ -289,7 +530,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_Material: {fileID: 0} - m_Color: {r: 1, g: 1, b: 1, a: 1} + m_Color: {r: 0.49019608, g: 0.49019608, b: 0.49019608, a: 1} m_RaycastTarget: 1 m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 @@ -310,6 +551,81 @@ MonoBehaviour: m_VerticalOverflow: 0 m_LineSpacing: 1 m_Text: Co-Driver +--- !u!1 &8039057556249263921 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 173739580069041147} + - component: {fileID: 3300850338272086747} + - component: {fileID: 1636194949858596997} + m_Layer: 5 + m_Name: Mask + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 0 +--- !u!224 &173739580069041147 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8039057556249263921} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 7867255813535008431} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &3300850338272086747 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8039057556249263921} + m_CullTransparentMesh: 1 +--- !u!114 &1636194949858596997 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8039057556249263921} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: fc3cc6afdc9d6f645bad60ad7309189e, type: 3} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 --- !u!1 &8042575492156197460 GameObject: m_ObjectHideFlags: 0 @@ -344,8 +660,8 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0} m_AnchorMax: {x: 0.5, y: 0} - m_AnchoredPosition: {x: 0, y: 30} - m_SizeDelta: {x: 160, y: 160} + m_AnchoredPosition: {x: 0, y: 20} + m_SizeDelta: {x: 160, y: 30} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &8391011484397638028 CanvasRenderer: @@ -368,7 +684,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_Material: {fileID: 0} - m_Color: {r: 1, g: 1, b: 1, a: 1} + m_Color: {r: 0.49019608, g: 0.49019608, b: 0.49019608, a: 1} m_RaycastTarget: 1 m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 @@ -488,12 +804,14 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: - {fileID: 6401048517044185304} + - {fileID: 1958927731802958260} + - {fileID: 8940443638627649355} m_Father: {fileID: 1888392902869825718} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: 450, y: 0} - m_SizeDelta: {x: 700, y: 500} + m_AnchoredPosition: {x: 300, y: 0} + m_SizeDelta: {x: 256, y: 256} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &6645283475793646557 CanvasRenderer: @@ -523,7 +841,7 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_Sprite: {fileID: 21300000, guid: 4c832a36ea64b7b4da0fffa78a702860, type: 3} + m_Sprite: {fileID: 21300000, guid: 404ba9da9833aa0429c62a90c92f0e33, type: 3} m_Type: 0 m_PreserveAspect: 0 m_FillCenter: 1 @@ -629,7 +947,6 @@ RectTransform: m_Children: - {fileID: 7867255813535008431} - {fileID: 2576201504434113081} - - {fileID: 3633320821606894435} m_Father: {fileID: 5269051166812510682} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} @@ -637,137 +954,3 @@ RectTransform: m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: 0, y: 0} m_Pivot: {x: 0.5, y: 0.5} ---- !u!1 &9208700712057849365 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 3633320821606894435} - - component: {fileID: 6811689815506691815} - - component: {fileID: 5648383841688649297} - m_Layer: 5 - m_Name: label - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!224 &3633320821606894435 -RectTransform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 9208700712057849365} - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 1888392902869825718} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0.5, y: 0.5} - m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: 0, y: 350} - m_SizeDelta: {x: 450, y: 150} - m_Pivot: {x: 0.5, y: 0.5} ---- !u!222 &6811689815506691815 -CanvasRenderer: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 9208700712057849365} - m_CullTransparentMesh: 1 ---- !u!114 &5648383841688649297 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 9208700712057849365} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} - m_Name: - m_EditorClassIdentifier: - m_Material: {fileID: 0} - m_Color: {r: 1, g: 1, b: 1, a: 1} - m_RaycastTarget: 1 - m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} - m_Maskable: 1 - m_OnCullStateChanged: - m_PersistentCalls: - m_Calls: [] - m_text: Are you sitting at... - m_isRightToLeft: 0 - m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} - m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} - m_fontSharedMaterials: [] - m_fontMaterial: {fileID: 0} - m_fontMaterials: [] - m_fontColor32: - serializedVersion: 2 - rgba: 4286085240 - m_fontColor: {r: 0.47058824, g: 0.47058824, b: 0.47058824, a: 1} - m_enableVertexGradient: 0 - m_colorMode: 3 - m_fontColorGradient: - topLeft: {r: 1, g: 1, b: 1, a: 1} - topRight: {r: 1, g: 1, b: 1, a: 1} - bottomLeft: {r: 1, g: 1, b: 1, a: 1} - bottomRight: {r: 1, g: 1, b: 1, a: 1} - m_fontColorGradientPreset: {fileID: 0} - m_spriteAsset: {fileID: 0} - m_tintAllSprites: 0 - m_StyleSheet: {fileID: 0} - m_TextStyleHashCode: -1183493901 - m_overrideHtmlColors: 0 - m_faceColor: - serializedVersion: 2 - rgba: 4294967295 - m_fontSize: 50 - m_fontSizeBase: 50 - m_fontWeight: 400 - m_enableAutoSizing: 0 - m_fontSizeMin: 18 - m_fontSizeMax: 72 - m_fontStyle: 0 - m_HorizontalAlignment: 2 - m_VerticalAlignment: 512 - m_textAlignment: 65535 - m_characterSpacing: 0 - m_wordSpacing: 0 - m_lineSpacing: 0 - m_lineSpacingMax: 0 - m_paragraphSpacing: 0 - m_charWidthMaxAdj: 0 - m_enableWordWrapping: 1 - m_wordWrappingRatios: 0.4 - m_overflowMode: 0 - m_linkedTextComponent: {fileID: 0} - parentLinkedComponent: {fileID: 0} - m_enableKerning: 1 - m_enableExtraPadding: 0 - checkPaddingRequired: 0 - m_isRichText: 1 - m_parseCtrlCharacters: 1 - m_isOrthographic: 1 - m_isCullingEnabled: 0 - m_horizontalMapping: 0 - m_verticalMapping: 0 - m_uvLineOffset: 0 - m_geometrySortingOrder: 0 - m_IsTextObjectScaleStatic: 0 - m_VertexBufferAutoSizeReduction: 0 - m_useMaxVisibleDescender: 1 - m_pageToDisplay: 1 - m_margin: {x: 0, y: 0, z: 0, w: 0} - m_isUsingLegacyAnimationComponent: 0 - m_isVolumetricText: 0 - m_hasFontAssetChanged: 0 - m_baseMaterial: {fileID: 0} - m_maskOffset: {x: 0, y: 0, z: 0, w: 0} diff --git a/Assets/Scripts/PoseCheck/EstimateModel/CVEstimator.cs b/Assets/Scripts/PoseCheck/EstimateModel/CVEstimator.cs index f568761..a931aa2 100644 --- a/Assets/Scripts/PoseCheck/EstimateModel/CVEstimator.cs +++ b/Assets/Scripts/PoseCheck/EstimateModel/CVEstimator.cs @@ -100,7 +100,6 @@ public class CVEstimator : Singleton { DateTime startTime = DateTime.Now; Mat bgrMat = new Mat(); - while (IsRunning) { Mat rgbaMat = MotionCaptureManager.Instance.RgbaMat; @@ -112,15 +111,12 @@ public class CVEstimator : Singleton } Imgproc.cvtColor(rgbaMat, bgrMat, Imgproc.COLOR_RGBA2BGR); - if (!YogaManager.Instance.CurrentEstimator.TryGetROIRegionMat(rgbaMat, bgrMat, out List voloResultBox)) + if (!YogaManager.Instance.CurrentEstimator.TryGetROIRegionMat(bgrMat, rgbaMat, out Mat result)) { continue; //重新检测 } - //更新人物框检测结果 - MotionCaptureManager.Instance.VoloResult = voloResultBox; - - if (!YogaManager.Instance.CurrentEstimator.Esitmate(bgrMat, voloResultBox, out List points)) + if (!YogaManager.Instance.CurrentEstimator.Esitmate(bgrMat, rgbaMat, result, out List points)) { Debug.LogWarning("Pose estimation failed. Re-Estimating."); continue; //重新检测 diff --git a/Assets/Scripts/PoseCheck/EstimateModel/Estimator.cs b/Assets/Scripts/PoseCheck/EstimateModel/Estimator.cs index 33fd708..d49161a 100644 --- a/Assets/Scripts/PoseCheck/EstimateModel/Estimator.cs +++ b/Assets/Scripts/PoseCheck/EstimateModel/Estimator.cs @@ -37,9 +37,9 @@ public abstract class Estimator public abstract void DisposeModel(); //检测到的人体框 默认使用VOLO7 - public virtual bool TryGetROIRegionMat(Mat rgbaMat, Mat bgrMat, out List voloResult) + public virtual bool TryGetROIRegionMat(Mat bgrMat, Mat rgbaMat, out Mat result) { - voloResult = null; + result = null; if (_objectDetector == null) { Debug.LogWarning("ObjectDetector is not ready. "); @@ -47,37 +47,8 @@ public abstract class Estimator } //获取数据 - Mat results = _objectDetector.infer(bgrMat); - var voloResultBox = new List(); - bool hasValidObject = false; - for (int i = results.rows() - 1; i >= 0; --i) - { - float[] box = new float[4]; - results.get(i, 0, box); //方框 - float[] conf = new float[1]; - results.get(i, 4, conf); //检测数据 - float[] cls = new float[1]; - results.get(i, 5, cls); //类别 + result = _objectDetector.infer(bgrMat); - if (!IsObjectValid(box, conf, cls, rgbaMat)) - { - return false; - } - hasValidObject = true; - voloResultBox.Clear(); - voloResultBox.Add(box); - voloResultBox.Add(conf); - voloResultBox.Add(cls); - break; - } - - if (!hasValidObject) //没有检测到人体 - { - Debug.Log("No person block found. Re-Estimation."); - return false; //重新检测 - } - - voloResult = voloResultBox; return true; } @@ -128,7 +99,7 @@ public abstract class Estimator Imgproc.putText(image, label, new Point(left, top), Imgproc.FONT_HERSHEY_SIMPLEX, 0.5, Scalar.all(255), 1, Imgproc.LINE_AA); } - public abstract bool Esitmate(Mat bgrMat, List voloResultBox, out List points); + public abstract bool Esitmate(Mat bgrMat, Mat rgbaMat, Mat result, out List points); public virtual void Check(ref Mat img) { } } diff --git a/Assets/Scripts/PoseCheck/EstimateModel/MediaPipeEstimator.cs b/Assets/Scripts/PoseCheck/EstimateModel/MediaPipeEstimator.cs index a13491b..e87eeaa 100644 --- a/Assets/Scripts/PoseCheck/EstimateModel/MediaPipeEstimator.cs +++ b/Assets/Scripts/PoseCheck/EstimateModel/MediaPipeEstimator.cs @@ -1,21 +1,148 @@ using OpenCVForUnity.CoreModule; +using OpenCVForUnity.ImgprocModule; +using OpenCVForUnity.UnityUtils; +using OpenCVForUnityExample.DnnModel; using System; using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Experimental.GlobalIllumination; +using static UnityEngine.Networking.UnityWebRequest; +using OpenCVRange = OpenCVForUnity.CoreModule.Range; public class MediaPipeEstimator : Estimator { + private MediaPipePersonDetector _personDetector; + private MediaPipePoseEstimator _poseEstimator; + private bool _mask = false; public override void InitModel() { - throw new NotImplementedException(); + var personDetectModelPath = Utils.getFilePath(YogaConfig.MODEL_PATHS[ModelType.MediapipePersonDetect]); + if (string.IsNullOrEmpty(personDetectModelPath)) + { + Debug.LogError(YogaConfig.MODEL_PATHS[ModelType.MediapipePersonDetect] + " is not loaded. Please read “StreamingAssets/OpenCVForUnity/dnn/setup_dnn_module.pdf” to make the necessary setup."); + } + else + { + _personDetector = new MediaPipePersonDetector(personDetectModelPath, 0.3f, 0.6f, 5000); + } + + var poseModelPath = Utils.getFilePath(YogaConfig.MODEL_PATHS[ModelType.MediapipePose]); + if (string.IsNullOrEmpty(poseModelPath)) + { + Debug.LogError(YogaConfig.MODEL_PATHS[ModelType.MediapipePose] + " is not loaded. Please read “StreamingAssets/OpenCVForUnity/dnn/setup_dnn_module.pdf” to make the necessary setup."); + } + else + { + _poseEstimator = new MediaPipePoseEstimator(poseModelPath, 0.9f); + } } - public override bool Esitmate(Mat bgrMat, List voloResultBox, out List points) + public override void Check(ref Mat img) { - throw new NotImplementedException(); + if (_personDetector == null || _poseEstimator == null) + { + Imgproc.putText(img, "model file is not loaded.", new Point(5, img.rows() - 30), Imgproc.FONT_HERSHEY_SIMPLEX, 0.7, new Scalar(255, 255, 255, 255), 2, Imgproc.LINE_AA, false); + Imgproc.putText(img, "Please read console message.", new Point(5, img.rows() - 10), Imgproc.FONT_HERSHEY_SIMPLEX, 0.7, new Scalar(255, 255, 255, 255), 2, Imgproc.LINE_AA, false); + } + } + + public override bool TryGetROIRegionMat(Mat bgrMat, Mat rgbaMat, out Mat result) + { + result = _personDetector.infer(bgrMat); + + if (result.rows() == 0) + { + return false; + } + + return true; + } + + public override bool Esitmate(Mat bgrMat, Mat rgbaMat, Mat result, out List points) + { + points = null; + for (int i = 0; i < result.rows(); ++i) + { + List results = _poseEstimator.infer(bgrMat, result.row(i)); + + //_poseEstimator.visualize(rgbaMat, results[0], false, false); + + points = GetKeypoints(results[0]); + if (points != null && YogaManager.Instance.ActionCheckPoints(points)) + { + return true; + } + } + //没有检测到人体 + return false; + } + + private List GetKeypoints(Mat result) + { + var retVal = new List(); + if (result.empty() || result.rows() < 317) + return retVal; + + //获取关键点 + float[] conf = new float[1]; + result.get(316, 0, conf); + float[] bbox = new float[4]; + result.get(0, 0, bbox); + + int auxiliary_points_num = 6; + Mat results_col4_199_39x5 = result.rowRange(new OpenCVRange(4, 199 - (5 * auxiliary_points_num))).reshape(1, 39 - auxiliary_points_num); + float[] landmarks_screen_xy = new float[(39 - auxiliary_points_num) * 2]; + results_col4_199_39x5.colRange(new OpenCVRange(0, 2)).get(0, 0, landmarks_screen_xy); + + float[] landmarks_screen_xyz = new float[(39 - auxiliary_points_num) * 3]; + results_col4_199_39x5.colRange(new OpenCVRange(0, 3)).get(0, 0, landmarks_screen_xyz); + + // # only show visible keypoints which presence bigger than 0.8 + float[] landmarks_presence = new float[(39 - auxiliary_points_num)]; + results_col4_199_39x5.colRange(new OpenCVRange(4, 5)).get(0, 0, landmarks_presence); + + + Mat results_col199_316_39x3 = result.rowRange(new OpenCVRange(199, 316 - (3 * auxiliary_points_num))).reshape(1, 39 - auxiliary_points_num); + float[] landmarks_world = new float[(39 - auxiliary_points_num) * 3]; + results_col199_316_39x3.get(0, 0, landmarks_world); + + + + //将关键点映射到现有的open pose关键点上 + retVal.Add(GetPointData(landmarks_screen_xy, landmarks_presence, 0)); //Nose + retVal.Add(new Point(-1, -1)); //Neck + retVal.Add(GetPointData(landmarks_screen_xy, landmarks_presence, 11)); //LShoulder + retVal.Add(GetPointData(landmarks_screen_xy, landmarks_presence, 13)); //LElbow + retVal.Add(GetPointData(landmarks_screen_xy, landmarks_presence, 15)); //LWrist + retVal.Add(GetPointData(landmarks_screen_xy, landmarks_presence, 12)); //RShoulder + retVal.Add(GetPointData(landmarks_screen_xy, landmarks_presence, 14)); //RElbow + retVal.Add(GetPointData(landmarks_screen_xy, landmarks_presence, 16)); //RWrist + retVal.Add(GetPointData(landmarks_screen_xy, landmarks_presence, 23)); //LHip + retVal.Add(GetPointData(landmarks_screen_xy, landmarks_presence, 25)); //LKnee + retVal.Add(GetPointData(landmarks_screen_xy, landmarks_presence, 27)); //LAnkle + retVal.Add(GetPointData(landmarks_screen_xy, landmarks_presence, 24)); //RHip + retVal.Add(GetPointData(landmarks_screen_xy, landmarks_presence, 26)); //RKnee + retVal.Add(GetPointData(landmarks_screen_xy, landmarks_presence, 28)); //RAnkle + retVal.Add(GetPointData(landmarks_screen_xy, landmarks_presence, 2)); //LEye + retVal.Add(GetPointData(landmarks_screen_xy, landmarks_presence, 5)); //REye + retVal.Add(GetPointData(landmarks_screen_xy, landmarks_presence, 7)); //LEar + retVal.Add(GetPointData(landmarks_screen_xy, landmarks_presence, 8)); //REar + retVal.Add(new Point(-1, -1)); //Background + + return retVal; + } + + private Point GetPointData(float[] landmarks, float[] _landmarks_presence, int index) + { + if (_landmarks_presence[index] < 0.8f) + return null; + + index = index * 2; + return new Point(landmarks[index], landmarks[index + 1]); } public override void DisposeModel() { - throw new NotImplementedException(); + } } \ No newline at end of file diff --git a/Assets/Scripts/PoseCheck/EstimateModel/OpenPoseEsimater.cs b/Assets/Scripts/PoseCheck/EstimateModel/OpenPoseEsimater.cs index cb670a6..bc4fc3b 100644 --- a/Assets/Scripts/PoseCheck/EstimateModel/OpenPoseEsimater.cs +++ b/Assets/Scripts/PoseCheck/EstimateModel/OpenPoseEsimater.cs @@ -6,10 +6,11 @@ using System; using System.Collections.Generic; using UnityEngine; using Yoga; +using static UnityEngine.Networking.UnityWebRequest; public class OpenPoseEsimater : Estimator { - KeypointsModel _openPoseModel; + KeypointsModel _openPoseModel; private Net _net; private double threshold = 0.5; @@ -39,8 +40,42 @@ public class OpenPoseEsimater : Estimator _net.Dispose(); } - public override bool Esitmate(Mat bgrMat, List voloResultBox, out List points) + public override bool Esitmate(Mat bgrMat, Mat rgbaMat, Mat results, out List points) { + var voloResultBox = new List(); + points = null; + bool hasValidObject = false; + + for (int i = results.rows() - 1; i >= 0; --i) + { + float[] box = new float[4]; + results.get(i, 0, box); //方框 + float[] conf = new float[1]; + results.get(i, 4, conf); //检测数据 + float[] cls = new float[1]; + results.get(i, 5, cls); //类别 + + if (!IsObjectValid(box, conf, cls, rgbaMat)) + { + return false; + } + hasValidObject = true; + voloResultBox.Clear(); + voloResultBox.Add(box); + voloResultBox.Add(conf); + voloResultBox.Add(cls); + break; + } + + if (!hasValidObject) //没有检测到人体 + { + Debug.Log("No person block found. Re-Estimation."); + return false; //重新检测 + } + + //更新人物框检测结果 + MotionCaptureManager.Instance.VoloResult = voloResultBox; + points = null; if (_openPoseModel == null) diff --git a/Assets/Scripts/PoseCheck/HandsUp.cs b/Assets/Scripts/PoseCheck/HandsUp.cs index 93aa193..0dd5496 100644 --- a/Assets/Scripts/PoseCheck/HandsUp.cs +++ b/Assets/Scripts/PoseCheck/HandsUp.cs @@ -1,11 +1,13 @@ using OpenCVForUnity.CoreModule; +using System; using System.Collections.Generic; +using UnityEngine; public class HandsUp : PoseBase { public override bool CheckPose(List points) { - // 必须包含 Neck 和 LEar 和 Neck 的点位 + // 必须包含 "Nose", "RShoulder", "LShoulder", "RElbow", "LElbow" 点位 if (!YogaManager.Instance.ActionCheckPoints(points)) return false; @@ -13,6 +15,21 @@ public class HandsUp : PoseBase if (!YogaManager.Instance.ActionCheckPoints(basePoint)) return false; - return false; + + var rArmVector = ("RElbow".vector(points) - "RShoulder".vector(points)).normalized; + var lArmVector = ("LElbow".vector(points) - "LShoulder".vector(points)).normalized; + + var rArmBaseVector = ("RElbow".vector(basePoint) - "RShoulder".vector(basePoint)).normalized; + var lArmBaseVector = ("LElbow".vector(basePoint) - "LShoulder".vector(basePoint)).normalized; + + var angleR = Vector3.Angle(rArmVector, rArmBaseVector); + var angleL = Vector3.Angle(lArmVector, lArmBaseVector); + + Debug.LogWarning("angleR:" + angleR + " angleL:" + angleL); + + if (angleR < 10 || angleL < 10) + return false; + + return true; } } \ No newline at end of file diff --git a/Assets/Scripts/PoseCheck/MotionCaptureManager.cs b/Assets/Scripts/PoseCheck/MotionCaptureManager.cs index 698bd06..5885d58 100644 --- a/Assets/Scripts/PoseCheck/MotionCaptureManager.cs +++ b/Assets/Scripts/PoseCheck/MotionCaptureManager.cs @@ -61,10 +61,16 @@ namespace Yoga base.Init(); _webCamTextureToMatHelper = gameObject.GetComponent(); + +#if UNITY_ANDROID && !UNITY_EDITOR + // Avoids the front camera low light issue that occurs in only some Android devices (e.g. Google Pixel, Pixel2). + webCamTextureToMatHelper.avoidAndroidFrontCameraLowLightIssue = true; +#endif + _webCamTextureToMatHelper.Initialize(); UIManager.Instance.LoadReset(); - + if (YogaManager.Instance.Action == null) YogaManager.Instance.InitData(); @@ -78,7 +84,7 @@ namespace Yoga case ModelType.OpenPose: YogaManager.Instance.CurrentEstimator = new OpenPoseEsimater(); break; - case ModelType.Mediapipe: + case ModelType.MediapipePose: YogaManager.Instance.CurrentEstimator = new MediaPipeEstimator(); break; } diff --git a/Assets/Scripts/YogaConfig.cs b/Assets/Scripts/YogaConfig.cs index c5c9b8d..7a2dd87 100644 --- a/Assets/Scripts/YogaConfig.cs +++ b/Assets/Scripts/YogaConfig.cs @@ -57,6 +57,8 @@ public static class YogaConfig public static readonly Dictionary MODEL_PATHS = new Dictionary() { {ModelType.OpenPose,"OpenCVForUnity/dnn/lightweight_pose_estimation_201912.onnx" }, + {ModelType.MediapipePersonDetect,"OpenCVForUnity/dnn/person_detection_mediapipe_2023mar.onnx" }, + {ModelType.MediapipePose,"OpenCVForUnity/dnn/pose_estimation_mediapipe_2023mar.onnx" }, //{"","" }, }; diff --git a/Assets/Scripts/YogaDataLoader.cs b/Assets/Scripts/YogaDataLoader.cs index 9ee8b61..4029025 100644 --- a/Assets/Scripts/YogaDataLoader.cs +++ b/Assets/Scripts/YogaDataLoader.cs @@ -13,12 +13,12 @@ public class YogaDataLoader #if UNITY_EDITOR data[-1] = new YogaData() { - VideoPath = "Video/Action2", - Action = AvatarAction.Nod, - ModelType = ModelType.OpenPose, + VideoPath = "Video/Action3", + Action = AvatarAction.HandsUp, + ModelType = ModelType.MediapipePose, MaxActionCount = 1000, TotalSeconds = 20.0f, - MustPoints = new List() { "Nose", "REye", "LEye", "Neck" }, + MustPoints = new List() { "Nose", "RShoulder", "LShoulder", "RElbow", "LElbow" }, //AnyPoints = new List() { "RWrist", "LWrist" } }; #endif @@ -44,10 +44,10 @@ public class YogaDataLoader { VideoPath = "Video/Action3", Action = AvatarAction.HandsUp, - ModelType = ModelType.OpenPose, + ModelType = ModelType.MediapipePose, MaxActionCount = 4, TotalSeconds = 12.66f, - MustPoints = new List() { "Nose", "RShoulder", "LShoulder", "RightWrist" } + MustPoints = new List() { "Nose", "RShoulder", "LShoulder", "RElbow", "LElbow" } }; if (data.ContainsKey(index)) @@ -76,6 +76,7 @@ public enum ModelType { OpenPose, YoloV7, - Mediapipe, + MediapipePose, + MediapipePersonDetect, //, } \ No newline at end of file diff --git a/Assets/UI/Img/Steering wheel.png b/Assets/UI/Img/Steering wheel.png new file mode 100644 index 0000000000000000000000000000000000000000..c54d8b7fe79593af913df6897f6170ed83fec026 GIT binary patch literal 8514 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