{ "cells": [ { "cell_type": "code", "execution_count": 1, "id": "97103d1f-2f48-4613-b438-862dbcb50312", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import pandas as pd\n", "import trimesh\n", "from scipy.spatial.distance import directed_hausdorff\n", "from skimage import measure\n", "import os\n", "\n", "def check_model_similarity(model1, model2, savedir):\n", " # 加载模型\n", " mesh1 = trimesh.load_mesh(model1)\n", " mesh2 = trimesh.load_mesh(model2)\n", "\n", " # 计算两个模型之间的欧氏距离\n", " distance = np.linalg.norm(mesh1.centroid - mesh2.centroid)\n", "\n", " # 计算重建模型中未被覆盖的原始模型的比例\n", " uncovered_ratio = 1 - mesh2.area / mesh1.area\n", "\n", " # 计算重建模型的法向量和原始模型的法向量的差值的绝对值\n", " normals_difference = np.abs(mesh1.face_normals.mean(axis=0) - mesh2.face_normals.mean(axis=0)).sum()\n", "\n", " # 计算Hausdorff Distance\n", " hausdorff_distance = max(directed_hausdorff(mesh1.vertices, mesh2.vertices)[0],\n", " directed_hausdorff(mesh2.vertices, mesh1.vertices)[0])\n", "\n", " # 计算3D Histogram Comparison(这里只是一个简化的例子,具体的比较方法可能需要根据你的需求进行调整)\n", " hist1, _ = np.histogramdd(mesh1.vertices, bins=(30, 30, 30))\n", " hist2, _ = np.histogramdd(mesh2.vertices, bins=(30, 30, 30))\n", " hist_difference = np.abs(hist1 - hist2).sum()\n", "\n", "\n", " # 创建一个DataFrame来存储结果\n", " df = pd.DataFrame({\n", " 'Model1': [model1],\n", " 'Model2': [model2],\n", " 'Euclidean Distance': [distance],\n", " 'Uncovered Ratio': [uncovered_ratio],\n", " 'Normals Difference': [normals_difference],\n", " 'Hausdorff Distance': [hausdorff_distance],\n", " 'Histogram Difference': [hist_difference],\n", " })\n", "\n", " # 将结果保存到Excel文件\n", " df.to_excel(savedir + 'model_comparison.xlsx', index=False)\n", "def check_dir_models_similarity(target_model, model_dir, savedir):\n", " # 加载目标模型\n", " mesh_target = trimesh.load_mesh(target_model)\n", " \n", " # 获取文件夹中所有的模型文件\n", " models = [f for f in os.listdir(model_dir) if f.endswith('.stl') or f.endswith('.ply')] # 根据你的模型文件类型来修改\n", "\n", " result_list = []\n", " for model in models:\n", " # 加载模型\n", " mesh = trimesh.load_mesh(os.path.join(model_dir, model))\n", "\n", " # 计算两个模型之间的欧氏距离\n", " distance = np.linalg.norm(mesh_target.centroid - mesh.centroid)\n", "\n", " # 计算重建模型中未被覆盖的原始模型的比例\n", " uncovered_ratio = 1 - mesh.area / mesh_target.area\n", "\n", " # 计算重建模型的法向量和原始模型的法向量的差值的绝对值\n", " normals_difference = np.abs(mesh_target.face_normals.mean(axis=0) - mesh.face_normals.mean(axis=0)).sum()\n", "\n", " # 计算Hausdorff Distance\n", " hausdorff_distance = max(directed_hausdorff(mesh_target.vertices, mesh.vertices)[0],\n", " directed_hausdorff(mesh.vertices, mesh_target.vertices)[0])\n", "\n", " # 计算3D Histogram Comparison\n", " hist_target, _ = np.histogramdd(mesh_target.vertices, bins=(30, 30, 30))\n", " hist, _ = np.histogramdd(mesh.vertices, bins=(30, 30, 30))\n", " hist_difference = np.abs(hist_target - hist).sum()\n", "\n", " result_list.append({\n", " 'Model': model,\n", " 'Euclidean Distance': distance,\n", " 'Uncovered Ratio': uncovered_ratio,\n", " 'Normals Difference': normals_difference,\n", " 'Hausdorff Distance': hausdorff_distance,\n", " 'Histogram Difference': hist_difference,\n", " })\n", " \n", " # 创建一个DataFrame来存储结果\n", " df = pd.DataFrame(result_list)\n", "\n", " # 将结果保存到Excel文件\n", " df.to_excel(savedir + 'model_comparison.xlsx', index=False)" ] }, { "cell_type": "code", "execution_count": 2, "id": "98d18315-3d93-44e7-9c0c-4e94131fc568", "metadata": {}, "outputs": [], "source": [ "#运行检查模型相似度的函数,target_model是基准模型的地址,model_dir是和基准模型对比的模型的地址,savedir是保存检测结果的地址\n", "check_dir_models_similarity('928\\\\modelcompare\\\\e0\\\\0.ply', '928\\\\modelcompare\\\\e\\\\', '928\\\\modelcompare\\\\e\\\\')" ] }, { "cell_type": "code", "execution_count": 27, "id": "d6c299f1-f88a-4582-b340-748e0b10b983", "metadata": {}, "outputs": [], "source": [ "import os\n", "import pandas as pd\n", "from open3d import io as o3d_io\n", "import numpy as np\n", "\n", "def check_model_quality(modelsdir, savedir):\n", " if not os.path.exists(savedir):\n", " os.makedirs(savedir)\n", " # 创建一个空的DataFrame来存储结果\n", " result_df = pd.DataFrame(columns=['文件名', '法向量平均值', '法向量差异', '孤立顶点数量', '无效边数量', '相交面数量', '顶点数量'])\n", "\n", " # 遍历modelsdir中的所有文件\n", " for filename in os.listdir(modelsdir):\n", " # 只处理ply文件\n", " if filename.endswith(\".ply\"):\n", " # 读取模型\n", " model = o3d_io.read_triangle_mesh(os.path.join(modelsdir, filename))\n", " \n", " # 计算法向量平均值和差异\n", " avg_normal = np.mean(model.vertex_normals, axis=0)\n", " diff_normal = np.std(model.vertex_normals - avg_normal)\n", "\n", " # 计算孤立顶点数量、无效边数量、相交面数量\n", " # 这里需要您根据实际情况编写相应的函数\n", " isolated_vertices = 0\n", " invalid_edges = 0\n", " intersecting_faces = 0\n", "\n", " # 计算顶点数量\n", " vertices_array = np.asarray(model.vertices)\n", " vertices_in_cube = np.sum(\n", " (vertices_array >= 0) & (vertices_array <= 0.1),\n", " axis=0 # 在每个坐标上分别计算\n", " ).min() # 选择最小的数量,因为我们需要在所有坐标上都满足条件\n", "\n", " # 将结果添加到DataFrame中\n", " result_df = pd.concat([result_df, pd.DataFrame({\n", " '文件名': [filename],\n", " '法向量平均值': [avg_normal],\n", " '法向量差异': [diff_normal],\n", " '孤立顶点数量': [isolated_vertices],\n", " '无效边数量': [invalid_edges],\n", " '相交面数量': [intersecting_faces],\n", " '顶点数量': [vertices_in_cube]\n", " })], ignore_index=True)\n", "\n", " # 将结果保存到csv文件中\n", " result_df.to_csv(os.path.join(savedir, 'quality_report.csv'), index=False,encoding='gbk')\n" ] }, { "cell_type": "code", "execution_count": 17, "id": "030dd624-b910-4040-890f-b6c3471617fb", "metadata": {}, "outputs": [], "source": [ "check_model_similarity('928\\\\modelcompare\\\\b\\\\b0.ply', '928\\\\modelcompare\\\\b\\\\b2.ply', '928\\\\modelcompare\\\\b\\\\')\n" ] }, { "cell_type": "code", "execution_count": 11, "id": "03339287-7351-4f32-84e3-665a2a4d220c", "metadata": {}, "outputs": [ { "ename": "ImportError", "evalue": "cannot import name 'shape_context' from 'skimage.measure' (D:\\SubDiffusion\\venv\\lib\\site-packages\\skimage\\measure\\__init__.py)", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mImportError\u001b[0m Traceback (most recent call last)", "Cell \u001b[1;32mIn[11], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mskimage\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mmeasure\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m shape_context\n\u001b[0;32m 2\u001b[0m sc \u001b[38;5;241m=\u001b[39m shape_context\u001b[38;5;241m.\u001b[39mShapeContext()\n", "\u001b[1;31mImportError\u001b[0m: cannot import name 'shape_context' from 'skimage.measure' (D:\\SubDiffusion\\venv\\lib\\site-packages\\skimage\\measure\\__init__.py)" ] } ], "source": [ "from skimage.measure import shape_context\n", "sc = shape_context.ShapeContext()" ] }, { "cell_type": "code", "execution_count": 32, "id": "ab723713-9244-46ac-807b-e9cd064e4197", "metadata": { "scrolled": true }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "D:\\SubDiffusion\\venv\\lib\\site-packages\\numpy\\core\\fromnumeric.py:3504: RuntimeWarning: Mean of empty slice.\n", " return _methods._mean(a, axis=axis, dtype=dtype,\n", "D:\\SubDiffusion\\venv\\lib\\site-packages\\numpy\\core\\_methods.py:121: RuntimeWarning: invalid value encountered in divide\n", " ret = um.true_divide(\n", "D:\\SubDiffusion\\venv\\lib\\site-packages\\numpy\\core\\_methods.py:206: RuntimeWarning: Degrees of freedom <= 0 for slice\n", " ret = _var(a, axis=axis, dtype=dtype, out=out, ddof=ddof,\n", "D:\\SubDiffusion\\venv\\lib\\site-packages\\numpy\\core\\_methods.py:163: RuntimeWarning: invalid value encountered in divide\n", " arrmean = um.true_divide(arrmean, div, out=arrmean,\n", "D:\\SubDiffusion\\venv\\lib\\site-packages\\numpy\\core\\_methods.py:198: RuntimeWarning: invalid value encountered in scalar divide\n", " ret = ret.dtype.type(ret / rcount)\n", "C:\\Users\\胡逸飞\\AppData\\Local\\Temp\\ipykernel_9228\\3375845982.py:37: FutureWarning: The behavior of DataFrame concatenation with empty or all-NA entries is deprecated. In a future version, this will no longer exclude empty or all-NA columns when determining the result dtypes. To retain the old behavior, exclude the relevant entries before the concat operation.\n", " result_df = pd.concat([result_df, pd.DataFrame({\n" ] } ], "source": [ "check_model_quality('D:\\\\SubDiffusion\\\\928\\\\lunwen\\\\b\\\\mdoel', 'D:\\\\SubDiffusion\\\\928\\\\lunwen\\\\b\\\\excel')" ] }, { "cell_type": "code", "execution_count": 12, "id": "b2117192-647a-457e-998c-d9168a94259e", "metadata": {}, "outputs": [], "source": [ "model = o3d_io.read_triangle_mesh('D:\\\\SubDiffusion\\\\928\\\\lunwen\\\\b\\\\mdoel\\\\drill_0_1.ply')" ] }, { "cell_type": "code", "execution_count": 13, "id": "2d4516e7-372b-4373-a4a6-22ebc12cddcf", "metadata": {}, "outputs": [], "source": [ "avg_normal = np.mean(model.vertex_normals, axis=0)" ] }, { "cell_type": "code", "execution_count": 14, "id": "7ea93115-7759-4b21-8abb-c3c1c9f58462", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 0.00606041, -0.02948447, -0.01463793])" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "avg_normal" ] }, { "cell_type": "code", "execution_count": 15, "id": "f3c405fe-c413-4e6d-8f33-c10a07914717", "metadata": {}, "outputs": [], "source": [ "diff_normal = np.std(model.vertex_normals - avg_normal)" ] }, { "cell_type": "code", "execution_count": 16, "id": "1e1335e5-ec3b-47ef-b8c1-db4179b1369e", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0.5770267666909921" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "diff_normal" ] }, { "cell_type": "code", "execution_count": 20, "id": "0542de38-511a-4c4c-b697-5165070f9150", "metadata": {}, "outputs": [], "source": [ "vertices_array = np.asarray(model.vertices)" ] }, { "cell_type": "code", "execution_count": 21, "id": "34bc713d-077d-4181-b55d-1a24ded2b0da", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[-0.9866792 , 0.98449612, -0.75193799],\n", " [-0.98875535, 0.98449612, -0.7364341 ],\n", " [-0.98973423, 0.98449612, -0.72093022],\n", " ...,\n", " [ 0.96899223, 0.98449612, -0.45131803],\n", " [ 0.98449612, 0.98449612, -0.99188083],\n", " [ 0.98449612, 0.98449612, -0.45086342]])" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "vertices_array" ] }, { "cell_type": "code", "execution_count": 24, "id": "a7a1a534-c499-4cf9-8036-bcfd98d5f75d", "metadata": {}, "outputs": [], "source": [ "vertices_in_cube = np.sum((vertices_array >= 0) & (vertices_array <= 0.1))\n" ] }, { "cell_type": "code", "execution_count": 23, "id": "e2726a87-df35-45a9-9589-f8c8d79c976a", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "vertices_in_cube" ] }, { "cell_type": "code", "execution_count": null, "id": "abf0fcb1-bb1d-452d-ac9a-1bc5f3510e5b", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.8" } }, "nbformat": 4, "nbformat_minor": 5 }