设备上跑通 人脸检测和 线条绘制
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
}
|
||||
|
||||
ext.vvl_version='1.4.321.0'
|
||||
@@ -57,4 +58,10 @@ dependencies {
|
||||
testImplementation libs.junit
|
||||
androidTestImplementation libs.ext.junit
|
||||
androidTestImplementation libs.espresso.core
|
||||
implementation 'com.google.mediapipe:tasks-vision:0.10.26'
|
||||
def camerax_version = '1.4.2'
|
||||
implementation "androidx.camera:camera-core:$camerax_version"
|
||||
implementation "androidx.camera:camera-camera2:$camerax_version"
|
||||
implementation "androidx.camera:camera-lifecycle:$camerax_version"
|
||||
implementation "androidx.camera:camera-view:$camerax_version"
|
||||
}
|
||||
@@ -2,6 +2,12 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<!-- Declare features -->
|
||||
<uses-feature android:name="android.hardware.camera" />
|
||||
|
||||
<!-- Declare permissions -->
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
|
||||
Binary file not shown.
@@ -75,4 +75,47 @@ void android_main(struct android_app *pApp) {
|
||||
} while (!pApp->destroyRequested);
|
||||
application.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
extern void TextureLoadProcessWithVulkan(uint8_t* data, int width, int height, int rowStride, size_t dataSize);
|
||||
extern void ReceiveFacePoint(float* pos, int count, int width, int height);
|
||||
|
||||
void processWithVulkan(uint8_t* data, int width, int height, int format,
|
||||
int rowStride, size_t dataSize) {
|
||||
TextureLoadProcessWithVulkan(data, width, height, rowStride, dataSize);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hmwl_sample_MainActivity_processImageNative(JNIEnv *env, jobject thiz, jobject buffer,
|
||||
jint width, jint height, jint format,
|
||||
jint row_stride, jint pixel_stride,
|
||||
jint rotation) {
|
||||
|
||||
uint8_t* imageData = static_cast<uint8_t*>(env->GetDirectBufferAddress(buffer));
|
||||
jlong capacity = env->GetDirectBufferCapacity(buffer);
|
||||
|
||||
if (imageData == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 在这里将图像数据传递给 Vulkan
|
||||
// 创建 Vulkan 图像或更新现有图像
|
||||
processWithVulkan(imageData, width, height, format, row_stride, capacity);
|
||||
}
|
||||
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hmwl_sample_MainActivity_passDataToNative(JNIEnv *env, jobject thiz, jobject buffer,
|
||||
jint point_count, jint width, jint height) {
|
||||
float* pos = static_cast<float*>(env->GetDirectBufferAddress(buffer));
|
||||
|
||||
if (pos == nullptr) {
|
||||
// 处理错误
|
||||
return;
|
||||
}
|
||||
|
||||
// 或者直接将指针传递给Vulkan
|
||||
ReceiveFacePoint(pos, point_count, width, height);
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
|
||||
package com.hmwl.sample;
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Matrix
|
||||
import android.media.MediaMetadataRetriever
|
||||
import android.net.Uri
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.camera.core.ImageProxy
|
||||
import com.google.mediapipe.framework.image.BitmapImageBuilder
|
||||
import com.google.mediapipe.framework.image.MPImage
|
||||
import com.google.mediapipe.tasks.core.BaseOptions
|
||||
import com.google.mediapipe.tasks.core.Delegate
|
||||
import com.google.mediapipe.tasks.vision.core.RunningMode
|
||||
import com.google.mediapipe.tasks.vision.facelandmarker.FaceLandmarker
|
||||
import com.google.mediapipe.tasks.vision.facelandmarker.FaceLandmarkerResult
|
||||
|
||||
class FaceLandmarkerHelper(
|
||||
var minFaceDetectionConfidence: Float = DEFAULT_FACE_DETECTION_CONFIDENCE,
|
||||
var minFaceTrackingConfidence: Float = DEFAULT_FACE_TRACKING_CONFIDENCE,
|
||||
var minFacePresenceConfidence: Float = DEFAULT_FACE_PRESENCE_CONFIDENCE,
|
||||
var maxNumFaces: Int = DEFAULT_NUM_FACES,
|
||||
var currentDelegate: Int = DELEGATE_CPU,
|
||||
var runningMode: RunningMode = RunningMode.IMAGE,
|
||||
val context: Context,
|
||||
// this listener is only used when running in RunningMode.LIVE_STREAM
|
||||
val faceLandmarkerHelperListener: LandmarkerListener? = null
|
||||
) {
|
||||
|
||||
// For this example this needs to be a var so it can be reset on changes.
|
||||
// If the Face Landmarker will not change, a lazy val would be preferable.
|
||||
private var faceLandmarker: FaceLandmarker? = null
|
||||
|
||||
init {
|
||||
setupFaceLandmarker()
|
||||
}
|
||||
|
||||
fun clearFaceLandmarker() {
|
||||
faceLandmarker?.close()
|
||||
faceLandmarker = null
|
||||
}
|
||||
|
||||
// Return running status of FaceLandmarkerHelper
|
||||
fun isClose(): Boolean {
|
||||
return faceLandmarker == null
|
||||
}
|
||||
|
||||
fun setupFaceLandmarker() {
|
||||
// Set general face landmarker options
|
||||
val baseOptionBuilder = BaseOptions.builder()
|
||||
|
||||
// Use the specified hardware for running the model. Default to CPU
|
||||
when (currentDelegate) {
|
||||
DELEGATE_CPU -> {
|
||||
baseOptionBuilder.setDelegate(Delegate.CPU)
|
||||
}
|
||||
DELEGATE_GPU -> {
|
||||
baseOptionBuilder.setDelegate(Delegate.GPU)
|
||||
}
|
||||
}
|
||||
|
||||
baseOptionBuilder.setModelAssetPath(MP_FACE_LANDMARKER_TASK)
|
||||
|
||||
// Check if runningMode is consistent with faceLandmarkerHelperListener
|
||||
when (runningMode) {
|
||||
RunningMode.LIVE_STREAM -> {
|
||||
if (faceLandmarkerHelperListener == null) {
|
||||
throw IllegalStateException(
|
||||
"faceLandmarkerHelperListener must be set when runningMode is LIVE_STREAM."
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
val baseOptions = baseOptionBuilder.build()
|
||||
// Create an option builder with base options and specific
|
||||
// options only use for Face Landmarker.
|
||||
val optionsBuilder =
|
||||
FaceLandmarker.FaceLandmarkerOptions.builder()
|
||||
.setBaseOptions(baseOptions)
|
||||
.setMinFaceDetectionConfidence(minFaceDetectionConfidence)
|
||||
.setMinTrackingConfidence(minFaceTrackingConfidence)
|
||||
.setMinFacePresenceConfidence(minFacePresenceConfidence)
|
||||
.setNumFaces(maxNumFaces)
|
||||
.setOutputFaceBlendshapes(false)
|
||||
.setOutputFacialTransformationMatrixes(false)
|
||||
.setRunningMode(runningMode)
|
||||
|
||||
// The ResultListener and ErrorListener only use for LIVE_STREAM mode.
|
||||
if (runningMode == RunningMode.LIVE_STREAM) {
|
||||
optionsBuilder
|
||||
.setResultListener(this::returnLivestreamResult)
|
||||
.setErrorListener(this::returnLivestreamError)
|
||||
}
|
||||
|
||||
val options = optionsBuilder.build()
|
||||
faceLandmarker =
|
||||
FaceLandmarker.createFromOptions(context, options)
|
||||
} catch (e: IllegalStateException) {
|
||||
faceLandmarkerHelperListener?.onError(
|
||||
"Face Landmarker failed to initialize. See error logs for " +
|
||||
"details"
|
||||
)
|
||||
Log.e(
|
||||
TAG, "MediaPipe failed to load the task with error: " + e
|
||||
.message
|
||||
)
|
||||
} catch (e: RuntimeException) {
|
||||
// This occurs if the model being used does not support GPU
|
||||
faceLandmarkerHelperListener?.onError(
|
||||
"Face Landmarker failed to initialize. See error logs for " +
|
||||
"details", GPU_ERROR
|
||||
)
|
||||
Log.e(
|
||||
TAG,
|
||||
"Face Landmarker failed to load model with error: " + e.message
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private var frameId: Long = 0
|
||||
// 简化的延时跟踪
|
||||
private val frameTimings = mutableMapOf<Long, FrameTiming>()
|
||||
private var lastFpsUpdateTime: Long = 0
|
||||
private var frameCount: Int = 0
|
||||
private var currentFps: Double = 0.0
|
||||
|
||||
// 内部数据类用于存储时间信息
|
||||
private data class FrameTiming(
|
||||
var captureTime: Long = 0,
|
||||
var preprocessStartTime: Long = 0,
|
||||
var preprocessEndTime: Long = 0,
|
||||
var detectionStartTime: Long = 0
|
||||
)
|
||||
|
||||
// 辅助方法:记录帧时间信息
|
||||
private fun recordFrameTiming(frameId: Long, block: (FrameTiming) -> Unit) {
|
||||
val timing = frameTimings.getOrPut(frameId) { FrameTiming() }
|
||||
block(timing)
|
||||
}
|
||||
|
||||
// 辅助方法:更新FPS计算
|
||||
private fun updateFps() {
|
||||
frameCount++
|
||||
val currentTime = SystemClock.uptimeMillis()
|
||||
|
||||
if (lastFpsUpdateTime == 0L) {
|
||||
lastFpsUpdateTime = currentTime
|
||||
return
|
||||
}
|
||||
|
||||
val timeDiff = currentTime - lastFpsUpdateTime
|
||||
if (timeDiff >= 1000) { // 每秒更新一次FPS
|
||||
currentFps = frameCount * 1000.0 / timeDiff
|
||||
frameCount = 0
|
||||
lastFpsUpdateTime = currentTime
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助方法:清理旧的时间记录
|
||||
private fun cleanupOldTimings(currentFrameId: Long) {
|
||||
val framesToRemove = frameTimings.keys.filter { it < currentFrameId - 10 }
|
||||
framesToRemove.forEach { frameTimings.remove(it) }
|
||||
}
|
||||
|
||||
// Convert the ImageProxy to MP Image and feed it to FacelandmakerHelper.
|
||||
fun detectLiveStream(
|
||||
imageProxy: ImageProxy,
|
||||
isFrontCamera: Boolean
|
||||
) {
|
||||
if (runningMode != RunningMode.LIVE_STREAM) {
|
||||
throw IllegalArgumentException(
|
||||
"Attempting to call detectLiveStream" +
|
||||
" while not using RunningMode.LIVE_STREAM"
|
||||
)
|
||||
}
|
||||
val frameTime = SystemClock.uptimeMillis()
|
||||
|
||||
val currentFrameId = frameId++
|
||||
val captureTime = SystemClock.uptimeMillis()
|
||||
|
||||
// 记录捕获时间
|
||||
recordFrameTiming(currentFrameId) {
|
||||
it.captureTime = captureTime
|
||||
it.preprocessStartTime = SystemClock.uptimeMillis()
|
||||
}
|
||||
|
||||
// Copy out RGB bits from the frame to a bitmap buffer
|
||||
val bitmapBuffer =
|
||||
Bitmap.createBitmap(
|
||||
imageProxy.width,
|
||||
imageProxy.height,
|
||||
Bitmap.Config.ARGB_8888
|
||||
)
|
||||
imageProxy.use { bitmapBuffer.copyPixelsFromBuffer(imageProxy.planes[0].buffer) }
|
||||
imageProxy.close()
|
||||
|
||||
val matrix = Matrix().apply {
|
||||
// Rotate the frame received from the camera to be in the same direction as it'll be shown
|
||||
postRotate(imageProxy.imageInfo.rotationDegrees.toFloat())
|
||||
|
||||
// flip image if user use front camera
|
||||
if (isFrontCamera) {
|
||||
postScale(
|
||||
-1f,
|
||||
1f,
|
||||
imageProxy.width.toFloat(),
|
||||
imageProxy.height.toFloat()
|
||||
)
|
||||
}
|
||||
}
|
||||
val rotatedBitmap = Bitmap.createBitmap(
|
||||
bitmapBuffer, (640-480)/2, 0, bitmapBuffer.height, bitmapBuffer.height,
|
||||
matrix, true
|
||||
)
|
||||
|
||||
//val bitmap = BitmapFactory.decodeFile("/sdcard/Android/data/com.khronos.vulkan_samples/files/assets/face480.png")
|
||||
//val mpImage = BitmapImageBuilder(bitmap).build()
|
||||
|
||||
// Convert the input Bitmap object to an MPImage object to run inference
|
||||
val mpImage = BitmapImageBuilder(rotatedBitmap).build()
|
||||
|
||||
detectAsync(mpImage, frameTime)
|
||||
}
|
||||
|
||||
// Run face face landmark using MediaPipe Face Landmarker API
|
||||
@VisibleForTesting
|
||||
fun detectAsync(mpImage: MPImage, frameTime: Long) {
|
||||
faceLandmarker?.detectAsync(mpImage, frameTime)
|
||||
// As we're using running mode LIVE_STREAM, the landmark result will
|
||||
// be returned in returnLivestreamResult function
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Return the landmark result to this FaceLandmarkerHelper's caller
|
||||
private fun returnLivestreamResult(
|
||||
result: FaceLandmarkerResult,
|
||||
input: MPImage
|
||||
) {
|
||||
val resultTime = SystemClock.uptimeMillis()
|
||||
val frameId = result.timestampMs()
|
||||
if( result.faceLandmarks().size > 0 ) {
|
||||
// 计算各种延时
|
||||
val timings = frameTimings[frameId]
|
||||
if (timings != null) {
|
||||
val totalLatency = resultTime - timings.captureTime
|
||||
val preprocessLatency = timings.preprocessEndTime - timings.preprocessStartTime
|
||||
val detectionLatency = resultTime - timings.detectionStartTime
|
||||
val captureToDetectionLatency = timings.detectionStartTime - timings.captureTime
|
||||
|
||||
Log.i("TimeLatency",
|
||||
"总延时: ${totalLatency}ms | " +
|
||||
"预处理: ${preprocessLatency}ms | " +
|
||||
"检测: ${detectionLatency}ms | " +
|
||||
"捕获到检测: ${captureToDetectionLatency}ms | " +
|
||||
"FPS: ${String.format("%.1f", currentFps)}"
|
||||
)
|
||||
|
||||
val finishTimeMs = SystemClock.uptimeMillis()
|
||||
val inferenceTime = finishTimeMs - result.timestampMs()
|
||||
faceLandmarkerHelperListener?.onResults(
|
||||
ResultBundle(
|
||||
result,
|
||||
inferenceTime, // 主要是检测时间
|
||||
input.height,
|
||||
input.width,
|
||||
)
|
||||
)
|
||||
} else {
|
||||
val finishTimeMs = SystemClock.uptimeMillis()
|
||||
val inferenceTime = finishTimeMs - result.timestampMs()
|
||||
Log.i("TimeLatency",
|
||||
"总延时: ${inferenceTime}ms | "
|
||||
)
|
||||
faceLandmarkerHelperListener?.onResults(
|
||||
ResultBundle(
|
||||
result,
|
||||
inferenceTime,
|
||||
input.height,
|
||||
input.width
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
else {
|
||||
faceLandmarkerHelperListener?.onEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
// Return errors thrown during detection to this FaceLandmarkerHelper's
|
||||
// caller
|
||||
private fun returnLivestreamError(error: RuntimeException) {
|
||||
faceLandmarkerHelperListener?.onError(
|
||||
error.message ?: "An unknown error has occurred"
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "FaceLandmarkerHelper"
|
||||
private const val MP_FACE_LANDMARKER_TASK = "face_landmarker.task"
|
||||
|
||||
const val DELEGATE_CPU = 0
|
||||
const val DELEGATE_GPU = 1
|
||||
const val DEFAULT_FACE_DETECTION_CONFIDENCE = 0.5F
|
||||
const val DEFAULT_FACE_TRACKING_CONFIDENCE = 0.5F
|
||||
const val DEFAULT_FACE_PRESENCE_CONFIDENCE = 0.5F
|
||||
const val DEFAULT_NUM_FACES = 1
|
||||
const val OTHER_ERROR = 0
|
||||
const val GPU_ERROR = 1
|
||||
}
|
||||
|
||||
data class ResultBundle(
|
||||
val result: FaceLandmarkerResult,
|
||||
val inferenceTime: Long,
|
||||
val inputImageHeight: Int,
|
||||
val inputImageWidth: Int,
|
||||
)
|
||||
|
||||
interface LandmarkerListener {
|
||||
fun onError(error: String, errorCode: Int = OTHER_ERROR)
|
||||
fun onResults(resultBundle: ResultBundle)
|
||||
|
||||
fun onEmpty() {}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
id 'org.jetbrains.kotlin.android' version '2.2.0' apply false
|
||||
}
|
||||
@@ -626,13 +626,13 @@ uint32_t Application::findMemoryType(VkPhysicalDevice physicalDevice, uint32_t t
|
||||
throw std::runtime_error("Failed to find suitable memory type!");
|
||||
}
|
||||
|
||||
Texture Application::loadTexture(std::string path, Texture& tex)
|
||||
Texture Application::loadTexture(std::string path, Texture& tex, bool srgb)
|
||||
{
|
||||
std::vector<unsigned char> data = readFileEx(path);
|
||||
std::vector<unsigned char> image;
|
||||
unsigned w, h;
|
||||
unsigned error = lodepng::decode(image, w, h, data, LCT_RGBA, 8);
|
||||
processWithVulkan(image.data(), w, h, w * 4, image.size(), tex, true);
|
||||
processWithVulkan(image.data(), w, h, w * 4, image.size(), tex, srgb);
|
||||
return tex;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ public:
|
||||
virtual void render(VkCommandBuffer commandBuffer);
|
||||
virtual bool isInited() { return inited; }
|
||||
|
||||
void processWithVulkan(uint8_t* data, int width, int height, int rowStride, size_t dataSize, Texture& out_texture, bool srgb);
|
||||
|
||||
protected:
|
||||
|
||||
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; // 物理设备
|
||||
@@ -69,10 +71,11 @@ protected:
|
||||
int currentFrame = 0;
|
||||
int MAX_FRAMES_IN_FLIGHT = 2;
|
||||
bool inited = false;
|
||||
|
||||
|
||||
protected:
|
||||
Texture loadTexture(std::string path, Texture& tex);
|
||||
void processWithVulkan(uint8_t* data, int width, int height, int rowStride, size_t dataSize, Texture& out_texture, bool srgb);
|
||||
Texture loadTexture(std::string path, Texture& tex, bool srgb);
|
||||
|
||||
uint32_t findMemoryType(VkPhysicalDevice physicalDevice, uint32_t typeFilter,
|
||||
VkMemoryPropertyFlags properties);
|
||||
void createTexture(VkDevice device, VkPhysicalDevice physicalDevice, int width, int height, Texture& texture, bool srgb);
|
||||
|
||||
+12
-2
@@ -24,6 +24,14 @@ void ReceiveFacePoint(float* pos, int pointCount, int width, int height)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void TextureLoadProcessWithVulkan(uint8_t* data, int width, int height, int rowStride, size_t dataSize)
|
||||
{
|
||||
Texture& tex_bg = FaceApp::Get()->tex_bg;
|
||||
FaceApp::Get()->processWithVulkan(data, width, height, rowStride, dataSize, tex_bg, false);
|
||||
}
|
||||
|
||||
bool FaceApp::LoadOBJ(const std::string& filename,
|
||||
std::vector<TextureLoadingVertexStructure>& vertices,
|
||||
std::vector<uint32_t>& indices) {
|
||||
@@ -477,6 +485,8 @@ void FaceApp::setup_descriptor_set()
|
||||
|
||||
void FaceApp::render(VkCommandBuffer commandBuffer)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mtx);
|
||||
std::unique_lock<std::mutex> lock_point(mtx_point);
|
||||
//Application::render(commandBuffer);
|
||||
|
||||
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout_bg, 0, 1, &m_descriptor_set_bg, 0, NULL);
|
||||
@@ -539,8 +549,8 @@ void FaceApp::initVulkan()
|
||||
Application::initVulkan();
|
||||
createVmaAllocator();
|
||||
LoadOBJ("face_picture_3dmax.obj", obj_vertices, obj_indices);
|
||||
loadTexture("demo0.png", tex_demo0);
|
||||
loadTexture("out.png", tex_bg);
|
||||
loadTexture("demo0.png", tex_demo0, true);
|
||||
loadTexture("out.png", tex_bg, false);
|
||||
createVertexBuffer();
|
||||
createUniformBuffer();
|
||||
setup_descriptor_pool();
|
||||
|
||||
+4
-1
@@ -44,6 +44,9 @@ public:
|
||||
|
||||
virtual bool isInited() override { return inited && faceAppInited; }
|
||||
virtual void cleanup() override;
|
||||
|
||||
Texture tex_bg = {};
|
||||
|
||||
private:
|
||||
glm::vec3 rotation = glm::vec3();
|
||||
glm::vec3 camera_pos = glm::vec3();
|
||||
@@ -103,7 +106,7 @@ private:
|
||||
float myFloatValue = 1.5f;
|
||||
|
||||
|
||||
Texture tex_bg = {};
|
||||
|
||||
void create_pipelines_bg();
|
||||
void setup_descriptor_set_layout_bg();
|
||||
void setup_descriptor_set_bg();
|
||||
|
||||
Reference in New Issue
Block a user