diff --git a/area_scan_3d_camera/Advanced/CaptureDepthSource2DImage/CMakeLists.txt b/area_scan_3d_camera/Advanced/CaptureDepthSource2DImage/CMakeLists.txt new file mode 100644 index 0000000..49ce6f1 --- /dev/null +++ b/area_scan_3d_camera/Advanced/CaptureDepthSource2DImage/CMakeLists.txt @@ -0,0 +1,45 @@ +cmake_minimum_required(VERSION 3.5) + +project(CaptureDepthSource2DImage) + +set(CMAKE_CXX_STANDARD 14) + +set(Files ${PROJECT_SOURCE_DIR}/CaptureDepthSource2DImage.cpp) + +if(CMAKE_HOST_WIN32) + find_package(MechEyeApi REQUIRED CONFIG PATHS "$ENV{MECHEYE_DIR}/API") + if(NOT MechEyeApi_FOUND) + message( + FATAL_ERROR "MechEyeApi not found. Please install MechEyeApi first.") + endif() +elseif(CMAKE_HOST_UNIX) + find_package(PkgConfig) + if(NOT PkgConfig_FOUND) + message(FATAL_ERROR "PkgConfig not found.") + else() + pkg_check_modules(MECHEYEAPI REQUIRED MechEyeApi) + if(NOT MECHEYEAPI_FOUND) + message( + FATAL_ERROR "MechEyeApi not found. Please install MechEyeApi first.") + endif() + endif() +endif() + +# OpenCV_DIR: set as your OpenCV libraries directory; Uncomment next line to set +# OpenCV_DIR manually + +# set(OpenCV_DIR "path to OpenCV directory") +find_package(OpenCV REQUIRED) +if(NOT OpenCV_FOUND) + message( + FATAL_ERROR + "OpenCV not found. Please point OpenCV_DIR to the directory of your OpenCV installation (containing the file OpenCVConfig.cmake)." + ) +endif() + +include_directories(${MECHEYEAPI_INCLUDE_DIRS} ${OpenCV_INCLUDE_DIRS}) +link_directories(${MECHEYEAPI_LIBRARY_DIRS} ${OpenCV_LIBRARY_DIRS}) + +add_executable(${PROJECT_NAME} ${Files}) + +target_link_libraries(${PROJECT_NAME} ${MECHEYEAPI_LIBRARIES} ${OpenCV_LIBS}) \ No newline at end of file diff --git a/area_scan_3d_camera/Advanced/CaptureDepthSource2DImage/CaptureDepthSource2DImage.cpp b/area_scan_3d_camera/Advanced/CaptureDepthSource2DImage/CaptureDepthSource2DImage.cpp new file mode 100644 index 0000000..e22454e --- /dev/null +++ b/area_scan_3d_camera/Advanced/CaptureDepthSource2DImage/CaptureDepthSource2DImage.cpp @@ -0,0 +1,83 @@ +/******************************************************************************* + *BSD 3-Clause License + * + *Copyright (c) 2016-2025, Mech-Mind Robotics Technologies Co., Ltd. + *All rights reserved. + * + *Redistribution and use in source and binary forms, with or without + *modification, are permitted provided that the following conditions are met: + * + *1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + *2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + *3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + *THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + *AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + *IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + *DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + *FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + *DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + *SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + *CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + *OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + *OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ + +/* +With this sample, you can obtain and save the 2D image from the depth source camera. +*/ + +#include +#include "area_scan_3d_camera/Camera.h" +#include "area_scan_3d_camera/api_util.h" + +int main() +{ + mmind::eye::Camera camera; + if (!findAndConnect(camera)) + return -1; + + mmind::eye::Frame2D frame2D; + auto errorStatus = camera.captureDepthSource2D(frame2D); + if (!errorStatus.isOK()) { + showError(errorStatus); + return -1; + } + + cv::Mat image2D; + const std::string imageFile = "DepthSource2DImage.png"; + + switch (frame2D.colorType()) { + case mmind::eye::ColorTypeOf2DCamera::Monochrome: + { + mmind::eye::GrayScale2DImage grayImage = frame2D.getGrayScaleImage(); + image2D = cv::Mat(grayImage.height(), grayImage.width(), CV_8UC1, grayImage.data()); + break; + } + case mmind::eye::ColorTypeOf2DCamera::Color: + { + mmind::eye::Color2DImage colorImage = frame2D.getColorImage(); + image2D = cv::Mat(colorImage.height(), colorImage.width(), CV_8UC3, colorImage.data()); + break; + } + default: + std::cerr << "The depth source 2D image has an unsupported pixel type." << std::endl; + camera.disconnect(); + return -1; + } + + cv::imwrite(imageFile, image2D); + std::cout << "Capture and save the 2D image from the depth source camera: " << imageFile + << std::endl; + + camera.disconnect(); + std::cout << "Disconnected from the camera successfully." << std::endl; + return 0; +} \ No newline at end of file diff --git a/area_scan_3d_camera/Advanced/CaptureDepthSource2DImage/README.md b/area_scan_3d_camera/Advanced/CaptureDepthSource2DImage/README.md new file mode 100644 index 0000000..f224fab --- /dev/null +++ b/area_scan_3d_camera/Advanced/CaptureDepthSource2DImage/README.md @@ -0,0 +1,120 @@ +# CaptureDepthSource2DImage Sample + +With this sample, you can obtain and save the 2D image from the depth source camera. + +> Note: This sample is only applicable to camera models with an external 2D camera, such as ULTRA M and NANO ULTRA models that provide a dedicated depth source 2D image. + +If you have any questions or have anything to share, feel free to post on the [Mech-Mind Online Community](https://community.mech-mind.com/). The community also contains a [specific category for development with Mech-Eye SDK](https://community.mech-mind.com/c/mech-eye-sdk-development/19). + +## Build the Sample + +Prerequisites and instructions for building the sample on Windows and Ubuntu are provided. + +### Windows + +#### Prerequisites + +The following software are required to build this sample. Please download and install these software. + +* [Mech-Eye SDK (latest version)](https://downloads.mech-mind.com/?tab=tab-sdk) +* [Visual Studio (version 2017 or above)](https://visualstudio.microsoft.com/vs/community/) +* [CMake (version 3.2 or above)](https://cmake.org/download/) +* [OpenCV (version 3.4.5 or above)](https://opencv.org/releases/) + +#### Instructions + +1. Make sure that the sample is stored in a location with read and write permissions. +2. Add the following directories to the **Path** environment variable: + + * `xxx/opencv/build/x64/vc14/bin` + * `xxx/opencv/build/x64/vc14/lib` + +3. Run Cmake and set the source and build paths: + + | Field | Path | + | :---- | :---- | + | Where is the source code | xxx/CaptureDepthSource2DImage | + | Where to build the binaries | xxx/CaptureDepthSource2DImage/build | + +4. Click the **Configure** button. In the pop-up window, set the generator and platform according to the actual situation, and then click the **Finish** button. +5. When the log displays **Configuring done**, click the **Generate** button. When the log displays **Generating done**, click the **Open Project** button. +6. In Visual Studio toolbar, change the solution configuration from **Debug** to **Release**. +7. In the **Solution Explorer** panel, right-click the sample, and select **Set as Startup Project**. +8. Click the **Local Windows Debugger** button in the toolbar to run the sample. +9. Enter the index of the camera to which you want to connect, and press the Enter key. The obtained file is saved to the `build` folder. + +### Ubuntu + +Ubuntu 18 or above is required. + +#### Prerequisites + +* Update the software source list. + + ```bash + sudo apt-get update + ``` + +* Install required tools. + + ```bash + sudo apt-get install -y build-essential pkg-config cmake + ``` + +* Install [Mech-Eye SDK (latest version)](https://downloads.mech-mind.com/?tab=tab-sdk). + + >Note: If you have installed Mech-Eye SDK before, please uninstall it first with the following command: + > + >```bash + >sudo dpkg -P MechEyeApi + >``` + + * If the system architecture is AMD64, execute the following command: + + ```bash + sudo dpkg -i 'Mech-Eye_API_x.x.x_amd64.deb' + ``` + + * If the system architecture is ARM64, execute the following command: + + ```bash + sudo dpkg -i 'Mech-Eye_API_x.x.x_arm64.deb' + ``` + +* Install third-party libraries: OpenCV is required. + + * Install OpenCV (latest version): + + ```bash + sudo apt update && sudo apt install -y unzip + wget -O opencv.zip https://github.com/opencv/opencv/archive/4.x.zip + unzip opencv.zip + mkdir build && cd build + cmake ../opencv-4.x + cmake --build . + sudo make install + ``` + +#### Instructions + +1. Navigate to the directory of the sample. + + ```bash + cd xxx/area_scan_3d_camera/Advanced/CaptureDepthSource2DImage/ + ``` + +2. Configure and build the sample. + + ```bash + sudo mkdir build && cd build + sudo cmake .. + sudo make + ``` + +3. Run the sample. + + ```bash + sudo ./CaptureDepthSource2DImage + ``` + +4. Enter the index of the camera to which you want to connect, and press the Enter key. The obtained file is saved to `/CaptureDepthSource2DImage/build`. \ No newline at end of file diff --git a/area_scan_3d_camera/CMakeLists.txt b/area_scan_3d_camera/CMakeLists.txt index d9cd241..cbf5770 100644 --- a/area_scan_3d_camera/CMakeLists.txt +++ b/area_scan_3d_camera/CMakeLists.txt @@ -12,6 +12,9 @@ endif() option(USE_OPENCV "Enable samples which depend on OpenCV" ON) option(USE_PCL "Enable samples which depend on Point Cloud Library (PCL)" ON) +if(CMAKE_HOST_WIN32) + add_definitions(-DNOMINMAX) +endif() if(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "aarch64") set(USE_HALCON FALSE) add_compile_options(-mno-outline-atomics) @@ -28,6 +31,7 @@ set(SAMPLES Basic/CapturePointCloud Basic/CapturePointCloudWithNormals Basic/SaveVirtualDevice + Advanced/CaptureDepthSource2DImage Advanced/CaptureStereo2DImages Advanced/RenderDepthMap Advanced/ConvertDepthMapToPointCloud @@ -60,6 +64,7 @@ set(PCL_DEPENDING set(OpenCV_DEPENDING Capture2DImage + CaptureDepthSource2DImage CaptureDepthMap RenderDepthMap CaptureStereo2DImages diff --git a/area_scan_3d_camera/Calibration/HandEyeCalibration/HandEyeCalibration.cpp b/area_scan_3d_camera/Calibration/HandEyeCalibration/HandEyeCalibration.cpp index 4f087a7..56ab34f 100644 --- a/area_scan_3d_camera/Calibration/HandEyeCalibration/HandEyeCalibration.cpp +++ b/area_scan_3d_camera/Calibration/HandEyeCalibration/HandEyeCalibration.cpp @@ -39,6 +39,7 @@ complete hand-eye calibration. #include "area_scan_3d_camera/Camera.h" #include "area_scan_3d_camera/api_util.h" #include "HandEyeCalibrationUtil.h" +#include #include int main() @@ -197,6 +198,19 @@ int main() } break; } + case CommandType::GetCurrentImageFirstCorner: + { + mmind::eye::PointXYZ corner; + mmind::eye::ErrorStatus status = + calibration.extractCurrentImageFirstCorner(camera, corner); + showError(status); + if (status.isOK()) { + std::cout << "The first corner is: " << std::fixed << std::setprecision(3) + << corner.x << ", " << corner.y << ", " << corner.z + << std::endl; + } + break; + } case CommandType::Unknown: { std::cout << "Error: Unknown command" << std::endl; diff --git a/area_scan_3d_camera/Calibration/HandEyeCalibration/HandEyeCalibrationUtil.h b/area_scan_3d_camera/Calibration/HandEyeCalibration/HandEyeCalibrationUtil.h index 6c90575..51f6e7b 100644 --- a/area_scan_3d_camera/Calibration/HandEyeCalibration/HandEyeCalibrationUtil.h +++ b/area_scan_3d_camera/Calibration/HandEyeCalibration/HandEyeCalibrationUtil.h @@ -10,7 +10,14 @@ #define PI 3.14159265 namespace { -enum class CommandType { AddPose, Calibrate, GetPatternImg, GetOriginImg, Unknown }; +enum class CommandType { + AddPose, + Calibrate, + GetPatternImg, + GetOriginImg, + GetCurrentImageFirstCorner, + Unknown +}; // Obtain keyboard input. std::string getInputCommand() { @@ -121,6 +128,7 @@ CommandType enterCommand() std::cout << "T: obtain the 2D image with feature recognition result" << std::endl; std::cout << "A: enter the current robot pose" << std::endl; std::cout << "C: calculate extrinsic parameters" << std::endl; + std::cout << "F: obtain the first corner of current image" << std::endl; const auto command = getInputCommand(); if (command == "P" || command == "p") { return CommandType::GetOriginImg; @@ -134,6 +142,9 @@ CommandType enterCommand() if (command == "C" || command == "c") { return CommandType::Calibrate; } + if (command == "F" || command == "f") { + return CommandType::GetCurrentImageFirstCorner; + } return CommandType::Unknown; } diff --git a/area_scan_3d_camera/README.md b/area_scan_3d_camera/README.md index f0cf22c..d6535ad 100644 --- a/area_scan_3d_camera/README.md +++ b/area_scan_3d_camera/README.md @@ -34,7 +34,11 @@ The sample marked with `(HALCON)` requires [HALCON](https://www.mvtec.com/downlo Set multiple exposure times, and then obtain and save the point cloud. * [CapturePointCloudWithNormals](https://github.com/MechMindRobotics/mecheye_cpp_samples/tree/master/area_scan_3d_camera/Basic/CapturePointCloudWithNormals) Calculate normals and save the point cloud with normals. + * [SaveVirtualDevice](https://github.com/MechMindRobotics/mecheye_cpp_samples/tree/master/area_scan_3d_camera/Basic/SaveVirtualDevice) `(OpenCV)` + Save the data acquired by the camera as a virtual device file. * **Advanced** + * [CaptureDepthSource2DImage](https://github.com/MechMindRobotics/mecheye_cpp_samples/tree/master/area_scan_3d_camera/Advanced/CaptureDepthSource2DImage) `(OpenCV)` + Obtain and save the 2D image from the depth source camera. * [ConvertDepthMapToPointCloud](https://github.com/MechMindRobotics/mecheye_cpp_samples/tree/master/area_scan_3d_camera/Advanced/ConvertDepthMapToPointCloud) Generate a point cloud from the depth map and save the point cloud. * [MultipleCamerasCaptureSequentially](https://github.com/MechMindRobotics/mecheye_cpp_samples/tree/master/area_scan_3d_camera/Advanced/MultipleCamerasCaptureSequentially) `(OpenCV)` @@ -43,6 +47,8 @@ The sample marked with `(HALCON)` requires [HALCON](https://www.mvtec.com/downlo Obtain and save 2D images, depth maps, and point clouds simultaneously from multiple cameras. * [CapturePeriodically](https://github.com/MechMindRobotics/mecheye_cpp_samples/tree/master/area_scan_3d_camera/Advanced/CapturePeriodically) `(OpenCV)` Obtain and save 2D images, depth maps, and point clouds periodically for the specified duration from a camera. + * [WarmUp](https://github.com/MechMindRobotics/mecheye_cpp_samples/tree/master/area_scan_3d_camera/Advanced/WarmUp) `(OpenCV)` + Warm up the device. * [Mapping2DImageToDepthMap](https://github.com/MechMindRobotics/mecheye_cpp_samples/tree/master/area_scan_3d_camera/Advanced/Mapping2DImageToDepthMap) Generate untextured and textured point clouds from a masked 2D image and a depth map. * [RenderDepthMap](https://github.com/MechMindRobotics/mecheye_cpp_samples/tree/master/area_scan_3d_camera/Advanced/RenderDepthMap) `(OpenCV)` diff --git a/area_scan_3d_camera/Util/SetPointCloudProcessingParameters/SetPointCloudProcessingParameters.cpp b/area_scan_3d_camera/Util/SetPointCloudProcessingParameters/SetPointCloudProcessingParameters.cpp index 1a6e0f0..bc9f634 100644 --- a/area_scan_3d_camera/Util/SetPointCloudProcessingParameters/SetPointCloudProcessingParameters.cpp +++ b/area_scan_3d_camera/Util/SetPointCloudProcessingParameters/SetPointCloudProcessingParameters.cpp @@ -60,6 +60,29 @@ int main() showError(currentUserSet.setEnumValue( mmind::eye::pointcloud_processing_setting::NoiseRemoval::name, static_cast(mmind::eye::pointcloud_processing_setting::NoiseRemoval::Value::Normal))); + showError(currentUserSet.setEnumValue( + mmind::eye::pointcloud_processing_setting::DepthSmooth::name, + static_cast(mmind::eye::pointcloud_processing_setting::DepthSmooth::Value::Normal))); + showError(currentUserSet.setEnumValue( + mmind::eye::pointcloud_processing_setting::DepthHoleFilling::name, + static_cast( + mmind::eye::pointcloud_processing_setting::DepthHoleFilling::Value::Normal))); + showError(currentUserSet.setEnumValue( + mmind::eye::pointcloud_processing_setting::DepthSurfaceNoiseRemoval::name, + static_cast( + mmind::eye::pointcloud_processing_setting::DepthSurfaceNoiseRemoval::Value::Normal))); + showError(currentUserSet.setEnumValue( + mmind::eye::pointcloud_processing_setting::PhaseClusterOutlierRemoval::name, + static_cast( + mmind::eye::pointcloud_processing_setting::PhaseClusterOutlierRemoval::Value::L5))); + showError(currentUserSet.setEnumValue( + mmind::eye::pointcloud_processing_setting::SpuriousPhaseRemoval::name, + static_cast( + mmind::eye::pointcloud_processing_setting::SpuriousPhaseRemoval::Value::Normal))); + showError(currentUserSet.setEnumValue( + mmind::eye::pointcloud_processing_setting::LargeGradNoiseRemoval::name, + static_cast( + mmind::eye::pointcloud_processing_setting::LargeGradNoiseRemoval::Value::Normal))); showError(currentUserSet.setEnumValue( mmind::eye::pointcloud_processing_setting::OutlierRemoval::name, static_cast( @@ -71,12 +94,34 @@ int main() int surfaceSmoothing = 0; int noiseRemoval = 0; + int depthSmooth = 0; + int depthHoleFilling = 0; + int depthSurfaceNoiseRemoval = 0; + int phaseClusterOutlierRemoval = 0; + int spuriousPhaseRemoval = 0; + int largeGradNoiseRemoval = 0; int outlierRemoval = 0; int edgePreservation = 0; showError(currentUserSet.getEnumValue( mmind::eye::pointcloud_processing_setting::SurfaceSmoothing::name, surfaceSmoothing)); showError(currentUserSet.getEnumValue( mmind::eye::pointcloud_processing_setting::NoiseRemoval::name, noiseRemoval)); + showError(currentUserSet.getEnumValue( + mmind::eye::pointcloud_processing_setting::DepthSmooth::name, depthSmooth)); + showError(currentUserSet.getEnumValue( + mmind::eye::pointcloud_processing_setting::DepthHoleFilling::name, depthHoleFilling)); + showError(currentUserSet.getEnumValue( + mmind::eye::pointcloud_processing_setting::DepthSurfaceNoiseRemoval::name, + depthSurfaceNoiseRemoval)); + showError(currentUserSet.getEnumValue( + mmind::eye::pointcloud_processing_setting::PhaseClusterOutlierRemoval::name, + phaseClusterOutlierRemoval)); + showError(currentUserSet.getEnumValue( + mmind::eye::pointcloud_processing_setting::SpuriousPhaseRemoval::name, + spuriousPhaseRemoval)); + showError(currentUserSet.getEnumValue( + mmind::eye::pointcloud_processing_setting::LargeGradNoiseRemoval::name, + largeGradNoiseRemoval)); showError(currentUserSet.getEnumValue( mmind::eye::pointcloud_processing_setting::OutlierRemoval::name, outlierRemoval)); showError(currentUserSet.getEnumValue( @@ -86,6 +131,20 @@ int main() << " (0: Off, 1: Weak, 2: Normal, 3: Strong)" << std::endl; std::cout << "Point Cloud Noise Removal: " << noiseRemoval << " (0: Off, 1: Weak, 2: Normal, 3: Strong)" << std::endl; + std::cout << "Depth Smooth: " << depthSmooth << " (0: Off, 1: Weak, 2: Normal, 3: Strong)" + << std::endl; + std::cout << "Depth Hole Filling: " << depthHoleFilling + << " (0: Off, 1: Weak, 2: Normal, 3: Strong)" << std::endl; + std::cout << "Depth Surface Noise Removal: " << depthSurfaceNoiseRemoval + << " (0: Off, 1: Weak, 2: Normal, 3: Strong)" << std::endl; + std::cout << "Phase Cluster Outlier Removal: " << phaseClusterOutlierRemoval + << " (0: Off, 1: L1, 2: L2, 3: L3, 4: L4, 5: L5, 6: L6, 7: L7, 8: L8, 9: L9, 10: " + "L10)" + << std::endl; + std::cout << "Spurious Phase Removal: " << spuriousPhaseRemoval + << " (0: Off, 1: Weak, 2: Normal, 3: Strong)" << std::endl; + std::cout << "Large Gradient Noise Removal: " << largeGradNoiseRemoval + << " (0: Off, 1: Weak, 2: Normal, 3: Strong)" << std::endl; std::cout << "Point Cloud Outlier Removal: " << outlierRemoval << " (0: Off, 1: Weak, 2: Normal, 3: Strong)" << std::endl; std::cout << "Point Cloud Edge Preservation: " << edgePreservation diff --git a/area_scan_3d_camera/Util/SetScanningParameters/SetScanningParameters.cpp b/area_scan_3d_camera/Util/SetScanningParameters/SetScanningParameters.cpp index 03d13c9..6b041f5 100644 --- a/area_scan_3d_camera/Util/SetScanningParameters/SetScanningParameters.cpp +++ b/area_scan_3d_camera/Util/SetScanningParameters/SetScanningParameters.cpp @@ -34,6 +34,7 @@ With this sample, you can set the parameters in the "3D Parameters", "2D Parameters", and "ROI" categories. */ +#include #include "area_scan_3d_camera/Camera.h" #include "area_scan_3d_camera/api_util.h" #include "area_scan_3d_camera/parameters/Scanning3D.h" @@ -52,23 +53,90 @@ int main() printCameraInfo(cameraInfo); mmind::eye::UserSet& currentUserSet = camera.currentUserSet(); + std::vector availableParams; + showError(currentUserSet.getAvailableParameterNames(availableParams)); + auto hasParam = [&availableParams](const std::string& name) { + return std::find(availableParams.begin(), availableParams.end(), name) != + availableParams.end(); + }; // Set the exposure times for acquiring depth information. - showError(currentUserSet.setFloatArrayValue( - mmind::eye::scanning3d_setting::ExposureSequence::name, std::vector{5})); - // showError(currentUserSet.setFloatArrayValue( - // mmind::eye::scanning3d_setting::ExposureSequence::name, std::vector{5, 10})); - - // Obtain the current exposure times for acquiring depth information to check if the setting was - // successful. - std::vector exposureSequence; - showError(currentUserSet.getFloatArrayValue( - mmind::eye::scanning3d_setting::ExposureSequence::name, exposureSequence)); - std::cout << "3D scanning exposure multiplier : " << exposureSequence.size() << "." - << std::endl; - for (size_t i = 0; i < exposureSequence.size(); i++) { - std::cout << "3D scanning exposure time " << i + 1 << ": " << exposureSequence[i] << " ms." + if (hasParam(mmind::eye::scanning3d_setting::ExposureCount::name)) { + showError( + currentUserSet.setIntValue(mmind::eye::scanning3d_setting::ExposureCount::name, 2)); + int exposureCount = 1; + showError(currentUserSet.getIntValue(mmind::eye::scanning3d_setting::ExposureCount::name, + exposureCount)); + std::cout << "3D scanning exposure count: " << exposureCount << "." << std::endl; + } + + // Some models provide exposure group parameters. Use "GroupExposureSelector" to select the + // target group, and then set the group exposure time, gain, and power level. + // If HDR parameters can be set through the "GroupExposureSelector" method, + // then you will not be able to set the 3D exposure time through the "ExposureSequence." + if (hasParam(mmind::eye::scanning3d_setting::GroupExposureSelector::name) && + hasParam(mmind::eye::scanning3d_setting::GroupExposureTime::name) && + hasParam(mmind::eye::scanning3d_setting::GroupGain::name) && + hasParam(mmind::eye::scanning3d_setting::GroupDlpPowerLevel::name)) { + showError(currentUserSet.setEnumValue( + mmind::eye::scanning3d_setting::GroupExposureSelector::name, + static_cast( + mmind::eye::scanning3d_setting::GroupExposureSelector::Value::Exposure1))); + showError(currentUserSet.setFloatValue( + mmind::eye::scanning3d_setting::GroupExposureTime::name, 10)); + showError( + currentUserSet.setFloatValue(mmind::eye::scanning3d_setting::GroupGain::name, 2.0)); + showError(currentUserSet.setIntValue( + mmind::eye::scanning3d_setting::GroupDlpPowerLevel::name, 80)); + + double groupExposureTime = 0.0; + double groupGain = 0.0; + int groupDlpPowerLevel = 0; + showError(currentUserSet.getFloatValue( + mmind::eye::scanning3d_setting::GroupExposureTime::name, groupExposureTime)); + showError(currentUserSet.getFloatValue(mmind::eye::scanning3d_setting::GroupGain::name, + groupGain)); + showError(currentUserSet.getIntValue( + mmind::eye::scanning3d_setting::GroupDlpPowerLevel::name, groupDlpPowerLevel)); + std::cout << "Group Exposure1: exposure time " << groupExposureTime << " ms, gain " + << groupGain << " dB, DLP power level " << groupDlpPowerLevel << "." << std::endl; + + showError(currentUserSet.setEnumValue( + mmind::eye::scanning3d_setting::GroupExposureSelector::name, + static_cast( + mmind::eye::scanning3d_setting::GroupExposureSelector::Value::Exposure2))); + showError(currentUserSet.setFloatValue( + mmind::eye::scanning3d_setting::GroupExposureTime::name, 5)); + showError( + currentUserSet.setFloatValue(mmind::eye::scanning3d_setting::GroupGain::name, 0.0)); + showError(currentUserSet.setIntValue( + mmind::eye::scanning3d_setting::GroupDlpPowerLevel::name, 60)); + + showError(currentUserSet.getFloatValue( + mmind::eye::scanning3d_setting::GroupExposureTime::name, groupExposureTime)); + showError(currentUserSet.getFloatValue(mmind::eye::scanning3d_setting::GroupGain::name, + groupGain)); + showError(currentUserSet.getIntValue( + mmind::eye::scanning3d_setting::GroupDlpPowerLevel::name, groupDlpPowerLevel)); + std::cout << "Group Exposure2: exposure time " << groupExposureTime << " ms, gain " + << groupGain << " dB, DLP power level " << groupDlpPowerLevel << "." << std::endl; + } else if (hasParam(mmind::eye::scanning3d_setting::ExposureSequence::name)) { + showError(currentUserSet.setFloatArrayValue( + mmind::eye::scanning3d_setting::ExposureSequence::name, std::vector{5})); + showError(currentUserSet.setFloatArrayValue( + mmind::eye::scanning3d_setting::ExposureSequence::name, std::vector{5, 10})); + + // Obtain the current exposure times for acquiring depth information to check if the setting + // was successful. + std::vector exposureSequence; + showError(currentUserSet.getFloatArrayValue( + mmind::eye::scanning3d_setting::ExposureSequence::name, exposureSequence)); + std::cout << "3D scanning exposure multiplier : " << exposureSequence.size() << "." << std::endl; + for (size_t i = 0; i < exposureSequence.size(); i++) { + std::cout << "3D scanning exposure time " << i + 1 << ": " << exposureSequence[i] + << "ms." << std::endl; + } } // Set the ROI for the depth map and point cloud, and then obtain the parameter value to check @@ -94,7 +162,7 @@ int main() // adjusting the exposure mode for acquiring the 2D images (depth source). Uncomment the // following lines to set this parameter to "Timed". // showError(currentUserSet.setEnumValue( - // mmind::eye::scanning2d_setting::ExposureMode::name, + // mmind::eye::scanning2d_setting::DepthSourceExposureMode::name, // static_cast(mmind::eye::scanning2d_setting::DepthSourceExposureMode::Value::Timed))); // You can also use the projector for supplemental light when acquiring the 2D image / 2D images @@ -107,7 +175,7 @@ int main() // DEEP and LSR series: Uncomment the following lines to set the exposure mode to "Flash" for // supplemental light. // showError(currentUserSet.setEnumValue( - // mmind::eye::scanning2d_setting::ExposureMode::name, + // mmind::eye::scanning2d_setting::DepthSourceExposureMode::name, // static_cast(mmind::eye::scanning2d_setting::DepthSourceExposureMode::Value::Flash))); // The following models also provide a "FlashAcquisitionMode" when using the flash exposure @@ -125,7 +193,8 @@ int main() // 20)); // Uncomment the following lines to check the values of the "FlashAcquisitionMode" and - // "FlashExposureTime" parameters. int flashAcquisitionMode = 0; + // "FlashExposureTime" parameters. + // int flashAcquisitionMode = 0; // showError(currentUserSet.getEnumValue( // mmind::eye::scanning2d_setting::FlashAcquisitionMode::name, flashAcquisitionMode)); @@ -150,7 +219,27 @@ int main() // double scan2dGain{}; // showError(currentUserSet.getFloatValue(mmind::eye::scanning2d_setting::Gain::name, - // scan2dGain)); std::cout << "2D image gain: " << scan2dGain << " dB." << std::endl; + // scan2dGain)); + // std::cout << "2D image gain: " << scan2dGain << " dB." << std::endl; + + showError( + currentUserSet.setFloatValue(mmind::eye::scanning2d_setting::PatternRoleGain::name, 2.0)); + showError(currentUserSet.setFloatValue(mmind::eye::scanning2d_setting::FlashGain::name, 3.0)); + showError( + currentUserSet.setIntValue(mmind::eye::scanning2d_setting::FlashPowerLevel::name, 75)); + + double patternRoleGain{}; + showError(currentUserSet.getFloatValue(mmind::eye::scanning2d_setting::PatternRoleGain::name, + patternRoleGain)); + double flashGain{}; + showError( + currentUserSet.getFloatValue(mmind::eye::scanning2d_setting::FlashGain::name, flashGain)); + int flashPowerLevel{}; + showError(currentUserSet.getIntValue(mmind::eye::scanning2d_setting::FlashPowerLevel::name, + flashPowerLevel)); + std::cout << "2D scanning pattern role gain: " << patternRoleGain + << "dB, flash gain: " << flashGain << " dB, flash power level:" << flashPowerLevel + << "%. " << std::endl; int exposureMode2D = 0; showError(currentUserSet.getEnumValue(mmind::eye::scanning2d_setting::ExposureMode::name, diff --git a/profiler/Advanced/TransformPointCloud/TransformPointCloud.cpp b/profiler/Advanced/TransformPointCloud/TransformPointCloud.cpp index 3a6e5d4..31daf93 100644 --- a/profiler/Advanced/TransformPointCloud/TransformPointCloud.cpp +++ b/profiler/Advanced/TransformPointCloud/TransformPointCloud.cpp @@ -107,6 +107,9 @@ void setParameters(mmind::eye::UserSet& userSet) // Set the "Scan Line Count" parameter (the number of lines to be scanned) to 1600 showError(userSet.setIntValue(mmind::eye::scan_settings::ScanLineCount::name, 1600)); + // Set the "Travel Speed" parameter to 100 mm/s. This value is used to calculate the + // Y-axis resolution and scan distance when line scan is triggered at a fixed rate. + showError(userSet.setFloatValue(mmind::eye::trigger_settings::TravelSpeed::name, 100.0)); } // Convert the profile data to an untextured point cloud in the custom reference frame and save it @@ -135,6 +138,16 @@ void convertBatchToPointCloudWithTransformation( showError(status); return; } + + double scanDistance{}; + status = userSet.getFloatValue(mmind::eye::scan_settings::ScanDistance::name, scanDistance); + if (!status.isOK()) { + showError(status); + return; + } + std::cout << "Current Y-axis resolution: " << yResolution + << " um, scan distance: " << scanDistance << " um." << std::endl; + // // Uncomment the following lines for custom Y Unit // // Prompt to enter the desired encoder resolution, which is the travel distance corresponding // // to diff --git a/profiler/Advanced/TriggerMultipleProfilersSimultaneously/TriggerMultipleProfilersSimultaneously.cpp b/profiler/Advanced/TriggerMultipleProfilersSimultaneously/TriggerMultipleProfilersSimultaneously.cpp index afaa120..106cc3b 100644 --- a/profiler/Advanced/TriggerMultipleProfilersSimultaneously/TriggerMultipleProfilersSimultaneously.cpp +++ b/profiler/Advanced/TriggerMultipleProfilersSimultaneously/TriggerMultipleProfilersSimultaneously.cpp @@ -168,6 +168,9 @@ void setParameters(mmind::eye::UserSet& userSet) // Set the "Scan Line Count" parameter (the number of lines to be scanned) to 1600 showError(userSet.setIntValue(mmind::eye::scan_settings::ScanLineCount::name, 1600)); + // Set the "Travel Speed" parameter to 100 mm/s. This value is used to calculate the + // Y-axis resolution and scan distance when line scan is triggered at a fixed rate. + showError(userSet.setFloatValue(mmind::eye::trigger_settings::TravelSpeed::name, 100.0)); // Set the "Laser Power" parameter to 100 showError(userSet.setIntValue(mmind::eye::brightness_settings::LaserPower::name, 100)); @@ -238,13 +241,39 @@ bool acquireProfileData(mmind::eye::Profiler& profiler, mmind::eye::ProfileBatch totalBatch.clear(); totalBatch.reserve(captureLineCount); + const int kMaxEmptyRetrievalCount = 15; // About 3 s with 200 ms sleep + int emptyRetrievalCount = 0; while (totalBatch.height() < captureLineCount) { // Retrieve the profile data mmind::eye::ProfileBatch batch(dataWidth); status = profiler.retrieveBatchData(batch); if (status.isOK()) { - if (!totalBatch.append(batch)) + if (batch.isEmpty()) { + ++emptyRetrievalCount; + if (emptyRetrievalCount >= kMaxEmptyRetrievalCount) { + std::cout << "No new data received for " << emptyRetrievalCount + << " consecutive retrievals. Stop waiting for data." << std::endl; + break; + } + } else { + emptyRetrievalCount = 0; + if (!totalBatch.append(batch)) + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } else if (!isSoftwareTrigger && + status.errorCode == + mmind::eye::ErrorStatus::MMIND_STATUS_TIMEOUT_ERROR) { + // In external trigger mode, a retrieval timeout is expected when the trigger + // signal stops. Treat it the same as an empty retrieval instead of failing + // immediately. Other errors (e.g., device disconnect) should still cause + // failure. + ++emptyRetrievalCount; + if (emptyRetrievalCount >= kMaxEmptyRetrievalCount) { + std::cout << "No new data received for " << emptyRetrievalCount + << " consecutive retrievals. Stop waiting for data." << std::endl; break; + } std::this_thread::sleep_for(std::chrono::milliseconds(200)); } else { showError(status); diff --git a/profiler/Advanced/WarmUp/WarmUp.cpp b/profiler/Advanced/WarmUp/WarmUp.cpp index a3b8844..7e957aa 100644 --- a/profiler/Advanced/WarmUp/WarmUp.cpp +++ b/profiler/Advanced/WarmUp/WarmUp.cpp @@ -1,4 +1,4 @@ -/******************************************************************************* +/******************************************************************************* *BSD 3-Clause License * *Copyright (c) 2016-2025, Mech-Mind Robotics Technologies Co., Ltd. @@ -59,22 +59,19 @@ for stabilization and not saved. namespace { std::atomic kStopWarmup{false}; -const int kMinScanLineNum = 1; -const int kMaxScanLineNum = 20000; +constexpr int kMinScanLineNum = 1; +constexpr int kMaxScanLineNum = 20000; constexpr int kDefaultWarmupMinutes = 30; constexpr int kDefaultIntervalSeconds = 5; constexpr int kMinWarmupMinutes = 30; constexpr int kMaxWarmupMinutes = 90; constexpr int kMinIntervalSeconds = 3; constexpr int kMaxIntervalSeconds = 30; -std::condition_variable* g_captureCvPtr = nullptr; void handleInterrupt(int) { - std::cout << "\n[Interrupt] Ctrl+C received. Preparing to stop warmup..." << std::endl; + std::cout << "\n[Interrupt] Ctrl+C received. Prepare to stop warmup..." << std::endl; kStopWarmup.store(true); - if (g_captureCvPtr) - g_captureCvPtr->notify_all(); } } // namespace @@ -135,7 +132,6 @@ class WarmUp } std::cout << "LNX Profiler warmup finished." << std::endl; } - std::condition_variable& getConditionVariable() { return _captureCv; } ~WarmUp() { cleanup(); } private: @@ -191,7 +187,7 @@ class WarmUp break; } else { std::cout << "[Error] Scan line count must be in range " << kMinScanLineNum - << "–" << kMaxScanLineNum << "." << std::endl; + << "-" << kMaxScanLineNum << "." << std::endl; } } catch (...) { std::cout << "[Error] Invalid input. Please enter a valid integer." << std::endl; @@ -216,7 +212,7 @@ class WarmUp break; } else { std::cout << "[Error] Software trigger rate must be in range " - << _minSoftwareTriggerRate << "–" << _maxSoftwareTriggerRate << "." + << _minSoftwareTriggerRate << "-" << _maxSoftwareTriggerRate << "." << std::endl; } } catch (...) { @@ -295,7 +291,11 @@ class WarmUp const int totalWaitMs = _intervalSeconds * 1000; while (!kStopWarmup.load() && waitedMs < totalWaitMs) { - std::this_thread::sleep_for(std::chrono::milliseconds(pollIntervalMs)); + std::unique_lock lock(_waitMutex); + if (_captureCv.wait_for(lock, std::chrono::milliseconds(pollIntervalMs), + [this] { return kStopWarmup.load(); })) { + return true; + } waitedMs += pollIntervalMs; } return kStopWarmup.load(); @@ -309,8 +309,10 @@ class WarmUp void finishCaptureSuccessfully() { - std::lock_guard lock(_captureMutex); - _isCapturing = false; + { + std::lock_guard lock(_captureMutex); + _isCapturing = false; + } _captureCv.notify_one(); } @@ -477,6 +479,7 @@ class WarmUp mmind::eye::Profiler _profiler; std::atomic _isCapturing{false}; std::mutex _captureMutex; + std::mutex _waitMutex; std::condition_variable _captureCv; std::string _originalUserSet; std::string _warmupUserSet; @@ -558,10 +561,12 @@ int main(int argc, char** argv) { CommandLineArgs args = parseArguments(argc, argv); std::signal(SIGINT, handleInterrupt); + WarmUp warmup(args.warmupTimeMinutes, args.sampleIntervalSeconds); - g_captureCvPtr = &(warmup.getConditionVariable()); + if (!warmup.init()) return -1; + warmup.run(); return 0; } diff --git a/profiler/Basic/TriggerWithExternalDeviceAndEncoder/TriggerWithExternalDeviceAndEncoder.cpp b/profiler/Basic/TriggerWithExternalDeviceAndEncoder/TriggerWithExternalDeviceAndEncoder.cpp index 6f10057..2f2c7f3 100644 --- a/profiler/Basic/TriggerWithExternalDeviceAndEncoder/TriggerWithExternalDeviceAndEncoder.cpp +++ b/profiler/Basic/TriggerWithExternalDeviceAndEncoder/TriggerWithExternalDeviceAndEncoder.cpp @@ -99,7 +99,7 @@ void setEncoderTrigger( mmind::eye::UserSet& userSet, mmind::eye::trigger_settings::EncoderTriggerDirection::Value triggerDirection, mmind::eye::trigger_settings::EncoderTriggerSignalCountingMode::Value triggerSignalCountingMode, - int triggerInterval) + int triggerInterval, double encoderResolution) { // Set the "Line Scan Trigger Source" parameter to "Encoder" showError(userSet.setEnumValue( @@ -115,6 +115,10 @@ void setEncoderTrigger( // Set the (encoder) "Trigger Interval" parameter to 10 showError(userSet.setIntValue(mmind::eye::trigger_settings::EncoderTriggerInterval::name, triggerInterval)); + // Set the "Encoder Resolution" parameter (unit: μm). This is the travel distance + // corresponding to one quadrature signal. + showError(userSet.setFloatValue(mmind::eye::trigger_settings::EncoderResolution::name, + encoderResolution)); } void setParameters(mmind::eye::UserSet& userSet) @@ -160,9 +164,10 @@ void setParameters(mmind::eye::UserSet& userSet) // Set the (encoder) "Trigger Direction" parameter to "Both" // Set the (encoder) "Trigger Signal Counting Mode" parameter to "1×" // Set the (encoder) "Trigger Interval" parameter to 10 + // Set the "Encoder Resolution" parameter to 5 μm setEncoderTrigger( userSet, mmind::eye::trigger_settings::EncoderTriggerDirection::Value::Both, - mmind::eye::trigger_settings::EncoderTriggerSignalCountingMode::Value::Multiple_1, 10); + mmind::eye::trigger_settings::EncoderTriggerSignalCountingMode::Value::Multiple_1, 10, 5); // Set the "Scan Line Count" parameter (the number of lines to be scanned) to 1600 showError(userSet.setIntValue(mmind::eye::scan_settings::ScanLineCount::name, 1600)); diff --git a/profiler/Basic/TriggerWithExternalDeviceAndFixedRate/TriggerWithExternalDeviceAndFixedRate.cpp b/profiler/Basic/TriggerWithExternalDeviceAndFixedRate/TriggerWithExternalDeviceAndFixedRate.cpp index 1c56743..0997c33 100644 --- a/profiler/Basic/TriggerWithExternalDeviceAndFixedRate/TriggerWithExternalDeviceAndFixedRate.cpp +++ b/profiler/Basic/TriggerWithExternalDeviceAndFixedRate/TriggerWithExternalDeviceAndFixedRate.cpp @@ -143,6 +143,9 @@ void setParameters(mmind::eye::UserSet& userSet) // Set the "Scan Line Count" parameter (the number of lines to be scanned) to 1600 showError(userSet.setIntValue(mmind::eye::scan_settings::ScanLineCount::name, 1600)); + // Set the "Travel Speed" parameter to 100 mm/s. This value is used to calculate the + // Y-axis resolution and scan distance when line scan is triggered at a fixed rate. + showError(userSet.setFloatValue(mmind::eye::trigger_settings::TravelSpeed::name, 100.0)); // Set the "Laser Power" parameter to 100 showError(userSet.setIntValue(mmind::eye::brightness_settings::LaserPower::name, 100)); diff --git a/profiler/Basic/TriggerWithSoftwareAndEncoder/TriggerWithSoftwareAndEncoder.cpp b/profiler/Basic/TriggerWithSoftwareAndEncoder/TriggerWithSoftwareAndEncoder.cpp index 6038c22..069a88b 100644 --- a/profiler/Basic/TriggerWithSoftwareAndEncoder/TriggerWithSoftwareAndEncoder.cpp +++ b/profiler/Basic/TriggerWithSoftwareAndEncoder/TriggerWithSoftwareAndEncoder.cpp @@ -101,7 +101,7 @@ void setEncoderTrigger( mmind::eye::UserSet& userSet, mmind::eye::trigger_settings::EncoderTriggerDirection::Value triggerDirection, mmind::eye::trigger_settings::EncoderTriggerSignalCountingMode::Value triggerSignalCountingMode, - int triggerInterval) + int triggerInterval, double encoderResolution) { // Set the "Line Scan Trigger Source" parameter to "Encoder" showError(userSet.setEnumValue( @@ -117,6 +117,10 @@ void setEncoderTrigger( // Set the (encoder) "Trigger Interval" parameter to 10 showError(userSet.setIntValue(mmind::eye::trigger_settings::EncoderTriggerInterval::name, triggerInterval)); + // Set the "Encoder Resolution" parameter (unit: μm). This is the travel distance + // corresponding to one quadrature signal. + showError(userSet.setFloatValue(mmind::eye::trigger_settings::EncoderResolution::name, + encoderResolution)); } void setParameters(mmind::eye::UserSet& userSet) @@ -162,9 +166,10 @@ void setParameters(mmind::eye::UserSet& userSet) // Set the (encoder) "Trigger Direction" parameter to "Both" // Set the (encoder) "Trigger Signal Counting Mode" parameter to "1×" // Set the (encoder) "Trigger Interval" parameter to 10 + // Set the "Encoder Resolution" parameter to 5 μm setEncoderTrigger( userSet, mmind::eye::trigger_settings::EncoderTriggerDirection::Value::Both, - mmind::eye::trigger_settings::EncoderTriggerSignalCountingMode::Value::Multiple_1, 10); + mmind::eye::trigger_settings::EncoderTriggerSignalCountingMode::Value::Multiple_1, 10, 5); // Set the "Scan Line Count" parameter (the number of lines to be scanned) to 1600 showError(userSet.setIntValue(mmind::eye::scan_settings::ScanLineCount::name, 1600)); diff --git a/profiler/Basic/TriggerWithSoftwareAndFixedRate/TriggerWithSoftwareAndFixedRate.cpp b/profiler/Basic/TriggerWithSoftwareAndFixedRate/TriggerWithSoftwareAndFixedRate.cpp index e331d8a..62f3b4f 100644 --- a/profiler/Basic/TriggerWithSoftwareAndFixedRate/TriggerWithSoftwareAndFixedRate.cpp +++ b/profiler/Basic/TriggerWithSoftwareAndFixedRate/TriggerWithSoftwareAndFixedRate.cpp @@ -145,6 +145,9 @@ void setParameters(mmind::eye::UserSet& userSet) // Set the "Scan Line Count" parameter (the number of lines to be scanned) to 1600 showError(userSet.setIntValue(mmind::eye::scan_settings::ScanLineCount::name, 1600)); + // Set the "Travel Speed" parameter to 100 mm/s. This value is used to calculate the + // Y-axis resolution and scan distance when line scan is triggered at a fixed rate. + showError(userSet.setFloatValue(mmind::eye::trigger_settings::TravelSpeed::name, 100.0)); // Set the "Laser Power" parameter to 100 showError(userSet.setIntValue(mmind::eye::brightness_settings::LaserPower::name, 100)); diff --git a/profiler/CMakeLists.txt b/profiler/CMakeLists.txt index 76d15ad..b905f13 100644 --- a/profiler/CMakeLists.txt +++ b/profiler/CMakeLists.txt @@ -12,6 +12,9 @@ endif() option(USE_OPENCV "Enable samples which depend on OpenCV" ON) option(USE_PCL "Enable samples which depend on Point Cloud Library (PCL)" ON) +if(CMAKE_HOST_WIN32) + add_definitions(-DNOMINMAX) +endif() if(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "aarch64") add_compile_options(-mno-outline-atomics) endif() @@ -31,6 +34,8 @@ set(SAMPLES Basic/TriggerWithSoftwareAndEncoder Basic/TriggerWithSoftwareAndFixedRate Calibration/MultipleProfilersCalibration + Calibration/MoveDirVecCalibration + Calibration/WidthExpansionCalibration Util/ManageUserSets Util/PrintProfilerStatus Util/RenderDepthMap @@ -55,6 +60,8 @@ set(OpenCV_DEPENDING RenderDepthMap NoiseRemoval MultipleProfilersCalibration + MoveDirVecCalibration + WidthExpansionCalibration HandleNanAndNegativeInDepth ) diff --git a/profiler/Calibration/MoveDirVecCalibration/CMakeLists.txt b/profiler/Calibration/MoveDirVecCalibration/CMakeLists.txt new file mode 100644 index 0000000..314e41c --- /dev/null +++ b/profiler/Calibration/MoveDirVecCalibration/CMakeLists.txt @@ -0,0 +1,49 @@ +cmake_minimum_required(VERSION 3.5) + +project(MoveDirVecCalibration) + +set(CMAKE_CXX_STANDARD 17) + +if(CMAKE_HOST_UNIX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") +endif() + +set(Files ${PROJECT_SOURCE_DIR}/MoveDirVecCalibration.cpp) + +if(CMAKE_HOST_WIN32) + find_package(MechEyeApi REQUIRED CONFIG PATHS "$ENV{MECHEYE_DIR}/API") + if(NOT MechEyeApi_FOUND) + message( + FATAL_ERROR "MechEyeApi not found. Please install MechEyeApi first.") + endif() +elseif(CMAKE_HOST_UNIX) + find_package(PkgConfig) + if(NOT PkgConfig_FOUND) + message(FATAL_ERROR "PkgConfig not found.") + else() + pkg_check_modules(MECHEYEAPI REQUIRED MechEyeApi) + if(NOT MECHEYEAPI_FOUND) + message( + FATAL_ERROR "MechEyeApi not found. Please install MechEyeApi first.") + endif() + endif() +endif() + +# OpenCV_DIR: set as your OpenCV libraries directory; Uncomment next line to set +# OpenCV_DIR manually + +# set(OpenCV_DIR "path to OpenCV directory") +find_package(OpenCV REQUIRED) +if(NOT OpenCV_FOUND) + message( + FATAL_ERROR + "OpenCV not found. Please point OpenCV_DIR to the directory of your OpenCV installation (containing the file OpenCVConfig.cmake)." + ) +endif() + +include_directories(${MECHEYEAPI_INCLUDE_DIRS} ${OpenCV_INCLUDE_DIRS}) +link_directories(${MECHEYEAPI_LIBRARY_DIRS} ${OpenCV_LIBRARY_DIRS}) + +add_executable(${PROJECT_NAME} ${Files}) + +target_link_libraries(${PROJECT_NAME} ${MECHEYEAPI_LIBRARIES} ${OpenCV_LIBS}) \ No newline at end of file diff --git a/profiler/Calibration/MoveDirVecCalibration/MoveDirVecCalibration.cpp b/profiler/Calibration/MoveDirVecCalibration/MoveDirVecCalibration.cpp new file mode 100644 index 0000000..d8aac10 --- /dev/null +++ b/profiler/Calibration/MoveDirVecCalibration/MoveDirVecCalibration.cpp @@ -0,0 +1,210 @@ +#include +#include +#include "../SingleProfilersCalibration.h" + +using namespace mmind::eye; +using Calibration = mmind::eye::ProfilerCalibrationInterfaces; + +/** + * @brief Entry of single profiler movement direction calibration and correction demo. + * + * This demo demonstrates the complete workflow of: + * 1) Capturing depth images using a single line-scan profiler + * 2) Calibrating the movement direction vector of the profiler + * 3) Correcting the scanning direction to obtain a rectified depth image + * 4) Removing pixel offset to align the result back to the camera coordinate system + * + * The final aligned depth image can be directly used for stitching or further measurement. + */ +int main() +{ + // Step 1. Input target parameters. + TargetSize targetSize; + while (true) { + std::cout << "\nPlease input target parameters:\n"; + std::cout << "Target top length: "; + targetSize.targetTopLength = getInputNumber(); + + std::cout << "Target bottom length: "; + targetSize.targetBottomLength = getInputNumber(); + + std::cout << "Target height: "; + targetSize.targetHeight = getInputNumber(); + + std::cout << "\nConfirm target parameters:\n" + << "Top Length : " << targetSize.targetTopLength << "\n" + << "Bottom Length : " << targetSize.targetBottomLength << "\n" + << "Height : " << targetSize.targetHeight << "\n" + << "Continue? (y/n): "; + + char confirm; + std::cin >> confirm; + if (confirm == 'y' || confirm == 'Y') { + break; + } + } + + // Step 2. Connect profiler and acquire device information. + auto profilerOpt = findAndConnectProfilerForCalibration(); + if (!profilerOpt) { + std::cout << "Failed to connect profiler.\n"; + return -1; + } + auto profiler = profilerOpt.value(); + + // Get the device Info by retrieving user input. + std::cout << "\nGet the major deviceInfo" << std::endl; + DeviceInfo profilerDeviceInfo = getDeviceInfo(profiler); + + // Get the profiler info + mmind::eye::ProfilerInfo profilerInfo; + auto status = profiler.getProfilerInfo(profilerInfo); + if (!status.isOK()) { + showError(status); + return -1; + } + + /************************************************************************** + * Step 3. Capture images for movement direction calibration + * + * Two depth images are required: + * - depth1: captured during the first scan (A -> B) + * - depth2: captured during the second scan (C -> D) + **************************************************************************/ + std::vector profilerImages; + // Start to capture images for calibration. + if (!captureImages(profiler, profilerImages) || profilerImages.size() < 2) { + std::cerr << "Failed to capture sufficient images for calibration.\n"; + return -1; + } + + // Step 4. Configure calibration parameters. + std::vector targetsPoses; + TargetPose targetPose; + + bool continuePoseProcess = false; + while (!continuePoseProcess) { + std::cout << "\nEnter the distance between targets: " << std::endl; + targetPose.translateDistance = getInputNumber(); + + std::cout << "\nEnter the rotation angle between targets: " << std::endl; + targetPose.rotateAngleInDegree = getInputNumber(); + + std::cout << "\nEnter the rotation radius between targets: " << std::endl; + targetPose.rotateRadius = getInputNumber(); + + // Show the input values for confirmation. + std::cout << "\nYou entered the following values:" + << "\nDistance: " << targetPose.translateDistance + << "\nRotation Angle: " << targetPose.rotateAngleInDegree + << "\nRotation Radius: " << targetPose.rotateRadius << std::endl; + // Ask user if they want to continue or reinput + std::cout << "\nContinue with the process? (y to continue, any other key to reinput): "; + char choice; + std::cin >> choice; + continuePoseProcess = (choice == 'y' || choice == 'Y'); + } + // Set the calib mode. + ProfilerCalibrationMode calibMode = inputCalibType(); + + // Set the transform axis. + TargetTransformAxis transformAxis = inputTargetTransformAxis(); + + // Set the translation/rotation axis parameters based on the calibration mode type. + targetPose.mode = calibMode; + + switch (calibMode) { + case ProfilerCalibrationMode::Wide: + targetPose.translateAxis = TargetTranslateAxis(static_cast(transformAxis)); + targetPose.rotateAxis = TargetRotateAxis::NullAxis; + break; + + case ProfilerCalibrationMode::Angle: + targetPose.rotateAxis = TargetRotateAxis(static_cast(transformAxis)); + targetPose.translateAxis = TargetTranslateAxis::NullAxis; + break; + } + targetsPoses.push_back(targetPose); + + ProfilerMovementAxis moveAxis = inputMovementAxis(); + + std::cout << "\n+++++++Starting movement direction calibration++++++" << std::endl; + + // Set the params for calibration. + Calibration calibInstance(profilerInfo.model, profilerDeviceInfo, targetSize, targetsPoses); + + MoveDirCalibResult calibResult; + + auto errorStatus = calibInstance.calibrateSingleProfilerMoveDirection( + profilerImages[0].depth, profilerImages[1].depth, moveAxis, calibResult); + + if (!errorStatus.isOK()) { + printError(errorStatus); + return -1; + } + + std::cout << "\nCalibration completed successfully." << std::endl; + std::cout << "Movement direct vector is : {" << calibResult.moveDirVec.x << "," + << calibResult.moveDirVec.y << "," << calibResult.moveDirVec.z << "}" << std::endl; + + /************************************************************************** + * Step 6. Correct scanning direction (rotation only) + * + * This step rectifies the scanning direction so that the scan axis aligns + * with the camera coordinate system. The output image is direction-corrected + * but may contain a pixel offset relative to the camera origin. + **************************************************************************/ + Calibration::CorrectedResult correctedResult; + + errorStatus = calibInstance.correctProfilerMoveDir(calibResult.moveDirVec, profilerImages[0], + correctedResult); + + if (!errorStatus.isOK()) { + printError(errorStatus); + return -1; + } + + std::cout << "\nDirection correction completed." << std::endl; + std::cout << "Pixel offset to camera origin: [" << correctedResult.pixelBiasToCamCoord.x << ", " + << correctedResult.pixelBiasToCamCoord.y << "] (pixels)\n"; + + /************************************************************************** + * Step 7. Remove pixel offset (align back to camera coordinate system) + * + * The correction step rotates the scan around the camera origin, which + * introduces a pixel offset. This offset must be removed before the result + * can be used for stitching or multi-frame fusion. + **************************************************************************/ + cv::Mat alignedDepth; + cv::Mat translationMat = + (cv::Mat_(2, 3) << 1, 0, -correctedResult.pixelBiasToCamCoord.x, 0, 1, + -correctedResult.pixelBiasToCamCoord.y); + + cv::warpAffine(correctedResult.depth, alignedDepth, translationMat, + correctedResult.depth.size(), cv::INTER_NEAREST, cv::BORDER_CONSTANT, + cv::Scalar(std::numeric_limits::quiet_NaN())); + + // Save results for verification. + cv::imwrite("01_raw_depth.tiff", profilerImages[0].depth); + cv::imwrite("02_corrected_depth_with_offset.tiff", correctedResult.depth); + cv::imwrite("03_aligned_depth_for_stitching.tiff", alignedDepth); + + std::cout << "\nDemo completed successfully.\n"; + std::cout << "Saved results:\n" + << " - 01_raw_depth.tiff\n" + << " - 02_corrected_depth_with_offset.tiff\n" + << " - 03_aligned_depth_for_stitching.tiff\n"; + + /************************************************************************** + * // Pixel-to-physical resolution (example: mm per pixel) + * constexpr double kPixelResolutionX = 0.0235; // mm / pixel + * constexpr double kPixelResolutionY = 0.0235; + * + * // Convert pixel bias to physical offset (mm) + * double offsetX_mm = correctedResult.pixelBiasToCamCoord.x * kPixelResolutionX; + * double offsetY_mm = correctedResult.pixelBiasToCamCoord.y * kPixelResolutionY; + * // Use these values in camera/world coordinate calibration + **************************************************************************/ + + return 0; +} \ No newline at end of file diff --git a/profiler/Calibration/MoveDirVecCalibration/README.md b/profiler/Calibration/MoveDirVecCalibration/README.md new file mode 100644 index 0000000..9c44d72 --- /dev/null +++ b/profiler/Calibration/MoveDirVecCalibration/README.md @@ -0,0 +1,221 @@ +# MoveDirVecCalibration Sample + +Using this sample, you can complete the calibration of the movement direction vector for a single laser profiler, obtain the calibrated movement direction vector, the corresponding reprojection error, and correction parameters for downstream processing (including depth maps and pixel offsets obtained from direction correction). The sample aims for engineering reproducibility and result acceptability, demonstrating a complete closed-loop process from acquisition to correction and back to the camera coordinate system. + +If you have any questions or have anything to share, feel free to post on the [Mech-Mind Online Community](https://community.mech-mind.com/). The community also contains a [specific category for development with Mech-Eye SDK](https://community.mech-mind.com/c/mech-eye-sdk-development/19). + +## Build the Sample + +Prerequisites and instructions for building the sample on Windows and Ubuntu are provided. + +### Windows + +#### Prerequisites + +The following software are required to build this sample. Please download and install these software. + +- [Mech-Eye SDK (latest version)](https://downloads.mech-mind.com/?tab=tab-sdk) +- [Visual Studio (version 2017 or above)](https://visualstudio.microsoft.com/vs/community/) +- [CMake (version 3.2 or above)](https://cmake.org/download/) +- [OpenCV (version 3.4.5 or above)](https://opencv.org/releases/) + +#### Instructions + +1. Make sure that the sample is stored in a location with read and write permissions. +2. Add the following directories to the **Path** environment variable: + + - `xxx/opencv/build/x64/vc14/bin` + - `xxx/opencv/build/x64/vc14/lib` + +3. Run Cmake and set the source and build paths: + + | Field | Path | + | :-------------------------- | :------------------------------ | + | Where is the source code | xxx/MoveDirVecCalibration | + | Where to build the binaries | xxx/MoveDirVecCalibration/build | + +4. Click the **Configure** button. In the pop-up window, set the generator and platform according to the actual situation, and then click the **Finish** button. +5. When the log displays **Configuring done**, click the **Generate** button. When the log displays **Generating done**, click the **Open Project** button. +6. In Visual Studio toolbar, change the solution configuration from **Debug** to **Release**. +7. In the **Solution Explorer** panel, right-click the sample, and select **Set as Startup Project**. +8. Click the **Local Windows Debugger** button in the toolbar to run the sample. +9. During runtime, enter the index of the laser profiler you want to connect to as prompted. The program will save the generated result files to the build folder (the sample saves three types of files: original depth, corrected depth with offset, and aligned depth (returned to camera coordinate system) for verifying the geometric meaning of each step). + +### Ubuntu + +Ubuntu 18 or above is required. + +#### Prerequisites + +- Update the software source list. + + ```bash + sudo apt-get update + ``` + +- Install required tools. + + ```bash + sudo apt-get install -y build-essential pkg-config cmake + ``` + +- Install [Mech-Eye SDK (latest version)](https://downloads.mech-mind.com/?tab=tab-sdk). + + > Note: If you have installed Mech-Eye SDK before, please uninstall it first with the following command: + > + > ```bash + > sudo dpkg -P MechEyeApi + > ``` + +- If the system architecture is AMD64, execute the following command: + + ```bash + sudo dpkg -i 'Mech-Eye_API_x.x.x_amd64.deb' + ``` + +- If the system architecture is ARM64, execute the following command: + + ```bash + sudo dpkg -i 'Mech-Eye_API_x.x.x_arm64.deb' + ``` + +- Install third-party libraries: OpenCV is required. + + - Install OpenCV (latest version): + + ```bash + sudo apt update && sudo apt install -y unzip + wget -O opencv.zip https://github.com/opencv/opencv/archive/4.x.zip + unzip opencv.zip + mkdir build && cd build + cmake ../opencv-4.x + cmake --build . + sudo make install + ``` + + > Note: On Ubuntu, OpenCV 4.x is recommended. Ensure that CMake can find OpenCVConfig.cmake during the build process, or specify the path using -DOpenCV_DIR=.... + +#### Instructions + +1. Navigate to the directory of the sample. + + ```bash + cd xxx/profiler/Calibration/MoveDirVecCalibration + ``` + +2. Configure and build the sample. + + ```bash + sudo mkdir build && cd build + sudo cmake .. + sudo make + ``` + +3. Run the sample. + + ```bash + sudo ./MoveDirVecCalibration + ``` + +4. Enter the index of the profiler to connect to as prompted and press Enter. The program will save the generated results to `/MoveDirVecCalibration/build` (or the current build directory), including the original depth, direction correction results (with pixel offset), and aligned depth map for stitching, facilitating step-by-step verification.. + +## Sample Usage + +### Overview + +This sample demonstrates the complete calibration and correction process for a single profiler moving along a specified direction. The calibration results can be used for subsequent stitching or measurement. The key steps are as follows (the sample saves the results of each step as independent files for verification and troubleshooting): + +1. Input calibration target dimensions and pose parameters (to reconstruct geometry and improve calibration stability). +2. Connect to and identify the target profiler device. +3. Capture depth (and optionally intensity) images: at least two frames are required for calibration (A->B and C->D). +4. Perform single-direction movement vector calibration to obtain moveDirVec and reprojection error. +5. Use moveDirVec to perform direction correction (rotation/resampling) on the captured depth maps, resulting in correctedDepth and returning pixelBiasToCamCoord (pixel offset). +6. Use the returned pixel offset to translate correctedDepth back to the original camera coordinate system (removing the offset), resulting in the final alignedDepth which can be directly used for stitching/measurement. +7. Save and output three types of files (as named in the sample) for comparative verification: + +- 01_raw_depth.tiff: Original captured depth (scan coordinate system) +- 02_corrected_depth_with_offset.tiff: Direction-corrected (with offset) +- 03_aligned_depth_for_stitching.tiff: Offset-removed, aligned to camera coordinate system (ready for stitching) + +--- + +### 1. Input Target Dimensions + +Input the calibration target frustum parameters: + +- **Top Length** (mm): Upper base length of the frustums (e.g., `100.0`). +- **Bottom Length** (mm): Lower base length of the frustums (e.g., `150.0`). +- **Height** (mm): Height of the frustums (e.g., `50.0`). + These parameters are used to reconstruct the target geometry during calibration and constrain point cloud matching, improving calibration robustness. + +--- + +### 2. Connect Profiler + +- The program automatically detects and lists visible profiler devices. +- Ensure the device is powered on, connected, and within the visible range. +- Enter the index of the device to connect to as prompted and press Enter. + +### 3. Input Target Pose Parameters + +For the single profiler: + +- Translation Mode: + - Distance between two target frustums (mm) + - Translation axis: X, Y, or Z +- Rotation Mode: + + - Rotation angle (°) + - Rotation radius (mm) + - Rotation axis: X, Y, or Z + +Select the appropriate mode based on the actual calibration stage movement type and input the parameters. + +--- + +### 4. Capture Images + +- The sample automatically captures depth and intensity maps. +- Capture requirements: The target must be clearly visible in the field of view, and the start/end positions of the movement segment must match the documentation (to ensure consistent movement between the two captured frames). +- The two captured depth frames serve as input for calibration: depth1 (A->B) and depth2 (C->D). + +--- + +### 5. Perform Move Direction Calibration + +- Calculate the actual movement direction vector moveDirVec of the profiler (unit vector in the camera coordinate system) using the two depth maps. +- Output the reprojection errors of the two calibrations (to determine calibration quality). +- It is strongly recommended to first calibrate and lock Y (the main scanning direction) before estimating the X/Z components to improve X/Z calibration accuracy (see comments for details). + +--- + +### 6. Correct Depth Map + +- Use moveDirVec to perform direction correction on the captured depth maps (mapping the scan coordinate system to the ideal camera coordinate system). +- The correction process involves rotation and resampling to align the scan line direction with the camera Y-axis. +- Correction outputs include: + - Corrected depth map (corrected.depth, with direction corrected but origin possibly translated) + - Pixel offset pixelBiasToCamCoord (in pixels, representing the translation of the corrected image relative to the original camera coordinate system origin) + - (In the sample) corrected.depth needs to be translated based on the pixel offset to return to the camera coordinate system, resulting in alignedDepth, which can be directly used for stitching or measurement. + +--- + +### Key Notes + +#### File Paths + +- Use **English-only characters** in paths (e.g., avoid characters in `中文` or `日本語`) +- Ensure write permissions to target folders + +#### Error Handling + +- The sample prints detailed error information. If data acquisition or calibration fails, check the device connection, target placement, and acquisition parameters as prompted and try again. +- Common failure reasons include: target occlusion, inconsistent movement between captured frames, poor alignment between camera and target, etc. + +### Troubleshooting Tips + +| Error Message | Solution | +| ---------------------- | ------------------------------------------------ | +| "Capture failed" | Ensure all laser profilers are visible to system | +| "Failed to save files" | Verify folder permissions and path validity | +| Calibration errors | Re-measure target dimensions and retry | diff --git a/profiler/Calibration/MultipleProfilersCalibration/MultipleProfilersCalibrationUtil.cpp b/profiler/Calibration/MultipleProfilersCalibration/MultipleProfilersCalibrationUtil.cpp index 8c35371..7ee35e8 100644 --- a/profiler/Calibration/MultipleProfilersCalibration/MultipleProfilersCalibrationUtil.cpp +++ b/profiler/Calibration/MultipleProfilersCalibration/MultipleProfilersCalibrationUtil.cpp @@ -294,8 +294,8 @@ mmind::eye::DeviceInfo getDeviceInfo(mmind::eye::Profiler& profiler) std::cout << "Please confirm the following device settings:" << std::endl; std::cout << "--------------------------------------------" << std::endl; - std::cout << "1. X-Axis Resolution (um): " << float(xResolution / 1000) << std::endl; - std::cout << "2. Y-Axis Resolution (um): " << float(yResolution / 1000) << std::endl; + std::cout << "1. X-Axis Resolution (mm): " << float(xResolution / 1000) << std::endl; + std::cout << "2. Y-Axis Resolution (mm): " << float(yResolution / 1000) << std::endl; std::cout << "3. Downsampling Factor (X): " << downsampleX << std::endl; std::cout << "4. Downsampling Factor (Y): " << downsampleY << std::endl; std::cout << "5. Motion Direction Sign: " << directionPositive << std::endl; diff --git a/profiler/Calibration/SingleProfilersCalibration.h b/profiler/Calibration/SingleProfilersCalibration.h new file mode 100644 index 0000000..0d3437b --- /dev/null +++ b/profiler/Calibration/SingleProfilersCalibration.h @@ -0,0 +1,398 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include "profiler/calibration/ProfilerCalibrationInterfaces.h" +#include "profiler/parameters/RawImageParameters.h" +#include "profiler/api_util.h" + +// Enumeration for target transformation axis +enum class TargetTransformAxis { X, Y, Unknown }; + +// Get keyboard input as a number +template +T getInputNumber() +{ + if (std::cin.rdbuf()->in_avail() != 0) { + std::cin.ignore((std::numeric_limits::max)(), '\n'); + } + T input; + std::string inputStr; + while (true) { + std::cin.clear(); + std::getline(std::cin, inputStr); + std::istringstream iss(inputStr); + if (iss >> input && iss.eof()) { + break; + } else { + std::cout << "Invalid input. Please enter a valid number." << std::endl; + } + } + return input; +} + +/* Discovers and connects Mech-Eye 3D Laser Profilers for calibration.*/ +std::optional findAndConnectProfilerForCalibration() +{ + std::cout << "Find Mech-Eye 3D Laser Profilers..." << std::endl; + std::vector profilerInfoList = + mmind::eye::Profiler::discoverProfilers(); + + if (profilerInfoList.empty()) { + std::cout << "No Mech-Eye 3D Laser Profilers found." << std::endl; + return {}; + } + + for (size_t i = 0; i < profilerInfoList.size(); i++) { + std::cout << "Mech-Eye 3D Laser Profiler index : " << i << std::endl; + printProfilerInfo(profilerInfoList[i]); + } + + std::string str; + unsigned index = 0; + std::cout << "Please enter the device index you want to choose as calibration profiler: " + << std::endl; + + std::cin >> str; + if (std::regex_match(str.begin(), str.end(), std::regex{"[0-9]+"}) && + static_cast(atoi(str.c_str())) < profilerInfoList.size()) { + index = static_cast(atoi(str.c_str())); + } else { + std::cout << "Input invalid. Please enter the device index you want to connect: "; + return {}; + } + // Connect to selected profilers + mmind::eye::Profiler profiler; + auto status = profiler.connect(profilerInfoList[index]); + if (!status.isOK()) { + showError(status); + return std::nullopt; + } + return profiler; +} +// Get device information and interact with the user for additional parameters +mmind::eye::DeviceInfo getDeviceInfo(mmind::eye::Profiler& profiler) +{ + mmind::eye::UserSet userSet = profiler.currentUserSet(); + mmind::eye::ProfilerInfo profilerInfo; + auto status = profiler.getProfilerInfo(profilerInfo); + if (!status.isOK()) { + showError(status); + return {}; + } + printProfilerInfo(profilerInfo); + + // Get X-axis resolution + double xResolution{}; + status = userSet.getFloatValue(mmind::eye::point_cloud_resolutions::XAxisResolution::name, + xResolution); + if (!status.isOK()) { + showError(status); + return {}; + } + + /*When scanning is triggered by an encoder, the Y - axis resolution can be calculated using the + following equation : + Y-axis resolution = encoder resolution * Trigger Interval / Trigger Signal Counting Mode * 4*/ + double yResolution{}; + status = + userSet.getFloatValue(mmind::eye::point_cloud_resolutions::YResolution::name, yResolution); + if (!status.isOK()) { + showError(status); + return {}; + } + + // Get ROI (Region of Interest) value + mmind::eye::ProfileROI roiValue; + status = userSet.getProfileRoiValue(mmind::eye::roi::ROI::name, roiValue); + if (!status.isOK()) { + showError(status); + return {}; + } + + // Prompt user for downsampling intervals and camera motion direction + std::cout << "\nEnter the downsampling interval in the X direction: " << std::endl; + unsigned int downsampleX = getInputNumber(); + + std::cout << "\nEnter the downsampling interval in the Y direction: " << std::endl; + unsigned int downsampleY = getInputNumber(); + + std::cout << "\nEnter the Camera motion direction: " << std::endl; + bool directionPositive = getInputNumber(); + + std::cout << "Please confirm the following device settings:" << std::endl; + std::cout << "--------------------------------------------" << std::endl; + std::cout << "1. X-Axis Resolution (mm): " << float(xResolution / 1000) << std::endl; + std::cout << "2. Y-Axis Resolution (mm): " << float(yResolution / 1000) << std::endl; + std::cout << "3. Downsampling Factor (X): " << downsampleX << std::endl; + std::cout << "4. Downsampling Factor (Y): " << downsampleY << std::endl; + std::cout << "5. Motion Direction Sign: " << directionPositive << std::endl; + std::cout << "6. ROI Size (Width , Height): (" << roiValue.width << " , " << roiValue.height + << ")" << std::endl; + std::cout << "7. ROI Center (X, Y): (" << roiValue.xAxisCenter << " , " << 0 << ")" + << std::endl; + std::cout << "--------------------------------------------" << std::endl; + + return { + float(xResolution / 1000), + float(yResolution / 1000), + downsampleX, + downsampleY, + directionPositive, + {static_cast(roiValue.width), static_cast(roiValue.height)}, + {static_cast(roiValue.xAxisCenter), 0}, + }; +} + +// Print error messages when stitching fails +void printError(mmind::eye::MultiProfilerErrorStatus errorStatus) +{ + std::cout << "\nerrorStatus:" << errorStatus.errorCode << std::endl; + std::cout << "\nerrorDescription:" << errorStatus.errorDescription << std::endl; + std::cout << "\nerrorSource:" << errorStatus.errorSource << " " << errorStatus.groupID + << std::endl; +} + +// Set the calibration mode based on user input +mmind::eye::WidthExpansionType inputWidthExpansionCalibType() +{ + while (true) { + std::cout << "\nEnter the number that represents the calibration types:" + << "\n1: S" + << "\n2: Z" + << "\n3: Disorder" << std::endl; + + switch (getInputNumber()) { + case 1: + return mmind::eye::WidthExpansionType::S; + case 2: + return mmind::eye::WidthExpansionType::Z; + case 3: + return mmind::eye::WidthExpansionType::Disorder; + default: + std::cout << "Invalid input! Please enter 1��2 or 3." << std::endl; + } + } +} + +// Set the calibration mode based on user input +mmind::eye::ProfilerCalibrationMode inputCalibType() +{ + while (true) { + std::cout << "\nEnter the number that represents the calibration types:" + << "\n1: Wide" + << "\n2: Angle" << std::endl; + + switch (getInputNumber()) { + case 1: + return mmind::eye::ProfilerCalibrationMode::Wide; + case 2: + return mmind::eye::ProfilerCalibrationMode::Angle; + default: + std::cout << "Invalid input! Please enter 1 or 2." << std::endl; + } + } +} + +mmind::eye::ProfilerMovementAxis inputMovementAxis() +{ + while (true) { + std::cout << "\nEnter the number that represents the movement axis:" + << "\n1: X" + << "\n2: Y" + << "\n3: Z" << std::endl; + + switch (getInputNumber()) { + case 1: + return mmind::eye::ProfilerMovementAxis::X; + case 2: + return mmind::eye::ProfilerMovementAxis::Y; + case 3: + return mmind::eye::ProfilerMovementAxis::Z; + default: + std::cout << "Invalid input! Please enter 1,2 or 3 ." << std::endl; + } + } +} + +// Set the target transformation axis based on user input +TargetTransformAxis inputTargetTransformAxis() +{ + while (true) { + std::cout << "\nEnter the number that represents the transform axis:" + << "\n1: X" + << "\n2: Y" << std::endl; + + switch (getInputNumber()) { + case 1: + return TargetTransformAxis::X; + case 2: + return TargetTransformAxis::Y; + default: + std::cout << "Invalid input! Please enter 1 or 2." << std::endl; + } + } +} + +// Acquire profile data from a profiler +bool acquireProfileData(mmind::eye::Profiler& profiler, mmind::eye::ProfileBatch& totalBatch, + int captureLineCount, int dataWidth, bool isSoftwareTrigger) +{ + std::cout << "Start data acquisition." << std::endl; + auto status = profiler.startAcquisition(); + if (!status.isOK()) { + showError(status); + return false; + } + + if (isSoftwareTrigger) { + status = profiler.triggerSoftware(); + if (!status.isOK()) { + showError(status); + return false; + } + } + + totalBatch.clear(); + totalBatch.reserve(captureLineCount); + const int kMaxEmptyRetrievalCount = 15; // About 3 s with 200 ms sleep + int emptyRetrievalCount = 0; + while (totalBatch.height() < captureLineCount) { + mmind::eye::ProfileBatch batch(dataWidth); + status = profiler.retrieveBatchData(batch); + if (status.isOK()) { + if (batch.isEmpty()) { + ++emptyRetrievalCount; + if (emptyRetrievalCount >= kMaxEmptyRetrievalCount) { + std::cout << "No new data received for " << emptyRetrievalCount + << " consecutive retrievals. Stop waiting for data." << std::endl; + break; + } + } else { + emptyRetrievalCount = 0; + if (!totalBatch.append(batch)) + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } else if (!isSoftwareTrigger && + status.errorCode == + mmind::eye::ErrorStatus::MMIND_STATUS_TIMEOUT_ERROR) { + // In external trigger mode, a retrieval timeout is expected when the trigger + // signal stops. Treat it the same as an empty retrieval instead of failing + // immediately. Other errors (e.g., device disconnect) should still cause + // failure. + ++emptyRetrievalCount; + if (emptyRetrievalCount >= kMaxEmptyRetrievalCount) { + std::cout << "No new data received for " << emptyRetrievalCount + << " consecutive retrievals. Stop waiting for data." << std::endl; + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } else { + showError(status); + return false; + } + } + + std::cout << "Stop data acquisition." << std::endl; + status = profiler.stopAcquisition(); + if (!status.isOK()) + showError(status); + return status.isOK(); +} + +// Asynchronously capture images from a profiler +mmind::eye::ProfilerImage captureAsync(mmind::eye::Profiler& profiler) +{ + mmind::eye::ProfilerInfo profilerInfo; + showError(profiler.getProfilerInfo(profilerInfo)); + + // Select "calib" user set to capture image. + const std::string calibSetting{"calib"}; + mmind::eye::UserSetManager& userSetManager = profiler.userSetManager(); + std::string successMessage = "Set current set as the \"" + calibSetting + "\" user set."; + showError(userSetManager.selectUserSet(calibSetting), successMessage); + + mmind::eye::UserSet userSet = profiler.currentUserSet(); + + int dataWidth = 0; + showError( + userSet.getIntValue(mmind::eye::scan_settings::DataPointsPerProfile::name, dataWidth)); + int captureLineCount = 0; + userSet.getIntValue(mmind::eye::scan_settings::ScanLineCount::name, captureLineCount); + + mmind::eye::ProfileBatch profileBatch(dataWidth); + + int dataAcquisitionTriggerSource{}; + showError(userSet.getEnumValue(mmind::eye::trigger_settings::DataAcquisitionTriggerSource::name, + dataAcquisitionTriggerSource)); + + //// Adjust the "Trigger Delay" appropriately to avoid interference between devices and ensure + /// optimal imaging performance. + // showError(userSet.setEnumValue(mmind::eye::trigger_settings::TriggerDelay::name, 100)); + + bool isSoftwareTrigger = + dataAcquisitionTriggerSource == + static_cast( + mmind::eye::trigger_settings::DataAcquisitionTriggerSource::Value::Software); + + if (!acquireProfileData(profiler, profileBatch, captureLineCount, dataWidth, isSoftwareTrigger)) + return {}; + + if (profileBatch.checkFlag(mmind::eye::ProfileBatch::BatchFlag::Incomplete)) + std::cout << "Part of the batch's data is lost, the number of valid profiles is: " + << profileBatch.validHeight() << "." << std::endl; + + mmind::eye::ProfilerImage result; + result.depth = + cv::Mat(captureLineCount, dataWidth, CV_32FC1, profileBatch.getDepthMap().data()).clone(); + result.intensity = + cv::Mat(captureLineCount, dataWidth, CV_8UC1, profileBatch.getIntensityImage().data()) + .clone(); + return result; +} + +// Capture images from profiler +bool captureImages(mmind::eye::Profiler& profiler, + std::vector& profilerImages) +{ + while (true) { + // Confirm capture with the user + if (!confirmCapture()) { + profiler.disconnect(); + break; + } + auto profilerImage = captureAsync(profiler); + profilerImages.push_back(profilerImage); + } + return true; +} + +// Capture images from profiler +bool captureImages(mmind::eye::Profiler& profiler, + std::vector& profilerImages, + mmind::eye::ProfilerImage& firstImage) +{ + bool isFirst = true; + + while (true) { + if (!confirmCapture()) { + profiler.disconnect(); + break; + } + + auto img = captureAsync(profiler); + + if (isFirst) { + firstImage = img; + isFirst = false; + } else { + profilerImages.push_back(img); + } + } + + return true; +} diff --git a/profiler/Calibration/WidthExpansionCalibration/CMakeLists.txt b/profiler/Calibration/WidthExpansionCalibration/CMakeLists.txt new file mode 100644 index 0000000..c82bec4 --- /dev/null +++ b/profiler/Calibration/WidthExpansionCalibration/CMakeLists.txt @@ -0,0 +1,49 @@ +cmake_minimum_required(VERSION 3.5) + +project(WidthExpansionCalibration) + +set(CMAKE_CXX_STANDARD 17) + +if(CMAKE_HOST_UNIX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") +endif() + +set(Files ${PROJECT_SOURCE_DIR}/WidthExpansionCalibration.cpp) + +if(CMAKE_HOST_WIN32) + find_package(MechEyeApi REQUIRED CONFIG PATHS "$ENV{MECHEYE_DIR}/API") + if(NOT MechEyeApi_FOUND) + message( + FATAL_ERROR "MechEyeApi not found. Please install MechEyeApi first.") + endif() +elseif(CMAKE_HOST_UNIX) + find_package(PkgConfig) + if(NOT PkgConfig_FOUND) + message(FATAL_ERROR "PkgConfig not found.") + else() + pkg_check_modules(MECHEYEAPI REQUIRED MechEyeApi) + if(NOT MECHEYEAPI_FOUND) + message( + FATAL_ERROR "MechEyeApi not found. Please install MechEyeApi first.") + endif() + endif() +endif() + +# OpenCV_DIR: set as your OpenCV libraries directory; Uncomment next line to set +# OpenCV_DIR manually + +# set(OpenCV_DIR "path to OpenCV directory") +find_package(OpenCV REQUIRED) +if(NOT OpenCV_FOUND) + message( + FATAL_ERROR + "OpenCV not found. Please point OpenCV_DIR to the directory of your OpenCV installation (containing the file OpenCVConfig.cmake)." + ) +endif() + +include_directories(${MECHEYEAPI_INCLUDE_DIRS} ${OpenCV_INCLUDE_DIRS}) +link_directories(${MECHEYEAPI_LIBRARY_DIRS} ${OpenCV_LIBRARY_DIRS}) + +add_executable(${PROJECT_NAME} ${Files}) + +target_link_libraries(${PROJECT_NAME} ${MECHEYEAPI_LIBRARIES} ${OpenCV_LIBS}) \ No newline at end of file diff --git a/profiler/Calibration/WidthExpansionCalibration/README.md b/profiler/Calibration/WidthExpansionCalibration/README.md new file mode 100644 index 0000000..c3bb407 --- /dev/null +++ b/profiler/Calibration/WidthExpansionCalibration/README.md @@ -0,0 +1,204 @@ +# WidthExpansionCalibration Sample + +This sample demonstrates width-expansion calibration and stitching for a single laser profiler. The calibration estimates horizontal expansion parameters using reference position biases, generates the stitching configuration, and outputs the final results along with corrected calibration parameters. + +If you have any questions or have anything to share, feel free to post on the [Mech-Mind Online Community](https://community.mech-mind.com/). The community also contains a [specific category for development with Mech-Eye SDK](https://community.mech-mind.com/c/mech-eye-sdk-development/19). + +## Build the Sample + +Prerequisites and instructions for building the sample on Windows and Ubuntu are provided. + +### Windows + +#### Prerequisites + +The following software are required to build this sample. Please download and install these software. + +- [Mech-Eye SDK (latest version)](https://downloads.mech-mind.com/?tab=tab-sdk) +- [Visual Studio (version 2017 or above)](https://visualstudio.microsoft.com/vs/community/) +- [CMake (version 3.2 or above)](https://cmake.org/download/) +- [OpenCV (version 3.4.5 or above)](https://opencv.org/releases/) + +#### Instructions + +1. Make sure that the sample is stored in a location with read and write permissions. +2. Add the following directories to the **Path** environment variable: + + - `xxx/opencv/build/x64/vc14/bin` + - `xxx/opencv/build/x64/vc14/lib` + +3. Run Cmake and set the source and build paths: + + | Field | Path | + | :-------------------------- | :---------------------------------- | + | Where is the source code | xxx/WidthExpansionCalibration | + | Where to build the binaries | xxx/WidthExpansionCalibration/build | + +4. Click the **Configure** button. In the pop-up window, set the generator and platform according to the actual situation, and then click the **Finish** button. +5. When the log displays **Configuring done**, click the **Generate** button. When the log displays **Generating done**, click the **Open Project** button. +6. In Visual Studio toolbar, change the solution configuration from **Debug** to **Release**. +7. In the **Solution Explorer** panel, right-click the sample, and select **Set as Startup Project**. +8. Click the **Local Windows Debugger** button in the toolbar to run the sample. +9. Enter the index of the laser profiler to which you want to connect, and press the Enter key. The obtained files are saved to the `build` folder. + +### Ubuntu + +Ubuntu 18 or above is required. + +#### Prerequisites + +- Update the software source list. + + ```bash + sudo apt-get update + ``` + +- Install required tools. + + ```bash + sudo apt-get install -y build-essential pkg-config cmake + ``` + +- Install [Mech-Eye SDK (latest version)](https://downloads.mech-mind.com/?tab=tab-sdk). + + > Note: If you have installed Mech-Eye SDK before, please uninstall it first with the following command: + > + > ```bash + > sudo dpkg -P MechEyeApi + > ``` + + - If the system architecture is AMD64, execute the following command: + + ```bash + sudo dpkg -i 'Mech-Eye_API_x.x.x_amd64.deb' + ``` + + - If the system architecture is ARM64, execute the following command: + + ```bash + sudo dpkg -i 'Mech-Eye_API_x.x.x_arm64.deb' + ``` + +- Install third-party libraries: OpenCV is required. + + - Install OpenCV (latest version): + + ```bash + sudo apt update && sudo apt install -y unzip + wget -O opencv.zip https://github.com/opencv/opencv/archive/4.x.zip + unzip opencv.zip + mkdir build && cd build + cmake ../opencv-4.x + cmake --build . + sudo make install + ``` + +#### Instructions + +1. Navigate to the directory of the sample. + + ```bash + cd xxx/profiler/Calibration/WidthExpansionCalibration/ + ``` + +2. Configure and build the sample. + + ```bash + sudo mkdir build && cd build + sudo cmake .. + sudo make + ``` + +3. Run the sample. + + ```bash + sudo ./WidthExpansionCalibration + ``` + +4. Enter the index of the laser profiler to which you want to connect, and press the Enter key. The obtained files are saved to `/WidthExpansionCalibration/build`. + +## Sample Usage + +### Overview + +This sample performs single-profiler calibration along a specified movement direction. After calibration, the depth map can be corrected for downstream applications. + +1. Input calibration target dimensions. +2. Connect and identify the profiler. +3. Acquire depth and intensity images. +4. Select Calibration Mode. +5. Capture Images & Execute Calibration. +6. Save Calibration & Optional Image Stitching. + +--- + +### 1. Input Target Dimensions + +Input the calibration target frustum parameters: + +- **Top Length** (mm): Upper base length of the frustums (e.g., `100.0`). +- **Bottom Length** (mm): Lower base length of the frustums (e.g., `150.0`). +- **Height** (mm): Height of the frustums (e.g., `50.0`). + +--- + +### 2. Connect Profiler + +- The system detects all available profilers automatically. +- Ensure the device is powered on and connected. + +--- + +### 3. Input Target Pose Parameters + +For the single profiler: + +- Translation Mode: + - Distance between two target frustums (mm) + - Translation axis: X, Y, or Z +- Rotation Mode: + - Rotation angle (°) + - Rotation radius (mm) + - Rotation axis: X, Y, or Z + +--- + +### 4. Select Calibration Mode + +- Choose the width expansion type (translation-based or rotation-based). + +--- + +### 5. Capture Images & Execute Calibration + +- The program captures images automatically and performs calibration. +- Ensure the calibration target is fully visible and lighting is stable. + +--- + +### 6. Save Calibration & Optional Image Stitching + +- Save calibration files to the specified folder. +- Optionally, fuse captured images into a stitched depth map (.tiff). + +--- + +### Key Notes + +#### File Paths + +- Use **English-only characters** in paths (e.g., avoid characters in `中文` or `日本語`) +- Ensure write permissions to target folders + +#### Error Handling + +- Detailed error messages are displayed for failed data acquisition or calibrations. +- Check device connections and retry. + +### Troubleshooting Tips + +| Error Message | Solution | +| ---------------------- | ------------------------------------------------ | +| "Capture failed" | Ensure all laser profilers are visible to system | +| "Failed to save files" | Verify folder permissions and path validity | +| Calibration errors | Re-measure target dimensions and retry | diff --git a/profiler/Calibration/WidthExpansionCalibration/WidthExpansionCalibration.cpp b/profiler/Calibration/WidthExpansionCalibration/WidthExpansionCalibration.cpp new file mode 100644 index 0000000..dae9ac4 --- /dev/null +++ b/profiler/Calibration/WidthExpansionCalibration/WidthExpansionCalibration.cpp @@ -0,0 +1,208 @@ +#include +#include "../SingleProfilersCalibration.h" + +using namespace mmind::eye; +using Calibration = mmind::eye::ProfilerCalibrationInterfaces; + +int main() +{ + // Target geometry input. + TargetSize targetSize; + bool confirmed = false; + + while (!confirmed) { + std::cout << "\nInput target geometry parameters (unit: mm)\n"; + + std::cout << "Target top length: "; + targetSize.targetTopLength = getInputNumber(); + + std::cout << "Target bottom length: "; + targetSize.targetBottomLength = getInputNumber(); + + std::cout << "Target height: "; + targetSize.targetHeight = getInputNumber(); + + std::cout << "\nConfirm target geometry:\n" + << " Top Length : " << targetSize.targetTopLength << "\n" + << " Bottom Length: " << targetSize.targetBottomLength << "\n" + << " Height : " << targetSize.targetHeight << "\n"; + + std::cout << "\nContinue? (y/Y to confirm, others to re-input): "; + char choice; + std::cin >> choice; + confirmed = (choice == 'y' || choice == 'Y'); + } + + // Connect profiler. + auto profilerOpt = findAndConnectProfilerForCalibration(); + if (!profilerOpt) { + std::cout << "Error: Failed to connect profiler.\n"; + return -1; + } + auto profiler = profilerOpt.value(); + + std::cout << "\nRetrieving profiler device information...\n"; + DeviceInfo profilerDeviceInfo = getDeviceInfo(profiler); + + // Target pose input. + std::vector targetPoses; + TargetPose targetPose; + confirmed = false; + + while (!confirmed) { + std::cout << "\nInput target motion parameters\n"; + + std::cout << "Translation distance (mm): "; + targetPose.translateDistance = getInputNumber(); + + std::cout << "Rotation angle (degree): "; + targetPose.rotateAngleInDegree = getInputNumber(); + + std::cout << "Rotation radius (mm): "; + targetPose.rotateRadius = getInputNumber(); + + std::cout << "\nConfirm target pose:\n" + << " Translation distance: " << targetPose.translateDistance << "\n" + << " Rotation angle : " << targetPose.rotateAngleInDegree << "\n" + << " Rotation radius : " << targetPose.rotateRadius << "\n"; + + std::cout << "\nContinue? (y/Y to confirm, others to re-input): "; + char choice; + std::cin >> choice; + confirmed = (choice == 'y' || choice == 'Y'); + } + + // Calibration mode setup. + ProfilerCalibrationMode calibMode = inputCalibType(); + TargetTransformAxis transformAxis = inputTargetTransformAxis(); + + targetPose.mode = calibMode; + + switch (calibMode) { + case ProfilerCalibrationMode::Wide: + targetPose.translateAxis = TargetTranslateAxis(static_cast(transformAxis)); + targetPose.rotateAxis = TargetRotateAxis::NullAxis; + break; + + case ProfilerCalibrationMode::Angle: + targetPose.rotateAxis = TargetRotateAxis(static_cast(transformAxis)); + targetPose.translateAxis = TargetTranslateAxis::NullAxis; + break; + } + + targetPoses.push_back(targetPose); + + // Width expansion type. + WidthExpansionType widthExpansionMode = inputWidthExpansionCalibType(); + + /* ========================= + * Reference position bias. + * ========================= + * refPositionBias describes the expected relative spatial offset + * between the major scan and the minor scan (unit: mm). + * It is REQUIRED for width expansion calibration. + */ + std::vector refPositionBiases; + + std::cout << "\n==================== Start Calibration ====================\n"; + + // Capture calibration images. + ProfilerImage majorImage; + std::vector minorImages; + + ProfilerInfo profilerInfo; + auto status = profiler.getProfilerInfo(profilerInfo); + if (!status.isOK()) { + showError(status); + return -1; + } + + if (!captureImages(profiler, minorImages, majorImage)) { + std::cout << "Error: Failed to capture calibration images.\n"; + return -1; + } + + for (int i = 0; i < minorImages.size(); i++) { + RefPositionBias bias; + bias.groupID = (uint)i; + std::cout << "\nInput reference position bias X (mm): " << std::endl; + bias.biasMm.x = getInputNumber(); + std::cout << "\nInput reference position bias Y (mm): " << std::endl; + bias.biasMm.y = getInputNumber(); + std::cout << "\nInput reference position bias Z (mm): " << std::endl; + bias.biasMm.z = getInputNumber(); + refPositionBiases.push_back(bias); + } + + // Run calibration. + std::vector calibResults; + Calibration calibInstance(profilerInfo.model, profilerDeviceInfo, targetSize, targetPoses); + + auto errorStatus = calibInstance.calibrateSingleProfilerWidthExpansion( + majorImage.depth, minorImages[0].depth, refPositionBiases, calibResults); + + if (!errorStatus.isOK()) { + printError(errorStatus); + return -1; + } + + std::cout << "\nCalibration completed successfully.\n"; + + // Save calibration files. + std::string savePath; + std::cout << "\nInput path to save calibration files: "; + std::cin >> savePath; + + if (!calibInstance.saveCalibFiles(true, savePath)) { + std::cout << "Error: Failed to save calibration files.\n"; + return -1; + } + + std::cout << "Calibration files saved to: " << savePath << "\n"; + + // Optional stitching verification. + std::cout << "\nProceed with stitching verification? (y/Y to continue): "; + std::string option; + std::cin >> option; + + if (option == "y" || option == "Y") { + FusionResult fusionResult; + + ImageInfo majorImageInfo; + majorImageInfo.profilerImage = majorImage; + + std::vector minorImageInfos; + for (size_t i = 0; i < minorImages.size(); ++i) { + ImageInfo info; + info.groupID = static_cast(i); + info.profilerImage = minorImages[i]; + minorImageInfos.push_back(info); + } + + errorStatus = calibInstance.stitchProfilerWidthExpansion( + majorImageInfo, minorImageInfos, widthExpansionMode, fusionResult, calibResults, true); + + if (!errorStatus.isOK()) { + printError(errorStatus); + return -1; + } + + std::cout << "Stitching verification succeeded.\n"; + + std::cout << "\nSave refined calibration files? (y/Y to save): "; + std::cin >> option; + + if ((option == "y" || option == "Y") && !calibInstance.saveCalibFiles(false, savePath)) { + std::cout << "Error: Failed to save refined calibration files.\n"; + return -1; + } + + std::string fusionImagePath = savePath + "/fusionResult.tiff"; + cv::imwrite(fusionImagePath, fusionResult.combinedImage.depth); + + std::cout << "Fusion image saved to: " << fusionImagePath << "\n"; + } + + std::cout << "\nProcess finished successfully.\n"; + return 0; +} diff --git a/profiler/Pcl/ConvertPointCloudToPcl/ConvertPointCloudToPcl.cpp b/profiler/Pcl/ConvertPointCloudToPcl/ConvertPointCloudToPcl.cpp index f086c31..d386fcb 100644 --- a/profiler/Pcl/ConvertPointCloudToPcl/ConvertPointCloudToPcl.cpp +++ b/profiler/Pcl/ConvertPointCloudToPcl/ConvertPointCloudToPcl.cpp @@ -145,6 +145,9 @@ void setParameters(mmind::eye::UserSet& userSet) // Set the "Scan Line Count" parameter (the number of lines to be scanned) to 1600 showError(userSet.setIntValue(mmind::eye::scan_settings::ScanLineCount::name, 1600)); + // Set the "Travel Speed" parameter to 100 mm/s. This value is used to calculate the + // Y-axis resolution and scan distance when line scan is triggered at a fixed rate. + showError(userSet.setFloatValue(mmind::eye::trigger_settings::TravelSpeed::name, 100.0)); // Set the "Laser Power" parameter to 100 showError(userSet.setIntValue(mmind::eye::brightness_settings::LaserPower::name, 100)); diff --git a/profiler/README.md b/profiler/README.md index 0f2d054..5b81671 100644 --- a/profiler/README.md +++ b/profiler/README.md @@ -19,18 +19,18 @@ The samples marked with `(PCL)` require [PCL](https://github.com/PointCloudLibra * **Basic** * [TriggerWithSoftwareAndFixedRate](https://github.com/MechMindRobotics/mecheye_cpp_samples/tree/master/profiler/Basic/TriggerWithSoftwareAndFixedRate) `(OpenCV)` - Trigger data acquisition with signals input from software, trigger line scans at a fixed rate, and then retrieve and save the acquired data. + Trigger data acquisition with the software + fixed rate method, and then retrieve and save the acquired data. * [TriggerWithExternalDeviceAndFixedRate](https://github.com/MechMindRobotics/mecheye_cpp_samples/tree/master/profiler/Basic/TriggerWithExternalDeviceAndFixedRate) `(OpenCV)` - Trigger data acquisition with signals input from the external device, trigger line scans at a fixed rate, and then retrieve and save the acquired data. + Trigger data acquisition with the external + fixed rate method, and then retrieve and save the acquired data. * [TriggerWithSoftwareAndEncoder](https://github.com/MechMindRobotics/mecheye_cpp_samples/tree/master/profiler/Basic/TriggerWithSoftwareAndEncoder) `(OpenCV)` - Trigger data acquisition with signals input from software, trigger line scans with signals input from the encoder, and then retrieve and save the acquired data. - * [TriggerWithExternalDeviceAndEncoder](https://github.com/MechMindRobotics/mecheye_cpp_samples/tree/master/profiler/Basdic/TriggerWithExternalDeviceAndEncoder) `(OpenCV)` - Trigger data acquisition with signals input from the external device, trigger line scans with signals input from the encoder, and then retrieve and save the acquired data. + Trigger data acquisition with the software + encoder method, and then retrieve and save the acquired data. + * [TriggerWithExternalDeviceAndEncoder](https://github.com/MechMindRobotics/mecheye_cpp_samples/tree/master/profiler/Basic/TriggerWithExternalDeviceAndEncoder) `(OpenCV)` + Trigger data acquisition with the external + encoder method, and then retrieve and save the acquired data. * [TriggerNonStopAcquisition](https://github.com/MechMindRobotics/mecheye_cpp_samples/tree/master/profiler/Basic/TriggerNonStopAcquisition) `(OpenCV)` - Trigger non-stop acquisition, and then retrieve and save the acquired data. + Trigger a continuous scan of the target object, and then retrieve and save the acquired data. * **Advanced** * [TriggerMultipleProfilersSimultaneously](https://github.com/MechMindRobotics/mecheye_cpp_samples/tree/master/profiler/Advanced/TriggerMultipleProfilersSimultaneously) `(OpenCV)` - Trigger multiple laser profilers to acquire data asynchronously and retrieve the acquired data. + Trigger multiple laser profilers to acquire data asynchronously, and then retrieve and save the acquired data. * [BlindSpotFiltering](https://github.com/MechMindRobotics/mecheye_cpp_samples/tree/master/profiler/Advanced/BlindSpotFiltering) `(OpenCV)` Detect and remove the false data caused by blind spots and obtain the filtered profile data. * [NoiseRemoval](https://github.com/MechMindRobotics/mecheye_cpp_samples/tree/master/profiler/Advanced/NoiseRemoval) `(OpenCV)` @@ -44,7 +44,7 @@ The samples marked with `(PCL)` require [PCL](https://github.com/PointCloudLibra * [UseVirtualDevice](https://github.com/MechMindRobotics/mecheye_cpp_samples/tree/master/profiler/Advanced/UseVirtualDevice) `(OpenCV)` Acquire the profile data stored in a virtual device, generate the intensity image and depth map, and save the images. * [WarmUp](https://github.com/MechMindRobotics/mecheye_cpp_samples/tree/master/profiler/Advanced/WarmUp) `(OpenCV)` - Periodically trigger data acquisition for stabilization. + Warm up the device. * **Util** * [RenderDepthMap](https://github.com/MechMindRobotics/mecheye_cpp_samples/tree/master/profiler/Util/RenderDepthMap) `(OpenCV)` Obtain and save the depth map rendered with the jet color scheme. @@ -55,10 +55,14 @@ The samples marked with `(PCL)` require [PCL](https://github.com/PointCloudLibra * [HandleNanAndNegativeInDepth](https://github.com/MechMindRobotics/mecheye_cpp_samples/tree/master/profiler/Util/HandleNanAndNegativeInDepth) `(OpenCV)` Trigger data acquisition and handle NaN and negative values in depth data. * **Calibration** + * [MoveDirVecCalibration](https://github.com/MechMindRobotics/mecheye_cpp_samples/tree/master/profiler/Calibration/MoveDirVecCalibration) `(OpenCV)` + Calibrate the movement direction vector for a single laser profiler, and output calibration results and alignment parameters for subsequent stitching. * [MultipleProfilersCalibration](https://github.com/MechMindRobotics/mecheye_cpp_samples/tree/master/profiler/Calibration/MultipleProfilersCalibration) `(OpenCV)` - Calibrate multiple profilers that simultaneously scan the same target object, and output the calibration results and errors, stitching results, the stitched depth map, and the stitched point cloud. + Calibrate multiple laser profilers that simultaneously scan the same target object, and output the calibration results and errors, stitching results, as well as the stitched depth map and point cloud. + * [WidthExpansionCalibration](https://github.com/MechMindRobotics/mecheye_cpp_samples/tree/master/profiler/Calibration/WidthExpansionCalibration) `(OpenCV)` + Perform width-expansion calibration and stitching for a single laser profiler, and output corrected calibration parameters and stitching results. * **Pcl** - * [ConvertPointCloudToPcl](https://github.com/MechMindRobotics/mecheye_cpp_samples/tree/master/profiler/Pcl/ConvertPointCloudToPCL) `(PCL)` + * [ConvertPointCloudToPcl](https://github.com/MechMindRobotics/mecheye_cpp_samples/tree/master/profiler/Pcl/ConvertPointCloudToPcl) `(PCL)` Obtain the point cloud data from the profiler and convert it to the PCL data structure. ## Build the Samples diff --git a/profiler/Util/HandleNanAndNegativeInDepth/HandleNanAndNegativeInDepth.cpp b/profiler/Util/HandleNanAndNegativeInDepth/HandleNanAndNegativeInDepth.cpp index 95f895b..be691fa 100644 --- a/profiler/Util/HandleNanAndNegativeInDepth/HandleNanAndNegativeInDepth.cpp +++ b/profiler/Util/HandleNanAndNegativeInDepth/HandleNanAndNegativeInDepth.cpp @@ -148,6 +148,9 @@ void setParameters(mmind::eye::UserSet& userSet) // Set the "Scan Line Count" parameter (the number of lines to be scanned) to 1600 showError(userSet.setIntValue(mmind::eye::scan_settings::ScanLineCount::name, 1600)); + // Set the "Travel Speed" parameter to 100 mm/s. This value is used to calculate the + // Y-axis resolution and scan distance when line scan is triggered at a fixed rate. + showError(userSet.setFloatValue(mmind::eye::trigger_settings::TravelSpeed::name, 100.0)); // Set the "Laser Power" parameter to 100 showError(userSet.setIntValue(mmind::eye::brightness_settings::LaserPower::name, 100));