今天開始給大家分享mediapipe學習,踩坑過程.
首先還是要學習官方教程的 https://google.github.io/mediapipe/getting_started/hello_world_android.html
環境搭建后期在分享給大家
我現在使用的是windown系統, 也跑過centos上,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
helloworld!android
介紹
此 Codelab 在 Android 設備上使用 MediaPipe。
你會學到什么
如何開發使用 MediaPipe 的 Android 應用程序並在 Android 上運行 MediaPipe 圖。
你將建造什么
用於實時 Sobel 邊緣檢測的簡單相機應用程序,適用於 Android 設備上的實時視頻流。
設置
- 在您的系統上安裝 MediaPipe,有關詳細信息,請參閱MediaPipe 安裝指南。
- 安裝 Android 開發 SDK 和 Android NDK。另請參閱 [MediaPipe 安裝指南] 中的操作方法。
- 在您的 Android 設備上啟用開發者選項。
- 在您的系統上設置Bazel以構建和部署 Android 應用程序。
邊緣檢測圖
我們將使用下圖edge_detection_mobile_gpu.pbtxt
:
# MediaPipe graph that performs GPU Sobel edge detection on a live video stream.
# Used in the examples in
# mediapipe/examples/android/src/java/com/mediapipe/apps/basic and
# mediapipe/examples/ios/edgedetectiongpu.
# Images coming into and out of the graph.
input_stream: "input_video"
output_stream: "output_video"
# Converts RGB images into luminance images, still stored in RGB format.
node: {
calculator: "LuminanceCalculator"
input_stream: "input_video"
output_stream: "luma_video"
}
# Applies the Sobel filter to luminance images stored in RGB format.
node: {
calculator: "SobelEdgesCalculator"
input_stream: "luma_video"
output_stream: "output_video"
}
圖表的可視化如下所示:
該圖有一個輸入流,命名input_video
為所有傳入的幀,這些幀將由您的設備的相機提供。
圖中的第一個節點LuminanceCalculator
,采用單個數據包(圖像幀)並使用 OpenGL 着色器應用亮度變化。生成的圖像幀被發送到luma_video
輸出流。
第二個節點對流SobelEdgesCalculator
中的傳入數據包應用邊緣檢測,luma_video
並在輸出流中輸出結果output_video
。
我們的 Android 應用程序將顯示output_video
流的輸出圖像幀。
初始最小應用程序設置
我們首先從一個顯示“Hello World!”的簡單 Android 應用程序開始。屏幕上。如果您熟悉使用bazel
.
創建一個新目錄,您將在其中創建 Android 應用程序。例如,本教程的完整代碼可以在mediapipe/examples/android/src/java/com/google/mediapipe/apps/basic
. $APPLICATION_PATH
我們將在整個 Codelab 中引用此路徑。
請注意,在應用程序的路徑中:
- 該應用程序名為
helloworld
. - 該
$PACKAGE_PATH
應用程序是com.google.mediapipe.apps.basic
. 這在本教程的代碼片段中使用,因此請記住$PACKAGE_PATH
在復制/使用代碼片段時使用您自己的。
將文件添加activity_main.xml
到$APPLICATION_PATH/res/layout
. 這將TextView
在應用程序的全屏上顯示一個字符串Hello World!
:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
添加一個加載布局內容的簡單對象,MainActivity.java
如下所示:$APPLICATION_PATH
activity_main.xml
package com.google.mediapipe.apps.basic;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
/** Bare-bones main activity. */
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
添加一個清單文件AndroidManifest.xml
to $APPLICATION_PATH
,它MainActivity
在應用程序啟動時啟動:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.mediapipe.apps.basic">
<uses-sdk
android:minSdkVersion="19"
android:targetSdkVersion="19" />
<application
android:allowBackup="true"
android:label="${appName}"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name="${mainActivity}"
android:exported="true"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
在我們的應用程序中,我們在應用程序中使用了一個Theme.AppCompat
主題,因此我們需要適當的主題引用。添加colors.xml
到$APPLICATION_PATH/res/values/
:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
</resources>
添加styles.xml
到$APPLICATION_PATH/res/values/
:
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>
要構建應用程序,請將BUILD
文件添加到清單中$APPLICATION_PATH
,並且${appName}
清單${mainActivity}
中的和將替換為中指定的字符串,BUILD
如下所示。
android_library(
name = "basic_lib",
srcs = glob(["*.java"]),
manifest = "AndroidManifest.xml",
resource_files = glob(["res/**"]),
deps = [
"//third_party:android_constraint_layout",
"//third_party:androidx_appcompat",
],
)
android_binary(
name = "helloworld",
manifest = "AndroidManifest.xml",
manifest_values = {
"applicationId": "com.google.mediapipe.apps.basic",
"appName": "Hello World",
"mainActivity": ".MainActivity",
},
multidex = "native",
deps = [
":basic_lib",
],
)
該android_library
規則為MainActivity
、 資源文件和AndroidManifest.xml
.
該android_binary
規則使用basic_lib
生成的 Android 庫構建二進制 APK 以安裝在您的 Android 設備上。
要構建應用程序,請使用以下命令:
bazel build -c opt --config=android_arm64 $APPLICATION_PATH:helloworld
使用 . 安裝生成的 APK 文件adb install
。例如:
adb install bazel-bin/$APPLICATION_PATH/helloworld.apk
在您的設備上打開應用程序。它應該顯示一個帶有文本的屏幕Hello World!
。
通過使用相機CameraX
相機權限
要在我們的應用程序中使用攝像頭,我們需要請求用戶提供對攝像頭的訪問權限。要請求相機權限,請將以下內容添加到AndroidManifest.xml
:
<!-- For using the camera -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
在同一文件中將最低 SDK 版本更改為21
和目標 SDK 版本:27
<uses-sdk
android:minSdkVersion="21"
android:targetSdkVersion="27" />
這可確保提示用戶請求相機權限,並使我們能夠使用CameraX庫進行相機訪問。
要請求攝像頭權限,我們可以使用 MediaPipe 組件提供的實用程序,即PermissionHelper
. 要使用它,請"//mediapipe/java/com/google/mediapipe/components:android_components"
在.mediapipe_lib
BUILD
要使用PermissionHelper
in MainActivity
,請將以下行添加到onCreate
函數中:
PermissionHelper.checkAndRequestCameraPermissions(this);
這會通過屏幕上的對話框提示用戶請求在此應用程序中使用相機的權限。
添加以下代碼來處理用戶響應:
@Override
public void onRequestPermissionsResult(
int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
PermissionHelper.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
protected void onResume() {
super.onResume();
if (PermissionHelper.cameraPermissionsGranted(this)) {
startCamera();
}
}
public void startCamera() {}
我們暫時將startCamera()
方法留空。當用戶響應提示時,MainActivity
將恢復並onResume()
調用。該代碼將確認已授予使用相機的權限,然后將啟動相機。
重建並安裝應用程序。您現在應該會看到請求訪問應用程序的攝像頭的提示。
注意:如果沒有對話框提示,請卸載並重新安裝應用程序。如果您沒有更改文件中的minSdkVersion
and也可能發生這種情況。targetSdkVersion
AndroidManifest.xml
相機訪問
有了可用的相機權限,我們就可以開始並從相機中獲取幀。
要查看來自相機的幀,我們將使用SurfaceView
. 來自相機的每一幀都將存儲在一個SurfaceTexture
對象中。要使用這些,我們首先需要更改應用程序的布局。
從中刪除整個TextView
代碼塊$APPLICATION_PATH/res/layout/activity_main.xml
並添加以下代碼:
<FrameLayout
android:id="@+id/preview_display_layout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1">
<TextView
android:id="@+id/no_camera_access_view"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:gravity="center"
android:text="@string/no_camera_access" />
</FrameLayout>
這個代碼塊有一個新的FrameLayout
命名preview_display_layout
和TextView
嵌套在它里面,命名no_camera_access_preview
。當未授予相機訪問權限時,我們的應用程序將顯示TextView
存儲在變量中的字符串消息no_camera_access
。在文件中添加以下行$APPLICATION_PATH/res/values/strings.xml
:
<string name="no_camera_access" translatable="false">Please grant camera permissions.</string>
當用戶不授予相機權限時,屏幕現在將如下所示:
現在,我們將SurfaceTexture
和SurfaceView
對象添加到MainActivity
:
private SurfaceTexture previewFrameTexture;
private SurfaceView previewDisplayView;
在函數中,在請求相機權限之前onCreate(Bundle)
添加以下兩行:
previewDisplayView = new SurfaceView(this);
setupPreviewDisplayView();
現在添加代碼定義setupPreviewDisplayView()
:
private void setupPreviewDisplayView() {
previewDisplayView.setVisibility(View.GONE);
ViewGroup viewGroup = findViewById(R.id.preview_display_layout);
viewGroup.addView(previewDisplayView);
}
我們定義了一個新SurfaceView
對象並將其添加到preview_display_layout
FrameLayout
對象中,以便我們可以使用它來使用SurfaceTexture
名為 的對象顯示相機幀previewFrameTexture
。
要previewFrameTexture
用於獲取相機幀,我們將使用CameraX。MediaPipe 提供了一個名為CameraXPreviewHelper
使用CameraX的實用程序。當通過 啟動相機時,此類會更新偵聽器onCameraStarted(@Nullable SurfaceTexture)
。
要使用此實用程序,請修改BUILD
文件以添加對"//mediapipe/java/com/google/mediapipe/components:android_camerax_helper"
.
現在導入CameraXPreviewHelper
並將以下行添加到MainActivity
:
private CameraXPreviewHelper cameraHelper;
現在,我們可以將我們的實現添加到startCamera()
:
public void startCamera() {
cameraHelper = new CameraXPreviewHelper();
cameraHelper.setOnCameraStartedListener(
surfaceTexture -> {
previewFrameTexture = surfaceTexture;
// Make the display view visible to start showing the preview.
previewDisplayView.setVisibility(View.VISIBLE);
});
}
這將創建一個新CameraXPreviewHelper
對象並在該對象上添加一個匿名偵聽器。當cameraHelper
有信號表明相機已經啟動並且surfaceTexture
可以獲取幀時,我們將其另存surfaceTexture
為previewFrameTexture
,並使其previewDisplayView
可見,以便我們可以開始查看previewFrameTexture
.
但是,在啟動相機之前,我們需要決定要使用哪個相機。CameraXPreviewHelper
繼承 from CameraHelper
which 提供兩個選項,FRONT
和BACK
. 我們可以將BUILD
文件中的決定作為元數據傳遞,這樣無需更改代碼即可使用不同的相機構建另一個版本的應用程序。
假設我們想使用BACK
相機對我們從相機查看的實時場景執行邊緣檢測,請將元數據添加到AndroidManifest.xml
:
...
<meta-data android:name="cameraFacingFront" android:value="${cameraFacingFront}"/>
</application>
</manifest>
BUILD
並在helloworld
android 二進制規則中使用新條目指定選擇manifest_values
:
manifest_values = {
"applicationId": "com.google.mediapipe.apps.basic",
"appName": "Hello World",
"mainActivity": ".MainActivity",
"cameraFacingFront": "False",
},
現在,MainActivity
要檢索 中指定的元數據manifest_values
,添加一個ApplicationInfo
對象:
private ApplicationInfo applicationInfo;
在onCreate()
函數中,添加:
try {
applicationInfo =
getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);
} catch (NameNotFoundException e) {
Log.e(TAG, "Cannot find application info: " + e);
}
startCamera()
現在在函數末尾添加以下行:
CameraHelper.CameraFacing cameraFacing =
applicationInfo.metaData.getBoolean("cameraFacingFront", false)
? CameraHelper.CameraFacing.FRONT
: CameraHelper.CameraFacing.BACK;
cameraHelper.startCamera(this, cameraFacing, /*unusedSurfaceTexture=*/ null);
此時,應用程序應該構建成功。但是,當您在設備上運行應用程序時,您會看到黑屏(即使已授予相機權限)。這是因為即使我們保存了surfaceTexture
提供的變量CameraXPreviewHelper
,previewSurfaceView
也不會使用它的輸出並將其顯示在屏幕上。
由於我們想在 MediaPipe 圖中使用幀,因此我們不會在本教程中添加代碼來直接查看相機輸出。相反,我們直接跳到如何將相機幀發送到 MediaPipe 圖形並在屏幕上顯示圖形的輸出進行處理。
ExternalTextureConverter
設置
ASurfaceTexture
從流中捕獲圖像幀作為 OpenGL ES 紋理。要使用 MediaPipe 圖,從相機捕獲的幀應存儲在常規 Open GL 紋理對象中。MediaPipe 提供了一個類,ExternalTextureConverter
用於將存儲在SurfaceTexture
對象中的圖像轉換為常規的 OpenGL 紋理對象。
要使用ExternalTextureConverter
,我們還需要一個EGLContext
,它由一個EglManager
對象創建和管理。將依賴項添加到BUILD
要使用的文件中EglManager
,"//mediapipe/java/com/google/mediapipe/glutil"
.
在MainActivity
中,添加以下聲明:
private EglManager eglManager;
private ExternalTextureConverter converter;
在函數中,在請求相機權限之前onCreate(Bundle)
添加一條語句來初始化對象:eglManager
eglManager = new EglManager(null);
回想一下,我們在 中定義了確認相機權限已被授予並調用的onResume()
函數。在此檢查之前,添加以下行以初始化對象:MainActivity
startCamera()
onResume()
converter
converter = new ExternalTextureConverter(eglManager.getContext());
這converter
現在使用由GLContext
管理eglManager
。
我們還需要重寫中的onPause()
函數,MainActivity
以便如果應用程序進入暫停狀態,我們會converter
正確關閉:
@Override
protected void onPause() {
super.onPause();
converter.close();
}
要將輸出通過管道previewFrameTexture
傳輸到converter
,請將以下代碼塊添加到setupPreviewDisplayView()
:
previewDisplayView
.getHolder()
.addCallback(
new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// (Re-)Compute the ideal size of the camera-preview display (the area that the
// camera-preview frames get rendered onto, potentially with scaling and rotation)
// based on the size of the SurfaceView that contains the display.
Size viewSize = new Size(width, height);
Size displaySize = cameraHelper.computeDisplaySizeFromViewSize(viewSize);
// Connect the converter to the camera-preview frames as its input (via
// previewFrameTexture), and configure the output width and height as the computed
// display size.
converter.setSurfaceTextureAndAttachToGLContext(
previewFrameTexture, displaySize.getWidth(), displaySize.getHeight());
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {}
});
在此代碼塊中,我們添加了一個自定義SurfaceHolder.Callback
並previewDisplayView
實現該surfaceChanged(SurfaceHolder holder, int format, int width, int height)
函數,以計算設備屏幕上相機幀的適當顯示大小,並將previewFrameTexture
對象綁定並將計算的幀發送displaySize
到converter
.
我們現在准備好在 MediaPipe 圖中使用相機幀。
在 Android 中使用 MediaPipe 圖
添加相關依賴
要使用 MediaPipe 圖,我們需要將依賴項添加到 Android 上的 MediaPipe 框架。我們將首先添加一個構建規則來構建一個cc_binary
使用 MediaPipe 框架的 JNI 代碼,然后構建一個cc_library
規則以在我們的應用程序中使用這個二進制文件。將以下代碼塊添加到您的BUILD
文件中:
cc_binary(
name = "libmediapipe_jni.so",
linkshared = 1,
linkstatic = 1,
deps = [
"//mediapipe/java/com/google/mediapipe/framework/jni:mediapipe_framework_jni",
],
)
cc_library(
name = "mediapipe_jni_lib",
srcs = [":libmediapipe_jni.so"],
alwayslink = 1,
)
將依賴項添加":mediapipe_jni_lib"
到文件中的mediapipe_lib
構建規則BUILD
。
接下來,我們需要添加特定於我們要在應用程序中使用的 MediaPipe 圖的依賴項。
libmediapipe_jni.so
首先,在構建規則中為所有計算器代碼添加依賴項:
"//mediapipe/graphs/edge_detection:mobile_calculators",
MediaPipe 圖表是.pbtxt
文件,但要在應用程序中使用它們,我們需要使用mediapipe_binary_graph
構建規則生成.binarypb
文件。
在helloworld
android 二進制構建規則中,將mediapipe_binary_graph
特定於圖形的目標添加為資產:
assets = [
"//mediapipe/graphs/edge_detection:mobile_gpu_binary_graph",
],
assets_dir = "",
在assets
構建規則中,您還可以添加其他資產,例如圖形中使用的 TensorFlowLite 模型。
此外,manifest_values
為特定於圖形的屬性添加其他屬性,以便稍后在以下位置檢索MainActivity
:
manifest_values = {
"applicationId": "com.google.mediapipe.apps.basic",
"appName": "Hello World",
"mainActivity": ".MainActivity",
"cameraFacingFront": "False",
"binaryGraphName": "mobile_gpu.binarypb",
"inputVideoStreamName": "input_video",
"outputVideoStreamName": "output_video",
},
請注意,binaryGraphName
表示二進制圖的文件名,由目標output_name
中的字段確定mediapipe_binary_graph
。inputVideoStreamName
和outputVideoStreamName
分別是圖中指定的輸入和輸出視頻流名稱。
現在,MainActivity
需要加載 MediaPipe 框架。此外,該框架使用 OpenCV,因此MainActvity
也應該加載OpenCV
. MainActivity
在(在類內部,但不在任何函數內部)使用以下代碼來加載兩個依賴項:
static {
// Load all native libraries needed by the app.
System.loadLibrary("mediapipe_jni");
System.loadLibrary("opencv_java3");
}
使用圖表MainActivity
首先,我們需要加載包含從圖形文件.binarypb
編譯的資產。.pbtxt
為此,我們可以使用 MediaPipe 實用程序,AndroidAssetUtil
.
onCreate(Bundle)
在初始化之前初始化資產管理器eglManager
:
// Initialize asset manager so that MediaPipe native libraries can access the app assets, e.g.,
// binary graphs.
AndroidAssetUtil.initializeNativeAssetManager(this);
現在,我們需要設置一個FrameProcessor
對象,將 准備好的相機幀發送converter
到 MediaPipe 圖並運行該圖,准備輸出,然后更新previewDisplayView
以顯示輸出。添加以下代碼以聲明FrameProcessor
:
private FrameProcessor processor;
並在初始化onCreate(Bundle)
后對其進行初始化eglManager
:
processor =
new FrameProcessor(
this,
eglManager.getNativeContext(),
applicationInfo.metaData.getString("binaryGraphName"),
applicationInfo.metaData.getString("inputVideoStreamName"),
applicationInfo.metaData.getString("outputVideoStreamName"));
processor
需要消耗來自converter
處理的轉換幀。onResume()
在初始化之后添加以下行converter
:
converter.setConsumer(processor);
應該將processor
其輸出發送到previewDisplayView
為此,將以下函數定義添加到我們的自定義SurfaceHolder.Callback
:
@Override
public void surfaceCreated(SurfaceHolder holder) {
processor.getVideoSurfaceOutput().setSurface(holder.getSurface());
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
processor.getVideoSurfaceOutput().setSurface(null);
}
創建時,SurfaceHolder
我們Surface
有VideoSurfaceOutput
. processor
當它被銷毀時,我們將其VideoSurfaceOutput
從processor
.
就是這樣!您現在應該能夠在設備上成功構建和運行應用程序,並在實時攝像機源上看到 Sobel 邊緣檢測運行!恭喜!
如果您遇到任何問題,請在此處查看教程的完整代碼