设备上跑通 人脸检测和 线条绘制
This commit is contained in:
@@ -1,18 +1,202 @@
|
||||
package com.hmwl.sample;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import com.google.androidgamesdk.GameActivity;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.camera.core.AspectRatio;
|
||||
import androidx.camera.core.CameraSelector;
|
||||
import androidx.camera.core.ImageAnalysis;
|
||||
import androidx.camera.core.ImageProxy;
|
||||
import androidx.camera.lifecycle.ProcessCameraProvider;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
public class MainActivity extends GameActivity{
|
||||
import com.google.androidgamesdk.GameActivity;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.mediapipe.tasks.components.containers.NormalizedLandmark;
|
||||
import com.google.mediapipe.tasks.vision.core.RunningMode;
|
||||
import com.google.mediapipe.tasks.vision.facelandmarker.FaceLandmarkerResult;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class MainActivity extends GameActivity implements FaceLandmarkerHelper.LandmarkerListener {
|
||||
private static final String TAG = "MainActivity";
|
||||
|
||||
static {
|
||||
System.loadLibrary("sample");
|
||||
}
|
||||
|
||||
private ProcessCameraProvider cameraProvider;
|
||||
private void initCamera(){
|
||||
backgroundExecutor = Executors.newSingleThreadExecutor();
|
||||
if (hasCameraPermission()) {
|
||||
startCamera();
|
||||
} else {
|
||||
requestCameraPermission();
|
||||
}
|
||||
}
|
||||
|
||||
private void startCamera() {
|
||||
ListenableFuture<ProcessCameraProvider> cameraProviderFuture =
|
||||
ProcessCameraProvider.getInstance(this);
|
||||
|
||||
cameraProviderFuture.addListener(() -> {
|
||||
try {
|
||||
cameraProvider = cameraProviderFuture.get();
|
||||
|
||||
CameraSelector cameraSelector = new CameraSelector.Builder()
|
||||
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
|
||||
.build();
|
||||
|
||||
imageAnalyzer = new ImageAnalysis.Builder()
|
||||
.setTargetAspectRatio(AspectRatio.RATIO_4_3)
|
||||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||
.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
|
||||
.build();
|
||||
|
||||
|
||||
imageAnalyzer.setAnalyzer(backgroundExecutor, new ImageAnalysis.Analyzer() {
|
||||
private long lastCaptureTime = 0;
|
||||
private int frameCount = 0;
|
||||
|
||||
@Override
|
||||
public void analyze(@NonNull ImageProxy image) {
|
||||
frameCount++;
|
||||
long currentTime = System.currentTimeMillis();
|
||||
try {
|
||||
long captureTimeNs = image.getImageInfo().getTimestamp();
|
||||
long analyzeTime = System.currentTimeMillis();
|
||||
|
||||
// 简单直接的计算方法
|
||||
long captureTimeMs = TimeUnit.NANOSECONDS.toMillis(captureTimeNs);
|
||||
long latency = analyzeTime - captureTimeMs;
|
||||
|
||||
// 帧率计算
|
||||
if (lastCaptureTime > 0) {
|
||||
long frameInterval = captureTimeMs - lastCaptureTime;
|
||||
double fps = 1000.0 / frameInterval;
|
||||
|
||||
if (frameCount % 10 == 0) { // 每30帧打印一次
|
||||
Log.i("TimeLatency", String.format(
|
||||
"Frame %d - Latency: %d ms, FPS: %.1f",
|
||||
frameCount, latency, fps
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
lastCaptureTime = captureTimeMs;
|
||||
|
||||
processImageForVulkan(image);
|
||||
if(frameCount %2 == 0)
|
||||
{
|
||||
detectFace(image);
|
||||
}
|
||||
else
|
||||
{
|
||||
image.close();
|
||||
}
|
||||
} finally {
|
||||
//image.close(); // 确保在这里关闭
|
||||
//Log.d("Camera", "Image closed, ready for next frame");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cameraProvider.bindToLifecycle(this, cameraSelector, imageAnalyzer);
|
||||
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
Log.e("MainActivity", "Error starting camera: " + e.getMessage());
|
||||
}
|
||||
}, ContextCompat.getMainExecutor(this));
|
||||
}
|
||||
|
||||
private void processImageForVulkan(ImageProxy image) {
|
||||
// 获取图像信息
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
int format = image.getFormat();
|
||||
|
||||
// 获取图像数据平面
|
||||
ImageProxy.PlaneProxy[] planes = image.getPlanes();
|
||||
|
||||
// 对于 RGBA_8888 格式,通常只有一个平面
|
||||
if (planes.length > 0) {
|
||||
ImageProxy.PlaneProxy plane = planes[0];
|
||||
ByteBuffer buffer = plane.getBuffer();
|
||||
int rowStride = plane.getRowStride();
|
||||
int pixelStride = plane.getPixelStride();
|
||||
|
||||
// 调用 Native 方法处理图像
|
||||
processImageNative(buffer, width, height, format,
|
||||
rowStride, pixelStride, image.getImageInfo().getRotationDegrees());
|
||||
}
|
||||
}
|
||||
|
||||
// Native 方法
|
||||
private native void processImageNative(ByteBuffer buffer, int width, int height,
|
||||
int format, int rowStride, int pixelStride,
|
||||
int rotation);
|
||||
|
||||
private void detectFace(ImageProxy imageProxy) {
|
||||
faceLandmarkerHelper.detectLiveStream(
|
||||
imageProxy,false
|
||||
);
|
||||
}
|
||||
|
||||
private boolean hasCameraPermission() {
|
||||
return ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
|
||||
== PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
private void requestCameraPermission() {
|
||||
ActivityCompat.requestPermissions(this,
|
||||
new String[]{Manifest.permission.CAMERA},
|
||||
REQUEST_CAMERA_PERMISSION);
|
||||
}
|
||||
|
||||
private static final int REQUEST_CAMERA_PERMISSION = 1001;
|
||||
ImageAnalysis imageAnalyzer;
|
||||
ExecutorService backgroundExecutor;
|
||||
FaceLandmarkerHelper faceLandmarkerHelper;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
initCamera();
|
||||
|
||||
backgroundExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
faceLandmarkerHelper = new FaceLandmarkerHelper(
|
||||
0.5f,
|
||||
0.5f,
|
||||
0.5f,
|
||||
1,
|
||||
0,
|
||||
RunningMode.LIVE_STREAM,
|
||||
MainActivity.this,
|
||||
MainActivity.this
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
int action = event.getActionMasked();
|
||||
@@ -57,4 +241,71 @@ public class MainActivity extends GameActivity{
|
||||
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NotNull String error, int errorCode) {
|
||||
|
||||
}
|
||||
|
||||
private ByteBuffer nativeBuffer = null;
|
||||
float[] points = new float[480 * 3];
|
||||
|
||||
|
||||
public class Vector {
|
||||
public float x, y, z;
|
||||
|
||||
public Vector(float x, float y, float z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
}
|
||||
|
||||
public float z_rate = -1.0f;
|
||||
|
||||
@Override
|
||||
public void onResults(FaceLandmarkerHelper.@NotNull ResultBundle resultBundle) {
|
||||
FaceLandmarkerResult faceLandmarkerResult = resultBundle.getResult();
|
||||
int height = resultBundle.getInputImageHeight();
|
||||
int width = resultBundle.getInputImageWidth();
|
||||
int bufferSize = 480 * 3 * 4;
|
||||
|
||||
if(nativeBuffer == null)
|
||||
{
|
||||
// 创建直接ByteBuffer(在native内存中)
|
||||
nativeBuffer = ByteBuffer.allocateDirect(bufferSize);
|
||||
nativeBuffer.order(ByteOrder.nativeOrder());
|
||||
}
|
||||
|
||||
long start_time = System.currentTimeMillis();
|
||||
|
||||
int index = 0;
|
||||
// Iterate through each detected face
|
||||
for (List<NormalizedLandmark> faceLandmarks : faceLandmarkerResult.faceLandmarks()) {
|
||||
//should be 1
|
||||
|
||||
int arrayIndex = 0;
|
||||
for (NormalizedLandmark p : faceLandmarks)
|
||||
{
|
||||
//sb.append("[");
|
||||
points[arrayIndex++] = p.x();
|
||||
//sb.append(""+p.x()+",");
|
||||
points[arrayIndex++] = p.y();
|
||||
//sb.append(""+p.y()+",");
|
||||
points[arrayIndex++] = p.z() * z_rate;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
FloatBuffer floatBuffer = nativeBuffer.asFloatBuffer();
|
||||
floatBuffer.put(points);
|
||||
floatBuffer.position(0);
|
||||
long cur_time = System.currentTimeMillis();
|
||||
Log.i("TimeLatency","Result Data ProcessTime:" + (cur_time-start_time));
|
||||
passDataToNative(nativeBuffer, index, width, height);
|
||||
Log.i("TimeLatency","passDataToNative ProcessTime:" + (System.currentTimeMillis() - cur_time));
|
||||
}
|
||||
|
||||
public native void passDataToNative(ByteBuffer buffer, int pointCount, int width, int height);
|
||||
}
|
||||
Reference in New Issue
Block a user