How to create hand surface

#214 · closed · 23 comments

View on GitHub ↗

Skullface9512

Hi, through the Animation tutorial, I can change the vertice position in surface, but when i tried to translate five points, the show image is also three points, and the code is that: ``` int main(int argc, char **argv) { // initialize Easy3D. initialize(); //------------------------------------------------------------- // Create the default Easy3D viewer. // Note: a viewer must be created before creating any drawables. Viewer viewer(EXAMPLE_TITLE); //------------------------------------------------------------- //// We use the points and indices of the "bunny". //const std::vector<vec3> &points = resource::bunny_vertices; //// Each consecutive 3 indices represent a triangle. //const std::vector<unsigned int> &indices = resource::bunny_indices; double joint_3d[5][3] = { {2, -10, 1}, {-5, 2, 1}, {0, 0, 0}, {3, -3, 1}, {7, 2, -4} }; std::vector<vec3> vec1; vec1.reserve(5); for (int i = 0; i < 5; ++i) { vec1.emplace_back(joint_3d[i][0], joint_3d[i][1], joint_3d[i][2]); } const std::vector<vec3> &points = vec1; const std::vector<unsigned int>& indices = { 0, 1, 2, 3, 4 }; //------------------------------------------------------------- // Create a TrianglesDrawable to visualize the surface of the "bunny". viewer.set_background_color(vec4(0.6, 0.8, 0.8, 0.1)); auto surface = new TrianglesDrawable("faces"); surface->update_vertex_buffer(points, true); surface->update_element_buffer(indices); surface->set_uniform_coloring(vec4(0.1, 0.4, 0.8, 1.0)); viewer.add_drawable(std::shared_ptr<TrianglesDrawable>(surface)); viewer.fit_screen(); viewer.set_animation(true); // Define an animation function to specify how vertex positions are updated. // In this trivial example, we stretch the model along the Z-axis. viewer.animation_func_ = [&](easy3d::Viewer* v) -> bool { (void)v; // map the vertex buffer into the client's address space void* pointer = VertexArrayObject::map_buffer(GL_ARRAY_BUFFER, surface->vertex_buffer(), GL_WRITE_ONLY); vec3* vertices = reinterpret_cast<vec3*>(pointer); if (!vertices) return false; static float total_scale = 1.0f; float scale = 0.2f; if (total_scale > 1.5f) { scale = 1.0f / total_scale; total_scale = 1.0f; } else total_scale *= scale; for (std::size_t i = 0; i < points.size(); ++i) { /*vertices[i].z *= scale; vertices[i].x *= scale; vertices[i].y *= scale;*/ //vertices[i].z += 0.5; //vertices[i].x += 0.5; vertices[i].y -= 0.02; } // unmap the vertex buffer VertexArrayObject::unmap_buffer(GL_ARRAY_BUFFER, surface->vertex_buffer()); viewer.update(); return true; }; // run the viewer return viewer.run(); } ``` Do you know why happened this.

Comments

LiangliangNan

Because you're using a TrianglesDrawable, but the "const std::vector<unsigned int>& indices = { 0, 1, 2, 3, 4 }" contains vertex indices ONLY valid for 1 triangle. For your skeleton, I think you should use the following two types of drawables (instead of TrianglesDrawable): * LinesDrawable: draw the skeleton. It is actually a set of line segments. You should organize the index buffer such that each consecutive index pair denotes a line segment in the skeleton; * PointsDrawable: draw the set of vertices (better show them as spheres).

Skullface9512

If I want to draw hand mesh vertives, just like bunny in tutorial_311, which drawables should i use, and can you tell me how to do that. I'm so sorry for taking up your time.

LiangliangNan

You should use the following two types of drawables: - **LinesDrawable**: draw the skeleton. It is actually a set of line segments. You should organize the index buffer such that each consecutive index pair denotes a line segment in the skeleton; - **PointsDrawable**: draw the set of vertices (and show them as spheres).

Skullface9512

Yeah, i realize your thoughts, and I tried to add animation into Tutorials_301_Drawables, the main purpose is changing the position of vertices, and the skeleton follow the changes, the code is below: ``` int main(int argc, char** argv) { // initialize Easy3D. initialize(); Viewer viewer(EXAMPLE_TITLE); viewer.set_usage(""); //------------------------------------------------------------- double joint_3d[3][3] = { {0.0, 0.0, 0.0}, {0.5, 0.5, 0.5}, {-2, 0, 2} }; std::vector<vec3> vec1; vec1.reserve(21); for (int i = 0; i < 3; ++i) { vec1.emplace_back(joint_3d[i][0], joint_3d[i][1], joint_3d[i][2]); } const std::vector<vec3>& points = vec1; // The coordinates of the vertices. //const std::vector<vec3>& points = resource::bunny_vertices; // The indices represent how the vertices are connected to form triangles. The "bunny" is a triangle mesh, and thus // each consecutive three indices represent a triangle. //const std::vector<unsigned int>& indices = resource::bunny_indices; //const std::vector<unsigned int>& indices = { 1, 2, 3 }; //std::cout << "{ "; //for (auto it = indices.cbegin(); it != indices.cend(); ++it) { // std::cout << *it << " "; //} //std::cout << "}"; auto vertices = new PointsDrawable("vertices"); vertices->update_vertex_buffer(points); vertices->set_uniform_coloring(vec4(0.0f, 1.0f, 0.0f, 1.0f)); // RBGA vertices->set_impostor_type(PointsDrawable::SPHERE); vertices->set_point_size(20); viewer.add_drawable(std::shared_ptr<PointsDrawable>(vertices)); auto bbox_drawable = new LinesDrawable("bbox"); const std::vector<vec3> bbox_points = { vec3(joint_3d[0][0], joint_3d[0][1], joint_3d[0][2]), vec3(joint_3d[1][0], joint_3d[1][1], joint_3d[1][2]),vec3(joint_3d[2][0], joint_3d[2][1], joint_3d[2][2]) }; const std::vector<unsigned int> bbox_indices = { 0, 1, 0, 2, 1, 2 }; bbox_drawable->update_vertex_buffer(bbox_points); bbox_drawable->update_element_buffer(bbox_indices); bbox_drawable->set_uniform_coloring(vec4(0.0f, 0.0f, 1.0f, 1.0f)); // r, g, b, a bbox_drawable->set_line_width(5.0f); viewer.add_drawable(std::shared_ptr<LinesDrawable>(bbox_drawable)); viewer.fit_screen(); viewer.set_animation(true); viewer.animation_func_ = [&](easy3d::Viewer* v) -> bool { (void)v; // map the vertex buffer into the client's address space void* pointer = VertexArrayObject::map_buffer(GL_ARRAY_BUFFER, vertices->vertex_buffer(), GL_WRITE_ONLY); vec3* vertices = reinterpret_cast<vec3*>(pointer); if (!vertices) return false; static float total_scale = 1.0f; float scale = 0.2f; if (total_scale > 1.5f) { scale = 1.0f / total_scale; total_scale = 1.0f; } else total_scale *= scale; for (std::size_t i = 0; i < points.size(); ++i) { /*vertices[i].z *= scale; vertices[i].x *= scale; vertices[i].y *= scale;*/ //vertices[i].z += 0.5; //vertices[i].x += 0.5; vertices[i].y -= 0.2; } // unmap the vertex buffer VertexArrayObject::unmap_buffer(GL_ARRAY_BUFFER, vertices->vertex_buffer()); viewer.update(); return true; }; // run the viewer return viewer.run(); } ``` As you can see, in "VertexArrayObject::unmap_buffer(GL_ARRAY_BUFFER, vertices->vertex_buffer());" line, it indicates "easy3d::Vec<3Ui64,float>" have no member "vertex_buffer", can you give me some recommendation, thank you very much!

LiangliangNan

Sorry, I found it not affordable to have multiple iterations for teaching others to use Easy3D. I assume the users are confident with C++ and also know the basics of OpenGL. But if you want and okay to make your data public, I can create a demo for you (this would be easier for me). If you prefer this, please save each of your frames into a PLY file (if you already have data of the frames separately), and post the files here.

Skullface9512

Yes, as you say, I still have no enough experience in OpenGL, Here is the joints data in 50 frames. [https://drive.google.com/drive/folders/1LzwHP2Nsznv61j60Arr2FwWn-wnQtZuZ?dmr=1&ec=wgc-drive-globalnav-goto](url) And the following is the connection relationship between joints which represent skeletons, ``` edge = [ (0, 1), (1, 2), (2, 3), (3, 4), (0, 5), (5, 6), (6, 7), (7, 8), (5, 9), (9, 10), (10, 11), (11, 12), (9, 13), (13, 14), (14, 15), (15, 16), (13, 17), (17, 18), (18, 19), (19, 20), (0, 17), ] ``` Thank you for me such more recommendation.

LiangliangNan

The link doesn't work, and I cannot access it.

Skullface9512

sorry my fault, and you can try this link: [https://drive.google.com/file/d/17RJol8Wc-YJ1_uUNFVURmYLfIWX7JVvH/view?usp=sharing](url)

LiangliangNan

Below is the complete code: - change `const std::string data_dir = "/home/liangliang/Downloads/hand/";` to point to your actual path. **Note**: You need to ensure consistent file names. For example, change "file1.ply" to "file01.ply" so that all file names are of the same length, so they can be unambiguously sorted. An alternative is to save all frames into a single file (but I leave this implementation to you). - by default, it visualizes only the FIRST frame. - you can change `viewer.set_animation(false);` to `viewer.set_animation(true);` to visualize the animation. I didn't show the animation by default because I found your frames seem to be random, and they don't form a smooth sequence of animation. ![Image](https://github.com/user-attachments/assets/3001ff99-d700-4cca-a60c-0fd58b91a687) ```c++ #include <easy3d/viewer/viewer.h> #include <easy3d/renderer/drawable_points.h> #include <easy3d/renderer/drawable_lines.h> #include "easy3d/renderer/camera.h" #include <easy3d/util/initializer.h> #include "easy3d/util/file_system.h" #include <easy3d/fileio/ply_reader_writer.h> using namespace easy3d; int main(int argc, char **argv) { // initialize Easy3D. initialize(); //------------------------------------------------------------- // Create the default Easy3D viewer. // Note: a viewer must be created before creating any drawables. Viewer viewer(EXAMPLE_TITLE); //------------------------------------------------------------- // The directory containing the frames of the hand skeleton. // Note: each file contains the 21 vertices of the hand. const std::string data_dir = "/home/liangliang/Downloads/hand/"; std::vector<std::string> files; file_system::get_files(data_dir, files, false); std::sort(files.begin(), files.end()); typedef std::vector<vec3> Frame; std::vector<Frame> frames(files.size()); for (std::size_t i=0; i<files.size(); ++i) { const std::string file = data_dir + files[i]; // We could use the general PointCloudIO to load each file as a point cloud. // But for this special case, we use the PlyReader (to avoid loading each file into a point cloud model). io::PlyReader reader; std::vector<io::Element> elements; reader.read(file, elements); if (elements.size() == 1) // each file only contains vertices property. frames[i] = elements[0].vec3_properties[0]; } // To ensure all vertices are within the view frustum, compute the bounding box of all vertices across frames. Box3 box; for (const auto& frame : frames) for (const auto& v : frame) box.grow(v); viewer.camera()->setSceneBoundingBox(box.min_point(), box.max_point()); // Each consecutive 2 indices represent a line segment. const std::vector<unsigned int> indices = { 0, 1, 1, 2, 2, 3, 3, 4, 0, 5, 5, 6, 6, 7, 7, 8, 5, 9, 9, 10, 10, 11, 11, 12,9, 13, 13, 14, 14, 15, 15, 16, 13, 17, 17, 18, 18, 19, 19, 20, 0, 17 }; //------------------------------------------------------------- // We create two drawable to render the hand: // - a PointsDrawable for the vertices of the hand. // - a LinesDrawable for the skeleton of the hand. auto vertices = new PointsDrawable("vertices"); auto lines = new LinesDrawable("lines"); // Render vertices as spheres vertices->set_impostor_type(PointsDrawable::SPHERE); vertices->set_point_size(15.0f); vertices->set_color(vec4(0.8f,0.3f, 0.3f, 1.0f)); // Render lines as cylinders lines->set_impostor_type(LinesDrawable::CYLINDER); lines->set_line_width(5.0f); lines->set_color(vec4(0.3f,0.3f, 0.8f, 1.0f)); // Upload the vertice to GPU. The second argument is 'true' to allow dynamic update. vertices->update_vertex_buffer(frames[0], true); lines->update_vertex_buffer(frames[0], true); // Upload the vertex indices of the lines to the GPU. lines->update_element_buffer(indices); // Add the drawables to the viewer viewer.add_drawable(std::shared_ptr<PointsDrawable>(vertices)); viewer.add_drawable(std::shared_ptr<LinesDrawable>(lines)); // allow animation viewer.set_animation(false); // Define an animation function to specify how vertex positions are updated. // In this trivial example, we stretch the model along the Z-axis. viewer.animation_func_ = [&](easy3d::Viewer* v) -> bool { (void)v; static std::size_t index = 1; const auto& vts = frames[index % frames.size()]; vertices->update_vertex_buffer(vts, true); lines->update_vertex_buffer(vts, true); ++index; viewer.update(); return true; }; // run the viewer return viewer.run(); } ```

Skullface9512

Yes, according to the this demo, I got a lot of inspiration and try to render hand mesh based on the known hand vertices. ![Image](https://github.com/user-attachments/assets/5e8a313e-76c1-4770-bdc0-cd64f4e861ed) I want to ask one more question, due to hand detection is real-time, so i can't feed all the frame into the array "frames" in advance, and how can i transfer new value into the "vts", my initial idea is putting the model inference process into the animation function and update the value of "vts" constantly, do you think this idea is reasonable?

LiangliangNan

I think your data actually doesn't represent an animation. Of course, you can still treat your data as if it were a smooth animation and use the existing code framework. I don't know how frequent your data acquisition is. But given that your data is quite small (I mean, not many MB or GB), it makes sense to **transfer data to the GPU only when you receive the new data**. That means, you only need to call `update_vertex_buffer(...)` and perhaps also `update_element_buffer(...)` functions for the relevant drawables when new data is acquired.

Skullface9512

Yes, the previous data i uploaded is from FreiHand dataset and it's not smooth. Today i try to render the whole hand motion through consecutive frames and attach the video, in hand mesh show, the hand will turns red from time to time, do you this reason, thank you! https://drive.google.com/file/d/1ChrZQLqB_MNOaFVz8ioWdPrMVd4cTjy1/view?usp=drive_link

LiangliangNan

The color alters because the orientation of your mesh models is not consistent. - yellow: face/vertex normals are pointing outside (this is expected); - red: the normals are pointing inward. This feature is enabled by default becasue in graphics research the orientation is quite important. For your use case, you can simply disable it by calling `set_distinct_back_color(false)` of your TrianglesDrawable. Then both sides will be rendered with the same color. BTW, as your sequence doesn't represent a smooth animation, you should avoid using the animation function (as it updates at about 30 fps and is for real animation). The best is to update the GPU buffers at a specified interval or only then the data of a new fram is received (and you need to implement this).

Skullface9512

Today I tried to add the 3d modeling into model inference, and didn't use animation function in the process. But came across some errors, when the program go into viewer.run(), it would stop suddenly, the log is below: ``` ================================================================= program started ... V 04/07/2025 16:43:22.704 logging.cpp:201] executable path: /home/goertek/workspace/simpleHand/tech_show/spike_handpoints_detection/build V 04/07/2025 16:43:22.704 logging.cpp:202] current working dir: /home/goertek/workspace/simpleHand/tech_show/spike_handpoints_detection/build V 04/07/2025 16:43:22.704 resource.cpp:51] resource directory: /home/goertek/software/Easy3D-main/resources V 04/07/2025 16:43:23.007 viewer.cpp:292] OpenGL vendor: NVIDIA Corporation V 04/07/2025 16:43:23.007 viewer.cpp:293] OpenGL renderer: NVIDIA GeForce RTX 4090 D/PCIe/SSE2 V 04/07/2025 16:43:23.007 viewer.cpp:294] OpenGL version requested: 3.2 V 04/07/2025 16:43:23.007 viewer.cpp:295] OpenGL version received: 3.2.0 NVIDIA 570.133.20 V 04/07/2025 16:43:23.007 viewer.cpp:296] GLSL version received: 1.50 NVIDIA via Cg compiler V 04/07/2025 16:43:23.007 viewer.cpp:310] samples received: 4 (4 requested, max support is 32) V 04/07/2025 16:43:23.007 viewer.cpp:315] viewer size (width/height): 800/600 V 04/07/2025 16:43:23.007 viewer.cpp:321] DPI scaling: 1 V 04/07/2025 16:43:24.147 resource.cpp:61] resource directory: /home/goertek/software/Easy3D-main/resources V 04/07/2025 16:43:24.148 text_renderer.cpp:756] loaded font 'en_Earth-Normal.ttf' in texture (512 x 512) V 04/07/2025 16:43:24.148 text_renderer.cpp:756] loaded font 'en_Roboto-Medium.ttf' in texture (512 x 512) F 04/07/2025 16:43:24.168 logging.cpp:105] Application has crashed due to [SIGSEGV] signal Invalid access to memory Program is trying to read an invalid (unallocated, deleted or corrupted) or inaccessible memory. ================================================================================= Easy3D has encountered a fatal error and has to abort. The error has been recorded in the log file [/home/goertek/workspace/simpleHand/tech_show/spike_handpoints_detection/build/handrects_detection_3d.log]. Please report this issue with the complete log, a description of how to reproduce the issue, and possibly your data to Liangliang Nan ([email protected]). ================================================================================= *** Check failure stack trace (most recent call first): *** ``` Do you know why happened this phenomenon, thank you!

LiangliangNan

The log doesn't show useful information. You can run the program in the Debug mode, and that might be helpful for you to figure out the issue. Or, come up with a minimum example that can replicate your issue, and send me the code so I can check it for you.

Skullface9512

Due to the purpose of showing hand joints and vertices of two hands at the same time, I create four Drawable structures in this demo. At first, the origin image which contains hand and background is feed into hand detection model to locate hand's position,then according to this result crop the image which only consist of hand, and detect 3d joints and vertices finally. The simple code, thank you! ``` int main(int argc, char **argv) { initialize(); MultiViewer viewer(2, 2, EXAMPLE_TITLE); folderPath = "../images/"; for (const auto& entry : fs::directory_iterator(folderPath)) { std::string filePath = entry.path().string(); std::string extension = entry.path().extension().string(); if (extension == ".jpg" || extension == ".png" ) { imagePaths.push_back(filePath); } } the initial joints postion const std::string data_dir = "./ply/hand_vertice/"; std::vector<std::string> files; file_system::get_files(data_dir, files, false); std::sort(files.begin(), files.end()); // The initial vertives postion const std::string data_dir1 = "./ply/hand_joints/"; std::vector<std::string> files1; file_system::get_files(data_dir1, files1, false); std::sort(files1.begin(), files1.end()); typedef std::vector<vec3> Frame; std::vector<Frame> joints_l(1); std::vector<Frame> joints_r(1); std::vector<Frame> vertices_l(1); std::vector<Frame> vertices_r(1); const std::string file = data_dir + files[0]; io::PlyReader reader; std::vector<io::Element> elements; reader.read(file, elements); if (elements.size() == 1) { joints_l[0] = elements[0].vec3_properties[0]; joints_r[0] = elements[0].vec3_properties[0]; } const std::string file1 = data_dir1 + files1[0]; io::PlyReader reader1; std::vector<io::Element> elements1; reader.read(file1, elements); if (elements1.size() == 1) { vertices_l[0] = elements1[0].vec3_properties[0]; vertices_r[0] = elements1[0].vec3_properties[0]; } const std::vector<unsigned int> indices = { 0, 1, 1, 2, 2, 3, 3, 4, 0, 5, 5, 6, 6, 7, 7, 8, 5, 9, 9, 10, 10, 11, 11, 12,9, 13, 13, 14, 14, 15, 15, 16, 13, 17, 17, 18, 18, 19, 19, 20, 0, 17 }; // Create needed Drawable auto vertices_l_D = new PointsDrawable("vertices"); auto lines_l_D = new LinesDrawable("lines"); auto vertices_r_D = new PointsDrawable("vertices"); auto lines_r_D = new LinesDrawable("lines"); vertices_l_D->set_impostor_type(PointsDrawable::SPHERE); vertices_l_D->set_point_size(15.0f); vertices_l_D->set_color(vec4(0.8f,0.3f, 0.3f, 1.0f)); lines_l_D->set_impostor_type(LinesDrawable::CYLINDER); lines_l_D->set_line_width(5.0f); lines_l_D->set_color(vec4(0.3f,0.3f, 0.8f, 1.0f)); vertices_r_D->set_impostor_type(PointsDrawable::SPHERE); vertices_r_D->set_point_size(15.0f); vertices_r_D->set_color(vec4(0.8f,0.3f, 0.3f, 1.0f)); lines_r_D->set_impostor_type(LinesDrawable::CYLINDER); lines_r_D->set_line_width(5.0f); lines_r_D->set_color(vec4(0.3f,0.3f, 0.8f, 1.0f)); vertices_l_D->update_vertex_buffer(joints_l[0], true); lines_l_D->update_vertex_buffer(joints_l[0], true); lines_l_D->update_element_buffer(indices); vertices_r_D->update_vertex_buffer(joints_r[0], true); lines_r_D->update_vertex_buffer(joints_r[0], true); lines_r_D->update_element_buffer(indices); auto surface_l_D = new TrianglesDrawable("faces"); surface_l_D->update_vertex_buffer(vertices_l[0], true); surface_l_D->update_element_buffer(edge); surface_l_D->set_distinct_back_color(false); auto surface_r_D = new TrianglesDrawable("faces"); surface_r_D->update_vertex_buffer(vertices_r[0], true); surface_r_D->update_element_buffer(edge); surface_r_D->set_distinct_back_color(false); viewer.assign(0, 0, vertices_l_D); viewer.assign(0, 0, lines_l_D); viewer.assign(0, 1, vertices_r_D); viewer.assign(0, 1, lines_r_D); viewer.assign(1, 0, surface_l_D); viewer.assign(1, 1, surface_r_D); // Hand detect model struct hand_detector_t kd = init_keypoint_detector(); // Keypoint detect model struct hand_detector_t hd = init_hand_detector(); for (size_t i = 0; i < imagePaths.size(); i++) { cv::Mat img_rect_show; cv::Mat raw_input = cv::imread(image_file); auto [hand_exists, cx, cy, out_size] = Rundetnet(img_rect_show, hd, inputTensorSize); viewer.run(); // Hand_idx=0:left hand; hand_idx=1:right hand for (int hand_idx = 0; hand_idx < 2; hand_idx++) { float x = cx[hand_idx]; float y = cy[hand_idx]; float size = out_size[hand_idx]; float confidence = hand_exists[hand_idx]; detection det; std::tuple<float, float, float, float> bbox_result = Compute_bbox(det, x, y, size, confidence); float cx = std::get<0>(bbox_result), cy = std::get<1>(bbox_result), w = std::get<2>(bbox_result), h = std::get<3>(bbox_result); int rect_length = w > h ? w : h; cv::Mat crop_128_imge(224, 224, 0); crop_128_imge = crop_small_images_sub(img_rect, hand_idx, cx, cy, rect_length, h); std::vector<cv::Mat> channels(3, crop_128_imge); cv::Mat simple_input; cv::merge(channels, simple_input); // Uv:2d joints in images; joint_pred:3d joints position; vertice_pred:hand vertices position auto [uv, joint_pred, vertice_pred] = Runkeynet(simple_input, kd, inputTensorSize1); double px_coord[21][2]; double joints_res[21][3]; double vertices_res[778][3]; for (int ii = 0; ii < 21; ii++) { px_coord[ii][0] = uv[2 * ii]; px_coord[ii][1] = uv[2 * ii + 1]; joints_res[ii][0] = joint_pred[3*ii]; joints_res[ii][1] = joint_pred[3*ii+1]; joints_res[ii][2] = joint_pred[3*ii+2]; } for (int kk = 0; kk < 778; kk++) { vertices_res[kk][0] = vertice_pred[3*kk]; vertices_res[kk][1] = vertice_pred[3*kk+1]; vertices_res[kk][2] = vertice_pred[3*kk+2]; } std::vector<vec3> result_j; result_j.reserve(21); for (int joint_id = 0; joint_id < 21; ++joint_id) { result_j.emplace_back(joints_res[joint_id][0], joints_res[joint_id][1], joints_res[joint_id][2]); if (hand_idx == 0) { vertices_l_D->update_vertex_buffer(result_j, true); lines_l_D->update_vertex_buffer(result_j, true); } else { vertices_r_D->update_vertex_buffer(result_j, true); lines_r_D->update_vertex_buffer(result_j, true); } } std::vector<vec3> result_v; result_v.reserve(778); for (int vertice_id = 0; vertice_id < 778; ++vertice_id) { result_v.emplace_back(vertices_res[vertice_id][0], vertices_res[vertice_id][1], vertices_res[vertice_id][2]); if (hand_idx == 0) { surface_l_D->update_vertex_buffer(result_v, true); } if (hand_idx == 0) { surface_r_D->update_vertex_buffer(result_v, true); } } } cv::imshow("hand_detection", raw_input); cv::waitKey(20); } cv::destroyAllWindows(); return 0; } ```

LiangliangNan

I am able to run your code because there are many undefined variables. Besides, I also don't have OpenCV installed. When looking through your code, I found you put "viewer.run();" within a for loop. This might cause issues (but don't know if this is related to yours). The run() function should be called only once.

Skullface9512

Today I put "viewer.run();" outside the for loop and modified some mistakes in the early code. Now it can show 3D hand structure normally but it would not update position of joint and only show the first frame, I put the simple code below that you can run. Due to I need to obtain image data constantly, so must use while loop and update vertex's value based on the detection result. The ply file you can use I uploaded before, it just justify whether it can move normally. ``` #include <easy3d/viewer/viewer.h> #include <easy3d/renderer/drawable_points.h> #include <easy3d/renderer/drawable_lines.h> #include "easy3d/renderer/camera.h" #include <easy3d/util/initializer.h> #include "easy3d/util/file_system.h" #include <easy3d/fileio/ply_reader_writer.h> #include <easy3d/viewer/multi_viewer.h> using namespace easy3d; #define EXAMPLE_TITLE "Hand_detection" int main(int argc, char **argv) { // initialize Easy3D. initialize(); //------------------------------------------------------------- // Create the default Easy3D viewer. // Note: a viewer must be created before creating any drawables. // Viewer viewer(EXAMPLE_TITLE); MultiViewer viewer(2, 2, EXAMPLE_TITLE); //------------------------------------------------------------- // The directory containing the frames of the hand skeleton. // Note: each file contains the 21 vertices of the hand. const std::string data_dir = "/home/goertek/workspace/simpleHand/ply/hand_joints/"; std::vector<std::string> files; file_system::get_files(data_dir, files, false); std::sort(files.begin(), files.end()); typedef std::vector<vec3> Frame; std::vector<Frame> frames(files.size()); for (std::size_t i=0; i<files.size(); ++i) { const std::string file = data_dir + files[i]; // We could use the general PointCloudIO to load each file as a point cloud. // But for this special case, we use the PlyReader (to avoid loading each file into a point cloud model). io::PlyReader reader; std::vector<io::Element> elements; reader.read(file, elements); if (elements.size() == 1) // each file only contains vertices property. frames[i] = elements[0].vec3_properties[0]; } // To ensure all vertices are within the view frustum, compute the bounding box of all vertices across frames. Box3 box; for (const auto& frame : frames) for (const auto& v : frame) box.grow(v); viewer.camera()->setSceneBoundingBox(box.min_point(), box.max_point()); // Each consecutive 2 indices represent a line segment. const std::vector<unsigned int> indices = { 0, 1, 1, 2, 2, 3, 3, 4, 0, 5, 5, 6, 6, 7, 7, 8, 5, 9, 9, 10, 10, 11, 11, 12,9, 13, 13, 14, 14, 15, 15, 16, 13, 17, 17, 18, 18, 19, 19, 20, 0, 17 }; //------------------------------------------------------------- // We create two drawable to render the hand: // - a PointsDrawable for the vertices of the hand. // - a LinesDrawable for the skeleton of the hand. auto vertices = new PointsDrawable("vertices"); auto lines = new LinesDrawable("lines"); // Render vertices as spheres vertices->set_impostor_type(PointsDrawable::SPHERE); vertices->set_point_size(15.0f); vertices->set_color(vec4(0.8f,0.3f, 0.3f, 1.0f)); // Render lines as cylinders lines->set_impostor_type(LinesDrawable::CYLINDER); lines->set_line_width(5.0f); lines->set_color(vec4(0.3f,0.3f, 0.8f, 1.0f)); // Upload the vertice to GPU. The second argument is 'true' to allow dynamic update. vertices->update_vertex_buffer(frames[0], true); lines->update_vertex_buffer(frames[0], true); // Upload the vertex indices of the lines to the GPU. lines->update_element_buffer(indices); // Add the drawables to the viewer // viewer.add_drawable(std::shared_ptr<PointsDrawable>(vertices)); // viewer.add_drawable(std::shared_ptr<LinesDrawable>(lines)); viewer.assign(0, 0, vertices); viewer.assign(0, 0, lines); viewer.assign(0, 1, vertices); viewer.assign(0, 1, lines); viewer.run(); size_t index = 1; while (index<50) { const auto& vts = frames[index]; vertices->update_vertex_buffer(vts, true); lines->update_vertex_buffer(vts, true); ++index; viewer.update(); } // allow animation // viewer.set_animation(true); // // Define an animation function to specify how vertex positions are updated. // // In this trivial example, we stretch the model along the Z-axis. // viewer.animation_func_ = [&](easy3d::Viewer* v) -> bool { // (void)v; // static std::size_t index = 1; // const auto& vts = frames[index % frames.size()]; // vertices->update_vertex_buffer(vts, true); // lines->update_vertex_buffer(vts, true); // ++index; // viewer.update(); // return true; // }; // // run the viewer // return viewer.run(); return 0; } ```

LiangliangNan

You need a mechanism to trigger the "update". There are multiple options, for example, using another thread or using a timer. I recommend using Easy3D's timer (see some [examples](https://github.com/LiangliangNan/Easy3D/blob/main/tests/test_timer.cpp). Basically, it allows you to run a function at a fixed interval. In case you receive new data from your device at a non-uniform interval, you should perform the update every time new data is received.

Skullface9512

Actually, the interval between adjacent frames in real time is not fixed, so I think using timer may have some problems in practice. Could you please create a simple demo based on the above code which show how to update its value or some relative examples, thanks!

LiangliangNan

Then you should check your sensor's documentation (or consult the relevant people) to see how the user can be informed when new data is generated. This is completely out of the scope of using Easy3D.

Skullface9512

Yes, Can you tell what mechanism of its update, Why when I change vertices' value use viewer.update() function in while loop, it have no movement in rendering, thank you!

LiangliangNan

After executing "viewer.run()", the viewer will show, and the program will stay within the run() function. This means that the "while (index<50) ... " will never be reached when the viewer is running; it is executed only after you close the viewer. The Easy3D viewer is based on GLFW. You can check the [GLFW documentation](https://www.glfw.org/docs/3.3/quick_guide.html#quick_window_close)to better understand how the viewer works.