Project import generated by Copybara.
GitOrigin-RevId: 1610e588e497817fae2d9a458093ab6a370e2972
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
@rem Remove the current res dir symlinks that are for Linux and macOS and recreate res dir symlinks for Windows.
|
||||
@rem This script needs administrator permission. Must run this script as administrator.
|
||||
|
||||
@rem for hands example app.
|
||||
cd /d %~dp0
|
||||
cd hands\src\main
|
||||
rm res
|
||||
mklink /d res ..\..\..\res
|
||||
|
||||
@rem for facemesh example app.
|
||||
cd /d %~dp0
|
||||
cd facemesh\src\main
|
||||
rm res
|
||||
mklink /d res ..\..\..\res
|
||||
dir
|
||||
pause
|
||||
@@ -0,0 +1,50 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion "30.0.3"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.google.mediapipe.apps.hands"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
|
||||
implementation 'androidx.appcompat:appcompat:1.3.0'
|
||||
implementation 'com.google.android.material:material:1.3.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||
testImplementation 'junit:junit:4.+'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
||||
// MediaPipe hands solution API and solution-core.
|
||||
implementation 'com.google.mediapipe:solution-core:latest.release'
|
||||
implementation 'com.google.mediapipe:facemesh:latest.release'
|
||||
// MediaPipe deps
|
||||
implementation 'com.google.flogger:flogger:latest.release'
|
||||
implementation 'com.google.flogger:flogger-system-backend:latest.release'
|
||||
implementation 'com.google.guava:guava:27.0.1-android'
|
||||
implementation 'com.google.protobuf:protobuf-java:3.11.4'
|
||||
// CameraX core library
|
||||
def camerax_version = "1.0.0-beta10"
|
||||
implementation "androidx.camera:camera-core:$camerax_version"
|
||||
implementation "androidx.camera:camera-camera2:$camerax_version"
|
||||
implementation "androidx.camera:camera-lifecycle:$camerax_version"
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.google.mediapipe.examples.facemesh">
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="21"
|
||||
android:targetSdkVersion="30" />
|
||||
|
||||
<!-- For loading images from gallery -->
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<!-- For using the camera -->
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-feature android:name="android.hardware.camera" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="MediaPipe FaceMesh"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity android:name=".MainActivity"
|
||||
android:screenOrientation="portrait">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,44 @@
|
||||
# Copyright 2021 The MediaPipe Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
package(default_visibility = ["//visibility:private"])
|
||||
|
||||
android_binary(
|
||||
name = "facemesh",
|
||||
srcs = glob(["**/*.java"]),
|
||||
custom_package = "com.google.mediapipe.examples.facemesh",
|
||||
manifest = "AndroidManifest.xml",
|
||||
manifest_values = {
|
||||
"applicationId": "com.google.mediapipe.examples.facemesh",
|
||||
},
|
||||
multidex = "native",
|
||||
resource_files = ["//mediapipe/examples/android/solutions:resource_files"],
|
||||
deps = [
|
||||
"//mediapipe/framework/formats:landmark_java_proto_lite",
|
||||
"//mediapipe/java/com/google/mediapipe/solutioncore:camera_input",
|
||||
"//mediapipe/java/com/google/mediapipe/solutioncore:mediapipe_jni_lib",
|
||||
"//mediapipe/java/com/google/mediapipe/solutioncore:solution_rendering",
|
||||
"//mediapipe/java/com/google/mediapipe/solutioncore:video_input",
|
||||
"//mediapipe/java/com/google/mediapipe/solutions/facemesh",
|
||||
"//third_party:androidx_appcompat",
|
||||
"//third_party:androidx_constraint_layout",
|
||||
"//third_party:opencv",
|
||||
"@maven//:androidx_activity_activity",
|
||||
"@maven//:androidx_concurrent_concurrent_futures",
|
||||
"@maven//:androidx_fragment_fragment",
|
||||
"@maven//:com_google_guava_guava",
|
||||
],
|
||||
)
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
// Copyright 2021 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.mediapipe.examples.facemesh;
|
||||
|
||||
import android.opengl.GLES20;
|
||||
import android.opengl.Matrix;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.mediapipe.formats.proto.LandmarkProto.NormalizedLandmark;
|
||||
import com.google.mediapipe.solutioncore.ResultGlBoundary;
|
||||
import com.google.mediapipe.solutioncore.ResultGlRenderer;
|
||||
import com.google.mediapipe.solutions.facemesh.FaceMeshConnections;
|
||||
import com.google.mediapipe.solutions.facemesh.FaceMeshResult;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.List;
|
||||
|
||||
/** A custom implementation of {@link ResultGlRenderer} to render MediaPope FaceMesh results. */
|
||||
public class FaceMeshResultGlRenderer implements ResultGlRenderer<FaceMeshResult> {
|
||||
private static final String TAG = "FaceMeshResultGlRenderer";
|
||||
|
||||
private static final float[] TESSELATION_COLOR = new float[] {0.75f, 0.75f, 0.75f, 0.5f};
|
||||
private static final int TESSELATION_THICKNESS = 5;
|
||||
private static final float[] RIGHT_EYE_COLOR = new float[] {1f, 0.2f, 0.2f, 1f};
|
||||
private static final int RIGHT_EYE_THICKNESS = 8;
|
||||
private static final float[] RIGHT_EYEBROW_COLOR = new float[] {1f, 0.2f, 0.2f, 1f};
|
||||
private static final int RIGHT_EYEBROW_THICKNESS = 8;
|
||||
private static final float[] LEFT_EYE_COLOR = new float[] {0.2f, 1f, 0.2f, 1f};
|
||||
private static final int LEFT_EYE_THICKNESS = 8;
|
||||
private static final float[] LEFT_EYEBROW_COLOR = new float[] {0.2f, 1f, 0.2f, 1f};
|
||||
private static final int LEFT_EYEBROW_THICKNESS = 8;
|
||||
private static final float[] FACE_OVAL_COLOR = new float[] {0.9f, 0.9f, 0.9f, 1f};
|
||||
private static final int FACE_OVAL_THICKNESS = 8;
|
||||
private static final float[] LIPS_COLOR = new float[] {0.9f, 0.9f, 0.9f, 1f};
|
||||
private static final int LIPS_THICKNESS = 8;
|
||||
private static final String VERTEX_SHADER =
|
||||
"uniform mat4 uTransformMatrix;\n"
|
||||
+ "attribute vec4 vPosition;\n"
|
||||
+ "void main() {\n"
|
||||
+ " gl_Position = uTransformMatrix * vPosition;\n"
|
||||
+ "}";
|
||||
private static final String FRAGMENT_SHADER =
|
||||
"precision mediump float;\n"
|
||||
+ "uniform vec4 uColor;\n"
|
||||
+ "void main() {\n"
|
||||
+ " gl_FragColor = uColor;\n"
|
||||
+ "}";
|
||||
private int program;
|
||||
private int positionHandle;
|
||||
private int transformMatrixHandle;
|
||||
private int colorHandle;
|
||||
private final float[] transformMatrix = new float[16];
|
||||
|
||||
private int loadShader(int type, String shaderCode) {
|
||||
int shader = GLES20.glCreateShader(type);
|
||||
GLES20.glShaderSource(shader, shaderCode);
|
||||
GLES20.glCompileShader(shader);
|
||||
return shader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupRendering() {
|
||||
program = GLES20.glCreateProgram();
|
||||
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER);
|
||||
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER);
|
||||
GLES20.glAttachShader(program, vertexShader);
|
||||
GLES20.glAttachShader(program, fragmentShader);
|
||||
GLES20.glLinkProgram(program);
|
||||
positionHandle = GLES20.glGetAttribLocation(program, "vPosition");
|
||||
transformMatrixHandle = GLES20.glGetUniformLocation(program, "uTransformMatrix");
|
||||
colorHandle = GLES20.glGetUniformLocation(program, "uColor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderResult(FaceMeshResult result, ResultGlBoundary boundary) {
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
GLES20.glUseProgram(program);
|
||||
// Sets the transform matrix to align the result rendering with the scaled output texture.
|
||||
// Also flips the rendering vertically since OpenGL assumes the coordinate origin is at the
|
||||
// bottom-left corner, whereas MediaPipe landmark data assumes the coordinate origin is at the
|
||||
// top-left corner.
|
||||
Matrix.setIdentityM(transformMatrix, 0);
|
||||
Matrix.scaleM(
|
||||
transformMatrix,
|
||||
0,
|
||||
2 / (boundary.right() - boundary.left()),
|
||||
-2 / (boundary.top() - boundary.bottom()),
|
||||
1.0f);
|
||||
GLES20.glUniformMatrix4fv(transformMatrixHandle, 1, false, transformMatrix, 0);
|
||||
|
||||
int numFaces = result.multiFaceLandmarks().size();
|
||||
for (int i = 0; i < numFaces; ++i) {
|
||||
drawLandmarks(
|
||||
result.multiFaceLandmarks().get(i).getLandmarkList(),
|
||||
FaceMeshConnections.FACEMESH_TESSELATION,
|
||||
TESSELATION_COLOR,
|
||||
TESSELATION_THICKNESS);
|
||||
drawLandmarks(
|
||||
result.multiFaceLandmarks().get(i).getLandmarkList(),
|
||||
FaceMeshConnections.FACEMESH_RIGHT_EYE,
|
||||
RIGHT_EYE_COLOR,
|
||||
RIGHT_EYE_THICKNESS);
|
||||
drawLandmarks(
|
||||
result.multiFaceLandmarks().get(i).getLandmarkList(),
|
||||
FaceMeshConnections.FACEMESH_RIGHT_EYEBROW,
|
||||
RIGHT_EYEBROW_COLOR,
|
||||
RIGHT_EYEBROW_THICKNESS);
|
||||
drawLandmarks(
|
||||
result.multiFaceLandmarks().get(i).getLandmarkList(),
|
||||
FaceMeshConnections.FACEMESH_LEFT_EYE,
|
||||
LEFT_EYE_COLOR,
|
||||
LEFT_EYE_THICKNESS);
|
||||
drawLandmarks(
|
||||
result.multiFaceLandmarks().get(i).getLandmarkList(),
|
||||
FaceMeshConnections.FACEMESH_LEFT_EYEBR0W,
|
||||
LEFT_EYEBROW_COLOR,
|
||||
LEFT_EYEBROW_THICKNESS);
|
||||
drawLandmarks(
|
||||
result.multiFaceLandmarks().get(i).getLandmarkList(),
|
||||
FaceMeshConnections.FACEMESH_FACE_OVAL,
|
||||
FACE_OVAL_COLOR,
|
||||
FACE_OVAL_THICKNESS);
|
||||
drawLandmarks(
|
||||
result.multiFaceLandmarks().get(i).getLandmarkList(),
|
||||
FaceMeshConnections.FACEMESH_LIPS,
|
||||
LIPS_COLOR,
|
||||
LIPS_THICKNESS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls this to delete the shader program.
|
||||
*
|
||||
* <p>This is only necessary if one wants to release the program while keeping the context around.
|
||||
*/
|
||||
public void release() {
|
||||
GLES20.glDeleteProgram(program);
|
||||
}
|
||||
|
||||
private void drawLandmarks(
|
||||
List<NormalizedLandmark> faceLandmarkList,
|
||||
ImmutableSet<FaceMeshConnections.Connection> connections,
|
||||
float[] colorArray,
|
||||
int thickness) {
|
||||
GLES20.glUniform4fv(colorHandle, 1, colorArray, 0);
|
||||
GLES20.glLineWidth(thickness);
|
||||
for (FaceMeshConnections.Connection c : connections) {
|
||||
float[] vertex = new float[4];
|
||||
NormalizedLandmark start = faceLandmarkList.get(c.start());
|
||||
vertex[0] = normalizedLandmarkValue(start.getX());
|
||||
vertex[1] = normalizedLandmarkValue(start.getY());
|
||||
NormalizedLandmark end = faceLandmarkList.get(c.end());
|
||||
vertex[2] = normalizedLandmarkValue(end.getX());
|
||||
vertex[3] = normalizedLandmarkValue(end.getY());
|
||||
FloatBuffer vertexBuffer =
|
||||
ByteBuffer.allocateDirect(vertex.length * 4)
|
||||
.order(ByteOrder.nativeOrder())
|
||||
.asFloatBuffer()
|
||||
.put(vertex);
|
||||
vertexBuffer.position(0);
|
||||
GLES20.glEnableVertexAttribArray(positionHandle);
|
||||
GLES20.glVertexAttribPointer(positionHandle, 2, GLES20.GL_FLOAT, false, 0, vertexBuffer);
|
||||
GLES20.glDrawArrays(GLES20.GL_LINES, 0, 2);
|
||||
}
|
||||
}
|
||||
|
||||
// Normalizes the value from the landmark value range:[0, 1] to the standard OpenGL coordinate
|
||||
// value range: [-1, 1].
|
||||
private float normalizedLandmarkValue(float value) {
|
||||
return value * 2 - 1;
|
||||
}
|
||||
}
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
// Copyright 2021 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.mediapipe.examples.facemesh;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
import android.util.Size;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.mediapipe.formats.proto.LandmarkProto.NormalizedLandmark;
|
||||
import com.google.mediapipe.solutions.facemesh.FaceMeshConnections;
|
||||
import com.google.mediapipe.solutions.facemesh.FaceMeshResult;
|
||||
import java.util.List;
|
||||
|
||||
/** An ImageView implementation for displaying MediaPipe FaceMesh results. */
|
||||
public class FaceMeshResultImageView extends AppCompatImageView {
|
||||
private static final String TAG = "FaceMeshResultImageView";
|
||||
|
||||
private static final int TESSELATION_COLOR = Color.parseColor("#70C0C0C0");
|
||||
private static final int TESSELATION_THICKNESS = 5;
|
||||
private static final int RIGHT_EYE_COLOR = Color.parseColor("#FF3030");
|
||||
private static final int RIGHT_EYE_THICKNESS = 8;
|
||||
private static final int RIGHT_EYEBROW_COLOR = Color.parseColor("#FF3030");
|
||||
private static final int RIGHT_EYEBROW_THICKNESS = 8;
|
||||
private static final int LEFT_EYE_COLOR = Color.parseColor("#30FF30");
|
||||
private static final int LEFT_EYE_THICKNESS = 8;
|
||||
private static final int LEFT_EYEBROW_COLOR = Color.parseColor("#30FF30");
|
||||
private static final int LEFT_EYEBROW_THICKNESS = 8;
|
||||
private static final int FACE_OVAL_COLOR = Color.parseColor("#E0E0E0");
|
||||
private static final int FACE_OVAL_THICKNESS = 8;
|
||||
private static final int LIPS_COLOR = Color.parseColor("#E0E0E0");
|
||||
private static final int LIPS_THICKNESS = 8;
|
||||
private Bitmap latest;
|
||||
|
||||
public FaceMeshResultImageView(Context context) {
|
||||
super(context);
|
||||
setScaleType(AppCompatImageView.ScaleType.FIT_CENTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a {@link FaceMeshResult} to render.
|
||||
*
|
||||
* @param result a {@link FaceMeshResult} object that contains the solution outputs and the input
|
||||
* {@link Bitmap}.
|
||||
*/
|
||||
public void setFaceMeshResult(FaceMeshResult result) {
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
Bitmap bmInput = result.inputBitmap();
|
||||
int width = bmInput.getWidth();
|
||||
int height = bmInput.getHeight();
|
||||
latest = Bitmap.createBitmap(width, height, bmInput.getConfig());
|
||||
Canvas canvas = new Canvas(latest);
|
||||
Size imageSize = new Size(width, height);
|
||||
canvas.drawBitmap(bmInput, new Matrix(), null);
|
||||
int numFaces = result.multiFaceLandmarks().size();
|
||||
for (int i = 0; i < numFaces; ++i) {
|
||||
drawLandmarksOnCanvas(
|
||||
canvas,
|
||||
result.multiFaceLandmarks().get(i).getLandmarkList(),
|
||||
FaceMeshConnections.FACEMESH_TESSELATION,
|
||||
imageSize,
|
||||
TESSELATION_COLOR,
|
||||
TESSELATION_THICKNESS);
|
||||
drawLandmarksOnCanvas(
|
||||
canvas,
|
||||
result.multiFaceLandmarks().get(i).getLandmarkList(),
|
||||
FaceMeshConnections.FACEMESH_RIGHT_EYE,
|
||||
imageSize,
|
||||
RIGHT_EYE_COLOR,
|
||||
RIGHT_EYE_THICKNESS);
|
||||
drawLandmarksOnCanvas(
|
||||
canvas,
|
||||
result.multiFaceLandmarks().get(i).getLandmarkList(),
|
||||
FaceMeshConnections.FACEMESH_RIGHT_EYEBROW,
|
||||
imageSize,
|
||||
RIGHT_EYEBROW_COLOR,
|
||||
RIGHT_EYEBROW_THICKNESS);
|
||||
drawLandmarksOnCanvas(
|
||||
canvas,
|
||||
result.multiFaceLandmarks().get(i).getLandmarkList(),
|
||||
FaceMeshConnections.FACEMESH_LEFT_EYE,
|
||||
imageSize,
|
||||
LEFT_EYE_COLOR,
|
||||
LEFT_EYE_THICKNESS);
|
||||
drawLandmarksOnCanvas(
|
||||
canvas,
|
||||
result.multiFaceLandmarks().get(i).getLandmarkList(),
|
||||
FaceMeshConnections.FACEMESH_LEFT_EYEBR0W,
|
||||
imageSize,
|
||||
LEFT_EYEBROW_COLOR,
|
||||
LEFT_EYEBROW_THICKNESS);
|
||||
drawLandmarksOnCanvas(
|
||||
canvas,
|
||||
result.multiFaceLandmarks().get(i).getLandmarkList(),
|
||||
FaceMeshConnections.FACEMESH_FACE_OVAL,
|
||||
imageSize,
|
||||
FACE_OVAL_COLOR,
|
||||
FACE_OVAL_THICKNESS);
|
||||
drawLandmarksOnCanvas(
|
||||
canvas,
|
||||
result.multiFaceLandmarks().get(i).getLandmarkList(),
|
||||
FaceMeshConnections.FACEMESH_LIPS,
|
||||
imageSize,
|
||||
LIPS_COLOR,
|
||||
LIPS_THICKNESS);
|
||||
}
|
||||
}
|
||||
|
||||
/** Updates the image view with the latest facemesh result. */
|
||||
public void update() {
|
||||
postInvalidate();
|
||||
if (latest != null) {
|
||||
setImageBitmap(latest);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Better hand landmark and hand connection drawing.
|
||||
private void drawLandmarksOnCanvas(
|
||||
Canvas canvas,
|
||||
List<NormalizedLandmark> faceLandmarkList,
|
||||
ImmutableSet<FaceMeshConnections.Connection> connections,
|
||||
Size imageSize,
|
||||
int color,
|
||||
int thickness) {
|
||||
// Draw connections.
|
||||
for (FaceMeshConnections.Connection c : connections) {
|
||||
Paint connectionPaint = new Paint();
|
||||
connectionPaint.setColor(color);
|
||||
connectionPaint.setStrokeWidth(thickness);
|
||||
NormalizedLandmark start = faceLandmarkList.get(c.start());
|
||||
NormalizedLandmark end = faceLandmarkList.get(c.end());
|
||||
canvas.drawLine(
|
||||
start.getX() * imageSize.getWidth(),
|
||||
start.getY() * imageSize.getHeight(),
|
||||
end.getX() * imageSize.getWidth(),
|
||||
end.getY() * imageSize.getHeight(),
|
||||
connectionPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
+308
@@ -0,0 +1,308 @@
|
||||
// Copyright 2021 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.mediapipe.examples.facemesh;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.provider.MediaStore;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import com.google.mediapipe.formats.proto.LandmarkProto.NormalizedLandmark;
|
||||
import com.google.mediapipe.solutioncore.CameraInput;
|
||||
import com.google.mediapipe.solutioncore.SolutionGlSurfaceView;
|
||||
import com.google.mediapipe.solutioncore.VideoInput;
|
||||
import com.google.mediapipe.solutions.facemesh.FaceMesh;
|
||||
import com.google.mediapipe.solutions.facemesh.FaceMeshOptions;
|
||||
import com.google.mediapipe.solutions.facemesh.FaceMeshResult;
|
||||
import java.io.IOException;
|
||||
|
||||
/** Main activity of MediaPipe FaceMesh app. */
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
private static final String TAG = "MainActivity";
|
||||
|
||||
private FaceMesh facemesh;
|
||||
// Run the pipeline and the model inference on GPU or CPU.
|
||||
private static final boolean RUN_ON_GPU = true;
|
||||
|
||||
private enum InputSource {
|
||||
UNKNOWN,
|
||||
IMAGE,
|
||||
VIDEO,
|
||||
CAMERA,
|
||||
}
|
||||
private InputSource inputSource = InputSource.UNKNOWN;
|
||||
// Image demo UI and image loader components.
|
||||
private ActivityResultLauncher<Intent> imageGetter;
|
||||
private FaceMeshResultImageView imageView;
|
||||
// Video demo UI and video loader components.
|
||||
private VideoInput videoInput;
|
||||
private ActivityResultLauncher<Intent> videoGetter;
|
||||
// Live camera demo UI and camera components.
|
||||
private CameraInput cameraInput;
|
||||
private SolutionGlSurfaceView<FaceMeshResult> glSurfaceView;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
setupStaticImageDemoUiComponents();
|
||||
setupVideoDemoUiComponents();
|
||||
setupLiveDemoUiComponents();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (inputSource == InputSource.CAMERA) {
|
||||
// Restarts the camera and the opengl surface rendering.
|
||||
cameraInput = new CameraInput(this);
|
||||
cameraInput.setNewFrameListener(textureFrame -> facemesh.send(textureFrame));
|
||||
glSurfaceView.post(this::startCamera);
|
||||
glSurfaceView.setVisibility(View.VISIBLE);
|
||||
} else if (inputSource == InputSource.VIDEO) {
|
||||
videoInput.resume();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (inputSource == InputSource.CAMERA) {
|
||||
glSurfaceView.setVisibility(View.GONE);
|
||||
cameraInput.close();
|
||||
} else if (inputSource == InputSource.VIDEO) {
|
||||
videoInput.pause();
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets up the UI components for the static image demo. */
|
||||
private void setupStaticImageDemoUiComponents() {
|
||||
// The Intent to access gallery and read images as bitmap.
|
||||
imageGetter =
|
||||
registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
Intent resultIntent = result.getData();
|
||||
if (resultIntent != null) {
|
||||
if (result.getResultCode() == RESULT_OK) {
|
||||
Bitmap bitmap = null;
|
||||
try {
|
||||
bitmap =
|
||||
MediaStore.Images.Media.getBitmap(
|
||||
this.getContentResolver(), resultIntent.getData());
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Bitmap reading error:" + e);
|
||||
}
|
||||
if (bitmap != null) {
|
||||
facemesh.send(bitmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
Button loadImageButton = findViewById(R.id.button_load_picture);
|
||||
loadImageButton.setOnClickListener(
|
||||
v -> {
|
||||
if (inputSource != InputSource.IMAGE) {
|
||||
stopCurrentPipeline();
|
||||
setupStaticImageModePipeline();
|
||||
}
|
||||
// Reads images from gallery.
|
||||
Intent gallery =
|
||||
new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.INTERNAL_CONTENT_URI);
|
||||
imageGetter.launch(gallery);
|
||||
});
|
||||
imageView = new FaceMeshResultImageView(this);
|
||||
}
|
||||
|
||||
/** The core MediaPipe FaceMesh setup workflow for its static image mode. */
|
||||
private void setupStaticImageModePipeline() {
|
||||
this.inputSource = InputSource.IMAGE;
|
||||
// Initializes a new MediaPipe FaceMesh instance in the static image mode.
|
||||
facemesh =
|
||||
new FaceMesh(
|
||||
this,
|
||||
FaceMeshOptions.builder()
|
||||
.setMode(FaceMeshOptions.STATIC_IMAGE_MODE)
|
||||
.setRunOnGpu(RUN_ON_GPU)
|
||||
.build());
|
||||
|
||||
// Connects MediaPipe FaceMesh to the user-defined FaceMeshResultImageView.
|
||||
facemesh.setResultListener(
|
||||
faceMeshResult -> {
|
||||
logNoseLandmark(faceMeshResult, /*showPixelValues=*/ true);
|
||||
imageView.setFaceMeshResult(faceMeshResult);
|
||||
runOnUiThread(() -> imageView.update());
|
||||
});
|
||||
facemesh.setErrorListener((message, e) -> Log.e(TAG, "MediaPipe FaceMesh error:" + message));
|
||||
|
||||
// Updates the preview layout.
|
||||
FrameLayout frameLayout = findViewById(R.id.preview_display_layout);
|
||||
frameLayout.removeAllViewsInLayout();
|
||||
imageView.setImageDrawable(null);
|
||||
frameLayout.addView(imageView);
|
||||
imageView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
/** Sets up the UI components for the video demo. */
|
||||
private void setupVideoDemoUiComponents() {
|
||||
// The Intent to access gallery and read a video file.
|
||||
videoGetter =
|
||||
registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
Intent resultIntent = result.getData();
|
||||
if (resultIntent != null) {
|
||||
if (result.getResultCode() == RESULT_OK) {
|
||||
glSurfaceView.post(
|
||||
() ->
|
||||
videoInput.start(
|
||||
this,
|
||||
resultIntent.getData(),
|
||||
facemesh.getGlContext(),
|
||||
glSurfaceView.getWidth(),
|
||||
glSurfaceView.getHeight()));
|
||||
}
|
||||
}
|
||||
});
|
||||
Button loadVideoButton = findViewById(R.id.button_load_video);
|
||||
loadVideoButton.setOnClickListener(
|
||||
v -> {
|
||||
stopCurrentPipeline();
|
||||
setupStreamingModePipeline(InputSource.VIDEO);
|
||||
// Reads video from gallery.
|
||||
Intent gallery =
|
||||
new Intent(Intent.ACTION_PICK, MediaStore.Video.Media.INTERNAL_CONTENT_URI);
|
||||
videoGetter.launch(gallery);
|
||||
});
|
||||
}
|
||||
|
||||
/** Sets up the UI components for the live demo with camera input. */
|
||||
private void setupLiveDemoUiComponents() {
|
||||
Button startCameraButton = findViewById(R.id.button_start_camera);
|
||||
startCameraButton.setOnClickListener(
|
||||
v -> {
|
||||
if (inputSource == InputSource.CAMERA) {
|
||||
return;
|
||||
}
|
||||
stopCurrentPipeline();
|
||||
setupStreamingModePipeline(InputSource.CAMERA);
|
||||
});
|
||||
}
|
||||
|
||||
/** The core MediaPipe FaceMesh setup workflow for its streaming mode. */
|
||||
private void setupStreamingModePipeline(InputSource inputSource) {
|
||||
this.inputSource = inputSource;
|
||||
// Initializes a new MediaPipe FaceMesh instance in the streaming mode.
|
||||
facemesh =
|
||||
new FaceMesh(
|
||||
this,
|
||||
FaceMeshOptions.builder()
|
||||
.setMode(FaceMeshOptions.STREAMING_MODE)
|
||||
.setRunOnGpu(RUN_ON_GPU)
|
||||
.build());
|
||||
facemesh.setErrorListener((message, e) -> Log.e(TAG, "MediaPipe FaceMesh error:" + message));
|
||||
|
||||
if (inputSource == InputSource.CAMERA) {
|
||||
// Initializes a new CameraInput instance and connects it to MediaPipe FaceMesh.
|
||||
cameraInput = new CameraInput(this);
|
||||
cameraInput.setNewFrameListener(textureFrame -> facemesh.send(textureFrame));
|
||||
} else if (inputSource == InputSource.VIDEO) {
|
||||
// Initializes a new VideoInput instance and connects it to MediaPipe FaceMesh.
|
||||
videoInput = new VideoInput(this);
|
||||
videoInput.setNewFrameListener(textureFrame -> facemesh.send(textureFrame));
|
||||
}
|
||||
|
||||
// Initializes a new Gl surface view with a user-defined FaceMeshResultGlRenderer.
|
||||
glSurfaceView =
|
||||
new SolutionGlSurfaceView<>(this, facemesh.getGlContext(), facemesh.getGlMajorVersion());
|
||||
glSurfaceView.setSolutionResultRenderer(new FaceMeshResultGlRenderer());
|
||||
glSurfaceView.setRenderInputImage(true);
|
||||
facemesh.setResultListener(
|
||||
faceMeshResult -> {
|
||||
logNoseLandmark(faceMeshResult, /*showPixelValues=*/ false);
|
||||
glSurfaceView.setRenderData(faceMeshResult);
|
||||
glSurfaceView.requestRender();
|
||||
});
|
||||
|
||||
// The runnable to start camera after the gl surface view is attached.
|
||||
// For video input source, videoInput.start() will be called when the video uri is available.
|
||||
if (inputSource == InputSource.CAMERA) {
|
||||
glSurfaceView.post(this::startCamera);
|
||||
}
|
||||
|
||||
// Updates the preview layout.
|
||||
FrameLayout frameLayout = findViewById(R.id.preview_display_layout);
|
||||
imageView.setVisibility(View.GONE);
|
||||
frameLayout.removeAllViewsInLayout();
|
||||
frameLayout.addView(glSurfaceView);
|
||||
glSurfaceView.setVisibility(View.VISIBLE);
|
||||
frameLayout.requestLayout();
|
||||
}
|
||||
|
||||
private void startCamera() {
|
||||
cameraInput.start(
|
||||
this,
|
||||
facemesh.getGlContext(),
|
||||
CameraInput.CameraFacing.FRONT,
|
||||
glSurfaceView.getWidth(),
|
||||
glSurfaceView.getHeight());
|
||||
}
|
||||
|
||||
private void stopCurrentPipeline() {
|
||||
if (cameraInput != null) {
|
||||
cameraInput.setNewFrameListener(null);
|
||||
cameraInput.close();
|
||||
}
|
||||
if (videoInput != null) {
|
||||
videoInput.setNewFrameListener(null);
|
||||
videoInput.close();
|
||||
}
|
||||
if (glSurfaceView != null) {
|
||||
glSurfaceView.setVisibility(View.GONE);
|
||||
}
|
||||
if (facemesh != null) {
|
||||
facemesh.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void logNoseLandmark(FaceMeshResult result, boolean showPixelValues) {
|
||||
if (result == null || result.multiFaceLandmarks().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
NormalizedLandmark noseLandmark = result.multiFaceLandmarks().get(0).getLandmarkList().get(1);
|
||||
// For Bitmaps, show the pixel values. For texture inputs, show the normalized coordinates.
|
||||
if (showPixelValues) {
|
||||
int width = result.inputBitmap().getWidth();
|
||||
int height = result.inputBitmap().getHeight();
|
||||
Log.i(
|
||||
TAG,
|
||||
String.format(
|
||||
"MediaPipe FaceMesh nose coordinates (pixel values): x=%f, y=%f",
|
||||
noseLandmark.getX() * width, noseLandmark.getY() * height));
|
||||
} else {
|
||||
Log.i(
|
||||
TAG,
|
||||
String.format(
|
||||
"MediaPipe FaceMesh nose normalized coordinates (value range: [0, 1]): x=%f, y=%f",
|
||||
noseLandmark.getX(), noseLandmark.getY()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../res
|
||||
@@ -19,7 +19,8 @@
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity android:name=".MainActivity">
|
||||
<activity android:name=".MainActivity"
|
||||
android:screenOrientation="portrait">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
|
||||
@@ -31,10 +31,14 @@ android_binary(
|
||||
"//mediapipe/java/com/google/mediapipe/solutioncore:camera_input",
|
||||
"//mediapipe/java/com/google/mediapipe/solutioncore:mediapipe_jni_lib",
|
||||
"//mediapipe/java/com/google/mediapipe/solutioncore:solution_rendering",
|
||||
"//mediapipe/java/com/google/mediapipe/solutioncore:video_input",
|
||||
"//mediapipe/java/com/google/mediapipe/solutions/hands",
|
||||
"//third_party:androidx_appcompat",
|
||||
"//third_party:androidx_constraint_layout",
|
||||
"//third_party:opencv",
|
||||
"@maven//:androidx_activity_activity",
|
||||
"@maven//:androidx_concurrent_concurrent_futures",
|
||||
"@maven//:androidx_fragment_fragment",
|
||||
"@maven//:com_google_guava_guava",
|
||||
],
|
||||
)
|
||||
|
||||
+5
-3
@@ -46,7 +46,6 @@ public class HandsResultGlRenderer implements ResultGlRenderer<HandsResult> {
|
||||
private int positionHandle;
|
||||
private int transformMatrixHandle;
|
||||
private final float[] transformMatrix = new float[16];
|
||||
private FloatBuffer vertexBuffer;
|
||||
|
||||
private int loadShader(int type, String shaderCode) {
|
||||
int shader = GLES20.glCreateShader(type);
|
||||
@@ -74,12 +73,15 @@ public class HandsResultGlRenderer implements ResultGlRenderer<HandsResult> {
|
||||
}
|
||||
GLES20.glUseProgram(program);
|
||||
// Sets the transform matrix to align the result rendering with the scaled output texture.
|
||||
// Also flips the rendering vertically since OpenGL assumes the coordinate origin is at the
|
||||
// bottom-left corner, whereas MediaPipe landmark data assumes the coordinate origin is at the
|
||||
// top-left corner.
|
||||
Matrix.setIdentityM(transformMatrix, 0);
|
||||
Matrix.scaleM(
|
||||
transformMatrix,
|
||||
0,
|
||||
2 / (boundary.right() - boundary.left()),
|
||||
2 / (boundary.top() - boundary.bottom()),
|
||||
-2 / (boundary.top() - boundary.bottom()),
|
||||
1.0f);
|
||||
GLES20.glUniformMatrix4fv(transformMatrixHandle, 1, false, transformMatrix, 0);
|
||||
GLES20.glLineWidth(CONNECTION_THICKNESS);
|
||||
@@ -109,7 +111,7 @@ public class HandsResultGlRenderer implements ResultGlRenderer<HandsResult> {
|
||||
NormalizedLandmark end = handLandmarkList.get(c.end());
|
||||
vertex[2] = normalizedLandmarkValue(end.getX());
|
||||
vertex[3] = normalizedLandmarkValue(end.getY());
|
||||
vertexBuffer =
|
||||
FloatBuffer vertexBuffer =
|
||||
ByteBuffer.allocateDirect(vertex.length * 4)
|
||||
.order(ByteOrder.nativeOrder())
|
||||
.asFloatBuffer()
|
||||
|
||||
+13
-6
@@ -20,7 +20,7 @@ import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.widget.ImageView;
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
import com.google.mediapipe.formats.proto.LandmarkProto;
|
||||
import com.google.mediapipe.formats.proto.LandmarkProto.NormalizedLandmark;
|
||||
import com.google.mediapipe.solutions.hands.Hands;
|
||||
@@ -28,17 +28,18 @@ import com.google.mediapipe.solutions.hands.HandsResult;
|
||||
import java.util.List;
|
||||
|
||||
/** An ImageView implementation for displaying MediaPipe Hands results. */
|
||||
public class HandsResultImageView extends ImageView {
|
||||
public class HandsResultImageView extends AppCompatImageView {
|
||||
private static final String TAG = "HandsResultImageView";
|
||||
|
||||
private static final int LANDMARK_COLOR = Color.RED;
|
||||
private static final int LANDMARK_RADIUS = 15;
|
||||
private static final int CONNECTION_COLOR = Color.GREEN;
|
||||
private static final int CONNECTION_THICKNESS = 10;
|
||||
private Bitmap latest;
|
||||
|
||||
public HandsResultImageView(Context context) {
|
||||
super(context);
|
||||
setScaleType(ImageView.ScaleType.FIT_CENTER);
|
||||
setScaleType(AppCompatImageView.ScaleType.FIT_CENTER);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,8 +55,8 @@ public class HandsResultImageView extends ImageView {
|
||||
Bitmap bmInput = result.inputBitmap();
|
||||
int width = bmInput.getWidth();
|
||||
int height = bmInput.getHeight();
|
||||
Bitmap bmOutput = Bitmap.createBitmap(width, height, bmInput.getConfig());
|
||||
Canvas canvas = new Canvas(bmOutput);
|
||||
latest = Bitmap.createBitmap(width, height, bmInput.getConfig());
|
||||
Canvas canvas = new Canvas(latest);
|
||||
|
||||
canvas.drawBitmap(bmInput, new Matrix(), null);
|
||||
int numHands = result.multiHandLandmarks().size();
|
||||
@@ -63,8 +64,14 @@ public class HandsResultImageView extends ImageView {
|
||||
drawLandmarksOnCanvas(
|
||||
result.multiHandLandmarks().get(i).getLandmarkList(), canvas, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
/** Updates the image view with the latest hands result. */
|
||||
public void update() {
|
||||
postInvalidate();
|
||||
setImageBitmap(bmOutput);
|
||||
if (latest != null) {
|
||||
setImageBitmap(latest);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Better hand landmark and hand connection drawing.
|
||||
|
||||
+129
-61
@@ -28,6 +28,7 @@ import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import com.google.mediapipe.formats.proto.LandmarkProto.NormalizedLandmark;
|
||||
import com.google.mediapipe.solutioncore.CameraInput;
|
||||
import com.google.mediapipe.solutioncore.SolutionGlSurfaceView;
|
||||
import com.google.mediapipe.solutioncore.VideoInput;
|
||||
import com.google.mediapipe.solutions.hands.HandLandmark;
|
||||
import com.google.mediapipe.solutions.hands.Hands;
|
||||
import com.google.mediapipe.solutions.hands.HandsOptions;
|
||||
@@ -39,14 +40,24 @@ public class MainActivity extends AppCompatActivity {
|
||||
private static final String TAG = "MainActivity";
|
||||
|
||||
private Hands hands;
|
||||
private int mode = HandsOptions.STATIC_IMAGE_MODE;
|
||||
// Run the pipeline and the model inference on GPU or CPU.
|
||||
private static final boolean RUN_ON_GPU = true;
|
||||
|
||||
private enum InputSource {
|
||||
UNKNOWN,
|
||||
IMAGE,
|
||||
VIDEO,
|
||||
CAMERA,
|
||||
}
|
||||
private InputSource inputSource = InputSource.UNKNOWN;
|
||||
|
||||
// Image demo UI and image loader components.
|
||||
private Button loadImageButton;
|
||||
private ActivityResultLauncher<Intent> imageGetter;
|
||||
private HandsResultImageView imageView;
|
||||
|
||||
// Video demo UI and video loader components.
|
||||
private VideoInput videoInput;
|
||||
private ActivityResultLauncher<Intent> videoGetter;
|
||||
// Live camera demo UI and camera components.
|
||||
private Button startCameraButton;
|
||||
private CameraInput cameraInput;
|
||||
private SolutionGlSurfaceView<HandsResult> glSurfaceView;
|
||||
|
||||
@@ -55,26 +66,32 @@ public class MainActivity extends AppCompatActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
setupStaticImageDemoUiComponents();
|
||||
setupVideoDemoUiComponents();
|
||||
setupLiveDemoUiComponents();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (mode == HandsOptions.STREAMING_MODE) {
|
||||
if (inputSource == InputSource.CAMERA) {
|
||||
// Restarts the camera and the opengl surface rendering.
|
||||
cameraInput = new CameraInput(this);
|
||||
cameraInput.setCameraNewFrameListener(textureFrame -> hands.send(textureFrame));
|
||||
cameraInput.setNewFrameListener(textureFrame -> hands.send(textureFrame));
|
||||
glSurfaceView.post(this::startCamera);
|
||||
glSurfaceView.setVisibility(View.VISIBLE);
|
||||
} else if (inputSource == InputSource.VIDEO) {
|
||||
videoInput.resume();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (mode == HandsOptions.STREAMING_MODE) {
|
||||
stopLiveDemo();
|
||||
if (inputSource == InputSource.CAMERA) {
|
||||
glSurfaceView.setVisibility(View.GONE);
|
||||
cameraInput.close();
|
||||
} else if (inputSource == InputSource.VIDEO) {
|
||||
videoInput.pause();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,80 +119,122 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
});
|
||||
loadImageButton = (Button) findViewById(R.id.button_load_picture);
|
||||
Button loadImageButton = findViewById(R.id.button_load_picture);
|
||||
loadImageButton.setOnClickListener(
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mode == HandsOptions.STREAMING_MODE) {
|
||||
stopLiveDemo();
|
||||
}
|
||||
if (hands == null || mode != HandsOptions.STATIC_IMAGE_MODE) {
|
||||
setupStaticImageModePipeline();
|
||||
}
|
||||
// Reads images from gallery.
|
||||
Intent gallery =
|
||||
new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.INTERNAL_CONTENT_URI);
|
||||
imageGetter.launch(gallery);
|
||||
v -> {
|
||||
if (inputSource != InputSource.IMAGE) {
|
||||
stopCurrentPipeline();
|
||||
setupStaticImageModePipeline();
|
||||
}
|
||||
// Reads images from gallery.
|
||||
Intent gallery =
|
||||
new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.INTERNAL_CONTENT_URI);
|
||||
imageGetter.launch(gallery);
|
||||
});
|
||||
imageView = new HandsResultImageView(this);
|
||||
}
|
||||
|
||||
/** The core MediaPipe Hands setup workflow for its static image mode. */
|
||||
private void setupStaticImageModePipeline() {
|
||||
this.inputSource = InputSource.IMAGE;
|
||||
// Initializes a new MediaPipe Hands instance in the static image mode.
|
||||
mode = HandsOptions.STATIC_IMAGE_MODE;
|
||||
if (hands != null) {
|
||||
hands.close();
|
||||
}
|
||||
hands = new Hands(this, HandsOptions.builder().setMode(mode).build());
|
||||
hands =
|
||||
new Hands(
|
||||
this,
|
||||
HandsOptions.builder()
|
||||
.setMode(HandsOptions.STATIC_IMAGE_MODE)
|
||||
.setMaxNumHands(1)
|
||||
.setRunOnGpu(RUN_ON_GPU)
|
||||
.build());
|
||||
|
||||
// Connects MediaPipe Hands to the user-defined HandsResultImageView.
|
||||
hands.setResultListener(
|
||||
handsResult -> {
|
||||
logWristLandmark(handsResult, /*showPixelValues=*/ true);
|
||||
runOnUiThread(() -> imageView.setHandsResult(handsResult));
|
||||
imageView.setHandsResult(handsResult);
|
||||
runOnUiThread(() -> imageView.update());
|
||||
});
|
||||
hands.setErrorListener((message, e) -> Log.e(TAG, "MediaPipe hands error:" + message));
|
||||
hands.setErrorListener((message, e) -> Log.e(TAG, "MediaPipe Hands error:" + message));
|
||||
|
||||
// Updates the preview layout.
|
||||
FrameLayout frameLayout = (FrameLayout) findViewById(R.id.preview_display_layout);
|
||||
FrameLayout frameLayout = findViewById(R.id.preview_display_layout);
|
||||
frameLayout.removeAllViewsInLayout();
|
||||
imageView.setImageDrawable(null);
|
||||
frameLayout.addView(imageView);
|
||||
imageView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
/** Sets up the UI components for the video demo. */
|
||||
private void setupVideoDemoUiComponents() {
|
||||
// The Intent to access gallery and read a video file.
|
||||
videoGetter =
|
||||
registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
Intent resultIntent = result.getData();
|
||||
if (resultIntent != null) {
|
||||
if (result.getResultCode() == RESULT_OK) {
|
||||
glSurfaceView.post(
|
||||
() ->
|
||||
videoInput.start(
|
||||
this,
|
||||
resultIntent.getData(),
|
||||
hands.getGlContext(),
|
||||
glSurfaceView.getWidth(),
|
||||
glSurfaceView.getHeight()));
|
||||
}
|
||||
}
|
||||
});
|
||||
Button loadVideoButton = findViewById(R.id.button_load_video);
|
||||
loadVideoButton.setOnClickListener(
|
||||
v -> {
|
||||
stopCurrentPipeline();
|
||||
setupStreamingModePipeline(InputSource.VIDEO);
|
||||
// Reads video from gallery.
|
||||
Intent gallery =
|
||||
new Intent(Intent.ACTION_PICK, MediaStore.Video.Media.INTERNAL_CONTENT_URI);
|
||||
videoGetter.launch(gallery);
|
||||
});
|
||||
}
|
||||
|
||||
/** Sets up the UI components for the live demo with camera input. */
|
||||
private void setupLiveDemoUiComponents() {
|
||||
startCameraButton = (Button) findViewById(R.id.button_start_camera);
|
||||
Button startCameraButton = findViewById(R.id.button_start_camera);
|
||||
startCameraButton.setOnClickListener(
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (hands == null || mode != HandsOptions.STREAMING_MODE) {
|
||||
setupStreamingModePipeline();
|
||||
}
|
||||
v -> {
|
||||
if (inputSource == InputSource.CAMERA) {
|
||||
return;
|
||||
}
|
||||
stopCurrentPipeline();
|
||||
setupStreamingModePipeline(InputSource.CAMERA);
|
||||
});
|
||||
}
|
||||
|
||||
/** The core MediaPipe Hands setup workflow for its streaming mode. */
|
||||
private void setupStreamingModePipeline() {
|
||||
private void setupStreamingModePipeline(InputSource inputSource) {
|
||||
this.inputSource = inputSource;
|
||||
// Initializes a new MediaPipe Hands instance in the streaming mode.
|
||||
mode = HandsOptions.STREAMING_MODE;
|
||||
if (hands != null) {
|
||||
hands.close();
|
||||
hands =
|
||||
new Hands(
|
||||
this,
|
||||
HandsOptions.builder()
|
||||
.setMode(HandsOptions.STREAMING_MODE)
|
||||
.setMaxNumHands(1)
|
||||
.setRunOnGpu(RUN_ON_GPU)
|
||||
.build());
|
||||
hands.setErrorListener((message, e) -> Log.e(TAG, "MediaPipe Hands error:" + message));
|
||||
|
||||
if (inputSource == InputSource.CAMERA) {
|
||||
// Initializes a new CameraInput instance and connects it to MediaPipe Hands.
|
||||
cameraInput = new CameraInput(this);
|
||||
cameraInput.setNewFrameListener(textureFrame -> hands.send(textureFrame));
|
||||
} else if (inputSource == InputSource.VIDEO) {
|
||||
// Initializes a new VideoInput instance and connects it to MediaPipe Hands.
|
||||
videoInput = new VideoInput(this);
|
||||
videoInput.setNewFrameListener(textureFrame -> hands.send(textureFrame));
|
||||
}
|
||||
hands = new Hands(this, HandsOptions.builder().setMode(mode).build());
|
||||
hands.setErrorListener((message, e) -> Log.e(TAG, "MediaPipe hands error:" + message));
|
||||
|
||||
// Initializes a new CameraInput instance and connects it to MediaPipe Hands.
|
||||
cameraInput = new CameraInput(this);
|
||||
cameraInput.setCameraNewFrameListener(textureFrame -> hands.send(textureFrame));
|
||||
|
||||
// Initalizes a new Gl surface view with a user-defined HandsResultGlRenderer.
|
||||
// Initializes a new Gl surface view with a user-defined HandsResultGlRenderer.
|
||||
glSurfaceView =
|
||||
new SolutionGlSurfaceView<>(this, hands.getGlContext(), hands.getGlMajorVersion());
|
||||
glSurfaceView.setSolutionResultRenderer(new HandsResultGlRenderer());
|
||||
@@ -188,10 +247,13 @@ public class MainActivity extends AppCompatActivity {
|
||||
});
|
||||
|
||||
// The runnable to start camera after the gl surface view is attached.
|
||||
glSurfaceView.post(this::startCamera);
|
||||
// For video input source, videoInput.start() will be called when the video uri is available.
|
||||
if (inputSource == InputSource.CAMERA) {
|
||||
glSurfaceView.post(this::startCamera);
|
||||
}
|
||||
|
||||
// Updates the preview layout.
|
||||
FrameLayout frameLayout = (FrameLayout) findViewById(R.id.preview_display_layout);
|
||||
FrameLayout frameLayout = findViewById(R.id.preview_display_layout);
|
||||
imageView.setVisibility(View.GONE);
|
||||
frameLayout.removeAllViewsInLayout();
|
||||
frameLayout.addView(glSurfaceView);
|
||||
@@ -208,34 +270,40 @@ public class MainActivity extends AppCompatActivity {
|
||||
glSurfaceView.getHeight());
|
||||
}
|
||||
|
||||
private void stopLiveDemo() {
|
||||
private void stopCurrentPipeline() {
|
||||
if (cameraInput != null) {
|
||||
cameraInput.stop();
|
||||
cameraInput.setNewFrameListener(null);
|
||||
cameraInput.close();
|
||||
}
|
||||
if (videoInput != null) {
|
||||
videoInput.setNewFrameListener(null);
|
||||
videoInput.close();
|
||||
}
|
||||
if (glSurfaceView != null) {
|
||||
glSurfaceView.setVisibility(View.GONE);
|
||||
}
|
||||
if (hands != null) {
|
||||
hands.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void logWristLandmark(HandsResult result, boolean showPixelValues) {
|
||||
NormalizedLandmark wristLandmark = Hands.getHandLandmark(result, 0, HandLandmark.WRIST);
|
||||
// For Bitmaps, show the pixel values. For texture inputs, show the normoralized cooridanates.
|
||||
// For Bitmaps, show the pixel values. For texture inputs, show the normalized coordinates.
|
||||
if (showPixelValues) {
|
||||
int width = result.inputBitmap().getWidth();
|
||||
int height = result.inputBitmap().getHeight();
|
||||
Log.i(
|
||||
TAG,
|
||||
"MediaPipe Hand wrist coordinates (pixel values): x= "
|
||||
+ wristLandmark.getX() * width
|
||||
+ " y="
|
||||
+ wristLandmark.getY() * height);
|
||||
String.format(
|
||||
"MediaPipe Hand wrist coordinates (pixel values): x=%f, y=%f",
|
||||
wristLandmark.getX() * width, wristLandmark.getY() * height));
|
||||
} else {
|
||||
Log.i(
|
||||
TAG,
|
||||
"MediaPipe Hand wrist normalized coordinates (value range: [0, 1]): x= "
|
||||
+ wristLandmark.getX()
|
||||
+ " y="
|
||||
+ wristLandmark.getY());
|
||||
String.format(
|
||||
"MediaPipe Hand wrist normalized coordinates (value range: [0, 1]): x=%f, y=%f",
|
||||
wristLandmark.getX(), wristLandmark.getY()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,18 +8,23 @@
|
||||
android:id="@+id/buttons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
style="?android:attr/buttonBarStyle" android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
<Button
|
||||
android:id="@+id/button_load_picture"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Load Picture" />
|
||||
style="?android:attr/buttonBarButtonStyle" android:layout_height="wrap_content"
|
||||
android:text="@string/load_picture" />
|
||||
<Button
|
||||
android:id="@+id/button_load_video"
|
||||
android:layout_width="wrap_content"
|
||||
style="?android:attr/buttonBarButtonStyle" android:layout_height="wrap_content"
|
||||
android:text="@string/load_video" />
|
||||
<Button
|
||||
android:id="@+id/button_start_camera"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Start Camera" />
|
||||
style="?android:attr/buttonBarButtonStyle" android:layout_height="wrap_content"
|
||||
android:text="@string/start_camera" />
|
||||
</LinearLayout>
|
||||
<FrameLayout
|
||||
android:id="@+id/preview_display_layout"
|
||||
@@ -27,9 +32,9 @@
|
||||
android:layout_height="match_parent">
|
||||
<TextView
|
||||
android:id="@+id/no_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="Please press any button above to start" />
|
||||
android:text="@string/instruction" />
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
<resources>
|
||||
<string name="no_camera_access" translatable="false">Please grant camera permissions.</string>
|
||||
<string name="load_picture" translatable="false">Load Picture</string>
|
||||
<string name="load_video" translatable="false">Load Video</string>
|
||||
<string name="start_camera" translatable="false">Start Camera</string>
|
||||
<string name="instruction" translatable="false">Please press any button above to start</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
rootProject.name = "mediapipe-solutions-examples"
|
||||
include ':hands'
|
||||
include ':facemesh'
|
||||
|
||||
+4
-6
@@ -169,6 +169,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
|
||||
public void startCamera() {
|
||||
cameraHelper = new CameraXPreviewHelper();
|
||||
previewFrameTexture = converter.getSurfaceTexture();
|
||||
cameraHelper.setOnCameraStartedListener(
|
||||
surfaceTexture -> {
|
||||
onCameraStarted(surfaceTexture);
|
||||
@@ -178,7 +179,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
? CameraHelper.CameraFacing.FRONT
|
||||
: CameraHelper.CameraFacing.BACK;
|
||||
cameraHelper.startCamera(
|
||||
this, cameraFacing, /*unusedSurfaceTexture=*/ null, cameraTargetResolution());
|
||||
this, cameraFacing, previewFrameTexture, cameraTargetResolution());
|
||||
}
|
||||
|
||||
protected Size computeViewSize(int width, int height) {
|
||||
@@ -194,11 +195,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
Size displaySize = cameraHelper.computeDisplaySizeFromViewSize(viewSize);
|
||||
boolean isCameraRotated = cameraHelper.isCameraRotated();
|
||||
|
||||
// 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,
|
||||
// Configure the output width and height as the computed display size.
|
||||
converter.setDestinationSize(
|
||||
isCameraRotated ? displaySize.getHeight() : displaySize.getWidth(),
|
||||
isCameraRotated ? displaySize.getWidth() : displaySize.getHeight());
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ cc_library(
|
||||
cc_binary(
|
||||
name = "object_detection_tpu",
|
||||
deps = [
|
||||
"//mediapipe/calculators/image:image_transformation_calculator",
|
||||
"//mediapipe/examples/coral:demo_run_graph_main",
|
||||
"//mediapipe/graphs/object_detection:desktop_tflite_calculators",
|
||||
],
|
||||
@@ -51,6 +52,12 @@ cc_binary(
|
||||
cc_binary(
|
||||
name = "face_detection_tpu",
|
||||
deps = [
|
||||
"//mediapipe/calculators/image:image_transformation_calculator",
|
||||
"//mediapipe/calculators/tflite:tflite_converter_calculator",
|
||||
"//mediapipe/calculators/tflite:tflite_inference_calculator",
|
||||
"//mediapipe/calculators/tflite:tflite_tensors_to_detections_calculator",
|
||||
"//mediapipe/calculators/util:detection_label_id_to_text_calculator",
|
||||
"//mediapipe/calculators/util:detection_letterbox_removal_calculator",
|
||||
"//mediapipe/examples/coral:demo_run_graph_main",
|
||||
"//mediapipe/graphs/face_detection:desktop_live_calculators",
|
||||
],
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
# Copyright 2019 The MediaPipe Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
#==== ! Prerequisite ! ====
|
||||
# $ sh mediapipe/examples/coral/setup.sh
|
||||
#====
|
||||
|
||||
# for opencv 3.2 default
|
||||
FROM ubuntu:18.04
|
||||
|
||||
MAINTAINER <[email protected]>
|
||||
|
||||
WORKDIR /mediapipe
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install MediaPipe & Coral deps
|
||||
|
||||
COPY update_sources.sh /
|
||||
RUN /update_sources.sh
|
||||
|
||||
RUN dpkg --add-architecture armhf
|
||||
RUN dpkg --add-architecture arm64
|
||||
RUN apt-get update && apt-get install -y \
|
||||
build-essential \
|
||||
crossbuild-essential-arm64 \
|
||||
libusb-1.0-0-dev:arm64 \
|
||||
zlibc:arm64 \
|
||||
pkg-config \
|
||||
zip \
|
||||
unzip \
|
||||
curl \
|
||||
wget \
|
||||
git \
|
||||
python \
|
||||
python-pip \
|
||||
python3-pip \
|
||||
python-numpy \
|
||||
vim-common \
|
||||
ca-certificates \
|
||||
emacs \
|
||||
software-properties-common && \
|
||||
add-apt-repository -y ppa:openjdk-r/ppa && \
|
||||
apt-get update && apt-get install -y openjdk-8-jdk
|
||||
|
||||
RUN pip install --upgrade setuptools
|
||||
RUN pip install future
|
||||
RUN pip3 install six
|
||||
|
||||
COPY . /mediapipe/
|
||||
|
||||
# Install bazel
|
||||
# Please match the current MediaPipe Bazel requirements according to docs.
|
||||
ARG BAZEL_VERSION=3.7.2
|
||||
RUN mkdir /bazel && \
|
||||
wget --no-check-certificate -O /bazel/installer.sh "https://github.com/bazelbuild/bazel/releases/download/${BAZEL_VERSION}/bazel-${BAZEL_VERSION}-installer-linux-x86_64.sh" && \
|
||||
wget --no-check-certificate -O /bazel/LICENSE.txt "https://raw.githubusercontent.com/bazelbuild/bazel/master/LICENSE" && \
|
||||
chmod +x /bazel/installer.sh && \
|
||||
/bazel/installer.sh && \
|
||||
rm -f /bazel/installer.sh
|
||||
|
||||
# OpenCV (3.2 default in 18.04)
|
||||
|
||||
RUN apt-get update && apt-get install -y libopencv-dev
|
||||
|
||||
# Opencv libs copied from coral device into opencv32_arm64_libs
|
||||
|
||||
RUN cp opencv32_arm64_libs/* /usr/lib/aarch64-linux-gnu/.
|
||||
|
||||
# Edge tpu header and lib
|
||||
|
||||
RUN git clone https://github.com/google-coral/edgetpu.git /edgetpu
|
||||
RUN cp /edgetpu/libedgetpu/direct/aarch64/libedgetpu.so.1.0 /usr/lib/aarch64-linux-gnu/libedgetpu.so
|
||||
|
||||
# See mediapipe/examples/coral/README.md to finish setup
|
||||
@@ -0,0 +1,45 @@
|
||||
# Copyright 2021 The MediaPipe Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
FROM debian:buster
|
||||
MAINTAINER <[email protected]>
|
||||
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
build-essential \
|
||||
crossbuild-essential-arm64 \
|
||||
pkg-config \
|
||||
zip \
|
||||
unzip \
|
||||
curl \
|
||||
wget \
|
||||
git \
|
||||
tree \
|
||||
vim \
|
||||
sudo \
|
||||
python3-all \
|
||||
python3-pip \
|
||||
python3-numpy \
|
||||
ca-certificates \
|
||||
software-properties-common \
|
||||
libusb-1.0-0-dev \
|
||||
libopencv-core-dev \
|
||||
libopencv-imgproc-dev \
|
||||
libopencv-video-dev \
|
||||
libopencv-highgui-dev \
|
||||
libopencv-videoio-dev \
|
||||
libopencv-contrib-dev
|
||||
RUN update-alternatives --install /usr/bin/python python /usr/bin/python3 3
|
||||
RUN wget -O /usr/bin/bazel \
|
||||
https://github.com/bazelbuild/bazelisk/releases/download/v1.10.0/bazelisk-linux-amd64 && \
|
||||
echo "038c0990a48ccd69932e4e8ecf8baa459e05a6b4c9e4cc492ac836b777caaf9d /usr/bin/bazel" sha256sum --check - && \
|
||||
chmod +x /usr/bin/bazel
|
||||
ENV BAZEL_CPU=k8
|
||||
@@ -0,0 +1,47 @@
|
||||
# Copyright 2021 The MediaPipe Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
FROM debian:buster
|
||||
MAINTAINER <[email protected]>
|
||||
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
build-essential \
|
||||
crossbuild-essential-arm64 \
|
||||
pkg-config \
|
||||
zip \
|
||||
unzip \
|
||||
curl \
|
||||
wget \
|
||||
git \
|
||||
tree \
|
||||
vim \
|
||||
sudo \
|
||||
python3-all \
|
||||
python3-pip \
|
||||
python3-numpy \
|
||||
ca-certificates \
|
||||
software-properties-common
|
||||
RUN dpkg --add-architecture arm64
|
||||
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
libusb-1.0-0-dev:arm64 \
|
||||
libopencv-core-dev:arm64 \
|
||||
libopencv-imgproc-dev:arm64 \
|
||||
libopencv-video-dev:arm64 \
|
||||
libopencv-highgui-dev:arm64 \
|
||||
libopencv-videoio-dev:arm64 \
|
||||
libopencv-contrib-dev:arm64
|
||||
RUN update-alternatives --install /usr/bin/python python /usr/bin/python3 3
|
||||
RUN wget -O /usr/bin/bazel \
|
||||
https://github.com/bazelbuild/bazelisk/releases/download/v1.10.0/bazelisk-linux-amd64 && \
|
||||
echo "038c0990a48ccd69932e4e8ecf8baa459e05a6b4c9e4cc492ac836b777caaf9d /usr/bin/bazel" sha256sum --check - && \
|
||||
chmod +x /usr/bin/bazel
|
||||
ENV BAZEL_CPU=aarch64
|
||||
@@ -0,0 +1,47 @@
|
||||
# Copyright 2021 The MediaPipe Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
FROM debian:buster
|
||||
MAINTAINER <[email protected]>
|
||||
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
build-essential \
|
||||
crossbuild-essential-armhf \
|
||||
pkg-config \
|
||||
zip \
|
||||
unzip \
|
||||
curl \
|
||||
wget \
|
||||
git \
|
||||
tree \
|
||||
vim \
|
||||
sudo \
|
||||
python3-all \
|
||||
python3-pip \
|
||||
python3-numpy \
|
||||
ca-certificates \
|
||||
software-properties-common
|
||||
RUN dpkg --add-architecture armhf
|
||||
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
libusb-1.0-0-dev:armhf \
|
||||
libopencv-core-dev:armhf \
|
||||
libopencv-imgproc-dev:armhf \
|
||||
libopencv-video-dev:armhf \
|
||||
libopencv-highgui-dev:armhf \
|
||||
libopencv-videoio-dev:armhf \
|
||||
libopencv-contrib-dev:armhf
|
||||
RUN update-alternatives --install /usr/bin/python python /usr/bin/python3 3
|
||||
RUN wget -O /usr/bin/bazel \
|
||||
https://github.com/bazelbuild/bazelisk/releases/download/v1.10.0/bazelisk-linux-amd64 && \
|
||||
echo "038c0990a48ccd69932e4e8ecf8baa459e05a6b4c9e4cc492ac836b777caaf9d /usr/bin/bazel" sha256sum --check - && \
|
||||
chmod +x /usr/bin/bazel
|
||||
ENV BAZEL_CPU=armv7a
|
||||
@@ -0,0 +1,55 @@
|
||||
SHELL := /bin/bash
|
||||
|
||||
MAKEFILE_DIR := $(realpath $(dir $(lastword $(MAKEFILE_LIST))))
|
||||
MEDIAPIPE_DIR := $(MAKEFILE_DIR)/../../..
|
||||
|
||||
BAZEL_COMPILATION_MODE ?= opt
|
||||
BAZEL_TARGET ?= mediapipe/examples/coral:face_detection_tpu
|
||||
BAZEL_CPU ?= k8
|
||||
|
||||
OUT_DIR := $(MEDIAPIPE_DIR)/out/$(BAZEL_CPU)
|
||||
|
||||
PLATFORM ?= amd64
|
||||
DOCKER_FILE ?= $(MAKEFILE_DIR)/Dockerfile.$(PLATFORM)
|
||||
DOCKER_COMMAND ?=
|
||||
|
||||
bazel_output = $(MEDIAPIPE_DIR)/bazel-bin/$(subst :,/,$(1))
|
||||
|
||||
define run_command
|
||||
chmod a+w /; \
|
||||
groupadd --gid $(shell id -g) $(shell id -g -n); \
|
||||
useradd -m -e '' -s /bin/bash --gid $(shell id -g) --uid $(shell id -u) $(shell id -u -n); \
|
||||
echo '$(shell id -u -n) ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers; \
|
||||
su $(shell id -u -n) $(if $(1),-c '$(1)',)
|
||||
endef
|
||||
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo "make help - Print help"
|
||||
@echo "make docker - Run Docker environment"
|
||||
@echo "make build - Run Bazel build, use BAZEL_TARGET to choose which target to build"
|
||||
|
||||
ifeq (,$(wildcard /.dockerenv))
|
||||
.PHONY: docker
|
||||
docker:
|
||||
docker run --rm -i --tty \
|
||||
-v $(MEDIAPIPE_DIR):/mediapipe \
|
||||
--workdir /mediapipe/ \
|
||||
$(shell docker build -q - < $(DOCKER_FILE)) \
|
||||
/bin/bash -c "$(call run_command,$(DOCKER_COMMAND))"
|
||||
endif
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
(cd $(MEDIAPIPE_DIR) && \
|
||||
bazel build \
|
||||
--crosstool_top=@crosstool//:toolchains \
|
||||
--compiler=gcc \
|
||||
--cpu=${BAZEL_CPU} \
|
||||
--compilation_mode=${BAZEL_COMPILATION_MODE} \
|
||||
--define darwinn_portable=1 \
|
||||
--define MEDIAPIPE_DISABLE_GPU=1 \
|
||||
--define MEDIAPIPE_EDGE_TPU=all \
|
||||
$(BAZEL_TARGET) && \
|
||||
mkdir -p $(OUT_DIR) && \
|
||||
cp -f $(call bazel_output,$(BAZEL_TARGET)) $(OUT_DIR))
|
||||
+136
-119
@@ -1,156 +1,173 @@
|
||||
# Coral Dev Board Setup (experimental)
|
||||
# Coral Support
|
||||
|
||||
**Disclaimer**: Running MediaPipe on Coral is experimental, and this process may
|
||||
not be exact and is subject to change. These instructions have only been tested
|
||||
on the [Coral Dev Board](https://coral.ai/products/dev-board/)
|
||||
running [Mendel Enterprise Day 13](https://coral.ai/software/) OS and
|
||||
using [Diploria2](https://github.com/google-coral/edgetpu/tree/diploria2)
|
||||
edgetpu libs, and may vary for different devices and workstations.
|
||||
## Bazel Setup
|
||||
|
||||
This file describes how to prepare a Coral Dev Board and setup a Linux
|
||||
Docker container for building MediaPipe applications that run on Edge TPU.
|
||||
You can compile MediaPipe with enabled Edge TPU support to run
|
||||
[Coral models](http://coral.ai/models). Just add
|
||||
`--define MEDIAPIPE_EDGE_TPU=<type>` to the `bazel` command:
|
||||
|
||||
## Before creating the Docker
|
||||
* `--define MEDIAPIPE_EDGE_TPU=usb` for Coral USB devices on Linux and macOS
|
||||
* `--define MEDIAPIPE_EDGE_TPU=pci` for Coral PCIe devices on Linux
|
||||
* `--define MEDIAPIPE_EDGE_TPU=all` for both Coral USB and PCIe devices on Linux
|
||||
|
||||
* (on host machine) run _setup.sh_ from MediaPipe root directory
|
||||
You have to install `libusb` library in order to compile with USB support:
|
||||
|
||||
sh mediapipe/examples/coral/setup.sh
|
||||
* `libusb-1.0-0-dev` on Linux
|
||||
* `libusb` on macOS via MacPorts or Homebrew
|
||||
|
||||
* Setup the coral device via [here](https://coral.withgoogle.com/docs/dev-board/get-started/), and ensure the _mdt_ command works
|
||||
Command to compile face detection Coral example:
|
||||
|
||||
Note: alias mdt="python3 -m mdt.main" may be needed on some systems
|
||||
```bash
|
||||
bazel build \
|
||||
--compilation_mode=opt \
|
||||
--define darwinn_portable=1 \
|
||||
--define MEDIAPIPE_DISABLE_GPU=1 \
|
||||
--define MEDIAPIPE_EDGE_TPU=usb \
|
||||
--linkopt=-l:libusb-1.0.so \
|
||||
mediapipe/examples/coral:face_detection_tpu build
|
||||
```
|
||||
|
||||
* (on coral device) prepare MediaPipe
|
||||
## Cross-compilation
|
||||
|
||||
cd ~
|
||||
sudo apt-get update && sudo apt-get install -y git
|
||||
git clone https://github.com/google/mediapipe.git
|
||||
mkdir mediapipe/bazel-bin
|
||||
Sometimes you need to cross-compile MediaPipe source code, e.g. get `ARM32`
|
||||
or `ARM64` binaries on `x86` system. Install cross-compilation toolchain on
|
||||
your system or use our preconfigured Docker environment for that:
|
||||
|
||||
* (on coral device) install opencv 3.2
|
||||
```bash
|
||||
# For ARM32 (e.g. Raspberry Pi)
|
||||
make -C mediapipe/examples/coral PLATFORM=armhf docker
|
||||
|
||||
sudo apt-get update && sudo apt-get install -y libopencv-dev
|
||||
# For ARM64 (e.g. Coral Dev Board)
|
||||
make -C mediapipe/examples/coral PLATFORM=arm64 docker
|
||||
```
|
||||
|
||||
* (on coral device) find all opencv libs
|
||||
After running this command you'll get a shell to the Docker environment which
|
||||
has everything ready to start compilation:
|
||||
|
||||
find /usr/lib/aarch64-linux-gnu/ -name 'libopencv*so'
|
||||
```bash
|
||||
# For ARM32 (e.g. Raspberry Pi)
|
||||
bazel build \
|
||||
--crosstool_top=@crosstool//:toolchains \
|
||||
--compiler=gcc \
|
||||
--cpu=armv7a \
|
||||
--define darwinn_portable=1 \
|
||||
--define MEDIAPIPE_DISABLE_GPU=1 \
|
||||
--define MEDIAPIPE_EDGE_TPU=usb \
|
||||
--linkopt=-l:libusb-1.0.so \
|
||||
mediapipe/examples/coral:face_detection_tpu build
|
||||
|
||||
* (on host machine) copy core opencv libs from coral device to a local folder inside MediaPipe checkout:
|
||||
# For ARM64 (e.g. Coral Dev Board)
|
||||
bazel build \
|
||||
--crosstool_top=@crosstool//:toolchains \
|
||||
--compiler=gcc \
|
||||
--cpu=aarch64 \
|
||||
--define darwinn_portable=1 \
|
||||
--define MEDIAPIPE_DISABLE_GPU=1 \
|
||||
--define MEDIAPIPE_EDGE_TPU=usb \
|
||||
--linkopt=-l:libusb-1.0.so \
|
||||
mediapipe/examples/coral:face_detection_tpu build
|
||||
```
|
||||
|
||||
# in root level mediapipe folder #
|
||||
mdt pull /usr/lib/aarch64-linux-gnu/libopencv_core.so opencv32_arm64_libs
|
||||
mdt pull /usr/lib/aarch64-linux-gnu/libopencv_calib3d.so opencv32_arm64_libs
|
||||
mdt pull /usr/lib/aarch64-linux-gnu/libopencv_features2d.so opencv32_arm64_libs
|
||||
mdt pull /usr/lib/aarch64-linux-gnu/libopencv_highgui.so opencv32_arm64_libs
|
||||
mdt pull /usr/lib/aarch64-linux-gnu/libopencv_imgcodecs.so opencv32_arm64_libs
|
||||
mdt pull /usr/lib/aarch64-linux-gnu/libopencv_imgproc.so opencv32_arm64_libs
|
||||
mdt pull /usr/lib/aarch64-linux-gnu/libopencv_video.so opencv32_arm64_libs
|
||||
mdt pull /usr/lib/aarch64-linux-gnu/libopencv_videoio.so opencv32_arm64_libs
|
||||
Our Docker environment defines `${BAZEL_CPU}` value, so you can use it directly:
|
||||
|
||||
* (on host machine) Create and start the docker environment
|
||||
```bash
|
||||
bazel build \
|
||||
--crosstool_top=@crosstool//:toolchains \
|
||||
--compiler=gcc \
|
||||
--cpu=${BAZEL_CPU} \
|
||||
--define darwinn_portable=1 \
|
||||
--define MEDIAPIPE_DISABLE_GPU=1 \
|
||||
--define MEDIAPIPE_EDGE_TPU=usb \
|
||||
--linkopt=-l:libusb-1.0.so \
|
||||
mediapipe/examples/coral:face_detection_tpu build
|
||||
```
|
||||
|
||||
# from mediapipe root level directory #
|
||||
docker build -t coral .
|
||||
docker run -it --name coral coral:latest
|
||||
The command above is already defined in our `Makefile`, so you can simply run:
|
||||
|
||||
## Inside the Docker environment
|
||||
```bash
|
||||
make -C mediapipe/examples/coral \
|
||||
BAZEL_TARGET=mediapipe/examples/coral:face_detection_tpu \
|
||||
build
|
||||
```
|
||||
|
||||
* Update library paths in /mediapipe/third_party/opencv_linux.BUILD
|
||||
The output binary will be automatically copied to `out/<platform>` directory.
|
||||
|
||||
(replace 'x86_64-linux-gnu' with 'aarch64-linux-gnu')
|
||||
You can also run compilation inside Docker environment as a single
|
||||
command:
|
||||
|
||||
"lib/aarch64-linux-gnu/libopencv_core.so",
|
||||
"lib/aarch64-linux-gnu/libopencv_calib3d.so",
|
||||
"lib/aarch64-linux-gnu/libopencv_features2d.so",
|
||||
"lib/aarch64-linux-gnu/libopencv_highgui.so",
|
||||
"lib/aarch64-linux-gnu/libopencv_imgcodecs.so",
|
||||
"lib/aarch64-linux-gnu/libopencv_imgproc.so",
|
||||
"lib/aarch64-linux-gnu/libopencv_video.so",
|
||||
"lib/aarch64-linux-gnu/libopencv_videoio.so",
|
||||
```bash
|
||||
make -C mediapipe/examples/coral \
|
||||
PLATFORM=armhf \
|
||||
DOCKER_COMMAND="make -C mediapipe/examples/coral BAZEL_TARGET=mediapipe/examples/coral:face_detection_tpu build" \
|
||||
docker
|
||||
```
|
||||
|
||||
* Attempt to build hello world (to download external deps)
|
||||
and get the output binary from `out/<platform>` directory. Any Mediapipe target
|
||||
can be cross-compiled this way, e.g. try
|
||||
`mediapipe/examples/desktop/hand_tracking:hand_tracking_cpu`.
|
||||
|
||||
bazel build -c opt --define MEDIAPIPE_DISABLE_GPU=1 mediapipe/examples/desktop/hello_world:hello_world
|
||||
To summarize everything:
|
||||
|
||||
* Edit /edgetpu/libedgetpu/BUILD
|
||||
| Arch | PLATFORM | Output | Board |
|
||||
| ----- | -------------- | ----------- | -------------------------------------------------------- |
|
||||
| ARM32 | PLATFORM=armhf | out/armv7a | [Raspberry Pi](https://www.raspberrypi.org/products/) |
|
||||
| ARM64 | PLATFORM=arm64 | out/aarch64 | [Coral Dev Board](https://coral.ai/products/dev-board/) |
|
||||
|
||||
to add this build target
|
||||
## Coral Examples
|
||||
|
||||
cc_library(
|
||||
name = "lib",
|
||||
srcs = [
|
||||
"libedgetpu.so",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
There are two Coral examples in `mediapipe/examples/coral` directory. Compile
|
||||
them for your platform:
|
||||
|
||||
* Edit /edgetpu/WORKSPACE
|
||||
```bash
|
||||
# Face detection
|
||||
make -C mediapipe/examples/coral \
|
||||
PLATFORM=armhf \
|
||||
DOCKER_COMMAND="make -C mediapipe/examples/coral BAZEL_TARGET=mediapipe/examples/coral:face_detection_tpu build" \
|
||||
docker
|
||||
|
||||
update /mediapipe/WORKSPACE TENSORFLOW_* variables to match what /edgetpu/WORKSPACE has:
|
||||
# Object detection
|
||||
make -C mediapipe/examples/coral \
|
||||
PLATFORM=armhf \
|
||||
DOCKER_COMMAND="make -C mediapipe/examples/coral BAZEL_TARGET=mediapipe/examples/coral:object_detection_tpu build" \
|
||||
docker
|
||||
```
|
||||
|
||||
grep TENSORFLOW_ /mediapipe/WORKSPACE
|
||||
grep TENSORFLOW_ /edgetpu/WORKSPACE
|
||||
Copy output binaries along with corresponding auxiliary files to your target
|
||||
system. You can copy the whole `mediapipe` folder for simplicity:
|
||||
|
||||
# Make sure the /mediapipe/WORKSPACE _TENSORFLOW_GIT_COMMIT and _TENSORFLOW_SHA256
|
||||
# match the /edgetpu/WORKSPACE TENSORFLOW_COMMIT and TENSORFLOW_SHA256 respectively.
|
||||
```bash
|
||||
scp -r mediapipe <user>@<host>:.
|
||||
```
|
||||
|
||||
# If they do not match, modify /mediapipe/WORKSPACE to match what /edgetpu/WORKSPACE has.
|
||||
# Also comment out the MediaPipe org_tensorflow patch section.
|
||||
OpenCV runtime libraries need to be installed on your target system:
|
||||
|
||||
* Edit /mediapipe/mediapipe/calculators/tflite/BUILD to change rules for *tflite_inference_calculator.cc*
|
||||
```bash
|
||||
sudo apt-get install -y \
|
||||
libopencv-core-dev \
|
||||
libopencv-highgui-dev \
|
||||
libopencv-calib3d-dev \
|
||||
libopencv-features2d-dev \
|
||||
libopencv-imgproc-dev \
|
||||
libopencv-video-dev
|
||||
```
|
||||
|
||||
sed -i 's/\":tflite_inference_calculator_cc_proto\",/\":tflite_inference_calculator_cc_proto\",\n\t\"@edgetpu\/\/:header\",\n\t\"@libedgetpu\/\/:lib\",/g' /mediapipe/mediapipe/calculators/tflite/BUILD
|
||||
If you are going to connect Coral USB accelerator to your target system then
|
||||
you'll also need `libusb` library:
|
||||
|
||||
The above command should add
|
||||
```shell
|
||||
sudo apt-get install -y \
|
||||
libusb-1.0-0
|
||||
```
|
||||
|
||||
"@edgetpu//:header",
|
||||
"@libedgetpu//:lib",
|
||||
Connect USB camera and Coral device to your target system and run the copied
|
||||
binaries:
|
||||
|
||||
to the _deps_ of tflite_inference_calculator.cc
|
||||
|
||||
Now also remove XNNPACK deps:
|
||||
|
||||
sed -i 's/\"@org_tensorflow\/\/tensorflow\/lite\/delegates\/xnnpack/#\"@org_tensorflow\/\/tensorflow\/lite\/delegates\/xnnpack/g' /mediapipe/mediapipe/calculators/tflite/BUILD
|
||||
|
||||
#### Now try cross-compiling for device
|
||||
|
||||
* Object detection demo
|
||||
|
||||

|
||||
|
||||
bazel build -c opt --crosstool_top=@crosstool//:toolchains --compiler=gcc --cpu=aarch64 --define MEDIAPIPE_DISABLE_GPU=1 --copt -DMEDIAPIPE_EDGE_TPU --copt=-flax-vector-conversions mediapipe/examples/coral:object_detection_tpu
|
||||
|
||||
Copy object_detection_tpu binary to the MediaPipe checkout on the coral device
|
||||
|
||||
# outside docker env, open new terminal on host machine #
|
||||
docker ps
|
||||
docker cp <container-id>:/mediapipe/bazel-bin/mediapipe/examples/coral/object_detection_tpu /tmp/.
|
||||
mdt push /tmp/object_detection_tpu /home/mendel/mediapipe/bazel-bin/.
|
||||
|
||||
* Face detection demo
|
||||
|
||||

|
||||
|
||||
bazel build -c opt --crosstool_top=@crosstool//:toolchains --compiler=gcc --cpu=aarch64 --define MEDIAPIPE_DISABLE_GPU=1 --copt -DMEDIAPIPE_EDGE_TPU --copt=-flax-vector-conversions mediapipe/examples/coral:face_detection_tpu
|
||||
|
||||
Copy face_detection_tpu binary to the MediaPipe checkout on the coral device
|
||||
|
||||
# outside docker env, open new terminal on host machine #
|
||||
docker ps
|
||||
docker cp <container-id>:/mediapipe/bazel-bin/mediapipe/examples/coral/face_detection_tpu /tmp/.
|
||||
mdt push /tmp/face_detection_tpu /home/mendel/mediapipe/bazel-bin/.
|
||||
|
||||
## On the coral device (with display)
|
||||
|
||||
# Object detection
|
||||
cd ~/mediapipe
|
||||
chmod +x bazel-bin/object_detection_tpu
|
||||
export GLOG_logtostderr=1
|
||||
bazel-bin/object_detection_tpu --calculator_graph_config_file=mediapipe/examples/coral/graphs/object_detection_desktop_live.pbtxt
|
||||
|
||||
# Face detection
|
||||
cd ~/mediapipe
|
||||
chmod +x bazel-bin/face_detection_tpu
|
||||
export GLOG_logtostderr=1
|
||||
bazel-bin/face_detection_tpu --calculator_graph_config_file=mediapipe/examples/coral/graphs/face_detection_desktop_live.pbtxt
|
||||
```bash
|
||||
# Face Detection
|
||||
GLOG_logtostderr=1 ./face_detection_tpu --calculator_graph_config_file \
|
||||
mediapipe/examples/coral/graphs/face_detection_desktop_live.pbtxt
|
||||
|
||||
# Object Detection
|
||||
GLOG_logtostderr=1 ./object_detection_tpu --calculator_graph_config_file \
|
||||
mediapipe/examples/coral/graphs/object_detection_desktop_live.pbtxt
|
||||
```
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
|
||||
### Coral additions to MediaPipe WORKSPACE ###
|
||||
|
||||
#COMMIT=$(git ls-remote https://github.com/google-coral/crosstool master | awk '{print $1}')
|
||||
#SHA256=$(curl -L "https://github.com/google-coral/crosstool/archive/${COMMIT}.tar.gz" | sha256sum | awk '{print $1}')
|
||||
# Oct 2019
|
||||
#COMMIT=9e00d5be43bf001f883b5700f5d04882fea00229
|
||||
#SHA256=cb31b1417ccdcf7dd9fca5ec63e1571672372c30427730255997a547569d2feb
|
||||
http_archive(
|
||||
name = "coral_crosstool",
|
||||
sha256 = "cb31b1417ccdcf7dd9fca5ec63e1571672372c30427730255997a547569d2feb",
|
||||
strip_prefix = "crosstool-9e00d5be43bf001f883b5700f5d04882fea00229",
|
||||
urls = [
|
||||
"https://github.com/google-coral/crosstool/archive/9e00d5be43bf001f883b5700f5d04882fea00229.tar.gz",
|
||||
],
|
||||
)
|
||||
load("@coral_crosstool//:configure.bzl", "cc_crosstool")
|
||||
cc_crosstool(name = "crosstool")
|
||||
|
||||
# EdgeTPU
|
||||
new_local_repository(
|
||||
name = "edgetpu",
|
||||
path = "/edgetpu/libedgetpu",
|
||||
build_file = "/edgetpu/libedgetpu/BUILD"
|
||||
)
|
||||
new_local_repository(
|
||||
name = "libedgetpu",
|
||||
path = "/usr/lib/aarch64-linux-gnu",
|
||||
build_file = "/edgetpu/libedgetpu/BUILD"
|
||||
)
|
||||
@@ -74,43 +74,12 @@ node {
|
||||
}
|
||||
}
|
||||
|
||||
# Generates a single side packet containing a vector of SSD anchors based on
|
||||
# the specification in the options.
|
||||
node {
|
||||
calculator: "SsdAnchorsCalculator"
|
||||
output_side_packet: "anchors"
|
||||
options: {
|
||||
[mediapipe.SsdAnchorsCalculatorOptions.ext] {
|
||||
num_layers: 6
|
||||
min_scale: 0.2
|
||||
max_scale: 0.95
|
||||
input_size_height: 300
|
||||
input_size_width: 300
|
||||
anchor_offset_x: 0.5
|
||||
anchor_offset_y: 0.5
|
||||
strides: 16
|
||||
strides: 32
|
||||
strides: 64
|
||||
strides: 128
|
||||
strides: 256
|
||||
strides: 512
|
||||
aspect_ratios: 1.0
|
||||
aspect_ratios: 2.0
|
||||
aspect_ratios: 0.5
|
||||
aspect_ratios: 3.0
|
||||
aspect_ratios: 0.3333
|
||||
reduce_boxes_in_lowest_layer: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Decodes the detection tensors generated by the TensorFlow Lite model, based on
|
||||
# the SSD anchors and the specification in the options, into a vector of
|
||||
# detections. Each detection describes a detected object.
|
||||
node {
|
||||
calculator: "TfLiteTensorsToDetectionsCalculator"
|
||||
input_stream: "TENSORS:detection_tensors"
|
||||
input_side_packet: "ANCHORS:anchors"
|
||||
output_stream: "DETECTIONS:detections"
|
||||
options: {
|
||||
[mediapipe.TfLiteTensorsToDetectionsCalculatorOptions.ext] {
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
set -v
|
||||
|
||||
echo 'Please run this from root level mediapipe directory! \n Ex:'
|
||||
echo ' sh mediapipe/examples/coral/setup.sh '
|
||||
|
||||
sleep 3
|
||||
|
||||
mkdir -p opencv32_arm64_libs
|
||||
|
||||
# prepare docker aux script
|
||||
cp mediapipe/examples/coral/update_sources.sh update_sources.sh
|
||||
chmod +x update_sources.sh
|
||||
|
||||
# backup non-coral Dockerfile
|
||||
mv Dockerfile Dockerfile.orig
|
||||
cp mediapipe/examples/coral/Dockerfile Dockerfile
|
||||
|
||||
# backup non-coral workspace
|
||||
cp WORKSPACE WORKSPACE.orig
|
||||
|
||||
# create temps
|
||||
cp WORKSPACE WORKSPACE.1
|
||||
cp mediapipe/examples/coral/WORKSPACE.coral WORKSPACE.2
|
||||
|
||||
# merge (shell decides concat order, unless numbered appropriately)
|
||||
cat WORKSPACE.1 WORKSPACE.2 > WORKSPACE
|
||||
|
||||
# cleanup
|
||||
rm WORKSPACE.1 WORKSPACE.2
|
||||
|
||||
echo 'done'
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# To run in the Coral Docker environment.
|
||||
|
||||
. /etc/os-release
|
||||
|
||||
sed -i "s/deb\ /deb \[arch=amd64\]\ /g" /etc/apt/sources.list
|
||||
|
||||
echo "deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports ${UBUNTU_CODENAME} main universe" >> /etc/apt/sources.list
|
||||
echo "deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports ${UBUNTU_CODENAME}-updates main universe" >> /etc/apt/sources.list
|
||||
echo "deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports ${UBUNTU_CODENAME}-security main universe" >> /etc/apt/sources.list
|
||||
@@ -17,8 +17,10 @@ load("//mediapipe/framework/port:build_config.bzl", "mediapipe_cc_proto_library"
|
||||
licenses(["notice"])
|
||||
|
||||
package(default_visibility = [
|
||||
"//buzz/diffractor/mediapipe:__subpackages__",
|
||||
"//mediapipe/examples:__subpackages__",
|
||||
"//mediapipe/viz:__subpackages__",
|
||||
"//mediapipe/web/solutions:__subpackages__",
|
||||
])
|
||||
|
||||
cc_library(
|
||||
|
||||
+18
-15
@@ -43,6 +43,9 @@ namespace mediapipe {
|
||||
namespace autoflip {
|
||||
namespace {
|
||||
|
||||
constexpr char kDetectedBordersTag[] = "DETECTED_BORDERS";
|
||||
constexpr char kVideoTag[] = "VIDEO";
|
||||
|
||||
const char kConfig[] = R"(
|
||||
calculator: "BorderDetectionCalculator"
|
||||
input_stream: "VIDEO:camera_frames"
|
||||
@@ -81,14 +84,14 @@ TEST(BorderDetectionCalculatorTest, NoBorderTest) {
|
||||
ImageFormat::SRGB, kTestFrameWidth, kTestFrameHeight);
|
||||
cv::Mat input_mat = mediapipe::formats::MatView(input_frame.get());
|
||||
input_mat.setTo(cv::Scalar(0, 0, 0));
|
||||
runner->MutableInputs()->Tag("VIDEO").packets.push_back(
|
||||
runner->MutableInputs()->Tag(kVideoTag).packets.push_back(
|
||||
Adopt(input_frame.release()).At(Timestamp::PostStream()));
|
||||
|
||||
// Run the calculator.
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("DETECTED_BORDERS").packets;
|
||||
runner->Outputs().Tag(kDetectedBordersTag).packets;
|
||||
ASSERT_EQ(1, output_packets.size());
|
||||
const auto& static_features = output_packets[0].Get<StaticFeatures>();
|
||||
ASSERT_EQ(0, static_features.border().size());
|
||||
@@ -115,14 +118,14 @@ TEST(BorderDetectionCalculatorTest, TopBorderTest) {
|
||||
cv::Mat sub_image =
|
||||
input_mat(cv::Rect(0, 0, kTestFrameWidth, kTopBorderHeight));
|
||||
sub_image.setTo(cv::Scalar(255, 0, 0));
|
||||
runner->MutableInputs()->Tag("VIDEO").packets.push_back(
|
||||
runner->MutableInputs()->Tag(kVideoTag).packets.push_back(
|
||||
Adopt(input_frame.release()).At(Timestamp::PostStream()));
|
||||
|
||||
// Run the calculator.
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("DETECTED_BORDERS").packets;
|
||||
runner->Outputs().Tag(kDetectedBordersTag).packets;
|
||||
ASSERT_EQ(1, output_packets.size());
|
||||
const auto& static_features = output_packets[0].Get<StaticFeatures>();
|
||||
ASSERT_EQ(1, static_features.border().size());
|
||||
@@ -155,14 +158,14 @@ TEST(BorderDetectionCalculatorTest, TopBorderPadTest) {
|
||||
cv::Mat sub_image =
|
||||
input_mat(cv::Rect(0, 0, kTestFrameWidth, kTopBorderHeight));
|
||||
sub_image.setTo(cv::Scalar(255, 0, 0));
|
||||
runner->MutableInputs()->Tag("VIDEO").packets.push_back(
|
||||
runner->MutableInputs()->Tag(kVideoTag).packets.push_back(
|
||||
Adopt(input_frame.release()).At(Timestamp::PostStream()));
|
||||
|
||||
// Run the calculator.
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("DETECTED_BORDERS").packets;
|
||||
runner->Outputs().Tag(kDetectedBordersTag).packets;
|
||||
ASSERT_EQ(1, output_packets.size());
|
||||
const auto& static_features = output_packets[0].Get<StaticFeatures>();
|
||||
ASSERT_EQ(1, static_features.border().size());
|
||||
@@ -197,14 +200,14 @@ TEST(BorderDetectionCalculatorTest, BottomBorderTest) {
|
||||
input_mat(cv::Rect(0, kTestFrameHeight - kBottomBorderHeight,
|
||||
kTestFrameWidth, kBottomBorderHeight));
|
||||
bottom_image.setTo(cv::Scalar(255, 0, 0));
|
||||
runner->MutableInputs()->Tag("VIDEO").packets.push_back(
|
||||
runner->MutableInputs()->Tag(kVideoTag).packets.push_back(
|
||||
Adopt(input_frame.release()).At(Timestamp::PostStream()));
|
||||
|
||||
// Run the calculator.
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("DETECTED_BORDERS").packets;
|
||||
runner->Outputs().Tag(kDetectedBordersTag).packets;
|
||||
ASSERT_EQ(1, output_packets.size());
|
||||
const auto& static_features = output_packets[0].Get<StaticFeatures>();
|
||||
ASSERT_EQ(1, static_features.border().size());
|
||||
@@ -238,14 +241,14 @@ TEST(BorderDetectionCalculatorTest, TopBottomBorderTest) {
|
||||
input_mat(cv::Rect(0, kTestFrameHeight - kBottomBorderHeight,
|
||||
kTestFrameWidth, kBottomBorderHeight));
|
||||
bottom_image.setTo(cv::Scalar(255, 0, 0));
|
||||
runner->MutableInputs()->Tag("VIDEO").packets.push_back(
|
||||
runner->MutableInputs()->Tag(kVideoTag).packets.push_back(
|
||||
Adopt(input_frame.release()).At(Timestamp::PostStream()));
|
||||
|
||||
// Run the calculator.
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("DETECTED_BORDERS").packets;
|
||||
runner->Outputs().Tag(kDetectedBordersTag).packets;
|
||||
ASSERT_EQ(1, output_packets.size());
|
||||
const auto& static_features = output_packets[0].Get<StaticFeatures>();
|
||||
ASSERT_EQ(2, static_features.border().size());
|
||||
@@ -291,14 +294,14 @@ TEST(BorderDetectionCalculatorTest, TopBottomBorderTestAspect2) {
|
||||
input_mat(cv::Rect(0, kTestFrameHeightTall - kBottomBorderHeight,
|
||||
kTestFrameWidthTall, kBottomBorderHeight));
|
||||
bottom_image.setTo(cv::Scalar(255, 0, 0));
|
||||
runner->MutableInputs()->Tag("VIDEO").packets.push_back(
|
||||
runner->MutableInputs()->Tag(kVideoTag).packets.push_back(
|
||||
Adopt(input_frame.release()).At(Timestamp::PostStream()));
|
||||
|
||||
// Run the calculator.
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("DETECTED_BORDERS").packets;
|
||||
runner->Outputs().Tag(kDetectedBordersTag).packets;
|
||||
ASSERT_EQ(1, output_packets.size());
|
||||
const auto& static_features = output_packets[0].Get<StaticFeatures>();
|
||||
ASSERT_EQ(2, static_features.border().size());
|
||||
@@ -352,14 +355,14 @@ TEST(BorderDetectionCalculatorTest, DominantColor) {
|
||||
input_mat(cv::Rect(0, 0, kTestFrameWidth / 2 + 50, kTestFrameHeight / 2));
|
||||
sub_image.setTo(cv::Scalar(255, 0, 0));
|
||||
|
||||
runner->MutableInputs()->Tag("VIDEO").packets.push_back(
|
||||
runner->MutableInputs()->Tag(kVideoTag).packets.push_back(
|
||||
Adopt(input_frame.release()).At(Timestamp::PostStream()));
|
||||
|
||||
// Run the calculator.
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("DETECTED_BORDERS").packets;
|
||||
runner->Outputs().Tag(kDetectedBordersTag).packets;
|
||||
ASSERT_EQ(1, output_packets.size());
|
||||
const auto& static_features = output_packets[0].Get<StaticFeatures>();
|
||||
ASSERT_EQ(0, static_features.border().size());
|
||||
@@ -383,7 +386,7 @@ void BM_Large(benchmark::State& state) {
|
||||
cv::Mat sub_image =
|
||||
input_mat(cv::Rect(0, 0, kTestFrameLargeWidth, kTopBorderHeight));
|
||||
sub_image.setTo(cv::Scalar(255, 0, 0));
|
||||
runner->MutableInputs()->Tag("VIDEO").packets.push_back(
|
||||
runner->MutableInputs()->Tag(kVideoTag).packets.push_back(
|
||||
Adopt(input_frame.release()).At(Timestamp::PostStream()));
|
||||
|
||||
// Run the calculator.
|
||||
|
||||
@@ -31,7 +31,11 @@ constexpr char kVideoSize[] = "VIDEO_SIZE";
|
||||
constexpr char kSalientRegions[] = "SALIENT_REGIONS";
|
||||
constexpr char kDetections[] = "DETECTIONS";
|
||||
constexpr char kDetectedBorders[] = "BORDERS";
|
||||
// Crop location as abs rect discretized.
|
||||
constexpr char kCropRect[] = "CROP_RECT";
|
||||
// Crop location as normalized rect.
|
||||
constexpr char kNormalizedCropRect[] = "NORMALIZED_CROP_RECT";
|
||||
// Crop location without position smoothing.
|
||||
constexpr char kFirstCropRect[] = "FIRST_CROP_RECT";
|
||||
// Can be used to control whether an animated zoom should actually performed
|
||||
// (configured through option us_to_first_rect). If provided, a non-zero integer
|
||||
@@ -51,6 +55,8 @@ constexpr float kFieldOfView = 60;
|
||||
// Used to save state on Close and load state on Open in a new graph.
|
||||
// Can be used to preserve state between graphs.
|
||||
constexpr char kStateCache[] = "STATE_CACHE";
|
||||
// Tolerance for zooming out recentering.
|
||||
constexpr float kPixelTolerance = 3;
|
||||
|
||||
namespace mediapipe {
|
||||
namespace autoflip {
|
||||
@@ -166,6 +172,9 @@ absl::Status ContentZoomingCalculator::GetContract(
|
||||
if (cc->Outputs().HasTag(kCropRect)) {
|
||||
cc->Outputs().Tag(kCropRect).Set<mediapipe::Rect>();
|
||||
}
|
||||
if (cc->Outputs().HasTag(kNormalizedCropRect)) {
|
||||
cc->Outputs().Tag(kNormalizedCropRect).Set<mediapipe::NormalizedRect>();
|
||||
}
|
||||
if (cc->Outputs().HasTag(kFirstCropRect)) {
|
||||
cc->Outputs().Tag(kFirstCropRect).Set<mediapipe::NormalizedRect>();
|
||||
}
|
||||
@@ -553,6 +562,16 @@ absl::Status ContentZoomingCalculator::Process(
|
||||
cc->Outputs().Tag(kCropRect).Add(default_rect.release(),
|
||||
Timestamp(cc->InputTimestamp()));
|
||||
}
|
||||
if (cc->Outputs().HasTag(kNormalizedCropRect)) {
|
||||
auto default_rect = absl::make_unique<mediapipe::NormalizedRect>();
|
||||
default_rect->set_x_center(0.5);
|
||||
default_rect->set_y_center(0.5);
|
||||
default_rect->set_width(1.0);
|
||||
default_rect->set_height(1.0);
|
||||
cc->Outputs()
|
||||
.Tag(kNormalizedCropRect)
|
||||
.Add(default_rect.release(), Timestamp(cc->InputTimestamp()));
|
||||
}
|
||||
// Also provide a first crop rect: in this case a zero-sized one.
|
||||
if (cc->Outputs().HasTag(kFirstCropRect)) {
|
||||
cc->Outputs()
|
||||
@@ -634,9 +653,9 @@ absl::Status ContentZoomingCalculator::Process(
|
||||
// Compute smoothed zoom camera path.
|
||||
MP_RETURN_IF_ERROR(path_solver_zoom_->AddObservation(
|
||||
height, cc->InputTimestamp().Microseconds()));
|
||||
int path_height;
|
||||
float path_height;
|
||||
MP_RETURN_IF_ERROR(path_solver_zoom_->GetState(&path_height));
|
||||
int path_width = path_height * target_aspect_;
|
||||
float path_width = path_height * target_aspect_;
|
||||
|
||||
// Update pixel-per-degree value for pan/tilt.
|
||||
int target_height;
|
||||
@@ -652,11 +671,48 @@ absl::Status ContentZoomingCalculator::Process(
|
||||
offset_x, cc->InputTimestamp().Microseconds()));
|
||||
MP_RETURN_IF_ERROR(path_solver_tilt_->AddObservation(
|
||||
offset_y, cc->InputTimestamp().Microseconds()));
|
||||
int path_offset_x;
|
||||
float path_offset_x;
|
||||
MP_RETURN_IF_ERROR(path_solver_pan_->GetState(&path_offset_x));
|
||||
int path_offset_y;
|
||||
float path_offset_y;
|
||||
MP_RETURN_IF_ERROR(path_solver_tilt_->GetState(&path_offset_y));
|
||||
|
||||
float delta_height;
|
||||
MP_RETURN_IF_ERROR(path_solver_zoom_->GetDeltaState(&delta_height));
|
||||
int delta_width = delta_height * target_aspect_;
|
||||
|
||||
// Smooth centering when zooming out.
|
||||
float remaining_width = target_width - path_width;
|
||||
int width_space = frame_width_ - target_width;
|
||||
if (abs(path_offset_x - frame_width_ / 2) >
|
||||
width_space / 2 + kPixelTolerance &&
|
||||
remaining_width > kPixelTolerance) {
|
||||
float required_width =
|
||||
abs(path_offset_x - frame_width_ / 2) - width_space / 2;
|
||||
if (path_offset_x < frame_width_ / 2) {
|
||||
path_offset_x += delta_width * (required_width / remaining_width);
|
||||
MP_RETURN_IF_ERROR(path_solver_pan_->SetState(path_offset_x));
|
||||
} else {
|
||||
path_offset_x -= delta_width * (required_width / remaining_width);
|
||||
MP_RETURN_IF_ERROR(path_solver_pan_->SetState(path_offset_x));
|
||||
}
|
||||
}
|
||||
|
||||
float remaining_height = target_height - path_height;
|
||||
int height_space = frame_height_ - target_height;
|
||||
if (abs(path_offset_y - frame_height_ / 2) >
|
||||
height_space / 2 + kPixelTolerance &&
|
||||
remaining_height > kPixelTolerance) {
|
||||
float required_height =
|
||||
abs(path_offset_y - frame_height_ / 2) - height_space / 2;
|
||||
if (path_offset_y < frame_height_ / 2) {
|
||||
path_offset_y += delta_height * (required_height / remaining_height);
|
||||
MP_RETURN_IF_ERROR(path_solver_tilt_->SetState(path_offset_y));
|
||||
} else {
|
||||
path_offset_y -= delta_height * (required_height / remaining_height);
|
||||
MP_RETURN_IF_ERROR(path_solver_tilt_->SetState(path_offset_y));
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent box from extending beyond the image after camera smoothing.
|
||||
if (path_offset_y - ceil(path_height / 2.0) < 0) {
|
||||
path_offset_y = ceil(path_height / 2.0);
|
||||
@@ -705,7 +761,7 @@ absl::Status ContentZoomingCalculator::Process(
|
||||
is_animating = IsAnimatingToFirstRect(cc->InputTimestamp());
|
||||
}
|
||||
|
||||
// Transmit downstream to glcroppingcalculator.
|
||||
// Transmit downstream to glcroppingcalculator in discrete int values.
|
||||
if (cc->Outputs().HasTag(kCropRect)) {
|
||||
std::unique_ptr<mediapipe::Rect> gpu_rect;
|
||||
if (is_animating) {
|
||||
@@ -716,13 +772,36 @@ absl::Status ContentZoomingCalculator::Process(
|
||||
} else {
|
||||
gpu_rect = absl::make_unique<mediapipe::Rect>();
|
||||
gpu_rect->set_x_center(path_offset_x);
|
||||
gpu_rect->set_width(path_height * target_aspect_);
|
||||
gpu_rect->set_width(path_width);
|
||||
gpu_rect->set_y_center(path_offset_y);
|
||||
gpu_rect->set_height(path_height);
|
||||
}
|
||||
cc->Outputs().Tag(kCropRect).Add(gpu_rect.release(),
|
||||
Timestamp(cc->InputTimestamp()));
|
||||
}
|
||||
if (cc->Outputs().HasTag(kNormalizedCropRect)) {
|
||||
std::unique_ptr<mediapipe::NormalizedRect> gpu_rect =
|
||||
absl::make_unique<mediapipe::NormalizedRect>();
|
||||
float float_frame_width = static_cast<float>(frame_width_);
|
||||
float float_frame_height = static_cast<float>(frame_height_);
|
||||
if (is_animating) {
|
||||
auto rect =
|
||||
GetAnimationRect(frame_width, frame_height, cc->InputTimestamp());
|
||||
MP_RETURN_IF_ERROR(rect.status());
|
||||
gpu_rect->set_x_center(rect->x_center() / float_frame_width);
|
||||
gpu_rect->set_width(rect->width() / float_frame_width);
|
||||
gpu_rect->set_y_center(rect->y_center() / float_frame_height);
|
||||
gpu_rect->set_height(rect->height() / float_frame_height);
|
||||
} else {
|
||||
gpu_rect->set_x_center(path_offset_x / float_frame_width);
|
||||
gpu_rect->set_width(path_width / float_frame_width);
|
||||
gpu_rect->set_y_center(path_offset_y / float_frame_height);
|
||||
gpu_rect->set_height(path_height / float_frame_height);
|
||||
}
|
||||
cc->Outputs()
|
||||
.Tag(kNormalizedCropRect)
|
||||
.Add(gpu_rect.release(), Timestamp(cc->InputTimestamp()));
|
||||
}
|
||||
|
||||
if (cc->Outputs().HasTag(kFirstCropRect)) {
|
||||
cc->Outputs()
|
||||
|
||||
+142
-93
@@ -38,6 +38,17 @@ namespace mediapipe {
|
||||
namespace autoflip {
|
||||
namespace {
|
||||
|
||||
constexpr char kFirstCropRectTag[] = "FIRST_CROP_RECT";
|
||||
constexpr char kStateCacheTag[] = "STATE_CACHE";
|
||||
constexpr char kCropRectTag[] = "CROP_RECT";
|
||||
constexpr char kBordersTag[] = "BORDERS";
|
||||
constexpr char kSalientRegionsTag[] = "SALIENT_REGIONS";
|
||||
constexpr char kVideoTag[] = "VIDEO";
|
||||
constexpr char kMaxZoomFactorPctTag[] = "MAX_ZOOM_FACTOR_PCT";
|
||||
constexpr char kAnimateZoomTag[] = "ANIMATE_ZOOM";
|
||||
constexpr char kVideoSizeTag[] = "VIDEO_SIZE";
|
||||
constexpr char kDetectionsTag[] = "DETECTIONS";
|
||||
|
||||
const char kConfigA[] = R"(
|
||||
calculator: "ContentZoomingCalculator"
|
||||
input_stream: "VIDEO:camera_frames"
|
||||
@@ -48,12 +59,15 @@ const char kConfigA[] = R"(
|
||||
max_zoom_value_deg: 0
|
||||
kinematic_options_zoom {
|
||||
min_motion_to_reframe: 1.2
|
||||
max_velocity: 18
|
||||
}
|
||||
kinematic_options_tilt {
|
||||
min_motion_to_reframe: 1.2
|
||||
max_velocity: 18
|
||||
}
|
||||
kinematic_options_pan {
|
||||
min_motion_to_reframe: 1.2
|
||||
max_velocity: 18
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,12 +87,15 @@ const char kConfigB[] = R"(
|
||||
max_zoom_value_deg: 0
|
||||
kinematic_options_zoom {
|
||||
min_motion_to_reframe: 1.2
|
||||
max_velocity: 18
|
||||
}
|
||||
kinematic_options_tilt {
|
||||
min_motion_to_reframe: 1.2
|
||||
max_velocity: 18
|
||||
}
|
||||
kinematic_options_pan {
|
||||
min_motion_to_reframe: 1.2
|
||||
max_velocity: 18
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,12 +111,15 @@ const char kConfigC[] = R"(
|
||||
max_zoom_value_deg: 0
|
||||
kinematic_options_zoom {
|
||||
min_motion_to_reframe: 1.2
|
||||
max_velocity: 18
|
||||
}
|
||||
kinematic_options_tilt {
|
||||
min_motion_to_reframe: 1.2
|
||||
max_velocity: 18
|
||||
}
|
||||
kinematic_options_pan {
|
||||
min_motion_to_reframe: 1.2
|
||||
max_velocity: 18
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,17 +131,21 @@ const char kConfigD[] = R"(
|
||||
input_stream: "DETECTIONS:detections"
|
||||
output_stream: "CROP_RECT:rect"
|
||||
output_stream: "FIRST_CROP_RECT:first_rect"
|
||||
output_stream: "NORMALIZED_CROP_RECT:float_rect"
|
||||
options: {
|
||||
[mediapipe.autoflip.ContentZoomingCalculatorOptions.ext]: {
|
||||
max_zoom_value_deg: 0
|
||||
kinematic_options_zoom {
|
||||
min_motion_to_reframe: 1.2
|
||||
max_velocity: 18
|
||||
}
|
||||
kinematic_options_tilt {
|
||||
min_motion_to_reframe: 1.2
|
||||
max_velocity: 18
|
||||
}
|
||||
kinematic_options_pan {
|
||||
min_motion_to_reframe: 1.2
|
||||
max_velocity: 18
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,12 +163,15 @@ const char kConfigE[] = R"(
|
||||
max_zoom_value_deg: 0
|
||||
kinematic_options_zoom {
|
||||
min_motion_to_reframe: 1.2
|
||||
max_velocity: 18
|
||||
}
|
||||
kinematic_options_tilt {
|
||||
min_motion_to_reframe: 1.2
|
||||
max_velocity: 18
|
||||
}
|
||||
kinematic_options_pan {
|
||||
min_motion_to_reframe: 1.2
|
||||
max_velocity: 18
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,12 +189,15 @@ const char kConfigF[] = R"(
|
||||
max_zoom_value_deg: 0
|
||||
kinematic_options_zoom {
|
||||
min_motion_to_reframe: 1.2
|
||||
max_velocity: 18
|
||||
}
|
||||
kinematic_options_tilt {
|
||||
min_motion_to_reframe: 1.2
|
||||
max_velocity: 18
|
||||
}
|
||||
kinematic_options_pan {
|
||||
min_motion_to_reframe: 1.2
|
||||
max_velocity: 18
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,17 +250,17 @@ void AddDetectionFrameSize(const cv::Rect_<float>& position, const int64 time,
|
||||
detections->push_back(detection);
|
||||
}
|
||||
runner->MutableInputs()
|
||||
->Tag("DETECTIONS")
|
||||
->Tag(kDetectionsTag)
|
||||
.packets.push_back(Adopt(detections.release()).At(Timestamp(time)));
|
||||
|
||||
auto input_size = ::absl::make_unique<std::pair<int, int>>(width, height);
|
||||
runner->MutableInputs()
|
||||
->Tag("VIDEO_SIZE")
|
||||
->Tag(kVideoSizeTag)
|
||||
.packets.push_back(Adopt(input_size.release()).At(Timestamp(time)));
|
||||
|
||||
if (flags.animated_zoom.has_value()) {
|
||||
runner->MutableInputs()
|
||||
->Tag("ANIMATE_ZOOM")
|
||||
->Tag(kAnimateZoomTag)
|
||||
.packets.push_back(
|
||||
mediapipe::MakePacket<bool>(flags.animated_zoom.value())
|
||||
.At(Timestamp(time)));
|
||||
@@ -238,7 +268,7 @@ void AddDetectionFrameSize(const cv::Rect_<float>& position, const int64 time,
|
||||
|
||||
if (flags.max_zoom_factor_percent.has_value()) {
|
||||
runner->MutableInputs()
|
||||
->Tag("MAX_ZOOM_FACTOR_PCT")
|
||||
->Tag(kMaxZoomFactorPctTag)
|
||||
.packets.push_back(
|
||||
mediapipe::MakePacket<int>(flags.max_zoom_factor_percent.value())
|
||||
.At(Timestamp(time)));
|
||||
@@ -250,6 +280,21 @@ void AddDetection(const cv::Rect_<float>& position, const int64 time,
|
||||
AddDetectionFrameSize(position, time, 1000, 1000, runner);
|
||||
}
|
||||
|
||||
void CheckCropRectFloats(const float x_center, const float y_center,
|
||||
const float width, const float height,
|
||||
const int frame_number,
|
||||
const CalculatorRunner::StreamContentsSet& output) {
|
||||
ASSERT_GT(output.Tag("NORMALIZED_CROP_RECT").packets.size(), frame_number);
|
||||
auto float_rect = output.Tag("NORMALIZED_CROP_RECT")
|
||||
.packets[frame_number]
|
||||
.Get<mediapipe::NormalizedRect>();
|
||||
|
||||
EXPECT_FLOAT_EQ(float_rect.x_center(), x_center);
|
||||
EXPECT_FLOAT_EQ(float_rect.y_center(), y_center);
|
||||
EXPECT_FLOAT_EQ(float_rect.width(), width);
|
||||
EXPECT_FLOAT_EQ(float_rect.height(), height);
|
||||
}
|
||||
|
||||
void CheckCropRect(const int x_center, const int y_center, const int width,
|
||||
const int height, const int frame_number,
|
||||
const std::vector<Packet>& output_packets) {
|
||||
@@ -274,21 +319,21 @@ TEST(ContentZoomingCalculatorTest, ZoomTest) {
|
||||
|
||||
auto input_frame =
|
||||
::absl::make_unique<ImageFrame>(ImageFormat::SRGB, 1000, 1000);
|
||||
runner->MutableInputs()->Tag("VIDEO").packets.push_back(
|
||||
runner->MutableInputs()->Tag(kVideoTag).packets.push_back(
|
||||
Adopt(input_frame.release()).At(Timestamp(0)));
|
||||
|
||||
runner->MutableInputs()
|
||||
->Tag("SALIENT_REGIONS")
|
||||
->Tag(kSalientRegionsTag)
|
||||
.packets.push_back(Adopt(detection_set.release()).At(Timestamp(0)));
|
||||
|
||||
// Run the calculator.
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("BORDERS").packets;
|
||||
runner->Outputs().Tag(kBordersTag).packets;
|
||||
ASSERT_EQ(1, output_packets.size());
|
||||
const auto& static_features = output_packets[0].Get<StaticFeatures>();
|
||||
CheckBorder(static_features, 1000, 1000, 495, 395);
|
||||
CheckBorder(static_features, 1000, 1000, 494, 394);
|
||||
}
|
||||
|
||||
TEST(ContentZoomingCalculatorTest, ZoomTestFullPTZ) {
|
||||
@@ -297,7 +342,7 @@ TEST(ContentZoomingCalculatorTest, ZoomTestFullPTZ) {
|
||||
AddDetection(cv::Rect_<float>(.4, .5, .1, .1), 0, runner.get());
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckCropRect(450, 550, 111, 111, 0,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
}
|
||||
|
||||
TEST(ContentZoomingCalculatorTest, PanConfig) {
|
||||
@@ -313,9 +358,9 @@ TEST(ContentZoomingCalculatorTest, PanConfig) {
|
||||
AddDetection(cv::Rect_<float>(.45, .55, .15, .15), 1000000, runner.get());
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckCropRect(450, 550, 111, 111, 0,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
CheckCropRect(483, 550, 111, 111, 1,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
}
|
||||
|
||||
TEST(ContentZoomingCalculatorTest, PanConfigWithCache) {
|
||||
@@ -330,31 +375,31 @@ TEST(ContentZoomingCalculatorTest, PanConfigWithCache) {
|
||||
options->mutable_kinematic_options_zoom()->set_min_motion_to_reframe(50.0);
|
||||
{
|
||||
auto runner = ::absl::make_unique<CalculatorRunner>(config);
|
||||
runner->MutableSidePackets()->Tag("STATE_CACHE") = MakePacket<
|
||||
runner->MutableSidePackets()->Tag(kStateCacheTag) = MakePacket<
|
||||
mediapipe::autoflip::ContentZoomingCalculatorStateCacheType*>(&cache);
|
||||
AddDetection(cv::Rect_<float>(.4, .5, .1, .1), 0, runner.get());
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckCropRect(450, 550, 111, 111, 0,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
}
|
||||
{
|
||||
auto runner = ::absl::make_unique<CalculatorRunner>(config);
|
||||
runner->MutableSidePackets()->Tag("STATE_CACHE") = MakePacket<
|
||||
runner->MutableSidePackets()->Tag(kStateCacheTag) = MakePacket<
|
||||
mediapipe::autoflip::ContentZoomingCalculatorStateCacheType*>(&cache);
|
||||
AddDetection(cv::Rect_<float>(.45, .55, .15, .15), 1000000, runner.get());
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckCropRect(483, 550, 111, 111, 0,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
}
|
||||
// Now repeat the last frame for a new runner without the cache to see a reset
|
||||
{
|
||||
auto runner = ::absl::make_unique<CalculatorRunner>(config);
|
||||
runner->MutableSidePackets()->Tag("STATE_CACHE") = MakePacket<
|
||||
runner->MutableSidePackets()->Tag(kStateCacheTag) = MakePacket<
|
||||
mediapipe::autoflip::ContentZoomingCalculatorStateCacheType*>(nullptr);
|
||||
AddDetection(cv::Rect_<float>(.45, .55, .15, .15), 2000000, runner.get());
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckCropRect(525, 625, 166, 166, 0, // Without a cache, state was lost.
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,9 +416,9 @@ TEST(ContentZoomingCalculatorTest, TiltConfig) {
|
||||
AddDetection(cv::Rect_<float>(.45, .55, .15, .15), 1000000, runner.get());
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckCropRect(450, 550, 111, 111, 0,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
CheckCropRect(450, 583, 111, 111, 1,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
}
|
||||
|
||||
TEST(ContentZoomingCalculatorTest, ZoomConfig) {
|
||||
@@ -389,9 +434,9 @@ TEST(ContentZoomingCalculatorTest, ZoomConfig) {
|
||||
AddDetection(cv::Rect_<float>(.45, .55, .15, .15), 1000000, runner.get());
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckCropRect(450, 550, 111, 111, 0,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
CheckCropRect(450, 550, 139, 139, 1,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
CheckCropRect(450, 550, 138, 138, 1,
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
}
|
||||
|
||||
TEST(ContentZoomingCalculatorTest, ZoomConfigWithCache) {
|
||||
@@ -406,31 +451,31 @@ TEST(ContentZoomingCalculatorTest, ZoomConfigWithCache) {
|
||||
options->mutable_kinematic_options_zoom()->set_update_rate_seconds(2);
|
||||
{
|
||||
auto runner = ::absl::make_unique<CalculatorRunner>(config);
|
||||
runner->MutableSidePackets()->Tag("STATE_CACHE") = MakePacket<
|
||||
runner->MutableSidePackets()->Tag(kStateCacheTag) = MakePacket<
|
||||
mediapipe::autoflip::ContentZoomingCalculatorStateCacheType*>(&cache);
|
||||
AddDetection(cv::Rect_<float>(.4, .5, .1, .1), 0, runner.get());
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckCropRect(450, 550, 111, 111, 0,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
}
|
||||
{
|
||||
auto runner = ::absl::make_unique<CalculatorRunner>(config);
|
||||
runner->MutableSidePackets()->Tag("STATE_CACHE") = MakePacket<
|
||||
runner->MutableSidePackets()->Tag(kStateCacheTag) = MakePacket<
|
||||
mediapipe::autoflip::ContentZoomingCalculatorStateCacheType*>(&cache);
|
||||
AddDetection(cv::Rect_<float>(.45, .55, .15, .15), 1000000, runner.get());
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckCropRect(450, 550, 139, 139, 0,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
CheckCropRect(450, 550, 138, 138, 0,
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
}
|
||||
// Now repeat the last frame for a new runner without the cache to see a reset
|
||||
{
|
||||
auto runner = ::absl::make_unique<CalculatorRunner>(config);
|
||||
runner->MutableSidePackets()->Tag("STATE_CACHE") = MakePacket<
|
||||
runner->MutableSidePackets()->Tag(kStateCacheTag) = MakePacket<
|
||||
mediapipe::autoflip::ContentZoomingCalculatorStateCacheType*>(nullptr);
|
||||
AddDetection(cv::Rect_<float>(.45, .55, .15, .15), 2000000, runner.get());
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckCropRect(525, 625, 166, 166, 0, // Without a cache, state was lost.
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,18 +493,18 @@ TEST(ContentZoomingCalculatorTest, MinAspectBorderValues) {
|
||||
|
||||
auto input_frame =
|
||||
::absl::make_unique<ImageFrame>(ImageFormat::SRGB, 1000, 1000);
|
||||
runner->MutableInputs()->Tag("VIDEO").packets.push_back(
|
||||
runner->MutableInputs()->Tag(kVideoTag).packets.push_back(
|
||||
Adopt(input_frame.release()).At(Timestamp(0)));
|
||||
|
||||
runner->MutableInputs()
|
||||
->Tag("SALIENT_REGIONS")
|
||||
->Tag(kSalientRegionsTag)
|
||||
.packets.push_back(Adopt(detection_set.release()).At(Timestamp(0)));
|
||||
|
||||
// Run the calculator.
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("BORDERS").packets;
|
||||
runner->Outputs().Tag(kBordersTag).packets;
|
||||
ASSERT_EQ(1, output_packets.size());
|
||||
const auto& static_features = output_packets[0].Get<StaticFeatures>();
|
||||
CheckBorder(static_features, 1000, 1000, 250, 250);
|
||||
@@ -485,18 +530,18 @@ TEST(ContentZoomingCalculatorTest, TwoFacesWide) {
|
||||
|
||||
auto input_frame =
|
||||
::absl::make_unique<ImageFrame>(ImageFormat::SRGB, 1000, 1000);
|
||||
runner->MutableInputs()->Tag("VIDEO").packets.push_back(
|
||||
runner->MutableInputs()->Tag(kVideoTag).packets.push_back(
|
||||
Adopt(input_frame.release()).At(Timestamp(0)));
|
||||
|
||||
runner->MutableInputs()
|
||||
->Tag("SALIENT_REGIONS")
|
||||
->Tag(kSalientRegionsTag)
|
||||
.packets.push_back(Adopt(detection_set.release()).At(Timestamp(0)));
|
||||
|
||||
// Run the calculator.
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("BORDERS").packets;
|
||||
runner->Outputs().Tag(kBordersTag).packets;
|
||||
ASSERT_EQ(1, output_packets.size());
|
||||
const auto& static_features = output_packets[0].Get<StaticFeatures>();
|
||||
|
||||
@@ -510,18 +555,18 @@ TEST(ContentZoomingCalculatorTest, NoDetectionOnInit) {
|
||||
|
||||
auto input_frame =
|
||||
::absl::make_unique<ImageFrame>(ImageFormat::SRGB, 1000, 1000);
|
||||
runner->MutableInputs()->Tag("VIDEO").packets.push_back(
|
||||
runner->MutableInputs()->Tag(kVideoTag).packets.push_back(
|
||||
Adopt(input_frame.release()).At(Timestamp(0)));
|
||||
|
||||
runner->MutableInputs()
|
||||
->Tag("SALIENT_REGIONS")
|
||||
->Tag(kSalientRegionsTag)
|
||||
.packets.push_back(Adopt(detection_set.release()).At(Timestamp(0)));
|
||||
|
||||
// Run the calculator.
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("BORDERS").packets;
|
||||
runner->Outputs().Tag(kBordersTag).packets;
|
||||
ASSERT_EQ(1, output_packets.size());
|
||||
const auto& static_features = output_packets[0].Get<StaticFeatures>();
|
||||
|
||||
@@ -542,21 +587,21 @@ TEST(ContentZoomingCalculatorTest, ZoomTestPairSize) {
|
||||
|
||||
auto input_size = ::absl::make_unique<std::pair<int, int>>(1000, 1000);
|
||||
runner->MutableInputs()
|
||||
->Tag("VIDEO_SIZE")
|
||||
->Tag(kVideoSizeTag)
|
||||
.packets.push_back(Adopt(input_size.release()).At(Timestamp(0)));
|
||||
|
||||
runner->MutableInputs()
|
||||
->Tag("SALIENT_REGIONS")
|
||||
->Tag(kSalientRegionsTag)
|
||||
.packets.push_back(Adopt(detection_set.release()).At(Timestamp(0)));
|
||||
|
||||
// Run the calculator.
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("BORDERS").packets;
|
||||
runner->Outputs().Tag(kBordersTag).packets;
|
||||
ASSERT_EQ(1, output_packets.size());
|
||||
const auto& static_features = output_packets[0].Get<StaticFeatures>();
|
||||
CheckBorder(static_features, 1000, 1000, 495, 395);
|
||||
CheckBorder(static_features, 1000, 1000, 494, 394);
|
||||
}
|
||||
|
||||
TEST(ContentZoomingCalculatorTest, ZoomTestNearOutsideBorder) {
|
||||
@@ -571,9 +616,9 @@ TEST(ContentZoomingCalculatorTest, ZoomTestNearOutsideBorder) {
|
||||
AddDetection(cv::Rect_<float>(.9, .9, .1, .1), 1000000, runner.get());
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckCropRect(972, 972, 55, 55, 0,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
CheckCropRect(958, 958, 83, 83, 1,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
CheckCropRect(944, 944, 83, 83, 1,
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
}
|
||||
|
||||
TEST(ContentZoomingCalculatorTest, ZoomTestNearInsideBorder) {
|
||||
@@ -587,8 +632,8 @@ TEST(ContentZoomingCalculatorTest, ZoomTestNearInsideBorder) {
|
||||
AddDetection(cv::Rect_<float>(0, 0, .05, .05), 0, runner.get());
|
||||
AddDetection(cv::Rect_<float>(0, 0, .1, .1), 1000000, runner.get());
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckCropRect(28, 28, 55, 55, 0, runner->Outputs().Tag("CROP_RECT").packets);
|
||||
CheckCropRect(42, 42, 83, 83, 1, runner->Outputs().Tag("CROP_RECT").packets);
|
||||
CheckCropRect(28, 28, 55, 55, 0, runner->Outputs().Tag(kCropRectTag).packets);
|
||||
CheckCropRect(56, 56, 83, 83, 1, runner->Outputs().Tag(kCropRectTag).packets);
|
||||
}
|
||||
|
||||
TEST(ContentZoomingCalculatorTest, VerticalShift) {
|
||||
@@ -601,7 +646,9 @@ TEST(ContentZoomingCalculatorTest, VerticalShift) {
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
// 1000px * .1 offset + 1000*.1*.1 shift = 170
|
||||
CheckCropRect(150, 170, 111, 111, 0,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
CheckCropRectFloats(150 / 1000.0, 170 / 1000.0, 111 / 1000.0, 111 / 1000.0, 0,
|
||||
runner->Outputs());
|
||||
}
|
||||
|
||||
TEST(ContentZoomingCalculatorTest, HorizontalShift) {
|
||||
@@ -614,7 +661,9 @@ TEST(ContentZoomingCalculatorTest, HorizontalShift) {
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
// 1000px * .1 offset + 1000*.1*.1 shift = 170
|
||||
CheckCropRect(170, 150, 111, 111, 0,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
CheckCropRectFloats(170 / 1000.0, 150 / 1000.0, 111 / 1000.0, 111 / 1000.0, 0,
|
||||
runner->Outputs());
|
||||
}
|
||||
|
||||
TEST(ContentZoomingCalculatorTest, ShiftOutsideBounds) {
|
||||
@@ -627,14 +676,14 @@ TEST(ContentZoomingCalculatorTest, ShiftOutsideBounds) {
|
||||
AddDetection(cv::Rect_<float>(.9, 0, .1, .1), 0, runner.get());
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckCropRect(944, 56, 111, 111, 0,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
}
|
||||
|
||||
TEST(ContentZoomingCalculatorTest, EmptySize) {
|
||||
auto config = ParseTextProtoOrDie<CalculatorGraphConfig::Node>(kConfigD);
|
||||
auto runner = ::absl::make_unique<CalculatorRunner>(config);
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
ASSERT_EQ(runner->Outputs().Tag("CROP_RECT").packets.size(), 0);
|
||||
ASSERT_EQ(runner->Outputs().Tag(kCropRectTag).packets.size(), 0);
|
||||
}
|
||||
|
||||
TEST(ContentZoomingCalculatorTest, EmptyDetections) {
|
||||
@@ -642,11 +691,11 @@ TEST(ContentZoomingCalculatorTest, EmptyDetections) {
|
||||
auto runner = ::absl::make_unique<CalculatorRunner>(config);
|
||||
auto input_size = ::absl::make_unique<std::pair<int, int>>(1000, 1000);
|
||||
runner->MutableInputs()
|
||||
->Tag("VIDEO_SIZE")
|
||||
->Tag(kVideoSizeTag)
|
||||
.packets.push_back(Adopt(input_size.release()).At(Timestamp(0)));
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckCropRect(500, 500, 1000, 1000, 0,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
}
|
||||
|
||||
TEST(ContentZoomingCalculatorTest, ResolutionChangeStationary) {
|
||||
@@ -658,9 +707,9 @@ TEST(ContentZoomingCalculatorTest, ResolutionChangeStationary) {
|
||||
runner.get());
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckCropRect(500, 500, 222, 222, 0,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
CheckCropRect(500 * 0.5, 500 * 0.5, 222 * 0.5, 222 * 0.5, 1,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
}
|
||||
|
||||
TEST(ContentZoomingCalculatorTest, ResolutionChangeStationaryWithCache) {
|
||||
@@ -669,23 +718,23 @@ TEST(ContentZoomingCalculatorTest, ResolutionChangeStationaryWithCache) {
|
||||
config.add_input_side_packet("STATE_CACHE:state_cache");
|
||||
{
|
||||
auto runner = ::absl::make_unique<CalculatorRunner>(config);
|
||||
runner->MutableSidePackets()->Tag("STATE_CACHE") = MakePacket<
|
||||
runner->MutableSidePackets()->Tag(kStateCacheTag) = MakePacket<
|
||||
mediapipe::autoflip::ContentZoomingCalculatorStateCacheType*>(&cache);
|
||||
AddDetectionFrameSize(cv::Rect_<float>(.4, .4, .2, .2), 0, 1000, 1000,
|
||||
runner.get());
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckCropRect(500, 500, 222, 222, 0,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
}
|
||||
{
|
||||
auto runner = ::absl::make_unique<CalculatorRunner>(config);
|
||||
runner->MutableSidePackets()->Tag("STATE_CACHE") = MakePacket<
|
||||
runner->MutableSidePackets()->Tag(kStateCacheTag) = MakePacket<
|
||||
mediapipe::autoflip::ContentZoomingCalculatorStateCacheType*>(&cache);
|
||||
AddDetectionFrameSize(cv::Rect_<float>(.4, .4, .2, .2), 1, 500, 500,
|
||||
runner.get());
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckCropRect(500 * 0.5, 500 * 0.5, 222 * 0.5, 222 * 0.5, 0,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -700,11 +749,11 @@ TEST(ContentZoomingCalculatorTest, ResolutionChangeZooming) {
|
||||
runner.get());
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckCropRect(500, 500, 888, 888, 0,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
CheckCropRect(500, 500, 588, 588, 1,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
CheckCropRect(500 * 0.5, 500 * 0.5, 288 * 0.5, 288 * 0.5, 2,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
}
|
||||
|
||||
TEST(ContentZoomingCalculatorTest, ResolutionChangeZoomingWithCache) {
|
||||
@@ -713,18 +762,18 @@ TEST(ContentZoomingCalculatorTest, ResolutionChangeZoomingWithCache) {
|
||||
config.add_input_side_packet("STATE_CACHE:state_cache");
|
||||
{
|
||||
auto runner = ::absl::make_unique<CalculatorRunner>(config);
|
||||
runner->MutableSidePackets()->Tag("STATE_CACHE") = MakePacket<
|
||||
runner->MutableSidePackets()->Tag(kStateCacheTag) = MakePacket<
|
||||
mediapipe::autoflip::ContentZoomingCalculatorStateCacheType*>(&cache);
|
||||
AddDetectionFrameSize(cv::Rect_<float>(.1, .1, .8, .8), 0, 1000, 1000,
|
||||
runner.get());
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckCropRect(500, 500, 888, 888, 0,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
}
|
||||
// The second runner should just resume based on state from the first runner.
|
||||
{
|
||||
auto runner = ::absl::make_unique<CalculatorRunner>(config);
|
||||
runner->MutableSidePackets()->Tag("STATE_CACHE") = MakePacket<
|
||||
runner->MutableSidePackets()->Tag(kStateCacheTag) = MakePacket<
|
||||
mediapipe::autoflip::ContentZoomingCalculatorStateCacheType*>(&cache);
|
||||
AddDetectionFrameSize(cv::Rect_<float>(.4, .4, .2, .2), 1000000, 1000, 1000,
|
||||
runner.get());
|
||||
@@ -732,9 +781,9 @@ TEST(ContentZoomingCalculatorTest, ResolutionChangeZoomingWithCache) {
|
||||
runner.get());
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckCropRect(500, 500, 588, 588, 0,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
CheckCropRect(500 * 0.5, 500 * 0.5, 288 * 0.5, 288 * 0.5, 1,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -749,7 +798,7 @@ TEST(ContentZoomingCalculatorTest, MaxZoomValue) {
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
// 55/60 * 1000 = 916
|
||||
CheckCropRect(500, 500, 916, 916, 0,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
}
|
||||
|
||||
TEST(ContentZoomingCalculatorTest, MaxZoomValueOverride) {
|
||||
@@ -772,11 +821,11 @@ TEST(ContentZoomingCalculatorTest, MaxZoomValueOverride) {
|
||||
// Max. 133% zoomed in means min. (100/133) ~ 75% of height left: ~360
|
||||
// Max. 166% zoomed in means min. (100/166) ~ 60% of height left: ~430
|
||||
CheckCropRect(320, 240, 480, 360, 0,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
CheckCropRect(640, 360, 769, 433, 2,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
CheckCropRect(320, 240, 480, 360, 3,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
}
|
||||
|
||||
TEST(ContentZoomingCalculatorTest, MaxZoomOutValue) {
|
||||
@@ -795,9 +844,9 @@ TEST(ContentZoomingCalculatorTest, MaxZoomOutValue) {
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
// 55/60 * 1000 = 916
|
||||
CheckCropRect(500, 500, 950, 950, 0,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
CheckCropRect(500, 500, 1000, 1000, 2,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
}
|
||||
|
||||
TEST(ContentZoomingCalculatorTest, StartZoomedOut) {
|
||||
@@ -816,13 +865,13 @@ TEST(ContentZoomingCalculatorTest, StartZoomedOut) {
|
||||
runner.get());
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckCropRect(500, 500, 1000, 1000, 0,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
CheckCropRect(500, 500, 880, 880, 1,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
CheckCropRect(500, 500, 760, 760, 2,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
CheckCropRect(500, 500, 655, 655, 3,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
}
|
||||
|
||||
TEST(ContentZoomingCalculatorTest, AnimateToFirstRect) {
|
||||
@@ -844,15 +893,15 @@ TEST(ContentZoomingCalculatorTest, AnimateToFirstRect) {
|
||||
runner.get());
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckCropRect(500, 500, 1000, 1000, 0,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
CheckCropRect(500, 500, 1000, 1000, 1,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
CheckCropRect(500, 500, 470, 470, 2,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
CheckCropRect(500, 500, 222, 222, 3,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
CheckCropRect(500, 500, 222, 222, 4,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
}
|
||||
|
||||
TEST(ContentZoomingCalculatorTest, CanControlAnimation) {
|
||||
@@ -879,15 +928,15 @@ TEST(ContentZoomingCalculatorTest, CanControlAnimation) {
|
||||
runner.get(), {.animated_zoom = false});
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckCropRect(500, 500, 1000, 1000, 0,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
CheckCropRect(500, 500, 1000, 1000, 1,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
CheckCropRect(500, 500, 470, 470, 2,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
CheckCropRect(500, 500, 222, 222, 3,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
CheckCropRect(500, 500, 222, 222, 4,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
}
|
||||
|
||||
TEST(ContentZoomingCalculatorTest, DoesNotAnimateIfDisabledViaInput) {
|
||||
@@ -907,11 +956,11 @@ TEST(ContentZoomingCalculatorTest, DoesNotAnimateIfDisabledViaInput) {
|
||||
runner.get(), {.animated_zoom = false});
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckCropRect(500, 500, 1000, 1000, 0,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
CheckCropRect(500, 500, 880, 880, 1,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
CheckCropRect(500, 500, 760, 760, 2,
|
||||
runner->Outputs().Tag("CROP_RECT").packets);
|
||||
runner->Outputs().Tag(kCropRectTag).packets);
|
||||
}
|
||||
|
||||
TEST(ContentZoomingCalculatorTest, ProvidesZeroSizeFirstRectWithoutDetections) {
|
||||
@@ -920,13 +969,13 @@ TEST(ContentZoomingCalculatorTest, ProvidesZeroSizeFirstRectWithoutDetections) {
|
||||
|
||||
auto input_size = ::absl::make_unique<std::pair<int, int>>(1000, 1000);
|
||||
runner->MutableInputs()
|
||||
->Tag("VIDEO_SIZE")
|
||||
->Tag(kVideoSizeTag)
|
||||
.packets.push_back(Adopt(input_size.release()).At(Timestamp(0)));
|
||||
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("FIRST_CROP_RECT").packets;
|
||||
runner->Outputs().Tag(kFirstCropRectTag).packets;
|
||||
ASSERT_EQ(output_packets.size(), 1);
|
||||
const auto& rect = output_packets[0].Get<mediapipe::NormalizedRect>();
|
||||
EXPECT_EQ(rect.x_center(), 0);
|
||||
@@ -951,7 +1000,7 @@ TEST(ContentZoomingCalculatorTest, ProvidesConstantFirstRect) {
|
||||
runner.get());
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("FIRST_CROP_RECT").packets;
|
||||
runner->Outputs().Tag(kFirstCropRectTag).packets;
|
||||
ASSERT_EQ(output_packets.size(), 4);
|
||||
const auto& first_rect = output_packets[0].Get<mediapipe::NormalizedRect>();
|
||||
EXPECT_NEAR(first_rect.x_center(), 0.5, 0.05);
|
||||
|
||||
@@ -64,7 +64,7 @@ message FaceBoxAdjusterCalculatorOptions {
|
||||
|
||||
// Max value of head motion (max of current or history) to be considered still
|
||||
// stable.
|
||||
optional float head_motion_threshold = 14 [default = 10.0];
|
||||
optional float head_motion_threshold = 14 [default = 360.0];
|
||||
|
||||
// The max amount of time to use an old eye distance when the face look angle
|
||||
// is unstable.
|
||||
|
||||
@@ -32,6 +32,10 @@
|
||||
namespace mediapipe {
|
||||
namespace autoflip {
|
||||
|
||||
constexpr char kRegionsTag[] = "REGIONS";
|
||||
constexpr char kFacesTag[] = "FACES";
|
||||
constexpr char kVideoTag[] = "VIDEO";
|
||||
|
||||
// This calculator converts detected faces to SalientRegion protos that can be
|
||||
// used for downstream processing. Each SalientRegion is scored using image
|
||||
// cues. Scoring can be controlled through
|
||||
@@ -80,17 +84,17 @@ FaceToRegionCalculator::FaceToRegionCalculator() {}
|
||||
|
||||
absl::Status FaceToRegionCalculator::GetContract(
|
||||
mediapipe::CalculatorContract* cc) {
|
||||
if (cc->Inputs().HasTag("VIDEO")) {
|
||||
cc->Inputs().Tag("VIDEO").Set<ImageFrame>();
|
||||
if (cc->Inputs().HasTag(kVideoTag)) {
|
||||
cc->Inputs().Tag(kVideoTag).Set<ImageFrame>();
|
||||
}
|
||||
cc->Inputs().Tag("FACES").Set<std::vector<mediapipe::Detection>>();
|
||||
cc->Outputs().Tag("REGIONS").Set<DetectionSet>();
|
||||
cc->Inputs().Tag(kFacesTag).Set<std::vector<mediapipe::Detection>>();
|
||||
cc->Outputs().Tag(kRegionsTag).Set<DetectionSet>();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status FaceToRegionCalculator::Open(mediapipe::CalculatorContext* cc) {
|
||||
options_ = cc->Options<FaceToRegionCalculatorOptions>();
|
||||
if (!cc->Inputs().HasTag("VIDEO")) {
|
||||
if (!cc->Inputs().HasTag(kVideoTag)) {
|
||||
RET_CHECK(!options_.use_visual_scorer())
|
||||
<< "VIDEO input must be provided when using visual_scorer.";
|
||||
RET_CHECK(!options_.export_individual_face_landmarks())
|
||||
@@ -146,24 +150,24 @@ void FaceToRegionCalculator::ExtendSalientRegionWithPoint(
|
||||
}
|
||||
|
||||
absl::Status FaceToRegionCalculator::Process(mediapipe::CalculatorContext* cc) {
|
||||
if (cc->Inputs().HasTag("VIDEO") &&
|
||||
cc->Inputs().Tag("VIDEO").Value().IsEmpty()) {
|
||||
if (cc->Inputs().HasTag(kVideoTag) &&
|
||||
cc->Inputs().Tag(kVideoTag).Value().IsEmpty()) {
|
||||
return mediapipe::UnknownErrorBuilder(MEDIAPIPE_LOC)
|
||||
<< "No VIDEO input at time " << cc->InputTimestamp().Seconds();
|
||||
}
|
||||
|
||||
cv::Mat frame;
|
||||
if (cc->Inputs().HasTag("VIDEO")) {
|
||||
if (cc->Inputs().HasTag(kVideoTag)) {
|
||||
frame = mediapipe::formats::MatView(
|
||||
&cc->Inputs().Tag("VIDEO").Get<ImageFrame>());
|
||||
&cc->Inputs().Tag(kVideoTag).Get<ImageFrame>());
|
||||
frame_width_ = frame.cols;
|
||||
frame_height_ = frame.rows;
|
||||
}
|
||||
|
||||
auto region_set = ::absl::make_unique<DetectionSet>();
|
||||
if (!cc->Inputs().Tag("FACES").Value().IsEmpty()) {
|
||||
if (!cc->Inputs().Tag(kFacesTag).Value().IsEmpty()) {
|
||||
const auto& input_faces =
|
||||
cc->Inputs().Tag("FACES").Get<std::vector<mediapipe::Detection>>();
|
||||
cc->Inputs().Tag(kFacesTag).Get<std::vector<mediapipe::Detection>>();
|
||||
|
||||
for (const auto& input_face : input_faces) {
|
||||
RET_CHECK(input_face.location_data().format() ==
|
||||
@@ -276,7 +280,9 @@ absl::Status FaceToRegionCalculator::Process(mediapipe::CalculatorContext* cc) {
|
||||
}
|
||||
}
|
||||
}
|
||||
cc->Outputs().Tag("REGIONS").Add(region_set.release(), cc->InputTimestamp());
|
||||
cc->Outputs()
|
||||
.Tag(kRegionsTag)
|
||||
.Add(region_set.release(), cc->InputTimestamp());
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -33,6 +33,10 @@ namespace mediapipe {
|
||||
namespace autoflip {
|
||||
namespace {
|
||||
|
||||
constexpr char kRegionsTag[] = "REGIONS";
|
||||
constexpr char kFacesTag[] = "FACES";
|
||||
constexpr char kVideoTag[] = "VIDEO";
|
||||
|
||||
const char kConfig[] = R"(
|
||||
calculator: "FaceToRegionCalculator"
|
||||
input_stream: "VIDEO:frames"
|
||||
@@ -100,7 +104,7 @@ void SetInputs(const std::vector<std::string>& faces, const bool include_video,
|
||||
if (include_video) {
|
||||
auto input_frame =
|
||||
::absl::make_unique<ImageFrame>(ImageFormat::SRGB, 800, 600);
|
||||
runner->MutableInputs()->Tag("VIDEO").packets.push_back(
|
||||
runner->MutableInputs()->Tag(kVideoTag).packets.push_back(
|
||||
Adopt(input_frame.release()).At(Timestamp::PostStream()));
|
||||
}
|
||||
// Setup two faces as input.
|
||||
@@ -109,7 +113,7 @@ void SetInputs(const std::vector<std::string>& faces, const bool include_video,
|
||||
for (const auto& face : faces) {
|
||||
input_faces->push_back(ParseTextProtoOrDie<Detection>(face));
|
||||
}
|
||||
runner->MutableInputs()->Tag("FACES").packets.push_back(
|
||||
runner->MutableInputs()->Tag(kFacesTag).packets.push_back(
|
||||
Adopt(input_faces.release()).At(Timestamp::PostStream()));
|
||||
}
|
||||
|
||||
@@ -144,7 +148,7 @@ TEST(FaceToRegionCalculatorTest, FaceFullTypeSize) {
|
||||
|
||||
// Check the output regions.
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("REGIONS").packets;
|
||||
runner->Outputs().Tag(kRegionsTag).packets;
|
||||
ASSERT_EQ(1, output_packets.size());
|
||||
|
||||
const auto& regions = output_packets[0].Get<DetectionSet>();
|
||||
@@ -177,7 +181,7 @@ TEST(FaceToRegionCalculatorTest, FaceLandmarksTypeSize) {
|
||||
|
||||
// Check the output regions.
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("REGIONS").packets;
|
||||
runner->Outputs().Tag(kRegionsTag).packets;
|
||||
ASSERT_EQ(1, output_packets.size());
|
||||
|
||||
const auto& regions = output_packets[0].Get<DetectionSet>();
|
||||
@@ -208,7 +212,7 @@ TEST(FaceToRegionCalculatorTest, FaceLandmarksBox) {
|
||||
|
||||
// Check the output regions.
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("REGIONS").packets;
|
||||
runner->Outputs().Tag(kRegionsTag).packets;
|
||||
ASSERT_EQ(1, output_packets.size());
|
||||
|
||||
const auto& regions = output_packets[0].Get<DetectionSet>();
|
||||
@@ -243,7 +247,7 @@ TEST(FaceToRegionCalculatorTest, FaceScore) {
|
||||
|
||||
// Check the output regions.
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("REGIONS").packets;
|
||||
runner->Outputs().Tag(kRegionsTag).packets;
|
||||
ASSERT_EQ(1, output_packets.size());
|
||||
const auto& regions = output_packets[0].Get<DetectionSet>();
|
||||
ASSERT_EQ(1, regions.detections().size());
|
||||
@@ -292,7 +296,7 @@ TEST(FaceToRegionCalculatorTest, FaceNoVideoPass) {
|
||||
|
||||
// Check the output regions.
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("REGIONS").packets;
|
||||
runner->Outputs().Tag(kRegionsTag).packets;
|
||||
ASSERT_EQ(1, output_packets.size());
|
||||
|
||||
const auto& regions = output_packets[0].Get<DetectionSet>();
|
||||
|
||||
+7
-4
@@ -52,6 +52,9 @@ LocalizationToRegionCalculator::LocalizationToRegionCalculator() {}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char kRegionsTag[] = "REGIONS";
|
||||
constexpr char kDetectionsTag[] = "DETECTIONS";
|
||||
|
||||
// Converts an object detection to a autoflip SignalType. Returns true if the
|
||||
// std::string label has a autoflip label.
|
||||
bool MatchType(const std::string& label, SignalType* type) {
|
||||
@@ -86,8 +89,8 @@ void FillSalientRegion(const mediapipe::Detection& detection,
|
||||
|
||||
absl::Status LocalizationToRegionCalculator::GetContract(
|
||||
mediapipe::CalculatorContract* cc) {
|
||||
cc->Inputs().Tag("DETECTIONS").Set<std::vector<mediapipe::Detection>>();
|
||||
cc->Outputs().Tag("REGIONS").Set<DetectionSet>();
|
||||
cc->Inputs().Tag(kDetectionsTag).Set<std::vector<mediapipe::Detection>>();
|
||||
cc->Outputs().Tag(kRegionsTag).Set<DetectionSet>();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -101,7 +104,7 @@ absl::Status LocalizationToRegionCalculator::Open(
|
||||
absl::Status LocalizationToRegionCalculator::Process(
|
||||
mediapipe::CalculatorContext* cc) {
|
||||
const auto& annotations =
|
||||
cc->Inputs().Tag("DETECTIONS").Get<std::vector<mediapipe::Detection>>();
|
||||
cc->Inputs().Tag(kDetectionsTag).Get<std::vector<mediapipe::Detection>>();
|
||||
auto regions = ::absl::make_unique<DetectionSet>();
|
||||
for (const auto& detection : annotations) {
|
||||
RET_CHECK_EQ(detection.label().size(), 1)
|
||||
@@ -118,7 +121,7 @@ absl::Status LocalizationToRegionCalculator::Process(
|
||||
}
|
||||
}
|
||||
|
||||
cc->Outputs().Tag("REGIONS").Add(regions.release(), cc->InputTimestamp());
|
||||
cc->Outputs().Tag(kRegionsTag).Add(regions.release(), cc->InputTimestamp());
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
|
||||
+7
-4
@@ -31,6 +31,9 @@ namespace mediapipe {
|
||||
namespace autoflip {
|
||||
namespace {
|
||||
|
||||
constexpr char kRegionsTag[] = "REGIONS";
|
||||
constexpr char kDetectionsTag[] = "DETECTIONS";
|
||||
|
||||
const char kConfig[] = R"(
|
||||
calculator: "LocalizationToRegionCalculator"
|
||||
input_stream: "DETECTIONS:detections"
|
||||
@@ -81,7 +84,7 @@ void SetInputs(CalculatorRunner* runner,
|
||||
inputs->push_back(ParseTextProtoOrDie<Detection>(detection));
|
||||
}
|
||||
runner->MutableInputs()
|
||||
->Tag("DETECTIONS")
|
||||
->Tag(kDetectionsTag)
|
||||
.packets.push_back(Adopt(inputs.release()).At(Timestamp::PostStream()));
|
||||
}
|
||||
|
||||
@@ -109,7 +112,7 @@ TEST(LocalizationToRegionCalculatorTest, StandardTypes) {
|
||||
|
||||
// Check the output regions.
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("REGIONS").packets;
|
||||
runner->Outputs().Tag(kRegionsTag).packets;
|
||||
ASSERT_EQ(1, output_packets.size());
|
||||
const auto& regions = output_packets[0].Get<DetectionSet>();
|
||||
ASSERT_EQ(2, regions.detections().size());
|
||||
@@ -137,7 +140,7 @@ TEST(LocalizationToRegionCalculatorTest, AllTypes) {
|
||||
|
||||
// Check the output regions.
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("REGIONS").packets;
|
||||
runner->Outputs().Tag(kRegionsTag).packets;
|
||||
ASSERT_EQ(1, output_packets.size());
|
||||
const auto& regions = output_packets[0].Get<DetectionSet>();
|
||||
ASSERT_EQ(3, regions.detections().size());
|
||||
@@ -153,7 +156,7 @@ TEST(LocalizationToRegionCalculatorTest, BothTypes) {
|
||||
|
||||
// Check the output regions.
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("REGIONS").packets;
|
||||
runner->Outputs().Tag(kRegionsTag).packets;
|
||||
ASSERT_EQ(1, output_packets.size());
|
||||
const auto& regions = output_packets[0].Get<DetectionSet>();
|
||||
ASSERT_EQ(5, regions.detections().size());
|
||||
|
||||
@@ -34,6 +34,23 @@ namespace mediapipe {
|
||||
namespace autoflip {
|
||||
namespace {
|
||||
|
||||
constexpr char kFramingDetectionsVizFramesTag[] =
|
||||
"FRAMING_DETECTIONS_VIZ_FRAMES";
|
||||
constexpr char kExternalRenderingFullVidTag[] = "EXTERNAL_RENDERING_FULL_VID";
|
||||
constexpr char kExternalRenderingPerFrameTag[] = "EXTERNAL_RENDERING_PER_FRAME";
|
||||
constexpr char kCroppingSummaryTag[] = "CROPPING_SUMMARY";
|
||||
constexpr char kSalientPointFrameVizFramesTag[] =
|
||||
"SALIENT_POINT_FRAME_VIZ_FRAMES";
|
||||
constexpr char kKeyFrameCropRegionVizFramesTag[] =
|
||||
"KEY_FRAME_CROP_REGION_VIZ_FRAMES";
|
||||
constexpr char kCroppedFramesTag[] = "CROPPED_FRAMES";
|
||||
constexpr char kShotBoundariesTag[] = "SHOT_BOUNDARIES";
|
||||
constexpr char kStaticFeaturesTag[] = "STATIC_FEATURES";
|
||||
constexpr char kVideoSizeTag[] = "VIDEO_SIZE";
|
||||
constexpr char kVideoFramesTag[] = "VIDEO_FRAMES";
|
||||
constexpr char kDetectionFeaturesTag[] = "DETECTION_FEATURES";
|
||||
constexpr char kKeyFramesTag[] = "KEY_FRAMES";
|
||||
|
||||
using ::testing::HasSubstr;
|
||||
|
||||
constexpr char kConfig[] = R"(
|
||||
@@ -241,10 +258,10 @@ void AddKeyFrameFeatures(const int64 time_ms, const int key_frame_width,
|
||||
const int key_frame_height, bool randomize,
|
||||
CalculatorRunner::StreamContentsSet* inputs) {
|
||||
Timestamp timestamp(time_ms);
|
||||
if (inputs->HasTag("KEY_FRAMES")) {
|
||||
if (inputs->HasTag(kKeyFramesTag)) {
|
||||
auto key_frame = MakeImageFrameFromColor(GetRandomColor(), key_frame_width,
|
||||
key_frame_height);
|
||||
inputs->Tag("KEY_FRAMES")
|
||||
inputs->Tag(kKeyFramesTag)
|
||||
.packets.push_back(Adopt(key_frame.release()).At(timestamp));
|
||||
}
|
||||
if (randomize) {
|
||||
@@ -252,11 +269,11 @@ void AddKeyFrameFeatures(const int64 time_ms, const int key_frame_width,
|
||||
kMinNumDetections, kMaxNumDetections)(GetGen());
|
||||
auto detections =
|
||||
MakeDetections(num_detections, key_frame_width, key_frame_height);
|
||||
inputs->Tag("DETECTION_FEATURES")
|
||||
inputs->Tag(kDetectionFeaturesTag)
|
||||
.packets.push_back(Adopt(detections.release()).At(timestamp));
|
||||
} else {
|
||||
auto detections = MakeCenterDetection(key_frame_width, key_frame_height);
|
||||
inputs->Tag("DETECTION_FEATURES")
|
||||
inputs->Tag(kDetectionFeaturesTag)
|
||||
.packets.push_back(Adopt(detections.release()).At(timestamp));
|
||||
}
|
||||
}
|
||||
@@ -272,19 +289,19 @@ void AddScene(const int start_frame_index, const int num_scene_frames,
|
||||
int64 time_ms = start_frame_index * kTimestampDiff;
|
||||
for (int i = 0; i < num_scene_frames; ++i) {
|
||||
Timestamp timestamp(time_ms);
|
||||
if (inputs->HasTag("VIDEO_FRAMES")) {
|
||||
if (inputs->HasTag(kVideoFramesTag)) {
|
||||
auto frame =
|
||||
MakeImageFrameFromColor(GetRandomColor(), frame_width, frame_height);
|
||||
inputs->Tag("VIDEO_FRAMES")
|
||||
inputs->Tag(kVideoFramesTag)
|
||||
.packets.push_back(Adopt(frame.release()).At(timestamp));
|
||||
} else {
|
||||
auto input_size =
|
||||
::absl::make_unique<std::pair<int, int>>(frame_width, frame_height);
|
||||
inputs->Tag("VIDEO_SIZE")
|
||||
inputs->Tag(kVideoSizeTag)
|
||||
.packets.push_back(Adopt(input_size.release()).At(timestamp));
|
||||
}
|
||||
auto static_features = absl::make_unique<StaticFeatures>();
|
||||
inputs->Tag("STATIC_FEATURES")
|
||||
inputs->Tag(kStaticFeaturesTag)
|
||||
.packets.push_back(Adopt(static_features.release()).At(timestamp));
|
||||
if (DownSampleRate == 1) {
|
||||
AddKeyFrameFeatures(time_ms, key_frame_width, key_frame_height, false,
|
||||
@@ -294,7 +311,7 @@ void AddScene(const int start_frame_index, const int num_scene_frames,
|
||||
inputs);
|
||||
}
|
||||
if (i == num_scene_frames - 1) { // adds shot boundary
|
||||
inputs->Tag("SHOT_BOUNDARIES")
|
||||
inputs->Tag(kShotBoundariesTag)
|
||||
.packets.push_back(Adopt(new bool(true)).At(Timestamp(time_ms)));
|
||||
}
|
||||
time_ms += kTimestampDiff;
|
||||
@@ -306,8 +323,8 @@ void AddScene(const int start_frame_index, const int num_scene_frames,
|
||||
void CheckCroppedFrames(const CalculatorRunner& runner, const int num_frames,
|
||||
const int target_width, const int target_height) {
|
||||
const auto& outputs = runner.Outputs();
|
||||
EXPECT_TRUE(outputs.HasTag("CROPPED_FRAMES"));
|
||||
const auto& cropped_frames_outputs = outputs.Tag("CROPPED_FRAMES").packets;
|
||||
EXPECT_TRUE(outputs.HasTag(kCroppedFramesTag));
|
||||
const auto& cropped_frames_outputs = outputs.Tag(kCroppedFramesTag).packets;
|
||||
EXPECT_EQ(cropped_frames_outputs.size(), num_frames);
|
||||
for (int i = 0; i < num_frames; ++i) {
|
||||
const auto& cropped_frame = cropped_frames_outputs[i].Get<ImageFrame>();
|
||||
@@ -392,23 +409,23 @@ TEST(SceneCroppingCalculatorTest, OutputsDebugStreams) {
|
||||
|
||||
MP_EXPECT_OK(runner->Run());
|
||||
const auto& outputs = runner->Outputs();
|
||||
EXPECT_TRUE(outputs.HasTag("KEY_FRAME_CROP_REGION_VIZ_FRAMES"));
|
||||
EXPECT_TRUE(outputs.HasTag("SALIENT_POINT_FRAME_VIZ_FRAMES"));
|
||||
EXPECT_TRUE(outputs.HasTag("CROPPING_SUMMARY"));
|
||||
EXPECT_TRUE(outputs.HasTag("EXTERNAL_RENDERING_PER_FRAME"));
|
||||
EXPECT_TRUE(outputs.HasTag("EXTERNAL_RENDERING_FULL_VID"));
|
||||
EXPECT_TRUE(outputs.HasTag("FRAMING_DETECTIONS_VIZ_FRAMES"));
|
||||
EXPECT_TRUE(outputs.HasTag(kKeyFrameCropRegionVizFramesTag));
|
||||
EXPECT_TRUE(outputs.HasTag(kSalientPointFrameVizFramesTag));
|
||||
EXPECT_TRUE(outputs.HasTag(kCroppingSummaryTag));
|
||||
EXPECT_TRUE(outputs.HasTag(kExternalRenderingPerFrameTag));
|
||||
EXPECT_TRUE(outputs.HasTag(kExternalRenderingFullVidTag));
|
||||
EXPECT_TRUE(outputs.HasTag(kFramingDetectionsVizFramesTag));
|
||||
const auto& crop_region_viz_frames_outputs =
|
||||
outputs.Tag("KEY_FRAME_CROP_REGION_VIZ_FRAMES").packets;
|
||||
outputs.Tag(kKeyFrameCropRegionVizFramesTag).packets;
|
||||
const auto& salient_point_viz_frames_outputs =
|
||||
outputs.Tag("SALIENT_POINT_FRAME_VIZ_FRAMES").packets;
|
||||
const auto& summary_output = outputs.Tag("CROPPING_SUMMARY").packets;
|
||||
outputs.Tag(kSalientPointFrameVizFramesTag).packets;
|
||||
const auto& summary_output = outputs.Tag(kCroppingSummaryTag).packets;
|
||||
const auto& ext_render_per_frame =
|
||||
outputs.Tag("EXTERNAL_RENDERING_PER_FRAME").packets;
|
||||
outputs.Tag(kExternalRenderingPerFrameTag).packets;
|
||||
const auto& ext_render_full_vid =
|
||||
outputs.Tag("EXTERNAL_RENDERING_FULL_VID").packets;
|
||||
outputs.Tag(kExternalRenderingFullVidTag).packets;
|
||||
const auto& framing_viz_frames_output =
|
||||
outputs.Tag("FRAMING_DETECTIONS_VIZ_FRAMES").packets;
|
||||
outputs.Tag(kFramingDetectionsVizFramesTag).packets;
|
||||
EXPECT_EQ(crop_region_viz_frames_outputs.size(), num_frames);
|
||||
EXPECT_EQ(salient_point_viz_frames_outputs.size(), num_frames);
|
||||
EXPECT_EQ(framing_viz_frames_output.size(), num_frames);
|
||||
@@ -597,7 +614,7 @@ TEST(SceneCroppingCalculatorTest, ProducesEvenFrameSize) {
|
||||
kKeyFrameHeight, kDownSampleRate, runner->MutableInputs());
|
||||
MP_EXPECT_OK(runner->Run());
|
||||
const auto& output_frame = runner->Outputs()
|
||||
.Tag("CROPPED_FRAMES")
|
||||
.Tag(kCroppedFramesTag)
|
||||
.packets[0]
|
||||
.Get<ImageFrame>();
|
||||
EXPECT_EQ(output_frame.Width() % 2, 0);
|
||||
@@ -646,7 +663,7 @@ TEST(SceneCroppingCalculatorTest, PadsWithSolidColorFromStaticFeatures) {
|
||||
Timestamp timestamp(time_ms);
|
||||
auto frame =
|
||||
MakeImageFrameFromColor(GetRandomColor(), input_width, input_height);
|
||||
inputs->Tag("VIDEO_FRAMES")
|
||||
inputs->Tag(kVideoFramesTag)
|
||||
.packets.push_back(Adopt(frame.release()).At(timestamp));
|
||||
if (i % static_features_downsample_rate == 0) {
|
||||
auto static_features = absl::make_unique<StaticFeatures>();
|
||||
@@ -657,7 +674,7 @@ TEST(SceneCroppingCalculatorTest, PadsWithSolidColorFromStaticFeatures) {
|
||||
color->set_g(green);
|
||||
color->set_b(red);
|
||||
}
|
||||
inputs->Tag("STATIC_FEATURES")
|
||||
inputs->Tag(kStaticFeaturesTag)
|
||||
.packets.push_back(Adopt(static_features.release()).At(timestamp));
|
||||
num_static_features++;
|
||||
}
|
||||
@@ -672,7 +689,7 @@ TEST(SceneCroppingCalculatorTest, PadsWithSolidColorFromStaticFeatures) {
|
||||
location->set_y(0);
|
||||
location->set_width(80);
|
||||
location->set_height(input_height);
|
||||
inputs->Tag("DETECTION_FEATURES")
|
||||
inputs->Tag(kDetectionFeaturesTag)
|
||||
.packets.push_back(Adopt(detections.release()).At(timestamp));
|
||||
}
|
||||
time_ms += kTimestampDiff;
|
||||
@@ -683,7 +700,7 @@ TEST(SceneCroppingCalculatorTest, PadsWithSolidColorFromStaticFeatures) {
|
||||
// Checks that the top and bottom borders indeed have the background color.
|
||||
const int border_size = 37;
|
||||
const auto& cropped_frames_outputs =
|
||||
runner->Outputs().Tag("CROPPED_FRAMES").packets;
|
||||
runner->Outputs().Tag(kCroppedFramesTag).packets;
|
||||
EXPECT_EQ(cropped_frames_outputs.size(), kSceneSize);
|
||||
for (int i = 0; i < kSceneSize; ++i) {
|
||||
const auto& cropped_frame = cropped_frames_outputs[i].Get<ImageFrame>();
|
||||
@@ -727,7 +744,7 @@ TEST(SceneCroppingCalculatorTest, RemovesStaticBorders) {
|
||||
auto mat = formats::MatView(frame.get());
|
||||
mat(top_border_rect) = border_color;
|
||||
mat(bottom_border_rect) = border_color;
|
||||
inputs->Tag("VIDEO_FRAMES")
|
||||
inputs->Tag(kVideoFramesTag)
|
||||
.packets.push_back(Adopt(frame.release()).At(timestamp));
|
||||
// Set borders in static features.
|
||||
auto static_features = absl::make_unique<StaticFeatures>();
|
||||
@@ -737,11 +754,11 @@ TEST(SceneCroppingCalculatorTest, RemovesStaticBorders) {
|
||||
auto* bottom_part = static_features->add_border();
|
||||
bottom_part->set_relative_position(Border::BOTTOM);
|
||||
bottom_part->mutable_border_position()->set_height(bottom_border_size);
|
||||
inputs->Tag("STATIC_FEATURES")
|
||||
inputs->Tag(kStaticFeaturesTag)
|
||||
.packets.push_back(Adopt(static_features.release()).At(timestamp));
|
||||
// Add empty detections to ensure no padding is used.
|
||||
auto detections = absl::make_unique<DetectionSet>();
|
||||
inputs->Tag("DETECTION_FEATURES")
|
||||
inputs->Tag(kDetectionFeaturesTag)
|
||||
.packets.push_back(Adopt(detections.release()).At(timestamp));
|
||||
|
||||
MP_EXPECT_OK(runner->Run());
|
||||
@@ -749,7 +766,7 @@ TEST(SceneCroppingCalculatorTest, RemovesStaticBorders) {
|
||||
// Checks that the top and bottom borders are removed. Each frame should have
|
||||
// solid color equal to frame color.
|
||||
const auto& cropped_frames_outputs =
|
||||
runner->Outputs().Tag("CROPPED_FRAMES").packets;
|
||||
runner->Outputs().Tag(kCroppedFramesTag).packets;
|
||||
EXPECT_EQ(cropped_frames_outputs.size(), 1);
|
||||
const auto& cropped_frame = cropped_frames_outputs[0].Get<ImageFrame>();
|
||||
const auto cropped_mat = formats::MatView(&cropped_frame);
|
||||
@@ -775,7 +792,7 @@ TEST(SceneCroppingCalculatorTest, OutputsCropMessagePolyPath) {
|
||||
MP_EXPECT_OK(runner->Run());
|
||||
const auto& outputs = runner->Outputs();
|
||||
const auto& ext_render_per_frame =
|
||||
outputs.Tag("EXTERNAL_RENDERING_PER_FRAME").packets;
|
||||
outputs.Tag(kExternalRenderingPerFrameTag).packets;
|
||||
EXPECT_EQ(ext_render_per_frame.size(), num_frames);
|
||||
|
||||
for (int i = 0; i < num_frames - 1; ++i) {
|
||||
@@ -813,7 +830,7 @@ TEST(SceneCroppingCalculatorTest, OutputsCropMessageKinematicPath) {
|
||||
MP_EXPECT_OK(runner->Run());
|
||||
const auto& outputs = runner->Outputs();
|
||||
const auto& ext_render_per_frame =
|
||||
outputs.Tag("EXTERNAL_RENDERING_PER_FRAME").packets;
|
||||
outputs.Tag(kExternalRenderingPerFrameTag).packets;
|
||||
EXPECT_EQ(ext_render_per_frame.size(), num_frames);
|
||||
|
||||
for (int i = 0; i < num_frames - 1; ++i) {
|
||||
@@ -846,7 +863,7 @@ TEST(SceneCroppingCalculatorTest, OutputsCropMessagePolyPathNoVideo) {
|
||||
MP_EXPECT_OK(runner->Run());
|
||||
const auto& outputs = runner->Outputs();
|
||||
const auto& ext_render_per_frame =
|
||||
outputs.Tag("EXTERNAL_RENDERING_PER_FRAME").packets;
|
||||
outputs.Tag(kExternalRenderingPerFrameTag).packets;
|
||||
EXPECT_EQ(ext_render_per_frame.size(), num_frames);
|
||||
|
||||
for (int i = 0; i < num_frames - 1; ++i) {
|
||||
@@ -886,7 +903,7 @@ TEST(SceneCroppingCalculatorTest, OutputsCropMessageKinematicPathNoVideo) {
|
||||
MP_EXPECT_OK(runner->Run());
|
||||
const auto& outputs = runner->Outputs();
|
||||
const auto& ext_render_per_frame =
|
||||
outputs.Tag("EXTERNAL_RENDERING_PER_FRAME").packets;
|
||||
outputs.Tag(kExternalRenderingPerFrameTag).packets;
|
||||
EXPECT_EQ(ext_render_per_frame.size(), num_frames);
|
||||
|
||||
for (int i = 0; i < num_frames - 1; ++i) {
|
||||
|
||||
@@ -43,6 +43,9 @@ namespace mediapipe {
|
||||
namespace autoflip {
|
||||
namespace {
|
||||
|
||||
constexpr char kIsShotChangeTag[] = "IS_SHOT_CHANGE";
|
||||
constexpr char kVideoTag[] = "VIDEO";
|
||||
|
||||
const char kConfig[] = R"(
|
||||
calculator: "ShotBoundaryCalculator"
|
||||
input_stream: "VIDEO:camera_frames"
|
||||
@@ -70,7 +73,7 @@ void AddFrames(const int number_of_frames, const std::set<int>& skip_frames,
|
||||
if (skip_frames.count(i) < 1) {
|
||||
sub_image.copyTo(frame_area);
|
||||
}
|
||||
runner->MutableInputs()->Tag("VIDEO").packets.push_back(
|
||||
runner->MutableInputs()->Tag(kVideoTag).packets.push_back(
|
||||
Adopt(input_frame.release()).At(Timestamp(i * 1000000)));
|
||||
}
|
||||
}
|
||||
@@ -97,7 +100,7 @@ TEST(ShotBoundaryCalculatorTest, NoShotChange) {
|
||||
|
||||
AddFrames(10, {}, runner.get());
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckOutput(10, {}, runner->Outputs().Tag("IS_SHOT_CHANGE").packets);
|
||||
CheckOutput(10, {}, runner->Outputs().Tag(kIsShotChangeTag).packets);
|
||||
}
|
||||
|
||||
TEST(ShotBoundaryCalculatorTest, ShotChangeSingle) {
|
||||
@@ -110,7 +113,7 @@ TEST(ShotBoundaryCalculatorTest, ShotChangeSingle) {
|
||||
|
||||
AddFrames(20, {10}, runner.get());
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckOutput(20, {10}, runner->Outputs().Tag("IS_SHOT_CHANGE").packets);
|
||||
CheckOutput(20, {10}, runner->Outputs().Tag(kIsShotChangeTag).packets);
|
||||
}
|
||||
|
||||
TEST(ShotBoundaryCalculatorTest, ShotChangeDouble) {
|
||||
@@ -123,7 +126,7 @@ TEST(ShotBoundaryCalculatorTest, ShotChangeDouble) {
|
||||
|
||||
AddFrames(20, {14, 17}, runner.get());
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckOutput(20, {14, 17}, runner->Outputs().Tag("IS_SHOT_CHANGE").packets);
|
||||
CheckOutput(20, {14, 17}, runner->Outputs().Tag(kIsShotChangeTag).packets);
|
||||
}
|
||||
|
||||
TEST(ShotBoundaryCalculatorTest, ShotChangeFiltered) {
|
||||
@@ -140,7 +143,7 @@ TEST(ShotBoundaryCalculatorTest, ShotChangeFiltered) {
|
||||
|
||||
AddFrames(24, {16, 19}, runner.get());
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
CheckOutput(24, {16}, runner->Outputs().Tag("IS_SHOT_CHANGE").packets);
|
||||
CheckOutput(24, {16}, runner->Outputs().Tag(kIsShotChangeTag).packets);
|
||||
}
|
||||
|
||||
TEST(ShotBoundaryCalculatorTest, ShotChangeSingleOnOnChange) {
|
||||
@@ -153,7 +156,7 @@ TEST(ShotBoundaryCalculatorTest, ShotChangeSingleOnOnChange) {
|
||||
|
||||
AddFrames(20, {15}, runner.get());
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
auto output_packets = runner->Outputs().Tag("IS_SHOT_CHANGE").packets;
|
||||
auto output_packets = runner->Outputs().Tag(kIsShotChangeTag).packets;
|
||||
ASSERT_EQ(output_packets.size(), 1);
|
||||
ASSERT_EQ(output_packets[0].Get<bool>(), true);
|
||||
ASSERT_EQ(output_packets[0].Timestamp().Value(), 15000000);
|
||||
|
||||
@@ -32,6 +32,9 @@ namespace mediapipe {
|
||||
namespace autoflip {
|
||||
namespace {
|
||||
|
||||
constexpr char kOutputTag[] = "OUTPUT";
|
||||
constexpr char kIsShotBoundaryTag[] = "IS_SHOT_BOUNDARY";
|
||||
|
||||
const char kConfigA[] = R"(
|
||||
calculator: "SignalFusingCalculator"
|
||||
input_stream: "scene_change"
|
||||
@@ -160,7 +163,7 @@ TEST(SignalFusingCalculatorTest, TwoInputShotLabeledTags) {
|
||||
|
||||
auto input_shot = absl::make_unique<bool>(false);
|
||||
runner->MutableInputs()
|
||||
->Tag("IS_SHOT_BOUNDARY")
|
||||
->Tag(kIsShotBoundaryTag)
|
||||
.packets.push_back(Adopt(input_shot.release()).At(Timestamp(0)));
|
||||
|
||||
auto input_face =
|
||||
@@ -200,7 +203,7 @@ TEST(SignalFusingCalculatorTest, TwoInputShotLabeledTags) {
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("OUTPUT").packets;
|
||||
runner->Outputs().Tag(kOutputTag).packets;
|
||||
const auto& detection_set = output_packets[0].Get<DetectionSet>();
|
||||
|
||||
ASSERT_EQ(detection_set.detections().size(), 4);
|
||||
@@ -251,7 +254,7 @@ TEST(SignalFusingCalculatorTest, TwoInputNoShotLabeledTags) {
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
|
||||
const std::vector<Packet>& output_packets =
|
||||
runner->Outputs().Tag("OUTPUT").packets;
|
||||
runner->Outputs().Tag(kOutputTag).packets;
|
||||
const auto& detection_set = output_packets[0].Get<DetectionSet>();
|
||||
|
||||
ASSERT_EQ(detection_set.detections().size(), 4);
|
||||
|
||||
@@ -31,6 +31,9 @@ namespace mediapipe {
|
||||
namespace autoflip {
|
||||
namespace {
|
||||
|
||||
constexpr char kOutputFramesTag[] = "OUTPUT_FRAMES";
|
||||
constexpr char kInputFramesTag[] = "INPUT_FRAMES";
|
||||
|
||||
// Default configuration of the calculator.
|
||||
CalculatorGraphConfig::Node GetCalculatorNode(
|
||||
const std::string& fail_if_any, const std::string& extra_options = "") {
|
||||
@@ -65,10 +68,10 @@ TEST(VideoFilterCalculatorTest, UpperBoundNoPass) {
|
||||
ImageFormat::SRGB, kFixedWidth,
|
||||
static_cast<int>(kFixedWidth / kAspectRatio), 16);
|
||||
runner->MutableInputs()
|
||||
->Tag("INPUT_FRAMES")
|
||||
->Tag(kInputFramesTag)
|
||||
.packets.push_back(Adopt(input_frame.release()).At(Timestamp(1000)));
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
const auto& output_packet = runner->Outputs().Tag("OUTPUT_FRAMES").packets;
|
||||
const auto& output_packet = runner->Outputs().Tag(kOutputFramesTag).packets;
|
||||
EXPECT_TRUE(output_packet.empty());
|
||||
}
|
||||
|
||||
@@ -88,10 +91,10 @@ TEST(VerticalFrameRemovalCalculatorTest, UpperBoundPass) {
|
||||
auto input_frame =
|
||||
::absl::make_unique<ImageFrame>(ImageFormat::SRGB, kWidth, kHeight, 16);
|
||||
runner->MutableInputs()
|
||||
->Tag("INPUT_FRAMES")
|
||||
->Tag(kInputFramesTag)
|
||||
.packets.push_back(Adopt(input_frame.release()).At(Timestamp(1000)));
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
const auto& output_packet = runner->Outputs().Tag("OUTPUT_FRAMES").packets;
|
||||
const auto& output_packet = runner->Outputs().Tag(kOutputFramesTag).packets;
|
||||
EXPECT_EQ(1, output_packet.size());
|
||||
auto& output_frame = output_packet[0].Get<ImageFrame>();
|
||||
EXPECT_EQ(kWidth, output_frame.Width());
|
||||
@@ -114,10 +117,10 @@ TEST(VideoFilterCalculatorTest, LowerBoundNoPass) {
|
||||
ImageFormat::SRGB, kFixedWidth,
|
||||
static_cast<int>(kFixedWidth / kAspectRatio), 16);
|
||||
runner->MutableInputs()
|
||||
->Tag("INPUT_FRAMES")
|
||||
->Tag(kInputFramesTag)
|
||||
.packets.push_back(Adopt(input_frame.release()).At(Timestamp(1000)));
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
const auto& output_packet = runner->Outputs().Tag("OUTPUT_FRAMES").packets;
|
||||
const auto& output_packet = runner->Outputs().Tag(kOutputFramesTag).packets;
|
||||
EXPECT_TRUE(output_packet.empty());
|
||||
}
|
||||
|
||||
@@ -137,10 +140,10 @@ TEST(VerticalFrameRemovalCalculatorTest, LowerBoundPass) {
|
||||
auto input_frame =
|
||||
::absl::make_unique<ImageFrame>(ImageFormat::SRGB, kWidth, kHeight, 16);
|
||||
runner->MutableInputs()
|
||||
->Tag("INPUT_FRAMES")
|
||||
->Tag(kInputFramesTag)
|
||||
.packets.push_back(Adopt(input_frame.release()).At(Timestamp(1000)));
|
||||
MP_ASSERT_OK(runner->Run());
|
||||
const auto& output_packet = runner->Outputs().Tag("OUTPUT_FRAMES").packets;
|
||||
const auto& output_packet = runner->Outputs().Tag(kOutputFramesTag).packets;
|
||||
EXPECT_EQ(1, output_packet.size());
|
||||
auto& output_frame = output_packet[0].Get<ImageFrame>();
|
||||
EXPECT_EQ(kWidth, output_frame.Width());
|
||||
@@ -164,7 +167,7 @@ TEST(VerticalFrameRemovalCalculatorTest, OutputError) {
|
||||
ImageFormat::SRGB, kFixedWidth,
|
||||
static_cast<int>(kFixedWidth / kAspectRatio), 16);
|
||||
runner->MutableInputs()
|
||||
->Tag("INPUT_FRAMES")
|
||||
->Tag(kInputFramesTag)
|
||||
.packets.push_back(Adopt(input_frame.release()).At(Timestamp(1000)));
|
||||
absl::Status status = runner->Run();
|
||||
EXPECT_EQ(status.code(), absl::StatusCode::kUnknown);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "mediapipe/examples/desktop/autoflip/quality/kinematic_path_solver.h"
|
||||
|
||||
constexpr float kMinVelocity = 0.5;
|
||||
|
||||
namespace mediapipe {
|
||||
namespace autoflip {
|
||||
namespace {
|
||||
@@ -75,6 +77,7 @@ absl::Status KinematicPathSolver::AddObservation(int position,
|
||||
current_position_px_ = position;
|
||||
}
|
||||
target_position_px_ = position;
|
||||
prior_position_px_ = current_position_px_;
|
||||
motion_state_ = false;
|
||||
mean_delta_t_ = -1;
|
||||
raw_positions_at_time_.push_front(
|
||||
@@ -106,6 +109,11 @@ absl::Status KinematicPathSolver::AddObservation(int position,
|
||||
options_.reframe_window())
|
||||
<< "Reframe window cannot exceed min_motion_to_reframe.";
|
||||
}
|
||||
RET_CHECK(options_.has_max_velocity() ^
|
||||
(options_.has_max_velocity_scale() &&
|
||||
options_.has_max_velocity_shift()))
|
||||
<< "Must either set max_velocity or set both max_velocity_scale and "
|
||||
"max_velocity_shift.";
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -123,9 +131,29 @@ absl::Status KinematicPathSolver::AddObservation(int position,
|
||||
}
|
||||
|
||||
int filtered_position = Median(raw_positions_at_time_);
|
||||
|
||||
float min_reframe = (options_.has_min_motion_to_reframe()
|
||||
? options_.min_motion_to_reframe()
|
||||
: options_.min_motion_to_reframe_lower()) *
|
||||
pixels_per_degree_;
|
||||
float max_reframe = (options_.has_min_motion_to_reframe()
|
||||
? options_.min_motion_to_reframe()
|
||||
: options_.min_motion_to_reframe_upper()) *
|
||||
pixels_per_degree_;
|
||||
|
||||
filtered_position = fmax(min_location_ - min_reframe, filtered_position);
|
||||
filtered_position = fmin(max_location_ + max_reframe, filtered_position);
|
||||
|
||||
double delta_degs =
|
||||
(filtered_position - current_position_px_) / pixels_per_degree_;
|
||||
|
||||
double max_velocity =
|
||||
options_.has_max_velocity()
|
||||
? options_.max_velocity()
|
||||
: fmax(abs(delta_degs * options_.max_velocity_scale()) +
|
||||
options_.max_velocity_shift(),
|
||||
kMinVelocity);
|
||||
|
||||
// If the motion is smaller than the min_motion_to_reframe and camera is
|
||||
// stationary, don't use the update.
|
||||
if (IsMotionTooSmall(delta_degs) && !motion_state_) {
|
||||
@@ -169,10 +197,9 @@ absl::Status KinematicPathSolver::AddObservation(int position,
|
||||
options_.max_update_rate());
|
||||
double updated_velocity = current_velocity_deg_per_s_ * (1 - update_rate) +
|
||||
observed_velocity * update_rate;
|
||||
// Limited current velocity.
|
||||
current_velocity_deg_per_s_ =
|
||||
updated_velocity > 0 ? fmin(updated_velocity, options_.max_velocity())
|
||||
: fmax(updated_velocity, -options_.max_velocity());
|
||||
current_velocity_deg_per_s_ = updated_velocity > 0
|
||||
? fmin(updated_velocity, max_velocity)
|
||||
: fmax(updated_velocity, -max_velocity);
|
||||
|
||||
// Update prediction based on time input.
|
||||
return UpdatePrediction(time_us);
|
||||
@@ -182,6 +209,9 @@ absl::Status KinematicPathSolver::UpdatePrediction(const int64 time_us) {
|
||||
RET_CHECK(current_time_ < time_us)
|
||||
<< "Prediction time added before a prior observation or prediction.";
|
||||
|
||||
// Store prior pixel location.
|
||||
prior_position_px_ = current_position_px_;
|
||||
|
||||
// Position update limited by min/max.
|
||||
double update_position_px =
|
||||
current_position_px_ +
|
||||
@@ -209,7 +239,19 @@ absl::Status KinematicPathSolver::GetState(int* position) {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status KinematicPathSolver::SetState(const int position) {
|
||||
absl::Status KinematicPathSolver::GetState(float* position) {
|
||||
RET_CHECK(initialized_) << "GetState called before first observation added.";
|
||||
*position = current_position_px_;
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status KinematicPathSolver::GetDeltaState(float* delta_position) {
|
||||
RET_CHECK(initialized_) << "GetState called before first observation added.";
|
||||
*delta_position = current_position_px_ - prior_position_px_;
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status KinematicPathSolver::SetState(const float position) {
|
||||
RET_CHECK(initialized_) << "SetState called before first observation added.";
|
||||
current_position_px_ = position;
|
||||
return absl::OkStatus();
|
||||
@@ -218,7 +260,15 @@ absl::Status KinematicPathSolver::SetState(const int position) {
|
||||
absl::Status KinematicPathSolver::GetTargetPosition(int* target_position) {
|
||||
RET_CHECK(initialized_)
|
||||
<< "GetTargetPosition called before first observation added.";
|
||||
*target_position = round(target_position_px_);
|
||||
|
||||
// Provide target position clamped by min/max locations.
|
||||
if (target_position_px_ < min_location_) {
|
||||
*target_position = min_location_;
|
||||
} else if (target_position_px_ > max_location_) {
|
||||
*target_position = max_location_;
|
||||
} else {
|
||||
*target_position = round(target_position_px_);
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -238,6 +288,7 @@ absl::Status KinematicPathSolver::UpdateMinMaxLocation(const int min_location,
|
||||
double updated_distance = max_location - min_location;
|
||||
double scale_change = updated_distance / prior_distance;
|
||||
current_position_px_ = current_position_px_ * scale_change;
|
||||
prior_position_px_ = prior_position_px_ * scale_change;
|
||||
target_position_px_ = target_position_px_ * scale_change;
|
||||
max_location_ = max_location;
|
||||
min_location_ = min_location;
|
||||
|
||||
@@ -46,10 +46,12 @@ class KinematicPathSolver {
|
||||
absl::Status AddObservation(int position, const uint64 time_us);
|
||||
// Get the predicted position at a time.
|
||||
absl::Status UpdatePrediction(const int64 time_us);
|
||||
// Get the state at a time.
|
||||
// Get the state at a time, as an int.
|
||||
absl::Status GetState(int* position);
|
||||
// Get the state at a time, as a float.
|
||||
absl::Status GetState(float* position);
|
||||
// Overwrite the current state value.
|
||||
absl::Status SetState(const int position);
|
||||
absl::Status SetState(const float position);
|
||||
// Update PixelPerDegree value.
|
||||
absl::Status UpdatePixelsPerDegree(const float pixels_per_degree);
|
||||
// Provide the current target position of the reframe action.
|
||||
@@ -66,6 +68,8 @@ class KinematicPathSolver {
|
||||
// Clear any history buffer of positions that are used when
|
||||
// filtering_time_window_us is set to a non-zero value.
|
||||
void ClearHistory();
|
||||
// Provides the change in position from last state.
|
||||
absl::Status GetDeltaState(float* delta_position);
|
||||
|
||||
private:
|
||||
// Tuning options.
|
||||
@@ -77,6 +81,7 @@ class KinematicPathSolver {
|
||||
float pixels_per_degree_;
|
||||
// Current state values.
|
||||
double current_position_px_;
|
||||
double prior_position_px_;
|
||||
double current_velocity_deg_per_s_;
|
||||
uint64 current_time_;
|
||||
// History of observations (second) and their time (first).
|
||||
|
||||
@@ -6,8 +6,9 @@ message KinematicOptions {
|
||||
// Weighted update of new camera velocity (measurement) vs current state
|
||||
// (prediction).
|
||||
optional double update_rate = 1 [default = 0.5, deprecated = true];
|
||||
// Max velocity (degrees per second) that the camera can move.
|
||||
optional double max_velocity = 2 [default = 18];
|
||||
// Max velocity (degrees per second) that the camera can move. Cannot be used
|
||||
// with max_velocity_scale or max_velocity_shift.
|
||||
optional double max_velocity = 2;
|
||||
// Min motion (in degrees) to react for both upper and lower directions. Must
|
||||
// not be set if using min_motion_to_reframe_lower and
|
||||
// min_motion_to_reframe_upper.
|
||||
@@ -30,4 +31,12 @@ message KinematicOptions {
|
||||
optional int64 filtering_time_window_us = 7 [default = 0];
|
||||
// Weighted update of average period, used for motion updates.
|
||||
optional float mean_period_update_rate = 8 [default = 0.25];
|
||||
// Scale factor for max velocity, to be multiplied by the distance from center
|
||||
// in degrees. Cannot be used with max_velocity and must be used with
|
||||
// max_velocity_shift.
|
||||
optional float max_velocity_scale = 11;
|
||||
// Shift factor for max velocity, to be added to the scaled distance from
|
||||
// center in degrees. Cannot be used with max_velocity and must be used with
|
||||
// max_velocity_scale.
|
||||
optional float max_velocity_shift = 12;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ TEST(KinematicPathSolverTest, FailZeroPixelsPerDegree) {
|
||||
TEST(KinematicPathSolverTest, FailNotInitializedState) {
|
||||
KinematicOptions options;
|
||||
KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView);
|
||||
int state;
|
||||
float state;
|
||||
EXPECT_FALSE(solver.GetState(&state).ok());
|
||||
}
|
||||
|
||||
@@ -55,13 +55,13 @@ TEST(KinematicPathSolverTest, PassNotEnoughMotionLargeImg) {
|
||||
options.set_max_velocity(1000);
|
||||
// Set degrees / pixel to 16.6
|
||||
KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView);
|
||||
int state;
|
||||
float state;
|
||||
MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0));
|
||||
// Move target by 20px / 16.6 = 1.2deg
|
||||
MP_ASSERT_OK(solver.AddObservation(520, kMicroSecInSec * 1));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
// Expect cam to not move.
|
||||
EXPECT_EQ(state, 500);
|
||||
EXPECT_FLOAT_EQ(state, 500);
|
||||
}
|
||||
|
||||
TEST(KinematicPathSolverTest, PassNotEnoughMotionSmallImg) {
|
||||
@@ -72,13 +72,13 @@ TEST(KinematicPathSolverTest, PassNotEnoughMotionSmallImg) {
|
||||
options.set_max_velocity(500);
|
||||
// Set degrees / pixel to 8.3
|
||||
KinematicPathSolver solver(options, 0, 500, 500.0 / kWidthFieldOfView);
|
||||
int state;
|
||||
float state;
|
||||
MP_ASSERT_OK(solver.AddObservation(400, kMicroSecInSec * 0));
|
||||
// Move target by 10px / 8.3 = 1.2deg
|
||||
MP_ASSERT_OK(solver.AddObservation(410, kMicroSecInSec * 1));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
// Expect cam to not move.
|
||||
EXPECT_EQ(state, 400);
|
||||
EXPECT_FLOAT_EQ(state, 400);
|
||||
}
|
||||
|
||||
TEST(KinematicPathSolverTest, PassEnoughMotionFiltered) {
|
||||
@@ -90,7 +90,7 @@ TEST(KinematicPathSolverTest, PassEnoughMotionFiltered) {
|
||||
options.set_filtering_time_window_us(3000000);
|
||||
// Set degrees / pixel to 16.6
|
||||
KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView);
|
||||
int state;
|
||||
float state;
|
||||
MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0));
|
||||
// Move target by 20px / 16.6 = 1.2deg
|
||||
MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 1));
|
||||
@@ -98,7 +98,7 @@ TEST(KinematicPathSolverTest, PassEnoughMotionFiltered) {
|
||||
MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 3));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
// Expect cam to not move.
|
||||
EXPECT_EQ(state, 500);
|
||||
EXPECT_FLOAT_EQ(state, 500);
|
||||
}
|
||||
|
||||
TEST(KinematicPathSolverTest, PassEnoughMotionNotFiltered) {
|
||||
@@ -110,7 +110,7 @@ TEST(KinematicPathSolverTest, PassEnoughMotionNotFiltered) {
|
||||
options.set_filtering_time_window_us(0);
|
||||
// Set degrees / pixel to 16.6
|
||||
KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView);
|
||||
int state;
|
||||
float state;
|
||||
MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0));
|
||||
// Move target by 20px / 16.6 = 1.2deg
|
||||
MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 1));
|
||||
@@ -118,7 +118,7 @@ TEST(KinematicPathSolverTest, PassEnoughMotionNotFiltered) {
|
||||
MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 3));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
// Expect cam to not move.
|
||||
EXPECT_EQ(state, 506);
|
||||
EXPECT_FLOAT_EQ(state, 506.4);
|
||||
}
|
||||
|
||||
TEST(KinematicPathSolverTest, PassEnoughMotionLargeImg) {
|
||||
@@ -130,13 +130,13 @@ TEST(KinematicPathSolverTest, PassEnoughMotionLargeImg) {
|
||||
options.set_max_velocity(1000);
|
||||
// Set degrees / pixel to 16.6
|
||||
KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView);
|
||||
int state;
|
||||
float state;
|
||||
MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0));
|
||||
// Move target by 20px / 16.6 = 1.2deg
|
||||
MP_ASSERT_OK(solver.AddObservation(520, kMicroSecInSec * 1));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
// Expect cam to move.
|
||||
EXPECT_EQ(state, 520);
|
||||
EXPECT_FLOAT_EQ(state, 520);
|
||||
}
|
||||
|
||||
TEST(KinematicPathSolverTest, PassEnoughMotionSmallImg) {
|
||||
@@ -148,13 +148,13 @@ TEST(KinematicPathSolverTest, PassEnoughMotionSmallImg) {
|
||||
options.set_max_velocity(18);
|
||||
// Set degrees / pixel to 8.3
|
||||
KinematicPathSolver solver(options, 0, 500, 500.0 / kWidthFieldOfView);
|
||||
int state;
|
||||
float state;
|
||||
MP_ASSERT_OK(solver.AddObservation(400, kMicroSecInSec * 0));
|
||||
// Move target by 10px / 8.3 = 1.2deg
|
||||
MP_ASSERT_OK(solver.AddObservation(410, kMicroSecInSec * 1));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
// Expect cam to move.
|
||||
EXPECT_EQ(state, 410);
|
||||
EXPECT_FLOAT_EQ(state, 410);
|
||||
}
|
||||
|
||||
TEST(KinematicPathSolverTest, FailReframeWindowSetting) {
|
||||
@@ -181,13 +181,13 @@ TEST(KinematicPathSolverTest, PassReframeWindow) {
|
||||
options.set_reframe_window(0.75);
|
||||
// Set degrees / pixel to 16.6
|
||||
KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView);
|
||||
int state;
|
||||
float state;
|
||||
MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0));
|
||||
// Move target by 20px / 16.6 = 1.2deg
|
||||
MP_ASSERT_OK(solver.AddObservation(520, kMicroSecInSec * 1));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
// Expect cam to move 1.2-.75 deg, * 16.6 = 7.47px + 500 =
|
||||
EXPECT_EQ(state, 508);
|
||||
EXPECT_FLOAT_EQ(state, 507.5);
|
||||
}
|
||||
|
||||
TEST(KinematicPathSolverTest, PassReframeWindowLowerUpper) {
|
||||
@@ -202,17 +202,17 @@ TEST(KinematicPathSolverTest, PassReframeWindowLowerUpper) {
|
||||
options.set_reframe_window(0.75);
|
||||
// Set degrees / pixel to 16.6
|
||||
KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView);
|
||||
int state;
|
||||
float state;
|
||||
MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0));
|
||||
// Move target by 20px / 16.6 = 1.2deg
|
||||
MP_ASSERT_OK(solver.AddObservation(520, kMicroSecInSec * 1));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
// Expect cam to not move
|
||||
EXPECT_EQ(state, 500);
|
||||
EXPECT_FLOAT_EQ(state, 500);
|
||||
MP_ASSERT_OK(solver.AddObservation(480, kMicroSecInSec * 2));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
// Expect cam to move
|
||||
EXPECT_EQ(state, 493);
|
||||
EXPECT_FLOAT_EQ(state, 492.5);
|
||||
}
|
||||
|
||||
TEST(KinematicPathSolverTest, PassCheckState) {
|
||||
@@ -241,12 +241,12 @@ TEST(KinematicPathSolverTest, PassUpdateRate30FPS) {
|
||||
options.set_max_update_rate(0.8);
|
||||
options.set_max_velocity(18);
|
||||
KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView);
|
||||
int state;
|
||||
float state;
|
||||
MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0));
|
||||
MP_ASSERT_OK(solver.AddObservation(520, kMicroSecInSec * 1 / 30));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
// (0.033 / .25) * 20 =
|
||||
EXPECT_EQ(state, 503);
|
||||
EXPECT_FLOAT_EQ(state, 502.6667);
|
||||
}
|
||||
|
||||
TEST(KinematicPathSolverTest, PassUpdateRate10FPS) {
|
||||
@@ -256,12 +256,12 @@ TEST(KinematicPathSolverTest, PassUpdateRate10FPS) {
|
||||
options.set_max_update_rate(0.8);
|
||||
options.set_max_velocity(18);
|
||||
KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView);
|
||||
int state;
|
||||
float state;
|
||||
MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0));
|
||||
MP_ASSERT_OK(solver.AddObservation(520, kMicroSecInSec * 1 / 10));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
// (0.1 / .25) * 20 =
|
||||
EXPECT_EQ(state, 508);
|
||||
EXPECT_FLOAT_EQ(state, 508);
|
||||
}
|
||||
|
||||
TEST(KinematicPathSolverTest, PassUpdateRate) {
|
||||
@@ -271,7 +271,8 @@ TEST(KinematicPathSolverTest, PassUpdateRate) {
|
||||
options.set_max_update_rate(1.0);
|
||||
options.set_max_velocity(18);
|
||||
KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView);
|
||||
int state, target_position;
|
||||
int target_position;
|
||||
float state;
|
||||
MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0));
|
||||
MP_ASSERT_OK(solver.GetTargetPosition(&target_position));
|
||||
EXPECT_EQ(target_position, 500);
|
||||
@@ -279,7 +280,7 @@ TEST(KinematicPathSolverTest, PassUpdateRate) {
|
||||
MP_ASSERT_OK(solver.GetTargetPosition(&target_position));
|
||||
EXPECT_EQ(target_position, 520);
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
EXPECT_EQ(state, 505);
|
||||
EXPECT_FLOAT_EQ(state, 505);
|
||||
}
|
||||
|
||||
TEST(KinematicPathSolverTest, PassUpdateRateResolutionChange) {
|
||||
@@ -289,7 +290,8 @@ TEST(KinematicPathSolverTest, PassUpdateRateResolutionChange) {
|
||||
options.set_max_update_rate(1.0);
|
||||
options.set_max_velocity(18);
|
||||
KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView);
|
||||
int state, target_position;
|
||||
int target_position;
|
||||
float state;
|
||||
MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0));
|
||||
MP_ASSERT_OK(solver.GetTargetPosition(&target_position));
|
||||
EXPECT_EQ(target_position, 500);
|
||||
@@ -299,10 +301,10 @@ TEST(KinematicPathSolverTest, PassUpdateRateResolutionChange) {
|
||||
MP_ASSERT_OK(solver.GetTargetPosition(&target_position));
|
||||
EXPECT_EQ(target_position, 520 * 0.5);
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
EXPECT_EQ(state, 253);
|
||||
EXPECT_FLOAT_EQ(state, 252.5);
|
||||
}
|
||||
|
||||
TEST(KinematicPathSolverTest, PassMaxVelocity) {
|
||||
TEST(KinematicPathSolverTest, PassMaxVelocityInt) {
|
||||
KinematicOptions options;
|
||||
options.set_min_motion_to_reframe(1.0);
|
||||
options.set_update_rate(1.0);
|
||||
@@ -315,6 +317,33 @@ TEST(KinematicPathSolverTest, PassMaxVelocity) {
|
||||
EXPECT_EQ(state, 600);
|
||||
}
|
||||
|
||||
TEST(KinematicPathSolverTest, PassMaxVelocity) {
|
||||
KinematicOptions options;
|
||||
options.set_min_motion_to_reframe(1.0);
|
||||
options.set_update_rate(1.0);
|
||||
options.set_max_velocity(6);
|
||||
KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView);
|
||||
float state;
|
||||
MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0));
|
||||
MP_ASSERT_OK(solver.AddObservation(1000, kMicroSecInSec * 1));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
EXPECT_FLOAT_EQ(state, 600);
|
||||
}
|
||||
|
||||
TEST(KinematicPathSolverTest, PassMaxVelocityScale) {
|
||||
KinematicOptions options;
|
||||
options.set_min_motion_to_reframe(1.0);
|
||||
options.set_update_rate(1.0);
|
||||
options.set_max_velocity_scale(0.4);
|
||||
options.set_max_velocity_shift(-2.0);
|
||||
KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView);
|
||||
float state;
|
||||
MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0));
|
||||
MP_ASSERT_OK(solver.AddObservation(1000, kMicroSecInSec * 1));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
EXPECT_FLOAT_EQ(state, 666.6667);
|
||||
}
|
||||
|
||||
TEST(KinematicPathSolverTest, PassDegPerPxChange) {
|
||||
KinematicOptions options;
|
||||
// Set min motion to 2deg
|
||||
@@ -323,18 +352,18 @@ TEST(KinematicPathSolverTest, PassDegPerPxChange) {
|
||||
options.set_max_velocity(1000);
|
||||
// Set degrees / pixel to 16.6
|
||||
KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView);
|
||||
int state;
|
||||
float state;
|
||||
MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0));
|
||||
// Move target by 20px / 16.6 = 1.2deg
|
||||
MP_ASSERT_OK(solver.AddObservation(520, kMicroSecInSec * 1));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
// Expect cam to not move.
|
||||
EXPECT_EQ(state, 500);
|
||||
EXPECT_FLOAT_EQ(state, 500);
|
||||
MP_ASSERT_OK(solver.UpdatePixelsPerDegree(500.0 / kWidthFieldOfView));
|
||||
MP_ASSERT_OK(solver.AddObservation(520, kMicroSecInSec * 2));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
// Expect cam to move.
|
||||
EXPECT_EQ(state, 516);
|
||||
EXPECT_FLOAT_EQ(state, 516);
|
||||
}
|
||||
|
||||
TEST(KinematicPathSolverTest, NoTimestampSmoothing) {
|
||||
@@ -344,14 +373,14 @@ TEST(KinematicPathSolverTest, NoTimestampSmoothing) {
|
||||
options.set_max_velocity(6);
|
||||
options.set_mean_period_update_rate(1.0);
|
||||
KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView);
|
||||
int state;
|
||||
float state;
|
||||
MP_ASSERT_OK(solver.AddObservation(500, 0));
|
||||
MP_ASSERT_OK(solver.AddObservation(1000, 1000000));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
EXPECT_EQ(state, 600);
|
||||
EXPECT_FLOAT_EQ(state, 600);
|
||||
MP_ASSERT_OK(solver.AddObservation(1000, 2200000));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
EXPECT_EQ(state, 720);
|
||||
EXPECT_FLOAT_EQ(state, 720);
|
||||
}
|
||||
|
||||
TEST(KinematicPathSolverTest, TimestampSmoothing) {
|
||||
@@ -361,14 +390,14 @@ TEST(KinematicPathSolverTest, TimestampSmoothing) {
|
||||
options.set_max_velocity(6);
|
||||
options.set_mean_period_update_rate(0.05);
|
||||
KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView);
|
||||
int state;
|
||||
float state;
|
||||
MP_ASSERT_OK(solver.AddObservation(500, 0));
|
||||
MP_ASSERT_OK(solver.AddObservation(1000, 1000000));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
EXPECT_EQ(state, 600);
|
||||
EXPECT_FLOAT_EQ(state, 600);
|
||||
MP_ASSERT_OK(solver.AddObservation(1000, 2200000));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
EXPECT_EQ(state, 701);
|
||||
EXPECT_FLOAT_EQ(state, 701);
|
||||
}
|
||||
|
||||
TEST(KinematicPathSolverTest, PassSetPosition) {
|
||||
@@ -380,16 +409,30 @@ TEST(KinematicPathSolverTest, PassSetPosition) {
|
||||
options.set_max_velocity(18);
|
||||
// Set degrees / pixel to 8.3
|
||||
KinematicPathSolver solver(options, 0, 500, 500.0 / kWidthFieldOfView);
|
||||
int state;
|
||||
float state;
|
||||
MP_ASSERT_OK(solver.AddObservation(400, kMicroSecInSec * 0));
|
||||
// Move target by 10px / 8.3 = 1.2deg
|
||||
MP_ASSERT_OK(solver.AddObservation(410, kMicroSecInSec * 1));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
// Expect cam to move.
|
||||
EXPECT_EQ(state, 410);
|
||||
EXPECT_FLOAT_EQ(state, 410);
|
||||
MP_ASSERT_OK(solver.SetState(400));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
EXPECT_EQ(state, 400);
|
||||
EXPECT_FLOAT_EQ(state, 400);
|
||||
}
|
||||
TEST(KinematicPathSolverTest, PassBorderTest) {
|
||||
KinematicOptions options;
|
||||
options.set_min_motion_to_reframe(1.0);
|
||||
options.set_max_update_rate(0.25);
|
||||
options.set_max_velocity_scale(0.5);
|
||||
options.set_max_velocity_shift(-1.0);
|
||||
|
||||
KinematicPathSolver solver(options, 0, 500, 500.0 / kWidthFieldOfView);
|
||||
float state;
|
||||
MP_ASSERT_OK(solver.AddObservation(400, kMicroSecInSec * 0));
|
||||
MP_ASSERT_OK(solver.AddObservation(800, kMicroSecInSec * 0.1));
|
||||
MP_ASSERT_OK(solver.GetState(&state));
|
||||
EXPECT_FLOAT_EQ(state, 404.56668);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Reference in New Issue
Block a user