Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96c24da077 | ||
|
|
ee9e70c53a | ||
|
|
bfb061d88a | ||
|
|
e85f78ece2 | ||
|
|
e6f7d1efb3 | ||
|
|
4a45c4dd43 | ||
|
|
ca4bb5c34b | ||
|
|
42cd8265f5 | ||
|
|
fd25180989 | ||
|
|
b55d4b9600 | ||
|
|
463b4724c9 | ||
|
|
5e851505bb | ||
|
|
5b9ca62976 | ||
|
|
346de14e00 | ||
|
|
8d1f34604e | ||
|
|
3f4dcfa0a7 |
|
After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 139 KiB |
|
After Width: | Height: | Size: 139 KiB |
@@ -17,6 +17,7 @@ package com.khronos.vulkan_samples
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Matrix
|
||||
import android.media.MediaMetadataRetriever
|
||||
import android.net.Uri
|
||||
@@ -103,7 +104,8 @@ class FaceLandmarkerHelper(
|
||||
.setMinTrackingConfidence(minFaceTrackingConfidence)
|
||||
.setMinFacePresenceConfidence(minFacePresenceConfidence)
|
||||
.setNumFaces(maxNumFaces)
|
||||
.setOutputFaceBlendshapes(true)
|
||||
.setOutputFaceBlendshapes(false)
|
||||
.setOutputFacialTransformationMatrixes(false)
|
||||
.setRunningMode(runningMode)
|
||||
|
||||
// The ResultListener and ErrorListener only use for LIVE_STREAM mode.
|
||||
@@ -138,6 +140,51 @@ class FaceLandmarkerHelper(
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -151,6 +198,15 @@ class FaceLandmarkerHelper(
|
||||
}
|
||||
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(
|
||||
@@ -180,6 +236,9 @@ class FaceLandmarkerHelper(
|
||||
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()
|
||||
|
||||
@@ -194,152 +253,57 @@ class FaceLandmarkerHelper(
|
||||
// be returned in returnLivestreamResult function
|
||||
}
|
||||
|
||||
// Accepts the URI for a video file loaded from the user's gallery and attempts to run
|
||||
// face landmarker inference on the video. This process will evaluate every
|
||||
// frame in the video and attach the results to a bundle that will be
|
||||
// returned.
|
||||
fun detectVideoFile(
|
||||
videoUri: Uri,
|
||||
inferenceIntervalMs: Long
|
||||
): VideoResultBundle? {
|
||||
if (runningMode != RunningMode.VIDEO) {
|
||||
throw IllegalArgumentException(
|
||||
"Attempting to call detectVideoFile" +
|
||||
" while not using RunningMode.VIDEO"
|
||||
)
|
||||
}
|
||||
|
||||
// Inference time is the difference between the system time at the start and finish of the
|
||||
// process
|
||||
val startTime = SystemClock.uptimeMillis()
|
||||
|
||||
var didErrorOccurred = false
|
||||
|
||||
// Load frames from the video and run the face landmarker.
|
||||
val retriever = MediaMetadataRetriever()
|
||||
retriever.setDataSource(context, videoUri)
|
||||
val videoLengthMs =
|
||||
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
|
||||
?.toLong()
|
||||
|
||||
// Note: We need to read width/height from frame instead of getting the width/height
|
||||
// of the video directly because MediaRetriever returns frames that are smaller than the
|
||||
// actual dimension of the video file.
|
||||
val firstFrame = retriever.getFrameAtTime(0)
|
||||
val width = firstFrame?.width
|
||||
val height = firstFrame?.height
|
||||
|
||||
// If the video is invalid, returns a null detection result
|
||||
if ((videoLengthMs == null) || (width == null) || (height == null)) return null
|
||||
|
||||
// Next, we'll get one frame every frameInterval ms, then run detection on these frames.
|
||||
val resultList = mutableListOf<FaceLandmarkerResult>()
|
||||
val numberOfFrameToRead = videoLengthMs.div(inferenceIntervalMs)
|
||||
|
||||
for (i in 0..numberOfFrameToRead) {
|
||||
val timestampMs = i * inferenceIntervalMs // ms
|
||||
|
||||
retriever
|
||||
.getFrameAtTime(
|
||||
timestampMs * 1000, // convert from ms to micro-s
|
||||
MediaMetadataRetriever.OPTION_CLOSEST
|
||||
)
|
||||
?.let { frame ->
|
||||
// Convert the video frame to ARGB_8888 which is required by the MediaPipe
|
||||
val argb8888Frame =
|
||||
if (frame.config == Bitmap.Config.ARGB_8888) frame
|
||||
else frame.copy(Bitmap.Config.ARGB_8888, false)
|
||||
|
||||
// Convert the input Bitmap object to an MPImage object to run inference
|
||||
val mpImage = BitmapImageBuilder(argb8888Frame).build()
|
||||
|
||||
// Run face landmarker using MediaPipe Face Landmarker API
|
||||
faceLandmarker?.detectForVideo(mpImage, timestampMs)
|
||||
?.let { detectionResult ->
|
||||
resultList.add(detectionResult)
|
||||
} ?: {
|
||||
didErrorOccurred = true
|
||||
faceLandmarkerHelperListener?.onError(
|
||||
"ResultBundle could not be returned" +
|
||||
" in detectVideoFile"
|
||||
)
|
||||
}
|
||||
}
|
||||
?: run {
|
||||
didErrorOccurred = true
|
||||
faceLandmarkerHelperListener?.onError(
|
||||
"Frame at specified time could not be" +
|
||||
" retrieved when detecting in video."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
retriever.release()
|
||||
|
||||
val inferenceTimePerFrameMs =
|
||||
(SystemClock.uptimeMillis() - startTime).div(numberOfFrameToRead)
|
||||
|
||||
return if (didErrorOccurred) {
|
||||
null
|
||||
} else {
|
||||
VideoResultBundle(resultList, inferenceTimePerFrameMs, height, width)
|
||||
}
|
||||
}
|
||||
|
||||
// Accepted a Bitmap and runs face landmarker inference on it to return
|
||||
// results back to the caller
|
||||
fun detectImage(image: Bitmap): ResultBundle? {
|
||||
if (runningMode != RunningMode.IMAGE) {
|
||||
throw IllegalArgumentException(
|
||||
"Attempting to call detectImage" +
|
||||
" while not using RunningMode.IMAGE"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// Inference time is the difference between the system time at the
|
||||
// start and finish of the process
|
||||
val startTime = SystemClock.uptimeMillis()
|
||||
|
||||
// Convert the input Bitmap object to an MPImage object to run inference
|
||||
val mpImage = BitmapImageBuilder(image).build()
|
||||
|
||||
// Run face landmarker using MediaPipe Face Landmarker API
|
||||
faceLandmarker?.detect(mpImage)?.also { landmarkResult ->
|
||||
val inferenceTimeMs = SystemClock.uptimeMillis() - startTime
|
||||
return ResultBundle(
|
||||
landmarkResult,
|
||||
inferenceTimeMs,
|
||||
image.height,
|
||||
image.width
|
||||
)
|
||||
}
|
||||
|
||||
// If faceLandmarker?.detect() returns null, this is likely an error. Returning null
|
||||
// to indicate this.
|
||||
faceLandmarkerHelperListener?.onError(
|
||||
"Face Landmarker failed to detect."
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
// 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 finishTimeMs = SystemClock.uptimeMillis()
|
||||
val inferenceTime = finishTimeMs - result.timestampMs()
|
||||
// 计算各种延时
|
||||
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
|
||||
|
||||
faceLandmarkerHelperListener?.onResults(
|
||||
ResultBundle(
|
||||
result,
|
||||
inferenceTime,
|
||||
input.height,
|
||||
input.width
|
||||
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()
|
||||
@@ -375,13 +339,6 @@ class FaceLandmarkerHelper(
|
||||
val inputImageWidth: Int,
|
||||
)
|
||||
|
||||
data class VideoResultBundle(
|
||||
val results: List<FaceLandmarkerResult>,
|
||||
val inferenceTime: Long,
|
||||
val inputImageHeight: Int,
|
||||
val inputImageWidth: Int,
|
||||
)
|
||||
|
||||
interface LandmarkerListener {
|
||||
fun onError(error: String, errorCode: Int = OTHER_ERROR)
|
||||
fun onResults(resultBundle: ResultBundle)
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
@@ -36,6 +37,7 @@ 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, CameraDataFetcher.CameraDataCallback {
|
||||
private ProcessCameraProvider cameraProvider;
|
||||
@@ -66,8 +68,8 @@ public class MainActivity extends GameActivity implements FaceLandmarkerHelper.L
|
||||
// return;
|
||||
// }
|
||||
|
||||
AssetsCopyUtil.copyAssetsToAppFiles(this, "assets");
|
||||
AssetsCopyUtil.copyAssetsToAppFiles(this, "shaders");
|
||||
//AssetsCopyUtil.copyAssetsToAppFiles(this, "assets");
|
||||
//AssetsCopyUtil.copyAssetsToAppFiles(this, "shaders");
|
||||
|
||||
|
||||
String native_lib_name = getResources().getString(R.string.native_lib_name);
|
||||
@@ -105,16 +107,16 @@ public class MainActivity extends GameActivity implements FaceLandmarkerHelper.L
|
||||
});
|
||||
|
||||
// 初始化Runnable
|
||||
cameraDataRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
fetchCameraData();
|
||||
handler.postDelayed(this, INTERVAL); // 再次调度
|
||||
}
|
||||
};
|
||||
// cameraDataRunnable = new Runnable() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
// fetchCameraData();
|
||||
// handler.postDelayed(this, INTERVAL); // 再次调度
|
||||
// }
|
||||
// };
|
||||
|
||||
// 开始周期性调用
|
||||
startPeriodicFetching();
|
||||
// startPeriodicFetching();
|
||||
|
||||
|
||||
Log.i("MainActivity", "onCreate: ");
|
||||
@@ -178,13 +180,45 @@ public class MainActivity extends GameActivity implements FaceLandmarkerHelper.L
|
||||
|
||||
|
||||
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 {
|
||||
//Log.d("Camera", "Received image: " + image.getWidth() + "x" + image.getHeight());
|
||||
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);
|
||||
detectFace(image);
|
||||
if(frameCount %2 == 0)
|
||||
{
|
||||
detectFace(image);
|
||||
}
|
||||
else
|
||||
{
|
||||
image.close();
|
||||
}
|
||||
} finally {
|
||||
//image.close(); // 确保在这里关闭
|
||||
//Log.d("Camera", "Image closed, ready for next frame");
|
||||
@@ -315,7 +349,7 @@ public class MainActivity extends GameActivity implements FaceLandmarkerHelper.L
|
||||
nativeBuffer.order(ByteOrder.nativeOrder());
|
||||
}
|
||||
|
||||
|
||||
long start_time = System.currentTimeMillis();
|
||||
|
||||
// ArrayList<Vector> vertexs = new ArrayList<>();
|
||||
// ArrayList<Integer> triangle_index = new ArrayList<>();
|
||||
@@ -325,8 +359,8 @@ public class MainActivity extends GameActivity implements FaceLandmarkerHelper.L
|
||||
// }
|
||||
|
||||
|
||||
// StringBuilder sb = new StringBuilder();
|
||||
// sb.append("[");
|
||||
//StringBuilder sb = new StringBuilder();
|
||||
//sb.append("[");
|
||||
|
||||
int index = 0;
|
||||
// Iterate through each detected face
|
||||
@@ -337,7 +371,6 @@ public class MainActivity extends GameActivity implements FaceLandmarkerHelper.L
|
||||
for (NormalizedLandmark p : faceLandmarks)
|
||||
{
|
||||
//sb.append("[");
|
||||
//Log.i(TAG, "Index" + index + " x:"+p.x() + " y:"+p.y() + " z:"+p.z());
|
||||
points[arrayIndex++] = p.x();
|
||||
//sb.append(""+p.x()+",");
|
||||
points[arrayIndex++] = p.y();
|
||||
@@ -359,7 +392,10 @@ public class MainActivity extends GameActivity implements FaceLandmarkerHelper.L
|
||||
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 float z_rate = -1.0f;
|
||||
|
||||
|
After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 139 KiB |
|
After Width: | Height: | Size: 139 KiB |
@@ -0,0 +1,161 @@
|
||||
import numpy as np
|
||||
|
||||
def read_obj_vertices(obj_file_path):
|
||||
"""读取OBJ文件中的顶点数据"""
|
||||
vertices = []
|
||||
with open(obj_file_path, 'r') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line.startswith('v ') and not line.startswith('vt ') and not line.startswith('vn '):
|
||||
parts = line.split()
|
||||
if len(parts) >= 4:
|
||||
# 提取顶点坐标,转换为浮点数
|
||||
vertex = [float(parts[1]), float(parts[2]), float(parts[3])]
|
||||
vertices.append(vertex)
|
||||
return np.array(vertices)
|
||||
|
||||
def find_vertex_mapping(original_vertices, modified_vertices, tolerance=1e-4):
|
||||
"""
|
||||
找到修改后顶点在原始顶点中的索引映射
|
||||
使用严格的数值匹配,匹配失败或重复匹配都会报错
|
||||
"""
|
||||
mapping = []
|
||||
used_indices = set()
|
||||
|
||||
for i, mod_vertex in enumerate(modified_vertices):
|
||||
matches = []
|
||||
|
||||
# 查找所有在容差范围内的匹配顶点
|
||||
for j, orig_vertex in enumerate(original_vertices):
|
||||
if np.allclose(mod_vertex, orig_vertex, atol=tolerance):
|
||||
matches.append(j)
|
||||
|
||||
# 检查匹配结果
|
||||
if len(matches) == 0:
|
||||
# 没有找到匹配
|
||||
print(f"错误: 无法为修改后顶点 {i} ({mod_vertex}) 找到匹配的原始顶点")
|
||||
print(f" 最近的原始顶点:")
|
||||
# 找到最近的几个顶点供参考
|
||||
distances = []
|
||||
for j, orig_vertex in enumerate(original_vertices):
|
||||
dist = np.linalg.norm(mod_vertex - orig_vertex)
|
||||
distances.append((dist, j, orig_vertex))
|
||||
distances.sort()
|
||||
for dist, j, vertex in distances[:3]: # 显示最近的3个
|
||||
print(f" 索引 {j}: {vertex}, 距离: {dist:.6f}")
|
||||
raise ValueError(f"顶点 {i} 匹配失败")
|
||||
|
||||
elif len(matches) > 1:
|
||||
# 找到多个匹配
|
||||
print(f"错误: 为修改后顶点 {i} ({mod_vertex}) 找到多个匹配的原始顶点:")
|
||||
for match_idx in matches:
|
||||
print(f" 原始顶点索引 {match_idx}: {original_vertices[match_idx]}")
|
||||
raise ValueError(f"顶点 {i} 匹配到多个原始顶点")
|
||||
|
||||
else:
|
||||
# 找到一个匹配
|
||||
match_idx = matches[0]
|
||||
if match_idx in used_indices:
|
||||
print(f"错误: 原始顶点 {match_idx} 已经被多个修改后顶点匹配")
|
||||
print(f" 当前修改后顶点 {i}: {mod_vertex}")
|
||||
# 找出哪个修改后顶点已经匹配了这个原始顶点
|
||||
for k, mapped_idx in enumerate(mapping):
|
||||
if mapped_idx == match_idx:
|
||||
print(f" 已经被修改后顶点 {k}: {modified_vertices[k]} 匹配")
|
||||
raise ValueError(f"原始顶点 {match_idx} 被重复匹配")
|
||||
|
||||
mapping.append(match_idx)
|
||||
used_indices.add(match_idx)
|
||||
|
||||
return mapping
|
||||
|
||||
def validate_mapping_completeness(mapping, original_vertices, modified_vertices):
|
||||
"""验证映射的完整性"""
|
||||
print("\n验证映射完整性...")
|
||||
|
||||
# 检查是否有未匹配的原始顶点
|
||||
all_original_indices = set(range(len(original_vertices)))
|
||||
used_original_indices = set(mapping)
|
||||
unused_original_indices = all_original_indices - used_original_indices
|
||||
|
||||
if unused_original_indices:
|
||||
print(f"警告: 有 {len(unused_original_indices)} 个原始顶点未被使用:")
|
||||
for idx in list(unused_original_indices)[:5]: # 只显示前5个
|
||||
print(f" 原始顶点 {idx}: {original_vertices[idx]}")
|
||||
if len(unused_original_indices) > 5:
|
||||
print(f" ... 还有 {len(unused_original_indices) - 5} 个")
|
||||
|
||||
# 检查顶点数量是否一致
|
||||
if len(modified_vertices) != len(original_vertices):
|
||||
print(f"警告: 顶点数量不一致 - 原始: {len(original_vertices)}, 修改后: {len(modified_vertices)}")
|
||||
|
||||
return len(unused_original_indices) == 0
|
||||
|
||||
def generate_header_file(mapping, output_file="map.h"):
|
||||
"""生成C++头文件"""
|
||||
with open(output_file, 'w') as f:
|
||||
f.write("#ifndef VERTEX_MAP_H\n")
|
||||
f.write("#define VERTEX_MAP_H\n\n")
|
||||
f.write("// 顶点索引映射表\n")
|
||||
f.write("// 用于将face_picture_3dmax.obj的顶点索引映射到face_picture.obj的顶点索引\n")
|
||||
f.write("// 生成说明: modified_vertex_index -> original_vertex_index\n")
|
||||
f.write("// 注意: 这个映射是严格的一对一匹配,匹配容差为1e-4\n")
|
||||
f.write(f"const int indexMap[{len(mapping)}] = {{\n")
|
||||
|
||||
# 每行输出10个元素
|
||||
for i in range(0, len(mapping), 10):
|
||||
line_indices = mapping[i:i+10]
|
||||
line = " " + ", ".join(f"{idx:3d}" for idx in line_indices)
|
||||
if i + 10 < len(mapping):
|
||||
line += ","
|
||||
f.write(line + "\n")
|
||||
|
||||
f.write("};\n\n")
|
||||
f.write("#endif // VERTEX_MAP_H\n")
|
||||
|
||||
def main():
|
||||
# 文件路径
|
||||
original_obj = "../assets/face_picture.obj"
|
||||
modified_obj = "../assets/face_picture_3dmax.obj"
|
||||
output_header = "map.h"
|
||||
|
||||
# 匹配容差 - 根据你的数据精度调整这个值
|
||||
tolerance = 1e-4
|
||||
|
||||
print("正在读取OBJ文件...")
|
||||
|
||||
try:
|
||||
# 读取顶点数据
|
||||
original_vertices = read_obj_vertices(original_obj)
|
||||
modified_vertices = read_obj_vertices(modified_obj)
|
||||
|
||||
print(f"原始文件顶点数: {len(original_vertices)}")
|
||||
print(f"修改文件顶点数: {len(modified_vertices)}")
|
||||
print(f"匹配容差: {tolerance}")
|
||||
|
||||
# 找到顶点映射
|
||||
print("正在严格匹配顶点...")
|
||||
mapping = find_vertex_mapping(original_vertices, modified_vertices, tolerance)
|
||||
|
||||
# 验证完整性
|
||||
is_complete = validate_mapping_completeness(mapping, original_vertices, modified_vertices)
|
||||
|
||||
if is_complete:
|
||||
print("✓ 所有原始顶点都被正确使用")
|
||||
else:
|
||||
print("⚠ 部分原始顶点未被使用")
|
||||
|
||||
print(f"✓ 成功匹配所有 {len(mapping)} 个顶点")
|
||||
|
||||
# 生成头文件
|
||||
print(f"生成头文件: {output_header}")
|
||||
generate_header_file(mapping, output_header)
|
||||
|
||||
print("\n完成!映射文件已生成。")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ 错误: {e}")
|
||||
print("映射生成失败,请检查数据或调整匹配容差")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,81 @@
|
||||
import json
|
||||
|
||||
def create_triangular_obj_from_json(vertices_file, indices_file, output_file):
|
||||
# 读取顶点数据
|
||||
with open(vertices_file, 'r') as f:
|
||||
vertices = json.load(f)
|
||||
|
||||
# 读取线索引数据
|
||||
with open(indices_file, 'r') as f:
|
||||
edges = json.load(f)
|
||||
|
||||
print(f"读取到 {len(vertices)} 个顶点")
|
||||
print(f"读取到 {len(edges)} 条边")
|
||||
|
||||
# 根据边的连接关系自动识别三角形面
|
||||
triangles = edges_to_triangles(edges)
|
||||
|
||||
|
||||
print(f"识别到 {len(triangles)} 个三角形面")
|
||||
|
||||
# 创建OBJ文件
|
||||
with open(output_file, 'w') as f:
|
||||
# 写入文件头
|
||||
f.write("# OBJ文件 - 三角形面网格\n")
|
||||
f.write(f"# 顶点数: {len(vertices)}, 面数: {len(triangles)}\n\n")
|
||||
|
||||
# 写入顶点信息 (v x y z)
|
||||
for i, vertex in enumerate(vertices):
|
||||
#f.write(f"# 顶点 {i}\n")
|
||||
f.write(f"v {vertex[0]:.6f} {vertex[1]:.6f} {vertex[2]:.6f}\n")
|
||||
|
||||
f.write("\n")
|
||||
|
||||
# 写入面信息 (f v1 v2 v3)
|
||||
for triangle in triangles:
|
||||
# OBJ文件索引从1开始,所以需要+1
|
||||
#f.write(f"# 面 {triangle[0]} {triangle[1]} {triangle[2]}\n")
|
||||
f.write(f"f {triangle[0]+1} {triangle[1]+1} {triangle[2]+1}\n")
|
||||
|
||||
def edges_to_triangles(edges):
|
||||
"""
|
||||
将edges数组转换为triangles数组
|
||||
每三个元素的第一值组成一个三角形
|
||||
|
||||
Args:
|
||||
edges: 从JSON读取的边数组
|
||||
|
||||
Returns:
|
||||
triangles: 三角形数组,每个元素包含三个点的索引
|
||||
"""
|
||||
triangles = []
|
||||
|
||||
# 确保边的数量是3的倍数(每个三角形由3条边组成)
|
||||
if len(edges) % 3 != 0:
|
||||
print(f"警告: 边的数量({len(edges)})不是3的倍数")
|
||||
|
||||
# 每3条边组成一个三角形,取每条边的第一个点
|
||||
for i in range(0, len(edges), 3):
|
||||
if i + 2 < len(edges): # 确保有足够的边
|
||||
triangle = [
|
||||
edges[i][0], # 第一条边的第一个点
|
||||
edges[i+1][0], # 第二条边的第一个点
|
||||
edges[i+2][0] # 第三条边的第一个点
|
||||
]
|
||||
triangles.append(triangle)
|
||||
|
||||
return triangles
|
||||
|
||||
|
||||
# 使用示例
|
||||
if __name__ == "__main__":
|
||||
# 输入文件
|
||||
vertices_file = "face_picture_point.json" # 顶点数据文件
|
||||
indices_file = "point_connection.json" # 线索引数据文件
|
||||
|
||||
# 输出文件
|
||||
output_file = "face_picture.obj" # 生成的OBJ文件
|
||||
|
||||
# 生成OBJ文件
|
||||
create_triangular_obj_from_json(vertices_file, indices_file, output_file)
|
||||
print(f"OBJ文件已生成: {output_file}")
|
||||
@@ -0,0 +1,58 @@
|
||||
#ifndef VERTEX_MAP_H
|
||||
#define VERTEX_MAP_H
|
||||
|
||||
// 顶点索引映射表
|
||||
// 用于将face_picture_3dmax.obj的顶点索引映射到face_picture.obj的顶点索引
|
||||
// 生成说明: modified_vertex_index -> original_vertex_index
|
||||
// 注意: 这个映射是严格的一对一匹配,匹配容差为1e-4
|
||||
const int indexMap[468] = {
|
||||
127, 34, 139, 11, 0, 37, 232, 231, 120, 72,
|
||||
39, 128, 121, 47, 104, 69, 67, 175, 171, 148,
|
||||
118, 50, 101, 73, 40, 9, 151, 108, 48, 115,
|
||||
131, 194, 204, 211, 74, 185, 80, 42, 183, 92,
|
||||
186, 230, 229, 202, 212, 214, 83, 18, 17, 76,
|
||||
61, 146, 160, 29, 30, 56, 157, 173, 106, 135,
|
||||
192, 203, 165, 98, 21, 71, 68, 51, 45, 4,
|
||||
144, 24, 23, 77, 91, 205, 187, 201, 200, 182,
|
||||
90, 181, 85, 84, 206, 36, 140, 193, 189, 244,
|
||||
159, 158, 28, 247, 246, 161, 236, 3, 196, 54,
|
||||
168, 8, 117, 228, 31, 55, 97, 99, 126, 100,
|
||||
166, 79, 218, 155, 154, 26, 209, 49, 136, 150,
|
||||
217, 223, 52, 53, 134, 170, 43, 119, 226, 130,
|
||||
63, 238, 20, 242, 46, 70, 156, 78, 62, 96,
|
||||
143, 227, 123, 111, 44, 125, 19, 216, 153, 22,
|
||||
167, 208, 142, 57, 60, 35, 113, 27, 210, 225,
|
||||
137, 116, 41, 38, 129, 64, 240, 102, 207, 184,
|
||||
169, 149, 176, 105, 66, 122, 6, 147, 65, 107,
|
||||
89, 180, 93, 15, 86, 14, 87, 145, 88, 179,
|
||||
95, 138, 172, 215, 58, 219, 81, 195, 199, 82,
|
||||
163, 110, 234, 109, 235, 191, 222, 141, 221, 197,
|
||||
25, 7, 33, 220, 237, 245, 162, 188, 174, 2,
|
||||
241, 164, 12, 13, 198, 133, 112, 243, 239, 190,
|
||||
32, 178, 132, 177, 1, 213, 59, 94, 75, 224,
|
||||
233, 114, 124, 356, 389, 368, 302, 267, 452, 350,
|
||||
349, 303, 269, 357, 343, 277, 453, 333, 332, 297,
|
||||
152, 377, 347, 348, 330, 304, 270, 336, 337, 278,
|
||||
279, 360, 418, 262, 431, 408, 409, 310, 415, 407,
|
||||
410, 450, 422, 430, 434, 313, 314, 306, 307, 375,
|
||||
387, 388, 260, 286, 414, 398, 335, 406, 364, 367,
|
||||
416, 423, 358, 327, 251, 284, 298, 281, 5, 373,
|
||||
374, 253, 320, 321, 425, 427, 411, 421, 405, 404,
|
||||
315, 16, 426, 266, 400, 369, 322, 391, 417, 465,
|
||||
464, 386, 257, 258, 466, 456, 399, 419, 285, 346,
|
||||
340, 261, 413, 441, 460, 328, 355, 371, 329, 392,
|
||||
439, 438, 382, 341, 256, 429, 420, 394, 379, 437,
|
||||
443, 444, 283, 275, 440, 363, 338, 273, 451, 446,
|
||||
342, 467, 293, 334, 282, 458, 461, 462, 276, 353,
|
||||
383, 308, 324, 325, 300, 372, 345, 447, 352, 274,
|
||||
248, 436, 381, 252, 393, 428, 287, 250, 384, 265,
|
||||
259, 424, 292, 366, 271, 294, 455, 272, 432, 395,
|
||||
299, 351, 280, 319, 295, 296, 403, 323, 454, 316,
|
||||
380, 318, 402, 365, 435, 397, 344, 311, 291, 396,
|
||||
268, 445, 254, 339, 449, 264, 10, 442, 370, 263,
|
||||
255, 359, 412, 301, 378, 326, 457, 362, 459, 463,
|
||||
354, 401, 361, 309, 376, 433, 289, 305, 448, 290,
|
||||
288, 249, 103, 385, 331, 317, 312, 390
|
||||
};
|
||||
|
||||
#endif // VERTEX_MAP_H
|
||||
@@ -49,7 +49,7 @@ public:
|
||||
uint32_t mip_levels;
|
||||
};
|
||||
|
||||
Texture texture = { 0 };
|
||||
Texture tex_demo0 = { 0 };
|
||||
Texture tex_demo1 = { 0 };
|
||||
Texture tex_demo2 = { 0 };
|
||||
Texture tex_demo3 = { 0 };
|
||||
@@ -183,8 +183,8 @@ public:
|
||||
virtual void view_changed() override;
|
||||
virtual void on_update_ui_overlay(vkb::Drawer& drawer) override;
|
||||
|
||||
void processWithVulkan(uint8_t* data, int width, int height, int rowStride, size_t dataSize, Texture& out_texture);
|
||||
void createTexture(VkDevice device, VkPhysicalDevice physicalDevice, int width, int height, Texture& texture);
|
||||
void processWithVulkan(uint8_t* data, int width, int height, int rowStride, size_t dataSize, Texture& out_texture, bool srgb);
|
||||
void createTexture(VkDevice device, VkPhysicalDevice physicalDevice, int width, int height, Texture& texture, bool srgb);
|
||||
void updateTexture(VkDevice device, VkPhysicalDevice physicalDevice, VkCommandPool commandPool, VkQueue queue, uint8_t* data, int width, int height, int rowStride, size_t dataSize, Texture& texture);
|
||||
uint32_t findMemoryType(VkPhysicalDevice physicalDevice, uint32_t typeFilter, VkMemoryPropertyFlags properties);
|
||||
VkCommandBuffer beginSingleTimeCommands(VkDevice device, VkCommandPool commandPool);
|
||||
@@ -206,6 +206,7 @@ private:
|
||||
std::vector<TextureLoadingVertexStructure> obj_vertices;
|
||||
std::vector<uint32_t> obj_indices;
|
||||
std::map<int, int> obj_vertices_map;
|
||||
std::map<int, int> vertices_map_3dmax;
|
||||
std::thread workerThread;
|
||||
bool running = false;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#version 450
|
||||
|
||||
layout (binding = 1) uniform sampler2D samplerColor;
|
||||
layout (binding = 2) uniform sampler2D samplerColor_ex;
|
||||
|
||||
layout (location = 0) in vec2 inUV;
|
||||
layout (location = 1) in vec3 inNormal;
|
||||
@@ -11,16 +12,27 @@ layout (location = 0) out vec4 outFragColor;
|
||||
void main()
|
||||
{
|
||||
vec4 color = texture(samplerColor, inUV);
|
||||
vec4 color_ex = texture(samplerColor_ex, inUV);
|
||||
|
||||
vec3 N = normalize(inNormal);
|
||||
|
||||
vec4 textureColor = color;
|
||||
if (textureColor.a < 0.1) {
|
||||
|
||||
//if (textureColor.a < 0.1)
|
||||
//{
|
||||
//textureColor = vec4(0.2, 0.2, 0.2, 0.5);
|
||||
discard;
|
||||
}
|
||||
// discard;
|
||||
//}
|
||||
|
||||
// ¼ÆËãÓë (0,0,1) µÄµã³Ë
|
||||
float ndot = dot(N, vec3(0.0, 0.0, -1.0));
|
||||
|
||||
// ±£Ö¤½á¹û >= 0
|
||||
ndot = max(ndot, 0.0);
|
||||
|
||||
outFragColor = textureColor;
|
||||
vec4 blendedColor = mix(color_ex, color, ndot);
|
||||
|
||||
outFragColor = blendedColor;
|
||||
|
||||
// outFragColor = vec4(1, 0, 0, 1);
|
||||
}
|
||||