From 18ed229e92e1c7d48a1b81cf486c43f79643952d Mon Sep 17 00:00:00 2001 From: lgv Date: Wed, 11 Feb 2026 15:23:11 +0800 Subject: [PATCH] feat: add ibvs demo test --- README.md | 3 + cmake/FindExternalLib.cmake | 29 +- cmvr-es/controller/CMakeLists.txt | 40 +- cmvr-es/controller/src/controller_test.cpp | 867 +++++ .../mujoco_viewer/include/mujoco_viewer.h | 47 +- .../mujoco_viewer/src/mujoco_viewer.cpp | 160 +- .../visp/3.7.0/bin/visp-calibrate-camera | Bin 0 -> 126576 bytes .../3.7.0/bin/visp-compute-apriltag-poses | Bin 0 -> 59632 bytes .../3.7.0/bin/visp-compute-chessboard-poses | Bin 0 -> 74480 bytes .../bin/visp-compute-eye-in-hand-calibration | Bin 0 -> 82240 bytes .../bin/visp-compute-eye-to-hand-calibration | Bin 0 -> 86528 bytes .../third_party/visp/3.7.0/bin/visp-config | 98 + .../visp/3.7.0/bin/visp-read-rs-dataset | Bin 0 -> 87336 bytes .../visp/3.7.0/bin/visp-save-rs-dataset | Bin 0 -> 203432 bytes .../visp/3.7.0/include/visp/visp_modules.h | 12 + .../3.7.0/include/visp/vp1394CMUGrabber.h | 12 + .../3.7.0/include/visp/vp1394TwoGrabber.h | 12 + .../visp/3.7.0/include/visp/vpAR.h | 12 + .../visp/3.7.0/include/visp/vpAROgre.h | 12 + .../visp/3.7.0/include/visp/vpAdaptiveGain.h | 12 + .../visp/3.7.0/include/visp/vpAfma6.h | 12 + .../visp/3.7.0/include/visp/vpArray2D.h | 12 + .../visp/3.7.0/include/visp/vpBSpline.h | 12 + .../visp/3.7.0/include/visp/vpBasicFeature.h | 12 + .../visp/3.7.0/include/visp/vpBasicKeyPoint.h | 12 + .../visp/3.7.0/include/visp/vpBiclops.h | 12 + .../visp/3.7.0/include/visp/vpCPUFeatures.h | 12 + .../visp/3.7.0/include/visp/vpCalibration.h | 12 + .../include/visp/vpCalibrationException.h | 12 + .../3.7.0/include/visp/vpCameraParameters.h | 12 + .../3.7.0/include/visp/vpCannyEdgeDetection.h | 12 + .../visp/3.7.0/include/visp/vpCircle.h | 12 + .../include/visp/vpCircleHoughTransform.h | 12 + .../visp/3.7.0/include/visp/vpClient.h | 12 + .../visp/3.7.0/include/visp/vpColVector.h | 12 + .../visp/3.7.0/include/visp/vpColor.h | 12 + .../visp/vpColorBlindFriendlyPalette.h | 12 + .../include/visp/vpColorDepthConversion.h | 12 + .../visp/3.7.0/include/visp/vpColorGetter.h | 12 + .../visp/3.7.0/include/visp/vpColormap.h | 12 + .../visp/3.7.0/include/visp/vpComedi.h | 12 + .../visp/3.7.0/include/visp/vpConfig.h | 12 + .../visp/3.7.0/include/visp/vpContours.h | 12 + .../visp/3.7.0/include/visp/vpConvert.h | 12 + .../visp/3.7.0/include/visp/vpCylinder.h | 12 + .../visp/3.7.0/include/visp/vpD3DRenderer.h | 12 + .../visp/3.7.0/include/visp/vpDebug.h | 12 + .../3.7.0/include/visp/vpDetectorAprilTag.h | 12 + .../visp/3.7.0/include/visp/vpDetectorBase.h | 12 + .../3.7.0/include/visp/vpDetectorDNNOpenCV.h | 12 + .../include/visp/vpDetectorDataMatrixCode.h | 12 + .../visp/3.7.0/include/visp/vpDetectorFace.h | 12 + .../3.7.0/include/visp/vpDetectorQRCode.h | 12 + .../3.7.0/include/visp/vpDirectShowDevice.h | 12 + .../3.7.0/include/visp/vpDirectShowGrabber.h | 12 + .../include/visp/vpDirectShowGrabberImpl.h | 12 + .../include/visp/vpDirectShowSampleGrabberI.h | 12 + .../visp/3.7.0/include/visp/vpDiskGrabber.h | 12 + .../visp/3.7.0/include/visp/vpDisplay.h | 12 + .../visp/3.7.0/include/visp/vpDisplayD3D.h | 12 + .../3.7.0/include/visp/vpDisplayException.h | 12 + .../3.7.0/include/visp/vpDisplayFactory.h | 12 + .../visp/3.7.0/include/visp/vpDisplayGDI.h | 12 + .../visp/3.7.0/include/visp/vpDisplayGTK.h | 12 + .../visp/3.7.0/include/visp/vpDisplayOpenCV.h | 12 + .../visp/3.7.0/include/visp/vpDisplayPCL.h | 12 + .../visp/3.7.0/include/visp/vpDisplayWin32.h | 12 + .../visp/3.7.0/include/visp/vpDisplayX.h | 12 + .../visp/3.7.0/include/visp/vpDot.h | 12 + .../visp/3.7.0/include/visp/vpDot2.h | 12 + .../3.7.0/include/visp/vpEigenConversion.h | 12 + .../visp/3.7.0/include/visp/vpEndian.h | 12 + .../visp/3.7.0/include/visp/vpException.h | 12 + .../3.7.0/include/visp/vpExponentialMap.h | 12 + .../3.7.0/include/visp/vpFeatureBuilder.h | 12 + .../visp/3.7.0/include/visp/vpFeatureDepth.h | 12 + .../3.7.0/include/visp/vpFeatureDisplay.h | 12 + .../3.7.0/include/visp/vpFeatureEllipse.h | 12 + .../3.7.0/include/visp/vpFeatureException.h | 12 + .../visp/3.7.0/include/visp/vpFeatureLine.h | 12 + .../3.7.0/include/visp/vpFeatureLuminance.h | 12 + .../include/visp/vpFeatureLuminanceMapping.h | 12 + .../visp/3.7.0/include/visp/vpFeatureMoment.h | 12 + .../3.7.0/include/visp/vpFeatureMomentAlpha.h | 12 + .../3.7.0/include/visp/vpFeatureMomentArea.h | 12 + .../visp/vpFeatureMomentAreaNormalized.h | 12 + .../3.7.0/include/visp/vpFeatureMomentBasic.h | 12 + .../include/visp/vpFeatureMomentCInvariant.h | 12 + .../include/visp/vpFeatureMomentCentered.h | 12 + .../include/visp/vpFeatureMomentCommon.h | 12 + .../include/visp/vpFeatureMomentDatabase.h | 12 + .../visp/vpFeatureMomentGravityCenter.h | 12 + .../vpFeatureMomentGravityCenterNormalized.h | 12 + .../visp/3.7.0/include/visp/vpFeaturePoint.h | 12 + .../3.7.0/include/visp/vpFeaturePoint3D.h | 12 + .../3.7.0/include/visp/vpFeaturePointPolar.h | 12 + .../3.7.0/include/visp/vpFeatureSegment.h | 12 + .../visp/3.7.0/include/visp/vpFeatureThetaU.h | 12 + .../3.7.0/include/visp/vpFeatureTranslation.h | 12 + .../include/visp/vpFeatureVanishingPoint.h | 12 + .../3.7.0/include/visp/vpFlyCaptureGrabber.h | 12 + .../visp/3.7.0/include/visp/vpFont.h | 12 + .../visp/vpForceTorqueAtiNetFTSensor.h | 12 + .../include/visp/vpForceTorqueAtiSensor.h | 12 + .../include/visp/vpForceTorqueIitSensor.h | 12 + .../3.7.0/include/visp/vpForceTwistMatrix.h | 12 + .../3.7.0/include/visp/vpForwardProjection.h | 12 + .../visp/3.7.0/include/visp/vpFrameGrabber.h | 12 + .../include/visp/vpFrameGrabberException.h | 12 + .../visp/3.7.0/include/visp/vpGDIRenderer.h | 12 + .../visp/3.7.0/include/visp/vpGEMM.h | 12 + .../visp/3.7.0/include/visp/vpGaussRand.h | 12 + .../3.7.0/include/visp/vpGaussianFilter.h | 12 + .../3.7.0/include/visp/vpGenericFeature.h | 12 + .../visp/3.7.0/include/visp/vpHSV.h | 12 + .../3.7.0/include/visp/vpHandEyeCalibration.h | 12 + .../visp/3.7.0/include/visp/vpHinkley.h | 12 + .../visp/3.7.0/include/visp/vpHistogram.h | 12 + .../visp/3.7.0/include/visp/vpHistogramPeak.h | 12 + .../3.7.0/include/visp/vpHistogramValey.h | 12 + .../3.7.0/include/visp/vpHomogeneousMatrix.h | 12 + .../visp/3.7.0/include/visp/vpHomography.h | 12 + .../visp/3.7.0/include/visp/vpImage.h | 12 + .../visp/3.7.0/include/visp/vpImageCircle.h | 12 + .../visp/3.7.0/include/visp/vpImageConvert.h | 12 + .../visp/3.7.0/include/visp/vpImageDraw.h | 12 + .../3.7.0/include/visp/vpImageException.h | 12 + .../visp/3.7.0/include/visp/vpImageFilter.h | 12 + .../visp/3.7.0/include/visp/vpImageIo.h | 12 + .../3.7.0/include/visp/vpImageMorphology.h | 12 + .../visp/3.7.0/include/visp/vpImagePoint.h | 12 + .../visp/3.7.0/include/visp/vpImageQueue.h | 12 + .../3.7.0/include/visp/vpImageSimulator.h | 12 + .../3.7.0/include/visp/vpImageStorageWorker.h | 12 + .../visp/3.7.0/include/visp/vpImageTools.h | 12 + .../3.7.0/include/visp/vpImageTools_warp.h | 12 + .../visp/3.7.0/include/visp/vpImage_getters.h | 12 + .../visp/3.7.0/include/visp/vpImage_lut.h | 12 + .../3.7.0/include/visp/vpImage_operators.h | 12 + .../visp/3.7.0/include/visp/vpImgproc.h | 12 + .../visp/3.7.0/include/visp/vpIoException.h | 12 + .../visp/3.7.0/include/visp/vpIoTools.h | 12 + .../3.7.0/include/visp/vpJsonArgumentParser.h | 12 + .../visp/3.7.0/include/visp/vpJsonParsing.h | 12 + .../visp/3.7.0/include/visp/vpKalmanFilter.h | 12 + .../visp/3.7.0/include/visp/vpKeyPoint.h | 12 + .../visp/3.7.0/include/visp/vpKeyboard.h | 12 + .../visp/3.7.0/include/visp/vpKinect.h | 12 + .../visp/3.7.0/include/visp/vpKltOpencv.h | 12 + .../visp/3.7.0/include/visp/vpLaserScan.h | 12 + .../visp/3.7.0/include/visp/vpLaserScanner.h | 12 + .../visp/3.7.0/include/visp/vpLinProg.h | 12 + .../visp/3.7.0/include/visp/vpLine.h | 12 + .../visp/vpLinearKalmanFilterInstantiation.h | 12 + .../visp/3.7.0/include/visp/vpList.h | 12 + .../visp/3.7.0/include/visp/vpMath.h | 12 + .../visp/3.7.0/include/visp/vpMatrix.h | 12 + .../3.7.0/include/visp/vpMatrixException.h | 12 + .../include/visp/vpMbDepthDenseTracker.h | 12 + .../include/visp/vpMbDepthNormalTracker.h | 12 + .../3.7.0/include/visp/vpMbEdgeKltTracker.h | 12 + .../visp/3.7.0/include/visp/vpMbEdgeTracker.h | 12 + .../3.7.0/include/visp/vpMbGenericTracker.h | 12 + .../visp/3.7.0/include/visp/vpMbHiddenFaces.h | 12 + .../visp/3.7.0/include/visp/vpMbKltTracker.h | 12 + .../visp/3.7.0/include/visp/vpMbScanLine.h | 12 + .../visp/3.7.0/include/visp/vpMbTracker.h | 12 + .../3.7.0/include/visp/vpMbtDistanceCircle.h | 12 + .../include/visp/vpMbtDistanceCylinder.h | 12 + .../include/visp/vpMbtDistanceKltCylinder.h | 12 + .../include/visp/vpMbtDistanceKltPoints.h | 12 + .../3.7.0/include/visp/vpMbtDistanceLine.h | 12 + .../3.7.0/include/visp/vpMbtFaceDepthDense.h | 12 + .../3.7.0/include/visp/vpMbtFaceDepthNormal.h | 12 + .../visp/3.7.0/include/visp/vpMbtMeEllipse.h | 12 + .../visp/3.7.0/include/visp/vpMbtMeLine.h | 12 + .../visp/3.7.0/include/visp/vpMbtPolygon.h | 12 + .../3.7.0/include/visp/vpMbtTukeyEstimator.h | 12 + .../include/visp/vpMbtXmlGenericParser.h | 12 + .../visp/3.7.0/include/visp/vpMe.h | 12 + .../visp/3.7.0/include/visp/vpMeEllipse.h | 12 + .../visp/3.7.0/include/visp/vpMeLine.h | 12 + .../visp/3.7.0/include/visp/vpMeNurbs.h | 12 + .../visp/3.7.0/include/visp/vpMeSite.h | 12 + .../visp/3.7.0/include/visp/vpMeTracker.h | 12 + .../visp/3.7.0/include/visp/vpMegaPose.h | 12 + .../3.7.0/include/visp/vpMegaPoseTracker.h | 12 + .../include/visp/vpMeterPixelConversion.h | 12 + .../visp/3.7.0/include/visp/vpMocap.h | 12 + .../visp/3.7.0/include/visp/vpMocapQualisys.h | 12 + .../visp/3.7.0/include/visp/vpMocapVicon.h | 12 + .../visp/3.7.0/include/visp/vpMoment.h | 12 + .../visp/3.7.0/include/visp/vpMomentAlpha.h | 12 + .../visp/3.7.0/include/visp/vpMomentArea.h | 12 + .../include/visp/vpMomentAreaNormalized.h | 12 + .../visp/3.7.0/include/visp/vpMomentBasic.h | 12 + .../3.7.0/include/visp/vpMomentCInvariant.h | 12 + .../3.7.0/include/visp/vpMomentCentered.h | 12 + .../visp/3.7.0/include/visp/vpMomentCommon.h | 12 + .../3.7.0/include/visp/vpMomentDatabase.h | 12 + .../include/visp/vpMomentGravityCenter.h | 12 + .../visp/vpMomentGravityCenterNormalized.h | 12 + .../visp/3.7.0/include/visp/vpMomentObject.h | 12 + .../visp/3.7.0/include/visp/vpMouseButton.h | 12 + .../visp/3.7.0/include/visp/vpMunkres.h | 12 + .../visp/3.7.0/include/visp/vpMutex.h | 12 + .../visp/3.7.0/include/visp/vpNetwork.h | 12 + .../visp/3.7.0/include/visp/vpNoise.h | 12 + .../3.7.0/include/visp/vpNullptrEmulated.h | 12 + .../visp/3.7.0/include/visp/vpNurbs.h | 12 + .../3.7.0/include/visp/vpOccipitalStructure.h | 12 + .../include/visp/vpPanda3DBaseRenderer.h | 12 + .../include/visp/vpPanda3DCommonFilters.h | 12 + .../include/visp/vpPanda3DFrameworkManager.h | 12 + .../include/visp/vpPanda3DGeometryRenderer.h | 12 + .../visp/3.7.0/include/visp/vpPanda3DLight.h | 12 + .../include/visp/vpPanda3DPostProcessFilter.h | 12 + .../3.7.0/include/visp/vpPanda3DRGBRenderer.h | 12 + .../include/visp/vpPanda3DRenderParameters.h | 12 + .../3.7.0/include/visp/vpPanda3DRendererSet.h | 12 + .../visp/3.7.0/include/visp/vpParallelPort.h | 12 + .../include/visp/vpParallelPortException.h | 12 + .../visp/3.7.0/include/visp/vpParseArgv.h | 12 + .../3.7.0/include/visp/vpParticleFilter.h | 12 + .../visp/3.7.0/include/visp/vpPclViewer.h | 12 + .../visp/3.7.0/include/visp/vpPioneer.h | 12 + .../visp/3.7.0/include/visp/vpPioneerPan.h | 12 + .../include/visp/vpPixelMeterConversion.h | 12 + .../visp/3.7.0/include/visp/vpPlane.h | 12 + .../3.7.0/include/visp/vpPlaneEstimation.h | 12 + .../visp/3.7.0/include/visp/vpPlot.h | 12 + .../visp/3.7.0/include/visp/vpPlotCurve.h | 12 + .../visp/3.7.0/include/visp/vpPlotGraph.h | 12 + .../visp/3.7.0/include/visp/vpPoint.h | 12 + .../visp/3.7.0/include/visp/vpPololu.h | 12 + .../visp/3.7.0/include/visp/vpPolygon.h | 12 + .../visp/3.7.0/include/visp/vpPolygon3D.h | 12 + .../visp/3.7.0/include/visp/vpPose.h | 12 + .../visp/3.7.0/include/visp/vpPoseException.h | 12 + .../visp/3.7.0/include/visp/vpPoseFeatures.h | 12 + .../visp/3.7.0/include/visp/vpPoseVector.h | 12 + .../3.7.0/include/visp/vpProjectionDisplay.h | 12 + .../visp/3.7.0/include/visp/vpPtu46.h | 12 + .../visp/3.7.0/include/visp/vpPylonFactory.h | 12 + .../visp/3.7.0/include/visp/vpPylonGrabber.h | 12 + .../visp/3.7.0/include/visp/vpQbDevice.h | 12 + .../visp/3.7.0/include/visp/vpQbSoftHand.h | 12 + .../visp/3.7.0/include/visp/vpQuadProg.h | 12 + .../3.7.0/include/visp/vpQuaternionVector.h | 12 + .../visp/3.7.0/include/visp/vpRGBa.h | 12 + .../visp/3.7.0/include/visp/vpRGBf.h | 12 + .../visp/3.7.0/include/visp/vpRansac.h | 12 + .../visp/3.7.0/include/visp/vpRealSense2.h | 12 + .../visp/3.7.0/include/visp/vpRect.h | 12 + .../visp/3.7.0/include/visp/vpRectOriented.h | 12 + .../3.7.0/include/visp/vpReflexTakktile2.h | 12 + .../visp/3.7.0/include/visp/vpRequest.h | 12 + .../visp/3.7.0/include/visp/vpRingLight.h | 12 + .../visp/3.7.0/include/visp/vpRobot.h | 12 + .../visp/3.7.0/include/visp/vpRobotAfma6.h | 12 + .../visp/3.7.0/include/visp/vpRobotBebop2.h | 12 + .../visp/3.7.0/include/visp/vpRobotBiclops.h | 12 + .../visp/3.7.0/include/visp/vpRobotCamera.h | 12 + .../3.7.0/include/visp/vpRobotException.h | 12 + .../visp/3.7.0/include/visp/vpRobotFlirPtu.h | 12 + .../visp/3.7.0/include/visp/vpRobotFranka.h | 12 + .../visp/3.7.0/include/visp/vpRobotKinova.h | 12 + .../visp/3.7.0/include/visp/vpRobotMavsdk.h | 12 + .../visp/3.7.0/include/visp/vpRobotPioneer.h | 12 + .../3.7.0/include/visp/vpRobotPololuPtu.h | 12 + .../visp/3.7.0/include/visp/vpRobotPtu46.h | 12 + .../3.7.0/include/visp/vpRobotSimulator.h | 12 + .../visp/3.7.0/include/visp/vpRobotTemplate.h | 12 + .../include/visp/vpRobotUniversalRobots.h | 12 + .../visp/3.7.0/include/visp/vpRobotViper650.h | 12 + .../visp/3.7.0/include/visp/vpRobotViper850.h | 12 + .../include/visp/vpRobotWireFrameSimulator.h | 12 + .../visp/3.7.0/include/visp/vpRobust.h | 12 + .../3.7.0/include/visp/vpRotationMatrix.h | 12 + .../3.7.0/include/visp/vpRotationVector.h | 12 + .../visp/3.7.0/include/visp/vpRowVector.h | 12 + .../visp/3.7.0/include/visp/vpRxyzVector.h | 12 + .../visp/3.7.0/include/visp/vpRzyxVector.h | 12 + .../visp/3.7.0/include/visp/vpRzyzVector.h | 12 + .../visp/3.7.0/include/visp/vpScale.h | 12 + .../visp/3.7.0/include/visp/vpScanPoint.h | 12 + .../visp/3.7.0/include/visp/vpSerial.h | 12 + .../visp/3.7.0/include/visp/vpServer.h | 12 + .../visp/3.7.0/include/visp/vpServo.h | 12 + .../visp/3.7.0/include/visp/vpServoData.h | 12 + .../visp/3.7.0/include/visp/vpServoDisplay.h | 12 + .../3.7.0/include/visp/vpServoException.h | 12 + .../visp/3.7.0/include/visp/vpSickLDMRS.h | 12 + .../visp/3.7.0/include/visp/vpSimulator.h | 12 + .../3.7.0/include/visp/vpSimulatorAfma6.h | 12 + .../3.7.0/include/visp/vpSimulatorCamera.h | 12 + .../3.7.0/include/visp/vpSimulatorException.h | 12 + .../3.7.0/include/visp/vpSimulatorPioneer.h | 12 + .../include/visp/vpSimulatorPioneerPan.h | 12 + .../3.7.0/include/visp/vpSimulatorViper850.h | 12 + .../visp/3.7.0/include/visp/vpSphere.h | 12 + .../include/visp/vpStatisticalTestAbstract.h | 12 + .../include/visp/vpStatisticalTestEWMA.h | 12 + .../include/visp/vpStatisticalTestHinkley.h | 12 + .../visp/vpStatisticalTestMeanAdjustedCUSUM.h | 12 + .../include/visp/vpStatisticalTestShewhart.h | 12 + .../include/visp/vpStatisticalTestSigma.h | 12 + .../visp/3.7.0/include/visp/vpSubColVector.h | 12 + .../visp/3.7.0/include/visp/vpSubMatrix.h | 12 + .../visp/3.7.0/include/visp/vpSubRowVector.h | 12 + .../3.7.0/include/visp/vpTemplateTracker.h | 12 + .../include/visp/vpTemplateTrackerBSpline.h | 12 + .../include/visp/vpTemplateTrackerHeader.h | 12 + .../3.7.0/include/visp/vpTemplateTrackerMI.h | 12 + .../include/visp/vpTemplateTrackerMIBSpline.h | 12 + .../include/visp/vpTemplateTrackerMIESM.h | 12 + .../vpTemplateTrackerMIForwardAdditional.h | 12 + .../vpTemplateTrackerMIForwardCompositional.h | 12 + .../vpTemplateTrackerMIInverseCompositional.h | 12 + .../3.7.0/include/visp/vpTemplateTrackerSSD.h | 12 + .../include/visp/vpTemplateTrackerSSDESM.h | 12 + .../vpTemplateTrackerSSDForwardAdditional.h | 12 + ...vpTemplateTrackerSSDForwardCompositional.h | 12 + ...vpTemplateTrackerSSDInverseCompositional.h | 12 + .../include/visp/vpTemplateTrackerTriangle.h | 12 + .../include/visp/vpTemplateTrackerWarp.h | 12 + .../visp/vpTemplateTrackerWarpAffine.h | 12 + .../visp/vpTemplateTrackerWarpHomography.h | 12 + .../visp/vpTemplateTrackerWarpHomographySL3.h | 12 + .../include/visp/vpTemplateTrackerWarpRT.h | 12 + .../include/visp/vpTemplateTrackerWarpSRT.h | 12 + .../visp/vpTemplateTrackerWarpTranslation.h | 12 + .../include/visp/vpTemplateTrackerZNCC.h | 12 + .../vpTemplateTrackerZNCCForwardAdditional.h | 12 + ...pTemplateTrackerZNCCInverseCompositional.h | 12 + .../include/visp/vpTemplateTrackerZone.h | 12 + .../visp/3.7.0/include/visp/vpThetaUVector.h | 12 + .../visp/3.7.0/include/visp/vpThread.h | 12 + .../visp/3.7.0/include/visp/vpTime.h | 12 + .../visp/3.7.0/include/visp/vpTracker.h | 12 + .../3.7.0/include/visp/vpTrackingException.h | 12 + .../3.7.0/include/visp/vpTranslationVector.h | 12 + .../visp/3.7.0/include/visp/vpTriangle.h | 12 + .../visp/3.7.0/include/visp/vpUDPClient.h | 12 + .../visp/3.7.0/include/visp/vpUDPServer.h | 12 + .../include/visp/vpUKSigmaDrawerAbstract.h | 12 + .../3.7.0/include/visp/vpUKSigmaDrawerMerwe.h | 12 + .../visp/3.7.0/include/visp/vpUeyeGrabber.h | 12 + .../visp/3.7.0/include/visp/vpUniRand.h | 12 + .../visp/3.7.0/include/visp/vpUnicycle.h | 12 + .../3.7.0/include/visp/vpUnscentedKalman.h | 12 + .../visp/3.7.0/include/visp/vpV4l2Grabber.h | 12 + .../include/visp/vpVelocityTwistMatrix.h | 12 + .../visp/3.7.0/include/visp/vpVideoReader.h | 12 + .../visp/3.7.0/include/visp/vpVideoWriter.h | 12 + .../visp/3.7.0/include/visp/vpViewer.h | 12 + .../visp/3.7.0/include/visp/vpViper.h | 12 + .../visp/3.7.0/include/visp/vpViper650.h | 12 + .../visp/3.7.0/include/visp/vpViper850.h | 12 + .../visp/3.7.0/include/visp/vpVirtuose.h | 12 + .../visp/3.7.0/include/visp/vpWin32API.h | 12 + .../visp/3.7.0/include/visp/vpWin32Renderer.h | 12 + .../visp/3.7.0/include/visp/vpWin32Window.h | 12 + .../3.7.0/include/visp/vpWireFrameSimulator.h | 12 + .../include/visp/vpWireFrameSimulatorTypes.h | 12 + .../include/visp/vpXmlConfigParserKeyPoint.h | 12 + .../visp/3.7.0/include/visp/vpXmlParser.h | 12 + .../3.7.0/include/visp/vpXmlParserCamera.h | 12 + .../visp/vpXmlParserHomogeneousMatrix.h | 12 + .../include/visp/vpXmlParserRectOriented.h | 12 + .../visp/3.7.0/include/visp3/ar/vpAR.h | 158 + .../visp/3.7.0/include/visp3/ar/vpAROgre.h | 439 +++ .../include/visp3/ar/vpPanda3DBaseRenderer.h | 302 ++ .../include/visp3/ar/vpPanda3DCommonFilters.h | 125 + .../visp3/ar/vpPanda3DFrameworkManager.h | 103 + .../visp3/ar/vpPanda3DGeometryRenderer.h | 115 + .../3.7.0/include/visp3/ar/vpPanda3DLight.h | 324 ++ .../visp3/ar/vpPanda3DPostProcessFilter.h | 117 + .../include/visp3/ar/vpPanda3DRGBRenderer.h | 127 + .../visp3/ar/vpPanda3DRenderParameters.h | 136 + .../include/visp3/ar/vpPanda3DRendererSet.h | 239 ++ .../visp/3.7.0/include/visp3/ar/vpSimulator.h | 292 ++ .../include/visp3/ar/vpSimulatorException.h | 88 + .../visp/3.7.0/include/visp3/ar/vpViewer.h | 159 + .../visp/3.7.0/include/visp3/blob/vpDot.h | 494 +++ .../visp/3.7.0/include/visp3/blob/vpDot2.h | 572 ++++ .../visp/3.7.0/include/visp3/core/vpArray2D.h | 1534 +++++++++ .../visp/3.7.0/include/visp3/core/vpBSpline.h | 237 ++ .../3.7.0/include/visp3/core/vpCPUFeatures.h | 86 + .../include/visp3/core/vpCameraParameters.h | 568 ++++ .../include/visp3/core/vpCannyEdgeDetection.h | 620 ++++ .../visp/3.7.0/include/visp3/core/vpCircle.h | 179 + .../visp/3.7.0/include/visp3/core/vpClient.h | 222 ++ .../3.7.0/include/visp3/core/vpColVector.h | 1592 +++++++++ .../visp/3.7.0/include/visp3/core/vpColor.h | 386 +++ .../visp3/core/vpColorDepthConversion.h | 84 + .../3.7.0/include/visp3/core/vpColorGetter.h | 186 ++ .../3.7.0/include/visp3/core/vpColormap.h | 103 + .../visp/3.7.0/include/visp3/core/vpConfig.h | 759 +++++ .../visp/3.7.0/include/visp3/core/vpConvert.h | 109 + .../3.7.0/include/visp3/core/vpCylinder.h | 199 ++ .../visp/3.7.0/include/visp3/core/vpDebug.h | 610 ++++ .../visp/3.7.0/include/visp3/core/vpDisplay.h | 940 ++++++ .../include/visp3/core/vpDisplayException.h | 95 + .../include/visp3/core/vpEigenConversion.h | 109 + .../visp/3.7.0/include/visp3/core/vpEndian.h | 107 + .../3.7.0/include/visp3/core/vpException.h | 154 + .../include/visp3/core/vpExponentialMap.h | 98 + .../include/visp3/core/vpFeatureDisplay.h | 89 + .../visp/3.7.0/include/visp3/core/vpFont.h | 85 + .../include/visp3/core/vpForceTwistMatrix.h | 253 ++ .../include/visp3/core/vpForwardProjection.h | 224 ++ .../3.7.0/include/visp3/core/vpFrameGrabber.h | 143 + .../visp3/core/vpFrameGrabberException.h | 92 + .../visp/3.7.0/include/visp3/core/vpGEMM.h | 448 +++ .../3.7.0/include/visp3/core/vpGaussRand.h | 173 + .../include/visp3/core/vpGaussianFilter.h | 76 + .../visp/3.7.0/include/visp3/core/vpHSV.h | 655 ++++ .../visp/3.7.0/include/visp3/core/vpHinkley.h | 170 + .../3.7.0/include/visp3/core/vpHistogram.h | 574 ++++ .../include/visp3/core/vpHistogramPeak.h | 150 + .../include/visp3/core/vpHistogramValey.h | 144 + .../include/visp3/core/vpHomogeneousMatrix.h | 453 +++ .../visp/3.7.0/include/visp3/core/vpImage.h | 975 ++++++ .../3.7.0/include/visp3/core/vpImageCircle.h | 162 + .../3.7.0/include/visp3/core/vpImageConvert.h | 658 ++++ .../3.7.0/include/visp3/core/vpImageDraw.h | 128 + .../include/visp3/core/vpImageException.h | 94 + .../3.7.0/include/visp3/core/vpImageFilter.h | 2937 +++++++++++++++++ .../include/visp3/core/vpImageMorphology.h | 581 ++++ .../3.7.0/include/visp3/core/vpImagePoint.h | 386 +++ .../3.7.0/include/visp3/core/vpImageTools.h | 1548 +++++++++ .../include/visp3/core/vpImageTools_warp.h | 734 ++++ .../include/visp3/core/vpImage_getters.h | 1101 ++++++ .../3.7.0/include/visp3/core/vpImage_lut.h | 343 ++ .../include/visp3/core/vpImage_operators.h | 338 ++ .../3.7.0/include/visp3/core/vpIoException.h | 93 + .../visp/3.7.0/include/visp3/core/vpIoTools.h | 708 ++++ .../3.7.0/include/visp3/core/vpJsonParsing.h | 112 + .../3.7.0/include/visp3/core/vpKalmanFilter.h | 225 ++ .../visp/3.7.0/include/visp3/core/vpLinProg.h | 231 ++ .../visp/3.7.0/include/visp3/core/vpLine.h | 178 + .../core/vpLinearKalmanFilterInstantiation.h | 167 + .../visp/3.7.0/include/visp3/core/vpList.h | 714 ++++ .../visp/3.7.0/include/visp3/core/vpMath.h | 691 ++++ .../visp/3.7.0/include/visp3/core/vpMatrix.h | 1306 ++++++++ .../include/visp3/core/vpMatrixException.h | 104 + .../visp3/core/vpMeterPixelConversion.h | 369 +++ .../visp/3.7.0/include/visp3/core/vpMoment.h | 164 + .../3.7.0/include/visp3/core/vpMomentAlpha.h | 258 ++ .../3.7.0/include/visp3/core/vpMomentArea.h | 74 + .../visp3/core/vpMomentAreaNormalized.h | 191 ++ .../3.7.0/include/visp3/core/vpMomentBasic.h | 88 + .../include/visp3/core/vpMomentCInvariant.h | 288 ++ .../include/visp3/core/vpMomentCentered.h | 133 + .../3.7.0/include/visp3/core/vpMomentCommon.h | 148 + .../include/visp3/core/vpMomentDatabase.h | 174 + .../visp3/core/vpMomentGravityCenter.h | 144 + .../core/vpMomentGravityCenterNormalized.h | 75 + .../3.7.0/include/visp3/core/vpMomentObject.h | 326 ++ .../3.7.0/include/visp3/core/vpMouseButton.h | 57 + .../visp/3.7.0/include/visp3/core/vpMunkres.h | 381 +++ .../visp/3.7.0/include/visp3/core/vpMutex.h | 220 ++ .../visp/3.7.0/include/visp3/core/vpNetwork.h | 530 +++ .../visp/3.7.0/include/visp3/core/vpNoise.h | 39 + .../include/visp3/core/vpNullptrEmulated.h | 64 + .../include/visp3/core/vpParticleFilter.h | 811 +++++ .../visp3/core/vpPixelMeterConversion.h | 429 +++ .../visp/3.7.0/include/visp3/core/vpPlane.h | 173 + .../visp/3.7.0/include/visp3/core/vpPoint.h | 154 + .../visp/3.7.0/include/visp3/core/vpPolygon.h | 202 ++ .../3.7.0/include/visp3/core/vpPolygon3D.h | 274 ++ .../3.7.0/include/visp3/core/vpPoseVector.h | 346 ++ .../3.7.0/include/visp3/core/vpQuadProg.h | 164 + .../include/visp3/core/vpQuaternionVector.h | 168 + .../visp/3.7.0/include/visp3/core/vpRGBa.h | 339 ++ .../visp/3.7.0/include/visp3/core/vpRGBf.h | 190 ++ .../visp/3.7.0/include/visp3/core/vpRansac.h | 239 ++ .../visp/3.7.0/include/visp3/core/vpRect.h | 417 +++ .../3.7.0/include/visp3/core/vpRectOriented.h | 112 + .../visp/3.7.0/include/visp3/core/vpRequest.h | 241 ++ .../visp/3.7.0/include/visp3/core/vpRobust.h | 249 ++ .../include/visp3/core/vpRotationMatrix.h | 240 ++ .../include/visp3/core/vpRotationVector.h | 167 + .../3.7.0/include/visp3/core/vpRowVector.h | 371 +++ .../3.7.0/include/visp3/core/vpRxyzVector.h | 222 ++ .../3.7.0/include/visp3/core/vpRzyxVector.h | 223 ++ .../3.7.0/include/visp3/core/vpRzyzVector.h | 222 ++ .../visp/3.7.0/include/visp3/core/vpScale.h | 98 + .../visp/3.7.0/include/visp3/core/vpSerial.h | 187 ++ .../visp/3.7.0/include/visp3/core/vpServer.h | 223 ++ .../visp/3.7.0/include/visp3/core/vpSphere.h | 154 + .../visp3/core/vpStatisticalTestAbstract.h | 273 ++ .../visp3/core/vpStatisticalTestEWMA.h | 174 + .../visp3/core/vpStatisticalTestHinkley.h | 320 ++ .../core/vpStatisticalTestMeanAdjustedCUSUM.h | 267 ++ .../visp3/core/vpStatisticalTestShewhart.h | 232 ++ .../visp3/core/vpStatisticalTestSigma.h | 165 + .../3.7.0/include/visp3/core/vpSubColVector.h | 91 + .../3.7.0/include/visp3/core/vpSubMatrix.h | 96 + .../3.7.0/include/visp3/core/vpSubRowVector.h | 85 + .../3.7.0/include/visp3/core/vpThetaUVector.h | 240 ++ .../visp/3.7.0/include/visp3/core/vpThread.h | 205 ++ .../visp/3.7.0/include/visp3/core/vpTime.h | 109 + .../visp/3.7.0/include/visp3/core/vpTracker.h | 109 + .../include/visp3/core/vpTrackingException.h | 94 + .../include/visp3/core/vpTranslationVector.h | 213 ++ .../3.7.0/include/visp3/core/vpTriangle.h | 108 + .../3.7.0/include/visp3/core/vpUDPClient.h | 223 ++ .../3.7.0/include/visp3/core/vpUDPServer.h | 241 ++ .../visp3/core/vpUKSigmaDrawerAbstract.h | 88 + .../include/visp3/core/vpUKSigmaDrawerMerwe.h | 148 + .../visp/3.7.0/include/visp3/core/vpUniRand.h | 195 ++ .../include/visp3/core/vpUnscentedKalman.h | 480 +++ .../visp3/core/vpVelocityTwistMatrix.h | 251 ++ .../3.7.0/include/visp3/core/vpXmlParser.h | 323 ++ .../include/visp3/core/vpXmlParserCamera.h | 212 ++ .../visp3/core/vpXmlParserHomogeneousMatrix.h | 177 + .../visp3/core/vpXmlParserRectOriented.h | 124 + .../visp3/detection/vpDetectorAprilTag.h | 617 ++++ .../include/visp3/detection/vpDetectorBase.h | 136 + .../visp3/detection/vpDetectorDNNOpenCV.h | 632 ++++ .../detection/vpDetectorDataMatrixCode.h | 124 + .../include/visp3/detection/vpDetectorFace.h | 117 + .../visp3/detection/vpDetectorQRCode.h | 130 + .../include/visp3/dnn_tracker/vpMegaPose.h | 262 ++ .../visp3/dnn_tracker/vpMegaPoseTracker.h | 171 + .../visp3/gui/vpColorBlindFriendlyPalette.h | 198 ++ .../3.7.0/include/visp3/gui/vpD3DRenderer.h | 212 ++ .../3.7.0/include/visp3/gui/vpDisplayD3D.h | 121 + .../include/visp3/gui/vpDisplayFactory.h | 309 ++ .../3.7.0/include/visp3/gui/vpDisplayGDI.h | 144 + .../3.7.0/include/visp3/gui/vpDisplayGTK.h | 238 ++ .../3.7.0/include/visp3/gui/vpDisplayOpenCV.h | 220 ++ .../3.7.0/include/visp3/gui/vpDisplayPCL.h | 268 ++ .../3.7.0/include/visp3/gui/vpDisplayWin32.h | 211 ++ .../visp/3.7.0/include/visp3/gui/vpDisplayX.h | 236 ++ .../3.7.0/include/visp3/gui/vpGDIRenderer.h | 152 + .../3.7.0/include/visp3/gui/vpPclViewer.h | 257 ++ .../visp/3.7.0/include/visp3/gui/vpPlot.h | 208 ++ .../3.7.0/include/visp3/gui/vpPlotCurve.h | 86 + .../3.7.0/include/visp3/gui/vpPlotGraph.h | 225 ++ .../include/visp3/gui/vpProjectionDisplay.h | 133 + .../visp/3.7.0/include/visp3/gui/vpWin32API.h | 78 + .../3.7.0/include/visp3/gui/vpWin32Renderer.h | 213 ++ .../3.7.0/include/visp3/gui/vpWin32Window.h | 152 + .../visp3/imgproc/vpCircleHoughTransform.h | 1447 ++++++++ .../3.7.0/include/visp3/imgproc/vpContours.h | 351 ++ .../3.7.0/include/visp3/imgproc/vpImgproc.h | 655 ++++ .../3.7.0/include/visp3/io/vpDiskGrabber.h | 289 ++ .../visp/3.7.0/include/visp3/io/vpImageIo.h | 198 ++ .../3.7.0/include/visp3/io/vpImageQueue.h | 292 ++ .../include/visp3/io/vpImageStorageWorker.h | 138 + .../include/visp3/io/vpJsonArgumentParser.h | 291 ++ .../visp/3.7.0/include/visp3/io/vpKeyboard.h | 102 + .../3.7.0/include/visp3/io/vpParallelPort.h | 103 + .../visp3/io/vpParallelPortException.h | 93 + .../visp/3.7.0/include/visp3/io/vpParseArgv.h | 206 ++ .../3.7.0/include/visp3/io/vpVideoReader.h | 400 +++ .../3.7.0/include/visp3/io/vpVideoWriter.h | 286 ++ .../3.7.0/include/visp3/klt/vpKltOpencv.h | 452 +++ .../include/visp3/mbt/vpMbDepthDenseTracker.h | 193 ++ .../visp3/mbt/vpMbDepthNormalTracker.h | 205 ++ .../include/visp3/mbt/vpMbEdgeKltTracker.h | 375 +++ .../3.7.0/include/visp3/mbt/vpMbEdgeTracker.h | 547 +++ .../include/visp3/mbt/vpMbGenericTracker.h | 1073 ++++++ .../3.7.0/include/visp3/mbt/vpMbHiddenFaces.h | 966 ++++++ .../3.7.0/include/visp3/mbt/vpMbKltTracker.h | 503 +++ .../3.7.0/include/visp3/mbt/vpMbScanLine.h | 215 ++ .../3.7.0/include/visp3/mbt/vpMbTracker.h | 932 ++++++ .../include/visp3/mbt/vpMbtDistanceCircle.h | 270 ++ .../include/visp3/mbt/vpMbtDistanceCylinder.h | 302 ++ .../visp3/mbt/vpMbtDistanceKltCylinder.h | 234 ++ .../visp3/mbt/vpMbtDistanceKltPoints.h | 234 ++ .../include/visp3/mbt/vpMbtDistanceLine.h | 286 ++ .../include/visp3/mbt/vpMbtFaceDepthDense.h | 260 ++ .../include/visp3/mbt/vpMbtFaceDepthNormal.h | 371 +++ .../3.7.0/include/visp3/mbt/vpMbtMeEllipse.h | 92 + .../3.7.0/include/visp3/mbt/vpMbtMeLine.h | 129 + .../3.7.0/include/visp3/mbt/vpMbtPolygon.h | 173 + .../include/visp3/mbt/vpMbtTukeyEstimator.h | 498 +++ .../include/visp3/mbt/vpMbtXmlGenericParser.h | 180 + .../visp/3.7.0/include/visp3/me/vpMe.h | 698 ++++ .../visp/3.7.0/include/visp3/me/vpMeEllipse.h | 648 ++++ .../visp/3.7.0/include/visp3/me/vpMeLine.h | 580 ++++ .../visp/3.7.0/include/visp3/me/vpMeNurbs.h | 353 ++ .../visp/3.7.0/include/visp3/me/vpMeSite.h | 456 +++ .../visp/3.7.0/include/visp3/me/vpMeTracker.h | 323 ++ .../visp/3.7.0/include/visp3/me/vpNurbs.h | 528 +++ .../visp/3.7.0/include/visp3/robot/vpAfma6.h | 216 ++ .../3.7.0/include/visp3/robot/vpBiclops.h | 325 ++ .../include/visp3/robot/vpImageSimulator.h | 321 ++ .../3.7.0/include/visp3/robot/vpPioneer.h | 144 + .../3.7.0/include/visp3/robot/vpPioneerPan.h | 228 ++ .../visp/3.7.0/include/visp3/robot/vpPololu.h | 334 ++ .../visp/3.7.0/include/visp3/robot/vpPtu46.h | 102 + .../3.7.0/include/visp3/robot/vpQbDevice.h | 116 + .../3.7.0/include/visp3/robot/vpQbSoftHand.h | 105 + .../include/visp3/robot/vpReflexTakktile2.h | 159 + .../3.7.0/include/visp3/robot/vpRingLight.h | 128 + .../visp/3.7.0/include/visp3/robot/vpRobot.h | 184 ++ .../3.7.0/include/visp3/robot/vpRobotAfma6.h | 321 ++ .../3.7.0/include/visp3/robot/vpRobotBebop2.h | 257 ++ .../include/visp3/robot/vpRobotBiclops.h | 454 +++ .../3.7.0/include/visp3/robot/vpRobotCamera.h | 140 + .../include/visp3/robot/vpRobotException.h | 121 + .../include/visp3/robot/vpRobotFlirPtu.h | 174 + .../3.7.0/include/visp3/robot/vpRobotFranka.h | 372 +++ .../3.7.0/include/visp3/robot/vpRobotKinova.h | 206 ++ .../3.7.0/include/visp3/robot/vpRobotMavsdk.h | 165 + .../include/visp3/robot/vpRobotPioneer.h | 137 + .../include/visp3/robot/vpRobotPololuPtu.h | 273 ++ .../3.7.0/include/visp3/robot/vpRobotPtu46.h | 126 + .../include/visp3/robot/vpRobotSimulator.h | 85 + .../include/visp3/robot/vpRobotTemplate.h | 89 + .../visp3/robot/vpRobotUniversalRobots.h | 145 + .../include/visp3/robot/vpRobotViper650.h | 497 +++ .../include/visp3/robot/vpRobotViper850.h | 506 +++ .../visp3/robot/vpRobotWireFrameSimulator.h | 415 +++ .../include/visp3/robot/vpSimulatorAfma6.h | 278 ++ .../include/visp3/robot/vpSimulatorCamera.h | 142 + .../include/visp3/robot/vpSimulatorPioneer.h | 144 + .../visp3/robot/vpSimulatorPioneerPan.h | 141 + .../include/visp3/robot/vpSimulatorViper850.h | 304 ++ .../3.7.0/include/visp3/robot/vpUnicycle.h | 120 + .../visp/3.7.0/include/visp3/robot/vpViper.h | 175 + .../3.7.0/include/visp3/robot/vpViper650.h | 171 + .../3.7.0/include/visp3/robot/vpViper850.h | 171 + .../3.7.0/include/visp3/robot/vpVirtuose.h | 232 ++ .../visp3/robot/vpWireFrameSimulator.h | 618 ++++ .../visp3/robot/vpWireFrameSimulatorTypes.h | 130 + .../include/visp3/sensor/vp1394CMUGrabber.h | 351 ++ .../include/visp3/sensor/vp1394TwoGrabber.h | 471 +++ .../3.7.0/include/visp3/sensor/vpComedi.h | 182 + .../include/visp3/sensor/vpDirectShowDevice.h | 81 + .../visp3/sensor/vpDirectShowGrabber.h | 114 + .../visp3/sensor/vpDirectShowGrabberImpl.h | 214 ++ .../visp3/sensor/vpDirectShowSampleGrabberI.h | 101 + .../visp3/sensor/vpFlyCaptureGrabber.h | 246 ++ .../sensor/vpForceTorqueAtiNetFTSensor.h | 165 + .../visp3/sensor/vpForceTorqueAtiSensor.h | 119 + .../visp3/sensor/vpForceTorqueIitSensor.h | 191 ++ .../3.7.0/include/visp3/sensor/vpKinect.h | 182 + .../3.7.0/include/visp3/sensor/vpLaserScan.h | 127 + .../include/visp3/sensor/vpLaserScanner.h | 76 + .../visp/3.7.0/include/visp3/sensor/vpMocap.h | 107 + .../include/visp3/sensor/vpMocapQualisys.h | 71 + .../3.7.0/include/visp3/sensor/vpMocapVicon.h | 72 + .../visp3/sensor/vpOccipitalStructure.h | 369 +++ .../include/visp3/sensor/vpPylonFactory.h | 92 + .../include/visp3/sensor/vpPylonGrabber.h | 395 +++ .../3.7.0/include/visp3/sensor/vpRealSense2.h | 432 +++ .../3.7.0/include/visp3/sensor/vpScanPoint.h | 253 ++ .../3.7.0/include/visp3/sensor/vpSickLDMRS.h | 166 + .../include/visp3/sensor/vpUeyeGrabber.h | 124 + .../include/visp3/sensor/vpV4l2Grabber.h | 359 ++ .../include/visp3/tt/vpTemplateTracker.h | 307 ++ .../visp3/tt/vpTemplateTrackerBSpline.h | 71 + .../visp3/tt/vpTemplateTrackerHeader.h | 138 + .../include/visp3/tt/vpTemplateTrackerSSD.h | 88 + .../visp3/tt/vpTemplateTrackerSSDESM.h | 80 + .../vpTemplateTrackerSSDForwardAdditional.h | 84 + ...vpTemplateTrackerSSDForwardCompositional.h | 75 + ...vpTemplateTrackerSSDInverseCompositional.h | 83 + .../visp3/tt/vpTemplateTrackerTriangle.h | 177 + .../include/visp3/tt/vpTemplateTrackerWarp.h | 266 ++ .../visp3/tt/vpTemplateTrackerWarpAffine.h | 106 + .../tt/vpTemplateTrackerWarpHomography.h | 113 + .../tt/vpTemplateTrackerWarpHomographySL3.h | 105 + .../visp3/tt/vpTemplateTrackerWarpRT.h | 105 + .../visp3/tt/vpTemplateTrackerWarpSRT.h | 105 + .../tt/vpTemplateTrackerWarpTranslation.h | 106 + .../include/visp3/tt/vpTemplateTrackerZNCC.h | 93 + .../vpTemplateTrackerZNCCForwardAdditional.h | 72 + ...pTemplateTrackerZNCCInverseCompositional.h | 79 + .../include/visp3/tt/vpTemplateTrackerZone.h | 130 + .../include/visp3/tt_mi/vpTemplateTrackerMI.h | 182 + .../visp3/tt_mi/vpTemplateTrackerMIBSpline.h | 132 + .../visp3/tt_mi/vpTemplateTrackerMIESM.h | 120 + .../vpTemplateTrackerMIForwardAdditional.h | 89 + .../vpTemplateTrackerMIForwardCompositional.h | 77 + .../vpTemplateTrackerMIInverseCompositional.h | 103 + .../include/visp3/vision/vpBasicKeyPoint.h | 279 ++ .../include/visp3/vision/vpCalibration.h | 348 ++ .../visp3/vision/vpCalibrationException.h | 95 + .../visp3/vision/vpHandEyeCalibration.h | 152 + .../3.7.0/include/visp3/vision/vpHomography.h | 715 ++++ .../3.7.0/include/visp3/vision/vpKeyPoint.h | 2351 +++++++++++++ .../include/visp3/vision/vpPlaneEstimation.h | 129 + .../visp/3.7.0/include/visp3/vision/vpPose.h | 905 +++++ .../include/visp3/vision/vpPoseException.h | 96 + .../include/visp3/vision/vpPoseFeatures.h | 828 +++++ .../visp3/vision/vpXmlConfigParserKeyPoint.h | 204 ++ .../visp/3.7.0/include/visp3/visp.h | 96 + .../visp/3.7.0/include/visp3/visp_ar.h | 26 + .../visp/3.7.0/include/visp3/visp_blob.h | 14 + .../visp/3.7.0/include/visp3/visp_core.h | 146 + .../visp/3.7.0/include/visp3/visp_detection.h | 19 + .../3.7.0/include/visp3/visp_dnn_tracker.h | 14 + .../visp/3.7.0/include/visp3/visp_gui.h | 31 + .../visp/3.7.0/include/visp3/visp_imgproc.h | 15 + .../visp/3.7.0/include/visp3/visp_io.h | 23 + .../visp/3.7.0/include/visp3/visp_klt.h | 13 + .../visp/3.7.0/include/visp3/visp_mbt.h | 36 + .../visp/3.7.0/include/visp3/visp_me.h | 19 + .../visp/3.7.0/include/visp3/visp_modules.h | 31 + .../visp/3.7.0/include/visp3/visp_robot.h | 54 + .../visp/3.7.0/include/visp3/visp_sensor.h | 37 + .../visp/3.7.0/include/visp3/visp_tt.h | 33 + .../visp/3.7.0/include/visp3/visp_tt_mi.h | 18 + .../visp/3.7.0/include/visp3/visp_vision.h | 23 + .../include/visp3/visp_visual_features.h | 39 + .../visp/3.7.0/include/visp3/visp_vs.h | 18 + .../visp3/visual_features/vpBasicFeature.h | 149 + .../visp3/visual_features/vpFeatureBuilder.h | 166 + .../visp3/visual_features/vpFeatureDepth.h | 223 ++ .../visp3/visual_features/vpFeatureEllipse.h | 174 + .../visual_features/vpFeatureException.h | 97 + .../visp3/visual_features/vpFeatureLine.h | 245 ++ .../visual_features/vpFeatureLuminance.h | 134 + .../vpFeatureLuminanceMapping.h | 430 +++ .../visp3/visual_features/vpFeatureMoment.h | 292 ++ .../visual_features/vpFeatureMomentAlpha.h | 139 + .../visual_features/vpFeatureMomentArea.h | 93 + .../vpFeatureMomentAreaNormalized.h | 222 ++ .../visual_features/vpFeatureMomentBasic.h | 115 + .../vpFeatureMomentCInvariant.h | 338 ++ .../visual_features/vpFeatureMomentCentered.h | 123 + .../visual_features/vpFeatureMomentCommon.h | 276 ++ .../visual_features/vpFeatureMomentDatabase.h | 189 ++ .../vpFeatureMomentGravityCenter.h | 266 ++ .../vpFeatureMomentGravityCenterNormalized.h | 285 ++ .../visp3/visual_features/vpFeaturePoint.h | 242 ++ .../visp3/visual_features/vpFeaturePoint3D.h | 268 ++ .../visual_features/vpFeaturePointPolar.h | 319 ++ .../visp3/visual_features/vpFeatureSegment.h | 293 ++ .../visp3/visual_features/vpFeatureThetaU.h | 310 ++ .../visual_features/vpFeatureTranslation.h | 359 ++ .../visual_features/vpFeatureVanishingPoint.h | 114 + .../visp3/visual_features/vpGenericFeature.h | 226 ++ .../3.7.0/include/visp3/vs/vpAdaptiveGain.h | 312 ++ .../visp/3.7.0/include/visp3/vs/vpServo.h | 1367 ++++++++ .../visp/3.7.0/include/visp3/vs/vpServoData.h | 121 + .../3.7.0/include/visp3/vs/vpServoDisplay.h | 103 + .../3.7.0/include/visp3/vs/vpServoException.h | 96 + .../lib/cmake/visp/VISPConfig-version.cmake | 31 + .../3.7.0/lib/cmake/visp/VISPConfig.cmake | 684 ++++ .../lib/cmake/visp/VISPModules-release.cmake | 189 ++ .../3.7.0/lib/cmake/visp/VISPModules.cmake | 217 ++ .../visp/3.7.0/lib/cmake/visp/VISPUse.cmake | 43 + .../third_party/visp/3.7.0/lib/libvisp_ar.so | 1 + .../visp/3.7.0/lib/libvisp_ar.so.3.7 | 1 + .../visp/3.7.0/lib/libvisp_ar.so.3.7.0 | Bin 0 -> 18344 bytes .../visp/3.7.0/lib/libvisp_blob.so | 1 + .../visp/3.7.0/lib/libvisp_blob.so.3.7 | 1 + .../visp/3.7.0/lib/libvisp_blob.so.3.7.0 | Bin 0 -> 102904 bytes .../visp/3.7.0/lib/libvisp_core.so | 1 + .../visp/3.7.0/lib/libvisp_core.so.3.7 | 1 + .../visp/3.7.0/lib/libvisp_core.so.3.7.0 | Bin 0 -> 4914400 bytes .../visp/3.7.0/lib/libvisp_detection.so | 1 + .../visp/3.7.0/lib/libvisp_detection.so.3.7 | 1 + .../visp/3.7.0/lib/libvisp_detection.so.3.7.0 | Bin 0 -> 694272 bytes .../visp/3.7.0/lib/libvisp_dnn_tracker.so | 1 + .../visp/3.7.0/lib/libvisp_dnn_tracker.so.3.7 | 1 + .../3.7.0/lib/libvisp_dnn_tracker.so.3.7.0 | Bin 0 -> 303856 bytes .../third_party/visp/3.7.0/lib/libvisp_gui.so | 1 + .../visp/3.7.0/lib/libvisp_gui.so.3.7 | 1 + .../visp/3.7.0/lib/libvisp_gui.so.3.7.0 | Bin 0 -> 287336 bytes .../visp/3.7.0/lib/libvisp_imgproc.so | 1 + .../visp/3.7.0/lib/libvisp_imgproc.so.3.7 | 1 + .../visp/3.7.0/lib/libvisp_imgproc.so.3.7.0 | Bin 0 -> 484208 bytes .../third_party/visp/3.7.0/lib/libvisp_io.so | 1 + .../visp/3.7.0/lib/libvisp_io.so.3.7 | 1 + .../visp/3.7.0/lib/libvisp_io.so.3.7.0 | Bin 0 -> 807120 bytes .../third_party/visp/3.7.0/lib/libvisp_klt.so | 1 + .../visp/3.7.0/lib/libvisp_klt.so.3.7 | 1 + .../visp/3.7.0/lib/libvisp_klt.so.3.7.0 | Bin 0 -> 49016 bytes .../third_party/visp/3.7.0/lib/libvisp_mbt.so | 1 + .../visp/3.7.0/lib/libvisp_mbt.so.3.7 | 1 + .../visp/3.7.0/lib/libvisp_mbt.so.3.7.0 | Bin 0 -> 3827872 bytes .../third_party/visp/3.7.0/lib/libvisp_me.so | 1 + .../visp/3.7.0/lib/libvisp_me.so.3.7 | 1 + .../visp/3.7.0/lib/libvisp_me.so.3.7.0 | Bin 0 -> 242744 bytes .../visp/3.7.0/lib/libvisp_robot.so | 1 + .../visp/3.7.0/lib/libvisp_robot.so.3.7 | 1 + .../visp/3.7.0/lib/libvisp_robot.so.3.7.0 | Bin 0 -> 1102976 bytes .../visp/3.7.0/lib/libvisp_sensor.so | 1 + .../visp/3.7.0/lib/libvisp_sensor.so.3.7 | 1 + .../visp/3.7.0/lib/libvisp_sensor.so.3.7.0 | Bin 0 -> 311008 bytes .../third_party/visp/3.7.0/lib/libvisp_tt.so | 1 + .../visp/3.7.0/lib/libvisp_tt.so.3.7 | 1 + .../visp/3.7.0/lib/libvisp_tt.so.3.7.0 | Bin 0 -> 375832 bytes .../visp/3.7.0/lib/libvisp_tt_mi.so | 1 + .../visp/3.7.0/lib/libvisp_tt_mi.so.3.7 | 1 + .../visp/3.7.0/lib/libvisp_tt_mi.so.3.7.0 | Bin 0 -> 215392 bytes .../visp/3.7.0/lib/libvisp_vision.so | 1 + .../visp/3.7.0/lib/libvisp_vision.so.3.7 | 1 + .../visp/3.7.0/lib/libvisp_vision.so.3.7.0 | Bin 0 -> 1489304 bytes .../visp/3.7.0/lib/libvisp_visual_features.so | 1 + .../3.7.0/lib/libvisp_visual_features.so.3.7 | 1 + .../lib/libvisp_visual_features.so.3.7.0 | Bin 0 -> 560704 bytes .../third_party/visp/3.7.0/lib/libvisp_vs.so | 1 + .../visp/3.7.0/lib/libvisp_vs.so.3.7 | 1 + .../visp/3.7.0/lib/libvisp_vs.so.3.7.0 | Bin 0 -> 116248 bytes .../visp/3.7.0/lib/pkgconfig/visp.pc | 14 + .../3.7.0/share/man/man1/visp-config.1.gz | Bin 0 -> 428 bytes .../visp-3.7.0/data/font/Rubik-Regular.ttf | Bin 0 -> 152784 bytes .../data/robot-simulator/afma6_arm1.bnd | 22 + .../data/robot-simulator/afma6_arm2.bnd | 22 + .../data/robot-simulator/afma6_arm3.bnd | 22 + .../data/robot-simulator/afma6_arm4.bnd | 22 + .../data/robot-simulator/afma6_gate.bnd | 176 + .../data/robot-simulator/afma6_tool_ccmop.bnd | 66 + .../robot-simulator/afma6_tool_gripper.bnd | 44 + .../robot-simulator/afma6_tool_vacuum.bnd | 44 + .../data/robot-simulator/viper850_arm1.bnd | 93 + .../data/robot-simulator/viper850_arm2.bnd | 22 + .../data/robot-simulator/viper850_arm3.bnd | 73 + .../data/robot-simulator/viper850_arm4.bnd | 22 + .../data/robot-simulator/viper850_arm5.bnd | 22 + .../data/robot-simulator/viper850_arm6.bnd | 22 + .../data/wireframe-simulator/3pts.bnd | 301 ++ .../data/wireframe-simulator/3pts.sld | 40 + .../data/wireframe-simulator/camera.bnd | 19 + .../data/wireframe-simulator/camera.sld | 10 + .../data/wireframe-simulator/circle.bnd | 238 ++ .../data/wireframe-simulator/circle.sld | 10 + .../data/wireframe-simulator/circle_sq2.bnd | 210 ++ .../data/wireframe-simulator/circle_sq2.sld | 10 + .../data/wireframe-simulator/circles2.bnd | 423 +++ .../data/wireframe-simulator/circles2.sld | 17 + .../data/wireframe-simulator/cube.bnd | 22 + .../data/wireframe-simulator/cube.sld | 9 + .../data/wireframe-simulator/cylinder.bnd | 345 ++ .../data/wireframe-simulator/cylinder.sld | 12 + .../data/wireframe-simulator/diamond.bnd | 394 +++ .../data/wireframe-simulator/diamond.sld | 46 + .../data/wireframe-simulator/line.bnd | 66 + .../data/wireframe-simulator/line.sld | 28 + .../data/wireframe-simulator/pipe.bnd | 210 ++ .../data/wireframe-simulator/pipe.sld | 13 + .../data/wireframe-simulator/plan.bnd | 1100 ++++++ .../data/wireframe-simulator/plan.sld | 451 +++ .../data/wireframe-simulator/plate.bnd | 139 + .../data/wireframe-simulator/plate.sld | 46 + .../data/wireframe-simulator/plate_6cm.bnd | 372 +++ .../data/wireframe-simulator/plate_6cm.sld | 37 + .../data/wireframe-simulator/plate_8cm.bnd | 143 + .../data/wireframe-simulator/plate_8cm.sld | 46 + .../data/wireframe-simulator/point.bnd | 93 + .../data/wireframe-simulator/point.sld | 10 + .../data/wireframe-simulator/point_cloud.bnd | 764 +++++ .../data/wireframe-simulator/point_cloud.sld | 85 + .../data/wireframe-simulator/rectangle.bnd | 404 +++ .../data/wireframe-simulator/rectangle.sld | 46 + .../data/wireframe-simulator/road.bnd | 1386 ++++++++ .../data/wireframe-simulator/road.sld | 571 ++++ .../data/wireframe-simulator/sphere.bnd | 261 ++ .../data/wireframe-simulator/sphere.sld | 12 + .../data/wireframe-simulator/square10cm.bnd | 394 +++ .../data/wireframe-simulator/square10cm.sld | 46 + .../data/wireframe-simulator/tool.bnd | 137 + .../data/wireframe-simulator/tool.sld | 27 + .../data/wireframe-simulator/trapezoid.bnd | 394 +++ .../data/wireframe-simulator/trapezoid.sld | 46 + model/april_tag/tag36_11.png | Bin 0 -> 1832 bytes model/xiaoyan_description/dual_arm.xml | 75 +- request.txt | 1 + 868 files changed, 118766 insertions(+), 23 deletions(-) create mode 100644 cmvr-es/controller/src/controller_test.cpp create mode 100755 dependency/x86/third_party/visp/3.7.0/bin/visp-calibrate-camera create mode 100755 dependency/x86/third_party/visp/3.7.0/bin/visp-compute-apriltag-poses create mode 100755 dependency/x86/third_party/visp/3.7.0/bin/visp-compute-chessboard-poses create mode 100755 dependency/x86/third_party/visp/3.7.0/bin/visp-compute-eye-in-hand-calibration create mode 100755 dependency/x86/third_party/visp/3.7.0/bin/visp-compute-eye-to-hand-calibration create mode 100755 dependency/x86/third_party/visp/3.7.0/bin/visp-config create mode 100755 dependency/x86/third_party/visp/3.7.0/bin/visp-read-rs-dataset create mode 100755 dependency/x86/third_party/visp/3.7.0/bin/visp-save-rs-dataset create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/visp_modules.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vp1394CMUGrabber.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vp1394TwoGrabber.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpAR.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpAROgre.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpAdaptiveGain.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpAfma6.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpArray2D.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpBSpline.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpBasicFeature.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpBasicKeyPoint.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpBiclops.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpCPUFeatures.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpCalibration.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpCalibrationException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpCameraParameters.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpCannyEdgeDetection.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpCircle.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpCircleHoughTransform.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpClient.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpColVector.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpColor.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpColorBlindFriendlyPalette.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpColorDepthConversion.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpColorGetter.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpColormap.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpComedi.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpConfig.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpContours.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpConvert.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpCylinder.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpD3DRenderer.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpDebug.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpDetectorAprilTag.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpDetectorBase.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpDetectorDNNOpenCV.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpDetectorDataMatrixCode.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpDetectorFace.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpDetectorQRCode.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpDirectShowDevice.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpDirectShowGrabber.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpDirectShowGrabberImpl.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpDirectShowSampleGrabberI.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpDiskGrabber.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplay.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayD3D.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayFactory.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayGDI.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayGTK.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayOpenCV.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayPCL.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayWin32.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayX.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpDot.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpDot2.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpEigenConversion.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpEndian.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpExponentialMap.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureBuilder.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureDepth.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureDisplay.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureEllipse.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureLine.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureLuminance.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureLuminanceMapping.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMoment.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentAlpha.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentArea.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentAreaNormalized.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentBasic.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentCInvariant.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentCentered.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentCommon.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentDatabase.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentGravityCenter.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentGravityCenterNormalized.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFeaturePoint.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFeaturePoint3D.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFeaturePointPolar.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureSegment.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureThetaU.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureTranslation.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureVanishingPoint.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFlyCaptureGrabber.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFont.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpForceTorqueAtiNetFTSensor.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpForceTorqueAtiSensor.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpForceTorqueIitSensor.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpForceTwistMatrix.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpForwardProjection.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFrameGrabber.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpFrameGrabberException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpGDIRenderer.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpGEMM.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpGaussRand.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpGaussianFilter.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpGenericFeature.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpHSV.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpHandEyeCalibration.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpHinkley.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpHistogram.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpHistogramPeak.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpHistogramValey.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpHomogeneousMatrix.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpHomography.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpImage.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpImageCircle.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpImageConvert.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpImageDraw.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpImageException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpImageFilter.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpImageIo.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpImageMorphology.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpImagePoint.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpImageQueue.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpImageSimulator.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpImageStorageWorker.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpImageTools.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpImageTools_warp.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpImage_getters.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpImage_lut.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpImage_operators.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpImgproc.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpIoException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpIoTools.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpJsonArgumentParser.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpJsonParsing.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpKalmanFilter.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpKeyPoint.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpKeyboard.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpKinect.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpKltOpencv.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpLaserScan.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpLaserScanner.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpLinProg.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpLine.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpLinearKalmanFilterInstantiation.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpList.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMath.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMatrix.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMatrixException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMbDepthDenseTracker.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMbDepthNormalTracker.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMbEdgeKltTracker.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMbEdgeTracker.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMbGenericTracker.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMbHiddenFaces.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMbKltTracker.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMbScanLine.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMbTracker.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtDistanceCircle.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtDistanceCylinder.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtDistanceKltCylinder.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtDistanceKltPoints.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtDistanceLine.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtFaceDepthDense.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtFaceDepthNormal.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtMeEllipse.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtMeLine.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtPolygon.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtTukeyEstimator.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtXmlGenericParser.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMe.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMeEllipse.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMeLine.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMeNurbs.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMeSite.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMeTracker.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMegaPose.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMegaPoseTracker.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMeterPixelConversion.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMocap.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMocapQualisys.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMocapVicon.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMoment.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentAlpha.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentArea.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentAreaNormalized.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentBasic.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentCInvariant.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentCentered.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentCommon.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentDatabase.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentGravityCenter.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentGravityCenterNormalized.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentObject.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMouseButton.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMunkres.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpMutex.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpNetwork.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpNoise.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpNullptrEmulated.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpNurbs.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpOccipitalStructure.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DBaseRenderer.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DCommonFilters.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DFrameworkManager.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DGeometryRenderer.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DLight.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DPostProcessFilter.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DRGBRenderer.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DRenderParameters.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DRendererSet.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpParallelPort.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpParallelPortException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpParseArgv.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpParticleFilter.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPclViewer.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPioneer.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPioneerPan.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPixelMeterConversion.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPlane.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPlaneEstimation.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPlot.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPlotCurve.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPlotGraph.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPoint.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPololu.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPolygon.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPolygon3D.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPose.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPoseException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPoseFeatures.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPoseVector.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpProjectionDisplay.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPtu46.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPylonFactory.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpPylonGrabber.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpQbDevice.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpQbSoftHand.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpQuadProg.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpQuaternionVector.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRGBa.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRGBf.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRansac.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRealSense2.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRect.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRectOriented.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpReflexTakktile2.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRequest.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRingLight.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRobot.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotAfma6.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotBebop2.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotBiclops.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotCamera.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotFlirPtu.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotFranka.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotKinova.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotMavsdk.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotPioneer.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotPololuPtu.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotPtu46.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotSimulator.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotTemplate.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotUniversalRobots.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotViper650.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotViper850.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotWireFrameSimulator.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRobust.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRotationMatrix.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRotationVector.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRowVector.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRxyzVector.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRzyxVector.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpRzyzVector.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpScale.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpScanPoint.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpSerial.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpServer.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpServo.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpServoData.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpServoDisplay.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpServoException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpSickLDMRS.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulator.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulatorAfma6.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulatorCamera.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulatorException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulatorPioneer.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulatorPioneerPan.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulatorViper850.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpSphere.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpStatisticalTestAbstract.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpStatisticalTestEWMA.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpStatisticalTestHinkley.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpStatisticalTestMeanAdjustedCUSUM.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpStatisticalTestShewhart.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpStatisticalTestSigma.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpSubColVector.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpSubMatrix.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpSubRowVector.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTracker.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerBSpline.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerHeader.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerMI.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerMIBSpline.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerMIESM.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerMIForwardAdditional.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerMIForwardCompositional.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerMIInverseCompositional.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerSSD.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerSSDESM.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerSSDForwardAdditional.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerSSDForwardCompositional.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerSSDInverseCompositional.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerTriangle.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarp.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarpAffine.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarpHomography.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarpHomographySL3.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarpRT.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarpSRT.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarpTranslation.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerZNCC.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerZNCCForwardAdditional.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerZNCCInverseCompositional.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerZone.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpThetaUVector.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpThread.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTime.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTracker.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTrackingException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTranslationVector.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpTriangle.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpUDPClient.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpUDPServer.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpUKSigmaDrawerAbstract.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpUKSigmaDrawerMerwe.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpUeyeGrabber.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpUniRand.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpUnicycle.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpUnscentedKalman.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpV4l2Grabber.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpVelocityTwistMatrix.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpVideoReader.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpVideoWriter.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpViewer.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpViper.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpViper650.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpViper850.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpVirtuose.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpWin32API.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpWin32Renderer.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpWin32Window.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpWireFrameSimulator.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpWireFrameSimulatorTypes.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpXmlConfigParserKeyPoint.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpXmlParser.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpXmlParserCamera.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpXmlParserHomogeneousMatrix.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp/vpXmlParserRectOriented.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpAR.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpAROgre.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DBaseRenderer.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DCommonFilters.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DFrameworkManager.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DGeometryRenderer.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DLight.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DPostProcessFilter.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DRGBRenderer.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DRenderParameters.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DRendererSet.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpSimulator.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpSimulatorException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpViewer.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/blob/vpDot.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/blob/vpDot2.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpArray2D.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpBSpline.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpCPUFeatures.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpCameraParameters.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpCannyEdgeDetection.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpCircle.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpClient.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpColVector.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpColor.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpColorDepthConversion.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpColorGetter.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpColormap.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpConfig.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpConvert.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpCylinder.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpDebug.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpDisplay.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpDisplayException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpEigenConversion.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpEndian.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpExponentialMap.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpFeatureDisplay.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpFont.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpForceTwistMatrix.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpForwardProjection.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpFrameGrabber.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpFrameGrabberException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpGEMM.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpGaussRand.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpGaussianFilter.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpHSV.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpHinkley.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpHistogram.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpHistogramPeak.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpHistogramValey.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpHomogeneousMatrix.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImage.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageCircle.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageConvert.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageDraw.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageFilter.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageMorphology.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImagePoint.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageTools.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageTools_warp.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImage_getters.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImage_lut.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImage_operators.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpIoException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpIoTools.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpJsonParsing.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpKalmanFilter.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpLinProg.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpLine.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpLinearKalmanFilterInstantiation.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpList.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMath.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMatrix.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMatrixException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMeterPixelConversion.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMoment.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentAlpha.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentArea.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentAreaNormalized.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentBasic.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentCInvariant.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentCentered.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentCommon.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentDatabase.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentGravityCenter.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentGravityCenterNormalized.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentObject.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMouseButton.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMunkres.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMutex.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpNetwork.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpNoise.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpNullptrEmulated.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpParticleFilter.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpPixelMeterConversion.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpPlane.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpPoint.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpPolygon.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpPolygon3D.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpPoseVector.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpQuadProg.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpQuaternionVector.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRGBa.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRGBf.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRansac.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRect.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRectOriented.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRequest.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRobust.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRotationMatrix.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRotationVector.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRowVector.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRxyzVector.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRzyxVector.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRzyzVector.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpScale.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpSerial.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpServer.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpSphere.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpStatisticalTestAbstract.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpStatisticalTestEWMA.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpStatisticalTestHinkley.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpStatisticalTestMeanAdjustedCUSUM.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpStatisticalTestShewhart.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpStatisticalTestSigma.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpSubColVector.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpSubMatrix.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpSubRowVector.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpThetaUVector.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpThread.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpTime.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpTracker.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpTrackingException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpTranslationVector.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpTriangle.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpUDPClient.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpUDPServer.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpUKSigmaDrawerAbstract.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpUKSigmaDrawerMerwe.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpUniRand.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpUnscentedKalman.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpVelocityTwistMatrix.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpXmlParser.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpXmlParserCamera.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpXmlParserHomogeneousMatrix.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpXmlParserRectOriented.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/detection/vpDetectorAprilTag.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/detection/vpDetectorBase.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/detection/vpDetectorDNNOpenCV.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/detection/vpDetectorDataMatrixCode.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/detection/vpDetectorFace.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/detection/vpDetectorQRCode.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/dnn_tracker/vpMegaPose.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/dnn_tracker/vpMegaPoseTracker.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpColorBlindFriendlyPalette.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpD3DRenderer.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayD3D.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayFactory.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayGDI.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayGTK.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayOpenCV.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayPCL.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayWin32.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayX.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpGDIRenderer.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpPclViewer.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpPlot.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpPlotCurve.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpPlotGraph.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpProjectionDisplay.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpWin32API.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpWin32Renderer.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpWin32Window.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/imgproc/vpCircleHoughTransform.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/imgproc/vpContours.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/imgproc/vpImgproc.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpDiskGrabber.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpImageIo.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpImageQueue.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpImageStorageWorker.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpJsonArgumentParser.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpKeyboard.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpParallelPort.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpParallelPortException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpParseArgv.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpVideoReader.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpVideoWriter.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/klt/vpKltOpencv.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbDepthDenseTracker.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbDepthNormalTracker.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbEdgeKltTracker.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbEdgeTracker.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbGenericTracker.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbHiddenFaces.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbKltTracker.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbScanLine.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbTracker.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtDistanceCircle.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtDistanceCylinder.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtDistanceKltCylinder.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtDistanceKltPoints.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtDistanceLine.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtFaceDepthDense.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtFaceDepthNormal.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtMeEllipse.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtMeLine.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtPolygon.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtTukeyEstimator.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtXmlGenericParser.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpMe.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpMeEllipse.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpMeLine.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpMeNurbs.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpMeSite.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpMeTracker.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpNurbs.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpAfma6.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpBiclops.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpImageSimulator.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpPioneer.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpPioneerPan.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpPololu.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpPtu46.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpQbDevice.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpQbSoftHand.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpReflexTakktile2.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRingLight.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobot.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotAfma6.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotBebop2.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotBiclops.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotCamera.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotFlirPtu.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotFranka.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotKinova.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotMavsdk.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotPioneer.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotPololuPtu.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotPtu46.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotSimulator.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotTemplate.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotUniversalRobots.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotViper650.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotViper850.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotWireFrameSimulator.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpSimulatorAfma6.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpSimulatorCamera.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpSimulatorPioneer.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpSimulatorPioneerPan.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpSimulatorViper850.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpUnicycle.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpViper.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpViper650.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpViper850.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpVirtuose.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpWireFrameSimulator.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpWireFrameSimulatorTypes.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vp1394CMUGrabber.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vp1394TwoGrabber.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpComedi.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpDirectShowDevice.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpDirectShowGrabber.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpDirectShowGrabberImpl.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpDirectShowSampleGrabberI.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpFlyCaptureGrabber.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpForceTorqueAtiNetFTSensor.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpForceTorqueAtiSensor.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpForceTorqueIitSensor.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpKinect.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpLaserScan.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpLaserScanner.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpMocap.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpMocapQualisys.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpMocapVicon.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpOccipitalStructure.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpPylonFactory.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpPylonGrabber.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpRealSense2.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpScanPoint.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpSickLDMRS.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpUeyeGrabber.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpV4l2Grabber.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTracker.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerBSpline.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerHeader.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerSSD.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerSSDESM.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerSSDForwardAdditional.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerSSDForwardCompositional.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerSSDInverseCompositional.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerTriangle.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarp.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarpAffine.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarpHomography.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarpHomographySL3.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarpRT.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarpSRT.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarpTranslation.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerZNCC.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerZNCCForwardAdditional.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerZNCCInverseCompositional.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerZone.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/tt_mi/vpTemplateTrackerMI.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/tt_mi/vpTemplateTrackerMIBSpline.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/tt_mi/vpTemplateTrackerMIESM.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/tt_mi/vpTemplateTrackerMIForwardAdditional.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/tt_mi/vpTemplateTrackerMIForwardCompositional.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/tt_mi/vpTemplateTrackerMIInverseCompositional.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpBasicKeyPoint.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpCalibration.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpCalibrationException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpHandEyeCalibration.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpHomography.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpKeyPoint.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpPlaneEstimation.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpPose.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpPoseException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpPoseFeatures.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpXmlConfigParserKeyPoint.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visp.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visp_ar.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visp_blob.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visp_core.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visp_detection.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visp_dnn_tracker.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visp_gui.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visp_imgproc.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visp_io.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visp_klt.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visp_mbt.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visp_me.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visp_modules.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visp_robot.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visp_sensor.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visp_tt.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visp_tt_mi.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visp_vision.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visp_visual_features.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visp_vs.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpBasicFeature.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureBuilder.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureDepth.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureEllipse.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureLine.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureLuminance.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureLuminanceMapping.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMoment.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentAlpha.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentArea.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentAreaNormalized.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentBasic.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentCInvariant.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentCentered.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentCommon.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentDatabase.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentGravityCenter.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentGravityCenterNormalized.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeaturePoint.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeaturePoint3D.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeaturePointPolar.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureSegment.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureThetaU.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureTranslation.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureVanishingPoint.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpGenericFeature.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/vs/vpAdaptiveGain.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/vs/vpServo.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/vs/vpServoData.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/vs/vpServoDisplay.h create mode 100644 dependency/x86/third_party/visp/3.7.0/include/visp3/vs/vpServoException.h create mode 100644 dependency/x86/third_party/visp/3.7.0/lib/cmake/visp/VISPConfig-version.cmake create mode 100644 dependency/x86/third_party/visp/3.7.0/lib/cmake/visp/VISPConfig.cmake create mode 100644 dependency/x86/third_party/visp/3.7.0/lib/cmake/visp/VISPModules-release.cmake create mode 100644 dependency/x86/third_party/visp/3.7.0/lib/cmake/visp/VISPModules.cmake create mode 100644 dependency/x86/third_party/visp/3.7.0/lib/cmake/visp/VISPUse.cmake create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_ar.so create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_ar.so.3.7 create mode 100644 dependency/x86/third_party/visp/3.7.0/lib/libvisp_ar.so.3.7.0 create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_blob.so create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_blob.so.3.7 create mode 100644 dependency/x86/third_party/visp/3.7.0/lib/libvisp_blob.so.3.7.0 create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_core.so create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_core.so.3.7 create mode 100644 dependency/x86/third_party/visp/3.7.0/lib/libvisp_core.so.3.7.0 create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_detection.so create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_detection.so.3.7 create mode 100644 dependency/x86/third_party/visp/3.7.0/lib/libvisp_detection.so.3.7.0 create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_dnn_tracker.so create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_dnn_tracker.so.3.7 create mode 100644 dependency/x86/third_party/visp/3.7.0/lib/libvisp_dnn_tracker.so.3.7.0 create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_gui.so create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_gui.so.3.7 create mode 100644 dependency/x86/third_party/visp/3.7.0/lib/libvisp_gui.so.3.7.0 create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_imgproc.so create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_imgproc.so.3.7 create mode 100644 dependency/x86/third_party/visp/3.7.0/lib/libvisp_imgproc.so.3.7.0 create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_io.so create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_io.so.3.7 create mode 100644 dependency/x86/third_party/visp/3.7.0/lib/libvisp_io.so.3.7.0 create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_klt.so create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_klt.so.3.7 create mode 100644 dependency/x86/third_party/visp/3.7.0/lib/libvisp_klt.so.3.7.0 create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_mbt.so create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_mbt.so.3.7 create mode 100644 dependency/x86/third_party/visp/3.7.0/lib/libvisp_mbt.so.3.7.0 create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_me.so create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_me.so.3.7 create mode 100644 dependency/x86/third_party/visp/3.7.0/lib/libvisp_me.so.3.7.0 create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_robot.so create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_robot.so.3.7 create mode 100644 dependency/x86/third_party/visp/3.7.0/lib/libvisp_robot.so.3.7.0 create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_sensor.so create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_sensor.so.3.7 create mode 100644 dependency/x86/third_party/visp/3.7.0/lib/libvisp_sensor.so.3.7.0 create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_tt.so create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_tt.so.3.7 create mode 100644 dependency/x86/third_party/visp/3.7.0/lib/libvisp_tt.so.3.7.0 create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_tt_mi.so create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_tt_mi.so.3.7 create mode 100644 dependency/x86/third_party/visp/3.7.0/lib/libvisp_tt_mi.so.3.7.0 create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_vision.so create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_vision.so.3.7 create mode 100644 dependency/x86/third_party/visp/3.7.0/lib/libvisp_vision.so.3.7.0 create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_visual_features.so create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_visual_features.so.3.7 create mode 100644 dependency/x86/third_party/visp/3.7.0/lib/libvisp_visual_features.so.3.7.0 create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_vs.so create mode 120000 dependency/x86/third_party/visp/3.7.0/lib/libvisp_vs.so.3.7 create mode 100644 dependency/x86/third_party/visp/3.7.0/lib/libvisp_vs.so.3.7.0 create mode 100644 dependency/x86/third_party/visp/3.7.0/lib/pkgconfig/visp.pc create mode 100644 dependency/x86/third_party/visp/3.7.0/share/man/man1/visp-config.1.gz create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/font/Rubik-Regular.ttf create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/robot-simulator/afma6_arm1.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/robot-simulator/afma6_arm2.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/robot-simulator/afma6_arm3.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/robot-simulator/afma6_arm4.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/robot-simulator/afma6_gate.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/robot-simulator/afma6_tool_ccmop.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/robot-simulator/afma6_tool_gripper.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/robot-simulator/afma6_tool_vacuum.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/robot-simulator/viper850_arm1.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/robot-simulator/viper850_arm2.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/robot-simulator/viper850_arm3.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/robot-simulator/viper850_arm4.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/robot-simulator/viper850_arm5.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/robot-simulator/viper850_arm6.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/3pts.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/3pts.sld create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/camera.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/camera.sld create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/circle.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/circle.sld create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/circle_sq2.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/circle_sq2.sld create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/circles2.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/circles2.sld create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/cube.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/cube.sld create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/cylinder.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/cylinder.sld create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/diamond.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/diamond.sld create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/line.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/line.sld create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/pipe.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/pipe.sld create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/plan.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/plan.sld create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/plate.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/plate.sld create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/plate_6cm.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/plate_6cm.sld create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/plate_8cm.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/plate_8cm.sld create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/point.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/point.sld create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/point_cloud.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/point_cloud.sld create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/rectangle.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/rectangle.sld create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/road.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/road.sld create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/sphere.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/sphere.sld create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/square10cm.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/square10cm.sld create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/tool.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/tool.sld create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/trapezoid.bnd create mode 100644 dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator/trapezoid.sld create mode 100644 model/april_tag/tag36_11.png diff --git a/README.md b/README.md index 9178ae6d..e9f13ade 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,9 @@ sudo apt-get install libglfw3-dev # Assimp sudo apt-get install libassimp-dev +# visp +sudo apt-get install -y libx11-dev liblapack-dev libv4l-dev libzbar-dev libpthread-stubs0-dev libdc1394-dev nlohmann-json3-dev + [//]: # () [//]: # (# json) diff --git a/cmake/FindExternalLib.cmake b/cmake/FindExternalLib.cmake index 72afc10d..b426441e 100644 --- a/cmake/FindExternalLib.cmake +++ b/cmake/FindExternalLib.cmake @@ -93,17 +93,15 @@ function(setup_external_libs ARCH) endif() endif() - # ---- bin (protoc / grpc_cpp_plugin) ---- + + # ---- bin: if dependency provides tools, install them to /bin ---- if(EXISTS "${FULL_PATH}/bin") - # 精确收集你需要的两个 - if(EXISTS "${FULL_PATH}/bin/grpc_cpp_plugin") - list(APPEND INSTALL_BIN_FILES "${FULL_PATH}/bin/grpc_cpp_plugin") - endif() - if(EXISTS "${FULL_PATH}/bin/protoc") - list(APPEND INSTALL_BIN_FILES "${FULL_PATH}/bin/protoc") - endif() - if(EXISTS "${FULL_PATH}/bin/protoc-31.1.0") - list(APPEND INSTALL_BIN_FILES "${FULL_PATH}/bin/protoc-31.1.0") + file(GLOB _BIN_FILES + LIST_DIRECTORIES false + "${FULL_PATH}/bin/*" + ) + if(_BIN_FILES) + list(APPEND INSTALL_BIN_FILES ${_BIN_FILES}) endif() endif() @@ -163,6 +161,7 @@ function(setup_external_libs ARCH) list(REMOVE_DUPLICATES INSTALL_BIN_FILES) install(PROGRAMS ${INSTALL_BIN_FILES} DESTINATION bin) + # After installing, patch RPATH of installed tools to $ORIGIN/../lib # After installing, patch RPATH of installed tools to $ORIGIN/../lib install(CODE [[ execute_process(COMMAND bash -lc @@ -171,15 +170,19 @@ function(setup_external_libs ARCH) echo '[install] patchelf not found, skip patch bin rpath' exit 0 fi + echo '[install] patch rpath under: ${CMAKE_INSTALL_PREFIX}/bin' - for f in '${CMAKE_INSTALL_PREFIX}/bin/protoc' '${CMAKE_INSTALL_PREFIX}/bin/grpc_cpp_plugin'; do - if [ -f \"$f\" ]; then - patchelf --set-rpath '$ORIGIN/../lib' \"$f\" + # -perm -111: any executable bit set + find '${CMAKE_INSTALL_PREFIX}/bin' -maxdepth 1 -type f -perm -111 | while read -r f; do + # only patch ELF binaries (skip scripts/text) + if file -b \"$f\" 2>/dev/null | grep -qi 'ELF'; then + patchelf --set-rpath '$ORIGIN/../lib' \"$f\" || true fi done echo '[install] done (bin rpath)'" ) ]]) + endif() if(LIB_COUNT GREATER 0) diff --git a/cmvr-es/controller/CMakeLists.txt b/cmvr-es/controller/CMakeLists.txt index a80a810e..34d7c50a 100644 --- a/cmvr-es/controller/CMakeLists.txt +++ b/cmvr-es/controller/CMakeLists.txt @@ -1,5 +1,16 @@ + + +# 如果报 relocation ... can not be used when making a shared object; recompile with -fPIC ,说明SRC 中包含了test文件 ,test +# 中链接 -lgtest -lgtest_main ,这是静态库,导致 libcontroller.so 被迫依赖 gtest。 +# 所以 add_library(controller SHARED +# src/controller_creator.cpp +# src/pid_controller.cpp +# ❌ 不要把 src/controller_test.cpp 放进来 +#) 其他动态库类似 file(GLOB SRC - ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/controller_creator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/pid_controller.cpp + ) add_library(controller SHARED ${SRC}) @@ -21,3 +32,30 @@ target_link_libraries(controller PUBLIC ) add_library(cmvr_es::controller ALIAS controller) + + + + +# -------------------------------------------------------- +# Unit test +# -------------------------------------------------------- +find_package(VISP REQUIRED) +find_package(realsense2 REQUIRED) +add_executable(controller_test + ${CMAKE_CURRENT_SOURCE_DIR}/src/controller_test.cpp +) + +target_link_libraries(controller_test + PRIVATE + cmvr_es::utils + cmvr_es::ik_solver + cmvr_es::planner + cmvr_es::proto + cmvr_es::mujoco_viewer + gtest + gtest_main + pthread + glog + ${VISP_LIBRARIES} + realsense2::realsense2 +) \ No newline at end of file diff --git a/cmvr-es/controller/src/controller_test.cpp b/cmvr-es/controller/src/controller_test.cpp new file mode 100644 index 00000000..a0c97de8 --- /dev/null +++ b/cmvr-es/controller/src/controller_test.cpp @@ -0,0 +1,867 @@ +// +// Created by lgv on 2026/2/10. +// TEST(contrller_test, visp_test){ +#include "gtest/gtest.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ik_solver/include/lawba_ik_solver.h" +#include "simulate/mujoco/mujoco_viewer/include/mujoco_viewer.h" +using namespace cmvr; + + + + +#include "gtest/gtest.h" + +#include +#include +#include +#include + +#include + +#include "simulate/mujoco/mujoco_viewer/include/mujoco_viewer.h" + +// ---- ViSP ---- +#include +#include +#include +#include + +// Created by lgv on 2026/2/10. + +#include "gtest/gtest.h" + +#include +#include +#include +#include + +#include + +#include "simulate/mujoco/mujoco_viewer/include/mujoco_viewer.h" + +// ---- ViSP ---- +#include +#include +#include +#include + +using namespace cmvr; + +namespace { + +// clamp helper +static inline double clamp(double x, double lo, double hi) { + return std::max(lo, std::min(hi, x)); +} + +// MuJoCo xmat(9) -> Eigen::Matrix3d (row-major) +static inline Eigen::Matrix3d xmat_to_R(const mjtNum* xmat9) { + Eigen::Matrix3d R; + R << xmat9[0], xmat9[1], xmat9[2], + xmat9[3], xmat9[4], xmat9[5], + xmat9[6], xmat9[7], xmat9[8]; + return R; +} + +// Build vpHomogeneousMatrix from Eigen R,t +static inline vpHomogeneousMatrix make_cMo_from_Eigen(const Eigen::Matrix3d& R, + const Eigen::Vector3d& t) { + vpRotationMatrix vR; + for (int r = 0; r < 3; ++r) + for (int c = 0; c < 3; ++c) + vR[r][c] = R(r, c); + + vpTranslationVector vt(t(0), t(1), t(2)); + vpHomogeneousMatrix cMo(vt, vR); + return cMo; +} + +} // namespace + +class IBVSHomingViewer : public MuJocoViewer { +public: + using MuJocoViewer::MuJocoViewer; + +protected: + enum class Mode { HOMING, IBVS }; + + void initOnce(mjModel* m, mjData* d) override { + // 1) 主视角自由相机 + PiP显示 hand_cam + setupCamera(3.0, -170.0, -40.0); + enablePiPCamera("hand_cam", 405, 1200, 320, 240); + + // 2) 右臂 7DOF actuator/joint + const char* act_names[7] = { + "R_SHOULDER_P_pos", + "R_SHOULDER_R_pos", + "R_SHOULDER_Y_pos", + "R_ELBOW_R_pos", + "R_WRIST_P_pos", + "R_WRIST_Y_pos", + "R_WRIST_R_pos" + }; + const char* jnt_names[7] = { + "R_SHOULDER_P", + "R_SHOULDER_R", + "R_SHOULDER_Y", + "R_ELBOW_R", + "R_WRIST_P", + "R_WRIST_Y", + "R_WRIST_R" + }; + + for (int i = 0; i < 7; ++i) { + act_ids_[i] = mj_name2id(m, mjOBJ_ACTUATOR, act_names[i]); + jnt_ids_[i] = mj_name2id(m, mjOBJ_JOINT, jnt_names[i]); + + if (act_ids_[i] < 0) std::fprintf(stderr, "Cannot find actuator %s\n", act_names[i]); + if (jnt_ids_[i] < 0) std::fprintf(stderr, "Cannot find joint %s\n", jnt_names[i]); + + if (jnt_ids_[i] >= 0) { + qpos_adr_[i] = m->jnt_qposadr[jnt_ids_[i]]; + dof_adr_[i] = m->jnt_dofadr[jnt_ids_[i]]; + } + } + + // 3) 找相机 site + tag + cam_site_id_ = mj_name2id(m, mjOBJ_SITE, "R_CAM_SITE"); + tag_body_id_ = mj_name2id(m, mjOBJ_BODY, "tag_board"); + + std::printf("[IBVS] R_CAM_SITE id=%d, tag_board id=%d\n", + cam_site_id_, tag_body_id_); + + if (cam_site_id_ < 0 || tag_body_id_ < 0) { + std::fprintf(stderr, "[IBVS] Missing R_CAM_SITE or tag_board. Check XML names.\n"); + ready_ = false; + return; + } + + // 4) 初始姿态(保证 PiP 里能看到 tag) + q_home_ = { 0.25, 1.00, M_PI/2 - 0.2, M_PI/2 - 0.2, -M_PI + 0.5, 0.0, 0.0 }; + + // 5) 坐标轴对齐:site相机系 -> ViSP相机系 + // 你现在截图表现是“画面左上”,但 t_co.x,y 是正,说明 x/y 反了 + // 先试这个:翻转 x,y + R_cv_ = (Eigen::Matrix3d() << + -1, 0, 0, + 0,-1, 0, + 0, 0, 1).finished(); + + // 6) ViSP IBVS 参数 + tag_half_ = 0.06; // 12cm tag -> half 6cm + Z_des_ = 0.34; + lambda_ = 0.7; + + task_.setServo(vpServo::EYEINHAND_CAMERA); + task_.setInteractionMatrixType(vpServo::CURRENT); + task_.setLambda(lambda_); + + obj_pts_[0].setWorldCoordinates(-tag_half_, -tag_half_, 0.0); + obj_pts_[1].setWorldCoordinates( tag_half_, -tag_half_, 0.0); + obj_pts_[2].setWorldCoordinates( tag_half_, tag_half_, 0.0); + obj_pts_[3].setWorldCoordinates(-tag_half_, tag_half_, 0.0); + + // desired: 正对 + 距离 Z_des + { + vpTranslationVector t_des(0.0, 0.0, Z_des_); + vpRotationMatrix R_des; R_des.eye(); + vpHomogeneousMatrix cMo_des(t_des, R_des); + + for (int i = 0; i < 4; ++i) { + obj_pts_[i].track(cMo_des); + s_star_[i].buildFrom(obj_pts_[i].get_x(), + obj_pts_[i].get_y(), + obj_pts_[i].get_Z()); + + s_cur_[i].buildFrom(0.0, 0.0, 1.0); + task_.addFeature(s_cur_[i], s_star_[i]); + } + } + + // 初始化 q_cmd 为当前 qpos,避免突变 + for (int i = 0; i < 7; ++i) { + q_cmd_[i] = (qpos_adr_[i] >= 0) ? d->qpos[qpos_adr_[i]] : 0.0; + } + + mode_ = Mode::HOMING; + home_hold_acc_ = 0.0; + step_count_ = 0; + ready_ = true; + } + + void controlCallback(mjModel* m, mjData* d) override { + if (!ready_) return; + const double dt = m->opt.timestep; + + // ====================== + // A) HOMING + // ====================== + if (mode_ == Mode::HOMING) { + double max_err = 0.0; + for (int i = 0; i < 7; ++i) { + if (act_ids_[i] < 0 || qpos_adr_[i] < 0) continue; + d->ctrl[act_ids_[i]] = q_home_[i]; + const double qi = d->qpos[qpos_adr_[i]]; + max_err = std::max(max_err, std::abs(qi - q_home_[i])); + } + + if (max_err < home_tol_) home_hold_acc_ += dt; + else home_hold_acc_ = 0.0; + + if (home_hold_acc_ > home_hold_time_) { + for (int i = 0; i < 7; ++i) { + if (qpos_adr_[i] >= 0) q_cmd_[i] = d->qpos[qpos_adr_[i]]; + } + mode_ = Mode::IBVS; + std::printf("[IBVS] switch HOMING -> IBVS\n"); + } + return; + } + + // ====================== + // B) IBVS + // ====================== + + // 1) camera(site) pose in world + const mjtNum* pc = d->site_xpos + 3 * cam_site_id_; + const mjtNum* Rc9 = d->site_xmat + 9 * cam_site_id_; + Eigen::Vector3d p_cw(pc[0], pc[1], pc[2]); + Eigen::Matrix3d R_cw = xmat_to_R(Rc9); // site(cam)->world + Eigen::Matrix3d R_wc = R_cw.transpose(); // world->site(cam) + + // 2) tag_board body pose in world + const mjtNum* po = d->xpos + 3 * tag_body_id_; + const mjtNum* Ro9 = d->xmat + 9 * tag_body_id_; + Eigen::Vector3d p_ow(po[0], po[1], po[2]); + Eigen::Matrix3d R_ow = xmat_to_R(Ro9); + + // 3) object->camera(site) + Eigen::Matrix3d R_co_site = R_wc * R_ow; + Eigen::Vector3d t_co_site = R_wc * (p_ow - p_cw); + + // 4) site相机系 -> ViSP相机系(对齐) + Eigen::Matrix3d R_co = R_cv_ * R_co_site; + Eigen::Vector3d t_co = R_cv_ * t_co_site; + + if ((step_count_ % 60) == 0) { + std::printf("[IBVS] t_co = [%.3f %.3f %.3f]\n", t_co.x(), t_co.y(), t_co.z()); + } + + vpHomogeneousMatrix cMo = make_cMo_from_Eigen(R_co, t_co); + + // 5) current features + for (int i = 0; i < 4; ++i) { + obj_pts_[i].track(cMo); + double x = obj_pts_[i].get_x(); + double y = obj_pts_[i].get_y(); + double Z = std::max(obj_pts_[i].get_Z(), 0.05); + s_cur_[i].buildFrom(x, y, Z); + } + + // 6) ViSP control law -> v_c (ViSP camera frame) + vpColVector v_c = task_.computeControlLaw(); + + // 限幅(关键) + for (int k = 0; k < 6; ++k) { + v_c[k] = clamp(v_c[k], -vmax6_[k], vmax6_[k]); + } + + if ((step_count_ % 60) == 0) { + std::printf("[IBVS] v_c = [%+.3f %+.3f %+.3f %+.3f %+.3f %+.3f]\n", + v_c[0], v_c[1], v_c[2], v_c[3], v_c[4], v_c[5]); + } + + // 7) ViSP相机速度 -> site相机速度(逆对齐) + Eigen::Vector3d v_visp(v_c[0], v_c[1], v_c[2]); + Eigen::Vector3d w_visp(v_c[3], v_c[4], v_c[5]); + + Eigen::Vector3d v_site = R_cv_.transpose() * v_visp; + Eigen::Vector3d w_site = R_cv_.transpose() * w_visp; + + // 8) site相机速度 -> world twist(给 world Jacobian 用) + Eigen::Vector3d v_w = R_cw * v_site; + Eigen::Vector3d w_w = R_cw * w_site; + + Eigen::Matrix twist_w; + twist_w << v_w(0), v_w(1), v_w(2), w_w(0), w_w(1), w_w(2); + + // 9) Jacobian for R_CAM_SITE (world) + std::vector jacp(3 * m->nv); + std::vector jacr(3 * m->nv); + mj_jacSite(m, d, jacp.data(), jacr.data(), cam_site_id_); + + Eigen::Matrix J; + J.setZero(); + for (int j = 0; j < 7; ++j) { + const int dof = dof_adr_[j]; + if (dof < 0) continue; + + J(0,j) = jacp[0*m->nv + dof]; + J(1,j) = jacp[1*m->nv + dof]; + J(2,j) = jacp[2*m->nv + dof]; + J(3,j) = jacr[0*m->nv + dof]; + J(4,j) = jacr[1*m->nv + dof]; + J(5,j) = jacr[2*m->nv + dof]; + } + + // 10) DLS inverse: qdot + Eigen::Matrix A = J * J.transpose(); + A += (mu_*mu_) * Eigen::Matrix::Identity(); + Eigen::Matrix qdot = J.transpose() * A.inverse() * twist_w; + + for (int i = 0; i < 7; ++i) { + qdot(i) = clamp(qdot(i), -qdot_max_, qdot_max_); + } + + // 11) integrate -> position targets + for (int i = 0; i < 7; ++i) { + q_cmd_[i] += qdot(i) * dt; + + if (jnt_ids_[i] >= 0 && m->jnt_limited[jnt_ids_[i]]) { + const double lo = m->jnt_range[2*jnt_ids_[i] + 0]; + const double hi = m->jnt_range[2*jnt_ids_[i] + 1]; + q_cmd_[i] = clamp(q_cmd_[i], lo, hi); + } + + if (act_ids_[i] >= 0) { + d->ctrl[act_ids_[i]] = q_cmd_[i]; + } + } + + // 12) sanity: 看看机械臂是否真的在动 + if ((step_count_ % 120) == 0 && qpos_adr_[0] >= 0 && act_ids_[0] >= 0) { + std::printf("[IBVS] qpos0=%.3f ctrl0=%.3f\n", + d->qpos[qpos_adr_[0]], d->ctrl[act_ids_[0]]); + } + + ++step_count_; + } + + void onReset(mjModel* m, mjData* d) override { + (void)m; + for (int i = 0; i < 7; ++i) { + if (qpos_adr_[i] >= 0) q_cmd_[i] = d->qpos[qpos_adr_[i]]; + } + mode_ = Mode::HOMING; + home_hold_acc_ = 0.0; + step_count_ = 0; + } + +private: + bool ready_{false}; + + std::array act_ids_{}; + std::array jnt_ids_{}; + std::array qpos_adr_{ { -1,-1,-1,-1,-1,-1,-1 } }; + std::array dof_adr_{ { -1,-1,-1,-1,-1,-1,-1 } }; + + int cam_site_id_{-1}; + int tag_body_id_{-1}; + + // frame align + Eigen::Matrix3d R_cv_{Eigen::Matrix3d::Identity()}; + + // homing + Mode mode_{Mode::HOMING}; + std::array q_home_{ {0,0,0,0,0,0,0} }; + double home_tol_{0.02}; + double home_hold_time_{0.3}; + double home_hold_acc_{0.0}; + + // IBVS + double lambda_{0.7}; + double tag_half_{0.06}; + double Z_des_{0.60}; + + double mu_{0.02}; + double qdot_max_{0.6}; + double vmax6_[6] = {0.15, 0.15, 0.20, 0.6, 0.6, 0.6}; + + int step_count_{0}; + + vpServo task_; + vpPoint obj_pts_[4]; + vpFeaturePoint s_cur_[4]; + vpFeaturePoint s_star_[4]; + + std::array q_cmd_{ {0,0,0,0,0,0,0} }; +}; + + + + + + +// ---- ViSP ---- +#include +#include + +#include + + +class IBVSFromMujocoCameraViewer : public MuJocoViewer { +public: + using MuJocoViewer::MuJocoViewer; + +protected: + enum class Mode { HOMING, IBVS }; + + void initOnce(mjModel* m, mjData* d) override { + // 1) 主视角 + PiP(你的 renderPiP 会自动把 hand_cam 画出来并缓存 RGBD) + setupCamera(3.0, -170.0, -40.0); + enablePiPCamera("hand_cam", 405, 1000, 320, 240); + + // 2) 右臂 7DOF actuator / joint + const char* act_names[7] = { + "R_SHOULDER_P_pos", + "R_SHOULDER_R_pos", + "R_SHOULDER_Y_pos", + "R_ELBOW_R_pos", + "R_WRIST_P_pos", + "R_WRIST_Y_pos", + "R_WRIST_R_pos" + }; + const char* jnt_names[7] = { + "R_SHOULDER_P", + "R_SHOULDER_R", + "R_SHOULDER_Y", + "R_ELBOW_R", + "R_WRIST_P", + "R_WRIST_Y", + "R_WRIST_R" + }; + + for (int i = 0; i < 7; ++i) { + act_ids_[i] = mj_name2id(m, mjOBJ_ACTUATOR, act_names[i]); + jnt_ids_[i] = mj_name2id(m, mjOBJ_JOINT, jnt_names[i]); + if (act_ids_[i] < 0) { + std::cout << "[IBVS] Cannot find actuator " << act_names[i] << std::endl; + } + if (jnt_ids_[i] < 0) { + std::cout << "[IBVS] Cannot find joint " << jnt_names[i] << std::endl; + } + + if (jnt_ids_[i] >= 0) { + qpos_adr_[i] = m->jnt_qposadr[jnt_ids_[i]]; + dof_adr_[i] = m->jnt_dofadr[jnt_ids_[i]]; + } + } + + // 3) 必须有:R_CAM_SITE 用来算 jacobian(末端相机坐标) + cam_site_id_ = mj_name2id(m, mjOBJ_SITE, "R_CAM_SITE"); + if (cam_site_id_ < 0) { + std::cout << "[IBVS] Missing site R_CAM_SITE in XML" << std::endl; + ready_ = false; + return; + } + + // 4) hand_cam:用它的 fovy 计算内参 + hand_cam_id_ = mj_name2id(m, mjOBJ_CAMERA, "hand_cam"); + if (hand_cam_id_ < 0) { + std::cout << "[IBVS] Missing camera hand_cam in XML" << std::endl; + ready_ = false; + return; + } + + // 5) homing 姿态(先让相机看到 tag) + q_home_ = { 0.25, 1.00, M_PI/2 - 0.2, M_PI/2 - 0.2, -M_PI + 0.5, 0.0, 0.0 }; + + // 6) 坐标对齐(site相机系 -> ViSP相机系) + // 这就是你之前验证过能收敛的那套:x/y 同时翻转 + R_cv_ = (Eigen::Matrix3d() << + -1, 0, 0, + 0,-1, 0, + 0, 0, 1).finished(); + + // 7) IBVS 任务:用 AprilTag 的 4 个角点特征 + tag_size_m_ = 0.12; // 12cm + tag_half_ = tag_size_m_ * 0.5; + Z_des_ = 0.60; + lambda_ = 0.7; + + task_.setServo(vpServo::EYEINHAND_CAMERA); + task_.setInteractionMatrixType(vpServo::CURRENT); + task_.setLambda(lambda_); + + // tag 平面 z=0,原点在 tag 中心 + obj_pts_[0].setWorldCoordinates(-tag_half_, -tag_half_, 0.0); + obj_pts_[1].setWorldCoordinates( tag_half_, -tag_half_, 0.0); + obj_pts_[2].setWorldCoordinates( tag_half_, tag_half_, 0.0); + obj_pts_[3].setWorldCoordinates(-tag_half_, tag_half_, 0.0); + + // desired: 正对 + 距离 Z_des + { + vpTranslationVector t_des(0.0, 0.0, Z_des_); + vpRotationMatrix R_des; R_des.eye(); + vpHomogeneousMatrix cMo_des(t_des, R_des); + + for (int i = 0; i < 4; ++i) { + obj_pts_[i].track(cMo_des); + s_star_[i].buildFrom(obj_pts_[i].get_x(), + obj_pts_[i].get_y(), + obj_pts_[i].get_Z()); + + s_cur_[i].buildFrom(0.0, 0.0, 1.0); + task_.addFeature(s_cur_[i], s_star_[i]); + } + } + + // 8) AprilTag detector + detector_ = vpDetectorAprilTag(vpDetectorAprilTag::TAG_36h11); + detector_.setAprilTagPoseEstimationMethod(vpDetectorAprilTag::HOMOGRAPHY_VIRTUAL_VS); + + // 初始化 q_cmd,防跳变 + for (int i = 0; i < 7; ++i) { + q_cmd_[i] = (qpos_adr_[i] >= 0) ? d->qpos[qpos_adr_[i]] : 0.0; + } + + mode_ = Mode::HOMING; + home_hold_acc_ = 0.0; + step_count_ = 0; + last_frame_id_ = 0; + ready_ = true; + + std::cout << "[IBVS] initOnce OK. cam_site_id=" << cam_site_id_ + << " hand_cam_id=" << hand_cam_id_ << std::endl; + } + + void controlCallback(mjModel* m, mjData* d) override { + if (!ready_) return; + const double dt = m->opt.timestep; + + // ====================== + // A) HOMING + // ====================== + if (mode_ == Mode::HOMING) { + double max_err = 0.0; + for (int i = 0; i < 7; ++i) { + if (act_ids_[i] < 0 || qpos_adr_[i] < 0) continue; + d->ctrl[act_ids_[i]] = q_home_[i]; + const double qi = d->qpos[qpos_adr_[i]]; + max_err = std::max(max_err, std::abs(qi - q_home_[i])); + } + + if (max_err < home_tol_) home_hold_acc_ += dt; + else home_hold_acc_ = 0.0; + + if (home_hold_acc_ > home_hold_time_) { + for (int i = 0; i < 7; ++i) { + if (qpos_adr_[i] >= 0) q_cmd_[i] = d->qpos[qpos_adr_[i]]; + } + mode_ = Mode::IBVS; + std::cout << "[IBVS] switch HOMING -> IBVS" << std::endl; + } + return; + } + + // ====================== + // B) IBVS(用 MuJoCo 相机图像) + // ====================== + + // 1) 取最新 PiP 图像(RGB + depth z-buffer) + std::vector rgb; + std::vector depth; + int w=0, h=0; + uint64_t fid=0; + + if (!getPiPCameraRGBD(rgb, depth, w, h, fid)) { + return; + } + if (fid == last_frame_id_) { + return; // 没新帧就不做视觉(避免重复计算) + } + last_frame_id_ = fid; + + if (w <= 0 || h <= 0 || (int)rgb.size() != 3*w*h) return; + + // 2) 计算相机内参(从 hand_cam 的 fovy) + // MuJoCo 的 fovy 是“垂直视场角(度)” + const double fovy_deg = m->cam_fovy[hand_cam_id_]; + const double fovy = fovy_deg * M_PI / 180.0; + const double fy = (h * 0.5) / std::tan(fovy * 0.5); + const double fx = fy; // 由几何关系可得(见推导) + const double cx = w * 0.5; + const double cy = h * 0.5; + + vpCameraParameters cam; + cam.initPersProjWithoutDistortion(fx, fy, cx, cy); + + // 3) RGB -> 灰度 vpImage + vpImage I(h, w); + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + const int idx = (y*w + x) * 3; + const unsigned char r = rgb[idx + 0]; + const unsigned char g = rgb[idx + 1]; + const unsigned char b = rgb[idx + 2]; + I[y][x] = static_cast(0.299*r + 0.587*g + 0.114*b); + } + } + + // 4) AprilTag 检测 + 位姿估计(直接得到 cMo) + std::vector cMo_vec; + bool ok = detector_.detect(I, tag_size_m_, cam, cMo_vec); + + if (!ok || cMo_vec.empty()) { + // 没检测到:最简单策略:保持当前位置(不更新 ctrl) + if ((step_count_ % 60) == 0) { + std::cout << "[IBVS] no tag detected" << std::endl; + } + ++step_count_; + return; + } + + vpHomogeneousMatrix cMo = cMo_vec[0]; + + // 5) 用 cMo 更新四角点特征 + for (int i = 0; i < 4; ++i) { + obj_pts_[i].track(cMo); + const double x = obj_pts_[i].get_x(); + const double y = obj_pts_[i].get_y(); + const double Z = std::max(obj_pts_[i].get_Z(), 0.05); + s_cur_[i].buildFrom(x, y, Z); + } + + // 6) ViSP 控制律:得到相机速度 v_c(ViSP 相机系) + vpColVector v_c = task_.computeControlLaw(); + + // 限幅(很关键) + for (int k = 0; k < 6; ++k) v_c[k] = clamp(v_c[k], -vmax6_[k], vmax6_[k]); + + if ((step_count_ % 60) == 0) { + // 粗略打印一下位姿平移(单位 m) + vpTranslationVector t = cMo.getTranslationVector(); + std::cout << "[IBVS] t_co(visp)=[" << t[0] << " " << t[1] << " " << t[2] + << "]" << std::endl; + std::cout << "[IBVS] v_c=[" << v_c[0] << " " << v_c[1] << " " << v_c[2] + << " " << v_c[3] << " " << v_c[4] << " " << v_c[5] + << "]" << std::endl; + } + + // 7) 把 ViSP 相机速度 -> site 相机速度 -> world twist + // 先:ViSP -> site(逆对齐) + Eigen::Vector3d v_visp(v_c[0], v_c[1], v_c[2]); + Eigen::Vector3d w_visp(v_c[3], v_c[4], v_c[5]); + Eigen::Vector3d v_site = R_cv_.transpose() * v_visp; + Eigen::Vector3d w_site = R_cv_.transpose() * w_visp; + + // 再:site -> world(用 R_cw) + const mjtNum* pc = d->site_xpos + 3 * cam_site_id_; + const mjtNum* Rc9 = d->site_xmat + 9 * cam_site_id_; + (void)pc; + Eigen::Matrix3d R_cw = xmat_to_R(Rc9); + + Eigen::Vector3d v_w = R_cw * v_site; + Eigen::Vector3d w_w = R_cw * w_site; + + Eigen::Matrix twist_w; + twist_w << v_w(0), v_w(1), v_w(2), w_w(0), w_w(1), w_w(2); + + // 8) Jacobian(mj_jacSite 给的是 world 线速度/角速度) + std::vector jacp(3 * m->nv); + std::vector jacr(3 * m->nv); + mj_jacSite(m, d, jacp.data(), jacr.data(), cam_site_id_); + + Eigen::Matrix J; + J.setZero(); + for (int j = 0; j < 7; ++j) { + const int dof = dof_adr_[j]; + if (dof < 0) continue; + J(0,j) = jacp[0*m->nv + dof]; + J(1,j) = jacp[1*m->nv + dof]; + J(2,j) = jacp[2*m->nv + dof]; + J(3,j) = jacr[0*m->nv + dof]; + J(4,j) = jacr[1*m->nv + dof]; + J(5,j) = jacr[2*m->nv + dof]; + } + + // 9) DLS: qdot + Eigen::Matrix A = J * J.transpose(); + A += (mu_*mu_) * Eigen::Matrix::Identity(); + Eigen::Matrix qdot = J.transpose() * A.inverse() * twist_w; + + for (int i = 0; i < 7; ++i) qdot(i) = clamp(qdot(i), -qdot_max_, qdot_max_); + + // 10) integrate -> 位置控制 ctrl + for (int i = 0; i < 7; ++i) { + q_cmd_[i] += qdot(i) * dt; + + // joint limit clamp + if (jnt_ids_[i] >= 0 && m->jnt_limited[jnt_ids_[i]]) { + const double lo = m->jnt_range[2*jnt_ids_[i] + 0]; + const double hi = m->jnt_range[2*jnt_ids_[i] + 1]; + q_cmd_[i] = clamp(q_cmd_[i], lo, hi); + } + + if (act_ids_[i] >= 0) d->ctrl[act_ids_[i]] = q_cmd_[i]; + } + + ++step_count_; + } + + void onReset(mjModel* m, mjData* d) override { + (void)m; + for (int i = 0; i < 7; ++i) { + if (qpos_adr_[i] >= 0) q_cmd_[i] = d->qpos[qpos_adr_[i]]; + } + mode_ = Mode::HOMING; + home_hold_acc_ = 0.0; + step_count_ = 0; + last_frame_id_ = 0; + } + +private: + bool ready_{false}; + + // ids + std::array act_ids_{}; + std::array jnt_ids_{}; + std::array qpos_adr_{ { -1,-1,-1,-1,-1,-1,-1 } }; + std::array dof_adr_{ { -1,-1,-1,-1,-1,-1,-1 } }; + int cam_site_id_{-1}; + int hand_cam_id_{-1}; + + // homing + Mode mode_{Mode::HOMING}; + std::array q_home_{ {0,0,0,0,0,0,0} }; + double home_tol_{0.02}; + double home_hold_time_{0.3}; + double home_hold_acc_{0.0}; + + // align: site -> ViSP + Eigen::Matrix3d R_cv_{Eigen::Matrix3d::Identity()}; + + // IBVS params + double lambda_{0.7}; + double tag_size_m_{0.12}; + double tag_half_{0.06}; + double Z_des_{0.60}; + + double mu_{0.02}; // DLS damping + double qdot_max_{0.6}; // rad/s + double vmax6_[6] = {0.15, 0.15, 0.20, 0.6, 0.6, 0.6}; + + // ViSP + vpServo task_; + vpPoint obj_pts_[4]; + vpFeaturePoint s_cur_[4]; + vpFeaturePoint s_star_[4]; + vpDetectorAprilTag detector_; + + // control state + std::array q_cmd_{ {0,0,0,0,0,0,0} }; + int step_count_{0}; + uint64_t last_frame_id_{0}; +}; + + + + + +class ControllerViewer : public MuJocoViewer { +public: + using MuJocoViewer::MuJocoViewer; + + void setTarget(const std::vector &q_target) { + std::lock_guard lock(mtx_); + q_cmd_ = q_target; + } + +protected: + void initOnce(mjModel *m, mjData *d) override + { + (void)d; + setupCamera(3.0, -170.0, -40.0); + // enablePiPCamera("hand_cam", 320, 240, 10); + enablePiPCamera("hand_cam", 405, 1200, 320, 240); + + const char *act_names[7] = { + "R_SHOULDER_P_pos", + "R_SHOULDER_R_pos", + "R_SHOULDER_Y_pos", + "R_ELBOW_R_pos", + "R_WRIST_P_pos", + "R_WRIST_Y_pos", + "R_WRIST_R_pos" + }; + + for (int i = 0; i < 7; ++i) { + act_ids_[i] = mj_name2id(m, mjOBJ_ACTUATOR, act_names[i]); + if (act_ids_[i] < 0) { + std::cout << "Cannot find actuator " << act_names[i] << std::endl; + } + } + + act_ids_inited_ = true; + } + + void controlCallback(mjModel *m, mjData *d) override + { + (void)m; + if (!act_ids_inited_) return; + + std::vector q_local; + { + std::lock_guard lock(mtx_); + q_local = q_cmd_; + } + + for (int i = 0; i < 7; ++i) { + if (act_ids_[i] < 0) continue; + if (i < static_cast(q_local.size())) { + d->ctrl[act_ids_[i]] = q_local[i]; + } + } + } + + void onReset(mjModel *m, mjData *d) override + { + (void)m; + (void)d; + std::lock_guard lock(mtx_); + q_cmd_.assign(7, 0.0); + } + +private: + std::array act_ids_{}; + bool act_ids_inited_{false}; + std::vector q_cmd_{7, 0.0}; + std::mutex mtx_; +}; + +TEST(controller_test, mujoco_viewer_smoke) +{ + ControllerViewer viewer("/home/lgv/cmvr/0-workspace/cmvr-es/model/xiaoyan_description/dual_arm.xml"); + + std::vector q_seed = { + 0.25, 1.00, M_PI / 2 - 0.2, M_PI / 2 - 0.2, -M_PI+ 0.5 , 0, 0 + }; + viewer.setTarget(q_seed); + viewer.run(); +} + +TEST(controller_test, mujoco_ibvs_sim_gt) +{ + IBVSHomingViewer viewer("/home/lgv/cmvr/0-workspace/cmvr-es/model/xiaoyan_description/dual_arm.xml"); + viewer.run(); +} + +TEST(controller_test, mujoco_camera_apriltag_ibvs_full) +{ + IBVSFromMujocoCameraViewer viewer( + "/home/lgv/cmvr/0-workspace/cmvr-es/model/xiaoyan_description/dual_arm.xml" + ); + viewer.run(); +} diff --git a/cmvr-es/simulate/mujoco/mujoco_viewer/include/mujoco_viewer.h b/cmvr-es/simulate/mujoco/mujoco_viewer/include/mujoco_viewer.h index 46dc5a87..fc64a4ab 100644 --- a/cmvr-es/simulate/mujoco/mujoco_viewer/include/mujoco_viewer.h +++ b/cmvr-es/simulate/mujoco/mujoco_viewer/include/mujoco_viewer.h @@ -4,16 +4,20 @@ #pragma once -#include -#include #include +#include +#include +#include +#include #include "mujoco/mujoco.h" #include "simulate/mujoco/mujoco_viewer/include/simulate.h" #include "common/consts/constant.h" namespace cmvr { + class PiPGlfwAdapter; class MuJocoViewer { + friend class PiPGlfwAdapter; public: explicit MuJocoViewer(const char *model_path); @@ -29,6 +33,24 @@ namespace cmvr { mjModel *model() const { return m_; } mjData *data() const { return d_; } + // 同一窗口画中画:显示模型内固定相机视角(像素坐标) + void enablePiPCamera(const char *camera_name, + int left, + int bottom, + int width, + int height); + void disablePiPCamera(); + // 获取 PiP 相机 RGB+Depth(Depth 是 OpenGL z-buffer 0..1) + // depth 可不取(传 nullptr 或者用 getPiPCameraRGB 旧接口) + bool getPiPCameraRGBD(std::vector &rgb, + std::vector &depth, + int &width, + int &height, + uint64_t &frame_id) const; + + // 只拿 frame_id,便于 physics 线程判断是否新帧 + uint64_t getPiPCameraFrameId() const; + protected: // 每次 mj_step 前,physics 线程回调控制逻辑 virtual void controlCallback(mjModel *m, mjData *d) { @@ -59,6 +81,7 @@ namespace cmvr { void printCameraState() const; private: + void renderPiP(); void initSim(); // 只创建 Simulate,不 load void physicsThreadFunc(); // 加载模型 + 物理循环 void physicsLoop(); // 真正的一步一步仿真 @@ -87,6 +110,26 @@ namespace cmvr { bool inited_ = false; mjtNum last_time_ = 0.0; + bool pip_enabled_ = false; + std::string pip_camera_name_; + int pip_camera_id_ = -1; + int pip_width_ = 320; + int pip_height_ = 240; + int pip_margin_ = 10; + bool pip_custom_pos_ = false; + int pip_left_ = 0; + int pip_bottom_ = 0; + mjvCamera pip_cam_; + mjvScene pip_scene_; + bool pip_scene_inited_ = false; + mjModel *pip_scene_model_ = nullptr; + mutable std::mutex pip_rgb_mtx_; + std::vector pip_rgb_; + std::vector pip_depth_; // 新增:z-buffer + int pip_rgb_width_ = 0; + int pip_rgb_height_ = 0; + bool pip_rgb_valid_ = false; + uint64_t pip_frame_id_ = 0; // 新增:帧序号 }; } // namespace cmvr diff --git a/cmvr-es/simulate/mujoco/mujoco_viewer/src/mujoco_viewer.cpp b/cmvr-es/simulate/mujoco/mujoco_viewer/src/mujoco_viewer.cpp index 7db5656a..fd2b05be 100644 --- a/cmvr-es/simulate/mujoco/mujoco_viewer/src/mujoco_viewer.cpp +++ b/cmvr-es/simulate/mujoco/mujoco_viewer/src/mujoco_viewer.cpp @@ -3,6 +3,7 @@ // +#include #include #include #include @@ -18,6 +19,23 @@ namespace cmvr { constexpr double kSyncMisalign = 0.1; constexpr double kSimRefreshFraction = 0.7; + constexpr int kPiPMaxGeom = 100000; + + class PiPGlfwAdapter : public mj::GlfwAdapter { + public: + explicit PiPGlfwAdapter(MuJocoViewer *owner) + : owner_(owner) {} + + void SwapBuffers() override { + if (owner_) { + owner_->renderPiP(); + } + mj::GlfwAdapter::SwapBuffers(); + } + + private: + MuJocoViewer *owner_; + }; static const char *CheckDiverged(int disableflags, const mjData *d) { @@ -76,8 +94,10 @@ namespace cmvr { mjv_defaultCamera(&cam_); mjv_defaultOption(&opt_); mjv_defaultPerturb(&pert_); + mjv_defaultCamera(&pip_cam_); + mjv_defaultScene(&pip_scene_); - auto platform_ui = std::make_unique(); + auto platform_ui = std::make_unique(this); sim_ = std::make_unique( std::move(platform_ui), &cam_, &opt_, &pert_, @@ -93,6 +113,11 @@ namespace cmvr { physics_thread_.join(); } + if (pip_scene_inited_) { + mjv_freeScene(&pip_scene_); + pip_scene_inited_ = false; + } + if (d_) mj_deleteData(d_); if (m_) mj_deleteModel(m_); } @@ -155,6 +180,117 @@ namespace cmvr { cam_.elevation = elevation; // 俯仰角(度) } + void MuJocoViewer::enablePiPCamera(const char *camera_name, + int left, + int bottom, + int width, + int height) { + pip_enabled_ = true; + pip_camera_name_ = camera_name ? camera_name : ""; + pip_camera_id_ = -1; + pip_left_ = left; + pip_bottom_ = bottom; + pip_width_ = width > 0 ? width : 320; + pip_height_ = height > 0 ? height : 240; + pip_custom_pos_ = true; + } + + void MuJocoViewer::disablePiPCamera() { + pip_enabled_ = false; + } + + + + void MuJocoViewer::renderPiP() { + if (!pip_enabled_ || !sim_ || !m_ || !d_) return; + if (pip_camera_name_.empty()) return; + + if (pip_camera_id_ < 0) { + pip_camera_id_ = mj_name2id(m_, mjOBJ_CAMERA, pip_camera_name_.c_str()); + if (pip_camera_id_ < 0) { + return; + } + } + + auto [fb_width, fb_height] = sim_->platform_ui->GetFramebufferSize(); + if (fb_width <= 0 || fb_height <= 0) return; + + int left = 0; + int bottom = 0; + int width = 0; + int height = 0; + + if (pip_custom_pos_) { + left = std::max(0, std::min(pip_left_, fb_width - 1)); + bottom = std::max(0, std::min(pip_bottom_, fb_height - 1)); + width = std::min(pip_width_, fb_width - left); + height = std::min(pip_height_, fb_height - bottom); + } else { + width = std::min(pip_width_, fb_width - 2 * pip_margin_); + height = std::min(pip_height_, fb_height - 2 * pip_margin_); + left = fb_width - pip_margin_ - width; + bottom = pip_margin_; + } + + if (width <= 0 || height <= 0) return; + + mjrRect rect; + rect.width = width; + rect.height = height; + rect.left = left; + rect.bottom = bottom; + + const std::unique_lock lock(sim_->mtx); + + if (!pip_scene_inited_ || pip_scene_model_ != m_) { + if (pip_scene_inited_) { + mjv_freeScene(&pip_scene_); + } + mjv_makeScene(m_, &pip_scene_, kPiPMaxGeom); + pip_scene_inited_ = true; + pip_scene_model_ = m_; + } + + pip_cam_.type = mjCAMERA_FIXED; + pip_cam_.fixedcamid = pip_camera_id_; + pip_cam_.trackbodyid = -1; + + mjv_updateScene(m_, d_, &opt_, &pert_, &pip_cam_, mjCAT_ALL, &pip_scene_); + mjr_render(rect, &pip_scene_, &sim_->platform_ui->mjr_context()); + + { + std::lock_guard lock(pip_rgb_mtx_); + const int w = rect.width; + const int h = rect.height; + if (w > 0 && h > 0) { + pip_rgb_.resize(static_cast(3 * w * h)); + pip_depth_.resize(static_cast(w * h)); + + // 同时读 RGB 和 depth(z-buffer 0..1) + mjr_readPixels(pip_rgb_.data(), pip_depth_.data(), + rect, &sim_->platform_ui->mjr_context()); + + // OpenGL 像素原点在左下,需要竖直翻转 RGB 和 depth + for (int r = 0; r < h / 2; ++r) { + // flip rgb row + unsigned char *top_row = pip_rgb_.data() + 3 * w * r; + unsigned char *bottom_row = pip_rgb_.data() + 3 * w * (h - 1 - r); + std::swap_ranges(top_row, top_row + 3 * w, bottom_row); + + // flip depth row + float *top_d = pip_depth_.data() + w * r; + float *bot_d = pip_depth_.data() + w * (h - 1 - r); + std::swap_ranges(top_d, top_d + w, bot_d); + } + + pip_rgb_width_ = w; + pip_rgb_height_ = h; + pip_rgb_valid_ = true; + ++pip_frame_id_; // 新帧 + } + } + } + void MuJocoViewer::physicsLoop() { using Clock = mj::Simulate::Clock; @@ -288,4 +424,26 @@ namespace cmvr { physics_thread_.join(); } } + + + uint64_t MuJocoViewer::getPiPCameraFrameId() const { + std::lock_guard lock(pip_rgb_mtx_); + return pip_frame_id_; + } + + bool MuJocoViewer::getPiPCameraRGBD(std::vector &rgb, + std::vector &depth, + int &width, + int &height, + uint64_t &frame_id) const { + std::lock_guard lock(pip_rgb_mtx_); + if (!pip_rgb_valid_ || pip_rgb_.empty()) return false; + + rgb = pip_rgb_; + depth = pip_depth_; + width = pip_rgb_width_; + height = pip_rgb_height_; + frame_id = pip_frame_id_; + return true; + } } // namespace cmvr diff --git a/dependency/x86/third_party/visp/3.7.0/bin/visp-calibrate-camera b/dependency/x86/third_party/visp/3.7.0/bin/visp-calibrate-camera new file mode 100755 index 0000000000000000000000000000000000000000..ca509c45c6d03a89d2fa7172fedf59d06fedec75 GIT binary patch literal 126576 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 WVforMPA^ULpMC0{Jd6mU)8zk0bC7@l literal 0 HcmV?d00001 diff --git a/dependency/x86/third_party/visp/3.7.0/bin/visp-compute-apriltag-poses b/dependency/x86/third_party/visp/3.7.0/bin/visp-compute-apriltag-poses new file mode 100755 index 0000000000000000000000000000000000000000..bdf454a1e8dd4897a7e268bb47191ea5cc195507 GIT binary patch literal 59632 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�sb*?8o$c^ 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 auWDY~S7@)akG}k-?%}R<)DStRt^WbDUGwVz literal 0 HcmV?d00001 diff --git a/dependency/x86/third_party/visp/3.7.0/bin/visp-compute-eye-in-hand-calibration b/dependency/x86/third_party/visp/3.7.0/bin/visp-compute-eye-in-hand-calibration new file mode 100755 index 0000000000000000000000000000000000000000..a56a3b0f40e6984e58221932e8fadfed9c3c5908 GIT binary patch literal 82240 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

QNB4}eD5Ur$b9jYS=5)O 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 LhEXo$xE22g`q0Nb literal 0 HcmV?d00001 diff --git a/dependency/x86/third_party/visp/3.7.0/bin/visp-save-rs-dataset b/dependency/x86/third_party/visp/3.7.0/bin/visp-save-rs-dataset new file mode 100755 index 0000000000000000000000000000000000000000..59dbe024174682fde9e02095065ced3a8ee74517 GIT binary patch literal 203432 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 K*b3Mlwf_Z2yZ?Fs literal 0 HcmV?d00001 diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/visp_modules.h b/dependency/x86/third_party/visp/3.7.0/include/visp/visp_modules.h new file mode 100644 index 00000000..e4140530 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/visp_modules.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __visp_modules_gen_h_ +#define __visp_modules_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vp1394CMUGrabber.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vp1394CMUGrabber.h new file mode 100644 index 00000000..550b25e9 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vp1394CMUGrabber.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vp1394CMUGrabber_gen_h_ +#define __vp1394CMUGrabber_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vp1394TwoGrabber.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vp1394TwoGrabber.h new file mode 100644 index 00000000..355d92b9 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vp1394TwoGrabber.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vp1394TwoGrabber_gen_h_ +#define __vp1394TwoGrabber_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpAR.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpAR.h new file mode 100644 index 00000000..6dab355f --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpAR.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpAR_gen_h_ +#define __vpAR_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpAROgre.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpAROgre.h new file mode 100644 index 00000000..5f4e06cc --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpAROgre.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpAROgre_gen_h_ +#define __vpAROgre_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpAdaptiveGain.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpAdaptiveGain.h new file mode 100644 index 00000000..b5f76063 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpAdaptiveGain.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpAdaptiveGain_gen_h_ +#define __vpAdaptiveGain_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpAfma6.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpAfma6.h new file mode 100644 index 00000000..f2e7b76b --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpAfma6.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpAfma6_gen_h_ +#define __vpAfma6_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpArray2D.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpArray2D.h new file mode 100644 index 00000000..30b6cedf --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpArray2D.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpArray2D_gen_h_ +#define __vpArray2D_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpBSpline.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpBSpline.h new file mode 100644 index 00000000..87d0d36a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpBSpline.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpBSpline_gen_h_ +#define __vpBSpline_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpBasicFeature.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpBasicFeature.h new file mode 100644 index 00000000..8cf68975 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpBasicFeature.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpBasicFeature_gen_h_ +#define __vpBasicFeature_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpBasicKeyPoint.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpBasicKeyPoint.h new file mode 100644 index 00000000..5be6700a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpBasicKeyPoint.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpBasicKeyPoint_gen_h_ +#define __vpBasicKeyPoint_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpBiclops.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpBiclops.h new file mode 100644 index 00000000..fa1a65a8 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpBiclops.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpBiclops_gen_h_ +#define __vpBiclops_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpCPUFeatures.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpCPUFeatures.h new file mode 100644 index 00000000..4ba89b9f --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpCPUFeatures.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpCPUFeatures_gen_h_ +#define __vpCPUFeatures_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpCalibration.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpCalibration.h new file mode 100644 index 00000000..52210c96 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpCalibration.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpCalibration_gen_h_ +#define __vpCalibration_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpCalibrationException.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpCalibrationException.h new file mode 100644 index 00000000..6790d8fe --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpCalibrationException.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpCalibrationException_gen_h_ +#define __vpCalibrationException_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpCameraParameters.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpCameraParameters.h new file mode 100644 index 00000000..6d61f54a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpCameraParameters.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpCameraParameters_gen_h_ +#define __vpCameraParameters_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpCannyEdgeDetection.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpCannyEdgeDetection.h new file mode 100644 index 00000000..1fdd46aa --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpCannyEdgeDetection.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpCannyEdgeDetection_gen_h_ +#define __vpCannyEdgeDetection_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpCircle.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpCircle.h new file mode 100644 index 00000000..fb1e3d74 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpCircle.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpCircle_gen_h_ +#define __vpCircle_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpCircleHoughTransform.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpCircleHoughTransform.h new file mode 100644 index 00000000..0b7ce853 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpCircleHoughTransform.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpCircleHoughTransform_gen_h_ +#define __vpCircleHoughTransform_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpClient.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpClient.h new file mode 100644 index 00000000..81880dd5 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpClient.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpClient_gen_h_ +#define __vpClient_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpColVector.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpColVector.h new file mode 100644 index 00000000..3257e9cf --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpColVector.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpColVector_gen_h_ +#define __vpColVector_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpColor.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpColor.h new file mode 100644 index 00000000..3f1cd1d9 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpColor.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpColor_gen_h_ +#define __vpColor_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpColorBlindFriendlyPalette.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpColorBlindFriendlyPalette.h new file mode 100644 index 00000000..06b3cb01 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpColorBlindFriendlyPalette.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpColorBlindFriendlyPalette_gen_h_ +#define __vpColorBlindFriendlyPalette_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpColorDepthConversion.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpColorDepthConversion.h new file mode 100644 index 00000000..cfa1cb16 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpColorDepthConversion.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpColorDepthConversion_gen_h_ +#define __vpColorDepthConversion_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpColorGetter.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpColorGetter.h new file mode 100644 index 00000000..de887613 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpColorGetter.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpColorGetter_gen_h_ +#define __vpColorGetter_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpColormap.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpColormap.h new file mode 100644 index 00000000..f0cab453 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpColormap.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpColormap_gen_h_ +#define __vpColormap_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpComedi.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpComedi.h new file mode 100644 index 00000000..c074cde7 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpComedi.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpComedi_gen_h_ +#define __vpComedi_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpConfig.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpConfig.h new file mode 100644 index 00000000..5a84d452 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpConfig.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpConfig_gen_h_ +#define __vpConfig_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpContours.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpContours.h new file mode 100644 index 00000000..027455f1 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpContours.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpContours_gen_h_ +#define __vpContours_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpConvert.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpConvert.h new file mode 100644 index 00000000..0d2e2880 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpConvert.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpConvert_gen_h_ +#define __vpConvert_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpCylinder.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpCylinder.h new file mode 100644 index 00000000..ea5a5b92 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpCylinder.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpCylinder_gen_h_ +#define __vpCylinder_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpD3DRenderer.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpD3DRenderer.h new file mode 100644 index 00000000..f20e5ec8 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpD3DRenderer.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpD3DRenderer_gen_h_ +#define __vpD3DRenderer_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpDebug.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDebug.h new file mode 100644 index 00000000..86c18f45 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDebug.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpDebug_gen_h_ +#define __vpDebug_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpDetectorAprilTag.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDetectorAprilTag.h new file mode 100644 index 00000000..d1d8adb2 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDetectorAprilTag.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpDetectorAprilTag_gen_h_ +#define __vpDetectorAprilTag_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpDetectorBase.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDetectorBase.h new file mode 100644 index 00000000..c64b12c0 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDetectorBase.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpDetectorBase_gen_h_ +#define __vpDetectorBase_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpDetectorDNNOpenCV.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDetectorDNNOpenCV.h new file mode 100644 index 00000000..c2c5a234 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDetectorDNNOpenCV.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpDetectorDNNOpenCV_gen_h_ +#define __vpDetectorDNNOpenCV_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpDetectorDataMatrixCode.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDetectorDataMatrixCode.h new file mode 100644 index 00000000..924cf1fd --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDetectorDataMatrixCode.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpDetectorDataMatrixCode_gen_h_ +#define __vpDetectorDataMatrixCode_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpDetectorFace.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDetectorFace.h new file mode 100644 index 00000000..306a2a4b --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDetectorFace.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpDetectorFace_gen_h_ +#define __vpDetectorFace_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpDetectorQRCode.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDetectorQRCode.h new file mode 100644 index 00000000..5f748989 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDetectorQRCode.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpDetectorQRCode_gen_h_ +#define __vpDetectorQRCode_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpDirectShowDevice.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDirectShowDevice.h new file mode 100644 index 00000000..f554ed66 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDirectShowDevice.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpDirectShowDevice_gen_h_ +#define __vpDirectShowDevice_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpDirectShowGrabber.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDirectShowGrabber.h new file mode 100644 index 00000000..cf472f56 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDirectShowGrabber.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpDirectShowGrabber_gen_h_ +#define __vpDirectShowGrabber_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpDirectShowGrabberImpl.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDirectShowGrabberImpl.h new file mode 100644 index 00000000..7121957f --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDirectShowGrabberImpl.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpDirectShowGrabberImpl_gen_h_ +#define __vpDirectShowGrabberImpl_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpDirectShowSampleGrabberI.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDirectShowSampleGrabberI.h new file mode 100644 index 00000000..c2dc3713 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDirectShowSampleGrabberI.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpDirectShowSampleGrabberI_gen_h_ +#define __vpDirectShowSampleGrabberI_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpDiskGrabber.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDiskGrabber.h new file mode 100644 index 00000000..68161a24 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDiskGrabber.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpDiskGrabber_gen_h_ +#define __vpDiskGrabber_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplay.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplay.h new file mode 100644 index 00000000..5dd4bc25 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplay.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpDisplay_gen_h_ +#define __vpDisplay_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayD3D.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayD3D.h new file mode 100644 index 00000000..3220450d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayD3D.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpDisplayD3D_gen_h_ +#define __vpDisplayD3D_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayException.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayException.h new file mode 100644 index 00000000..950519c5 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayException.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpDisplayException_gen_h_ +#define __vpDisplayException_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayFactory.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayFactory.h new file mode 100644 index 00000000..aacdd9ce --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayFactory.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpDisplayFactory_gen_h_ +#define __vpDisplayFactory_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayGDI.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayGDI.h new file mode 100644 index 00000000..579e52a3 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayGDI.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpDisplayGDI_gen_h_ +#define __vpDisplayGDI_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayGTK.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayGTK.h new file mode 100644 index 00000000..96e58a69 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayGTK.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpDisplayGTK_gen_h_ +#define __vpDisplayGTK_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayOpenCV.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayOpenCV.h new file mode 100644 index 00000000..637e57a5 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayOpenCV.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpDisplayOpenCV_gen_h_ +#define __vpDisplayOpenCV_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayPCL.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayPCL.h new file mode 100644 index 00000000..2b226c67 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayPCL.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpDisplayPCL_gen_h_ +#define __vpDisplayPCL_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayWin32.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayWin32.h new file mode 100644 index 00000000..4083b66a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayWin32.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpDisplayWin32_gen_h_ +#define __vpDisplayWin32_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayX.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayX.h new file mode 100644 index 00000000..511569a5 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDisplayX.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpDisplayX_gen_h_ +#define __vpDisplayX_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpDot.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDot.h new file mode 100644 index 00000000..3b5a43aa --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDot.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpDot_gen_h_ +#define __vpDot_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpDot2.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDot2.h new file mode 100644 index 00000000..4ae06ea6 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpDot2.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpDot2_gen_h_ +#define __vpDot2_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpEigenConversion.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpEigenConversion.h new file mode 100644 index 00000000..d8b91243 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpEigenConversion.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpEigenConversion_gen_h_ +#define __vpEigenConversion_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpEndian.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpEndian.h new file mode 100644 index 00000000..d3bbb13e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpEndian.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpEndian_gen_h_ +#define __vpEndian_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpException.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpException.h new file mode 100644 index 00000000..4d234ab8 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpException.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpException_gen_h_ +#define __vpException_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpExponentialMap.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpExponentialMap.h new file mode 100644 index 00000000..66ea423d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpExponentialMap.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpExponentialMap_gen_h_ +#define __vpExponentialMap_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureBuilder.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureBuilder.h new file mode 100644 index 00000000..a356d6b1 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureBuilder.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFeatureBuilder_gen_h_ +#define __vpFeatureBuilder_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureDepth.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureDepth.h new file mode 100644 index 00000000..1de9d2ea --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureDepth.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFeatureDepth_gen_h_ +#define __vpFeatureDepth_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureDisplay.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureDisplay.h new file mode 100644 index 00000000..73317e25 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureDisplay.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFeatureDisplay_gen_h_ +#define __vpFeatureDisplay_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureEllipse.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureEllipse.h new file mode 100644 index 00000000..326093a3 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureEllipse.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFeatureEllipse_gen_h_ +#define __vpFeatureEllipse_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureException.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureException.h new file mode 100644 index 00000000..2e5ab1a4 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureException.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFeatureException_gen_h_ +#define __vpFeatureException_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureLine.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureLine.h new file mode 100644 index 00000000..58bba33d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureLine.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFeatureLine_gen_h_ +#define __vpFeatureLine_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureLuminance.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureLuminance.h new file mode 100644 index 00000000..e53642f8 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureLuminance.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFeatureLuminance_gen_h_ +#define __vpFeatureLuminance_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureLuminanceMapping.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureLuminanceMapping.h new file mode 100644 index 00000000..2532491e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureLuminanceMapping.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFeatureLuminanceMapping_gen_h_ +#define __vpFeatureLuminanceMapping_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMoment.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMoment.h new file mode 100644 index 00000000..12ff728d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMoment.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFeatureMoment_gen_h_ +#define __vpFeatureMoment_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentAlpha.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentAlpha.h new file mode 100644 index 00000000..659832c2 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentAlpha.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFeatureMomentAlpha_gen_h_ +#define __vpFeatureMomentAlpha_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentArea.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentArea.h new file mode 100644 index 00000000..cc1c0199 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentArea.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFeatureMomentArea_gen_h_ +#define __vpFeatureMomentArea_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentAreaNormalized.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentAreaNormalized.h new file mode 100644 index 00000000..abf95c48 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentAreaNormalized.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFeatureMomentAreaNormalized_gen_h_ +#define __vpFeatureMomentAreaNormalized_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentBasic.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentBasic.h new file mode 100644 index 00000000..d21e052d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentBasic.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFeatureMomentBasic_gen_h_ +#define __vpFeatureMomentBasic_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentCInvariant.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentCInvariant.h new file mode 100644 index 00000000..2f2cfa0d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentCInvariant.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFeatureMomentCInvariant_gen_h_ +#define __vpFeatureMomentCInvariant_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentCentered.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentCentered.h new file mode 100644 index 00000000..add7d386 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentCentered.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFeatureMomentCentered_gen_h_ +#define __vpFeatureMomentCentered_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentCommon.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentCommon.h new file mode 100644 index 00000000..4dbc3750 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentCommon.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFeatureMomentCommon_gen_h_ +#define __vpFeatureMomentCommon_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentDatabase.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentDatabase.h new file mode 100644 index 00000000..630b27c4 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentDatabase.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFeatureMomentDatabase_gen_h_ +#define __vpFeatureMomentDatabase_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentGravityCenter.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentGravityCenter.h new file mode 100644 index 00000000..77f0a914 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentGravityCenter.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFeatureMomentGravityCenter_gen_h_ +#define __vpFeatureMomentGravityCenter_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentGravityCenterNormalized.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentGravityCenterNormalized.h new file mode 100644 index 00000000..d666521b --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureMomentGravityCenterNormalized.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFeatureMomentGravityCenterNormalized_gen_h_ +#define __vpFeatureMomentGravityCenterNormalized_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeaturePoint.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeaturePoint.h new file mode 100644 index 00000000..82b8a4a8 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeaturePoint.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFeaturePoint_gen_h_ +#define __vpFeaturePoint_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeaturePoint3D.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeaturePoint3D.h new file mode 100644 index 00000000..b96809e5 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeaturePoint3D.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFeaturePoint3D_gen_h_ +#define __vpFeaturePoint3D_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeaturePointPolar.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeaturePointPolar.h new file mode 100644 index 00000000..938e10ab --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeaturePointPolar.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFeaturePointPolar_gen_h_ +#define __vpFeaturePointPolar_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureSegment.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureSegment.h new file mode 100644 index 00000000..68b7675d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureSegment.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFeatureSegment_gen_h_ +#define __vpFeatureSegment_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureThetaU.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureThetaU.h new file mode 100644 index 00000000..aef97b16 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureThetaU.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFeatureThetaU_gen_h_ +#define __vpFeatureThetaU_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureTranslation.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureTranslation.h new file mode 100644 index 00000000..0587200e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureTranslation.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFeatureTranslation_gen_h_ +#define __vpFeatureTranslation_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureVanishingPoint.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureVanishingPoint.h new file mode 100644 index 00000000..a2513543 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFeatureVanishingPoint.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFeatureVanishingPoint_gen_h_ +#define __vpFeatureVanishingPoint_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFlyCaptureGrabber.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFlyCaptureGrabber.h new file mode 100644 index 00000000..c255b598 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFlyCaptureGrabber.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFlyCaptureGrabber_gen_h_ +#define __vpFlyCaptureGrabber_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFont.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFont.h new file mode 100644 index 00000000..e257d345 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFont.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFont_gen_h_ +#define __vpFont_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpForceTorqueAtiNetFTSensor.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpForceTorqueAtiNetFTSensor.h new file mode 100644 index 00000000..14b0a3c4 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpForceTorqueAtiNetFTSensor.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpForceTorqueAtiNetFTSensor_gen_h_ +#define __vpForceTorqueAtiNetFTSensor_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpForceTorqueAtiSensor.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpForceTorqueAtiSensor.h new file mode 100644 index 00000000..5fdabddf --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpForceTorqueAtiSensor.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpForceTorqueAtiSensor_gen_h_ +#define __vpForceTorqueAtiSensor_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpForceTorqueIitSensor.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpForceTorqueIitSensor.h new file mode 100644 index 00000000..af9a8193 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpForceTorqueIitSensor.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpForceTorqueIitSensor_gen_h_ +#define __vpForceTorqueIitSensor_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpForceTwistMatrix.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpForceTwistMatrix.h new file mode 100644 index 00000000..8ce0f94b --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpForceTwistMatrix.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpForceTwistMatrix_gen_h_ +#define __vpForceTwistMatrix_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpForwardProjection.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpForwardProjection.h new file mode 100644 index 00000000..d373f300 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpForwardProjection.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpForwardProjection_gen_h_ +#define __vpForwardProjection_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFrameGrabber.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFrameGrabber.h new file mode 100644 index 00000000..d3adbaa3 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFrameGrabber.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFrameGrabber_gen_h_ +#define __vpFrameGrabber_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpFrameGrabberException.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFrameGrabberException.h new file mode 100644 index 00000000..c952f03f --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpFrameGrabberException.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpFrameGrabberException_gen_h_ +#define __vpFrameGrabberException_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpGDIRenderer.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpGDIRenderer.h new file mode 100644 index 00000000..20742d4e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpGDIRenderer.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpGDIRenderer_gen_h_ +#define __vpGDIRenderer_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpGEMM.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpGEMM.h new file mode 100644 index 00000000..91a6a6cb --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpGEMM.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpGEMM_gen_h_ +#define __vpGEMM_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpGaussRand.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpGaussRand.h new file mode 100644 index 00000000..6dcead65 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpGaussRand.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpGaussRand_gen_h_ +#define __vpGaussRand_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpGaussianFilter.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpGaussianFilter.h new file mode 100644 index 00000000..e6fe19e1 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpGaussianFilter.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpGaussianFilter_gen_h_ +#define __vpGaussianFilter_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpGenericFeature.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpGenericFeature.h new file mode 100644 index 00000000..9e05c15d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpGenericFeature.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpGenericFeature_gen_h_ +#define __vpGenericFeature_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpHSV.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpHSV.h new file mode 100644 index 00000000..a95dd81a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpHSV.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpHSV_gen_h_ +#define __vpHSV_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpHandEyeCalibration.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpHandEyeCalibration.h new file mode 100644 index 00000000..aa7a7d45 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpHandEyeCalibration.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpHandEyeCalibration_gen_h_ +#define __vpHandEyeCalibration_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpHinkley.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpHinkley.h new file mode 100644 index 00000000..5cb1ccdf --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpHinkley.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpHinkley_gen_h_ +#define __vpHinkley_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpHistogram.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpHistogram.h new file mode 100644 index 00000000..7263be29 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpHistogram.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpHistogram_gen_h_ +#define __vpHistogram_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpHistogramPeak.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpHistogramPeak.h new file mode 100644 index 00000000..9a3a7e20 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpHistogramPeak.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpHistogramPeak_gen_h_ +#define __vpHistogramPeak_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpHistogramValey.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpHistogramValey.h new file mode 100644 index 00000000..481391e1 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpHistogramValey.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpHistogramValey_gen_h_ +#define __vpHistogramValey_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpHomogeneousMatrix.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpHomogeneousMatrix.h new file mode 100644 index 00000000..57ddad09 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpHomogeneousMatrix.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpHomogeneousMatrix_gen_h_ +#define __vpHomogeneousMatrix_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpHomography.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpHomography.h new file mode 100644 index 00000000..c4655b75 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpHomography.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpHomography_gen_h_ +#define __vpHomography_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpImage.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImage.h new file mode 100644 index 00000000..e3213911 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImage.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpImage_gen_h_ +#define __vpImage_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageCircle.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageCircle.h new file mode 100644 index 00000000..94c9e358 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageCircle.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpImageCircle_gen_h_ +#define __vpImageCircle_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageConvert.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageConvert.h new file mode 100644 index 00000000..c5253561 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageConvert.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpImageConvert_gen_h_ +#define __vpImageConvert_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageDraw.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageDraw.h new file mode 100644 index 00000000..64b44cfe --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageDraw.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpImageDraw_gen_h_ +#define __vpImageDraw_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageException.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageException.h new file mode 100644 index 00000000..b34c64e5 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageException.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpImageException_gen_h_ +#define __vpImageException_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageFilter.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageFilter.h new file mode 100644 index 00000000..46c6df56 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageFilter.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpImageFilter_gen_h_ +#define __vpImageFilter_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageIo.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageIo.h new file mode 100644 index 00000000..f061bb9f --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageIo.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpImageIo_gen_h_ +#define __vpImageIo_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageMorphology.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageMorphology.h new file mode 100644 index 00000000..d69c4e03 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageMorphology.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpImageMorphology_gen_h_ +#define __vpImageMorphology_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpImagePoint.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImagePoint.h new file mode 100644 index 00000000..1233f3be --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImagePoint.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpImagePoint_gen_h_ +#define __vpImagePoint_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageQueue.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageQueue.h new file mode 100644 index 00000000..82876c35 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageQueue.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpImageQueue_gen_h_ +#define __vpImageQueue_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageSimulator.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageSimulator.h new file mode 100644 index 00000000..252c1f7d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageSimulator.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpImageSimulator_gen_h_ +#define __vpImageSimulator_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageStorageWorker.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageStorageWorker.h new file mode 100644 index 00000000..ca7e94a2 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageStorageWorker.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpImageStorageWorker_gen_h_ +#define __vpImageStorageWorker_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageTools.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageTools.h new file mode 100644 index 00000000..60f4ff5d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageTools.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpImageTools_gen_h_ +#define __vpImageTools_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageTools_warp.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageTools_warp.h new file mode 100644 index 00000000..d3cb3e1e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImageTools_warp.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpImageTools_warp_gen_h_ +#define __vpImageTools_warp_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpImage_getters.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImage_getters.h new file mode 100644 index 00000000..e0a83c75 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImage_getters.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpImage_getters_gen_h_ +#define __vpImage_getters_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpImage_lut.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImage_lut.h new file mode 100644 index 00000000..e539b86a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImage_lut.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpImage_lut_gen_h_ +#define __vpImage_lut_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpImage_operators.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImage_operators.h new file mode 100644 index 00000000..1bf60144 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImage_operators.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpImage_operators_gen_h_ +#define __vpImage_operators_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpImgproc.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImgproc.h new file mode 100644 index 00000000..214bb76e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpImgproc.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpImgproc_gen_h_ +#define __vpImgproc_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpIoException.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpIoException.h new file mode 100644 index 00000000..a909ae3f --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpIoException.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpIoException_gen_h_ +#define __vpIoException_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpIoTools.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpIoTools.h new file mode 100644 index 00000000..d9769643 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpIoTools.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpIoTools_gen_h_ +#define __vpIoTools_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpJsonArgumentParser.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpJsonArgumentParser.h new file mode 100644 index 00000000..370a726f --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpJsonArgumentParser.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpJsonArgumentParser_gen_h_ +#define __vpJsonArgumentParser_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpJsonParsing.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpJsonParsing.h new file mode 100644 index 00000000..4c8e6ed6 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpJsonParsing.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpJsonParsing_gen_h_ +#define __vpJsonParsing_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpKalmanFilter.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpKalmanFilter.h new file mode 100644 index 00000000..0332d6d6 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpKalmanFilter.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpKalmanFilter_gen_h_ +#define __vpKalmanFilter_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpKeyPoint.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpKeyPoint.h new file mode 100644 index 00000000..6c85cf2e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpKeyPoint.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpKeyPoint_gen_h_ +#define __vpKeyPoint_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpKeyboard.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpKeyboard.h new file mode 100644 index 00000000..ffbef082 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpKeyboard.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpKeyboard_gen_h_ +#define __vpKeyboard_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpKinect.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpKinect.h new file mode 100644 index 00000000..7a1644d1 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpKinect.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpKinect_gen_h_ +#define __vpKinect_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpKltOpencv.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpKltOpencv.h new file mode 100644 index 00000000..7bc995d3 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpKltOpencv.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpKltOpencv_gen_h_ +#define __vpKltOpencv_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpLaserScan.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpLaserScan.h new file mode 100644 index 00000000..af415a50 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpLaserScan.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpLaserScan_gen_h_ +#define __vpLaserScan_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpLaserScanner.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpLaserScanner.h new file mode 100644 index 00000000..05684ef1 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpLaserScanner.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpLaserScanner_gen_h_ +#define __vpLaserScanner_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpLinProg.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpLinProg.h new file mode 100644 index 00000000..0b0ccd31 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpLinProg.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpLinProg_gen_h_ +#define __vpLinProg_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpLine.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpLine.h new file mode 100644 index 00000000..a1949ceb --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpLine.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpLine_gen_h_ +#define __vpLine_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpLinearKalmanFilterInstantiation.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpLinearKalmanFilterInstantiation.h new file mode 100644 index 00000000..1e79cf65 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpLinearKalmanFilterInstantiation.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpLinearKalmanFilterInstantiation_gen_h_ +#define __vpLinearKalmanFilterInstantiation_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpList.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpList.h new file mode 100644 index 00000000..2390661c --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpList.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpList_gen_h_ +#define __vpList_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMath.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMath.h new file mode 100644 index 00000000..543f2f26 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMath.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMath_gen_h_ +#define __vpMath_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMatrix.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMatrix.h new file mode 100644 index 00000000..f8ae2f09 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMatrix.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMatrix_gen_h_ +#define __vpMatrix_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMatrixException.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMatrixException.h new file mode 100644 index 00000000..61542a12 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMatrixException.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMatrixException_gen_h_ +#define __vpMatrixException_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbDepthDenseTracker.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbDepthDenseTracker.h new file mode 100644 index 00000000..ea5e9866 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbDepthDenseTracker.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMbDepthDenseTracker_gen_h_ +#define __vpMbDepthDenseTracker_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbDepthNormalTracker.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbDepthNormalTracker.h new file mode 100644 index 00000000..4dde9d87 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbDepthNormalTracker.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMbDepthNormalTracker_gen_h_ +#define __vpMbDepthNormalTracker_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbEdgeKltTracker.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbEdgeKltTracker.h new file mode 100644 index 00000000..7e710d28 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbEdgeKltTracker.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMbEdgeKltTracker_gen_h_ +#define __vpMbEdgeKltTracker_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbEdgeTracker.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbEdgeTracker.h new file mode 100644 index 00000000..fd192d4f --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbEdgeTracker.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMbEdgeTracker_gen_h_ +#define __vpMbEdgeTracker_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbGenericTracker.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbGenericTracker.h new file mode 100644 index 00000000..fd924508 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbGenericTracker.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMbGenericTracker_gen_h_ +#define __vpMbGenericTracker_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbHiddenFaces.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbHiddenFaces.h new file mode 100644 index 00000000..a5e4a289 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbHiddenFaces.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMbHiddenFaces_gen_h_ +#define __vpMbHiddenFaces_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbKltTracker.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbKltTracker.h new file mode 100644 index 00000000..226204cb --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbKltTracker.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMbKltTracker_gen_h_ +#define __vpMbKltTracker_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbScanLine.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbScanLine.h new file mode 100644 index 00000000..8ee6bb9e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbScanLine.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMbScanLine_gen_h_ +#define __vpMbScanLine_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbTracker.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbTracker.h new file mode 100644 index 00000000..63634ff5 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbTracker.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMbTracker_gen_h_ +#define __vpMbTracker_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtDistanceCircle.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtDistanceCircle.h new file mode 100644 index 00000000..aa1a52b7 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtDistanceCircle.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMbtDistanceCircle_gen_h_ +#define __vpMbtDistanceCircle_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtDistanceCylinder.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtDistanceCylinder.h new file mode 100644 index 00000000..db364462 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtDistanceCylinder.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMbtDistanceCylinder_gen_h_ +#define __vpMbtDistanceCylinder_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtDistanceKltCylinder.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtDistanceKltCylinder.h new file mode 100644 index 00000000..38cdf40b --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtDistanceKltCylinder.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMbtDistanceKltCylinder_gen_h_ +#define __vpMbtDistanceKltCylinder_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtDistanceKltPoints.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtDistanceKltPoints.h new file mode 100644 index 00000000..252c7b07 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtDistanceKltPoints.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMbtDistanceKltPoints_gen_h_ +#define __vpMbtDistanceKltPoints_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtDistanceLine.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtDistanceLine.h new file mode 100644 index 00000000..bc8c6906 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtDistanceLine.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMbtDistanceLine_gen_h_ +#define __vpMbtDistanceLine_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtFaceDepthDense.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtFaceDepthDense.h new file mode 100644 index 00000000..58864f69 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtFaceDepthDense.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMbtFaceDepthDense_gen_h_ +#define __vpMbtFaceDepthDense_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtFaceDepthNormal.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtFaceDepthNormal.h new file mode 100644 index 00000000..126656da --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtFaceDepthNormal.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMbtFaceDepthNormal_gen_h_ +#define __vpMbtFaceDepthNormal_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtMeEllipse.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtMeEllipse.h new file mode 100644 index 00000000..01adb3d9 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtMeEllipse.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMbtMeEllipse_gen_h_ +#define __vpMbtMeEllipse_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtMeLine.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtMeLine.h new file mode 100644 index 00000000..a6bd336f --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtMeLine.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMbtMeLine_gen_h_ +#define __vpMbtMeLine_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtPolygon.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtPolygon.h new file mode 100644 index 00000000..a7805a80 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtPolygon.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMbtPolygon_gen_h_ +#define __vpMbtPolygon_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtTukeyEstimator.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtTukeyEstimator.h new file mode 100644 index 00000000..315370e5 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtTukeyEstimator.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMbtTukeyEstimator_gen_h_ +#define __vpMbtTukeyEstimator_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtXmlGenericParser.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtXmlGenericParser.h new file mode 100644 index 00000000..4f07fa14 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMbtXmlGenericParser.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMbtXmlGenericParser_gen_h_ +#define __vpMbtXmlGenericParser_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMe.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMe.h new file mode 100644 index 00000000..a94513c2 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMe.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMe_gen_h_ +#define __vpMe_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMeEllipse.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMeEllipse.h new file mode 100644 index 00000000..edb0ed61 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMeEllipse.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMeEllipse_gen_h_ +#define __vpMeEllipse_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMeLine.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMeLine.h new file mode 100644 index 00000000..c50c1bcb --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMeLine.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMeLine_gen_h_ +#define __vpMeLine_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMeNurbs.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMeNurbs.h new file mode 100644 index 00000000..4d3724ba --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMeNurbs.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMeNurbs_gen_h_ +#define __vpMeNurbs_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMeSite.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMeSite.h new file mode 100644 index 00000000..7d002a21 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMeSite.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMeSite_gen_h_ +#define __vpMeSite_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMeTracker.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMeTracker.h new file mode 100644 index 00000000..260f8572 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMeTracker.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMeTracker_gen_h_ +#define __vpMeTracker_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMegaPose.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMegaPose.h new file mode 100644 index 00000000..67f0f5cb --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMegaPose.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMegaPose_gen_h_ +#define __vpMegaPose_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMegaPoseTracker.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMegaPoseTracker.h new file mode 100644 index 00000000..e6ea89a5 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMegaPoseTracker.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMegaPoseTracker_gen_h_ +#define __vpMegaPoseTracker_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMeterPixelConversion.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMeterPixelConversion.h new file mode 100644 index 00000000..a292e99b --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMeterPixelConversion.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMeterPixelConversion_gen_h_ +#define __vpMeterPixelConversion_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMocap.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMocap.h new file mode 100644 index 00000000..5bd5f41b --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMocap.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMocap_gen_h_ +#define __vpMocap_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMocapQualisys.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMocapQualisys.h new file mode 100644 index 00000000..f9bc08c2 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMocapQualisys.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMocapQualisys_gen_h_ +#define __vpMocapQualisys_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMocapVicon.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMocapVicon.h new file mode 100644 index 00000000..bdbaa722 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMocapVicon.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMocapVicon_gen_h_ +#define __vpMocapVicon_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMoment.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMoment.h new file mode 100644 index 00000000..442e88ad --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMoment.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMoment_gen_h_ +#define __vpMoment_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentAlpha.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentAlpha.h new file mode 100644 index 00000000..1637a574 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentAlpha.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMomentAlpha_gen_h_ +#define __vpMomentAlpha_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentArea.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentArea.h new file mode 100644 index 00000000..0cc13fb1 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentArea.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMomentArea_gen_h_ +#define __vpMomentArea_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentAreaNormalized.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentAreaNormalized.h new file mode 100644 index 00000000..36fd7a25 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentAreaNormalized.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMomentAreaNormalized_gen_h_ +#define __vpMomentAreaNormalized_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentBasic.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentBasic.h new file mode 100644 index 00000000..e3df3df6 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentBasic.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMomentBasic_gen_h_ +#define __vpMomentBasic_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentCInvariant.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentCInvariant.h new file mode 100644 index 00000000..20f50075 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentCInvariant.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMomentCInvariant_gen_h_ +#define __vpMomentCInvariant_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentCentered.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentCentered.h new file mode 100644 index 00000000..5fa2bbde --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentCentered.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMomentCentered_gen_h_ +#define __vpMomentCentered_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentCommon.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentCommon.h new file mode 100644 index 00000000..840aedbf --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentCommon.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMomentCommon_gen_h_ +#define __vpMomentCommon_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentDatabase.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentDatabase.h new file mode 100644 index 00000000..797fd0b9 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentDatabase.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMomentDatabase_gen_h_ +#define __vpMomentDatabase_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentGravityCenter.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentGravityCenter.h new file mode 100644 index 00000000..a1b23224 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentGravityCenter.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMomentGravityCenter_gen_h_ +#define __vpMomentGravityCenter_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentGravityCenterNormalized.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentGravityCenterNormalized.h new file mode 100644 index 00000000..b175fd5f --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentGravityCenterNormalized.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMomentGravityCenterNormalized_gen_h_ +#define __vpMomentGravityCenterNormalized_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentObject.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentObject.h new file mode 100644 index 00000000..7336b487 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMomentObject.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMomentObject_gen_h_ +#define __vpMomentObject_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMouseButton.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMouseButton.h new file mode 100644 index 00000000..fe0ac1b5 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMouseButton.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMouseButton_gen_h_ +#define __vpMouseButton_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMunkres.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMunkres.h new file mode 100644 index 00000000..08e99410 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMunkres.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMunkres_gen_h_ +#define __vpMunkres_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpMutex.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMutex.h new file mode 100644 index 00000000..0ff64da7 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpMutex.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpMutex_gen_h_ +#define __vpMutex_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpNetwork.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpNetwork.h new file mode 100644 index 00000000..f52abe40 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpNetwork.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpNetwork_gen_h_ +#define __vpNetwork_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpNoise.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpNoise.h new file mode 100644 index 00000000..b5cea597 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpNoise.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpNoise_gen_h_ +#define __vpNoise_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpNullptrEmulated.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpNullptrEmulated.h new file mode 100644 index 00000000..bad7e27b --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpNullptrEmulated.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpNullptrEmulated_gen_h_ +#define __vpNullptrEmulated_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpNurbs.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpNurbs.h new file mode 100644 index 00000000..cb513549 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpNurbs.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpNurbs_gen_h_ +#define __vpNurbs_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpOccipitalStructure.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpOccipitalStructure.h new file mode 100644 index 00000000..b387ddbf --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpOccipitalStructure.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpOccipitalStructure_gen_h_ +#define __vpOccipitalStructure_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DBaseRenderer.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DBaseRenderer.h new file mode 100644 index 00000000..fc4a9a77 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DBaseRenderer.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPanda3DBaseRenderer_gen_h_ +#define __vpPanda3DBaseRenderer_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DCommonFilters.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DCommonFilters.h new file mode 100644 index 00000000..7909c983 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DCommonFilters.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPanda3DCommonFilters_gen_h_ +#define __vpPanda3DCommonFilters_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DFrameworkManager.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DFrameworkManager.h new file mode 100644 index 00000000..d909010a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DFrameworkManager.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPanda3DFrameworkManager_gen_h_ +#define __vpPanda3DFrameworkManager_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DGeometryRenderer.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DGeometryRenderer.h new file mode 100644 index 00000000..09f7fc8a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DGeometryRenderer.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPanda3DGeometryRenderer_gen_h_ +#define __vpPanda3DGeometryRenderer_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DLight.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DLight.h new file mode 100644 index 00000000..d0ee0d5d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DLight.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPanda3DLight_gen_h_ +#define __vpPanda3DLight_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DPostProcessFilter.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DPostProcessFilter.h new file mode 100644 index 00000000..0bce85ca --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DPostProcessFilter.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPanda3DPostProcessFilter_gen_h_ +#define __vpPanda3DPostProcessFilter_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DRGBRenderer.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DRGBRenderer.h new file mode 100644 index 00000000..e35e1d97 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DRGBRenderer.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPanda3DRGBRenderer_gen_h_ +#define __vpPanda3DRGBRenderer_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DRenderParameters.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DRenderParameters.h new file mode 100644 index 00000000..2a883ff4 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DRenderParameters.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPanda3DRenderParameters_gen_h_ +#define __vpPanda3DRenderParameters_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DRendererSet.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DRendererSet.h new file mode 100644 index 00000000..77312e32 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPanda3DRendererSet.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPanda3DRendererSet_gen_h_ +#define __vpPanda3DRendererSet_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpParallelPort.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpParallelPort.h new file mode 100644 index 00000000..f782fc45 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpParallelPort.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpParallelPort_gen_h_ +#define __vpParallelPort_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpParallelPortException.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpParallelPortException.h new file mode 100644 index 00000000..46c71581 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpParallelPortException.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpParallelPortException_gen_h_ +#define __vpParallelPortException_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpParseArgv.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpParseArgv.h new file mode 100644 index 00000000..6fefd05d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpParseArgv.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpParseArgv_gen_h_ +#define __vpParseArgv_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpParticleFilter.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpParticleFilter.h new file mode 100644 index 00000000..5191a662 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpParticleFilter.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpParticleFilter_gen_h_ +#define __vpParticleFilter_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPclViewer.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPclViewer.h new file mode 100644 index 00000000..cfc79b89 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPclViewer.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPclViewer_gen_h_ +#define __vpPclViewer_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPioneer.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPioneer.h new file mode 100644 index 00000000..60f39891 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPioneer.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPioneer_gen_h_ +#define __vpPioneer_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPioneerPan.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPioneerPan.h new file mode 100644 index 00000000..bc54f3ef --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPioneerPan.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPioneerPan_gen_h_ +#define __vpPioneerPan_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPixelMeterConversion.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPixelMeterConversion.h new file mode 100644 index 00000000..df55352d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPixelMeterConversion.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPixelMeterConversion_gen_h_ +#define __vpPixelMeterConversion_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPlane.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPlane.h new file mode 100644 index 00000000..edca18e0 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPlane.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPlane_gen_h_ +#define __vpPlane_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPlaneEstimation.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPlaneEstimation.h new file mode 100644 index 00000000..ad564998 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPlaneEstimation.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPlaneEstimation_gen_h_ +#define __vpPlaneEstimation_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPlot.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPlot.h new file mode 100644 index 00000000..1b055364 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPlot.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPlot_gen_h_ +#define __vpPlot_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPlotCurve.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPlotCurve.h new file mode 100644 index 00000000..eab6864a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPlotCurve.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPlotCurve_gen_h_ +#define __vpPlotCurve_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPlotGraph.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPlotGraph.h new file mode 100644 index 00000000..9ee8b0be --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPlotGraph.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPlotGraph_gen_h_ +#define __vpPlotGraph_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPoint.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPoint.h new file mode 100644 index 00000000..11e597ef --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPoint.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPoint_gen_h_ +#define __vpPoint_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPololu.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPololu.h new file mode 100644 index 00000000..a8c38e9e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPololu.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPololu_gen_h_ +#define __vpPololu_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPolygon.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPolygon.h new file mode 100644 index 00000000..441c0b79 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPolygon.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPolygon_gen_h_ +#define __vpPolygon_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPolygon3D.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPolygon3D.h new file mode 100644 index 00000000..a30d9f99 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPolygon3D.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPolygon3D_gen_h_ +#define __vpPolygon3D_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPose.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPose.h new file mode 100644 index 00000000..092b6ca1 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPose.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPose_gen_h_ +#define __vpPose_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPoseException.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPoseException.h new file mode 100644 index 00000000..8fdac19f --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPoseException.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPoseException_gen_h_ +#define __vpPoseException_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPoseFeatures.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPoseFeatures.h new file mode 100644 index 00000000..bdd3e503 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPoseFeatures.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPoseFeatures_gen_h_ +#define __vpPoseFeatures_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPoseVector.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPoseVector.h new file mode 100644 index 00000000..3e63b426 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPoseVector.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPoseVector_gen_h_ +#define __vpPoseVector_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpProjectionDisplay.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpProjectionDisplay.h new file mode 100644 index 00000000..3949dab9 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpProjectionDisplay.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpProjectionDisplay_gen_h_ +#define __vpProjectionDisplay_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPtu46.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPtu46.h new file mode 100644 index 00000000..842c29c2 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPtu46.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPtu46_gen_h_ +#define __vpPtu46_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPylonFactory.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPylonFactory.h new file mode 100644 index 00000000..e932c460 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPylonFactory.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPylonFactory_gen_h_ +#define __vpPylonFactory_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpPylonGrabber.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPylonGrabber.h new file mode 100644 index 00000000..65387210 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpPylonGrabber.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpPylonGrabber_gen_h_ +#define __vpPylonGrabber_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpQbDevice.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpQbDevice.h new file mode 100644 index 00000000..56f52c3d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpQbDevice.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpQbDevice_gen_h_ +#define __vpQbDevice_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpQbSoftHand.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpQbSoftHand.h new file mode 100644 index 00000000..7960cf01 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpQbSoftHand.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpQbSoftHand_gen_h_ +#define __vpQbSoftHand_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpQuadProg.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpQuadProg.h new file mode 100644 index 00000000..a94539d2 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpQuadProg.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpQuadProg_gen_h_ +#define __vpQuadProg_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpQuaternionVector.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpQuaternionVector.h new file mode 100644 index 00000000..cd237089 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpQuaternionVector.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpQuaternionVector_gen_h_ +#define __vpQuaternionVector_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRGBa.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRGBa.h new file mode 100644 index 00000000..9570c6b2 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRGBa.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRGBa_gen_h_ +#define __vpRGBa_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRGBf.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRGBf.h new file mode 100644 index 00000000..93e364fa --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRGBf.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRGBf_gen_h_ +#define __vpRGBf_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRansac.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRansac.h new file mode 100644 index 00000000..d9610485 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRansac.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRansac_gen_h_ +#define __vpRansac_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRealSense2.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRealSense2.h new file mode 100644 index 00000000..3e7f07c0 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRealSense2.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRealSense2_gen_h_ +#define __vpRealSense2_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRect.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRect.h new file mode 100644 index 00000000..8b6d75ef --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRect.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRect_gen_h_ +#define __vpRect_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRectOriented.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRectOriented.h new file mode 100644 index 00000000..9e6aa035 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRectOriented.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRectOriented_gen_h_ +#define __vpRectOriented_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpReflexTakktile2.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpReflexTakktile2.h new file mode 100644 index 00000000..0d4869a8 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpReflexTakktile2.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpReflexTakktile2_gen_h_ +#define __vpReflexTakktile2_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRequest.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRequest.h new file mode 100644 index 00000000..fcc37650 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRequest.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRequest_gen_h_ +#define __vpRequest_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRingLight.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRingLight.h new file mode 100644 index 00000000..db2d341b --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRingLight.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRingLight_gen_h_ +#define __vpRingLight_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobot.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobot.h new file mode 100644 index 00000000..0b3005cb --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobot.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRobot_gen_h_ +#define __vpRobot_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotAfma6.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotAfma6.h new file mode 100644 index 00000000..e356ad7c --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotAfma6.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRobotAfma6_gen_h_ +#define __vpRobotAfma6_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotBebop2.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotBebop2.h new file mode 100644 index 00000000..a6869238 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotBebop2.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRobotBebop2_gen_h_ +#define __vpRobotBebop2_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotBiclops.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotBiclops.h new file mode 100644 index 00000000..03319bb1 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotBiclops.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRobotBiclops_gen_h_ +#define __vpRobotBiclops_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotCamera.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotCamera.h new file mode 100644 index 00000000..a28a28c2 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotCamera.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRobotCamera_gen_h_ +#define __vpRobotCamera_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotException.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotException.h new file mode 100644 index 00000000..b806244d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotException.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRobotException_gen_h_ +#define __vpRobotException_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotFlirPtu.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotFlirPtu.h new file mode 100644 index 00000000..47dc7c22 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotFlirPtu.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRobotFlirPtu_gen_h_ +#define __vpRobotFlirPtu_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotFranka.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotFranka.h new file mode 100644 index 00000000..8ddd0b48 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotFranka.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRobotFranka_gen_h_ +#define __vpRobotFranka_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotKinova.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotKinova.h new file mode 100644 index 00000000..c15ab62f --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotKinova.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRobotKinova_gen_h_ +#define __vpRobotKinova_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotMavsdk.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotMavsdk.h new file mode 100644 index 00000000..95d35691 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotMavsdk.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRobotMavsdk_gen_h_ +#define __vpRobotMavsdk_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotPioneer.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotPioneer.h new file mode 100644 index 00000000..dd190c36 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotPioneer.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRobotPioneer_gen_h_ +#define __vpRobotPioneer_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotPololuPtu.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotPololuPtu.h new file mode 100644 index 00000000..c419f07d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotPololuPtu.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRobotPololuPtu_gen_h_ +#define __vpRobotPololuPtu_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotPtu46.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotPtu46.h new file mode 100644 index 00000000..5c35d664 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotPtu46.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRobotPtu46_gen_h_ +#define __vpRobotPtu46_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotSimulator.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotSimulator.h new file mode 100644 index 00000000..60a5e288 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotSimulator.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRobotSimulator_gen_h_ +#define __vpRobotSimulator_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotTemplate.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotTemplate.h new file mode 100644 index 00000000..1dfd032b --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotTemplate.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRobotTemplate_gen_h_ +#define __vpRobotTemplate_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotUniversalRobots.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotUniversalRobots.h new file mode 100644 index 00000000..48f3ecba --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotUniversalRobots.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRobotUniversalRobots_gen_h_ +#define __vpRobotUniversalRobots_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotViper650.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotViper650.h new file mode 100644 index 00000000..45a44c77 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotViper650.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRobotViper650_gen_h_ +#define __vpRobotViper650_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotViper850.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotViper850.h new file mode 100644 index 00000000..f71bd5e3 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotViper850.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRobotViper850_gen_h_ +#define __vpRobotViper850_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotWireFrameSimulator.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotWireFrameSimulator.h new file mode 100644 index 00000000..8a1f46db --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobotWireFrameSimulator.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRobotWireFrameSimulator_gen_h_ +#define __vpRobotWireFrameSimulator_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobust.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobust.h new file mode 100644 index 00000000..bd531826 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRobust.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRobust_gen_h_ +#define __vpRobust_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRotationMatrix.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRotationMatrix.h new file mode 100644 index 00000000..26925ce4 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRotationMatrix.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRotationMatrix_gen_h_ +#define __vpRotationMatrix_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRotationVector.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRotationVector.h new file mode 100644 index 00000000..9d3d6831 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRotationVector.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRotationVector_gen_h_ +#define __vpRotationVector_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRowVector.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRowVector.h new file mode 100644 index 00000000..7c0c95f3 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRowVector.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRowVector_gen_h_ +#define __vpRowVector_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRxyzVector.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRxyzVector.h new file mode 100644 index 00000000..ab11f6ed --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRxyzVector.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRxyzVector_gen_h_ +#define __vpRxyzVector_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRzyxVector.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRzyxVector.h new file mode 100644 index 00000000..da4f1fbe --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRzyxVector.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRzyxVector_gen_h_ +#define __vpRzyxVector_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpRzyzVector.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRzyzVector.h new file mode 100644 index 00000000..9ae3d356 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpRzyzVector.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpRzyzVector_gen_h_ +#define __vpRzyzVector_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpScale.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpScale.h new file mode 100644 index 00000000..c05ba488 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpScale.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpScale_gen_h_ +#define __vpScale_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpScanPoint.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpScanPoint.h new file mode 100644 index 00000000..fca984d1 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpScanPoint.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpScanPoint_gen_h_ +#define __vpScanPoint_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpSerial.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpSerial.h new file mode 100644 index 00000000..74966c26 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpSerial.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpSerial_gen_h_ +#define __vpSerial_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpServer.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpServer.h new file mode 100644 index 00000000..4c8d1165 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpServer.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpServer_gen_h_ +#define __vpServer_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpServo.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpServo.h new file mode 100644 index 00000000..d045d3e8 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpServo.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpServo_gen_h_ +#define __vpServo_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpServoData.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpServoData.h new file mode 100644 index 00000000..0ab3c917 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpServoData.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpServoData_gen_h_ +#define __vpServoData_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpServoDisplay.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpServoDisplay.h new file mode 100644 index 00000000..1ea8e94b --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpServoDisplay.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpServoDisplay_gen_h_ +#define __vpServoDisplay_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpServoException.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpServoException.h new file mode 100644 index 00000000..5109bd92 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpServoException.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpServoException_gen_h_ +#define __vpServoException_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpSickLDMRS.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpSickLDMRS.h new file mode 100644 index 00000000..83190670 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpSickLDMRS.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpSickLDMRS_gen_h_ +#define __vpSickLDMRS_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulator.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulator.h new file mode 100644 index 00000000..b72c7236 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulator.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpSimulator_gen_h_ +#define __vpSimulator_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulatorAfma6.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulatorAfma6.h new file mode 100644 index 00000000..ec7a526a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulatorAfma6.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpSimulatorAfma6_gen_h_ +#define __vpSimulatorAfma6_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulatorCamera.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulatorCamera.h new file mode 100644 index 00000000..f5c92875 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulatorCamera.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpSimulatorCamera_gen_h_ +#define __vpSimulatorCamera_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulatorException.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulatorException.h new file mode 100644 index 00000000..4d3d0cd6 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulatorException.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpSimulatorException_gen_h_ +#define __vpSimulatorException_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulatorPioneer.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulatorPioneer.h new file mode 100644 index 00000000..f0d30bcd --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulatorPioneer.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpSimulatorPioneer_gen_h_ +#define __vpSimulatorPioneer_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulatorPioneerPan.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulatorPioneerPan.h new file mode 100644 index 00000000..5c1ad597 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulatorPioneerPan.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpSimulatorPioneerPan_gen_h_ +#define __vpSimulatorPioneerPan_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulatorViper850.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulatorViper850.h new file mode 100644 index 00000000..4beefd2b --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpSimulatorViper850.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpSimulatorViper850_gen_h_ +#define __vpSimulatorViper850_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpSphere.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpSphere.h new file mode 100644 index 00000000..4178292f --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpSphere.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpSphere_gen_h_ +#define __vpSphere_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpStatisticalTestAbstract.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpStatisticalTestAbstract.h new file mode 100644 index 00000000..ff105d3e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpStatisticalTestAbstract.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpStatisticalTestAbstract_gen_h_ +#define __vpStatisticalTestAbstract_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpStatisticalTestEWMA.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpStatisticalTestEWMA.h new file mode 100644 index 00000000..7afb98c4 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpStatisticalTestEWMA.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpStatisticalTestEWMA_gen_h_ +#define __vpStatisticalTestEWMA_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpStatisticalTestHinkley.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpStatisticalTestHinkley.h new file mode 100644 index 00000000..4a23b96d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpStatisticalTestHinkley.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpStatisticalTestHinkley_gen_h_ +#define __vpStatisticalTestHinkley_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpStatisticalTestMeanAdjustedCUSUM.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpStatisticalTestMeanAdjustedCUSUM.h new file mode 100644 index 00000000..949fa17c --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpStatisticalTestMeanAdjustedCUSUM.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpStatisticalTestMeanAdjustedCUSUM_gen_h_ +#define __vpStatisticalTestMeanAdjustedCUSUM_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpStatisticalTestShewhart.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpStatisticalTestShewhart.h new file mode 100644 index 00000000..51a0c4f0 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpStatisticalTestShewhart.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpStatisticalTestShewhart_gen_h_ +#define __vpStatisticalTestShewhart_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpStatisticalTestSigma.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpStatisticalTestSigma.h new file mode 100644 index 00000000..6546eb9f --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpStatisticalTestSigma.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpStatisticalTestSigma_gen_h_ +#define __vpStatisticalTestSigma_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpSubColVector.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpSubColVector.h new file mode 100644 index 00000000..74daa61a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpSubColVector.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpSubColVector_gen_h_ +#define __vpSubColVector_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpSubMatrix.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpSubMatrix.h new file mode 100644 index 00000000..5a0ecbbf --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpSubMatrix.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpSubMatrix_gen_h_ +#define __vpSubMatrix_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpSubRowVector.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpSubRowVector.h new file mode 100644 index 00000000..e791d8f2 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpSubRowVector.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpSubRowVector_gen_h_ +#define __vpSubRowVector_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTracker.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTracker.h new file mode 100644 index 00000000..5e847b78 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTracker.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTemplateTracker_gen_h_ +#define __vpTemplateTracker_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerBSpline.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerBSpline.h new file mode 100644 index 00000000..9ce16d2a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerBSpline.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTemplateTrackerBSpline_gen_h_ +#define __vpTemplateTrackerBSpline_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerHeader.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerHeader.h new file mode 100644 index 00000000..efd2d1ec --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerHeader.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTemplateTrackerHeader_gen_h_ +#define __vpTemplateTrackerHeader_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerMI.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerMI.h new file mode 100644 index 00000000..f0779fb1 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerMI.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTemplateTrackerMI_gen_h_ +#define __vpTemplateTrackerMI_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerMIBSpline.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerMIBSpline.h new file mode 100644 index 00000000..0d82c45b --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerMIBSpline.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTemplateTrackerMIBSpline_gen_h_ +#define __vpTemplateTrackerMIBSpline_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerMIESM.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerMIESM.h new file mode 100644 index 00000000..0cad6a37 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerMIESM.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTemplateTrackerMIESM_gen_h_ +#define __vpTemplateTrackerMIESM_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerMIForwardAdditional.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerMIForwardAdditional.h new file mode 100644 index 00000000..2a32cb23 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerMIForwardAdditional.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTemplateTrackerMIForwardAdditional_gen_h_ +#define __vpTemplateTrackerMIForwardAdditional_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerMIForwardCompositional.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerMIForwardCompositional.h new file mode 100644 index 00000000..aae1e2dc --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerMIForwardCompositional.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTemplateTrackerMIForwardCompositional_gen_h_ +#define __vpTemplateTrackerMIForwardCompositional_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerMIInverseCompositional.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerMIInverseCompositional.h new file mode 100644 index 00000000..4487876e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerMIInverseCompositional.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTemplateTrackerMIInverseCompositional_gen_h_ +#define __vpTemplateTrackerMIInverseCompositional_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerSSD.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerSSD.h new file mode 100644 index 00000000..8e6b4ea7 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerSSD.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTemplateTrackerSSD_gen_h_ +#define __vpTemplateTrackerSSD_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerSSDESM.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerSSDESM.h new file mode 100644 index 00000000..39ff2415 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerSSDESM.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTemplateTrackerSSDESM_gen_h_ +#define __vpTemplateTrackerSSDESM_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerSSDForwardAdditional.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerSSDForwardAdditional.h new file mode 100644 index 00000000..7d1f8924 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerSSDForwardAdditional.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTemplateTrackerSSDForwardAdditional_gen_h_ +#define __vpTemplateTrackerSSDForwardAdditional_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerSSDForwardCompositional.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerSSDForwardCompositional.h new file mode 100644 index 00000000..cbb92710 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerSSDForwardCompositional.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTemplateTrackerSSDForwardCompositional_gen_h_ +#define __vpTemplateTrackerSSDForwardCompositional_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerSSDInverseCompositional.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerSSDInverseCompositional.h new file mode 100644 index 00000000..c486f4d9 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerSSDInverseCompositional.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTemplateTrackerSSDInverseCompositional_gen_h_ +#define __vpTemplateTrackerSSDInverseCompositional_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerTriangle.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerTriangle.h new file mode 100644 index 00000000..8573b74f --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerTriangle.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTemplateTrackerTriangle_gen_h_ +#define __vpTemplateTrackerTriangle_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarp.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarp.h new file mode 100644 index 00000000..c404eaa8 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarp.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTemplateTrackerWarp_gen_h_ +#define __vpTemplateTrackerWarp_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarpAffine.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarpAffine.h new file mode 100644 index 00000000..6b092c73 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarpAffine.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTemplateTrackerWarpAffine_gen_h_ +#define __vpTemplateTrackerWarpAffine_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarpHomography.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarpHomography.h new file mode 100644 index 00000000..c36afdf0 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarpHomography.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTemplateTrackerWarpHomography_gen_h_ +#define __vpTemplateTrackerWarpHomography_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarpHomographySL3.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarpHomographySL3.h new file mode 100644 index 00000000..6f3d839e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarpHomographySL3.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTemplateTrackerWarpHomographySL3_gen_h_ +#define __vpTemplateTrackerWarpHomographySL3_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarpRT.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarpRT.h new file mode 100644 index 00000000..b71a2403 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarpRT.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTemplateTrackerWarpRT_gen_h_ +#define __vpTemplateTrackerWarpRT_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarpSRT.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarpSRT.h new file mode 100644 index 00000000..b120f27c --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarpSRT.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTemplateTrackerWarpSRT_gen_h_ +#define __vpTemplateTrackerWarpSRT_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarpTranslation.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarpTranslation.h new file mode 100644 index 00000000..8829722c --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerWarpTranslation.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTemplateTrackerWarpTranslation_gen_h_ +#define __vpTemplateTrackerWarpTranslation_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerZNCC.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerZNCC.h new file mode 100644 index 00000000..99a87321 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerZNCC.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTemplateTrackerZNCC_gen_h_ +#define __vpTemplateTrackerZNCC_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerZNCCForwardAdditional.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerZNCCForwardAdditional.h new file mode 100644 index 00000000..2ae5b73e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerZNCCForwardAdditional.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTemplateTrackerZNCCForwardAdditional_gen_h_ +#define __vpTemplateTrackerZNCCForwardAdditional_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerZNCCInverseCompositional.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerZNCCInverseCompositional.h new file mode 100644 index 00000000..76755375 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerZNCCInverseCompositional.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTemplateTrackerZNCCInverseCompositional_gen_h_ +#define __vpTemplateTrackerZNCCInverseCompositional_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerZone.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerZone.h new file mode 100644 index 00000000..9d87e9c5 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTemplateTrackerZone.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTemplateTrackerZone_gen_h_ +#define __vpTemplateTrackerZone_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpThetaUVector.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpThetaUVector.h new file mode 100644 index 00000000..50fba8c8 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpThetaUVector.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpThetaUVector_gen_h_ +#define __vpThetaUVector_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpThread.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpThread.h new file mode 100644 index 00000000..3b4b0d2a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpThread.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpThread_gen_h_ +#define __vpThread_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTime.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTime.h new file mode 100644 index 00000000..aeb0e83f --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTime.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTime_gen_h_ +#define __vpTime_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTracker.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTracker.h new file mode 100644 index 00000000..9561941e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTracker.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTracker_gen_h_ +#define __vpTracker_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTrackingException.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTrackingException.h new file mode 100644 index 00000000..783847e4 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTrackingException.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTrackingException_gen_h_ +#define __vpTrackingException_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTranslationVector.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTranslationVector.h new file mode 100644 index 00000000..c76c71d4 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTranslationVector.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTranslationVector_gen_h_ +#define __vpTranslationVector_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpTriangle.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTriangle.h new file mode 100644 index 00000000..dee353ce --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpTriangle.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpTriangle_gen_h_ +#define __vpTriangle_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpUDPClient.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpUDPClient.h new file mode 100644 index 00000000..c01922f2 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpUDPClient.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpUDPClient_gen_h_ +#define __vpUDPClient_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpUDPServer.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpUDPServer.h new file mode 100644 index 00000000..d32f64fe --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpUDPServer.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpUDPServer_gen_h_ +#define __vpUDPServer_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpUKSigmaDrawerAbstract.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpUKSigmaDrawerAbstract.h new file mode 100644 index 00000000..c136b9e2 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpUKSigmaDrawerAbstract.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpUKSigmaDrawerAbstract_gen_h_ +#define __vpUKSigmaDrawerAbstract_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpUKSigmaDrawerMerwe.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpUKSigmaDrawerMerwe.h new file mode 100644 index 00000000..fd184658 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpUKSigmaDrawerMerwe.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpUKSigmaDrawerMerwe_gen_h_ +#define __vpUKSigmaDrawerMerwe_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpUeyeGrabber.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpUeyeGrabber.h new file mode 100644 index 00000000..38ce01ee --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpUeyeGrabber.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpUeyeGrabber_gen_h_ +#define __vpUeyeGrabber_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpUniRand.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpUniRand.h new file mode 100644 index 00000000..93baf72b --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpUniRand.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpUniRand_gen_h_ +#define __vpUniRand_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpUnicycle.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpUnicycle.h new file mode 100644 index 00000000..013ce0f4 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpUnicycle.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpUnicycle_gen_h_ +#define __vpUnicycle_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpUnscentedKalman.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpUnscentedKalman.h new file mode 100644 index 00000000..6bc5890c --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpUnscentedKalman.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpUnscentedKalman_gen_h_ +#define __vpUnscentedKalman_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpV4l2Grabber.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpV4l2Grabber.h new file mode 100644 index 00000000..7dec3b95 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpV4l2Grabber.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpV4l2Grabber_gen_h_ +#define __vpV4l2Grabber_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpVelocityTwistMatrix.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpVelocityTwistMatrix.h new file mode 100644 index 00000000..08fd3032 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpVelocityTwistMatrix.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpVelocityTwistMatrix_gen_h_ +#define __vpVelocityTwistMatrix_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpVideoReader.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpVideoReader.h new file mode 100644 index 00000000..0fdbf2f2 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpVideoReader.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpVideoReader_gen_h_ +#define __vpVideoReader_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpVideoWriter.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpVideoWriter.h new file mode 100644 index 00000000..fea0930c --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpVideoWriter.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpVideoWriter_gen_h_ +#define __vpVideoWriter_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpViewer.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpViewer.h new file mode 100644 index 00000000..48829567 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpViewer.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpViewer_gen_h_ +#define __vpViewer_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpViper.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpViper.h new file mode 100644 index 00000000..b219e4c4 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpViper.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpViper_gen_h_ +#define __vpViper_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpViper650.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpViper650.h new file mode 100644 index 00000000..28ee2748 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpViper650.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpViper650_gen_h_ +#define __vpViper650_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpViper850.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpViper850.h new file mode 100644 index 00000000..35be79f7 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpViper850.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpViper850_gen_h_ +#define __vpViper850_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpVirtuose.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpVirtuose.h new file mode 100644 index 00000000..a5a2b2af --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpVirtuose.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpVirtuose_gen_h_ +#define __vpVirtuose_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpWin32API.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpWin32API.h new file mode 100644 index 00000000..5b8b1aab --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpWin32API.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpWin32API_gen_h_ +#define __vpWin32API_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpWin32Renderer.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpWin32Renderer.h new file mode 100644 index 00000000..865d7590 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpWin32Renderer.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpWin32Renderer_gen_h_ +#define __vpWin32Renderer_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpWin32Window.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpWin32Window.h new file mode 100644 index 00000000..7e70a319 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpWin32Window.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpWin32Window_gen_h_ +#define __vpWin32Window_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpWireFrameSimulator.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpWireFrameSimulator.h new file mode 100644 index 00000000..7642dd00 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpWireFrameSimulator.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpWireFrameSimulator_gen_h_ +#define __vpWireFrameSimulator_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpWireFrameSimulatorTypes.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpWireFrameSimulatorTypes.h new file mode 100644 index 00000000..7cebcbf4 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpWireFrameSimulatorTypes.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpWireFrameSimulatorTypes_gen_h_ +#define __vpWireFrameSimulatorTypes_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpXmlConfigParserKeyPoint.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpXmlConfigParserKeyPoint.h new file mode 100644 index 00000000..e8c100ae --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpXmlConfigParserKeyPoint.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpXmlConfigParserKeyPoint_gen_h_ +#define __vpXmlConfigParserKeyPoint_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpXmlParser.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpXmlParser.h new file mode 100644 index 00000000..491e57eb --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpXmlParser.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpXmlParser_gen_h_ +#define __vpXmlParser_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpXmlParserCamera.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpXmlParserCamera.h new file mode 100644 index 00000000..02b75b34 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpXmlParserCamera.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpXmlParserCamera_gen_h_ +#define __vpXmlParserCamera_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpXmlParserHomogeneousMatrix.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpXmlParserHomogeneousMatrix.h new file mode 100644 index 00000000..7f864e95 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpXmlParserHomogeneousMatrix.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpXmlParserHomogeneousMatrix_gen_h_ +#define __vpXmlParserHomogeneousMatrix_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp/vpXmlParserRectOriented.h b/dependency/x86/third_party/visp/3.7.0/include/visp/vpXmlParserRectOriented.h new file mode 100644 index 00000000..9a156e72 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp/vpXmlParserRectOriented.h @@ -0,0 +1,12 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __vpXmlParserRectOriented_gen_h_ +#define __vpXmlParserRectOriented_gen_h_ + +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpAR.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpAR.h new file mode 100644 index 00000000..98fed26d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpAR.h @@ -0,0 +1,158 @@ +/* + * + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Use to display an image behind the internal view of the simulator + * used for augmented reality application + */ + +/*! + \file vpAR.h + + \brief Class used to display an image behind the internal view of + the simulator. Used for augmented reality applications. + + \warning The content of this file is only available if Coin3D and + one of the GUI (SoWin, SoXT, SoQt) are installed. + +*/ + +#ifndef VP_AR_H +#define VP_AR_H + +#include + +#ifdef VISP_HAVE_COIN3D_AND_GUI + +// visp +#include +#include +#include + +#include + +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpAR + + \ingroup group_ar_renderer + + \brief Implementation of an augmented reality viewer using Coin3D 3rd party. + + Thus to be enabled this class requires Coin3D 3rd party installation. + Installation instructions are provided here https://visp.inria.fr/3rd_coin. + + This class can be used to display an image behind the internal view + of the simulator used for augmented reality application. + + \warning This class is only available if Coin3D and one of the GUI + (SoWin, SoXT, SoQt) are installed. + + The code below shows how to use the class. + + \code + #include + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + #ifdef VISP_HAVE_COIN3D_AND_GUI + static void *mainloopfunction(void *_simu) + { + vpAR *simu = (vpAR *)_simu ; + simu->initMainApplication() ; + + vpImage I; + vpHomogeneousMatrix cMo; + + //Your code to compute the pose cMo. + + //Set the image to use as background. + simu->setImage(I) ; + //Set the camera position thanks to the pose cMo computed before. + simu->setCameraPosition(cMo) ; + + simu->closeMainApplication(); + } + #endif + + int main() + { + #ifdef VISP_HAVE_COIN3D_AND_GUI + vpAR simu; + //Camera parameters. + vpCameraParameters cam(600,600,160,120); + + //Initialize the internal view of the simulator. + simu.initInternalViewer(640,480, vpSimulator::grayImage); + + vpTime::wait(300); + + // Load the cad model. 4points.iv can be downloaded on the website + // with the image package + simu.load("./4points.iv"); + + //Initialize the internal camera parameters. + simu.setInternalCameraParameters(cam); + + simu.initApplication(&mainloopfunction); + + simu.mainLoop(); + #endif + return 0; + } + \endcode +*/ +class VISP_EXPORT vpAR : public vpSimulator +{ + +private: + bool background; + +public: + vpAR() : background(false) { } + + virtual ~vpAR(); + void initInternalViewer(unsigned int width, unsigned int height, vpImageType type = grayImage); + void setImage(vpImage &I); + void setImage(vpImage &I); +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpAROgre.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpAROgre.h new file mode 100644 index 00000000..d209e968 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpAROgre.h @@ -0,0 +1,439 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Augmented Reality viewer using Ogre3D. + */ + +/*! + \file vpAROgre.h + + \brief Class that implements augmented reality viewer based on Ogre3D. + + \warning The content of this file is only available if Ogre3D and + one of the renderer (OpenGL or DirectX) are installed. +*/ + +#ifndef VP_AR_OGRE_H +#define VP_AR_OGRE_H + +#include + +#ifdef VISP_HAVE_OGRE +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#if (VISP_HAVE_OGRE_VERSION >= (1<<16 | 10 <<8 | 0)) +#include +#endif + +#if (VISP_HAVE_OGRE_VERSION >= (1<<16 | 11<<8 | 0)) +#include +#endif + +#if (VISP_HAVE_OGRE_VERSION >= (1<<16 | 12 <<8 | 0)) +#include +#elif (VISP_HAVE_OGRE_VERSION >= (1<<16 | 10 <<8 | 0)) +#include +#endif + +#if defined(OGRE_BUILD_COMPONENT_RTSHADERSYSTEM) & (VISP_HAVE_OGRE_VERSION >= (1<<16 | 10 <<8 | 0)) +#include +#include +#endif // INCLUDE_RTSHADER_SYSTEM + +#ifdef VISP_HAVE_OIS +#include +#endif + +BEGIN_VISP_NAMESPACE +/*! + \class vpAROgre + + \ingroup group_ar_renderer + + \brief Implementation of an augmented reality viewer using Ogre3D 3rd party. + + Thus to be enabled this class requires Ogre3D 3rd party installation. + Installation instructions are provided here https://visp.inria.fr/3rd_ogre. + + This class provides methods to show a 3D scene in a real world. To do that + you will need to initialise it with the parameters of your camera, then each + frame you will need to compute a pose for your camera and give it to the + application. With that information and the image to be shown in background + it will set up the 3D scene correspondingly. + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in using Ogre3D in Augmented Reality applications, you may have a look at:
+ + - \ref tutorial-ogre + +*/ +class VISP_EXPORT vpAROgre : public Ogre::FrameListener, +#if (VISP_HAVE_OGRE_VERSION >= (1<<16 | 11 <<8 | 0)) + public OgreBites::WindowEventListener +#else + public Ogre::WindowEventListener +#endif +#ifdef VISP_HAVE_OIS + , + public OIS::KeyListener +#endif +{ +public: + vpAROgre(const vpCameraParameters &cam = vpCameraParameters(), unsigned int width = 0, unsigned int height = 0, + const char *resourcePath = +#ifdef VISP_HAVE_OGRE_RESOURCES_PATH + VISP_HAVE_OGRE_RESOURCES_PATH, +#else + ".", +#endif + const char *pluginsPath = +#ifdef VISP_HAVE_OGRE_PLUGINS_PATH + VISP_HAVE_OGRE_PLUGINS_PATH +#else + "." +#endif + ); + + virtual ~vpAROgre(void); + + /*! + Add optional resource location. Since a resource file cannot be always + sufficient to manage multiple location media (depending on the computer + and the executable path), this method may be used to add such paths. + + \warning To be effective, this method must be called before the init() + one. + + \param resourceLocation : The resource location (it may be a folder or a + zip file). + */ + inline void addResource(const std::string &resourceLocation) + { + mOptionalResourceLocation.push_back(resourceLocation); + } + + void addRotation(const std::string &sceneName, const vpRotationMatrix &wRo); + + bool continueRendering(void); + + virtual bool customframeStarted(const Ogre::FrameEvent &evt); + + virtual bool customframeEnded(const Ogre::FrameEvent &evt); + + virtual void display(const vpImage &I, const vpHomogeneousMatrix &cMw); + + virtual void display(const vpImage &I, const vpHomogeneousMatrix &cMw); + + inline Ogre::Camera *getCamera() { return mCamera; } + + /*! + Get the far distance for clipping. + + \return Far clipping value. + */ + inline double getFarClippingDistance() const { return mFarClipping; } + + /*! + Get the near distance for clipping. + + \return Near clipping value. + */ + inline double getNearClippingDistance() const { return mNearClipping; } + + vpTranslationVector getPosition(const std::string &sceneName) const; + + void getRenderingOutput(vpImage &I, const vpHomogeneousMatrix &cMo); + + inline Ogre::SceneManager *getSceneManager() { return mSceneMgr; } + + virtual void init(vpImage &I, bool bufferedKeys = false, bool hidden = false); + virtual void init(vpImage &I, bool bufferedKeys = false, bool hidden = false); + + /*! + Test if the window is hidden or not. + + \warning True if the window is hidden, false otherwise. + */ + bool isWindowHidden() { return windowHidden; } + +#ifdef VISP_HAVE_OIS + /** + * Default event handler + */ + virtual bool keyPressed(const OIS::KeyEvent & /*e*/) { return true; } + /** + * Default event handler + */ + virtual bool keyReleased(const OIS::KeyEvent & /*e*/) { return true; } +#endif + + void load(const std::string &entityName, const std::string &model); + + bool renderOneFrame(const vpImage &I, const vpHomogeneousMatrix &cMw); + + bool renderOneFrame(const vpImage &I, const vpHomogeneousMatrix &cMw); + + void setCameraParameters(const vpCameraParameters &cameraP); + + /*! + Set the far distance for clipping. + + \param dist : Far clipping value. + */ + void setFarClippingDistance(const double &dist) + { + mFarClipping = dist; + updateCameraProjection(); + } + + void setMaterial(const std::string &entityName, const std::string &materialName); + + /*! + Set the near distance for clipping. + + \param dist : Near clipping value. + */ + void setNearClippingDistance(const double &dist) + { + mNearClipping = dist; + updateCameraProjection(); + } + + /*! + Set the plugins path used to locate the plugins.cfg file. + + By default, this path is set to VISP_HAVE_OGRE_PLUGINS_PATH defined in + vpConfig.h + + \warning To be effective, this method must be called before the init() + one. + + \param pluginsPath : The new plugins path (must not have a terminate /). + */ + inline void setPluginsPath(const char *pluginsPath) { mPluginsPath = pluginsPath; } + + void setPosition(const std::string &sceneName, const vpTranslationVector &wTo); + void setPosition(const std::string &sceneName, const vpHomogeneousMatrix &wMo); + + /*! + Set the resource path used to locate the resource.cfg file. + + By default, this path is set to VISP_HAVE_OGRE_RESOURCES_PATH defined in + vpConfig.h + + \warning To be effective, this method must be called before the init() + one. + + \param resourcePath : The new resource path (must not have a terminate /). + */ + inline void setResourcePath(const char *resourcePath) { mResourcePath = resourcePath; } + + void setRotation(const std::string &sceneName, const vpRotationMatrix &wRo); + + void setScale(const std::string &sceneName, float factorx, float factory, float factorz); + + /*! + Enable/Disable the appearance of the config dialog on startup. + + \warning To be effective, this method must be called before the init() + one. + + \param showConfigDialog : if true, shows the dialog window (used to set + the display options) + */ + inline void setShowConfigDialog(bool showConfigDialog) { mshowConfigDialog = showConfigDialog; } + + void setVisibility(const std::string &sceneName, bool isVisible); + + /*! + Set the name of the window. + + \warning Has to be called before initialisation. + + \param n : Name of the window. + */ + inline void setWindowName(const Ogre::String &n) { name = n; } + + /*! + Set the window position in the screen. + + \throw vpException::notInitialised if the window has not been created + (using the init() method). + + \param win_x : x coordinate of the new top left corner of the window in + the screen. \param win_y : y coordinate of the new top left corner of the + window in the screen. + */ + inline void setWindowPosition(unsigned int win_x, unsigned int win_y) + { + if (mWindow == nullptr) { + throw vpException(vpException::notInitialized, "Window not initialised, cannot set its position"); + } + mWindow->reposition(static_cast(win_x), static_cast(win_y)); + } + + virtual bool windowClosing(Ogre::RenderWindow *rw) VP_OVERRIDE; + + virtual void windowClosed(Ogre::RenderWindow *rw) VP_OVERRIDE; + +protected: + virtual void init(bool bufferedKeys = false, bool hidden = false); + virtual void createCamera(void); + + /** + * Build the 3D scene + * Override this to show what you want + */ + virtual void createScene(void) { } + + virtual void closeOIS(void); + + /*! + Update the 3D scene + + \return Always true. + */ + virtual bool updateScene(const Ogre::FrameEvent & /*evt*/) { return true; } + + /*! + Check for keyboard, mouse and joystick inputs. + + \return Always true. + */ + virtual bool processInputEvent(const Ogre::FrameEvent & /*evt*/) { return true; } + + /*! + Clean up the 3D scene. + + \return Always true. + */ + virtual bool destroyScene(void) + { + if (!mSceneMgr) { + return false; + } + + mSceneMgr->destroyAllCameras(); + mSceneMgr->clearScene(); + mRoot->destroySceneManager(mSceneMgr); + return true; + } + + virtual void updateCameraParameters(const vpHomogeneousMatrix &cMo); + + virtual void updateCameraProjection(void); + + virtual void updateBackgroundTexture(const vpImage &I); + + virtual void updateBackgroundTexture(const vpImage &I); + +private: + void createBackground(vpImage &I); + void createBackground(vpImage &I); + + bool frameStarted(const Ogre::FrameEvent &evt) VP_OVERRIDE; + + bool frameEnded(const Ogre::FrameEvent &evt) VP_OVERRIDE; + + bool stopTest(const Ogre::FrameEvent &evt); + + bool initialiseRTShaderSystem(); + + void destroyRTShaderSystem(); + +protected: + static unsigned int sID; + static unsigned int sRTSSUsers; + // Attributes + Ogre::String name; /**Name of th Window*/ + bool mInitialized; /** True once init(bool, bool) has been called.*/ + + // OGRE 3D System + Ogre::Root *mRoot; /** Application's root */ + Ogre::Camera *mCamera; /** Camera */ + Ogre::String mSceneManagerName; /**Name of the scene manager*/ + Ogre::SceneManager *mSceneMgr; /** Scene manager */ + Ogre::RenderWindow *mWindow; /** Display window */ + Ogre::String mResourcePath; /** Path to resources.cfg */ + Ogre::String mPluginsPath; /** Path to plugins.cfg */ + +#ifdef VISP_HAVE_OIS + // OIS Input manager and devices + OIS::InputManager *mInputManager; + OIS::Keyboard *mKeyboard; +#endif + +#if defined(OGRE_BUILD_COMPONENT_RTSHADERSYSTEM) & (VISP_HAVE_OGRE_VERSION >= (1<<16 | 10 <<8 | 0)) + Ogre::RTShader::ShaderGenerator *mShaderGenerator; // The Shader generator instance. + OgreBites::SGTechniqueResolverListener *mMaterialMgrListener; // Shader generator material manager listener. +#endif // INCLUDE_RTSHADER_SYSTEM + + // ViSP AR System + bool keepOn; /** Has the application received a signal to stop(false) or not + (true) */ + vpImage mImageRGBA; /** vpImage to store grabbed image */ + vpImage mImage; /** vpImage to store grabbed image */ + Ogre::HardwarePixelBufferSharedPtr mPixelBuffer; /** Pointer to the pixel buffer */ + Ogre::Rectangle2D *mBackground; /** Background image */ + unsigned int mBackgroundHeight; /** Height of the acquired image */ + unsigned int mBackgroundWidth; /** Width of the acquired image */ + unsigned int mWindowHeight; /** Height of the window */ + unsigned int mWindowWidth; /** Width of the window */ + bool windowHidden; /** Is window hidden */ + + // Camera calculations + double mNearClipping; /** Near Clipping Distance **/ + double mFarClipping; /** Far Clipping Distance **/ + vpCameraParameters mcam; /** The intrinsic camera parameters */ + + bool mshowConfigDialog; /** if true, shows the dialog window (used to set + the display options) */ + + std::list mOptionalResourceLocation; /** Optional resource location (used to + load mesh and material) */ +}; +END_VISP_NAMESPACE +#endif // VISP_HAVE_OGRE + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DBaseRenderer.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DBaseRenderer.h new file mode 100644 index 00000000..8749cf89 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DBaseRenderer.h @@ -0,0 +1,302 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#ifndef VP_PANDA3D_BASE_RENDERER_H +#define VP_PANDA3D_BASE_RENDERER_H + +#include + +#if defined(VISP_HAVE_PANDA3D) +#include +#include +#include + +#include +#include + +#include +#include +#include + + +BEGIN_VISP_NAMESPACE +/** + * \ingroup group_ar_renderer_panda3d + * + * \brief Base class for a panda3D renderer. This class handles basic functionalities, + * such as loading object, changing camera parameters. + * + * For a subclass to have a novel behaviour (e.g, display something else) These methods should be overriden: + * + * - setupScene: This is where you should apply your shaders. + * - setupCamera: This is where cameras are created and intrinsics parameters are applied + * - setupRenderTarget: This is where you should create the texture buffers, where the render results should be stored. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using Panda3D in Augmented Reality applications, you may have a look at:
+ * + * - \ref tutorial-panda3d +*/ +class VISP_EXPORT vpPanda3DBaseRenderer +{ +public: + vpPanda3DBaseRenderer(const std::string &rendererName) + : m_name(rendererName), m_renderOrder(-100), m_window(nullptr), m_camera(nullptr), m_isWindowOwner(false) + { + setVerticalSyncEnabled(false); + } + + virtual ~vpPanda3DBaseRenderer(); + + /** + * @brief Initialize the whole Panda3D framework. Create a new PandaFramework object and a new window. + * + * Will also perform the renderer setup (scene, camera and render targets) + */ + virtual void initFramework(); + virtual void initFromParent(PointerTo window); + virtual void initFromParent(const vpPanda3DBaseRenderer &renderer); + + virtual void beforeFrameRendered() { } + virtual void renderFrame(); + virtual void afterFrameRendered(); + + /** + * @brief Get the name of the renderer + * + * @return const std::string& + */ + const std::string &getName() const { return m_name; } + + void setName(const std::string &name) { m_name = name; } + + /** + * @brief Get the scene root + * + */ + NodePath &getRenderRoot() { return m_renderRoot; } + + virtual void clearScene() + { + m_renderRoot.node()->remove_all_children(); + } + + /** + * @brief Set new rendering parameters. If the scene has already been initialized, the renderer camera is updated. + * + * @param params the new rendering parameters + */ + virtual void setRenderParameters(const vpPanda3DRenderParameters ¶ms); + + /** + * @brief Returns true if this renderer process 3D data and its scene root can be interacted with. + * + * This value could be false, if for instance it is redefined in a subclass that performs postprocessing on a texture. + */ + virtual bool isRendering3DScene() const { return true; } + + /** + * @brief Get the rendering order of this renderer. + * If a renderer A has a lower order value than B, it will be rendered before B. + * This is useful, if for instance, B is a postprocessing filter that depends on the result of B. + * + * @return int + */ + int getRenderOrder() const { return m_renderOrder; } + + void setRenderOrder(int order); + + /** + * @brief Set the camera's pose. + * The pose is specified using the ViSP convention (Y-down right handed). + * + * @param wTc the new pose of the camera, in world frame + */ + virtual void setCameraPose(const vpHomogeneousMatrix &wTc); + + /** + * @brief Retrieve the camera's pose, in the world frame. + * The pose is specified using the ViSP convention (Y-down right handed). + */ + virtual vpHomogeneousMatrix getCameraPose(); + + /** + * @brief Set the pose of a node. This node can be any Panda object (light, mesh, camera). + * The pose is specified using the ViSP convention (Y-down right handed). + * + * @param name Node path to search for, from the render root. This is the object that will be modified See https://docs.panda3d.org/1.10/python/programming/scene-graph/searching-scene-graph + * @param wTo Pose of the object in the world frame + * + * \throws if the corresponding node cannot be found. + */ + virtual void setNodePose(const std::string &name, const vpHomogeneousMatrix &wTo); + + /** + * @brief Set the pose of a node. + * The pose is specified using the ViSP convention (Y-down right handed). + * This node can be any Panda object (light, mesh, camera). + * + * @param object The object for which to set the pose + * @param wTo Pose of the object in the world frame + */ + virtual void setNodePose(NodePath &object, const vpHomogeneousMatrix &wTo); + + /** + * @brief Get the pose of a Panda node, in world frame in the ViSP convention (Y-down right handed). + * + * @param name Node path to search for. \see setNodePose(const std::string &, const vpHomogeneousMatrix &) for more info + * @return wTo, the pose of the object in world frame + * \throws if no node can be found from the given path. + */ + virtual vpHomogeneousMatrix getNodePose(const std::string &name); + + /** + * @brief Get the pose of a Panda node, in world frame in the ViSP convention (Y-down right handed). This version of the method directly uses the Panda Nodepath. + */ + virtual vpHomogeneousMatrix getNodePose(NodePath &object); + + /** + * @brief Compute the near and far planes for the camera at the current pose, given a certain node/part of the graph. + * + * The near clipping value will be set to the distance to the closest point of the object. + * The far clipping value will be set to the distance to farthest vertex of the object. + * + * \warning Depending on geometry complexity, this may be an expensive operation. + * \warning if the object lies partly behind the camera, the near plane value will be zero. + * If it fully behind, the far plane will also be zero. If these near/far values are used to update the + * rendering parameters of the camera, this may result in an invalid projection matrix. + * + * @param name name of the node that should be used to compute near and far values. + * @param nearV resulting near clipping plane distance + * @param farV resulting far clipping plane distance + * @param fast Whether to use the axis align bounding box to compute the clipping planes. + * This is faster than reprojecting the full geometry in the camera frame + */ + void computeNearAndFarPlanesFromNode(const std::string &name, float &nearV, float &farV, bool fast); + + /** + * @brief Load a 3D object. To load an .obj file, Panda3D must be compiled with assimp support. + * + * Once loaded, the object will not be visible, it should be added to the scene. + * + * @param nodeName the name that will be used when inserting the node in the scene graph + * @param modelPath Path to the model file + * @return NodePath The NodePath containing the 3D model, which can now be added to the scene graph. + */ + NodePath loadObject(const std::string &nodeName, const std::string &modelPath); + + /** + * @brief Add a node to the scene. Its pose is set as the identity matrix + * + * @param object + */ + virtual void addNodeToScene(const NodePath &object); + + /** + * @brief Load and and an object to the scene + * + * @param name + * @param path + */ + virtual void addObjectToScene(const std::string &name, const std::string &path); + + /** + * @brief set whether vertical sync is enabled. + * When vertical sync is enabled, render speed will be limited by the display's refresh rate + * + * @param useVsync Whether to use vsync or not + */ + void setVerticalSyncEnabled(bool useVsync); + /** + * @brief Set the behaviour when a Panda3D assertion fails. If abort is true, the program will stop. + * Otherwise, an error will be displayed in the console. + * + * @param abort whether to abort (true) or display a message when an assertion fails. + */ + void setAbortOnPandaError(bool abort); + void enableDebugLog(); + + static vpColVector vispPointToPanda(const vpColVector &point); + static vpColVector vispVectorToPanda(const vpColVector &vec); + + void printStructure(); + + virtual PointerTo getMainOutputBuffer() { return nullptr; } + + virtual void enableSharedDepthBuffer(vpPanda3DBaseRenderer &sourceBuffer); + + static const vpHomogeneousMatrix &pandaToVisp(); + + static const vpHomogeneousMatrix &vispToPanda(); + + PointerTo &getWindowFramework() { return m_window; } + +protected: + +/** + * @brief Initialize the scene for this specific renderer. + * + * Creates a root scene for this node and applies shaders. that will be used for rendering + * + */ + virtual void setupScene(); + + /** + * @brief Initialize camera. Should be called when the scene root of this render has already been created. + * + */ + virtual void setupCamera(); + + /** + * @brief Initialize buffers and other objects that are required to save the render. + * + */ + virtual void setupRenderTarget() { } + + const static vpHomogeneousMatrix VISP_T_PANDA; //! Homogeneous transformation matrix to convert from the Panda coordinate system (right-handed Z-up) to the ViSP coordinate system (right-handed Y-Down) + const static vpHomogeneousMatrix PANDA_T_VISP; //! Inverse of VISP_T_PANDA + +protected: + std::string m_name; //! name of the renderer + int m_renderOrder; //! Rendering priority for this renderer and its buffers. A lower value will be rendered first. Should be used when calling make_output in setupRenderTarget() + PointerTo m_window; //! Pointer to owning window, which can create buffers etc. It is not necessarily visible. + vpPanda3DRenderParameters m_renderParameters; //! Rendering parameters + NodePath m_renderRoot; //! Node containing all the objects and the camera for this renderer + PointerTo m_camera; + NodePath m_cameraPath; //! NodePath of the camera + std::vector> m_buffers; //! Set of buffers that this renderer uses. This storage contains weak refs to those buffers and should not deallocate them. + bool m_isWindowOwner; // Whether this panda subrenderer is the "owner" of the window framework and should close all associated windows when getting destroyed +}; + +END_VISP_NAMESPACE +#endif //VISP_HAVE_PANDA3D +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DCommonFilters.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DCommonFilters.h new file mode 100644 index 00000000..07d2f2c0 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DCommonFilters.h @@ -0,0 +1,125 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#ifndef VP_PANDA3D_COMMON_FILTERS_H +#define VP_PANDA3D_COMMON_FILTERS_H + +#include + +#if defined(VISP_HAVE_PANDA3D) + +#include + + +BEGIN_VISP_NAMESPACE +class vpPanda3DRGBRenderer; + +/** + * \ingroup group_ar_renderer_panda3d_filters + * \brief Class that implements an RGB to grayscale conversion. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using Panda3D in Augmented Reality applications, you may have a look at:
+ * + * - \ref tutorial-panda3d + */ +class VISP_EXPORT vpPanda3DLuminanceFilter : public vpPanda3DPostProcessFilter +{ +public: + vpPanda3DLuminanceFilter(const std::string &name, std::shared_ptr inputRenderer, bool isOutput); + FrameBufferProperties getBufferProperties() const VP_OVERRIDE; + void getRender(vpImage &I) const; + +private: + static const std::string FRAGMENT_SHADER; +}; + +/** + * + * \ingroup group_ar_renderer_panda3d_filters + * \brief Class that implements a gaussian filter on a grayscale image. + * The grayscale image should be contained in the blue channel of the image. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using Panda3D in Augmented Reality applications, you may have a look at:
+ * + * - \ref tutorial-panda3d + */ +class VISP_EXPORT vpPanda3DGaussianBlur : public vpPanda3DPostProcessFilter +{ +public: + vpPanda3DGaussianBlur(const std::string &name, std::shared_ptr inputRenderer, bool isOutput); + FrameBufferProperties getBufferProperties() const VP_OVERRIDE; + void getRender(vpImage &I) const; + +private: + static const std::string FRAGMENT_SHADER; +}; + +/** + * \ingroup group_ar_renderer_panda3d_filters + * \brief Implementation of canny filtering, using Sobel kernels. + * + * The results of the canny are filtered based on a threshold value (defined between 0 and 255), checking whether there is enough gradient information. + * The output of this image is a floating RGB image containing: + * - In the red channel, the value of the convolution with the sobel horizontal kernel + * - In the green channel, the value of the convolution with the sobel vertical kernel + * - In the blue channel, the angle (in radians) of the edge normal. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using Panda3D in Augmented Reality applications, you may have a look at:
+ * + * - \ref tutorial-panda3d + */ +class VISP_EXPORT vpPanda3DCanny : public vpPanda3DPostProcessFilter +{ +public: + vpPanda3DCanny(const std::string &name, std::shared_ptr inputRenderer, bool isOutput, float edgeThreshold); + FrameBufferProperties getBufferProperties() const VP_OVERRIDE; + void getRender(vpImage &I) const; + void setEdgeThreshold(float edgeThreshold); + +protected: + void setupScene() VP_OVERRIDE; + +private: + static const std::string FRAGMENT_SHADER; + float m_edgeThreshold; +}; + +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DFrameworkManager.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DFrameworkManager.h new file mode 100644 index 00000000..8aa82f95 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DFrameworkManager.h @@ -0,0 +1,103 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#ifndef VP_PANDA3D_FRAMEWORK_MANAGER_H +#define VP_PANDA3D_FRAMEWORK_MANAGER_H + +#include + +#if defined(VISP_HAVE_PANDA3D) + +#include +#include + +class vpPanda3DBaseRenderer; + +BEGIN_VISP_NAMESPACE +/** + * \ingroup group_ar_renderer_panda3d + * + * \brief Base class for a panda3D renderer. This class handles basic functionalities, + * such as loading object, changing camera parameters. + * + * For a subclass to have a novel behaviour (e.g, display something else) These methods should be overriden: + * + * - setupScene: This is where you should apply your shaders. + * - setupCamera: This is where cameras are created and intrinsics parameters are applied + * - setupRenderTarget: This is where you should create the texture buffers, where the render results should be stored. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using Panda3D in Augmented Reality applications, you may have a look at:
+ * + * - \ref tutorial-panda3d +*/ +class VISP_EXPORT vpPanda3DFrameworkManager +{ +private: + + vpPanda3DFrameworkManager() : m_frameworkIsOpen(false) + { } + +public: + virtual ~vpPanda3DFrameworkManager() + { } + void initFramework(); + + void exit(); + + PandaFramework &getFramework() { return m_framework; } + + void enableSingleRenderer(vpPanda3DBaseRenderer &renderer); + + void registerDisabledWindow(PointerTo wf); + + void disableAllOtherRenderers(PointerTo &active); + + void enableAllRenderers(); + + static vpPanda3DFrameworkManager &getInstance() + { + static vpPanda3DFrameworkManager instance; + return instance; + } + +protected: + + PandaFramework m_framework; //! Panda Rendering framework + bool m_frameworkIsOpen; + std::vector> m_disabledWindows; + +}; + +END_VISP_NAMESPACE +#endif //VISP_HAVE_PANDA3D +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DGeometryRenderer.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DGeometryRenderer.h new file mode 100644 index 00000000..67806438 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DGeometryRenderer.h @@ -0,0 +1,115 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#ifndef VP_PANDA3D_GEOMETRY_RENDERER_H +#define VP_PANDA3D_GEOMETRY_RENDERER_H + +#include + +#if defined(VISP_HAVE_PANDA3D) +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/** + * \ingroup group_ar_renderer_panda3d_3d + * + * @brief Renderer that outputs object geometric information. + * + * This information may contain, depending on requested render type: + * + * - Normals in the world frame or in the camera frame. + * - Depth information + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using Panda3D in Augmented Reality applications, you may have a look at:
+ * + * - \ref tutorial-panda3d +*/ +class VISP_EXPORT vpPanda3DGeometryRenderer : public vpPanda3DBaseRenderer +{ +public: + + enum vpRenderType + { + OBJECT_NORMALS, //! Surface normals in the object frame. + CAMERA_NORMALS, //! Surface normals in the frame of the camera. Z points towards the camera and y is up. + }; + + vpPanda3DGeometryRenderer(vpRenderType renderType, bool fast = true); + + void beforeFrameRendered() VP_OVERRIDE; + + /** + * @brief Get render results into ViSP readable structures + * + * + * @param colorData Depending on the vpRenderType, normals in the world or camera frame may be stored in this image. + * @param depth Image used to store depth + */ + void getRender(vpImage &colorData, vpImage &depth) const; + + void getRender(vpImage &normals, vpImage &depth, const vpRect &bb, unsigned int h, unsigned w) const; + + /** + * @brief Get render results into ViSP readable structures. This version only retrieves the normal data + * @param colorData Depending on the vpRenderType, normals in the world or camera frame may be stored in this image. + */ + void getRender(vpImage &colorData) const; + /** + * @brief Get render results into ViSP readable structures. This version only retrieves the depth data. + * @param depth Depending on the vpRenderType, rendered depth may be stored in this image. + */ + void getRender(vpImage &depth) const; + + PointerTo getMainOutputBuffer() VP_OVERRIDE { return m_normalDepthBuffer; } + + bool isFastAndApproximateRendering() const { return m_fast; } + + vpRenderType getRenderType() const { return m_renderType; } + +protected: + void setupScene() VP_OVERRIDE; + void setupRenderTarget() VP_OVERRIDE; + +private: + vpRenderType m_renderType; + PointerTo m_normalDepthTexture; + PointerTo m_normalDepthBuffer; + //! Use fast approximate rendering. + bool m_fast; +}; +END_VISP_NAMESPACE +#endif //VISP_HAVE_PANDA3D +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DLight.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DLight.h new file mode 100644 index 00000000..f1d63cbd --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DLight.h @@ -0,0 +1,324 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#ifndef VP_PANDA3D_LIGHT_H +#define VP_PANDA3D_LIGHT_H + +#include + +#if defined(VISP_HAVE_PANDA3D) + +#include +#include +#include +#include + +#include "nodePath.h" +#include "ambientLight.h" +#include "directionalLight.h" +#include "pointLight.h" +#include "directionalLight.h" + +BEGIN_VISP_NAMESPACE +/** + * \ingroup group_ar_renderer_panda3d_lighting + * + * \brief Base class for a Light that can be added to a Panda3D scene. + * + * Note that modifying any object that inherits from this class + * after the method addToScene has been called * will not update the rendered light. + * + * \see https://docs.panda3d.org/1.10/cpp/programming/render-attributes/lighting + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using Panda3D in Augmented Reality applications, you may have a look at:
+ * + * - \ref tutorial-panda3d + * +*/ +class VISP_EXPORT vpPanda3DLight +{ +public: + /** + * \brief Build a new Panda3D light, given a unique name and an RGB color. + * + * + * \param name the name of the light: should be unique in the scene where the light will be added. + * \param color The color of the light: Each R,G,B component is unbounded and can exceed a value of 1 to increase its intensity. + */ + vpPanda3DLight(const std::string &name, const vpRGBf &color) : m_name(name), m_color(color) { } + + virtual ~vpPanda3DLight() = default; + + /** + * \brief Get the name of the light. + * + * This name should be unique and will be required when interacting with Panda3D to fetch the node. + */ + const std::string &getName() const { return m_name; } + /** + * \brief Get the light's color + * + * \return const vpRGBf& + */ + const vpRGBf &getColor() const { return m_color; } + + /** + * \brief Add the light to the scene. + * + * \param scene Scene where the light should be added. + */ + virtual void addToScene(NodePath &scene) const = 0; + +protected: + std::string m_name; //! Name of the light. Should be unique in the scene + vpRGBf m_color; //! RGB Color of the light. Can exceed 1 for each component. +}; + +/** + * + * \ingroup group_ar_renderer_panda3d_lighting + * + * \brief Class representing an ambient light. + * + * Ambient light are not physically possible, but are used to emulate light coming from all directions. + * They do not generate speculars or reflections. + * + * \see https://docs.panda3d.org/1.10/python/reference/panda3d.core.AmbientLight + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using Panda3D in Augmented Reality applications, you may have a look at:
+ * + * - \ref tutorial-panda3d + */ +class VISP_EXPORT vpPanda3DAmbientLight : public vpPanda3DLight +{ +public: + vpPanda3DAmbientLight(const std::string &name, const vpRGBf &color) : vpPanda3DLight(name, color) { } + + virtual ~vpPanda3DAmbientLight() = default; + + void addToScene(NodePath &scene) const VP_OVERRIDE + { + PT(AmbientLight) light = new AmbientLight(m_name); + light->set_color(LColor(m_color.R, m_color.G, m_color.B, 1)); + NodePath alnp = scene.attach_new_node(light); + scene.set_light(alnp); + } +}; + +/** + * \ingroup group_ar_renderer_panda3d_lighting + * + * \brief Class representing a Point Light. + * + * Point lights emit light all around them, from a single point. + * Their light can be subject to a distance-based attenuation. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using Panda3D in Augmented Reality applications, you may have a look at:
+ * + * - \ref tutorial-panda3d + */ +class VISP_EXPORT vpPanda3DPointLight : public vpPanda3DLight +{ +public: + /** + * \brief Build a new point light. + * + * \see vpPanda3DLight constructor. + * + * \param name name of the light + * \param color color of the light + * \param position Position in the scene of the light. Uses ViSP coordinates. + * \param attenuation Attenuation components of the light as a function of distance. + * Should be a vector of size 3 where the first component is the constant intensity factor (no falloff), + * the second is a linear falloff coefficient, and the last one is the quadratic falloff component. + * To follow the inverse square law, set this value vector to [0, 0, 1] + * To have no falloff, set it to [1, 0, 0]. + */ + vpPanda3DPointLight(const std::string &name, const vpRGBf &color, const vpColVector &position, const vpColVector &attenuation) + : vpPanda3DLight(name, color), m_attenuation(attenuation) + { + if (position.size() != 3) { + throw vpException(vpException::dimensionError, "Point light position must be a 3 dimensional vector"); + } + m_position.resize(4, false); + m_position.insert(0, position); + m_position[3] = 1.0; + if (attenuation.size() != 3) { + throw vpException(vpException::dimensionError, "Point light attenuation components must be a 3 dimensional vector"); + } + } + + virtual ~vpPanda3DPointLight() = default; + + void addToScene(NodePath &scene) const VP_OVERRIDE + { + PT(PointLight) light = new PointLight(m_name); + light->set_color(LColor(m_color.R, m_color.G, m_color.B, 1)); + light->set_attenuation(LVecBase3(m_attenuation[0], m_attenuation[1], m_attenuation[2])); + NodePath np = scene.attach_new_node(light); + //vpColVector posPanda = vpPanda3DBaseRenderer::vispPointToPanda(m_position); + np.set_pos(m_position[0], m_position[1], m_position[2]); + scene.set_light(np); + } + +private: + vpColVector m_position; //! Position of the light, in homogeneous coordinates + vpColVector m_attenuation; //! Attenuation components: [constant, linear, quadratic] +}; +/** + * + * + * \ingroup group_ar_renderer_panda3d_lighting + * \brief Class representing a directional light + * + * A directional light has no origin nor falloff. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using Panda3D in Augmented Reality applications, you may have a look at:
+ * + * - \ref tutorial-panda3d + */ +class VISP_EXPORT vpPanda3DDirectionalLight : public vpPanda3DLight +{ +public: + /** + * \brief Build a new directional light. + * + * \see vpPanda3DLight constructor. + * + * \param name name of the light + * \param color color of the light + * \param direction Position in the scene of the light. Uses ViSP coordinates. + */ + vpPanda3DDirectionalLight(const std::string &name, const vpRGBf &color, const vpColVector &direction) + : vpPanda3DLight(name, color), m_direction(direction) + { + if (m_direction.size() != 3) { + throw vpException(vpException::dimensionError, "Direction light direction must be a 3 dimensional vector"); + } + m_direction.normalize(); + } + + virtual ~vpPanda3DDirectionalLight() = default; + + void addToScene(NodePath &scene) const VP_OVERRIDE + { + PT(DirectionalLight) light = new DirectionalLight(m_name); + light->set_color(LColor(m_color.R, m_color.G, m_color.B, 1)); + vpColVector dir = vpPanda3DBaseRenderer::vispVectorToPanda(m_direction); + light->set_direction(LVector3f(m_direction[0], m_direction[1], m_direction[2])); + NodePath np = scene.attach_new_node(light); + scene.set_light(np); + } + +private: + vpColVector m_direction; //! Direction vector of the light, in scene frame +}; + +/** + * \ingroup group_ar_renderer_panda3d_lighting + * \brief Interface for objects, scenes or other Panda3D related data that can be lit by a vpPanda3DLight. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using Panda3D in Augmented Reality applications, you may have a look at:
+ * + * - \ref tutorial-panda3d + */ +class VISP_EXPORT vpPanda3DLightable +{ +public: + virtual ~vpPanda3DLightable() = default; + /** + * \brief Light this lightable object with a new light + * + * \param light + */ + virtual void addLight(const vpPanda3DLight &light) = 0; +}; +/** + * \ingroup group_ar_renderer_panda3d_lighting + * \brief Implementation of vpPanda3DLightable for a panda scene with a root node. + * + * The root node should be specified with setLightableScene by an inheriting implementation. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using Panda3D in Augmented Reality applications, you may have a look at:
+ * + * - \ref tutorial-panda3d + */ +class VISP_EXPORT vpPanda3DLightableScene : public vpPanda3DLightable +{ +public: + vpPanda3DLightableScene() : vpPanda3DLightable() + { } + + vpPanda3DLightableScene(NodePath &scene) : vpPanda3DLightable(), m_lightableScene(scene) + { } + + virtual ~vpPanda3DLightableScene() = default; + + /** + * \brief Add a light to the scene. All of the objects in the scene will be lit. + * + * \throws if the scene is not setup (setLightableScene or constructor with NodePath has not been called) + * \param light light to add + */ + void addLight(const vpPanda3DLight &light) VP_OVERRIDE + { + if (m_lightableScene.is_empty()) { + throw vpException(vpException::notInitialized, "Tried to add a light to a scene that is not initialized."); + } + light.addToScene(m_lightableScene); + } +protected: + void setLightableScene(NodePath &scene) { m_lightableScene = scene; } +private: + NodePath m_lightableScene; //! Scene that should be lit when calling addLight +}; + +END_VISP_NAMESPACE + +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DPostProcessFilter.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DPostProcessFilter.h new file mode 100644 index 00000000..4ee18073 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DPostProcessFilter.h @@ -0,0 +1,117 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#ifndef VP_PANDA3D_POST_PROCESS_FILTER_H +#define VP_PANDA3D_POST_PROCESS_FILTER_H + +#include + +#if defined(VISP_HAVE_PANDA3D) +#include +#include "cardMaker.h" +#include "orthographicLens.h" +#include "frameBufferProperties.h" + +BEGIN_VISP_NAMESPACE +/** + * \ingroup group_ar_renderer_panda3d_filters + * \brief Base class for postprocessing filters that map the result of a vpPanda3DBaseRenderer to a new image. + * + * Unlike 3D renderers, implementations of this class do not have access to 3D information + * (except if it is the result of the processed image). + * + * Implementation wise, the process is the following: + * + * - The output texture (retrieved through vpPanda3DBaseRenderer::getMainOutputBuffer) is blitted on a quad, + * that is placed perfectly in front of the camera. + * - A shader (given as an argument to the constructor) is applied to this quad. + * - The result is copied back to ram if required. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using Panda3D in Augmented Reality applications, you may have a look at:
+ * + * - \ref tutorial-panda3d +*/ +class VISP_EXPORT vpPanda3DPostProcessFilter : public vpPanda3DBaseRenderer +{ +public: + vpPanda3DPostProcessFilter(const std::string &name, std::shared_ptr inputRenderer, bool isOutput, std::string fragmentShader) + : vpPanda3DBaseRenderer(name), m_inputRenderer(inputRenderer), m_isOutput(isOutput), m_fragmentShader(fragmentShader) + { + m_renderOrder = m_inputRenderer->getRenderOrder() + 1; + } + + virtual ~vpPanda3DPostProcessFilter() = default; + + bool isRendering3DScene() const VP_OVERRIDE + { + return false; + } + + PointerTo getMainOutputBuffer() VP_OVERRIDE { return m_buffer; } + + void afterFrameRendered() VP_OVERRIDE + { + if (m_isOutput) { + vpPanda3DBaseRenderer::afterFrameRendered(); + } + } + +protected: + virtual void setupScene() VP_OVERRIDE; + + void setupCamera() VP_OVERRIDE; + + void setupRenderTarget() VP_OVERRIDE; + + void setRenderParameters(const vpPanda3DRenderParameters ¶ms) VP_OVERRIDE; + + void getRenderBasic(vpImage &I) const; + void getRenderBasic(vpImage &I) const; + + virtual FrameBufferProperties getBufferProperties() const = 0; + virtual PointerTo setupTexture(const FrameBufferProperties &fbp) const; + + std::shared_ptr m_inputRenderer; + bool m_isOutput; //! Whether this filter is an output to be used and should be copied to ram + std::string m_fragmentShader; + PointerTo m_shader; + PointerTo m_texture; + PointerTo m_buffer; + + static const std::string FILTER_VERTEX_SHADER; +}; + +END_VISP_NAMESPACE + +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DRGBRenderer.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DRGBRenderer.h new file mode 100644 index 00000000..b721371d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DRGBRenderer.h @@ -0,0 +1,127 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#ifndef VP_PANDA3D_RGB_RENDERER_H +#define VP_PANDA3D_RGB_RENDERER_H + +#include + +#if defined(VISP_HAVE_PANDA3D) + +#include +#include +#include + +#include "pointerTo.h" + +BEGIN_VISP_NAMESPACE +/** + * \ingroup group_ar_renderer_panda3d_3d + * \brief Implementation of a traditional RGB renderer in Panda3D + * + * The lighting model follows a Cook-torrance BRDF. + * + * For each object, a specific Version of the cook-torrance shader is compiled: diffuse textures are supported, but normal/bump/roughness maps are not. + * This class will try to automatically detect whether an object has RGB textures. + * + * Specular highlights and reflections can be ignored, depending on the value of isShowingSpeculars. + * + * \warning if an object is detected as having image textures but it actually doesn't have any, the object may appear washed out. + * + * \note Most of the tested objects were in BAM format, Panda3D's own format. + * The following pipeline was used: + * - Export to GLTF with Blender + * - In a Python environment, install gltf2bam with `pip install panda3d-gltf` + * - run gltf2bam path/to/yourObject.gltf path/to/yourObject.bam + * - then, in the code, use `renderer.addNodeToScene("/path/to/yourObject.bam");` + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using Panda3D in Augmented Reality applications, you may have a look at:
+ * + * - \ref tutorial-panda3d +*/ +class VISP_EXPORT vpPanda3DRGBRenderer : public vpPanda3DBaseRenderer, public vpPanda3DLightableScene +{ +public: + /** + * \brief Default constructor. Initialize an RGB renderer with the normal rendering behavior showing speculars + * + */ + vpPanda3DRGBRenderer() : vpPanda3DBaseRenderer("RGB"), m_showSpeculars(true), m_display2d(nullptr), m_backgroundTexture(nullptr) { } + /** + * \brief RGB renderer constructor allowing to specify + * whether specular highlights should be rendered or + * if only ambient/diffuse lighting should be considered. + * + * \param showSpeculars whether to render speculars + */ + vpPanda3DRGBRenderer(bool showSpeculars) : vpPanda3DBaseRenderer(showSpeculars ? "RGB" : "RGB-diffuse"), m_showSpeculars(showSpeculars) { } + + + /** + * @brief Store the render resulting from calling renderFrame() into a vpImage. + * + * If the image does not have the correct dimensions, it is resized. + * + * @param I The image in which to store the render. + */ + void getRender(vpImage &I) const; + + void addNodeToScene(const NodePath &object) VP_OVERRIDE; + + void setBackgroundImage(const vpImage &background); + + PointerTo getMainOutputBuffer() VP_OVERRIDE { return m_colorBuffer; } + + bool isShowingSpeculars() const { return m_showSpeculars; } + +protected: + void setupScene() VP_OVERRIDE; + void setupRenderTarget() VP_OVERRIDE; + + virtual std::string makeFragmentShader(bool hasTexture, bool specular); + +private: + bool m_showSpeculars; + PointerTo m_colorTexture; + PointerTo m_colorBuffer; + static const std::string COOK_TORRANCE_VERT; + static const std::string COOK_TORRANCE_FRAG; + + NodePath m_backgroundImage; + PointerTo m_display2d; + PointerTo m_backgroundTexture; +}; + +END_VISP_NAMESPACE +#endif //VISP_HAVE_PANDA3D +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DRenderParameters.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DRenderParameters.h new file mode 100644 index 00000000..8ad242f4 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DRenderParameters.h @@ -0,0 +1,136 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#ifndef VP_PANDA3D_RENDER_PARAMETERS_H +#define VP_PANDA3D_RENDER_PARAMETERS_H + +#include + +#if defined(VISP_HAVE_PANDA3D) +#include + +class Camera; + +BEGIN_VISP_NAMESPACE +/** + * @brief Rendering parameters for a panda3D simulation + * + * includes: + * - Camera intrinsics + * - Image resolution + * - Clipping parameters + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using Panda3D in Augmented Reality applications, you may have a look at:
+ * + * - \ref tutorial-panda3d +*/ +class VISP_EXPORT vpPanda3DRenderParameters +{ +public: + vpPanda3DRenderParameters() : m_cam(), m_height(0), m_width(0), m_clipNear(0.001), m_clipFar(10.0) { } + vpPanda3DRenderParameters(const vpCameraParameters &cam, unsigned int h, unsigned int w, + double clipNear, double clipFar) + : m_cam(cam), m_height(h), m_width(w), m_clipNear(clipNear), m_clipFar(clipFar) + { } + + /** + * @brief Retrieve camera intrinsics. + * + * @return const vpCameraParameters& + */ + const vpCameraParameters &getCameraIntrinsics() const { return m_cam; } + /** + * @brief set camera intrinsics. Only camera intrinsics for a lens without distortion are supported. + * \throws if camera intrinsics have a distortion model. + */ + void setCameraIntrinsics(const vpCameraParameters &cam) + { + if (cam.get_projModel() != vpCameraParameters::perspectiveProjWithoutDistortion) { + throw vpException(vpException::badValue, "Panda3D renderer: only lenses with no distortion are supported"); + } + m_cam = cam; + } + + double getNearClippingDistance() const { return m_clipNear; } + double getFarClippingDistance() const { return m_clipFar; } + + /** + * @brief Set the clipping distance. When a panda camera uses these render parameters, objects that are closer than "near" or further than "far" will be clipped. + * + * @param nearV near clipping distance + * @param farV far clipping distance + */ + void setClippingDistance(double nearV, double farV) + { + if (farV < nearV) { + std::swap(nearV, farV); + } + m_clipNear = nearV; + m_clipFar = farV; + } + + unsigned int getImageWidth() const { return m_width; } + unsigned int getImageHeight() const { return m_height; } + + /** + * @brief Set the image resolution. + * When this object is given to a vpPanda3DBaseRenderer, + * this will be the resolution of the renderer's output images. + * + * @param height vertical image resolution + * @param width horizontal image resolution + */ + void setImageResolution(unsigned int height, unsigned int width) + { + m_height = height; + m_width = width; + } + + /** + * @brief Update a Panda3D camera object to use this objects's parameters. + * + * @param camera the camera for which to update the rendering parameters + * + * \throws if getImageWidth() or getImageHeight() are equal to 0. + */ + void setupPandaCamera(Camera *camera); + +private: + vpCameraParameters m_cam; + unsigned int m_height, m_width; + double m_clipNear, m_clipFar; +}; + +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DRendererSet.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DRendererSet.h new file mode 100644 index 00000000..89c632c9 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpPanda3DRendererSet.h @@ -0,0 +1,239 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#ifndef VP_PANDA3D_RENDERER_SET_H +#define VP_PANDA3D_RENDERER_SET_H + +#include + +#if defined(VISP_HAVE_PANDA3D) + +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/** + * \ingroup group_ar_renderer_panda3d + * + * @brief Class that renders multiple datatypes, in a single pass. + * A renderer set contains multiple subrenderers, all inheriting from vpPanda3DBaseRenderer. + * The renderer set synchronizes all scene properties for the different subrenderers. This includes: + * * The camera properties (intrinsics, resolution) and extrinsics + * * The pose and properties of every object in the scene + * * The pose and properties of lights, for subrenderers that are defined as lightable. + * + * The overall usage workflow is the following: + * 1. Create vpPanda3DRendererSet instance + * 2. Create the subrenderers (e.g, vpPanda3DGeometryRenderer) + * 3. Add the subrenderers to the set with addSubRenderer() + * 4. Call renderFrame() on the renderer set. Each subrenderer now has its output computed and ready + * to be retrieved + * 5. Retrieve relevant outputs in ViSP format with something similar to + * \code + * rendererSet.getRenderer("MyRendererName").getRender(I) + * \endcode + * where `RendererType` is the relevant subclass of vpPanda3DBaseRenderer and `"MyRendererName"` + * its name (see vpPanda3DBaseRenderer::getName()) + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using Panda3D in Augmented Reality applications, you may have a look at:
+ * + * - \ref tutorial-panda3d +*/ +class VISP_EXPORT vpPanda3DRendererSet : public vpPanda3DBaseRenderer, public vpPanda3DLightable +{ +public: + vpPanda3DRendererSet(); + vpPanda3DRendererSet(const vpPanda3DRenderParameters &renderParameters); + + virtual ~vpPanda3DRendererSet() = default; + + /** + * @brief Initialize the framework and propagate the created panda3D framework to the subrenderers. + * + * The subrenderers will be initialized in the order of their priority as defined by vpPanda3DBaseRenderer::getRenderOrder + * Thus, if a renderer B depends on A for its render, and if B.getRenderOrder() > A.getRenderOrder() it can rely on A being initialized when B.initFromParent is called (along with the setupCamera, setupRenderTarget). + */ + void initFramework() VP_OVERRIDE; + void initFromParent(PointerTo window) VP_OVERRIDE; + void initFromParent(const vpPanda3DBaseRenderer &renderer) VP_OVERRIDE; + + + void clearScene() VP_OVERRIDE + { + for (std::shared_ptr renderer: m_subRenderers) { + renderer->clearScene(); + } + } + + /** + * @brief Set the pose of the camera, using the ViSP convention. This change is propagated to all subrenderers + * + * @param wTc Pose of the camera + */ + void setCameraPose(const vpHomogeneousMatrix &wTc) VP_OVERRIDE; + + /** + * @brief Retrieve the pose of the camera. As this renderer contains multiple other renderers. + * + * \warning It is assumed that all the sub renderers are synchronized (i.e., the setCameraPose of this renderer was called before calling this method). + * Otherwise, you may get incoherent results. + * + * @return the pose of the camera using the ViSP convention + */ + vpHomogeneousMatrix getCameraPose() VP_OVERRIDE; + + /** + * @brief Set the pose of an object for all the subrenderers. The pose is specified using the ViSP convention + * This method may fail if a subrenderer does not have a node with the given name. + * + * \warning This method may fail if a subrenderer does not have a node with the given name. It is assumed that the scenes are synchronized + * + * @param name + * @param wTo + */ + void setNodePose(const std::string &name, const vpHomogeneousMatrix &wTo) VP_OVERRIDE; + + /** + * @brief This method is not supported for this renderer type. Use the std::string version + * + * \throws vpException, as this method is not supported + * @param object + * @param wTo + */ + void setNodePose(NodePath &object, const vpHomogeneousMatrix &wTo) VP_OVERRIDE; + + /** + * @brief Retrieve the pose of a scene node. The pose is in the world frame, using a ViSP convention. + * + * \see the base method + * + * \warning It is assumed that all the sub renderers are synchronized (i.e., the setNodePose of this renderer was called before calling this method). + * Otherwise, you may get incoherent results. + * @param name name of the node + * @return vpHomogeneousMatrix the pose of the node in the world frame + */ + vpHomogeneousMatrix getNodePose(const std::string &name) VP_OVERRIDE; + + /** + * @brief This method is not supported for this renderer type. Use the std::string version + * + * \throws vpException, as this method is not supported + * @param object + */ + vpHomogeneousMatrix getNodePose(NodePath &object) VP_OVERRIDE; + + /** + * \warning This method is not supported and will throw + */ + void addNodeToScene(const NodePath &object) VP_OVERRIDE; + + virtual void setRenderParameters(const vpPanda3DRenderParameters ¶ms) VP_OVERRIDE; + + void addLight(const vpPanda3DLight &light) VP_OVERRIDE; + + /** + * @brief Add a new subrenderer: This subrenderer should have a unique name, not present in the set. + * + * \throws if the subrenderer's name is already present in the set. + * + * @param renderer the renderer to add + */ + void addSubRenderer(std::shared_ptr renderer); + + void enableSharedDepthBuffer(vpPanda3DBaseRenderer &sourceBuffer) VP_OVERRIDE; + + /** + * @brief Retrieve the first subrenderer with the specified template type. + * + * @tparam RendererType The type of the renderer to find + * @return std::shared_ptr Pointer to the first renderer match, nullptr if none is found. + */ + template + std::shared_ptr getRenderer() + { + for (std::shared_ptr &renderer: m_subRenderers) { + std::shared_ptr rendererCast = std::dynamic_pointer_cast(renderer); + if (rendererCast != nullptr) { + return rendererCast; + } + } + return nullptr; + } + + /** + * @brief Retrieve the subrenderer with the specified template type and the given name. + * + * @param name the name of the subrenderer to find + * @tparam RendererType The type of the renderer to find + * @return std::shared_ptr Pointer to the renderer, nullptr if none is found. + */ + template + std::shared_ptr getRenderer(const std::string &name) + { + for (std::shared_ptr &renderer: m_subRenderers) { + if (renderer->getName() == name) { + std::shared_ptr rendererCast = std::dynamic_pointer_cast(renderer); + if (rendererCast != nullptr) { + return rendererCast; + } + } + } + return nullptr; + } + + virtual void beforeFrameRendered() VP_OVERRIDE + { + for (std::shared_ptr &renderer: m_subRenderers) { + renderer->beforeFrameRendered(); + } + } + + virtual void afterFrameRendered() VP_OVERRIDE + { + for (std::shared_ptr &renderer: m_subRenderers) { + renderer->afterFrameRendered(); + } + } + +protected: + void setupScene() VP_OVERRIDE { } + + void setupCamera() VP_OVERRIDE { } + + std::vector> m_subRenderers; +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpSimulator.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpSimulator.h new file mode 100644 index 00000000..260bad9e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpSimulator.h @@ -0,0 +1,292 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Simulator based on Coin3d. + */ + +#ifndef VP_SIMULATOR_H +#define VP_SIMULATOR_H +/*! + \file vpSimulator.h + \brief Implementation of a simulator based on Coin3d (www.coin3d.org). + The simulator uses the vpViewer class. + + \warning The content of this file is only available if Coin3D and + one of the GUI (SoWin, SoXT, SoQt) are installed. +*/ +#include + +#ifdef VISP_HAVE_COIN3D_AND_GUI + +#include + +/* KNOWN ISSUE DEALING WITH X11 and QT + If you get a strange compiler error on the line with None, + it's probably because you're also including X11 headers, + which #define the symbol None. Put the X11 includes after + the Qt includes to solve this problem. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// visp +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpSimulator + + \ingroup group_ar_simulator + + \brief Implementation of a simulator based on Coin3d (www.coin3d.org). + + The simulator uses the vpViewer class. + + \warning This class is only available if Coin3D and one of the GUI + (SoWin, SoXT, SoQt) are installed. +*/ + +class VISP_EXPORT vpSimulator +{ +protected: + //! perform some initialization + void init(); + //! perform some destruction + void kill(); + +public: + //! constructor + vpSimulator(); + virtual ~vpSimulator(); + +protected: +//! main Widget +#if defined(VISP_HAVE_SOWIN) + HWND mainWindow; +#elif defined(VISP_HAVE_SOQT) + QWidget *mainWindow; +#elif defined(VISP_HAVE_SOXT) + Widget mainWindow; +#endif + + bool mainWindowInitialized; + + //! open the SoGui application + void initSoApplication(); + +public: + typedef enum { grayImage, colorImage } vpImageType; + vpImageType typeImage; + + GLubyte *image_background; + //! activate the mainloop + virtual void mainLoop(); + +protected: + //! view from the camera + vpViewer *internalView; + //! view from an external camera + vpViewer *externalView; + +public: + //! initialize the camera view + virtual void initInternalViewer(unsigned int nlig, unsigned int ncol); + //! initialize the external view + void initExternalViewer(unsigned int nlig, unsigned int ncol); + +protected: + //! thread with the main program + SbThread *mainThread; + // pthread_t mainThread; + +public: + //! begin the main program + void initApplication(void *(*start_routine)(void *)); + void initApplication(void *(*start_routine)(void *), void *data); + //! perform some initialization in the main program thread + void initMainApplication(); + void closeMainApplication(); + + //---------------------------------------------------- + // scene description +protected: + unsigned int internal_width; + unsigned int internal_height; + unsigned int external_width; + unsigned int external_height; + +public: + /*! + Return the width of the internal view. + + \return The width of the internal view. + */ + unsigned int getInternalWidth() const { return internal_width; } + /*! + Return the height of the internal view. + + \return The height of the internal view. + */ + unsigned int getInternalHeight() const { return internal_height; } + +protected: + //! root node of the scene : contains everything except stuff specific to + //! each view + SoSeparator *scene; + //! root node of the internal view + SoSeparator *internalRoot; + //! root node of the external view + SoSeparator *externalRoot; + + //! internal camera + SoPerspectiveCamera *internalCamera; + //! external camera + SoPerspectiveCamera *externalCamera; + + //! internal camera position + SoTransform *internalCameraPosition; + + //! external camera position + SoTransform *extrenalCameraPosition; + + //! representation of the camera in the external view + SoSeparator *internalCameraObject; + + //! initialize the scene graph + void initSceneGraph(); + + //! Add a new object in the scene graph ad a given location + void addObject(SoSeparator *object, const vpHomogeneousMatrix &fMo, SoSeparator *root); + +public: + //! Add a new object in the scene graph ad a given location + void addObject(SoSeparator *newObject, const vpHomogeneousMatrix &fMo); + +public: + //! display the scene (handle with care) + void redraw(); + //! load an iv file + void load(const char *file_name); + //! load an iv file, set the location of this scene + void load(const char *iv_filename, const vpHomogeneousMatrix &fMo); + //! save the scene in an iv file + void save(const char *name, bool binary = false); + + //! Add the representation of the absolute frame + void addAbsoluteFrame(float zoom = 1); + //! Add the representation of a frame + void addFrame(const vpHomogeneousMatrix &fMo, float zoom = 1); + //! set the size of the camera/frame + void setZoomFactor(float zoom); + +protected: + float zoomFactor; + //--------------------------------------------------- + // camera description +protected: + bool cameraPositionInitialized; + //! internal camera position + vpHomogeneousMatrix cMf; + //! internal camera parameters + vpCameraParameters internalCameraParameters; + //! internal camera parameters + vpCameraParameters externalCameraParameters; + +public: + //! set the camera position (from an homogeneous matrix) + void setCameraPosition(vpHomogeneousMatrix &cMf); + //! get the camera position (from an homogeneous matrix) + void getCameraPosition(vpHomogeneousMatrix &_cMf) { _cMf = cMf; } + //! modify the position of the camera in the scene graph + void moveInternalCamera(vpHomogeneousMatrix &cMf); + //! set internal camera parameters + void setInternalCameraParameters(vpCameraParameters &cam); + //! set external camera parameters + void setExternalCameraParameters(vpCameraParameters &cam); + //! get the external camera position + void getExternalCameraPosition(vpHomogeneousMatrix &cMf); + + //! get an Image of the internal view + void getInternalImage(vpImage &I); + //! get an Image of the internal view + void getInternalImage(vpImage &I); + /* --- Off screen rendering --- */ + + void changeZoomFactor(float zoom, int index); + +public: + typedef enum { INTERNAL, EXTERNAL } vpSimulatorViewType; +#ifdef VISP_HAVE_MODULE_IO + void write(const char *fileName); +#endif +protected: + SbTime *realtime; + SoOffscreenRenderer *offScreenRenderer; + void offScreenRendering(vpSimulatorViewType view = vpSimulator::EXTERNAL, int *width = nullptr, int *height = nullptr); + +public: + //! image of the internal view + unsigned char *bufferView; + + //! Flag to protect the read and write of the framebuffer (between the + //! simulator and the viewer). + int get; + +public: + //! get the image corresponding to the internal view + unsigned char *getBufferedOffScreenRenderer() { return bufferView; } + + //! get the size of the internal view + void getSizeInternalView(int &width, int &height); + + //! get the intrinsic parameters of the camera + void getCameraParameters(vpCameraParameters &cam) { cam = internalCameraParameters; } +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpSimulatorException.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpSimulatorException.h new file mode 100644 index 00000000..1f7b52d2 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpSimulatorException.h @@ -0,0 +1,88 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Exceptions that can be emitted by the simulator classes. + */ + +/*! + * \file vpSimulatorException.h + * \brief Error that can be emitted by the vpSimulator class and its derivatives + */ + +#ifndef VP_SIMULATOR_EXCEPTION_H +#define VP_SIMULATOR_EXCEPTION_H + +#include +#include + +#ifdef VISP_BUILD_DEPRECATED_FUNCTIONS +#include /* Classe std::ostream. */ +#include /* Classe string. */ + +BEGIN_VISP_NAMESPACE +/*! + * \class vpSimulatorException + * \brief Error that can be emitted by the vpSimulator class and its derivatives. +*/ +class VISP_EXPORT vpSimulatorException : public vpException +{ +public: + /*! + * Lists the possible error than can be emitted while calling + * vpSimulator member + */ + enum errorSimulatorCodeEnum + { + ioError, //!< I/O error + noFileNameError, //!< Filename error + notInitializedError, //!< Initialization error + windowSizeNotInitializedError, //!< Window size not initialized + badInitializationError //!< Initialization error + }; + +public: + /*! + * Constructor. + */ + vpSimulatorException(int id, const char *format, ...); + + /*! + * Constructor. + */ + vpSimulatorException(int id, const std::string &msg); + + /*! + * Constructor. + */ + VP_EXPLICIT vpSimulatorException(int id); +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpViewer.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpViewer.h new file mode 100644 index 00000000..f6d73975 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/ar/vpViewer.h @@ -0,0 +1,159 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Simulator based on Coin3d. + */ + +/*! + \file vpViewer.h + + Viewer used by the simulator. Under Windows, the viewer is based + either on SoWin or SoQt. Under Unix, the viewer is based on SoQt or + SoXt. + + \warning The content of this file is only available if Coin3D and + one of the GUI (SoWin, SoXT, SoQt) are installed. + +*/ + +#ifndef VP_VIEWER_H +#define VP_VIEWER_H + +#include + +#ifdef VISP_HAVE_COIN3D_AND_GUI + +#if defined(VISP_HAVE_SOWIN) + +#include +#include + +#elif defined(VISP_HAVE_SOQT) + +#include +#include + +#elif defined(VISP_HAVE_SOXT) + +#include +#include + +#endif + +// Coin stuff + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(VISP_HAVE_SOWIN) +#include +#elif defined(VISP_HAVE_SOQT) +#include +#elif defined(VISP_HAVE_SOXT) +#include +#endif + +// visp +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpViewer + \ingroup group_ar_simulator + + \brief Viewer used by the simulator. + + Under Windows, the viewer is based either on SoWin or SoQt. Under + Unix, the viewer is based on SoQt or SoXt. + + \warning This class is only available if Coin3D and one of the GUI + (SoWin, SoXT, SoQt) are installed. + +*/ + +class vpSimulator; + +#if defined(VISP_HAVE_SOWIN) +class VISP_EXPORT vpViewer : public SoWinExaminerViewer +#elif defined(VISP_HAVE_SOQT) +class VISP_EXPORT vpViewer : public SoQtExaminerViewer +#elif defined(VISP_HAVE_SOXT) +class VISP_EXPORT vpViewer : public SoXtExaminerViewer +#endif +{ + + friend class vpSimulator; + +public: + typedef enum { internalView, externalView } vpViewerType; +#if defined(VISP_HAVE_SOWIN) + vpViewer(HWND parent, vpSimulator *simu, vpViewerType type); +#elif defined(VISP_HAVE_SOQT) + vpViewer(QWidget *parent, vpSimulator *simu, vpViewerType type); +#elif defined(VISP_HAVE_SOXT) + vpViewer(Widget parent, vpSimulator *simu, vpViewerType type); +#endif + + virtual ~vpViewer(); + void resize(int x, int y, bool fixed = false); + virtual void actualRedraw(void); + +private: + vpViewerType viewerType; + vpSimulator *simu; + SbBool processSoEvent(const SoEvent *const event); +#if defined(VISP_HAVE_SOWIN) + static HWND init(const char *appname) { return SoWin::init(appname); } + static void mainLoop() { SoWin::mainLoop(); } + static void exitMainLoop() { SoWin::exitMainLoop(); } +#elif defined(VISP_HAVE_SOQT) + static QWidget *init(const char *appname) { return SoQt::init(appname); } + static void mainLoop() { SoQt::mainLoop(); } + static void exitMainLoop() { SoQt::exitMainLoop(); } +#elif defined(VISP_HAVE_SOXT) + static Widget init(const char *appname) { return SoXt::init(appname); } + static void mainLoop() { SoXt::mainLoop(); } + static void exitMainLoop() { SoXt::exitMainLoop(); } +#endif +}; +END_VISP_NAMESPACE +#endif // VISP_HAVE_COIN3D_AND_GUI + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/blob/vpDot.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/blob/vpDot.h new file mode 100644 index 00000000..433a5531 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/blob/vpDot.h @@ -0,0 +1,494 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Track a white dot. + */ + +/*! + \file vpDot.h + \brief Track a white dot +*/ + +#ifndef VP_DOT_H +#define VP_DOT_H + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifdef VISP_USE_MSVC +#pragma comment(linker, "/STACK:256000000") // Increase max recursion depth +#endif + +BEGIN_VISP_NAMESPACE + +/*! + * \class vpDot + * + * \ingroup module_blob + * + * \brief This tracker is meant to track a dot (connected pixels with same + * gray level) on a vpImage. + * + * The underground algorithm is based on a binarization of the image + * and a connex component segmentation to determine the dot + * characteristics (location, moments, size...). + * + * The following sample code shows how to grab images from a firewire camera, + * track a blob and display the tracking results. + * + * \code + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * #if defined(VISP_HAVE_DC1394) + * vpImage I; // Create a gray level image container + * vp1394TwoGrabber g(false); // Create a grabber based on libdc1394-2.x third party lib + * g.acquire(I); // Acquire an image + * + * #if defined(VISP_HAVE_X11) + * vpDisplayX d(I, 0, 0, "Camera view"); + * #endif + * vpDisplay::display(I); + * vpDisplay::flush(I); + * + * vpDot blob; + * blob.initTracking(I); + * blob.setGraphics(true); + * + * while(1) { + * g.acquire(I); // Acquire an image + * vpDisplay::display(I); + * blob.track(I); + * + * vpDisplay::flush(I); + * } + * #endif + * } + * \endcode + * + * \sa vpDot2 + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in blob tracking, you may have a look at:
+ * + * - \ref tutorial-tracking-blob +*/ +class VISP_EXPORT vpDot : public vpTracker +{ +public: + /*! \enum vpConnexityType + * Type of connexity 4, or 8. + */ + typedef enum + { + CONNEXITY_4, /*!< For a given pixel 4 neighbors are considered (left, + right, up, down) */ + CONNEXITY_8 /*!< For a given pixel 8 neighbors are considered (left, + right, up, down, and the 4 pixels located on the diagonal) */ + } vpConnexityType; + +#ifdef VISP_BUILD_DEPRECATED_FUNCTIONS +public: + static const unsigned int SPIRAL_SEARCH_SIZE; /*!< Spiral size for the dot search. */ + + double m00; /*!< Considering the general distribution moments for \f$ N \f$ + points defined by the relation \f$ m_{ij} = \sum_{h=0}^{N} + u_h^i v_h^j \f$, \f$ m_{00} \f$ is a zero order moment obtained + with \f$i = j = 0 \f$. + + \sa setComputeMoments() + */ + double m01; /*!< Considering the general distribution moments for \f$ N \f$ + points defined by the relation \f$ m_{ij} = \sum_{h=0}^{N} + u_h^i v_h^j \f$, \f$ m_{01} \f$ is a first order moment + obtained with \f$i = 0 \f$ and \f$ j = 1 \f$. + + \sa setComputeMoments() + */ + double m10; /*!< Considering the general distribution moments for \f$ N \f$ + points defined by the relation \f$ m_{ij} = \sum_{h=0}^{N} + u_h^i v_h^j \f$, \f$ m_{10} \f$ is a first order moment + obtained with \f$i = 1 \f$ and \f$ j = 0 \f$. + + \sa setComputeMoments() + */ + double m11; /*!< Considering the general distribution moments for \f$ N \f$ + points defined by the relation \f$ m_{ij} = \sum_{h=0}^{N} + u_h^i v_h^j \f$, \f$ m_{11} \f$ is a first order moment + obtained with \f$i = 1 \f$ and \f$ j = 1 \f$. + + \sa setComputeMoments() + */ + double m20; /*!< Considering the general distribution moments for \f$ N \f$ + points defined by the relation \f$ m_{ij} = \sum_{h=0}^{N} + u_h^i v_h^j \f$, \f$ m_{20} \f$ is a second order moment + obtained with \f$i = 2 \f$ and \f$ j = 0 \f$. + + \sa setComputeMoments() + */ + double m02; /*!< Considering the general distribution moments for \f$ N \f$ + points defined by the relation \f$ m_{ij} = \sum_{h=0}^{N} + u_h^i v_h^j \f$, \f$ m_{02} \f$ is a second order moment + obtained with \f$i = 0 \f$ and \f$ j = 2 \f$. + + \sa setComputeMoments() + */ + double mu11; /*!< \f$ \mu_{11} \f$ is a second order centered moment defined + by: \f$ \mu_{11} = m_{11} - \frac{m_{10}}{m_{00}}m_{01} \f$ + + \sa setComputeMoments() + */ + double mu20; /*!< \f$ \mu_{20} \f$ is a second order centered moment defined + by: \f$ \mu_{20} = m_{20} - \frac{m_{10}}{m_{00}}m_{10} \f$ + + \sa setComputeMoments() + */ + double mu02; /*!< \f$ \mu_{02} \f$ is a second order centered moment defined + by: \f$ \mu_{02} = m_{02} - \frac{m_{01}}{m_{00}}m_{01} \f$ + + \sa setComputeMoments() + */ +#endif + +public: + vpDot(); + VP_EXPLICIT vpDot(const vpImagePoint &ip); + vpDot(const vpDot &d); + virtual ~vpDot() VP_OVERRIDE; + + void display(const vpImage &I, vpColor color = vpColor::red, unsigned int thickness = 1) const; + + /*! + * Gets the second order normalized centered moment \f$ n_{ij} \f$ + * as a 3-dim vector containing \f$ n_{20}, n_{11}, n_{02} \f$ + * such as \f$ n_{ij} = \mu_{ij}/m_{00} \f$ + * + * \return The 3-dim vector containing \f$ n_{20}, n_{11}, n_{02} \f$. + * + * \sa getCog(), getArea() + */ + inline vpColVector get_nij() const + { + vpColVector nij(3); + const unsigned int index_0 = 0; + const unsigned int index_1 = 1; + const unsigned int index_2 = 2; + nij[index_0] = mu20 / m00; + nij[index_1] = mu11 / m00; + nij[index_2] = mu02 / m00; + + return nij; + } + + /*! + * Gets the area of the blob corresponding also to the zero order moment. + * + * \return The blob area. + */ + inline double getArea() const { return m00; } + + /*! + * Return the dot bounding box. + * + * \sa getWidth(), getHeight() + */ + inline vpRect getBBox() const + { + vpRect bbox; + + bbox.setRect(m_u_min, m_v_min, (m_u_max - m_u_min) + 1, (m_v_max - m_v_min) + 1); + + return bbox; + } + + /*! + * Return the location of the dot center of gravity. + * + * \return The coordinates of the center of gravity. + */ + inline vpImagePoint getCog() const { return m_cog; } + + /*! + * Return the list of all the image points on the border of the dot. + * + * \warning Doesn't return the image points inside the dot anymore. To get + * those points see getConnexities(). + */ + inline std::list getEdges() const { return m_ip_edges_list; } + + /*! + * Return the list of all the image points inside the dot. + * + * \return The list of all the images points in the dot. + * This list is updated after a call to track(). + */ + inline std::list getConnexities() const { return m_ip_connexities_list; } + + inline double getGamma() const { return m_gamma; } + + /*! + * Return the precision of the gray level of the dot. It is a double + * precision float witch value is in ]0,1]. 1 means full precision, whereas + * values close to 0 show a very bad precision. + */ + double getGrayLevelPrecision() const { return m_grayLevelPrecision; } + double getMaxDotSize() const { return m_maxDotSizePercentage; } + + /*! + * Return the mean gray level value of the dot. + */ + double getMeanGrayLevel() const { return m_mean_gray_level; } + + /*! + * \return a vpPolygon made from the edges of the dot. + */ + vpPolygon getPolygon() const { return (vpPolygon(m_ip_edges_list)); } + + /*! + * Return the width of the dot. + * + * \sa getHeight() + */ + inline unsigned int getWidth() const { return ((m_u_max - m_u_min) + 1); } + + /*! + * Return the width of the dot. + * + * \sa getHeight() + */ + inline unsigned int getHeight() const { return ((m_v_max - m_v_min) + 1); } + + void initTracking(const vpImage &I); + void initTracking(const vpImage &I, const vpImagePoint &ip); + void initTracking(const vpImage &I, const vpImagePoint &ip, unsigned int gray_level_min, + unsigned int gray_level_max); + + vpDot &operator=(const vpDot &d); + bool operator==(const vpDot &d) const; + bool operator!=(const vpDot &d) const; + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, vpDot &d); + + void print(std::ostream &os) { os << *this << std::endl; } + + /*! + * Initialize the dot center of gravity coordinates with \e cog. + */ + inline void setCog(const vpImagePoint &cog) { m_cog = cog; } + + /*! + * Activates the dot's moments computation. + * + * \param activate true, if you want to compute the moments. If false, + * moments are not computed. + * + * Computed moment are vpDot::m00, vpDot::m10, vpDot::m01, vpDot::m11, + * vpDot::m20, vpDot::m02 and second order centered moments vpDot::mu11, + * vpDot::mu20, vpDot::mu02 computed with respect to the blob centroid. + * + * The coordinates of the region's centroid (u, v) can be computed from the + * moments by \f$u=\frac{m10}{m00}\f$ and \f$v=\frac{m01}{m00}\f$. + */ + void setComputeMoments(bool activate) { m_compute_moment = activate; } + + /*! + * Set the type of connexity: 4 or 8. + */ + void setConnexity(const vpConnexityType &connexityType) { m_connexityType = connexityType; } + void setMaxDotSize(double percentage); + void setGrayLevelMin(const unsigned int &level_min) { m_gray_level_min = level_min; } + void setGrayLevelMax(const unsigned int &level_max) { m_gray_level_max = level_max; } + void setGrayLevelPrecision(const double &grayLevelPrecision); + + /*! + * Activates the display of all the pixels of the dot during the tracking. + * The default thickness of the overlayed drawings can be modified using + * setGraphicsThickness(). + * + * \warning To effectively display the dot graphics a call to + * vpDisplay::flush() is needed. + * + * \param activate true to activate the display of dot pixels, false to turn + * off the display. + * + * \sa setGraphicsThickness() + */ + + void setGraphics(bool activate) { m_graphics = activate; } + /*! + * Modify the default thickness that is set to 1 of the drawings in overlay + * when setGraphics() is enabled. + * + * \sa setGraphics() + */ + void setGraphicsThickness(unsigned int thickness) { m_thickness = thickness; } + + void track(const vpImage &I); + void track(const vpImage &I, vpImagePoint &ip); + + // Static Functions +public: + static void display(const vpImage &I, const vpImagePoint &cog, + const std::list &edges_list, vpColor color = vpColor::red, + unsigned int thickness = 1); + static void display(const vpImage &I, const vpImagePoint &cog, const std::list &edges_list, + vpColor color = vpColor::red, unsigned int thickness = 1); + +#ifndef VISP_BUILD_DEPRECATED_FUNCTIONS +private: + static const unsigned int SPIRAL_SEARCH_SIZE; /*!< Spiral size for the dot search. */ + + double m00; /*!< Considering the general distribution moments for \f$ N \f$ + points defined by the relation \f$ m_{ij} = \sum_{h=0}^{N} + u_h^i v_h^j \f$, \f$ m_{00} \f$ is a zero order moment obtained + with \f$i = j = 0 \f$. + + \sa setComputeMoments() + */ + double m01; /*!< Considering the general distribution moments for \f$ N \f$ + points defined by the relation \f$ m_{ij} = \sum_{h=0}^{N} + u_h^i v_h^j \f$, \f$ m_{01} \f$ is a first order moment + obtained with \f$i = 0 \f$ and \f$ j = 1 \f$. + + \sa setComputeMoments() + */ + double m10; /*!< Considering the general distribution moments for \f$ N \f$ + points defined by the relation \f$ m_{ij} = \sum_{h=0}^{N} + u_h^i v_h^j \f$, \f$ m_{10} \f$ is a first order moment + obtained with \f$i = 1 \f$ and \f$ j = 0 \f$. + + \sa setComputeMoments() + */ + double m11; /*!< Considering the general distribution moments for \f$ N \f$ + points defined by the relation \f$ m_{ij} = \sum_{h=0}^{N} + u_h^i v_h^j \f$, \f$ m_{11} \f$ is a first order moment + obtained with \f$i = 1 \f$ and \f$ j = 1 \f$. + + \sa setComputeMoments() + */ + double m20; /*!< Considering the general distribution moments for \f$ N \f$ + points defined by the relation \f$ m_{ij} = \sum_{h=0}^{N} + u_h^i v_h^j \f$, \f$ m_{20} \f$ is a second order moment + obtained with \f$i = 2 \f$ and \f$ j = 0 \f$. + + \sa setComputeMoments() + */ + double m02; /*!< Considering the general distribution moments for \f$ N \f$ + points defined by the relation \f$ m_{ij} = \sum_{h=0}^{N} + u_h^i v_h^j \f$, \f$ m_{02} \f$ is a second order moment + obtained with \f$i = 0 \f$ and \f$ j = 2 \f$. + + \sa setComputeMoments() + */ + double mu11; /*!< \f$ \mu_{11} \f$ is a second order centered moment defined + by: \f$ \mu_{11} = m_{11} - \frac{m_{10}}{m_{00}}m_{01} \f$ + + \sa setComputeMoments() + */ + double mu20; /*!< \f$ \mu_{20} \f$ is a second order centered moment defined + by: \f$ \mu_{20} = m_{20} - \frac{m_{10}}{m_{00}}m_{10} \f$ + + \sa setComputeMoments() + */ + double mu02; /*!< \f$ \mu_{02} \f$ is a second order centered moment defined + by: \f$ \mu_{02} = m_{02} - \frac{m_{01}}{m_{00}}m_{01} \f$ + + \sa setComputeMoments() + */ +#endif + +private: + //! internal use only + std::list m_ip_connexities_list; + + //! List of border points + std::list m_ip_edges_list; + + /*! Type of connexity + + \warning In previous version this variable was called connexity + */ + vpConnexityType m_connexityType; + + //! Coordinates of the point center of gravity + vpImagePoint m_cog; + + // Bounding box + unsigned int m_u_min, m_u_max, m_v_min, m_v_max; + + // Flag used to allow display + bool m_graphics; + + unsigned int m_thickness; // Graphics thickness + + double m_maxDotSizePercentage; + unsigned char m_gray_level_out; + + double m_mean_gray_level; // Mean gray level of the dot + unsigned int m_gray_level_min; // left threshold for binarization + unsigned int m_gray_level_max; // right threshold for binarization + double m_grayLevelPrecision; // precision of the gray level of the dot. + // It is a double precision float witch value is in ]0,1]. + // 1 means full precision, whereas values close to 0 show a very bad + // precision + double m_gamma; + //! flag : true moment are computed + bool m_compute_moment; + double m_nbMaxPoint; + + void init(); + void setGrayLevelOut(); + bool connexe(const vpImage &I, unsigned int u, unsigned int v, double &mean_value, + vpImagePoint &uv_cog, unsigned int &npoints); + bool connexe(const vpImage &I, unsigned int u, unsigned int v, double &mean_value, + vpImagePoint &uv_cog, unsigned int &npoints, std::vector &checkTab); + void COG(const vpImage &I, double &u, double &v); +}; + +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/blob/vpDot2.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/blob/vpDot2.h new file mode 100644 index 00000000..6c774ffa --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/blob/vpDot2.h @@ -0,0 +1,572 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Track a white dot. + */ + +/*! + \file vpDot2.h + \brief This tracker is meant to track some zones on a vpImage. +*/ + +#ifndef VP_DOT2_H +#define VP_DOT2_H + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE + +/*! + * \class vpDot2 + * + * \ingroup module_blob + * + * \brief This tracker is meant to track a blob (connex pixels with same + * gray level) on a vpImage. + * + * The underground algorithm is based on a binarization of the image + * and then on a contour detection using the Freeman chain coding to + * determine the blob characteristics (location, moments, size...). + * + * The binarization is done using gray level minimum and maximum values + * that define the admissible gray levels of the blob. You can specify these + * levels by setGrayLevelMin() and setGrayLevelMax(). These levels are also + * set automatically by setGrayLevelPrecision(). The algorithm allows + * to track white objects on a black background and vice versa. + * + * When a blob is found, some tests are done to see if it is valid: + * - A blob is considered by default as ellipsoid. The found blob could + * be rejected if the shape is not ellipsoid. To determine if the shape + * is ellipsoid the algorithm consider an inner and outside ellipse. + * Sampled points on these two ellipses should have the right gray levels. + * Along the inner ellipse the sampled points should have gray levels + * that are in the gray level minimum and maximum bounds, while + * on the outside ellipse, the gray levels should be out of the gray level + * bounds. To set the percentage of the sample points which should have the + * right levels use setEllipsoidBadPointsPercentage(). The distance between the + * inner ellipsoid and the blob contour, as well the distance between the + * blob contour and the outside ellipse is fixed by + * setEllipsoidShapePrecision(). If you want to track a non ellipsoid shape, + * and turn off this validation test, you have to call + * setEllipsoidShapePrecision(0). + * - The width, height and surface of the blob are compared to the + * corresponding values of the previous blob. If they differ to much + * the blob could be rejected. To set the admissible distance you can + * use setSizePrecision(). + * + * Note that track() and searchDotsInArea() are the most important features + * of this class. + * + * - track() estimate the current position of the dot using its previous + * position, then try to compute the new parameters of the dot. If everything + * went ok, tracking succeeds, otherwise we search this dot in a window + * around the last position of the dot. + * + * - searchDotsInArea() enable to find dots similar to this dot in a window. It + * is used when there was a problem performing basic tracking of the dot, but + * can also be used to find a certain type of dots in the full image. + * + * \sa vpDot + * + *

Tutorials & Examples

+ * + * Tutorials
+ * + * The following sample code, available in + * tutorial-blob-tracker-live.cpp and explained line-by-line in \ref tutorial-tracking-blob, + * shows how to grab images from a firewire camera, track a blob and display the tracking results. + * + * \include tutorial-blob-tracker-live.cpp + * + * This other example, available in tutorial-blob-auto-tracker.cpp + * and explained line-by-line in \ref tutorial-tracking-blob, section \ref tracking_blob_tracking, + * shows firstly how to detect in the first image all the blobs that match some + * characteristics in terms of size, area, gray level. Secondly, it shows how + * to track all the dots that are detected. + * + * \include tutorial-blob-auto-tracker.cpp + * +*/ +class VISP_EXPORT vpDot2 : public vpTracker +{ +public: + vpDot2(); + VP_EXPLICIT vpDot2(const vpImagePoint &ip); + vpDot2(const vpDot2 &twinDot); + + static vpMatrix defineDots(vpDot2 dot[], const unsigned int &n, const std::string &dotFile, vpImage &I, + vpColor col = vpColor::blue, bool trackDot = true); + + void display(const vpImage &I, vpColor color = vpColor::red, unsigned int thickness = 1) const; + + /*! + * Gets the second order normalized centered moment \f$ n_{ij} \f$ + * as a 3-dim vector containing \f$ n_{20}, n_{11}, n_{02} \f$ + * such as \f$ n_{ij} = \mu_{ij}/m_{00} \f$ + * + * \return The 3-dim vector containing \f$ n_{20}, n_{11}, n_{02} \f$. + * + * \sa getCog(), getArea() + */ + inline vpColVector get_nij() const + { + vpColVector nij(3); + const unsigned int index_0 = 0; + const unsigned int index_1 = 1; + const unsigned int index_2 = 2; + + nij[index_0] = mu20 / m00; + nij[index_1] = mu11 / m00; + nij[index_2] = mu02 / m00; + + return nij; + } + + double getArea() const; + + /*! + * Return the dot bounding box. + * + * \sa getWidth(), getHeight() + */ + inline vpRect getBBox() const + { + vpRect bbox; + + bbox.setRect(m_bbox_u_min, m_bbox_v_min, (m_bbox_u_max - m_bbox_u_min) + 1, + (m_bbox_v_max - m_bbox_v_min) + 1); + + return bbox; + } + + /*! + * Return the location of the dot center of gravity. + * + * \return The coordinates of the center of gravity. + */ + inline vpImagePoint getCog() const { return m_cog; } + + double getDistance(const vpDot2 &distantDot) const; + /*! + * Return the list of all the image points on the dot + * border. + * + * \param edges_list : The list of all the images points on the dot + * border. This list is update after a call to track(). + */ + void getEdges(std::list &edges_list) const { edges_list = m_ip_edges_list; } + + /*! + * Return the list of all the image points on the dot + * border. + * + * \return The list of all the images points on the dot + * border. This list is update after a call to track(). + */ + std::list getEdges() const { return m_ip_edges_list; } + + /*! + * Get the percentage of sampled points that are considered non conform + * in terms of the gray level on the inner and the outside ellipses. + * + * \sa setEllipsoidBadPointsPercentage() + */ + double getEllipsoidBadPointsPercentage() const { return m_allowedBadPointsPercentage; } + + double getEllipsoidShapePrecision() const; + void getFreemanChain(std::list &freeman_chain) const; + + inline double getGamma() const { return m_gamma; } + /*! + * Return the color level of pixels inside the dot. + * + * \sa getGrayLevelMax() + */ + inline unsigned int getGrayLevelMin() const { return m_gray_level_min; } + /*! + * Return the color level of pixels inside the dot. + * + * \sa getGrayLevelMin() + */ + inline unsigned int getGrayLevelMax() const { return m_gray_level_max; } + double getGrayLevelPrecision() const; + + double getHeight() const; + double getMaxSizeSearchDistPrecision() const; + + /*! + * \return The mean gray level value of the dot. + */ + double getMeanGrayLevel() const { return m_mean_gray_level; } + + /*! + * \return a vpPolygon made from the edges of the dot. + */ + vpPolygon getPolygon() const { return (vpPolygon(m_ip_edges_list)); } + double getSizePrecision() const; + double getWidth() const; + + void initTracking(const vpImage &I, unsigned int size = 0); + void initTracking(const vpImage &I, const vpImagePoint &ip, unsigned int size = 0); + void initTracking(const vpImage &I, const vpImagePoint &ip, unsigned int gray_lvl_min, + unsigned int gray_lvl_max, unsigned int size = 0); + + vpDot2 &operator=(const vpDot2 &twinDot); + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, vpDot2 &d); + + void print(std::ostream &os) { os << *this << std::endl; } + void searchDotsInArea(const vpImage &I, int area_u, int area_v, unsigned int area_w, + unsigned int area_h, std::list &niceDots); + + void searchDotsInArea(const vpImage &I, std::list &niceDots); + + void setArea(const double &area); + /*! + * Initialize the dot coordinates with \e ip. + */ + inline void setCog(const vpImagePoint &ip) { m_cog = ip; } + + /*! + * Activates the dot's moments computation. + * + * \param activate true, if you want to compute the moments. If false, + * moments are not computed. + * + * Computed moment are vpDot::m00, vpDot::m10, vpDot::m01, vpDot::m11, + * vpDot::m20, vpDot::m02 and second order centered moments vpDot::mu11, + * vpDot::mu20, vpDot::mu02 computed with respect to the blob centroid. + * + * The coordinates of the region's centroid (u, v) can be computed from the + * moments by \f$u=\frac{m10}{m00}\f$ and \f$v=\frac{m01}{m00}\f$. + */ + void setComputeMoments(bool activate) { m_compute_moment = activate; } + + /*! + * Set the percentage of sampled points that are considered non conform + * in terms of the gray level on the inner and the outside ellipses. + * Points located on the inner ellipse should have the same gray level + * than the blob, while points located on the outside ellipse should + * have a different gray level. + * + * \param percentage : Percentage of points sampled with bad gray level + * on the inner and outside ellipses that are admissible. 0 means + * that all the points should have a right level, while a value of 1 + * means that all the points can have a bad gray level. + */ + void setEllipsoidBadPointsPercentage(const double &percentage = 0.0) + { + if (percentage < 0.) { + m_allowedBadPointsPercentage = 0.; + } + else if (percentage > 1.) { + m_allowedBadPointsPercentage = 1.; + } + else { + m_allowedBadPointsPercentage = percentage; + } + } + + void setEllipsoidShapePrecision(const double &ellipsoidShapePrecision); + + /*! + * Activates the display of the border of the dot during the tracking. + * The default thickness of the overlayed drawings can be modified using + * setGraphicsThickness(). + * + * \warning To effectively display the dot graphics a call to + * vpDisplay::flush() is needed. + * + * \param activate If true, the border of the dot will be painted. false to + * turn off border painting. + * + * \sa setGraphicsThickness() + */ + void setGraphics(bool activate) { m_graphics = activate; } + + /*! + * Modify the default thickness that is set to 1 of the drawings in overlay + * when setGraphics() is enabled. + * + * \sa setGraphics() + */ + void setGraphicsThickness(unsigned int thickness) { m_thickness = thickness; } + + /*! + * Set the color level of the dot to search a dot in a region of interest. This + * level will be used to know if a pixel in the image belongs to the dot or + * not. Only pixels with higher level can belong to the dot. If the level is + * lower than the minimum level for a dot, set the level to MIN_IN_LEVEL. + * + * \param min : Color level of a dot to search in a region of interest. + * + * \sa setGrayLevelMax(), setGrayLevelPrecision() + */ + inline void setGrayLevelMin(const unsigned int &min) + { + const unsigned int val_max = 255; + if (min > val_max) { + m_gray_level_min = val_max; + } + else { + m_gray_level_min = min; + } + } + + /*! + * Set the color level of pixels surrounding the dot. This is meant to be used + * to search a dot in a region of interest. + * + * \param max : Intensity level of a dot to search in a region of interest. + * + * \sa setGrayLevelMin(), setGrayLevelPrecision() + */ + inline void setGrayLevelMax(const unsigned int &max) + { + const unsigned int val_max = 255; + if (max > val_max) { + m_gray_level_max = val_max; + } + else { + m_gray_level_max = max; + } + } + + void setGrayLevelPrecision(const double &grayLevelPrecision); + void setHeight(const double &height); + void setMaxSizeSearchDistPrecision(const double &maxSizeSearchDistancePrecision); + void setSizePrecision(const double &sizePrecision); + void setWidth(const double &width); + + void track(const vpImage &I, bool canMakeTheWindowGrow = true); + void track(const vpImage &I, vpImagePoint &cog, bool canMakeTheWindowGrow = true); + + static void trackAndDisplay(vpDot2 dot[], const unsigned int &n, vpImage &I, + std::vector &cogs, vpImagePoint *cogStar = nullptr); + + // Static funtions + static void display(const vpImage &I, const vpImagePoint &cog, + const std::list &edges_list, vpColor color = vpColor::red, + unsigned int thickness = 1); + static void display(const vpImage &I, const vpImagePoint &cog, const std::list &edges_list, + vpColor color = vpColor::red, unsigned int thickness = 1); + +#ifdef VISP_BUILD_DEPRECATED_FUNCTIONS +public: +#else +private: +#endif + double m00; /*!< Considering the general distribution moments for \f$ N \f$ + points defined by the relation \f$ m_{ij} = \sum_{h=0}^{N} + u_h^i v_h^j \f$, \f$ m_{00} \f$ is a zero order moment obtained + with \f$i = j = 0 \f$. This moment corresponds to the dot + surface. + + \sa setComputeMoments() + */ + double m10; /*!< Considering the general distribution moments for \f$ N \f$ + points defined by the relation \f$ m_{ij} = \sum_{h=0}^{N} + u_h^i v_h^j \f$, \f$ m_{10} \f$ is a first order moment + obtained with \f$i = 1 \f$ and \f$ j = 0 \f$. \f$ m_{10} \f$ + corresponds to the inertia first order moment along the v axis. + + \sa setComputeMoments() + */ + double m01; /*!< Considering the general distribution moments for \f$ N \f$ + points defined by the relation \f$ m_{ij} = \sum_{h=0}^{N} + u_h^i v_h^j \f$, \f$ m_{01} \f$ is a first order moment + obtained with \f$i = 0 \f$ and \f$ j = 1 \f$. \f$ m_{01} \f$ + corresponds to the inertia first order moment along the u axis. + + \sa setComputeMoments() + */ + double m11; /*!< Considering the general distribution moments for \f$ N \f$ + points defined by the relation \f$ m_{ij} = \sum_{h=0}^{N} + u_h^i v_h^j \f$, \f$ m_{11} \f$ is a first order moment + obtained with \f$i = 1 \f$ and \f$ j = 1 \f$. + + \sa setComputeMoments() + */ + double m20; /*!< Considering the general distribution moments for \f$ N \f$ + points defined by the relation \f$ m_{ij} = \sum_{h=0}^{N} + u_h^i v_h^j \f$, \f$ m_{20} \f$ is a second order moment + obtained with \f$i = 2 \f$ and \f$ j = 0 \f$. \f$ m_{20} \f$ + corresponds to the inertia second order moment along the v + axis. + + \sa setComputeMoments() + */ + double m02; /*!< Considering the general distribution moments for \f$ N \f$ + points defined by the relation \f$ m_{ij} = \sum_{h=0}^{N} + u_h^i v_h^j \f$, \f$ m_{02} \f$ is a second order moment + obtained with \f$i = 0 \f$ and \f$ j = 2 \f$. \f$ m_{02} \f$ + corresponds to the inertia second order moment along the u + axis. + + \sa setComputeMoments() + */ + double mu11; /*!< \f$ \mu_{11} \f$ is a second order centered moment defined + by: \f$ \mu_{11} = m_{11} - \frac{m_{10}}{m_{00}}m_{01} \f$ + + \sa setComputeMoments() + */ + double mu20; /*!< \f$ \mu_{20} \f$ is a second order centered moment defined + by: \f$ \mu_{20} = m_{20} - \frac{m_{10}}{m_{00}}m_{10} \f$ + + \sa setComputeMoments() + */ + double mu02; /*!< \f$ \mu_{02} \f$ is a second order centered moments defined + by: \f$ \mu_{02} = m_{02} - \frac{m_{01}}{m_{00}}m_{01} \f$ + + \sa setComputeMoments() + */ + +private: + virtual bool isValid(const vpImage &I, const vpDot2 &wantedDot); + + virtual bool hasGoodLevel(const vpImage &I, const unsigned int &u, const unsigned int &v) const; + virtual bool hasReverseLevel(const vpImage &I, const unsigned int &u, const unsigned int &v) const; + + virtual vpDot2 *getInstance(); + + void init(); + + bool computeParameters(const vpImage &I, const double &u = -1.0, const double &v = -1.0); + + bool findFirstBorder(const vpImage &I, const unsigned int &u, const unsigned int &v, + unsigned int &border_u, unsigned int &border_v); + void computeMeanGrayLevel(const vpImage &I); + + /*! + Get the starting point on a dot border. The dot border is + computed from this point. + * + \sa getFirstBorder_v() + */ + unsigned int getFirstBorder_u() const { return m_firstBorder_u; } + + /*! + Get the starting point on a dot border. The dot border is + computed from this point. + * + \sa getFirstBorder_u() + */ + unsigned int getFirstBorder_v() const { return m_firstBorder_v; } + + bool computeFreemanChainElement(const vpImage &I, const unsigned int &u, const unsigned int &v, + unsigned int &element); + void computeFreemanParameters(const int &u_p, const int &v_p, unsigned int &element, int &du, int &dv, float &dS, + float &dMu, float &dMv, float &dMuv, float &dMu2, float &dMv2); + void updateFreemanPosition(unsigned int &u, unsigned int &v, const unsigned int &dir); + + bool isInImage(const vpImage &I) const; + bool isInImage(const vpImage &I, const vpImagePoint &ip) const; + + bool isInArea(const unsigned int &u, const unsigned int &v) const; + + void getGridSize(unsigned int &gridWidth, unsigned int &gridHeight); + void setArea(const vpImage &I, int u, int v, unsigned int w, unsigned int h); + void setArea(const vpImage &I); + void setArea(const vpRect &a); + + unsigned char getMeanGrayLevel(vpImage &I) const; + + typedef struct vpSearchDotsInAreaGoodGermData + { + const vpImage &m_I; + const vpRect &m_area; + unsigned int &m_u; + unsigned int &m_v; + std::list &m_niceDots; + std::list &m_badDotsVector; + + vpSearchDotsInAreaGoodGermData(const vpImage &I, const vpRect &area, + unsigned int &u, unsigned int &v, + std::list &niceDots, std::list &badDotsVector) + : m_I(I) + , m_area(area) + , m_u(u) + , m_v(v) + , m_niceDots(niceDots) + , m_badDotsVector(badDotsVector) + { + + } + } vpSearchDotsInAreaGoodGermData; + + void searchDotsAreaGoodGerm(vpSearchDotsInAreaGoodGermData &data); + //! Coordinates (float) of the point center of gravity + vpImagePoint m_cog; + + double m_width; + double m_height; + double m_surface; + unsigned int m_gray_level_min; // minumum gray level for the dot. Pixel with lower level don't belong to this dot. + + unsigned int m_gray_level_max; // maximum gray level for the dot. Pixel with higher level don't belong to this dot. + double m_mean_gray_level; // Mean gray level of the dot + double m_grayLevelPrecision; + double m_gamma; + double m_sizePrecision; + double m_ellipsoidShapePrecision; + double m_maxSizeSearchDistPrecision; + double m_allowedBadPointsPercentage; + // Area where the dot is to search + vpRect m_area; + + // other + std::list m_direction_list; + std::list m_ip_edges_list; + + // flag + bool m_compute_moment; // true moment are computed + bool m_graphics; // true for graphic overlay display + + unsigned int m_thickness; // Graphics thickness + + // Bounding box + int m_bbox_u_min, m_bbox_u_max, m_bbox_v_min, m_bbox_v_max; + + // The first point coordinate on the dot border + unsigned int m_firstBorder_u; + unsigned int m_firstBorder_v; + +}; + +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpArray2D.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpArray2D.h new file mode 100644 index 00000000..eec47164 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpArray2D.h @@ -0,0 +1,1534 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * This class implements an 2D array as a template class. + */ +#ifndef VP_ARRAY2D_H +#define VP_ARRAY2D_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef VISP_HAVE_NLOHMANN_JSON +#include VISP_NLOHMANN_JSON(json.hpp) +#endif + +BEGIN_VISP_NAMESPACE +/*! + * \class vpArray2D + * \ingroup group_core_matrices + * + * \brief Implementation of a generic 2D array used as base class for matrices + * and vectors. + * + * This class implements a 2D array as a template class and all the basic + * functionalities common to matrices and vectors. More precisely: + * - concerning matrices, vpMatrix but also specific containers such as twist + * (vpVelocityTwistMatrix and vpForceTwistMatrix), homogeneous + * (vpHomogeneousMatrix), rotation (vpRotationMatrix) and homography + * (vpHomography) matrices inherit from vpArray2D. + * - concerning vectors, vpColVector, vpRowVector but also specific containers + * describing the pose (vpPoseVector) and the rotation (vpRotationVector) + * inherit also from vpArray2D. + * + * The code below shows how to create a 2-by-3 array of doubles, set the element values and access them: + * \code + * #include a(2, 3); + * a[0][0] = -1; a[0][1] = -2; a[0][2] = -3; + * a[1][0] = 4; a[1][1] = 5.5; a[1][2] = 6; + * + * std::cout << "a:" << std::endl; + * for (unsigned int i = 0; i < a.getRows(); ++i) { + * for (unsigned int j = 0; j < a.getCols(); ++j) { + * std::cout << a[i][j] << " "; + * } + * std::cout << std::endl; + * } + * } + * \endcode + * Once build, this previous code produces the following output: + * \code + * a: + * -1 -2 -3 + * 4 5.5 6 + * \endcode + * If ViSP is build with c++11 enabled, you can do the same using: + * \code + * #include a{ {-1, -2, -3}, {4, 5.5, 6.0f} }; + * std::cout << "a:\n" << a << std::endl; + * } + * \endcode + * The array could also be initialized using operator=(const std::initializer_list< std::initializer_list< Type > > &) + * \code + * #include a; + * a = { {-1, -2, -3}, {4, 5.5, 6.0f} }; + * } + * \endcode + * + * You can also use reshape() function: + * \code + * #include a{ -1, -2, -3, 4, 5.5, 6.0f }; + * a.reshape(2, 3); + * } + * \endcode +*/ +template class vpArray2D +{ +public: + //! Address of the first element of the data array + Type *data; + + /*! + * Basic constructor of a 2D array. + * Number of columns and rows are set to zero. + */ + vpArray2D() : data(nullptr), rowNum(0), colNum(0), rowPtrs(nullptr), dsize(0), isMemoryOwner(true), isRowPtrsOwner(true) { } + + /*! + Copy constructor of a 2D array. + */ + vpArray2D(const vpArray2D &A) + : +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher + vpArray2D() +#else + data(nullptr), rowNum(0), colNum(0), rowPtrs(nullptr), dsize(0), isMemoryOwner(true), isRowPtrsOwner(true) +#endif + { + resize(A.rowNum, A.colNum, false, false); + memcpy(data, A.data, static_cast(rowNum) * static_cast(colNum) * sizeof(Type)); + } + + /*! + Constructor that initializes a 2D array with 0. + + \param r : Array number of rows. + \param c : Array number of columns. + */ + vpArray2D(unsigned int r, unsigned int c) + : +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher + vpArray2D() +#else + data(nullptr), rowNum(0), colNum(0), rowPtrs(nullptr), dsize(0), isMemoryOwner(true), isRowPtrsOwner(true) +#endif + { + resize(r, c); + } + + /*! + Constructor that initialize a 2D array with \e val. + + \param r : Array number of rows. + \param c : Array number of columns. + \param val : Each element of the array is set to \e val. + */ + vpArray2D(unsigned int r, unsigned int c, Type val) + : +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher + vpArray2D() +#else + data(nullptr), rowNum(0), colNum(0), rowPtrs(nullptr), dsize(0), isMemoryOwner(true), isRowPtrsOwner(true) +#endif + { + resize(r, c, false, false); + *this = val; + } + + /*! + Constructor that initialize a 2D array from a std::vector. + + - When c = 0, create a colum vector with dimension (data.size, 1) + - When r = 0, create a row vector with dimension (1, data.size) + - Otherwise create an array with dimension (r, c) + + \param r : Array number of rows. + \param c : Array number of columns. + \param vec : Data used to initialize the 2D array. + */ + vpArray2D(const std::vector &vec, unsigned int r = 0, unsigned int c = 0) + : +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher + vpArray2D() +#else + data(nullptr), rowNum(0), colNum(0), rowPtrs(nullptr), dsize(0), isMemoryOwner(true), isRowPtrsOwner(true) +#endif + { + if ((r > 0) && (c > 0)) { + if ((r * c) != vec.size()) { + throw(vpException(vpException::dimensionError, + "Cannot initialize vpArray(%d, %d) from std::vector(%d). Wrong dimension", r, c, vec.size())); + } + resize(r, c, false, false); + } + else if ((c == 0) && (r == 0)) { + throw(vpException(vpException::dimensionError, + "Cannot initialize vpArray(%d, %d) from std::vector(%d). Using rows = 0 and cols = 0 is ambiguous", r, c, vec.size())); + } + else if (c == 0) { + if (r != vec.size()) { + throw(vpException(vpException::dimensionError, + "Cannot initialize vpArray(%d, %d) from std::vector(%d). Wrong dimension", r, c, vec.size())); + } + resize(static_cast(vec.size()), 1, false, false); + } + else if (r == 0) { + if (c != vec.size()) { + throw(vpException(vpException::dimensionError, + "Cannot initialize vpArray(%d, %d) from std::vector(%d). Wrong dimension", r, c, vec.size())); + } + resize(1, static_cast(vec.size()), false, false); + } + +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher + std::copy(vec.begin(), vec.end(), data); +#else + memcpy(data, vec.data(), vec.size() * sizeof(Type)); +#endif + } + +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher + vpArray2D(vpArray2D &&A) noexcept + { + rowNum = A.rowNum; + colNum = A.colNum; + rowPtrs = A.rowPtrs; + dsize = A.dsize; + data = A.data; + isMemoryOwner = A.isMemoryOwner; + isRowPtrsOwner = A.isRowPtrsOwner; + + A.rowNum = 0; + A.colNum = 0; + A.rowPtrs = nullptr; + A.dsize = 0; + A.data = nullptr; + } + + VP_EXPLICIT vpArray2D(const std::initializer_list &list) : vpArray2D() + { + resize(1, static_cast(list.size()), false, false); + std::copy(list.begin(), list.end(), data); + } + + VP_EXPLICIT vpArray2D(unsigned int nrows, unsigned int ncols, const std::initializer_list &list) + : data(nullptr), rowNum(0), colNum(0), rowPtrs(nullptr), dsize(0), isMemoryOwner(true), isRowPtrsOwner(true) + { + if ((nrows * ncols) != static_cast(list.size())) { + std::ostringstream oss; + oss << "Cannot create a vpArray2D of size (" << nrows << ", " << ncols << ") with a list of size " << list.size(); + throw vpException(vpException::dimensionError, oss.str()); + } + + resize(nrows, ncols, false, false); + std::copy(list.begin(), list.end(), data); + } + + VP_EXPLICIT vpArray2D(const std::initializer_list > &lists) : vpArray2D() + { + unsigned int nrows = static_cast(lists.size()), ncols = 0; + for (auto &l : lists) { + if (static_cast(l.size()) > ncols) { + ncols = static_cast(l.size()); + } + } + + resize(nrows, ncols, false, false); + auto it = lists.begin(); + for (unsigned int i = 0; i < rowNum; ++i, ++it) { + std::copy(it->begin(), it->end(), rowPtrs[i]); + } + } +#endif + + /** + * \brief Creates a view of the Matrix A. + * A view shares the same underlying memory as the original array. + * It can be written into, modifying the original data. However, the array cannot be resized. + * + * When you use this method, it is your responsibility to ensure that the lifespan of the view does not exceed the lifespan of the original array. + * + * \param A the array to view + * \return vpArray2D + */ + static vpArray2D view(const vpArray2D &A) + { + vpArray2D v; + v.rowNum = A.rowNum; + v.colNum = A.colNum; + v.rowPtrs = A.rowPtrs; + v.dsize = A.dsize; + v.data = A.data; + v.isMemoryOwner = false; + v.isRowPtrsOwner = false; + return v; + } + + /** + * \brief Create an array view of a raw data pointer. + * This data is not owned by the resulting array and should be freed after the array is destroyed (not before) + * + * \param data Pointer to the raw data + * \param numRows Number of rows + * \param numCols Number of columns + * \return vpArray2D + */ + static vpArray2D view(Type *data, unsigned int numRows, unsigned int numCols) + { + vpArray2D v; + vpArray2D::view(v, data, numRows, numCols); + return v; + } + + /** + * \brief Create an array view of a raw data pointer. + * After this function has been called, the array `data` can be modified through the view `v`. + * This data is not owned by the resulting array and should be freed after the array is destroyed (not before). + * + * \param v The resulting view array + * \param data Pointer to the raw data + * \param numRows Number of rows + * \param numCols Number of columns + */ + static void view(vpArray2D &v, Type *data, unsigned int numRows, unsigned int numCols) + { + unsigned int oldRows = v.rowNum; + unsigned int oldCols = v.colNum; + Type *oldData = v.data; + v.rowNum = numRows; + v.colNum = numCols; + v.dsize = numRows * numCols; + + if ((v.isMemoryOwner == true) && (v.data != nullptr)) { + free(v.data); + } + v.data = data; + v.isMemoryOwner = false; + bool requiresRowPtrRealloc = data != oldData || numRows != oldRows || numCols != oldCols; + + if (requiresRowPtrRealloc) { + if ((v.isRowPtrsOwner == true) && (v.rowPtrs != nullptr)) { + free(v.rowPtrs); + } + v.isRowPtrsOwner = true; + v.rowPtrs = reinterpret_cast(malloc(v.rowNum * sizeof(Type *))); + for (unsigned int i = 0; i < v.rowNum; ++i) { + v.rowPtrs[i] = data + i * v.colNum; + } + } + } + + + + /*! + * Destructor that deallocate memory. + */ + virtual ~vpArray2D() + { + if (data != nullptr) { + if (isMemoryOwner) { + free(data); + } + data = nullptr; + } + + if (rowPtrs != nullptr) { + if (isRowPtrsOwner) { + free(rowPtrs); + } + rowPtrs = nullptr; + } + rowNum = 0; + colNum = 0; + dsize = 0; + } + + /** @name Inherited functionalities from vpArray2D */ + //@{ + + /*! + * Return the number of columns of the 2D array. + * \sa getRows(), size() + */ + inline unsigned int getCols() const { return colNum; } + + Type getMaxValue() const; + + Type getMinValue() const; + + /*! + * Return the number of rows of the 2D array. + * \sa getCols(), size() + */ + inline unsigned int getRows() const { return rowNum; } + //! Return the number of elements of the 2D array. + inline unsigned int size() const { return colNum * rowNum; } + + /*! + Set the size of the array and initialize all the values to zero. + + \param nrows : number of rows. + \param ncols : number of column. + \param flagNullify : if true, then the array is re-initialized to 0 + after resize. If false, the initial values from the common part of the + array (common part between old and new version of the array) are kept. + Default value is true. + \param recopy_ : if true, will perform an explicit recopy of the old data. + */ + void resize(unsigned int nrows, unsigned int ncols, bool flagNullify = true, bool recopy_ = true) + { + if ((nrows == rowNum) && (ncols == colNum)) { + if (flagNullify && (this->data != nullptr)) { + memset(this->data, 0, this->dsize * sizeof(Type)); + } + } + else { + if (!isMemoryOwner) { + throw vpException(vpException::badValue, "Cannot resize an array that is a view of another array"); + } + bool recopy = (!flagNullify) && recopy_; // priority to flagNullify + bool colcond = (ncols != this->colNum) && (this->colNum > 0) && (ncols > 0); + const bool recopyNeeded = colcond && ((!flagNullify) || recopy); + Type *copyTmp = nullptr; + unsigned int rowTmp = 0, colTmp = 0; + + // Recopy case per case is required if number of cols has changed; + // structure of Type array is not the same in this case. + if (recopyNeeded && (this->data != nullptr)) { + copyTmp = new Type[this->dsize]; + memcpy(copyTmp, this->data, sizeof(Type) * this->dsize); + rowTmp = this->rowNum; + colTmp = this->colNum; + } + + // Reallocation of this->data array + this->dsize = nrows * ncols; + Type *tmp_data = reinterpret_cast(realloc(this->data, this->dsize * sizeof(Type))); + if (tmp_data) { + this->data = tmp_data; + } + else { + this->data = nullptr; + } + + if ((nullptr == this->data) && (0 != this->dsize)) { + if (copyTmp != nullptr) { + delete[] copyTmp; + } + throw(vpException(vpException::memoryAllocationError, "Memory allocation error when allocating 2D array data")); + } + + Type **tmp_rowPtrs = reinterpret_cast(realloc(this->rowPtrs, nrows * sizeof(Type *))); + if (tmp_rowPtrs) { + this->rowPtrs = tmp_rowPtrs; + } + else { + this->rowPtrs = nullptr; + } + if ((nullptr == this->rowPtrs) && (0 != this->dsize)) { + if (copyTmp != nullptr) { + delete[] copyTmp; + } + throw(vpException(vpException::memoryAllocationError, + "Memory allocation error when allocating 2D array rowPtrs")); + } + + // Update rowPtrs + + Type **t_ = rowPtrs; + for (unsigned int i = 0; i < dsize; i += ncols) { + *t_++ = this->data + i; + } + + this->rowNum = nrows; + this->colNum = ncols; + + // Recopy of this->data array values or nullify + if (flagNullify) { + // If dsize = 0 nothing to do + if ((nullptr != this->data) && (0 != this->dsize)) { + memset(this->data, 0, static_cast(this->dsize) * sizeof(Type)); + } + } + else if (recopyNeeded && (this->rowPtrs != nullptr)) { + // Recopy... + unsigned int minRow = (this->rowNum < rowTmp) ? this->rowNum : rowTmp; + unsigned int minCol = (this->colNum < colTmp) ? this->colNum : colTmp; + for (unsigned int i = 0; i < this->rowNum; ++i) { + for (unsigned int j = 0; j < this->colNum; ++j) { + if ((minRow > i) && (minCol > j)) { + (*this)[i][j] = copyTmp[(i * colTmp) + j]; + } + else { + (*this)[i][j] = 0; + } + } + } + } + + if (copyTmp != nullptr) { + delete[] copyTmp; + } + } + } + + void reshape(unsigned int nrows, unsigned int ncols) + { + if (dsize == 0) { + resize(nrows, ncols); + return; + } + + if ((nrows * ncols) != dsize) { + std::ostringstream oss; + oss << "Cannot reshape array of total size " << dsize << " into shape (" << nrows << ", " << ncols << ")"; + throw vpException(vpException::dimensionError, oss.str()); + } + + rowNum = nrows; + colNum = ncols; + if (rowPtrs) { + Type **tmp = reinterpret_cast(realloc(rowPtrs, nrows * sizeof(Type *))); + if (tmp) { + this->rowPtrs = tmp; + } + } + if (rowPtrs) { + // Update rowPtrs + Type **t_ = rowPtrs; + for (unsigned int i = 0; i < dsize; i += ncols) { + *t_++ = data + i; + } + } + } + + + /*! + Insert array A at the given position in the current array. + + \warning Throw vpException::dimensionError if the + dimensions of the matrices do not allow the operation. + + \param A : The array to insert. + \param r : The index of the row to begin to insert data. + \param c : The index of the column to begin to insert data. + */ + void insert(const vpArray2D &A, unsigned int r, unsigned int c) + { + if (((r + A.getRows()) <= rowNum) && ((c + A.getCols()) <= colNum)) { + if ((A.colNum == colNum) && (data != nullptr) && (A.data != nullptr) && (A.data != data)) { + memcpy(data + (r * colNum), A.data, sizeof(Type) * A.size()); + } + else if ((data != nullptr) && (A.data != nullptr) && (A.data != data)) { + unsigned int a_rows = A.getRows(); + for (unsigned int i = r; i < (r + a_rows); ++i) { + memcpy(data + (i * colNum) + c, A.data + ((i - r) * A.colNum), sizeof(Type) * A.colNum); + } + } + } + else { + throw vpException(vpException::dimensionError, "Cannot insert (%dx%d) array in (%dx%d) array at position (%d,%d)", + A.getRows(), A.getCols(), rowNum, colNum, r, c); + } + } + + /*! + Equal to comparison operator of a 2D array. + */ + bool operator==(const vpArray2D &A) const; + /*! + Not equal to comparison operator of a 2D array. + */ + bool operator!=(const vpArray2D &A) const; + + //! Set all the elements of the array to \e x. + vpArray2D &operator=(Type x) + { + std::fill(data, data + dsize, x); + return *this; + } + + /*! + Copy operator of a 2D array. + */ + vpArray2D &operator=(const vpArray2D &A) + { + resize(A.rowNum, A.colNum, false, false); + if ((data != nullptr) && (A.data != nullptr) && (data != A.data)) { + memcpy(data, A.data, static_cast(rowNum) * static_cast(colNum) * sizeof(Type)); + } + return *this; + } + +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher + vpArray2D &operator=(vpArray2D &&other) noexcept + { + if (this != &other) { + if (isMemoryOwner && data) { + free(data); + } + if (isRowPtrsOwner && rowPtrs) { + free(rowPtrs); + } + + rowNum = other.rowNum; + colNum = other.colNum; + rowPtrs = other.rowPtrs; + dsize = other.dsize; + data = other.data; + isMemoryOwner = other.isMemoryOwner; + isRowPtrsOwner = other.isRowPtrsOwner; + + other.rowNum = 0; + other.colNum = 0; + other.rowPtrs = nullptr; + other.dsize = 0; + other.data = nullptr; + } + + return *this; + } + + vpArray2D &operator=(const std::initializer_list &list) + { + if (dsize != static_cast(list.size())) { + resize(1, static_cast(list.size()), false, false); + } + std::copy(list.begin(), list.end(), data); + + return *this; + } + + vpArray2D &operator=(const std::initializer_list > &lists) + { + unsigned int nrows = static_cast(lists.size()), ncols = 0; + for (auto &l : lists) { + if (static_cast(l.size()) > ncols) { + ncols = static_cast(l.size()); + } + } + + resize(nrows, ncols, false, false); + auto it = lists.begin(); + for (unsigned int i = 0; i < rowNum; ++i, ++it) { + std::copy(it->begin(), it->end(), rowPtrs[i]); + } + + return *this; + } + +#ifdef VISP_HAVE_NLOHMANN_JSON + vpArray2D &operator=(const nlohmann::json &j) = delete; +#endif +#endif + + //! Set element \f$A_{ij} = x\f$ using A[i][j] = x + inline Type *operator[](unsigned int i) { return rowPtrs[i]; } + //! Get element \f$x = A_{ij}\f$ using x = A[i][j] + inline Type *operator[](unsigned int i) const { return rowPtrs[i]; } + + /*! + \relates vpArray2D + Writes the given array to the output stream and returns a reference to the + output stream. + */ + friend std::ostream &operator<<(std::ostream &s, const vpArray2D &A) + { + if ((A.data == nullptr) || (A.size() == 0)) { + return s; + } + std::ios_base::fmtflags original_flags = s.flags(); + const unsigned int precision = 10; + s.precision(precision); + unsigned int a_rows = A.getRows(); + unsigned int a_cols = A.getCols(); + for (unsigned int i = 0; i < a_rows; ++i) { + for (unsigned int j = 0; j < (a_cols - 1); ++j) { + s << A[i][j] << " "; + } + // We don't add " " after the last row element + s << A[i][a_cols - 1]; + // We don't add a \n char on the end of the last array line + if (i < (a_rows - 1)) { + s << std::endl; + } + } + + s.flags(original_flags); // restore s to standard state + + return s; + } + + vpArray2D hadamard(const vpArray2D &m) const; + + /** + * \brief Compute the transpose of the array + * + * @return vpArray2D C = A^T + */ + vpArray2D t() const; + //@} + + //--------------------------------- + // Inherited array I/O Static Public Member Functions + //--------------------------------- + /** @name Inherited I/O from vpArray2D with Static Public Member Functions */ + //@{ + /*! + Load a matrix from a file. + + \param filename : Absolute file name. + \param A : Array to be loaded + \param binary : If true the matrix is loaded from a binary file, else from + a text file. \param header : Header of the file is loaded in this + parameter. + + \return Returns true if success. + + \sa save() + */ + static bool load(const std::string &filename, vpArray2D &A, bool binary = false, char *header = nullptr) + { + std::fstream file; + + if (!binary) { + file.open(filename.c_str(), std::fstream::in); + } + else { + file.open(filename.c_str(), std::fstream::in | std::fstream::binary); + } + + if (!file) { + file.close(); + return false; + } + + if (!binary) { + std::string h; + bool headerIsDecoded = false; + do { + std::streampos pos = file.tellg(); + char line[FILENAME_MAX]; + file.getline(line, FILENAME_MAX); + std::string prefix("# "); + std::string line_(line); + if (line_.compare(0, prefix.size(), prefix.c_str()) == 0) { + // Line is a comment + // If we are not on the first line, we should add "\n" to the end of + // the previous line + if (pos) { + h += "\n"; + } + h += line_.substr(prefix.size()); // Remove "# " + } + else { + // rewind before the line + file.seekg(pos, file.beg); + headerIsDecoded = true; + } + } while (!headerIsDecoded); + + if (header != nullptr) { +#if defined(__MINGW32__) || \ + !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX + snprintf(header, h.size() + 1, "%s", h.c_str()); +#else + _snprintf_s(header, h.size() + 1, _TRUNCATE, "%s", h.c_str()); +#endif + } + + unsigned int rows, cols; + file >> rows; + file >> cols; + + if ((rows >= std::numeric_limits::max()) || (cols >= std::numeric_limits::max())) { + throw vpException(vpException::badValue, "Array exceed the max size."); + } + + A.resize(rows, cols); + + Type value; + for (unsigned int i = 0; i < rows; ++i) { + for (unsigned int j = 0; j < cols; ++j) { + file >> value; + A[i][j] = value; + } + } + } + else { + char c = '0'; + std::string h; + // Decode header until '\0' char that ends the header string + while (c != '\0') { + file.read(&c, 1); + h += c; + } + if (header != nullptr) { +#if defined(__MINGW32__) || \ + !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX + snprintf(header, h.size() + 1, "%s", h.c_str()); +#else + _snprintf_s(header, h.size() + 1, _TRUNCATE, "%s", h.c_str()); +#endif + } + + unsigned int rows, cols; + file.read(reinterpret_cast(&rows), sizeof(unsigned int)); + file.read(reinterpret_cast(&cols), sizeof(unsigned int)); + A.resize(rows, cols); + + Type value; + for (unsigned int i = 0; i < rows; ++i) { + for (unsigned int j = 0; j < cols; ++j) { + file.read(reinterpret_cast(&value), sizeof(Type)); + A[i][j] = value; + } + } + } + + file.close(); + return true; + } + + /*! + Load an array from a YAML-formatted file. + + \param filename : absolute file name. + \param A : array to be loaded from the file. + \param header : header of the file is loaded in this parameter. + + \return Returns true on success. + + \sa saveYAML() + */ + static bool loadYAML(const std::string &filename, vpArray2D &A, char *header = nullptr) + { + std::fstream file; + + file.open(filename.c_str(), std::fstream::in); + + if (!file) { + file.close(); + return false; + } + + unsigned int rows = 0, cols = 0; + std::string h; + std::string line, subs; + bool inheader = true; + unsigned int i = 0, j; + unsigned int lineStart = 0; + + while (getline(file, line)) { + if (inheader) { + const std::string str_rows("rows:"); + const std::string str_cols("cols:"); + const std::string str_data("data:"); + if ((rows == 0) && (line.compare(0, str_rows.size(), str_rows.c_str()) == 0)) { + std::stringstream ss(line); + ss >> subs; + ss >> rows; + } + else if ((cols == 0) && (line.compare(0, str_cols.size(), str_cols.c_str()) == 0)) { + std::stringstream ss(line); + ss >> subs; + ss >> cols; + } + else if (line.compare(0, str_data.size(), str_data.c_str()) == 0) { + inheader = false; + } + else { + h += line + "\n"; + } + } + else { + // if i == 0, we just got out of the header: initialize matrix + // dimensions + if (i == 0) { + if ((rows == 0) || (cols == 0)) { + file.close(); + return false; + } + A.resize(rows, cols); + // get indentation level which is common to all lines + lineStart = static_cast(line.find("[")) + 1; + } + std::stringstream ss(line.substr(lineStart, line.find("]") - lineStart)); + j = 0; + while (getline(ss, subs, ',')) { + A[i][j++] = static_cast(atof(subs.c_str())); + } + ++i; + } + } + + if (header != nullptr) { + std::string h_ = h.substr(0, h.size() - 1); // Remove last '\n' char +#if defined(__MINGW32__) || \ + !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX + snprintf(header, h_.size() + 1, "%s", h_.c_str()); +#else + _snprintf_s(header, h_.size() + 1, _TRUNCATE, "%s", h_.c_str()); +#endif + } + + file.close(); + return true; + } + + /*! + Save a matrix to a file. + + \param filename : Absolute file name. + \param A : Array to be saved. + \param binary : If true the matrix is saved in a binary file, else a text + file. \param header : Optional line that will be saved at the beginning of + the file. + + \return Returns true if success. + + Warning : If you save the matrix as in a text file the precision is + less than if you save it in a binary file. + + \sa load() + */ + static bool save(const std::string &filename, const vpArray2D &A, bool binary = false, const char *header = "") + { + std::fstream file; + + if (!binary) { + file.open(filename.c_str(), std::fstream::out); + } + else { + file.open(filename.c_str(), std::fstream::out | std::fstream::binary); + } + + if (!file) { + file.close(); + return false; + } + + if (!binary) { + unsigned int i = 0; + file << "# "; + while (header[i] != '\0') { + file << header[i]; + if (header[i] == '\n') { + file << "# "; + } + ++i; + } + file << std::endl; + file << A.getRows() << "\t" << A.getCols() << std::endl; + file << A << std::endl; + } + else { + int headerSize = 0; + while (header[headerSize] != '\0') { + ++headerSize; + } + file.write(header, static_cast(headerSize)+static_cast(1)); + unsigned int matrixSize; + matrixSize = A.getRows(); + file.write(reinterpret_cast(&matrixSize), sizeof(unsigned int)); + matrixSize = A.getCols(); + file.write(reinterpret_cast(&matrixSize), sizeof(unsigned int)); + Type value; + unsigned int a_rows = A.getRows(); + unsigned int a_cols = A.getCols(); + for (unsigned int i = 0; i < a_rows; ++i) { + for (unsigned int j = 0; j < a_cols; ++j) { + value = A[i][j]; + file.write(reinterpret_cast(&value), sizeof(Type)); + } + } + } + + file.close(); + return true; + } + + /*! + Save an array in a YAML-formatted file. + + \param filename : absolute file name. + \param A : array to be saved in the file. + \param header : optional lines that will be saved at the beginning of the + file. Should be YAML-formatted and will adapt to the indentation if any. + + \return Returns true if success. + + Here is an example of outputs. + \code + vpArray2D M(3,4); + vpArray2D::saveYAML("matrix.yml", M, "example: a YAML-formatted header"); + vpArray2D::saveYAML("matrixIndent.yml", M, "example:\n - a YAML-formatted \ + header\n - with inner indentation"); + \endcode + Content of matrix.yml: + \code + example: a YAML-formatted header + rows: 3 + cols: 4 + data: + - [0, 0, 0, 0] + - [0, 0, 0, 0] + - [0, 0, 0, 0] + \endcode + Content of matrixIndent.yml: + \code + example: + - a YAML-formatted header + - with inner indentation + rows: 3 + cols: 4 + data: + - [0, 0, 0, 0] + - [0, 0, 0, 0] + - [0, 0, 0, 0] + \endcode + + \sa loadYAML() + */ + static bool saveYAML(const std::string &filename, const vpArray2D &A, const char *header = "") + { + std::fstream file; + + file.open(filename.c_str(), std::fstream::out); + + if (!file) { + file.close(); + return false; + } + + unsigned int i = 0; + bool inIndent = false; + std::string indent = ""; + bool checkIndent = true; + while (header[i] != '\0') { + file << header[i]; + if (checkIndent) { + if (inIndent) { + if (header[i] == ' ') { + indent += " "; + } + else if (indent.length() > 0) { + checkIndent = false; + } + } + if ((header[i] == '\n') || (inIndent && (header[i] == ' '))) { + inIndent = true; + } + else { + inIndent = false; + } + } + ++i; + } + + if (i != 0) { + file << std::endl; + } + file << "rows: " << A.getRows() << std::endl; + file << "cols: " << A.getCols() << std::endl; + + if (indent.length() == 0) { + indent = " "; + } + + file << "data: " << std::endl; + unsigned int j; + unsigned int a_rows = A.getRows(); + unsigned int a_cols = A.getCols(); + for (i = 0; i < a_rows; ++i) { + file << indent << "- ["; + for (j = 0; j < (a_cols - 1); ++j) { + file << A[i][j] << ", "; + } + file << A[i][j] << "]" << std::endl; + } + + file.close(); + return true; + } +#ifdef VISP_HAVE_NLOHMANN_JSON + //template + template + friend void from_json(const nlohmann::json &j, vpArray2D &array); + //template + template + friend void to_json(nlohmann::json &j, const vpArray2D &array); +#endif + + /*! + Perform a 2D convolution similar to Matlab conv2 function: \f$ M \star kernel \f$. + + \param M : First matrix. + \param kernel : Second matrix. + \param mode : Convolution mode: "full" (default), "same", "valid". + + \image html vpMatrix-conv2-mode.jpg "Convolution mode: full, same, valid (image credit: Theano doc)." + + \note This is a very basic implementation that does not use FFT. + */ + static vpArray2D conv2(const vpArray2D &M, const vpArray2D &kernel, const std::string &mode); + + /*! + Perform a 2D convolution similar to Matlab conv2 function: \f$ M \star kernel \f$. + + \param M : First array. + \param kernel : Second array. + \param res : Result. + \param mode : Convolution mode: "full" (default), "same", "valid". + + \image html vpMatrix-conv2-mode.jpg "Convolution mode: full, same, valid (image credit: Theano doc)." + + \note This is a very basic implementation that does not use FFT. + */ + static void conv2(const vpArray2D &M, const vpArray2D &kernel, vpArray2D &res, const std::string &mode); + + /*! + Insert array B in array A at the given position. + + \param A : Main array. + \param B : Array to insert. + \param r : Index of the row where to add the array. + \param c : Index of the column where to add the array. + \return Array with B insert in A. + + \warning Throw exception if the sizes of the arrays do not allow the + insertion. + */ + vpArray2D insert(const vpArray2D &A, const vpArray2D &B, unsigned int r, unsigned int c); + + /*! + \relates vpArray2D + Insert array B in array A at the given position. + + \param A : Main array. + \param B : Array to insert. + \param C : Result array. + \param r : Index of the row where to insert array B. + \param c : Index of the column where to insert array B. + + \warning Throw exception if the sizes of the arrays do not + allow the insertion. + */ + static void insert(const vpArray2D &A, const vpArray2D &B, vpArray2D &C, unsigned int r, unsigned int c); + //@} + + static bool isFinite(const vpArray2D &A) + { + const unsigned int s = A.size(); + for (unsigned int i = 0; i < s; ++i) { + if (!vpMath::isFinite(A.data[i])) { + return false; + } + } + return true; + } + +protected: + //! Number of rows in the array + unsigned int rowNum; + //! Number of columns in the array + unsigned int colNum; + //! Address of the first element of each rows + Type **rowPtrs; + //! Current array size (rowNum * colNum) + unsigned int dsize; + //! Whether this array owns the memory it points to + bool isMemoryOwner; + //! Whether this array owns the row pointers + bool isRowPtrsOwner; + +}; + +/*! + Return the array min value. + */ +template Type vpArray2D::getMinValue() const +{ + Type *dataptr = data; + Type min = *dataptr; + ++dataptr; + for (unsigned int i = 0; i < (dsize - 1); ++i) { + if (*dataptr < min) { + min = *dataptr; + } + ++dataptr; + } + return min; +} + +/*! + Return the array max value. + */ +template Type vpArray2D::getMaxValue() const +{ + Type *dataptr = data; + Type max = *dataptr; + ++dataptr; + for (unsigned int i = 0; i < (dsize - 1); ++i) { + if (*dataptr > max) { + max = *dataptr; + } + ++dataptr; + } + return max; +} + +/*! + Compute the Hadamard product (element wise matrix multiplication). + \param m : Second matrix; + \return m1.hadamard(m2) The Hadamard product : \f$ m1 \circ m2 = (m1 \circ + m2)_{i,j} = (m1)_{i,j} (m2)_{i,j} \f$ +*/ +template vpArray2D vpArray2D::hadamard(const vpArray2D &m) const +{ + if ((m.getRows() != rowNum) || (m.getCols() != colNum)) { + throw(vpException(vpException::dimensionError, "Hadamard product: bad dimensions!")); + } + + vpArray2D out; + out.resize(rowNum, colNum, false); + + for (unsigned int i = 0; i < dsize; ++i) { + out.data[i] = data[i] * m.data[i]; + } + + return out; +} + +template vpArray2D vpArray2D::t() const +{ + vpArray2D At(colNum, rowNum); + for (unsigned int i = 0; i < rowNum; ++i) { + for (unsigned int j = 0; j < colNum; ++j) { + At[j][i] = (*this)[i][j]; + } + } + return At; +} + +template vpArray2D vpArray2D::conv2(const vpArray2D &M, const vpArray2D &kernel, const std::string &mode) +{ + vpArray2D res; + conv2(M, kernel, res, mode); + return res; +} + +template void vpArray2D::conv2(const vpArray2D &M, const vpArray2D &kernel, vpArray2D &res, const std::string &mode) +{ + if (((M.getRows() * M.getCols()) == 0) || ((kernel.getRows() * kernel.getCols()) == 0)) { + return; + } + + if (mode == "valid") { + if ((kernel.getRows() > M.getRows()) || (kernel.getCols() > M.getCols())) { + return; + } + } + + vpArray2D M_padded, res_same; + + if ((mode == "full") || (mode == "same")) { + const unsigned int pad_x = kernel.getCols() - 1; + const unsigned int pad_y = kernel.getRows() - 1; + const unsigned int pad = 2; + M_padded.resize(M.getRows() + (pad * pad_y), M.getCols() + (pad * pad_x), true, false); + M_padded.insert(M, pad_y, pad_x); + + if (mode == "same") { + res.resize(M.getRows(), M.getCols(), false, false); + res_same.resize(M.getRows() + pad_y, M.getCols() + pad_x, true, false); + } + else { + res.resize(M.getRows() + pad_y, M.getCols() + pad_x, true, false); + } + } + else if (mode == "valid") { + M_padded = M; + res.resize((M.getRows() - kernel.getRows()) + 1, (M.getCols() - kernel.getCols()) + 1); + } + else { + return; + } + + if (mode == "same") { + unsigned int res_same_rows = res_same.getRows(); + unsigned int res_same_cols = res_same.getCols(); + unsigned int kernel_rows = kernel.getRows(); + unsigned int kernel_cols = kernel.getCols(); + for (unsigned int i = 0; i < res_same_rows; ++i) { + for (unsigned int j = 0; j < res_same_cols; ++j) { + for (unsigned int k = 0; k < kernel_rows; ++k) { + for (unsigned int l = 0; l < kernel_cols; ++l) { + res_same[i][j] += M_padded[i + k][j + l] * kernel[kernel.getRows() - k - 1][kernel.getCols() - l - 1]; + } + } + } + } + + const unsigned int start_i = kernel.getRows() / 2; + const unsigned int start_j = kernel.getCols() / 2; + unsigned int m_rows = M.getRows(); + for (unsigned int i = 0; i < m_rows; ++i) { + memcpy(res.data + (i * M.getCols()), res_same.data + ((i + start_i) * res_same.getCols()) + start_j, + sizeof(Type) * M.getCols()); + } + } + else { + unsigned int res_rows = res.getRows(); + unsigned int res_cols = res.getCols(); + unsigned int kernel_rows = kernel.getRows(); + unsigned int kernel_cols = kernel.getCols(); + for (unsigned int i = 0; i < res_rows; ++i) { + for (unsigned int j = 0; j < res_cols; ++j) { + for (unsigned int k = 0; k < kernel_rows; ++k) { + for (unsigned int l = 0; l < kernel_cols; ++l) { + res[i][j] += M_padded[i + k][j + l] * kernel[kernel.getRows() - k - 1][kernel.getCols() - l - 1]; + } + } + } + } + } +} + +template vpArray2D vpArray2D::insert(const vpArray2D &A, const vpArray2D &B, unsigned int r, unsigned int c) +{ + vpArray2D C; + + insert(A, B, C, r, c); + + return C; +} + +template void vpArray2D::insert(const vpArray2D &A, const vpArray2D &B, vpArray2D &C, unsigned int r, unsigned int c) +{ + if (((r + B.getRows()) <= A.getRows()) && ((c + B.getCols()) <= A.getCols())) { + C.resize(A.getRows(), A.getCols(), false, false); + + unsigned int a_rows = A.getRows(); + unsigned int a_cols = A.getCols(); + for (unsigned int i = 0; i < a_rows; ++i) { + for (unsigned int j = 0; j < a_cols; ++j) { + if ((i >= r) && (i < (r + B.getRows())) && (j >= c) && (j < (c + B.getCols()))) { + C[i][j] = B[i - r][j - c]; + } + else { + C[i][j] = A[i][j]; + } + } + } + } + else { + throw vpException(vpException::dimensionError, "Cannot insert (%dx%d) array in (%dx%d) array at position (%d,%d)", + B.getRows(), B.getCols(), A.getCols(), A.getRows(), r, c); + } +} + +template bool vpArray2D::operator==(const vpArray2D &A) const +{ + if ((A.rowNum != rowNum) || (A.colNum != colNum)) { + return false; + } + + unsigned int a_size = A.size(); + for (unsigned int i = 0; i < a_size; ++i) { + if (data[i] != A.data[i]) { + return false; + } + } + + return true; +} + +/*! + * \relates vpArray2D + */ +template <> inline bool vpArray2D::operator==(const vpArray2D &A) const +{ + if ((A.rowNum != rowNum) || (A.colNum != colNum)) { + return false; + } + + unsigned int a_size = A.size(); + for (unsigned int i = 0; i < a_size; ++i) { + if (fabs(data[i] - A.data[i]) > std::numeric_limits::epsilon()) { + return false; + } + } + + return true; +} + +/*! + * \relates vpArray2D + */ +template <> inline bool vpArray2D::operator==(const vpArray2D &A) const +{ + if ((A.rowNum != rowNum) || (A.colNum != colNum)) { + return false; + } + + unsigned int a_size = A.size(); + for (unsigned int i = 0; i < a_size; ++i) { + if (fabsf(data[i] - A.data[i]) > std::numeric_limits::epsilon()) { + return false; + } + } + + return true; +} + +/*! + * \relates vpArray2D + */ +template bool vpArray2D::operator!=(const vpArray2D &A) const { return !(*this == A); } + +#ifdef VISP_HAVE_NLOHMANN_JSON +template +inline void from_json(const nlohmann::json &j, vpArray2D &array) +{ + if (j.is_array()) { + const unsigned int nrows = static_cast(j.size()); + if (nrows == 0) { // Initialize an empty array, Finished + array.resize(0, 0); + return; + } + unsigned int ncols = 0; + bool first = true; + for (const auto &item : j) { // Find number of columns, validate that all rows have same number of cols + if (!item.is_array()) { + throw vpException(vpException::badValue, "Trying to instantiate a 2D array with a JSON object that is not an array of array"); + } + if (first) { + first = false; + ncols = static_cast(item.size()); + } + else if (ncols != item.size()) { + throw vpException(vpException::badValue, "Trying to instantiate a 2D array with JSON row arrays that are not of the same size"); + } + } + array.resize(nrows, ncols); + unsigned i = 0; + for (const auto &item : j) { + std::vector row = item; + std::copy(row.begin(), row.end(), array.rowPtrs[i]); + ++i; + } + } + else if (j.is_object()) { + const unsigned ncols = j.at("cols"); + const unsigned nrows = j.at("rows"); + array.resize(nrows, ncols); + const nlohmann::json jData = j.at("data"); + if (!jData.is_array() || jData.size() != nrows * ncols) { + std::stringstream ss; + ss << "JSON \"data\" field must be an array of size " << nrows * ncols; + throw vpException(vpException::badValue, ss.str()); + } + unsigned i = 0; + for (const auto &jValue : jData) { + array.data[i] = jValue; + ++i; + } + } + else { + throw vpException(vpException::badValue, "Trying to read a vpArray2D from something that is not an array or object"); + } +} + + +template +inline void to_json(nlohmann::json &j, const vpArray2D &array) +{ + j = { + {"cols", array.colNum}, + {"rows", array.rowNum}, + {"type", "vpArray2D"} + }; + + nlohmann::json::array_t data; + data.reserve(array.size()); + for (unsigned i = 0; i < array.size(); ++i) { + data.push_back(array.data[i]); + } + j["data"] = data; +} +#endif + +END_VISP_NAMESPACE + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpBSpline.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpBSpline.h new file mode 100644 index 00000000..92ffbee1 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpBSpline.h @@ -0,0 +1,237 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * This class implements the B-Spline + */ + +/*! + \file vpBSpline.h + \brief Class that provides tools to compute and manipulate a B-Spline curve. +*/ + +#ifndef VP_BSPINE_H +#define VP_BSPINE_H + +#include + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +/*! + Structure that defines a B-Spline basis function \f$ N_{i,p}^k(u) \f$. + + - i is the number of the knot interval in which the basis function is + computed. + - p is the degree of the B-Spline basis function. + - u is the "point" point of the curve where the basis function is computed. + - k indicates which kth derivative is computed. + - value is the numerical value of \f$ N_{i,p}^k(u) \f$. +*/ +typedef struct vpBasisFunction +{ + unsigned int i; + unsigned int p; + double u; + unsigned int k; + double value; +} vpBasisFunction; +#endif + +/*! + \class vpBSpline + \ingroup group_core_math_spline + + \brief Class that provides tools to compute and manipulate a B-Spline curve. + + The different parameters are : + + - The knot vector \f$ U = {u_0, ... , u_m} \f$ where the knots \f$ u_i, i = + 0, ...,m \f$ are real number such as \f$ u_i < u_{i+1}, i = 0, ...,m \f$. To + define a curve, the knot vector is such as : \f$ U = {a , ... , a, u_{p+1} , + ... , u_{m-p-1} , b , ... , b} \f$ where \f$ a \f$ and \f$ b \f$ are real + numbers and p is the degree of the B-Spline basis functions. + + - The B-Spline basis functions \f$ N_{i,p} \f$ defined as : + \f[ N_{i,0}(u) = \left\{\begin{array}{cc} + 1 & \mbox{if } u_i \leq u \leq u_{i+1} \\ 0 & else + \end{array}\right.\f] + + \f[ N_{i,p}(u) = + \frac{u-u_i}{u_{i+p}-u_i}N_{i,p-1}(u)+\frac{u_{i+p+1}-u}{u_{i+p+1}-u_{i+1}}N_{i+1,p-1}(u)\f] + + where \f$ i = 0 , ... , m-1 \f$ and p is the degree of the B-Spline basis + functions. + + - The control points \f$ {P_i} \f$ which are defined by the coordinates \f$ + (i,j) \f$ of a point in an image. + + It is possible to compute the coordinates of a point corresponding to the + knots \f$ u \f$ (\f$ u \in [u_0,u_m]\f$) thanks to the formula : \f[ C(u) = + \sum_{i=0}^n (N_{i,p}(u)P_i)\f] + + You can find much more information about the B-Splines and the + implementation of all the methods in the Nurbs Book. +*/ + +class VISP_EXPORT vpBSpline +{ +public /*protected*/: + //! Vector which contains the control points + std::vector controlPoints; + //! Vector which contain the knots \f$ {u0, ..., um} \f$ + std::vector knots; + //! Degree of the B-Spline basis functions. + unsigned int p; + //! Vector which contains the points used during the interpolation method. + std::vector crossingPoints; + +public: + vpBSpline(); + + /*! + Gets the degree of the B-Spline. + + \return the degree of the B-Spline. + */ + inline unsigned int get_p() const { return p; } + + /*! + Gets all the control points. + + \param list : A std::list containing the coordinates of the control + points. + */ + inline void get_controlPoints(std::list &list) const + { + list.clear(); + for (unsigned int i = 0; i < controlPoints.size(); i++) { + list.push_back(*(&(controlPoints[0]) + i)); + } + } + + /*! + Gets all the knots. + + \param list : A std::list containing the value of the knots. + */ + inline void get_knots(std::list &list) const + { + list.clear(); + for (unsigned int i = 0; i < knots.size(); i++) { + list.push_back(*(&(knots[0]) + i)); + } + } + + /*! + Gets all the crossing points (used in the interpolation method) + + \param list : A std::list containing the coordinates of the crossing + points. + */ + inline void get_crossingPoints(std::list &list) const + { + list.clear(); + for (unsigned int i = 0; i < crossingPoints.size(); i++) { + list.push_back(*(&(crossingPoints[0]) + i)); + } + } + + /*! + Sets the degree of the B-Spline. + + \param degree : the degree of the B-Spline. + */ + inline void set_p(unsigned int degree) { this->p = degree; } + + /*! + Sets all the control points. + + \param list : A std::list containing the coordinates of the control points + */ + inline void set_controlPoints(const std::list &list) + { + controlPoints.clear(); + for (std::list::const_iterator it = list.begin(); it != list.end(); ++it) { + controlPoints.push_back(*it); + } + } + + /*! + Sets all the knots. + + \param list : A std::list containing the value of the knots. + */ + inline void set_knots(const std::list &list) + { + knots.clear(); + for (std::list::const_iterator it = list.begin(); it != list.end(); ++it) { + knots.push_back(*it); + } + } + + /*! + Sets all the crossing points (used in the interpolation method) + + \param list : A std::list containing the coordinates of the crossing + points + */ + inline void set_crossingPoints(const std::list &list) + { + crossingPoints.clear(); + for (std::list::const_iterator it = list.begin(); it != list.end(); ++it) { + crossingPoints.push_back(*it); + } + } + + static unsigned int findSpan(double l_u, unsigned int l_p, const std::vector &l_knots); + unsigned int findSpan(double u) const; + + static vpBasisFunction *computeBasisFuns(double l_u, unsigned int l_i, unsigned int l_p, + const std::vector &l_knots); + vpBasisFunction *computeBasisFuns(double u) const; + + static vpBasisFunction **computeDersBasisFuns(double l_u, unsigned int l_i, unsigned int l_p, unsigned int l_der, + const std::vector &l_knots); + vpBasisFunction **computeDersBasisFuns(double u, unsigned int der) const; + + static vpImagePoint computeCurvePoint(double l_u, unsigned int l_i, unsigned int l_p, const std::vector &l_knots, + const std::vector &l_controlPoints); + vpImagePoint computeCurvePoint(double u) const; + + static vpImagePoint *computeCurveDers(double l_u, unsigned int l_i, unsigned int l_p, unsigned int l_der, + const std::vector &l_knots, const std::vector &l_controlPoints); + vpImagePoint *computeCurveDers(double u, unsigned int der) const; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpCPUFeatures.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpCPUFeatures.h new file mode 100644 index 00000000..030e181b --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpCPUFeatures.h @@ -0,0 +1,86 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * CPU features (hardware capabilities). + */ + +/*! + \file vpCPUFeatures.h + \brief Check CPU features (hardware capabilities). +*/ + +#ifndef VP_CPU_FEATURES_H +#define VP_CPU_FEATURES_H + +#include + +BEGIN_VISP_NAMESPACE +/*! + \ingroup group_core_cpu_features + \brief Check CPU features (hardware capabilities). + + The example below shows how to check or get CPU capabilities. + + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + std::cout << "checkSSE2: " << vpCPUFeatures::checkSSE2() << std::endl; + std::cout << "CPU info: " << vpCPUFeatures::printCPUInfo() << std::endl; + return 0; + } + \endcode +*/ + +namespace vpCPUFeatures +{ +VISP_EXPORT bool checkSSE2(); +VISP_EXPORT bool checkSSE3(); +VISP_EXPORT bool checkSSSE3(); +VISP_EXPORT bool checkSSE41(); +VISP_EXPORT bool checkSSE42(); +VISP_EXPORT bool checkAVX(); +VISP_EXPORT bool checkAVX2(); + +#if defined(VISP_HAVE_SIMDLIB) +VISP_EXPORT bool checkNeon(); +VISP_EXPORT size_t getCPUCacheL1(); +VISP_EXPORT size_t getCPUCacheL2(); +VISP_EXPORT size_t getCPUCacheL3(); +#endif +VISP_EXPORT void printCPUInfo(); +} // namespace vpCPUFeatures +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpCameraParameters.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpCameraParameters.h new file mode 100644 index 00000000..43d9580a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpCameraParameters.h @@ -0,0 +1,568 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Camera intrinsic parameters. + */ + + /*! + \file vpCameraParameters.h + \brief Declaration of the vpCameraParameters class. + Class vpCameraParameters define the camera intrinsic parameters + + */ + +#ifndef VP_CAMERA_PARAMETERS_H +#define VP_CAMERA_PARAMETERS_H + +#include +#include + +#include +#include +#include +#include + +#ifdef VISP_HAVE_NLOHMANN_JSON +#include VISP_NLOHMANN_JSON(json.hpp) +#endif + +BEGIN_VISP_NAMESPACE +/*! + \class vpCameraParameters + + \ingroup group_core_camera + + \brief Generic class defining intrinsic camera parameters. + + 1. Supported camera models + + Two camera models are implemented in ViSP. + + 1.1. Pinhole camera model + + In this model \cite Marchand16a, a scene view is formed by projecting 3D points + into the image plane using a perspective transformation. + + \f[ + \left[ \begin{array}{c} + u \\ + v \\ + 1 + \end{array}\right] = + \left[ \begin{array}{ccc} + p_x & 0 & u_0 \\ + 0 & p_y & v_0 \\ + 0 & 0 & 1 + \end{array}\right] + \left[ \begin{array}{c} + x \\ + y \\ + 1 + \end{array}\right] + \f] + + where: + + - \f$(X_c,Y_c,Z_c)\f$ are the coordinates of a 3D point in the camera frame + - \f$(x,y)\f$ are the coordinates of the projection of the 3D point in the image plane + - \f$(u,v)\f$ are the coordinates in pixels of the projected 3D point + - \f$(u_0,v_0)\f$ are the coordinates of the principal point (the + intersection of the optical axes with the image plane) that is usually near + the image center + - \f$p_x\f$ (resp \f$p_y\f$) is the ratio between the focal length of the + lens \f$f\f$ in meters and the size of the pixel \f$l_x\f$ in meters: + \f$p_x=f/l_x\f$ (resp, \f$l_y\f$ being the height of a pixel, + \f$p_y=f/l_y\f$). + + When \f$Z_c \neq 0\f$, the previous equation is equivalent to the following: + \f[ + \begin{array}{lcl} + x &=& X_c / Z_c \\ + y &=& Y_c / Z_c \\ + u &=& u_0 + x \; p_x \\ + v &=& v_0 + y \; p_y + \end{array} + \f] + + Real lenses usually have some radial distortion. So, the above model is + extended as: + + \f[ + \begin{array}{lcl} + x &=& X_c / Z_c \\ + y &=& Y_c / Z_c \\ + x^{'} &=& x (1 + k_{ud} r^2) \\ + y^{'} &=& y (1 + k_{ud} r^2) \\ + r^2 &=& x^2 + y^2 \\ + u &=& u_0 + x^{'} \; p_x \\ + v &=& v_0 + y^{'} \; p_y + \end{array} + \f] + + where \f$k_{ud}\f$ is the first order radial distortion. Higher order + distortion coefficients are not considered in ViSP. + + Now in ViSP we consider also the inverse transformation, where from pixel + coordinates we want to compute their normalized coordinates in the image + plane. Previous equations could be written like: + + \f[ + \begin{array}{lcl} + x &=& (u - u_0) / p_x \\ + y &=& (v - v_0) / p_y + \end{array} + \f] + + Considering radial distortion, the above model is extended as: + \f[ + \begin{array}{lcl} + (u-u_0)^{'} &=& (u-u_0) (1 + k_{du} r^2) \\ + (v-v_0)^{'} &=& (v-v_0) (1 + k_{du} r^2) \\ + r^2 &=& ((u-u_0)/p_x)^2 + ((v-v_0)/p_y)^2 \\ + x &=& (u - u_0)^{'} / p_x \\ + y &=& (v - v_0)^{'} / p_y + \end{array} + \f] + + Finally, in ViSP the main intrinsic camera parameters are \f$(p_x, p_y)\f$ + the ratio between the focal length and the size of a pixel, and \f$(u_0, + v_0)\f$ the coordinates of the principal point in pixel. The lens + distortion can also be considered by two additional parameters + \f$(k_{ud}, k_{du})\f$. + + \note The \ref tutorial-calibration-intrinsic shows how to calibrate a camera + to estimate the parameters corresponding to the model implemented in this + class. + + \note Note also that \ref tutorial-bridge-opencv gives the correspondence + between ViSP and OpenCV camera modelization. + + \note The conversion from pixel coordinates \f$(u,v)\f$ in the normalized + space \f$(x,y)\f$ is implemented in vpPixelMeterConversion, whereas + the conversion from normalized coordinates into pixel is implemented + in vpMeterPixelConversion. + + From a practical point of view, two kinds of camera modelization are + implemented in this class: + + 1.1.1. Camera parameters for a perspective projection without distortion + model + + In this modelization, only \f$u_0,v_0,p_x,p_y\f$ parameters are considered. + + Initialization of such a model can be done using: + - initPersProjWithoutDistortion() that allows to set \f$u_0,v_0,p_x,p_y\f$ + parameters; + - initFromFov() that computes the parameters from an image size and a camera + field of view. + + 1.1.2. Camera parameters for a perspective projection with distortion + model + + In this modelization, all the parameters \f$u_0,v_0,p_x,p_y,k_{ud},k_{du}\f$ + are considered. Initialization of such a model can be done using: + - initPersProjWithDistortion() that allows to set + \f$u_0,v_0,p_x,p_y,k_{ud},k_{du}\f$ parameters; + + The selection of the camera model (without or with distortion) is done + during vpCameraParameters initialisation. + + Here an example of camera initialisation, for a model without distortion. A + complete example is given in initPersProjWithoutDistortion(). + + \code + double px = 600; double py = 600; double u0 = 320; double v0 = 240; + + // Create a camera parameter container + vpCameraParameters cam; + // Camera initialization with a perspective projection without distortion + // model + cam.initPersProjWithoutDistortion(px,py,u0,v0); + // It is also possible to print the current camera parameters + std::cout << cam << std::endl; + \endcode + + Here an example of camera initialisation, for a model with distortion. A + complete example is given in initPersProjWithDistortion(). + + \code + double px = 600; double py = 600; + double u0 = 320; double v0 = 240; + double kud = -0.19; double kdu = 0.20; + + // Create a camera parameter container + vpCameraParameters cam; + + // Camera initialization with a perspective projection without distortion + model cam.initPersProjWithDistortion(px,py,u0,v0,kud,kdu); + \endcode + + The code below shows how to know the currently used projection model: + \code + vpCameraParameters cam; + ... + vpCameraParameters::vpCameraParametersProjType projModel; + projModel = cam.get_projModel(); // Get the projection model type + \endcode + + An XML parser for camera parameters is also provided in vpXmlParserCamera. + + 1.2. Kannala-Brandt camera model + + This model \cite KannalaBrandt deals with fish-eye lenses designed to cover + the whole hemispherical field in front of the camera and the angle of view + is very large. In this case, the inherent distortion of a fish-eye lens should + not be considered only as a derivation from the pinhole model. + + The following projection in the general form is adapted: + + \f[ + \begin{array}{lcl} + r(\theta) &=& k_1 \theta + k_2 \theta^3 + k_3 \theta^5 + k_4 \theta^7 + k_5 \theta^9 + \end{array} + \f] + + where: + - \f$\theta\f$ is the angle in rad between a point in the real world and the + optical axis. + - \f$r\f$ is the distance between the image point and the principal point. + + In ViSP, we only consider radially symmetric distortions (caused by fisheye lenses). + + 2. JSON serialization + + Since ViSP 3.6.0, if ViSP is build with \ref soft_tool_json 3rd-party we introduce JSON serialization capabilities for vpCameraParameters. + The following sample code shows how to save camera parameters in a file named `cam.json` + and reload the parameters from this JSON file. + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #if defined(VISP_HAVE_NLOHMANN_JSON) + std::string filename = "cam.json"; + { + // Save camera parameters in a JSON file + vpCameraParameters cam(801, 802, 325, 245); + std::ofstream file(filename); + const nlohmann::json j = cam; + file << j; + file.close(); + } + { + // Load camera parameters from a JSON file + std::ifstream file(filename); + const nlohmann::json j = nlohmann::json::parse(file); + vpCameraParameters cam; + cam = j; + file.close(); + std::cout << "Read camera parameters from " << filename << ":\n" << cam << std::endl; + } + #endif + } + \endcode + If you build and execute the sample code, it will produce the following output: + \code{.unparsed} + Read camera parameters from cam.json: + Camera parameters for perspective projection without distortion: + px = 801 py = 802 + u0 = 325 v0 = 245 + \endcode + + The content of the `cam.json` file is the following: + \code{.unparsed} + $ cat cam.json + {"model":"perspectiveWithoutDistortion","px":801.0,"py":802.0,"u0":325.0,"v0":245.0} + \endcode +*/ + +class VISP_EXPORT vpCameraParameters +{ + friend class vpMeterPixelConversion; + friend class vpPixelMeterConversion; + +public: + typedef enum + { + perspectiveProjWithoutDistortion, //!< Perspective projection without distortion model + perspectiveProjWithDistortion, //!< Perspective projection with distortion model + ProjWithKannalaBrandtDistortion //!< Projection with Kannala-Brandt distortion model + } vpCameraParametersProjType; + + // generic functions + vpCameraParameters(); + vpCameraParameters(const vpCameraParameters &c); + vpCameraParameters(double px, double py, double u0, double v0); + vpCameraParameters(double px, double py, double u0, double v0, double kud, double kdu); + vpCameraParameters(double px, double py, double u0, double v0, const std::vector &distortion_coefficients); + + vpCameraParameters &operator=(const vpCameraParameters &c); + bool operator==(const vpCameraParameters &c) const; + bool operator!=(const vpCameraParameters &c) const; + virtual ~vpCameraParameters(); + + void init(); + void init(const vpCameraParameters &c); + void initFromCalibrationMatrix(const vpMatrix &K); + void initFromFov(const unsigned int &w, const unsigned int &h, const double &hfov, const double &vfov); + void initPersProjWithoutDistortion(double px, double py, double u0, double v0); + void initPersProjWithDistortion(double px, double py, double u0, double v0, double kud, double kdu); + void initProjWithKannalaBrandtDistortion(double px, double py, double u0, double v0, + const std::vector &distortion_coefficients); + + /*! + Specify if the fov has been computed. + + \sa computeFov() + + \return True if the fov has been computed, False otherwise. + */ + inline bool isFovComputed() const { return m_isFov; } + + void computeFov(const unsigned int &w, const unsigned int &h); + + /*! + Get the horizontal angle in radian of the field of view. + + \return FOV horizontal angle computed with px and width. + + \sa computeFov(), getVerticalFovAngle() + */ + inline double getHorizontalFovAngle() const + { + if (!m_isFov) { + std::cout << "Warning: The FOV is not computed, getHorizontalFovAngle() won't be significant." << std::endl; + } + return m_hFovAngle; + } + + /*! + Get the vertical angle in radian of the field of view. + + \return FOV vertical angle computed with py and height. + + \sa computeFov(), getHorizontalFovAngle() + */ + inline double getVerticalFovAngle() const + { + if (!m_isFov) { + std::cout << "Warning: The FOV is not computed, getVerticalFovAngle() won't be significant." << std::endl; + } + return m_vFovAngle; + } + + /*! + Get the list of the normals corresponding to planes describing the field + of view. + - vector[0] : Left Normal. + - vector[1] : Right Normal. + - vector[2] : Up Normal. + - vector[3] : Down Normal. + + \sa computeFov() + + \return List of the normals. + */ + inline std::vector getFovNormals() const + { + if (!m_isFov) { + std::cout << "Warning: The FOV is not computed, getFovNormals() won't be significant." << std::endl; + } + return m_fovNormals; + } + + inline double get_px() const { return m_px; } + inline double get_px_inverse() const { return m_inv_px; } + inline double get_py_inverse() const { return m_inv_py; } + inline double get_py() const { return m_py; } + inline double get_u0() const { return m_u0; } + inline double get_v0() const { return m_v0; } + inline double get_kud() const { return m_kud; } + inline double get_kdu() const { return m_kdu; } + inline std::vector getKannalaBrandtDistortionCoefficients() const { return m_dist_coefs; } + + inline vpCameraParametersProjType get_projModel() const { return m_projModel; } + + vpMatrix get_K() const; + vpMatrix get_K_inverse() const; + + void printParameters(); + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpCameraParameters &cam); + +private: + static const double DEFAULT_U0_PARAMETER; + static const double DEFAULT_V0_PARAMETER; + static const double DEFAULT_PX_PARAMETER; + static const double DEFAULT_PY_PARAMETER; + static const double DEFAULT_KUD_PARAMETER; + static const double DEFAULT_KDU_PARAMETER; + static const vpCameraParametersProjType DEFAULT_PROJ_TYPE; + + double m_px, m_py; //!< Pixel size + double m_u0, m_v0; //!< Principal point + double m_kud; //!< Radial distortion (from undistorted to distorted) + double m_kdu; //!< Radial distortion (from distorted to undistorted) + std::vector m_dist_coefs; //!< Coefficients for Kannala-Brandt distortion model + + unsigned int m_width; //!< Width of the image used for the fov computation + unsigned int m_height; //!< Height of the image used for the fov computation + bool m_isFov; //!< Boolean to specify if the fov has been computed + double m_hFovAngle; //!< Field of view horizontal angle + double m_vFovAngle; //!< Field of view vertical angle + std::vector m_fovNormals; //!< Normals of the planes describing the fov + + double m_inv_px, m_inv_py; + + vpCameraParametersProjType m_projModel; //!< used projection model +#ifdef VISP_HAVE_NLOHMANN_JSON + friend void to_json(nlohmann::json &j, const vpCameraParameters &cam); + friend void from_json(const nlohmann::json &j, vpCameraParameters &cam); +#endif +}; + +#ifdef VISP_HAVE_NLOHMANN_JSON + +#if defined(__clang__) +// Mute warning : declaration requires an exit-time destructor [-Wexit-time-destructors] +// message : expanded from macro 'NLOHMANN_JSON_SERIALIZE_ENUM' +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +#include VISP_NLOHMANN_JSON(json.hpp) +NLOHMANN_JSON_SERIALIZE_ENUM(vpCameraParameters::vpCameraParametersProjType, { + {vpCameraParameters::perspectiveProjWithoutDistortion, "perspectiveWithoutDistortion"}, + {vpCameraParameters::perspectiveProjWithDistortion, "perspectiveWithDistortion"}, + {vpCameraParameters::ProjWithKannalaBrandtDistortion, "kannalaBrandtDistortion"} + }); + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +/** + * \brief Converts camera parameters into a JSON representation. + * \sa from_json() for more information on the content. + * \param j The resulting JSON object. + * \param cam The camera to serialize. + */ +inline void to_json(nlohmann::json &j, const vpCameraParameters &cam) +{ + j["px"] = cam.m_px; + j["py"] = cam.m_py; + j["u0"] = cam.m_u0; + j["v0"] = cam.m_v0; + j["model"] = cam.m_projModel; + + switch (cam.m_projModel) { + case vpCameraParameters::perspectiveProjWithDistortion: + { + j["kud"] = cam.m_kud; + j["kdu"] = cam.m_kdu; + break; + } + case vpCameraParameters::ProjWithKannalaBrandtDistortion: + { + j["dist_coeffs"] = cam.m_dist_coefs; + break; + } + case vpCameraParameters::perspectiveProjWithoutDistortion: + break; + default: { + throw(vpException(vpException::fatalError, "Case not handled in to_json(nlohmann::json &, const vpCameraParameters &)")); + } + } +} + +/*! + * \brief Deserialize a JSON object into camera parameters. + * The minimal required properties are: + * - Pixel size: px, py + * - Principal point: u0, v0 + * + * If a projection model (\ref vpCameraParameters::vpCameraParametersProjType) is supplied, then other parameters may be expected: + * - In the case of perspective projection with distortion, ku, and kud must be supplied. + * - In the case of Kannala-Brandt distortion, the list of coefficients must be supplied. + * + * An example of a JSON object representing a camera is: + * \code{.json} + * { + * "px": 300.0, + * "py": 300.0, + * "u0": 120.5, + * "v0": 115.0, + * "model": "perspectiveWithDistortion", // one of ["perspectiveWithoutDistortion", "perspectiveWithDistortion", "kannalaBrandtDistortion"]. If omitted, camera is assumed to have no distortion + * "kud": 0.5, // required since "model" == perspectiveWithDistortion + * "kdu": 0.5 + * } + * \endcode + * + * \param j The json object to deserialize. + * \param cam The modified camera. + */ +inline void from_json(const nlohmann::json &j, vpCameraParameters &cam) +{ + const double px = j.at("px").get(); + const double py = j.at("py").get(); + const double u0 = j.at("u0").get(); + const double v0 = j.at("v0").get(); + const vpCameraParameters::vpCameraParametersProjType model = j.value("model", vpCameraParameters::perspectiveProjWithoutDistortion); + + switch (model) { + case vpCameraParameters::perspectiveProjWithoutDistortion: + { + cam.initPersProjWithoutDistortion(px, py, u0, v0); + break; + } + case vpCameraParameters::perspectiveProjWithDistortion: + { + const double kud = j.at("kud").get(); + const double kdu = j.at("kdu").get(); + cam.initPersProjWithDistortion(px, py, u0, v0, kud, kdu); + break; + } + case vpCameraParameters::ProjWithKannalaBrandtDistortion: + { + const std::vector coeffs = j.at("dist_coeffs").get>(); + cam.initProjWithKannalaBrandtDistortion(px, py, u0, v0, coeffs); + break; + } + default: { + throw(vpException(vpException::fatalError, "Case not handled in from_json(nlohmann::json &, const vpCameraParameters &)")); + } + } +} +#endif +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpCannyEdgeDetection.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpCannyEdgeDetection.h new file mode 100644 index 00000000..d551ce3c --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpCannyEdgeDetection.h @@ -0,0 +1,620 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#ifndef VP_CANNY_EDGE_DETECTION_H +#define VP_CANNY_EDGE_DETECTION_H + +// System includes +#include +#include +#include +#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX +#include // To dynamically change the stack size +#endif + +// ViSP include +#include +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) +#include +#endif +#include +#include +#include + +// 3rd parties include +#ifdef VISP_HAVE_NLOHMANN_JSON +#include VISP_NLOHMANN_JSON(json.hpp) +#endif + +BEGIN_VISP_NAMESPACE +/** + * \brief Class that implements the Canny's edge detector. + * It is possible to use a boolean mask to ignore some pixels of + * the input gray-scale image. + * + * \warning If the stack size is not sufficient, a SEGFAULT can occur on some images which + * are really over-exposed. + * \note The maximum stack on MacOS seems to be 65532000 bytes, see https://stackoverflow.com/a/13261334 +*/ +class VISP_EXPORT vpCannyEdgeDetection +{ + +public: + /** @name Constructors and initialization */ + //@{ + /** + * \brief Default constructor of the vpCannyEdgeDetection class. + * The thresholds used during the hysteresis thresholding step are set to be automatically computed. + */ + vpCannyEdgeDetection(); + + /** + * \brief Construct a new vpCannyEdgeDetection object that uses Gaussian blur + Sobel operators to compute + * the edge map. + * + * \param[in] gaussianKernelSize : The size of the Gaussian filter kernel. Must be odd. + * \param[in] gaussianStdev : The standard deviation of the Gaussian filter. + * \param[in] sobelAperture : The size of the Sobel filters kernel. Must be odd. + * \param[in] lowerThreshold : The lower threshold of the hysteresis thresholding step. If negative, will be computed + * from the upper threshold. + * \param[in] upperThreshold : The upper threshold of the hysteresis thresholding step. If negative, will be computed + * from the histogram of the absolute gradient. + * \param[in] lowerThresholdRatio : If the thresholds must be computed,the lower threshold will be equal to the upper + * threshold times \b lowerThresholdRatio . + * \param[in] upperThresholdRatio : If the thresholds must be computed,the upper threshold will be equal to the value + * such as the number of pixels of the image times \b upperThresholdRatio have an absolute gradient lower than the + * upper threshold. + * \param[in] filteringType : The filtering and gradient operators to apply to the image before the edge detection + * operation. + * \param[in] storeEdgePoints : If true, the list of edge-points will be available using + * \param[in] nbThread : Number of thread to use. -1 to let the program choose. + * \b vpCannyEdgeDetection::getEdgePointsList(). + */ + vpCannyEdgeDetection(const int &gaussianKernelSize, const float &gaussianStdev, const unsigned int &sobelAperture, + const float &lowerThreshold = -1.f, const float &upperThreshold = -1.f, + const float &lowerThresholdRatio = 0.6f, const float &upperThresholdRatio = 0.8f, + const vpImageFilter::vpCannyFilteringAndGradientType &filteringType = vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING, + const bool &storeEdgePoints = false, const int &nbThread = -1); + + /** + * \brief Reinitialize the detector: + * - compute the number of threads to use if applicable + * - initialize the Gaussian filters + * - initialize the derivative filters + * - forget the mask (but do not alter the memory) + * - forget GIx and GIy (but do not alter the memory) + * - delete previous results + */ + void reinit(); + + // // Configuration from files +#ifdef VISP_HAVE_NLOHMANN_JSON + /** + * \brief Construct a new vpCannyEdgeDetection object. + * + * \param[in] jsonPath : The path towards the JSON file to use to initialize the vpCannyEdgeDetection object. + */ + vpCannyEdgeDetection(const std::string &jsonPath); + + /** + * \brief Initialize all the algorithm parameters using the JSON file + * whose path is \b jsonPath. Throw a \b vpException error if the file + * does not exist. + * + * \param[in] jsonPath : The path towards the JSON configuration file. + */ + void initFromJSON(const std::string &jsonPath); + + /** + * \brief Read the detector configuration from JSON. All values are optional and if an argument is not present, + * the default value defined in the constructor is kept + * + * \param[in] j : The JSON object, resulting from the parsing of a JSON file. + * \param[out] detector : The detector that will be initialized from the JSON data. + */ + friend void from_json(const nlohmann::json &j, vpCannyEdgeDetection &detector); + + /** + * \brief Parse a vpCannyEdgeDetection object into JSON format. + * + * \param[out] j : A JSON parser object. + * \param[in] detector : The vpCannyEdgeDetection object that must be parsed into JSON format. + */ + friend void to_json(nlohmann::json &j, const vpCannyEdgeDetection &detector); +#endif + //@} + + /** @name Detection methods */ + //@{ +#ifdef HAVE_OPENCV_CORE + /** + * \brief Detect the edges in an image. + * Convert the color image into a ViSP gray-scale image. + * \warning If the stack size is not sufficient, a SEGFAULT can occur on some images which + * are really over-exposed. + * + * \param[in] cv_I A color image, in OpenCV format. + * \return vpImage 255 means an edge, 0 means not an edge. + */ + vpImage detect(const cv::Mat &cv_I); +#endif + + /** + * \brief Detect the edges in an image. + * Convert the color image into a gray-scale image. + * \warning If the stack size is not sufficient, a SEGFAULT can occur on some images which + * are really over-exposed. + * + * \param[in] I_color : An RGB image, in ViSP format. + * \return vpImage 255 means an edge, 0 means not an edge. + */ + vpImage detect(const vpImage &I_color); + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + template + vpImage detect(const vpImage> &Ihsv) + { + // // Step 1 and 2: filter the image and compute the gradient, if not given by the user + if (!m_areGradientAvailable) { + vpImage> Iblur; + vpImageFilter::gaussianBlur(Ihsv, Iblur, m_gaussianKernelSize, m_gaussianStdev, true, mp_mask); + vpImageFilter::gradientFilter(Iblur, m_dIx, m_dIy, m_nbThread, mp_mask, m_filteringAndGradientType); + } + m_areGradientAvailable = false; // Reset for next call + + // // Step 3: edge thining + float upperThreshold = m_upperThreshold; + float lowerThreshold = m_lowerThreshold; + if (upperThreshold < 0) { + upperThreshold = vpImageFilter::computeCannyThreshold(Ihsv, lowerThreshold, &m_dIx, &m_dIy, m_gaussianKernelSize, + m_gaussianStdev, m_lowerThresholdRatio, + m_upperThresholdRatio, m_filteringAndGradientType, mp_mask); + } + else if (m_lowerThreshold < 0) { + // Applying Canny recommendation to have the upper threshold 3 times greater than the lower threshold. + lowerThreshold = m_upperThreshold / 3.f; + } + // To ensure that if lowerThreshold = 0, we reject null gradient points + lowerThreshold = std::max(lowerThreshold, std::numeric_limits::epsilon()); + + step3to5(Ihsv.getHeight(), Ihsv.getWidth(), lowerThreshold, upperThreshold); + return m_edgeMap; + } +#endif + + /** + * \brief Detect the edges in a gray-scale image. + * \warning If the stack size is not sufficient, a SEGFAULT can occur on some images which + * are really over-exposed. + * + * \param[in] I : A gray-scale image, in ViSP format. + * \return vpImage 255 means an edge, 0 means not an edge. + */ + vpImage detect(const vpImage &I); + //@} + + /** @name Setters */ + //@{ + /** + * \brief Set the Filtering And Gradient operators to apply to the image before the edge detection operation. + * + * \param[in] type The operators to apply. + */ + inline void setFilteringAndGradientType(const vpImageFilter::vpCannyFilteringAndGradientType &type) + { + m_filteringAndGradientType = type; + initGradientFilters(); + } + + /** + * \brief Set the Gradients of the image that will be processed. + * + * \param[in] dIx : Gradient along the horizontal axis of the image. + * \param[in] dIy : Gradient along the vertical axis of the image. + */ + inline void setGradients(const vpImage &dIx, const vpImage &dIy) + { + m_dIx = dIx; + m_dIy = dIy; + m_areGradientAvailable = true; + } + + /** + * \brief Set the lower and upper Canny Thresholds used to qualify the edge point candidates. + * Edge point candidates whose gradient is between these two values is kept only if it + * linked somehow to a strong edge point. + * + * \param[in] lowerThresh : The lower threshold: each point whose gradient is below this threshold is discarded. + * When lower threshold value is negative, Canny recommendation is applied to have the lower threshold 3 times lower + * than the upper threshold. + * \param[in] upperThresh : The upper threshold: each point whose gradient is greater than this threshold is + * said to be a strong edge point and is kept. + */ + inline void setCannyThresholds(const float &lowerThresh, const float &upperThresh) + { + m_lowerThreshold = lowerThresh; + m_upperThreshold = upperThresh; + } + + /** + * \brief Set the lower and upper Canny Thresholds ratio that are used to compute them automatically. To ask to + * compute automatically the thresholds, you must set the lower and upper thresholds with negative values using the + * appropriate setter. + * + * \sa \ref vpCannyEdgeDetection::setCannyThresholds() "vpCannyEdgeDetection::setCannyThresholds(const float&, const float&)" + * \param[in] lowerThreshRatio : The lower threshold ratio: if the thresholds are computed automatically, the lower + * threshold will be equal to the upper threshold multiplied by \b lowerThreshRatio. + * \param[in] upperThreshRatio : The upper threshold ratio: if the thresholds are computed automatically, the upper + * threshold will be set such as \b upperThreshRatio times the number of pixels of the image have their absolute + * gradient lower then the upper threshold. + */ + inline void setCannyThresholdsRatio(const float &lowerThreshRatio, const float &upperThreshRatio) + { + m_lowerThresholdRatio = lowerThreshRatio; + m_upperThresholdRatio = upperThreshRatio; + } + + /** + * \brief Set the Gaussian Filters kernel size and standard deviation + * and initialize the aforementioned filters. + * + * \param[in] kernelSize : The size of the Gaussian filters kernel. + * \param[in] stdev : The standard deviation of the Gaussian filters used to blur and + * compute the gradient of the image. + */ + inline void setGaussianFilterParameters(const int &kernelSize, const float &stdev) + { + m_gaussianKernelSize = kernelSize; + m_gaussianStdev = stdev; + initGaussianFilters(); + } + + /** + * \brief Set the parameters of the gradient filter (Sobel or Scharr) kernel size filters. + * + * \param[in] apertureSize The size of the gradient filters kernel. Must be an odd value. + */ + inline void setGradientFilterAperture(const unsigned int &apertureSize) + { + m_gradientFilterKernelSize = apertureSize; + initGradientFilters(); + } + + /** + * \brief Set a mask to ignore pixels for which the mask is false. + * + * \warning The mask must be reset manually by the user (either for another mask + * or set to \b nullptr ) before computing the edge-map of another image. + * + * \param p_mask If different of \b nullptr , a mask of booleans where \b true + * indicates that a pixel must be considered and \b false that the pixel should + * be ignored. + */ + inline void setMask(const vpImage *p_mask) + { + mp_mask = p_mask; + } + + /** + * \brief Set the minimum stack size, expressed in bytes, due to the recursive algorithm. + * If not called, the stack size is left at its default value when running the + * Canny edge detection algorithm. + * + * \note The stack size is changed back to its original value after + * before leaving the detect() function. + * \note On Windows, the minimum stack size is defined at compilation time + * and cannot be changed during runtime. + * \note The maximum stack on MacOS seems to be 65532000 bytes, see https://stackoverflow.com/a/13261334 + * \warning If the stack size is not sufficient, a SEGFAULT can occur on some images which + * are really over-exposed. + * + * \param[in] requiredStackSize The required stack size, in bytes. + */ +#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX + inline void setMinimumStackSize(const rlim_t &requiredStackSize) + { + m_minStackSize = requiredStackSize; + } +#else + inline void setMinimumStackSize(const unsigned int &requiredStackSize) + { + (void)requiredStackSize; + static bool hasNotBeenDisplayed = true; + if (hasNotBeenDisplayed) { + std::cerr << "vpCannyEdgeDetection::setStackSize() has no effect on non-POSIX systems. The stack size is defined during compilation." << std::endl; + hasNotBeenDisplayed = false; + } + } +#endif + + inline void setNbThread(const int &maxNbThread) + { +#ifdef VISP_HAVE_OPENMP + int nbThread = maxNbThread; + if (nbThread < 0) { + nbThread = omp_get_max_threads(); + } + m_nbThread = nbThread; +#else + (void)maxNbThread; + m_nbThread = 1; + std::cout << "[WARNING] OpenMP is not available, setting the number of threads is ignored." << std::endl; +#endif + } + /** + * \brief If set to true, the list of the detected edge-points will be available + * calling the method \b vpCannyEdgeDetection::getEdgePointsList(). + * + * \param[in] storeEdgePoints The new desired status. + */ + inline void setStoreEdgePoints(const bool &storeEdgePoints) + { + m_storeListEdgePoints = storeEdgePoints; + } + //@} + + /** @name Getters */ + //@{ + /** + * \brief Get the list of edge-points that have been detected. + * + * \return std::vector The edge-points list. + */ + inline const std::vector &getEdgePointsList() const + { + if (!m_storeListEdgePoints) { + throw(vpException(vpException::fatalError, "Asking for the edge-points list while not asking to store it")); + } + return m_edgePointsList; + } + + /** + * \brief Get the minimum stack size used by the algorithm. + * + * \note On Windows, the minimum stack size is defined at compilation time + * and cannot be changed during runtime. + * + * \return rlim_t The minimum stack size. + */ +#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX + inline rlim_t getMinimumStackSize() const + { + return m_minStackSize; + } +#else + inline unsigned int getMinimumStackSize() const + { + const unsigned int limit = 65532000; + return limit; + } +#endif + + /** + * \brief Get the horizontal gradient. + * + * \return const vpImage& GIx + */ + const vpImage &getGIx() const + { + return m_dIx; + } + + /** + * \brief Get the vertical gradient. + * + * \return const vpImage& GIy + */ + const vpImage &getGIy() const + { + return m_dIy; + } + + /** + * \brief Get the final edge-map. + * + * \return const vpImage& The edge-map + */ + const vpImage &getEdgeMap() const + { + return m_edgeMap; + } + + //@} +private: + typedef enum EdgeType + { + STRONG_EDGE, /*!< This pixel exceeds the upper threshold of the double hysteresis phase, it is thus for sure an edge point.*/ + WEAK_EDGE,/*!< This pixel is between the lower and upper threshold of the double hysteresis phase, it is an edge point only if it is linked at some point to an edge point.*/ + ON_CHECK, /*!< This pixel is currently tested to know if it is linked to a strong edge point.*/ + NOT_EDGE /*!< This pixel is surely not an edge.*/ + } EdgeType; + + // Filtering + gradient methods choice + vpImageFilter::vpCannyFilteringAndGradientType m_filteringAndGradientType; /*!< Choice of the filter and + gradient operator to apply before the edge detection step*/ + + int m_nbThread; /*!< Number of threads to use.*/ + + // // Gaussian smoothing attributes + int m_gaussianKernelSize; /*!< Size of the Gaussian filter kernel used to smooth the input image. Must be an odd number.*/ + float m_gaussianStdev; /*!< Standard deviation of the Gaussian filter.*/ + vpArray2D m_fg; /*!< Array that contains the Gaussian kernel.*/ + + // // Gradient computation attributes + bool m_areGradientAvailable; /*!< Set to true if the user provides the gradient images, false otherwise. In the latter case, the class will compute the gradients.*/ + unsigned int m_gradientFilterKernelSize; /*!< The size of the Sobel kernels used to compute the gradients of the image.*/ + vpArray2D m_gradientFilterX; /*!< Array that contains the gradient filter kernel (Sobel or Scharr) along the X-axis.*/ + vpArray2D m_gradientFilterY; /*!< Array that contains the gradient filter kernel (Sobel or Scharr) along the Y-axis.*/ + vpImage m_dIx; /*!< X-axis gradient.*/ + vpImage m_dIy; /*!< Y-axis gradient.*/ + + // // Edge thining attributes + std::vector > m_edgeCandidateAndGradient; /*!< Map that contains point image coordinates and corresponding gradient value.*/ + + // // Hysteresis thresholding attributes + float m_lowerThreshold; /*!< Lower threshold for the hysteresis step. If negative, it will be deduced + as from m_upperThreshold. */ + float m_lowerThresholdRatio; /*!< If the thresholds must be computed, the ratio of the upper threshold the lower + threshold is equal: m_lowerThreshold = m_lowerThresholdRatio * m_upperThreshold. */ + float m_upperThreshold; /*!< Upper threshold for the hysteresis step.*/ + float m_upperThresholdRatio; /*!< If the thresholds must be computed, the ratio of pixels of the gradient image that + must be lower than the upper threshold \b m_upperThreshold.*/ + + // // Edge tracking attributes +#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX + rlim_t m_minStackSize; /*!< Minimum stack size, due to the recursivity used in this step of the algorithm.*/ +#endif + bool m_storeListEdgePoints; /*!< If true, the vector \b m_edgePointsList will contain the list of the edge points resulting from the whole algorithm.*/ + std::vector m_activeEdgeCandidates; /*!< Vector that contains only the IDs of the edge candidates.*/ + vpImage m_edgePointsCandidates; /*!< Map that contains the strong edge points, i.e. the points for which we know for sure they are edge points, + and the weak edge points, i.e. the points for which we still must determine if they are actual edge points.*/ + vpImage m_edgeMap; /*!< Final edge map that results from the whole Canny algorithm.*/ + std::vector m_edgePointsList; /*!< List of the edge points that belong to the final edge map.*/ + const vpImage *mp_mask; /*!< Mask that permits to consider only the pixels for which the mask is true.*/ + + float getGradientOrientation(const vpImage &dIx, const vpImage &dIy, const int &iter); + + void getInterpolWeightsAndOffsets(const float &gradientOrientation, float &alpha, float &beta, const int &nbCols, + int &dRowGradAlpha, int &dRowGradBeta, int &dColGradAlpha, int &dColGradBeta); + + float getManhattanGradient(const vpImage &dIx, const vpImage &dIy, const int &iter); + + /** @name Constructors and initialization */ + //@{ + /** + * \brief Initialize the Gaussian filters used to filter the input image. + */ + void initGaussianFilters(); + + /** + * \brief Initialize the gradient filters (Sobel or Scharr) used to compute the input image gradients. + */ + void initGradientFilters(); + //@} + + /** @name Different steps methods */ + /** + * \brief Step 1: filtering + Step 2: gradient computation + * \details First, perform Gaussian blur to the input image. + * Then, compute the x-axis and y-axis gradients of the image. + * \param[in] I : The image we want to compute the gradients. + */ + void computeFilteringAndGradient(const vpImage &I); + + /** + * \brief Perform the steps: + * * 3 := edge-thining + * * 4 := hysteresis thresholding + * * 5 := edge tracking + * + * \param[in] lowerThreshold The lower threshold for the hysteresis thresholding. + * \param[in] upperThreshold The upper threshold for the hysteresis thresholding. + */ + void step3to5(const unsigned int &height, const unsigned int &width, const float &lowerThreshold, const float &upperThreshold); + + /** + * \brief Step 3: Edge thining. + * \details Perform the edge thining step. + * Perform a non-maximum suppression to keep only local maxima as edge candidates. + * \param[in] lowerThreshold Edge candidates that are below this threshold are definitely not + * edges. + */ + void performEdgeThinning(const float &lowerThreshold); + + /** + * \brief Perform hysteresis thresholding. + * \details Edge candidates that are greater than \b m_upperThreshold are saved in \b m_strongEdgePoints + * and will be kept in the final edge map. + * Edge candidates that are between \b m_lowerThreshold and \b m_upperThreshold are saved in + * \b m_weakEdgePoints and will be kept in the final edge map only if they are connected + * to a strong edge point. + * Edge candidates that are below \b m_lowerThreshold are discarded. + * \param[in] lowerThreshold Edge candidates that are below this threshold are definitely not + * edges. + * \param[in] upperThreshold Edge candidates that are greater than this threshold are classified + * as strong edges. + */ + void performHysteresisThresholding(const float &lowerThreshold, const float &upperThreshold); + + /** + * \brief Search recursively for a strong edge in the neighborhood of a weak edge. + * + * \param[in] coordinates : The coordinates we are checking. + * \return true We found a strong edge point in its 8-connected neighborhood. + * \return false We did not found a strong edge point in its 8-connected neighborhood. + */ + bool recursiveSearchForStrongEdge(const unsigned int &coordinates); + + /** + * \brief Perform edge tracking. + * \details For each weak edge, we will recursively check if they are 8-connected to a strong edge point. + * If so, the weak edge will be saved in \b m_strongEdgePoints and will be kept in the final edge map. + * Otherwise, the edge point will be discarded. + */ + void performEdgeTracking(); + //@} +}; + +#ifdef VISP_HAVE_NLOHMANN_JSON +inline void from_json(const nlohmann::json &j, vpCannyEdgeDetection &detector) +{ + std::string filteringAndGradientName = vpImageFilter::vpCannyFiltAndGradTypeToStr(detector.m_filteringAndGradientType); + filteringAndGradientName = j.value("filteringAndGradientType", filteringAndGradientName); + detector.m_filteringAndGradientType = vpImageFilter::vpCannyFiltAndGradTypeFromStr(filteringAndGradientName); + detector.m_gaussianKernelSize = j.value("gaussianSize", detector.m_gaussianKernelSize); + detector.m_gaussianStdev = j.value("gaussianStdev", detector.m_gaussianStdev); + detector.m_lowerThreshold = j.value("lowerThreshold", detector.m_lowerThreshold); + detector.m_lowerThresholdRatio = j.value("lowerThresholdRatio", detector.m_lowerThresholdRatio); + detector.m_gradientFilterKernelSize = j.value("gradientFilterKernelSize", detector.m_gradientFilterKernelSize); + detector.m_upperThreshold = j.value("upperThreshold", detector.m_upperThreshold); + detector.m_upperThresholdRatio = j.value("upperThresholdRatio", detector.m_upperThresholdRatio); + detector.m_nbThread = j.value("nbThread", detector.m_nbThread); + detector.reinit(); +} + +inline void to_json(nlohmann::json &j, const vpCannyEdgeDetection &detector) +{ + std::string filteringAndGradientName = vpImageFilter::vpCannyFiltAndGradTypeToStr(detector.m_filteringAndGradientType); + j = nlohmann::json { + {"filteringAndGradientType", filteringAndGradientName}, + {"gaussianSize", detector.m_gaussianKernelSize}, + {"gaussianStdev", detector.m_gaussianStdev}, + {"lowerThreshold", detector.m_lowerThreshold}, + {"lowerThresholdRatio", detector.m_lowerThresholdRatio}, + {"gradientFilterKernelSize", detector.m_gradientFilterKernelSize}, + {"upperThreshold", detector.m_upperThreshold}, + {"upperThresholdRatio", detector.m_upperThresholdRatio}, + {"nbThread", detector.m_nbThread} + }; +} +#endif + +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpCircle.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpCircle.h new file mode 100644 index 00000000..0db62b5a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpCircle.h @@ -0,0 +1,179 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Visual feature circle. + */ + +/*! + \file vpCircle.h + \brief class that defines what is a circle +*/ + +#ifndef VP_CIRCLE_H +#define VP_CIRCLE_H + +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpCircle + * \ingroup group_core_geometry + * \brief Class that defines a 3D circle in the object frame and allows forward projection of a 3D circle in the + * camera frame and in the 2D image plane by perspective projection. + * All the parameters must be set in meter. + * + * Note that a 3D circle is defined from the intersection between a 3D plane and a 3D sphere. + * + * A 3D circle has the followings parameters: + * - **in the object frame**: the parameters oA, oB, oC corresponding to the 3D plane with equation + * oA*(X-oX)+oB*(Y-oY)+oC*(Z-oZ)=0 where (X,Y,Z) are the coordinates of a 3D point belonging to the plane passing through + * the 3D sphere center (oX,oY,oZ) and the 3D coordinates oX, oY, oZ of the center and radius R of the 3D sphere. These + * parameters registered in vpForwardProjection::oP internal 7-dim vector are set using the constructors + * vpCircle(double oA, double oB, double oC, double oX, double oY, double oZ, double R), + * vpCircle(const vpColVector &oP) or the functions + * setWorldCoordinates(double oA, double oB, double oC, double oX, double oY, double oZ, double R) + * and setWorldCoordinates(const vpColVector &oP). To get theses parameters use get_oP(). + * + * - **in the camera frame**: the parameters cA, cB, cC corresponding to the 3D plane cAx+cBy+cCz+D=0 + * and the coordinates cX, cY, cZ of the center and radius R of the 3D sphere. These + * parameters registered in vpTracker::cP internal 7-dim vector are computed using + * changeFrame(const vpHomogeneousMatrix &cMo, vpColVector &cP) const or changeFrame(const vpHomogeneousMatrix &cMo). + * These parameters could be retrieved using getA(), getB(), getC(), getX(), getY(), getZ() and getR(). + * To get theses parameters use get_cP(). + * + * - **in the image plane**: here we consider the parameters of the ellipse corresponding + * to the perspective projection of the 3D circle. The parameters are the ellipse centroid (x, y) + * and n20, n11, n02 which are the second order centered moments of + * the ellipse normalized by its area (i.e., such that \f$n_{ij} = \mu_{ij}/a\f$ where + * \f$\mu_{ij}\f$ are the centered moments and a the area). + * These parameters are registered in vpTracker::p internal 5-dim vector + * and computed using projection() and + * projection(const vpColVector &cP, vpColVector &p) const. They could be retrieved using get_x(), get_y(), get_n20(), + * get_n11() and get_n02(). They correspond to 2D normalized circle parameters with values expressed in meters. + * To get theses parameters use get_p(). +*/ +class VISP_EXPORT vpCircle : public vpForwardProjection +{ +public: + vpCircle(); + VP_EXPLICIT vpCircle(const vpColVector &oP); + vpCircle(double oA, double oB, double oC, double oX, double oY, double oZ, double R); + +#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98) + vpCircle(const vpCircle &) = default; + virtual ~vpCircle() VP_OVERRIDE = default; +#else + virtual ~vpCircle() { } +#endif + void changeFrame(const vpHomogeneousMatrix &noMo, vpColVector &noP) const VP_OVERRIDE; + void changeFrame(const vpHomogeneousMatrix &cMo) VP_OVERRIDE; + + void display(const vpImage &I, const vpCameraParameters &cam, const vpColor &color = vpColor::green, + unsigned int thickness = 1) VP_OVERRIDE; + void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &color = vpColor::green, unsigned int thickness = 1) VP_OVERRIDE; + + void display(const vpImage &I, const vpCameraParameters &cam, const vpColor &color = vpColor::green, + unsigned int thickness = 1); + void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &color = vpColor::green, unsigned int thickness = 1); + + vpCircle *duplicate() const VP_OVERRIDE; + + double get_x() const { return p[0]; } + double get_y() const { return p[1]; } + + double get_n20() const { const unsigned int index_2 = 2; return p[index_2]; } + double get_n11() const { const unsigned int index_3 = 3; return p[index_3]; } + double get_n02() const { const unsigned int index_4 = 4; return p[index_4]; } + + double getA() const { const unsigned int index_0 = 0; return cP[index_0]; } + double getB() const { const unsigned int index_1 = 1; return cP[index_1]; } + double getC() const { const unsigned int index_2 = 2; return cP[index_2]; } + + double getX() const { const unsigned int index_3 = 3; return cP[index_3]; } + double getY() const { const unsigned int index_4 = 4; return cP[index_4]; } + double getZ() const { const unsigned int index_5 = 5; return cP[index_5]; } + + double getR() const { const unsigned int index_6 = 6; return cP[index_6]; } + +#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98) + vpCircle &operator=(const vpCircle &) = default; +#endif + void projection() VP_OVERRIDE; + void projection(const vpColVector &cP, vpColVector &p) const VP_OVERRIDE; + void setWorldCoordinates(const vpColVector &oP) VP_OVERRIDE; + + void setWorldCoordinates(double oA, double oB, double oC, double oX, double oY, double oZ, double R); + + //################### + // Static Functions + //################### + static void computeIntersectionPoint(const vpCircle &circle, const vpCameraParameters &cam, const double &rho, + const double &theta, double &i, double &j); + +public: +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) + /*! + @name Deprecated functions + */ + //@{ + /*! + * \deprecated You should rather use get_n20(). + * This function is incorrectly named and is confusing since it + * returns second order centered moments of the ellipse normalized + * by its area that corresponds to \f$n_20 = mu_20/a\f$. + */ + VP_DEPRECATED double get_mu20() const { const unsigned int index_2 = 2; return p[index_2]; } + /*! + * \deprecated You should rather use get_n11(). + * This function is incorrectly named and is confusing since it + * returns second order centered moments of the ellipse normalized + * by its area that corresponds to \f$n_11 = mu@name Deprecated functions_11/a\f$. + */ + VP_DEPRECATED double get_mu11() const { const unsigned int index_3 = 3; return p[index_3]; } + /*! + * \deprecated You should rather use get_n02(). + * This function is incorrectly named and is confusing since it + * returns second order centered moments of the ellipse normalized + * by its area that corresponds to \f$n_02 = mu_02/a\f$. + */ + VP_DEPRECATED double get_mu02() const { const unsigned int index_4 = 4; return p[index_4]; } + //@} +#endif +protected: + void init() VP_OVERRIDE; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpClient.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpClient.h new file mode 100644 index 00000000..77d2a305 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpClient.h @@ -0,0 +1,222 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * TCP Client + */ + +#ifndef VP_CLIENT_H +#define VP_CLIENT_H + +#include +#include +#include +#include +#include +#include + + // inet_ntop() not supported on win XP +#ifdef VISP_HAVE_FUNC_INET_NTOP + +BEGIN_VISP_NAMESPACE +/*! + * \class vpClient + * + * \ingroup group_core_com_ethernet + * + * \brief This class represents a Transmission Control Protocol (TCP) client. + * + * TCP provides reliable, ordered delivery of a stream of bytes from a program + * on one computer to another program on another computer. + * + * Example of client's code, receiving and sending basic message + * It corresponds to the client used in the first example of vpServer class' + * documentation: + * + * \code + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * std::string servername = "localhost"; + * unsigned int port = 35000; + * + * vpClient client; + * client.connectToHostname(servername, port); + * //client.connectToIP("127.0.0.1",port); + * + * int val = 0; + * + * while(1) + * { + * // Sending the new value to the first client + * if(client.send(&val) != sizeof(int)) + * std::cout << "Error while sending" << std::endl; + * else + * std::cout << "Sending : " << val << std::endl; + * + * // Receiving a value from the first client + * if(client.receive(&val) != sizeof(int)) + * std::cout << "Error while receiving" << std::endl; + * else + * std::cout << "Received : " << val << std::endl; + * } + * + * return 0; + * } + * \endcode + * + * Example of client's code, sending a vpImage on request form. + * It correspond to the server used in the second example of vpServer class' + * documentation. + * + * \code + * #include + * #include + * #include + * #include + * #include + * #include + * + * #include "vpRequestImage.h" //See vpRequest class documentation + * + * int main(int argc, char **argv) + * { + * #if defined(VISP_HAVE_V4L2) + * std::string servername = "localhost"; + * unsigned int port = 35000; + * + * vpImage I; // Create a gray level image container + * + * // Create a grabber based on v4l2 third party lib (for usb cameras under + * // Linux) + * vpV4l2Grabber g; + * g.setScale(1); + * g.setInput(0); + * g.open(I); + * + * // Create an image viewer + * #if defined(VISP_HAVE_X11) + * vpDisplayX d(I, -1, -1, "Camera frame"); + * #elif defined(VISP_HAVE_GDI) //Win32 + * vpDisplayGDI d(I, -1, -1, "Camera frame"); + * #endif + * + * vpClient client; + * client.connectToHostname(servername, port); + * //client.connectToIP("127.0.0.1",port); + * + * vpRequestImage reqImage(&I); + * + * while(1) + * { + * double t = vpTime::measureTimeMs(); + * // Acquire a new image + * g.acquire(I); + * + * vpDisplay::display(I); + * vpDisplay::flush(I); + * + * client.sendAndEncodeRequest(reqImage); + * + * // A click in the viewer to exit + * if ( vpDisplay::getClick(I, false) ) + * break; + * } + * + * return 0; + * #endif + * } + * \endcode + * + * \sa vpClient + * \sa vpRequest + * \sa vpNetwork +*/ +class VISP_EXPORT vpClient : public vpNetwork +{ +private: + unsigned int m_numberOfAttempts; + + bool connectServer(vpNetwork::vpReceptor &serv); + +public: + vpClient(); + vpClient(const vpClient &client) : vpNetwork(client) + { + *this = client; + } + virtual ~vpClient() VP_OVERRIDE; + + vpClient &operator=(const vpClient &client) + { + m_numberOfAttempts = client.m_numberOfAttempts; + return *this; + } + bool connectToHostname(const std::string &hostname, const unsigned int &port_serv); + bool connectToIP(const std::string &ip, const unsigned int &port_serv); + + void deconnect(const unsigned int &index = 0); + /*! + * Get the actual number of attempts to connect to the server. + * + * \sa vpClient::setNumberOfAttempts() + * + * \return Actual number of attempts. + */ + unsigned int getNumberOfAttempts() { return m_numberOfAttempts; } + + /*! + * Get the number of server that the client is connected on. + * + * \return Number of servers. + */ + unsigned int getNumberOfServers() { return static_cast(receptor_list.size()); } + + void print(); + + /*! + * Set the number of attempts to connect to the server. + * + * \sa vpClient::getNumberOfAttempts() + * + * \param nb : Number of attempts. + */ + void setNumberOfAttempts(const unsigned int &nb) { m_numberOfAttempts = nb; } + + void stop(); +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpColVector.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpColVector.h new file mode 100644 index 00000000..1b734a3d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpColVector.h @@ -0,0 +1,1592 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Provide some simple operation on column vectors. + */ + +/*! + * \file vpColVector.h + * \brief definition of column vector class as well + * as a set of operations on these vector + */ + +#ifndef VP_COLVECTOR_H +#define VP_COLVECTOR_H + +#include + +#ifdef VISP_HAVE_NLOHMANN_JSON +#include VISP_NLOHMANN_JSON(json.hpp) +#endif +BEGIN_VISP_NAMESPACE +class vpMatrix; +class vpRowVector; +class vpRotationVector; +class vpTranslationVector; +class vpPoseVector; +END_VISP_NAMESPACE + +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpColVector + * \ingroup group_core_matrices + * + * \brief Implementation of column vector and the associated operations. + * + * This class provides a data structure for a column vector that contains + * values of double. It contains also some functions to achieve a set of + * operations on these vectors. + * + * The vpColVector class is derived from vpArray2D. + * + * The code below shows how to create a 3-element column vector of doubles, set the element values and access them: + * \code + * #include &) + * \code + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpColVector v; + * v = {-1, -2.1, -3}; + * } + * \endcode + * + * JSON serialization + * + * Since ViSP 3.6.0, if ViSP is build with \ref soft_tool_json 3rd-party we introduce JSON serialization capabilities for vpColVector. + * The following sample code shows how to save a pose vector in a file named `col-vector.json` + * and reload the values from this JSON file. + * \code + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * #if defined(VISP_HAVE_NLOHMANN_JSON) + * std::string filename = "col-vector.json"; + * { + * vpColVector v({ 1, 2, 3, 4 }); + * std::ofstream file(filename); + * const nlohmann::json j = v; + * file << j; + * file.close(); + * } + * { + * std::ifstream file(filename); + * const nlohmann::json j = nlohmann::json::parse(file); + * vpColVector v; + * v = j.get(); + * file.close(); + * std::cout << "Read homogeneous matrix from " << filename << ":\n" << v.t() << std::endl; + * } + * #endif + * } + * \endcode + * If you build and execute the sample code, it will produce the following output: + * \code{.unparsed} + * Read homogeneous matrix from col-vector.json: + * 1 2 3 4 + * \endcode + * + * The content of the `pose-vector.json` file is the following: + * \code{.unparsed} + * $ cat col-vector.json + * {"cols":1,"data":[1.0,2.0,3.0,4.0],"rows":4,"type":"vpColVector"} + * \endcode +*/ +class VISP_EXPORT vpColVector : public vpArray2D +{ + friend class vpMatrix; + +public: + /*! + * Basic constructor that creates an empty 0-size column vector. + */ + vpColVector() : vpArray2D() { } + + /*! + * Construct a column vector of size n. + * \warning Elements are not initialized. If you want to set an initial value use + * vpColVector(unsigned int, double). + */ + VP_EXPLICIT vpColVector(unsigned int n) : vpArray2D(n, 1) { } + + /*! + * Construct a column vector of size n. Each element is set to \e val. + */ + vpColVector(unsigned int n, double val) : vpArray2D(n, 1, val) { } + + /*! + * Copy constructor that allows to construct a column vector from an other one. + */ + vpColVector(const vpColVector &v) : vpArray2D(v) { } + + /*! + * Construct a column vector from a part of an input column vector \e v. + * + * \param v : Input column vector used for initialization. + * \param r : row index in \e v that corresponds to the first element of the + * column vector to construct. + * \param nrows : Number of rows of the constructed + * column vector. + * + * The sub-vector starting from v[r] element and ending on v[r+nrows-1] element + * is used to initialize the constructed column vector. + * + * \sa init() + */ + vpColVector(const vpColVector &v, unsigned int r, unsigned int nrows); + + /*! + * Constructor that initialize a column vector from a 3-dim (Euler or + * \f$\theta {\bf u}\f$) or 4-dim (quaternion) rotation vector. + */ + VP_EXPLICIT vpColVector(const vpRotationVector &v); + + /*! + * Constructor that initialize a column vector from a 6-dim pose vector. + */ + VP_EXPLICIT vpColVector(const vpPoseVector &p); + + /*! + * Constructor that initialize a column vector from a 3-dim translation vector. + */ + VP_EXPLICIT vpColVector(const vpTranslationVector &t); + + /*! + * Constructor that creates a column vector from a m-by-1 matrix `M`. + * + * \exception vpException::dimensionError If the matrix is not a m-by-1 + * matrix. + */ + VP_EXPLICIT vpColVector(const vpMatrix &M); + + /*! + * Constructor that takes column `j` of matrix `M`. + */ + vpColVector(const vpMatrix &M, unsigned int j); + + /*! + * Constructor that creates a column vector from a std vector of double. + */ + VP_EXPLICIT vpColVector(const std::vector &v); + + /*! + * Constructor that creates a column vector from a std vector of float. + */ + VP_EXPLICIT vpColVector(const std::vector &v); + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + /*! + * Move constructor that take rvalue. + */ + vpColVector(vpColVector &&v); +#endif + +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher + vpColVector(const std::initializer_list &list) : vpArray2D(static_cast(list.size()), 1) + { + std::copy(list.begin(), list.end(), data); + } +#endif + + static vpColVector view(double *raw_data, unsigned int rows); + static void view(vpColVector &v, double *raw_data, unsigned int rows); + + /*! + * Removes all elements from the vector (which are destroyed), + * leaving the container with a size of 0. + */ + void clear() + { + if (!isMemoryOwner) { + throw vpException(vpException::fatalError, "Cannot clear a vector view"); + } + if (data != nullptr) { + free(data); + data = nullptr; + } + + if (rowPtrs != nullptr) { + free(rowPtrs); + rowPtrs = nullptr; + } + rowNum = 0; + colNum = 0; + dsize = 0; + } + + /*! + * Print to be used as part of a C++ code later. + * + * \param os : the stream to be printed in. + * \param matrixName : name of the column vector, "A" by default. + * \param octet : if false, print using double, if true, print byte per byte + * each bytes of the double array. + * + * The following code shows how to use this function: + * \code + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpColVector v(3); + * for (unsigned int i=0; i + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * std::ofstream ofs("log.csv", std::ofstream::out); + * vpColVector v(3); + * for (unsigned int i=0; i= rowNum) || ((r + colsize) > rowNum)) { + throw(vpException(vpException::fatalError, + "Cannot extract a (%dx1) column vector from a (%dx1) " + "column vector starting at index %d", + colsize, rowNum, r)); + } + + return vpColVector(*this, r, colsize); + } + + /*! + * Compute and return the Frobenius norm \f$ ||v|| = \sqrt{ \sum_{v_{i}^2}} \f$ of + * all the elements \f$v_{i}\f$ of the column vector \f$ \bf v \f$ + * that is of dimension \f$ m \f$. + * + * \return The Frobenius norm if the vector is initialized, 0 otherwise. + * + * \sa infinityNorm() + * + */ + double frobeniusNorm() const; + + /*! + * Compute the Hadamard product (element wise vector multiplication). + * + * \param v : Second vector; + * \return v1.hadamard(v2) The kronecker product : + * \f$ v1 \circ v2 = (v1 \circ v2)_{i} = (v1)_{i} (v2)_{i} \f$ + */ + vpColVector hadamard(const vpColVector &v) const; + + /*! + * Compute and return the infinity norm \f$ {||v||}_{\infty} = + * max\left({\mid v_{i} \mid}\right) \f$ with \f$i \in + * \{0, ..., m-1\}\f$ where \e m is the vector size and \f$v_i\f$ an element of + * the vector. + * + * \return The infinity norm if the matrix is initialized, 0 otherwise. + * + * \sa frobeniusNorm() + */ + double infinityNorm() const; + + /*! + * Initialize the column vector from a part of an input column vector \e v. + * + * \param v : Input column vector used for initialization. + * \param r : row index in \e v that corresponds to the first element of the + * column vector to construct. + * \param nrows : Number of rows of the constructed + * column vector. + * + * The sub-vector starting from v[r] element and ending on v[r+nrows-1] element + * is used to initialize the constructed column vector. + * + * The following code shows how to use this function: + * \code + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpColVector v(4); + * int val = 0; + * for(size_t i=0; i + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpColVector v(4); + * for (unsigned int i=0; i < v.size(); ++i) + * v[i] = i; + * std::cout << "v: " << v.t() << std::endl; + * + * vpColVector w(2); + * for (unsigned int i=0; i < w.size(); ++i) + * w[i] = i+10; + * std::cout << "w: " << w.t() << std::endl; + * + * v.insert(1, w); + * std::cout << "v: " << v.t() << std::endl; + * } + * \endcode + * It produces the following output: + * \code + * v: 0 1 2 3 + * w: 10 11 + * v: 0 10 11 3 + * \endcode + */ + void insert(unsigned int i, const vpColVector &v); + + /*! + * Print using Maple syntax, to copy/paste in Maple later. + * + * The following code + * \code + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpColVector v(3); + * for (unsigned int i=0; i &list); +#endif + + /*! + * Compare two column vectors. + * + * \param v : Vector to compare with. + * \return true when their respective size and their respective values are the same, + * false when their size or values differ. + */ + bool operator==(const vpColVector &v) const; + + /*! + * Compare a column vector to a floating point value. + * + * \param v : Floating point value to compare with. + * \return true when all the values of the vector are equal to the floating point value `v`, + * false otherwise. + */ + bool operator==(double v) const; + + /*! + * Compare two column vectors. + * + * \param v : Vector to compare with. + * \return true when their respective size or their values differ, false when their size and values are the same. + */ + bool operator!=(const vpColVector &v) const; + + /*! + * Compare a column vector to a floating point value. + * + * \param v : Floating point value to compare with. + * \return true when at least one value of the vector differ from the floating point value `v`. + * false when all the vector values are equal to `v`. + */ + bool operator!=(double v) const; + + /*! + * Operator that performs the dot product between two column vectors. + * + * \exception vpException::dimensionError If the vector dimension differ. + * + * \sa dotProd() + */ + double operator*(const vpColVector &v) const; + + /*! + * Multiply a column vector by a row vector. + * + * \param v : Row vector. + * + * \return The resulting matrix. + */ + vpMatrix operator*(const vpRowVector &v) const; + + /*! + * Multiply a column vector by a matrix. + * + * \param M : Matrix. + * + * \return The resulting matrix. + */ + vpMatrix operator*(const vpMatrix &M) const; + + /*! + * Operator that allows to multiply each element of a column vector by a + * scalar. + * + * \param x : The scalar. + * + * \return The column vector multiplied by the scalar. The current + * column vector (*this) is unchanged. + * + * \code + * vpColVector v(3); + * v[0] = 1; + * v[1] = 2; + * v[2] = 3; + * + * vpColVector w = v * 3; + * // v is unchanged + * // w is now equal to : [3, 6, 9] + * \endcode + */ + vpColVector operator*(double x) const; + + /*! + * Operator that allows to multiply each element of a column vector by a + * scalar. + * + * \param x : The scalar. + * + * \return The column vector multiplied by the scalar. + * + * \code + * vpColVector v(3); + * v[0] = 1; + * v[1] = 2; + * v[2] = 3; + * + * v *= 3; + * // v is now equal to : [3, 6, 9] + * \endcode + */ + vpColVector &operator*=(double x); + + /*! + * Operator that allows to divide each element of a column vector by a scalar. + * + * \param x : The scalar. + * + * \return The column vector divided by the scalar. The current + * column vector (*this) is unchanged. + * + * \code + * vpColVector v(3); + * v[0] = 8; + * v[1] = 4; + * v[2] = 2; + * + * vpColVector w = v / 2; + * // v is unchanged + * // w is now equal to : [4, 2, 1] + * \endcode + */ + vpColVector operator/(double x) const; + + /*! + * Operator that allows to divide each element of a column vector by a scalar. + * + * \param x : The scalar. + * + * \return The column vector divided by the scalar. + * + * \code + * vpColVector v(3); + * v[0] = 8; + * v[1] = 4; + * v[2] = 2; + * + * v /= 2; + * // v is now equal to : [4, 2, 1] + * \endcode + */ + vpColVector &operator/=(double x); + + /*! + * Operator that allows to add two column vectors. + */ + vpColVector operator+(const vpColVector &v) const; + + /*! + * Operator that allows to add a column vector to a translation vector. + * + * \param t : 3-dimension translation vector to add. + * + * \return The sum of the current column vector (*this) and the translation + * vector to add. + * \code + * vpTranslationVector t1(1,2,3); + * vpColVector v(3); + * v[0] = 4; + * v[1] = 5; + * v[2] = 6; + * vpTranslationVector t2; + * + * t2 = v + t1; + * // t1 and v leave unchanged + * // t2 is now equal to : 5, 7, 9 + * \endcode + */ + vpTranslationVector operator+(const vpTranslationVector &t) const; + + /*! + * Operator that allows to add two column vectors. + */ + vpColVector &operator+=(const vpColVector &v); + + /*! + * Operator that allows to add a 3-dim translation vector to a 3-dim column vector. + */ + vpColVector &operator+=(const vpTranslationVector &t); + + /*! + * Operator subtraction of two vectors this = this - v + */ + vpColVector operator-(const vpColVector &v) const; + + /*! + * Operator that allows to subtract two column vectors. + */ + vpColVector &operator-=(const vpColVector &v); + + /*! + * Operator that allows to subtract a 3-dim translation vector to a 3-dim column vector. + */ + vpColVector &operator-=(const vpTranslationVector &t); + + /*! + * Operator that allows to negate all the column vector elements. + * + * \code + * vpColVector r(3, 1); + * // r contains [1 1 1]^T + * vpColVector v = -r; + * // v contains [-1 -1 -1]^T + * \endcode + */ + vpColVector operator-() const; + + /*! + * Copy operator. + * Allows operation such as A << v + * \code + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpColVector A, B(5); + * for (unsigned int i=0; i + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * size_t n = 5; + * vpColVector A(n); + * double *B = new double [n]; + * for (unsigned int i = 0; i < n; ++i) + * B[i] = i; + * A << B; + * std::cout << "A: " << A.t() << std::endl; + * delete [] B; + * } + * \endcode + * It produces the following output: + * \code + * A: 0 1 2 3 4 + * \endcode + */ + vpColVector &operator<<(double *x); + + /*! + * This operator could be used to set column vector elements: + * \code + * #include &A) + */ + int print(std::ostream &s, unsigned int length, char const *intro = nullptr) const; + + /*! + * Converts a column vector containing angles in radians into degrees and returns a reference + * to the vector. + * \return A reference to the vector with values expressed in [deg]. + * \sa deg2rad() + */ + inline vpColVector &rad2deg() + { + double r2d = 180.0 / M_PI; + + (*this) *= r2d; + return (*this); + } + + /*! + * Reshape the column vector in a matrix. + * + * \param M : the reshaped matrix. + * \param nrows : number of rows of the matrix. + * \param ncols : number of columns of the matrix. + * + * \exception vpException::dimensionError If the matrix and the column vector + * have not the same size. + * + * The following example shows how to use this method. + * \code + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * int var=0; + * vpMatrix mat(3, 4); + * for (int i = 0; i < 3; ++i) + * for (int j = 0; j < 4; ++j) + * mat[i][j] = ++var; + * std::cout << "mat: \n" << mat << std::endl; + * + * vpColVector col = mat.stackColumns(); + * std::cout << "column vector: \n" << col << std::endl; + * + * vpMatrix remat = col.reshape(3, 4); + * std::cout << "remat: \n" << remat << std::endl; + * } + * \endcode + * + * If you run the previous example, you get: + * \code + * mat: + * 1 2 3 4 + * 5 6 7 8 + * 9 10 11 12 + * column vector: + * 1 + * 5 + * 9 + * 2 + * 6 + * 10 + * 3 + * 7 + * 11 + * 4 + * 8 + * 12 + * remat: + * 1 2 3 4 + * 5 6 7 8 + * 9 10 11 12 + * \endcode + */ + void reshape(vpMatrix &M, const unsigned int &nrows, const unsigned int &ncols); + + /*! + * Reshape the column vector in a matrix. + * + * \param nrows : number of rows of the matrix + * \param ncols : number of columns of the matrix + * \return The reshaped matrix. + * + * \sa reshape(vpMatrix &, const unsigned int &, const unsigned int &) + */ + vpMatrix reshape(unsigned int nrows, unsigned int ncols); + + /*! + * Modify the size of the column vector. + + * \param i : Size of the vector. This value corresponds to the vector number + * of rows. + * \param flagNullify : If true, set the data to zero. + * \exception vpException::fatalError When \e ncols is not equal to 1. + */ + + void resize(unsigned int i, bool flagNullify = true) + { + vpArray2D::resize(i, 1, flagNullify); + } + + /*! + * Resize the column vector to a \e nrows-dimension vector. + * This function can only be used with \e ncols = 1. + * + * \param nrows : Vector number of rows. This value corresponds + * to the size of the vector. + * \param ncols : Vector number of columns. This value should be set to 1. + * \param flagNullify : If true, set the data to zero. + * + * \exception vpException::fatalError When \e ncols is not equal to 1. + */ + void resize(unsigned int nrows, unsigned int ncols, bool flagNullify) + { + if (ncols != 1) { + throw(vpException(vpException::fatalError, + "Cannot resize a column vector to a (%dx%d) " + "dimension vector that has more than one column", + nrows, ncols)); + } + vpArray2D::resize(nrows, ncols, flagNullify); + } + + /*! + * Stack column vector with a new element at the end of the vector. + * + * \param d : Element to stack to the existing vector. + * + * \code + * vpColVector v(3, 1); + * // v is equal to [1 1 1]^T + * v.stack(-2); + * // v is equal to [1 1 1 -2]^T + * \endcode + * + * \sa stack(const vpColVector &, const vpColVector &) + * \sa stack(const vpColVector &, const vpColVector &, vpColVector &) + */ + void stack(double d); + + /*! + * Stack column vectors. + * + * \param v : Vector to stack to the existing one. + * + * \code + * vpColVector v1(3, 1); + * // v1 is equal to [1 1 1]^T + * vpColVector v2(2, 3); + * // v2 is equal to [3 3]^T + * v1.stack(v2); + * // v1 is equal to [1 1 1 3 3]^T + * \endcode + * + * \sa stack(const vpColVector &, const double &) + * \sa stack(const vpColVector &, const vpColVector &) + * \sa stack(const vpColVector &, const vpColVector &, vpColVector &) + */ + void stack(const vpColVector &v); + + /*! + * Return the sum of all the elements \f$v_{i}\f$ of the column vector \f$ \bf v \f$ + * that is of dimension \f$ m \f$. + * + * \return The value \f[ \sum_{i=0}^{m-1} v_i \f]. + */ + double sum() const; + + /*! + * Return the sum of squares of all the elements \f$v_{i}\f$ of the column vector + * \f$ \bf v \f$ that is of dimension \f$ m \f$. + * + *\return The value \f[\sum_{i=0}^{m-1} v_i^{2}\f]. + */ + double sumSquare() const; + + /*! + * Transpose the column vector. The resulting vector becomes a row vector. + */ + vpRowVector t() const; + + /*! + * Converts the vpColVector to a std::vector. + * \return The corresponding std::vector. + */ + std::vector toStdVector() const; + + /*! + * Transpose the column vector. The resulting vector becomes a row vector. + * \sa t() + */ + vpRowVector transpose() const; + + /*! + * Transpose the column vector. The resulting vector \e v becomes a row vector. + * \sa t() + */ + void transpose(vpRowVector &v) const; + + /*! + * Compute and return the cross product of two 3-dimension vectors: \f$a + * \times b\f$. + * + * \param a : 3-dimension column vector. + * \param b : 3-dimension column vector. + * \return The cross product \f$a \times b\f$. + * + * \exception vpException::dimensionError If the vectors dimension is not + * equal to 3. + * + * \sa crossProd(), dotProd(), operator*(const vpColVector &) + */ + inline static vpColVector cross(const vpColVector &a, const vpColVector &b) { return crossProd(a, b); } + + /*! + * Compute and return the cross product of two vectors \f$a \times b\f$. + * + * \param[in] a : 3-dimension column vector. + * \param[in] b : 3-dimension column vector. + * \return The cross product \f$a \times b\f$. + * + * \exception vpException::dimensionError If the vectors dimension is not equal to 3. + * + * \sa dotProd() + */ + static vpColVector crossProd(const vpColVector &a, const vpColVector &b); + + /*! + * Compute end return the dot product of two column vectors: + * \f[ a \cdot b = \sum_{i=0}^n a_i * b_i\f] where \e n is the dimension of + * both vectors. + * + * \exception vpException::dimensionError If the vector dimension differ. + * + * \sa cross(), crossProd() + */ + static double dotProd(const vpColVector &a, const vpColVector &b); + + /*! + * Return a column vector with elements of \e v that are reverse sorted with + * values going from greatest to lowest. + * + * Example: + * \code + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpColVector v(10); + * v[0] = 5; v[1] = 7; v[2] = 4; v[3] = 2; v[4] = 8; + * v[5] = 6; v[6] = 1; v[7] = 9; v[8] = 0; v[9] = 3; + * + * std::cout << "v: " << v.t() << std::endl; + * + * vpColVector s = vpColVector::invSort(v); + * std::cout << "s: " << s.t() << std::endl; + * } + * \endcode + * Output: + * \code + * v: 5 7 4 2 8 6 1 9 0 3 + * s: 9 8 7 6 5 4 3 2 1 0 + * \endcode + * + * \sa sort() + */ + static vpColVector invSort(const vpColVector &v); + + /*! + * Compute the median value of all the elements of the vector. + */ + static double median(const vpColVector &v); + + /*! + * Compute the mean value of all the elements of the vector. + */ + static double mean(const vpColVector &v); + + /*! + * Compute the skew symmetric matrix \f$[{\bf v}]_\times\f$ of vector v. + * + * \f[ \mbox{if} \quad {\bf v} = \left( \begin{array}{c} x \\ y \\ z + * \end{array}\right), \quad \mbox{then} \qquad + * [{\bf v}]_\times = \left( \begin{array}{ccc} + * 0 & -z & y \\ + * z & 0 & -x \\ + * -y & x & 0 + * \end{array}\right) + * \f] + * + * \param v : Input vector used to compute the skew symmetric matrix. + */ + static vpMatrix skew(const vpColVector &v); + + /*! + * Return a column vector with elements of \e v that are sorted with values + * going from lowest to greatest. + * + * Example: + * \code + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpColVector v(10); + * v[0] = 5; v[1] = 7; v[2] = 4; v[3] = 2; v[4] = 8; + * v[5] = 6; v[6] = 1; v[7] = 9; v[8] = 0; v[9] = 3; + * + * std::cout << "v: " << v.t() << std::endl; + * + * vpColVector s = vpColVector::sort(v); + * std::cout << "s: " << s.t() << std::endl; + * } + * \endcode + * Output: + * \code + * v: 5 7 4 2 8 6 1 9 0 3 + * s: 0 1 2 3 4 5 6 7 8 9 + * \endcode + * \sa invSort() + */ + static vpColVector sort(const vpColVector &v); + + /*! + * Stack column vectors. + * + * \param A : Initial vector. + * \param B : Vector to stack at the end of A. + * \return Stacked vector \f$[A B]^T\f$. + * + * \code + * vpColVector A(3); + * vpColVector B(5); + * vpColVector C; + * C = vpColVector::stack(A, B); // C = [A B]T + * // C is now an 8 dimension column vector + * \endcode + * + * \sa stack(const vpColVector &) + * \sa stack(const vpColVector &, const vpColVector &, vpColVector &) + */ + static vpColVector stack(const vpColVector &A, const vpColVector &B); + + /*! + * Stack column vectors. + * + * \param A : Initial vector. + * \param B : Vector to stack at the end of A. + * \param C : Resulting stacked vector \f$C = [A B]^T\f$. + * + * \code{.cpp} + * vpColVector A(3); + * vpColVector B(5); + * vpColVector C; + * vpColVector::stack(A, B, C); // C = [A B]T + * // C is now an 8 dimension column vector + * \endcode + * + * \sa stack(const vpColVector &) + * \sa stack(const vpColVector &, const vpColVector &) + */ + static void stack(const vpColVector &A, const vpColVector &B, vpColVector &C); + + /*! + * Compute the standard deviation value of all the elements of the vector. + */ + static double stdev(const vpColVector &v, bool useBesselCorrection = false); + +#ifdef VISP_HAVE_NLOHMANN_JSON + /*! + * Convert a vpColVector object to a JSON representation. + * + * @param j : Resulting json object. + * @param v : The object to convert. + */ + friend void to_json(nlohmann::json &j, const vpColVector &v); + + /*! + * Retrieve a vpColVector object from a JSON representation. + * + * @param j : JSON representation to convert. + * @param v : Converted object. + */ + friend void from_json(const nlohmann::json &j, vpColVector &v); +#endif + +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) + /*! + @name Deprecated functions + */ + //@{ + /*! + * \deprecated Provided only for compat with previous releases. + * This function does nothing. + */ + VP_DEPRECATED void init() { } + + /*! + * \deprecated Provided only for compat with previous releases. Use rather + * insert(unsigned int i, const vpColVector &v) + * + * Insert a column vector. + * \param i : Index of the first element to introduce. This index starts from 0. + * \param v : Column vector to insert. + * + * The following example shows how to use this function: + * \code + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpColVector v(4); + * for (unsigned int i=0; i < v.size(); ++i) + * v[i] = i; + * std::cout << "v: " << v.t() << std::endl; + * + * vpColVector w(2); + * for (unsigned int i=0; i < w.size(); ++i) + * w[i] = i+10; + * std::cout << "w: " << w.t() << std::endl; + * + * v.insert(w, 1); + * std::cout << "v: " << v.t() << std::endl; + * } + * \endcode + * It produces the following output: + * \code + * v: 0 1 2 3 + * w: 10 11 + * v: 0 10 11 3 + * \endcode + */ + VP_DEPRECATED void insert(const vpColVector &v, unsigned int i); + + /*! + * \deprecated You should rather use extract(). + */ + VP_DEPRECATED vpColVector rows(unsigned int first_row, unsigned int last_row) const + { + return vpColVector(*this, first_row - 1, last_row - first_row + 1); + } + + /*! + * \deprecated You should rather use stack(const vpColVector &) + */ + VP_DEPRECATED void stackMatrices(const vpColVector &r) { stack(r); } + + /*! + * \deprecated You should rather use stack(const vpColVector &A, const vpColVector &B) + */ + VP_DEPRECATED static vpColVector stackMatrices(const vpColVector &A, const vpColVector &B) { return stack(A, B); } + + /*! + * \deprecated You should rather use stack(const vpColVector &A, const vpColVector &B, vpColVector &C) + */ + VP_DEPRECATED static void stackMatrices(const vpColVector &A, const vpColVector &B, vpColVector &C) + { + stack(A, B, C); + } + + /*! + * \deprecated You should rather use insert(unsigned int, const vpColVector &). + * + * Insert column vector \e v at the given position \e r in the current column + * vector. + * + * \warning Throw vpMatrixException::incorrectMatrixSizeError if the + * dimensions of the matrices do not allow the operation. + * + * \param v : The column vector to insert. + * \param r : The index of the row to begin to insert data. + * \param c : Not used. + */ + VP_DEPRECATED void insert(const vpColVector &v, unsigned int r, unsigned int c = 0); + + /*! + * \deprecated This function is deprecated. You should rather use frobeniusNorm(). + * + * Compute and return the Euclidean norm also called Frobenius norm \f$ ||v|| = \sqrt{ \sum{v_{i}^2}} \f$. + * + * \return The Euclidean norm if the vector is initialized, 0 otherwise. + * + * \sa frobeniusNorm(), infinityNorm() + */ + VP_DEPRECATED double euclideanNorm() const; + //@} +#endif +}; + +/*! + * \relates vpColVector + * Allows to multiply a scalar by a column vector. + */ +#ifndef DOXYGEN_SHOULD_SKIP_THIS +VISP_EXPORT +#endif +vpColVector operator*(const double &x, const vpColVector &v); + + +#ifdef VISP_HAVE_NLOHMANN_JSON +inline void to_json(nlohmann::json &j, const vpColVector &v) +{ + const vpArray2D *asArray = &v; + to_json(j, *asArray); + j["type"] = "vpColVector"; +} + +inline void from_json(const nlohmann::json &j, vpColVector &v) +{ + vpArray2D *asArray = &v; + from_json(j, *asArray); + if (v.getCols() != 1) { + throw vpException(vpException::badValue, "From JSON, tried to read a 2D array into a vpColVector"); + } +} +#endif +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpColor.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpColor.h new file mode 100644 index 00000000..b76bd78d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpColor.h @@ -0,0 +1,386 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Color definition. + */ + +#ifndef VP_COLOR_H +#define VP_COLOR_H + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + + \class vpColor + + \ingroup group_core_gui + + \brief Class to define RGB colors available for display functionalities. + + - You may use predefined colors: + - vpColor::black, vpColor::white, + - vpColor::lightGray, vpColor::gray, vpColor::darkGray, + - vpColor::lightRed, vpColor::red, vpColor::darkRed, + - vpColor::lightGreen, vpColor::green, vpColor::darkGreen, + - vpColor::lightBlue, vpColor::blue, vpColor::darkBlue, + - vpColor::yellow, vpColor::cyan, vpColor::orange, vpColor::purple + \code + vpColor color1(vpColor::lightGreen); + vpColor color2 = vpColor::lightGreen; + \endcode + - or specify your own color by settings its R,G,B values. + \code + unsigned char R=140, G=255, B=140; + vpColor color1(R, G, B); + vpColor color2; + color2.setColor(R, G, B); + \endcode + + \warning Since ViSP 3.3.1 or higher we introduce the alpha channel support for color + transparency. + This feature is for the moment only supported using vpDisplayOpenCV. + To use transparency you may set an additional alpha parameter. When the value is 255, + there is no transparency. A value equal to 0 means that the color is completely + transparent. The following examples show how to introduce transparency: + - using predefined colors: + \code + unsigned char alpha = 128; + vpColor color1(vpColor::lightGreen, alpha); + vpColor color2 = vpColor(vpColor::lightGreen, alpha); + \endcode + - or using user defined colors: + \code + unsigned char R=140, G=255, B=140, alpha=128; + vpColor color1(R, G, B, alpha); + vpColor color2; + color2.setColor(R, G, B, alpha); + \endcode + See displayOpenCV.cpp example for complete usage when displaying filled transparent + circles or rectangles. + + An identifier vpColor::vpColorIdentifier is associated to each color. This + identifier is useful to determine if a color is predefined or + specified by it R,G,B values. In that last case, the identifier is + always set to vpColor::id_unknown. + + The example below shows how to display geometric features in a + display overlay using predefined colors (here the blue color to draw + a circle) and a specific brown color (used to draw a rectangle). + + \code + #include + #include + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpImage I(240, 320); // Create a black grey level image + + vpDisplay *d; + + // Depending on the detected third party libraries, we instantiate here the + // first video device which is available + #if defined(VISP_HAVE_X11) + d = new vpDisplayX; + #elif defined(VISP_HAVE_GTK) + d = new vpDisplayGTK; + #elif defined(VISP_HAVE_GDI) + d = new vpDisplayGDI; + #elif defined(VISP_HAVE_D3D9) + d = new vpDisplayD3D; + #elif defined(HAVE_OPENCV_HIGHGUI) + d = new vpDisplayOpenCV; + #endif + + // Initialize the display with the image I. Display and image are + // now link together. + #ifdef VISP_HAVE_DISPLAY + d->init(I); + #endif + + // Set the display background with image I content + vpDisplay::display(I); + + // Draw a filled circle with the predefined blue color + vpDisplay::displayCircle(I, 100, 200, 30, vpColor::blue, true); + + // Creation of a new brown color with its RGB values + vpColor color(128, 100, 50); + + // Draw a brown rectangle in the display overlay (foreground) + vpDisplay::displayRectangle(I, 10, 10, 100, 20, color, true); + + // Flush the foreground and background display + vpDisplay::flush(I); + + delete d; + } + \endcode + +*/ +class VISP_EXPORT vpColor : public vpRGBa +{ +public: + /*! Predefined colors identifier. */ + typedef enum + { + id_black = 0, /*!< Identifier associated to the predefined vpColor::black color. */ + id_white, /*!< Identifier associated to the predefined vpColor::white color. */ + id_lightGray, /*!< Identifier associated to the predefined vpColor::lightGray color. */ + id_gray, /*!< Identifier associated to the predefined vpColor::gray color. */ + id_darkGray, /*!< Identifier associated to the predefined pColor::darkGray color. */ + id_lightRed, /*!< Identifier associated to the predefined vpColor::lightRed color. */ + id_red, /*!< Identifier associated to the predefined vpColor::red color. */ + id_darkRed, /*!< Identifier associated to the predefined vpColor::darkRed color. */ + id_lightGreen, /*!< Identifier associated to the predefined vpColor::lightGreen color. */ + id_green, /*!< Identifier associated to the predefined vpColor::green color. */ + id_darkGreen, /*!< Identifier associated to the predefined vpColor::darkGreen color. */ + id_lightBlue, /*!< Identifier associated to the predefined vpColor::lightBlue color. */ + id_blue, /*!< Identifier associated to the predefined vpColor::blue color. */ + id_darkBlue, /*!< Identifier associated to the predefined vpColor::darkBlue color. */ + id_yellow, /*!< Identifier associated to the predefined vpColor::yellow color. */ + id_cyan, /*!< Identifier associated to the predefined vpColor::cyan color. */ + id_orange, /*!< Identifier associated to the predefined vpColor::orange color. */ + id_purple, /*!< Identifier associated to the predefined vpColor::purple color. */ + + id_unknown /*!< Identifier associated with unknown colors. + By unknown, we mean that it is not a predefined colour. + This identifier can also be used to find out the number of predefined colours */ + + } vpColorIdentifier; + + vpColorIdentifier id; /*!< Color identifier to indicate if a color + is predefined or set by the user using its + RGB values. */ + + /* Predefined colors. */ + static const vpColor black; + static const vpColor white; + static const vpColor lightGray; + static const vpColor gray; + static const vpColor darkGray; + static const vpColor lightRed; + static const vpColor red; + static const vpColor darkRed; + static const vpColor lightGreen; + static const vpColor green; + static const vpColor darkGreen; + static const vpColor lightBlue; + static const vpColor blue; + static const vpColor darkBlue; + static const vpColor yellow; + static const vpColor cyan; + static const vpColor orange; + static const vpColor purple; + static const vpColor none; + + static const unsigned int nbColors; + static const vpColor allColors[]; + + /*! + Default constructor. All the colors components are set to zero. + + The color identifier is set to vpColor::id_unknown to indicate + that this color is not a predefined one. + + */ + inline vpColor() : vpRGBa(), id(id_unknown) { } + + /*! Default destructor. */ +#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98) + vpColor(const vpColor &) = default; + virtual ~vpColor() = default; +#else + virtual ~vpColor() { } +#endif + /*! + Construct a color from its RGB values. + + \param r : Red component. + \param g : Green component. + \param b : Blue component. + + \param cid : The color identifier to indicate if this color is or + not a predefined one. + */ + inline vpColor(unsigned char r, unsigned char g, unsigned char b, + vpColor::vpColorIdentifier cid = vpColor::id_unknown) + : vpRGBa(r, g, b), id(cid) + { } + /*! + Construct a color from its RGB values and alpha channel. + + \param r : Red component. + \param g : Green component. + \param b : Blue component. + \param alpha : Alpha channel for transparency. + + \param cid : The color identifier to indicate if this color is or + not a predefined one. + */ + inline vpColor(unsigned char r, unsigned char g, unsigned char b, unsigned char alpha, + vpColor::vpColorIdentifier cid = vpColor::id_unknown) + : vpRGBa(r, g, b, alpha), id(cid) + { } + /*! + Construct a color with an alpha channel. + + \param color : RGB color. + \param alpha : Alpha channel for transparency. + */ + inline vpColor(const vpColor &color, unsigned char alpha) : vpRGBa(color.R, color.G, color.B, alpha), id(color.id) { } + +#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98) + vpColor &operator=(const vpColor &) = default; +#endif + friend VISP_EXPORT bool operator==(const vpColor &c1, const vpColor &c2); + friend VISP_EXPORT bool operator!=(const vpColor &c1, const vpColor &c2); + /*! + Set a color from its RGB values. + + \param r : Red component. + \param g : Green component. + \param b : Blue component. + \param a : Alpha component for transparency. + + The color identifier is set to vpColor::id_unknown to indicate + that this color is not a predefined one. + + */ + inline void setColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a = vpRGBa::alpha_default) + { + this->R = r; + this->G = g; + this->B = b; + this->A = a; + id = id_unknown; + } + + /*! + + Get a predefined color + + \param i : color indice + */ + static inline vpColor getColor(const unsigned int &i) { return vpColor::allColors[i % vpColor::nbColors]; } +}; + +// In this file if windows +#if defined(VISP_USE_MSVC) && defined(visp_EXPORTS) +/*!< Predefined black color with R=G=B=0 and identifier vpColor::id_black. */ +VP_ATTRIBUTE_NO_DESTROY vpColor const __declspec(selectany) vpColor::black = vpColor(0, 0, 0, id_black); +/*!< Predefined white color with R=G=B=255 and identifier vpColor::id_white. + */ +VP_ATTRIBUTE_NO_DESTROY vpColor const __declspec(selectany) vpColor::white = vpColor(255, 255, 255, id_white); +/*!< Predefined light gray color with R=G=B=64 and identifier + * vpColor::id_lightGray. */ +VP_ATTRIBUTE_NO_DESTROY vpColor const __declspec(selectany) vpColor::lightGray = vpColor(192, 192, 192, id_lightGray); +/*!< Predefined gray color with R=G=B=128 and identifier vpColor::id_gray. */ +VP_ATTRIBUTE_NO_DESTROY vpColor const __declspec(selectany) vpColor::gray = vpColor(128, 128, 128, id_gray); +/*!< Predefined dark gray color with R=G=B=192 and identifier + * vpColor::id_darkGray. */ +VP_ATTRIBUTE_NO_DESTROY vpColor const __declspec(selectany) vpColor::darkGray = vpColor(64, 64, 64, id_darkGray); +/*!< Predefined light red color with R= 255 and G=B=140 and identifier + vpColor::id_lightRed. */ +VP_ATTRIBUTE_NO_DESTROY vpColor const __declspec(selectany) vpColor::lightRed = vpColor(255, 140, 140, id_lightRed); +/*!< Predefined red color with R=255 and G=B=0 and identifier + vpColor::id_red. */ +VP_ATTRIBUTE_NO_DESTROY vpColor const __declspec(selectany) vpColor::red = vpColor(255, 0, 0, id_red); +/*!< Predefined dark red color with R= 128 and G=B=0 and identifier + vpColor::id_darkRed. */ +VP_ATTRIBUTE_NO_DESTROY vpColor const __declspec(selectany) vpColor::darkRed = vpColor(128, 0, 0, id_darkRed); +/*!< Predefined light green color with G= 255 and R=B=140 and identifier + vpColor::id_lightGreen. */ +VP_ATTRIBUTE_NO_DESTROY vpColor const __declspec(selectany) vpColor::lightGreen = vpColor(140, 255, 140, id_lightGreen); +/*!< Predefined green color with G=255 and R=B=0 and identifier + vpColor::id_green. */ +VP_ATTRIBUTE_NO_DESTROY vpColor const __declspec(selectany) vpColor::green = vpColor(0, 255, 0, id_green); +/*!< Predefined dark green color with G= 128 and R=B=0 and identifier + vpColor::id_darkGreen. */ +VP_ATTRIBUTE_NO_DESTROY vpColor const __declspec(selectany) vpColor::darkGreen = vpColor(0, 128, 0, id_darkGreen); +/*!< Predefined light blue color with B= 255 and R=G=140 and identifier + vpColor::id_lightBlue. */ +VP_ATTRIBUTE_NO_DESTROY vpColor const __declspec(selectany) vpColor::lightBlue = vpColor(140, 140, 255, id_lightBlue); +/*!< Predefined blue color with R=G=0 and B=255 and identifier + vpColor::id_blue. */ +VP_ATTRIBUTE_NO_DESTROY vpColor const __declspec(selectany) vpColor::blue = vpColor(0, 0, 255, id_blue); +/*!< Predefined dark blue color with B= 128 and R=G=0 and identifier + vpColor::id_darkBlue. */ +VP_ATTRIBUTE_NO_DESTROY vpColor const __declspec(selectany) vpColor::darkBlue = vpColor(0, 0, 128, id_darkBlue); +/*!< Predefined yellow color with R=G=255 and B=0 and identifier + vpColor::id_yellow. */ +VP_ATTRIBUTE_NO_DESTROY vpColor const __declspec(selectany) vpColor::yellow = vpColor(255, 255, 0, id_yellow); +/*!< Predefined cyan color with R=0 and G=B=255 and identifier + vpColor::id_cyan. */ +VP_ATTRIBUTE_NO_DESTROY vpColor const __declspec(selectany) vpColor::cyan = vpColor(0, 255, 255, id_cyan); +/*!< Predefined orange color with R=255, G=165 and B=0 and identifier + vpColor::id_orange. */ +VP_ATTRIBUTE_NO_DESTROY vpColor const __declspec(selectany) vpColor::orange = vpColor(255, 165, 0, id_orange); +/*!< Predefined purple color with R=128, G=0 and B=128 and identifier + vpColor::id_purple. */ +VP_ATTRIBUTE_NO_DESTROY vpColor const __declspec(selectany) vpColor::purple = vpColor(128, 0, 128, id_purple); +/*!< Predefined none color with R=G=B=0 and identifier vpColor::id_unknown. */ +VP_ATTRIBUTE_NO_DESTROY vpColor const __declspec(selectany) vpColor::none = vpColor(0, 0, 0, id_unknown); + +VP_ATTRIBUTE_NO_DESTROY const __declspec(selectany) unsigned int vpColor::nbColors = 18; + +/*!< Array of available colors. */ +VP_ATTRIBUTE_NO_DESTROY vpColor const __declspec(selectany) vpColor::allColors[vpColor::nbColors] = { + vpColor::blue, // 12 + vpColor::green, // 9 + vpColor::red, // 6 + vpColor::cyan, // 15 + vpColor::purple, // 4 + vpColor::yellow, // 14 + vpColor::orange, // 16 + vpColor::lightBlue, // 11 + vpColor::lightGreen, // 8 + vpColor::lightRed, // 5 + vpColor::darkBlue, // 13 + vpColor::darkGreen, // 10 + vpColor::darkRed, // 7 + vpColor::lightGray, // 2 + vpColor::gray, // 3 + vpColor::darkGray, // 4 + vpColor::black, // 0 + vpColor::white +}; // 17 + +#endif +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpColorDepthConversion.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpColorDepthConversion.h new file mode 100644 index 00000000..cc1b849f --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpColorDepthConversion.h @@ -0,0 +1,84 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Color to Depth conversion. + */ + +#ifndef VP_COLOR_DEPTH_CONVERSION_H +#define VP_COLOR_DEPTH_CONVERSION_H + +#include + +// Internal +#include "vpCameraParameters.h" +#include "vpImage.h" + +BEGIN_VISP_NAMESPACE +/** + * \brief Class that permits to project a color image into a depth frame. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in learning how to use this class, you may have a look at:
+ * + * - \ref tutorial-planar-object-pose +*/ +class VISP_EXPORT vpColorDepthConversion +{ +public: + static vpImagePoint projectColorToDepth(const vpImage &I_depth, const double &depth_scale, const double &depth_min, + const double &depth_max, const vpCameraParameters &depth_intrinsics, + const vpCameraParameters &color_intrinsics, + const vpHomogeneousMatrix &color_M_depth, + const vpHomogeneousMatrix &depth_M_color, const vpImagePoint &from_pixel); + static vpImagePoint projectColorToDepth(const uint16_t *data, const double &depth_scale, const double &depth_min, const double &depth_max, + const double &depth_width, const double &depth_height, + const vpCameraParameters &depth_intrinsics, + const vpCameraParameters &color_intrinsics, + const vpHomogeneousMatrix &color_M_depth, + const vpHomogeneousMatrix &depth_M_color, const vpImagePoint &from_pixel); + + static vpImagePoint projectColorToDepth(const vpImage &I_depth, const double &depth_min, + const double &depth_max, const vpCameraParameters &depth_intrinsics, + const vpCameraParameters &color_intrinsics, + const vpHomogeneousMatrix &color_M_depth, + const vpHomogeneousMatrix &depth_M_color, const vpImagePoint &from_pixel); + + static vpImagePoint projectColorToDepth(const float *data, const double &depth_min, const double &depth_max, + const double &depth_width, const double &depth_height, + const vpCameraParameters &depth_intrinsics, + const vpCameraParameters &color_intrinsics, + const vpHomogeneousMatrix &color_M_depth, + const vpHomogeneousMatrix &depth_M_color, const vpImagePoint &from_pixel); +}; +END_VISP_NAMESPACE + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpColorGetter.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpColorGetter.h new file mode 100644 index 00000000..4462f7ee --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpColorGetter.h @@ -0,0 +1,186 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Color getter. + */ + +#ifndef VP_COLOR_GETTER_H +#define VP_COLOR_GETTER_H + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/** + * \brief Helper class that permits to get a channel of a color type by its index instead of its name. + * + * \tparam ID The index of the channel. + */ + template +class vpColorGetter +{ +public: +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) +/** + * \brief Setter for a channel of a HSV pixel. + * + * \tparam ArithmeticType The encoding type of a channel. + * \tparam useFullScale When using unsigned charn, either true if the Hue channel uses the full range + * of unsigned char or false if it uses a limited range. + * \param[in] col The pixel we want to set a channel value. + * \return ArithmeticType& The channel value. + */ + template + static ArithmeticType &get(vpHSV &col); + + /** + * \brief Getter for a channel of a HSV pixel. + * + * \tparam ArithmeticType The encoding type of a channel. + * \tparam useFullScale When using unsigned charn, either true if the Hue channel uses the full range + * of unsigned char or false if it uses a limited range. + * \param[in] col The pixel we want to get a channel value. + * \return ArithmeticType& The channel value. + */ + template + static const ArithmeticType &get(const vpHSV &col); +#endif + + /** + * \brief Setter for a vpRGBa pixel. + * + * \param[in] col The pixel. + * \return unsigned char& The channel after the change has been performed. + */ + static unsigned char &get(vpRGBa &col); + + /** + * \brief Getter for a vpRGBA pixel. + * + * \param[in] col The pixel. + * \return const unsigned char& The value of the desired channel. + */ + static const unsigned char &get(const vpRGBa &col); +}; + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) +template<> +template +inline ArithmeticType &vpColorGetter<0>::get(vpHSV &hsv) +{ + return hsv.H; +} + +template<> +template +inline ArithmeticType &vpColorGetter<1>::get(vpHSV &hsv) +{ + return hsv.S; +} + +template<> +template +inline ArithmeticType &vpColorGetter<2>::get(vpHSV &hsv) +{ + return hsv.V; +} + +template<> +template +inline const ArithmeticType &vpColorGetter<0>::get(const vpHSV &hsv) +{ + return hsv.H; +} + +template<> +template +inline const ArithmeticType &vpColorGetter<1>::get(const vpHSV &hsv) +{ + return hsv.S; +} + +template<> +template +inline const ArithmeticType &vpColorGetter<2>::get(const vpHSV &hsv) +{ + return hsv.V; +} +#endif + +template<> +inline unsigned char &vpColorGetter<0>::get(vpRGBa &rgba) +{ + return rgba.R; +} + +template<> +inline unsigned char &vpColorGetter<1>::get(vpRGBa &rgba) +{ + return rgba.G; +} + +template<> +inline unsigned char &vpColorGetter<2>::get(vpRGBa &rgba) +{ + return rgba.B; +} + +template<> +inline unsigned char &vpColorGetter<3>::get(vpRGBa &rgba) +{ + return rgba.A; +} + +template<> +inline const unsigned char &vpColorGetter<0>::get(const vpRGBa &rgba) +{ + return rgba.R; +} + +template<> +inline const unsigned char &vpColorGetter<1>::get(const vpRGBa &rgba) +{ + return rgba.G; +} + +template<> +inline const unsigned char &vpColorGetter<2>::get(const vpRGBa &rgba) +{ + return rgba.B; +} + +template<> +inline const unsigned char &vpColorGetter<3>::get(const vpRGBa &rgba) +{ + return rgba.A; +} +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpColormap.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpColormap.h new file mode 100644 index 00000000..d1131519 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpColormap.h @@ -0,0 +1,103 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Colormap class to recolor an image with different grayscale values into + * some corresponding color values, for better visualization for example. + */ + +/*! + * \file vpColormap.h + * + * \brief Colormap tool to have a mapping between 256 values and RGB values. + */ + +#ifndef _vpColormap_h_ +#define _vpColormap_h_ + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpColormap + * + * \ingroup group_core_image + * + * \brief Creates a colormap class to be able to recolor an image with different grayscale + * values into some corresponding color values, for better visualization for example. +*/ +class VISP_EXPORT vpColormap +{ +public: + enum vpColormapType + { + COLORMAP_AUTUMN, + COLORMAP_CIVIDIS, + COLORMAP_COOL, + COLORMAP_GIST_EARTH, + COLORMAP_GNUPLOT, + COLORMAP_GNUPLOT2, + COLORMAP_HOT, + COLORMAP_HSV, + COLORMAP_INFERNO, + COLORMAP_JET, + COLORMAP_MAGMA, + COLORMAP_OCEAN, + COLORMAP_PLASMA, + COLORMAP_RAINBOW, + COLORMAP_SPRING, + COLORMAP_SUMMER, + COLORMAP_TERRAIN, + COLORMAP_TURBO, + COLORMAP_TWILIGHT, + COLORMAP_TWILIGHT_SHIFTED, + COLORMAP_VIRIDIS, + COLORMAP_WINTER + }; + + vpColormap(const vpColormapType &colormapType); + + void convert(const vpImage &I, vpImage &Icolor, bool normalize = false); + void convert(const vpImage &I, vpImage &Icolor, bool normalize = false); + + void convert(const vpImage &I, vpImage &Icolor); + void convert(const vpImage &I, vpImage &Icolor); + +private: + vpColormapType m_colormapType; +#if VISP_CXX_STANDARD > VISP_CXX_STANDARD_98 + unsigned char m_colormapSrgbBytes[256][3] = {}; +#else + unsigned char m_colormapSrgbBytes[256][3]; +#endif +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpConfig.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpConfig.h new file mode 100644 index 00000000..7020d0b4 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpConfig.h @@ -0,0 +1,759 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * ViSP configuration. + */ + +#ifndef VP_CONFIG_H +#define VP_CONFIG_H + +// To get access to EXIT_SUCCESS and EXIT_FAILURE +#include + +#if defined _MSC_VER && _MSC_VER >= 1200 +#pragma warning( disable: 4100 4127 4251 4275 4351 4514 4668 4710 4820 ) +#if _MSC_VER >= 1400 // 1400 = MSVC 8 2005 +#pragma warning( disable: 4548 ) +#endif +#if _MSC_VER > 1500 // 1500 = MSVC 9 2008 +#pragma warning( disable: 4986 ) +#endif +#ifdef WINRT +#pragma warning(disable:4447) +#endif + +// 4100 : undocumented ("unreferenced formal parameter") +// 4127 : conditional expression is constant +// 4251 : 'identifier' : class 'type' needs to have dll-interface to be used by clients of class 'type2', ie. disable warnings related to inline functions +// 4275 : non – DLL-interface classkey 'identifier' used as base for DLL-interface classkey 'identifier' +// 4351 : new behavior: elements of array will be default initialized +// 4447 : Disable warning 'main' signature found without threading model +// 4514 : 'function' : unreferenced inline function has been removed +// 4548 : expression before comma has no effect +// 4668 : 'symbol' is not defined as a preprocessor macro, replacing with '0' for 'directives' +// 4710 : 'function' : function not inlined +// 4820 : 'bytes' bytes padding added after construct 'member_name' +// 4986 : undocumented + +#ifndef NOMINMAX +#define NOMINMAX +#endif +#endif + +#if defined _MSC_VER && (_MSC_VER == 1500) +// Visual Studio 9 2008 specific stuff +// Fix running 64-bit OpenMP Debug Builds compiled with Visual Studio 2008 SP1 +// See discussion on https://gforge.inria.fr/forum/message.php?msg_id=149273&group_id=397 +// and the proposed fix: http://www.johanseland.com/2010/08/running-64-bit-openmp-debug-builds.html +# define _BIND_TO_CURRENT_OPENMP_VERSION 1 +#endif + +#if defined(__MINGW__) || defined(__MINGW32__) || defined(__MINGW64__) +// Work around to fix build issues that may occur with Mingw: +// error: 'DBL_EPSILON' was not declared in this scope +// error: 'FLT_EPSILON' was not declared in this scope + +# include + +# ifndef DBL_EPSILON +# define DBL_EPSILON __DBL_EPSILON__ +# endif +# ifndef FLT_EPSILON +# define FLT_EPSILON __FLT_EPSILON__ +# endif +#endif + +#include + +// ViSP major version. +#define VISP_VERSION_MAJOR 3 + +// ViSP minor version. +#define VISP_VERSION_MINOR 7 + +// ViSP patch version. +#define VISP_VERSION_PATCH 0 + +// ViSP version with dots "3.7.0". +#define VISP_VERSION 3.7.0 + +// ViSP version as an integer +#define VP_VERSION_INT(a, b, c) (a<<16 | b<<8 | c) +#define VISP_VERSION_INT VP_VERSION_INT(VISP_VERSION_MAJOR, \ + VISP_VERSION_MINOR, \ + VISP_VERSION_PATCH) + +// Defined if the user wants to protect the classes in a dedicated visp namespace +/* #undef ENABLE_VISP_NAMESPACE */ +#define VISP_NAMESPACE_NAME visp +#ifdef ENABLE_VISP_NAMESPACE +#define VISP_NAMESPACE_ADDRESSING visp:: +#define BEGIN_VISP_NAMESPACE namespace visp { +#define END_VISP_NAMESPACE } +// Create an empty namespace to ensure that "using VISP_NAMESPACE_NAME;" does not raise an error +namespace VISP_NAMESPACE_NAME {} + +// Create an alias for compatibility with older versions of ViSP +namespace vp = VISP_NAMESPACE_NAME; +#else +#define VISP_NAMESPACE_ADDRESSING +#define BEGIN_VISP_NAMESPACE +#define END_VISP_NAMESPACE +#endif + +// Defined if the warp() methods of vpImageTools are enabled +#define ENABLE_IMAGE_TOOLS_WARP + +// Enable debug and trace printings +/* #undef VP_TRACE */ +/* #undef VP_DEBUG */ +/* #undef VP_DEBUG_MODE */ + +// ViSP library is either compiled static or shared +// Used to set declspec(import, export) in headers if required under Windows +#define VISP_BUILD_SHARED_LIBS + +// Defined if deprecated functionalities are requested to build +#define VISP_BUILD_DEPRECATED_FUNCTIONS + +// Defined if MSVC is the compiler +/* #undef VISP_USE_MSVC */ + +// Defined if Clipper is build and available. +#define VISP_HAVE_CLIPPER + +// Defined if AprilTag is build and available. +#define VISP_HAVE_APRILTAG + +// Defined if AprilTag big family (41h12, 48h12, 49h12, 52h13) known to produce long build time issue +// with gcc < 5.5 in RelWithDebInfo and Visual Studio 12 2013 in Release are available +/* #undef VISP_HAVE_APRILTAG_BIG_FAMILY */ + +// Defined if X11 library available. +#define VISP_HAVE_X11 + +// Always define pugixml for compatibility. +#define VISP_HAVE_PUGIXML + +// Defined if basisu_miniz is used internally +#define VISP_HAVE_MINIZ + +// Defined if XML2 library available. +#define VISP_HAVE_XML2 + +// Defined if pthread library available (deprecated). +#define VISP_HAVE_PTHREAD + +// Defined if std::thread available. +#define VISP_HAVE_THREADS + +// Defined if YARP available. +/* #undef VISP_HAVE_YARP */ + +// Defined if OpenCV available. +#define VISP_HAVE_OPENCV + +// Defined if using a headless version of OpenCV +/* #undef VISP_HAVE_OPENCV_HEADLESS */ + +// OpenCV version in hexadecimal (for example 2.1.0 gives 0x020100). +#ifdef VISP_HAVE_OPENCV +# define VISP_HAVE_OPENCV_VERSION (4<<16 | 13<<8 | 0) +# include +#endif + +// For compat with previous releases +#if defined(HAVE_OPENCV_XFEATURES2D) +#define VISP_HAVE_OPENCV_XFEATURES2D +#endif + +#if defined(HAVE_OPENCV_OBJDETECT) +#define VISP_HAVE_OPENCV_OBJDETECT +#endif + +// Defined if gtk+-2.0 library available +/* #undef VISP_HAVE_GTK */ + +// Defined if GDI (Graphics Device Interface) library available +/* #undef VISP_HAVE_GDI */ + +// Defined if Direct3D9 library available +/* #undef VISP_HAVE_D3D9 */ + +// Defined if one of the display device is available +#if defined(VISP_HAVE_X11) || defined(VISP_HAVE_GDI) || defined(HAVE_OPENCV_HIGHGUI) || defined(VISP_HAVE_D3D9) || defined(VISP_HAVE_GTK) +# define VISP_HAVE_DISPLAY +#endif + +// Defined if Catch2 library available +#define VISP_HAVE_CATCH2 + +// Defined if simdlib library available +#define VISP_HAVE_SIMDLIB + +// Defined if stb_image library available +#define VISP_HAVE_STBIMAGE + +// Defined if tinyexr library available +#define VISP_HAVE_TINYEXR + +// Defined if Eigen3 library available +/* #undef VISP_HAVE_EIGEN3 */ + +// Defined if Intel MKL library available +/* #undef VISP_HAVE_MKL */ +#ifdef VISP_HAVE_MKL +#define VISP_HAVE_LAPACK_MKL +#endif + +// Defined if OpenBLAS library available +/* #undef VISP_HAVE_OPENBLAS */ +#ifdef VISP_HAVE_OPENBLAS +#define VISP_HAVE_LAPACK_OPENBLAS +#endif + +// Defined if Atlas library available +/* #undef VISP_HAVE_ATLAS */ +#ifdef VISP_HAVE_ATLAS +#define VISP_HAVE_LAPACK_ATLAS +#endif + +// Defined if Netlib library available (-lblas -llapack) +#define VISP_HAVE_NETLIB +#ifdef VISP_HAVE_NETLIB +#define VISP_HAVE_LAPACK_NETLIB +#endif + +// Defined if GSL library available (-lgsl -lgslcblas) +/* #undef VISP_HAVE_GSL */ +#ifdef VISP_HAVE_GSL +#define VISP_HAVE_LAPACK_GSL +#endif + +// Defined if lapack/blas libraries are available (MKL, OpenBLAS, Atlas, Netlib or built-in) +#define VISP_HAVE_LAPACK +// To keep compat with previous versions +#ifdef VISP_HAVE_NETLIB +# define VISP_HAVE_LAPACK_C +#endif + +// Defined if clapack built-in +/* #undef VISP_HAVE_LAPACK_BUILT_IN */ + +// Defined the path to the Rubik-Regular.ttf used by vpFont +#define VISP_RUBIK_REGULAR_FONT_RESOURCES "/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/font/Rubik-Regular.ttf" + +// Defined the path to the basic scenes used by the simulator +#define VISP_SCENES_DIR "/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/wireframe-simulator" + +// Defined the path to the robot's arms 3D model used by the robot simulators +#define VISP_ROBOT_ARMS_DIR "/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/robot-simulator" + +// Defined if Ogre3d is available. +/* #undef VISP_HAVE_OGRE */ +#ifdef VISP_HAVE_OGRE +# define VISP_HAVE_OGRE_VERSION +#endif + +// Defined if Ogre3d plugins.cfg is available. +#define VISP_HAVE_OGRE_PLUGINS_PATH "/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/ogre-simulator" + +// Defined if Ogre3d resources.cfg is available. +#define VISP_HAVE_OGRE_RESOURCES_PATH "/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/visp/3.7.0/share/visp-3.7.0/data/ogre-simulator" + +// Defined if OIS (Object Oriented Input System) library available. +/* #undef VISP_HAVE_OIS */ + +// Defined if Coin3D and one of the GUI (SoXt, SoWin, SoQt + Qt) +// libraries are available. +/* #undef VISP_HAVE_COIN3D_AND_GUI */ +// provided for compat with previous releases +#ifdef VISP_HAVE_COIN3D_AND_GUI +# define VISP_HAVE_COIN_AND_GUI +#endif + +// Defined if Coin3D library available. +/* #undef VISP_HAVE_COIN3D */ +// provided for compat with previous releases +#ifdef VISP_HAVE_COIN3D +# define VISP_HAVE_COIN +#endif + +// Defined if OpenGL library available. +#define VISP_HAVE_OPENGL + +// Defined if Panda3D is available +/* #undef VISP_HAVE_PANDA3D */ + +// Defined if Qt library available (either Qt-3 or Qt-4). +/* #undef VISP_HAVE_QT */ + +// Defined if SoQt library available. +/* #undef VISP_HAVE_SOQT */ + +// Defined if SoWin library available. +/* #undef VISP_HAVE_SOWIN */ + +// Defined if SoXt library available. +/* #undef VISP_HAVE_SOXT */ + +// Defined if libjpeg library available. +#define VISP_HAVE_JPEG +#ifdef VISP_HAVE_JPEG +# define VISP_HAVE_LIBJPEG +#endif + +// Defined if libpng library available. +#define VISP_HAVE_PNG +#ifdef VISP_HAVE_PNG +# define VISP_HAVE_LIBPNG +#endif + +// Defined if libfreenect, libusb-1.0 and libpthread libraries available. +/* #undef VISP_HAVE_LIBFREENECT_AND_DEPENDENCIES */ + +// Defined if libfreenect library available. +/* #undef VISP_HAVE_LIBFREENECT */ + +// Defined if libfreenect library in an old version package for +// ubuntu 10.04 lucid is available. +// This is a workaround useful to initialise vpKinect depending on the +// libfreenect version +//#ifdef VISP_HAVE_LIBFREENECT_OLD +// Freenect::Freenect freenect; +// vpKinect & kinect = freenect.createDevice(0); +//#else +// Freenect::Freenect freenect; +// vpKinect & kinect = freenect.createDevice(0); +//#endif +/* #undef VISP_HAVE_LIBFREENECT_OLD */ + +// Defined if libusb-1.0 library available. +/* #undef VISP_HAVE_LIBUSB_1 */ + +// Defined if librealSense2 library is available. +#define VISP_HAVE_REALSENSE2 + +// Defined if libStructure library is available. +/* #undef VISP_HAVE_OCCIPITAL_STRUCTURE */ + +// Defined if raw1394 and dc1394-2.x libraries available. +#define VISP_HAVE_DC1394 +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) && defined(VISP_HAVE_DC1394) +# define VISP_HAVE_DC1394_2 +#endif + +// Defined if dc1394_camera_enumerate() is available in dc1394-2.x. +// dc1394_camera_enumerate() was introduced after libdc1394-2.0.0-rc7. +#define VISP_HAVE_DC1394_CAMERA_ENUMERATE +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) && defined(VISP_HAVE_DC1394_CAMERA_ENUMERATE) +# define VISP_HAVE_DC1394_2_CAMERA_ENUMERATE +#endif + +// Defined if dc1394_find_cameras() is available in dc1394-2.x +// dc1394_find_cameras() is still present until libdc1394-2.0.0-rc7. +// This function was suppress and replace by dc1394_camera_enumerate() +// in more recent releases. +/* #undef VISP_HAVE_DC1394_FIND_CAMERAS */ +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) && defined(VISP_HAVE_DC1394_FIND_CAMERAS) +# define VISP_HAVE_DC1394_2_FIND_CAMERAS +#endif + +// Defined if CMU 1394 Digital Camera SDK available. +/* #undef VISP_HAVE_CMU1394 */ + +// Defined if Video For Linux Two available. +#define VISP_HAVE_V4L2 + +// Defined if DirectShow library is available (only under Windows). +/* #undef VISP_HAVE_DIRECTSHOW */ + +// Defined if FLIR FlyCapture SDK available. +/* #undef VISP_HAVE_FLYCAPTURE */ + +// Defined if Basler Pylon SDK available. +/* #undef VISP_HAVE_PYLON */ + +// Defined if IDS uEye SDK available. +/* #undef VISP_HAVE_UEYE */ + +// Defined if Comedi (linux control and measurement cdevice interface) available. +/* #undef VISP_HAVE_COMEDI */ + +// Defined if ATIDAQ-C is build and available. +/* #undef VISP_HAVE_ATIDAQ */ + +// Defined if IIT force-torque SDK is available. +/* #undef VISP_HAVE_FT_IIT_SDK */ + +// Defined if Qualisys SDK is available for mocap. +/* #undef VISP_HAVE_QUALISYS */ + +// Defined if Vicon SDK is available for mocap. +/* #undef VISP_HAVE_VICON */ + +// Defined if Irisa's Afma6 robot available. +/* #undef VISP_HAVE_AFMA6 */ + +// Defined if Irisa's Afma6 data files (camera intrinsic/extrinsic parameters) are available +/* #undef VISP_HAVE_AFMA6_DATA */ +#define VISP_AFMA6_DATA_PATH "" + +// Defined if Biclops pan-tilt head available. +/* #undef VISP_HAVE_BICLOPS */ +/* #undef VISP_HAVE_BICLOPS_AND_GET_HOMED_STATE_FUNCTION */ + +// Defined if Rapa Pololu Maestro 3rd party library available. +#define VISP_HAVE_POLOLU + +// Defined if Irisa's Ptu-46 pan-tilt head available. +/* #undef VISP_HAVE_PTU46 */ + +// Defined if Flir PTU SDK is available to control Flir PTU robot. +/* #undef VISP_HAVE_FLIR_PTU_SDK */ + +// Defined if Irisa's Viper S650 robot available. +/* #undef VISP_HAVE_VIPER650 */ + +// Defined if Inria's Viper650 data files (camera intrinsic/extrinsic parameters) are available +/* #undef VISP_HAVE_VIPER650_DATA */ +#define VISP_VIPER650_DATA_PATH "" + +// Defined if Irisa's Viper S850 robot available. +/* #undef VISP_HAVE_VIPER850 */ + +// Defined if Inria's Viper850 data files (camera intrinsic/extrinsic parameters) are available +/* #undef VISP_HAVE_VIPER850_DATA */ +#define VISP_VIPER850_DATA_PATH "" + +// Defined if Universal Robot RTDE C++ interface is available +/* #undef VISP_HAVE_UR_RTDE */ + +// Defined if MAVSDK is available +/* #undef VISP_HAVE_MAVSDK */ + +// MAVSDK version in hexadecimal (for example 1.4.12 gives 0x010412). +#ifdef VISP_HAVE_MAVSDK +# define VISP_HAVE_MAVSDK_VERSION +#endif + +// Defined if libfranka from Franka Emika available to control Panda robot. +/* #undef VISP_HAVE_FRANKA */ + +// libfranka version in hexadecimal (for example 0.5.0 gives 0x000500). +#ifdef VISP_HAVE_FRANKA +# define VISP_HAVE_FRANKA_VERSION +#endif + +// Defined if Kinova Jaco SDK is available to control Kinova robot. +/* #undef VISP_HAVE_JACOSDK */ + +// Defined if the Aria library and (pthread, rt, dl libraries under Unix) is found. +// These libraries are used to control Pioneer mobile robots. +/* #undef VISP_HAVE_PIONEER */ + +// Defined if the Parrot ARSDK is found. +// This library is used to control Bebop2 drone. +/* #undef VISP_HAVE_ARSDK */ +// Defined if the Parrot ARSDK is found but also ffmpeg to get camera stream. +/* #undef VISP_HAVE_FFMPEG */ + +// Defined if Haption Virtuose SDK available. +/* #undef VISP_HAVE_VIRTUOSE */ + +// Defined if qbdevice api from qb-robotics available. +#define VISP_HAVE_QBDEVICE + +// Defined if takktile2 api from Right Hand Robotics available. +#define VISP_HAVE_TAKKTILE2 + +// Defined if linux/parport.h is available for parallel port usage. +#define VISP_HAVE_PARPORT + +// Defined if libzbar is available for bar code detection +#define VISP_HAVE_ZBAR + +// Defined if Point Cloud Library is available +/* #undef VISP_HAVE_PCL */ + +// Defined if required PCL components are found +#if defined(VISP_HAVE_PCL) +// PCL version in hexadecimal (for example 1.15.1 gives 0x010F01). +#define VISP_HAVE_PCL_VERSION +/* #undef VISP_HAVE_PCL_COMMON */ +/* #undef VISP_HAVE_PCL_FILTERS */ +/* #undef VISP_HAVE_PCL_IO */ +/* #undef VISP_HAVE_PCL_SEGMENTATION */ +/* #undef VISP_HAVE_PCL_VISUALIZATION */ +#endif + +// Defined if libdmtx is available for bar code detection +/* #undef VISP_HAVE_DMTX */ + +// Defined if Doxygen documentation tool is found +/* #undef VISP_HAVE_DOXYGEN */ + +// Defined if we want to compute interaction matrices by combining +// other interaction matrices +/* #undef VISP_MOMENTS_COMBINE_MATRICES */ + +// Defined if we want to use openmp +#define VISP_HAVE_OPENMP + +// Defined if nlohmann json parser is found (either system or coming from VTK) +#define VISP_HAVE_NLOHMANN_JSON + +// Defined if nlohmann json parser is found in PCL thanks to VTK 3rd party +/* #undef VISP_HAVE_NLOHMANN_JSON_FROM_VTK */ + +#ifdef VISP_HAVE_NLOHMANN_JSON +# if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_NLOHMANN_JSON_FROM_VTK) +# include +# define VISP_NLOHMANN_JSON(x) +# else +# define VISP_NLOHMANN_JSON(x) +# endif +#endif + +// Define c++ standard values also available in __cplusplus when gcc is used +#define VISP_CXX_STANDARD_98 199711L +#define VISP_CXX_STANDARD_11 201103L +#define VISP_CXX_STANDARD_14 201402L +#define VISP_CXX_STANDARD_17 201703L + +#define VISP_CXX_STANDARD 201703L + +// Defined if isnan macro is available +#define VISP_HAVE_FUNC_ISNAN + +// Defined if std::isnan function is available +#define VISP_HAVE_FUNC_STD_ISNAN + +// Defined if _isnan (Microsoft version) is available +/* #undef VISP_HAVE_FUNC__ISNAN */ + +// Defined if isinf macro is available +#define VISP_HAVE_FUNC_ISINF + +// Defined if std::isinf function is available +#define VISP_HAVE_FUNC_STD_ISINF + +// Defined if isfinite macro is available +#define VISP_HAVE_FUNC_ISFINITE + +// Defined if std::isfinite function is available +#define VISP_HAVE_FUNC_STD_ISFINITE + +// Defined if _finite (Microsoft version) function is available +/* #undef VISP_HAVE_FUNC__FINITE */ + +// Defined if the fast math compiler options are enabled +/* #undef VISP_HAVE_FAST_MATH */ + +// Defined if round function is available +#define VISP_HAVE_FUNC_ROUND + +// Defined if std::round function is available +#define VISP_HAVE_FUNC_STD_ROUND + +// Defined if erfc function is available +#define VISP_HAVE_FUNC_ERFC + +// Defined if std::erfc function is available +#define VISP_HAVE_FUNC_STD_ERFC + +// Defined if strtof function is available +#define VISP_HAVE_FUNC_STRTOF + +// Defined if log1p function is available +#define VISP_HAVE_FUNC_LOG1P + +// Defined if inet_ntop function is available +#define VISP_HAVE_FUNC_INET_NTOP + +// Defined if xrandr program available +#define VISP_HAVE_XRANDR + +// Defined if TensorRT available +/* #undef VISP_HAVE_TENSORRT */ + +// Defined if ViSP dataset found +/* #undef VISP_HAVE_DATASET */ +#ifdef VISP_HAVE_DATASET +# define VISP_HAVE_DATASET_VERSION +#endif + +// Defined if Bayer conversion has to be skipped +/* #undef VISP_SKIP_BAYER_CONVERSION */ + +// Defined if nullptr is available +#define VISP_HAVE_NULLPTR + +// Defined if vpHomography is available +#define VISP_HAVE_HOMOGRAPHY + +// Emulate nullptr when not available when cxx98 is enabled +// Note that on ubuntu 12.04 __cplusplus is equal to 1 that's why in the next line we consider __cplusplus <= 199711L +// and not __cplusplus == 199711L +#if (!defined(VISP_HAVE_NULLPTR)) && (__cplusplus <= 199711L) +#include +#endif + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) +// was implemented and released in GCC 4.9.0. In older version of GCC, it is not implemented. +// See https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions +// Calling std::regex lead to a segfault on centos 7.2 that has g++ (GCC) 4.8.5 20150623 (Red Hat 4.8.5-11) +#include +#if defined(WINRT) || defined(WIN32) +#define VISP_HAVE_WORKING_REGEX +#elif __cplusplus >= 201103L && \ + (!defined(__GLIBCXX__) || (__cplusplus >= 201402L) || \ + (defined(_GLIBCXX_REGEX_DFS_QUANTIFIERS_LIMIT) || \ + defined(_GLIBCXX_REGEX_STATE_LIMIT) || \ + (defined(_GLIBCXX_RELEASE) && \ + _GLIBCXX_RELEASE > 4))) +#define VISP_HAVE_WORKING_REGEX +#endif +#endif + +// Handle portable symbol export. +// Defining manually which symbol should be exported is required +// under Windows whether MinGW or MSVC is used. +// +// The headers then have to be able to work in two different modes: +// - dllexport when one is building the library, +// - dllimport for clients using the library. +// +// On Linux, set the visibility accordingly. If C++ symbol visibility +// is handled by the compiler, see: http://gcc.gnu.org/wiki/Visibility +# if defined(_WIN32) || defined(__CYGWIN__) +// On Microsoft Windows, use dllimport and dllexport to tag symbols. +# define VISP_DLLIMPORT __declspec(dllimport) +# define VISP_DLLEXPORT __declspec(dllexport) +# define VISP_DLLLOCAL +# else +// On Linux, for GCC >= 4, tag symbols using GCC extension. +# if __GNUC__ >= 4 +# define VISP_DLLIMPORT __attribute__ ((visibility("default"))) +# define VISP_DLLEXPORT __attribute__ ((visibility("default"))) +# define VISP_DLLLOCAL __attribute__ ((visibility("hidden"))) +# else +// Otherwise (GCC < 4 or another compiler is used), export everything. +# define VISP_DLLIMPORT +# define VISP_DLLEXPORT +# define VISP_DLLLOCAL +# endif // __GNUC__ >= 4 +# endif // defined(_WIN32) || defined(__CYGWIN__) + +// Under Windows, for shared libraries (DLL) we need to define export on +// compilation or import on use (like a third party project). +// We exploit here the fact that cmake auto set xxx_EXPORTS (with S) on +// compilation. +#if defined(VISP_BUILD_SHARED_LIBS) +// Depending on whether one is building or using the +// library define VISP_EXPORT to import or export. +# ifdef visp_EXPORTS +# define VISP_EXPORT VISP_DLLEXPORT +# else +# define VISP_EXPORT VISP_DLLIMPORT +# endif +# define VISP_LOCAL VISP_DLLLOCAL +#else +// If one is using the library statically, get rid of +// extra information. +# define VISP_EXPORT +# define VISP_LOCAL +#endif + +// Add the material to produce a warning when deprecated functions are used +#ifndef VP_DEPRECATED +# if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX +# define VP_DEPRECATED __attribute__((deprecated)) +# else +# define VP_DEPRECATED __declspec(deprecated) +# endif +#endif + +// Macro to be able to add override keyword +#ifndef VP_OVERRIDE +# if (__cplusplus >= 201103L) || (defined(_MSC_VER) && _MSC_VER >= 1600) +# define VP_OVERRIDE override +# else +# define VP_OVERRIDE +# endif +#endif + +// Defined if the user wants to enable explicit keyword +/* #undef ENABLE_EXPLICIT_KEYWORD */ + +// Add the macro for explicit keyword +#ifndef VP_EXPLICIT +# if defined(ENABLE_EXPLICIT_KEYWORD) +# define VP_EXPLICIT explicit +# else +# define VP_EXPLICIT +# endif +#endif + +// Macro to be able to add noexcept keyword +#ifndef VP_NOEXCEPT +# if (__cplusplus >= 201103L) || (defined(_MSC_VER) && _MSC_VER >= 1600) +# define VP_NOEXCEPT noexcept +# else +# define VP_NOEXCEPT +# endif +#endif + +// Macro to be able to add noreturn keyword +#ifndef VP_NORETURN +# if (__cplusplus >= 201103L) || (defined(_MSC_VER) && _MSC_VER >= 1600) +# define VP_NORETURN [[noreturn]] +# else +# define VP_NORETURN +# endif +#endif + +// Macro to fix warning with clang-cl : declaration requires an exit-time destructor [-Wexit-time-destructors] +#ifndef VISP_PYTHON_PREPROCESSOR_RUNNING +# ifndef VP_ATTRIBUTE_NO_DESTROY +# if defined(__clang__) +# if __has_cpp_attribute(clang::no_destroy) +# define VP_ATTRIBUTE_NO_DESTROY [[clang::no_destroy]] +# else +# define VP_ATTRIBUTE_NO_DESTROY +# endif +# else +# define VP_ATTRIBUTE_NO_DESTROY +# endif +# endif +#endif + + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpConvert.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpConvert.h new file mode 100644 index 00000000..40b4f074 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpConvert.h @@ -0,0 +1,109 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Directory management. + */ + +/*! + \file vpConvert.h + \brief Tools for type or general conversion. + */ + +#ifndef VP_CONVERT_H +#define VP_CONVERT_H + +#include + +#if defined(VISP_HAVE_OPENCV) && (defined(HAVE_OPENCV_FEATURES2D) || defined(HAVE_OPENCV_FEATURES)) + +#if defined(HAVE_OPENCV_FEATURES2D) +#include +#elif defined(HAVE_OPENCV_FEATURES) +#include +#endif + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpConvert + \ingroup group_core_bridges + Bridges over other frameworks like OpenCV. +*/ +class VISP_EXPORT vpConvert +{ +public: + static void convertFromOpenCV(const cv::KeyPoint &from, vpImagePoint &to); + static void convertFromOpenCV(const cv::Point2f &from, vpImagePoint &to); + static void convertFromOpenCV(const cv::Point2d &from, vpImagePoint &to); + static void convertFromOpenCV(const cv::Point3f &from, vpPoint &to, bool cameraFrame = false); + static void convertFromOpenCV(const cv::Point3d &from, vpPoint &to, bool cameraFrame = false); + + static void convertFromOpenCV(const std::vector &from, std::vector &to); + static void convertFromOpenCV(const std::vector &from, std::vector &to); + static void convertFromOpenCV(const std::vector &from, std::vector &to); + static void convertFromOpenCV(const std::vector &from, std::vector &to, + bool cameraFrame = false); + static void convertFromOpenCV(const std::vector &from, std::vector &to, + bool cameraFrame = false); + static void convertFromOpenCV(const std::vector &from, std::vector &to); + + static void convertToOpenCV(const vpImagePoint &from, cv::Point2f &to); + static void convertToOpenCV(const vpImagePoint &from, cv::Point2d &to); + static void convertToOpenCV(const vpPoint &from, cv::Point3f &to, bool cameraFrame = false); + static void convertToOpenCV(const vpPoint &from, cv::Point3d &to, bool cameraFrame = false); + + static void convertToOpenCV(const std::vector &from, std::vector &to); + static void convertToOpenCV(const std::vector &from, std::vector &to); + static void convertToOpenCV(const std::vector &from, std::vector &to, bool cameraFrame = false); + static void convertToOpenCV(const std::vector &from, std::vector &to, bool cameraFrame = false); + +private: + static vpImagePoint keyPointToVpImagePoint(const cv::KeyPoint &keypoint); + static vpImagePoint point2fToVpImagePoint(const cv::Point2f &point); + static vpImagePoint point2dToVpImagePoint(const cv::Point2d &point); + static vpPoint point3fToVpObjectPoint(const cv::Point3f &point3f); + static vpPoint point3dToVpObjectPoint(const cv::Point3d &point3d); + static vpPoint point3fToVpCamPoint(const cv::Point3f &point3f); + static vpPoint point3dToVpCamPoint(const cv::Point3d &point3d); + static int dMatchToTrainIndex(const cv::DMatch &match); + + static cv::Point2f vpImagePointToPoint2f(const vpImagePoint &point); + static cv::Point2d vpImagePointToPoint2d(const vpImagePoint &point); + static cv::Point3f vpCamPointToPoint3f(const vpPoint &point); + static cv::Point3d vpCamPointToPoint3d(const vpPoint &point); + static cv::Point3f vpObjectPointToPoint3f(const vpPoint &point); + static cv::Point3d vpObjectPointToPoint3d(const vpPoint &point); + +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpCylinder.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpCylinder.h new file mode 100644 index 00000000..32780dae --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpCylinder.h @@ -0,0 +1,199 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Cylinder feature. + */ + +/*! + \file vpCylinder.h + \brief class that defines what is a cylinder +*/ + +#ifndef vpCylinder_hh +#define vpCylinder_hh + +#include +#include +#include +#include + +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpCylinder + * \ingroup group_core_geometry + * \brief Class that defines a 3D cylinder in the object frame and allows forward projection of a 3D cylinder in the + * camera frame and in the 2D image plane by perspective projection. + * All the parameters must be set in meter. + * + * A 3D cylinder of radius R is defined by the set of circles of radius R whose center belongs + * to a straight line perpendicular to the plane of the circles. + * + * A 3D cylinder has the followings parameters: + * - **in the object frame**: the cylinder is represented by the equation: + * \f$ (X - oX)^2 + (Y - oY)^2 + (Z - oZ)^2 - (oA \; X + oB \; Y + oC \; Z)^2 - + * R^2 = 0 \f$ with + * \f[ + * \left\{ \begin{array}{l} + * oA^2 + oB^2 + oC^2 = 1 \\ + * oA \; oX + oB \; oY + oC \; oZ = 0 + * \end{array} \right. + * \f] + * where R is the radius of the cylinder, oA, oB, oC are the + * coordinates of its direction vector and oX, oY, oZ are the + * coordinates of the nearest point belonging to the cylinder axis from the + * projection center. + * The corresponding parameters are located in vpForwardProjection::oP 7-dim internal vector. They correspond + * to oP = (oA, oB, oC, oX, oY, oZ, R). + * Setting the cylinder parameters is achieved through the constructors with + * parameters or setWorldCoordinates() methods. + * To get theses parameters use get_oP(). + * + * - **in the camera frame**: parameters are saved in vpTracker::cP 7-dim internal vector + * with cP =(cA, cB, cC, cX, cY, cZ, R). Considering the set of parameters oP expressed in the object + * frame, cylinder coordinates expressed in the camera frame are obtained using + * changeFrame(). To get these parameters use get_cP(). + * + * - **in the 2D image plane**: parameters are saved in vpTracker::p 4-dim vector. + * They correspond to p = (\f$\rho_1\f$, \f$\theta_1\f$, \f$\rho_2\f$, \f$\theta_2\f$), noting + * that for non-degenerated cases, the perspective projection of a cylinder on the image plane is a set of two + * straight lines with equation: + * \f[ + * \left\{ \begin{array}{lll} + * x \;\cos\theta_1 + x \;\sin\theta_1 - \rho_1 = 0 \\ + * y \;\cos\theta_2 + y \;\sin\theta_2 - \rho_2 = 0 + * \end{array} \right. + * \f] + * + * Perspective projection is achieved using projection() methods. The methods + * get_p(), getRho1(), getTheta1() and getRho2(), getTheta2() allow to access to the + * projected line parameters. +*/ +class VISP_EXPORT vpCylinder : public vpForwardProjection +{ +public: + typedef enum + { + line1, /*!< First limb of the cylinder. */ + line2 /*!< Second limb of the cylinder. */ + } vpLineCylinderType; + + vpCylinder(); + VP_EXPLICIT vpCylinder(const vpColVector &oP); + vpCylinder(double oA, double oB, double oC, double oX, double oY, double oZ, double R); + + void changeFrame(const vpHomogeneousMatrix &cMo, vpColVector &cP) const VP_OVERRIDE; + void changeFrame(const vpHomogeneousMatrix &cMo) VP_OVERRIDE; + + double computeZ(double x, double y) const; + + void display(const vpImage &I, const vpCameraParameters &cam, const vpColor &color = vpColor::green, + unsigned int thickness = 1) VP_OVERRIDE; + void display(const vpImage &I, const vpCameraParameters &cam, const vpColor &color = vpColor::green, + unsigned int thickness = 1); + void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &color = vpColor::green, unsigned int thickness = 1) VP_OVERRIDE; + void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &color = vpColor::green, unsigned int thickness = 1); + + vpCylinder *duplicate() const VP_OVERRIDE; + +/*! + * Return the \f$\rho_1\f$ parameter of the line corresponding to the + * projection of the cylinder in the image plane. + * \sa getTheta1() + */ + double getRho1() const { return p[0]; } + /*! + * Return the \f$\theta_1\f$ parameter of the line corresponding to the + * projection of the cylinder in the image plane. + * \sa getRho1() + */ + double getTheta1() const { return p[1]; } + + /*! + * Return the \f$\rho_2\f$ parameter of the line corresponding to the + * projection of the cylinder in the image plane. + * \sa getTheta2() + */ + double getRho2() const { return p[2]; } + /*! + * Return the \f$\theta_2\f$ parameter of the line corresponding to the + * projection of the cylinder in the image plane. + * \sa getRho2() + */ + double getTheta2() const { return p[3]; } + + /*! + * Return cylinder cA parameter expressed in the camera frame. + */ + double getA() const { return cP[0]; } + + /*! + * Return cylinder cB parameter expressed in the camera frame. + */ + double getB() const { return cP[1]; } + + /*! + * Return cylinder cC parameter expressed in the camera frame. + */ + double getC() const { return cP[2]; } + + /*! + * Return cylinder cX parameter expressed in the camera frame. + */ + double getX() const { return cP[3]; } + + /*! + * Return cylinder cY parameter expressed in the camera frame. + */ + double getY() const { return cP[4]; } + + /*! + * Return cylinder cZ parameter expressed in the camera frame. + */ + double getZ() const { return cP[5]; } + + /*! + * Return cylinder R parameter corresponding to the cylinder radius. + */ + double getR() const { return cP[6]; } + + void init() VP_OVERRIDE; + + void projection() VP_OVERRIDE; + void projection(const vpColVector &cP, vpColVector &p) const VP_OVERRIDE; + + void setWorldCoordinates(const vpColVector &oP) VP_OVERRIDE; + void setWorldCoordinates(double oA, double oB, double oC, double oX, double oY, double oZ, double R); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpDebug.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpDebug.h new file mode 100644 index 00000000..49453a3e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpDebug.h @@ -0,0 +1,610 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Debug and trace macro. + * + * - TRACING: vpTRACE and vpERROR_TRACE work like printf with carreer + * return at the end of the string. vpCERROR et vpCTRACE work like the C++ + * output streams std::cout and std::cerr. + * - DEBUGING: vpDEBUG_TRACE(niv) and vpDERROR_TRACE(niv), work like + * printf, but print only if the tracing level niv is greater than the debug + * level VP_DEBUG_MODE. vpCDEBUG(niv) work like the C++ output + * stream std::cout. vpDEBUG_ENABLE(niv) is equal to 1 if the + * debug level niv is greater than the debug mode + * VP_DEBUG_MODE, 0 else. + * - PROG DEFENSIVE: DEFENSIF(a) is equal to a if defensive mode is active, + * 0 else. + */ + +#ifndef VP_DEBUG_H +#define VP_DEBUG_H + +#include +#include +#include +#include + +#if defined(_WIN32) +#ifndef __FUNCTION__ +#define __FUNCTION__ " " +#endif +#endif + +#ifndef VP_DEBUG_MODE +#define VP_DEBUG_MODE 0 +#endif + +BEGIN_VISP_NAMESPACE +/*! + \class vpTraceOutput + + \ingroup group_core_debug + + \brief This class is used to display debug or error messages. + + It needs to be initialized with the file name, function name and + line, of the place where it is created. It is best used by first + instantiating the object and directly calling the () operator. This + is used to mimic variadic macros. + + This class is used to define the following macros: + + - Macros for tracing: vpTRACE(), vpERROR_TRACE(), vpIN_FCT() and + vpOUT_FCT() work like printf + with career return at the end of the string, while vpCTRACE() and + vpCERROR() work like the C++ output streams std::cout and + std::cerr. + + - Macros for debugging: vpDEBUG_TRACE(level) and vpDERROR_TRACE(level) + work like printf, but print only if the tracing level \e level is + greater than the debug level VP_DEBUG_MODE macro. vpCDEBUG(level) + work like the C++ output stream std::cout. vpDEBUG_ENABLE(level) is + equal to 1 if the debug level level is greater than the debug mode + VP_DEBUG_MODE, 0 else. + + The example below shows how to use these macros. + + \code + #define VP_TRACE // Activate the trace mode + #define VP_DEBUG // Activate the debug mode + #define VP_DEBUG_MODE 2 // Activate debug level 1 and 2 + + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpIN_FCT("main()"); + + // Check the active debug levels + std::cout << "Debug level 1 active: " << vpDEBUG_ENABLE(1) << std::endl; + std::cout << "Debug level 2 active: " << vpDEBUG_ENABLE(2) << std::endl; + std::cout << "Debug level 3 active: " << vpDEBUG_ENABLE(3) << std::endl; + + // C-like debug printings + vpTRACE("C-like trace"); // stdout + + // Printing depend only VP_DEBUG_MODE value is >= 1 + vpTRACE(1, "C-like trace level 1"); // stdout + vpERROR_TRACE(1, "C-like error trace level 1"); // stderr + + // Printing if VP_DEBUG defined and VP_DEBUG_MODE value >= 2 + vpDEBUG_TRACE(2, "C-like debug trace level 2"); // stdout + vpDERROR_TRACE(2, "C-like error trace level 2"); // stderr + + // C++-like debug printings + vpCTRACE << "C++-like trace" << std::endl; // stdout + vpCERROR << "C++-like error trace" << std::endl; // stderr + + // Printing if VP_DEBUG defined and VP_DEBUG_MODE value >= 2 + vpCDEBUG(2) << "C++-like debug trace level 2" << std::endl; // stdout + + vpOUT_FCT("main()"); + } + \endcode + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in the usage of this class, you may have a look at:
+ + - \ref tutorial-trace +*/ +class vpTraceOutput +{ +public: + /*! + Constructor. + \param file Should be the name of the file where this constructor was called. + \param line Should be the line in file where this constructor was called. + \param func Should be the name of the function where this constructor was called. + \param error If true, writes to the error stream. + \param s String to print before any other message (acts like a header). + \note Call the constructor with something like + vpTraceOutput(__FILE__,__LINE__, __FUNCTION__). + */ + vpTraceOutput(const char *file, int line, const char *func, bool error = false, const char *s = nullptr) + : currentFile(file), currentFunc(func), currentLine(line), err(error), header(s) + { } + + /*! + Displays a string if the debug level is inferior to VP_DEBUG_MODE. + \param level Level of this message. + \param format String format. + */ + void operator()(int level, const char *format, ...) + { + // if the level is inferior to VP_DEBUG_MODE + if (VP_DEBUG_MODE >= level) { + // gets the variable list of arguments + va_list args; + va_start(args, format); + + if (err) { + std::cerr << "(L" << level << ") "; + } + else { + std::cout << "(L" << level << ") "; + } + + // calls display with it + display(format, args); + + va_end(args); + } + } + + /*! + Displays a string. + \param format Formating string. + */ + void operator()(const char *format, ...) + { + // gets the variable list of arguments + va_list args; + va_start(args, format); + +#ifdef VP_DEBUG + std::cout << "(L0) "; +#endif + + // calls display with it + display(format, args); + + va_end(args); + } + +#if defined(__clang__) +// Mute warnings: +// vpDebug.h(221,24): warning : format string is not a string literal [-Wformat-nonliteral] +// vpDebug.h(234,15): warning : format string is not a string literal [-Wformat-nonliteral] +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wformat-nonliteral" +#endif + /*! + Displays a message to either stdout or + stderr (based on error boolean). + + \param format Formating string. + \param args List of arguments. + */ + void display(const char *format, va_list args) + { + // if we want to write to std::cerr/stderr + if (err) { + // first writes the header if there is one + if (header != nullptr) { + std::cerr << header; + } + // then writes the recorded namefile, function and line + std::cerr << "!!\t" << currentFile << ": " << currentFunc << "(#" << currentLine << ") : "; + // and finally writes the message passed to () operator. + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); + // flushes the buffer + fflush(stderr); + } + else { + // first writes the header if there is one + if (header != nullptr) { + std::cout << header; + } + // then writes the recorded namefile, function and line + std::cout << currentFile << ": " << currentFunc << "(#" << currentLine << ") : "; + // and finally writes the message passed to () operator. + vprintf(format, args); + printf("\n"); + // flushes the buffer + fflush(stdout); + } + } +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +private: + const char *currentFile; // Name of the file to use in the displays + const char *currentFunc; // Name of the function to use in the displays + int currentLine; // Line to use in the displays + + // if true, output to std::cerr/stderr else std::cout/stdout + bool err; + // string to display before anything else + const char *header; +}; + +/* ------------------------------------------------------------------------- */ +/* --- vpTRACE IN/OUT FONCTION --------------------------------------------- */ +/* ------------------------------------------------------------------------- */ + +#ifdef VP_TRACE // Activate the trace mode + +/*! + \ingroup group_core_debug + Works like vpTRACE() and should be used at the beginning of a function. + + \code + #define VP_TRACE // To activate the trace mode + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpIN_FCT("main()"); + // the body of the main() function + vpOUT_FCT("main()"); + } + \endcode + + \sa vpOUT_FCT +*/ +#define vpIN_FCT (vpTraceOutput(__FILE__, __LINE__, __FUNCTION__, false, "begin ")) + +/*! + \ingroup group_core_debug + Works like vpTRACE() and should be used at the end of a function. + + \code + #define VP_TRACE // To activate the trace mode + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpIN_FCT("main()"); + // the body of the main() function + vpOUT_FCT("main()"); + } + \endcode + + \sa vpIN_FCT +*/ +#define vpOUT_FCT (vpTraceOutput(__FILE__, __LINE__, __FUNCTION__, false, "end ")) + +#else // #ifdef VP_TRACE + +inline void vpIN_FCT(const char * /* a */, ...) { } +inline void vpOUT_FCT(const char * /* a */, ...) { } + +#endif // #ifdef VP_TRACE + +/* -------------------------------------------------------------------------- */ +/* --- vpTRACE -------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + +#ifdef VP_TRACE + +/*! + \ingroup group_core_debug + Used to display trace messages on the standard stream (C++). + Use like this : vpCTRACE << "my message" << std::endl; + + \code + #define VP_TRACE // To activate trace mode + #define VP_DEBUG // To activate the debug mode + #define VP_DEBUG_MODE 2 // To activate debug level 1 and 2 + + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + // C++-like debug printings + vpCTRACE << "C++-like trace" << std::endl; // stdout + vpCERROR << "C++-like error trace" << std::endl; // stderr + + // Printing if VP_DEBUG defined and VP_DEBUG_MODE value >= 2 + vpCDEBUG(2) << "C++-like debug trace level 2" << std::endl; // stdout + } + \endcode + + \sa vpTRACE(), vpCERROR(), vpCDEBUG() +*/ +#define vpCTRACE std::cout << "(L0) " << __FILE__ << ": " << __FUNCTION__ << "(#" << __LINE__ << ") : " + +/*! + \ingroup group_core_debug + Used to display error messages on the error stream (C++). + Use like this : vpCERROR<<"my message"< + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + // C++-like debug printings + vpCTRACE << "C++-like trace" << std::endl; // stdout + vpCERROR << "C++-like error trace" << std::endl; // stderr + + // Printing if VP_DEBUG defined and VP_DEBUG_MODE value >= 2 + vpCDEBUG(2) << "C++-like debug trace level 2" << std::endl; // stdout + } + \endcode + + \sa vpCTRACE(), vpCDEBUG() +*/ +#define vpCERROR std::cerr << "(L0) " << "!!\t" << __FILE__ << ": " << __FUNCTION__ << "(#" << __LINE__ << ") : " + +/*! + \ingroup group_core_debug + Used to display error messages on the error stream. + Prints the name of the file, the function name and the line where + it was used. + Use like this : vpERROR_TRACE("my error message number %d", i); + with any "printf" string. + + \code + #define VP_TRACE // To activate trace mode + #define VP_DEBUG_MODE 2 // Activate debug level 1 and 2 + + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + // Printing depend only VP_DEBUG_MODE value is >= 1 + vpTRACE(1, "C-like trace level 1"); // stdout + vpERROR_TRACE(1, "C-like error trace level 1"); // stderr + } + \endcode + + \sa vpTRACE() +*/ +#define vpERROR_TRACE (vpTraceOutput(__FILE__, __LINE__, __FUNCTION__, true)) + +/*! + \ingroup group_core_debug + Used to display trace messages on the standard stream. + Prints the name of the file, the function name and the line where + it was used. + Use like this : vpTRACE("my debug message number %d", i); + with any "printf" string. + + \code + #define VP_TRACE // To activate trace mode + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + // C-like debug printings + vpTRACE("C-like trace"); // stdout + } + \endcode + + \sa vpCTRACE(), vpERROR_TRACE() +*/ +#define vpTRACE (vpTraceOutput(__FILE__, __LINE__, __FUNCTION__, false)) + +#else // #ifdef VP_TRACE + +#define vpCTRACE \ + if (false) \ + std::cout // Warning C4127 +#define vpCERROR \ + if (false) \ + std::cerr // Warning C4127 + +inline void vpERROR_TRACE(const char * /* a */, ...) { } +inline void vpERROR_TRACE(int /* level */, const char * /* a */, ...) { } +inline void vpTRACE(const char * /* a */, ...) { } +inline void vpTRACE(int /* level */, const char * /* a */, ...) { } + +#endif // #ifdef VP_TRACE + +/* ------------------------------------------------------------------------- */ +/* --- VP_DEBUG ------------------------------------------------------------ */ +/* ------------------------------------------------------------------------- */ + +#ifdef VP_DEBUG + +/*! + \ingroup group_core_debug + vpDERROR_TRACE works like printf, but prints only if the + tracing level is smaller than the debug level VP_DEBUG_MODE. + + \code + #define VP_DEBUG // Activate the debug mode + #define VP_DEBUG_MODE 2 // Activate debug level 1 and 2 + + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + // Printing if VP_DEBUG defined and VP_DEBUG_MODE value >= 2 + vpDEBUG_TRACE(2, "C-like debug trace level 2"); // stdout + vpDERROR_TRACE(2, "C-like error trace level 2"); // stderr + } + \endcode + + \sa vpDEBUG_TRACE() +*/ +#define vpDERROR_TRACE (vpTraceOutput(__FILE__, __LINE__, __FUNCTION__, true)) + +/*! + \ingroup group_core_debug + vpDEBUG_TRACE works like printf, but prints only if the + tracing level level is greater than the debug level VP_DEBUG_MODE. + + \code + #define VP_DEBUG // Activate the debug mode + #define VP_DEBUG_MODE 2 // Activate debug level 1 and 2 + + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + // Printing if VP_DEBUG defined and VP_DEBUG_MODE value >= 2 + vpDEBUG_TRACE(2, "C-like debug trace level 2"); // stdout + vpDERROR_TRACE(2, "C-like error trace level 2"); // stderr + } + \endcode + + \sa vpDERROR_TRACE() +*/ +#define vpDEBUG_TRACE (vpTraceOutput(__FILE__, __LINE__, __FUNCTION__, false)) + +/*! + \ingroup group_core_debug + vpCDEBUG(level) work like the C++ output stream std::cout. + \code + #define VP_DEBUG // Activate the debug mode + #define VP_DEBUG_MODE 2 // Activate debug level 1 and 2 + + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + // C++-like debug printings + vpCTRACE << "C++-like trace" << std::endl; // stdout + vpCERROR << "C++-like error trace" << std::endl; // stderr + + // Printing if VP_DEBUG defined and VP_DEBUG_MODE value >= 2 + vpCDEBUG(2) << "C++-like debug trace level 2" << std::endl; // stdout + } + \endcode + + \sa vpCTRACE(), vpCERROR() +*/ +#define vpCDEBUG(level) \ + if (VP_DEBUG_MODE < level) \ + ; \ + else \ + std::cout << "(L" << level << ") " << __FILE__ << ": " << __FUNCTION__ << "(#" << __LINE__ << ") : " + +/*! + \ingroup group_core_debug + + vpDEBUG_ENABLE(level) is equal to 1 if the debug level \e level is greater + than the debug mode VP_DEBUG_MODE, 0 else. + + \code + #define VP_DEBUG // Activate the debug mode + #define VP_DEBUG_MODE 2 // Activate debug level 1 and 2 + + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + // Check the active debug levels + std::cout << "Debug level 1 active: " << vpDEBUG_ENABLE(1) << std::endl; + std::cout << "Debug level 2 active: " << vpDEBUG_ENABLE(2) << std::endl; + std::cout << "Debug level 3 active: " << vpDEBUG_ENABLE(3) << std::endl; + } + \endcode +*/ +#define vpDEBUG_ENABLE(level) (VP_DEBUG_MODE >= level) + +#else // #ifdef VP_DEBUG + +inline void vpDERROR_TRACE(const char * /* a */, ...) { } +inline void vpDEBUG_TRACE(const char * /* a */, ...) { } +inline void vpDERROR_TRACE(int /* level */, const char * /* a */, ...) { } +inline void vpDEBUG_TRACE(int /* level */, const char * /* a */, ...) { } + +#define vpCDEBUG(level) \ + if (false) \ + std::cout // Warning C4127 +#define vpDEBUG_ENABLE(level) (false) // Warning C4127 + +#endif // #ifdef VP_DEBUG + +/* -------------------------------------------------------------------------- */ +/* --- DEFENSIF ------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +#ifdef VP_DEFENSIF +#define DEFENSIF(a) (a) +#else +#define DEFENSIF(a) (0) +#endif /*#ifdef DEFENSIF*/ +END_VISP_NAMESPACE +#endif /* #ifdef __DEBUG_HH */ diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpDisplay.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpDisplay.h new file mode 100644 index 00000000..fa9d3070 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpDisplay.h @@ -0,0 +1,940 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Image display. + */ + +/*! + * \file vpDisplay.h + * \brief Generic class for image display, also provide the interface + * with the image. + */ + +#ifndef VP_DISPLAY_H +#define VP_DISPLAY_H + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpDisplay + * + * \ingroup group_core_gui + * + * \brief Class that defines generic functionalities for display. + * + * The \ref tutorial-getting-started is a good starting point to know + * how to use this class to display an image in a window. + * + * \warning Since ViSP 3.3.1 or higher we introduce the alpha channel support for color + * transparency. This new feature is only supported yet using vpDisplayOpenCV. See vpColor + * header documentation and displayOpenCV.cpp example for usage when displaying filled + * transparent circles and rectangles. + * + * The example below shows how to use this class. + * + * \code + * #include + * #include + * #include + * #include + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpImage I; // Grey level image + * + * // Read an image in PGM P5 format + * #ifdef _WIN32 + * vpImageIo::read(I, "C:/Temp/visp-images/Klimt/Klimt.pgm"); + * #else + * vpImageIo::read(I, "/local/soft/ViSP/ViSP-images/Klimt/Klimt.pgm"); + * #endif + * + * vpDisplay *d; + * + * // Initialize the display with the image I. Display and image are now linked together + * #if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + * std::shared_ptr d = vpDisplayFactory::createDisplay(I); + * #else + * vpDisplay *d = vpDisplayFactory::allocateDisplay(I); + * #endif + * + * // Specify the window location + * vpDisplay::setWindowPosition(I, 400, 100); + * + * // Set the display window title + * vpDisplay::setTitle(I, "My image"); + * + * // To initialize the video device, it is also possible to replace the 3 previous lines by: + * // d->init(I, 400, 100, "My image"); + * //#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + * // std::shared_ptr d = vpDisplayFactory::createDisplay(I, 400, 100, "My image"); + * //#else + * // vpDisplay *d = vpDisplayFactory::allocateDisplay(I, 400, 100, "My image"); + * //#endif + * + * // Set the display background with image I content + * vpDisplay::display(I); + * + * // Draw a red rectangle in the display overlay (foreground) + * vpDisplay::displayRectangle(I, 10, 10, 100, 20, vpColor::red, true); + * + * // Draw a red rectangle in the display overlay (foreground) + * vpImagePoint topLeftCorner; + * topLeftCorner.set_i(50); + * topLeftCorner.set_j(10); + * vpDisplay::displayRectangle(I, topLeftCorner, 100, 20, vpColor::green, true); + * + * // Flush the foreground and background display + * vpDisplay::flush(I); + * + * // Get non blocking keyboard events + * std::cout << "Check keyboard events..." << std::endl; + * char key[10]; sprintf(key, "\0"); + * bool ret; + * for (int i=0; i< 200; ++i) { + * bool ret = vpDisplay::getKeyboardEvent(I, key, false); + * if (ret) + * std::cout << "keyboard event: key: " << "\"" << key + * << "\"" << std::endl; + * vpTime::wait(40); + * } + * + * // Get a blocking keyboard event + * std::cout << "Wait for a keyboard event..." << std::endl; + * ret = vpDisplay::getKeyboardEvent(I, key, true); + * std::cout << "keyboard event: " << ret << std::endl; + * if (ret) { + * std::cout << "key: " << "\"" << key << "\"" << std::endl; + * } + * + * // Wait for a click in the display window + * std::cout << "Wait for a button click..." << std::endl; + * vpDisplay::getClick(I); + * + * #if (VISP_CXX_STANDARD < VISP_CXX_STANDARD_11) + * delete d; + * #endif + * } + * \endcode + * + * Other examples are available in tutorial-image-viewer.cpp and + * tutorial-viewer.cpp. +*/ +class VISP_EXPORT vpDisplay +{ +public: + //! Values that could be applied to a display to down scale the size of the + //! display. + typedef enum + { + SCALE_AUTO, /*!< Auto down scaling factor computed fom the screen + resolution. */ + SCALE_1, /*!< Display and image have the same size. */ + SCALE_2, /*!< Display width and height is down scaled by 2 wrt the image + size. */ + SCALE_3, /*!< Display width and height is down scaled by 3 wrt the image + size. */ + SCALE_4, /*!< Display width and height is down scaled by 4 wrt the image + size. */ + SCALE_5, /*!< Display width and height is down scaled by 5 wrt the image + size. */ + SCALE_6, /*!< Display width and height is down scaled by 6 wrt the image + size. */ + SCALE_7, /*!< Display width and height is down scaled by 7 wrt the image + size. */ + SCALE_8, /*!< Display width and height is down scaled by 8 wrt the image + size. */ + SCALE_9, /*!< Display width and height is down scaled by 9 wrt the image + size. */ + SCALE_10, /*!< Display width and height is down scaled by 10 wrt the image + size. */ + SCALE_DEFAULT /*!< Display and image have the same size. Similar to + vpDisplay::SCALE_1. */ + } vpScaleType; + +public: + vpDisplay(); + vpDisplay(const vpDisplay &d); + virtual ~vpDisplay(); + +#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98) + vpDisplay &operator=(const vpDisplay &) = default; +#endif + + /** @name Inherited functionalities from vpDisplay */ + //@{ + unsigned int computeAutoScale(unsigned int width, unsigned int height); + /*! + * Return the value of the down scale factor applied to the image in order to + * reduce the size of the window used to display the image. + */ + unsigned int getDownScalingFactor() { return m_scale; } + /*! + * Return the display height. + * \sa getWidth() + */ + inline unsigned int getHeight() const { return m_height; } + /*! + * Return the display width. + * \sa getHeight() + */ + inline unsigned int getWidth() const { return m_width; } + + /*! + * Return the position (along the horizontal axis) on the screen of the + * display window. \sa getWindowYPosition() + */ + int getWindowXPosition() const { return m_windowXPosition; } + /*! + * Return the position (along the vertical axis) on the screen of the display + * window. \sa getWindowXPosition() + */ + int getWindowYPosition() const { return m_windowYPosition; } + + /*! + * Check if the display has been initialised. + * + * \return True if the display has been initialised, otherwise False + */ + inline bool isInitialised() { return m_displayHasBeenInitialized; } + virtual void setDownScalingFactor(unsigned int scale); + virtual void setDownScalingFactor(vpScaleType scaleType); + //@} + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + /** @name vpDisplay pure virtual functions */ + //@{ + /*! + * Set the window background to \e color. + * \param color : Background color. + */ + virtual void clearDisplay(const vpColor &color = vpColor::white) = 0; + /*! + * Close the window. + */ + virtual void closeDisplay() = 0; + + /*! + * Display an arrow from image point \e ip1 to image point \e ip2. + * \param ip1 : Initial image point. + * \param ip2 : Final image point. + * \param color : Arrow color. + * \param w : Arrow width. + * \param h : Arrow height. + * \param thickness : Thickness of the lines used to display the arrow. + */ + virtual void displayArrow(const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color = vpColor::white, + unsigned int w = 4, unsigned int h = 2, unsigned int thickness = 1) = 0; + + /*! + * Display a circle. + * \param circle : Circle to display. + * \param color : Circle color. + * \param fill : When set to true fill the circle. + * \param thickness : Thickness of the circle. This parameter is only useful + * when \e fill is set to false. + */ + inline virtual void displayCircle(const vpImageCircle &circle, const vpColor &color, bool fill = false, + unsigned int thickness = 1) + { + this->displayCircle(circle.getCenter(), static_cast(circle.getRadius()), color, fill, thickness); + } + + /*! + * Display a circle. + * \param center : Circle center position. + * \param radius : Circle radius. + * \param color : Circle color. + * \param fill : When set to true fill the circle. + * \param thickness : Thickness of the circle. This parameter is only useful + * when \e fill is set to false. + */ + virtual void displayCircle(const vpImagePoint ¢er, unsigned int radius, const vpColor &color, bool fill = false, + unsigned int thickness = 1) = 0; + /*! + * Display a cross at the image point \e ip location. + * \param ip : Cross location. + * \param size : Size (width and height) of the cross. + * \param color : Cross color. + * \param thickness : Thickness of the lines used to display the cross. + */ + virtual void displayCross(const vpImagePoint &ip, unsigned int size, const vpColor &color, + unsigned int thickness = 1) = 0; + /*! + * Display a dashed line from image point \e ip1 to image point \e ip2. + * \param ip1 : Initial image point. + * \param ip2 : Final image point. + * \param color : Line color. + * \param thickness : Dashed line thickness. + */ + virtual void displayDotLine(const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color, + unsigned int thickness = 1) = 0; + /*! + * Display a line from image point \e ip1 to image point \e ip2. + * \param ip1 : Initial image point. + * \param ip2 : Final image point. + * \param color : Line color. + * \param thickness : Line thickness. + */ + virtual void displayLine(const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color, + unsigned int thickness = 1) = 0; + + /*! + * Display the gray level image \e I (8bits). + * \warning Display has to be initialized. + * \warning Suppress the overlay drawing. + * \param I : Image to display. + * \sa init(), closeDisplay() + */ + virtual void displayImage(const vpImage &I) = 0; + + /*! + * Display the color image \e I in RGBa format (32bits). + * \warning Display has to be initialized. + * \warning Suppress the overlay drawing. + * \param I : Image to display. + * \sa init(), closeDisplay() + */ + virtual void displayImage(const vpImage &I) = 0; + + virtual void displayImageROI(const vpImage &I, const vpImagePoint &iP, unsigned int width, + unsigned int height) = 0; + virtual void displayImageROI(const vpImage &I, const vpImagePoint &iP, unsigned int width, + unsigned int height) = 0; + + /*! + * Display a point at the image point \e ip location. + * \param ip : Point location. + * \param color : Point color. + * \param thickness : Point thickness. + */ + virtual void displayPoint(const vpImagePoint &ip, const vpColor &color, unsigned int thickness = 1) = 0; + + /*! + * Display a rectangle with \e topLeft as the top-left corner and \e + * width and \e height the rectangle size. + * + * \param topLeft : Top-left corner of the rectangle. + * \param width : Rectangle width. + * \param height : Rectangle height. + * \param color : Rectangle color. + * \param fill : When set to true fill the rectangle. + * \param thickness : Thickness of the four lines used to display the + * rectangle. This parameter is only useful when \e fill is set to + * false. + */ + virtual void displayRectangle(const vpImagePoint &topLeft, unsigned int width, unsigned int height, + const vpColor &color, bool fill = false, unsigned int thickness = 1) = 0; + /*! + * Display a rectangle with \e topLeft as the top-left corner and \e + * width and \e height the rectangle size. + * + * \param topLeft : Top-left corner of the rectangle. + * \param bottomRight : Bottom-right corner of the rectangle. + * \param color : Rectangle color. + * \param fill : When set to true fill the rectangle. + * \param thickness : Thickness of the four lines used to display the + * rectangle. This parameter is only useful when \e fill is set to + * false. + */ + virtual void displayRectangle(const vpImagePoint &topLeft, const vpImagePoint &bottomRight, const vpColor &color, + bool fill = false, unsigned int thickness = 1) = 0; + /*! + * Display a rectangle with \e topLeft as the top-left corner and \e + * width and \e height the rectangle size. + * + * \param rectangle : Rectangle characteristics. + * \param color : Rectangle color. + * \param fill : When set to true fill the rectangle. + * \param thickness : Thickness of the four lines used to display the + * rectangle. This parameter is only useful when \e fill is set to + * false. + */ + virtual void displayRectangle(const vpRect &rectangle, const vpColor &color, bool fill = false, + unsigned int thickness = 1) = 0; + + /*! + * Display a string at the image point \e ip location. + * + * To select the font used to display the string, use setFont(). + * + * \param ip : Upper left image point location of the string in the display. + * \param text : String to display in overlay. + * \param color : String color. + * + * \sa setFont() + */ + virtual void displayText(const vpImagePoint &ip, const std::string &text, const vpColor &color = vpColor::green) = 0; + + /*! + * Flushes the display. + * It's necessary to use this function to see the results of any drawing. + */ + virtual void flushDisplay() = 0; + + /*! + * Flushes the display. + * It's necessary to use this function to see the results of any drawing. + */ + virtual void flushDisplayROI(const vpImagePoint &iP, unsigned int width, unsigned int height) = 0; + + /* Simple interface with the mouse event */ + + /*! + * Wait for a click from one of the mouse button. + * + * \param blocking [in] : Blocking behavior. + * - When set to true, this method waits until a mouse button is + * pressed and then returns always true. + * - When set to false, returns true only if a mouse button is + * pressed, otherwise returns false. + * + * \return + * - true if a button was clicked. This is always the case if blocking is set + * to \e true. + * - false if no button was clicked. This can occur if blocking is set + * to \e false. + */ + virtual bool getClick(bool blocking = true) = 0; + + /*! + * Wait for a click from one of the mouse button and get the position + * of the clicked image point. + * + * \param ip [out] : The coordinates of the clicked image point. + * + * \param blocking [in] : true for a blocking behaviour waiting a mouse + * button click, false for a non blocking behaviour. + * + * \return + * - true if a button was clicked. This is always the case if blocking is set + * to \e true. + * - false if no button was clicked. This can occur if blocking is set + * to \e false. + */ + virtual bool getClick(vpImagePoint &ip, bool blocking = true) = 0; + /*! + * Wait for a mouse button click and get the position of the clicked + * pixel. The button used to click is also set. + * + * \param ip [out] : The coordinates of the clicked image point. + * + * \param button [out] : The button used to click. + * + * \param blocking [in] : + * - When set to true, this method waits until a mouse button is + * pressed and then returns always true. + * - When set to false, returns true only if a mouse button is + * pressed, otherwise returns false. + * + * \return true if a mouse button is pressed, false otherwise. If a + * button is pressed, the location of the mouse pointer is updated in + * \e ip. + */ + virtual bool getClick(vpImagePoint &ip, vpMouseButton::vpMouseButtonType &button, bool blocking = true) = 0; + /*! + * Wait for a mouse button click release and get the position of the + * image point were the click release occurs. The button used to click is + * also set. Same method as getClick(unsigned int&, unsigned int&, + * vpMouseButton::vpMouseButtonType &, bool). + * + * \param ip [out] : Position of the clicked image point. + * + * \param button [in] : Button used to click. + * + * \param blocking [in] : true for a blocking behaviour waiting a mouse + * button click, false for a non blocking behaviour. + * + * \return + * - true if a button was clicked. This is always the case if blocking is set + * to \e true. + * - false if no button was clicked. This can occur if blocking is set + * to \e false. + * + * \sa getClick(vpImagePoint &, vpMouseButton::vpMouseButtonType &, bool) + * + */ + virtual bool getClickUp(vpImagePoint &ip, vpMouseButton::vpMouseButtonType &button, bool blocking = true) = 0; + + /*! + * Get a keyboard event. + * + * \param blocking [in] : Blocking behavior. + * - When set to true, this method waits until a key is + * pressed and then returns always true. + * - When set to false, returns true only if a key is + * pressed, otherwise returns false. + * + * \return + * - true if a key was pressed. This is always the case if blocking is set + * to \e true. + * - false if no key was pressed. This can occur if blocking is set + * to \e false. + */ + virtual bool getKeyboardEvent(bool blocking = true) = 0; + /*! + * Get a keyboard event. + * + * \param blocking [in] : Blocking behavior. + * - When set to true, this method waits until a key is + * pressed and then returns always true. + * - When set to false, returns true only if a key is + * pressed, otherwise returns false. + * + * \param key [out]: If possible, an ISO Latin-1 character + * corresponding to the keyboard key. + * + * \return + * - true if a key was pressed. This is always the case if blocking is set + * to \e true. + * - false if no key was pressed. This can occur if blocking is set + * to \e false. + */ + virtual bool getKeyboardEvent(std::string &key, bool blocking = true) = 0; + /*! + * Get the coordinates of the mouse pointer. + * + * \param ip [out] : The coordinates of the mouse pointer. + * + * \return true if a pointer motion event was received, false otherwise. + * + * \exception vpDisplayException::notInitializedError : If the display + * was not initialized. + */ + virtual bool getPointerMotionEvent(vpImagePoint &ip) = 0; + + /*! + * Get the coordinates of the mouse pointer. + * + * \param ip [out] : The coordinates of the mouse pointer. + * + * \return true. + * + * \exception vpDisplayException::notInitializedError : If the display + * was not initialized. + */ + virtual bool getPointerPosition(vpImagePoint &ip) = 0; + + /*! + * Gets the screen vertical resolution in pixel. + */ + virtual unsigned int getScreenHeight() = 0; + /*! + * Gets the screen resolution in pixel. + * \param[out] width : Screen width resolution in pixels. + * \param[out] height : Screen height resolution in pixels. + */ + virtual void getScreenSize(unsigned int &width, unsigned int &height) = 0; + /*! + * Gets the screen horizontal resolution in pixel. + */ + virtual unsigned int getScreenWidth() = 0; + + /*! + * Initialize the display (size, position and title) of a gray level image. + + * \param I : Image to be displayed (not that image has to be initialized). + * \param x : Horizontal position of the upper/left window corner. + * \param y : Vertical position of the upper/left window corner. + * \param title : Window title. + */ + virtual void init(vpImage &I, int x = -1, int y = -1, const std::string &title = "") = 0; + /*! + * Initialize the display (size, position and title) of a color + * image in RGBa format. + * + * \param I : Image to be displayed (not that image has to be initialized). + * \param x : Horizontal position of the upper/left window corner. + * \param y : Vertical position of the upper/left window corner. + * \param title : Window title. + */ + virtual void init(vpImage &I, int x = -1, int y = -1, const std::string &title = "") = 0; + + /*! + * Initialize the display size, position and title. + * + * \param width : Window width. + * \param height : Window height. + * \param x : Horizontal position of the upper/left window corner. + * \param y : Vertical position of the upper/left window corner. + * \param title : Window title. + * + * The following example shows how to use this function + * \code + * #include + * #include + * #include + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * #ifdef VISP_HAVE_DISPLAY + * vpImage I; + * vpImageIo::read(I, "lena.pgm"); + * + * vpDisplay *d; + * + * #if defined(VISP_HAVE_X11) + * d = new vpDisplayX; + * #elif defined(VISP_HAVE_GTK) + * d = new vpDisplayGTK; + * #elif defined(VISP_HAVE_GDI) + * d = new vpDisplayGDI; + * #elif defined(VISP_HAVE_D3D9) + * d = new vpDisplayD3D; + * #elif defined(HAVE_OPENCV_HIGHGUI) + * d = new vpDisplayOpenCV; + * #else + * std::cout << "Sorry, no video device is available" << std::endl; + * return -1; + * #endif + * + * d->init(I.getWidth(), I.getHeight(), 10, 20, "viewer"); + * + * // Now associate the display to the image + * I.display = d; + * + * // Set the display background with image I content + * vpDisplay::display(I); + * + * // Flush the foreground and background display + * vpDisplay::flush(I); + * + * // wait for a mouse clink in the display to exit + * vpDisplay::getClick(I); + * + * delete d; + * #endif + * } + * \endcode + */ + virtual void init(unsigned int width, unsigned int height, int x = -1, int y = -1, const std::string &title = "") = 0; + + /*! + * Set the font used to display a text in overlay. The display is + * performed using displayText(). + * + * \param font : The expected font name. The available fonts are given by + * the "xlsfonts" binary. To choose a font you can also use the + * "xfontsel" binary. + * + * \note Under UNIX, to know all the available fonts, use the + * "xlsfonts" binary in a terminal. You can also use the "xfontsel" binary. + * + * \sa displayText() + */ + virtual void setFont(const std::string &font) = 0; + /*! + * Set the window title. + * \param title : Window title. + */ + virtual void setTitle(const std::string &title) = 0; + /*! + * Set the window position in the screen. + * + * \param x : Horizontal position of the upper/left window corner. + * \param y : Vertical position of the upper/left window corner. + + */ + virtual void setWindowPosition(int x, int y) = 0; + //@} +#endif // ifndef DOXYGEN_SHOULD_SKIP_THIS + + /*! + * @name Static public vpDisplay functionalities on gray level images. + */ + //@{ + static void close(vpImage &I); + static void display(const vpImage &I); + static void displayArrow(const vpImage &I, const vpImagePoint &ip1, const vpImagePoint &ip2, + const vpColor &color = vpColor::white, unsigned int w = 4, unsigned int h = 2, + unsigned int thickness = 1); + static void displayArrow(const vpImage &I, int i1, int j1, int i2, int j2, + const vpColor &color = vpColor::white, unsigned int w = 4, unsigned int h = 2, + unsigned int thickness = 1); + static void displayCamera(const vpImage &I, const vpHomogeneousMatrix &cMo, + const vpCameraParameters &cam, double size, const vpColor &color, unsigned int thickness); + static void displayCircle(const vpImage &I, const vpImageCircle &circle, + const vpColor &color, bool fill = false, unsigned int thickness = 1); + static void displayCircle(const vpImage &I, const vpImagePoint ¢er, unsigned int radius, + const vpColor &color, bool fill = false, unsigned int thickness = 1); + static void displayCircle(const vpImage &I, int i, int j, unsigned int radius, const vpColor &color, + bool fill = false, unsigned int thickness = 1); + static void displayCross(const vpImage &I, const vpImagePoint &ip, unsigned int size, + const vpColor &color, unsigned int thickness = 1); + static void displayCross(const vpImage &I, int i, int j, unsigned int size, const vpColor &color, + unsigned int thickness = 1); + static void displayDotLine(const vpImage &I, const vpImagePoint &ip1, const vpImagePoint &ip2, + const vpColor &color, unsigned int thickness = 1); + static void displayDotLine(const vpImage &I, int i1, int j1, int i2, int j2, const vpColor &color, + unsigned int thickness = 1); + static void displayDotLine(const vpImage &I, const std::vector &ips, bool closeTheShape, + const vpColor &color, unsigned int thickness = 1); + static void displayDotLine(const vpImage &I, const std::list &ips, bool closeTheShape, + const vpColor &color, unsigned int thickness = 1); + static void displayEllipse(const vpImage &I, const vpImagePoint ¢er, const double &coef1, + const double &coef2, const double &coef3, bool use_normalized_centered_moments, + const vpColor &color, unsigned int thickness = 1, bool display_center = false, + bool display_arc = false); + static void displayEllipse(const vpImage &I, const vpImagePoint ¢er, const double &coef1, + const double &coef2, const double &coef3, const double &smallalpha, + const double &highalpha, bool use_normalized_centered_moments, const vpColor &color, + unsigned int thickness = 1, bool display_center = false, bool display_arc = false); + static void displayFrame(const vpImage &I, const vpHomogeneousMatrix &cMo, + const vpCameraParameters &cam, double size, const vpColor &color = vpColor::none, + unsigned int thickness = 1, const vpImagePoint &offset = vpImagePoint(0, 0), + const std::string &frameName = "", const vpColor &textColor = vpColor::black, const vpImagePoint &textOffset = vpImagePoint(15, 15)); + static void displayLine(const vpImage &I, const vpImagePoint &ip1, const vpImagePoint &ip2, + const vpColor &color, unsigned int thickness = 1, bool segment = true); + static void displayLine(const vpImage &I, int i1, int j1, int i2, int j2, const vpColor &color, + unsigned int thickness = 1, bool segment = true); + static void displayLine(const vpImage &I, const std::vector &ips, bool closeTheShape, + const vpColor &color, unsigned int thickness = 1); + static void displayLine(const vpImage &I, const std::list &ips, bool closeTheShape, + const vpColor &color, unsigned int thickness = 1); + static void displayPoint(const vpImage &I, const vpImagePoint &ip, const vpColor &color, + unsigned int thickness = 1); + static void displayPoint(const vpImage &I, int i, int j, const vpColor &color, + unsigned int thickness = 1); + static void displayPolygon(const vpImage &I, const std::vector &vip, + const vpColor &color, unsigned int thickness = 1, bool closed = true); + static void displayPolygon(const vpImage &I, const vpPolygon &polygon, + const vpColor &color, unsigned int thickness = 1, bool closed = true); + static void displayRectangle(const vpImage &I, const vpImagePoint &topLeft, unsigned int width, + unsigned int height, const vpColor &color, bool fill = false, + unsigned int thickness = 1); + static void displayRectangle(const vpImage &I, const vpImagePoint &topLeft, + const vpImagePoint &bottomRight, const vpColor &color, bool fill = false, + unsigned int thickness = 1); + static void displayRectangle(const vpImage &I, const vpRect &rectangle, const vpColor &color, + bool fill = false, unsigned int thickness = 1); + static void displayRectangle(const vpImage &I, const vpImagePoint ¢er, float angle, + unsigned int width, unsigned int height, const vpColor &color, + unsigned int thickness = 1); + static void displayRectangle(const vpImage &I, int i, int j, unsigned int width, unsigned int height, + const vpColor &color, bool fill = false, unsigned int thickness = 1); + static void displayRectangle(const vpImage &I, unsigned int i, unsigned int j, float angle, + unsigned int width, unsigned int height, const vpColor &color, + unsigned int thickness = 1); + static void displayROI(const vpImage &I, const vpRect &roi); + static void displayText(const vpImage &I, const vpImagePoint &ip, const std::string &s, + const vpColor &color); + static void displayText(const vpImage &I, int i, int j, const std::string &s, const vpColor &color); + + static void flush(const vpImage &I); + static void flushROI(const vpImage &I, const vpRect &roi); + + static bool getClick(const vpImage &I, bool blocking = true); + static bool getClick(const vpImage &I, vpImagePoint &ip, bool blocking = true); + static bool getClick(const vpImage &I, vpImagePoint &ip, vpMouseButton::vpMouseButtonType &button, + bool blocking = true); + static bool getClick(const vpImage &I, vpMouseButton::vpMouseButtonType &button, bool blocking = true); + static bool getClickUp(const vpImage &I, vpImagePoint &ip, vpMouseButton::vpMouseButtonType &button, + bool blocking = true); + static bool getClickUp(const vpImage &I, vpMouseButton::vpMouseButtonType &button, + bool blocking = true); + static unsigned int getDownScalingFactor(const vpImage &I); + static void getImage(const vpImage &Is, vpImage &Id); + static bool getKeyboardEvent(const vpImage &I, bool blocking = true); + static bool getKeyboardEvent(const vpImage &I, std::string &key, bool blocking = true); + static bool getKeyboardEvent(const vpImage &I, char *key, bool blocking = true); + static bool getPointerMotionEvent(const vpImage &I, vpImagePoint &ip); + static bool getPointerPosition(const vpImage &I, vpImagePoint &ip); + + static void setBackground(const vpImage &I, const vpColor &color); + static void setFont(const vpImage &I, const std::string &font); + static void setTitle(const vpImage &I, const std::string &windowtitle); + static void setWindowPosition(const vpImage &I, int winx, int winy); + //@} + + /*! + * @name Static public vpDisplay functionalities on 32 bits color images. + */ + //@{ + static void close(vpImage &I); + static void display(const vpImage &I); + static void displayArrow(const vpImage &I, const vpImagePoint &ip1, const vpImagePoint &ip2, + const vpColor &color = vpColor::white, unsigned int w = 4, unsigned int h = 2, + unsigned int thickness = 1); + static void displayArrow(const vpImage &I, int i1, int j1, int i2, int j2, + const vpColor &color = vpColor::white, unsigned int w = 4, unsigned int h = 2, + unsigned int thickness = 1); + static void displayCamera(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + double size, const vpColor &color, unsigned int thickness); + static void displayCircle(const vpImage &I, const vpImageCircle &circle, + const vpColor &color, bool fill = false, unsigned int thickness = 1); + static void displayCircle(const vpImage &I, const vpImagePoint ¢er, unsigned int radius, + const vpColor &color, bool fill = false, unsigned int thickness = 1); + static void displayCircle(const vpImage &I, int i, int j, unsigned int radius, const vpColor &color, + bool fill = false, unsigned int thickness = 1); + static void displayCross(const vpImage &I, const vpImagePoint &ip, unsigned int size, const vpColor &color, + unsigned int thickness = 1); + static void displayCross(const vpImage &I, int i, int j, unsigned int size, const vpColor &color, + unsigned int thickness = 1); + static void displayDotLine(const vpImage &I, const vpImagePoint &ip1, const vpImagePoint &ip2, + const vpColor &color, unsigned int thickness = 1); + static void displayDotLine(const vpImage &I, int i1, int j1, int i2, int j2, const vpColor &color, + unsigned int thickness = 1); + static void displayDotLine(const vpImage &I, const std::vector &ips, bool closeTheShape, + const vpColor &color, unsigned int thickness = 1); + static void displayDotLine(const vpImage &I, const std::list &ips, bool closeTheShape, + const vpColor &color, unsigned int thickness = 1); + static void displayEllipse(const vpImage &I, const vpImagePoint ¢er, const double &coef1, + const double &coef2, const double &coef3, bool use_centered_moments, const vpColor &color, + unsigned int thickness = 1, bool display_center = false, bool display_arc = false); + static void displayEllipse(const vpImage &I, const vpImagePoint ¢er, const double &coef1, + const double &coef2, const double &coef3, const double &theta1, const double &theta2, + bool use_centered_moments, const vpColor &color, unsigned int thickness = 1, + bool display_center = false, bool display_arc = false); + static void displayFrame(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + double size, const vpColor &color = vpColor::none, unsigned int thickness = 1, + const vpImagePoint &offset = vpImagePoint(0, 0), const std::string &frameName = "", + const vpColor &textColor = vpColor::black, const vpImagePoint &textOffset = vpImagePoint(15, 15)); + static void displayLine(const vpImage &I, const vpImagePoint &ip1, const vpImagePoint &ip2, + const vpColor &color, unsigned int thickness = 1, bool segment = true); + static void displayLine(const vpImage &I, int i1, int j1, int i2, int j2, const vpColor &color, + unsigned int thickness = 1, bool segment = true); + static void displayLine(const vpImage &I, const std::vector &ips, bool closeTheShape, + const vpColor &color, unsigned int thickness = 1); + static void displayLine(const vpImage &I, const std::list &ips, bool closeTheShape, + const vpColor &color, unsigned int thickness = 1); + static void displayPoint(const vpImage &I, const vpImagePoint &ip, const vpColor &color, + unsigned int thickness = 1); + static void displayPoint(const vpImage &I, int i, int j, const vpColor &color, unsigned int thickness = 1); + static void displayPolygon(const vpImage &I, const std::vector &vip, const vpColor &color, + unsigned int thickness = 1, bool closed = true); + static void displayPolygon(const vpImage &I, const vpPolygon &polygon, + const vpColor &color, unsigned int thickness = 1, bool closed = true); + static void displayRectangle(const vpImage &I, const vpImagePoint &topLeft, unsigned int width, + unsigned int height, const vpColor &color, bool fill = false, + unsigned int thickness = 1); + static void displayRectangle(const vpImage &I, const vpImagePoint &topLeft, const vpImagePoint &bottomRight, + const vpColor &color, bool fill = false, unsigned int thickness = 1); + static void displayRectangle(const vpImage &I, const vpRect &rectangle, const vpColor &color, + bool fill = false, unsigned int thickness = 1); + static void displayRectangle(const vpImage &I, const vpImagePoint ¢er, float angle, unsigned int width, + unsigned int height, const vpColor &color, unsigned int thickness = 1); + static void displayRectangle(const vpImage &I, int i, int j, unsigned int width, unsigned int height, + const vpColor &color, bool fill = false, unsigned int thickness = 1); + static void displayRectangle(const vpImage &I, unsigned int i, unsigned int j, float angle, + unsigned int width, unsigned int height, const vpColor &color, + unsigned int thickness = 1); + static void displayROI(const vpImage &I, const vpRect &roi); + static void displayText(const vpImage &I, const vpImagePoint &ip, const std::string &s, const vpColor &color); + static void displayText(const vpImage &I, int i, int j, const std::string &s, const vpColor &color); + + static void flush(const vpImage &I); + static void flushROI(const vpImage &I, const vpRect &roi); + + static bool getClick(const vpImage &I, bool blocking = true); + static bool getClick(const vpImage &I, vpImagePoint &ip, bool blocking = true); + static bool getClick(const vpImage &I, vpImagePoint &ip, vpMouseButton::vpMouseButtonType &button, + bool blocking = true); + static bool getClick(const vpImage &I, vpMouseButton::vpMouseButtonType &button, bool blocking = true); + static bool getClickUp(const vpImage &I, vpImagePoint &ip, vpMouseButton::vpMouseButtonType &button, + bool blocking = true); + static bool getClickUp(const vpImage &I, vpMouseButton::vpMouseButtonType &button, bool blocking = true); + static unsigned int getDownScalingFactor(const vpImage &I); + static void getImage(const vpImage &Is, vpImage &Id); + static bool getKeyboardEvent(const vpImage &I, bool blocking = true); + static bool getKeyboardEvent(const vpImage &I, std::string &key, bool blocking = true); + static bool getKeyboardEvent(const vpImage &I, char *key, bool blocking = true); + static bool getPointerMotionEvent(const vpImage &I, vpImagePoint &ip); + static bool getPointerPosition(const vpImage &I, vpImagePoint &ip); + + static void setBackground(const vpImage &I, const vpColor &color); + static void setFont(const vpImage &I, const std::string &font); + static void setTitle(const vpImage &I, const std::string &windowtitle); + static void setWindowPosition(const vpImage &I, int winx, int winy); + //@} + +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) + /*! + * @name Deprecated functions + */ + //@{ + VP_DEPRECATED static void displayCharString(const vpImage &I, const vpImagePoint &ip, const char *string, + const vpColor &color); + VP_DEPRECATED static void displayCharString(const vpImage &I, int i, int j, const char *string, + const vpColor &color); + VP_DEPRECATED static void displayCharString(const vpImage &I, const vpImagePoint &ip, const char *string, + const vpColor &color); + VP_DEPRECATED static void displayCharString(const vpImage &I, int i, int j, const char *string, + const vpColor &color); + //@} +#endif + +protected: + //! display has been initialized + bool m_displayHasBeenInitialized; + //! display position + int m_windowXPosition; + //! display position + int m_windowYPosition; + unsigned int m_width; + unsigned int m_height; + std::string m_title; + unsigned int m_scale; + vpScaleType m_scaleType; + + void setScale(vpScaleType scaleType, unsigned int width, unsigned int height); + +private: + //! Get the window pixmap and put it in vpRGBa image. + virtual void getImage(vpImage &I) = 0; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpDisplayException.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpDisplayException.h new file mode 100644 index 00000000..4aed6b8c --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpDisplayException.h @@ -0,0 +1,95 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Exception that can be emitted by the vpDisplay class and its derivatives. + */ + +/*! + * \file vpDisplayException.h + * \brief error that can be emitted by the vpDisplay class and its derivatives + */ + +#ifndef VP_DISPLAY_EXCEPTION_H +#define VP_DISPLAY_EXCEPTION_H + +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpDisplayException + * \ingroup group_core_debug + * \brief Error that can be emitted by the vpDisplay class and its derivatives. +*/ +class VISP_EXPORT vpDisplayException : public vpException +{ +public: + /*! + * Lists the possible error than can be emitted while calling + * vpDisplay member + */ + enum errorDisplayCodeEnum + { + notInitializedError, //!< Display not initialized + cannotOpenWindowError, //!< Unable to open display window + connexionError, //!< Connection error + XWindowsError, //!< XWindow error + GTKWindowsError, //!< GTK error + colorAllocError, //!< Color allocation error + depthNotSupportedError //!< Color depth not supported + }; + +public: + /*! + * Constructor. + */ + vpDisplayException(int id, const char *format, ...) + { + this->code = id; + va_list args; + va_start(args, format); + setMessage(format, args); + va_end(args); + } + + /*! + * Constructor. + */ + vpDisplayException(int id, const std::string &msg) : vpException(id, msg) { } + + /*! + * Constructor. + */ + VP_EXPLICIT vpDisplayException(int id) : vpException(id) { } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpEigenConversion.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpEigenConversion.h new file mode 100644 index 00000000..c614fa5a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpEigenConversion.h @@ -0,0 +1,109 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * ViSP <--> Eigen conversion. + */ + +#ifndef VP_EIGEN_CONVERSION_H +#define VP_EIGEN_CONVERSION_H + +#include +#ifdef VISP_HAVE_EIGEN3 +#include +#endif +#include + +namespace VISP_NAMESPACE_NAME +{ +#ifdef VISP_HAVE_EIGEN3 +/* Eigen to ViSP */ +VISP_EXPORT void eigen2visp(const Eigen::MatrixXd &src, vpMatrix &dst); + +VISP_EXPORT void eigen2visp(const Eigen::MatrixXd &src, vpHomogeneousMatrix &dst); + +template +void eigen2visp(const Eigen::Quaternion &src, vpQuaternionVector &dst) +{ + dst.buildFrom(src.x(), src.y(), src.z(), src.w()); +} + +template +void eigen2visp(const Eigen::AngleAxis &src, vpThetaUVector &dst) +{ + const unsigned int val_2 = 2; + dst.buildFrom(src.angle() * src.axis()(0), src.angle() * src.axis()(1), src.angle() * src.axis()(val_2)); +} + +VISP_EXPORT void eigen2visp(const Eigen::VectorXd &src, vpColVector &dst); + +VISP_EXPORT void eigen2visp(const Eigen::RowVectorXd &src, vpRowVector &dst); + +/* ViSP to Eigen */ +template +void visp2eigen(const vpMatrix &src, Eigen::MatrixBase &dst) +{ + dst = Eigen::Map >(src.data, src.getRows(), + src.getCols()); +} + +template +void visp2eigen(const vpHomogeneousMatrix &src, Eigen::MatrixBase &dst) +{ + dst = Eigen::Map >(src.data, src.getRows(), + src.getCols()); +} + +template +void visp2eigen(const vpQuaternionVector &src, Eigen::Quaternion &dst) +{ + dst.w() = static_cast(src.w()); + dst.x() = static_cast(src.x()); + dst.y() = static_cast(src.y()); + dst.z() = static_cast(src.z()); +} + +template +void visp2eigen(const vpThetaUVector &src, Eigen::AngleAxis &dst) +{ + const unsigned int index_0 = 0; + const unsigned int index_1 = 1; + const unsigned int index_2 = 2; + dst.angle() = static_cast(src.getTheta()); + dst.axis()(index_0) = static_cast(src.getU()[index_0]); + dst.axis()(index_1) = static_cast(src.getU()[index_1]); + dst.axis()(index_2) = static_cast(src.getU()[index_2]); +} + +VISP_EXPORT void visp2eigen(const vpColVector &src, Eigen::VectorXd &dst); + +VISP_EXPORT void visp2eigen(const vpRowVector &src, Eigen::RowVectorXd &dst); +#endif +} // namespace VISP_NAMESPACE_NAME +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpEndian.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpEndian.h new file mode 100644 index 00000000..9fabf304 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpEndian.h @@ -0,0 +1,107 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Determine machine endianness and define VISP_LITTLE_ENDIAN, VISP_BIG_ENDIAN and VISP_PDP_ENDIAN macros. + */ + +/*! + \file vpEndian.h + \brief Determine machine endianness and define VISP_LITTLE_ENDIAN, VISP_BIG_ENDIAN and VISP_PDP_ENDIAN macros. +*/ + +#ifndef VP_ENDIAN_H +#define VP_ENDIAN_H + +// Visual Studio 2010 or previous is missing inttypes.h +#if defined(_MSC_VER) && (_MSC_VER < 1700) +typedef unsigned short uint16_t; +#else +#include +#endif +#include //for uint32_t related types ; works also with >= VS2010 / _MSC_VER >= 1600 +#include + +// Detect endianness of the host machine +// Reference: http://www.boost.org/doc/libs/1_36_0/boost/detail/endian.hpp +#if defined(__GLIBC__) || (defined(__GNUC__) && !defined(__llvm__) && !defined(__MINGW32__) && \ + !defined(__FreeBSD__) && defined(__BYTE_ORDER__)) +#include +#if (__BYTE_ORDER == __LITTLE_ENDIAN) +#define VISP_LITTLE_ENDIAN +#elif (__BYTE_ORDER == __BIG_ENDIAN) +#define VISP_BIG_ENDIAN +#elif (__BYTE_ORDER == __PDP_ENDIAN) +// Currently not supported when reading / writing binary file +#define VISP_PDP_ENDIAN +//#error PDP endian is not supported. //Uncomment if needed/happens +#else +#error Unknown machine endianness detected. +#endif +#elif defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN) || defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__) +#define VISP_BIG_ENDIAN +#elif defined(_LITTLE_ENDIAN) && !defined(_BIG_ENDIAN) || defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__) +#define VISP_LITTLE_ENDIAN +#elif defined(__sparc) || defined(__sparc__) || defined(_POWER) || defined(__powerpc__) || defined(__ppc__) || \ + defined(__hpux) || defined(_MIPSEB) || defined(_POWER) || defined(__s390__) + +#define VISP_BIG_ENDIAN +#elif defined(__i386__) || defined(__alpha__) || defined(__ia64) || defined(__ia64__) || defined(_M_IX86) || \ + defined(_M_IA64) || defined(_M_ALPHA) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64) || \ + defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || defined(__ANDROID__) +// It appears that all Android systems are little endian. +// Refer https://stackoverflow.com/questions/6212951/endianness-of-android-ndk +#define VISP_LITTLE_ENDIAN +#elif defined(WINRT) // For UWP +// Refer +// https://social.msdn.microsoft.com/Forums/en-US/04c92ef9-e38e-415f-8958-ec9f7c196fd3/arm-endianess-under-windows-mobile?forum=windowsmobiledev +#define VISP_LITTLE_ENDIAN +#else +#error Cannot detect host machine endianness. +#endif + +BEGIN_VISP_NAMESPACE +namespace vpEndian +{ +VISP_EXPORT uint16_t swap16bits(uint16_t val); + +VISP_EXPORT uint32_t swap32bits(uint32_t val); + +VISP_EXPORT uint64_t swap64bits(uint64_t val); + +VISP_EXPORT float swapFloat(float f); + +VISP_EXPORT double swapDouble(double d); + +VISP_EXPORT bool isBigEndian(); + +VISP_EXPORT uint16_t reinterpret_cast_uchar_to_uint16_LE(unsigned char *const ptr); +} // namespace vpEndian +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpException.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpException.h new file mode 100644 index 00000000..ccb591f6 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpException.h @@ -0,0 +1,154 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Exception handling. + */ + +/*! + * \file vpException.h + * \brief error that can be emitted by the vp class and its derivatives + */ + +#ifndef VP_EXCEPTION_H +#define VP_EXCEPTION_H + +#include + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpException + * \ingroup group_core_debug + * \brief error that can be emitted by ViSP classes. + * + * This class inherits from the standard std::exception contained in the C++ + * STL. + * It is therefore possible to catch vpException with any other derivative of + * std::exception in the same catch. +*/ +class VISP_EXPORT vpException : public std::exception +{ +public: + enum generalExceptionEnum + { + memoryAllocationError, //!< Memory allocation error + memoryFreeError, //!< Memory free error + functionNotImplementedError, //!< Function not implemented + ioError, //!< I/O error + cannotUseConstructorError, //!< constructor error + notImplementedError, //!< Not implemented + divideByZeroError, //!< Division by zero + dimensionError, //!< Bad dimension + fatalError, //!< Fatal error + badValue, //!< Used to indicate that a value is not in the allowed range. + notInitialized //!< Used to indicate that a parameter is not initialized. + }; + + /*! + * Constructor. + */ + vpException(int code, const char *format, va_list args); + /*! + * Constructor. + */ + vpException(int code, const char *format, ...); + + /*! + * Constructor. + */ + vpException(int code, const std::string &msg); + + /*! + Basic destructor. Do nothing but implemented to fit the inheritance from + std::exception + */ +#if (VISP_CXX_STANDARD == VISP_CXX_STANDARD_98) + virtual ~vpException() throw() { } +#endif + /*! + * Constructor. + */ + VP_EXPLICIT vpException(int code); + + /** @name Inherited functionalities from vpException */ + //@{ + /*! + * Send the object code. + */ + int getCode() const; + + /*! + * Send a reference (constant) related the error message (can be empty). + */ + const std::string &getStringMessage() const; + + /*! + * Send a pointer on the array of \e char related to the error string. + * Cannot be \e nullptr. + */ + const char *getMessage() const; + + /*! + * Overloading of the what() method of std::exception to return the vpException + * message. + * + * \return pointer on the array of \e char related to the error string. + */ +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + const char *what() const VP_NOEXCEPT VP_OVERRIDE; +#else + const char *what() const throw(); +#endif + //@} + + /*! + * Print the error structure. + */ + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpException &art); + +protected: + //! Contains the error code, see the errorCodeEnum table for details. + int code; + + //! Contains an error message (can be empty) + std::string message; + + //! Set the message container + void setMessage(const char *format, va_list args); + + //! forbid the empty constructor (protected) + vpException() : code(notInitialized), message("") { } + +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpExponentialMap.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpExponentialMap.h new file mode 100644 index 00000000..c9475d17 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpExponentialMap.h @@ -0,0 +1,98 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Exponential map. + */ + +/*! + \file vpExponentialMap.h + \brief Provides exponential map computation +*/ + +#ifndef VP_EXPONENTIAL_MAP_H +#define VP_EXPONENTIAL_MAP_H + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpExponentialMap + + \ingroup group_core_transformations + + \brief Direct or inverse exponential map computation. + + The exponential map gives the relationship between the velocity of a moving + body and its displacement: + + \f[ \exp({^c}{\bf v}_c(t - \Delta t)) = {^{c(t - \Delta t)}}{\bf M}_{c(t)} \f] + + where \f$ {^c}{\bf v}_c(t - \Delta t)\f$ is the velocity skew vector at the previous iteration applied during \f$\Delta t\f$ + seconds at point \f$ c \f$ in frame \f$ c \f$, while \f$ {^{c(t- \Delta t)}}{\bf M}_{c(t)} \f$ + is the corresponding displacement. + + This class allows to compute the direct or the inverse exponential map. + + - The direct exponential map allows to compute the displacement + \f${^{c(t - \Delta t)}}{\bf M}_{c(t)}\f$ using \f${^c}{\bf v}_c(t - \Delta t)\f$ as input: + \f[ {^{o}}{\bf M}_{c(t)} = {^{o}}{\bf M}_{c(t - \Delta t)} \exp({^c}{\bf v}_c(t - \Delta t)) \f] + where \f$ o \f$ is a reference frame. + With direct(), the velocity skew vector \f$ {^c}{\bf v}_c(t - \Delta t) \f$ is applied during 1 second + considering \f$ \Delta t = 1\f$. With direct(const vpColVector &, const double &) + the sampling time can be set to an other value where the second + argument is \f$ \Delta t \f$. + + - The inverse exponential map allows to compute the velocity skew vector \f$ + {^c}{\bf v}_c(t - \Delta t) \f$ from the displacement \f$ {^{c(t - \Delta t)}}{\bf M}_{c(t)}\f$ + measured during a time interval \f$ \Delta t \f$. With inverse() the time interval + also called sampling time is set to 1 second. With + inverse(const vpHomogeneousMatrix &, const double &) the sampling time can + be set to an other value where the second + argument is \f$ \Delta t \f$. + + A displacement \f$ \bf M \f$ is represented as an homogeneous matrix implemented in + vpHomogeneousMatrix. A velocity \f$ \bf v \f$ is represented as a + 6 dimension velocity skew vector \f$ [v, \omega] \f$, where \f$ v \f$ + is a velocity translation vector with values in m/s and \f$ \omega \f$ a + velocity rotation vector with values expressed in rad/s. + +*/ +class VISP_EXPORT vpExponentialMap +{ +public: + static vpHomogeneousMatrix direct(const vpColVector &v); + static vpHomogeneousMatrix direct(const vpColVector &v, const double &delta_t); + static vpColVector inverse(const vpHomogeneousMatrix &M); + static vpColVector inverse(const vpHomogeneousMatrix &M, const double &delta_t); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpFeatureDisplay.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpFeatureDisplay.h new file mode 100644 index 00000000..ac64e73c --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpFeatureDisplay.h @@ -0,0 +1,89 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Interface with the image for feature display. + */ + +/*! + \file vpFeatureDisplay.h + \brief interface with the image for feature display +*/ + +#ifndef VP_FEATURE_DISPLAY_H +#define VP_FEATURE_DISPLAY_H + +#include + +// Color / image / display +#include +#include +#include + +// Meter/pixel conversion +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpFeatureDisplay + + \ingroup group_core_gui + \brief Interface with the image for feature display. +*/ +class VISP_EXPORT vpFeatureDisplay +{ + +public: + static void displayCylinder(double rho1, double theta1, double rho2, double theta2, const vpCameraParameters &cam, + const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1); + static void displayCylinder(double rho1, double theta1, double rho2, double theta2, const vpCameraParameters &cam, + const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1); + + static void displayEllipse(double x, double y, double n20, double n11, double n02, const vpCameraParameters &cam, + const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1); + + static void displayEllipse(double x, double y, double n20, double n11, double n02, const vpCameraParameters &cam, + const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1); + + static void displayLine(double rho, double theta, const vpCameraParameters &cam, const vpImage &I, + const vpColor &color = vpColor::green, unsigned int thickness = 1); + static void displayLine(double rho, double theta, const vpCameraParameters &cam, const vpImage &I, + const vpColor &color = vpColor::green, unsigned int thickness = 1); + + static void displayPoint(double x, double y, const vpCameraParameters &cam, const vpImage &I, + const vpColor &color = vpColor::green, unsigned int thickness = 1); + static void displayPoint(double x, double y, const vpCameraParameters &cam, const vpImage &I, + const vpColor &color = vpColor::green, unsigned int thickness = 1); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpFont.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpFont.h new file mode 100644 index 00000000..e7960d07 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpFont.h @@ -0,0 +1,85 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Draw text in an image. + */ + +/*! + \file vpFont.h + + \brief Draw text in an image. +*/ + +#ifndef _vpFont_h_ +#define _vpFont_h_ + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpFont + + \ingroup group_core_image + + \brief Font drawing functions for image. +*/ +class VISP_EXPORT vpFont +{ +public: + enum vpFontFamily { GENERIC_MONOSPACE, TRUETYPE_FILE }; + + vpFont(unsigned int height = 16, const vpFontFamily &fontFamily = TRUETYPE_FILE, + const std::string &ttfFilename = std::string(VISP_RUBIK_REGULAR_FONT_RESOURCES)); + ~vpFont(); + + bool drawText(vpImage &I, const std::string &text, const vpImagePoint &position, + unsigned char color) const; + bool drawText(vpImage &I, const std::string &text, const vpImagePoint &position, unsigned char color, + unsigned char background) const; + + bool drawText(vpImage &I, const std::string &text, const vpImagePoint &position, const vpColor &color) const; + bool drawText(vpImage &I, const std::string &text, const vpImagePoint &position, const vpColor &color, + const vpColor &background) const; + + unsigned int getHeight() const; + vpImagePoint getMeasure(const std::string &text) const; + bool setHeight(unsigned int height); + +private: + vpFont(const vpFont &); // noncopyable + vpFont &operator=(const vpFont &); // + + // PIMPL idiom + class Impl; + Impl *m_impl; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpForceTwistMatrix.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpForceTwistMatrix.h new file mode 100644 index 00000000..d0e315ef --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpForceTwistMatrix.h @@ -0,0 +1,253 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Twist transformation matrix that allows to transform forces from one + * frame to an other. + */ + +#ifndef VP_FORCE_TWIST_MATRIX_H +#define VP_FORCE_TWIST_MATRIX_H + +#include + +BEGIN_VISP_NAMESPACE +class vpMatrix; +END_VISP_NAMESPACE + +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpForceTwistMatrix + + \ingroup group_core_transformations + + This class derived from vpArray2D implements the 6 by 6 matrix which + transforms force/torque from one frame to another. This matrix is also called + force/torque twist transformation matrix. + + The full force/torque twist transformation matrix allows to compute the + force/torque at point a expressed in frame a knowing its + force/torque at point b expressed in frame b. This matrix + is defined as: + + \f[ + ^a{\bf F}_b = \left[ \begin{array}{cc} + ^a{\bf R}_b & {\bf 0}_{3\times 3}\\ + {[^a{\bf t}_b]}_{\times} \; ^a{\bf R}_b & ^a{\bf R}_b + \end{array} + \right] + \f] + + where \f$ ^a{\bf R}_b \f$ is a rotation matrix and + \f$ ^a{\bf t}_b \f$ is a translation vector. + + There are different ways to initialize such a full force/torque twist matrix. The following + example shows how to proceed setting the translation and rotation matrix transformations: + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpTranslationVector stp(0.1, 0.2, 0.3); + vpRotationMatrix sRp( {0, 0, -1, + 0, -1, 0, + -1, 0, 0} ); + vpForceTwistMatrix sFp(stp, sRp); + std::cout << "sFp:\n" << sFp << std::endl; + } + \endcode + It produces the following printings: + \code + sFp: + 0 0 -1 0 0 0 + 0 -1 0 0 0 0 + -1 0 0 0 0 0 + -0.2 0.3 0 0 0 -1 + 0.1 0 -0.3 0 -1 0 + 0 -0.1 0.2 -1 0 0 + \endcode + + When the point where the velocity is expressed doesn't change, the matrix + becomes block diagonal. It allows than to compute the force/torque at point + b expressed in frame a knowing its + force/torque at point b expressed in frame b : + \f[ + ^a{\bf F}_b = \left[ \begin{array}{cc} + ^a{\bf R}_b & {\bf 0}_{3\times 3}\\ + {\bf 0}_{3\times 3} & ^a{\bf R}_b + \end{array} + \right] + \f] + + To initialize such a force/torque twist matrix where translation is not taken into account you + can proceed like in the following code: + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpRotationMatrix sRp( {0, 0, -1, + 0, -1, 0, + -1, 0, 0} ); + vpForceTwistMatrix sFp(sRp); + std::cout << "sFp:\n" << sFp << std::endl; + } + \endcode + It produces the following printings: + \code + sFp: + 0 0 -1 0 0 0 + 0 -1 0 0 0 0 + -1 0 0 0 0 0 + 0 0 0 0 0 -1 + 0 0 0 0 -1 0 + 0 0 0 -1 0 0 + \endcode + + The code belows shows for example how to convert a force/torque skew + from probe frame to a sensor frame. + + \code + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + // Twist transformation matrix from sensor to probe frame + vpForceTwistMatrix sFp; + + // Force/torque sensor frame to probe frame transformation + vpHomogeneousMatrix sMp; + // ... sMp need here to be initialized + + sFp.buildFrom(sMp); + + // Force/torque skew in the probe frame: fx,fy,fz,tx,ty,tz + vpColVector p_H(6); + // ... p_H should here have an initial value + + // Force/torque skew in the sensor frame: fx,fy,fz,tx,ty,tz + vpColVector s_H(6); + + // Compute the value of the force/torque in the sensor frame + s_H = sFp * p_H; + } + \endcode +*/ +class VISP_EXPORT vpForceTwistMatrix : public vpArray2D +{ +public: + // basic constructor + vpForceTwistMatrix(); + // copy constructor + vpForceTwistMatrix(const vpForceTwistMatrix &F); + // constructor from an homogeneous transformation + VP_EXPLICIT vpForceTwistMatrix(const vpHomogeneousMatrix &M, bool full = true); + + // Construction from Translation and rotation (matrix parameterization) + vpForceTwistMatrix(const vpTranslationVector &t, const vpRotationMatrix &R); + // Construction from Translation and rotation (ThetaU parameterization) + vpForceTwistMatrix(const vpTranslationVector &t, const vpThetaUVector &thetau); + vpForceTwistMatrix(double tx, double ty, double tz, double tux, double tuy, double tuz); + + VP_EXPLICIT vpForceTwistMatrix(const vpRotationMatrix &R); + VP_EXPLICIT vpForceTwistMatrix(const vpThetaUVector &thetau); + + vpForceTwistMatrix &buildFrom(const vpTranslationVector &t, const vpRotationMatrix &R); + vpForceTwistMatrix &buildFrom(const vpTranslationVector &t, const vpThetaUVector &thetau); + vpForceTwistMatrix &buildFrom(const vpHomogeneousMatrix &M, bool full = true); + + vpForceTwistMatrix &buildFrom(const vpRotationMatrix &R); + vpForceTwistMatrix &buildFrom(const vpThetaUVector &thetau); + + // Basic initialisation (identity) + void eye(); + + vpForceTwistMatrix operator*(const vpForceTwistMatrix &F) const; + vpMatrix operator*(const vpMatrix &M) const; + + vpColVector operator*(const vpColVector &H) const; + + // copy operator from vpMatrix (handle with care) + vpForceTwistMatrix &operator=(const vpForceTwistMatrix &H); + + int print(std::ostream &s, unsigned int length, char const *intro = nullptr) const; + + /*! + This function is not applicable to a velocity twist matrix that is always + a 6-by-6 matrix. \exception vpException::fatalError When this function is + called. + */ + VP_NORETURN void resize(unsigned int nrows, unsigned int ncols, bool flagNullify = true) + { + (void)nrows; + (void)ncols; + (void)flagNullify; + throw(vpException(vpException::fatalError, "Cannot resize a velocity twist matrix")); + } + +private: + static const unsigned int constr_value_6; +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) + /*! + @name Deprecated functions + */ + //@{ + /*! + \deprecated Provided only for compat with previous releases. + This function does nothing. + */ + VP_DEPRECATED void init() { } + /*! + \deprecated You should rather use eye(). + */ + VP_DEPRECATED void setIdentity(); + //@} +#endif +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpForwardProjection.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpForwardProjection.h new file mode 100644 index 00000000..5f5a275d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpForwardProjection.h @@ -0,0 +1,224 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Forward projection. + */ + +/*! + * \file vpForwardProjection.h + * \brief class that defines what is a generic geometric feature + */ + +#ifndef VP_FORWARD_PROJECTION_H +#define VP_FORWARD_PROJECTION_H + +#include +#include +#include +#include + +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpForwardProjection + * \brief Class that defines what is a generic geometric feature. + * + * Each geometric feature has parameters expressed: + * + * - in the object frame \e oP. These parameters are located in the public + * attribute vpForwardProjection::oP. + * - in the camera frame \e cP. These parameters are located in the public + * attribute vpTracker::cP. + * - in the image plane \e p. These parameters are located in the public + * attribute vpTracker::p. They correspond to normalized coordinates + * of the feature expressed in meters. +*/ +class VISP_EXPORT vpForwardProjection : public vpTracker +{ +public: + /*! + * Used for memory issue especially in the vpServo class. + */ + typedef enum { user, vpDisplayForwardProjection } vpForwardProjectionDeallocatorType; + + /** @name Public Member Functions Inherited from vpForwardProjection */ + //@{ + vpForwardProjection() : oP(), deallocate(user) { } + + /*! + * Computes the features parameters in the camera frame (\e cP) thanks + * to the parameters given in the object frame + * (vpForwardProjection::oP) and the homogeneous matrix relative to + * the pose (\e cMo) between the object frame and the camera frame. + * + * To set the parameters in the object frame you need to call + * setWorldCoordinates(). + * + * \param cMo : The homogeneous matrix corresponding to the pose + * between the camera frame and the object frame. + * + * \param cP : The vector which will contain the feature parameters + * expressed in the camera frame. + * + * With this method, the vpTracker::cP public attribute is not updated. + */ + virtual void changeFrame(const vpHomogeneousMatrix &cMo, vpColVector &cP) const = 0; + + /*! + * Computes the features parameters in the camera frame (\e cP) thanks + * to the parameters given in the object frame + * (vpForwardProjection::oP) and the homogeneous matrix relative to + * the pose (\e cMo) between the object frame and the camera frame. + * + * To set the parameters in the object frame you need to call + * setWorldCoordinates(). + * + * \param cMo : The homogeneous matrix corresponding to the pose + * between the camera frame and the object frame. + * + * The features parameters in the camera frame (cP) are updated in + * the vpTracker::cP public attribute. + */ + virtual void changeFrame(const vpHomogeneousMatrix &cMo) = 0; + + /*! + * Displays the feature in the image \e I thanks to the 2D feature + * parameters in the image plane (vpTracker::p) and the camera + * parameters which enable to convert the features from meter to pixel. + * + * \param I : The image where the feature must be displayed in overlay. + * + * \param cam : The camera parameters to enable the conversion from + * meter to pixel. + * + * \param color : The desired color to display the line in the image. + * \param thickness : Thickness of the feature representation. + */ + virtual void display(const vpImage &I, const vpCameraParameters &cam, + const vpColor &color = vpColor::green, unsigned int thickness = 1) = 0; + + /*! + * Displays the feature in the image \e I thanks to the features in + * the object frame (vpForwardProjection::oP), the homogeneous matrix + * relative to the pose between the object frame and the camera frame and the + * camera parameters which enable to convert the features from meter + * to pixel. + * + * \param I : The image where the line must be displayed in overlay. + * + * \param cMo : The homogeneous matrix corresponding to the pose + * between the camera frame and the object frame. + * + * \param cam : The camera parameters to enable the conversion from + * meter to pixel. + * + * \param color : The desired color to display the line in the image. + * \param thickness : Thickness of the feature representation. + */ + virtual void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &color = vpColor::green, unsigned int thickness = 1) = 0; + + /*! + * Create an object with the same type. + */ + virtual vpForwardProjection *duplicate() const = 0; + + //! Return object parameters expressed in the 3D object frame. + vpColVector get_oP() const { return oP; } + + vpForwardProjectionDeallocatorType getDeallocate() { return deallocate; } + + virtual void print() const; + + /*! + * Computes the feature parameters in the image plane from the + * parameters expressed in the camera frame. + * + * \param cP [input] : Feature parameters expressed in the camera frame. + * + * \param p [output] : Feature parameters expressed in the image plane. + */ + virtual void projection(const vpColVector &cP, vpColVector &p) const = 0; + + /*! + * Computes the feature parameters in the image plane. These + * parameters are than updated in the vpTracker::p public attribute. + * + * \warning To compute these parameters, the method exploit the + * feature parameters in the camera frame. Thus, vpTracker::cP need + * to be updated before the call of this method. For that, a call to + * changeFrame(const vpHomogeneousMatrix &) is requested. + */ + virtual void projection() = 0; + + void project(); + void project(const vpHomogeneousMatrix &cMo); + + void setDeallocate(vpForwardProjectionDeallocatorType d) { deallocate = d; } + + /*! + * Sets the parameters which define the feature in the object frame. + * + * \param oP : Feature parameters expressed in the object frame used + * to set the vpForwardProjection::oP public attribute. + */ + virtual void setWorldCoordinates(const vpColVector &oP) = 0; + + void track(const vpHomogeneousMatrix &cMo); + //@} + + +public: + /** @name Public Attributes Inherited from vpForwardProjection */ + //@{ + /*! + * Feature coordinates expressed in the object frame. + */ + vpColVector oP; + //@} + +protected: + /** @name Protected Member Functions Inherited from vpForwardProjection */ + //@{ + /*! + * Default initialisation of the feature parameters: + * - in the object frame: \e oP + * - in the camera frame: \e cP + * - in the image plane: \e p. + */ + virtual void init() = 0; + //@} + +private: + vpForwardProjectionDeallocatorType deallocate; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpFrameGrabber.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpFrameGrabber.h new file mode 100644 index 00000000..07a47cec --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpFrameGrabber.h @@ -0,0 +1,143 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Frame grabbing. + */ + +/*! + * \file vpFrameGrabber.h + * \brief Base class for all video devices. It is + * designed to provide a generic front end to video sources. + */ + +#ifndef VP_FRAME_GRABBER_H +#define VP_FRAME_GRABBER_H + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpFrameGrabber + * + * \brief Base class for all video devices. It is designed to provide a front + * end to video sources. + * + * This class should provide a virtual function that allows the acquisition + * of an image. + * + * The example below shows how to use this class. + * \code + * #include + * #include + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * #if defined(VISP_HAVE_V4L2) || defined(VISP_HAVE_DC1394) + * vpImage I; + * vpFrameGrabber *g; // Generic framegrabber + * + * #if defined( VISP_HAVE_DC1394 ) + * vp1394TwoGrabber *g_1394_2 = new vp1394TwoGrabber; + * // specific settings for firewire grabber based on libdc1394-2.x version + * g_1394_2->setVideoMode(vp1394TwoGrabber::vpVIDEO_MODE_320x240_YUV422); + * g_1394_2->setFramerate(vp1394TwoGrabber::vpFRAMERATE_30); + * g = g_1394_2; + * #elif defined( VISP_HAVE_V4L2 ) + * vpV4l2Grabber *g_v4l2 = new vpV4l2Grabber; + * // specific settings for Video For Linux Two grabber + * g_v4l2->setInput(2); // Input 2 on the board + * g_v4l2->setFramerate(vpV4l2Grabber::framerate_50fps); // 50 fps + * g_v4l2->setWidth(384); // Acquired images are 768 width + * g_v4l2->setHeight(288); // Acquired images are 576 height + * g_v4l2->setNBuffers(3); // 3 ring buffers to ensure real-time acquisition + * g = g_v4l2; + * #endif + * + * g->open(I); // Open the framegrabber + * g->acquire(I); // Acquire an image + * vpImageIo::write(I, "image.pgm"); // Write image on the disk + * #endif + * } + * \endcode +*/ +class VISP_EXPORT vpFrameGrabber +{ +public: + bool init; //!< Set to true if the frame grabber has been initialized. + +public: + /** @name Inherited functionalities from vpFramegrabber */ + //@{ + //! Return the number of rows in the image. + unsigned int getHeight() const; + //! Return the number of columns in the image. + unsigned int getWidth() const; + //@} + +public: + vpFrameGrabber() : init(false), height(0), width(0) { } + +#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98) + vpFrameGrabber(const vpFrameGrabber &) = default; + virtual ~vpFrameGrabber() = default; +#else + virtual ~vpFrameGrabber() { } +#endif + virtual void open(vpImage &I) = 0; + virtual void open(vpImage &I) = 0; + + virtual void acquire(vpImage &I) = 0; + virtual void acquire(vpImage &I) = 0; + + /*! + * This virtual function is used to de-allocate + * the memory used by a specific frame grabber + */ + virtual void close() = 0; + +#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98) + vpFrameGrabber &operator=(const vpFrameGrabber &) = default; +#endif + +protected: + unsigned int height; //!< Number of rows in the image. + unsigned int width; //!< Number of columns in the image. +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpFrameGrabberException.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpFrameGrabberException.h new file mode 100644 index 00000000..f1b0ca88 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpFrameGrabberException.h @@ -0,0 +1,92 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Exceptions that can be emitted by the vpFrameGrabber class and its + * derivates. + */ + +/*! + * \file vpFrameGrabberException.h + * \brief error that can be emitted by the vpFrameGrabber class and its + * derivates + */ + +#ifndef VP_FRAMEGRABBER_EXCEPTION_H +#define VP_FRAMEGRABBER_EXCEPTION_H + +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \brief Error that can be emitted by the vpFrameGrabber class and its + * derivates. +*/ +class VISP_EXPORT vpFrameGrabberException : public vpException +{ +public: + /*! + * Lists the possible error than can be emitted while calling + * vpFrameGrabber member + */ + enum errorFrameGrabberCodeEnum + { + settingError, //!< Grabber settings error + initializationError, //!< Grabber initialization error + otherError //!< Grabber returned an other error + }; + +public: + /*! + * Constructor. + */ + vpFrameGrabberException(int id, const char *format, ...) + { + this->code = id; + va_list args; + va_start(args, format); + setMessage(format, args); + va_end(args); + } + + /*! + * Constructor. + */ + vpFrameGrabberException(int id, const std::string &msg) : vpException(id, msg) { } + + /*! + * Constructor. + */ + VP_EXPLICIT vpFrameGrabberException(int id) : vpException(id) { } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpGEMM.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpGEMM.h new file mode 100644 index 00000000..0f95e960 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpGEMM.h @@ -0,0 +1,448 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Matrix generalized multiplication. + */ + +#ifndef VP_GEMM_H +#define VP_GEMM_H + +#include +#include + +BEGIN_VISP_NAMESPACE +VP_ATTRIBUTE_NO_DESTROY const vpArray2D null(0, 0); + +/*! + Enumeration of the operations applied on matrices in vpGEMM() function. + + Operations are : + - VP_GEMM_A_T to use the transpose matrix of A instead of the matrix A + - VP_GEMM_B_T to use the transpose matrix of B instead of the matrix B + - VP_GEMM_C_T to use the transpose matrix of C instead of the matrix C + + \relates vpArray2D + */ +typedef enum +{ + VP_GEMM_A_T = 1, //! Use A^T instead of A + VP_GEMM_B_T = 2, //! Use B^T instead of B + VP_GEMM_C_T = 4, //! Use C^T instead of C +} vpGEMMmethod; + +template +inline void GEMMsize(const vpArray2D & /*A*/, const vpArray2D & /*B*/, unsigned int & /*Arows*/, + unsigned int & /*Acols*/, unsigned int & /*Brows*/, unsigned int & /*Bcols*/) +{ } + +template <> +void inline GEMMsize<0>(const vpArray2D &A, const vpArray2D &B, unsigned int &Arows, + unsigned int &Acols, unsigned int &Brows, unsigned int &Bcols) +{ + Arows = A.getRows(); + Acols = A.getCols(); + Brows = B.getRows(); + Bcols = B.getCols(); +} + +template <> +inline void GEMMsize<1>(const vpArray2D &A, const vpArray2D &B, unsigned int &Arows, + unsigned int &Acols, unsigned int &Brows, unsigned int &Bcols) +{ + Arows = A.getCols(); + Acols = A.getRows(); + Brows = B.getRows(); + Bcols = B.getCols(); +} +template <> +inline void GEMMsize<2>(const vpArray2D &A, const vpArray2D &B, unsigned int &Arows, + unsigned int &Acols, unsigned int &Brows, unsigned int &Bcols) +{ + Arows = A.getRows(); + Acols = A.getCols(); + Brows = B.getCols(); + Bcols = B.getRows(); +} +template <> +inline void GEMMsize<3>(const vpArray2D &A, const vpArray2D &B, unsigned int &Arows, + unsigned int &Acols, unsigned int &Brows, unsigned int &Bcols) +{ + Arows = A.getCols(); + Acols = A.getRows(); + Brows = B.getCols(); + Bcols = B.getRows(); +} + +template <> +inline void GEMMsize<4>(const vpArray2D &A, const vpArray2D &B, unsigned int &Arows, + unsigned int &Acols, unsigned int &Brows, unsigned int &Bcols) +{ + Arows = A.getRows(); + Acols = A.getCols(); + Brows = B.getRows(); + Bcols = B.getCols(); +} + +template <> +inline void GEMMsize<5>(const vpArray2D &A, const vpArray2D &B, unsigned int &Arows, + unsigned int &Acols, unsigned int &Brows, unsigned int &Bcols) +{ + Arows = A.getCols(); + Acols = A.getRows(); + Brows = B.getRows(); + Bcols = B.getCols(); +} + +template <> +inline void GEMMsize<6>(const vpArray2D &A, const vpArray2D &B, unsigned int &Arows, + unsigned int &Acols, unsigned int &Brows, unsigned int &Bcols) +{ + Arows = A.getRows(); + Acols = A.getCols(); + Brows = B.getCols(); + Bcols = B.getRows(); +} + +template <> +inline void GEMMsize<7>(const vpArray2D &A, const vpArray2D &B, unsigned int &Arows, + unsigned int &Acols, unsigned int &Brows, unsigned int &Bcols) +{ + Arows = A.getCols(); + Acols = A.getRows(); + Brows = B.getCols(); + Bcols = B.getRows(); +} + +template +inline void GEMM1(const unsigned int & /*Arows*/, const unsigned int & /*Brows*/, const unsigned int & /*Bcols*/, + const vpArray2D & /*A*/, const vpArray2D & /*B*/, const double & /*alpha*/, + vpArray2D & /*D*/) +{ } + +template <> +inline void GEMM1<0>(const unsigned int &Arows, const unsigned int &Brows, const unsigned int &Bcols, + const vpArray2D &A, const vpArray2D &B, const double &alpha, vpArray2D &D) +{ + for (unsigned int r = 0; r < Arows; ++r) { + for (unsigned int c = 0; c < Bcols; ++c) { + double sum = 0; + for (unsigned int n = 0; n < Brows; ++n) { + sum += A[r][n] * B[n][c] * alpha; + } + D[r][c] = sum; + } + } +} + +template <> +inline void GEMM1<1>(const unsigned int &Arows, const unsigned int &Brows, const unsigned int &Bcols, + const vpArray2D &A, const vpArray2D &B, const double &alpha, vpArray2D &D) +{ + for (unsigned int r = 0; r < Arows; ++r) { + for (unsigned int c = 0; c < Bcols; ++c) { + double sum = 0; + for (unsigned int n = 0; n < Brows; ++n) { + sum += A[n][r] * B[n][c] * alpha; + } + D[r][c] = sum; + } + } +} + +template <> +inline void GEMM1<2>(const unsigned int &Arows, const unsigned int &Brows, const unsigned int &Bcols, + const vpArray2D &A, const vpArray2D &B, const double &alpha, vpArray2D &D) +{ + for (unsigned int r = 0; r < Arows; ++r) { + for (unsigned int c = 0; c < Bcols; ++c) { + double sum = 0; + for (unsigned int n = 0; n < Brows; ++n) { + sum += A[r][n] * B[c][n] * alpha; + } + D[r][c] = sum; + } + } +} + +template <> +inline void GEMM1<3>(const unsigned int &Arows, const unsigned int &Brows, const unsigned int &Bcols, + const vpArray2D &A, const vpArray2D &B, const double &alpha, vpArray2D &D) +{ + for (unsigned int r = 0; r < Arows; ++r) { + for (unsigned int c = 0; c < Bcols; ++c) { + double sum = 0; + for (unsigned int n = 0; n < Brows; ++n) { + sum += A[n][r] * B[c][n] * alpha; + } + D[r][c] = sum; + } + } +} + +template +inline void GEMM2(const unsigned int & /*Arows*/, const unsigned int & /*Brows*/, const unsigned int & /*Bcols*/, + const vpArray2D & /*A*/, const vpArray2D & /*B*/, const double & /*alpha*/, + const vpArray2D & /*C*/, const double & /*beta*/, vpArray2D & /*D*/) +{ } + +template <> +inline void GEMM2<0>(const unsigned int &Arows, const unsigned int &Brows, const unsigned int &Bcols, + const vpArray2D &A, const vpArray2D &B, const double &alpha, + const vpArray2D &C, const double &beta, vpArray2D &D) +{ + for (unsigned int r = 0; r < Arows; ++r) { + for (unsigned int c = 0; c < Bcols; ++c) { + double sum = 0; + for (unsigned int n = 0; n < Brows; ++n) { + sum += A[r][n] * B[n][c] * alpha; + } + D[r][c] = sum + (C[r][c] * beta); + } + } +} + +template <> +inline void GEMM2<1>(const unsigned int &Arows, const unsigned int &Brows, const unsigned int &Bcols, + const vpArray2D &A, const vpArray2D &B, const double &alpha, + const vpArray2D &C, const double &beta, vpArray2D &D) +{ + for (unsigned int r = 0; r < Arows; ++r) { + for (unsigned int c = 0; c < Bcols; ++c) { + double sum = 0; + for (unsigned int n = 0; n < Brows; ++n) { + sum += A[n][r] * B[n][c] * alpha; + } + D[r][c] = sum + (C[r][c] * beta); + } + } +} + +template <> +inline void GEMM2<2>(const unsigned int &Arows, const unsigned int &Brows, const unsigned int &Bcols, + const vpArray2D &A, const vpArray2D &B, const double &alpha, + const vpArray2D &C, const double &beta, vpArray2D &D) +{ + for (unsigned int r = 0; r < Arows; ++r) { + for (unsigned int c = 0; c < Bcols; ++c) { + double sum = 0; + for (unsigned int n = 0; n < Brows; ++n) { + sum += A[r][n] * B[c][n] * alpha; + } + D[r][c] = sum + (C[r][c] * beta); + } + } +} + +template <> +inline void GEMM2<3>(const unsigned int &Arows, const unsigned int &Brows, const unsigned int &Bcols, + const vpArray2D &A, const vpArray2D &B, const double &alpha, + const vpArray2D &C, const double &beta, vpArray2D &D) +{ + for (unsigned int r = 0; r < Arows; ++r) { + for (unsigned int c = 0; c < Bcols; ++c) { + double sum = 0; + for (unsigned int n = 0; n < Brows; ++n) { + sum += A[n][r] * B[c][n] * alpha; + } + D[r][c] = sum + (C[r][c] * beta); + } + } +} + +template <> +inline void GEMM2<4>(const unsigned int &Arows, const unsigned int &Brows, const unsigned int &Bcols, + const vpArray2D &A, const vpArray2D &B, const double &alpha, + const vpArray2D &C, const double &beta, vpArray2D &D) +{ + for (unsigned int r = 0; r < Arows; ++r) { + for (unsigned int c = 0; c < Bcols; ++c) { + double sum = 0; + for (unsigned int n = 0; n < Brows; ++n) { + sum += A[r][n] * B[n][c] * alpha; + } + D[r][c] = sum + (C[c][r] * beta); + } + } +} + +template <> +inline void GEMM2<5>(const unsigned int &Arows, const unsigned int &Brows, const unsigned int &Bcols, + const vpArray2D &A, const vpArray2D &B, const double &alpha, + const vpArray2D &C, const double &beta, vpArray2D &D) +{ + for (unsigned int r = 0; r < Arows; ++r) { + for (unsigned int c = 0; c < Bcols; ++c) { + double sum = 0; + for (unsigned int n = 0; n < Brows; ++n) { + sum += A[n][r] * B[n][c] * alpha; + } + D[r][c] = sum + (C[c][r] * beta); + } + } +} + +template <> +inline void GEMM2<6>(const unsigned int &Arows, const unsigned int &Brows, const unsigned int &Bcols, + const vpArray2D &A, const vpArray2D &B, const double &alpha, + const vpArray2D &C, const double &beta, vpArray2D &D) +{ + for (unsigned int r = 0; r < Arows; ++r) { + for (unsigned int c = 0; c < Bcols; ++c) { + double sum = 0; + for (unsigned int n = 0; n < Brows; ++n) { + sum += A[r][n] * B[c][n] * alpha; + } + D[r][c] = sum + (C[c][r] * beta); + } + } +} + +template <> +inline void GEMM2<7>(const unsigned int &Arows, const unsigned int &Brows, const unsigned int &Bcols, + const vpArray2D &A, const vpArray2D &B, const double &alpha, + const vpArray2D &C, const double &beta, vpArray2D &D) +{ + for (unsigned int r = 0; r < Arows; ++r) { + for (unsigned int c = 0; c < Bcols; ++c) { + double sum = 0; + for (unsigned int n = 0; n < Brows; ++n) { + sum += A[n][r] * B[c][n] * alpha; + } + D[r][c] = sum + (C[c][r] * beta); + } + } +} + +template +inline void vpTGEMM(const vpArray2D &A, const vpArray2D &B, const double &alpha, + const vpArray2D &C, const double &beta, vpArray2D &D) +{ + unsigned int Arows; + unsigned int Acols; + unsigned int Brows; + unsigned int Bcols; + + GEMMsize(A, B, Arows, Acols, Brows, Bcols); + + try { + if ((Arows != D.getRows()) || (Bcols != D.getCols())) { + D.resize(Arows, Bcols); + } + } + catch (...) { + throw; + } + + if (Acols != Brows) { + throw(vpException(vpException::dimensionError, "In vpGEMM, cannot multiply (%dx%d) matrix by (%dx%d) matrix", Arows, + Acols, Brows, Bcols)); + } + + if (C.getRows() != 0 && C.getCols() != 0) { + if ((Arows != C.getRows()) || (Bcols != C.getCols())) { + throw(vpException(vpException::dimensionError, "In vpGEMM, cannot add resulting (%dx%d) matrix to (%dx%d) matrix", + Arows, Bcols, C.getRows(), C.getCols())); + } + + GEMM2(Arows, Brows, Bcols, A, B, alpha, C, beta, D); + } + else { + GEMM1(Arows, Brows, Bcols, A, B, alpha, D); + } +} + +/*! + This function performs generalized matrix multiplication: + D = alpha*op(A)*op(B) + beta*op(C), where op(X) is X or X^T. + Operation on A, B and C matrices is described by enumeration + vpGEMMmethod(). + + For example, to compute D = alpha*A^T*B^T+beta*C we need to call : + \code + vpGEMM(A, B, alpha, C, beta, D, VP_GEMM_A_T + VP_GEMM_B_T); + \endcode + + If C is not used, vpGEMM must be called using an empty array \e null. + Thus to compute D = alpha*A^T*B, we have to call: + \code + vpGEMM(A, B, alpha, null, 0, D, VP_GEMM_B_T); + \endcode + + \exception vpException::incorrectMatrixSizeError if the sizes of the + matrices do not allow the operations. + + \param A : An array that could be a vpMatrix. + \param B : An array that could be a vpMatrix. + \param alpha : A scalar. + \param C : An array that could be a vpMatrix. + \param beta : A scalar. + \param D : The resulting array that could be a vpMatrix. + \param ops : A scalar describing operation applied on the matrices. + Possible values are the one defined in vpGEMMmethod(): VP_GEMM_A_T, + VP_GEMM_B_T, VP_GEMM_C_T. + + \relates vpArray2D + +*/ +inline void vpGEMM(const vpArray2D &A, const vpArray2D &B, const double &alpha, + const vpArray2D &C, const double &beta, vpArray2D &D, const unsigned int &ops = 0) +{ + switch (ops) { + case 0: + vpTGEMM<0>(A, B, alpha, C, beta, D); + break; + case 1: + vpTGEMM<1>(A, B, alpha, C, beta, D); + break; + case 2: + vpTGEMM<2>(A, B, alpha, C, beta, D); + break; + case 3: + vpTGEMM<3>(A, B, alpha, C, beta, D); + break; + case 4: + vpTGEMM<4>(A, B, alpha, C, beta, D); + break; + case 5: + vpTGEMM<5>(A, B, alpha, C, beta, D); + break; + case 6: + vpTGEMM<6>(A, B, alpha, C, beta, D); + break; + case 7: + vpTGEMM<7>(A, B, alpha, C, beta, D); + break; + default: + throw(vpException(vpException::functionNotImplementedError, "Operation on vpGEMM not implemented")); + break; + } +} +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpGaussRand.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpGaussRand.h new file mode 100644 index 00000000..2b63d518 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpGaussRand.h @@ -0,0 +1,173 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Generation of random number with uniform and normal probability density. + */ + +#ifndef VP_GAUSSRAND_H +#define VP_GAUSSRAND_H + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpGaussRand + \ingroup group_core_random + \brief Class for generating random number with normal probability density. + + The algorithms and notations used are described in \cite Gentle:2004. + + The code below shows how to use the random generator to get values that have + their mean equal to 10 with a standard deviation equal to 0.5. + + \code + #include + #include + + int main() + { + vpGaussRand noise(0.5, 10); + for(int i=0; i< 10; i++) { + std::cout << "noise " << i << ": " << noise() << std::endl; + } + return 0; + } + \endcode + + The previous example produces the following printings: + \verbatim + noise 0: 10.645 + noise 1: 9.67129 + noise 2: 10.1208 + noise 3: 10.1039 + noise 4: 10.8667 + noise 5: 9.89823 + noise 6: 9.81414 + noise 7: 9.96076 + noise 8: 11.0795 + noise 9: 9.79229 + \endverbatim + + Note that the previous example produces always the same "random" results. To + produce real random values, you need to initialize the random generator with + different values using seed(). For example, this could be done using the + current time. The code becomes: + + \verbatim + #include + #include + #include + + int main() + { + vpGaussRand noise(0.5, 10); + long seed = (long)vpTime::measureTimeMs(); + + noise.seed(seed); + for(int i=0; i< 10; i++) { + std::cout << "noise " << i << ": " << noise() << std::endl; + } + return 0; + } + \endverbatim + + Now if you run the previous example you will always get different values: + \verbatim + noise 0: 10.5982 + noise 1: 9.19111 + noise 2: 9.82498 + noise 3: 9.07857 + noise 4: 9.9285 + noise 5: 10.3688 + noise 6: 9.75621 + noise 7: 10.3259 + noise 8: 10.4238 + noise 9: 10.2391 + \endverbatim +*/ +class VISP_EXPORT vpGaussRand +{ +public: + /*! + Default noise generator constructor. + */ + vpGaussRand() : m_rng(), m_mean(0), m_sigma(0), m_AlreadyDone(false), m_x2(0) { } + + /*! + Gaussian noise random generator constructor. + + \param sigma_val : Standard deviation. + \param mean_val : Mean value. + \param noise_seed : Seed of the noise + */ + vpGaussRand(double sigma_val, double mean_val, uint64_t noise_seed = 0) + : m_rng(noise_seed), m_mean(mean_val), m_sigma(sigma_val), m_AlreadyDone(false), m_x2(0) + { } + + /*! + Set the standard deviation and mean for gaussian noise. + + \param sigma_val : New standard deviation sigma. + \param mean_val : New mean value. + */ + void setSigmaMean(double sigma_val, double mean_val) + { + m_mean = mean_val; + m_sigma = sigma_val; + } + + /*! + Set the seed of the noise. + + \param seed_val : New seed. + */ + void seed(uint64_t seed_val) + { + const uint64_t val_ull = 0x123465789ULL; + m_rng.setSeed(seed_val, val_ull); + } + + /*! + Return a random value from the Gaussian noise generator. + */ + double operator()() { return (m_sigma * gaussianDraw()) + m_mean; } + +private: + double gaussianDraw(); + + vpUniRand m_rng; + double m_mean; + double m_sigma; + bool m_AlreadyDone; + double m_x2; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpGaussianFilter.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpGaussianFilter.h new file mode 100644 index 00000000..04396430 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpGaussianFilter.h @@ -0,0 +1,76 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Gaussian filter class. + */ + +/*! + \file vpGaussianFilter.h + \brief Gaussian filter class +*/ + +#ifndef VP_GAUSSIAN_FILTER_H +#define VP_GAUSSIAN_FILTER_H + +#include +#include + +#if defined(VISP_HAVE_SIMDLIB) + +BEGIN_VISP_NAMESPACE +/*! + \class vpGaussianFilter + + \ingroup group_core_image + + \brief Gaussian filter class +*/ +class VISP_EXPORT vpGaussianFilter +{ +public: + vpGaussianFilter(unsigned int width, unsigned int height, float sigma, bool deinterleave = false); + + void apply(const vpImage &I, vpImage &I_blur); + + void apply(const vpImage &I, vpImage &I_blur); + + virtual ~vpGaussianFilter(); + +private: + vpGaussianFilter(const vpGaussianFilter &gf); + vpGaussianFilter &operator=(const vpGaussianFilter &gf); + + // PIMPL idiom + class Impl; + Impl *m_impl; +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpHSV.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpHSV.h new file mode 100644 index 00000000..86d3a971 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpHSV.h @@ -0,0 +1,655 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * HSV color scale. + */ + +#ifndef VP_HSV_H +#define VP_HSV_H + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) +#include + +BEGIN_VISP_NAMESPACE +template +class vpHSV; + +template +std::ostream &operator<<(std::ostream &os, const vpHSV &hsv); + +#ifndef VISP_PYTHON_PREPROCESSOR_RUNNING +namespace +{ +/** + * \brief Structure that gives the variance of the different channels of a HSV pixel assuming that + * each channel follows a continuous uniform law defined on the intervall [0.; 1.]. + * + * \tparam T : The type used to encode the channels. + * \tparam otherUseFullScale : Unused. + * \tparam EnableIf : Enable this structure only for floating point types. + */ +template +struct UniformLawVariance; + +/** + * \brief Structure that gives the variance of the different channels of a HSV pixel assuming that + * each channel follows a continuous uniform law defined on the intervall [0.; 1.]. + * + * \tparam T : The type used to encode the channels. + * \tparam useFullScale : Unused. + * \tparam std::enable_if::value, true>::type : Enable this structure only for floating point types. + */ +template +struct UniformLawVariance::value>::type> +{ + // Variance of a continuous uniform law defined on the intervall [a; b] = (b - a)^2 / 12 + // Here, a = 0, b = 1 + static constexpr float hueVariance = 1.f/12.f; + static constexpr float otherChannelsVariance = 1.f/12.f; +}; + +/** + * \brief Structure that gives the variance of the different channels of a HSV pixel assuming that + * each channel follows a discrete uniform law defined on the intervall {0; 1; ...; 255}. + */ +template <> +struct UniformLawVariance +{ + // Variance of a discrete uniform law defined on the intervall {a; a + 1; ... b} = ((b - a + 1)^2 - 1)/ 12 + // Here, a = 0, b = 255 + static constexpr float hueVariance = (256.f * 256.f - 1.f)/12.f; + static constexpr float otherChannelsVariance = (256.f * 256.f - 1.f)/12.f; +}; + +/** + * \brief Structure that gives the variance of the different channels of a HSV pixel assuming that + * each channel follows a discrete uniform law defined on the intervall {0; 1; ...; 255} for the Saturation and + * Value channels and {0; 1; ...; maxHueUsingLimitedRange} for the Hue channel. + */ +template <> +struct UniformLawVariance +{ + // Variance of a discrete uniform law defined on the intervall {a; a + 1; ... b} = ((b - a + 1)^2 - 1)/ 12 + // Here, a = 0, b = 255 for the Saturation and Value channels + // and {0; 1; ...; maxHueUsingLimitedRange} for the Hue channel. + static constexpr float hueVariance = (180.f * 180.f - 1.f)/12.f; + static constexpr float otherChannelsVariance = (256.f * 256.f - 1.f)/12.f; +}; +} +#endif + +/** + * \brief Class implementing the HSV pixel format. + * + * \tparam T The type of the channels. Either a floating point type (float, double) or unsigned char. + * \tparam useFullScale True if vpHSV uses unsigned char and the full range [0; 255], false if vpHSV uses unsigned char and the limited range [0; maxHueUsingLimitedRange]. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in how you can convert vpHSV to and from other type of data, or + * how to use it for color segmentation, you may have a look at:
+ + * - \ref tutorial-hsv-segmentation-intro + */ +template +class vpHSV +{ +public: + /** + * \brief Construct a new vpHSV object using floating point channels. + * + * \tparam U The type of the channels of the vpHSV pixels. + * \param[in] H_ The value of the Hue channel. + * \param[in] S_ The value of the Saturation channel. + * \param[in] V_ The value of the Value channel. + */ + explicit vpHSV(const double &H_ = 0., const double &S_ = 0., const double &V_ = 0.) + : H(static_cast(H_)) + , S(static_cast(S_)) + , V(static_cast(V_)) + { } + + /** + * \brief Construct a new vpHSV object from a vpColVector. + * + * \param[in] v The values must be in the range that corresponds to the type + * used to encode the channels. + */ + vpHSV(const vpColVector &v) + { + this->set(v); + } + + /** + * \brief Default copy constructor. + */ + vpHSV(const vpHSV &) = default; + +#ifndef VISP_PYTHON_PREPROCESSOR_RUNNING + /** + * \brief Construct a new vpHSV object using unsigned char channels and the full range [0; 255] from a vpHSV object + * whose channels are in floating point format. + * + * \tparam U The format of the constructed object. + * \tparam V The format of the base object. + * \tparam type Enable the method only if the constructed object uses unsigned char format and uses the full range + * [0; 255] and the object that is used as reference uses floating point format. + * \param[in] other A floating point format vpHSV. + */ + template::value &&std::is_floating_point::value &&useFullScale, U>::type = 0 > + vpHSV(const vpHSV &other) + { + buildFrom(other); + } + + /** + * \brief Construct a new vpHSV object using unsigned char channels and the limited range [0; maxHueUsingLimitedRange] + * from a vpHSV object whose channels are in floating point format. + * + * \tparam U The format of the constructed object. + * \tparam V The format of the base object. + * \tparam type Enable the method only if the constructed object uses unsigned char format and uses the limited range + * [0; maxHueUsingLimitedRange] and the object that is used as reference uses floating point format. + * \param[in] other A floating point format vpHSV. + */ + template::value &&std::is_floating_point::value && !useFullScale, U>::type = 0 > + vpHSV(const vpHSV &other) + { + buildFrom(other); + } + + /** + * \brief Construct a new floating point vpHSV object from an unsigned char vpHSV object. + * + * \tparam U The type of the channels of the constructed vpHSV pixels. + * \tparam otherUseFullScale True if the reference object uses unsigned char and the full range [0; 255], false if it + * uses unsigned char and the limited range [0; maxHueUsingLimitedRange]. + * \tparam type Enable the method only if the constructed object uses the floating point format for its channels. + * \param[in] other The reference object. + */ + template::value>::type...> + vpHSV(const vpHSV &other) + { + buildFrom(other); + } +#endif + + /** + * \brief Construct a new vpHSV object from a vpRGBa object. + * + * \param[in] rgba The reference vpRGBa object. + */ + vpHSV(const vpRGBa &rgba) + { + buildFrom(rgba); + } + + /** + * Default destructor. + */ +#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98) + virtual ~vpHSV() = default; +#else + virtual ~vpHSV() { } +#endif + + /** + * \brief Modify the object to be the result of the conversion of the vpRGBa object into HSV format. + * + * \param[in] rgba The vpRGBa object that serves as model/ + * \return Reference to the modified object. + */ + vpHSV &buildFrom(const vpRGBa &rgba); + + /** + * \brief Convert a floating point HSV into a unsigned char HSV using the full range [0; 255]. + * + * \tparam U The type of the channels of the vpHSV pixels that is modified. + * \tparam V The type of the channels of the vpHSV pixels that serves as reference. + * \tparam type Enable the method only if the modified object uses unsigned char and full range [0; 255] and the base + * object uses a floating point format. + * \param[in] other The floating point HSV. + * \return Reference to the modified object. + */ + template + typename std::enable_if::value &&std::is_floating_point::value &&useFullScale, vpHSV &>::type + buildFrom(const vpHSV &other) + { + H = static_cast(other.H * 255.); + S = static_cast(other.S * 255.); + this->V = static_cast(other.V * 255.); + return *this; + } + + /** + * \brief Convert a floating point HSV into a unsigned char HSV using the limited range [0; maxHueUsingLimitedRange]. + * + * \tparam U The type of the channels of the vpHSV pixels that is modified. + * \tparam V The type of the channels of the vpHSV pixels that serves as reference. + * \tparam type Enable the method only if the modified object uses unsigned char and limited range + * [0; maxHueUsingLimitedRange] and the base object uses a floating point format. + * \param[in] other The floating point HSV. + * \return Reference to the modified object. + */ + template + typename std::enable_if::value &&std::is_floating_point::value && !useFullScale, vpHSV &>::type + buildFrom(const vpHSV &other) + { + H = static_cast(other.H * static_cast(maxHueUsingLimitedRange)); + S = static_cast(other.S * 255.); + this->V = static_cast(other.V * 255.); + return *this; + } + + /** + * \brief Convert a vpHSV that uses unsigned char for its channels into a vpHSV that uses floating point for its channels. + * + * \tparam U The type of the channels of the vpHSV pixels. + * \tparam otherUseFullScale + * \tparam type Enable the method only if the modified object uses + * floating point format. + * \param[in] other The unsigned char vpHSV. + * \return Reference to the modified object. + */ + template + typename std::enable_if::value, vpHSV &>::type + buildFrom(const vpHSV &other) + { + if (otherUseFullScale) { + H = static_cast(other.H) / static_cast(255.); + } + else { + H = static_cast(other.H) / static_cast(maxHueUsingLimitedRange); + } + S = static_cast(other.S) / static_cast(255.); + V = static_cast(other.V) / static_cast(255.); + return *this; + } + + /** + * \brief Convert a floating point HSV into another floating point type HSV. + * + * \tparam U The type of the channels of the vpHSV pixels that is modified. + * \tparam V The type of the channels of the vpHSV pixels that serves as reference. + * \tparam otherUseFullScale To avoid problem if one was created with true and the other false (even it is not used for + * floating point types). + * \tparam type Enable the method only if the modified object uses is a floating point format, the base object too + * but the formats are different. The type "int" is not used, it is here only because float and doubles are + * "not [a] valid type for a template non-type parameter" + * \param[in] other The floating point HSV. + * \return vpHSV& Reference to the modified object. + */ + template + typename std::enable_if::value &&std::is_floating_point::value && !std::is_same::value, vpHSV &>::type + buildFrom(const vpHSV &other) + { + H = static_cast(other.H); + S = static_cast(other.S); + this->V = static_cast(other.V); + return *this; + } + + /** + * \brief Compute the normalized HSV values (i.e. in the range [0; 1]) that correspond to a vpRGBa object. + * + * \param[in] rgba The RGB pixel. + * \return vpColVector Vector of normalized HSV values. + */ + static vpColVector computeNormalizedHSV(const vpRGBa &rgba) + { + double red, green, blue; + double h, s, v; + double min, max; + + red = rgba.R / 255.0; + green = rgba.G / 255.0; + blue = rgba.B / 255.0; + + if (red > green) { + max = std::max(red, blue); + min = std::min(green, blue); + } + else { + max = std::max(green, blue); + min = std::min(red, blue); + } + + v = max; + + if (!vpMath::equal(max, 0.0, std::numeric_limits::epsilon())) { + s = (max - min) / max; + } + else { + s = 0.0; + } + + if (vpMath::equal(s, 0.0, std::numeric_limits::epsilon())) { + h = 0.0; + } + else { + double delta = max - min; + + if (vpMath::equal(red, max, std::numeric_limits::epsilon())) { + h = (green - blue) / delta; + } + else if (vpMath::equal(green, max, std::numeric_limits::epsilon())) { + h = 2.0 + ((blue - red) / delta); + } + else { + h = 4.0 + ((red - green) / delta); + } + + h /= 6.0; + if (h < 0.0) { + h += 1.0; + } + else if (h > 1.0) { + h -= 1.0; + } + } + + vpColVector hsv(3); + hsv[0] = h; + hsv[1] = s; + hsv[2] = v; + return hsv; + } + + /** + * \brief Compute the square of the Mahalanobis distance between two HSV pixels. + * It is assumed that the channels are independent and follow a uniform distribution law. + * + * \param[in] a The first pixel to compare. + * \param[in] b The second pixel to compare. + * \param[out] diff The vector (b - a). + * \return float The squared Mahalanobis distance between a and b. + */ + template + inline static ArithmeticType squaredMahalanobisDistance(const vpHSV &a, const vpHSV &b, vpColVector &diff) + { + static const ArithmeticType invHueVariance = 1.f / UniformLawVariance::hueVariance; + static const ArithmeticType invOtherChannelsVariance = 1.f / UniformLawVariance::otherChannelsVariance; + diff.resize(3); + diff[0] = b.H - a.H; + diff[1] = b.S - a.S; + diff[2] = b.V - a.V; + ArithmeticType distance = diff[0] * diff[0] * invHueVariance + invOtherChannelsVariance * (diff[1] * diff[1] + diff[2] * diff[2]); + return distance; + } + + /** + * \brief Compute the square of the Mahalanobis distance between two HSV pixels. + * It is assumed that the channels are independent and follow a uniform distribution law. + * + * \param[in] a The first pixel to compare. + * \param[in] b The second pixel to compare. + * \return float The squared Mahalanobis distance between a and b. + */ + template + inline static ArithmeticType squaredMahalanobisDistance(const vpHSV &a, const vpHSV &b) + { + vpColVector diff; + return squaredMahalanobisDistance(a, b, diff); + } + + /** + * \brief Compute the Mahalanobis distance between two HSV pixels. + * It is assumed that the channels are independent and follow a uniform distribution law. + * + * \param[in] a The first pixel to compare. + * \param[in] b The second pixel to compare. + * \return float The Mahalanobis distance between a and b. + */ + template + inline static ArithmeticType mahalanobisDistance(const vpHSV &a, const vpHSV &b) + { + return std::sqrt(squaredMahalanobisDistance(a, b)); + } + + /** + * \brief Compute the Mahalanobis distance between two HSV pixels. + * It is assumed that the channels are independent and follow a uniform distribution law. + * + * \param[in] a The first pixel to compare. + * \param[in] b The second pixel to compare. + * \param[out] diff The vector (b - a). + * \return float The Mahalanobis distance between a and b. + */ + template + inline static ArithmeticType mahalanobisDistance(const vpHSV &a, const vpHSV &b, vpColVector &diff) + { + return std::sqrt(squaredMahalanobisDistance(a, b, diff)); + } + + // Operators + vpHSV &operator=(vpHSV &&) = default; + vpHSV &operator=(const vpHSV &) = default; + + vpHSV &operator=(const vpColVector &v) + { + vpHSV vAsHSV(v); + *this = vAsHSV; + return *this; + } + + + bool operator==(const vpHSV &v) const + { + return(vpMath::equal(v.H, H, 1e-6) && vpMath::equal(v.S, S, 1e-6) && vpMath::equal(v.V, V, 1e-6)); + } + + bool operator!=(const vpHSV &v) const + { + return !(*this == v); + } + + vpColVector operator-(const vpHSV &v) const + { + return this->toColVector() - v.toColVector(); + } + + vpHSV operator+(const vpHSV &v) const + { + vpHSV result; + result.H = H + v.H; + result.S = S + v.S; + result.V = V + v.V; + return result; + } + + vpColVector operator-(const vpColVector &v) const + { + return this->toColVector() - v; + } + + vpColVector operator+(const vpColVector &v) const + { + vpColVector result(3); + result[0] = H + v[0]; + result[1] = S + v[1]; + result[2] = V + v[2]; + return result; + } + + /** + * \brief Cast a vpHSV into a vpColVector. + * + * \return vpColVector + */ + vpColVector toColVector() const + { + vpColVector color(3); + color[0] = H; + color[1] = S; + color[2] = V; + return color; + } + + /** + * \brief Cast a vpHSV into a string, for display purpose. + * + * \return std::string + */ + virtual std::string toString() const; + + friend std::ostream &operator<< <>(std::ostream &os, const vpHSV &hsv); + +public: + T H; /*!< The Hue channel.*/ + T S; /*!< The Saturation channel.*/ + T V; /*!< The Value channel.*/ + + /** + * \brief Number of channels a HSV pixel is made of. + */ + static constexpr unsigned char nbChannels = 3; + + /** + * \brief Maximum value of the Hue channel when using unsigned char and the limited range. + */ + static constexpr unsigned char maxHueUsingLimitedRange = 179; + +private: + /** + * \brief Permit to initialize a vpHSV object using a vector. + * + * \tparam Tp The type of the channels of the vpHSV pixels. + * \tparam VectorType The type of the vector. + * \param[in] v A vector whose size must be equal to 3. + * \param[in] limMin The lower limit of the acceptable range of values. + * \param[in] limMax The upper limit of the acceptable range of values. + */ + template + inline + typename std::enable_if::value, void>::type set(const vpColVector &v) + { + // const Tp limMin = 0.; + // const Tp limMax = 1.; + // if ((v[0] < limMin) || (v[0] > limMax)) { + // // throw exception + // } + H = v[0]; + // if ((v[1] < limMin) || (v[1] > limMax)) { + // // throw exception + // } + S = v[1]; + // if ((v[2] < limMin) || (v[2] > limMax)) { + // // throw exception + // } + V = v[2]; + } + + template + inline + typename std::enable_if::value, void>::type set(const vpColVector &v) + { + // const Tp otherMax = std::numeric_limits::max(); + // Tp hmax; + // if (useFullScale) { + // hmax = std::numeric_limits::max(); + // } + // else { + // hmax = maxHueUsingLimitedRange; + // } + + // if (v[0] > hmax) { + // // throw exception + // } + H = static_cast(v[0]); + + // if (v[1] > otherMax) { + // // throw exception + // } + S = static_cast(v[1]); + + // if (v[2] > otherMax) { + // // throw exception + // } + V = static_cast(v[2]); + } +}; + +template +std::string vpHSV::toString() const +{ + std::stringstream ss; + ss << "vpHSV<"; + using CastType = typename std::conditional::value, int, T>::type; + if (std::is_same::value) { + ss << "uchar"; + } + else if (std::is_same::value) { + ss << "double"; + } + else { + ss << "other"; + } + + if (useFullScale) { + std::cout << ", full"; + } + else { + std::cout << ", partial"; + } + ss << " scale> (H, S, V): (" << (CastType)H << " , " << (CastType)S << " , " << (CastType)V << ")"; + return ss.str(); +} + +template +std::ostream &operator<<(std::ostream &os, const vpHSV &hsv) +{ + os << hsv.toString(); + return os; +} + +template<> +VISP_EXPORT vpHSV &vpHSV::buildFrom(const vpRGBa &rgba); + +template<> +VISP_EXPORT vpHSV &vpHSV::buildFrom(const vpRGBa &rgba); + +template<> +VISP_EXPORT vpHSV &vpHSV::buildFrom(const vpRGBa &rgba); + +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpHinkley.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpHinkley.h new file mode 100644 index 00000000..377bf8df --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpHinkley.h @@ -0,0 +1,170 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Hinkley's cumulative sum test implementation. + */ + +/*! + \file vpHinkley.h + \brief class for Hinkley's cumulative test computation. +*/ + +#ifndef vpHinkley_H +#define vpHinkley_H + +#include + +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) + +BEGIN_VISP_NAMESPACE +/*! + \class vpHinkley + \warning This class is deprecated. You should rather use vpStatisticalTestHinkley. + + \ingroup group_core_math_tools + \brief This class implements the Hinkley's cumulative sum test. + + The Hinkley's cumulative sum test is designed to detect jump in mean + of an observed signal \f$ s(t) \f$. It is known to be robust (by + taking into account all the past of the observed quantity), + efficient, and inducing a very low computational load. The other + attractive features of this test are two-fold. First, it can + straightforwardly and accurately provide the jump instant. Secondly, + due to its formulation (cumulative sum test), it can simultaneously + handle both very abrupt and important changes, and gradual smaller + ones without adapting the involved thresholds. + + Two tests are performed in parallel to look for downwards or upwards + jumps in \f$ s(t) \f$, respectively defined by: + + \f[ S_k = \sum_{t=0}^{k} (s(t) - m_0 + \frac{\delta}{2}) \f] + \f[ M_k = \max_{0 \leq i \leq k} S_i\f] + \f[ T_k = \sum_{t=0}^{k} (s(t) - m_0 - \frac{\delta}{2}) \f] + \f[ N_k = \max_{0 \leq i \leq k} T_i\f] + + In which \f$m_o\f$ is computed on-line and corresponds to the mean + of the signal \f$ s(t) \f$ we want to detect a jump. \f$m_o\f$ is + re-initialized at zero after each jump detection. \f$\delta\f$ + denotes the jump minimal magnitude that we want to detect and + \f$\alpha\f$ is a predefined threshold. These values are set by + default to 0.2 in the default constructor vpHinkley(). To modify the + default values use setAlpha() and setDelta() or the + vpHinkley(double alpha, double delta) constructor. + + A downward jump is detected if \f$ M_k - S_k > \alpha \f$. + A upward jump is detected if \f$ T_k - N_k > \alpha \f$. + + To detect only downward jumps in \f$ s(t) \f$ use + testDownwardJump().To detect only upward jumps in \f$ s(t) \f$ use + testUpwardJump(). To detect both, downward and upward jumps use + testDownUpwardJump(). + + If a jump is detected, the jump location is given by the last instant + \f$k^{'}\f$ when \f$ M_{k^{'}} - S_{k^{'}} = 0 \f$, or \f$ T_{k^{'}} - + N_{k^{'}} = 0 \f$. + +*/ +class /* VP_DEPRECATED */ VISP_EXPORT vpHinkley +{ +public: + /*! \enum vpHinkleyJumpType + Indicates if a jump is detected by the Hinkley test. + */ + typedef enum + { + noJump, /*!< No jump is detected by the Hinkley test. */ + downwardJump, /*!< A downward jump is detected by the Hinkley test. */ + upwardJump /*!< An upward jump is detected by the Hinkley test. */ + } vpHinkleyJumpType; + +public: + VP_DEPRECATED vpHinkley(); + VP_DEPRECATED vpHinkley(double alpha, double delta); + + void init(); + void init(double alpha, double delta); + + void setDelta(double delta); + void setAlpha(double alpha); + vpHinkleyJumpType testDownwardJump(double signal); + vpHinkleyJumpType testUpwardJump(double signal); + vpHinkleyJumpType testDownUpwardJump(double signal); + + static void print(vpHinkleyJumpType jump); + + /*! + \return The mean value \f$m_0\f$ of the signal \f$ s(t) \f$. + + */ + inline double getMean() { return mean; } + /*! + \return The value of \f$S_k = \sum_{t=0}^{k} (s(t) - m_0 + + \frac{\delta}{2})\f$ + + */ + inline double getSk() { return Sk; } + /*! + \return The value of \f$M_k\f$, the maximum value of \f$S_k\f$. + + */ + inline double getMk() { return Mk; } + /*! + + \return The value of \f$T_k = \sum_{t=0}^{k} (s(t) - m_0 - + \frac{\delta}{2})\f$ + + */ + inline double getTk() { return Tk; } + /*! + \return The value of \f$N_k\f$, the maximum value of \f$T_k\f$. + + */ + inline double getNk() { return Nk; } + +private: + void computeMean(double signal); + void computeSk(double signal); + void computeMk(); + void computeTk(double signal); + void computeNk(); + +private: + double dmin2; + double alpha; + int nsignal; // Signal length + double mean; // Signal mean value + double Sk; + double Mk; + double Tk; + double Nk; +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpHistogram.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpHistogram.h new file mode 100644 index 00000000..7217c326 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpHistogram.h @@ -0,0 +1,574 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Gray level histogram manipulation. + */ + +/*! + \file vpHistogram.h + \brief Declaration of the vpHistogram class. + Class vpHistogram defines gray level image histograms + +*/ + +#ifndef VP_HISTOGRAM_H +#define VP_HISTOGRAM_H + +#include + +#include +#include +#include +#include +#include + +#include +#if defined(VISP_HAVE_THREADS) +#include +#endif + +BEGIN_VISP_NAMESPACE +/*! + \class vpHistogram + \ingroup group_core_histogram + \brief Class to compute a gray level image histogram. + + Here are two examples showing how to use this class to determine the + threshold which can be used to segment two objects. + + The code below: + + \code + vpImage I; + ... + unsigned char dist = 60; + vpHistogramValey valey; + vpHistogram h(I); + h.smooth(); // Filter the histogram values + vpHistogramPeak peakl, peakr; // Two highest peaks in the histogram + // - peakl: Peak on the left + // - peakr: Peak on the right + + h.getPeaks(dist, peakl, peakr, valey); + unsigned char threshold; // Position of the valey between the two peaks + threshold = valey.getLevel(); + \endcode + + has the same behaviour than this one: + + \code + vpImage I; + ... + unsigned char dist = 60; + vpHistogram h(I); + h.smooth(); // Filter the histogram values + vpHistogramPeak peak1, peak2; // Two highest peaks in the histogram + // - peak1: Highest peak + // - peakr: Second highest peak + + // Get the two highest peaks + h.getPeaks(dist, peak1, peak2); + + // Get the valey between the two highest peaks + vpHistogramValey valey; + h.getValey(peak1, peak2, valey); + + unsigned char threshold; // Position of the valey between the two peaks + threshold = valey.getLevel(); + \endcode + +*/ +class VISP_EXPORT vpHistogram +{ +public: + vpHistogram(const unsigned int &size = 256); + vpHistogram(const vpHistogram &h); + VP_EXPLICIT vpHistogram(const vpImage &I); + VP_EXPLICIT vpHistogram(const vpImage &I, const vpImage *p_mask); + virtual ~vpHistogram(); + + vpHistogram &operator=(const vpHistogram &h); + + /*! + + Return the number of pixels having the gray \e level. + + \param level : Gray level in the histogram. + + \return Number of pixels having the gray level. + + \code + vpImage I; // A gray level image + + vpHistogram h; + h.calculate(I); // Histogram of the gray level image + + // Print the histogram values + for (int i=0; i < h.getSize(); ++i) + printf("%d: %d\n", i, h[i]); + \endcode + + */ + inline unsigned operator[](const unsigned char level) const + { + if (level < m_size) { + return m_histogram[level]; + } + + std::stringstream ss; + ss << "Level is > to size (" << m_size << ") !"; + throw vpException(vpException::dimensionError, ss.str().c_str()); + } + /*! + + Return the number of pixels having the gray \e level. + + \param level : Gray level in the histogram. + + \return Number of pixels having the gray level. + + \code + vpImage I; // A gray level image + + vpHistogram h; + h.calculate(I); // Histogram of the gray level image + + // Print the histogram values + for (int i=0; i < h.getSize(); ++i) + printf("%d: %d\n", i, h(i)); + \endcode + + */ + inline unsigned operator()(const unsigned char level) const + { + if (level < m_size) { + return m_histogram[level]; + } + + std::stringstream ss; + ss << "Level is > to size (" << m_size << ") !"; + throw vpException(vpException::dimensionError, ss.str().c_str()); + } + /*! + + Return the number of pixels having the gray \e level. + + \param level : Gray level in the histogram. + + \return Number of pixels having the gray level. + + \code + vpImage I; // A gray level image + + vpHistogram h; + h.calculate(I); // Histogram of the gray level image + + // Print the histogram values + for (int i=0; i < h.getSize(); ++i) + printf("%d: %d\n", i, h.get(i)); + \endcode + + */ + inline unsigned get(const unsigned char level) const + { + if (level < m_size) { + return m_histogram[level]; + } + + std::stringstream ss; + ss << "Level is > to size (" << m_size << ") !"; + throw vpException(vpException::dimensionError, ss.str().c_str()); + } + + /*! + + Set the number of pixels having the gray \e level. + + \param level : Gray level in the histogram. Level is in [0:255] + + \param value : Number of pixels having the gray level. + + \code + vpHistogram h; + + // Set histogram values + for (int i=0; i < h.getSize(); ++i) + h.set(i, i*2); // for each level i, set a value of 2*i + \endcode + + */ + inline void set(const unsigned char level, unsigned int value) + { + if (level < m_size) { + m_histogram[level] = value; + } + else { + std::stringstream ss; + ss << "Level is > to size (" << m_size << ") !"; + throw vpException(vpException::dimensionError, ss.str().c_str()); + } + } + + /** + * \brief Set a mask to ignore pixels for which the mask is false. + * + * \warning The mask must be reset manually by the user (either for another mask + * or set to \b nullptr ) before computing the histogram of another image. + * + * @param p_mask If different of \b nullptr , a mask of booleans where \b true + * indicates that a pixel must be considered and \b false that the pixel should + * be ignored. + */ + inline void setMask(const vpImage *p_mask) + { + mp_mask = p_mask; + } + + void calculate(const vpImage &I, unsigned int nbins = 256, unsigned int nbThreads = 1); + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + template + typename std::enable_if::value, void>::type calculate(const vpImage &I, const ArithmeticType &minVal, const ArithmeticType &maxVal, ArithmeticType &widthBin, unsigned int nbins = 256, unsigned int nbThreads = 1) + { + widthBin = (maxVal - minVal)/static_cast(nbins); + if (m_size < nbins) { + init(nbins); + } + + memset(m_histogram, 0, m_size * sizeof(unsigned int)); + + bool use_single_thread; +#if !defined(VISP_HAVE_THREADS) + use_single_thread = true; +#else + use_single_thread = (nbThreads == 0 || nbThreads == 1); +#endif + + if ((!use_single_thread) && (I.getSize() <= nbThreads)) { + use_single_thread = true; + } + + if (use_single_thread) { + // Single thread + const bool alwaysTrue = true; + const bool *ptrMaskCurrent = &alwaysTrue; + if (mp_mask) { + ptrMaskCurrent = static_cast(mp_mask->bitmap); + } + + unsigned int size_ = I.getWidth() * I.getHeight(); + unsigned int idCurrent = 0; + + m_total = 0; + while (idCurrent < size_) { + if (*ptrMaskCurrent) { + unsigned int id = static_cast(std::floor((I.bitmap[idCurrent] - minVal)/widthBin)); + ++m_histogram[id]; + ++m_total; + } + ++idCurrent; + if (mp_mask) { + ++ptrMaskCurrent; + } + } + } + else { +#if defined(VISP_HAVE_THREADS) + // Multi-threads + std::vector threadpool; + std::vector *> histogramParams; + + unsigned int image_size = I.getSize(); + unsigned int step = image_size / nbThreads; + unsigned int last_step = image_size - step * (nbThreads - 1); + + for (unsigned int index = 0; index < nbThreads; ++index) { + unsigned int start_index = index * step; + unsigned int end_index = (index + 1) * step; + + if (index == nbThreads - 1) { + end_index = start_index + last_step; + } + + vpHistogramFloatingPoints_Param_t *histogram_param = new vpHistogramFloatingPoints_Param_t(start_index, end_index, minVal, widthBin, &I, mp_mask); + histogram_param->m_histogram = new unsigned int[m_size]; + histogram_param->m_mask = mp_mask; + memset(histogram_param->m_histogram, 0, m_size * sizeof(unsigned int)); + + histogramParams.push_back(histogram_param); + + // Start the threads + std::thread *histogram_thread = new std::thread(&computeHistogramFloatingPointThread, histogram_param); + threadpool.push_back(histogram_thread); + } + + for (size_t cpt = 0; cpt < threadpool.size(); ++cpt) { + // Wait until thread ends up + threadpool[cpt]->join(); + } + + m_total = 0; + for (unsigned int cpt1 = 0; cpt1 < m_size; ++cpt1) { + unsigned int sum = 0; + + for (size_t cpt2 = 0; cpt2 < histogramParams.size(); ++cpt2) { + sum += histogramParams[cpt2]->m_histogram[cpt1]; + } + + m_histogram[cpt1] = sum; + m_total += sum; + } + + // Delete + for (size_t cpt = 0; cpt < threadpool.size(); ++cpt) { + delete threadpool[cpt]; + } + + for (size_t cpt = 0; cpt < histogramParams.size(); ++cpt) { + delete histogramParams[cpt]; + } +#endif + } + } +#endif + + void equalize(const vpImage &I, vpImage &Iout); + + void display(const vpImage &I, const vpColor &color = vpColor::white, unsigned int thickness = 2, + unsigned int maxValue_ = 0); + + void smooth(unsigned int fsize = 3); + unsigned getPeaks(std::list &peaks); + unsigned getPeaks(unsigned char dist, vpHistogramPeak &peak1, vpHistogramPeak &peak2); + bool getPeaks(unsigned char dist, vpHistogramPeak &peakl, vpHistogramPeak &peakr, vpHistogramValey &valey); + unsigned getValey(std::list &valey); + bool getValey(const vpHistogramPeak &peak1, const vpHistogramPeak &peak2, vpHistogramValey &valey); + unsigned getValey(unsigned char dist, const vpHistogramPeak &peak, vpHistogramValey &valeyl, + vpHistogramValey &valeyr); + unsigned sort(std::list &peaks); + + bool write(const std::string &filename); + bool write(const char *filename); + + /*! + Get the histogram size. + + \return The size of the histogram, or the image maximum gray + levels numbers. + + \sa getValues() + */ + inline unsigned getSize() const { return m_size; } + + /*! + + Get the histogram values. + + \return A pointer to the array of histogram values. The size of + this array is given by getSize(). + + \code + vpImage I; // A gray level image + + vpHistogram h; + h.calculate(I); // Histogram of the gray level image + + // Print the histogram values + unsigned char *values = h.getValues(); + for (int i=0; i < h.getSize(); ++i) + printf("%d: %d\n", i, values[i]); + \endcode + + \sa getSize() + */ + inline unsigned *getValues() { return m_histogram; } + + /** + * \brief Get the total number of pixels in the input image. + * + * \return unsigned int Cumulated number of pixels in the input image. + */ + inline unsigned int getTotal() { return m_total; } + +private: +#if defined(VISP_HAVE_THREADS) && (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + template + struct vpHistogramFloatingPoints_Param_t + { + unsigned int m_start_index; + unsigned int m_end_index; + + unsigned int *m_histogram; + const ArithmeticType m_minVal; + const ArithmeticType m_step; + const vpImage *m_I; + const vpImage *m_mask; + + vpHistogramFloatingPoints_Param_t() : m_start_index(0), m_end_index(0), m_histogram(nullptr), m_minVal(0.), m_step(1.), m_I(nullptr), m_mask(nullptr) { } + + vpHistogramFloatingPoints_Param_t(unsigned int start_index, unsigned int end_index, const ArithmeticType &minVal, const ArithmeticType &step, const vpImage *const I, const vpImage *const mask) + : m_start_index(start_index), m_end_index(end_index), m_histogram(nullptr), m_minVal(minVal), m_step(step), m_I(I), m_mask(mask) + { } + + ~vpHistogramFloatingPoints_Param_t() + { + if (m_histogram != nullptr) { + delete[] m_histogram; + } + } + }; + + template + static typename std::enable_if::value, void>::type computeHistogramFloatingPointThread(vpHistogramFloatingPoints_Param_t *histogram_param) + { + unsigned int start_index = histogram_param->m_start_index; + unsigned int end_index = histogram_param->m_end_index; + unsigned int stopUnroll = end_index - 8; + unsigned int current_index = start_index; + + const vpImage *I = histogram_param->m_I; + + // Compute the index of a floating point value according to the min and step + const ArithmeticType &minVal = histogram_param->m_minVal; + const ArithmeticType &step = histogram_param->m_step; + + auto computeIndex = [&minVal, &step](const ArithmeticType &val) { + return static_cast (std::floor((val - minVal)/step)); + }; + + const bool alwaysTrue = true; + const bool *ptrMaskCurrent = &alwaysTrue; + if (histogram_param->m_mask) { + ptrMaskCurrent = (const bool *)histogram_param->m_mask->bitmap + start_index; + } + + if (end_index >= 8 + start_index) { + // Unroll loop version + while (current_index <= stopUnroll) { + if (*ptrMaskCurrent) { + unsigned int id = computeIndex(I->bitmap[current_index]); + histogram_param->m_histogram[id]++; + } + ++current_index; + if (histogram_param->m_mask != nullptr) { + ++ptrMaskCurrent; + } + + if (*ptrMaskCurrent) { + unsigned int id = computeIndex(I->bitmap[current_index]); + histogram_param->m_histogram[id]++; + } + ++current_index; + if (histogram_param->m_mask != nullptr) { + ++ptrMaskCurrent; + } + + if (*ptrMaskCurrent) { + unsigned int id = computeIndex(I->bitmap[current_index]); + histogram_param->m_histogram[id]++; + } + ++current_index; + if (histogram_param->m_mask != nullptr) { + ++ptrMaskCurrent; + } + + if (*ptrMaskCurrent) { + unsigned int id = computeIndex(I->bitmap[current_index]); + histogram_param->m_histogram[id]++; + } + ++current_index; + if (histogram_param->m_mask != nullptr) { + ++ptrMaskCurrent; + } + + if (*ptrMaskCurrent) { + unsigned int id = computeIndex(I->bitmap[current_index]); + histogram_param->m_histogram[id]++; + } + ++current_index; + if (histogram_param->m_mask != nullptr) { + ++ptrMaskCurrent; + } + + if (*ptrMaskCurrent) { + unsigned int id = computeIndex(I->bitmap[current_index]); + histogram_param->m_histogram[id]++; + } + ++current_index; + if (histogram_param->m_mask != nullptr) { + ++ptrMaskCurrent; + } + + if (*ptrMaskCurrent) { + unsigned int id = computeIndex(I->bitmap[current_index]); + histogram_param->m_histogram[id]++; + } + ++current_index; + if (histogram_param->m_mask != nullptr) { + ++ptrMaskCurrent; + } + + if (*ptrMaskCurrent) { + unsigned int id = computeIndex(I->bitmap[current_index]); + histogram_param->m_histogram[id]++; + } + ++current_index; + if (histogram_param->m_mask != nullptr) { + ++ptrMaskCurrent; + } + } + } + + while (current_index < end_index) { + if (*ptrMaskCurrent) { + unsigned int id = computeIndex(I->bitmap[current_index]); + histogram_param->m_histogram[id]++; + } + if (histogram_param->m_mask != nullptr) { + ++ptrMaskCurrent; + } + ++current_index; + } + } +#endif // DOXYGEN_SHOULD_SKIP_THIS +#endif + + void init(unsigned size = 256); + + unsigned int *m_histogram; /*!< The storage for the histogram.*/ + unsigned m_size; /*!< Histogram size (max allowed 256).*/ + const vpImage *mp_mask; /*!< Mask that permits to consider only the pixels for which the mask is true.*/ + unsigned int m_total; /*!< Cumulated number of pixels in the input image. */ + static const unsigned int constr_val_256; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpHistogramPeak.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpHistogramPeak.h new file mode 100644 index 00000000..3b599c91 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpHistogramPeak.h @@ -0,0 +1,150 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Gray level histogram manipulation. + */ + +/*! + \file vpHistogramPeak.h + \brief Declaration of the vpHistogramPeak class. + Class vpHistogramPeak defines a gray level histogram peak. +*/ + +#ifndef VP_HISTOGRAM_PEAK_H +#define VP_HISTOGRAM_PEAK_H + +#include + +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpHistogramPeak + + \ingroup group_core_histogram + \brief Declaration of the peak (maximum value) in a gray level image + histogram. + + A peak is internally represented as a gray \e level and a \e value. + The \e value represents the number of pixels having the gray \e level. + +*/ + +class VISP_EXPORT vpHistogramPeak +{ +public: + vpHistogramPeak(); + vpHistogramPeak(unsigned char level, unsigned value); + vpHistogramPeak(const vpHistogramPeak &p); + + /*! Destructor that does nothing. */ + virtual ~vpHistogramPeak() { } + + vpHistogramPeak &operator=(const vpHistogramPeak &p); + bool operator==(const vpHistogramPeak &p) const; + + /*! + + Set the peak gray \e level. To set the number of pixels having this + gray level use setValue(). + + \param lvl : Location of the peak or gray \e level. + + \sa setValue(), set() + + */ + inline void setLevel(unsigned char lvl) { this->level = lvl; } + /*! + + Set the peak number of pixels having a same gray level. To set the + gray level of this peak use setLevel(). + + \param val : Number of pixels having the same location or gray level. + + \sa setLevel(), set() + + */ + inline void setValue(unsigned val) { this->value = val; } + /*! + + Set the peak gray \e level and number of pixels at this gray level. + + \param lvl : Location of the peak or gray level. + \param val : Number of pixels having the same location or gray level. + + \sa setLevel(), setValue() + + */ + inline void set(unsigned char lvl, unsigned val) + { + this->level = lvl; + this->value = val; + } + + /*! + + Get the peak gray \e level. The number of pixels having this + gray level is available throw getValue(). + + \return Location of the peak or gray level. + + \sa getValue() + + */ + inline unsigned char getLevel() const { return level; } + /*! + + Get the peak number of pixels having a same gray level. The + corresponding gray level is available throw getLevel(). + + \return : Number of pixels having the same location or gray level. + + \sa getLevel() + + */ + inline unsigned getValue() const { return value; } + + //--------------------------------- + // Printing + //--------------------------------- + friend VISP_EXPORT std::ostream &operator<<(std::ostream &s, const vpHistogramPeak &p); + +protected: + unsigned char level; //! Gray level ot the peak. + unsigned value; //! Number of pixels on the gray level. +}; + +/* + * Local variables: + * c-basic-offset: 2 + * End: + */ +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpHistogramValey.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpHistogramValey.h new file mode 100644 index 00000000..c0621ae6 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpHistogramValey.h @@ -0,0 +1,144 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Gray level histogram manipulation. + */ + +/*! + \file vpHistogramValey.h + \brief Declaration of the vpHistogramValey class. + Class vpHistogramValey defines a gray level histogram valey. + +*/ + +#ifndef VP_HISTOGRAM_VALEY_H +#define VP_HISTOGRAM_VALEY_H + +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpHistogramValey + + \ingroup group_core_histogram + \brief Declaration of the valey (minimum value) in a gray level image + histogram. + + A valey is internally represented as a gray \e level and a \e value. + The \e value represents the number of pixels having the gray \e level. + +*/ + +class VISP_EXPORT vpHistogramValey : vpHistogramPeak +{ +public: + vpHistogramValey() : vpHistogramPeak() { } + + vpHistogramValey(unsigned char lvl, unsigned val) : vpHistogramPeak(lvl, val) { } + + vpHistogramValey(const vpHistogramValey &v) : vpHistogramPeak(v) { } + + vpHistogramValey &operator=(const vpHistogramValey &v); + bool operator==(const vpHistogramValey &v) const; + + /*! + + Set the valey gray \e level. To set the number of pixels having this + gray level use setValue(). + + \param lvl : Location of the valey or gray level. + + \sa setValue(), set() + + */ + inline void setLevel(unsigned char lvl) { this->level = lvl; } + /*! + + Set the valey number of pixels having a same gray level. To set the + gray level of this valey use setLevel(). + + \param val : Number of pixels having the same location or gray level. + + \sa setPosition(), set() + + */ + inline void setValue(unsigned val) { this->value = val; } + /*! + + Set the valey gray \e level and number of pixels at this location. + + \param lvl : Location of the valey or gray level. + \param val : Number of pixels having the same location or gray level. + + \sa setLevel(), setValue() + + */ + inline void set(unsigned char lvl, unsigned val) + { + this->level = lvl; + this->value = val; + } + + /*! + + Get the valey gray \e level. The number of pixels having this + gray level is available through getValue(). + + \return Location of the valey or gray level. + + \sa getValue() + + */ + inline unsigned char getLevel() const { return level; } + /*! + + Get the valey number of pixels having a same gray level. The + corresponding gray level is available through getLevel(). + + \return Number of pixels having the same location or gray level. + + \sa getLevel() + + */ + inline unsigned getValue() const { return value; } + + //--------------------------------- + // Printing + //--------------------------------- + friend VISP_EXPORT std::ostream &operator<<(std::ostream &s, const vpHistogramValey &v); +}; + +/* + * Local variables: + * c-basic-offset: 2 + * End: + */ +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpHomogeneousMatrix.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpHomogeneousMatrix.h new file mode 100644 index 00000000..76950d9a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpHomogeneousMatrix.h @@ -0,0 +1,453 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Homogeneous matrix. + */ + +/*! + \file vpHomogeneousMatrix.h + \brief Definition and computation on the homogeneous matrices + */ + +#ifndef VP_HOMOGENEOUS_MATRIX_H +#define VP_HOMOGENEOUS_MATRIX_H + +#include +#include + +#include + +BEGIN_VISP_NAMESPACE + +class vpTranslationVector; +class vpPoseVector; +class vpMatrix; +class vpRotationMatrix; +class vpPoseVector; +class vpThetaUVector; +class vpQuaternionVector; +class vpPoint; + +END_VISP_NAMESPACE + +#include +#include +#include +#include + +#ifdef VISP_HAVE_NLOHMANN_JSON +#include VISP_NLOHMANN_JSON(json.hpp) +#endif + +BEGIN_VISP_NAMESPACE +/*! + \class vpHomogeneousMatrix + + \ingroup group_core_transformations + + \brief Implementation of an homogeneous matrix and operations on such kind + of matrices. + + The class provides a data structure for the homogeneous matrices + as well as a set of operations on these matrices. + + The vpHomogeneousMatrix class is derived from vpArray2D. + + An homogeneous matrix is 4x4 matrix defines as + \f[ + ^a{\bf M}_b = \left(\begin{array}{cc} + ^a{\bf R}_b & ^a{\bf t}_b \\ + {\bf 0}_{1\times 3} & 1 + \end{array} + \right) + \f] + that defines the position of frame b in frame a + + \f$ ^a{\bf R}_b \f$ is a rotation matrix and + \f$ ^a{\bf t}_b \f$ is a translation vector. + + There are different ways to initialize an homogeneous matrix. You can set each element of the matrix like: + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpHomogeneousMatrix M; + M[0][0] = 0; M[0][1] = 0; M[0][2] = -1; M[0][3] = 0.1; + M[1][0] = 0; M[1][1] = -1; M[1][2] = 0; M[1][3] = 0.2; + M[2][0] = -1; M[2][1] = 0; M[2][2] = 0; M[2][3] = 0.3; + + std::cout << "M:" << std::endl; + for (unsigned int i = 0; i < M.getRows(); ++i) { + for (unsigned int j = 0; j < M.getCols(); ++j) { + std::cout << M[i][j] << " "; + } + std::cout << std::endl; + } + } + \endcode + It produces the following printings: + \code{.unparsed} + M: + 0 0 -1 0.1 + 0 -1 0 0.2 + -1 0 0 0.3 + 0 0 0 1 + \endcode + You can also use vpRotationMatrix::operator<< and vpTranslationVector::operator<< like: + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpTranslationVector t; + vpRotationMatrix R; + R << 0, 0, -1, + 0, -1, 0, + -1, 0, 0; + t << 0.1, 0.2, 0.3; + vpHomogeneousMatrix M(t, R); + std::cout << "M:\n" << M << std::endl; + } + \endcode + If ViSP is build with c++11 enabled, you can do the same using: + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + { + vpHomogeneousMatrix M( vpTranslationVector(0.1, 0.2, 0.3), vpRotationMatrix( {0, 0, -1, 0, -1, 0, -1, 0, 0} ) ); + std::cout << "M:\n" << M << std::endl; + } + { + vpHomogeneousMatrix M { 0, 0, -1, 0.1, + 0, -1, 0, 0.2, + -1, 0, 0, 0.3 }; + std::cout << "M:\n" << M << std::endl; + } + } + \endcode + + JSON serialization + + Since ViSP 3.6.0, if ViSP is build with \ref soft_tool_json 3rd-party we introduce JSON serialization capabilities for vpHomogeneousMatrix. + The following sample code shows how to save a homogeneous matrix in a file named `homo-mat.json` + and reload the values from this JSON file. + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #if defined(VISP_HAVE_NLOHMANN_JSON) + std::string filename = "homo-mat.json"; + { + vpHomogeneousMatrix M(vpTranslationVector(0.1, 0.2, 0.3), vpRotationMatrix({ 0, 0, -1, 0, -1, 0, -1, 0, 0 })); + std::ofstream file(filename); + const nlohmann::json j = M; + file << j; + file.close(); + } + { + std::ifstream file(filename); + const nlohmann::json j = nlohmann::json::parse(file); + vpHomogeneousMatrix M; + M = j; + file.close(); + std::cout << "Read homogeneous matrix from " << filename << ":\n" << M << std::endl; + } + #endif + } + \endcode + If you build and execute the sample code, it will produce the following output: + \code{.unparsed} + Read homogeneous matrix from homo-mat.json: + 0 0 -1 0.1 + 0 -1 0 0.2 + -1 0 0 0.3 + 0 0 0 1 + \endcode + + The content of the `homo-mat.json` file is the following: + \code{.unparsed} + $ cat homo-mat.json + {"cols":4,"data":[0.0,0.0,-1.0,0.1,0.0,-1.0,0.0,0.2,-1.0,0.0,0.0,0.3,0.0,0.0,0.0,1.0],"rows":4,"type":"vpHomogeneousMatrix"} + \endcode +*/ +class VISP_EXPORT vpHomogeneousMatrix : public vpArray2D +{ +public: + vpHomogeneousMatrix(); + vpHomogeneousMatrix(const vpHomogeneousMatrix &M); + vpHomogeneousMatrix(const vpTranslationVector &t, const vpRotationMatrix &R); + vpHomogeneousMatrix(const vpTranslationVector &t, const vpThetaUVector &tu); + vpHomogeneousMatrix(const vpTranslationVector &t, const vpQuaternionVector &q); + VP_EXPLICIT vpHomogeneousMatrix(const vpPoseVector &p); + VP_EXPLICIT vpHomogeneousMatrix(const std::vector &v); + VP_EXPLICIT vpHomogeneousMatrix(const std::vector &v); + vpHomogeneousMatrix(double tx, double ty, double tz, double tux, double tuy, double tuz); +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + VP_EXPLICIT vpHomogeneousMatrix(const std::initializer_list &list); +#endif + + vpHomogeneousMatrix &buildFrom(const vpTranslationVector &t, const vpRotationMatrix &R); + vpHomogeneousMatrix &buildFrom(const vpTranslationVector &t, const vpThetaUVector &tu); + vpHomogeneousMatrix &buildFrom(const vpTranslationVector &t, const vpQuaternionVector &q); + vpHomogeneousMatrix &buildFrom(const vpPoseVector &p); + vpHomogeneousMatrix &buildFrom(const std::vector &v); + vpHomogeneousMatrix &buildFrom(const std::vector &v); + vpHomogeneousMatrix &buildFrom(const double &tx, const double &ty, const double &tz, const double &tux, const double &tuy, const double &tuz); + + void convert(std::vector &M); + void convert(std::vector &M); + + // Set to identity + void eye(); + + vpColVector getCol(unsigned int j) const; + vpRotationMatrix getRotationMatrix() const; + vpThetaUVector getThetaUVector() const; + vpTranslationVector getTranslationVector() const; + + // Invert the homogeneous matrix. + vpHomogeneousMatrix inverse() const; + // Invert the homogeneous matrix. + void inverse(vpHomogeneousMatrix &Mi) const; + + // Test if the rotational part of the matrix is a rotation matrix. + bool isAnHomogeneousMatrix(double threshold = 1e-6) const; + bool isValid() const; + + void insert(const vpRotationMatrix &R); + void insert(const vpThetaUVector &tu); + void insert(const vpTranslationVector &t); + void insert(const vpQuaternionVector &t); + + void extract(vpRotationMatrix &R) const; + void extract(vpThetaUVector &tu) const; + void extract(vpTranslationVector &t) const; + void extract(vpQuaternionVector &q) const; + + /*! + * Read an homogeneous matrix from an input file stream. The + * homogeneous matrix is considered as a 4 by 4 matrix. + * + * \param f : Input file stream. + * + * The code below shows how to get an homogeneous matrix from a file. + * + * \code + * vpHomogeneousMatrix M; + * + * std::ifstream f("homogeneous.dat"); + * M.load(f); + * \endcode + * + * \sa load(const std::string &), save(std::ifstream &) + */ + void load(std::ifstream &f); + + /*! + * Read an homogeneous matrix from an input file. The + * homogeneous matrix is considered as a 4 by 4 matrix. + * + * \param filename : Input file name. + * + * The code below shows how to get an homogeneous matrix from a file. + * + * \code + * vpHomogeneousMatrix M; + * + * M.load("homogeneous.dat"); + * \endcode + * + * \sa load(std::ifstream &), save(const std::string &) + */ + void load(const std::string &filename); + + /*! + * Save an homogeneous matrix in an output file stream. + * + * \param f : Output file stream. The homogeneous matrix is saved as a 4 by 4 matrix. + * + * The code below shows how to save an homogeneous matrix in a file. + * + * \code + * // Construct an homogeneous matrix + * vpTranslationVector t(1,2,3); + * vpRxyzVector r(M_PI, 0, -M_PI/4.); + * vpRotationMatrix R(r); + * vpHomogeneousMatrix M(t, R); + * + * // Save the content of the matrix in "homogeneous.dat" + * std::ofstream f("homogeneous.dat"); + * M.save(f); + * \endcode + * The content of "homogeneous.dat" is the following: + * \verbatim + * 0.7071067812 0.7071067812 0 1 + * 0.7071067812 -0.7071067812 -1.224646799e-16 2 + * -8.659560562e-17 8.659560562e-17 -1 3 + * 0 0 0 1 + * \endverbatim + * + * \sa save(const std::string &), load(std::ifstream &) + */ + void save(std::ofstream &f) const; + + /*! + * Save an homogeneous matrix in a file. + * + * \param filename : Output file name. The homogeneous matrix is saved as a 4 by 4 matrix. + * + * The code below shows how to save an homogeneous matrix in a file. + * + * \code + * // Construct an homogeneous matrix + * vpTranslationVector t(1,2,3); + * vpRxyzVector r(M_PI, 0, -M_PI/4.); + * vpRotationMatrix R(r); + * vpHomogeneousMatrix M(t, R); + * + * // Save the content of the matrix in "homogeneous.dat" + * M.save("homogeneous.dat"); + * \endcode + * The content of "homogeneous.dat" is the following: + * \verbatim + * 0.7071067812 0.7071067812 0 1 + * 0.7071067812 -0.7071067812 -1.224646799e-16 2 + * -8.659560562e-17 8.659560562e-17 -1 3 + * 0 0 0 1 + * \endverbatim + * + * \sa save(std::ofstream &), load(const std::string &) + */ + void save(const std::string &filename) const; + + vpHomogeneousMatrix &operator=(const vpHomogeneousMatrix &M); + vpHomogeneousMatrix operator*(const vpHomogeneousMatrix &M) const; + vpHomogeneousMatrix &operator*=(const vpHomogeneousMatrix &M); + + vpColVector operator*(const vpColVector &v) const; + vpTranslationVector operator*(const vpTranslationVector &t) const; + vpHomogeneousMatrix operator*(const vpRotationMatrix &R) const; + + // Multiply by a point + vpPoint operator*(const vpPoint &bP) const; + + vpHomogeneousMatrix &operator<<(double val); + vpHomogeneousMatrix &operator,(double val); + + void orthogonalizeRotation(); + + void print() const; + + /*! + * This function is not applicable to an homogeneous matrix that is always a + * 4-by-4 matrix. + * \exception vpException::fatalError When this function is called. + */ + VP_NORETURN void resize(unsigned int nrows, unsigned int ncols, bool flagNullify = true) + { + (void)nrows; + (void)ncols; + (void)flagNullify; + throw(vpException(vpException::fatalError, "Cannot resize an homogeneous matrix")); + } + + static vpHomogeneousMatrix compute3d3dTransformation(const std::vector &p, const std::vector &q); + + static vpHomogeneousMatrix mean(const std::vector &vec_M); + +#ifdef VISP_HAVE_NLOHMANN_JSON +public: + static const std::string jsonTypeName; +private: + friend void to_json(nlohmann::json &j, const vpHomogeneousMatrix &T); + friend void from_json(const nlohmann::json &j, vpHomogeneousMatrix &T); + // Conversion helper function to avoid circular dependencies and MSVC errors that are not exported in the DLL + void parse_json(const nlohmann::json &j); + void convert_to_json(nlohmann::json &j) const; +public: + +#endif + +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) + /*! + * @name Deprecated functions + */ + //@{ + /*! + * \deprecated Provided only for compat with previous releases. + * This function does nothing. + */ + VP_DEPRECATED void init() { } + /*! + * \deprecated You should rather use eye(). + */ + VP_DEPRECATED void setIdentity(); + //@} +#endif + +protected: + unsigned int m_index; +private: + static const unsigned int constr_value_4; +}; + +#ifdef VISP_HAVE_NLOHMANN_JSON +inline void to_json(nlohmann::json &j, const vpHomogeneousMatrix &T) +{ + T.convert_to_json(j); +} + +inline void from_json(const nlohmann::json &j, vpHomogeneousMatrix &T) +{ + T.parse_json(j); +} +#endif +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImage.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImage.h new file mode 100644 index 00000000..ee883726 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImage.h @@ -0,0 +1,975 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Image handling. + */ + +/*! + * \file vpImage.h + * \brief Image handling. + */ + +#ifndef VP_IMAGE_H +#define VP_IMAGE_H + +#include +#include +#include +#include +#include +#include +#include + +#if defined(VISP_HAVE_THREADS) +#include +#endif + +#include +#include // std::setw +#include +#include +#include + +// Visual Studio 2010 or previous is missing inttypes.h +#if defined(_MSC_VER) && (_MSC_VER < 1700) +typedef long long int64_t; +typedef unsigned short uint16_t; +#else +#include +#endif + +BEGIN_VISP_NAMESPACE +class vpDisplay; +// Ref: http://en.cppreference.com/w/cpp/language/friend#Template_friends +template class vpImage; // forward declare to make function declaration possible + +// declarations +template std::ostream &operator<<(std::ostream &s, const vpImage &I); + +std::ostream &operator<<(std::ostream &s, const vpImage &I); +std::ostream &operator<<(std::ostream &s, const vpImage &I); +std::ostream &operator<<(std::ostream &s, const vpImage &I); +std::ostream &operator<<(std::ostream &s, const vpImage &I); + +/*! + \class vpImage + + \ingroup group_core_image + + \brief Definition of the vpImage class member functions. + + This is a template class, therefore the type of each element of the + array is not a priori defined. + +

Data structure

+ + Each image is build using two structure (an array bitmap which size + is [width*height]) and an array of pointer row (which size is [nrow]) + the ith element in the row array row[i] is pointer toward the ith + "line" of the image (ie, bitmap +i*width ) + + \image html image-data-structure.gif + \image latex image-data-structure.ps width=10cm + + Such a structure allows a fast access to each element of the image. + if i is the ith rows and j the jth columns the value of this pixel + is given by I[i][j] (that is equivalent to row[i][j]). + +

Example

+ The following example available in tutorial-image-manipulation.cpp shows how + to create gray level and color images and how to access to the pixels. + \include tutorial-image-manipulation.cpp + +

Important remark

To provide high-performance access there + is no verification to ensure that 0 \f$\le\f$ i < height and 0 + \f$\le\f$ j < width. Since the memory allocated in the bitmap array + is continuous, that means that if (i, j) is outside the image you + will manipulate a pixel that is not as expected. To highlight this + remark, we provide hereafter an example where the considered pixel + is outside the image: + + \code + unsigned int width = 320; + unsigned int height = 240; + vpImage I(height, width); // Create an 320x240 image + // Set pixel coordinates that is outside the image + unsigned int i = 100; + unsigned int j = 400; + unsigned char value; + value = I[i][j]; // Here we will get the pixel value at position (101, 80) + \endcode + +*/ +template class vpImage +{ + friend class vpImageConvert; + +public: + Type *bitmap; //!< points toward the bitmap + vpDisplay *display; + + //! constructor + vpImage(); + //! copy constructor + vpImage(const vpImage &img); +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher + //! move constructor + vpImage(vpImage &&img); +#endif + //! constructor set the size of the image + vpImage(unsigned int height, unsigned int width); + //! constructor set the size of the image and init all the pixel + vpImage(unsigned int height, unsigned int width, Type value); + //! constructor from an image stored as a continuous array in memory + vpImage(Type *const array, unsigned int height, unsigned int width, bool copyData = false); + //! destructor + virtual ~vpImage(); + + /** @name Inherited functionalities from vpImage */ + //@{ + + // destructor + void destroy(); + + // Returns a new image that's double size of the current image + void doubleSizeImage(vpImage &res); + + /*! + Get the number of columns in the image. + + \return The image number of column, or image width. + + \sa getWidth() + */ + inline unsigned int getCols() const { return width; } + + /*! + Get the image height. + + \return The image height. + + \sa getWidth() + + */ + inline unsigned int getHeight() const { return height; } + + // Return the maximum value within the bitmap + Type getMaxValue(bool onlyFiniteVal = true) const; + // Return the mean value of the bitmap + double getMeanValue(const vpImage *p_mask = nullptr, unsigned int *nbValidPoints = nullptr) const; + + // Return the minumum value within the bitmap + Type getMinValue(bool onlyFiniteVal = true) const; + // Look for the minumum and the maximum value within the bitmap + void getMinMaxValue(Type &min, Type &max, bool onlyFiniteVal = true) const; + // Look for the minumum and the maximum value within the bitmap and get their location + void getMinMaxLoc(vpImagePoint *minLoc, vpImagePoint *maxLoc, Type *minVal = nullptr, Type *maxVal = nullptr) const; + + /*! + Get the image number of pixels which corresponds to the image + width multiplied by the image height. + + \return The image number of pixels or image size. + + \sa getWidth(), getHeight() + */ + inline unsigned int getNumberOfPixel() const { return npixels; } + + /*! + Get the number of rows in the image. + + \return The image number of rows, or image height. + + \sa getHeight() + */ + inline unsigned int getRows() const { return height; } + + /*! + Get the image size. + + \return The image size = width * height. + + \sa getWidth(), getHeight() + */ + inline unsigned int getSize() const { return width * height; } + + double getStdev(const vpImage *p_mask = nullptr, unsigned int *nbValidPoints = nullptr) const; + double getStdev(const double &mean, const vpImage *p_mask = nullptr, unsigned int *nbValidPoints = nullptr) const; + + double getSum(const vpImage *p_mask = nullptr, unsigned int *nbValidPoints = nullptr) const; + + // Gets the value of a pixel at a location. + Type getValue(unsigned int i, unsigned int j) const; + // Gets the value of a pixel at a location with bilinear interpolation. + Type getValue(double i, double j) const; + // Gets the value of a pixel at a location with bilinear interpolation. + Type getValue(const vpImagePoint &ip) const; + + /*! + Get the image width. + + \return The image width. + + \sa getHeight() + */ + inline unsigned int getWidth() const { return width; } + + // Returns a new image that's half size of the current image + void halfSizeImage(vpImage &res) const; + + //! Set the size of the image + void init(unsigned int height, unsigned int width); + //! Set the size of the image and initialize all the elements to 'value' + void init(unsigned int height, unsigned int width, Type value); + //! Initialization from an image stored as a continuous array in memory + void init(Type *const array, unsigned int height, unsigned int width, bool copyData = false); + void insert(const vpImage &src, const vpImagePoint &topLeft); + + //------------------------------------------------------------------ + // Access to the image + + //! operator[] allows operation like I[i] = x. + inline Type *operator[](unsigned int i) { return row[i]; } + inline Type *operator[](int i) { return row[i]; } + + //! operator[] allows operation like x = I[i] + inline const Type *operator[](unsigned int i) const { return row[i]; } + inline const Type *operator[](int i) const { return row[i]; } + + /*! + Get the value of an image point with coordinates (i, j), with i the row + position and j the column position. + + \return Value of the image point (i, j). + */ + inline Type operator()(unsigned int i, unsigned int j) const { return bitmap[(i * width) + j]; } + + /*! + Set the value \e v of an image point with coordinates (i, j), with i the + row position and j the column position. + */ + inline void operator()(unsigned int i, unsigned int j, const Type &v) { bitmap[(i * width) + j] = v; } + + /*! + Get the value of an image point. + + \param ip : An image point with sub-pixel coordinates. Sub-pixel + coordinates are roughly transformed to insigned int coordinates by cast. + + \return Value of the image point \e ip. + + \sa getValue(const vpImagePoint &) + */ + inline Type operator()(const vpImagePoint &ip) const + { + unsigned int i = static_cast(ip.get_i()); + unsigned int j = static_cast(ip.get_j()); + + return bitmap[(i * width) + j]; + } + + /*! + Set the value of an image point. + + \param ip : An image point with sub-pixel coordinates. Sub-pixel + coordinates are roughly transformed to insigned int coordinates by cast. + + \param v : Value to set for the image point. + */ + inline void operator()(const vpImagePoint &ip, const Type &v) + { + unsigned int i = static_cast(ip.get_i()); + unsigned int j = static_cast(ip.get_j()); + + bitmap[(i * width) + j] = v; + } + + vpImage operator-(const vpImage &B) const; + + //! Copy operator + vpImage &operator=(const vpImage &other); +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher + //! move constructor + vpImage &operator=(vpImage &&other); +#endif + + vpImage &operator=(const Type &v); + bool operator==(const vpImage &I) const; + bool operator!=(const vpImage &I) const; + friend std::ostream &operator<< <>(std::ostream &s, const vpImage &I); + friend std::ostream &operator<<(std::ostream &s, const vpImage &I); + friend std::ostream &operator<<(std::ostream &s, const vpImage &I); + friend std::ostream &operator<<(std::ostream &s, const vpImage &I); + friend std::ostream &operator<<(std::ostream &s, const vpImage &I); + + // Perform a look-up table transformation + // static const unsigned int val_256 = 256; + // void performLut(const Type(&lut)[val_256], unsigned int nbThreads = 1); // Doesn't pass CI + void performLut(const Type(&lut)[256], unsigned int nbThreads = 1); + + // Returns a new image that's a quarter size of the current image + void quarterSizeImage(vpImage &res) const; + + // set the size of the image without initializing it. + void resize(unsigned int h, unsigned int w); + // set the size of the image and initialize it. + void resize(unsigned int h, unsigned int w, const Type &val); + + void sub(const vpImage &B, vpImage &C) const; + void sub(const vpImage &A, const vpImage &B, vpImage &C) const; + void subsample(unsigned int v_scale, unsigned int h_scale, vpImage &sampled) const; + + // See https://stackoverflow.com/questions/11562/how-to-overload-stdswap to understand why swap is in visp namespace + friend void swap(vpImage &first, vpImage &second) + { + using std::swap; + swap(first.bitmap, second.bitmap); + swap(first.display, second.display); + swap(first.npixels, second.npixels); + swap(first.width, second.width); + swap(first.height, second.height); + swap(first.row, second.row); + } + + //@} + +private: + unsigned int npixels; ///! number of pixel in the image + unsigned int width; ///! number of columns + unsigned int height; ///! number of rows + Type **row; ///! points the row pointer array + bool hasOwnership; ///! true if this instance owns the bitmap, false otherwise (e.g. copyData=false) +}; + +#include +#include +#include + +/*! + \relates vpImage +*/ +template void vpImage::init(unsigned int h, unsigned int w, Type value) +{ + init(h, w); + std::fill(bitmap, bitmap + npixels, value); +} + +/*! + \relates vpImage +*/ +template void vpImage::init(unsigned int h, unsigned int w) +{ + if (h != this->height) { + if (row != nullptr) { + delete[] row; + row = nullptr; + } + } + + if ((h != this->height) || (w != this->width)) { + if (bitmap != nullptr) { + if (hasOwnership) { + delete[] bitmap; + } + bitmap = nullptr; + } + } + + this->width = w; + this->height = h; + + npixels = width * height; + + if (bitmap == nullptr) { + bitmap = new Type[npixels]; + hasOwnership = true; + } + if (bitmap == nullptr) { + throw(vpException(vpException::memoryAllocationError, "cannot allocate bitmap ")); + } + if (row == nullptr) { + row = new Type *[height]; + } + if (row == nullptr) { + throw(vpException(vpException::memoryAllocationError, "cannot allocate row ")); + } + + for (unsigned int i = 0; i < height; ++i) { + row[i] = bitmap + (i * width); + } +} + +/*! + \relates vpImage +*/ +template void vpImage::init(Type *const array, unsigned int h, unsigned int w, bool copyData) +{ + if (h != this->height) { + if (row != nullptr) { + delete[] row; + row = nullptr; + } + } + + // Delete bitmap if copyData==false, otherwise only if the dimension differs + if ((copyData && ((h != this->height) || (w != this->width))) || (!copyData)) { + if (bitmap != nullptr) { + if (hasOwnership) { + delete[] bitmap; + } + bitmap = nullptr; + } + } + + hasOwnership = copyData; + this->width = w; + this->height = h; + + npixels = width * height; + + if (copyData) { + if (bitmap == nullptr) { + bitmap = new Type[npixels]; + } + + if (bitmap == nullptr) { + throw(vpException(vpException::memoryAllocationError, "cannot allocate bitmap ")); + } + + // Copy the image data + memcpy(static_cast(bitmap), static_cast(array), static_cast(npixels * sizeof(Type))); + } + else { + // Copy the address of the array in the bitmap + bitmap = array; + } + + if (row == nullptr) { + row = new Type *[height]; + } + if (row == nullptr) { + throw(vpException(vpException::memoryAllocationError, "cannot allocate row ")); + } + + for (unsigned int i = 0; i < height; ++i) { + row[i] = bitmap + (i * width); + } +} + +/*! + \relates vpImage +*/ +template +vpImage::vpImage(unsigned int h, unsigned int w) + : bitmap(nullptr), display(nullptr), npixels(0), width(0), height(0), row(nullptr), hasOwnership(true) +{ + Type val(0); + init(h, w, val); +} + +/*! + \relates vpImage +*/ +template +vpImage::vpImage(unsigned int h, unsigned int w, Type value) + : bitmap(nullptr), display(nullptr), npixels(0), width(0), height(0), row(nullptr), hasOwnership(true) +{ + init(h, w, value); +} + +/*! + \relates vpImage +*/ +template +vpImage::vpImage(Type *const array, unsigned int h, unsigned int w, bool copyData) + : bitmap(nullptr), display(nullptr), npixels(0), width(0), height(0), row(nullptr), hasOwnership(true) +{ + init(array, h, w, copyData); +} + +/*! + \relates vpImage +*/ +template +vpImage::vpImage() : bitmap(nullptr), display(nullptr), npixels(0), width(0), height(0), row(nullptr), hasOwnership(true) +{ } + +/*! + \brief resize the image : Image initialization + + Allocate memory for an [height x width] image. + + \warning The image is not initialized. + + \param w : Image width. + \param h : Image height. + + Element of the bitmap are not initialized + + If the image has been already initialized, memory allocation is done + only if the new image size is different, else we re-use the same + memory space. + + \exception vpException::memoryAllocationError Memory allocation error. + + \sa init(unsigned int, unsigned int) +*/ +template void vpImage::resize(unsigned int h, unsigned int w) { init(h, w); } + +/*! + \brief resize the image : Image initialization + + Allocate memory for an [height x width] image and initialize the image. + + \param w : Image width. + \param h : Image height. + \param val : Pixels value. + + Element of the bitmap are not initialized + + If the image has been already initialized, memory allocation is done + only if the new image size is different, else we re-use the same + memory space. + + \exception vpException::memoryAllocationError Memory allocation error. + + \sa init(unsigned int, unsigned int) +*/ +template void vpImage::resize(unsigned int h, unsigned int w, const Type &val) { init(h, w, val); } + +/*! + \brief Destructor : Memory de-allocation + + \warning does not deallocate memory for display and video + +*/ +template void vpImage::destroy() +{ + if (bitmap != nullptr) { + if (hasOwnership) { + delete[] bitmap; + } + bitmap = nullptr; + } + + if (row != nullptr) { + delete[] row; + row = nullptr; + } +} + +/*! + \brief Destructor : Memory de-allocation + + \warning does not deallocate memory for display and video + +*/ +template vpImage::~vpImage() { destroy(); } + +/*! + \relates vpImage +*/ +template +vpImage::vpImage(const vpImage &I) + : bitmap(nullptr), display(nullptr), npixels(0), width(0), height(0), row(nullptr), hasOwnership(true) +{ + resize(I.getHeight(), I.getWidth()); + if (bitmap) { + memcpy(static_cast(bitmap), static_cast(I.bitmap), I.npixels * sizeof(Type)); + } +} + +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher +/*! + \relates vpImage +*/ +template +vpImage::vpImage(vpImage &&I) + : bitmap(I.bitmap), display(I.display), npixels(I.npixels), width(I.width), height(I.height), row(I.row), + hasOwnership(I.hasOwnership) +{ + I.bitmap = nullptr; + I.display = nullptr; + I.npixels = 0; + I.width = 0; + I.height = 0; + I.row = nullptr; + I.hasOwnership = false; +} +#endif + +/*! + Insert an image into another one. + + It is possible to insert the image \f$ src \f$ into the calling vpImage. + You can set the point in the destination image where the top left corner of + the \f$ src \f$ image will be located. + + \param src : Image to insert + \param topLeft : Upper/left coordinates in the image where the image \e src + is inserted in the destination image. +*/ +template void vpImage::insert(const vpImage &src, const vpImagePoint &topLeft) +{ + int itl = static_cast(topLeft.get_i()); + int jtl = static_cast(topLeft.get_j()); + + int dest_ibegin = 0; + int dest_jbegin = 0; + int src_ibegin = 0; + int src_jbegin = 0; + int dest_w = static_cast(this->getWidth()); + int dest_h = static_cast(this->getHeight()); + int src_w = static_cast(src.getWidth()); + int src_h = static_cast(src.getHeight()); + int wsize = static_cast(src.getWidth()); + int hsize = static_cast(src.getHeight()); + + if ((itl >= dest_h) || (jtl >= dest_w)) { + return; + } + + if (itl < 0) { + src_ibegin = -itl; + } + else { + dest_ibegin = itl; + } + + if (jtl < 0) { + src_jbegin = -jtl; + } + else { + dest_jbegin = jtl; + } + + if ((src_w - src_jbegin) >(dest_w - dest_jbegin)) { + wsize = dest_w - dest_jbegin; + } + else { + wsize = src_w - src_jbegin; + } + + if ((src_h - src_ibegin) > (dest_h - dest_ibegin)) { + hsize = dest_h - dest_ibegin; + } + else { + hsize = src_h - src_ibegin; + } + + for (int i = 0; i < hsize; ++i) { + Type *srcBitmap = src.bitmap + (((src_ibegin + i) * src_w) + src_jbegin); + Type *destBitmap = this->bitmap + (((dest_ibegin + i) * dest_w) + dest_jbegin); + + memcpy(static_cast(destBitmap), static_cast(srcBitmap), static_cast(wsize) * sizeof(Type)); + } +} + +/*! + Returns a new image that's half size of the current image. + No filtering is used during the sub sampling. + + Used for building pyramid of the image. + \warning Operator = must be defined for Type. + + \param res [out] : Subsampled image that is half size of the current image. + + The example below shows how to use this method: + \code + vpImage I; // original image + vpImageIo::read(I, "myImage.pgm"); + vpImage I2; // half size image + I.halfSizeImage(I2); + vpImageIo::write(I2, "myHalfSizeImage.pgm"); + \endcode + + This other example shows how to construct a pyramid of the image: + \code + vpImage I[4]; // pyramid with 4 levels + vpImageIo::read(I[1], "myImage.pgm"); // Original image at level 1 + // compute the other levels + I5[1].doubleSizeImage(I5[0]); // double size image at level 0 + I5[1].halfSizeImage(I5[2]); // half size image at level 2 + I5[1].quarterSizeImage(I5[3]); // quarter size image at level 3 + \endcode + + \sa subsample() +*/ +template void vpImage::halfSizeImage(vpImage &res) const +{ + unsigned int h = height / 2; + unsigned int w = width / 2; + res.resize(h, w); + for (unsigned int i = 0; i < h; ++i) { + for (unsigned int j = 0; j < w; ++j) { + res[i][j] = (*this)[i << 1][j << 1]; + } + } +} + +/*! + Computes a subsampled image. + No filtering is used during the sub sampling. + + \param v_scale [in] : Vertical subsampling factor. + \param h_scale [in] : Horizontal subsampling factor. + \param sampled [out] : Subsampled image. + + The example below shows how to use this method: + \code + vpImage I; // original image + vpImageIo::read(I, "myImage.pgm"); + vpImage I2; // half size image + I.subsample(2, 2, I2); + vpImageIo::write(I2, "myHalfSizeImage.pgm"); + \endcode +*/ +template +void vpImage::subsample(unsigned int v_scale, unsigned int h_scale, vpImage &sampled) const +{ + if ((v_scale == 1) && (h_scale == 1)) { + sampled = *this; + return; + } + unsigned int h = height / v_scale; + unsigned int w = width / h_scale; + sampled.resize(h, w); + for (unsigned int i = 0; i < h; ++i) { + for (unsigned int j = 0; j < w; ++j) { + sampled[i][j] = (*this)[i * v_scale][j * h_scale]; + } + } +} + +/*! + Returns a new image that's a quarter size of the current image. + No filtering is used during the sub sampling. + Used for building a quarter of the image. + \warning Operator = must be defined for Type. + + \param res [out] : Subsampled image that is quarter size of the + current image. + + The example below shows how to use this method: + \code + vpImage I; // original image + vpImageIo::read(I, "myImage.pgm"); + vpImage I4; // quarter size image + I.halfSizeImage(I4); + vpImageIo::write(I4, "myQuarterSizeImage.pgm"); + \endcode + + See halfSizeImage(vpImage &) for an example of pyramid construction. + + \sa subsample() +*/ +template void vpImage::quarterSizeImage(vpImage &res) const +{ + unsigned int h = height / 4; + unsigned int w = width / 4; + const unsigned int magic_2 = 2; + res.resize(h, w); + for (unsigned int i = 0; i < h; ++i) { + for (unsigned int j = 0; j < w; ++j) { + res[i][j] = (*this)[i << magic_2][j << magic_2]; + } + } +} + +/*! + Returns a new image that's double size of the current image. + Used (eg. in case of keypoints extraction, we might + double size of the image in order to have more keypoints). + The double size image is computed by nearest-neighbour interpolation: + + \code + A B C + E F G + H I J + + where + A C H J are pixels from original image + B E G I are interpolated pixels + \endcode + + \warning Operator = must be defined for Type. + + \param res [out] : Image that is double size of the current image. + + The example below shows how to use this method: + \code + vpImage I; // original image + vpImageIo::read(I, "myImage.pgm"); + vpImage I2; // double size image + I.doubleSizeImage(I2); + vpImageIo::write(I2, "myDoubleSizeImage.pgm"); + \endcode + + See halfSizeImage(vpImage &) for an example of pyramid construction. +*/ +template void vpImage::doubleSizeImage(vpImage &res) +{ + const unsigned int magic_2 = 2; + unsigned int h = height * magic_2; + unsigned int w = width * magic_2; + + res.resize(h, w); + + for (unsigned int i = 0; i < h; ++i) { + for (unsigned int j = 0; j < w; ++j) { + res[i][j] = (*this)[i >> 1][j >> 1]; + } + } + + /* + A B C + E F G + H I J + A C H J are pixels from original image + B E G I are interpolated pixels + */ + + // interpolate pixels B and I + for (unsigned int i = 0; i < h; i += magic_2) { + for (unsigned int j = 1; j < (w - 1); j += magic_2) { + res[i][j] = static_cast(0.5 * ((*this)[i >> 1][j >> 1] + (*this)[i >> 1][(j >> 1) + 1])); + } + } + + // interpolate pixels E and G + for (unsigned int i = 1; i < (h - 1); i += magic_2) { + for (unsigned int j = 0; j < w; j += magic_2) { + res[i][j] = static_cast(0.5 * ((*this)[i >> 1][j >> 1] + (*this)[(i >> 1) + 1][j >> 1])); + } + } + + // interpolate pixel F + for (unsigned int i = 1; i < (h - 1); i += magic_2) { + for (unsigned int j = 1; j < (w - 1); j += magic_2) { + res[i][j] = static_cast(0.25 * ((*this)[i >> 1][j >> 1] + (*this)[i >> 1][(j >> 1) + 1] + + (*this)[(i >> 1) + 1][j >> 1] + (*this)[(i >> 1) + 1][(j >> 1) + 1])); + } + } +} + +/*! + Operation C = *this - B. + + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpImage A(288, 384); + vpImage B(288, 384); + vpImage C; + + A = 128; + B = 120; + + A.sub(B, C); // C = A - B + + return 0; + } + \endcode + + The result is placed in the third parameter C and not returned. + A new image won't be allocated for every use of the function + (Speed gain if used many times with the same result matrix size). + + \exception vpException::memoryAllocationError If the images size differ. + + \sa operator-() +*/ +template void vpImage::sub(const vpImage &B, vpImage &C) const +{ + + try { + if ((this->getHeight() != C.getHeight()) || (this->getWidth() != C.getWidth())) { + C.resize(this->getHeight(), this->getWidth()); + } + } + catch (const vpException &me) { + std::cout << me << std::endl; + throw; + } + + if ((this->getWidth() != B.getWidth()) || (this->getHeight() != B.getHeight())) { + throw(vpException(vpException::memoryAllocationError, "vpImage mismatch in vpImage/vpImage subtraction")); + } + + unsigned int this_width = this->getWidth(); + unsigned int this_height = this->getHeight(); + for (unsigned int i = 0; i < (this_width * this_height); ++i) { + *(C.bitmap + i) = *(bitmap + i) - *(B.bitmap + i); + } +} + +/*! + Operation C = A - B. + + The result is placed in the third parameter C and not returned. + A new image won't be allocated for every use of the function + (Speed gain if used many times with the same result matrix size). + + \exception vpException::memoryAllocationError If the images size differ. + + \sa operator-() +*/ +template void vpImage::sub(const vpImage &A, const vpImage &B, vpImage &C) const +{ + + try { + if ((A.getHeight() != C.getHeight()) || (A.getWidth() != C.getWidth())) { + C.resize(A.getHeight(), A.getWidth()); + } + } + catch (const vpException &me) { + std::cout << me << std::endl; + throw; + } + + if ((A.getWidth() != B.getWidth()) || (A.getHeight() != B.getHeight())) { + throw(vpException(vpException::memoryAllocationError, "vpImage mismatch in vpImage/vpImage subtraction ")); + } + + unsigned int a_width = A.getWidth(); + unsigned int a_height = A.getHeight(); + for (unsigned int i = 0; i < (a_width * a_height); ++i) { + *(C.bitmap + i) = *(A.bitmap + i) - *(B.bitmap + i); + } +} + +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageCircle.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageCircle.h new file mode 100644 index 00000000..a1af5c80 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageCircle.h @@ -0,0 +1,162 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Image circle, i.e. circle in the image space. + */ + +/*! + * \file vpImageCircle.h + * \brief Image circle, i.e. circle in the image space. + */ + +#ifndef VP_IMAGE_CIRCLE_H +#define VP_IMAGE_CIRCLE_H + +#include +#include +#include +#include +#include + +#if defined(VISP_HAVE_OPENCV) +#include +#endif + +BEGIN_VISP_NAMESPACE +/** + * \brief Class that defines a 2D circle in an image. +*/ +class VISP_EXPORT vpImageCircle +{ +public: + /*! + * Default constructor. + */ + vpImageCircle(); + + /*! + * Constructor from a center and radius. + */ + vpImageCircle(const vpImagePoint ¢er, const float &radius); + +#ifdef HAVE_OPENCV_CORE + /*! + * Constructor from an OpenCV vector that contains [center_x, center_y, radius]. + */ + vpImageCircle(const cv::Vec3f &vec); +#endif + + /*! + * Compute the angular coverage, in terms of radians, that is contained in the Region of Interest (RoI). + * \sa computeArcLengthInRoI(), computeArcLengthInRoI(const vpRect &roi) + * \param[in] roi The rectangular RoI in which we want to know the number of pixels of the circle that are contained. + * \param[in] roundingTolerance The tolerance on the angle when the angle is close to a negative multiple of 2 * M_PI_FLOAT. + * \return Returns angular coverage of a circle in a ROI as an angle value in radians. + * More precisely, it returns 2.f * M_PI for a circle that is fully visible in the RoI, or the sum of the angles + * of the arc(s) that is(are) visible in the RoI. + */ + float computeAngularCoverageInRoI(const vpRect &roi, const float &roundingTolerance = 0.001f) const; + + /*! + * Compute the arc length, in terms of number of pixels, that is contained in the Region of Interest (RoI). + * \sa computeAngularCoverageInRoI(), computeAngularCoverageInRoI(const vpRect &roi) + * \param[in] roi The rectangular RoI in which we want to know the number of pixels of the circle that are contained. + * \param[in] roundingTolerance The tolerance on the angle when the angle is close to 2.f * M_PI_FLOAT . + * \return The number of pixels of the circle that are contained in the RoI. + */ + float computeArcLengthInRoI(const vpRect &roi, const float &roundingTolerance = 0.001f) const; + + /** + * \brief Count the number of pixels of the circle whose value in the mask is true. + * + * \param mask A mask where true indicates that a pixel must be taken into account and false + * that it must be ignored. + * \return unsigned int The number of pixels in the mask. + */ + unsigned int computePixelsInMask(const vpImage &mask) const; + + /*! + * Get the center of the image (2D) circle + * \return The center of the image (2D) circle. + */ + vpImagePoint getCenter() const; + + /*! + * Get the radius of the image (2D) circle. + * \return The radius of the image (2D) circle. + */ + float getRadius() const; + + /*! + * Compute the bounding box, in the image, of the image (2D) circle. + * \return the 2D circle bounding box. + */ + vpRect getBBox() const; + + /*! + * Compute the normalized moment \f$n_{20}\f$ of the image (2D) circle. + * \return The normalized moment \f$n_{20}\f$. + */ + float get_n20() const; + + /*! + * Compute the normalized moment \f$n_{02}\f$ of the image (2D) circle. + * \return The normalized moment \f$n_{02}\f$. + */ + float get_n02() const; + + /*! + * Compute the normalized moment \f$n_{11}\f$ of the image (2D) circle. + * \return The normalized moment \f$n_{11}\f$. + */ + float get_n11() const; + + /** + * \brief Returns true if the image point belongs to the circle and + * false otherwise. + * + * \param[in] ip The image point that we are interested in. + * \return true The image point belongs to the circle. + * \return false Otherwise. + */ + inline bool isInside(const vpImagePoint &ip) const + { + double radius = static_cast(m_radius); + double squaredRadius = radius * radius; + double distance = vpImagePoint::sqrDistance(m_center, ip); + return (distance <= squaredRadius); + } + +private: + vpImagePoint m_center; + float m_radius; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageConvert.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageConvert.h new file mode 100644 index 00000000..5c96f3a3 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageConvert.h @@ -0,0 +1,658 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Convert image types. + */ + +/*! + \file vpImageConvert.h + \brief Convert image types +*/ + +#ifndef VP_IMAGE_CONVERT_H +#define VP_IMAGE_CONVERT_H + +#include + +// image +#include +#include +// color +#include +#include + +#if defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) +#include +#if (VISP_HAVE_OPENCV_VERSION < 0x050000) +#include +#endif +#endif + +#ifdef VISP_HAVE_YARP +#include +#endif + +#if defined(_WIN32) + +// Mute warning with clang-cl +// warning : non-portable path to file ''; specified path differs in case from file name on disk [-Wnonportable-system-include-path] +// warning : non-portable path to file ''; specified path differs in case from file name on disk [-Wnonportable-system-include-path] +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wnonportable-system-include-path" +#endif + +// Include WinSock2.h before windows.h to ensure that winsock.h is not +// included by windows.h since winsock.h and winsock2.h are incompatible +#include +#include + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif +#endif + +#ifdef VISP_HAVE_OPENMP +#include +#endif + +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_COMMON) && defined(VISP_HAVE_THREADS) +#include +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) +#include +#endif +#include +#include +#include + +#include +#include +#include +#endif + +BEGIN_VISP_NAMESPACE +/*! + \class vpImageConvert + + \ingroup group_core_image + + Convert image types. + + The following example available in tutorial-image-converter.cpp shows how to + convert an OpenCV cv::Mat image into a vpImage: + + \include tutorial-image-converter.cpp + +*/ +class VISP_EXPORT vpImageConvert +{ + +public: + static void createDepthHistogram(const vpImage &src_depth, vpImage &dest_rgba); + static void createDepthHistogram(const vpImage &src_depth, vpImage &dest_depth); + + static void createDepthHistogram(const vpImage &src_depth, vpImage &dest_depth); + static void createDepthHistogram(const vpImage &src_depth, vpImage &dest_depth); + + static void convert(const vpImage &src, vpImage &dest); + static void convert(const vpImage &src, vpImage &dest, unsigned int nThreads = 0); + + static void convert(const vpImage &src, vpImage &dest); + static void convert(const vpImage &src, vpImage &dest); + static void convert(const vpImage &src, vpImage &dest); + + static void convert(const vpImage &src, vpImage &dest); + static void convert(const vpImage &src, vpImage &dest); + + static void convert(const vpImage &src, vpImage &dest, unsigned char bitshift = 8); + static void convert(const vpImage &src, vpImage &dest, unsigned char bitshift = 8); + + /*! + Make a copy of an image. + \param src : source image. + \param dest : destination image. + */ + template static void convert(const vpImage &src, vpImage &dest) { dest = src; } + +#if defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) + static void convert(const cv::Mat &src, vpImage &dest, bool flip = false); + static void convert(const cv::Mat &src, vpImage &dest, bool flip = false, unsigned int nThreads = 0); + static void convert(const cv::Mat &src, vpImage &dest, bool flip = false); + static void convert(const cv::Mat &src, vpImage &dest, bool flip = false); + static void convert(const cv::Mat &src, vpImage &dest, bool flip = false); + static void convert(const cv::Mat &src, vpImage &dest, bool flip = false); + static void convert(const vpImage &src, cv::Mat &dest); + static void convert(const vpImage &src, cv::Mat &dest, bool copyData = true); + static void convert(const vpImage &src, cv::Mat &dest, bool copyData = true); + static void convert(const vpImage &src, cv::Mat &dest, bool copyData = true); + static void convert(const vpImage &src, cv::Mat &dest); +#endif +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + template + static void convert(const vpImage &src, vpImage> &dest); + + template + static void convert(const vpImage> &src, vpImage &dest); + + template + static typename std::enable_if::value, void>::type convert(const vpImage> &src, vpImage> &dest) + { + const int height = src.getHeight(), width = src.getWidth(); + const int size = height * width; + dest.resize(height, width); +#ifdef VISP_HAVE_OPENMP +#pragma omp parallel for +#endif + for (int i = 0; i < size; ++i) { + dest.bitmap[i].buildFrom(src.bitmap[i]); + } + } +#endif + +#ifdef VISP_HAVE_YARP + static void convert(const vpImage &src, yarp::sig::ImageOf *dest, + bool copyData = true); + static void convert(const yarp::sig::ImageOf *src, vpImage &dest, + bool copyData = true); + + static void convert(const vpImage &src, yarp::sig::ImageOf *dest, bool copyData = true); + static void convert(const yarp::sig::ImageOf *src, vpImage &dest, bool copyData = true); + + static void convert(const vpImage &src, yarp::sig::ImageOf *dest); + static void convert(const yarp::sig::ImageOf *src, vpImage &dest); +#endif + +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_COMMON) && defined(VISP_HAVE_THREADS) +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + template + static typename std::enable_if< std::is_same::value || std::is_same::value, int>::type +#else + template + static int +#endif + /** + * \ingroup group_core_bridges + * \brief Convert a raw depth image into a pcl::PointCloud that has no texture. + * + * \param[in] depth_raw Raw depth image. + * \param[in] depth_scale Depth scale to convert the raw depth image into meters. + * \param[in] cam_depth The depth camera parameters. + * \param[out] pointcloud A pointer towards the pcl::PointCloud that has no texture. + * \param[in] pointcloud_mutex Optional, if set a pointer towards the mutex that protects the point cloud. + * \param[in] depth_mask Optional, if set a pointer towards a binary image that indicates if the point must be + * considered or not. (Either true or a value different from 0 to keep the point, false or 0 to discard it). + * \param[in] Z_min The minimum depth to keep the point. + * \param[in] Z_max The maximum depth to keep the point. + * + * \sa To see how to use it in the context of color segmentation on a point-cloud , \ref tutorial-hsv-segmentation-pcl + */ + depthToPointCloud(const vpImage &depth_raw, + float depth_scale, const vpCameraParameters &cam_depth, + pcl::PointCloud::Ptr pointcloud, + std::mutex *pointcloud_mutex = nullptr, + const vpImage *depth_mask = nullptr, float Z_min = 0.2, float Z_max = 2.5) + { + int size = static_cast(depth_raw.getSize()); + unsigned int width = depth_raw.getWidth(); + unsigned int height = depth_raw.getHeight(); + int pcl_size = 0; + const unsigned int index_0 = 0; + const unsigned int index_1 = 1; + const unsigned int index_2 = 2; + + if (depth_mask) { + if ((width != depth_mask->getWidth()) || (height != depth_mask->getHeight())) { + throw(vpImageException(vpImageException::notInitializedError, "Depth image and mask size differ")); + } + if (pointcloud_mutex) { + pointcloud_mutex->lock(); + } + pointcloud->clear(); +#if defined(VISP_HAVE_OPENMP) + std::mutex mutex; +#pragma omp parallel for +#endif + for (int p = 0; p < size; ++p) { + if (depth_mask->bitmap[p]) { + if (static_cast(depth_raw.bitmap[p])) { + float Z = static_cast(depth_raw.bitmap[p]) * depth_scale; + if (Z < Z_max) { + double x = 0; + double y = 0; + unsigned int j = p % width; + unsigned int i = (p - j) / width; + vpPixelMeterConversion::convertPoint(cam_depth, j, i, x, y); + vpColVector point_3D({ x * Z, y * Z, Z }); + if (point_3D[index_2] > Z_min) { +#if defined(VISP_HAVE_OPENMP) + std::lock_guard lock(mutex); +#endif + pointcloud->push_back(pcl::PointXYZ(point_3D[index_0], point_3D[index_1], point_3D[index_2])); + } + } + } + } + } + pcl_size = pointcloud->size(); + if (pointcloud_mutex) { + pointcloud_mutex->unlock(); + } + } + else { + if (pointcloud_mutex) { + pointcloud_mutex->lock(); + } + pointcloud->clear(); +#if defined(VISP_HAVE_OPENMP) + std::mutex mutex; +#pragma omp parallel for +#endif + for (int p = 0; p < size; ++p) { + if (static_cast(depth_raw.bitmap[p])) { + float Z = static_cast(depth_raw.bitmap[p]) * depth_scale; + if (Z < Z_max) { + double x = 0; + double y = 0; + unsigned int j = p % width; + unsigned int i = (p - j) / width; + vpPixelMeterConversion::convertPoint(cam_depth, j, i, x, y); + vpColVector point_3D({ x * Z, y * Z, Z, 1 }); + if (point_3D[index_2] >= 0.1) { +#if defined(VISP_HAVE_OPENMP) + std::lock_guard lock(mutex); +#endif + pointcloud->push_back(pcl::PointXYZ(point_3D[index_0], point_3D[index_1], point_3D[index_2])); + } + } + } + } + pcl_size = pointcloud->size(); + if (pointcloud_mutex) { + pointcloud_mutex->unlock(); + } + } + + return pcl_size; + } + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + template + static typename std::enable_if< std::is_same::value || std::is_same::value, int>::type +#else + template + static int +#endif + /** + * \ingroup group_core_bridges + * \brief Convert a raw depth image in a textured pcl::PointCloud using a vpImage that is aligned with the + * raw depth image to get the texture. + * + * \param[in] color The color image that gives the texture of the points. + * \param[in] depth_raw Raw depth image. + * \param[in] depth_scale Depth scale to convert the raw depth image into meters. + * \param[in] cam_depth The depth camera parameters. + * \param[out] pointcloud A pointer towards the pcl::PointCloud that has no texture. + * \param[in] pointcloud_mutex Optional, if set a pointer towards the mutex that protects the point cloud. + * \param[in] depth_mask Optional, if set a pointer towards a binary image that indicates if the point must be + * considered or not. (Either true or a value different from 0 to keep the point, false or 0 to discard it). + * \param[in] Z_min The minimum depth to keep the point. + * \param[in] Z_max The maximum depth to keep the point. + * + * \sa To see how to use it in the context of color segmentation on a point-cloud , \ref tutorial-hsv-segmentation-pcl + */ + depthToPointCloud(const vpImage &color, const vpImage &depth_raw, + float depth_scale, const vpCameraParameters &cam_depth, + pcl::PointCloud::Ptr pointcloud, + std::mutex *pointcloud_mutex = nullptr, + const vpImage *depth_mask = nullptr, float Z_min = 0.2, float Z_max = 2.5) + { + int size = static_cast(depth_raw.getSize()); + unsigned int width = depth_raw.getWidth(); + unsigned int height = depth_raw.getHeight(); + int pcl_size = 0; + const unsigned int index_0 = 0; + const unsigned int index_1 = 1; + const unsigned int index_2 = 2; + + if (depth_mask) { + if ((width != depth_mask->getWidth()) || (height != depth_mask->getHeight())) { + throw(vpImageException(vpImageException::notInitializedError, "Depth image and mask size differ")); + } + if (pointcloud_mutex) { + pointcloud_mutex->lock(); + } + pointcloud->clear(); +#if defined(VISP_HAVE_OPENMP) + std::mutex mutex; +#pragma omp parallel for +#endif + for (int p = 0; p < size; ++p) { + if (depth_mask->bitmap[p]) { + if (static_cast(depth_raw.bitmap[p])) { + float Z = static_cast(depth_raw.bitmap[p]) * depth_scale; + if (Z < Z_max) { + double x = 0; + double y = 0; + unsigned int j = p % width; + unsigned int i = (p - j) / width; + vpPixelMeterConversion::convertPoint(cam_depth, j, i, x, y); + vpColVector point_3D({ x * Z, y * Z, Z }); + if (point_3D[index_2] > Z_min) { +#if defined(VISP_HAVE_OPENMP) + std::lock_guard lock(mutex); +#endif +#if (VISP_HAVE_PCL_VERSION >= 0x010E01) // 1.14.1 + pointcloud->push_back(pcl::PointXYZRGB(point_3D[index_0], point_3D[index_1], point_3D[index_2], + color.bitmap[p].R, color.bitmap[p].G, color.bitmap[p].B)); +#else + pcl::PointXYZRGB pt(color.bitmap[p].R, color.bitmap[p].G, color.bitmap[p].B); + pt.x = point_3D[index_0]; + pt.y = point_3D[index_1]; + pt.z = point_3D[index_2]; + pointcloud->push_back(pcl::PointXYZRGB(pt)); +#endif + } + } + } + } + } + pcl_size = pointcloud->size(); + if (pointcloud_mutex) { + pointcloud_mutex->unlock(); + } + } + else { + if (pointcloud_mutex) { + pointcloud_mutex->lock(); + } + pointcloud->clear(); +#if defined(VISP_HAVE_OPENMP) + std::mutex mutex; +#pragma omp parallel for +#endif + for (int p = 0; p < size; ++p) { + if (static_cast(depth_raw.bitmap[p])) { + float Z = static_cast(depth_raw.bitmap[p]) * depth_scale; + if (Z < Z_max) { + double x = 0; + double y = 0; + unsigned int j = p % width; + unsigned int i = (p - j) / width; + vpPixelMeterConversion::convertPoint(cam_depth, j, i, x, y); + vpColVector point_3D({ x * Z, y * Z, Z, 1 }); + if (point_3D[index_2] >= 0.1) { +#if defined(VISP_HAVE_OPENMP) + std::lock_guard lock(mutex); +#endif +#if (VISP_HAVE_PCL_VERSION >= 0x010E01) // 1.14.1 + pointcloud->push_back(pcl::PointXYZRGB(point_3D[index_0], point_3D[index_1], point_3D[index_2], + color.bitmap[p].R, color.bitmap[p].G, color.bitmap[p].B)); +#else + pcl::PointXYZRGB pt(color.bitmap[p].R, color.bitmap[p].G, color.bitmap[p].B); + pt.x = point_3D[index_0]; + pt.y = point_3D[index_1]; + pt.z = point_3D[index_2]; + pointcloud->push_back(pcl::PointXYZRGB(pt)); +#endif + } + } + } + } + pcl_size = pointcloud->size(); + if (pointcloud_mutex) { + pointcloud_mutex->unlock(); + } + } + + return pcl_size; + } +#endif + + static void split(const vpImage &src, vpImage *pR, vpImage *pG, + vpImage *pB, vpImage *pa = nullptr); + + static void merge(const vpImage *R, const vpImage *G, const vpImage *B, + const vpImage *a, vpImage &RGBa); + + /*! + Converts a yuv pixel value in rgb format. + + \param y Y component of a pixel. + \param u U component of a pixel. + \param v V component of a pixel. + \param r Red component from the YUV coding format. This value is computed + using: + \f[ r = 0.9999695*y - 0.0009508*(u-128) + 1.1359061*(v-128) \f] + \param g Green component from the YUV coding format. This value is + computed using: \f[g = 0.9999695*y - 0.3959609*(u-128) - 0.5782955*(v-128) \f] + \param b Blue component from the YUV coding format. This value is + computed using: \f[b = 0.9999695*y + 2.04112*(u-128) - 0.0016314*(v-128) \f] + */ + static inline void YUVToRGB(unsigned char y, unsigned char u, unsigned char v, unsigned char &r, unsigned char &g, + unsigned char &b) + { + double dr, dg, db; + dr = floor(((0.9999695 * y) - (0.0009508 * (u - 128))) + (1.1359061 * (v - 128))); + dg = floor(((0.9999695 * y) - (0.3959609 * (u - 128))) - (0.5782955 * (v - 128))); + db = floor(((0.9999695 * y) + (2.04112 * (u - 128))) - (0.0016314 * (v - 128))); + + dr = dr < 0. ? 0. : dr; + dg = dg < 0. ? 0. : dg; + db = db < 0. ? 0. : db; + dr = dr > 255. ? 255. : dr; + dg = dg > 255. ? 255. : dg; + db = db > 255. ? 255. : db; + + r = static_cast(dr); + g = static_cast(dg); + b = static_cast(db); + } + static void YUYVToRGBa(unsigned char *yuyv, unsigned char *rgba, unsigned int width, unsigned int height); + static void YUYVToRGB(unsigned char *yuyv, unsigned char *rgb, unsigned int width, unsigned int height); + static void YUYVToGrey(unsigned char *yuyv, unsigned char *grey, unsigned int size); + static void YUV411ToRGBa(unsigned char *yuv, unsigned char *rgba, unsigned int size); + static void YUV411ToRGB(unsigned char *yuv, unsigned char *rgb, unsigned int size); + static void YUV411ToGrey(unsigned char *yuv, unsigned char *grey, unsigned int size); + static void YUV422ToRGBa(unsigned char *yuv, unsigned char *rgba, unsigned int size); + static void YUV422ToRGB(unsigned char *yuv, unsigned char *rgb, unsigned int size); + static void YUV422ToGrey(unsigned char *yuv, unsigned char *grey, unsigned int size); + static void YUV420ToRGBa(unsigned char *yuv, unsigned char *rgba, unsigned int width, unsigned int height); + static void YUV420ToRGB(unsigned char *yuv, unsigned char *rgb, unsigned int width, unsigned int height); + static void YUV420ToGrey(unsigned char *yuv, unsigned char *grey, unsigned int size); + + static void YUV444ToRGBa(unsigned char *yuv, unsigned char *rgba, unsigned int size); + static void YUV444ToRGB(unsigned char *yuv, unsigned char *rgb, unsigned int size); + static void YUV444ToGrey(unsigned char *yuv, unsigned char *grey, unsigned int size); + + static void YV12ToRGBa(unsigned char *yuv, unsigned char *rgba, unsigned int width, unsigned int height); + static void YV12ToRGB(unsigned char *yuv, unsigned char *rgb, unsigned int width, unsigned int height); + static void YVU9ToRGBa(unsigned char *yuv, unsigned char *rgba, unsigned int width, unsigned int height); + static void YVU9ToRGB(unsigned char *yuv, unsigned char *rgb, unsigned int width, unsigned int height); + static void RGBToRGBa(unsigned char *rgb, unsigned char *rgba, unsigned int size); + static void RGBaToRGB(unsigned char *rgba, unsigned char *rgb, unsigned int size); + + static void RGBToGrey(unsigned char *rgb, unsigned char *grey, unsigned int width, unsigned int height, + bool flip = false); + static void RGBToGrey(unsigned char *rgb, unsigned char *grey, unsigned int size); + static void RGBaToGrey(unsigned char *rgba, unsigned char *grey, unsigned int width, unsigned int height, + unsigned int nThreads = 0); + static void RGBaToGrey(unsigned char *rgba, unsigned char *grey, unsigned int size); + + static void RGBToRGBa(unsigned char *rgb, unsigned char *rgba, unsigned int width, unsigned int height, + bool flip = false); + + static void GreyToRGBa(unsigned char *grey, unsigned char *rgba, unsigned int width, unsigned int height); + static void GreyToRGBa(unsigned char *grey, unsigned char *rgba, unsigned int size); + static void GreyToRGB(unsigned char *grey, unsigned char *rgb, unsigned int size); + + static void BGRToRGBa(unsigned char *bgr, unsigned char *rgba, unsigned int width, unsigned int height, + bool flip = false); + + static void BGRToGrey(unsigned char *bgr, unsigned char *grey, unsigned int width, unsigned int height, + bool flip = false, unsigned int nThreads = 0); + + static void BGRaToGrey(unsigned char *bgra, unsigned char *grey, unsigned int width, unsigned int height, + bool flip = false, unsigned int nThreads = 0); + static void BGRaToRGBa(unsigned char *bgra, unsigned char *rgba, unsigned int width, unsigned int height, + bool flip = false); + + static void YCbCrToRGB(unsigned char *ycbcr, unsigned char *rgb, unsigned int size); + static void YCbCrToRGBa(unsigned char *ycbcr, unsigned char *rgb, unsigned int size); + static void YCbCrToGrey(unsigned char *ycbcr, unsigned char *grey, unsigned int size); + static void YCrCbToRGB(unsigned char *ycrcb, unsigned char *rgb, unsigned int size); + static void YCrCbToRGBa(unsigned char *ycrcb, unsigned char *rgb, unsigned int size); + static void MONO16ToGrey(unsigned char *grey16, unsigned char *grey, unsigned int size); + static void MONO16ToRGBa(unsigned char *grey16, unsigned char *rgba, unsigned int size); + + static void HSVToRGBa(const double *hue, const double *saturation, const double *value, unsigned char *rgba, + unsigned int size); + static void HSVToRGBa(const unsigned char *hue, const unsigned char *saturation, const unsigned char *value, + unsigned char *rgba, unsigned int size, bool h_full = true); + static void RGBaToHSV(const unsigned char *rgba, double *hue, double *saturation, double *value, unsigned int size); + static void RGBaToHSV(const unsigned char *rgba, unsigned char *hue, unsigned char *saturation, unsigned char *value, + unsigned int size, bool h_full = true); + + static void HSVToRGB(const double *hue, const double *saturation, const double *value, unsigned char *rgb, + unsigned int size); + static void HSVToRGB(const unsigned char *hue, const unsigned char *saturation, const unsigned char *value, + unsigned char *rgb, unsigned int size, bool h_full = true); + static void RGBToHSV(const unsigned char *rgb, double *hue, double *saturation, double *value, unsigned int size); + static void RGBToHSV(const unsigned char *rgb, unsigned char *hue, unsigned char *saturation, unsigned char *value, + unsigned int size, bool h_full = true); + +#ifndef VISP_SKIP_BAYER_CONVERSION + static void demosaicBGGRToRGBaBilinear(const uint8_t *bggr, uint8_t *rgba, unsigned int width, unsigned int height, + unsigned int nThreads = 0); + static void demosaicBGGRToRGBaBilinear(const uint16_t *bggr, uint16_t *rgba, unsigned int width, unsigned int height, + unsigned int nThreads = 0); + + static void demosaicGBRGToRGBaBilinear(const uint8_t *gbrg, uint8_t *rgba, unsigned int width, unsigned int height, + unsigned int nThreads = 0); + static void demosaicGBRGToRGBaBilinear(const uint16_t *gbrg, uint16_t *rgba, unsigned int width, unsigned int height, + unsigned int nThreads = 0); + + static void demosaicGRBGToRGBaBilinear(const uint8_t *grbg, uint8_t *rgba, unsigned int width, unsigned int height, + unsigned int nThreads = 0); + static void demosaicGRBGToRGBaBilinear(const uint16_t *grbg, uint16_t *rgba, unsigned int width, unsigned int height, + unsigned int nThreads = 0); + + static void demosaicRGGBToRGBaBilinear(const uint8_t *rggb, uint8_t *rgba, unsigned int width, unsigned int height, + unsigned int nThreads = 0); + static void demosaicRGGBToRGBaBilinear(const uint16_t *rggb, uint16_t *rgba, unsigned int width, unsigned int height, + unsigned int nThreads = 0); + + static void demosaicBGGRToRGBaMalvar(const uint8_t *bggr, uint8_t *rgba, unsigned int width, unsigned int height, + unsigned int nThreads = 0); + static void demosaicBGGRToRGBaMalvar(const uint16_t *bggr, uint16_t *rgba, unsigned int width, unsigned int height, + unsigned int nThreads = 0); + + static void demosaicGBRGToRGBaMalvar(const uint8_t *gbrg, uint8_t *rgba, unsigned int width, unsigned int height, + unsigned int nThreads = 0); + static void demosaicGBRGToRGBaMalvar(const uint16_t *gbrg, uint16_t *rgba, unsigned int width, unsigned int height, + unsigned int nThreads = 0); + + static void demosaicGRBGToRGBaMalvar(const uint8_t *grbg, uint8_t *rgba, unsigned int width, unsigned int height, + unsigned int nThreads = 0); + static void demosaicGRBGToRGBaMalvar(const uint16_t *grbg, uint16_t *rgba, unsigned int width, unsigned int height, + unsigned int nThreads = 0); + + static void demosaicRGGBToRGBaMalvar(const uint8_t *rggb, uint8_t *rgba, unsigned int width, unsigned int height, + unsigned int nThreads = 0); + static void demosaicRGGBToRGBaMalvar(const uint16_t *rggb, uint16_t *rgba, unsigned int width, unsigned int height, + unsigned int nThreads = 0); +#endif + +private: + static void computeYCbCrLUT(); + + static void HSV2RGB(const double *hue, const double *saturation, const double *value, unsigned char *rgba, + unsigned int size, unsigned int step); + static void HSV2RGB(const unsigned char *hue, const unsigned char *saturation, const unsigned char *value, unsigned char *rgba, + unsigned int size, unsigned int step, bool h_full); + static void RGB2HSV(const unsigned char *rgb, double *hue, double *saturation, double *value, unsigned int size, + unsigned int step); + static void RGB2HSV(const unsigned char *rgb, unsigned char *hue, unsigned char *saturation, unsigned char *value, + unsigned int size, unsigned int step, bool h_full); + +private: + static bool YCbCrLUTcomputed; + static int vpCrr[256]; + static int vpCgb[256]; + static int vpCgr[256]; + static int vpCbb[256]; +}; + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) +/** + * \brief Convert a RGBa image into a HSV image. + * + * \tparam T The type of the channels of the vpHSV pixels. + * \tparam useFullScale True if vpHSV uses unsigned char and the full range [0; 255], false if vpHSV uses unsigned char and the limited range [0; 180]. + * \param[in] src The RGBa image. + * \param[in] dest The HSV image. + */ +template +void vpImageConvert::convert(const vpImage &src, vpImage> &dest) +{ + const int height = src.getHeight(), width = src.getWidth(); + const int size = height * width; + dest.resize(height, width); +#ifdef VISP_HAVE_OPENMP +#pragma omp parallel for +#endif + for (int i = 0; i < size; ++i) { + dest.bitmap[i].buildFrom(src.bitmap[i]); + } +} + +/** + * \brief Convert an HSV image into a RGBa image. + * + * \tparam T The type of the channels of the vpHSV pixels. + * \tparam useFullScale True if vpHSV uses unsigned char and the full range [0; 255], false if vpHSV uses unsigned char and the limited range [0; 180]. + * \param[in] src The HSV image. + * \param[in] dest The RGBa image. + */ +template +void vpImageConvert::convert(const vpImage> &src, vpImage &dest) +{ + const int height = src.getHeight(), width = src.getWidth(); + const int size = height * width; + dest.resize(height, width); +#ifdef VISP_HAVE_OPENMP +#pragma omp parallel for +#endif + for (int i = 0; i < size; ++i) { + dest.bitmap[i].buildFrom(src.bitmap[i]); + } +} +#endif +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageDraw.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageDraw.h new file mode 100644 index 00000000..9606c6ee --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageDraw.h @@ -0,0 +1,128 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Drawing functions. + */ + +/*! + \file vpImageDraw.h + + \brief Drawing functions for image. +*/ + +#ifndef _vpImageDraw_h_ +#define _vpImageDraw_h_ + +#include +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpImageDraw + + \ingroup group_core_image + + \brief Drawing functions for image. + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in inserting basic drawings in an image, you may have a look at:
+ + - \ref tutorial-basic-drawings +*/ +class VISP_EXPORT vpImageDraw +{ + +public: + static void drawArrow(vpImage &I, const vpImagePoint &ip1, const vpImagePoint &ip2, + unsigned char color, unsigned int w = 4, unsigned int h = 2, unsigned int thickness = 1); + static void drawArrow(vpImage &I, const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color, + unsigned int w = 4, unsigned int h = 2, unsigned int thickness = 1); + + static void drawCircle(vpImage &I, const vpImageCircle &circle, + unsigned char color, unsigned int thickness = 1); + static void drawCircle(vpImage &I, const vpImagePoint ¢er, unsigned int radius, + unsigned char color, unsigned int thickness = 1); + static void drawCircle(vpImage &I, const vpImageCircle &circle, + const vpColor &color, unsigned int thickness = 1); + static void drawCircle(vpImage &I, const vpImagePoint ¢er, unsigned int radius, const vpColor &color, + unsigned int thickness = 1); + + static void drawCross(vpImage &I, const vpImagePoint &ip, unsigned int size, unsigned char color, + unsigned int thickness = 1); + static void drawCross(vpImage &I, const vpImagePoint &ip, unsigned int size, const vpColor &color, + unsigned int thickness = 1); + + static void drawDottedLine(vpImage &I, const vpImagePoint &ip1, const vpImagePoint &ip2, + unsigned char color, unsigned int thickness = 1); + static void drawDottedLine(vpImage &I, const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color, + unsigned int thickness = 1); + + static void drawEllipse(vpImage &I, const vpImagePoint ¢er, double coef1, double coef2, + double coef3, bool use_normalized_centered_moments, unsigned char color, + double smallalpha = 0, double highalpha = 2 * M_PI, unsigned int thickness = 1); + static void drawEllipse(vpImage &I, const vpImagePoint ¢er, double coef1, double coef2, double coef3, + bool use_normalized_centered_moments, const vpColor &color, double smallalpha = 0, + double highalpha = 2 * M_PI, unsigned int thickness = 1); + + static void drawFrame(vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + double size, unsigned char color, unsigned int thickness = 1, + const vpImagePoint &offset = vpImagePoint(0, 0)); + static void drawFrame(vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, double size, + const vpColor &color = vpColor::none, unsigned int thickness = 1, + const vpImagePoint &offset = vpImagePoint(0, 0)); + + static void drawLine(vpImage &I, const vpImagePoint &ip1, const vpImagePoint &ip2, unsigned char color, + unsigned int thickness = 1); + static void drawLine(vpImage &I, const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color, + unsigned int thickness = 1); + + static void drawPoint(vpImage &I, const vpImagePoint &ip, unsigned char color, + unsigned int thickness = 1); + static void drawPoint(vpImage &I, const vpImagePoint &ip, const vpColor &color, unsigned int thickness = 1); + + static void drawPolygon(vpImage &I, const std::vector &vip, unsigned char color, + unsigned int thickness = 1, bool closed = true); + static void drawPolygon(vpImage &I, const std::vector &vip, const vpColor &color, + unsigned int thickness = 1, bool closed = true); + + static void drawRectangle(vpImage &I, const vpRect &rectangle, unsigned char color, bool fill = false, + unsigned int thickness = 1); + static void drawRectangle(vpImage &I, const vpRect &rectangle, const vpColor &color, bool fill = false, + unsigned int thickness = 1); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageException.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageException.h new file mode 100644 index 00000000..8fa26bde --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageException.h @@ -0,0 +1,94 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Exceptions that can be emitted by the vpImage class and its derivatives. + */ + +/*! + * \file vpImageException.h + * \brief error that can be emitted by the vpImage class and its derivatives + */ + +#ifndef VP_IMAGE_EXCEPTION_H +#define VP_IMAGE_EXCEPTION_H + +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpImageException + * \ingroup group_core_debug + * \brief Error that can be emitted by the vpImage class and its derivatives. +*/ +class VISP_EXPORT vpImageException : public vpException +{ +public: + /*! + * \brief Lists the possible error than can be emitted while calling + * vpImage member + */ + enum errorImageCodeEnum + { + ioError, //!< Image io error + noFileNameError, //!< Image file name error + notInitializedError, //!< Image not initialized + incorrectInitializationError, //!< Wrong image initialization + notInTheImage //!< Pixel not in the image + }; + +public: + /*! + * Constructor. + */ + vpImageException(int id, const char *format, ...) + { + this->code = id; + va_list args; + va_start(args, format); + setMessage(format, args); + va_end(args); + } + + /*! + * Constructor. + */ + vpImageException(int id, const std::string &msg) : vpException(id, msg) { } + + /*! + * Constructor. + */ + VP_EXPLICIT vpImageException(int id) : vpException(id) { } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageFilter.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageFilter.h new file mode 100644 index 00000000..a0fc786c --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageFilter.h @@ -0,0 +1,2937 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Various image tools, convolution, ... + */ + +/*! + * \file vpImageFilter.h + * \brief Various image filter, convolution, etc... + */ + +#ifndef VP_IMAGE_FILTER_H +#define VP_IMAGE_FILTER_H + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) +#include +#if (VISP_HAVE_OPENCV_VERSION < 0x050000) +#include +#endif +#endif + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdocumentation" +#endif + +#ifdef VISP_HAVE_OPENMP +#include +#endif + +BEGIN_VISP_NAMESPACE +/*! + * \class vpImageFilter + * + * \ingroup group_core_image + * + * \brief Various image filter, convolution, etc... + * + * Tutorials
+ If you are interested in applying various filters on an image, you may have a look at:
+ + - \ref tutorial-image-filtering +*/ +class VISP_EXPORT vpImageFilter +{ +public: + //! Canny filter backends for the edge detection operations + typedef enum vpCannyBackendType + { + CANNY_OPENCV_BACKEND = 0, //!< Use OpenCV + CANNY_VISP_BACKEND = 1, //!< Use ViSP + CANNY_COUNT_BACKEND = 2 //! Number of supported backends + } vpCannyBackendType; + + static std::string vpCannyBackendTypeList(const std::string &pref = "<", const std::string &sep = " , ", + const std::string &suf = ">"); + + static std::string vpCannyBackendTypeToString(const vpCannyBackendType &type); + + static vpCannyBackendType vpCannyBackendTypeFromString(const std::string &name); + + //! Canny filter and gradient operators to apply on the image before the edge detection stage + typedef enum vpCannyFilteringAndGradientType + { + CANNY_GBLUR_SOBEL_FILTERING = 0, //!< Apply Gaussian blur + Sobel operator on the input image + CANNY_GBLUR_SCHARR_FILTERING = 1, //!< Apply Gaussian blur + Scharr operator on the input image + CANNY_COUNT_FILTERING = 2 //! Number of supported backends + } vpCannyFilteringAndGradientType; + + static std::string vpGetCannyFiltAndGradTypes(const std::string &pref = "<", const std::string &sep = " , ", + const std::string &suf = ">"); + + static std::string vpCannyFiltAndGradTypeToStr(const vpCannyFilteringAndGradientType &type); + + static vpCannyFilteringAndGradientType vpCannyFiltAndGradTypeFromStr(const std::string &name); + + static void canny(const vpImage &I, vpImage &Ic, const unsigned int &gaussianFilterSize, + const float &thresholdCanny, const unsigned int &apertureSobel); + + static void canny(const vpImage &I, vpImage &Ic, const unsigned int &gaussianFilterSize, + const float &lowerThresholdCanny, const float &higherThresholdCanny, + const unsigned int &apertureSobel); + + static void canny(const vpImage &I, vpImage &Ic, const unsigned int &gaussianFilterSize, + const float &lowerThresholdCanny, const float &higherThresholdCanny, + const unsigned int &apertureSobel, const float &gaussianStdev, const float &lowerThresholdRatio, + const float &upperThresholdRatio, const bool &normalizeGradients, + const vpCannyBackendType &cannyBackend, const vpCannyFilteringAndGradientType &cannyFilteringSteps, + const vpImage *p_mask = nullptr); + +#if defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) + static float computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p_cv_dIx, const cv::Mat *p_cv_dIy, + float &lowerThresh, const unsigned int &gaussianKernelSize = 5, + const float &gaussianStdev = 2.f, const unsigned int &apertureGradient = 3, + const float &lowerThresholdRatio = 0.6f, const float &upperThresholdRatio = 0.8f, + const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING); + + static void computePartialDerivatives(const cv::Mat &cv_I, + cv::Mat &cv_dIx, cv::Mat &cv_dIy, + const bool &computeDx = true, const bool &computeDy = true, const bool &normalize = true, + const unsigned int &gaussianKernelSize = 5, const float &gaussianStdev = 2.f, + const unsigned int &apertureGradient = 3, + const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING); +#endif + + /** + * \brief Compute the partial derivatives (i.e. horizontal and vertical gradients) of the input image. + * + * \tparam ImageType Either unsigned char, float or double + * \tparam FilterType Either float or double. + * \param[in] I The input image we want the partial derivatives. + * \param[out] dIx The horizontal partial derivative, i.e. horizontal gradient. + * \param[out] dIy The vertical partial derivative, i.e. vertical gradient. + * \param[in] computeDx Indicate if we must compute the horizontal gradient. + * \param[in] computeDy Indicate if we must compute the vertical gradient. + * \param[in] normalize Indicate if we must normalize the gradient filters. + * \param[in] gaussianKernelSize The size of the kernel of the Gaussian filter used to blur the image. + * If it is non-positive, it is computed from kernel size (`gaussianKernelSize` parameter) as + * \f$\sigma = 0.3*((gaussianKernelSize-1)*0.5 - 1) + 0.8\f$. + * \param[in] gaussianStdev The standard deviation of the Gaussian filter used to blur the image. + * \param[in] apertureGradient The size of the kernel of the gradient filter. + * \param[in] filteringType The type of filters to apply to compute the gradients. + * \param[in] backend The type of backend to use to compute the gradients. + * \param[in] p_mask If different from nullptr, mask indicating which points to consider (true) or to ignore(false). + */ + template + inline static void computePartialDerivatives(const vpImage &I, + vpImage &dIx, vpImage &dIy, + const bool &computeDx = true, const bool &computeDy = true, const bool &normalize = true, + const unsigned int &gaussianKernelSize = 5, const FilterType &gaussianStdev = 2.f, + const unsigned int &apertureGradient = 3, + const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING, + const vpCannyBackendType &backend = CANNY_VISP_BACKEND, + const vpImage *p_mask = nullptr) + { + if (backend == CANNY_OPENCV_BACKEND) { +#if defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) + cv::Mat cv_I, cv_dIx, cv_dIy; + vpImageConvert::convert(I, cv_I); + computePartialDerivatives(cv_I, cv_dIx, cv_dIy, computeDx, computeDy, normalize, gaussianKernelSize, + static_cast(gaussianStdev), apertureGradient, filteringType); + if (computeDx) { + vpImageConvert::convert(cv_dIx, dIx); + } + if (computeDy) { + vpImageConvert::convert(cv_dIy, dIy); + } +#else + throw(vpException(vpException::badValue, "You need to compile ViSP with OpenCV to use CANNY_OPENCV_BACKEND")); +#endif + } + else { + if ((filteringType == CANNY_GBLUR_SCHARR_FILTERING) || (filteringType == CANNY_GBLUR_SOBEL_FILTERING)) { + dIx.resize(I.getHeight(), I.getWidth()); + dIy.resize(I.getHeight(), I.getWidth()); + + // Computing the Gaussian blur + gradients of the image + vpImage Iblur; + vpImageFilter::gaussianBlur(I, Iblur, gaussianKernelSize, gaussianStdev, true, p_mask); + + vpArray2D gradientFilterX(apertureGradient, apertureGradient); // Gradient filter along the X-axis + vpArray2D gradientFilterY(apertureGradient, apertureGradient); // Gradient filter along the Y-axis + +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher + // Helper to apply the scale to the raw values of the filters + auto scaleFilter = [](vpArray2D &filter, const FilterType &scale) { + const unsigned int nbRows = filter.getRows(); + const unsigned int nbCols = filter.getCols(); + for (unsigned int r = 0; r < nbRows; ++r) { + for (unsigned int c = 0; c < nbCols; ++c) { + filter[r][c] = filter[r][c] * scale; + } + } + }; +#endif + + // Scales to apply to the filters to get a normalized gradient filter that gives a gradient + // between 0 and 255 for an vpImage + FilterType scaleX = 1.0; + FilterType scaleY = 1.0; + const unsigned int val2 = 2U; + + if (filteringType == CANNY_GBLUR_SOBEL_FILTERING) { + if (computeDx) { + scaleX = static_cast(vpImageFilter::getSobelKernelX(gradientFilterX.data, (apertureGradient - 1) / val2)); + } + if (computeDy) { + scaleY = static_cast(vpImageFilter::getSobelKernelY(gradientFilterY.data, (apertureGradient - 1) / val2)); + } + } + else if (filteringType == CANNY_GBLUR_SCHARR_FILTERING) { + if (computeDx) { + scaleX = static_cast(vpImageFilter::getScharrKernelX(gradientFilterX.data, (apertureGradient - 1) / val2)); + } + if (computeDy) { + scaleY = static_cast(vpImageFilter::getScharrKernelY(gradientFilterY.data, (apertureGradient - 1) / val2)); + } + } + + // Scale the gradient filters to have a normalized gradient filter + if (normalize) { + if (computeDx) { + scaleFilter(gradientFilterX, scaleX); + } + if (computeDy) { + scaleFilter(gradientFilterY, scaleY); + } + } + + // Apply the gradient filters to get the gradients + if (computeDx) { + vpImageFilter::filter(Iblur, dIx, gradientFilterX, true, p_mask); + } + + if (computeDy) { + vpImageFilter::filter(Iblur, dIy, gradientFilterY, true, p_mask); + } + } + else { + std::string errMsg = "[vpImageFilter::computePartialDerivatives] Filtering + gradient method \""; + errMsg += vpCannyFiltAndGradTypeToStr(filteringType); + errMsg += "\" is not implemented yet\n"; + throw(vpException(vpException::notImplementedError, errMsg)); + } + } + } + +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher + template + inline static void computePartialDerivatives(const vpImage &I, + vpImage &dIx, vpImage &dIy, + const bool &computeDx = true, const bool &computeDy = true, const bool &normalize = true, + const unsigned int &gaussianKernelSize = 5, const FilterType &gaussianStdev = 2.f, + const unsigned int &apertureGradient = 3, + const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING, + const vpCannyBackendType &backend = CANNY_VISP_BACKEND, const vpImage *p_mask = nullptr) = delete; + + template + inline static void computePartialDerivatives(const vpImage &I, + vpImage &dIx, vpImage &dIy, + const bool &computeDx = true, const bool &computeDy = true, const bool &normalize = true, + const unsigned int &gaussianKernelSize = 5, const unsigned char &gaussianStdev = 2.f, + const unsigned int &apertureGradient = 3, + const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING, + const vpCannyBackendType &backend = CANNY_VISP_BACKEND, const vpImage *p_mask = nullptr) = delete; + + template + inline static void computePartialDerivatives(const vpImage &I, + vpImage &dIx, vpImage &dIy, + const bool &computeDx = true, const bool &computeDy = true, const bool &normalize = true, + const unsigned int gaussianKernelSize = 5, const vpRGBa gaussianStdev = vpRGBa(), + const unsigned int apertureGradient = 3, + const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING, + const vpCannyBackendType &backend = CANNY_VISP_BACKEND, const vpImage *p_mask = nullptr) = delete; +#else + template + inline static void computePartialDerivatives(const vpImage &I, + vpImage &dIx, vpImage &dIy, + const bool &computeDx = true, const bool &computeDy = true, const bool &normalize = true, + const unsigned int &gaussianKernelSize = 5, const FilterType &gaussianStdev = 2.f, + const unsigned int &apertureGradient = 3, + const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING, + const vpCannyBackendType &backend = CANNY_VISP_BACKEND, const vpImage *p_mask = nullptr); + + template + inline static void computePartialDerivatives(const vpImage &I, + vpImage &dIx, vpImage &dIy, + const bool &computeDx = true, const bool &computeDy = true, const bool &normalize = true, + const unsigned int &gaussianKernelSize = 5, const unsigned char &gaussianStdev = 2.f, + const unsigned int &apertureGradient = 3, + const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING, + const vpCannyBackendType &backend = CANNY_VISP_BACKEND, const vpImage *p_mask = nullptr); + + template + inline static void computePartialDerivatives(const vpImage &I, + vpImage &dIx, vpImage &dIy, + const bool &computeDx = true, const bool &computeDy = true, const bool &normalize = true, + const unsigned int gaussianKernelSize = 5, const vpRGBa gaussianStdev = vpRGBa(), + const unsigned int apertureGradient = 3, + const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING, + const vpCannyBackendType &backend = CANNY_VISP_BACKEND, const vpImage *p_mask = nullptr); +#endif + + /** + * \brief Compute the upper Canny edge filter threshold, using Gaussian blur + Sobel or + Scharr operators to compute + * the gradient of the image. + * + * \tparam OutType : Either float, to accelerate the computation time, or double, to have greater precision. + * \param[in] I : The gray-scale image, in ViSP format. + * \param[in] p_dIx : If different from nullptr, must contain the gradient of the image with regard to the horizontal axis. + * \param[in] p_dIy : If different from nullptr, must contain the gradient of the image with regard to the vertical axis. + * \param[in] lowerThresh : Canny lower threshold. + * \param[in] gaussianKernelSize : The size of the mask of the Gaussian filter to apply (an odd number). + * \param[in] gaussianStdev : The standard deviation of the Gaussian filter to apply. + * \param[in] apertureGradient : Size of the mask for the Sobel operator (odd number). + * \param[in] lowerThresholdRatio : The ratio of the upper threshold the lower threshold must be equal to. + * \param[in] upperThresholdRatio : The ratio of pixels whose absolute gradient Gabs is lower or equal to define + * the upper threshold. + * \param[in] filteringType : The gradient filter to apply to compute the gradient, if \b p_dIx and \b p_dIy are + * nullptr. + * \param[in] p_mask : If different from \b nullptr , only the pixels for which \b p_mask is true will be considered. + * \return The upper Canny edge filter threshold. + */ + template + inline static float computeCannyThreshold(const vpImage &I, float &lowerThresh, + const vpImage *p_dIx = nullptr, const vpImage *p_dIy = nullptr, + const unsigned int &gaussianKernelSize = 5, + const OutType &gaussianStdev = 2.f, const unsigned int &apertureGradient = 3, + const float &lowerThresholdRatio = 0.6f, const float &upperThresholdRatio = 0.8f, + const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING, + const vpImage *p_mask = nullptr) + { + const unsigned int w = I.getWidth(); + const unsigned int h = I.getHeight(); + const int size = static_cast(I.getSize()); + + if ((lowerThresholdRatio <= 0.f) || (lowerThresholdRatio >= 1.f)) { + std::stringstream errMsg; + errMsg << "Lower ratio (" << lowerThresholdRatio << ") " << (lowerThresholdRatio < 0.f ? "should be greater than 0 !" : "should be lower than 1 !"); + throw(vpException(vpException::fatalError, errMsg.str())); + } + + if ((upperThresholdRatio <= 0.f) || (upperThresholdRatio >= 1.f)) { + std::stringstream errMsg; + errMsg << "Upper ratio (" << upperThresholdRatio << ") " << (upperThresholdRatio < 0.f ? "should be greater than 0 !" : "should be lower than 1 !"); + throw(vpException(vpException::fatalError, errMsg.str())); + } + + if (lowerThresholdRatio >= upperThresholdRatio) { + std::stringstream errMsg; + errMsg << "Lower ratio (" << lowerThresholdRatio << ") should be lower than the upper ratio (" << upperThresholdRatio << ")"; + throw(vpException(vpException::fatalError, errMsg.str())); + } + + vpImage dI(h, w); + vpImage dIx(h, w), dIy(h, w); + if ((p_dIx != nullptr) && (p_dIy != nullptr)) { + dIx = *p_dIx; + dIy = *p_dIy; + } + else { + computePartialDerivatives(I, dIx, dIy, true, true, true, gaussianKernelSize, gaussianStdev, + apertureGradient, filteringType, vpImageFilter::CANNY_VISP_BACKEND, p_mask); + } + + // Computing the absolute gradient of the image G = |dIx| + |dIy| +#ifdef VISP_HAVE_OPENMP +#pragma omp parallel for +#endif + for (int iter = 0; iter < size; ++iter) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, iter); + + if (computeVal) { + float dx = static_cast(dIx.bitmap[iter]); + float dy = static_cast(dIy.bitmap[iter]); + float gradient = std::abs(dx) + std::abs(dy); + float gradientClamped = std::min(gradient, static_cast(std::numeric_limits::max())); + dI.bitmap[iter] = static_cast(gradientClamped); + } + } + + // Compute the histogram + vpHistogram hist; + hist.setMask(p_mask); + const unsigned int nbBins = 256; + hist.calculate(dI, nbBins); + float totalNbPixels = static_cast(hist.getTotal()); + float accu = 0; + float t = upperThresholdRatio * totalNbPixels; + float tLow = lowerThresholdRatio * totalNbPixels; + float bon = 0; + unsigned int i = 0; + bool notFound = true, notFoundLower = true; + while ((i < nbBins) && notFound) { + float tf = static_cast(hist[static_cast(i)]); + accu = accu + tf; + if ((accu > tLow) && notFoundLower) { + lowerThresh = static_cast(i); + notFoundLower = false; + } + if (accu > t) { + bon = static_cast(i); + notFound = false; + } + ++i; + } + if (notFound) { + std::stringstream errMsg; + errMsg << "Could not find a bin for which " << upperThresholdRatio * 100.f << " percents of the pixels had a gradient lower than the upper threshold."; + throw(vpException(vpException::fatalError, errMsg.str())); + } + + float upperThresh = std::max(bon, 1.f); + lowerThresh = std::max(lowerThresh, std::numeric_limits::epsilon()); + return upperThresh; + } + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) // Check if cxx11 or higher + /** + * \brief Compute the upper Canny edge filter threshold for a HSV image. + * + * \tparam ArithmeticType : The type of encoding of the channels of the HSV image. + * \tparam useFullScale : When using unsigned char, true means that Hue is encoded on the range [0; 255], otherwise it + * uses the limited range as defined in the vpHSV class. + * \tparam OutType : Either float, to accelerate the computation time, or double, to have greater precision. + * \param[in] I : The HSV image. + * \param[in] p_dIx : If different from nullptr, must contain the gradient of the image with regard to the horizontal axis. + * \param[in] p_dIy : If different from nullptr, must contain the gradient of the image with regard to the vertical axis. + * \param[in] lowerThresh : Canny lower threshold. + * \param[in] gaussianKernelSize : The size of the mask of the Gaussian filter to apply (an odd number). + * \param[in] gaussianStdev : The standard deviation of the Gaussian filter to apply. + * \param[in] lowerThresholdRatio : The ratio of the upper threshold the lower threshold must be equal to. + * \param[in] upperThresholdRatio : The ratio of pixels whose absolute gradient Gabs is lower or equal to define + * the upper threshold. + * \param[in] filteringType : Canny filter type. + * \param[in] p_mask : If different from \b nullptr , only the pixels for which \b p_mask is true will be considered. + * \return The upper Canny edge filter threshold. + */ + template + inline static float computeCannyThreshold(const vpImage> &I, float &lowerThresh, + const vpImage *p_dIx = nullptr, const vpImage *p_dIy = nullptr, + const unsigned int &gaussianKernelSize = 5, + const OutType &gaussianStdev = 2.f, + const float &lowerThresholdRatio = 0.6f, const float &upperThresholdRatio = 0.8f, + const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING, + const vpImage *p_mask = nullptr) + { + const unsigned int w = I.getWidth(); + const unsigned int h = I.getHeight(); + const int size = I.getSize(); + + if ((lowerThresholdRatio <= 0.f) || (lowerThresholdRatio >= 1.f)) { + std::stringstream errMsg; + errMsg << "Lower ratio (" << lowerThresholdRatio << ") " << (lowerThresholdRatio < 0.f ? "should be greater than 0 !" : "should be lower than 1 !"); + throw(vpException(vpException::fatalError, errMsg.str())); + } + + if ((upperThresholdRatio <= 0.f) || (upperThresholdRatio >= 1.f)) { + std::stringstream errMsg; + errMsg << "Upper ratio (" << upperThresholdRatio << ") " << (upperThresholdRatio < 0.f ? "should be greater than 0 !" : "should be lower than 1 !"); + throw(vpException(vpException::fatalError, errMsg.str())); + } + + if (lowerThresholdRatio >= upperThresholdRatio) { + std::stringstream errMsg; + errMsg << "Lower ratio (" << lowerThresholdRatio << ") should be lower than the upper ratio (" << upperThresholdRatio << ")"; + throw(vpException(vpException::fatalError, errMsg.str())); + } + + int nbThread = 1; +#ifdef VISP_HAVE_OPENMP + nbThread = omp_get_max_threads(); +#endif + + vpImage dI(h, w); + vpImage dIx(h, w), dIy(h, w); + if ((p_dIx != nullptr) && (p_dIy != nullptr)) { + dIx = *p_dIx; + dIy = *p_dIy; + } + else { + vpImage> Iblur; + gaussianBlur(I, Iblur, gaussianKernelSize, gaussianStdev, true, p_mask); + gradientFilter(Iblur, dIx, dIy, nbThread, p_mask, filteringType); + } + + // Computing the absolute gradient of the image G = |dIx| + |dIy| + float dIMax = -1.; // dI is the absolute gradient => positive + float dIMin = std::numeric_limits::max(); + int iter, istart = 0, istop = size; +#ifdef VISP_HAVE_OPENMP + int iam, nt, ipoints, npoints(size); +#pragma omp parallel default(shared) private(iter, iam, nt, ipoints, istart, istop) + { + iam = omp_get_thread_num(); + nt = omp_get_num_threads(); + ipoints = npoints / nt; + // size of partition + istart = iam * ipoints; // starting array index + if (iam == nt-1) { + // last thread may do more + ipoints = npoints - istart; + } + istop = istart + ipoints; +#endif + float localdImin = std::numeric_limits::max(), localdImax = -1.; + for (iter = istart; iter < istop; ++iter) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, iter); + + if (computeVal) { + float dx = static_cast(dIx.bitmap[iter]); + float dy = static_cast(dIy.bitmap[iter]); + float gradient = std::abs(dx) + std::abs(dy); + localdImax = std::max(localdImax, gradient); + localdImin = std::min(localdImin, gradient); + dI.bitmap[iter] = gradient; + } + } +#ifdef VISP_HAVE_OPENMP +#pragma omp critical + { + dIMin = std::min(dIMin, localdImin); + dIMax = std::max(dIMax, localdImax); + } +#else + dIMin = localdImin; + dIMax = localdImax; +#endif +#ifdef VISP_HAVE_OPENMP + } +#endif + + // Compute the histogram +#ifdef VISP_HAVE_THREADS + nbThread = std::max(static_cast(std::thread::hardware_concurrency()), 1); // The method can return 0 when it is not able to detect the supported number of threads. +#endif + const unsigned int nbBins = 1024; + vpHistogram hist(nbBins); + hist.setMask(p_mask); + OutType step = 0.; + hist.calculate(dI, dIMin, dIMax, step, nbBins, nbThread); + float totalNbPixels = static_cast(hist.getTotal()); + float accu = 0; + float t = upperThresholdRatio * totalNbPixels; + float tLow = lowerThresholdRatio * totalNbPixels; + float bon = 0; + unsigned int i = 0; + bool notFound = true, notFoundLower = true; + while ((i < nbBins) && notFound) { + float tf = static_cast(hist[i]); + accu = accu + tf; + if ((accu > tLow) && notFoundLower) { + lowerThresh = static_cast(i) * static_cast(step) + dIMin; + notFoundLower = false; + } + if (accu > t) { + bon = static_cast(i); + notFound = false; + } + ++i; + } + if (notFound) { + std::stringstream errMsg; + errMsg << "Could not find a bin for which " << upperThresholdRatio * 100.f << " percents of the pixels had a gradient lower than the upper threshold."; + throw(vpException(vpException::fatalError, errMsg.str())); + } + float upperThresh = bon * static_cast(step) + dIMin; + lowerThresh = std::max(lowerThresh, std::numeric_limits::epsilon()); + return upperThresh; + } +#endif + + /*! + * Apply a 1x3 derivative filter to an image pixel. + * + * \param I : Image to filter + * \param r : coordinates (row) of the pixel + * \param c : coordinates (column) of the pixel + */ + template static double derivativeFilterX(const vpImage &I, unsigned int r, unsigned int c) + { + const int val1 = 1, val2 = 2, val3 = 3; + return ((2047.0 * static_cast(I[r][c + val1] - I[r][c - val1])) + (913.0 * static_cast(I[r][c + val2] - I[r][c - val2])) + + (112.0 * static_cast(I[r][c + val3] - I[r][c - val3]))) / 8418.0; + } + + /*! + * Apply a 3x1 derivative filter to an image pixel. + * + * \param I : Image to filter + * \param r : coordinates (row) of the pixel + * \param c : coordinates (column) of the pixel + */ + template static double derivativeFilterY(const vpImage &I, unsigned int r, unsigned int c) + { + const int val1 = 1, val2 = 2, val3 = 3; + return ((2047.0 * static_cast(I[r + val1][c] - I[r - val1][c])) + (913.0 * static_cast(I[r + val2][c] - I[r - val2][c])) + + (112.0 * static_cast(I[r + val3][c] - I[r - val3][c]))) / 8418.0; + } + + /*! + * Apply a 1 x size Derivative Filter in X to an image pixel. + * + * \tparam FilterType : Either float, to accelerate the computation time, or double, to have greater precision. + * \param I : Image to filter + * \param r : Coordinates(row) of the pixel + * \param c : Coordinates(column) of the pixel + * \param filter : Coefficients of the filter to be initialized using + * vpImageFilter::getGaussianDerivativeKernel(). + * \param size : Size of the filter. + * + * \sa vpImageFilter::getGaussianDerivativeKernel() + */ + template + static FilterType derivativeFilterX(const vpImage &I, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) + { + const unsigned int stop = (size - 1) / 2; + unsigned int i; + FilterType result; + + result = 0; + + for (i = 1; i <= stop; ++i) { + result += filter[i] * static_cast(I[r][c + i] - I[r][c - i]); + } + return result; + } + + /*! + * Apply a size x 1 Derivative Filter in Y to an image pixel. + * + * \tparam FilterType : Either float, to accelerate the computation time, or double, to have greater precision. + * \param I : Image to filter. + * \param r : Coordinates (row) of the pixel. + * \param c : Coordinates (column) of the pixel. + * \param filter : Coefficients of the filter to be initialized using + * vpImageFilter::getGaussianDerivativeKernel(). + * \param size : Size of the filter. + * + * \sa vpImageFilter::getGaussianDerivativeKernel() + */ + template + static FilterType derivativeFilterY(const vpImage &I, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) + { + const unsigned int stop = (size - 1) / 2; + unsigned int i; + FilterType result; + + result = 0; + + for (i = 1; i <= stop; ++i) { + result += filter[i] * static_cast(I[r + i][c] - I[r - i][c]); + } + return result; + } + + /*! + Apply a filter to an image. + \tparam FilterType : Either float, to accelerate the computation time, or double, to have greater precision. + \param I : Image to filter + \param If : Filtered image. + \param M : Filter kernel. + \param convolve : If true, perform a convolution otherwise a correlation. + \param p_mask : If different from nullptr, mask indicating which points to consider (true) or to ignore(false). + + \note By default it performs a correlation: + \f[ + \textbf{I\_filtered} \left( u,v \right) = + \sum_{y=0}^{\textbf{kernel\_h}} + \sum_{x=0}^{\textbf{kernel\_w}} + \textbf{M} \left( x,y \right ) \times + \textbf{I} \left( + u-\frac{\textbf{kernel\_w}}{2}+x,v-\frac{\textbf{kernel\_h}}{2}+y \right) + \f] + The convolution is almost the same operation: + \f[ + \textbf{I\_filtered} \left( u,v \right) = + \sum_{y=0}^{\textbf{kernel\_h}} + \sum_{x=0}^{\textbf{kernel\_w}} + \textbf{M} \left( x,y \right ) \times + \textbf{I} \left( + u+\frac{\textbf{kernel\_w}}{2}-x,v+\frac{\textbf{kernel\_h}}{2}-y \right) + \f] + Only pixels in the input image fully covered by the kernel are considered. + */ + template + static void filter(const vpImage &I, vpImage &If, const vpArray2D &M, bool convolve = false, + const vpImage *p_mask = nullptr) + { + const unsigned int size_y = M.getRows(), size_x = M.getCols(); + const unsigned int half_size_y = size_y / 2, half_size_x = size_x / 2; + + const unsigned int inputHeight = I.getHeight(), inputWidth = I.getWidth(); + If.resize(inputHeight, inputWidth, 0.0); + + if (convolve) { + const unsigned int stopHeight = inputHeight - half_size_y; + const unsigned int stopWidth = inputWidth - half_size_x; + for (unsigned int i = half_size_y; i < stopHeight; ++i) { + for (unsigned int j = half_size_x; j < stopWidth; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); + if (computeVal) { + FilterType conv = 0; + + for (unsigned int a = 0; a < size_y; ++a) { + for (unsigned int b = 0; b < size_x; ++b) { + FilterType val = static_cast(I[(i + half_size_y) - a][(j + half_size_x) - b]); // Convolution + conv += M[a][b] * val; + } + } + If[i][j] = conv; + } + } + } + } + else { + const unsigned int stopHeight = inputHeight - half_size_y; + const unsigned int stopWidth = inputWidth - half_size_x; + for (unsigned int i = half_size_y; i < stopHeight; ++i) { + for (unsigned int j = half_size_x; j < stopWidth; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); + if (computeVal) { + FilterType corr = 0; + + for (unsigned int a = 0; a < size_y; ++a) { + for (unsigned int b = 0; b < size_x; ++b) { + FilterType val = static_cast(I[(i - half_size_y) + a][(j - half_size_x) + b]); // Correlation + corr += M[a][b] * val; + } + } + If[i][j] = corr; + } + } + } + } + } + + /** + * \brief Apply a filter at a given image location + * + * \tparam FilterType Image and filter types: double or float + * \param I The input image + * \param row The row coordinate where the filter should be applied + * \param col The column coordinate where the filter should be applied + * \param M the filter + */ + template + static FilterType filter(const vpImage &I, const vpArray2D &M, unsigned int row, unsigned int col) + { + const unsigned int size_y = M.getRows(), size_x = M.getCols(); + const unsigned int half_size_y = size_y / 2, half_size_x = size_x / 2; + FilterType corr = 0; + + for (unsigned int a = 0; a < size_y; ++a) { + for (unsigned int b = 0; b < size_x; ++b) { + FilterType val = static_cast(I[row - half_size_y + a][col - half_size_x + b]); // Correlation + corr += M[a][b] * val; + } + } + return corr; + } + +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher + template + static void filter(const vpImage &I, vpImage &If, const vpArray2D &M, bool convolve = false) = delete; +#else + template + static void filter(const vpImage &I, vpImage &If, const vpArray2D &M, bool convolve = false); +#endif + + /*! + * Apply a filter to an image: + * \f[ + * \textbf{I}_u = \textbf{M} \ast \textbf{I} \textbf{ and } \textbf{I}_v = + * \textbf{M}^t \ast \textbf{I} \f] + * \tparam FilterType : Either float, to accelerate the computation time, or double, to have greater precision. + * \param I : Image to filter + * \param Iu : Filtered image along the horizontal axis (u = columns). + * \param Iv : Filtered image along the vertical axis (v = rows). + * \param M : Filter kernel. + * \param convolve : If true, perform a convolution otherwise a correlation. + * \param p_mask : If different from nullptr, mask indicating which points to consider (true) or to ignore(false). + */ + template + static void filter(const vpImage &I, vpImage &Iu, vpImage &Iv, const vpArray2D &M, + bool convolve = false, const vpImage *p_mask = nullptr) + { + const unsigned int size = M.getRows(); + const unsigned int half_size = size / 2; + const unsigned int height = I.getHeight(), width = I.getWidth(); + const unsigned int stopV = height - half_size; + const unsigned int stopU = width - half_size; + + Iu.resize(height, width, 0.0); + Iv.resize(height, width, 0.0); + + if (convolve) { + for (unsigned int v = half_size; v < stopV; ++v) { + for (unsigned int u = half_size; u < stopU; ++u) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, v, u); + if (computeVal) { + FilterType conv_u = 0; + FilterType conv_v = 0; + + for (unsigned int a = 0; a < size; ++a) { + for (unsigned int b = 0; b < size; ++b) { + FilterType val = static_cast(I[(v + half_size) - a][(u + half_size) - b]); // Convolution + conv_u += M[a][b] * val; + conv_v += M[b][a] * val; + } + } + Iu[v][u] = conv_u; + Iv[v][u] = conv_v; + } + } + } + } + else { + for (unsigned int v = half_size; v < stopV; ++v) { + for (unsigned int u = half_size; u < stopU; ++u) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, v, u); + + if (computeVal) { + FilterType conv_u = 0; + FilterType conv_v = 0; + + for (unsigned int a = 0; a < size; ++a) { + for (unsigned int b = 0; b < size; ++b) { + FilterType val = static_cast(I[(v - half_size) + a][(u - half_size) + b]); // Correlation + conv_u += M[a][b] * val; + conv_v += M[b][a] * val; + } + } + Iu[v][u] = conv_u; + Iv[v][u] = conv_v; + } + } + } + } + } + +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher + template + static void filter(const vpImage &I, vpImage &Iu, vpImage &Iv, const vpArray2D &M, bool convolve) = delete; + + template + static void filter(const vpImage &I, vpImage &Iu, vpImage &Iv, const vpArray2D &M, bool convolve) = delete; +#else + template + static void filter(const vpImage &I, vpImage &Iu, vpImage &Iv, const vpArray2D &M, bool convolve); + + template + static void filter(const vpImage &I, vpImage &Iu, vpImage &Iv, const vpArray2D &M, bool convolve); +#endif + + static void sepFilter(const vpImage &I, vpImage &If, const vpColVector &kernelH, const vpColVector &kernelV); + + /*! + * Apply a separable filter. + * \tparam FilterType : Either float, to accelerate the computation time, or double, to have greater precision. + * \param I : The original image. + * \param GI : The filtered image. + * \param filter : The separable filter. + * \param size : The size of the filter. + * \param p_mask : If different from nullptr, mask indicating which points to consider (true) or to ignore(false). + */ + template + static void filter(const vpImage &I, vpImage &GI, const FilterType *filter, unsigned int size, const vpImage *p_mask = nullptr) + { + vpImage GIx; + filterX(I, GIx, filter, size, p_mask); + filterY(GIx, GI, filter, size, p_mask); + GIx.destroy(); + } + + static inline unsigned char filterGaussXPyramidal(const vpImage &I, unsigned int i, unsigned int j) + { + const int val2 = 2; + return static_cast(((1. * I[i][j - val2]) + (4. * I[i][j - 1]) + (6. * I[i][j]) + (4. * I[i][j + 1]) + (1. * I[i][j + val2])) / 16.); + } + static inline unsigned char filterGaussYPyramidal(const vpImage &I, unsigned int i, unsigned int j) + { + const int val2 = 2; + return static_cast(((1. * I[i - val2][j]) + (4. * I[i - 1][j]) + (6. * I[i][j]) + (4. * I[i + 1][j]) + (1. * I[i + val2][j])) / 16.); + } + +#if (VISP_CXX_STANDARD < VISP_CXX_STANDARD_11) + template + static void filterX(const vpImage &I, vpImage &dIx, const FilterType *filter, unsigned int size, + const vpImage *p_mask = nullptr) + { + const unsigned int height = I.getHeight(); + const unsigned int width = I.getWidth(); + const unsigned int stop1J = (size - 1) / 2; + const unsigned int stop2J = width - ((size - 1) / 2); + resizeAndInitializeIfNeeded(p_mask, height, width, dIx); + + for (unsigned int i = 0; i < height; ++i) { + for (unsigned int j = 0; j < stop1J; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); + if (computeVal) { + dIx[i][j] = vpImageFilter::filterXLeftBorder(I, i, j, filter, size); + } + } + for (unsigned int j = stop1J; j < stop2J; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); + if (computeVal) { + dIx[i][j] = vpImageFilter::filterX(I, i, j, filter, size); + } + } + for (unsigned int j = stop2J; j < width; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); + if (computeVal) { + dIx[i][j] = vpImageFilter::filterXRightBorder(I, i, j, filter, size); + } + } + } + } + + static void filterX(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size, const vpImage *p_mask = nullptr); + + template + static inline FilterType filterX(const vpImage &I, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) + { + const unsigned int stop = (size - 1) / 2; + FilterType result = static_cast(0.); + + for (unsigned int i = 1; i <= stop; ++i) { + result += filter[i] * static_cast(I[r][c + i] + I[r][c - i]); + } + return result + (filter[0] * static_cast(I[r][c])); + } + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + static void filterXR(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); + static void filterXG(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); + static void filterXB(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); + + static double filterXR(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size); + static double filterXG(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size); + static double filterXB(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size); + + static double filterXLeftBorderR(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size); + static double filterXLeftBorderG(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size); + static double filterXLeftBorderB(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size); + static double filterXRightBorderR(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size); + static double filterXRightBorderG(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size); + static double filterXRightBorderB(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size); + + template + static inline FilterType filterXLeftBorder(const vpImage &I, unsigned int r, unsigned int c, + const FilterType *filter, unsigned int size) + { + const unsigned int stop = (size - 1) / 2; + FilterType result = static_cast(0.); + + for (unsigned int i = 1; i <= stop; ++i) { + if (c > i) { + result += filter[i] * static_cast(I[r][c + i] + I[r][c - i]); + } + else { + result += filter[i] * static_cast(I[r][c + i] + I[r][i - c]); + } + } + return result + (filter[0] * static_cast(I[r][c])); + } + + template + static inline FilterType filterXRightBorder(const vpImage &I, unsigned int r, unsigned int c, + const FilterType *filter, unsigned int size) + { + const unsigned int stop = (size - 1) / 2; + const unsigned int width = I.getWidth(); + FilterType result = static_cast(0.); + const unsigned int twice = 2; + + for (unsigned int i = 1; i <= stop; ++i) { + if ((c + i) < width) { + result += filter[i] * static_cast(I[r][c + i] + I[r][c - i]); + } + else { + result += filter[i] * static_cast(I[r][((twice * width) - c) - i - 1] + I[r][c - i]); + } + } + return result + (filter[0] * static_cast(I[r][c])); + } +#endif +#else +#ifndef DOXYGEN_SHOULD_SKIP_THIS + /** + * \brief Apply separately a filter to all the channels of a vpHSV. + * + * \tparam A specialization of a vpHSV color. + * \tparam FilterType An arithmetic type. + * \param[in] in The vpHSV that is filtered. + * \param[in] out The result of the filtering, stored in a vector of doubles to limit rounding errors. + * \param[in] coeff The filter coefficient to apply. + */ + template + static inline typename std::enable_if::value, void>::type + filterChannel(const Color &in, vpColVector &out, const FilterType &coeff) + { + out[0] = coeff * vpColorGetter<0>::get(in); + out[1] = coeff * vpColorGetter<1>::get(in); + out[2] = coeff * vpColorGetter<2>::get(in); + } + + /** + * \brief Apply separately a filter to all the channels of a vpRGBa. + * + * \tparam FilterType An arithmetic type. + * \param[in] in The RGBa that is filtered. + * \param[in] out The result of the filtering, stored in a vector of doubles to limit rounding errors. + * \param[in] coeff The filter coefficient to apply. + */ + template + static inline typename std::enable_if::value, void>::type + filterChannel(const Color &in, vpColVector &out, const FilterType &coeff) + { + out[0] = coeff * vpColorGetter<0>::get(in); + out[1] = coeff * vpColorGetter<1>::get(in); + out[2] = coeff * vpColorGetter<2>::get(in); + out[3] = vpRGBa::alpha_default; + } + + /** + * \brief Apply separately a filter to all the channels of two colors. + * + * \tparam Color A color class, such as vpHSV or vpRGBa. + * \tparam FilterType An arithmetic type. + * \param[in] in1 The first RGBa that is filtered. + * \param[in] in2 The second RGBa that is filtered with the same coefficient. + * \param[in] out The result of the filtering, stored in a vector of doubles to limit rounding errors. + * \param[in] coeff The filter coefficient to apply. + */ + template + inline static void + filterChannel(const Color &in1, const Color &in2, vpColVector &out, const FilterType &coeff) + { + out[0] += coeff * (vpColorGetter<0>::get(in1) + vpColorGetter<0>::get(in2)); + out[1] += coeff * (vpColorGetter<1>::get(in1) + vpColorGetter<1>::get(in2)); + out[2] += coeff * (vpColorGetter<2>::get(in1) + vpColorGetter<2>::get(in2)); + } +#endif + + /** + * \brief Filter along the horizontal direction. + * + * \tparam ImageType The type of pixels. It can be an arithmetic type or a color type (vpRGBa, vpHSV). + * \tparam OutputType The type of pixels in the resulting image. It can be an arithmetic type or a color type (vpRGBa, vpHSV). + * \tparam FilterType An arithmetic type. + * \param[in] I The image that must be filtered. + * \param[in] dIx The image filtered along the horizontal direction. + * \param[in] filter The coefficients of the filter. + * \param[in] size The size of the filter. + * \param[in] p_mask A boolean mask that permits to select the pixels that must be filtered if different from nullptr, + * unused otherwise. + */ + template + static void filterX(const vpImage &I, vpImage &dIx, const FilterType *filter, unsigned int size, + const vpImage *p_mask = nullptr) + { + const int height = static_cast(I.getHeight()); + const int width = static_cast(I.getWidth()); + const int stop1J = static_cast((size - 1) / 2); + const int stop2J = static_cast(width - ((size - 1) / 2)); + resizeAndInitializeIfNeeded(p_mask, height, width, dIx); + + int istart = 0; + int istop = height; +#ifdef VISP_HAVE_OPENMP + int iam, nt, ipoints, npoints(height); +#pragma omp parallel default(shared) private(iam, nt, ipoints, istart, istop) + { + iam = omp_get_thread_num(); + nt = omp_get_num_threads(); + ipoints = npoints / nt; + // size of partition + istart = iam * ipoints; // starting array index + if (iam == nt-1) { + // last thread may do more + ipoints = npoints - istart; + } + istop = istart + ipoints; +#endif + for (int i = istart; i < istop; ++i) { + for (int j = 0; j < stop1J; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); + if (computeVal) { + vpImageFilter::filterXLeftBorder(I, dIx[i][j], i, j, filter, size); + } + } + + for (int j = stop1J; j < stop2J; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); + if (computeVal) { + vpImageFilter::filterX(I, dIx[i][j], i, j, filter, size); + } + } + for (int j = stop2J; j < width; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); + if (computeVal) { + vpImageFilter::filterXRightBorder(I, dIx[i][j], i, j, filter, size); + } + } + } +#ifdef VISP_HAVE_OPENMP + } +#endif + } + + /** + * \brief Filter along the horizontal direction "in the middle" of the image (i.e not on a border). + * + * \tparam ImageType The type of pixels. In this case, it must be an arithmetic type. + * \tparam OutputType The type of pixels in the resulting image. In this case, it must be an arithmetic type. + * \tparam FilterType An arithmetic type. + * \param[in] I The image that must be filtered. + * \param[in] result The pixel resulting from the filtering operation. + * \param[in] r The row index. + * \param[in] c The column index. + * \param[in] filter The coefficients of the filter. + * \param[in] size The size of the filter. + * \return The method is enabled only for arithmetic input type. + */ + template + static inline typename std::enable_if::value, void>::type + filterX(const vpImage &I, OutputType &result, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) + { + const unsigned int stop = (size - 1) / 2; + FilterType res = filter[0] * static_cast(I[r][c]); + + for (unsigned int i = 1; i <= stop; ++i) { + res += filter[i] * static_cast(I[r][c + i] + I[r][c - i]); + } + result = static_cast(res); + } + + /** + * \brief Filter along the horizontal direction "in the middle" of the image (i.e not on a border). + * + * \tparam ImageType The type of pixels. In this case, it must be a color type (vpRGBa, vpHSV). + * \tparam OutputType The type of pixels in the resulting image. It can be an arithmetic type or a color type (vpRGBa, vpHSV). + * \tparam FilterType An arithmetic type. + * \param[in] I The image that must be filtered. + * \param[in] result The pixel resulting from the filtering operation. + * \param[in] r The row index. + * \param[in] c The column index. + * \param[in] filter The coefficients of the filter. + * \param[in] size The size of the filter. + * \return The method is enabled only for color input type. + */ + template + static inline typename std::enable_if::value, void>::type + filterX(const vpImage &I, OutputType &result, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) + { + const unsigned int stop = (size - 1) / 2; +#ifdef VISP_HAVE_OPENMP + vpColVector res(ImageType::nbChannels); +#else + static vpColVector res(ImageType::nbChannels); +#endif + filterChannel(I[r][c], res, filter[0]); + + for (unsigned int i = 1; i <= stop; ++i) { + filterChannel(I[r][c + i], I[r][c - i], res, filter[i]); + } + result = OutputType(res); + } + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + /** + * \brief Filter along the horizontal direction "on the left border" of the image (the width of the border depends on + * the filter length). + * + * \tparam ImageType The type of pixels. In this case, it must be an arithmetic type. + * \tparam OutputType The type of pixels in the resulting image. In this case, it must be an arithmetic type. + * \tparam FilterType An arithmetic type. + * \param[in] I The image that must be filtered. + * \param[in] result The pixel resulting from the filtering operation. + * \param[in] r The row index. + * \param[in] c The column index. + * \param[in] filter The coefficients of the filter. + * \param[in] size The size of the filter. + * \return The method is enabled only for arithmetic input type. + */ + template + static inline typename std::enable_if::value, void>::type + filterXLeftBorder(const vpImage &I, OutputType &result, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) + { + const unsigned int stop = (size - 1) / 2; + FilterType res = filter[0] * static_cast(I[r][c]); + + for (unsigned int i = 1; i <= stop; ++i) { + if (c > i) { + res += filter[i] * static_cast(I[r][c + i] + I[r][c - i]); + } + else { + res += filter[i] * static_cast(I[r][c + i] + I[r][i - c]); + } + } + result = static_cast(res); + } + + /** + * \brief Filter along the horizontal direction "on the left border" of the image (the width of the border depends on + * the filter length). + * + * \tparam ImageType The type of pixels. In this case, it must be a color type (vpRGBa, vpHSV). + * \tparam OutputType The type of pixels in the resulting image. It can be an arithmetic type or a color type (vpRGBa, vpHSV). + * \tparam FilterType An arithmetic type. + * \param[in] I The image that must be filtered. + * \param[in] result The pixel resulting from the filtering operation. + * \param[in] r The row index. + * \param[in] c The column index. + * \param[in] filter The coefficients of the filter. + * \param[in] size The size of the filter. + * \return The method is enabled only for color input type. + */ + template + static inline typename std::enable_if::value, void>::type + filterXLeftBorder(const vpImage &I, OutputType &result, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) + { + const unsigned int stop = (size - 1) / 2; +#ifdef VISP_HAVE_OPENMP + vpColVector res(ImageType::nbChannels); +#else + static vpColVector res(ImageType::nbChannels); +#endif + filterChannel(I[r][c], res, filter[0]); + + for (unsigned int i = 1; i <= stop; ++i) { + if (c > i) { + filterChannel(I[r][c + i], I[r][c - i], res, filter[i]); + } + else { + filterChannel(I[r][c + i], I[r][i - c], res, filter[i]); + } + } + result = OutputType(res); + } + + /** + * \brief Filter along the horizontal direction "on the right border" of the image (the width of the border depends on + * the filter length). + * + * \tparam ImageType The type of pixels. In this case, it must be an arithmetic type. + * \tparam OutputType The type of pixels in the resulting image. In this case, it must be an arithmetic type. + * \tparam FilterType An arithmetic type. + * \param[in] I The image that must be filtered. + * \param[in] result The pixel resulting from the filtering operation. + * \param[in] r The row index. + * \param[in] c The column index. + * \param[in] filter The coefficients of the filter. + * \param[in] size The size of the filter. + * \return The method is enabled only for arithmetic input type. + */ + template + static inline typename std::enable_if::value, void>::type + filterXRightBorder(const vpImage &I, OutputType &result, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) + { + const unsigned int stop = (size - 1) / 2; + const unsigned int width = I.getWidth(); + const unsigned int twice = 2; + FilterType res = filter[0] * static_cast(I[r][c]); + + for (unsigned int i = 1; i <= stop; ++i) { + if ((c + i) < width) { + res += filter[i] * static_cast(I[r][c + i] + I[r][c - i]); + } + else { + res += filter[i] * static_cast(I[r][((twice * width) - c) - i - 1] + I[r][c - i]); + } + } + result = static_cast(res); + } + + /** + * \brief Filter along the horizontal direction "on the right border" of the image (the width of the border depends on + * the filter length). + * + * \tparam ImageType The type of pixels. In this case, it must be a color type (vpRGBa, vpHSV). + * \tparam OutputType The type of pixels in the resulting image. It can be an arithmetic type or a color type (vpRGBa, vpHSV). + * \tparam FilterType An arithmetic type. + * \param[in] I The image that must be filtered. + * \param[in] result The pixel resulting from the filtering operation. + * \param[in] r The row index. + * \param[in] c The column index. + * \param[in] filter The coefficients of the filter. + * \param[in] size The size of the filter. + * \return The method is enabled only for color input type. + */ + template + static inline typename std::enable_if::value, void>::type + filterXRightBorder(const vpImage &I, OutputType &result, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) + { + const unsigned int stop = (size - 1) / 2; + const unsigned int width = I.getWidth(); + const unsigned int twice = 2; +#ifdef VISP_HAVE_OPENMP + vpColVector res(ImageType::nbChannels); +#else + static vpColVector res(ImageType::nbChannels); +#endif + filterChannel(I[r][c], res, filter[0]); + + for (unsigned int i = 1; i <= stop; ++i) { + if ((c + i) < width) { + filterChannel(I[r][c + i], I[r][c - i], res, filter[i]); + } + else { + filterChannel(I[r][((twice * width) - c) - i - 1], I[r][c - i], res, filter[i]); + } + } + result = OutputType(res); + } +#endif +#endif + + +#if (VISP_CXX_STANDARD < VISP_CXX_STANDARD_11) + static void filterY(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size, const vpImage *p_mask = nullptr); + + template + static void filterY(const vpImage &I, vpImage &dIy, const FilterType *filter, unsigned int size, + const vpImage *p_mask = nullptr) + { + const unsigned int height = I.getHeight(), width = I.getWidth(); + const unsigned int stop1I = (size - 1) / 2; + const unsigned int stop2I = height - ((size - 1) / 2); + resizeAndInitializeIfNeeded(p_mask, height, width, dIy); + + for (unsigned int i = 0; i < stop1I; ++i) { + for (unsigned int j = 0; j < width; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); + if (computeVal) { + dIy[i][j] = vpImageFilter::filterYTopBorder(I, i, j, filter, size); + } + } + } + for (unsigned int i = stop1I; i < stop2I; ++i) { + for (unsigned int j = 0; j < width; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); + if (computeVal) { + dIy[i][j] = vpImageFilter::filterY(I, i, j, filter, size); + } + } + } + for (unsigned int i = stop2I; i < height; ++i) { + for (unsigned int j = 0; j < width; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); + if (computeVal) { + dIy[i][j] = vpImageFilter::filterYBottomBorder(I, i, j, filter, size); + } + } + } + } + + template + static inline FilterType filterY(const vpImage &I, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) + { + const unsigned int stop = (size - 1) / 2; + FilterType result = static_cast(0.); + + for (unsigned int i = 1; i <= stop; ++i) { + result += filter[i] * static_cast(I[r + i][c] + I[r - i][c]); + } + return result + (filter[0] * static_cast(I[r][c])); + } +#ifndef DOXYGEN_SHOULD_SKIP_THIS + static void filterYR(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); + static void filterYG(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); + static void filterYB(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); + + static double filterYR(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size); + static double filterYG(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size); + static double filterYB(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size); + + static double filterYTopBorderR(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size); + static double filterYTopBorderG(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size); + static double filterYTopBorderB(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size); + static double filterYBottomBorderR(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size); + static double filterYBottomBorderG(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size); + static double filterYBottomBorderB(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size); + + template + static inline FilterType filterYTopBorder(const vpImage &I, unsigned int r, unsigned int c, + const FilterType *filter, unsigned int size) + { + const unsigned int stop = (size - 1) / 2; + FilterType result = static_cast(0.); + + for (unsigned int i = 1; i <= stop; ++i) { + if (r > i) { + result += filter[i] * static_cast(I[r + i][c] + I[r - i][c]); + } + else { + result += filter[i] * static_cast(I[r + i][c] + I[i - r][c]); + } + } + return result + (filter[0] * static_cast(I[r][c])); + } + + template + static inline FilterType filterYBottomBorder(const vpImage &I, unsigned int r, unsigned int c, + const FilterType *filter, unsigned int size) + { + const unsigned int height = I.getHeight(); + const unsigned int stop = (size - 1) / 2; + FilterType result = static_cast(0.); + const unsigned int twiceHeight = 2 * height; + for (unsigned int i = 1; i <= stop; ++i) { + if ((r + i) < height) { + result += filter[i] * static_cast(I[r + i][c] + I[r - i][c]); + } + else { + result += filter[i] * static_cast(I[(twiceHeight - r) - i - 1][c] + I[r - i][c]); + } + } + return result + (filter[0] * static_cast(I[r][c])); + } +#endif +#else + + /** + * \brief Filter along the vertical direction. + * + * \tparam ImageType The type of pixels. It can be an arithmetic type or a color type (vpRGBa, vpHSV). + * \tparam OutputType The type of pixels in the resulting image. It can be an arithmetic type or a color type (vpRGBa, vpHSV). + * \tparam FilterType An arithmetic type. + * \param[in] I The image that must be filtered. + * \param[in] dIy The image filtered along the vertical direction. + * \param[in] filter The coefficients of the filter. + * \param[in] size The size of the filter. + * \param[in] p_mask A boolean mask that permits to select the pixels that must be filtered if different from nullptr, + * unused otherwise. + */ + template + static void filterY(const vpImage &I, vpImage &dIy, const FilterType *filter, unsigned int size, + const vpImage *p_mask = nullptr) + { + const unsigned int height = I.getHeight(), width = I.getWidth(); + const unsigned int stop1I = (size - 1) / 2; + const unsigned int stop2I = height - ((size - 1) / 2); + resizeAndInitializeIfNeeded(p_mask, height, width, dIy); + + unsigned int jstart = 0; + unsigned int jstop = width; +#ifdef VISP_HAVE_OPENMP + unsigned int iam, nt, jpoints, npoints(width); +#pragma omp parallel default(shared) private(iam, nt, jpoints, jstart, jstop) + { + iam = omp_get_thread_num(); + nt = omp_get_num_threads(); + jpoints = npoints / nt; + // size of partition + jstart = iam * jpoints; // starting array index + if (iam == nt-1) { + // last thread may do more + jpoints = npoints - jstart; + } + jstop = jstart + jpoints; +#endif + for (unsigned int i = 0; i < stop1I; ++i) { + for (unsigned int j = jstart; j < jstop; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); + if (computeVal) { + vpImageFilter::filterYTopBorder(I, dIy[i][j], i, j, filter, size); + } + } + } + for (unsigned int i = stop1I; i < stop2I; ++i) { + for (unsigned int j = jstart; j < jstop; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); + if (computeVal) { + vpImageFilter::filterY(I, dIy[i][j], i, j, filter, size); + } + } + } + for (unsigned int i = stop2I; i < height; ++i) { + for (unsigned int j = jstart; j < jstop; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); + if (computeVal) { + vpImageFilter::filterYBottomBorder(I, dIy[i][j], i, j, filter, size); + } + } +#ifdef VISP_HAVE_OPENMP + } +#endif + } + } + + /** + * \brief Filter along the vertical direction "in the middle" of the image (i.e not on a border). + * + * \tparam ImageType The type of pixels. In this case, it must be an arithmetic type. + * \tparam OutputType The type of pixels in the resulting image. In this case, it must be an arithmetic type. + * \tparam FilterType An arithmetic type. + * \param[in] I The image that must be filtered. + * \param[in] result The pixel resulting from the filtering operation. + * \param[in] r The row index. + * \param[in] c The column index. + * \param[in] filter The coefficients of the filter. + * \param[in] size The size of the filter. + * \return The method is enabled only for arithmetic input type. + */ + template + static inline typename std::enable_if::value, void>::type + filterY(const vpImage &I, OutputType &result, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) + { + const unsigned int stop = (size - 1) / 2; + FilterType res = (filter[0] * static_cast(I[r][c])); + + for (unsigned int i = 1; i <= stop; ++i) { + res += filter[i] * static_cast(I[r + i][c] + I[r - i][c]); + } + result = static_cast(res); + } + + /** + * \brief Filter along the vertical direction "in the middle" of the image (i.e not on a border). + * + * \tparam ImageType The type of pixels. In this case, it must be a color type (vpRGBa, vpHSV). + * \tparam OutputType The type of pixels in the resulting image. It can be an arithmetic type or a color type (vpRGBa, vpHSV). + * \tparam FilterType An arithmetic type. + * \param[in] I The image that must be filtered. + * \param[in] result The pixel resulting from the filtering operation. + * \param[in] r The row index. + * \param[in] c The column index. + * \param[in] filter The coefficients of the filter. + * \param[in] size The size of the filter. + * \return The method is enabled only for color input type. + */ + template + static inline typename std::enable_if::value, void>::type + filterY(const vpImage &I, OutputType &result, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) + { + const unsigned int stop = (size - 1) / 2; +#ifdef VISP_HAVE_OPENMP + vpColVector res(ImageType::nbChannels); +#else + static vpColVector res(ImageType::nbChannels); +#endif + filterChannel(I[r][c], res, filter[0]); + + for (unsigned int i = 1; i <= stop; ++i) { + filterChannel(I[r + i][c], I[r - i][c], res, filter[i]); + } + result = OutputType(res); + } + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + /** + * \brief Filter along the vertical direction "on the top border" of the image (the height of the border depends on + * the filter length). + * + * \tparam ImageType The type of pixels. In this case, it must be an arithmetic type. + * \tparam OutputType The type of pixels in the resulting image. In this case, it must be an arithmetic type. + * \tparam FilterType An arithmetic type. + * \param[in] I The image that must be filtered. + * \param[in] result The pixel resulting from the filtering operation. + * \param[in] r The row index. + * \param[in] c The column index. + * \param[in] filter The coefficients of the filter. + * \param[in] size The size of the filter. + * \return The method is enabled only for arithmetic input type. + */ + template + static inline typename std::enable_if::value, void>::type + filterYTopBorder(const vpImage &I, OutputType &result, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) + { + const unsigned int stop = (size - 1) / 2; + FilterType res = (filter[0] * static_cast(I[r][c])); + + for (unsigned int i = 1; i <= stop; ++i) { + if (r > i) { + res += filter[i] * static_cast(I[r + i][c] + I[r - i][c]); + } + else { + res += filter[i] * static_cast(I[r + i][c] + I[i - r][c]); + } + } + result = static_cast(res); + } + + /** + * \brief Filter along the vertical direction "on the top border" of the image (the height of the border depends on + * the filter length). + * + * \tparam ImageType The type of pixels. In this case, it must be a color type (vpRGBa, vpHSV). + * \tparam OutputType The type of pixels in the resulting image. It can be an arithmetic type or a color type (vpRGBa, vpHSV). + * \tparam FilterType An arithmetic type. + * \param[in] I The image that must be filtered. + * \param[in] result The pixel resulting from the filtering operation. + * \param[in] r The row index. + * \param[in] c The column index. + * \param[in] filter The coefficients of the filter. + * \param[in] size The size of the filter. + * \return std::enable_if::value, void>::type The method is enabled only for color input type. + */ + template + static inline typename std::enable_if::value, void>::type filterYTopBorder(const vpImage &I, OutputType &result, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) + { + const unsigned int stop = (size - 1) / 2; +#ifdef VISP_HAVE_OPENMP + vpColVector res(ImageType::nbChannels); +#else + static vpColVector res(ImageType::nbChannels); +#endif + filterChannel(I[r][c], res, filter[0]); + + for (unsigned int i = 1; i <= stop; ++i) { + if (r > i) { + filterChannel(I[r + i][c], I[r - i][c], res, filter[i]); + } + else { + filterChannel(I[r + i][c], I[i - r][c], res, filter[i]); + } + } + result = OutputType(res); + } + + /** + * \brief Filter along the vertical direction "on the bottom border" of the image (the height of the border depends on + * the filter length). + * + * \tparam ImageType The type of pixels. In this case, it must be an arithmetic type. + * \tparam OutputType The type of pixels in the resulting image. In this case, it must be an arithmetic type. + * \tparam FilterType An arithmetic type. + * \param[in] I The image that must be filtered. + * \param[in] result The pixel resulting from the filtering operation. + * \param[in] r The row index. + * \param[in] c The column index. + * \param[in] filter The coefficients of the filter. + * \param[in] size The size of the filter. + * \return std::enable_if::value, void>::type The method is enabled only for arithmetic input type. + */ + template + static inline typename std::enable_if::value, void>::type filterYBottomBorder(const vpImage &I, OutputType &result, unsigned int r, unsigned int c, + const FilterType *filter, unsigned int size) + { + const unsigned int height = I.getHeight(); + const unsigned int stop = (size - 1) / 2; + const unsigned int twiceHeight = 2 * height; + FilterType res = (filter[0] * static_cast(I[r][c])); + for (unsigned int i = 1; i <= stop; ++i) { + if ((r + i) < height) { + res += filter[i] * static_cast(I[r + i][c] + I[r - i][c]); + } + else { + res += filter[i] * static_cast(I[(twiceHeight - r) - i - 1][c] + I[r - i][c]); + } + } + result = static_cast(res); + } + + /** + * \brief Filter along the vertical direction "on the bottom border" of the image (the height of the border depends on + * the filter length). + * + * \tparam ImageType The type of pixels. In this case, it must be a color type (vpRGBa, vpHSV). + * \tparam OutputType The type of pixels in the resulting image. It can be an arithmetic type or a color type (vpRGBa, vpHSV). + * \tparam FilterType An arithmetic type. + * \param[in] I The image that must be filtered. + * \param[in] result The pixel resulting from the filtering operation. + * \param[in] r The row index. + * \param[in] c The column index. + * \param[in] filter The coefficients of the filter. + * \param[in] size The size of the filter. + * \return std::enable_if::value, void>::type The method is enabled only for color input type. + */ + template + static inline typename std::enable_if::value, void>::type filterYBottomBorder(const vpImage &I, OutputType &result, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) + { + const unsigned int stop = (size - 1) / 2; + const unsigned int height = I.getHeight(); + const unsigned int twiceHeight = 2 * height; +#ifdef VISP_HAVE_OPENMP + vpColVector res(ImageType::nbChannels); +#else + static vpColVector res(ImageType::nbChannels); +#endif + filterChannel(I[r][c], res, filter[0]); + + for (unsigned int i = 1; i <= stop; ++i) { + if ((r + i) < height) { + filterChannel(I[r + i][c], I[r - i][c], res, filter[i]); + } + else { + filterChannel(I[(twiceHeight - r) - i - 1][c], I[r - i][c], res, filter[i]); + } + } + result = OutputType(res); + } +#endif +#endif + + /*! + * Apply a Gaussian blur to an image. + * \tparam ImageType : Either an arithmetic type or a color image. + * \tparam OutputType : Either the same type than the ImageType, for color images, or an arithmetic type. + * \tparam FilterType : Either float, to accelerate the computation time, or double, to have greater precision. + * \param I : Input image. + * \param GI : Filtered image. + * \param size : Filter size. This value should be odd. + * \param sigma : Gaussian standard deviation. If it is equal to zero or + * negative, it is computed from filter size as sigma = (size-1)/6. + * \param normalize : Flag indicating whether to normalize the filter coefficients or not. + * \param p_mask : If different from nullptr, mask indicating which points to consider (true) or to ignore(false). + * + * \sa getGaussianKernel() to know which kernel is used. + */ +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + template + static inline void + gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size = 7, FilterType sigma = 0., bool normalize = true, + const vpImage *p_mask = nullptr) + { + if (size == 0 || size-1 > I.getWidth() || size-1 > I.getHeight()) { + std::ostringstream oss; + oss << "Image size (" << I.getWidth() << "x" << I.getHeight() << ") is too small for the Gaussian kernel (" + << "size=" << size << "), min size is " << (size-1); + throw vpException(vpException::dimensionError, oss.str()); + } + + FilterType *fg = new FilterType[(size + 1) / 2]; + vpImageFilter::getGaussianKernel(fg, size, sigma, normalize); + vpImage GIx; + vpImageFilter::filterX(I, GIx, fg, size, p_mask); + vpImageFilter::filterY(GIx, GI, fg, size, p_mask); + GIx.destroy(); + delete[] fg; + } +#else + template + static inline void + gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size, float sigma, bool normalize = true, + const vpImage *p_mask = nullptr) + { + gaussianBlur(I, GI, size, sigma, normalize, p_mask); + } + + template + static inline void + gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size = 7, FilterType sigma = 0., bool normalize = true, + const vpImage *p_mask = nullptr) + { + if (size == 0 || size-1 > I.getWidth() || size-1 > I.getHeight()) { + std::ostringstream oss; + oss << "Image size (" << I.getWidth() << "x" << I.getHeight() << ") is too small for the Gaussian kernel (" + << "size=" << size << "), min size is " << (size-1); + throw vpException(vpException::dimensionError, oss.str()); + } + + FilterType *fg = new FilterType[(size + 1) / 2]; + vpImageFilter::getGaussianKernel(fg, size, sigma, normalize); + vpImage GIx; + vpImageFilter::filterX(I, GIx, fg, size, p_mask); + vpImageFilter::filterY(GIx, GI, fg, size, p_mask); + GIx.destroy(); + delete[] fg; + } +#endif + +#if (VISP_CXX_STANDARD < VISP_CXX_STANDARD_11) + static void gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size = 7, double sigma = 0., bool normalize = true, + const vpImage *p_mask = nullptr); +#endif + + /*! + * Apply a 5x5 Gaussian filter to an image pixel. + * + * \param fr : Image to filter + * \param r : coordinates (row) of the pixel + * \param c : coordinates (column) of the pixel + */ + template static double gaussianFilter(const vpImage &fr, unsigned int r, unsigned int c) + { + const int val2 = 2; + return ((15.0 * fr[r][c]) + + (12.0 * (fr[r - 1][c] + fr[r][c - 1] + fr[r + 1][c] + fr[r][c + 1])) + + (9.0 * (fr[r - 1][c - 1] + fr[r + 1][c - 1] + fr[r - 1][c + 1] + fr[r + 1][c + 1])) + + (5.0 * (fr[r - val2][c] + fr[r][c - val2] + fr[r + val2][c] + fr[r][c + val2])) + + (4.0 * (fr[r - val2][c + 1] + fr[r - val2][c - 1] + fr[r - 1][c - val2] + fr[r + 1][c - val2] + fr[r + val2][c - 1] + + fr[r + val2][c + 1] + fr[r - 1][c + val2] + fr[r + 1][c + val2])) + + (2.0 * (fr[r - val2][c - val2] + fr[r + val2][c - val2] + fr[r - val2][c + val2] + fr[r + val2][c + val2]))) / 159.0; + } + // Gaussian pyramid operation + static void getGaussPyramidal(const vpImage &I, vpImage &GI); + static void getGaussXPyramidal(const vpImage &I, vpImage &GI); + static void getGaussYPyramidal(const vpImage &I, vpImage &GI); + + /*! + * Return the coefficients \f$G_i\f$ of a Gaussian filter. + * \tparam FilterType : Either float, to accelerate the computation time, or double, to have greater precision. + * \param[out] filter : Pointer to the half size filter kernel that should refer to a + * (size+1)/2 array. The first value refers to the central coefficient, the + * next one to the right coefficients. Left coefficients could be deduced by + * symmetry. + * \param[in] size : Filter size. This value should be odd and positive. + * \param[in] sigma : Gaussian standard deviation \f$ \sigma \f$. If it is equal to zero or negative, it is + * computed from filter size as sigma = (size-1)/6. + * \param[in] normalize : Flag indicating whether to normalize the filter coefficients or not. In that case + * \f$\Sigma G_i = 1 \f$. + * + * The function computes the \e (size+1)/2 values of the Gaussian filter coefficients \f$ G_i \f$ as: + * \f[ G_i = \frac{1}{\sigma \sqrt{2 \pi}} \exp{(-i^2 / (2. * \sigma^2))}\f] + */ + template + static void getGaussianKernel(FilterType *filter, unsigned int size, FilterType sigma = 0., bool normalize = true) + { + const unsigned int mod2 = 2; + if ((size % mod2) != 1) { + throw(vpImageException(vpImageException::incorrectInitializationError, "Bad Gaussian filter size")); + } + + if (sigma <= 0) { + sigma = static_cast((size - 1) / 6.0); + } + + int middle = (static_cast(size) - 1) / 2; + FilterType sigma2 = static_cast(vpMath::sqr(static_cast(sigma))); + FilterType coef1 = static_cast(1. / (static_cast(sigma) * sqrt(2. * M_PI))); + FilterType v_2_sigma2 = static_cast(2. * static_cast(sigma2)); + for (int i = 0; i <= middle; ++i) { + filter[i] = coef1 * static_cast(exp(static_cast(-static_cast(i * i) / v_2_sigma2))); + } + if (normalize) { + // renormalization + FilterType sum = 0; + const unsigned int val2 = 2U; + for (int i = 1; i <= middle; ++i) { + sum += val2 * filter[i]; + } + sum += filter[0]; + + for (int i = 0; i <= middle; ++i) { + filter[i] = filter[i] / sum; + } + } + } + + /*! + * Return the coefficients of a Gaussian derivative filter that may be used to + * compute spatial image derivatives after applying a Gaussian blur. + * + * \tparam FilterType : Either float, to accelerate the computation time, or double, to have greater precision. + * \param filter : Pointer to the filter kernel that should refer to a + * (size+1)/2 array. The first value refers to the central coefficient, the + * next one to the right coefficients. Left coefficients could be deduced by + * symmetry. + * \param size : Filter size. This value should be odd. + * \param sigma : Gaussian standard deviation. If it is equal to zero or negative, it is + * computed from filter size as sigma = (size-1)/6. + * \param normalize : Flag indicating whether to normalize the filter coefficients or not. + */ + template + static void getGaussianDerivativeKernel(FilterType *filter, unsigned int size, FilterType sigma = 0., bool normalize = true) + { + const unsigned int mod2 = 2; + if ((size % mod2) != 1) { + throw(vpImageException(vpImageException::incorrectInitializationError, "Bad Gaussian filter size")); + } + + if (sigma <= 0) { + sigma = static_cast((size - 1) / 6.0); + } + + const int half = 2; + int middle = (static_cast(size) - 1) / half; + FilterType sigma2 = static_cast(vpMath::sqr(static_cast(sigma))); + FilterType coef_1 = static_cast(1. / (static_cast(sigma) * sqrt(2. * M_PI))); + FilterType coef_1_over_2 = coef_1 / static_cast(2.); + FilterType v_2_coef_1 = static_cast(2.) * coef_1; + FilterType v_2_sigma2 = static_cast(2.) * sigma2; + filter[0] = 0.; + for (int i = 1; i <= middle; ++i) { + FilterType i_plus_1 = static_cast(i + 1); + FilterType i_minus_1 = static_cast(i - 1); + filter[i] = -coef_1_over_2 * (static_cast(exp(-static_cast(i_plus_1 * i_plus_1 / v_2_sigma2))) - static_cast(exp(-static_cast(i_minus_1 * i_minus_1 / v_2_sigma2)))); + } + + if (normalize) { + FilterType sum = static_cast(0); + for (int i = 1; i <= middle; ++i) { + FilterType i_ = static_cast(i); + sum += v_2_coef_1 * static_cast(exp(-static_cast(i_ * i_ / v_2_sigma2))); + } + sum += coef_1; + + for (int i = 1; i <= middle; ++i) { + filter[i] = filter[i] / sum; + } + } + } + + // Gradient along X + template + static void getGradX(const vpImage &I, vpImage &dIx, const vpImage *p_mask = nullptr) + { + const unsigned int height = I.getHeight(), width = I.getWidth(); + const unsigned int stopJ = width - 3; + const unsigned int val_3 = 3; + resizeAndInitializeIfNeeded(p_mask, height, width, dIx); + + for (unsigned int i = 0; i < height; ++i) { + for (unsigned int j = 0; j < val_3; ++j) { + // If a mask is used, the image is already initialized with 0s + bool computeVal = (p_mask == nullptr); + if (computeVal) { + dIx[i][j] = static_cast(0); + } + } + for (unsigned int j = 3; j < stopJ; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); + if (computeVal) { + dIx[i][j] = static_cast(vpImageFilter::derivativeFilterX(I, i, j)); + } + } + for (unsigned int j = stopJ; j < width; ++j) { + // If a mask is used, the image is already initialized with 0s + bool computeVal = (p_mask == nullptr); + if (computeVal) { + dIx[i][j] = static_cast(0); + } + } + } + } + + template + static void getGradX(const vpImage &I, vpImage &dIx, const FilterType *filter, unsigned int size, const vpImage *p_mask = nullptr) + { + const unsigned int height = I.getHeight(), width = I.getWidth(); + const unsigned int stop1J = (size - 1) / 2; + const unsigned int stop2J = width - ((size - 1) / 2); + resizeAndInitializeIfNeeded(p_mask, height, width, dIx); + + for (unsigned int i = 0; i < height; ++i) { + for (unsigned int j = 0; j < stop1J; ++j) { + // If a mask is used, the image is already initialized with 0s + bool computeVal = (p_mask == nullptr); + if (computeVal) { + dIx[i][j] = static_cast(0); + } + } + for (unsigned int j = stop1J; j < stop2J; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); + if (computeVal) { + dIx[i][j] = vpImageFilter::derivativeFilterX(I, i, j, filter, size); + } + } + for (unsigned int j = stop2J; j < width; ++j) { + // If a mask is used, the image is already initialized with 0s + bool computeVal = (p_mask == nullptr); + if (computeVal) { + dIx[i][j] = static_cast(0); + } + } + } + } + + /*! + * Compute the gradient along X after applying a gaussian filter along Y. + * \tparam FilterType : Either float, to accelerate the computation time, or double, to have greater precision. + * \param I : Input image + * \param dIx : Gradient along X. + * \param gaussianKernel : Gaussian kernel which values should be computed using vpImageFilter::getGaussianKernel(). + * \param gaussianDerivativeKernel : Gaussian derivative kernel which values should be computed using + * vpImageFilter::getGaussianDerivativeKernel(). + * \param size : Size of the Gaussian and Gaussian derivative kernels. + * \param p_mask : If different from nullptr, mask indicating which points to consider (true) or to ignore(false). + */ + template + static void getGradXGauss2D(const vpImage &I, vpImage &dIx, const FilterType *gaussianKernel, + const FilterType *gaussianDerivativeKernel, unsigned int size, const vpImage *p_mask = nullptr) + { + vpImage GIy; + vpImageFilter::filterY(I, GIy, gaussianKernel, size, p_mask); + vpImageFilter::getGradX(GIy, dIx, gaussianDerivativeKernel, size, p_mask); + } + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) +/** + * \brief Gradient filter along the X-axis for HSV images. + * + * \tparam ArithmeticType The encoding of the HSV channels. + * \tparam FilterType The type to use for the filter and the type of the resulting gradient image. + * \tparam useFullScale See the documentation of the vpHSV class to have details on this template parameter. + * \param[in] I The input image. + * \param[in] GIx The resulting gradient image along the X-axis. + * \param[in] nbThread The number of threads to use to compute the gradient. + * \param[in] p_mask Optional boolean mask to indicate which pixels should be considered and which should be ignored. + * \param[in] type The type of the gradient filter. + */ + template + static void gradientFilterX(const vpImage> &I, vpImage &GIx, const int &nbThread, const vpImage *p_mask, const vpImageFilter::vpCannyFilteringAndGradientType &type) + { + const unsigned int nbRows = I.getRows(), nbCols = I.getCols(); + GIx.resize(nbRows, nbCols, 0.); + std::vector filter(3); + FilterType scale; + switch (type) { + case vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING: + filter = { 1., 2., 1. }; + scale = 8.; + break; + case vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING: + filter = { 3., 10., 3. }; + scale = 32.; + break; + default: + throw(vpException(vpException::badValue, "Wrong type of filtering")); + } + + for (unsigned char i = 0; i < 3; ++i) { + filter[i] = filter[i] / scale; + } + +#ifdef VISP_HAVE_OPENMP + if (nbThread == 1) { + gradientFilterXMonothread(I, GIx, filter, p_mask); + } + else { + gradientFilterXMultithread(I, GIx, filter, nbThread, p_mask); + } +#else + (void)nbThread; // Unused paramter when OpenMP is unavailable + gradientFilterXMonothread(I, GIx, filter, p_mask); +#endif + } +#endif + + // Gradient along Y + template + static void getGradY(const vpImage &I, vpImage &dIy, const vpImage *p_mask = nullptr) + { + const unsigned int height = I.getHeight(), width = I.getWidth(); + const unsigned int stopI = height - 3; + resizeAndInitializeIfNeeded(p_mask, height, width, dIy); + const unsigned int val_3 = 3; + for (unsigned int i = 0; i < val_3; ++i) { + for (unsigned int j = 0; j < width; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); + if (computeVal) { + dIy[i][j] = static_cast(0); + } + } + } + for (unsigned int i = 3; i < stopI; ++i) { + for (unsigned int j = 0; j < width; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); + if (computeVal) { + dIy[i][j] = static_cast(vpImageFilter::derivativeFilterY(I, i, j)); + } + } + } + for (unsigned int i = stopI; i < height; ++i) { + for (unsigned int j = 0; j < width; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); + if (computeVal) { + dIy[i][j] = static_cast(0); + } + } + } + } + + template + static void getGradY(const vpImage &I, vpImage &dIy, const FilterType *filter, unsigned int size, const vpImage *p_mask = nullptr) + { + const unsigned int height = I.getHeight(), width = I.getWidth(); + const unsigned int stop1I = (size - 1) / 2; + const unsigned int stop2I = height - ((size - 1) / 2); + resizeAndInitializeIfNeeded(p_mask, height, width, dIy); + + for (unsigned int i = 0; i < stop1I; ++i) { + for (unsigned int j = 0; j < width; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); + if (computeVal) { + dIy[i][j] = static_cast(0); + } + } + } + for (unsigned int i = stop1I; i < stop2I; ++i) { + for (unsigned int j = 0; j < width; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); + if (computeVal) { + dIy[i][j] = vpImageFilter::derivativeFilterY(I, i, j, filter, size); + } + } + } + for (unsigned int i = stop2I; i < height; ++i) { + for (unsigned int j = 0; j < width; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); + if (computeVal) { + dIy[i][j] = static_cast(0); + } + } + } + } + + /*! + * Compute the gradient along Y after applying a gaussian filter along X. + * \tparam FilterType : Either float, to accelerate the computation time, or double, to have greater precision. + * \param I : Input image + * \param dIy : Gradient along Y. + * \param gaussianKernel : Gaussian kernel which values should be computed using vpImageFilter::getGaussianKernel(). + * \param gaussianDerivativeKernel : Gaussian derivative kernel which values should be computed using + * vpImageFilter::getGaussianDerivativeKernel(). + * \param size : Size of the Gaussian and Gaussian derivative kernels. + * \param p_mask : If different from nullptr, mask indicating which points to consider (true) or to ignore(false). + */ + template + static void getGradYGauss2D(const vpImage &I, vpImage &dIy, const FilterType *gaussianKernel, + const FilterType *gaussianDerivativeKernel, unsigned int size, const vpImage *p_mask = nullptr) + { + vpImage GIx; + vpImageFilter::filterX(I, GIx, gaussianKernel, size, p_mask); + vpImageFilter::getGradY(GIx, dIy, gaussianDerivativeKernel, size, p_mask); + } + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + /** + * \brief Gradient filter along the Y-axis for HSV images. + * + * \tparam ArithmeticType The encoding of the HSV channels. + * \tparam FilterType The type to use for the filter and the type of the resulting gradient image. + * \tparam useFullScale See the documentation of the vpHSV class to have details on this template parameter. + * \param[in] I The input image. + * \param[in] GIy The resulting gradient image along the Y-axis. + * \param[in] nbThread The number of threads to use to compute the gradient. + * \param[in] p_mask Optional boolean mask to indicate which pixels should be considered and which should be ignored. + * \param[in] type The type of the gradient filter. + */ + template + static void gradientFilterY(const vpImage> &I, vpImage &GIy, const int &nbThread, const vpImage *p_mask, const vpImageFilter::vpCannyFilteringAndGradientType &type) + { + const unsigned int nbRows = I.getRows(), nbCols = I.getCols(); + GIy.resize(nbRows, nbCols); + std::vector filter(3); + FilterType scale; + switch (type) { + case vpImageFilter::CANNY_COUNT_FILTERING: + // Prewitt case + filter = { 1., 1., 1. }; + scale = 6.; + break; + case vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING: + filter = { 1., 2., 1. }; + scale = 8.; + break; + case vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING: + filter = { 3., 10., 3. }; + scale = 32.; + break; + default: + throw(vpException(vpException::badValue, "Wrong type of filtering")); + } + for (unsigned char i = 0; i < 3; ++i) { + filter[i] = filter[i] / scale; + } + +#ifdef VISP_HAVE_OPENMP + if (nbThread == 1) { + gradientFilterYMonothread(I, GIy, filter, p_mask); + } + else { + gradientFilterYMultithread(I, GIy, filter, nbThread, p_mask); + } +#else + (void)nbThread; // Unused paramter when OpenMP is unavailable + gradientFilterYMonothread(I, GIy, filter, p_mask); +#endif + } +#endif + + /*! + Get Scharr kernel for X-direction. + \tparam FilterType : Either float, to accelerate the computation time, or double, to have greater precision. + \param filter : Pointer to a double array already allocated. + \param size : Kernel size computed as: kernel_size = size*2 + 1 (max size is 20). + \return Scaling factor to normalize the Scharr kernel. + */ + template + inline static FilterType getScharrKernelX(FilterType *filter, unsigned int size) + { + const unsigned int actualKernelSize = (size * 2) + 1; + if (size != 1) { + // Size = 1 => kernel_size = 2*1 + 1 = 3 + std::stringstream errMsg; + errMsg << "Cannot get Scharr kernel of size " << actualKernelSize << " != 3"; + throw vpException(vpException::dimensionError, errMsg.str()); + } + + vpArray2D ScharrY(actualKernelSize, actualKernelSize); + FilterType norm = getScharrKernelY(ScharrY.data, size); + memcpy(filter, ScharrY.t().data, ScharrY.getRows() * ScharrY.getCols() * sizeof(FilterType)); + return norm; + } + + /*! + Get Scharr kernel for Y-direction. + \tparam FilterType : Either float, to accelerate the computation time, or double, to have greater precision. + \param filter : Pointer to a double array already allocated. + \param size : Kernel size computed as: kernel_size = size*2 + 1 (max size is 20). + \return Scaling factor to normalize the Scharr kernel. + */ + template + inline static FilterType getScharrKernelY(FilterType *filter, unsigned int size) + { + // Scharr kernel pre-computed for the usual size + static const FilterType ScharrY3x3[9] = { -3.0, -10.0, -3.0, 0.0, 0.0, 0.0, 3.0, 10.0, 3.0 }; + + if (size != 1) { + // Size = 1 => kernel_size = 2*1 + 1 = 3 + std::stringstream errMsg; + errMsg << "Cannot get Scharr kernel of size " << ((size * 2) + 1) << " != 3"; + throw vpException(vpException::dimensionError, errMsg.str()); + } + + const unsigned int kernel_size = (size * 2) + 1; + const unsigned int kernel3 = 3; + if (kernel_size == kernel3) { + memcpy(filter, ScharrY3x3, kernel_size * kernel_size * sizeof(FilterType)); + return static_cast(1.0 / 32.0); + } + + return static_cast(0.); + } + + /*! + * Get Sobel kernel for X-direction. + * \tparam FilterType: Either float, to accelerate the computation time, or double, to have greater precision. + * \param filter : Pointer to a double array already allocated. + * \param size : Kernel size computed as: kernel_size = size*2 + 1 (max size is 20). + * \return Scaling factor to normalize the Sobel kernel. + */ + template + inline static FilterType getSobelKernelX(FilterType *filter, unsigned int size) + { + const unsigned int maxSize = 20; + if (size == 0) { + throw vpException(vpException::dimensionError, "Cannot get Sobel kernel of size 0!"); + } + if (size > maxSize) { + throw vpException(vpException::dimensionError, "Cannot get Sobel kernel of size > 20!"); + } + + const unsigned int kernel_size = (size * 2) + 1; + vpArray2D SobelY(kernel_size, kernel_size); + FilterType norm = getSobelKernelY(SobelY.data, size); + memcpy(filter, SobelY.t().data, SobelY.getRows() * SobelY.getCols() * sizeof(FilterType)); + return norm; + } + + /*! + * Get Sobel kernel for Y-direction. + * \tparam FilterType : Either float, to accelerate the computation time, or double, to have greater precision. + * \param filter : Pointer to a double array already allocated. + * \param size : Kernel size computed as: kernel_size = size*2 + 1 (max size is 20). + * \return Scaling factor to normalize the Sobel kernel. + */ + template + inline static FilterType getSobelKernelY(FilterType *filter, unsigned int size) + { + // Sobel kernel pre-computed for the usual size + static const FilterType SobelY3x3[9] = { -1.0, -2.0, -1.0, 0.0, 0.0, 0.0, 1.0, 2.0, 1.0 }; + static const FilterType SobelY5x5[25] = { -1.0, -4.0, -6.0, -4.0, -1.0, -2.0, -8.0, -12.0, -8.0, -2.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 2.0, 8.0, 12.0, 8.0, 2.0, 1.0, 4.0, 6.0, 4.0, 1.0 }; + static const FilterType SobelY7x7[49] = { -1, -6, -15, -20, -15, -6, -1, -4, -24, -60, -80, -60, -24, -4, -5, -30, -75, + -100, -75, -30, -5, 0, 0, 0, 0, 0, 0, 0, 5, 30, 75, 100, 75, 30, + 5, 4, 24, 60, 80, 60, 24, 4, 1, 6, 15, 20, 15, 6, 1 }; + const vpArray2D smoothingKernel(3, 3); + const unsigned int index_0 = 0; + const unsigned int index_1 = 1; + const unsigned int index_2 = 2; + smoothingKernel[index_0][index_0] = 1.0; + smoothingKernel[index_0][index_1] = 2.0; + smoothingKernel[index_0][index_2] = 1.0; + smoothingKernel[index_1][index_0] = 2.0; + smoothingKernel[index_1][index_1] = 4.0; + smoothingKernel[index_1][index_2] = 2.0; + smoothingKernel[index_2][index_0] = 1.0; + smoothingKernel[index_2][index_1] = 2.0; + smoothingKernel[index_2][index_2] = 1.0; + + const unsigned int maxSize = 20; + if (size == 0) { + throw vpException(vpException::dimensionError, "Cannot get Sobel kernel of size 0!"); + } + if (size > maxSize) { + throw vpException(vpException::dimensionError, "Cannot get Sobel kernel of size > 20!"); + } + + const unsigned int kernel_size = (size * 2) + 1; + FilterType scale = static_cast(1. / 8.); // Scale to normalize Sobel3x3 + const unsigned int kernel3 = 3, kernel5 = 5, kernel7 = 7; + if (kernel_size == kernel3) { + memcpy(filter, SobelY3x3, kernel_size * kernel_size * sizeof(FilterType)); + return scale; + } + scale *= static_cast(1. / 16.); // Sobel5x5 is the convolution of smoothingKernel, which needs 1/16 scale factor, with Sobel3x3 + if (kernel_size == kernel5) { + memcpy(filter, SobelY5x5, kernel_size * kernel_size * sizeof(FilterType)); + return scale; + } + scale *= static_cast(1. / 16.); // Sobel7x7 is the convolution of smoothingKernel, which needs 1/16 scale factor, with Sobel5x5 + if (kernel_size == kernel7) { + memcpy(filter, SobelY7x7, kernel_size * kernel_size * sizeof(FilterType)); + return scale; + } + + vpArray2D sobelY(7, 7); + memcpy(sobelY.data, SobelY7x7, sobelY.getRows() * sobelY.getCols() * sizeof(FilterType)); + for (unsigned int i = 4; i <= size; ++i) { + sobelY = vpArray2D::conv2(sobelY, smoothingKernel, "full"); + // Sobel(N+1)x(N+1) is the convolution of smoothingKernel, which needs 1/16 scale factor, with SobelNxN + scale *= static_cast(1. / 16.); + } + + memcpy(filter, sobelY.data, sobelY.getRows() * sobelY.getCols() * sizeof(FilterType)); + + return scale; + } + +#if defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) + static float median(const cv::Mat &cv_I); + static float median(const vpImage &Isrc); + static std::vector median(const vpImage &Isrc); +#endif + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + /** + * \brief Compute the horizontal and vertical gradients for HSV images. + * + * \tparam ArithmeticType The encoding of the HSV channels. + * \tparam FilterType The type to use for the filter and the type of the resulting gradient image. + * \tparam useFullScale See the documentation of the vpHSV class to have details on this template parameter. + * \param[in] I The input image. + * \param[in] GIx The resulting gradient image along the X-axis. + * \param[in] GIy The resulting gradient image along the Y-axis. + * \param[in] nbThread The number of threads to use to compute the gradient. + * \param[in] p_mask Optional boolean mask to indicate which pixels should be considered and which should be ignored. + * \param[in] type The type of the gradient filter. + */ + template + static void gradientFilter(const vpImage> &I, vpImage &GIx, vpImage &GIy, const int &nbThread = -1, const vpImage *p_mask = nullptr, const vpImageFilter::vpCannyFilteringAndGradientType &type = CANNY_GBLUR_SCHARR_FILTERING) + { + const unsigned int nbRows = I.getRows(), nbCols = I.getCols(); + GIx.resize(nbRows, nbCols, 0.); + GIy.resize(nbRows, nbCols, 0.); + gradientFilterX(I, GIx, nbThread, p_mask, type); + gradientFilterY(I, GIy, nbThread, p_mask, type); + } +#endif + +private: + /** + * \brief Resize the image \b I to the desired size and, if \b p_mask is different from nullptr, initialize + * \b I with 0s. + * + * \tparam ImageType Any numerical type (int, float, ...) + * \param p_mask If different from nullptr, a boolean mask that tells which pixels must be computed. + * \param height The desired height. + * \param width The desired width. + * \param I The image that must be resized and potentially initialized. + */ + template + static void resizeAndInitializeIfNeeded(const vpImage *p_mask, const unsigned int height, const unsigned int width, vpImage &I) + { + if (p_mask == nullptr) { + // Just need to resize the output image, values will be computed and overwrite what is inside the image + I.resize(height, width); + } + else { + // Need to reset the image because some points will not be computed + I.resize(height, width, static_cast(0)); + } + } + + /** + * \brief Indicates if the boolean mask is true at the desired coordinates. + * + * \param[in] p_mask Pointer towards the boolean mask if any or nullptr. + * \param[in] r The row index in the boolean mask. + * \param[in] c The column index in the boolean mask. + * \return true If the boolean mask is true at the desired coordinates or if \b p_mask is equal to \b nullptr. + * \return false False otherwise. + */ + static bool checkBooleanMask(const vpImage *p_mask, const unsigned int &r, const unsigned int &c) + { + bool computeVal = true; +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher + if (p_mask != nullptr) +#else + if (p_mask != NULL) +#endif + { + computeVal = (*p_mask)[r][c]; + } + return computeVal; + } + + /** + * \brief Indicates if the boolean mask is true at the desired coordinates. + * + * \param[in] p_mask Pointer towards the boolean mask if any or nullptr. + * \param[in] id The index in the boolean mask bitmap. + * \return true If the boolean mask is true at the desired coordinates or if \b p_mask is equal to \b nullptr. + * \return false False otherwise. + */ + static bool checkBooleanMask(const vpImage *p_mask, const int &id) + { + bool computeVal = true; +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher + if (p_mask != nullptr) +#else + if (p_mask != NULL) +#endif + { + computeVal = p_mask->bitmap[id]; + } + return computeVal; + } + + // Note that on ubuntu 12.04 __cplusplus is equal to 1 that's why in the next line we consider __cplusplus <= 199711L + // and not __cplusplus == 199711L +#if ((__cplusplus <= 199711L) || (defined(_MSVC_LANG) && (_MSVC_LANG == 199711L))) // Check if cxx98 + // Helper to apply the scale to the raw values of the filters + template + static void scaleFilter(vpArray2D &filter, const float &scale) + { + const unsigned int nbRows = filter.getRows(); + const unsigned int nbCols = filter.getCols(); + for (unsigned int r = 0; r < nbRows; ++r) { + for (unsigned int c = 0; c < nbCols; ++c) { + filter[r][c] = filter[r][c] * scale; + } + } + } +#endif + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + /** + * \brief Return true if the distance must be computed because at least one point in its neighborhood + * needs to compute a gradient. + * + * \param[in] p_mask The boolean mask that indicates for which pixels we must compute the gradient. + * \param[in] iter The iterator that indicates which pixel of the image is currently considered. + * \param[in] c The column counter. + * \param[in] h The height of the image. + * \param[in] w The width of the image. + * \param[in] isGradientX If true, it means that we are investigating an horizontal gradient. If false, it is a + * vertical gradient. + * \return true + * \return false + */ + static bool checkBooleanPatch(const vpImage *p_mask, const int &iter, const int &c, + const int &h, const int &w, + const bool &isGradientX) + { + if (!p_mask) { + return true; + } + const int maxIter = (h - 1) * w; + const int minIter = w; + bool hasToCompute = p_mask->bitmap[iter]; + if (c < w - 1) { // We do not compute gradient on the last column + hasToCompute |= p_mask->bitmap[iter + 1]; // I[r][c + 1]; + if (iter < maxIter) { // We do not compute gradient on the last row + hasToCompute |= p_mask->bitmap[iter + w + 1]; // I[r + 1][c + 1]; + } + } + + if (iter < maxIter) { // We do not compute gradient on the last row + hasToCompute |= p_mask->bitmap[iter + w]; // I[r + 1][c]; + } + + if (isGradientX) { + if (iter > minIter) { // We do not compute gradient on the first row + hasToCompute |= p_mask->bitmap[iter - w]; // I[r - 1][c]; + if (c < w - 1) { // We do not compute gradient on the last column + hasToCompute |= p_mask->bitmap[iter - w + 1]; // I[r - 1][c + 1]; + } + } + } + else { + if (c > 1) { // We do not compute gradient on the first column + hasToCompute |= p_mask->bitmap[iter - 1]; // Checking mask[r][c - 1] + if (iter < maxIter) { // We do not compute gradient on the last row + hasToCompute |= p_mask->bitmap[iter + w - 1]; // Checking mask[r + 1][c - 1] + } + } + } + return hasToCompute; + } + + + template + static typename std::enable_if::value, void>::type initGradientFilterDifferenceImageX( + const vpImage> &I, std::vector &Idiff + ) + { + const unsigned int nbCols = I.getCols(); + + Idiff[0] = static_cast(I.bitmap[1].V - I.bitmap[0].V); + + // Computing the difference and sign for row 1 column 0 + Idiff[nbCols] = static_cast(I.bitmap[nbCols + 1].V - I.bitmap[nbCols].V); + + for (unsigned int iter = 1; iter < nbCols - 1; ++iter) { + // Computing the difference and sign for row 0 + OutputType distanceRow0 = static_cast(I.bitmap[iter + 1].V - I.bitmap[iter].V); + Idiff[iter] = distanceRow0; + + // Computing the difference and sign for row 1 + OutputType distanceRow1 = static_cast(I.bitmap[nbCols + iter + 1].V - I.bitmap[nbCols + iter].V); + Idiff[nbCols + iter] = distanceRow1; + } + } + + template + static typename std::enable_if::value, void>::type gradientFilterXMonothread( + const vpImage> &I, vpImage &GI, const std::vector &filter, + const vpImage *p_mask = nullptr + ) + { + const int nbRows = I.getRows(), nbCols = I.getCols(); + const int size = I.getSize(); + const int offsetIdiff = nbCols; + + std::vector Idiff(size); + initGradientFilterDifferenceImageX(I, Idiff); + const int resetCounter = nbCols - 1; + const int stopIter = size - (nbCols + 1); + int counter = resetCounter, idCol = 0; + for (int iter = nbCols; iter < stopIter; ++iter) { + if (counter) { + // Computing the amplitude of the difference + OutputType futureDiff = 0.; + if (checkBooleanPatch(p_mask, iter + offsetIdiff, idCol, nbRows, nbCols, true)) { + futureDiff = static_cast(I.bitmap[iter + nbCols +1].V - I.bitmap[iter + offsetIdiff].V); + Idiff[iter + offsetIdiff] = futureDiff; + } + } + if (counter) { + if ((counter != resetCounter)) { + if (checkBooleanMask(p_mask, iter)) { + OutputType gradient = 0.; + int offset = iter - nbCols; // Looking in the row above first + for (int i = -1; i <= 1; ++i) { + // Kind of +/- (I[r + i][c + 1] - I[r + i][c]) +/- (I[r + i][c] - I[r + i][c - 1]) + gradient += filter[i + 1] * (Idiff[offset] + Idiff[offset - 1]); + offset += nbCols; // Preparing to look in the next row + } + GI.bitmap[iter] = gradient; + } + } + --counter; + } + else { + counter = resetCounter; + } + + if (idCol < resetCounter) { + ++idCol; + } + else { + idCol = 0; + } + } + } + + template + static typename std::enable_if::value, void>::type initGradientFilterDifferenceImageY( + const vpImage> &I, std::vector &Idiff + ) + { + const unsigned int nbCols = I.getCols(); + // Computing the sign and distance for the first row + for (unsigned int iter = 0; iter < nbCols; ++iter) { + OutputType distance = static_cast(I.bitmap[iter + nbCols].V - I.bitmap[iter].V); + Idiff[iter] = distance; + } + // Computing the distance and sign for I[1][0] + OutputType distance = static_cast(I.bitmap[nbCols + nbCols].V - I.bitmap[nbCols].V); + Idiff[nbCols] = distance; + } + + template + static typename std::enable_if::value, void>::type gradientFilterYMonothread( + const vpImage> &I, vpImage &GI, const std::vector &filter, + const vpImage *p_mask = nullptr + ) + { + const int nbRows = I.getRows(), nbCols = I.getCols(); + const int size = I.getSize(); + const int offsetIdiff = 1; + + std::vector Idiff(size); + initGradientFilterDifferenceImageY(I, Idiff); + const int resetCounter = nbCols - 1; + const int stopIter = size - (nbCols + 1); + int counter = resetCounter, iterSign = offsetIdiff; + for (int iter = nbCols; iter < stopIter; ++iter) { + // Computing the amplitude of the difference + OutputType futureDiff = 0.; + + if (checkBooleanPatch(p_mask, iter + offsetIdiff, iterSign, nbRows, nbCols, false)) { + futureDiff = static_cast(I.bitmap[iter + nbCols +1].V - I.bitmap[iter + offsetIdiff].V); + Idiff[iter + offsetIdiff] = futureDiff; + } + + if (counter) { + if ((counter != resetCounter)) { + if (checkBooleanMask(p_mask, iter)) { + OutputType gradient = 0.; + for (int i = -1; i <= 1; ++i) { + // Kind of +/- (I[r + 1][c + i] - I[r][c + 1]) +/- (I[r][c + i] - I[r - 1][c + 1]) + gradient += filter[i + 1] * (Idiff[iter + i] + Idiff[iter - nbCols + i]); + } + GI.bitmap[iter] = gradient; + } + } + --counter; + } + else { + counter = resetCounter; + } + if (iterSign < resetCounter) { + ++iterSign; + } + else { + iterSign = 0; + } + } + } + +#ifdef VISP_HAVE_OPENMP + template + static typename std::enable_if::value, void>::type initGradientFilterDifferenceImageX( + const vpImage> &I, std::vector &Idiff, + const int &istart, const int &iam + ) + { + const int nbCols = I.getCols(); + + if (iam > 0) { + Idiff[0] = static_cast(I.bitmap[istart - nbCols + 1].V - I.bitmap[istart - nbCols].V); + } + + // Computing the difference and sign for row 1 column 0, which corresponds to the current row of the image + Idiff[nbCols] = static_cast(I.bitmap[istart + 1].V - I.bitmap[istart].V); + + for (int iter = 1; iter < nbCols - 1; ++iter) { + if (iam > 0) { + // Computing the difference and sign for row 0, which corresponds to the previous row of the image + OutputType distanceRow0 = static_cast(I.bitmap[istart - nbCols + iter + 1].V - I.bitmap[istart - nbCols + iter].V); + Idiff[iter] = distanceRow0; + } + + // Computing the difference and sign for row 1, which corresponds to the current row of the image + OutputType distanceRow1 = static_cast(I.bitmap[istart + iter + 1].V - I.bitmap[istart + iter].V); + Idiff[nbCols + iter] = distanceRow1; + } + } + + template + static typename std::enable_if::value, void>::type gradientFilterXMultithread( + const vpImage> &I, vpImage &GI, const std::vector &filter, + const int &maxNbThread, const vpImage *p_mask = nullptr) + { + const int nbRows = I.getRows(), nbCols = I.getCols(); + const int offsetIdiff = nbCols; + const int resetCounter = nbCols - 1; + const int nrows(nbRows - 1); + + int nbThread = maxNbThread; + if (nbThread < 0) { + nbThread = omp_get_max_threads(); + } + + if (static_cast(nbRows) < (4 * nbThread)) { + gradientFilterXMonothread(I, GI, filter, p_mask); + } + + int iam, nt, irows, rstart, istart, istop; + +#pragma omp parallel default(shared) private(iam, nt, irows, rstart, istart, istop) num_threads(nbThread) + { + iam = omp_get_thread_num(); + nt = omp_get_num_threads(); + irows = nrows / nt; + // size of partition + rstart = irows * iam; + istart = rstart * nbCols; // starting array index + if (iam == nt-1) { + // last thread may do more + irows = nrows - rstart; + } + istop = istart + irows * nbCols; + + std::vector Idiff((irows + 2) * nbCols); + std::vector GItemp(irows * nbCols); + initGradientFilterDifferenceImageX(I, Idiff, istart, iam); + + int counter = resetCounter, idCol = 0; + int iterStart = (iam != 0 ? istart : istart + nbCols); + for (int iter = iterStart; iter < istop; ++iter) { + if (counter) { + // Computing the amplitude of the difference + OutputType futureDiff = 0.; + if (checkBooleanPatch(p_mask, iter + offsetIdiff, idCol, nbRows, nbCols, true)) { + futureDiff = static_cast(I.bitmap[iter + nbCols +1].V - I.bitmap[iter + offsetIdiff].V); + Idiff[iter + 2 * offsetIdiff - istart] = futureDiff; + } + } + if (counter) { + if ((counter != resetCounter)) { + if (checkBooleanMask(p_mask, iter)) { + OutputType gradient = 0.; + int offset = iter - istart; // Looking in the row above first + for (int i = -1; i <= 1; ++i) { + // Kind of +/- (I[r + i][c + 1] - I[r + i][c]) +/- (I[r + i][c] - I[r + i][c - 1]) + gradient += filter[i + 1] * (Idiff[offset] + Idiff[offset - 1]); + offset += nbCols; // Preparing to look in the next row + } + GItemp[iter - istart] = gradient; + } + } + --counter; + } + else { + counter = resetCounter; + } + + if (idCol < resetCounter) { + ++idCol; + } + else { + idCol = 0; + } + } +#pragma omp critical + { + std::memcpy(GI.bitmap + istart, GItemp.data(), GItemp.size() * sizeof(OutputType)); + } + } + } + + template + static typename std::enable_if::value, void>::type initGradientFilterDifferenceImageY( + const vpImage> &I, std::vector &Idiff, + const int &istart + ) + { + const int nbCols = I.getCols(); + // Computing the sign and distance for the first row, which corresponds to the row above the beginning of the gradient computation in the thread + int idDiff = 0; + for (int iter = istart - nbCols; iter < istart; ++iter) { + OutputType distance = static_cast(I.bitmap[iter + nbCols].V - I.bitmap[iter].V); + Idiff[idDiff] = distance; + ++idDiff; + } + // Computing the distance and sign for I[1][0] + OutputType distance = static_cast(I.bitmap[nbCols + nbCols].V - I.bitmap[nbCols].V); + Idiff[nbCols] = distance; + } + + template + static typename std::enable_if::value, void>::type gradientFilterYMultithread( + const vpImage> &I, vpImage &GI, const std::vector &filter, + const int &maxNbThread, const vpImage *p_mask = nullptr) + { + const int nbRows = I.getRows(), nbCols = I.getCols(); + const int offsetIdiff = 1; + const int resetCounter = nbCols - 1; + const int nrows(nbRows - 1); + + int nbThread = maxNbThread; + if (nbThread < 0) { + nbThread = omp_get_max_threads(); + } + + if (static_cast(nbRows) < (4 * nbThread)) { + gradientFilterXMonothread(I, GI, filter, p_mask); + } + + int iam, nt, irows, rstart, istart, istop; + +#pragma omp parallel default(shared) private(iam, nt, irows, rstart, istart, istop) num_threads(nbThread) + { + iam = omp_get_thread_num(); + nt = omp_get_num_threads(); + irows = nrows / nt; + // size of partition + rstart = irows * iam; + istart = rstart * nbCols; // starting array index + if (iam == nt-1) { + // last thread may do more + irows = nrows - rstart; + } + istop = istart + irows * nbCols; + + std::vector Idiff((irows + 2) * nbCols); + std::vector GItemp(irows * nbCols); + + if (iam == 0) { + initGradientFilterDifferenceImageY(I, Idiff); + } + else { + initGradientFilterDifferenceImageY(I, Idiff, istart); + } + + int counter = resetCounter, iterSign = offsetIdiff; + int iterStart = (iam != 0 ? istart : istart + nbCols); + for (int iter = iterStart; iter < istop; ++iter) { + // Computing the amplitude of the difference + OutputType futureDiff = 0.; + + if (checkBooleanPatch(p_mask, iter + offsetIdiff, iterSign, nbRows, nbCols, false)) { + futureDiff = static_cast(I.bitmap[iter + nbCols +1].V - I.bitmap[iter + offsetIdiff].V); + Idiff[iter - istart + nbCols + offsetIdiff] = futureDiff; + } + + if (counter) { + if ((counter != resetCounter)) { + if (checkBooleanMask(p_mask, iter)) { + OutputType gradient = 0.; + for (int i = -1; i <= 1; ++i) { + // Kind of +/- (I[r + 1][c + i] - I[r][c + 1]) +/- (I[r][c + i] - I[r - 1][c + 1]) + gradient += filter[i + 1] * (Idiff[iter - istart + nbCols + i] + Idiff[iter - istart + i]); + } + GItemp[iter - istart] = gradient; + } + } + --counter; + } + else { + counter = resetCounter; + } + if (iterSign < resetCounter) { + ++iterSign; + } + else { + iterSign = 0; + } + } +#pragma omp critical + { + std::memcpy(GI.bitmap + istart, GItemp.data(), GItemp.size() * sizeof(OutputType)); + } + } + } +#endif +#endif +}; +#if defined(__clang__) +# pragma clang diagnostic pop +#endif +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageMorphology.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageMorphology.h new file mode 100644 index 00000000..0f4ff764 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageMorphology.h @@ -0,0 +1,581 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Morphology tools. + */ + +/*! + \file vpImageMorphology.h + \brief Various mathematical morphology tools, erosion, dilatation... + +*/ + +#ifndef VP_IMAGE_MORPHOLOGY_H +#define VP_IMAGE_MORPHOLOGY_H + +#include +#include +#include +#include + +#include +#include +#include +#include + +#if defined(__clang__) +// Mute warning : '\tparam' command used in a comment that is not attached to a template declaration [-Wdocumentation] +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +BEGIN_VISP_NAMESPACE +/*! + \class vpImageMorphology + + \ingroup group_core_image + + \brief Various mathematical morphology tools, erosion, dilatation... +*/ +class VISP_EXPORT vpImageMorphology +{ +public: + /*! \enum vpConnexityType + Type of connexity 4, or 8. + */ + typedef enum + { + CONNEXITY_4, /*!< For a given pixel 4 neighbors are considered (left, + right, up, down) */ + CONNEXITY_8 /*!< For a given pixel 8 neighbors are considered (left, + right, up, down, and the 4 pixels located on the + diagonal) */ + } vpConnexityType; + +public: + template + static void erosion(vpImage &I, Type value, Type value_out, vpConnexityType connexity = CONNEXITY_4); + + template + static void dilatation(vpImage &I, Type value, Type value_out, vpConnexityType connexity = CONNEXITY_4); + + template + static void erosion(vpImage &I, const vpConnexityType &connexity = CONNEXITY_4); + + template + static void dilatation(vpImage &I, const vpConnexityType &connexity = CONNEXITY_4); + + template + static void erosion(vpImage &I, const int &size); + + template + static void dilatation(vpImage &I, const int &size); + +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) + /*! + @name Deprecated functions + */ + //@{ + /*! + \brief An erosion is performed with a flat structuring element + \f$ \left( B \left( x,y \right) = 0 \right) \f$. The erosion using + such a structuring element is equivalent to a local-minimum operator: \f[ + \left ( A \ominus B \right ) \left( x,y \right) = \textbf{min} \left \{ A + \left ( x+x', y+y' \right ) | \left ( x', y'\right ) \subseteq D_B \right \} + \f] + \deprecated Provided only for compat with previous releases. Use rather the template function erosion(vpImage &, const vpConnexityType &) + \param I : Gray-scale image to process. + \param connexity : Type of connexity: 4 or 8. + */ + VP_DEPRECATED static void erosion(vpImage &I, const vpConnexityType &connexity = CONNEXITY_4) + { + vpImageMorphology::erosion(I, connexity); + } + + /*! + \brief A dilatation is performed with a flat structuring element + \f$ \left( B \left( x,y \right) = 0 \right) \f$. The erosion using + such a structuring element is equivalent to a local-maximum operator: \f[ + \left ( A \ominus B \right ) \left( x,y \right) = \textbf{max} \left \{ A + \left ( x+x', y+y' \right ) | \left ( x', y'\right ) \subseteq D_B \right \} + \f] + + \deprecated Provided only for compat with previous releases. Use rather the template function dilatation(vpImage &, const vpConnexityType &) + \param I : Gray-scale image to process. + \param connexity : Type of connexity: 4 or 8. + */ + VP_DEPRECATED static void dilatation(vpImage &I, const vpConnexityType &connexity = CONNEXITY_4) + { + vpImageMorphology::dilatation(I, connexity); + } + //@} +#endif + +private: + template + class vpPixelOperation + { + public: + vpPixelOperation() { } + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + virtual ~vpPixelOperation() = default; +#endif + + virtual T operator()(const T &, const T &) = 0; + }; + + template + class vpPixelOperationMax : public vpPixelOperation + { + public: + vpPixelOperationMax() { } + + virtual T operator()(const T &a, const T &b) VP_OVERRIDE + { + return std::max(a, b); + } + }; + + template + class vpPixelOperationMin : public vpPixelOperation + { + public: + vpPixelOperationMin() { } + + T operator()(const T &a, const T &b) VP_OVERRIDE + { + return std::min(a, b); + } + }; + + /** + * \brief Modify the image by applying the \b operation on each of its elements on a 3x3 + * grid. + * + * \tparam T Either a class such as vpRGBa or a type such as double, unsigned char ... + * \param[out] I The image we want to modify. + * \param[in] null_value The value that is padded to the input image to manage the borders. + * \param[in] operation The operation to apply to its elements on a 3x3 grid. + * \param[in] connexity Either a 4-connexity, if we want to take into account only the horizontal + * and vertical neighbors, or a 8-connexity, if we want to also take into account the diagonal neighbors. + */ + template + static void imageOperation(vpImage &I, const T &null_value, vpPixelOperation *operation, const vpConnexityType &connexity = CONNEXITY_4); + + /** + * \brief Modify the image by applying the \b operation on each of its elements on a \b size x \b size + * grid. + * + * \tparam T Any type such as double, unsigned char ... + * \param[out] I The image we want to modify. + * \param[in] operation The operation to apply to its elements on a the grid. + * \param[in] size Size of the kernel of the operation. + */ + template + static void imageOperation(vpImage &I, vpPixelOperation *operation, const int &size = 3); + +}; + +/*! + + Erode a binary image using a structuring element of size one. + + \param I : Image to process. + \param value : Values of the pixels to erode. + \param value_out : Value to set if erosion is done. + \param connexity : Type of connexity: 4 or 8. + + To erode a black area in an unsigned char image, set \e value to + 0 and \e value_out to 255. + + To erode a white area in an unsigned char image with one element mask, set + \e value to 255 and \e value_out to 0. + + \sa dilatation() +*/ +template +void vpImageMorphology::erosion(vpImage &I, Type value, Type value_out, vpConnexityType connexity) +{ + if (I.getSize() == 0) { + std::cerr << "Input image is empty!" << std::endl; + return; + } + + vpImage J(I.getHeight() + 2, I.getWidth() + 2); + // Copy I to J and add border + unsigned int j_height = J.getHeight(); + unsigned int j_width = J.getWidth(); + for (unsigned int i = 0; i < j_height; ++i) { + if ((i == 0) || (i == (j_height - 1))) { + for (unsigned int j = 0; j < j_width; ++j) { + J[i][j] = value; + } + } + else { + J[i][0] = value; + memcpy(J[i] + 1, I[i - 1], sizeof(unsigned char) * I.getWidth()); + J[i][J.getWidth() - 1] = value; + } + } + + if (connexity == CONNEXITY_4) { + unsigned int i_height = I.getHeight(); + unsigned int i_width = I.getWidth(); + for (unsigned int i = 0; i < i_height; ++i) { + for (unsigned int j = 0; j < i_width; ++j) { + if (J[i + 1][j + 1] == value) { + // Consider 4 neighbors + if ((J[i][j + 1] == value_out) || // Top + (J[i + 2][j + 1] == value_out) || // Bottom + (J[i + 1][j] == value_out) || // Left + (J[i + 1][j + 2] == value_out)) { // Right + I[i][j] = value_out; + } + } + } + } + } + else { + unsigned int i_height = I.getHeight(); + unsigned int i_width = I.getWidth(); + for (unsigned int i = 0; i < i_height; ++i) { + for (unsigned int j = 0; j < i_width; ++j) { + if (J[i + 1][j + 1] == value) { + // Consider 8 neighbors + bool cond4firstneighbors = (J[i][j] == value_out) || (J[i][j + 1] == value_out) || + (J[i][j + 2] == value_out) || (J[i + 1][j] == value_out); + bool cond4secondneighbors = (J[i + 1][j + 2] == value_out) || (J[i + 2][j] == value_out) || + (J[i + 2][j + 1] == value_out) || (J[i + 2][j + 2] == value_out); + if (cond4firstneighbors || cond4secondneighbors) { + I[i][j] = value_out; + } + } + } + } + } +} + +/*! + + Dilate a binary image using a structuring element of size one. + + \param I : Image to process. + \param value : Values of the pixels to dilate. + \param value_out : Value to set if dilatation is done. + \param connexity : Type of connexity: 4 or 8. + + To dilate a black area in an unsigned char image with one element mask, set + \e value to 0 and \e value_out to 255. + + To dilate a white area in an unsigned char image with one element mask, set + \e value to 255 and \e value_out to 0. + + \sa erosion() +*/ +template +void vpImageMorphology::dilatation(vpImage &I, Type value, Type value_out, vpConnexityType connexity) +{ + if (I.getSize() == 0) { + std::cerr << "Input image is empty!" << std::endl; + return; + } + + vpImage J(I.getHeight() + 2, I.getWidth() + 2); + // Copy I to J and add border + unsigned int j_height = J.getHeight(); + unsigned int j_width = J.getWidth(); + for (unsigned int i = 0; i < j_height; ++i) { + if ((i == 0) || (i == (j_height - 1))) { + for (unsigned int j = 0; j < j_width; ++j) { + J[i][j] = value_out; + } + } + else { + J[i][0] = value_out; + memcpy(J[i] + 1, I[i - 1], sizeof(unsigned char) * I.getWidth()); + J[i][J.getWidth() - 1] = value_out; + } + } + + if (connexity == CONNEXITY_4) { + unsigned int i_height = I.getHeight(); + unsigned int i_width = I.getWidth(); + for (unsigned int i = 0; i < i_height; ++i) { + for (unsigned int j = 0; j < i_width; ++j) { + if (J[i + 1][j + 1] == value_out) { + // Consider 4 neighbors + if ((J[i][j + 1] == value) || // Top + (J[i + 2][j + 1] == value) || // Bottom + (J[i + 1][j] == value) || // Left + (J[i + 1][j + 2] == value)) { // Right + I[i][j] = value; + } + } + } + } + } + else { + unsigned int i_height = I.getHeight(); + unsigned int i_width = I.getWidth(); + for (unsigned int i = 0; i < i_height; ++i) { + for (unsigned int j = 0; j < i_width; ++j) { + if (J[i + 1][j + 1] == value_out) { + // Consider 8 neighbors + bool cond4firstneighbors = (J[i][j] == value) || (J[i][j + 1] == value) || (J[i][j + 2] == value) || (J[i + 1][j] == value); + bool cond4secondneighbors = (J[i + 1][j + 2] == value) || (J[i + 2][j] == value) || (J[i + 2][j + 1] == value) || + (J[i + 2][j + 2] == value); + if (cond4firstneighbors || cond4secondneighbors) { + I[i][j] = value; + } + } + } + } + } +} + +template +void vpImageMorphology::imageOperation(vpImage &I, const T &null_value, vpPixelOperation *operation, const vpConnexityType &connexity) +{ + const int width_in = static_cast(I.getWidth()); + const int height_in = static_cast(I.getHeight()); + const unsigned int width_dilat = I.getWidth() + 2; + const unsigned int height_dilat = I.getHeight() + 2; + vpImage J(height_dilat, width_dilat, null_value); + + // Copy I to J and add border + J.insert(I, vpImagePoint(1, 1)); + + if (connexity == vpImageMorphology::CONNEXITY_4) { + const int nbOffset = 5; + int offset_x[nbOffset] = { 0, -1, 0, 1, 0 }; + int offset_y[nbOffset] = { -1, 0, 0, 0, 1 }; + + for (int i = 0; i < height_in; ++i) { + for (int j = 0; j < width_in; ++j) { + T value = null_value; + for (int k = 0; k < nbOffset; ++k) { + value = (*operation)(value, J[i + 1 + offset_y[k]][j + 1 + offset_x[k]]); + } + + I[i][j] = value; + } + } + } + else { + const int nbOffset = 9; + int offset_x[nbOffset] = { -1, 0, 1,-1, 0, 1,-1, 0, 1 }; + int offset_y[nbOffset] = { -1,-1,-1, 0, 0, 0, 1, 1, 1 }; + + for (int i = 0; i < height_in; ++i) { + for (int j = 0; j < width_in; ++j) { + T value = null_value; + for (int k = 0; k < nbOffset; ++k) { + value = (*operation)(value, J[i + 1 + offset_y[k]][j + 1 + offset_x[k]]); + } + + I[i][j] = value; + } + } + } +} + +/*! + Erode an image using the given structuring element. + + The erosion of \f$ A \left( x, y \right) \f$ by \f$ B \left (x, y + \right) \f$ is defined as: \f[ \left ( A \ominus B \right ) \left( x,y + \right) = \textbf{min} \left \{ A \left ( x+x', y+y' \right ) - B \left ( + x', y'\right ) | \left ( x', y'\right ) \subseteq D_B \right \} \f] where + \f$ D_B \f$ is the domain of the structuring element \f$ B \f$ and \f$ A + \left( x,y \right) \f$ is assumed to be \f$ + \infty \f$ outside the domain + of the image. + + In our case, the erosion is performed with a flat structuring element + \f$ \left( B \left( x,y \right) = 0 \right) \f$. The erosion using + such a structuring element is equivalent to a local-minimum operator: \f[ + \left ( A \ominus B \right ) \left( x,y \right) = \textbf{min} \left \{ A + \left ( x+x', y+y' \right ) | \left ( x', y'\right ) \subseteq D_B \right \} + \f] + + \param I : Image to process. + \param connexity : Type of connexity: 4 or 8. + + \sa dilatation(vpImage &, const vpConnexityType &) +*/ +template +void vpImageMorphology::erosion(vpImage &I, const vpConnexityType &connexity) +{ + vpPixelOperationMin operation; + vpImageMorphology::imageOperation(I, std::numeric_limits::max(), &operation, connexity); +} + +/*! + Dilate an image using the given structuring element. + + The dilatation of \f$ A \left( x, y \right) \f$ by \f$ B \left + (x, y \right) \f$ is defined as: \f[ \left ( A \oplus B \right ) \left( x,y + \right) = \textbf{max} \left \{ A \left ( x-x', y-y' \right ) + B \left ( + x', y'\right ) | \left ( x', y'\right ) \subseteq D_B \right \} \f] where + \f$ D_B \f$ is the domain of the structuring element \f$ B \f$ and \f$ A + \left( x,y \right) \f$ is assumed to be \f$ - \infty \f$ outside the domain + of the image. + + In our case, the dilatation is performed with a flat structuring element + \f$ \left( B \left( x,y \right) = 0 \right) \f$. The dilatation using + such a structuring element is equivalent to a local-maximum operator: \f[ + \left ( A \oplus B \right ) \left( x,y \right) = \textbf{max} \left \{ A + \left ( x-x', y-y' \right ) | \left ( x', y'\right ) \subseteq D_B \right \} + \f] + + \param I : Image to process. + \param connexity : Type of connexity: 4 or 8. + + \sa erosion(vpImage &, const vpConnexityType &) +*/ +template +void vpImageMorphology::dilatation(vpImage &I, const vpConnexityType &connexity) +{ + vpPixelOperationMax operation; + vpImageMorphology::imageOperation(I, std::numeric_limits::min(), &operation, connexity); +} + +template +void vpImageMorphology::imageOperation(vpImage &I, vpPixelOperation *operation, const int &size) +{ + if ((size % 2) != 1) { + throw(vpException(vpException::badValue, "Dilatation/erosion kernel must be odd.")); + } + + const int width_in = I.getWidth(); + const int height_in = I.getHeight(); + int halfKernelSize = size / 2; + vpImage J = I; + + for (int r = 0; r < height_in; ++r) { + // Computing the rows we can explore without going outside the limits of the image + int r_iterator_start = -halfKernelSize, r_iterator_stop = halfKernelSize + 1; + if ((r - halfKernelSize) < 0) { + r_iterator_start = -r; + } + else if ((r + halfKernelSize) >= height_in) { + r_iterator_stop = height_in - r; + } + for (int c = 0; c < width_in; ++c) { + T value = I[r][c]; + // Computing the columns we can explore without going outside the limits of the image + int c_iterator_start = -halfKernelSize, c_iterator_stop = halfKernelSize + 1; + if ((c - halfKernelSize) < 0) { + c_iterator_start = -c; + } + else if ((c + halfKernelSize) >= width_in) { + c_iterator_stop = width_in - c; + } + for (int r_iterator = r_iterator_start; r_iterator < r_iterator_stop; ++r_iterator) { + for (int c_iterator = c_iterator_start; c_iterator < c_iterator_stop; ++c_iterator) { + value = (*operation)(value, J[r + r_iterator][c + c_iterator]); + } + } + I[r][c] = value; + } + } +} + +/*! + * \brief Erosion of \b size >=3 with 8-connectivity. + Erode an image using the given structuring element. + + The erosion of \f$ A \left( x, y \right) \f$ by \f$ B \left (x, y + \right) \f$ is defined as: \f[ \left ( A \ominus B \right ) \left( x,y + \right) = \textbf{min} \left \{ A \left ( x+x', y+y' \right ) - B \left ( + x', y'\right ) | \left ( x', y'\right ) \subseteq D_B \right \} \f] where + \f$ D_B \f$ is the domain of the structuring element \f$ B \f$ and \f$ A + \left( x,y \right) \f$ is assumed to be \f$ + \infty \f$ outside the domain + of the image. + + In our case, the erosion is performed with a flat structuring element + \f$ \left( B \left( x,y \right) = 0 \right) \f$. The erosion using + such a structuring element is equivalent to a local-minimum operator: \f[ + \left ( A \ominus B \right ) \left( x,y \right) = \textbf{min} \left \{ A + \left ( x+x', y+y' \right ) | \left ( x', y'\right ) \subseteq D_B \right \} + \f] + + * \tparam T Any type of image, except vpRGBa . + * \param[out] I The image to which the erosion must be applied, where the erosion corresponds + * to a min operator on a window of size \b size. + * \param[in] size The size of the window on which is performed the min operator for each pixel. + + \sa dilatation(vpImage &, const int &) +*/ +template +void vpImageMorphology::erosion(vpImage &I, const int &size) +{ + vpPixelOperationMin operation; + vpImageMorphology::imageOperation(I, &operation, size); +} + +/** + * \brief Dilatation of \b size >=3 with 8-connectivity. + * + * The dilatation of \f$ A \left( x, y \right) \f$ by \f$ B \left + (x, y \right) \f$ is defined as: \f[ \left ( A \oplus B \right ) \left( x,y + \right) = \textbf{max} \left \{ A \left ( x-x', y-y' \right ) + B \left ( + x', y'\right ) | \left ( x', y'\right ) \subseteq D_B \right \} \f] where + \f$ D_B \f$ is the domain of the structuring element \f$ B \f$ and \f$ A + \left( x,y \right) \f$ is assumed to be \f$ - \infty \f$ outside the domain + of the image. + + In our case, the dilatation is performed with a flat structuring element + \f$ \left( B \left( x,y \right) = 0 \right) \f$. The dilatation using + such a structuring element is equivalent to a local-maximum operator: \f[ + \left ( A \oplus B \right ) \left( x,y \right) = \textbf{max} \left \{ A + \left ( x-x', y-y' \right ) | \left ( x', y'\right ) \subseteq D_B \right \} + \f] + * + * \tparam T Any type of image, except vpRGBa . + * \param[out] I The image to which the dilatation must be applied, where the dilatation corresponds + * to a max operator on a window of size \b size. + * \param[in] size The size of the window on which is performed the max operator for each pixel. + * + * \sa erosion(vpImage &, const int &) + */ +template +void vpImageMorphology::dilatation(vpImage &I, const int &size) +{ + vpPixelOperationMax operation; + vpImageMorphology::imageOperation(I, &operation, size); +} +END_VISP_NAMESPACE + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImagePoint.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImagePoint.h new file mode 100644 index 00000000..bb0207f6 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImagePoint.h @@ -0,0 +1,386 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * 2D point useful for image processing + */ + +/*! + \file vpImagePoint.h + \brief Class that defines a 2D point in an image. This class is useful + for image processing + */ + +#ifndef VP_IMAGE_POINT_H +#define VP_IMAGE_POINT_H + +#include + +#include // std::fabs +#include +#include + +BEGIN_VISP_NAMESPACE +class vpRect; + +/*! + \class vpImagePoint + \ingroup group_core_image + + \brief Class that defines a 2D point in an image. This class is + useful for image processing and stores only the 2D coordinates + given in sub-pixel. + + \warning If you want to define a point thanks to its coordinates + given in meter in the object frame, the camera frame or the image + plane, you have to use the class vpPoint. + + In this class, the 2D coordinates are not necessary integer + values. It is easy to manipulate the given coordinates in the two + frames used in ViSP : the (i,j) coordinates and the (u,v) + coordinates. The two following images illustrate the two coordinate + systems. + + \image html vpImagePoint.gif + \image latex vpImagePoint.ps width=10cm + + \warning An instance of the vpImagePoint class corresponds to a + particular point. Thus, if you change the point coordinate using the + method set_i(double i), it produces the same effect than if + you used the method set_v(double v). These two methods change + the same private attribute. It is also true for the two methods + set_j(double j) and set_u(double u). +*/ + +class VISP_EXPORT vpImagePoint +{ +public: + /*! + Default constructor that initialize the coordinates of the image + point to zero. + */ + inline vpImagePoint() : i(0), j(0) { } + /*! + Default constructor that initialize the coordinates of the image + thanks to the parameters \f$ ii \f$ and \f$ jj \f$. + */ + inline vpImagePoint(double ii, double jj) : i(ii), j(jj) { } + /*! + Copy constructor. + + Initialize the coordinates of the image point with \e ip. + + \param ip : An image point. + */ + inline vpImagePoint(const vpImagePoint &ip) : i(ip.i), j(ip.j) { } + //! Destructor. + inline virtual ~vpImagePoint() { } + + /*! + + Gets the point coordinate corresponding to the \f$ i \f$ axes in + the frame (i,j). + + \return The value of the coordinate along the \f$ i \f$ axes. + + \sa get_j(), get_u(), get_v() + */ + inline double get_i() const { return i; } + + /*! + + Gets the point coordinate corresponding to the \f$ j \f$ axes in + the frame (i,j). + + \return The value of the coordinate along the \f$ j \f$ axes. + + \sa get_i(), get_u(), get_v() + */ + inline double get_j() const { return j; } + + /*! + + Gets the point coordinate corresponding to the \f$ u \f$ axes in + the frame (u,v). + + \return The value of the coordinate along the \f$ u \f$ axes. + + \sa get_i(), get_j(), get_v() + */ + inline double get_u() const { return j; } + + /*! + + Gets the point coordinate corresponding to the \f$ v \f$ axes in + the frame (u,v). + + \return The value of the coordinate along the \f$ v \f$ axes. + + \sa get_i(), get_j(), get_u() + */ + inline double get_v() const { return i; } + + bool inRectangle(const vpRect &rect) const; + + /*! + * Test if the image point belongs to a segment represented by two image points. + * + * \param[in] start : Segment start image point. + * \param[in] end : Segment end image point. + * \return True if current image point belongs to the segment. False otherwise. + * + * To see how to use this function, a code snippet is given in nextInSegment(). + * + * \sa nextInSegment() + */ + inline bool inSegment(const vpImagePoint &start, const vpImagePoint &end) const + { + bool cond11 = ((end.get_j() >= start.get_j()) && (end.get_j() >= this->j) && (this->j >= start.get_j())); + bool cond12 = ((end.get_j() <= start.get_j()) && (end.get_j() <= this->j) && (this->j <= start.get_j())); + bool cond21 = ((end.get_i() >= start.get_i()) && (end.get_i() >= this->i) && (this->i >= start.get_i())); + bool cond22 = ((end.get_i() <= start.get_i()) && (end.get_i() <= this->i) && (this->i <= start.get_i())); + return (cond11 || cond12) && (cond21 || cond22); + } + + /*! + * Considering current image point, returns the next image point that belongs to the segment [start,end]. + * + * \param[in] start : Segment start image point. + * \param[in] end : Segment end image point. + * \return Regarding current image point, next image point that belongs to the liSegmentne [start,end]. + * + * The following sample code shows how to use this function to find all the pixels that belong + * to the segment defined by 2 image points with coordinates [10,12] and [20,16]: + * \code + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpImagePoint start_pixel(10, 12); + * vpImagePoint end_pixel(20, 16); + * + * for (auto curr_pixel = start_pixel; curr_pixel.inSegment(start_pixel, end_pixel); + * curr_pixel = curr_pixel.nextInSegment(start_pixel, end_pixel)) { + * std::cout << "pixel: " << curr_pixel << std::endl; + * if (curr_pixel == end_pixel) break; + * } + * + * return EXIT_SUCCESS; + * } + * \endcode + * + * It produces the following output by printing all the pixels belonging to the segment: + * \code + * pixel: 10, 12 + * pixel: 11, 12.4 + * pixel: 12, 12.8 + * pixel: 13, 13.2 + * pixel: 14, 13.6 + * pixel: 15, 14 + * pixel: 16, 14.4 + * pixel: 17, 14.8 + * pixel: 18, 15.2 + * pixel: 19, 15.6 + * pixel: 20, 16 + * \endcode + * + * \sa inSegment() + */ + inline vpImagePoint nextInSegment(const vpImagePoint &start, const vpImagePoint &end) const + { + const double line_slope = (end.get_i() - start.get_i()) / (end.get_j() - start.get_j()); + if (fabs(end.get_j() - this->j) > fabs(end.get_i() - this->i)) { + double j_ = (end.get_j() > this->j ? (this->j + 1) : (this->j - 1)); +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher + return { end.get_i() - (line_slope * (end.get_j() - j_)), j_ }; +#else + return vpImagePoint(end.get_i() - (line_slope * (end.get_j() - j_)), j_); +#endif + } + else { + double i_ = (end.get_i() > this->i ? (this->i + 1) : (this->i - 1)); +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher + return { i_, end.get_j() - ((end.get_i() - i_) / line_slope) }; +#else + return vpImagePoint(i_, end.get_j() - ((end.get_i() - i_) / line_slope)); +#endif + } + } + + /*! + Copy operator. + */ + inline vpImagePoint &operator=(const vpImagePoint &ip) + { + this->i = ip.i; + this->j = ip.j; + return *this; + } + +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher + /*! + Move operator. + */ + inline vpImagePoint &operator=(const vpImagePoint &&ip) noexcept + { + this->i = ip.i; + this->j = ip.j; + return *this; + } +#endif + + vpImagePoint &operator+=(const vpImagePoint &ip); + + /*! + + Operator -=. + + */ + inline vpImagePoint &operator-=(const vpImagePoint &ip) + { + this->i -= ip.i; + this->j -= ip.j; + return *this; + } + vpImagePoint &operator/=(double scale); + + /*! + + Operator *=. + */ + inline vpImagePoint &operator*=(double scale) + { + this->i *= scale; + this->j *= scale; + return *this; + } + + /*! + + Sets the point coordinate corresponding to the \f$ i \f$ axes in + the frame (i,j). + + \param ii : The desired value for the coordinate along the \f$ i \f$ axes. + + \sa set_j(), set_u(), set_v() + */ + inline void set_i(double ii) { this->i = ii; } + + /*! + + Sets the point coordinate corresponding to the \f$ j \f$ axes in + the frame (i,j). + + \param jj : The desired value for the coordinate along the \f$ j \f$ axes. + + \sa set_i(), set_u(), set_v() + */ + inline void set_j(double jj) { this->j = jj; } + + /*! + + Sets the point coordinates in the frame (i,j). + + \param ii : The desired value for the coordinate along the \f$ i \f$ axes. + \param jj : The desired value for the coordinate along the \f$ j \f$ axes. + + \sa set_i(), set_j(), set_u(), set_v() + */ + inline void set_ij(double ii, double jj) + { + this->i = ii; + this->j = jj; + } + + /*! + + Sets the point coordinate corresponding to the \f$ u \f$ axes in + the frame (u,v). + + \param u : The desired value for the coordinate along the \f$ u \f$ axes. + + \sa set_i(), set_j(), set_v() + */ + inline void set_u(double u) { j = u; } + + /*! + + Sets the point coordinate corresponding to the \f$ v \f$ axes in + the frame (u,v). + + \param v : The desired value for the coordinate along the \f$ v \f$ axes. + + \sa set_i(), set_j(), set_u() + */ + inline void set_v(double v) { i = v; } + + /*! + + Sets the point coordinates in the frame (u,v). + + \param u : The desired value for the coordinate along the \f$ u \f$ axes. + \param v : The desired value for the coordinate along the \f$ v \f$ axes. + + \sa set_i(), set_j(), set_u(), set_v() + */ + inline void set_uv(double u, double v) + { + this->i = v; + this->j = u; + } + + static double distance(const vpImagePoint &iP1, const vpImagePoint &iP2); + static vpRect getBBox(const std::vector &ipVec); + static double sqrDistance(const vpImagePoint &iP1, const vpImagePoint &iP2); + + friend VISP_EXPORT bool operator==(const vpImagePoint &ip1, const vpImagePoint &ip2); + friend VISP_EXPORT bool operator!=(const vpImagePoint &ip1, const vpImagePoint &ip2); + friend VISP_EXPORT vpImagePoint operator+=(const vpImagePoint &ip1, const vpImagePoint &ip2); + friend VISP_EXPORT vpImagePoint operator+(const vpImagePoint &ip1, const vpImagePoint &ip2); + friend VISP_EXPORT vpImagePoint operator+(const vpImagePoint &ip1, int offset); + friend VISP_EXPORT vpImagePoint operator+(const vpImagePoint &ip1, unsigned int offset); + friend VISP_EXPORT vpImagePoint operator+(const vpImagePoint &ip1, double offset); + friend VISP_EXPORT vpImagePoint operator-(const vpImagePoint &ip1, const vpImagePoint &ip2); + friend VISP_EXPORT vpImagePoint operator-(const vpImagePoint &ip1, int offset); + friend VISP_EXPORT vpImagePoint operator-(const vpImagePoint &ip1, unsigned int offset); + friend VISP_EXPORT vpImagePoint operator-(const vpImagePoint &ip1, double offset); + friend VISP_EXPORT vpImagePoint operator*(const vpImagePoint &ip1, double scale); + friend VISP_EXPORT vpImagePoint operator/(const vpImagePoint &ip1, double scale); + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpImagePoint &ip); + +private: + double i, j; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageTools.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageTools.h new file mode 100644 index 00000000..4e97c7de --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageTools.h @@ -0,0 +1,1548 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Image tools. + */ + +/*! + \file vpImageTools.h + + \brief Various image tools; sub-image extraction, modification of + the look up table, binarisation... +*/ + +#ifndef VP_IMAGE_TOOLS_H +#define VP_IMAGE_TOOLS_H + +#ifdef VISP_HAVE_THREADS +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#if defined(_OPENMP) +#include +#endif + +BEGIN_VISP_NAMESPACE +/*! + \class vpImageTools + + \ingroup group_core_image + + \brief Various image tools; sub-image extraction, modification of + the look up table, binarisation... +*/ +class VISP_EXPORT vpImageTools +{ +public: + enum vpImageInterpolationType + { + INTERPOLATION_NEAREST, /*!< Nearest neighbor interpolation. */ + INTERPOLATION_LINEAR, /*!< Bi-linear interpolation (optimized by SIMD lib if enabled). */ + INTERPOLATION_CUBIC, /*!< Bi-cubic interpolation. */ + INTERPOLATION_AREA /*!< Area interpolation (optimized by SIMD lib if enabled). */ + }; + + template + static inline void binarise(vpImage &I, Type threshold1, Type threshold2, Type value1, Type value2, Type value3, + bool useLUT = true); + static void changeLUT(vpImage &I, unsigned char A, unsigned char newA, unsigned char B, + unsigned char newB); + + template + static void crop(const vpImage &I, double roi_top, double roi_left, unsigned int roi_height, + unsigned int roi_width, vpImage &crop, unsigned int v_scale = 1, unsigned int h_scale = 1); + + static void columnMean(const vpImage &I, vpRowVector &result); + + template + static void crop(const vpImage &I, const vpImagePoint &topLeft, unsigned int roi_height, unsigned int roi_width, + vpImage &crop, unsigned int v_scale = 1, unsigned int h_scale = 1); + template + static void crop(const vpImage &I, const vpRect &roi, vpImage &crop, unsigned int v_scale = 1, + unsigned int h_scale = 1); + template + static void crop(const unsigned char *bitmap, unsigned int width, unsigned int height, const vpRect &roi, + vpImage &crop, unsigned int v_scale = 1, unsigned int h_scale = 1); + + static void extract(const vpImage &src, vpImage &dst, const vpRectOriented &r); + static void extract(const vpImage &src, vpImage &dst, const vpRectOriented &r); + + template static void flip(const vpImage &I, vpImage &newI); + + template static void flip(vpImage &I); + + static void imageDifference(const vpImage &I1, const vpImage &I2, + vpImage &Idiff); + static void imageDifference(const vpImage &I1, const vpImage &I2, vpImage &Idiff); + + static void imageDifferenceAbsolute(const vpImage &I1, const vpImage &I2, + vpImage &Idiff); + static void imageDifferenceAbsolute(const vpImage &I1, const vpImage &I2, vpImage &Idiff); + static void imageDifferenceAbsolute(const vpImage &I1, const vpImage &I2, vpImage &Idiff); + + static void imageAdd(const vpImage &I1, const vpImage &I2, vpImage &Ires, + bool saturate = false); + + static void imageSubtract(const vpImage &I1, const vpImage &I2, + vpImage &Ires, bool saturate = false); + + /*! + * Keep the part of an image that is in the mask. + * + * \param[in] I : Input image. + * \param[in] mask : Mask where pixels to consider have value equal to true. + * \param[out] I_mask : Resulting image where pixels that are in the mask are kept. + * \return The number of pixels that are in the mask. + * + * \sa To see how to use it to perform color segmentation on an image, \ref tutorial-hsv-segmentation-intro + */ + inline static int inMask(const vpImage &I, const vpImage &mask, vpImage &I_mask) + { + return inMask(I, mask, I_mask, true, vpRGBa(0, 0, 0)); + } + + /*! + * Keep the part of an image that is in the mask. + * + * \param[in] I : Input image. + * \param[in] mask : Mask where pixels to consider have values that differ from 0. + * \param[out] I_mask : Resulting image where pixels that are in the mask are kept. + * \return The number of pixels that are in the mask. + * + * \sa To see how to use it to perform color segmentation on an image, \ref tutorial-hsv-segmentation-intro + */ + inline static int inMask(const vpImage &I, const vpImage &mask, vpImage &I_mask) + { + const unsigned char inRangeVal = 255; + return inMask(I, mask, I_mask, inRangeVal, vpRGBa(0, 0, 0)); + } + + /*! + * Keep the part of an image that is in the mask. + * + * \param[in] I : Input image. + * \param[in] mask : Mask where pixels to consider have value equal to true. + * \param[out] I_mask : Resulting image where pixels that are in the mask are kept. + * \return The number of pixels that are in the mask. + * + * \sa To see how to use it to perform color segmentation on an image, \ref tutorial-hsv-segmentation-intro + */ + inline static int inMask(const vpImage &I, const vpImage &mask, vpImage &I_mask) + { + return inMask(I, mask, I_mask, true, static_cast(0)); + } + + /*! + * Keep the part of an image that is in the mask. + * + * \param[in] I : Input image. + * \param[in] mask : Mask where pixels to consider have values that differ from 0. + * \param[out] I_mask : Resulting image where pixels that are in the mask are kept. + * \return The number of pixels that are in the mask. + * + * \sa To see how to use it to perform color segmentation on an image, \ref tutorial-hsv-segmentation-intro + */ + inline static int inMask(const vpImage &I, const vpImage &mask, vpImage &I_mask) + { + const unsigned char inRangeVal = 255; + return inMask(I, mask, I_mask, inRangeVal, static_cast(0)); + } + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) +/*! + * Keep the part of an image that is in the mask. + * + * \param[in] I : Input image. + * \param[in] mask : Mask where pixels to consider have value equal to true. + * \param[out] I_mask : Resulting image where pixels that are in the mask are kept. + * \return The number of pixels that are in the mask. + * + * \sa To see how to use it to perform color segmentation on an image, \ref tutorial-hsv-segmentation-intro + */ + template + inline static int inMask(const vpImage> &I, const vpImage &mask, vpImage> &I_mask) + { + vpHSV black(static_cast(0), static_cast(0), static_cast(0)); + return inMask(I, mask, I_mask, true, black); + } + + /*! + * Keep the part of an image that is in the mask. + * + * \param[in] I : Input image. + * \param[in] mask : Mask where pixels to consider have values that differ from 0. + * \param[out] I_mask : Resulting image where pixels that are in the mask are kept. + * \return The number of pixels that are in the mask. + * + * \sa To see how to use it to perform color segmentation on an image, \ref tutorial-hsv-segmentation-intro + */ + template + inline static int inMask(const vpImage> &I, const vpImage &mask, vpImage> &I_mask) + { + const unsigned char inRangeVal = 255; + vpHSV black(static_cast(0), static_cast(0), static_cast(0)); + return inMask(I, mask, I_mask, inRangeVal, black); + } +#endif + + static int inRange(const unsigned char *hue, const unsigned char *saturation, const unsigned char *value, + const vpColVector &hsv_range, unsigned char *mask, unsigned int size); + static int inRange(const unsigned char *hue, const unsigned char *saturation, const unsigned char *value, + const std::vector &hsv_range, unsigned char *mask, unsigned int size); +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + /** + * \brief Create binary mask by checking if HSV (hue, saturation, value) channels lie between low and high HSV thresholds. + * + * \tparam ArithmeticType The arithmetic type used to encode the Hue, Saturation and Value channels. + * \tparam useFullScale If ArithmeticType is unsigned char, true means that Hue is encoded on the full + * range [0;255] and false means it is encoded in a limited range as defined in the vpHSV documentation. + * \tparam RangeType The arithmetic type used to encode the ranges. Be careful that the validity of the range values is + * not checked. + * \param[in] Iin The input image. + * \param[in] hsv_range 6-dim vector that contains the low/high range values for each HSV channel respectively. + * Each element of this vector should be in the range defined by the ArithmeticType and useFullRange template parameters. + * Note that there is also tutorial-hsv-tuner.cpp that may help to determine low/high HSV values. + * \param[in] out The output mask encoded as booleans. True means that the pixel is in range and false that it + * is not in range. + * \return int The number of pixels that are in the HSV range. + * + * \sa To see how to use it to perform color segmentation on an image, \ref tutorial-hsv-segmentation-intro or \ref tutorial-hsv-range-tuner + * \sa To see how to use it to perform color segmentation on a point-cloud , \ref tutorial-hsv-segmentation-pcl + */ + template + static int inRange(const vpImage> &Iin, + const std::vector &hsv_range, vpImage &out) + { + return inRange(Iin, hsv_range, out, true, false); + } + + /** + * \brief Create binary mask by checking if HSV (hue, saturation, value) channels lie between low and high HSV thresholds. + * + * \tparam ArithmeticType The arithmetic type used to encode the Hue, Saturation and Value channels. + * \tparam useFullScale If ArithmeticType is unsigned char, true means that Hue is encoded on the full + * range [0;255] and false means it is encoded in a limited range as defined in the vpHSV documentation. + * \tparam RangeType The arithmetic type used to encode the ranges. Be careful that the validity of the range values is + * not checked. + * \param[in] Iin The input image. + * \param[in] hsv_range 6-dim vector that contains the low/high range values for each HSV channel respectively. + * Each element of this vector should be in the range defined by the ArithmeticType and useFullRange template parameters. + * Note that there is also tutorial-hsv-tuner.cpp that may help to determine low/high HSV values. + * \param[in] out The output mask encoded as unsigned char. 255 means that the pixel is in range and 0 that it + * is not in range. + * \return int The number of pixels that are in the HSV range. + * + * \sa To see how to use it to perform color segmentation on an image, \ref tutorial-hsv-segmentation-intro or \ref tutorial-hsv-range-tuner + * \sa To see how to use it to perform color segmentation on a point-cloud , \ref tutorial-hsv-segmentation-pcl + */ + template + static int inRange(const vpImage> &Iin, + const std::vector &hsv_range, vpImage &out) + { + const unsigned char inRangeVal = 255; + return inRange(Iin, hsv_range, out, inRangeVal, static_cast(0)); + } + + /** + * \brief Create binary mask by checking if HSV (hue, saturation, value) channels lie between low and high HSV thresholds. + * + * \tparam ArithmeticType The arithmetic type used to encode the Hue, Saturation and Value channels. + * \tparam useFullScale If ArithmeticType is unsigned char, true means that Hue is encoded on the full + * range [0;255] and false means it is encoded in a limited range as defined in the vpHSV documentation. + * \param[in] Iin The input image. + * \param[in] hsv_range 6-dim vector that contains the low/high range values for each HSV channel respectively. + * Each element of this vector should be in the range defined by the ArithmeticType and useFullRange template parameters. + * Note that there is also tutorial-hsv-tuner.cpp that may help to determine low/high HSV values. + * \warning The range values will be converted in ArithmeticType without checking the validity of the values. + * \param[in] out The output mask encoded as booleans. True means that the pixel is in range and false that it + * is not in range. + * \return int The number of pixels that are in the HSV range. + * + * \sa To see how to use it to perform color segmentation on an image, \ref tutorial-hsv-segmentation-intro or \ref tutorial-hsv-range-tuner + * \sa To see how to use it to perform color segmentation on a point-cloud , \ref tutorial-hsv-segmentation-pcl + */ + template + static int inRange(const vpImage> &Iin, + const vpColVector &hsv_range, vpImage &out) + { + const unsigned int nbItems = hsv_range.getRows(); + std::vector range(nbItems); + for (unsigned int r = 0; r < nbItems; ++r) { + range[r] = static_cast(hsv_range[r]); + } + return inRange(Iin, range, out, true, false); + } + + /** + * \brief Create binary mask by checking if HSV (hue, saturation, value) channels lie between low and high HSV thresholds. + * + * \tparam ArithmeticType The arithmetic type used to encode the Hue, Saturation and Value channels. + * \tparam useFullScale If ArithmeticType is unsigned char, true means that Hue is encoded on the full + * range [0;255] and false means it is encoded in a limited range as defined in the vpHSV documentation. + * \param[in] Iin The input image. + * \param[in] hsv_range 6-dim vector that contains the low/high range values for each HSV channel respectively. + * Each element of this vector should be in the range defined by the ArithmeticType and useFullRange template parameters. + * Note that there is also tutorial-hsv-tuner.cpp that may help to determine low/high HSV values. + * \warning The range values will be converted in ArithmeticType without checking the validity of the values. + * \param[in] out The output mask encoded as unsigned char. 255 means that the pixel is in range and 0 that it + * is not in range. + * \return int The number of pixels that are in the HSV range. + * + * \sa To see how to use it to perform color segmentation on an image, \ref tutorial-hsv-segmentation-intro or \ref tutorial-hsv-range-tuner + * \sa To see how to use it to perform color segmentation on a point-cloud , \ref tutorial-hsv-segmentation-pcl + */ + template + static int inRange(const vpImage> &Iin, + const vpColVector &hsv_range, vpImage &out) + { + const unsigned char inRangeVal = 255; + const unsigned int nbItems = hsv_range.getRows(); + std::vector range(nbItems); + for (unsigned int r = 0; r < nbItems; ++r) { + range[r] = static_cast(hsv_range[r]); + } + return inRange(Iin, range, out, inRangeVal, static_cast(0)); + } +#endif + + + + static void initUndistortMap(const vpCameraParameters &cam, unsigned int width, unsigned int height, + vpArray2D &mapU, vpArray2D &mapV, vpArray2D &mapDu, + vpArray2D &mapDv); + + static double interpolate(const vpImage &I, const vpImagePoint &point, + const vpImageInterpolationType &method = INTERPOLATION_NEAREST); + + static void integralImage(const vpImage &I, vpImage &II, vpImage &IIsq); + + static double normalizedCorrelation(const vpImage &I1, const vpImage &I2, bool useOptimized = true); + + static void normalize(vpImage &I); + + static void remap(const vpImage &I, const vpArray2D &mapU, const vpArray2D &mapV, + const vpArray2D &mapDu, const vpArray2D &mapDv, vpImage &Iundist); + static void remap(const vpImage &I, const vpArray2D &mapU, const vpArray2D &mapV, + const vpArray2D &mapDu, const vpArray2D &mapDv, vpImage &Iundist); + + template + static void resize(const vpImage &I, vpImage &Ires, unsigned int width, unsigned int height, + const vpImageInterpolationType &method = INTERPOLATION_NEAREST, unsigned int nThreads = 0); + + template + static void resize(const vpImage &I, vpImage &Ires, + const vpImageInterpolationType &method = INTERPOLATION_NEAREST, unsigned int nThreads = 0); + + static void templateMatching(const vpImage &I, const vpImage &I_tpl, + vpImage &I_score, unsigned int step_u, unsigned int step_v, + bool useOptimized = true); + + template + static void undistort(const vpImage &I, const vpCameraParameters &cam, vpImage &newI, + unsigned int nThreads = 2); + + template + static void undistort(const vpImage &I, vpArray2D mapU, vpArray2D mapV, vpArray2D mapDu, + vpArray2D mapDv, vpImage &newI); + + template + static void warpImage(const vpImage &src, const vpMatrix &T, vpImage &dst, + const vpImageInterpolationType &interpolation = INTERPOLATION_NEAREST, + bool fixedPointArithmetic = true, bool pixelCenter = false); + +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) + /*! + @name Deprecated functions + */ + //@{ + template + VP_DEPRECATED static void createSubImage(const vpImage &I, unsigned int i_sub, unsigned int j_sub, + unsigned int nrow_sub, unsigned int ncol_sub, vpImage &S); + + template + VP_DEPRECATED static void createSubImage(const vpImage &I, const vpRect &rect, vpImage &S); + //@} +#endif + +private: + // Cubic interpolation + static float cubicHermite(const float A, const float B, const float C, const float D, const float t); + + template static Type getPixelClamped(const vpImage &I, float u, float v); + + static int coordCast(double x); + + // Linear interpolation + static double lerp(double A, double B, double t); + static float lerp(float A, float B, float t); + static int64_t lerp2(int64_t A, int64_t B, int64_t t, int64_t t_1); + + static double normalizedCorrelation(const vpImage &I1, const vpImage &I2, const vpImage &II, + const vpImage &IIsq, const vpImage &II_tpl, + const vpImage &IIsq_tpl, unsigned int i0, unsigned int j0); + + template + static void resizeBicubic(const vpImage &I, vpImage &Ires, unsigned int i, unsigned int j, float u, + float v, float xFrac, float yFrac); + + template + static void resizeBilinear(const vpImage &I, vpImage &Ires, unsigned int i, unsigned int j, float u, + float v, float xFrac, float yFrac); + + template + static void resizeNearest(const vpImage &I, vpImage &Ires, unsigned int i, unsigned int j, float u, + float v); + +#if defined(VISP_HAVE_SIMDLIB) + static void resizeSimdlib(const vpImage &Isrc, unsigned int resizeWidth, unsigned int resizeHeight, + vpImage &Idst, int method); + static void resizeSimdlib(const vpImage &Isrc, unsigned int resizeWidth, unsigned int resizeHeight, + vpImage &Idst, int method); +#endif + + template + static void warpNN(const vpImage &src, const vpMatrix &T, vpImage &dst, bool affine, bool centerCorner, + bool fixedPoint); + + template + static void warpLinear(const vpImage &src, const vpMatrix &T, vpImage &dst, bool affine, + bool centerCorner, bool fixedPoint); + + static bool checkFixedPoint(unsigned int x, unsigned int y, const vpMatrix &T, bool affine); + + static void warpLinearFixedPointNotCenter(const vpImage &src, const vpMatrix &T, vpImage &dst, bool affine); + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + template + static int inRange(const vpImage> &Iin, + const std::vector &hsv_range, vpImage &mask, const OutType &valueInRange, const OutType &valueOutRange) + { + const std::size_t val_6 = 6; + if (hsv_range.size() != val_6) { + throw(vpImageException(vpImageException::notInitializedError, + "Error in vpImageTools::inRange(): wrong values vector size (%d)", hsv_range.size())); + } + const unsigned int index_0 = 0; + const unsigned int index_1 = 1; + const unsigned int index_2 = 2; + const unsigned int index_3 = 3; + const unsigned int index_4 = 4; + const unsigned int index_5 = 5; + ArithmeticType h_low = static_cast(hsv_range[index_0]); + ArithmeticType h_high = static_cast(hsv_range[index_1]); + ArithmeticType s_low = static_cast(hsv_range[index_2]); + ArithmeticType s_high = static_cast(hsv_range[index_3]); + ArithmeticType v_low = static_cast(hsv_range[index_4]); + ArithmeticType v_high = static_cast(hsv_range[index_5]); + int size_ = Iin.getSize(); + mask.resize(Iin.getRows(), Iin.getCols()); + int cpt_in_range = 0; + +#if defined(VISP_HAVE_OPENMP) +#pragma omp parallel for reduction(+:cpt_in_range) +#endif + for (int i = 0; i < size_; ++i) { + bool check_h_low_high_hue = (h_low <= Iin.bitmap[i].H) && (Iin.bitmap[i].H <= h_high); + bool check_s_low_high_saturation = (s_low <= Iin.bitmap[i].S) && (Iin.bitmap[i].S <= s_high); + bool check_v_low_high_value = (v_low <= Iin.bitmap[i].V) && (Iin.bitmap[i].V <= v_high); + if (check_h_low_high_hue && check_s_low_high_saturation && check_v_low_high_value) { + mask.bitmap[i] = valueInRange; + ++cpt_in_range; + } + else { + mask.bitmap[i] = valueOutRange; + } + } + return cpt_in_range; + } +#endif + + template + static int inMask(const vpImage &I, const vpImage &mask, vpImage &I_mask + , const MaskType &inRangeCheck, const ImageType &outRangeValue) + { + if ((I.getHeight() != mask.getHeight()) || (I.getWidth() != mask.getWidth())) { + throw(vpImageException(vpImageException::incorrectInitializationError, + "Error in vpImageTools::inMask(): image (%dx%d) and mask (%dx%d) size doesn't match", + I.getWidth(), I.getHeight(), mask.getWidth(), mask.getHeight())); + } + + I_mask.resize(I.getHeight(), I.getWidth()); + int cpt_in_mask = 0; + int size_ = static_cast(I.getSize()); +#if defined(_OPENMP) +#pragma omp parallel for reduction(+:cpt_in_mask) +#endif + for (int i = 0; i < size_; ++i) { + if (mask.bitmap[i] == inRangeCheck) { + I_mask.bitmap[i] = I.bitmap[i]; + ++cpt_in_mask; + } + else { + I_mask.bitmap[i] = outRangeValue; + } + } + return cpt_in_mask; + } +#endif +}; + +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) +/*! + Crop a region of interest (ROI) in an image. + + \deprecated This function is deprecated. You should rather use + crop(const vpImage &, unsigned int, unsigned int, unsigned int, + unsigned int, vpImage &). + + \param I : Input image from which a sub image will be extracted. + \param roi_top : ROI vertical position of the upper/left corner in the input + image. + \param roi_left : ROI horizontal position of the upper/left corner + in the input image. + \param roi_height : Cropped image height corresponding to the ROI height. + \param roi_width : Cropped image width corresponding to the ROI height. + \param crop : Cropped image. + + \sa crop(const vpImage &, unsigned int, unsigned int, unsigned int, + unsigned int, vpImage &) +*/ +template +void vpImageTools::createSubImage(const vpImage &I, unsigned int roi_top, unsigned int roi_left, + unsigned int roi_height, unsigned int roi_width, vpImage &crop) +{ + vpImageTools::crop(I, roi_top, roi_left, roi_height, roi_width, crop); +} + +/*! + Crop an image region of interest. + + \deprecated This function is deprecated. You should rather use + crop(const vpImage &, const vpRect &, vpImage &). + + \param I : Input image from which a sub image will be extracted. + + \param roi : Region of interest in image \e I corresponding to the + cropped part of the image. + + \param crop : Cropped image. + + \sa crop(const vpImage &, const vpRect &, vpImage &) +*/ +template void vpImageTools::createSubImage(const vpImage &I, const vpRect &roi, vpImage &crop) +{ + vpImageTools::crop(I, roi, crop); +} + +#endif // #if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) + +/*! + Crop a region of interest (ROI) in an image. The ROI coordinates and + dimension are defined in the original image. + + Setting \e v_scale and \e h_scale to values different from 1 allows also to + subsample the cropped image. + + \param[in] I : Input image from which a sub image will be extracted. + \param[in] roi_top : ROI vertical position of the upper/left corner in the input + image. + \param[in] roi_left : ROI horizontal position of the upper/left corner + in the input image. + \param[in] roi_height : Cropped image height corresponding + to the ROI height. + \param[in] roi_width : Cropped image width corresponding to + the ROI height. + \param[out] crop : Cropped image. + \param[in] v_scale : Vertical subsampling factor applied to the ROI. + \param[in] h_scale : Horizontal subsampling factor applied to the ROI. + + \sa crop(const vpImage &, const vpRect &, vpImage &) +*/ +template +void vpImageTools::crop(const vpImage &I, double roi_top, double roi_left, unsigned int roi_height, + unsigned int roi_width, vpImage &crop, unsigned int v_scale, unsigned int h_scale) +{ + int i_min = std::max(static_cast(ceil(roi_top / v_scale)), 0); + int j_min = std::max(static_cast(ceil(roi_left / h_scale)), 0); + int i_max = std::min(static_cast(ceil(roi_top + roi_height) / v_scale), static_cast(I.getHeight() / v_scale)); + int j_max = std::min(static_cast(ceil(roi_left + roi_width) / h_scale), static_cast(I.getWidth() / h_scale)); + + unsigned int i_min_u = static_cast(i_min); + unsigned int j_min_u = static_cast(j_min); + + unsigned int r_width = static_cast(j_max - j_min); + unsigned int r_height = static_cast(i_max - i_min); + + crop.resize(r_height, r_width); + + if ((v_scale == 1) && (h_scale == 1)) { + for (unsigned int i = 0; i < r_height; ++i) { + void *src = (void *)(I[i + i_min_u] + j_min_u); + void *dst = static_cast(crop[i]); + memcpy(dst, src, r_width * sizeof(Type)); + } + } + else if (h_scale == 1) { + for (unsigned int i = 0; i < r_height; ++i) { + void *src = (void *)(I[(i + i_min_u) * v_scale] + j_min_u); + void *dst = static_cast(crop[i]); + memcpy(dst, src, r_width * sizeof(Type)); + } + } + else { + for (unsigned int i = 0; i < r_height; ++i) { + for (unsigned int j = 0; j < r_width; ++j) { + crop[i][j] = I[(i + i_min_u) * v_scale][(j + j_min_u) * h_scale]; + } + } + } +} + +/*! + Crop a region of interest (ROI) in an image. The ROI coordinates and + dimension are defined in the original image. + + Setting \e v_scale and \e h_scale to values different from 1 allows also to + subsample the cropped image. + + \param I : Input image from which a sub image will be extracted. + \param topLeft : ROI position of the upper/left corner in the input image. + \param roi_height : Cropped image height corresponding to the ROI height. + \param roi_width : Cropped image width corresponding to the ROI height. + \param crop : Cropped image. + \param v_scale [in] : Vertical subsampling factor applied to the ROI. + \param h_scale [in] : Horizontal subsampling factor applied to the ROI. + + \sa crop(const vpImage &, const vpRect &, vpImage &) +*/ +template +void vpImageTools::crop(const vpImage &I, const vpImagePoint &topLeft, unsigned int roi_height, + unsigned int roi_width, vpImage &crop, unsigned int v_scale, unsigned int h_scale) +{ + vpImageTools::crop(I, topLeft.get_i(), topLeft.get_j(), roi_height, roi_width, crop, v_scale, h_scale); +} + +/*! + Crop a region of interest (ROI) in an image. The ROI coordinates and + dimension are defined in the original image. + + Setting \e v_scale and \e h_scale to values different from 1 allows also to + subsample the cropped image. + + \param I : Input image from which a sub image will be extracted. + + \param roi : Region of interest in image \e I corresponding to the + cropped part of the image. + + \param crop : Cropped image. + \param v_scale [in] : Vertical subsampling factor applied to the ROI. + \param h_scale [in] : Horizontal subsampling factor applied to the ROI. +*/ +template +void vpImageTools::crop(const vpImage &I, const vpRect &roi, vpImage &crop, unsigned int v_scale, + unsigned int h_scale) +{ + vpImageTools::crop(I, roi.getTop(), roi.getLeft(), static_cast(roi.getHeight()), static_cast(roi.getWidth()), crop, + v_scale, h_scale); +} + +/*! + Crop a region of interest (ROI) in an image. The ROI coordinates and + dimension are defined in the original image. + + Setting \e v_scale and \e h_scale to values different from 1 allows also to + subsample the cropped image. + + \param[in] bitmap : Pointer to the input image from which a sub image will be extracted. + \param[in] width : Width of the input image. + \param[in] height : Height of the input image. + + \param[in] roi : Region of interest corresponding to the cropped part of the image. + + \param[out] crop : Cropped image. + \param[in] v_scale [in] : Vertical subsampling factor applied to the ROI. + \param[in] h_scale [in] : Horizontal subsampling factor applied to the ROI. +*/ +template +void vpImageTools::crop(const unsigned char *bitmap, unsigned int width, unsigned int height, const vpRect &roi, + vpImage &crop, unsigned int v_scale, unsigned int h_scale) +{ + int i_min = std::max(static_cast(ceil(roi.getTop() / v_scale)), 0); + int j_min = std::max(static_cast(ceil(roi.getLeft() / h_scale)), 0); + int i_max = std::min(static_cast(ceil((roi.getTop() + roi.getHeight()) / v_scale)), static_cast(height / v_scale)); + int j_max = std::min(static_cast(ceil((roi.getLeft() + roi.getWidth()) / h_scale)), static_cast(width / h_scale)); + + unsigned int i_min_u = static_cast(i_min); + unsigned int j_min_u = static_cast(j_min); + + unsigned int r_width = static_cast(j_max - j_min); + unsigned int r_height = static_cast(i_max - i_min); + + crop.resize(r_height, r_width); + + if (v_scale == 1 && h_scale == 1) { + for (unsigned int i = 0; i < r_height; ++i) { + void *src = (void *)(bitmap + ((((i + i_min_u) * width) + j_min_u) * sizeof(Type))); + void *dst = (void *)(crop[i]); + memcpy(dst, src, r_width * sizeof(Type)); + } + } + else if (h_scale == 1) { + for (unsigned int i = 0; i < r_height; ++i) { + void *src = (void *)(bitmap + (((((i + i_min_u) * width) * v_scale) + j_min_u) * sizeof(Type))); + void *dst = (void *)(crop[i]); + memcpy(dst, src, r_width * sizeof(Type)); + } + } + else { + for (unsigned int i = 0; i < r_height; ++i) { + unsigned int i_src = (((i + i_min_u) * width) * v_scale) + (j_min_u * h_scale); + for (unsigned int j = 0; j < r_width; ++j) { + void *src = (void *)(bitmap + ((i_src + (j * h_scale)) * sizeof(Type))); + void *dst = (void *)(&crop[i][j]); + memcpy(dst, src, sizeof(Type)); + } + } + } +} + +/*! + Binarise an image. + + - Pixels whose values are less than \e threshold1 are set to \e value1 + + - Pixels whose values are greater then or equal to \e threshold1 and + less then or equal to \e threshold2 are set to \e value2 + + - Pixels whose values are greater than \e threshold2 are set to \e value3 +*/ +template +inline void vpImageTools::binarise(vpImage &I, Type threshold1, Type threshold2, Type value1, Type value2, + Type value3, bool useLUT) +{ + if (useLUT) { + std::cerr << "LUT not available for this type ! Will use the iteration method." << std::endl; + } + + Type v; + Type *p = I.bitmap; + Type *pend = I.bitmap + (I.getWidth() * I.getHeight()); + for (; p < pend; ++p) { + v = *p; + if (v < threshold1) { + *p = value1; + } + else if (v > threshold2) { + *p = value3; + } + else { + *p = value2; + } + } +} + +/*! + Binarise an image. + + - Pixels whose values are less than \e threshold1 are set to \e value1 + + - Pixels whose values are greater then or equal to \e threshold1 and + less then or equal to \e threshold2 are set to \e value2 + + - Pixels whose values are greater than \e threshold2 are set to \e value3 +*/ +template <> +inline void vpImageTools::binarise(vpImage &I, unsigned char threshold1, unsigned char threshold2, + unsigned char value1, unsigned char value2, unsigned char value3, bool useLUT) +{ + if (useLUT) { + // Construct the LUT + const unsigned int sizeLut = 256; + unsigned char lut[sizeLut]; + for (unsigned int i = 0; i < sizeLut; ++i) { + lut[i] = i < threshold1 ? value1 : (i > threshold2 ? value3 : value2); + } + + I.performLut(lut); + } + else { + unsigned char *p = I.bitmap; + unsigned char *pend = I.bitmap + (I.getWidth() * I.getHeight()); + for (; p < pend; ++p) { + unsigned char v = *p; + if (v < threshold1) { + *p = value1; + } + else if (v > threshold2) { + *p = value3; + } + else { + *p = value2; + } + } + } +} + +#ifdef VISP_HAVE_THREADS + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +template class vpUndistortInternalType +{ +public: + Type *src; + Type *dst; + unsigned int width; + unsigned int height; + vpCameraParameters cam; + unsigned int nthreads; + unsigned int threadid; + +public: + vpUndistortInternalType() : src(nullptr), dst(nullptr), width(0), height(0), cam(), nthreads(0), threadid(0) { } + + vpUndistortInternalType(const vpUndistortInternalType &u) { *this = u; } + vpUndistortInternalType &operator=(const vpUndistortInternalType &u) + { + src = u.src; + dst = u.dst; + width = u.width; + height = u.height; + cam = u.cam; + nthreads = u.nthreads; + threadid = u.threadid; + + return *this; + } + + static void vpUndistort_threaded(vpUndistortInternalType &undistortSharedData); +}; + +template void vpUndistortInternalType::vpUndistort_threaded(vpUndistortInternalType &undistortSharedData) +{ + int offset = static_cast(undistortSharedData.threadid); + int width = static_cast(undistortSharedData.width); + int height = static_cast(undistortSharedData.height); + int nthreads = static_cast(undistortSharedData.nthreads); + + double u0 = undistortSharedData.cam.get_u0(); + double v0 = undistortSharedData.cam.get_v0(); + double px = undistortSharedData.cam.get_px(); + double py = undistortSharedData.cam.get_py(); + double kud = undistortSharedData.cam.get_kud(); + + double invpx = 1.0 / px; + double invpy = 1.0 / py; + + double kud_px2 = kud * invpx * invpx; + double kud_py2 = kud * invpy * invpy; + + Type *dst = undistortSharedData.dst + (height / nthreads * offset) * width; + Type *src = undistortSharedData.src; + + for (double v = height / nthreads * offset; v < height / nthreads * (offset + 1); ++v) { + double deltav = v - v0; + // double fr1 = 1.0 + kd * (vpMath::sqr(deltav * invpy)); + double fr1 = 1.0 + kud_py2 * deltav * deltav; + + for (double u = 0; u < width; ++u) { + // computation of u,v : corresponding pixel coordinates in I. + double deltau = u - u0; + // double fr2 = fr1 + kd * (vpMath::sqr(deltau * invpx)); + double fr2 = fr1 + kud_px2 * deltau * deltau; + + double u_double = deltau * fr2 + u0; + double v_double = deltav * fr2 + v0; + + // computation of the bilinear interpolation + + // declarations + int u_round = static_cast(u_double); + int v_round = static_cast(v_double); + if (u_round < 0) { + u_round = -1; + } + if (v_round < 0) { + v_round = -1; + } + double du_double = (u_double)-static_cast(u_round); + double dv_double = (v_double)-static_cast(v_round); + Type v01; + Type v23; + if ((0 <= u_round) && (0 <= v_round) && (u_round < (width-1)) && (v_round < (height-1))) { + // process interpolation + const Type *_mp = &src[v_round * width + u_round]; + v01 = (Type)(_mp[0] + ((_mp[1] - _mp[0]) * du_double)); + _mp += width; + v23 = (Type)(_mp[0] + ((_mp[1] - _mp[0]) * du_double)); + *dst = (Type)(v01 + ((v23 - v01) * dv_double)); + } + else { + *dst = 0; + } + dst++; + } + } +} +#endif // DOXYGEN_SHOULD_SKIP_THIS +#endif // VISP_HAVE_THREADS + +/*! + Undistort an image + + \param I : Input image to undistort. + + \param cam : Parameters of the camera causing distortion. + + \param undistI : Undistorted output image. The size of this image + will be the same than the input image \e I. If the distortion + parameter \f$k_{ud}\f$ is null, meaning that `cam.get_kud() == 0`, \e undistI is + just a copy of \e I. + + \param nThreads : Number of threads to use if pthreads library is available. + + \warning This function works only with Types authorizing "+,-, + multiplication by a scalar" operators. + + Since this function is time consuming, if you want to undistort multiple images, you should rather + call initUndistortMap() once and then remap() to undistort the images. + This will be less time consuming. + + \sa initUndistortMap(), remap() +*/ +template +void vpImageTools::undistort(const vpImage &I, const vpCameraParameters &cam, vpImage &undistI, + unsigned int nThreads) +{ +#if defined(VISP_HAVE_THREADS) + // + // Optimized version using pthreads + // + unsigned int width = I.getWidth(); + unsigned int height = I.getHeight(); + + undistI.resize(height, width); + + double kud = cam.get_kud(); + + // if (kud == 0) { + if (std::fabs(kud) <= std::numeric_limits::epsilon()) { + // There is no need to undistort the image + undistI = I; + return; + } + + unsigned int nthreads = nThreads; + std::vector threadpool; + + vpUndistortInternalType *undistortSharedData = new vpUndistortInternalType[nthreads]; + + for (unsigned int i = 0; i < nthreads; ++i) { + // Each thread works on a different set of data. + undistortSharedData[i].src = I.bitmap; + undistortSharedData[i].dst = undistI.bitmap; + undistortSharedData[i].width = I.getWidth(); + undistortSharedData[i].height = I.getHeight(); + undistortSharedData[i].cam = cam; + undistortSharedData[i].nthreads = nthreads; + undistortSharedData[i].threadid = i; + std::thread *undistort_thread = new std::thread(&vpUndistortInternalType::vpUndistort_threaded, std::ref(undistortSharedData[i])); + threadpool.push_back(undistort_thread); + } + /* Wait on the other threads */ + + for (unsigned int i = 0; i < nthreads; ++i) { + threadpool[i]->join(); + } + + for (unsigned int i = 0; i < nthreads; ++i) { + delete threadpool[i]; + } + + delete[] undistortSharedData; +#else // VISP_HAVE_THREADS + (void)nThreads; + // + // optimized version without pthreads + // + unsigned int width = I.getWidth(); + unsigned int height = I.getHeight(); + + undistI.resize(height, width); + + double u0 = cam.get_u0(); + double v0 = cam.get_v0(); + double px = cam.get_px(); + double py = cam.get_py(); + double kud = cam.get_kud(); + + /* + // if (kud == 0) { + */ + if (std::fabs(kud) <= std::numeric_limits::epsilon()) { + // There is no need to undistort the image + undistI = I; + return; + } + + double invpx = 1.0 / px; + double invpy = 1.0 / py; + + double kud_px2 = kud * invpx * invpx; + double kud_py2 = kud * invpy * invpy; + + Type *dst = undistI.bitmap; + for (double v = 0; v < height; ++v) { + double deltav = v - v0; + /* + // double fr1 = 1.0 + kd * (vpMath::sqr(deltav * invpy)); + */ + double fr1 = 1.0 + (kud_py2 * deltav * deltav); + + for (double u = 0; u < width; ++u) { + /* + // computation of u,v : corresponding pixel coordinates in I. + */ + double deltau = u - u0; + /* + // double fr2 = fr1 + kd * (vpMath::sqr(deltau * invpx)); + */ + double fr2 = fr1 + (kud_px2 * deltau * deltau); + + double u_double = (deltau * fr2) + u0; + double v_double = (deltav * fr2) + v0; + + // printf("[%g][%g] %g %g : ", u, v, u_double, v_double ); + + // computation of the bilinear interpolation + + // declarations + int u_round = static_cast(u_double); + int v_round = static_cast(v_double); + if (u_round < 0.f) { + u_round = -1; + } + if (v_round < 0.f) { + v_round = -1; + } + double du_double = u_double-static_cast(u_round); + double dv_double = v_double-static_cast(v_round); + Type v01; + Type v23; + if ((0 <= u_round) && (0 <= v_round) && (u_round < ((static_cast(width)) - 1)) && (v_round < ((static_cast(height)) - 1))) { + // process interpolation + const Type *v_mp = &I[static_cast(v_round)][static_cast(u_round)]; + v01 = static_cast(v_mp[0] + ((v_mp[1] - v_mp[0]) * du_double)); + v_mp += width; + v23 = static_cast(v_mp[0] + ((v_mp[1] - v_mp[0]) * du_double)); + *dst = static_cast(v01 + ((v23 - v01) * dv_double)); + /* + // printf("R %d G %d B %d\n", dst->R, dst->G, dst->B); + */ + } + else { + *dst = 0; + } + ++dst; + } + } +#endif // VISP_HAVE_THREADS +} + +/*! + Undistort an image. + + \param I : Input image to undistort. + \param mapU : Map that contains at each destination coordinate the u-coordinate in the source image. + \param mapV : Map that contains at each destination coordinate the v-coordinate in the source image. + \param mapDu : Map that contains at each destination coordinate the \f$ \Delta u \f$ for the interpolation. + \param mapDv : Map that contains at each destination coordinate the \f$ \Delta v \f$ for the interpolation. + \param newI : Undistorted output image. The size of this image will be the same as the input image \e I. + + \note To undistort a fisheye image, you have to first call initUndistortMap() function to calculate maps and then + call undistort() with input maps. + + */ +template +void vpImageTools::undistort(const vpImage &I, vpArray2D mapU, vpArray2D mapV, vpArray2D mapDu, + vpArray2D mapDv, vpImage &newI) +{ + remap(I, mapU, mapV, mapDu, mapDv, newI); +} + +/*! + Flip vertically the input image and give the result in the output image. + + \param I : Input image to flip. + \param newI : Output image which is the flipped input image. +*/ +template void vpImageTools::flip(const vpImage &I, vpImage &newI) +{ + unsigned int height = I.getHeight(), width = I.getWidth(); + newI.resize(height, width); + + for (unsigned int i = 0; i < height; ++i) { + memcpy(newI.bitmap + (i * width), I.bitmap + ((height - 1 - i) * width), width * sizeof(Type)); + } +} + +/*! + Flip vertically the input image. + + \param I : Input image which is flipped and modified in output. + + The following example shows how to use this function: + \code + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpImage I; + #ifdef _WIN32 + std::string filename("C:/Temp/visp-images/Klimt/Klimt.ppm"); + #else + std::string filename("/local/soft/ViSP/ViSP-images/Klimt/Klimt.ppm"); + #endif + + // Read an image from the disk + vpImageIo::read(I, filename); + + // Flip the image + vpImageTools::flip(I); + + // Write the image in a PGM P5 image file format + vpImageIo::write(I, "Klimt-flip.ppm"); + } + \endcode +*/ +template void vpImageTools::flip(vpImage &I) +{ + unsigned int height = I.getHeight(), width = I.getWidth(); + vpImage Ibuf; + Ibuf.resize(1, width); + + const unsigned int halfHeight = height / 2; + for (unsigned int i = 0; i < halfHeight; ++i) { + memcpy(Ibuf.bitmap, I.bitmap + (i * width), width * sizeof(Type)); + + memcpy(I.bitmap + (i * width), I.bitmap + ((height - 1 - i) * width), width * sizeof(Type)); + memcpy(I.bitmap + ((height - 1 - i) * width), Ibuf.bitmap, width * sizeof(Type)); + } +} + +template Type vpImageTools::getPixelClamped(const vpImage &I, float u, float v) +{ + int x = vpMath::round(static_cast(u)); + int y = vpMath::round(static_cast(v)); + x = std::max(0, std::min(x, static_cast(I.getWidth()) - 1)); + y = std::max(0, std::min(y, static_cast(I.getHeight()) - 1)); + + return I[y][x]; +} + +// Reference: +// http://blog.demofox.org/2015/08/15/resizing-images-with-bicubic-interpolation/ +template +void vpImageTools::resizeBicubic(const vpImage &I, vpImage &Ires, unsigned int i, unsigned int j, float u, + float v, float xFrac, float yFrac) +{ + // 1st row + Type p00 = getPixelClamped(I, u - 1, v - 1); + Type p01 = getPixelClamped(I, u + 0, v - 1); + Type p02 = getPixelClamped(I, u + 1, v - 1); + Type p03 = getPixelClamped(I, u + 2, v - 1); + + // 2nd row + Type p10 = getPixelClamped(I, u - 1, v + 0); + Type p11 = getPixelClamped(I, u + 0, v + 0); + Type p12 = getPixelClamped(I, u + 1, v + 0); + Type p13 = getPixelClamped(I, u + 2, v + 0); + + // 3rd row + Type p20 = getPixelClamped(I, u - 1, v + 1); + Type p21 = getPixelClamped(I, u + 0, v + 1); + Type p22 = getPixelClamped(I, u + 1, v + 1); + Type p23 = getPixelClamped(I, u + 2, v + 1); + + // 4th row + Type p30 = getPixelClamped(I, u - 1, v + 2); + Type p31 = getPixelClamped(I, u + 0, v + 2); + Type p32 = getPixelClamped(I, u + 1, v + 2); + Type p33 = getPixelClamped(I, u + 2, v + 2); + + float col0 = cubicHermite(p00, p01, p02, p03, xFrac); + float col1 = cubicHermite(p10, p11, p12, p13, xFrac); + float col2 = cubicHermite(p20, p21, p22, p23, xFrac); + float col3 = cubicHermite(p30, p31, p32, p33, xFrac); + float value = cubicHermite(col0, col1, col2, col3, yFrac); + Ires[i][j] = vpMath::saturate(value); +} + +template <> +inline void vpImageTools::resizeBicubic(const vpImage &I, vpImage &Ires, unsigned int i, unsigned int j, + float u, float v, float xFrac, float yFrac) +{ + // 1st row + vpRGBa p00 = getPixelClamped(I, u - 1, v - 1); + vpRGBa p01 = getPixelClamped(I, u + 0, v - 1); + vpRGBa p02 = getPixelClamped(I, u + 1, v - 1); + vpRGBa p03 = getPixelClamped(I, u + 2, v - 1); + + // 2nd row + vpRGBa p10 = getPixelClamped(I, u - 1, v + 0); + vpRGBa p11 = getPixelClamped(I, u + 0, v + 0); + vpRGBa p12 = getPixelClamped(I, u + 1, v + 0); + vpRGBa p13 = getPixelClamped(I, u + 2, v + 0); + + // 3rd row + vpRGBa p20 = getPixelClamped(I, u - 1, v + 1); + vpRGBa p21 = getPixelClamped(I, u + 0, v + 1); + vpRGBa p22 = getPixelClamped(I, u + 1, v + 1); + vpRGBa p23 = getPixelClamped(I, u + 2, v + 1); + + // 4th row + vpRGBa p30 = getPixelClamped(I, u - 1, v + 2); + vpRGBa p31 = getPixelClamped(I, u + 0, v + 2); + vpRGBa p32 = getPixelClamped(I, u + 1, v + 2); + vpRGBa p33 = getPixelClamped(I, u + 2, v + 2); + + const int nbChannels = 3; + for (int c = 0; c < nbChannels; ++c) { + float col0 = cubicHermite(static_cast(reinterpret_cast(&p00)[c]), + static_cast(reinterpret_cast(&p01)[c]), + static_cast(reinterpret_cast(&p02)[c]), + static_cast(reinterpret_cast(&p03)[c]), xFrac); + float col1 = cubicHermite(static_cast(reinterpret_cast(&p10)[c]), + static_cast(reinterpret_cast(&p11)[c]), + static_cast(reinterpret_cast(&p12)[c]), + static_cast(reinterpret_cast(&p13)[c]), xFrac); + float col2 = cubicHermite(static_cast(reinterpret_cast(&p20)[c]), + static_cast(reinterpret_cast(&p21)[c]), + static_cast(reinterpret_cast(&p22)[c]), + static_cast(reinterpret_cast(&p23)[c]), xFrac); + float col3 = cubicHermite(static_cast(reinterpret_cast(&p30)[c]), + static_cast(reinterpret_cast(&p31)[c]), + static_cast(reinterpret_cast(&p32)[c]), + static_cast(reinterpret_cast(&p33)[c]), xFrac); + float value = cubicHermite(col0, col1, col2, col3, yFrac); + + reinterpret_cast(&Ires[i][j])[c] = vpMath::saturate(value); + } +} + +template +void vpImageTools::resizeBilinear(const vpImage &I, vpImage &Ires, unsigned int i, unsigned int j, float u, + float v, float xFrac, float yFrac) +{ + int u0 = static_cast(u); + int v0 = static_cast(v); + + int u1 = std::min(static_cast(I.getWidth()) - 1, u0 + 1); + int v1 = v0; + + int u2 = u0; + int v2 = std::min(static_cast(I.getHeight()) - 1, v0 + 1); + + int u3 = u1; + int v3 = v2; + + float col0 = lerp(I[v0][u0], I[v1][u1], xFrac); + float col1 = lerp(I[v2][u2], I[v3][u3], xFrac); + float value = lerp(col0, col1, yFrac); + + Ires[i][j] = vpMath::saturate(value); +} + +template <> +inline void vpImageTools::resizeBilinear(const vpImage &I, vpImage &Ires, unsigned int i, + unsigned int j, float u, float v, float xFrac, float yFrac) +{ + int u0 = static_cast(u); + int v0 = static_cast(v); + + int u1 = std::min(static_cast(I.getWidth()) - 1, u0 + 1); + int v1 = v0; + + int u2 = u0; + int v2 = std::min(static_cast(I.getHeight()) - 1, v0 + 1); + + int u3 = u1; + int v3 = v2; + + const int nbChannels = 3; + for (int c = 0; c < nbChannels; ++c) { + float col0 = lerp(static_cast(reinterpret_cast(&I[v0][u0])[c]), + static_cast(reinterpret_cast(&I[v1][u1])[c]), xFrac); + float col1 = lerp(static_cast(reinterpret_cast(&I[v2][u2])[c]), + static_cast(reinterpret_cast(&I[v3][u3])[c]), xFrac); + float value = lerp(col0, col1, yFrac); + + reinterpret_cast(&Ires[i][j])[c] = vpMath::saturate(value); + } +} + +template +void vpImageTools::resizeNearest(const vpImage &I, vpImage &Ires, unsigned int i, unsigned int j, float u, + float v) +{ + Ires[i][j] = getPixelClamped(I, u, v); +} + +/*! + Resize the image using one interpolation method (by default it uses the + nearest neighbor interpolation). + + \param I : Input image. + \param Ires : Output image resized to \e width, \e height. + \param width : Resized width. + \param height : Resized height. + \param method : Interpolation method. + \param nThreads : Number of threads to use if OpenMP is available + (zero will let OpenMP uses the optimal number of threads). + + \warning The input \e I and output \e Ires images must be different objects. + + \note The SIMD lib is used to accelerate processing on x86 and ARM architecture for: + - unsigned char and vpRGBa image types + - and only with INTERPOLATION_AREA and INTERPOLATION_LINEAR methods +*/ +template +void vpImageTools::resize(const vpImage &I, vpImage &Ires, unsigned int width, unsigned int height, + const vpImageInterpolationType &method, unsigned int nThreads) +{ + Ires.resize(height, width); + + vpImageTools::resize(I, Ires, method, nThreads); +} + +/*! + Resize the image using one interpolation method (by default it uses the + nearest neighbor interpolation). + + \param I : Input image. + \param Ires : Output image resized (you have to init the image \e Ires at + the desired size). + \param method : Interpolation method. + \param nThreads : Number of threads to use if OpenMP is available + (zero will let OpenMP uses the optimal number of threads). Unused if OpenMP is not enabled. + + \warning The input \e I and output \e Ires images must be different objects. + + \note The SIMD lib is used to accelerate processing on x86 and ARM architecture for: + - unsigned char and vpRGBa image types + - and only with INTERPOLATION_AREA and INTERPOLATION_LINEAR methods +*/ +template +void vpImageTools::resize(const vpImage &I, vpImage &Ires, const vpImageInterpolationType &method, + unsigned int nThreads) +{ +#if !defined(_OPENMP) + (void)nThreads; +#endif + const unsigned int minWidth = 2, minHeight = 2; + if ((I.getWidth() < minWidth) || (I.getHeight() < minHeight) || (Ires.getWidth() < minWidth) || (Ires.getHeight() < minHeight)) { + std::cerr << "Input or output image is too small!" << std::endl; + return; + } + + if (method == INTERPOLATION_AREA) { + std::cerr << "INTERPOLATION_AREA is not implemented for this type." << std::endl; + return; + } + + const float scaleY = I.getHeight() / static_cast(Ires.getHeight()); + const float scaleX = I.getWidth() / static_cast(Ires.getWidth()); + const float half = 0.5f; + const int ires_height = static_cast(Ires.getHeight()); +#if defined(_OPENMP) + if (nThreads > 0) { + omp_set_num_threads(static_cast(nThreads)); + } +#pragma omp parallel for schedule(dynamic) +#endif + for (int i = 0; i < ires_height; ++i) { + const float v = ((i + half) * scaleY) - half; + const float v0 = std::floor(v); + const float yFrac = v - v0; + + unsigned int ires_width = static_cast(Ires.getWidth()); + for (unsigned int j = 0; j < ires_width; ++j) { + const float u = ((j + half) * scaleX) - half; + const float u0 = std::floor(u); + const float xFrac = u - u0; + + if (method == INTERPOLATION_NEAREST) { + resizeNearest(I, Ires, static_cast(i), j, u, v); + } + else if (method == INTERPOLATION_LINEAR) { + resizeBilinear(I, Ires, static_cast(i), j, u0, v0, xFrac, yFrac); + } + else if (method == INTERPOLATION_CUBIC) { + resizeBicubic(I, Ires, static_cast(i), j, u, v, xFrac, yFrac); + } + } + } +} + +#if defined(VISP_HAVE_SIMDLIB) +template <> +inline void vpImageTools::resize(const vpImage &I, vpImage &Ires, + const vpImageInterpolationType &method, + unsigned int +#if defined(_OPENMP) + nThreads +#endif +) +{ + const unsigned int minWidth = 2, minHeight = 2; + + if ((I.getWidth() < minWidth) || (I.getHeight() < minHeight) || (Ires.getWidth() < minWidth) || (Ires.getHeight() < minHeight)) { + std::cerr << "Input or output image is too small!" << std::endl; + return; + } + + if (method == INTERPOLATION_AREA) { + resizeSimdlib(I, Ires.getWidth(), Ires.getHeight(), Ires, INTERPOLATION_AREA); + } + else if (method == INTERPOLATION_LINEAR) { + resizeSimdlib(I, Ires.getWidth(), Ires.getHeight(), Ires, INTERPOLATION_LINEAR); + } + else { + const float scaleY = static_cast(I.getHeight()) / static_cast(Ires.getHeight()); + const float scaleX = static_cast(I.getWidth()) / static_cast(Ires.getWidth()); + const float half = 0.5f; + const int ires_height = static_cast(Ires.getHeight()); +#if defined(_OPENMP) + if (nThreads > 0) { + omp_set_num_threads(static_cast(nThreads)); + } +#pragma omp parallel for schedule(dynamic) +#endif + for (int i = 0; i < ires_height; ++i) { + float v = ((static_cast(i) + half) * scaleY) - half; + float yFrac = static_cast(v - static_cast(static_cast(v))); + + unsigned int ires_width = static_cast(Ires.getWidth()); + for (unsigned int j = 0; j < ires_width; ++j) { + float u = ((static_cast(j) + half) * scaleX) - half; + float xFrac = static_cast(u - static_cast(static_cast(u))); + + if (method == INTERPOLATION_NEAREST) { + resizeNearest(I, Ires, static_cast(i), j, u, v); + } + else if (method == INTERPOLATION_CUBIC) { + resizeBicubic(I, Ires, static_cast(i), j, u, v, xFrac, yFrac); + } + } + } + } +} + +template <> +inline void vpImageTools::resize(const vpImage &I, vpImage &Ires, + const vpImageInterpolationType &method, + unsigned int +#if defined(_OPENMP) + nThreads +#endif +) +{ + const unsigned int minWidth = 2, minHeight = 2; + + if ((I.getWidth() < minWidth) || (I.getHeight() < minHeight) || (Ires.getWidth() < minWidth) || (Ires.getHeight() < minHeight)) { + std::cerr << "Input or output image is too small!" << std::endl; + return; + } + + if (method == INTERPOLATION_AREA) { + resizeSimdlib(I, Ires.getWidth(), Ires.getHeight(), Ires, INTERPOLATION_AREA); + } + else if (method == INTERPOLATION_LINEAR) { + resizeSimdlib(I, Ires.getWidth(), Ires.getHeight(), Ires, INTERPOLATION_LINEAR); + } + else { + const float scaleY = static_cast(I.getHeight()) / static_cast(Ires.getHeight()); + const float scaleX = static_cast(I.getWidth()) / static_cast(Ires.getWidth()); + const float half = 0.5f; + const int ires_height = static_cast(Ires.getHeight()); +#if defined(_OPENMP) + if (nThreads > 0) { + omp_set_num_threads(static_cast(nThreads)); + } +#pragma omp parallel for schedule(dynamic) +#endif + for (int i = 0; i < ires_height; ++i) { + float v = ((static_cast(i) + half) * scaleY) - half; + float yFrac = static_cast(v - static_cast(static_cast(v))); + + unsigned int ires_width = static_cast(Ires.getWidth()); + for (unsigned int j = 0; j < ires_width; ++j) { + float u = ((static_cast(j) + half) * scaleX) - half; + float xFrac = static_cast(u - static_cast(static_cast(u))); + + if (method == INTERPOLATION_NEAREST) { + resizeNearest(I, Ires, static_cast(i), j, u, v); + } + else if (method == INTERPOLATION_CUBIC) { + resizeBicubic(I, Ires, static_cast(i), j, u, v, xFrac, yFrac); + } + } + } + } +} +#endif + +#ifdef ENABLE_IMAGE_TOOLS_WARP +#include +#endif + +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageTools_warp.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageTools_warp.h new file mode 100644 index 00000000..b6826a32 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImageTools_warp.h @@ -0,0 +1,734 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Image handling. + */ + +#ifndef VP_IMAGE_TOOLS_WARP_H +#define VP_IMAGE_TOOLS_WARP_H + +// Warning: this file shouldn't be included by the user. Internal usage only to reduce length of vpImage.h +#include + +/*! + Apply a warping (affine or perspective) transformation to an image. + + \param src : Input image. + \param T : Transformation / warping matrix, a `2x3` matrix for an affine transformation + or a `3x3` matrix for a perspective transformation (homography). + \param dst : Output image, if empty it will be of the same size than src and zero-initialized. + \param interpolation : Interpolation method (only INTERPOLATION_NEAREST and INTERPOLATION_LINEAR + are accepted, if INTERPOLATION_CUBIC is passed, INTERPOLATION_NEAREST will be used instead). + \param fixedPointArithmetic : If true and if `pixelCenter` is false, fixed-point arithmetic is used if + possible. Otherwise (e.g. the input image is too big) it fallbacks to the default implementation. + \param pixelCenter : If true, pixel coordinates are at (0.5, 0.5), otherwise at (0,0). Fixed-point + arithmetic cannot be used with `pixelCenter` option. +*/ +template +void vpImageTools::warpImage(const vpImage &src, const vpMatrix &T, vpImage &dst, + const vpImageInterpolationType &interpolation, bool fixedPointArithmetic, bool pixelCenter) +{ + const unsigned int expectedNbCols = 3, expectedNbRows1stOpt = 2, expectedNbRows2ndOpt = 3; + if (((T.getRows() != expectedNbRows1stOpt) && (T.getRows() != expectedNbRows2ndOpt)) || (T.getCols() != expectedNbCols)) { + std::cerr << "Input transformation must be a (2x3) or (3x3) matrix." << std::endl; + return; + } + + if (src.getSize() == 0) { + return; + } + + const bool affine = (T.getRows() == 2); + const bool interp_NN = (interpolation == INTERPOLATION_NEAREST) || (interpolation == INTERPOLATION_CUBIC); + + if (dst.getSize() == 0) { + dst.resize(src.getHeight(), src.getWidth(), Type(0)); + } + + vpMatrix M = T; + if (affine) { + const unsigned int index_0 = 0; + const unsigned int index_1 = 1; + const unsigned int index_2 = 2; + double D = (M[index_0][index_0] * M[index_1][index_1]) - (M[index_0][index_1] * M[index_1][index_0]); + D = !vpMath::nul(D, std::numeric_limits::epsilon()) ? (1.0 / D) : 0; + double A11 = M[index_1][index_1] * D, A22 = M[index_0][index_0] * D; + M[index_0][index_0] = A11; + M[index_0][index_1] *= -D; + M[index_1][index_0] *= -D; + M[index_1][index_1] = A22; + double b1 = (-M[index_0][index_0] * M[index_0][index_2]) - (M[index_0][index_1] * M[index_1][index_2]); + double b2 = (-M[index_1][index_0] * M[index_0][index_2]) - (M[index_1][index_1] * M[index_1][index_2]); + M[index_0][index_2] = b1; + M[index_1][index_2] = b2; + } + else { + M = T.inverseByLU(); + } + + if (fixedPointArithmetic && (!pixelCenter)) { + fixedPointArithmetic = checkFixedPoint(0, 0, M, affine) && checkFixedPoint(dst.getWidth() - 1, 0, M, affine) && + checkFixedPoint(0, dst.getHeight() - 1, M, affine) && + checkFixedPoint(dst.getWidth() - 1, dst.getHeight() - 1, M, affine); + } + + if (interp_NN) { + // nearest neighbor interpolation + warpNN(src, M, dst, affine, pixelCenter, fixedPointArithmetic); + } + else { + // bilinear interpolation + warpLinear(src, M, dst, affine, pixelCenter, fixedPointArithmetic); + } +} + +template +void vpImageTools::warpNN(const vpImage &src, const vpMatrix &T, vpImage &dst, bool affine, + bool centerCorner, bool fixedPoint) +{ + if (fixedPoint && (!centerCorner)) { + const int nbits = 16; + const int32_t precision = 1 << nbits; + const float precision_1 = 1 / static_cast(precision); + const unsigned int index_0 = 0; + const unsigned int index_1 = 1; + const unsigned int index_2 = 2; + int32_t a0_i32 = static_cast(T[index_0][index_0] * precision); + int32_t a1_i32 = static_cast(T[index_0][index_1] * precision); + int32_t a2_i32 = static_cast(T[index_0][index_2] * precision); + int32_t a3_i32 = static_cast(T[index_1][index_0] * precision); + int32_t a4_i32 = static_cast(T[index_1][index_1] * precision); + int32_t a5_i32 = static_cast(T[index_1][index_2] * precision); + int32_t a6_i32 = T.getRows() == 3 ? static_cast(T[index_2][index_0] * precision) : 0; + int32_t a7_i32 = T.getRows() == 3 ? static_cast(T[index_2][index_1] * precision) : 0; + int32_t a8_i32 = T.getRows() == 3 ? static_cast(T[index_2][index_2] * precision) : 1; + + int32_t height_1_i32 = static_cast((src.getHeight() - 1) * precision) + 0x8000; + int32_t width_1_i32 = static_cast((src.getWidth() - 1) * precision) + 0x8000; + + if (affine) { + unsigned int dst_height = dst.getHeight(); + unsigned int dst_width = dst.getWidth(); + for (unsigned int i = 0; i < dst_height; ++i) { + int32_t xi = a2_i32; + int32_t yi = a5_i32; + + for (unsigned int j = 0; j < dst_width; ++j) { + if ((yi >= 0) && (yi < height_1_i32) && (xi >= 0) && (xi < width_1_i32)) { + float x_ = (xi >> nbits) + ((xi & 0xFFFF) * precision_1); + float y_ = (yi >> nbits) + ((yi & 0xFFFF) * precision_1); + + int x = vpMath::round(x_); + int y = vpMath::round(y_); + dst[i][j] = src[y][x]; + } + + xi += a0_i32; + yi += a3_i32; + } + + a2_i32 += a1_i32; + a5_i32 += a4_i32; + } + } + else { + unsigned int dst_height = dst.getHeight(); + unsigned int dst_width = dst.getWidth(); + int src_height = static_cast(src.getHeight()); + int src_width = static_cast(src.getWidth()); + for (unsigned int i = 0; i < dst_height; ++i) { + int64_t xi = a2_i32; + int64_t yi = a5_i32; + int64_t wi = a8_i32; + + for (unsigned int j = 0; j < dst_width; ++j) { + bool cond_on_y = (yi >= 0) && (yi <= ((src_height - 1) * wi)); + bool cond_on_x = (xi >= 0) && (xi <= ((src_width - 1) * wi)); + if ((wi != 0) && cond_on_y && cond_on_x) { + float w_ = (wi >> nbits) + ((wi & 0xFFFF) * precision_1); + float x_ = ((xi >> nbits) + ((xi & 0xFFFF) * precision_1)) / w_; + float y_ = ((yi >> nbits) + ((yi & 0xFFFF) * precision_1)) / w_; + + int x = vpMath::round(x_); + int y = vpMath::round(y_); + + dst[i][j] = src[y][x]; + } + + xi += a0_i32; + yi += a3_i32; + wi += a6_i32; + } + + a2_i32 += a1_i32; + a5_i32 += a4_i32; + a8_i32 += a7_i32; + } + } + } + else { + const unsigned int index_0 = 0; + const unsigned int index_1 = 1; + const unsigned int index_2 = 2; + double a0 = T[index_0][index_0]; + double a1 = T[index_0][index_1]; + double a2 = T[index_0][index_2]; + double a3 = T[index_1][index_0]; + double a4 = T[index_1][index_1]; + double a5 = T[index_1][index_2]; + double a6 = affine ? 0.0 : T[index_2][index_0]; + double a7 = affine ? 0.0 : T[index_2][index_1]; + double a8 = affine ? 1.0 : T[index_2][index_2]; + + unsigned int dst_height = dst.getHeight(); + unsigned int dst_width = dst.getWidth(); + for (unsigned int i = 0; i < dst_height; ++i) { + for (unsigned int j = 0; j < dst_width; ++j) { + double x = ((a0 * (centerCorner ? (j + 0.5) : j)) + (a1 * (centerCorner ? (i + 0.5) : i))) + a2; + double y = ((a3 * (centerCorner ? (j + 0.5) : j)) + (a4 * (centerCorner ? (i + 0.5) : i))) + a5; + double w = ((a6 * (centerCorner ? (j + 0.5) : j)) + (a7 * (centerCorner ? (i + 0.5) : i))) + a8; + + if (vpMath::nul(w, std::numeric_limits::epsilon())) { + w = 1.0; + } + + int x_ = centerCorner ? coordCast(x / w) : vpMath::round(x / w); + int y_ = centerCorner ? coordCast(y / w) : vpMath::round(y / w); + + if ((x_ >= 0) && (x_ < static_cast(src.getWidth())) && (y_ >= 0) && (y_ < static_cast(src.getHeight()))) { + dst[i][j] = src[y_][x_]; + } + } + } + } +} + +template +void vpImageTools::warpLinear(const vpImage &src, const vpMatrix &T, vpImage &dst, bool affine, + bool centerCorner, bool fixedPoint) +{ + if (fixedPoint && (!centerCorner)) { + const int nbits = 16; + const uint64_t precision = 1 << nbits; + const float precision_1 = 1 / static_cast(precision); + const uint64_t precision2 = 1ULL << (2 * nbits); + const float precision_2 = 1 / static_cast(precision2); + const unsigned int index_0 = 0; + const unsigned int index_1 = 1; + const unsigned int index_2 = 2; + + int64_t a0_i64 = static_cast(T[index_0][index_0] * precision); + int64_t a1_i64 = static_cast(T[index_0][index_1] * precision); + int64_t a2_i64 = static_cast(T[index_0][index_2] * precision); + int64_t a3_i64 = static_cast(T[index_1][index_0] * precision); + int64_t a4_i64 = static_cast(T[index_1][index_1] * precision); + int64_t a5_i64 = static_cast(T[index_1][index_2] * precision); + int64_t a6_i64 = T.getRows() == 3 ? static_cast(T[index_2][index_0] * precision) : 0; + int64_t a7_i64 = T.getRows() == 3 ? static_cast(T[index_2][index_1] * precision) : 0; + int64_t a8_i64 = T.getRows() == 3 ? static_cast(T[index_2][index_2] * precision) : 1; + + int64_t height_i64 = static_cast(src.getHeight() * precision); + int64_t width_i64 = static_cast(src.getWidth() * precision); + + if (affine) { + unsigned int dst_height = dst.getHeight(); + unsigned int dst_width = dst.getWidth(); + for (unsigned int i = 0; i < dst_height; ++i) { + int64_t xi_ = a2_i64; + int64_t yi_ = a5_i64; + + for (unsigned int j = 0; j < dst_width; ++j) { + if ((yi_ >= 0) && (yi_ < height_i64) && (xi_ >= 0) && (xi_ < width_i64)) { + const int64_t xi_lower = xi_ & (~0xFFFF); + const int64_t yi_lower = yi_ & (~0xFFFF); + + const int64_t t = yi_ - yi_lower; + const int64_t t_1 = precision - t; + const int64_t s = xi_ - xi_lower; + const int64_t s_1 = precision - s; + + const int x_ = static_cast(xi_ >> nbits); + const int y_ = static_cast(yi_ >> nbits); + + if ((y_ < (static_cast(src.getHeight()) - 1)) && (x_ < (static_cast(src.getWidth()) - 1))) { + const Type val00 = src[y_][x_]; + const Type val01 = src[y_][x_ + 1]; + const Type val10 = src[y_ + 1][x_]; + const Type val11 = src[y_ + 1][x_ + 1]; + const int64_t interp_i64 = + static_cast(((s_1 * t_1) * val00) + ((s * t_1) * val01) + ((s_1 * t) * val10) + ((s * t) * val11)); + const float interp = (interp_i64 >> (nbits * 2)) + ((interp_i64 & 0xFFFFFFFFU) * precision_2); + dst[i][j] = vpMath::saturate(interp); + } + else if (y_ < (static_cast(src.getHeight()) - 1)) { + const Type val00 = src[y_][x_]; + const Type val10 = src[y_ + 1][x_]; + const int64_t interp_i64 = static_cast((t_1 * val00) + (t * val10)); + const float interp = (interp_i64 >> nbits) + ((interp_i64 & 0xFFFF) * precision_1); + dst[i][j] = vpMath::saturate(interp); + } + else if (x_ < (static_cast(src.getWidth()) - 1)) { + const Type val00 = src[y_][x_]; + const Type val01 = src[y_][x_ + 1]; + const int64_t interp_i64 = static_cast((s_1 * val00) + (s * val01)); + const float interp = (interp_i64 >> nbits) + ((interp_i64 & 0xFFFF) * precision_1); + dst[i][j] = vpMath::saturate(interp); + } + else { + dst[i][j] = src[y_][x_]; + } + } + + xi_ += a0_i64; + yi_ += a3_i64; + } + + a2_i64 += a1_i64; + a5_i64 += a4_i64; + } + } + else { + unsigned int dst_height = dst.getHeight(); + unsigned int dst_width = dst.getWidth(); + int src_height = static_cast(src.getHeight()); + int src_width = static_cast(src.getWidth()); + for (unsigned int i = 0; i < dst_height; ++i) { + int64_t xi = a2_i64; + int64_t yi = a5_i64; + int64_t wi = a8_i64; + + for (unsigned int j = 0; j < dst_width; ++j) { + bool cond_on_y = (yi >= 0) && (yi <= ((src_height - 1) * wi)); + bool cond_on_x = (xi >= 0) && (xi <= ((src_width - 1) * wi)); + if ((wi != 0) && cond_on_y && cond_on_x) { + const float wi_ = (wi >> nbits) + ((wi & 0xFFFF) * precision_1); + const float xi_ = ((xi >> nbits) + ((xi & 0xFFFF) * precision_1)) / wi_; + const float yi_ = ((yi >> nbits) + ((yi & 0xFFFF) * precision_1)) / wi_; + + const int x_ = static_cast(xi_); + const int y_ = static_cast(yi_); + + const float t = yi_ - y_; + const float s = xi_ - x_; + + if ((y_ < (src_height - 1)) && (x_ < (src_width - 1))) { + const float val00 = static_cast(src[y_][x_]); + const float val01 = static_cast(src[y_][x_ + 1]); + const float val10 = static_cast(src[y_ + 1][x_]); + const float val11 = static_cast(src[y_ + 1][x_ + 1]); + const float col0 = lerp(val00, val01, s); + const float col1 = lerp(val10, val11, s); + const float interp = lerp(col0, col1, t); + dst[i][j] = vpMath::saturate(interp); + } + else if (y_ < (src_height - 1)) { + const float val00 = static_cast(src[y_][x_]); + const float val10 = static_cast(src[y_ + 1][x_]); + const float interp = lerp(val00, val10, t); + dst[i][j] = vpMath::saturate(interp); + } + else if (x_ < (src_width - 1)) { + const float val00 = static_cast(src[y_][x_]); + const float val01 = static_cast(src[y_][x_ + 1]); + const float interp = lerp(val00, val01, s); + dst[i][j] = vpMath::saturate(interp); + } + else { + dst[i][j] = src[y_][x_]; + } + } + + xi += a0_i64; + yi += a3_i64; + wi += a6_i64; + } + + a2_i64 += a1_i64; + a5_i64 += a4_i64; + a8_i64 += a7_i64; + } + } + } + else { + const unsigned int index_0 = 0; + const unsigned int index_1 = 1; + const unsigned int index_2 = 2; + double a0 = T[index_0][index_0]; + double a1 = T[index_0][index_1]; + double a2 = T[index_0][index_2]; + double a3 = T[index_1][index_0]; + double a4 = T[index_1][index_1]; + double a5 = T[index_1][index_2]; + double a6 = affine ? 0.0 : T[index_2][index_0]; + double a7 = affine ? 0.0 : T[index_2][index_1]; + double a8 = affine ? 1.0 : T[index_2][index_2]; + + unsigned int dst_height = dst.getHeight(); + unsigned int dst_width = dst.getWidth(); + int src_height = static_cast(src.getHeight()); + int src_width = static_cast(src.getWidth()); + for (unsigned int i = 0; i < dst_height; ++i) { + for (unsigned int j = 0; j < dst_width; ++j) { + double x = (a0 * (centerCorner ? (j + 0.5) : j)) + (a1 * (centerCorner ? (i + 0.5) : i)) + a2; + double y = (a3 * (centerCorner ? (j + 0.5) : j)) + (a4 * (centerCorner ? (i + 0.5) : i)) + a5; + double w = (a6 * (centerCorner ? (j + 0.5) : j)) + (a7 * (centerCorner ? (i + 0.5) : i)) + a8; + if (vpMath::nul(w, std::numeric_limits::epsilon())) { + w = 1; + } + + x = (x / w) - (centerCorner ? 0.5 : 0); + y = (y / w) - (centerCorner ? 0.5 : 0); + + int x_lower = static_cast(x); + int y_lower = static_cast(y); + bool stop_for_loop = false; + if ((y_lower >= src_height) || (x_lower >= src_width) || (y < 0) || (x < 0)) { + stop_for_loop = true; + } + if (!stop_for_loop) { + double s = x - x_lower; + double t = y - y_lower; + + if ((y_lower < (src_height - 1)) && (x_lower < (src_width - 1))) { + const double val00 = static_cast(src[y_lower][x_lower]); + const double val01 = static_cast(src[y_lower][x_lower + 1]); + const double val10 = static_cast(src[y_lower + 1][x_lower]); + const double val11 = static_cast(src[y_lower + 1][x_lower + 1]); + const double col0 = lerp(val00, val01, s); + const double col1 = lerp(val10, val11, s); + const double interp = lerp(col0, col1, t); + dst[i][j] = vpMath::saturate(interp); + } + else if (y_lower < (src_height - 1)) { + const double val00 = static_cast(src[y_lower][x_lower]); + const double val10 = static_cast(src[y_lower + 1][x_lower]); + const double interp = lerp(val00, val10, t); + dst[i][j] = vpMath::saturate(interp); + } + else if (x_lower < (src_width - 1)) { + const double val00 = static_cast(src[y_lower][x_lower]); + const double val01 = static_cast(src[y_lower][x_lower + 1]); + const double interp = lerp(val00, val01, s); + dst[i][j] = vpMath::saturate(interp); + } + else { + dst[i][j] = src[y_lower][x_lower]; + } + } + } + } + } +} + +inline void vpImageTools::warpLinearFixedPointNotCenter(const vpImage &src, const vpMatrix &T, + vpImage &dst, bool affine) +{ + const unsigned int index_0 = 0, index_1 = 1, index_2 = 2; + const int nbits = 16; + const int64_t precision = 1 << nbits; + const float precision_1 = 1.f / static_cast(precision); + const int64_t precision2 = 1ULL << (2 * nbits); + const float precision_2 = 1.f / static_cast(precision2); + + int64_t a0_i64 = static_cast(T[index_0][index_0] * precision); + int64_t a1_i64 = static_cast(T[index_0][index_1] * precision); + int64_t a2_i64 = static_cast(T[index_0][index_2] * precision); + int64_t a3_i64 = static_cast(T[index_1][index_0] * precision); + int64_t a4_i64 = static_cast(T[index_1][index_1] * precision); + int64_t a5_i64 = static_cast(T[index_1][index_2] * precision); + int64_t a6_i64 = T.getRows() == 3 ? static_cast(T[index_2][index_0] * precision) : 0; + int64_t a7_i64 = T.getRows() == 3 ? static_cast(T[index_2][index_1] * precision) : 0; + int64_t a8_i64 = precision; + + int64_t height_i64 = static_cast(src.getHeight() * precision); + int64_t width_i64 = static_cast(src.getWidth() * precision); + + if (affine) { + unsigned int dst_height = dst.getHeight(); + unsigned int dst_width = dst.getWidth(); + int src_height = static_cast(src.getHeight()); + int src_width = static_cast(src.getWidth()); + const unsigned char aChannelVal = 255; + for (unsigned int i = 0; i < dst_height; ++i) { + int64_t xi = a2_i64; + int64_t yi = a5_i64; + + for (unsigned int j = 0; j < dst_width; ++j) { + if ((yi >= 0) && (yi < height_i64) && (xi >= 0) && (xi < width_i64)) { + const int64_t xi_lower = xi & (~0xFFFF); + const int64_t yi_lower = yi & (~0xFFFF); + + const int64_t t = yi - yi_lower; + const int64_t t_1 = precision - t; + const int64_t s = xi - xi_lower; + const int64_t s_1 = precision - s; + + const int x_ = static_cast(xi >> nbits); + const int y_ = static_cast(yi >> nbits); + + if ((y_ < (src_height - 1)) && (x_ < (src_width - 1))) { + const vpRGBa val00 = src[y_][x_]; + const vpRGBa val01 = src[y_][x_ + 1]; + const vpRGBa val10 = src[y_ + 1][x_]; + const vpRGBa val11 = src[y_ + 1][x_ + 1]; + const int64_t interpR_i64 = + static_cast((s_1 * t_1 * val00.R) + (s * t_1 * val01.R) + (s_1 * t * val10.R) + (s * t * val11.R)); + const float interpR = static_cast(interpR_i64 >> (nbits * 2)) + (static_cast(interpR_i64 & 0xFFFFFFFFU) * precision_2); + + const int64_t interpG_i64 = + static_cast((s_1 * t_1 * val00.G) + (s * t_1 * val01.G) + (s_1 * t * val10.G) + (s * t * val11.G)); + const float interpG = static_cast(interpG_i64 >> (nbits * 2)) + (static_cast(interpG_i64 & 0xFFFFFFFFU) * precision_2); + + const int64_t interpB_i64 = + static_cast((s_1 * t_1 * val00.B) + (s * t_1 * val01.B) + (s_1 * t * val10.B) + (s * t * val11.B)); + const float interpB = static_cast(interpB_i64 >> (nbits * 2)) + (static_cast(interpB_i64 & 0xFFFFFFFFU) * precision_2); + + dst[i][j] = vpRGBa(vpMath::saturate(interpR), vpMath::saturate(interpG), + vpMath::saturate(interpB), aChannelVal); + } + else if (y_ < (src_height - 1)) { + const vpRGBa val00 = src[y_][x_]; + const vpRGBa val10 = src[y_ + 1][x_]; + const int64_t interpR_i64 = static_cast((t_1 * val00.R) + (t * val10.R)); + const float interpR = static_cast(interpR_i64 >> nbits) + (static_cast(interpR_i64 & 0xFFFF) * precision_1); + + const int64_t interpG_i64 = static_cast((t_1 * val00.G) + (t * val10.G)); + const float interpG = static_cast(interpG_i64 >> nbits) + (static_cast(interpG_i64 & 0xFFFF) * precision_1); + + const int64_t interpB_i64 = static_cast((t_1 * val00.B) + (t * val10.B)); + const float interpB = static_cast(interpB_i64 >> nbits) + (static_cast(interpB_i64 & 0xFFFF) * precision_1); + + dst[i][j] = vpRGBa(vpMath::saturate(interpR), vpMath::saturate(interpG), + vpMath::saturate(interpB), aChannelVal); + } + else if (x_ < (src_width - 1)) { + const vpRGBa val00 = src[y_][x_]; + const vpRGBa val01 = src[y_][x_ + 1]; + const int64_t interpR_i64 = static_cast((s_1 * val00.R) + (s * val01.R)); + const float interpR = static_cast(interpR_i64 >> nbits) + (static_cast(interpR_i64 & 0xFFFF) * precision_1); + + const int64_t interpG_i64 = static_cast((s_1 * val00.G) + (s * val01.G)); + const float interpG = static_cast(interpG_i64 >> nbits) + (static_cast(interpG_i64 & 0xFFFF) * precision_1); + + const int64_t interpB_i64 = static_cast((s_1 * val00.B) + (s * val01.B)); + const float interpB = static_cast(interpB_i64 >> nbits) + (static_cast(interpB_i64 & 0xFFFF) * precision_1); + + dst[i][j] = vpRGBa(vpMath::saturate(interpR), vpMath::saturate(interpG), + vpMath::saturate(interpB), aChannelVal); + } + else { + dst[i][j] = src[y_][x_]; + } + } + + xi += a0_i64; + yi += a3_i64; + } + + a2_i64 += a1_i64; + a5_i64 += a4_i64; + } + } + else { + unsigned int dst_height = dst.getHeight(); + unsigned int dst_width = dst.getWidth(); + int src_height = static_cast(src.getHeight()); + int src_width = static_cast(src.getWidth()); + const unsigned char aChannelVal = 255; + for (unsigned int i = 0; i < dst_height; ++i) { + int64_t xi = a2_i64; + int64_t yi = a5_i64; + int64_t wi = a8_i64; + + for (unsigned int j = 0; j < dst_width; ++j) { + if ((yi >= 0) && (yi <= ((src_height - 1) * wi)) && (xi >= 0) && + (xi <= ((src_width - 1) * wi))) { + const float wi_ = static_cast(wi >> nbits) + (static_cast(wi & 0xFFFF) * precision_1); + const float xi_ = (static_cast(xi >> nbits) + (static_cast(xi & 0xFFFF) * precision_1)) / wi_; + const float yi_ = (static_cast(yi >> nbits) + (static_cast(yi & 0xFFFF) * precision_1)) / wi_; + + const int x_ = static_cast(xi_); + const int y_ = static_cast(yi_); + + const float t = yi_ - static_cast(y_); + const float s = xi_ - static_cast(x_); + + if ((y_ < (src_height - 1)) && (x_ < (src_width - 1))) { + const vpRGBa val00 = src[y_][x_]; + const vpRGBa val01 = src[y_][x_ + 1]; + const vpRGBa val10 = src[y_ + 1][x_]; + const vpRGBa val11 = src[y_ + 1][x_ + 1]; + const float colR0 = lerp(val00.R, val01.R, s); + const float colR1 = lerp(val10.R, val11.R, s); + const float interpR = lerp(colR0, colR1, t); + + const float colG0 = lerp(val00.G, val01.G, s); + const float colG1 = lerp(val10.G, val11.G, s); + const float interpG = lerp(colG0, colG1, t); + + const float colB0 = lerp(val00.B, val01.B, s); + const float colB1 = lerp(val10.B, val11.B, s); + const float interpB = lerp(colB0, colB1, t); + + dst[i][j] = vpRGBa(vpMath::saturate(interpR), vpMath::saturate(interpG), + vpMath::saturate(interpB), aChannelVal); + } + else if (y_ < (src_height - 1)) { + const vpRGBa val00 = src[y_][x_]; + const vpRGBa val10 = src[y_ + 1][x_]; + const float interpR = lerp(val00.R, val10.R, t); + const float interpG = lerp(val00.G, val10.G, t); + const float interpB = lerp(val00.B, val10.B, t); + + dst[i][j] = vpRGBa(vpMath::saturate(interpR), vpMath::saturate(interpG), + vpMath::saturate(interpB), aChannelVal); + } + else if (x_ < (src_width - 1)) { + const vpRGBa val00 = src[y_][x_]; + const vpRGBa val01 = src[y_][x_ + 1]; + const float interpR = lerp(val00.R, val01.R, s); + const float interpG = lerp(val00.G, val01.G, s); + const float interpB = lerp(val00.B, val01.B, s); + + dst[i][j] = vpRGBa(vpMath::saturate(interpR), vpMath::saturate(interpG), + vpMath::saturate(interpB), aChannelVal); + } + else { + dst[i][j] = src[y_][x_]; + } + } + + xi += a0_i64; + yi += a3_i64; + wi += a6_i64; + } + + a2_i64 += a1_i64; + a5_i64 += a4_i64; + a8_i64 += a7_i64; + } + } + +} + +template <> +inline void vpImageTools::warpLinear(const vpImage &src, const vpMatrix &T, vpImage &dst, bool affine, + bool centerCorner, bool fixedPoint) +{ + const unsigned int index_0 = 0, index_1 = 1, index_2 = 2; + if (fixedPoint && (!centerCorner)) { + warpLinearFixedPointNotCenter(src, T, dst, affine); + } + else { + double a0 = T[index_0][index_0]; + double a1 = T[index_0][index_1]; + double a2 = T[index_0][index_2]; + double a3 = T[index_1][index_0]; + double a4 = T[index_1][index_1]; + double a5 = T[index_1][index_2]; + double a6 = affine ? 0.0 : T[index_2][index_0]; + double a7 = affine ? 0.0 : T[index_2][index_1]; + double a8 = affine ? 1.0 : T[index_2][index_2]; + + unsigned int dst_height = dst.getHeight(); + unsigned int dst_width = dst.getWidth(); + int src_height = static_cast(src.getHeight()); + int src_width = static_cast(src.getWidth()); + const unsigned char aChannelVal = 255; + for (unsigned int i = 0; i < dst_height; ++i) { + for (unsigned int j = 0; j < dst_width; ++j) { + double x = (a0 * (centerCorner ? (j + 0.5) : j)) + (a1 * (centerCorner ? (i + 0.5) : i)) + a2; + double y = (a3 * (centerCorner ? (j + 0.5) : j)) + (a4 * (centerCorner ? (i + 0.5) : i)) + a5; + double w = (a6 * (centerCorner ? (j + 0.5) : j)) + (a7 * (centerCorner ? (i + 0.5) : i)) + a8; + + x = (x / w) - (centerCorner ? 0.5 : 0); + y = (y / w) - (centerCorner ? 0.5 : 0); + + int x_lower = static_cast(x); + int y_lower = static_cast(y); + + bool stop_for_loop = false; + if ((y_lower >= src_height) || (x_lower >= src_width) || (y < 0) || (x < 0)) { + stop_for_loop = true; + } + if (!stop_for_loop) { + double s = x - x_lower; + double t = y - y_lower; + + if ((y_lower < (src_height - 1)) && (x_lower < (src_width - 1))) { + const vpRGBa val00 = src[y_lower][x_lower]; + const vpRGBa val01 = src[y_lower][x_lower + 1]; + const vpRGBa val10 = src[y_lower + 1][x_lower]; + const vpRGBa val11 = src[y_lower + 1][x_lower + 1]; + const double colR0 = lerp(val00.R, val01.R, s); + const double colR1 = lerp(val10.R, val11.R, s); + const double interpR = lerp(colR0, colR1, t); + + const double colG0 = lerp(val00.G, val01.G, s); + const double colG1 = lerp(val10.G, val11.G, s); + const double interpG = lerp(colG0, colG1, t); + + const double colB0 = lerp(val00.B, val01.B, s); + const double colB1 = lerp(val10.B, val11.B, s); + const double interpB = lerp(colB0, colB1, t); + + dst[i][j] = vpRGBa(vpMath::saturate(interpR), vpMath::saturate(interpG), + vpMath::saturate(interpB), aChannelVal); + } + else if (y_lower < (src_height - 1)) { + const vpRGBa val00 = src[y_lower][x_lower]; + const vpRGBa val10 = src[y_lower + 1][x_lower]; + const double interpR = lerp(val00.R, val10.R, t); + const double interpG = lerp(val00.G, val10.G, t); + const double interpB = lerp(val00.B, val10.B, t); + + dst[i][j] = vpRGBa(vpMath::saturate(interpR), vpMath::saturate(interpG), + vpMath::saturate(interpB), aChannelVal); + } + else if (x_lower < (src_width - 1)) { + const vpRGBa val00 = src[y_lower][x_lower]; + const vpRGBa val01 = src[y_lower][x_lower + 1]; + const double interpR = lerp(val00.R, val01.R, s); + const double interpG = lerp(val00.G, val01.G, s); + const double interpB = lerp(val00.B, val01.B, s); + + dst[i][j] = vpRGBa(vpMath::saturate(interpR), vpMath::saturate(interpG), + vpMath::saturate(interpB), aChannelVal); + } + else { + dst[i][j] = src[y_lower][x_lower]; + } + } + } + } + } +} + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImage_getters.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImage_getters.h new file mode 100644 index 00000000..6beb9eeb --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImage_getters.h @@ -0,0 +1,1101 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Image handling. + */ + +#ifndef VP_IMAGE_GETTERS_H +#define VP_IMAGE_GETTERS_H + +// Warning: this file shouldn't be included by the user. Internal usage only to reduce length of vpImage.h + +/*! + Retrieves pixel value from an image containing values of type `Type`. + + Gets the value of a sub-pixel with coordinates (i,j). + + \param i : Pixel coordinate along the rows. + \param j : Pixel coordinate along the columns. + + \return Pixel value. + + \exception vpImageException::notInTheImage : If (i,j) is out of the image. +*/ +template inline Type vpImage::getValue(unsigned int i, unsigned int j) const +{ + if ((i >= height) || (j >= width)) { + throw(vpException(vpImageException::notInTheImage, "Pixel outside the image")); + } + + return row[i][j]; +} + +/*! + Retrieves pixel value from an image containing values of type `Type` with + sub-pixel accuracy. + + Gets the value of a sub-pixel with coordinates (i,j) with bilinear + interpolation. + + See also vpImageTools::interpolate() for a similar result, but with a choice of the interpolation method. + + \param i : Sub-pixel coordinate along the rows. + \param j : Sub-pixel coordinate along the columns. + + \return Interpolated sub-pixel value from the four neighbours. + + \exception vpImageException::notInTheImage : If (i,j) is out of the image. +*/ +template Type vpImage::getValue(double i, double j) const +{ + if ((i < 0) || (j < 0) || ((i + 1) > height) || ((j + 1) > width)) { + throw(vpException(vpImageException::notInTheImage, "Pixel outside of the image")); + } + if ((height * width) == 0) { + throw vpException(vpImageException::notInitializedError, "Empty image!"); + } + + unsigned int iround = static_cast(floor(i)); + unsigned int jround = static_cast(floor(j)); + + double rratio = i - static_cast(iround); + double cratio = j - static_cast(jround); + + double rfrac = 1.0 - rratio; + double cfrac = 1.0 - cratio; + + unsigned int iround_1 = std::min(height - 1, iround + 1); + unsigned int jround_1 = std::min(width - 1, jround + 1); + + double value = + (((static_cast(row[iround][jround]) * rfrac) + (static_cast(row[iround_1][jround]) * rratio)) * cfrac) + + (((static_cast(row[iround][jround_1]) * rfrac) + (static_cast(row[iround_1][jround_1]) * rratio)) * + cratio); + + return static_cast(vpMath::round(value)); +} + +/*! + \relates vpImage +*/ +template <> inline double vpImage::getValue(double i, double j) const +{ + if ((i < 0) || (j < 0) || ((i + 1) > height) || ((j + 1) > width)) { + throw(vpException(vpImageException::notInTheImage, "Pixel outside of the image")); + } + if ((height * width) == 0) { + throw vpException(vpImageException::notInitializedError, "Empty image!"); + } + + unsigned int iround = static_cast(floor(i)); + unsigned int jround = static_cast(floor(j)); + + double rratio = i - static_cast(iround); + double cratio = j - static_cast(jround); + + double rfrac = 1.0 - rratio; + double cfrac = 1.0 - cratio; + + unsigned int iround_1 = std::min(height - 1, iround + 1); + unsigned int jround_1 = std::min(width - 1, jround + 1); + + return (((row[iround][jround] * rfrac) + (row[iround_1][jround] * rratio)) * cfrac) + + (((row[iround][jround_1] * rfrac) + (row[iround_1][jround_1] * rratio)) * cratio); +} + +/*! + \relates vpImage + */ +template <> inline unsigned char vpImage::getValue(double i, double j) const +{ + if ((i < 0) || (j < 0) || ((i + 1) > height) || ((j + 1) > width)) { + throw(vpException(vpImageException::notInTheImage, "Pixel outside of the image")); + } + if ((height * width) == 0) { + throw vpException(vpImageException::notInitializedError, "Empty image!"); + } + + // alpha architecture is bi-endianness. The following optimization makes testImageGetValue failing +#if (defined(VISP_LITTLE_ENDIAN) || defined(VISP_BIG_ENDIAN)) && !(defined(__alpha__) || defined(_M_ALPHA)) + // Fixed-point arithmetic + const uint32_t magic_8 = 8; + const uint32_t magic_16 = 16; + const uint32_t magic_32 = 32; + const uint32_t magic_0x00FF = 0x00FF; + const uint32_t precision = 1U << magic_16; + uint64_t y = static_cast(i * precision); + uint64_t x = static_cast(j * precision); + + uint64_t iround = y & (~0xFFFFU); + uint64_t jround = x & (~0xFFFFU); + + uint64_t rratio = y - iround; + uint64_t cratio = x - jround; + + uint64_t rfrac = precision - rratio; + uint64_t cfrac = precision - cratio; + + uint64_t x_ = x >> magic_16; + uint64_t y_ = y >> magic_16; + + if (((y_ + 1) < height) && ((x_ + 1) < width)) { + uint16_t up = vpEndian::reinterpret_cast_uchar_to_uint16_LE(bitmap + (y_ * width) + x_); + uint16_t down = vpEndian::reinterpret_cast_uchar_to_uint16_LE(bitmap + ((y_ + 1) * width) + x_); + + return static_cast((((((up & magic_0x00FF) * rfrac) + ((down & magic_0x00FF) * rratio)) * cfrac) + + (((up >> magic_8) * rfrac) + ((down >> magic_8) * rratio)) * cratio) >> magic_32); + } + else if ((y_ + 1) < height) { + return static_cast(((row[y_][x_] * rfrac) + (row[y_ + 1][x_] * rratio)) >> magic_16); + } + else if ((x_ + 1) < width) { + uint16_t up = vpEndian::reinterpret_cast_uchar_to_uint16_LE(bitmap + (y_ * width) + x_); + return static_cast((((up & magic_0x00FF) * cfrac) + ((up >> magic_8) * cratio)) >> magic_16); + } + else { + return row[y_][x_]; + } +#else + unsigned int iround = static_cast(floor(i)); + unsigned int jround = static_cast(floor(j)); + + if (iround >= height || jround >= width) { + throw(vpException(vpImageException::notInTheImage, "Pixel outside the image")); + } + + double rratio = i - static_cast(iround); + double cratio = j - static_cast(jround); + + double rfrac = 1.0 - rratio; + double cfrac = 1.0 - cratio; + + unsigned int iround_1 = std::min(height - 1, iround + 1); + unsigned int jround_1 = std::min(width - 1, jround + 1); + + double value = + (static_cast(row[iround][jround]) * rfrac + static_cast(row[iround_1][jround]) * rratio) * cfrac + + (static_cast(row[iround][jround_1]) * rfrac + static_cast(row[iround_1][jround_1]) * rratio) * + cratio; + return static_cast(vpMath::round(value)); +#endif +} + +/*! + \relates vpImage + */ +template <> inline vpRGBa vpImage::getValue(double i, double j) const +{ + if ((i < 0) || (j < 0) || ((i + 1) > height) || ((j + 1) > width)) { + throw(vpException(vpImageException::notInTheImage, "Pixel outside of the image")); + } + if ((height * width) == 0) { + throw vpException(vpImageException::notInitializedError, "Empty image!"); + } + + unsigned int iround = static_cast(floor(i)); + unsigned int jround = static_cast(floor(j)); + + double rratio = i - static_cast(iround); + double cratio = j - static_cast(jround); + + double rfrac = 1.0 - rratio; + double cfrac = 1.0 - cratio; + + unsigned int iround_1 = std::min(height - 1, iround + 1); + unsigned int jround_1 = std::min(width - 1, jround + 1); + + double valueR = + (((static_cast(row[iround][jround].R) * rfrac) + (static_cast(row[iround_1][jround].R) * rratio)) * + cfrac) + + (((static_cast(row[iround][jround_1].R) * rfrac) + (static_cast(row[iround_1][jround_1].R) * rratio)) * + cratio); + double valueG = + (((static_cast(row[iround][jround].G) * rfrac) + (static_cast(row[iround_1][jround].G) * rratio)) * + cfrac) + + (((static_cast(row[iround][jround_1].G) * rfrac) + (static_cast(row[iround_1][jround_1].G) * rratio)) * + cratio); + double valueB = + (((static_cast(row[iround][jround].B) * rfrac) + (static_cast(row[iround_1][jround].B) * rratio)) * + cfrac) + + (((static_cast(row[iround][jround_1].B) * rfrac) + (static_cast(row[iround_1][jround_1].B) * rratio)) * + cratio); + + return vpRGBa(static_cast(vpMath::round(valueR)), static_cast(vpMath::round(valueG)), + static_cast(vpMath::round(valueB))); +} + +/*! + \relates vpImage + */ +template <> inline vpRGBf vpImage::getValue(double i, double j) const +{ + if ((i < 0) || (j < 0) || ((i + 1) > height) || ((j + 1) > width)) { + throw(vpException(vpImageException::notInTheImage, "Pixel outside of the image")); + } + if ((height * width) == 0) { + throw vpException(vpImageException::notInitializedError, "Empty image!"); + } + + unsigned int iround = static_cast(floor(i)); + unsigned int jround = static_cast(floor(j)); + + double rratio = i - static_cast(iround); + double cratio = j - static_cast(jround); + + double rfrac = 1.0 - rratio; + double cfrac = 1.0 - cratio; + + unsigned int iround_1 = std::min(height - 1, iround + 1); + unsigned int jround_1 = std::min(width - 1, jround + 1); + + double valueR = + (((static_cast(row[iround][jround].R) * rfrac) + (static_cast(row[iround_1][jround].R) * rratio)) * + cfrac) + + (((static_cast(row[iround][jround_1].R) * rfrac) + (static_cast(row[iround_1][jround_1].R) * rratio)) * + cratio); + double valueG = + (((static_cast(row[iround][jround].G) * rfrac) + (static_cast(row[iround_1][jround].G) * rratio)) * + cfrac) + + (((static_cast(row[iround][jround_1].G) * rfrac) + (static_cast(row[iround_1][jround_1].G) * rratio)) * + cratio); + double valueB = + (((static_cast(row[iround][jround].B) * rfrac) + (static_cast(row[iround_1][jround].B) * rratio)) * + cfrac) + + (((static_cast(row[iround][jround_1].B) * rfrac) + (static_cast(row[iround_1][jround_1].B) * rratio)) * + cratio); + + return vpRGBf(static_cast(valueR), static_cast(valueG), static_cast(valueB)); +} + +/*! + Retrieves pixel value from an image containing values of type \e Type with + sub-pixel accuracy. + + Gets the value of a sub-pixel with coordinates (i,j) with bilinear + interpolation. + + See also vpImageTools::interpolate() for a similar result, but with a choice of the interpolation method. + + \param ip : Sub-pixel coordinates of a point in the image. + + \return Interpolated sub-pixel value from the four neighbors. + + \exception vpImageException::notInTheImage : If the image point \e ip is out + of the image. +*/ +template inline Type vpImage::getValue(const vpImagePoint &ip) const +{ + return getValue(ip.get_i(), ip.get_j()); +} + +/*! + \relates vpImage + */ +template <> inline double vpImage::getValue(const vpImagePoint &ip) const +{ + return getValue(ip.get_i(), ip.get_j()); +} + +/*! + \relates vpImage + */ +template <> inline unsigned char vpImage::getValue(const vpImagePoint &ip) const +{ + return getValue(ip.get_i(), ip.get_j()); +} + +/*! + \relates vpImage + */ +template <> inline vpRGBa vpImage::getValue(const vpImagePoint &ip) const +{ + return getValue(ip.get_i(), ip.get_j()); +} + +/*! + * \brief Return the maximum value within the bitmap + * \param onlyFiniteVal : This parameter is ignored for non double or non float bitmap. + * If true, consider only finite values. + * + * \sa getMinValue() + */ +template Type vpImage::getMaxValue(bool onlyFiniteVal) const +{ + if (npixels == 0) { + throw(vpException(vpException::fatalError, "Cannot compute maximum value of an empty image")); + } + Type m = bitmap[0]; + for (unsigned int i = 0; i < npixels; ++i) { + if (bitmap[i] > m) { + m = bitmap[i]; + } + } + (void)onlyFiniteVal; + return m; +} + +/*! + * \relates vpImage + * \brief Return the maximum value within the double bitmap. + * \param onlyFiniteVal : This parameter is ignored for non double or non float bitmap. + * If true, consider only finite values. + * + * \sa getMinValue() + */ +template <> inline double vpImage::getMaxValue(bool onlyFiniteVal) const +{ + if (npixels == 0) { + throw(vpException(vpException::fatalError, "Cannot compute maximum value of an empty image")); + } + double m = bitmap[0]; + if (onlyFiniteVal) { + for (unsigned int i = 0; i < npixels; ++i) { + if ((bitmap[i] > m) && (vpMath::isFinite(bitmap[i]))) { + m = bitmap[i]; + } + } + } + else { + for (unsigned int i = 0; i < npixels; ++i) { + if (bitmap[i] > m) { + m = bitmap[i]; + } + } + } + return m; +} + +/*! + * \relates vpImage + * \brief Return the maximum value within the float bitmap. + * \param onlyFiniteVal : This parameter is ignored for non double or non float bitmap. + * If true, consider only finite values. + * + * \sa getMinValue() + */ +template <> inline float vpImage::getMaxValue(bool onlyFiniteVal) const +{ + if (npixels == 0) { + throw(vpException(vpException::fatalError, "Cannot compute maximum value of an empty image")); + } + float m = bitmap[0]; + if (onlyFiniteVal) { + for (unsigned int i = 0; i < npixels; ++i) { + if ((bitmap[i] > m) && (vpMath::isFinite(bitmap[i]))) { + m = bitmap[i]; + } + } + } + else { + for (unsigned int i = 0; i < npixels; ++i) { + if (bitmap[i] > m) { + m = bitmap[i]; + } + } + } + return m; +} + +/*! + * \brief Return the minimum value within the bitmap + * \param onlyFiniteVal : This parameter is ignored for non double or non float bitmap. + * If true, consider only finite values. + * + * \sa getMaxValue() + */ +template Type vpImage::getMinValue(bool onlyFiniteVal) const +{ + if (npixels == 0) { + throw(vpException(vpException::fatalError, "Cannot compute minimum value of an empty image")); + } + Type m = bitmap[0]; + for (unsigned int i = 0; i < npixels; ++i) { + if (bitmap[i] < m) { + m = bitmap[i]; + } + } + (void)onlyFiniteVal; + return m; +} + +/*! + * \relates vpImage + * \brief Return the minimum value within the double bitmap. + * \param onlyFiniteVal : This parameter is ignored for non double or non float bitmap. + * If true, consider only finite values. + * + * \sa getMaxValue() + */ +template <> inline double vpImage::getMinValue(bool onlyFiniteVal) const +{ + if (npixels == 0) { + throw(vpException(vpException::fatalError, "Cannot compute minimum value of an empty image")); + } + double m = bitmap[0]; + if (onlyFiniteVal) { + for (unsigned int i = 0; i < npixels; ++i) { + if ((bitmap[i] < m) && (vpMath::isFinite(bitmap[i]))) { + m = bitmap[i]; + } + } + } + else { + for (unsigned int i = 0; i < npixels; ++i) { + if (bitmap[i] < m) { + m = bitmap[i]; + } + } + } + return m; +} + +/*! + * \relates vpImage + * \brief Return the minimum value within the float bitmap. + * \param onlyFiniteVal : This parameter is ignored for non double or non float bitmap. + * If true, consider only finite values. + * + * \sa getMaxValue() + */ +template <> inline float vpImage::getMinValue(bool onlyFiniteVal) const +{ + if (npixels == 0) { + throw(vpException(vpException::fatalError, "Cannot compute minimum value of an empty image")); + } + float m = bitmap[0]; + if (onlyFiniteVal) { + for (unsigned int i = 0; i < npixels; ++i) { + if ((bitmap[i] < m) && (vpMath::isFinite(bitmap[i]))) { + m = bitmap[i]; + } + } + } + else { + for (unsigned int i = 0; i < npixels; ++i) { + if (bitmap[i] < m) { + m = bitmap[i]; + } + } + } + return m; +} + +/*! + * \brief Look for the minimum and the maximum value within the bitmap + * \param min : The minimal value within the bitmap. + * \param max : The maximal value within the bitmap. + * \param onlyFiniteVal : This parameter is ignored for non double or non float bitmap. + * + * \sa getMaxValue() + * \sa getMinValue() + * \sa getMinMaxLoc() + */ +template void vpImage::getMinMaxValue(Type &min, Type &max, bool onlyFiniteVal) const +{ + if (npixels == 0) { + throw(vpException(vpException::fatalError, "Cannot get minimum/maximum values of an empty image")); + } + + min = bitmap[0]; + max = bitmap[0]; + for (unsigned int i = 0; i < npixels; ++i) { + if (bitmap[i] < min) { + min = bitmap[i]; + } + if (bitmap[i] > max) { + max = bitmap[i]; + } + } + (void)onlyFiniteVal; +} + +/*! + * \relates vpImage + * \brief Look for the minimum and the maximum value within the double bitmap + * \param min : The minimal value within the bitmap. + * \param max : The maximal value within the bitmap. + * \param onlyFiniteVal : If true, consider only finite values. + * + * \sa getMaxValue() + * \sa getMinValue() + * \sa getMinMaxLoc() + */ +template <> inline void vpImage::getMinMaxValue(double &min, double &max, bool onlyFiniteVal) const +{ + if (npixels == 0) { + throw(vpException(vpException::fatalError, "Cannot get minimum/maximum values of an empty image")); + } + + min = bitmap[0]; + max = bitmap[0]; + if (onlyFiniteVal) { + for (unsigned int i = 0; i < npixels; ++i) { + if (vpMath::isFinite(bitmap[i])) { + if (bitmap[i] < min) { + min = bitmap[i]; + } + if (bitmap[i] > max) { + max = bitmap[i]; + } + } + } + } + else { + for (unsigned int i = 0; i < npixels; ++i) { + if (bitmap[i] < min) { + min = bitmap[i]; + } + if (bitmap[i] > max) { + max = bitmap[i]; + } + } + } +} + +/*! + * \relates vpImage + * \brief Look for the minimum and the maximum value within the float bitmap + * \param min : The minimal value within the bitmap. + * \param max : The maximal value within the bitmap. + * \param onlyFiniteVal : If true, consider only finite values. + * + * \sa getMaxValue() + * \sa getMinValue() + * \sa getMinMaxLoc() + */ +template <> inline void vpImage::getMinMaxValue(float &min, float &max, bool onlyFiniteVal) const +{ + if (npixels == 0) { + throw(vpException(vpException::fatalError, "Cannot get minimum/maximum values of an empty image")); + } + + min = bitmap[0]; + max = bitmap[0]; + if (onlyFiniteVal) { + for (unsigned int i = 0; i < npixels; ++i) { + if (vpMath::isFinite(bitmap[i])) { + if (bitmap[i] < min) { + min = bitmap[i]; + } + if (bitmap[i] > max) { + max = bitmap[i]; + } + } + } + } + else { + for (unsigned int i = 0; i < npixels; ++i) { + if (bitmap[i] < min) { + min = bitmap[i]; + } + if (bitmap[i] > max) { + max = bitmap[i]; + } + } + } +} + +/*! + * \relates vpImage + * \brief Look for the minimum and the maximum value within the 3-channels float bitmap + * \param min : The minimal values within the bitmap. + * \param max : The maximal values within the bitmap. + * \param onlyFiniteVal : If true, consider only finite values. + * + * \sa getMaxValue() + * \sa getMinValue() + * \sa getMinMaxLoc() + */ +template <> inline void vpImage::getMinMaxValue(vpRGBf &min, vpRGBf &max, bool onlyFiniteVal) const +{ + if (npixels == 0) { + throw(vpException(vpException::fatalError, "Cannot get minimum/maximum values of an empty image")); + } + + min = bitmap[0]; + max = bitmap[0]; + if (onlyFiniteVal) { + for (unsigned int i = 0; i < npixels; ++i) { + if (vpMath::isFinite(bitmap[i].R)) { + if (bitmap[i].R < min.R) { + min.R = bitmap[i].R; + } + if (bitmap[i].R > max.R) { + max.R = bitmap[i].R; + } + } + if (vpMath::isFinite(bitmap[i].G)) { + if (bitmap[i].G < min.G) { + min.G = bitmap[i].G; + } + if (bitmap[i].G > max.G) { + max.G = bitmap[i].G; + } + } + if (vpMath::isFinite(bitmap[i].B)) { + if (bitmap[i].B < min.B) { + min.B = bitmap[i].B; + } + if (bitmap[i].B > max.B) { + max.B = bitmap[i].B; + } + } + } + } + else { + for (unsigned int i = 0; i < npixels; ++i) { + if (bitmap[i].R < min.R) { + min.R = bitmap[i].R; + } + if (bitmap[i].R > max.R) { + max.R = bitmap[i].R; + } + + if (bitmap[i].G < min.G) { + min.G = bitmap[i].G; + } + if (bitmap[i].G > max.G) { + max.G = bitmap[i].G; + } + + if (bitmap[i].B < min.B) { + min.B = bitmap[i].B; + } + if (bitmap[i].B > max.B) { + max.B = bitmap[i].B; + } + } + } +} + +/*! + \brief Get the position of the minimum and/or the maximum pixel value within the bitmap and + the corresponding value. + Following code allows retrieving only minimum value and position: + \code + vpImage I(h, w); + //[...] Fill I + vpImagePoint min_loc; + double min_val = 0.0; + I.getMinMaxLoc(&min_loc, nullptr, &min_val, nullptr); + \endcode + + \param minLoc : Position of the pixel with minimum value if not nullptr. + \param maxLoc : Position of the pixel with maximum value if not nullptr. + \param minVal : Minimum pixel value if not nullptr. + \param maxVal : Maximum pixel value if not nullptr. + + \sa getMaxValue() + \sa getMinValue() + \sa getMinMaxValue() +*/ +template +void vpImage::getMinMaxLoc(vpImagePoint *minLoc, vpImagePoint *maxLoc, Type *minVal, Type *maxVal) const +{ + if (npixels == 0) { + throw(vpException(vpException::fatalError, "Cannot get location of minimum/maximum " + "values of an empty image")); + } + + Type min = bitmap[0], max = bitmap[0]; + vpImagePoint minLoc_, maxLoc_; + for (unsigned int i = 0; i < height; ++i) { + for (unsigned int j = 0; j < width; ++j) { + if (row[i][j] < min) { + min = row[i][j]; + minLoc_.set_ij(i, j); + } + + if (row[i][j] > max) { + max = row[i][j]; + maxLoc_.set_ij(i, j); + } + } + } + + if (minLoc != nullptr) { + *minLoc = minLoc_; + } + + if (maxLoc != nullptr) { + *maxLoc = maxLoc_; + } + + if (minVal != nullptr) { + *minVal = min; + } + + if (maxVal != nullptr) { + *maxVal = max; + } +} + +/*! + * \brief Return the mean value of the bitmap. + * + * For vpRGBa and vpRGBf image types, the sum of image intensities is computed by (R+G+B). + * + * \param[in] p_mask Optional parameter. If not set to nullptr, a boolean mask that indicates which points must be + * considered, if set to true. + * \param[out] nbValidPoints Optional parameter. When different from nullptr contains the number of points that are + * valid according to the boolean mask or image size when `p_mask` is set to nullptr. + */ +template double vpImage::getMeanValue(const vpImage *p_mask, unsigned int *nbValidPoints) const +{ + if ((height == 0) || (width == 0)) { + return 0.0; + } + unsigned int nbPointsInMask = 0; + double sum = getSum(p_mask, &nbPointsInMask); + if (nbPointsInMask == 0) { + throw(vpException(vpException::divideByZeroError, "Division by zero in vpImage::getMeanValue()")); + } + if (nbValidPoints) { + *nbValidPoints = nbPointsInMask; + } + return sum / nbPointsInMask; +} + +/*! +* \brief Return the standard deviation of the bitmap +* +* - For a vpRGBa or a vpRGBf image, we compute the standard deviation as follow: +* \f[ stdev = \sqrt{\frac{1}{size} \sum_{r = 0}^{height-1} \sum_{c = 0}^{width-1} (I[r][c].R + I[r][c].G + I[r][c].B - \mu)^2}\f] +* - For a unary type image (unsigned char, float, double), we compute the standard deviation as follow: +* \f[ stdev = \sqrt{\frac{1}{size} \sum_{r = 0}^{height-1} \sum_{c = 0}^{width-1} (I[r][c] - \mu)^2}\f] +* +* where \f$ \mu \f$ is the mean of the image as computed by \b vpImage::getMeanValue() and \f$ \mbox{size} \f$ +* is the number of pixels to consider in the mask. +* +* \param[in] p_mask A boolean mask that indicates which points must be considered, if set to true. +* \param[out] nbValidPoints Optional parameter. When different from nullptr contains the number of points that are +* valid according to the boolean mask or image size when `p_mask` is set to nullptr. +*/ +template double vpImage::getStdev(const vpImage *p_mask, unsigned int *nbValidPoints) const +{ + double mean = getMeanValue(p_mask, nbValidPoints); + return getStdev(mean, p_mask); +} + +/*! +* \brief Return the standard deviation of the bitmap +* +* - For a vpRGBa or a vpRGBf image, we compute the standard deviation as follow: +* \f[ stdev = \sqrt{\frac{1}{size} \sum_{r = 0}^{height-1} \sum_{c = 0}^{width-1} (I[r][c].R + I[r][c].G + I[r][c].B - \mu)^2}\f] +* - For a unary type image (unsigned char, float, double), we compute the standard deviation as follow: +* \f[ stdev = \sqrt{\frac{1}{size} \sum_{r = 0}^{height-1} \sum_{c = 0}^{width-1} (I[r][c] - \mu)^2}\f] +* +* where \f$ \mu \f$ is the mean of the image as computed by \b vpImage::getMeanValue() and \f$ \mbox{size} \f$ +* is the number of pixels to consider in the mask. +* +* \param[in] mean The mean of the image. +* \param[in] p_mask Optional parameter. When different from nullptr, a boolean mask that indicates which pixels must +* be considered, if set to true. +* \param[out] nbValidPoints Optional parameter. When different from nullptr contains the number of points that are +* valid according to the boolean mask or image size when `p_mask` is set to nullptr. +* \return double The standard deviation taking into account only the points for which the mask is true. +*/ +template double vpImage::getStdev(const double &mean, const vpImage *p_mask, unsigned int *nbValidPoints) const +{ + if ((height == 0) || (width == 0)) { + return 0.0; + } + const unsigned int size = width * height; + double sum = 0.; + unsigned int nbPointsInMask = 0; + if (p_mask) { + if ((p_mask->getWidth() != width) || (p_mask->getHeight() != height)) { + throw(vpException(vpException::fatalError, "Cannot compute standard deviation: image and mask size differ")); + } + for (unsigned int i = 0; i < size; ++i) { + if (p_mask->bitmap[i]) { + sum += (bitmap[i] - mean) * (bitmap[i] - mean); + ++nbPointsInMask; + } + } + } + else { + for (unsigned int i = 0; i < size; ++i) { + sum += (bitmap[i] - mean) * (bitmap[i] - mean); + } + nbPointsInMask = size; + } + sum /= static_cast(nbPointsInMask); + if (nbValidPoints) { + *nbValidPoints = nbPointsInMask; + } + return std::sqrt(sum); +} + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +/*! +* \relates vpImage +* \brief Return the standard deviation of the bitmap +* +* - For a vpRGBa or a vpRGBf image, we compute the standard deviation as follow: +* \f[ stdev = \sqrt{\frac{1}{size} \sum_{r = 0}^{height-1} \sum_{c = 0}^{width-1} (I[r][c].R + I[r][c].G + I[r][c].B - \mu)^2}\f] +* - For a unary type image (unsigned char, float, double), we compute the standard deviation as follow: +* \f[ stdev = \sqrt{\frac{1}{size} \sum_{r = 0}^{height-1} \sum_{c = 0}^{width-1} (I[r][c] - \mu)^2}\f] +* +* where \f$ \mu \f$ is the mean of the image as computed by \b vpImage::getMeanValue() and \f$ \mbox{size} \f$ +* is the number of pixels to consider in the mask. +* +* \param[in] mean The mean of the image. +* \param[in] p_mask Optional parameter. When different from nullptr, a boolean mask that indicates which pixels must +* be considered, if set to true. +* \param[out] nbValidPoints Optional parameter. When different from nullptr contains the number of points that are +* valid according to the boolean mask or image size when `p_mask` is set to nullptr. +* \return double The standard deviation taking into account only the points for which the mask is true. +*/ +template <> inline double vpImage::getStdev(const double &mean, const vpImage *p_mask, unsigned int *nbValidPoints) const +{ + if ((height == 0) || (width == 0)) { + return 0.0; + } + const unsigned int size = width * height; + double sum = 0.; + unsigned int nbPointsInMask = 0; + if (p_mask) { + if ((p_mask->getWidth() != width) || (p_mask->getHeight() != height)) { + throw(vpException(vpException::fatalError, "Cannot compute standard deviation: image and mask size differ")); + } + for (unsigned int i = 0; i < size; ++i) { + if (p_mask->bitmap[i]) { + double val = static_cast(bitmap[i].R) + static_cast(bitmap[i].G) + static_cast(bitmap[i].B); + sum += (val - mean) * (val - mean); + ++nbPointsInMask; + } + } + } + else { + for (unsigned int i = 0; i < size; ++i) { + double val = static_cast(bitmap[i].R) + static_cast(bitmap[i].G) + static_cast(bitmap[i].B); + sum += (val - mean) * (val - mean); + } + nbPointsInMask = size; + } + sum /= static_cast(nbPointsInMask); + if (nbValidPoints) { + *nbValidPoints = nbPointsInMask; + } + return std::sqrt(sum); +} + +/*! +* \relates vpImage +* \brief Return the standard deviation of the bitmap +* +* - For a vpRGBa or a vpRGBf image, we compute the standard deviation as follow: +* \f[ stdev = \sqrt{\frac{1}{size} \sum_{r = 0}^{height-1} \sum_{c = 0}^{width-1} (I[r][c].R + I[r][c].G + I[r][c].B - \mu)^2}\f] +* - For a unary type image (unsigned char, float, double), we compute the standard deviation as follow: +* \f[ stdev = \sqrt{\frac{1}{size} \sum_{r = 0}^{height-1} \sum_{c = 0}^{width-1} (I[r][c] - \mu)^2}\f] +* +* where \f$ \mu \f$ is the mean of the image as computed by \b vpImage::getMeanValue() and \f$ \mbox{size} \f$ +* is the number of pixels to consider in the mask. +* +* \param[in] mean The mean of the image. +* \param[in] p_mask Optional parameter. When different from nullptr, a boolean mask that indicates which pixels must +* be considered, if set to true. +* \param[out] nbValidPoints Optional parameter. When different from nullptr contains the number of points that are +* valid according to the boolean mask or image size when `p_mask` is set to nullptr. +* \return double The standard deviation taking into account only the points for which the mask is true. +*/ +template <> inline double vpImage::getStdev(const double &mean, const vpImage *p_mask, unsigned int *nbValidPoints) const +{ + if ((height == 0) || (width == 0)) { + return 0.0; + } + const unsigned int size = width * height; + double sum = 0.; + unsigned int nbPointsInMask = 0; + if (p_mask) { + if ((p_mask->getWidth() != width) || (p_mask->getHeight() != height)) { + throw(vpException(vpException::fatalError, "Cannot compute standard deviation: image and mask size differ")); + } + for (unsigned int i = 0; i < size; ++i) { + if (p_mask->bitmap[i]) { + double val = static_cast(bitmap[i].R) + static_cast(bitmap[i].G) + static_cast(bitmap[i].B); + sum += (val - mean) * (val - mean); + ++nbPointsInMask; + } + } + } + else { + for (unsigned int i = 0; i < size; ++i) { + double val = static_cast(bitmap[i].R) + static_cast(bitmap[i].G) + static_cast(bitmap[i].B); + sum += (val - mean) * (val - mean); + } + nbPointsInMask = size; + } + sum /= static_cast(nbPointsInMask); + if (nbValidPoints) { + *nbValidPoints = nbPointsInMask; + } + return std::sqrt(sum); +} +#endif // DOXYGEN_SHOULD_SKIP_THIS + +/** + * \brief Compute the sum of image intensities. + * - For unary image types (unsigned char, float, double), compute the sum of image intensities. + * - For vpRGBa image type, compute the sum (R+G+B) of image intensities. + * - For vpRGBf image type, compute the sum (R+G+B) of image intensities. + * + * \param[in] p_mask Optional parameter. If not set to nullptr, pointer to a boolean mask that indicates the valid + * points by a true flag. + * \param[out] nbValidPoints Optional parameter. When different from nullptr contains the number of points that are + * valid according to the boolean mask or image size when `p_mask` is set to nullptr. + */ +template inline double vpImage::getSum(const vpImage *p_mask, unsigned int *nbValidPoints) const +{ + if ((height == 0) || (width == 0)) { + if (nbValidPoints) { + *nbValidPoints = 0; + } + return 0.0; + } + if (p_mask) { + if ((p_mask->getWidth() != width) || (p_mask->getHeight() != height)) { + throw(vpException(vpException::fatalError, "Cannot compute sum: image and mask size differ")); + } + } + double res = 0.0; + unsigned int nbPointsInMask = 0; + unsigned int size = height * width; + if (p_mask) { + for (unsigned int i = 0; i < size; ++i) { + if (p_mask->bitmap[i]) { + res += static_cast(bitmap[i]); + ++nbPointsInMask; + } + } + } + else { + for (unsigned int i = 0; i < size; ++i) { + res += static_cast(bitmap[i]); + } + nbPointsInMask = size; + } + if (nbValidPoints) { + *nbValidPoints = nbPointsInMask; + } + + return res; +} + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +/** + * \relates vpImage + * \brief Compute the sum of image intensities. + * - For unary image types (unsigned char, float, double), compute the sum of image intensities. + * - For vpRGBa image type, compute the sum (R+G+B) of image intensities. + * - For vpRGBf image type, compute the sum (R+G+B) of image intensities. + * + * \param[in] p_mask Optional parameter. If not set to nullptr, pointer to a boolean mask that indicates the valid + * points by a true flag. + * \param[out] nbValidPoints Optional parameter. When different from nullptr contains the number of points that are + * valid according to the boolean mask or image size when `p_mask` is set to nullptr. + */ +template <> inline double vpImage::getSum(const vpImage *p_mask, unsigned int *nbValidPoints) const +{ + if ((height == 0) || (width == 0)) { + return 0.0; + } + double res = 0.0; + unsigned int nbPointsInMask = 0; + unsigned int size = height * width; + if (p_mask) { + if ((p_mask->getWidth() != width) || (p_mask->getHeight() != height)) { + throw(vpException(vpException::fatalError, "Cannot compute sum: image and mask size differ")); + } + for (unsigned int i = 0; i < size; ++i) { + if (p_mask->bitmap[i]) { + res += static_cast(bitmap[i].R) + static_cast(bitmap[i].G) + static_cast(bitmap[i].B); + ++nbPointsInMask; + } + } + } + else { + for (unsigned int i = 0; i < (height * width); ++i) { + res += static_cast(bitmap[i].R) + static_cast(bitmap[i].G) + static_cast(bitmap[i].B); + } + nbPointsInMask = size; + } + if (nbValidPoints) { + *nbValidPoints = nbPointsInMask; + } + return res; +} + +/** + * \relates vpImage + * \brief Compute the sum of image intensities. + * - For unary image types (unsigned char, float, double), compute the sum of image intensities. + * - For vpRGBa image type, compute the sum (R+G+B) of image intensities. + * - For vpRGBf image type, compute the sum (R+G+B) of image intensities. + * + * \param[in] p_mask Optional parameter. If not set to nullptr, pointer to a boolean mask that indicates the valid + * points by a true flag. + * \param[out] nbValidPoints Optional parameter. When different from nullptr contains the number of points that are + * valid according to the boolean mask or image size when `p_mask` is set to nullptr. + */ +template <> inline double vpImage::getSum(const vpImage *p_mask, unsigned int *nbValidPoints) const +{ + if ((height == 0) || (width == 0)) { + return 0.0; + } + double res = 0.0; + unsigned int nbPointsInMask = 0; + unsigned int size = height * width; + if (p_mask) { + if ((p_mask->getWidth() != width) || (p_mask->getHeight() != height)) { + throw(vpException(vpException::fatalError, "Cannot compute sum: image and mask size differ")); + } + for (unsigned int i = 0; i < size; ++i) { + if (p_mask->bitmap[i]) { + res += static_cast(bitmap[i].R) + static_cast(bitmap[i].G) + static_cast(bitmap[i].B); + ++nbPointsInMask; + } + } + } + else { + for (unsigned int i = 0; i < (height * width); ++i) { + res += static_cast(bitmap[i].R) + static_cast(bitmap[i].G) + static_cast(bitmap[i].B); + } + nbPointsInMask = size; + } + if (nbValidPoints) { + *nbValidPoints = nbPointsInMask; + } + return res; +} +#endif // DOXYGEN_SHOULD_SKIP_THIS + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImage_lut.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImage_lut.h new file mode 100644 index 00000000..0693bbf6 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImage_lut.h @@ -0,0 +1,343 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Image handling. + */ + +#ifndef VP_IMAGE_LUT_H +#define VP_IMAGE_LUT_H + +// Warning: this file shouldn't be included by the user. Internal usage only to reduce length of vpImage.h + +#if defined(VISP_HAVE_THREADS) +namespace +{ +struct vpImageLut_Param_t +{ + unsigned int m_start_index; + unsigned int m_end_index; + + unsigned char m_lut[256]; + unsigned char *m_bitmap; + + vpImageLut_Param_t(unsigned int start_index, unsigned int end_index, unsigned char *bitmap) + : m_start_index(start_index), m_end_index(end_index), m_lut(), m_bitmap(bitmap) + { } +}; + +void performLutThread(vpImageLut_Param_t *imageLut_param) +{ + unsigned int start_index = imageLut_param->m_start_index; + unsigned int end_index = imageLut_param->m_end_index; + + unsigned char *bitmap = imageLut_param->m_bitmap; + + unsigned char *ptrStart = bitmap + start_index; + unsigned char *ptrEnd = bitmap + end_index; + unsigned char *ptrCurrent = ptrStart; + + if (end_index - start_index >= 8) { + // Unroll loop version + for (; ptrCurrent <= ptrEnd - 8;) { + *ptrCurrent = imageLut_param->m_lut[*ptrCurrent]; + ++ptrCurrent; + + *ptrCurrent = imageLut_param->m_lut[*ptrCurrent]; + ++ptrCurrent; + + *ptrCurrent = imageLut_param->m_lut[*ptrCurrent]; + ++ptrCurrent; + + *ptrCurrent = imageLut_param->m_lut[*ptrCurrent]; + ++ptrCurrent; + + *ptrCurrent = imageLut_param->m_lut[*ptrCurrent]; + ++ptrCurrent; + + *ptrCurrent = imageLut_param->m_lut[*ptrCurrent]; + ++ptrCurrent; + + *ptrCurrent = imageLut_param->m_lut[*ptrCurrent]; + ++ptrCurrent; + + *ptrCurrent = imageLut_param->m_lut[*ptrCurrent]; + ++ptrCurrent; + } + } + + for (; ptrCurrent != ptrEnd; ++ptrCurrent) { + *ptrCurrent = imageLut_param->m_lut[*ptrCurrent]; + } +} + +struct vpImageLutRGBa_Param_t +{ + unsigned int m_start_index; + unsigned int m_end_index; + + VISP_NAMESPACE_ADDRESSING vpRGBa m_lut[256]; + unsigned char *m_bitmap; + + vpImageLutRGBa_Param_t(unsigned int start_index, unsigned int end_index, unsigned char *bitmap) + : m_start_index(start_index), m_end_index(end_index), m_lut(), m_bitmap(bitmap) + { } +}; + +void performLutRGBaThread(vpImageLutRGBa_Param_t *imageLut_param) +{ + unsigned int start_index = imageLut_param->m_start_index; + unsigned int end_index = imageLut_param->m_end_index; + + unsigned char *bitmap = imageLut_param->m_bitmap; + + unsigned char *ptrStart = bitmap + start_index * 4; + unsigned char *ptrEnd = bitmap + end_index * 4; + unsigned char *ptrCurrent = ptrStart; + + if (end_index - start_index >= 4 * 2) { + // Unroll loop version + for (; ptrCurrent <= ptrEnd - 4 * 2;) { + *ptrCurrent = imageLut_param->m_lut[*ptrCurrent].R; + ptrCurrent++; + *ptrCurrent = imageLut_param->m_lut[*ptrCurrent].G; + ptrCurrent++; + *ptrCurrent = imageLut_param->m_lut[*ptrCurrent].B; + ptrCurrent++; + *ptrCurrent = imageLut_param->m_lut[*ptrCurrent].A; + ptrCurrent++; + + *ptrCurrent = imageLut_param->m_lut[*ptrCurrent].R; + ptrCurrent++; + *ptrCurrent = imageLut_param->m_lut[*ptrCurrent].G; + ptrCurrent++; + *ptrCurrent = imageLut_param->m_lut[*ptrCurrent].B; + ptrCurrent++; + *ptrCurrent = imageLut_param->m_lut[*ptrCurrent].A; + ptrCurrent++; + } + } + + while (ptrCurrent != ptrEnd) { + *ptrCurrent = imageLut_param->m_lut[*ptrCurrent].R; + ptrCurrent++; + + *ptrCurrent = imageLut_param->m_lut[*ptrCurrent].G; + ptrCurrent++; + + *ptrCurrent = imageLut_param->m_lut[*ptrCurrent].B; + ptrCurrent++; + + *ptrCurrent = imageLut_param->m_lut[*ptrCurrent].A; + ptrCurrent++; + } +} +} // namespace +#endif + +/*! + \warning This generic method is not implemented. You should rather use the + instantiated methods for unsigned char and vpRGBa images. + + \sa vpImage::performLut(const unsigned char (&lut)[256], unsigned int nbThreads) + \sa vpImage::performLut(const vpRGBa (&lut)[256], unsigned int nbThreads) + +*/ +template void vpImage::performLut(const Type(&)[256], unsigned int) +{ + std::cerr << "Not implemented !" << std::endl; +} + +/*! + \relates vpImage + + Modify the intensities of a grayscale image using the look-up table passed + in parameter. + + \param lut : Look-up table (unsigned char array of size=256) which maps each + intensity to his new value. + \param nbThreads : Number of threads to use for the computation. +*/ +template <> inline void vpImage::performLut(const unsigned char(&lut)[256], unsigned int nbThreads) +{ + unsigned int size = getWidth() * getHeight(); + unsigned char *ptrStart = static_cast(bitmap); + unsigned char *ptrEnd = ptrStart + size; + unsigned char *ptrCurrent = ptrStart; + + bool use_single_thread = ((nbThreads == 0) || (nbThreads == 1)); +#if !defined(VISP_HAVE_THREADS) + use_single_thread = true; +#endif + + if ((!use_single_thread) && (getSize() <= nbThreads)) { + use_single_thread = true; + } + + if (use_single_thread) { + // Single thread + + while (ptrCurrent != ptrEnd) { + *ptrCurrent = lut[*ptrCurrent]; + ++ptrCurrent; + } + } + else { +#if defined(VISP_HAVE_THREADS) + // Multi-threads + std::vector threadpool; + std::vector imageLutParams; + + unsigned int image_size = getSize(); + unsigned int step = image_size / nbThreads; + unsigned int last_step = image_size - step * (nbThreads - 1); + + for (unsigned int index = 0; index < nbThreads; ++index) { + unsigned int start_index = index * step; + unsigned int end_index = (index + 1) * step; + + if (index == nbThreads - 1) { + end_index = start_index + last_step; + } + + vpImageLut_Param_t *imageLut_param = new vpImageLut_Param_t(start_index, end_index, bitmap); + memcpy(imageLut_param->m_lut, lut, 256 * sizeof(unsigned char)); + + imageLutParams.push_back(imageLut_param); + + // Start the threads + std::thread *imageLut_thread = new std::thread(&performLutThread, imageLut_param); + threadpool.push_back(imageLut_thread); + } + + for (size_t cpt = 0; cpt < threadpool.size(); ++cpt) { + // Wait until thread ends up + threadpool[cpt]->join(); + } + + // Delete + for (size_t cpt = 0; cpt < threadpool.size(); ++cpt) { + delete threadpool[cpt]; + } + + for (size_t cpt = 0; cpt < imageLutParams.size(); ++cpt) { + delete imageLutParams[cpt]; + } +#endif + } + } + + /*! + \relates vpImage + + Modify the intensities of a color image using the look-up table passed in + parameter. + + \param lut : Look-up table (vpRGBa array of size=256) which maps each + intensity to his new value. + \param nbThreads : Number of threads to use for the computation. + */ +template <> inline void vpImage::performLut(const vpRGBa(&lut)[256], unsigned int nbThreads) +{ + unsigned int size = getWidth() * getHeight(); + unsigned char *ptrStart = reinterpret_cast(bitmap); + unsigned char *ptrEnd = ptrStart + (size * 4); + unsigned char *ptrCurrent = ptrStart; + + bool use_single_thread = ((nbThreads == 0) || (nbThreads == 1)); +#if !defined(VISP_HAVE_THREADS) + use_single_thread = true; +#endif + + if ((!use_single_thread) && (getSize() <= nbThreads)) { + use_single_thread = true; + } + + if (use_single_thread) { + // Single thread + while (ptrCurrent != ptrEnd) { + *ptrCurrent = lut[*ptrCurrent].R; + ++ptrCurrent; + + *ptrCurrent = lut[*ptrCurrent].G; + ++ptrCurrent; + + *ptrCurrent = lut[*ptrCurrent].B; + ++ptrCurrent; + + *ptrCurrent = lut[*ptrCurrent].A; + ++ptrCurrent; + } + } + else { +#if defined(VISP_HAVE_THREADS) + // Multi-threads + std::vector threadpool; + std::vector imageLutParams; + + unsigned int image_size = getSize(); + unsigned int step = image_size / nbThreads; + unsigned int last_step = image_size - step * (nbThreads - 1); + + for (unsigned int index = 0; index < nbThreads; ++index) { + unsigned int start_index = index * step; + unsigned int end_index = (index + 1) * step; + + if (index == nbThreads - 1) { + end_index = start_index + last_step; + } + + vpImageLutRGBa_Param_t *imageLut_param = new vpImageLutRGBa_Param_t(start_index, end_index, reinterpret_cast(bitmap)); + memcpy(static_cast(imageLut_param->m_lut), lut, 256 * sizeof(vpRGBa)); + + imageLutParams.push_back(imageLut_param); + + // Start the threads + std::thread *imageLut_thread = new std::thread(&performLutRGBaThread, imageLut_param); + threadpool.push_back(imageLut_thread); + } + + for (size_t cpt = 0; cpt < threadpool.size(); ++cpt) { + // Wait until thread ends up + threadpool[cpt]->join(); + } + + // Delete + for (size_t cpt = 0; cpt < threadpool.size(); ++cpt) { + delete threadpool[cpt]; + } + + for (size_t cpt = 0; cpt < imageLutParams.size(); ++cpt) { + delete imageLutParams[cpt]; + } +#endif + } + } + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImage_operators.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImage_operators.h new file mode 100644 index 00000000..26187968 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpImage_operators.h @@ -0,0 +1,338 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Image handling. + */ + +#ifndef VP_IMAGE_OPERATOR_H +#define VP_IMAGE_OPERATOR_H + +// Warning: this file shouldn't be included by the user. Internal usage only to reduce length of vpImage.h + +template std::ostream &operator<<(std::ostream &s, const vpImage &I) +{ + if (I.bitmap == nullptr) { + return s; + } + + unsigned int i_height = I.getHeight(); + unsigned int i_width = I.getWidth(); + for (unsigned int i = 0; i < i_height; ++i) { + for (unsigned int j = 0; j < (i_width - 1); ++j) { + s << I[i][j] << " "; + } + + // We don't add " " after the last column element + s << I[i][i_width - 1]; + + // We don't add a \n character at the end of the last row line + if (i < (i_height - 1)) { + s << std::endl; + } + } + + return s; +} + +inline std::ostream &operator<<(std::ostream &s, const vpImage &I) +{ + if (I.bitmap == nullptr) { + return s; + } + + std::ios_base::fmtflags original_flags = s.flags(); + const unsigned int magic_3 = 3; + + unsigned int i_height = I.getHeight(); + unsigned int i_width = I.getWidth(); + for (unsigned int i = 0; i < i_height; ++i) { + for (unsigned int j = 0; j < (i_width - 1); ++j) { + s << std::setw(magic_3) << static_cast(I[i][j]) << " "; + } + + // We don't add " " after the last column element + s << std::setw(magic_3) << static_cast(I[i][I.getWidth() - 1]); + + // We don't add a \n character at the end of the last row line + if (i < (i_height - 1)) { + s << std::endl; + } + } + + s.flags(original_flags); // restore s to standard state + return s; +} + +inline std::ostream &operator<<(std::ostream &s, const vpImage &I) +{ + if (I.bitmap == nullptr) { + return s; + } + + std::ios_base::fmtflags original_flags = s.flags(); + const unsigned int magic_4 = 4; + + unsigned int i_height = I.getHeight(); + unsigned int i_width = I.getWidth(); + for (unsigned int i = 0; i < i_height; ++i) { + for (unsigned int j = 0; j < (i_width - 1); ++j) { + s << std::setw(magic_4) << static_cast(I[i][j]) << " "; + } + + // We don't add " " after the last column element + s << std::setw(magic_4) << static_cast(I[i][i_width - 1]); + + // We don't add a \n character at the end of the last row line + if (i < (i_height - 1)) { + s << std::endl; + } + } + + s.flags(original_flags); // restore s to standard state + return s; +} + +inline std::ostream &operator<<(std::ostream &s, const vpImage &I) +{ + if (I.bitmap == nullptr) { + return s; + } + + std::ios_base::fmtflags original_flags = s.flags(); + const unsigned int magic_9 = 9; + s.precision(magic_9); // http://en.cppreference.com/w/cpp/types/numeric_limits/max_digits10 + + unsigned int i_height = I.getHeight(); + unsigned int i_width = I.getWidth(); + for (unsigned int i = 0; i < i_height; ++i) { + for (unsigned int j = 0; j < (i_width - 1); ++j) { + s << I[i][j] << " "; + } + + // We don't add " " after the last column element + s << I[i][i_width - 1]; + + // We don't add a \n character at the end of the last row line + if (i < (i_height - 1)) { + s << std::endl; + } + } + + s.flags(original_flags); // restore s to standard state + return s; +} + +inline std::ostream &operator<<(std::ostream &s, const vpImage &I) +{ + if (I.bitmap == nullptr) { + return s; + } + + std::ios_base::fmtflags original_flags = s.flags(); + const unsigned int magic_17 = 17; + s.precision(magic_17); // http://en.cppreference.com/w/cpp/types/numeric_limits/max_digits10 + + unsigned int i_height = I.getHeight(); + unsigned int i_width = I.getWidth(); + for (unsigned int i = 0; i < i_height; ++i) { + for (unsigned int j = 0; j < (i_width - 1); ++j) { + s << I[i][j] << " "; + } + + // We don't add " " after the last column element + s << I[i][i_width - 1]; + + // We don't add a \n character at the end of the last row line + if (i < (i_height - 1)) { + s << std::endl; + } + } + + s.flags(original_flags); // restore s to standard state + return s; +} + +/*! + \brief Copy operator. + Resizes and copies the contents of the image `other`. + The pointer to the display remains unchanged. + + \param[in] other : Image to copy. + \exception When the display is initialised and the images have different sizes. +*/ +template vpImage &vpImage::operator=(const vpImage &other) +{ + if (display != nullptr) { + if ((height != other.height) || (width != other.width)) { + throw(vpException(vpException::dimensionError, + "Error in vpImage::operator=() where the display is initialised but the image size is different")); + } + } + resize(other.height, other.width); + memcpy(static_cast(bitmap), static_cast(other.bitmap), other.npixels * sizeof(Type)); + + return *this; +} + +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher +/*! + * Move operator. + * Moves the image pointers without deep copying image content. + * + * \param[in] other : Image to move. + * \exception vpException::dimensionError When the display is initialised and the images have different sizes. + * \exception vpException::fatalError When the display attached to the `other` image is initialized. + */ +template vpImage &vpImage::operator=(vpImage &&other) +{ + if (row != nullptr) { + delete[] row; + } + row = other.row; + if (bitmap != nullptr && hasOwnership) { + delete[] bitmap; + } + bitmap = other.bitmap; + + if (display != nullptr) { + if ((height != other.height) || (width != other.width)) { + throw(vpException(vpException::dimensionError, + "Error in vpImage::operator=(&) where the display is initialised but the image size is different")); + } + } + if (other.display != nullptr) { + throw(vpException(vpException::fatalError, + "Error in vpImage::operator=(&&) where the display of the image to move is initialised")); + } + height = other.height; + width = other.width; + npixels = other.npixels; + hasOwnership = other.hasOwnership; + + other.bitmap = nullptr; + other.display = nullptr; + other.npixels = 0; + other.width = 0; + other.height = 0; + other.row = nullptr; + other.hasOwnership = false; + + return *this; +} +#endif + +/*! + \brief = operator : Set all the element of the bitmap to a given value \e + v. \f$ A = v <=> A[i][j] = v \f$ + + \warning = must be defined for \f$ <\f$ Type \f$ > \f$ +*/ +template vpImage &vpImage::operator=(const Type &v) +{ + for (unsigned int i = 0; i < npixels; ++i) { + bitmap[i] = v; + } + + return *this; +} + +/*! + Compare two images. + + \return true if the images are the same, false otherwise. +*/ +template bool vpImage::operator==(const vpImage &I) const +{ + if (this->width != I.getWidth()) { + return false; + } + if (this->height != I.getHeight()) { + return false; + } + + /* + // printf("wxh: %dx%d bitmap: %p I.bitmap %p\n", width, height, bitmap, + // I.bitmap); + */ + for (unsigned int i = 0; i < npixels; ++i) { + if (bitmap[i] != I.bitmap[i]) { + /* + // std::cout << "differ for pixel " << i << " (" << i%this->height + // << ", " << i - i%this->height << ")" << std::endl; + */ + return false; + } + } + return true; +} +/*! + Compare two images. + + \return true if the images are different, false if they are the same. +*/ +template bool vpImage::operator!=(const vpImage &I) const { return !(*this == I); } + +/*! + Operation A - B (A is unchanged). + + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpImage A(288, 384); + vpImage B(288, 384); + vpImage C; + + A = 128; + B = 120; + + // operator-() : C = A - B + C = A - B; + + return 0; + } + \endcode + + \sa sub(const vpImage &, const vpImage &, vpImage &) to + avoid matrix allocation for each use. +*/ +template vpImage vpImage::operator-(const vpImage &B) const +{ + vpImage C; + sub(*this, B, C); + return C; +} + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpIoException.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpIoException.h new file mode 100644 index 00000000..13ae69bf --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpIoException.h @@ -0,0 +1,93 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Exceptions that can be emitted by the vpIo class and its derivatives. + */ + +/*! + * \file vpIoException.h + * \brief Error that can be emitted by the vpIoTools class and its derivatives. +*/ + +#ifndef VP_IO_EXCEPTION_H +#define VP_IO_EXCEPTION_H + +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpIoException + * \ingroup group_core_debug + * \brief Error that can be emitted by the vpIoTools class and its derivatives. +*/ +class VISP_EXPORT vpIoException : public vpException +{ +public: + /*! + * \brief Lists the possible error than can be emitted while calling + * vpIo member. + */ + enum error + { + invalidDirectoryName, //!< Directory name is invalid. + cantCreateDirectory, //!< Unable to create a directory. + cantGetUserName, //!< User name is not available. + cantGetenv //!< Cannot get environment variable value. + }; + +public: + /*! + * Constructor. + */ + vpIoException(int id, const char *format, ...) + { + this->code = id; + va_list args; + va_start(args, format); + setMessage(format, args); + va_end(args); + } + + /*! + * Constructor. + */ + vpIoException(int id, const std::string &msg) : vpException(id, msg) { } + + /*! + * Constructor. + */ + VP_EXPLICIT vpIoException(int id) : vpException(id) { } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpIoTools.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpIoTools.h new file mode 100644 index 00000000..3687c4c3 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpIoTools.h @@ -0,0 +1,708 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Directory management. + */ + +/*! + * \file vpIoTools.h + * \brief File and directories basic tools. + */ + +#ifndef VP_IO_TOOLS_H +#define VP_IO_TOOLS_H + +#include + +#include +#include +#include //for uint32_t related types ; works also with >= VS2010 / _MSC_VER >= 1600 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#if VISP_CXX_STANDARD > VISP_CXX_STANDARD_98 + +namespace visp +{ +#ifndef DOXYGEN_SHOULD_SKIP_THIS +// https://github.com/BinomialLLC/basis_universal/blob/ad9386a4a1cf2a248f7bbd45f543a7448db15267/encoder/basisu_miniz.h#L665 +static inline unsigned long vp_mz_crc32(unsigned long crc, const unsigned char *ptr, size_t buf_len) +{ + static const unsigned int s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; + unsigned int crcu32 = static_cast(crc); + if (!ptr) return 0; + crcu32 = ~crcu32; + while (buf_len--) { + unsigned char b = *ptr++; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; + } + return ~crcu32; +} +#endif // DOXYGEN_SHOULD_SKIP_THIS + +#if defined(VISP_HAVE_MINIZ) && defined(VISP_HAVE_WORKING_REGEX) +/** + * \ingroup group_core_bridges + * \brief Helpers to convert NPY/NPZ format to/from ViSP format. + * + *

Tutorials & Examples

+ + Tutorials
+ If you are interested in converting NPY/NPZ format to/from ViSP format, you may have a look at:
+ + - \ref tutorial-npz + */ +namespace cnpy +{ +// Copyright (C) 2011 Carl Rogers +// Released under MIT License +// license available in LICENSE file, or at http://www.opensource.org/licenses/mit-license.php +struct NpyArray +{ + NpyArray(const std::vector &_shape, size_t _word_size, bool _fortran_order, char _data_type) : + shape(_shape), word_size(_word_size), fortran_order(_fortran_order), data_type(_data_type) + { + num_vals = 1; + for (size_t i = 0; i < shape.size(); ++i) num_vals *= shape[i]; + data_holder = std::shared_ptr >( + new std::vector(num_vals * word_size)); + } + + NpyArray() : shape(0), word_size(0), fortran_order(0), num_vals(0), data_type(0) { } + + template + T *data() + { + return reinterpret_cast(&(*data_holder)[0]); + } + + template + const T *data() const + { + return reinterpret_cast(&(*data_holder)[0]); + } + + template + std::vector as_vec() const + { + const T *p = data(); + if (data_type == 'U') { + if (!std::is_same::value) { + throw std::runtime_error("NpyArray.as_vec(): datatype contains string data but as_vec() is not templated as "); + } + return std::vector(p, p+(num_vals*word_size)); + } + else { + return std::vector(p, p+num_vals); + } + } + + std::vector as_utf8_string_vec() const + { + if (data_type != 'U') { + throw std::runtime_error("NpyArray.as_utf8_string_vec(): not a string data"); + } + + std::vector vec_string; + vec_string.reserve(num_vals); + + for (size_t i = 0; i < num_vals; i++) { + std::string str; + + for (size_t idx = i*word_size; idx < (i+1)*word_size; idx += 4) { + if ((*data_holder)[idx] == 0) { + // \0 char + break; + } + str += (*data_holder)[idx]; + } + + vec_string.push_back(str); + } + + return vec_string; + } + + size_t num_bytes() const + { + return data_holder->size(); + } + + std::shared_ptr > data_holder; + std::vector shape; + size_t word_size; + bool fortran_order; + size_t num_vals; + char data_type; +}; + +using npz_t = std::map; +VISP_EXPORT npz_t npz_load(const std::string &fname); +VISP_EXPORT char BigEndianTest(); +VISP_EXPORT char map_type(const std::type_info &t); +template std::vector create_npy_header(const std::vector &shape); +VISP_EXPORT void parse_npy_header(FILE *fp, size_t &word_size, std::vector &shape, bool &fortran_order, bool &little_endian, char &data_type); +VISP_EXPORT void parse_npy_header(unsigned char *buffer, size_t &word_size, std::vector &shape, bool &fortran_order, bool &little_endian, char &data_type); +VISP_EXPORT void parse_zip_footer(FILE *fp, uint16_t &nrecs, size_t &global_header_size, size_t &global_header_offset); +VISP_EXPORT NpyArray npz_load(const std::string &fname, const std::string &varname); +VISP_EXPORT NpyArray npy_load(const std::string &fname); +// Dedicated functions for saving std::string data +VISP_EXPORT void npz_save(const std::string &zipname, std::string fname, const std::vector &data_vec, const std::vector &shape, const std::string &mode = "w"); +VISP_EXPORT void npz_save(const std::string &zipname, const std::string &fname, const std::string &data, const std::string &mode = "w"); + +template std::vector &operator+=(std::vector &lhs, const T rhs) +{ + //write in little endian + for (size_t byte = 0; byte < sizeof(T); ++byte) { + char val = *((char *)&rhs+byte); + lhs.push_back(val); + } + return lhs; +} + +template<> inline std::vector &operator+=(std::vector &lhs, const std::string rhs) +{ + lhs.insert(lhs.end(), rhs.begin(), rhs.end()); + return lhs; +} + +template<> inline std::vector &operator+=(std::vector &lhs, const char *rhs) +{ + //write in little endian + size_t len = strlen(rhs); + lhs.reserve(len); + for (size_t byte = 0; byte < len; ++byte) { + lhs.push_back(rhs[byte]); + } + return lhs; +} + +/*! + Save an array of data (\p data) into the \p fname npy file. This function is similar to the +
numpy.save function. + \param[in] fname : Path to the npy file. + \param[in] data : Pointer to an array of basic datatype (int, float, double, std::complex, ...). + \param[in] shape : Shape of the array, e.g. Nz x Ny x Nx. + \param[in] mode : Writing mode, i.e. overwrite (w) or append (a) to the file. + \warning This function should also work on big-endian platform, without guarantee since it has not been tested extensively. + \note Original library: cnpy with MIT license. + */ +template void npy_save(const std::string &fname, const T *data, const std::vector &shape, const std::string &mode = "w") +{ + FILE *fp = NULL; + std::vector true_data_shape; //if appending, the shape of existing + new data + + if (mode == "a") fp = fopen(fname.c_str(), "r+b"); + + if (fp) { + //file exists. we need to append to it. read the header, modify the array size + size_t word_size; + bool fortran_order, little_endian; + char data_type = 'i'; + parse_npy_header(fp, word_size, true_data_shape, fortran_order, little_endian, data_type); + assert(!fortran_order); + + if (word_size != sizeof(T)) { + std::cerr << "libnpy error: " << fname << " has word size " << word_size << " but npy_save appending data sized " << sizeof(T) << "\n"; + assert(word_size == sizeof(T)); + } + if (true_data_shape.size() != shape.size()) { + std::cerr << "libnpy error: npy_save attempting to append misdimensioned data to " << fname << "\n"; + assert(true_data_shape.size() != shape.size()); + } + + for (size_t i = 1; i < shape.size(); ++i) { + if (shape[i] != true_data_shape[i]) { + std::cerr << "libnpy error: npy_save attempting to append misshaped data to " << fname << "\n"; + assert(shape[i] == true_data_shape[i]); + } + } + true_data_shape[0] += shape[0]; + } + else { + fp = fopen(fname.c_str(), "wb"); + true_data_shape = shape; + } + + std::vector header = create_npy_header(true_data_shape); + // https://github.com/rogersce/cnpy/pull/58/files + size_t nels = std::accumulate(shape.begin(), shape.end(), static_cast(1), std::multiplies()); + + fseek(fp, 0, SEEK_SET); + fwrite(&header[0], sizeof(char), header.size(), fp); + fseek(fp, 0, SEEK_END); + if (data != nullptr) { + fwrite(&data[0], sizeof(T), nels, fp); + } + fclose(fp); +} + +/*! + Save the specified \p fname array of data (\p data) into the \p zipname npz file. This function is similar to the + numpy.savez function. + \param[in] zipname : Path to the npz file. + \param[in] fname : Identifier for the corresponding array of data. + \param[in] data : Pointer to an array of basic datatype (int, float, double, std::complex, ...). + \param[in] shape : Shape of the array, e.g. Nz x Ny x Nx. + \param[in] mode : Writing mode, i.e. overwrite (w) or append (a) to the file. + \warning This function should also work on big-endian platform, without guarantee since it has not been tested extensively. + \note Original library: cnpy with MIT license. + + \sa To see how to use it, you may have a look at \ref tutorial-npz + */ +template void npz_save(const std::string &zipname, std::string fname, const T *data, const std::vector &shape, const std::string &mode = "w") +{ + //first, append a .npy to the fname + fname += ".npy"; + + //now, on with the show + FILE *fp = NULL; + uint16_t nrecs = 0; + size_t global_header_offset = 0; + std::vector global_header; + + if (mode == "a") fp = fopen(zipname.c_str(), "r+b"); + + if (fp) { + //zip file exists. we need to add a new npy file to it. + //first read the footer. this gives us the offset and size of the global header + //then read and store the global header. + //below, we will write the the new data at the start of the global header then append the global header and footer below it + size_t global_header_size; + parse_zip_footer(fp, nrecs, global_header_size, global_header_offset); + fseek(fp, static_cast(global_header_offset), SEEK_SET); + global_header.resize(global_header_size); + size_t res = fread(&global_header[0], sizeof(char), global_header_size, fp); + if (res != global_header_size) { + throw std::runtime_error("npz_save: header read error while adding to existing zip"); + } + fseek(fp, static_cast(global_header_offset), SEEK_SET); + } + else { + fp = fopen(zipname.c_str(), "wb"); + } + + std::vector npy_header = create_npy_header(shape); + + // https://github.com/rogersce/cnpy/pull/58/files + size_t nels = std::accumulate(shape.begin(), shape.end(), static_cast(1), std::multiplies()); + size_t nbytes = nels*sizeof(T) + npy_header.size(); + + //get the CRC of the data to be added + uint32_t crc = vp_mz_crc32(0L, (uint8_t *)&npy_header[0], npy_header.size()); + if (nels > 0) { + crc = vp_mz_crc32(crc, (uint8_t *)data, nels*sizeof(T)); + } + + //build the local header + std::vector local_header; + local_header += "PK"; //first part of sig +#ifdef VISP_BIG_ENDIAN + local_header += vpEndian::swap16bits(static_cast(0x0403)); //second part of sig + local_header += vpEndian::swap16bits(static_cast(20)); //min version to extract + local_header += vpEndian::swap16bits(static_cast(0)); //general purpose bit flag + local_header += vpEndian::swap16bits(static_cast(0)); //compression method + local_header += vpEndian::swap16bits(static_cast(0)); //file last mod time + local_header += vpEndian::swap16bits(static_cast(0)); //file last mod date + local_header += vpEndian::swap32bits(static_cast(crc)); //crc + local_header += vpEndian::swap32bits(static_cast(nbytes)); //compressed size + local_header += vpEndian::swap32bits(static_cast(nbytes)); //uncompressed size + local_header += vpEndian::swap16bits(static_cast(fname.size())); //fname length + local_header += vpEndian::swap16bits(static_cast(0)); //extra field length +#else + local_header += static_cast(0x0403); //second part of sig + local_header += static_cast(20); //min version to extract + local_header += static_cast(0); //general purpose bit flag + local_header += static_cast(0); //compression method + local_header += static_cast(0); //file last mod time + local_header += static_cast(0); //file last mod date + local_header += static_cast(crc); //crc + local_header += static_cast(nbytes); //compressed size + local_header += static_cast(nbytes); //uncompressed size + local_header += static_cast(fname.size()); //fname length + local_header += static_cast(0); //extra field length +#endif + local_header += fname; + + //build global header + global_header += "PK"; //first part of sig +#ifdef VISP_BIG_ENDIAN + global_header += vpEndian::swap16bits(static_cast(0x0201)); //second part of sig + global_header += vpEndian::swap16bits(static_cast(20)); //version made by + global_header.insert(global_header.end(), local_header.begin()+4, local_header.begin()+30); + global_header += static_cast(0); //file comment length + global_header += static_cast(0); //disk number where file starts + global_header += static_cast(0); //internal file attributes + global_header += static_cast(0); //external file attributes + global_header += vpEndian::swap32bits(static_cast(global_header_offset)); //relative offset of local file header, since it begins where the global header used to begin +#else + global_header += static_cast(0x0201); //second part of sig + global_header += static_cast(20); //version made by + global_header.insert(global_header.end(), local_header.begin()+4, local_header.begin()+30); + global_header += static_cast(0); //file comment length + global_header += static_cast(0); //disk number where file starts + global_header += static_cast(0); //internal file attributes + global_header += static_cast(0); //external file attributes + global_header += static_cast(global_header_offset); //relative offset of local file header, since it begins where the global header used to begin +#endif + global_header += fname; + + //build footer + std::vector footer; + footer += "PK"; //first part of sig +#ifdef VISP_BIG_ENDIAN + footer += vpEndian::swap16bits(static_cast(0x0605)); //second part of sig + footer += static_cast(0); //number of this disk + footer += static_cast(0); //disk where footer starts + footer += vpEndian::swap16bits(static_cast(nrecs+1)); //number of records on this disk + footer += vpEndian::swap16bits(static_cast(nrecs+1)); //total number of records + footer += vpEndian::swap32bits(static_cast(global_header.size())); //nbytes of global headers + footer += vpEndian::swap32bits(static_cast(global_header_offset + nbytes + local_header.size())); //offset of start of global headers, since global header now starts after newly written array +#else + footer += static_cast(0x0605); //second part of sig + footer += static_cast(0); //number of this disk + footer += static_cast(0); //disk where footer starts + footer += static_cast(nrecs+1); //number of records on this disk + footer += static_cast(nrecs+1); //total number of records + footer += static_cast(global_header.size()); //nbytes of global headers + footer += static_cast(global_header_offset + nbytes + local_header.size()); //offset of start of global headers, since global header now starts after newly written array +#endif + footer += static_cast(0); //zip file comment length + + //write everything + fwrite(&local_header[0], sizeof(char), local_header.size(), fp); + fwrite(&npy_header[0], sizeof(char), npy_header.size(), fp); + if (data != nullptr) { + fwrite(&data[0], sizeof(T), nels, fp); + } + fwrite(&global_header[0], sizeof(char), global_header.size(), fp); + fwrite(&footer[0], sizeof(char), footer.size(), fp); + fclose(fp); +} + +/*! + Save the specified 1-D array of data (\p data) into the \p fname npz file. This function is similar to the + numpy.save function. + \param[in] fname : Path to the npy file. + \param[in] data : Pointer to a 1-D array of basic datatype (int, float, double, std::complex, ...). + \param[in] mode : Writing mode, i.e. overwrite (w) or append (a) to the file. + \warning This function should also work on big-endian platform, without guarantee since it has not been tested extensively. + \note Original library: cnpy with MIT license. + */ +template void npy_save(const std::string &fname, const std::vector &data, const std::string &mode = "w") +{ + std::vector shape; + shape.push_back(data.size()); + npy_save(fname, &data[0], shape, mode); +} + +/*! + Save the specified \p fname 1-D array of data (\p data) into the \p zipname npz file. This function is similar to the + numpy.savez function. + \param[in] zipname : Path to the npz file. + \param[in] fname : Identifier for the corresponding array of data. + \param[in] data : Pointer to a 1-D array of basic datatype (int, float, double, std::complex, ...). + \param[in] mode : Writing mode, i.e. overwrite (w) or append (a) to the file. + \warning This function should also work on big-endian platform, without guarantee since it has not been tested extensively. + \note Original library: cnpy with MIT license. + + \sa To see how to use it, you may have a look at \ref tutorial-npz + */ +template void npz_save(const std::string &zipname, const std::string &fname, const std::vector &data, const std::string &mode = "w") +{ + std::vector shape; + shape.push_back(data.size()); + npz_save(zipname, fname, &data[0], shape, mode); +} + +template std::vector create_npy_header(const std::vector &shape) +{ + std::vector dict; + dict += "{'descr': '"; + dict += BigEndianTest(); + dict += map_type(typeid(T)); + dict += std::to_string(sizeof(T)); + dict += "', 'fortran_order': False, 'shape': ("; + dict += std::to_string(shape[0]); + for (size_t i = 1; i < shape.size(); ++i) { + dict += ", "; + dict += std::to_string(shape[i]); + } + if (shape.size() == 1) dict += ","; + dict += "), }"; + //pad with spaces so that preamble+dict is modulo 16 bytes. preamble is 10 bytes. dict needs to end with \n + int remainder = 16 - (10 + dict.size()) % 16; + dict.insert(dict.end(), remainder, ' '); + dict.back() = '\n'; + + std::vector header; + header += static_cast(0x93); + header += "NUMPY"; + header += static_cast(0x01); //major version of numpy format + header += static_cast(0x00); //minor version of numpy format +#ifdef VISP_BIG_ENDIAN + header += vpEndian::swap16bits(static_cast(dict.size())); +#else + header += static_cast(dict.size()); +#endif + header.insert(header.end(), dict.begin(), dict.end()); + + return header; +} + +} // namespace cnpy +#endif +} // namespace VISP_NAMESPACE_NAME +#endif + +/*! + * \class vpIoTools + * \ingroup group_core_files_io + * \brief File and directories basic tools. + * + * The example below shows how to manipulate the functions of this + * class to create first a directory which name corresponds to the user + * name and then create a file in this directory. + * + * \code + * #include + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * std::string username; + * vpIoTools::getUserName(username); + * + * // Test if a username directory exist. If no try to create it + * if (vpIoTools::checkDirectory(username) == false) { + * try { + * // Create a directory with name "username" + * vpIoTools::makeDirectory(username); + * } + * catch (...) { + * std::cout << "Cannot create " << username << " directory" << std::endl; + * return EXIT_FAILURE; + * } + * } + * // Create a empty filename with name "username/file.txt" + * std::ofstream f; + * std::string filename = username + "/file.txt"; + * // Under Windows converts the filename string into "username\\file.txt" + * filename = vpIoTools::path(filename); + * std::cout << "Create: " << filename << std::endl; + * f.open(filename.c_str()); + * f.close(); + * + * // Rename the file + * std::string newfilename = username + "/newfile.txt"; + * std::cout << "Rename: " << filename << " in: " << newfilename << std::endl; + * if (vpIoTools::rename(filename, newfilename) == false) + * std::cout << "Unable to rename: " << filename << std::endl; + * + * // Remove the file + * std::cout << "Remove: " << newfilename << std::endl; + * if (vpIoTools::remove(newfilename) == false) + * std::cout << "Unable to remove: " << newfilename << std::endl; + * + * return EXIT_SUCCESS; + * } + * \endcode + * + * The example below shows how to read a configuration file and how to create a name + * for experiment files. We assume the following file "/home/user/demo/config.txt" : + * \code + * expNumber 2 + * save 0 + * lambda 0.4 + * use2D 0 + * use3D 1 + * \endcode + * + * \code + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * // reading configuration file + * vpIoTools::loadConfigFile("/home/user/demo/config.txt"); + * std::string nExp;vpIoTools::readConfigVar("expNumber", nExp); // nExp <- "2" + * double lambda;vpIoTools::readConfigVar("lambda", lambda); // lambda <- 0.4 + * bool use2D;vpIoTools::readConfigVar("use2D", use2D); // use2D <- false + * bool use3D;vpIoTools::readConfigVar("use3D", use3D); // use3D <- true + * bool doSave;vpIoTools::readConfigVar("save", doSave); // doSave <- false + * + * // creating name for experiment files + * vpIoTools::setBaseDir("/home/user/data"); + * // full name <- "/home/user/data/exp2" + * vpIoTools::setBaseName("exp" + nExp); + * // full name <- "/home/user/data/exp2" since use2D==false + * vpIoTools::addNameElement("2D", use2D); + * // full name <- "/home/user/data/exp2_3D" + * vpIoTools::addNameElement("3D", use3D); + * // full name <- "/home/user/data/exp2_3D_lambda0.4" + * vpIoTools::addNameElement("lambda", lambda); + * + * // Saving file.Would copy "/home/user/demo/config.txt" to + * // "/home/user/data/exp2_3D_lambda0.4_config.txt" if doSave was true + * vpIoTools::saveConfigFile(doSave); + * // create sub directory + * vpIoTools::createBaseNamePath(); // creates "/home/user/data/exp2_3D_lambda0.4/" + * } + * \endcode + */ +BEGIN_VISP_NAMESPACE +class VISP_EXPORT vpIoTools +{ +public: + static const std::string &getBuildInformation(); + static std::string getTempPath(); + static void getUserName(std::string &username); + static std::string getUserName(); + static std::string getenv(const std::string &env); + static std::string getViSPImagesDataPath(); + static void getVersion(const std::string &version, unsigned int &major, unsigned int &minor, unsigned int &patch); + static bool checkDirectory(const std::string &dirname); + static bool checkFifo(const std::string &filename); + static bool checkFilename(const std::string &filename); + static bool copy(const std::string &src, const std::string &dst); + static std::string formatString(const std::string &name, unsigned int val); + + static void makeDirectory(const std::string &dirname); + static void makeFifo(const std::string &dirname); + static std::string makeTempDirectory(const std::string &dirname); + static std::string path(const std::string &pathname); + + static bool remove(const std::string &filename); + static bool rename(const std::string &oldfilename, const std::string &newfilename); + + /*! + * Define the directory separator character, backslash ('\') for windows + * platform or slash ('/') otherwise. + */ + static const char separator; + + static std::string toUpperCase(const std::string &input); + static std::string toLowerCase(const std::string &input); + + static std::string getAbsolutePathname(const std::string &pathname); + static std::string getFileExtension(const std::string &pathname, bool checkFile = false); + static long getIndex(const std::string &filename, const std::string &format); + static std::string getName(const std::string &pathname); + static std::string getNameWE(const std::string &pathname); + static std::string getParent(const std::string &pathname); + static std::string createFilePath(const std::string &parent, const std::string &child); + static bool isAbsolutePathname(const std::string &pathname); + static bool isSamePathname(const std::string &pathname1, const std::string &pathname2); + static std::pair splitDrive(const std::string &pathname); + static std::vector splitChain(const std::string &chain, const std::string &sep); + static std::vector getDirFiles(const std::string &dirname); + + /*! + * @name Configuration file parsing + */ + //@{ + // read configuration file + static bool loadConfigFile(const std::string &confFile); + static bool readConfigVar(const std::string &var, float &value); + static bool readConfigVar(const std::string &var, double &value); + static bool readConfigVar(const std::string &var, int &value); + static bool readConfigVar(const std::string &var, unsigned int &value); + static bool readConfigVar(const std::string &var, bool &value); + static bool readConfigVar(const std::string &var, std::string &value); + static bool readConfigVar(const std::string &var, vpColor &value); + static bool readConfigVar(const std::string &var, vpArray2D &value, const unsigned int &nCols = 0, + const unsigned int &nRows = 0); + + // construct experiment filename & path + static void setBaseName(const std::string &s); + static void setBaseDir(const std::string &dir); + static void addNameElement(const std::string &strTrue, const bool &cond = true, const std::string &strFalse = ""); + static void addNameElement(const std::string &strTrue, const double &val); + static std::string getBaseName(); + static std::string getFullName(); + + // write files + static void saveConfigFile(const bool &actuallySave = true); + static void createBaseNamePath(const bool &empty = false); + //@} + + static void readBinaryValueLE(std::ifstream &file, int16_t &short_value); + static void readBinaryValueLE(std::ifstream &file, uint16_t &ushort_value); + static void readBinaryValueLE(std::ifstream &file, int32_t &int_value); + static void readBinaryValueLE(std::ifstream &file, uint32_t &int_value); + static void readBinaryValueLE(std::ifstream &file, float &float_value); + static void readBinaryValueLE(std::ifstream &file, double &double_value); + + static void writeBinaryValueLE(std::ofstream &file, const int16_t short_value); + static void writeBinaryValueLE(std::ofstream &file, const uint16_t ushort_value); + static void writeBinaryValueLE(std::ofstream &file, const int32_t int_value); + static void writeBinaryValueLE(std::ofstream &file, const uint32_t int_value); + static void writeBinaryValueLE(std::ofstream &file, float float_value); + static void writeBinaryValueLE(std::ofstream &file, double double_value); + + static bool parseBoolean(std::string input); + static std::string trim(std::string s); + +protected: + static std::string baseName; + static std::string baseDir; + static std::string configFile; + static std::vector configVars; + static std::vector configValues; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + static int mkdir_p(const std::string &path, int mode); +#endif +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpJsonParsing.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpJsonParsing.h new file mode 100644 index 00000000..0675c7e9 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpJsonParsing.h @@ -0,0 +1,112 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * JSON parsing helpers. + */ + +#ifndef VP_JSON_PARSING_H +#define VP_JSON_PARSING_H + +#include + +#ifdef VISP_HAVE_NLOHMANN_JSON +#include VISP_NLOHMANN_JSON(json.hpp) + +BEGIN_VISP_NAMESPACE +/*! + Parse the flag values defined in a JSON object. + if the flags are defined as an int, then this is int is directly returned. + If it is defined as a combination of options (defined from an enumeration E) then the logical or of theses enum values is returned. + Beware that invalid values may be defined in the JSON object: the int value may be invalid, or the parsing of enum values may fail. + + \param j : the JSON object to parse + + \return an int, corresponding to the combination of boolean flags + +*/ +template +int flagsFromJSON(const nlohmann::json &j) +{ + int flags = 0; + if (j.is_array()) { + flags = 0; + for (const auto &v : j) { + E value = v.get(); // If a value is incorrect, this will default to the first value of the enum + flags |= value; + } + } + else if (j.is_number_integer()) { + flags = j.get(); + } + return flags; +} + +/*! + Serialize flag values as a json array. + \param flags The value to serialize. + \param options The possible values that can be contained in flags. A flag i is set if flags & options[i] != 0. + + \return A json object (an array) that contains the different flags of the variable flags. + +*/ +template +nlohmann::json flagsToJSON(const unsigned int flags, const std::vector &options) +{ + nlohmann::json j = nlohmann::json::array(); + for (const E option : options) { + if (flags & option) { + j.push_back(option); + } + } + return j; +} + +template +bool convertFromTypeAndBuildFrom(const nlohmann::json &, T &) +{ + return false; +} + +template +bool convertFromTypeAndBuildFrom(const nlohmann::json &j, T &t) +{ + if (j["type"] == O::jsonTypeName) { + O other; + from_json(j, other); + t.buildFrom(other); + return true; + } + else { + return convertFromTypeAndBuildFrom(j, t); + } +} +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpKalmanFilter.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpKalmanFilter.h new file mode 100644 index 00000000..b382743d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpKalmanFilter.h @@ -0,0 +1,225 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Kalman filtering. + */ + +/*! + \file vpKalmanFilter.h + \brief Generic kalman filtering implementation +*/ + +#ifndef VP_KALMAN_FILTER_H +#define VP_KALMAN_FILTER_H + +#include +#include + +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpKalmanFilter + \ingroup group_core_kalman + \brief This class provides a generic Kalman filtering algorithm along with + some specific state model (constant velocity, constant acceleration) + which are implemented in the vpLinearKalmanFilterInstantiation class. + + The state evolution equation is given by: + \f[ + {\bf x}_k= {\bf F}_{k-1} {\bf x}_{k-1} + {\bf w}_{k-1} \\ + \f] + where \f${\bf x}_{k}\f$ is the unknown state at iteration \f$k\f$. + + The measurement equation is given by: + \f[ + {\bf z}_k = {\bf H} {\bf x}_k + {\bf r}_k + \f] + where \f${\bf z}_{k}\f$ is the measure (also named observation) at iteration + \f$k\f$. + + The predicted state is obtained by: + \f[ + {\bf x}_{k|k-1} = {\bf F}_{k-1} {\bf x}_{k-1\mid k-1} + \f] + \f[ + {\bf P}_{k \mid k-1} = {\bf F}_{k-1} {\bf P}_{k-1 \mid k-1} {\bf F}^T_{k-1} + + {\bf Q}_k \f] + where +
    +
  • \f$ {\bf x}_{k|k-1}\f$ is the prediction of the state, +
  • \f$ {\bf P}_{k \mid k-1}\f$ is the state prediction covariance matrix. +
+ Filtering equation are: + \f[ + {\bf W}_k = {\bf P}_{k \mid k-1} {\bf H}^T + \left[ {\bf H P}_{k \mid k-1} {\bf H}^T + {\bf R}_k \right]^{-1} + \f] + \f[ + {\bf x}_{k \mid k} = {\bf x}_{k \mid k-1} + {\bf W}_k \left[ {\bf z}_k - + {\bf H x}_{k \mid k-1} \right] + \f] + \f[ + {\bf P}_{k \mid k} = \left({\bf I - W}_k {\bf H} \right) {\bf P}_{k \mid + k-1} \f] + + where \f$ {\bf W}_k \f$ is the filter gain. + + Notice that there is a recursion for the inverse covariance + \f[ + {\bf P}_{k \mid k}^{-1}= {\bf P}_{k \mid k-1}^{-1} + {\bf H}^T {\bf + R}^{-1} {\bf H} + \f] + where \f${\bf P}_{k \mid k}^{-1}\f$ is the inverse of the covariance matrix. + + ViSP provides different state evolution models implemented in the + vpLinearKalmanFilterInstantiation class. +*/ +class VISP_EXPORT vpKalmanFilter +{ +protected: + // bool init_done ; + + //! Filter step or iteration. When set to zero, initialize the filter. + long iter; + + //! Size of the state vector \f${\bf x}_k\f$. + unsigned int size_state; + //! Size of the measure vector \f${\bf z}_k\f$. + unsigned int size_measure; + //! Number of signal to filter. + unsigned int nsignal; + + //! When set to true, print the content of internal variables during + //! filtering() and prediction(). + bool verbose_mode; + +public: + vpKalmanFilter(); + VP_EXPLICIT vpKalmanFilter(unsigned int n_signal); + vpKalmanFilter(unsigned int size_state, unsigned int size_measure, unsigned int n_signal); + /*! Destructor that does noting. */ +#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98) + vpKalmanFilter(const vpKalmanFilter &) = default; + virtual ~vpKalmanFilter() = default; +#else + virtual ~vpKalmanFilter() { } +#endif + /*! + Set the number of signal to filter. + */ + void setNumberOfSignal(unsigned int n_signal) { this->nsignal = n_signal; } + + // int init() { return init_done ; } + void init(unsigned int size_state, unsigned int size_measure, unsigned int n_signal); + void prediction(); + void filtering(const vpColVector &z); + /*! + Return the size of the state vector \f${\bf x}_{(k)}\f$ for one signal. + */ + unsigned int getStateSize() { return size_state; } + /*! + Return the size of the measure vector \f${\bf z}_{(k)}\f$ for one signal. + */ + unsigned int getMeasureSize() { return size_measure; } + /*! + Return the number of signal to filter. + */ + unsigned int getNumberOfSignal() { return nsignal; } + /*! + Return the iteration number. + */ + long getIteration() { return iter; } + +#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98) + vpKalmanFilter &operator=(const vpKalmanFilter &) = default; +#endif + /*! + Sets the verbose mode. + \param on : If true, activates the verbose mode which consists in printing + the Kalman filter internal values. + */ + void verbose(bool on) { verbose_mode = on; } + +public: + /*! + The updated state estimate \f${\bf x}_{k \mid k} \f$ where + \f${\bf x}_{k \mid k} = {\bf x}_{k \mid k-1} + {\bf W}_k + \left[ {\bf z}_k - {\bf H x}_{k \mid k-1} \right]\f$. + */ + vpColVector Xest; + /*! + The predicted state \f${\bf x}_{k \mid k-1} \f$ where + \f$ {\bf x}_{k|k-1} = {\bf F}_{k-1} {\bf x}_{k-1\mid k-1}\f$. + */ + vpColVector Xpre; + //! Transition matrix \f${\bf F}\f$ that describes the evolution of the + //! state. + vpMatrix F; + + //! Matrix \f${\bf H}\f$ that describes the evolution of the measurements. + vpMatrix H; + + //! Measurement noise covariance matrix \f${\bf R}\f$. + vpMatrix R; + //! Process noise covariance matrix \f${\bf Q}\f$. + vpMatrix Q; + /*! Sampling time \f$\Delta t\f$ in second between two successive + iterations. Only used in some specific state models implemented + in vpLinearKalmanFilterInstantiation.*/ + double dt; + /*! + The state prediction covariance \f${\bf P}_{k \mid k-1} \f$ where + \f$ {\bf P}_{k \mid k-1} = {\bf F}_{k-1} {\bf P}_{k-1 \mid k-1} {\bf + F}^T_{k-1} + + {\bf Q}_k\f$. + */ + vpMatrix Ppre; + + /*! + The updated covariance of the state \f${\bf P}_{k \mid k}\f$ + where \f${\bf P}_{k \mid k} = \left({\bf I - W}_k {\bf H} + \right) {\bf P}_{k \mid k-1}\f$. + */ + vpMatrix Pest; + +protected: + /*! + Filter gain \f${\bf W}_k\f$ where \f$ {\bf W}_k = {\bf P}_{k + \mid k-1} {\bf H}^T \left[ {\bf H P}_{k \mid k-1} {\bf H}^T + {\bf + R}_k \right]^{-1}\f$. + */ + vpMatrix W; + + //! Identity matrix \f$ \bf I\f$. + vpMatrix I; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpLinProg.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpLinProg.h new file mode 100644 index 00000000..2289a80a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpLinProg.h @@ -0,0 +1,231 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Linear Programming with simplex + */ + +/*! + \file vpLinProg.h + \brief Implementation of Linear Program with simplex algorithm. +*/ + +#ifndef vpLinProgh +#define vpLinProgh + +#include // For std::abs() on iOS +#include // For std::abs() on iOS + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpLinProg + \ingroup group_core_optim + \brief This class provides two solvers for Linear Programs. + + One is a classical simplex, the other can deal with various inequality or bound constraints. + + Utility functions to reduce or check linear equalities or inequalities are also available. + + \warning The solvers are only available if c++11 or higher is activated during build. + Configure ViSP using cmake -DUSE_CXX_STANDARD=11. +*/ +class VISP_EXPORT vpLinProg +{ +public: +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + /*! + * Used to pass a list of bounded variables to solveLP(), as a list of (index, bound). + * + * The type is compatible with C++11's braced initialization. + * Construction can be done in the call to solveLP or before, as shown in this example: + * + * \f$\begin{array}{lll} + * (x,y,z) = & \arg\min & -2x -3y -4z\\ + * & \text{s.t.}& 3x + 2y + z \leq 10\\ + * & \text{s.t.}& 2x + 5y + 3z \leq 15\\ + * & \text{s.t.}& x, y, z \geq 0\\ + * & \text{s.t.}& z \leq 6\end{array}\f$ + * + * Here the lower bound is built explicitly while the upper one is built during the call to solveLP(): + * + * \code + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpColVector c(3), x; + * vpMatrix C(2, 3); + * vpColVector d(2); + * c[0] = -2; c[1] = -3; c[2] = -4; + * C[0][0] = 3; C[0][1] = 2; C[0][2] = 1; d[0] = 10; + * C[1][0] = 2; C[1][1] = 5; C[1][2] = 3; d[1] = 15; + * + * // build lower bounds explicitly as a std::vector of std::pair + * std::vector lower_bound; + * for(unsigned int i = 0; i < 3; ++i) + * { + * vpLinProg::BoundedIndex bound; + * bound.first = i; // index + * bound.second = 0; // lower bound for this index + * lower_bound.push_back(bound); + * } + * + * if(vpLinProg::solveLP(c, vpMatrix(0,0), vpColVector(0), C, d, x, + * lower_bound, + * {{2,6}})) // upper bound is passed with braced initialization + * { + * std::cout << "x: " << x.t() << std::endl; + * std::cout << "cost: " << c.t()*x << std::endl; + * } + * } + * \endcode + * + * \sa solveLP() + */ + typedef std::pair BoundedIndex; + + /** @name Solvers */ + //@{ + static bool simplex(const vpColVector &c, vpMatrix A, vpColVector b, vpColVector &x, const double &tol = 1e-6); + + static bool solveLP(const vpColVector &c, vpMatrix A, vpColVector b, const vpMatrix &C, const vpColVector &d, + vpColVector &x, std::vector l = {}, std::vector u = {}, + const double &tol = 1e-6); + + //@} +#endif + + /** @name Dimension reduction for equality constraints */ + //@{ + static bool colReduction(vpMatrix &A, vpColVector &b, bool full_rank = false, const double &tol = 1e-6); + + static bool rowReduction(vpMatrix &A, vpColVector &b, const double &tol = 1e-6); + //@} + + /** @name Vector and equality checking */ + //@{ + /*! + * Check if all elements of \f$x\f$ are near zero. + * + * \param x : vector to be checked + * \param tol : tolerance + * + * \return True if \f$\forall i, |\mathbf{x}_i| < \text{~tol} \f$ + */ + static bool allZero(const vpColVector &x, const double &tol = 1e-6) + { + for (unsigned int i = 0; i < x.getRows(); ++i) { + if (std::abs(x[i]) > tol) + return false; + } + return true; + } + + /*! + * Check if \f$\mathbf{A}\mathbf{x}\f$ is near \f$\mathbf{b}\f$. + * + * \param A : matrix (dimension m x n) + * \param x : vector (dimension n) + * \param b : vector (dimension m) + * \param tol : tolerance + * + * \return True if \f$ \forall i, |\mathbf{A}_i\mathbf{x} - \mathbf{b}_i| < \text{~tol}\f$ + */ + static bool allClose(const vpMatrix &A, const vpColVector &x, const vpColVector &b, const double &tol = 1e-6) + { + for (unsigned int i = 0; i < b.getRows(); ++i) { + if (std::abs(A.getRow(i) * x - b[i]) > tol) + return false; + } + return true; + } + + /*! + * Check if all elements of \f$\mathbf{C}\mathbf{x} - \mathbf{d}\f$ are lesser or equal to threshold. + * \param C : matrix (dimension m x n) + * \param x : vector (dimension n) + * \param d : vector (dimension m) + * \param thr : threshold + * + * \return True if \f$ \forall i, \mathbf{C}_i\mathbf{x} - \mathbf{d}_i \leq \text{~thr}\f$ + */ + static bool allLesser(const vpMatrix &C, const vpColVector &x, const vpColVector &d, const double &thr = 1e-6) + { + for (unsigned int i = 0; i < d.getRows(); ++i) { + if (C.getRow(i) * x - d[i] > thr) + return false; + } + return true; + } + + /*! + * Check if all elements of \f$\mathbf{x}\f$ are lesser or equal to threshold. + * + * \param x : vector (dimension n) + * \param thr : threshold + * + * \return True if \f$ \forall i, \mathbf{x}_i \leq \text{~thr}\f$ + */ + static bool allLesser(const vpColVector &x, const double &thr = 1e-6) + { + for (unsigned int i = 0; i < x.getRows(); ++i) { + if (x[i] > thr) + return false; + } + return true; + } + + /*! + * Check if all elements of \f$\mathbf{x}\f$ are greater or equal to threshold. + * + * \param x : vector (dimension n) + * \param thr : threshold + * + * \return True if \f$ \forall i, \mathbf{x}_i \geq \text{~thr}\f$ + */ + static bool allGreater(const vpColVector &x, const double &thr = 1e-6) + { + for (unsigned int i = 0; i < x.getRows(); ++i) { + if (x[i] < thr) + return false; + } + return true; + } + //@} +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpLine.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpLine.h new file mode 100644 index 00000000..63236417 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpLine.h @@ -0,0 +1,178 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Line feature. + */ + +/*! + * \file vpLine.h + * \brief class that defines what is a line + */ + +#ifndef VP_LINE_H +#define VP_LINE_H + +#include +#include +#include + +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpLine + * \ingroup group_core_geometry + * + * \brief Class that defines a 3D line in the object frame and allows forward projection + * of the line in the camera frame and in the 2D image plane by perspective projection. + * All the parameters must be set in meter. + * + * Note that a 3D line is defined from the intersection between two 3D planes. + * + * A 3D line has the followings parameters: + * - **in the 3D object frame**: parameters are located in vpForwardProjection::oP 8-dim internal vector. They correspond + * to the parameters oA1, oB1, oC1, oD1 and oA2, oB2, oC2, oD2 defining the equations of the two planes. + * Each point \f$ (X, Y, Z) \f$ which belongs to the 3D line is a solution of those two + * equations: + * \f[ oA1*X + oB1*Y + oC1*Z + oD1 = 0 \f] + * \f[ oA2*X + oB2*Y + oC2*Z + oD2 = 0 \f] + * To update these line parameters you may use setWorldCoordinates(). To get theses parameters use get_oP(). + * + * - **in the 3D camera frame**: parameters are saved in vpTracker::cP 8-dim internal vector. They correspond + * to the parameters cA1, cB1, cC1, cD1 and cA2, cB2, cC2, cD2 defining the equations of the two planes. + * Each point \f$ (X, Y, Z) \f$ which belongs to the 3D line is a solution of those two + * equations: + * \f[ cA1*X + cB1*Y + cC1*Z + cD1 = 0 \f] + * \f[ cA2*X + cB2*Y + cC2*Z + cD2 = 0 \f] + * It is easily possible to compute these parameters thanks to the corresponding 3D parameters oP in the + * object frame. But you have to note that four constraints are added in the planes equations. + * \f[ cD1 = 0 \f] + * \f[ cD2 > 0 \f] + * \f[ cA1*cA2 + cB1*cB2 + cC1*cC2 = 0 \f] + * \f[ || cA2 || = 1 \f] + * To compute these parameters you may use changeFrame(). To get these parameters use get_cP(). + * + * - **in the 2D image plane**: parameters are saved in vpTracker::p 2-dim vector. They correspond + * to the parameters (\f$\rho\f$, \f$\theta\f$). These + * 2D parameters are obtained from the perspective projection of the 3D line parameters expressed + * in the camera frame. They are defined thanks to the 2D equation of a line. + * \f[ x \; cos(\theta) + y \; sin(\theta) -\rho = 0 \f] Here \f$ x + * \f$ and \f$ y \f$ are the coordinates of a point belonging to the + * line in the image plane while \f$ \rho \f$ and \f$ \theta \f$ are + * the parameters used to define the line. The value of \f$ \theta + * \f$ is between \f$ -\pi/2 \f$ and \f$ \pi/2 \f$ and the value of + * \f$ \rho \f$ can be positive or negative. The conventions used to + * choose the sign of \f$ \rho \f$ and the value of \f$ \theta \f$ + * are illustrated by the following image. + * \image html vpFeatureLine.gif + * \image latex vpFeatureLine.ps width=10cm + * + * The line parameters corresponding to the image frame are located + * in the vpTracker::p public attribute, where \e p is a vector defined + * as: \f[ p = \left[\begin{array}{c} \rho \\ \theta \end{array}\right] \f] + * To compute these parameters use projection(). To get the corresponding values use get_p(). +*/ +class VISP_EXPORT vpLine : public vpForwardProjection +{ +public: + vpLine(); + + void changeFrame(const vpHomogeneousMatrix &cMo, vpColVector &cP) const VP_OVERRIDE; + void changeFrame(const vpHomogeneousMatrix &cMo) VP_OVERRIDE; + + void display(const vpImage &I, const vpCameraParameters &cam, const vpColor &color = vpColor::green, + unsigned int thickness = 1) VP_OVERRIDE; + void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &color = vpColor::green, unsigned int thickness = 1) VP_OVERRIDE; + + void display(const vpImage &I, const vpCameraParameters &cam, const vpColor &color = vpColor::green, + unsigned int thickness = 1); + void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &color = vpColor::green, unsigned int thickness = 1); + + vpLine *duplicate() const VP_OVERRIDE; + + + /*! + * Gets the \f$ \rho \f$ value corresponding to one of the + * two parameters used to define the line parametrization in the + * image plane. + * + * \return Returns the current value of \f$ \rho \f$. + * + * \sa getTheta() + */ + double getRho() const { return p[0]; } + + /*! + * Gets the \f$ \theta \f$ angle value corresponding to one of the + * two parameters used to define the line parametrization in the + * image plane. + * + * \return Returns the current value of \f$ \theta \f$. + * + * \sa getRho() + */ + double getTheta() const { return p[1]; } + + /*! + * Sets the \f$ \rho \f$ parameter used to define the line in the + * image plane. + * + * \param rho : The desired value for \f$ \rho \f$. + * + * \sa setTheta() + */ + void setRho(double rho) { p[0] = rho; } + + /*! + * Sets the \f$ \theta \f$ angle value used to define the line in the + * image plane. + * + * \param theta : The desired value for \f$ \theta \f$ angle. + * + * \sa setRho() + */ + void setTheta(double theta) { p[1] = theta; } + + void setWorldCoordinates(const double &oA1, const double &oB1, const double &oC1, const double &oD1, + const double &oA2, const double &oB2, const double &oC2, const double &oD2); + + void setWorldCoordinates(const vpColVector &oP1, const vpColVector &oP2); + + void setWorldCoordinates(const vpColVector &oP) VP_OVERRIDE; + void projection() VP_OVERRIDE; + void projection(const vpColVector &cP, vpColVector &p) const VP_OVERRIDE; + +protected: + void init() VP_OVERRIDE; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpLinearKalmanFilterInstantiation.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpLinearKalmanFilterInstantiation.h new file mode 100644 index 00000000..298306a3 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpLinearKalmanFilterInstantiation.h @@ -0,0 +1,167 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Kalman filtering. + */ + +/*! + \file vpLinearKalmanFilterInstantiation.h + \brief Implementation of some specific linear Kalman filters. +*/ + +#ifndef VP_LINEAR_KALMAN_FILTER_INSTANCIATION_H +#define VP_LINEAR_KALMAN_FILTER_INSTANCIATION_H + +#include + +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpLinearKalmanFilterInstantiation + \ingroup group_core_kalman + \brief This class provides an implementation of some specific linear Kalman + filters. +*/ +class VISP_EXPORT vpLinearKalmanFilterInstantiation : public vpKalmanFilter +{ +public: + /*! + Selector used to set the Kalman filter state model. + */ + typedef enum + { +/*! Consider the state as a constant velocity model with white + noise. Measures available are the successive positions of the + target. To know more about this state model, see + initStateConstVel_MeasurePos(). */ + stateConstVel_MeasurePos, + /*! Consider the state as a constant velocity model with colored noise + measurements as acceleration terms. Measured available are the + velocities of the target. To know more about this state model, + see initStateConstVelWithColoredNoise_MeasureVel(). */ + stateConstVelWithColoredNoise_MeasureVel, + /*! Consider the state as a constant acceleration model with colored noise + measurements as acceleration terms. Measured available are the + velocities of the target. To know more about this state model, + see initStateConstAccWithColoredNoise_MeasureVel(). */ + stateConstAccWithColoredNoise_MeasureVel, + /*! Used to indicate that the state model is not initialized. */ + unknown + } vpStateModel; + + /*! + Default linear Kalman filter. + + By default the state model is unknown and set to + vpLinearKalmanFilterInstantiation::unknown. + */ + vpLinearKalmanFilterInstantiation() : model(unknown) { } + + /*! + Return the current state model. + */ + inline vpStateModel getStateModel() { return model; } + void filter(vpColVector &z); + + /*! @name Generic linear filter initializer */ + //@{ + inline void setStateModel(vpStateModel model); + + void initFilter(unsigned int nsignal, vpColVector &sigma_state, vpColVector &sigma_measure, double rho, double dt); + //@} + + /*! @name Linear filter initializer with constant velocity models */ + //@{ + void initStateConstVel_MeasurePos(unsigned int nsignal, vpColVector &sigma_state, vpColVector &sigma_measure, + double dt); + void initStateConstVelWithColoredNoise_MeasureVel(unsigned int nsignal, vpColVector &sigma_state, + vpColVector &sigma_measure, double rho); + //@} + + /*! @name Linear filter initializer with constant acceleration models */ + //@{ + void initStateConstAccWithColoredNoise_MeasureVel(unsigned int nsignal, vpColVector &sigma_state, + vpColVector &sigma_measure, double rho, double dt); + //@} + +protected: + vpStateModel model; +}; + +/*! + Set the Kalman state model. Depending on the state model, we set + the state vector size and the measure vector size. + + The example below shows how to use this method and then to get the + size of the state and measure vectors. + + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpLinearKalmanFilterInstantiation kalman; + + kalman.setStateModel(vpLinearKalmanFilterInstantiation::stateConstVelWithColoredNoise_MeasureVel); + // Value is 2 + std::cout << "State vector size: " << kalman.getStateSize() << std::endl; + // Value is 1 + std::cout << "Measure vector size: " << kalman.getMeasureSize() << std::endl; + } + \endcode +*/ +void vpLinearKalmanFilterInstantiation::setStateModel(vpStateModel mdl) +{ + this->model = mdl; + switch (model) { + case stateConstVel_MeasurePos: + case stateConstVelWithColoredNoise_MeasureVel: + size_state = 2; + size_measure = 1; + break; + case stateConstAccWithColoredNoise_MeasureVel: + size_state = 3; + size_measure = 1; + break; + case unknown: + default: + size_state = 0; + size_measure = 0; + throw(vpException(vpException::notInitialized, "Unsupported Kalman state model in vpLinearKalmanFilterInstantiation::setStateModel()")); + break; + } +} +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpList.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpList.h new file mode 100644 index 00000000..79b0a3d2 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpList.h @@ -0,0 +1,714 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * List data structure. + */ + +/*! + * \file vpList.h + * \brief Definition of the list management class + */ + +#ifndef VP_LIST_H +#define VP_LIST_H + +#include +#include +#include + +#include + +BEGIN_VISP_NAMESPACE +#ifndef DOXYGEN_SHOULD_SKIP_THIS +/*! + \class vpListElement + \brief Each element of a list +*/ +template class vpListElement +{ + /* + // private: + // vpListElement(const vpListElement &) + // : prev(nullptr), next(nullptr), val() + // { + // throw vpException(vpException::functionNotImplementedError,"Not + // implemented!"); + // } + // vpListElement &operator=(const vpListElement &){ + // throw vpException(vpException::functionNotImplementedError,"Not + // implemented!"); return *this; + // } + */ +public: + vpListElement() : prev(nullptr), next(nullptr), val() { } + vpListElement *prev; ///! pointer to the previous element in the list + vpListElement *next; ///! pointer to the next element in the list + type val; ///! value of the element +}; + +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ + +/*! + \class vpList + \brief Provide simple list management + + \verbatim + Data structure: + each object vpListElement contains its own value and + two pointers to the next element of the list and to the previous one + + A list is mainly a pointer to three of these elements + - one (cur) points the current element + - one (first) points to a virtual element located at the + beginning of the list + - one (last) points to a virtual element located at the + end of the list + + first, last and cur are used internally and are not to be considered + by "basic" user. + + Notation: + In this documentation we note such a list as + [*, a, b, c, *] + f ^ l + where + - the * are the two virtual elements (first (f) and last (l)) + - the ^ denotes the position of the current element + + \endverbatim +*/ + +template class vpList +{ +private: + void init(); + +public: + unsigned int nb; ///! number of items in the List + /*! + \brief the first virtual item in the list + \verbatim + [*, a, b, c, *] + f + \endverbatim + */ + vpListElement *first; + /*! + \brief the last virtualitem in the list + \verbatim + [*, a, b, c, *] + l + \endverbatim + */ + vpListElement *last; + /*! + \brief the current item in the list + \verbatim + [*, a, b, c, *] + ^ + \endverbatim + */ + vpListElement *cur; // the current element +public: + vpList(); // constr. + vpList(const vpList &l); // cloning + virtual ~vpList(); // destr. + + inline void next(void); // current element's successor ( cur = cur->next ) + inline void previous(void); // current element's predecessor ( cur = cur->pred ) + inline void front(void); // go to the front of the List (cur = first) + inline void end(void); // go back to the end of the List ( cur = last ) + inline bool outside(void) const; // test whether we are outside the List + + bool empty(void) const; // tests whether the List is empty + + inline type &value(void); // returns the current element value + inline const type &value(void) const; // returns the current element value + + void suppress(void); // deletes the current item + void kill(); // empties the List + + void display(); // displays the content of the list + void print() { display(); } // displays the content of the list + + inline void addRight(const type &el); // inserts an element on the right + inline void addLeft(const type &el); // inserts an element on the left + inline void modify(const type &el); // modifies thevalue field of the curr. el. + inline void addRight(type &el); // inserts an element on the right + inline void addLeft(type &el); // inserts an element on the left + inline void swapLeft(); // Switch the current element with the element on the left + inline void swapRight(); // Switch the current element with the element on the right + inline unsigned int nbElement(void); // returns the number of items currently in the list + inline unsigned int nbElements(void); // returns the number of items currently in the list + + vpList &operator=(const vpList &l); + inline void operator+=(vpList &l); + inline void operator+=(const type &l); + + // Other non fundamental member (may be somehow useful) + bool nextOutside(void) const; // test whether we are outside the List + bool previousOutside(void) const; // test whether we are outside the List + + type &previousValue(void); // returns the previous element value + type &nextValue(void); // returns the next element value + type &firstValue(void); + type &lastValue(void); +}; + +/*! + \brief initialization, Create an empty list + \verbatim + init() --> [*, *] + \endverbatim */ +template void vpList::init() +{ + vpListElement *x = new vpListElement; + vpListElement *y = new vpListElement; + + first = x; + last = y; + + x->prev = nullptr; + x->next = y; + y->prev = x; + y->next = nullptr; + + cur = x; + nb = 0; +} + +/*! + \brief Basic constructor, initialization, Create an empty list + \verbatim + init() --> [*, *] + \endverbatim + \sa init() + */ +template vpList::vpList() : nb(0), first(nullptr), last(nullptr), cur(nullptr) { init(); } +/*! + \brief vpList destructor + \sa kill() + */ +template vpList::~vpList() +{ + kill(); + + /*if (first != nullptr) */ delete first; + /*if (last != nullptr) */ delete last; +} + +/*! + \brief return the number of element in the list + */ +template unsigned int vpList::nbElement(void) { return nb; } + +/*! + \brief return the number of element in the list + */ +template unsigned int vpList::nbElements(void) { return nb; } + +/*! + \brief position the current element on the next one + \verbatim + [*, a, b, c, d, *] --> next() --> [*, a, b, c, d, *] + ^ ^ + \endverbatim +*/ +template void vpList::next(void) { cur = cur->next; } + +/*! + \brief position the current element on the previous one + \verbatim + [*, a, b, c, d, *] --> previous() --> [*, a, b, c, d, *] + ^ ^ + \endverbatim +*/ +template void vpList::previous(void) { cur = cur->prev; } + +/*! + \brief return the value of the current element + + \verbatim + [*, a, b, c, *] --> value() return b + ^ + \endverbatim + */ +template type &vpList::value(void) { return (cur->val); } + +/*! + \brief return the value of the current element using a const ref. + + \verbatim + [*, a, b, c, *] --> value() return b + ^ + \endverbatim + */ +template const type &vpList::value(void) const { return (cur->val); } + +/*! + \brief return the value of the previous element + + \verbatim + [*, a, b, c, *] --> previousValue() return a + ^ + \endverbatim +*/ +template type &vpList::previousValue(void) { return (cur->prev->val); } + +/*! + \brief return the value of the next element + \verbatim + [*, a, b, c, d, *] --> nextValue() return c + ^ + \endverbatim +*/ +template type &vpList::nextValue(void) { return (cur->next->val); } + +/*! + \brief return the first element of the list + \verbatim + [*, a, b, c, d, *] --> firstValue() return a + \endverbatim + */ +template type &vpList::firstValue(void) { return (first->next->val); } + +/*!\brief return the last element of the list + \verbatim + [*, a, b, c, d, *] --> lastValue() return d + \endverbatim + */ +template type &vpList::lastValue(void) { return (last->prev->val); } + +/*! + \brief Position the current element on the first element of the list + + \verbatim + [*, a, b, c, d, *] --> front() --> [*, a, b, c, d, *] + ^ ^ + \endverbatim + */ +template void vpList::front(void) { cur = first->next; } + +/*! + \brief Position the current element on the last element of the list + + \verbatim + [*, a, b, c, d, *] --> end() --> [*, a, b, c, d, *] + ^ ^ + \endverbatim + */ +template void vpList::end(void) { cur = last->prev; } + +/*! + \brief Test if the list is empty + + \verbatim + [*, a, b, c, d, *] --> empty return false + [*, *] --> empty return true + \endverbatim + */ +template bool vpList::empty(void) const { return ((first->next == last) && (first == last->prev)); } + +/*! + \brief Test if the current element is outside the list + (on the virtual element) + + \verbatim + [*, a, b, c, d, *] --> outside return false + ^ + [*, a, b, c, d, *] --> outside return true + ^ or ^ + \endverbatim + */ +template bool vpList::outside(void) const { return ((cur == first) || (cur == last)); } + +/*! + \brief Test if the next element is outside the list + (ie if the current element is the last one) + + \verbatim + [*, a, b, c, d, *] --> nextOutside return true + ^ + \endverbatim + */ +template bool vpList::nextOutside(void) const +{ + return ((cur->next == first) || (cur->next == last)); +} + +/*! + \brief Test if the previous element is outside the list + (ie if the current element is the firts one) + + \verbatim + [*, a, b, c, d, *] --> nextOutside return true + ^ + \endverbatim + */ +template bool vpList::previousOutside(void) const +{ + return ((cur->prev == first) || (cur->prev == last)); +} + +/*! + \brief add a new element in the list, at the right of the current one + + \warning the new element becomes the current one + + \verbatim + [*, a, b, c, *] --> addRight(i) --> [*, a, b, i, c, *] + ^ ^ + \endverbatim + */ +template void vpList::addRight(const type &v) +{ + vpListElement *x = new vpListElement; + + x->val = v; + if (empty()) { + cur = first; + } + else { + if (outside()) { + std::cout << "vpList: outside with addRight " << std::endl; + } + } + cur->next->prev = x; + x->next = cur->next; + x->prev = cur; + cur->next = x; + cur = x; + nb++; +} + +/*! + \brief add a new element in the list, at the left of the current one + + \warning the new element becomes the current one + + \verbatim + [*, a, b, c, *] --> addLeft(i) --> [*, a, i, b, c, *] + ^ ^ + \endverbatim + */ +template void vpList::addLeft(const type &v) +{ + vpListElement *x = new vpListElement; + + x->val = v; + + if (empty()) { + cur = last; + } + else { + if (outside()) { + std::cout << "vpList: outside with addLeft " << std::endl; + } + } + x->next = cur; + x->prev = cur->prev; + cur->prev->next = x; + cur->prev = x; + cur = x; + ++nb; +} + +/*! + \brief add a new element in the list, at the right of the current one + + \warning the new element becomes the current one + + \verbatim + [*, a, b, c, *] --> addRight(i) --> [*, a, b, i, c, *] + ^ ^ + \endverbatim + */ +template void vpList::addRight(type &v) +{ + vpListElement *x = new vpListElement; + + x->val = v; + if (empty()) { + cur = first; + } + else { + if (outside()) { + std::cout << "vpList: outside with addRight " << std::endl; + } + } + cur->next->prev = x; + x->next = cur->next; + x->prev = cur; + cur->next = x; + cur = x; + ++nb; +} + +/*! + \brief add a new element in the list, at the left of the current one + + \warning the new element becomes the current one + + \verbatim + [*, a, b, c, *] --> addLeft(i) --> [*, a, i, b, c, *] + ^ ^ + \endverbatim + */ +template void vpList::addLeft(type &v) +{ + vpListElement *x = new vpListElement; + + x->val = v; + + if (empty()) { + cur = last; + } + else { + if (outside()) { + std::cout << "vpList: outside with addLeft " << std::endl; + } + } + x->next = cur; + x->prev = cur->prev; + cur->prev->next = x; + cur->prev = x; + cur = x; + ++nb; +} + +/*! + \brief Modify the value of the current element + + \verbatim + [*, a, b, c, *] --> modify(i) --> [*, a, i, c, *] + ^ ^ + \endverbatim + */ +template void vpList::modify(const type &v) { cur->val = v; } + +/*! + \brief Switch the current element with the element on the left + + \verbatim + [*, a, b, c, *] --> swapLeft --> [*, b, a, c, *] + ^ ^ + \endverbatim + */ +template void vpList::swapLeft() +{ + if (cur->prev != first) { + cur->prev->prev->next = cur; + cur->next->prev = cur->prev; + + vpListElement *nextTmp; + vpListElement *prevTmp; + + nextTmp = cur->next; + prevTmp = cur->prev; + + cur->next = cur->prev; + cur->prev = cur->prev->prev; + + prevTmp->prev = cur; + prevTmp->next = nextTmp; + } + else { + std::cout << "vpList: previous element is outside (swapLeft) " << std::endl; + } +} + +/*! + \brief Switch the current element with the element on the right + + \verbatim + [*, a, b, c, *] --> swapRight --> [*, a, c, b, *] + ^ ^ + \endverbatim + */ +template void vpList::swapRight() +{ + if (cur->next != last) { + cur->prev->next = cur->next; + cur->next->next->prev = cur; + + vpListElement *nextTmp; + vpListElement *prevTmp; + + nextTmp = cur->next; + prevTmp = cur->prev; + + cur->next = nextTmp->next; + cur->prev = nextTmp; + + nextTmp->prev = prevTmp; + nextTmp->next = cur; + } + else { + std::cout << "vpList: next element is outside (swapRight) " << std::endl; + } +} + +/*! + \brief Destroy the list + + \verbatim + [*, a, b, c, *] --> kill --> [*, *] + ^ ^ + \endverbatim + */ +template void vpList::kill() +{ + + front(); + while (!empty()) { + suppress(); + } +} + +/*! + \brief suppress the current element + + \warning new current element is on the next one + + \verbatim + [*, a, b, c, d, *] --> suppress --> [*, a, c, d, *] + ^ ^ + \endverbatim + */ +template void vpList::suppress(void) +{ + vpListElement *x; + + cur->prev->next = cur->next; + cur->next->prev = cur->prev; + x = cur; + cur = cur->next; + + if (x != nullptr) { + delete x; + } + + --nb; +} + +/*! + \brief Copy constructor const + + \param l : the list to copy + */ + +template vpList &vpList::operator=(const vpList &l) +{ + type x; + vpListElement *e; + + kill(); + e = l.first->next; + front(); + while (e != l.last) { + x = e->val; + addRight(x); + e = e->next; + } + + nb = l.nb; + cur = first->next; + + return *this; +} + +/*! + \brief Append two lists + + \verbatim + [*, a, b, *] += [*, c, d, *] --> [*, a, b, c, d, *] + ^ + \endverbatim + */ +template void vpList::operator+=(vpList &l) +{ + type x; + + l.front(); + end(); + while (!l.outside()) { + x = l.value(); + addRight(x); + l.next(); + } +} + +/*! + \brief Append an element to a list + + \verbatim + [*, a, b, *] += c --> [*, a, b, c, *] + ^ + \endverbatim + */ +template void vpList::operator+=(const type &l) +{ + end(); + addRight(l); +} + +/*! + \brief copy constructor + + \param l : the list to copy +*/ +template vpList::vpList(const vpList &l) : nb(0), first(nullptr), last(nullptr), cur(nullptr) +{ + init(); + *this = l; +} + +/*! + \brief Print (std::cout) all the element of the list + */ +template void vpList::display() +{ + unsigned int k = 1; + front(); + while (!outside()) { + std::cout << k << " ---> " << value() << std::endl; + next(); + ++k; + } + std::cout << std::endl << std::endl; +} +END_VISP_NAMESPACE +#endif /* #ifndef VP_LIST_H */ + +/* + * Local variables: + * c-basic-offset: 2 + * End: + */ diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMath.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMath.h new file mode 100644 index 00000000..5b37bd5f --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMath.h @@ -0,0 +1,691 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Simple mathematical function not available in the C math library (math.h). + */ + +/*! + * \file vpMath.h + * \brief Provides simple Math computation that are not available in + * the C mathematics library (math.h) + */ + +#ifndef VP_MATH_H +#define VP_MATH_H + +#include + +#include +#include +#include +#if defined(_WIN32) +// Define _USE_MATH_DEFINES before including to expose these macro +// definitions for common math constants. These are placed under an #ifdef +// since these commonly-defined names are not part of the C or C++ standards +#ifndef _USE_MATH_DEFINES +#define _USE_MATH_DEFINES +#endif +#endif +#include +#include + +#if defined(VISP_HAVE_FUNC_ISNAN) || defined(VISP_HAVE_FUNC_STD_ISNAN) || defined(VISP_HAVE_FUNC_ISINF) || \ + defined(VISP_HAVE_FUNC_STD_ISINF) || defined(VISP_HAVE_FUNC_STD_ROUND) +#include +#endif + +#if defined(_WIN32) // Not defined in Microsoft math.h + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#ifndef M_PI_2 +#define M_PI_2 (M_PI / 2.0) +#endif + +#ifndef M_PI_4 +#define M_PI_4 (M_PI / 4.0) +#endif + +#endif + +#ifndef M_PI_FLOAT +#define M_PI_FLOAT 3.14159265358979323846f +#endif + +#ifndef M_PI_2_FLOAT +#define M_PI_2_FLOAT (M_PI_FLOAT / 2.0f) +#endif + +#ifndef M_PI_4_FLOAT +#define M_PI_4_FLOAT (M_PI_FLOAT / 4.0f) +#endif + +#include +#include + +BEGIN_VISP_NAMESPACE + +class vpPoint; +class vpHomogeneousMatrix; +class vpColVector; +class vpRotationVector; +class vpRxyzVector; +class vpTranslationVector; + +/*! + * \class vpMath + * \ingroup group_core_math_tools + * \brief Provides simple mathematics computation tools that are not + * available in the C mathematics library (math.h) +*/ +class VISP_EXPORT vpMath +{ +public: + /*! + * Convert an angle in radians into degrees. + * + * \param rad : Angle in radians. + * \return Angle converted in degrees. + */ + static inline double deg(double rad) { return (rad * 180.0) / M_PI; } + + static vpColVector deg(const vpRotationVector &r); + static vpColVector deg(const vpColVector &r); + + /*! + * Convert an angle in degrees into radian. + * \param deg : Angle in degrees. + * \return Angle converted in radians. + */ + static inline double rad(double deg) { return (deg * M_PI) / 180.0; } + + static vpColVector rad(const vpColVector &r); + + /*! + * Convert angle between \f$-\pi\f$ and \f$\pi\f$. + * + * \param[in] theta The input angle we want to ensure it is in the interval \f$[-\pi ; \pi]\f$. + * \return The corresponding angle in the interval \f$[-\pi ; \pi]\f$. + */ + static float getAngleBetweenMinPiAndPi(const float &theta) + { + float theta1 = theta; + if (theta1 > M_PI_FLOAT) { + theta1 -= 2.0f * M_PI_FLOAT; + } + else if (theta1 <= -M_PI_FLOAT) { + theta1 += 2.0f * M_PI_FLOAT; + } + return theta1; + } + + /*! + * Convert angle between \f$-\pi\f$ and \f$\pi\f$. + * + * \param[in] theta The input angle we want to ensure it is in the interval \f$[-\pi ; \pi]\f$. + * \return The corresponding angle in the interval \f$[-\pi ; \pi]\f$. + */ + static double getAngleBetweenMinPiAndPi(const double &theta) + { + double theta1 = theta; + if (theta1 > M_PI) { + theta1 -= 2.0 * M_PI; + } + else if (theta1 < -M_PI) { + theta1 += 2.0 * M_PI; + } + return theta1; + } + + /** + * \brief Gives the rest of \b value divided by \b modulo when + * the quotient can only be an integer. + * + * \param[in] value The value we want to know the rest in the "modulo" operation. + * \param[in] modulo The divider. + * \return float The rest as in a modulo operation. + */ + static float modulo(const float &value, const float &modulo) + { + float quotient = std::floor(value / modulo); + float rest = value - (quotient * modulo); + return rest; + } + + /** + * \brief Gives the rest of \b value divided by \b modulo when + * the quotient can only be an integer. + * + * \param[in] value The value we want to know the rest in the "modulo" operation. + * \param[in] modulo The divider. + * \return double The rest as in a modulo operation. + */ + static double modulo(const double &value, const double &modulo) + { + double quotient = std::floor(value / modulo); + double rest = value - (quotient * modulo); + return rest; + } + + /*! + Compute x square value. + \return Square value \f$ x^2 \f$. + */ + static inline double sqr(double x) { return x * x; } + + // factorial of x + static inline double fact(unsigned int x); + + // combinaison + static inline long double comb(unsigned int n, unsigned int p); + + /*! + Clamp a value to boundaries. + \param[in] v : The value to clamp. + \param[in] lower : The lower boundary to clamp `v` to. + \param[in] upper : The upper boundary to clamp `v` to. + + Throw a vpException if the value of `lower` is greater than `upper`. + */ + template static inline T clamp(const T &v, const T &lower, const T &upper) + { + // Check if std:c++17 or higher. + // Here we cannot use (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17) when ViSP + // is used as a 3rdparty. See issue #1274 +#if ((__cplusplus >= 201703L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L))) + return std::clamp(v, lower, upper); +#else + if (upper < lower) { + throw vpException(vpException::badValue, "clamp: lower bound is greater than upper bound"); + } + return (v < lower) ? lower : (upper < v) ? upper : v; +#endif + } + + // round x to the nearest integer + static inline int round(double x); + + // return the sign of x (+-1) + static inline int sign(double x); + + // test if a number equals 0 (with threshold value) + static inline bool nul(double x, double threshold = 0.001); + static inline bool nul(float x, float threshold = 0.001f); + + // test if two numbers are equals (with a user defined threshold) + static inline bool equal(double x, double y, double threshold = 0.001); + static inline bool equal(float x, float y, float threshold = 0.001f); + + // test if a number is greater than another (with a user defined threshold) + static inline bool greater(double x, double y, double threshold = 0.001); + + /*! + Find the maximum between two numbers (or other). + \param a : First number. + \param b : Second number. + \return The maximum of the two numbers. + */ + template static Type maximum(const Type &a, const Type &b) { return (a > b) ? a : b; } + + /*! + Find the minimum between two numbers (or other). + \param a : First number. + \param b : Second number. + \return The minimum of the two numbers. + */ + template static Type minimum(const Type &a, const Type &b) { return (a < b) ? a : b; } + + /*! + Find the absolute value of a number (or other). + \param x : The number. + \return The absolute value of x + */ + template static Type abs(const Type &x) { return (x < 0) ? -x : x; } + + // sinus cardinal + static double sinc(double x); + static double sinc(double sinx, double x); + static double mcosc(double cosx, double x); + static double msinc(double sinx, double x); + + // sigmoid + static inline double sigmoid(double x, double x0 = 0., double x1 = 1., double n = 12.); + + /*! + Exchange two numbers. + + \param a First number to exchange. + \param b Second number to exchange + */ + template static void swap(Type &a, Type &b) + { + Type tmp = b; + b = a; + a = tmp; + } + + static bool isNaN(double value); + static bool isNaN(float value); + static bool isInf(double value); + static bool isInf(float value); + static bool isFinite(double value); + static bool isFinite(float value); + static bool isNumber(const std::string &str); + + static double lineFitting(const std::vector &imPts, double &a, double &b, double &c); + + template static inline Tp saturate(unsigned char v) { return Tp(v); } + template static inline Tp saturate(char v) { return Tp(v); } + template static inline Tp saturate(unsigned short v) { return Tp(v); } + template static inline Tp saturate(short v) { return Tp(v); } + template static inline Tp saturate(unsigned v) { return Tp(v); } + template static inline Tp saturate(int v) { return Tp(v); } + template static inline Tp saturate(float v) { return Tp(v); } + template static inline Tp saturate(double v) { return Tp(v); } + + static double getMean(const std::vector &v); + static double getMedian(const std::vector &v); + static double getStdev(const std::vector &v, bool useBesselCorrection = false); + + static int modulo(int a, int n); + static unsigned int modulo(unsigned int a, unsigned int n); + + static vpHomogeneousMatrix ned2ecef(double lonDeg, double latDeg, double radius); + static vpHomogeneousMatrix enu2ecef(double lonDeg, double latDeg, double radius); + static vpHomogeneousMatrix enu2ned(const vpHomogeneousMatrix &enu_M); + + /*! + Similar to the NumPy linspace function: "Return evenly spaced numbers over a specified interval." + Code from: https://stackoverflow.com/a/27030598 + + \param start_in : The starting value of the sequence. + \param end_in : The end value of the sequence. + \param num_in : Number of samples to generate. + + \return Returns \e num_in evenly spaced samples, calculated over the interval [\e start_in, \e end_in]. + */ + template static std::vector linspace(T start_in, T end_in, unsigned int num_in) + { + std::vector linspaced; + + double start = static_cast(start_in); + double end = static_cast(end_in); + double num = static_cast(num_in); + + if (std::fabs(num) < std::numeric_limits::epsilon()) { + return linspaced; + } + if (std::fabs(num - 1) < std::numeric_limits::epsilon()) { + linspaced.push_back(start); + return linspaced; + } + + double delta = (end - start) / (num - 1); + + for (int i = 0; i < (num - 1); ++i) { + linspaced.push_back(start + (delta * i)); + } + linspaced.push_back(end); // I want to ensure that start and end + // are exactly the same as the input + return linspaced; + } + + static std::vector > computeRegularPointsOnSphere(unsigned int maxPoints); + + typedef vpHomogeneousMatrix(*LongLattToHomogeneous)(double lonDeg, double latDeg, double radius); + static std::vector + getLocalTangentPlaneTransformations(const std::vector > &lonlatVec, double radius, + LongLattToHomogeneous func); + + static vpHomogeneousMatrix lookAt(const vpColVector &from, const vpColVector &to, vpColVector tmp); + +private: + static const double ang_min_sinc; + static const double ang_min_mc; +}; + +// Begining of the inline functions definition + +/*! + Computes and returns x! + \param x : parameter of factorial function. +*/ +double vpMath::fact(unsigned int x) +{ + if ((x == 1) || (x == 0)) { + return 1; + } + return x * fact(x - 1); +} + +/*! + Computes the number of combination of p elements inside n elements. + + \param n : total number of elements. + \param p : requested number of elements. + + \return Combination number \f$ n! / ((n-p)! p!) \f$ +*/ +long double vpMath::comb(unsigned int n, unsigned int p) +{ + if (n == p) { + return 1; + } + return fact(n) / (fact(n - p) * fact(p)); +} + +/*! + Round x to the nearest integer. + + \param x : Value to round. + + \return Nearest integer of x. +*/ +int vpMath::round(double x) +{ +#if defined(VISP_HAVE_FUNC_STD_ROUND) + return static_cast(std::round(x)); +#elif defined(VISP_HAVE_FUNC_ROUND) + //:: to design the global namespace and avoid to call recursively + // vpMath::round + return static_cast(::round(x)); +#else + return (x > 0.0) ? (static_cast(floor(x + 0.5))) : (static_cast(ceil(x - 0.5))); +#endif +} + +/*! + Return the sign of x. + + \param x : Value to test. + \return -1 if x is negative, +1 if positive and 0 if zero. +*/ +int vpMath::sign(double x) +{ + if (fabs(x) < std::numeric_limits::epsilon()) { + return 0; + } + else { + if (x < 0) { + return -1; + } + else { + return 1; + } + } +} + +/*! + Compares \f$ | x | \f$ to `threshold`. + \param x : Value to test. + \param threshold : Tolerance threshold + \return true if \f$ | x | <\f$ `threshold`. +*/ +bool vpMath::nul(double x, double threshold) { return (fabs(x) < threshold); } + +/*! + Compares \f$ | x | \f$ to `threshold`. + \param x : Value to test. + \param threshold : Tolerance threshold + \return true if \f$ | x | <\f$ `threshold`. +*/ +bool vpMath::nul(float x, float threshold) { return (fabs(x) < threshold); } + +/*! + Compares \f$ | x - y | \f$ to `threshold`. + \param x : x value. + \param y : y value. + \param threshold : Tolerance threshold. + \return true if \f$ | x - y | <\f$ `threshold`. +*/ +bool vpMath::equal(double x, double y, double threshold) { return (nul(x - y, threshold)); } + +/*! + Compares \f$ | x - y | \f$ to `threshold`. + \param x : x value. + \param y : y value. + \param threshold : Tolerance threshold. + \return true if \f$ | x - y | <\f$ `threshold`. +*/ +bool vpMath::equal(float x, float y, float threshold) { return (nul(x - y, threshold)); } + +/*! + Compares \f$ x \f$ to \f$ y -\f$ `threshold`. + \param x : x value. + \param y : y value. + \param threshold : Tolerance threshold. + \return true if \f$ x > y -\f$ `threshold`. +*/ +bool vpMath::greater(double x, double y, double threshold) { return (x > (y - threshold)); } + +/*! + + Sigmoid function between [x0,x1] with \f$ s(x)=0 if x\le x0\f$ and \f$ s(x)=1 + if x \ge x1 \f$ + \param x : Value of x. + \param x0 : Lower bound (default 0). + \param x1 : Upper bound (default 1). + \param n : Degree of the exponential (default 12). + + \return Sigmoid value \f$1/(1+exp(-n*((x-x0)/(x1-x0)-0.5)))\f$ + */ +double vpMath::sigmoid(double x, double x0, double x1, double n) +{ + if (x < x0) { + return 0.; + } + else if (x > x1) { + return 1.; + } + double l0 = 1. / (1. + exp(0.5 * n)); + double l1 = 1. / (1. + exp(-0.5 * n)); + return ((1. / (1. + exp(-n * (((x - x0) / (x1 - x0)) - 0.5)))) - l0) / (l1 - l0); +} + +// unsigned char +template <> inline unsigned char vpMath::saturate(char v) +{ + // On big endian arch like powerpc, char implementation is unsigned + // with CHAR_MIN=0, CHAR_MAX=255 and SCHAR_MIN=-128, SCHAR_MAX=127 + // leading to static_cast(char -127) = 129. + // On little endian arch, CHAR_MIN=-127 and CHAR_MAX=128 leading to + // static_cast(char -127) = -127. + if (std::numeric_limits::is_signed) { + return static_cast(std::max(static_cast(v), 0)); + } + else { + return static_cast(static_cast(v) > SCHAR_MAX ? 0 : v); + } +} + +template <> inline unsigned char vpMath::saturate(unsigned short v) +{ + return static_cast(std::min(static_cast(v), static_cast(UCHAR_MAX))); +} + +template <> inline unsigned char vpMath::saturate(int v) +{ + return static_cast(static_cast(v) <= UCHAR_MAX ? v : v > 0 ? UCHAR_MAX : 0); +} + +template <> inline unsigned char vpMath::saturate(short v) +{ + return saturate(static_cast(v)); +} + +template <> inline unsigned char vpMath::saturate(unsigned int v) +{ + return static_cast(std::min(v, static_cast(UCHAR_MAX))); +} + +template <> inline unsigned char vpMath::saturate(float v) +{ + int iv = vpMath::round(static_cast(v)); + return saturate(iv); +} + +template <> inline unsigned char vpMath::saturate(double v) +{ + int iv = vpMath::round(v); + return saturate(iv); +} + +// char +template <> inline char vpMath::saturate(unsigned char v) +{ + return static_cast(std::min(static_cast(v), SCHAR_MAX)); +} + +template <> inline char vpMath::saturate(unsigned short v) +{ + return static_cast(std::min(static_cast(v), static_cast(SCHAR_MAX))); +} + +template <> inline char vpMath::saturate(int v) +{ + return static_cast(static_cast(v - SCHAR_MIN) <= static_cast(UCHAR_MAX) ? v : v > 0 ? SCHAR_MAX : SCHAR_MIN); +} + +template <> inline char vpMath::saturate(short v) +{ + return saturate(static_cast(v)); +} + +template <> inline char vpMath::saturate(unsigned int v) +{ + return static_cast(std::min(v, static_cast(SCHAR_MAX))); +} + +template <> inline char vpMath::saturate(float v) +{ + int iv = vpMath::round(static_cast(v)); + return saturate(iv); +} + +template <> inline char vpMath::saturate(double v) +{ + int iv = vpMath::round(v); + return saturate(iv); +} + +// unsigned short +template <> inline unsigned short vpMath::saturate(char v) +{ + // On big endian arch like powerpc, char implementation is unsigned + // with CHAR_MIN=0, CHAR_MAX=255 and SCHAR_MIN=-128, SCHAR_MAX=127 + // leading to static_cast(char -127) = 129. + // On little endian arch, CHAR_MIN=-127 and CHAR_MAX=128 leading to + // static_cast(char -127) = -127. + if (std::numeric_limits::is_signed) { + return static_cast(std::max(static_cast(v), 0)); + } + else { + return static_cast(static_cast(v) > SCHAR_MAX ? 0 : v); + } +} + +template <> inline unsigned short vpMath::saturate(short v) +{ + return static_cast(std::max(static_cast(v), 0)); +} + +template <> inline unsigned short vpMath::saturate(int v) +{ + return static_cast(static_cast(v) <= static_cast(USHRT_MAX) ? v : v > 0 ? USHRT_MAX : 0); +} + +template <> inline unsigned short vpMath::saturate(unsigned int v) +{ + return static_cast(std::min(v, static_cast(USHRT_MAX))); +} + +template <> inline unsigned short vpMath::saturate(float v) +{ + int iv = vpMath::round(static_cast(v)); + return vpMath::saturate(iv); +} + +template <> inline unsigned short vpMath::saturate(double v) +{ + int iv = vpMath::round(v); + return vpMath::saturate(iv); +} + +// short +template <> inline short vpMath::saturate(unsigned short v) +{ + return static_cast(std::min(static_cast(v), SHRT_MAX)); +} +template <> inline short vpMath::saturate(int v) +{ + return static_cast(static_cast(v - SHRT_MIN) <= static_cast(USHRT_MAX) ? v : v > 0 ? SHRT_MAX : SHRT_MIN); +} +template <> inline short vpMath::saturate(unsigned int v) +{ + return static_cast(std::min(v, static_cast(SHRT_MAX))); +} +template <> inline short vpMath::saturate(float v) +{ + int iv = vpMath::round(static_cast(v)); + return vpMath::saturate(iv); +} +template <> inline short vpMath::saturate(double v) +{ + int iv = vpMath::round(v); + return vpMath::saturate(iv); +} + +// int +template <> inline int vpMath::saturate(float v) +{ + return vpMath::round(static_cast(v)); +} + +template <> inline int vpMath::saturate(double v) +{ + return vpMath::round(v); +} + +// unsigned int +// (Comment from OpenCV) we intentionally do not clip negative numbers, to +// make -1 become 0xffffffff etc. +template <> inline unsigned int vpMath::saturate(float v) +{ + return static_cast(vpMath::round(static_cast(v))); +} + +template <> inline unsigned int vpMath::saturate(double v) +{ + return static_cast(vpMath::round(v)); +} +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMatrix.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMatrix.h new file mode 100644 index 00000000..1cd469f1 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMatrix.h @@ -0,0 +1,1306 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Matrix manipulation. + */ + +/*! + \file vpMatrix.h + + \brief Definition of matrix class as well as a set of operations on + these matrices. +*/ + +#ifndef VP_MATRIX_H +#define VP_MATRIX_H + +#include + +BEGIN_VISP_NAMESPACE +class vpRowVector; +class vpColVector; +class vpTranslationVector; +class vpHomogeneousMatrix; +class vpVelocityTwistMatrix; +class vpForceTwistMatrix; +END_VISP_NAMESPACE + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE + +/*! + \class vpMatrix + \ingroup group_core_matrices + + \brief Implementation of a matrix and operations on matrices. + + This class needs one of the following third-party to compute matrix inverse, + pseudo-inverse, singular value decomposition, determinant: + - If Lapack is installed and detected by ViSP, this 3rd party is used by + vpMatrix. Installation instructions are provided here + https://visp.inria.fr/3rd_lapack; + - else if Eigen3 is installed and detected by ViSP, this 3rd party is used + by vpMatrix. Installation instructions are provided here + https://visp.inria.fr/3rd_eigen; + - else if OpenCV is installed and detected by ViSP, this 3rd party is used, + Installation instructions are provided here + https://visp.inria.fr/3rd_opencv; + - If none of these previous 3rd parties is installed, we use by default a + Lapack built-in version. + + vpMatrix class provides a data structure for the matrices as well + as a set of operations on these matrices. + + The vpMatrix class is derived from vpArray2D. + + The code below shows how to create a 2-by-3 matrix of doubles, set the element values and access them: + \code + #include > &) + \code + #include Tutorials
+ If you are interested in linear algebra using vpMatrix, + you may have a look at:
+ + - \ref tutorial-basic-linear-algebra +*/ +class VISP_EXPORT vpMatrix : public vpArray2D +{ +public: + /*! + Method used to compute the determinant of a square matrix. + \sa det() + */ + typedef enum + { + LU_DECOMPOSITION /*!< LU decomposition method. */ + } vpDetMethod; + +public: + /*! + Basic constructor of a matrix of double. Number of columns and rows are + zero. + */ + vpMatrix() : vpArray2D(0, 0) { } + + /*! + Constructor that initialize a matrix of double with 0. + + \param r : Matrix number of rows. + \param c : Matrix number of columns. + */ + vpMatrix(unsigned int r, unsigned int c) : vpArray2D(r, c) { } + + /*! + Constructor that initialize a matrix of double with \e val. + + \param r : Matrix number of rows. + \param c : Matrix number of columns. + \param val : Each element of the matrix is set to \e val. + */ + vpMatrix(unsigned int r, unsigned int c, double val) : vpArray2D(r, c, val) { } + vpMatrix(const vpMatrix &M, unsigned int r, unsigned int c, unsigned int nrows, unsigned int ncols); + + /*! + Create a matrix from a 2D array that could be one of the following + container that inherit from vpArray2D such as vpMatrix, vpRotationMatrix, + vpHomogeneousMatrix, vpPoseVector, vpColVector, vpRowVector... + + The following example shows how to create a matrix from an homogeneous + matrix: + \code + vpRotationMatrix R; + vpMatrix M(R); + \endcode + */ + VP_EXPLICIT vpMatrix(const vpArray2D &A) : vpArray2D(A) { } + vpMatrix(const vpMatrix &A) : vpArray2D(A) { } + VP_EXPLICIT vpMatrix(const vpHomogeneousMatrix &R); + VP_EXPLICIT vpMatrix(const vpRotationMatrix &R); + VP_EXPLICIT vpMatrix(const vpVelocityTwistMatrix &V); + VP_EXPLICIT vpMatrix(const vpForceTwistMatrix &F); + VP_EXPLICIT vpMatrix(const vpColVector &v); + VP_EXPLICIT vpMatrix(const vpRowVector &v); + VP_EXPLICIT vpMatrix(const vpTranslationVector &t); + + static vpMatrix view(double *data, unsigned int rows, unsigned int cols); + static void view(vpMatrix &v, double *data, unsigned int rows, unsigned int cols); + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpMatrix(vpMatrix &&A); + VP_EXPLICIT vpMatrix(const std::initializer_list &list); + VP_EXPLICIT vpMatrix(unsigned int nrows, unsigned int ncols, const std::initializer_list &list); + VP_EXPLICIT vpMatrix(const std::initializer_list > &lists); +#endif + + /*! + Removes all elements from the matrix (which are destroyed), + leaving the container with a size of 0. + */ + void clear() + { + if (data != nullptr) { + free(data); + data = nullptr; + } + + if (rowPtrs != nullptr) { + free(rowPtrs); + rowPtrs = nullptr; + } + rowNum = 0; + colNum = 0; + dsize = 0; + } + + //------------------------------------------------- + // Setting a diagonal matrix + //------------------------------------------------- + /** @name Linear algebra optimization */ + //@{ + /*! + * Return the minimum size of rows and columns required to enable Blas/Lapack + * usage on matrices and vectors. + * + * To get more info see \ref tutorial-basic-linear-algebra. + * + * \sa setLapackMatrixMinSize() + */ + static unsigned int getLapackMatrixMinSize() { return m_lapack_min_size; } + + /*! + * Modify default size used to determine if Blas/Lapack basic linear algebra operations are enabled. + * + * To get more info see \ref tutorial-basic-linear-algebra. + * + * \param min_size : Minimum size of rows and columns required for a matrix or a vector to use + * Blas/Lapack third parties like MKL, OpenBLAS, Netlib or Atlas. When matrix or vector size is + * lower or equal to this parameter, Blas/Lapack is not used. In that case we prefer use naive code + * that runs faster for small matrices. + * + * \sa getLapackMatrixMinSize() + */ + static void setLapackMatrixMinSize(unsigned int min_size) { m_lapack_min_size = min_size; } + //@} + + //------------------------------------------------- + // Setting a diagonal matrix + //------------------------------------------------- + /** @name Setting a diagonal matrix */ + //@{ + void diag(const double &val = 1.0); + void diag(const vpColVector &A); + // Initialize an identity matrix n-by-n + void eye(); + void eye(unsigned int n); + // Initialize an identity matrix m-by-n + void eye(unsigned int m, unsigned int n); + //@} + + //--------------------------------- + // Assignment + //--------------------------------- + /** @name Assignment operators */ + //@{ + vpMatrix &operator<<(double *p); + vpMatrix &operator<<(double val); + vpMatrix &operator,(double val); + vpMatrix &operator=(const vpArray2D &A); + vpMatrix &operator=(const vpMatrix &A); + vpMatrix &operator=(const vpHomogeneousMatrix &M); + vpMatrix &operator=(const vpRotationMatrix &R); + vpMatrix &operator=(const vpVelocityTwistMatrix &V); + vpMatrix &operator=(const vpForceTwistMatrix &F); + vpMatrix &operator=(const vpColVector &v); + vpMatrix &operator=(const vpRowVector &v); + vpMatrix &operator=(const vpTranslationVector &t); + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpMatrix &operator=(vpMatrix &&A); + + vpMatrix &operator=(const std::initializer_list &list); + vpMatrix &operator=(const std::initializer_list > &lists); +#endif + vpMatrix &operator=(double x); + //@} + + //------------------------------------------------- + // Stacking + //------------------------------------------------- + /** @name Stacking */ + //@{ + // Stack the matrix A below the current one, copy if not initialized this = + // [ this A ]^T + void stack(const vpMatrix &A); + void stack(const vpRowVector &r); + void stack(const vpColVector &c); + // Stacks columns of a matrix in a vector + void stackColumns(vpColVector &out); + + // Stacks columns of a matrix in a vector + vpColVector stackColumns(); + + // Stacks columns of a matrix in a vector + void stackRows(vpRowVector &out); + + // Stacks columns of a matrix in a vector + vpRowVector stackRows(); + //@} + + //--------------------------------- + // Matrix insertion + //--------------------------------- + /** @name Matrix insertion */ + //@{ + // Insert matrix A in the current matrix at the given position (r, c). + void insert(const vpMatrix &A, unsigned int r, unsigned int c); + //@} + + //------------------------------------------------- + // Columns, Rows, Diag extraction, SubMatrix + //------------------------------------------------- + /** @name Columns, rows, sub-matrices extraction */ + //@{ + vpMatrix extract(unsigned int r, unsigned int c, unsigned int nrows, unsigned int ncols) const; + vpColVector getCol(unsigned int j) const; + vpColVector getCol(unsigned int j, unsigned int i_begin, unsigned int size) const; + vpRowVector getRow(unsigned int i) const; + vpRowVector getRow(unsigned int i, unsigned int j_begin, unsigned int size) const; + vpColVector getDiag() const; + void init(const vpMatrix &M, unsigned int r, unsigned int c, unsigned int nrows, unsigned int ncols); + //@} + + //--------------------------------- + // Matrix operations. + //--------------------------------- + /** @name Matrix operations */ + //@{ + /*! + Perform a 2D convolution similar to Matlab conv2 function: \f$ M \star kernel \f$. + + \param M : First matrix. + \param kernel : Second matrix. + \param mode : Convolution mode: "full" (default), "same", "valid". + + \image html vpMatrix-conv2-mode.jpg "Convolution mode: full, same, valid (image credit: Theano doc)." + + \note This is a very basic implementation that does not use FFT. + */ + static vpMatrix conv2(const vpMatrix &M, const vpMatrix &kernel, const std::string &mode); + + /*! + Perform a 2D convolution similar to Matlab conv2 function: \f$ M \star kernel \f$. + + \param M : First array. + \param kernel : Second array. + \param res : Result. + \param mode : Convolution mode: "full" (default), "same", "valid". + + \image html vpMatrix-conv2-mode.jpg "Convolution mode: full, same, valid (image credit: Theano doc)." + + \note This is a very basic implementation that does not use FFT. + */ + static void conv2(const vpMatrix &M, const vpMatrix &kernel, vpMatrix &res, const std::string &mode); + + // return the determinant of the matrix. + double det(vpDetMethod method = LU_DECOMPOSITION) const; + double detByLU() const; +#if defined(VISP_HAVE_EIGEN3) + double detByLUEigen3() const; +#endif +#if defined(VISP_HAVE_LAPACK) + double detByLULapack() const; +#endif +#if defined(VISP_HAVE_OPENCV) + double detByLUOpenCV() const; +#endif + vpMatrix cholesky() const; +#if defined(VISP_HAVE_EIGEN3) + vpMatrix choleskyByEigen3() const; +#endif +#if defined(VISP_HAVE_LAPACK) + vpMatrix choleskyByLapack() const; +#endif +#if defined(VISP_HAVE_OPENCV) + vpMatrix choleskyByOpenCV() const; +#endif + + // Compute the exponential matrix of a square matrix + vpMatrix expm() const; + + // operation A = A + B + vpMatrix &operator+=(const vpMatrix &B); + // operation A = A - B + vpMatrix &operator-=(const vpMatrix &B); + vpMatrix operator*(const vpMatrix &B) const; + vpMatrix operator*(const vpRotationMatrix &R) const; + vpMatrix operator*(const vpHomogeneousMatrix &R) const; + vpMatrix operator*(const vpVelocityTwistMatrix &V) const; + vpMatrix operator*(const vpForceTwistMatrix &V) const; + // operation t_out = A * t (A is unchanged, t and t_out are translation + // vectors) + vpTranslationVector operator*(const vpTranslationVector &tv) const; + vpColVector operator*(const vpColVector &v) const; + vpMatrix operator+(const vpMatrix &B) const; + vpMatrix operator-(const vpMatrix &B) const; + vpMatrix operator-() const; + + //! Add x to all the element of the matrix : Aij = Aij + x + vpMatrix &operator+=(double x); + //! subtract x to all the element of the matrix : Aij = Aij - x + vpMatrix &operator-=(double x); + //! Multiply all the element of the matrix by x : Aij = Aij * x + vpMatrix &operator*=(double x); + //! Divide all the element of the matrix by x : Aij = Aij / x + vpMatrix &operator/=(double x); + + // Cij = Aij * x (A is unchanged) + vpMatrix operator*(double x) const; + // Cij = Aij / x (A is unchanged) + vpMatrix operator/(double x) const; + + /*! + Return the sum of all the \f$a_{ij}\f$ elements of the matrix. + + \return Value of \f$\sum a_{ij}\f$ + */ + double sum() const; + double sumSquare() const; + + //------------------------------------------------- + // Hadamard product + //------------------------------------------------- + /** @name Hadamard product */ + vpMatrix hadamard(const vpMatrix &m) const; + + //------------------------------------------------- + // Kronecker product + //------------------------------------------------- + /** @name Kronecker product */ + //@{ + // Compute Kronecker product matrix + void kron(const vpMatrix &m1, vpMatrix &out) const; + + // Compute Kronecker product matrix + vpMatrix kron(const vpMatrix &m1) const; + //@} + + //------------------------------------------------- + // Transpose + //------------------------------------------------- + /** @name Transpose */ + //@{ + // Compute the transpose C = A^T + vpMatrix t() const; + + // Compute the transpose C = A^T + vpMatrix transpose() const; + void transpose(vpMatrix &At) const; + + vpMatrix AAt() const; + void AAt(vpMatrix &B) const; + + vpMatrix AtA() const; + void AtA(vpMatrix &B) const; + //@} + + //------------------------------------------------- + // Matrix inversion + //------------------------------------------------- + /** @name Matrix inversion */ + //@{ + // inverse matrix A using the LU decomposition + vpMatrix inverseByLU() const; + +#if defined(VISP_HAVE_EIGEN3) + vpMatrix inverseByLUEigen3() const; +#endif +#if defined(VISP_HAVE_LAPACK) + vpMatrix inverseByLULapack() const; +#endif +#if defined(VISP_HAVE_OPENCV) + vpMatrix inverseByLUOpenCV() const; +#endif + + // inverse matrix A using the Cholesky decomposition (only for real + // symmetric matrices) + vpMatrix inverseByCholesky() const; + +#if defined(VISP_HAVE_LAPACK) + vpMatrix inverseByCholeskyLapack() const; +#endif +#if defined(VISP_HAVE_OPENCV) + vpMatrix inverseByCholeskyOpenCV() const; +#endif + + // inverse matrix A using the QR decomposition + vpMatrix inverseByQR() const; +#if defined(VISP_HAVE_LAPACK) + vpMatrix inverseByQRLapack() const; +#endif + + // inverse triangular matrix + vpMatrix inverseTriangular(bool upper = true) const; + + vpMatrix pseudoInverse(double svThreshold = 1e-6) const; + unsigned int pseudoInverse(vpMatrix &Ap, double svThreshold = 1e-6) const; + unsigned int pseudoInverse(vpMatrix &Ap, vpColVector &sv, double svThreshold = 1e-6) const; + unsigned int pseudoInverse(vpMatrix &Ap, vpColVector &sv, double svThreshold, vpMatrix &imA, vpMatrix &imAt) const; + unsigned int pseudoInverse(vpMatrix &Ap, vpColVector &sv, double svThreshold, vpMatrix &imA, vpMatrix &imAt, vpMatrix &kerAt) const; + vpMatrix pseudoInverse(int rank_in) const; + int pseudoInverse(vpMatrix &Ap, int rank_in) const; + int pseudoInverse(vpMatrix &Ap, vpColVector &sv, int rank_in) const; + int pseudoInverse(vpMatrix &Ap, vpColVector &sv, int rank_in, vpMatrix &imA, vpMatrix &imAt) const; + int pseudoInverse(vpMatrix &Ap, vpColVector &sv, int rank_in, vpMatrix &imA, vpMatrix &imAt, vpMatrix &kerAt) const; + +#if defined(VISP_HAVE_LAPACK) + vpMatrix pseudoInverseLapack(double svThreshold = 1e-6) const; + unsigned int pseudoInverseLapack(vpMatrix &Ap, double svThreshold = 1e-6) const; + unsigned int pseudoInverseLapack(vpMatrix &Ap, vpColVector &sv, double svThreshold = 1e-6) const; + unsigned int pseudoInverseLapack(vpMatrix &Ap, vpColVector &sv, double svThreshold, vpMatrix &imA, vpMatrix &imAt, vpMatrix &kerAt) const; + vpMatrix pseudoInverseLapack(int rank_in) const; + int pseudoInverseLapack(vpMatrix &Ap, int rank_in) const; + int pseudoInverseLapack(vpMatrix &Ap, vpColVector &sv, int rank_in) const; + int pseudoInverseLapack(vpMatrix &Ap, vpColVector &sv, int rank_in, vpMatrix &imA, vpMatrix &imAt, vpMatrix &kerAt) const; +#endif +#if defined(VISP_HAVE_EIGEN3) + vpMatrix pseudoInverseEigen3(double svThreshold = 1e-6) const; + unsigned int pseudoInverseEigen3(vpMatrix &Ap, double svThreshold = 1e-6) const; + unsigned int pseudoInverseEigen3(vpMatrix &Ap, vpColVector &sv, double svThreshold = 1e-6) const; + unsigned int pseudoInverseEigen3(vpMatrix &Ap, vpColVector &sv, double svThreshold, vpMatrix &imA, vpMatrix &imAt, vpMatrix &kerAt) const; + vpMatrix pseudoInverseEigen3(int rank_in) const; + int pseudoInverseEigen3(vpMatrix &Ap, int rank_in) const; + int pseudoInverseEigen3(vpMatrix &Ap, vpColVector &sv, int rank_in) const; + int pseudoInverseEigen3(vpMatrix &Ap, vpColVector &sv, int rank_in, vpMatrix &imA, vpMatrix &imAt, vpMatrix &kerAt) const; +#endif +#if defined(VISP_HAVE_OPENCV) + vpMatrix pseudoInverseOpenCV(double svThreshold = 1e-6) const; + unsigned int pseudoInverseOpenCV(vpMatrix &Ap, double svThreshold = 1e-6) const; + unsigned int pseudoInverseOpenCV(vpMatrix &Ap, vpColVector &sv, double svThreshold = 1e-6) const; + unsigned int pseudoInverseOpenCV(vpMatrix &Ap, vpColVector &sv, double svThreshold, vpMatrix &imA, vpMatrix &imAt, vpMatrix &kerAt) const; + vpMatrix pseudoInverseOpenCV(int rank_in) const; + int pseudoInverseOpenCV(vpMatrix &Ap, int rank_in) const; + int pseudoInverseOpenCV(vpMatrix &Ap, vpColVector &sv, int rank_in) const; + int pseudoInverseOpenCV(vpMatrix &Ap, vpColVector &sv, int rank_in, vpMatrix &imA, vpMatrix &imAt, vpMatrix &kerAt) const; +#endif + + vpMatrix dampedInverse(const double &ratioOfMaxSvd = 1e-4) const; + //@} + + //------------------------------------------------- + // SVD decomposition + //------------------------------------------------- + + /** @name SVD decomposition */ + //@{ + double cond(double svThreshold = 1e-6) const; + unsigned int kernel(vpMatrix &kerAt, double svThreshold = 1e-6) const; + unsigned int nullSpace(vpMatrix &kerA, double svThreshold = 1e-6) const; + unsigned int nullSpace(vpMatrix &kerA, int dim) const; + + // solve Ax=B using the SVD decomposition (usage A = solveBySVD(B,x) ) + void solveBySVD(const vpColVector &B, vpColVector &x) const; + // solve Ax=B using the SVD decomposition (usage x=A.solveBySVD(B)) + vpColVector solveBySVD(const vpColVector &B) const; + + // singular value decomposition SVD + void svd(vpColVector &w, vpMatrix &V); +#ifdef VISP_HAVE_EIGEN3 + void svdEigen3(vpColVector &w, vpMatrix &V); +#endif +#if defined(VISP_HAVE_LAPACK) + void svdLapack(vpColVector &w, vpMatrix &V); +#endif +#if defined(VISP_HAVE_OPENCV) // Require opencv >= 2.1.1 + void svdOpenCV(vpColVector &w, vpMatrix &V); +#endif + //@} + + //------------------------------------------------- + // QR decomposition + //------------------------------------------------- + + /** @name QR decomposition */ + //@{ + unsigned int qr(vpMatrix &Q, vpMatrix &R, bool full = false, bool squareR = false, double tol = 1e-6) const; + unsigned int qrPivot(vpMatrix &Q, vpMatrix &R, vpMatrix &P, bool full = false, bool squareR = false, + double tol = 1e-6) const; + void solveByQR(const vpColVector &b, vpColVector &x) const; + vpColVector solveByQR(const vpColVector &b) const; + //@} + + //------------------------------------------------- + // Eigen values and vectors + //------------------------------------------------- + /** @name Eigen values */ + + //@{ + // Compute the eigen values using Lapack. + vpColVector eigenValues() const; + void eigenValues(vpColVector &evalue, vpMatrix &evector) const; + //@} + + //------------------------------------------------- + // Norms + //------------------------------------------------- + /** @name Norms */ + //@{ + double frobeniusNorm() const; + double inducedL2Norm() const; + double infinityNorm() const; + //@} + + //--------------------------------- + // Printing + //--------------------------------- + /** @name Printing */ + //@{ + std::ostream &cppPrint(std::ostream &os, const std::string &matrixName = "A", bool octet = false) const; + std::ostream &csvPrint(std::ostream &os) const; + std::ostream &maplePrint(std::ostream &os) const; + std::ostream &matlabPrint(std::ostream &os) const; + int print(std::ostream &s, unsigned int length, const std::string &intro = "") const; + void printSize() const { std::cout << getRows() << " x " << getCols() << " "; } + //@} + + //------------------------------------------------------------------ + // Static functionalities + //------------------------------------------------------------------ + + //--------------------------------- + // Setting a diagonal matrix with Static Public Member Functions + //--------------------------------- + /** @name Setting a diagonal matrix with Static Public Member Functions */ + //@{ + // Create a diagonal matrix with the element of a vector DAii = Ai + static void createDiagonalMatrix(const vpColVector &A, vpMatrix &DA); + //@} + + //--------------------------------- + // Matrix insertion with Static Public Member Functions + //--------------------------------- + /** @name Matrix insertion with Static Public Member Functions */ + //@{ + // Insert matrix B in matrix A at the given position (r, c). + static vpMatrix insert(const vpMatrix &A, const vpMatrix &B, unsigned int r, unsigned int c); + // Insert matrix B in matrix A (not modified) at the given position (r, c), + // the result is given in matrix C. + static void insert(const vpMatrix &A, const vpMatrix &B, vpMatrix &C, unsigned int r, unsigned int c); + + //--------------------------------- + // Stacking with Static Public Member Functions + //--------------------------------- + /** @name Stacking with Static Public Member Functions */ + //@{ + // Juxtapose to matrices C = [ A B ] + static vpMatrix juxtaposeMatrices(const vpMatrix &A, const vpMatrix &B); + // Juxtapose to matrices C = [ A B ] + static void juxtaposeMatrices(const vpMatrix &A, const vpMatrix &B, vpMatrix &C); + // Stack two matrices C = [ A B ]^T + static vpMatrix stack(const vpMatrix &A, const vpMatrix &B); + static vpMatrix stack(const vpMatrix &A, const vpRowVector &r); + static vpMatrix stack(const vpMatrix &A, const vpColVector &c); + + // Stack two matrices C = [ A B ]^T + static void stack(const vpMatrix &A, const vpMatrix &B, vpMatrix &C); + static void stack(const vpMatrix &A, const vpRowVector &r, vpMatrix &C); + static void stack(const vpMatrix &A, const vpColVector &c, vpMatrix &C); + //@} + + //--------------------------------- + // Matrix operations Static Public Member Functions + //--------------------------------- + /** @name Matrix operations with Static Public Member Functions */ + //@{ + static void add2Matrices(const vpMatrix &A, const vpMatrix &B, vpMatrix &C); + static void add2Matrices(const vpColVector &A, const vpColVector &B, vpColVector &C); + static void add2WeightedMatrices(const vpMatrix &A, const double &wA, const vpMatrix &B, const double &wB, + vpMatrix &C); + static void computeHLM(const vpMatrix &H, const double &alpha, vpMatrix &HLM); + static void mult2Matrices(const vpMatrix &A, const vpMatrix &B, vpMatrix &C); + static void mult2Matrices(const vpMatrix &A, const vpMatrix &B, vpRotationMatrix &C); + static void mult2Matrices(const vpMatrix &A, const vpMatrix &B, vpHomogeneousMatrix &C); + static void mult2Matrices(const vpMatrix &A, const vpColVector &B, vpColVector &C); + static void mult2Matrices(const vpMatrix &A, const vpRotationMatrix &B, vpMatrix &C); + static void mult2Matrices(const vpRotationMatrix &A, const vpMatrix &B, vpMatrix &C); + + static void multMatrixVector(const vpMatrix &A, const vpColVector &v, vpColVector &w); + static void negateMatrix(const vpMatrix &A, vpMatrix &C); + static void sub2Matrices(const vpMatrix &A, const vpMatrix &B, vpMatrix &C); + static void sub2Matrices(const vpColVector &A, const vpColVector &B, vpColVector &C); + //@} + + //--------------------------------- + // Kronecker product Static Public Member Functions + //--------------------------------- + /** @name Kronecker product with Static Public Member Functions */ + //@{ + // Compute Kronecker product matrix + static void kron(const vpMatrix &m1, const vpMatrix &m2, vpMatrix &out); + + // Compute Kronecker product matrix + static vpMatrix kron(const vpMatrix &m1, const vpMatrix &m2); + //@} + + //--------------------------------- + // Covariance computation Static Public Member Functions + //--------------------------------- + /** @name Covariance computation with Static Public Member Functions */ + //@{ + static vpMatrix computeCovarianceMatrix(const vpMatrix &A, const vpColVector &x, const vpColVector &b); + static vpMatrix computeCovarianceMatrix(const vpMatrix &A, const vpColVector &x, const vpColVector &b, + const vpMatrix &w); + static vpMatrix computeCovarianceMatrixVVS(const vpHomogeneousMatrix &cMo, const vpColVector &deltaS, + const vpMatrix &Ls, const vpMatrix &W); + static vpMatrix computeCovarianceMatrixVVS(const vpHomogeneousMatrix &cMo, const vpColVector &deltaS, + const vpMatrix &Ls); + //@} + + //--------------------------------- + // Matrix I/O Static Public Member Functions + //--------------------------------- + /** @name Matrix I/O with Static Public Member Functions */ + //@{ + /*! + Load a matrix from a file. This function overloads vpArray2D::load(). + + \param filename : Absolute file name. + \param M : Matrix to be loaded. + \param binary : If true the matrix data are considered as binary, otherwise as human readable (text) data. Using + binary data allows to keep data precision. + \param header : Header of the file is loaded in this parameter. + + \return Returns true if success, false otherwise. + + The following example shows how to use this function: + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + std::string filename("matrix.bin"); + bool binary_data = true; + { + vpMatrix M(2, 3); + M[0][0] = -1; M[0][1] = -2; M[0][2] = -3; + M[1][0] = 4; M[1][1] = 5.5; M[1][2] = 6.0f; + + std::string header("My header"); + + if (vpMatrix::saveMatrix(filename, M, binary_data, header.c_str())) { + std::cout << "Matrix saved in " << filename << std::endl; + M.print(std::cout, 10, header); + } else { + std::cout << "Cannot save matrix in " << filename << std::endl; + } + } + { + vpMatrix N; + char header[FILENAME_MAX]; + if (vpMatrix::loadMatrix(filename, N, binary_data, header)) { + std::cout << "Matrix loaded from " << filename << std::endl; + N.print(std::cout, 10, header); + } else { + std::cout << "Cannot load matrix from " << filename << std::endl; + } + } + } + \endcode + + The output of this example is the following: + \verbatim + Matrix saved in matrix.bin + My header[2,3]= + -1.0 -2.0 -3.0 + 4.0 5.5 6.0 + Matrix loaded from matrix.bin + My header[2,3]= + -1.0 -2.0 -3.0 + 4.0 5.5 6.0 + \endverbatim + + And the content of `matrix.bin` file where data are saved as binary data is the following: + \verbatim + % cat matrix.bin + My header??@@@% + \endverbatim + + \sa saveMatrix(), saveMatrixYAML(), loadMatrixYAML() + */ + static inline bool loadMatrix(const std::string &filename, vpArray2D &M, bool binary = false, + char *header = nullptr) + { + return vpArray2D::load(filename, M, binary, header); + } + + /*! + Load a matrix from a YAML-formatted file. This function overloads + vpArray2D::loadYAML(). + + \param filename : Absolute YAML file name. + \param M : Matrix to be loaded from the file. + \param header : Header of the file is loaded in this parameter. + + \return Returns true when success, false otherwise. + + The following example shows how to use this function: + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + std::string filename("matrix.yaml"); + { + vpMatrix M(2, 3); + M[0][0] = -1; M[0][1] = -2; M[0][2] = -3; + M[1][0] = 4; M[1][1] = 5.5; M[1][2] = 6.0f; + + std::string header("My header"); + + if (vpMatrix::saveMatrixYAML(filename, M, header.c_str())) { + std::cout << "Matrix saved in " << filename << std::endl; + M.print(std::cout, 10, header); + } else { + std::cout << "Cannot save matrix in " << filename << std::endl; + } + } + { + vpMatrix N; + char header[FILENAME_MAX]; + if (vpMatrix::loadMatrixYAML(filename, N, header)) { + std::cout << "Matrix loaded from " << filename << std::endl; + N.print(std::cout, 10, header); + } else { + std::cout << "Cannot load matrix from " << filename << std::endl; + } + } + } + \endcode + + The output of this example is the following: + \verbatim + Matrix saved in matrix.yaml + My header[2,3]= + -1.0 -2.0 -3.0 + 4.0 5.5 6.0 + Matrix loaded from matrix.yaml + My header[2,3]= + -1.0 -2.0 -3.0 + 4.0 5.5 6.0 + \endverbatim + + And the content of `matrix.yaml` file is the following: + \verbatim + % cat matrix.yaml + My header + rows: 2 + cols: 3 + data: + - [-1, -2, -3] + - [4, 5.5, 6] + \endverbatim + + \sa saveMatrixYAML(), saveMatrix(), loadMatrix() + */ + static inline bool loadMatrixYAML(const std::string &filename, vpArray2D &M, char *header = nullptr) + { + return vpArray2D::loadYAML(filename, M, header); + } + + /*! + Save a matrix to a file. This function overloads vpArray2D::save(). + + \param filename : Absolute file name. + \param M : Matrix to be saved. + \param binary : If true the matrix is save as a binary file, otherwise as a text file. + \param header : Optional line that will be saved at the beginning of the file as a header. + + \return Returns true if no problem appends. + + \warning If you save the matrix as a text file the precision is less + than if you save it as a binary file. + + The following example shows how to use this function: + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + std::string filename("matrix.bin"); + bool binary_data = true; + { + vpMatrix M(2, 3); + M[0][0] = -1; M[0][1] = -2; M[0][2] = -3; + M[1][0] = 4; M[1][1] = 5.5; M[1][2] = 6.0f; + + std::string header("My header"); + + if (vpMatrix::saveMatrix(filename, M, binary_data, header.c_str())) { + std::cout << "Matrix saved in " << filename << std::endl; + M.print(std::cout, 10, header); + } else { + std::cout << "Cannot save matrix in " << filename << std::endl; + } + } + { + vpMatrix N; + char header[FILENAME_MAX]; + if (vpMatrix::loadMatrix(filename, N, binary_data, header)) { + std::cout << "Matrix loaded from " << filename << std::endl; + N.print(std::cout, 10, header); + } else { + std::cout << "Cannot load matrix from " << filename << std::endl; + } + } + } + \endcode + + The output of this example is the following: + \verbatim + Matrix saved in matrix.bin + My header[2,3]= + -1.0 -2.0 -3.0 + 4.0 5.5 6.0 + Matrix loaded from matrix.bin + My header[2,3]= + -1.0 -2.0 -3.0 + 4.0 5.5 6.0 + \endverbatim + + And the content of `matrix.bin` file where data are saved as binary data is the following: + \verbatim + % cat matrix.bin + My header??@@@% + \endverbatim + + \sa loadMatrix(), saveMatrixYAML(), loadMatrixYAML() + */ + static inline bool saveMatrix(const std::string &filename, const vpArray2D &M, bool binary = false, + const char *header = "") + { + return vpArray2D::save(filename, M, binary, header); + } + + /*! + Save a matrix in a YAML-formatted file. This function overloads + vpArray2D::saveYAML(). + + \param filename : Absolute file name. + \param M : Matrix to be saved in the file. + \param header : Optional lines that will be saved at the beginning of the + file as a header. + + \return Returns true if success. + + The following example shows how to use this function: + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + std::string filename("matrix.yaml"); + { + vpMatrix M(2, 3); + M[0][0] = -1; M[0][1] = -2; M[0][2] = -3; + M[1][0] = 4; M[1][1] = 5.5; M[1][2] = 6.0f; + + std::string header("My header"); + + if (vpMatrix::saveMatrixYAML(filename, M, header.c_str())) { + std::cout << "Matrix saved in " << filename << std::endl; + M.print(std::cout, 10, header); + } else { + std::cout << "Cannot save matrix in " << filename << std::endl; + } + } + { + vpMatrix N; + char header[FILENAME_MAX]; + if (vpMatrix::loadMatrixYAML(filename, N, header)) { + std::cout << "Matrix loaded from " << filename << std::endl; + N.print(std::cout, 10, header); + } else { + std::cout << "Cannot load matrix from " << filename << std::endl; + } + } + } + \endcode + + The output of this example is the following: + \verbatim + Matrix saved in matrix.yaml + My header[2,3]= + -1.0 -2.0 -3.0 + 4.0 5.5 6.0 + Matrix loaded from matrix.yaml + My header[2,3]= + -1.0 -2.0 -3.0 + 4.0 5.5 6.0 + \endverbatim + + And the content of `matrix.yaml` file is the following: + \verbatim + % cat matrix.yaml + My header + rows: 2 + cols: 3 + data: + - [-1, -2, -3] + - [4, 5.5, 6] + \endverbatim + + \sa saveMatrix(), loadMatrix(), loadMatrixYAML() + */ + static inline bool saveMatrixYAML(const std::string &filename, const vpArray2D &M, const char *header = "") + { + return vpArray2D::saveYAML(filename, M, header); + } + //@} + +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) + VP_DEPRECATED double euclideanNorm() const; + + /*! + @name Deprecated functions + */ + //@{ + /*! + \deprecated Only provided for compatibility with ViSP previous releases. + This function does nothing. + */ + VP_DEPRECATED void init() { } + + /*! + \deprecated You should rather use stack(const vpMatrix &A) + */ + VP_DEPRECATED void stackMatrices(const vpMatrix &A) { stack(A); } + /*! + \deprecated You should rather use stack(const vpMatrix &A, const vpMatrix + &B) + */ + VP_DEPRECATED static vpMatrix stackMatrices(const vpMatrix &A, const vpMatrix &B) { return stack(A, B); } + /*! + \deprecated You should rather use stack(const vpMatrix &A, const vpMatrix + &B, vpMatrix &C) + */ + VP_DEPRECATED static void stackMatrices(const vpMatrix &A, const vpMatrix &B, vpMatrix &C) { stack(A, B, C); } + /*! + \deprecated You should rather use stack(const vpMatrix &A, const vpMatrix + &B) + */ + VP_DEPRECATED static vpMatrix stackMatrices(const vpMatrix &A, const vpRowVector &B); + /*! + \deprecated You should rather use stack(const vpMatrix &A, const + vpRowVector &B, vpMatrix &C) + */ + VP_DEPRECATED static void stackMatrices(const vpMatrix &A, const vpRowVector &B, vpMatrix &C); + /*! + \deprecated You should rather use vpColVector::stack(const vpColVector + &A, const vpColVector &B) + */ + VP_DEPRECATED static vpMatrix stackMatrices(const vpColVector &A, const vpColVector &B); + /*! + \deprecated You should rather use vpColVector::stack(const vpColVector + &A, const vpColVector &B, vpColVector &C) + */ + VP_DEPRECATED static void stackMatrices(const vpColVector &A, const vpColVector &B, vpColVector &C); + + /*! + \deprecated You should rather use diag(const double &) + */ + VP_DEPRECATED void setIdentity(const double &val = 1.0); + + VP_DEPRECATED vpRowVector row(unsigned int i); + VP_DEPRECATED vpColVector column(unsigned int j); + + // Deprecated functions using GSL +#ifndef DOXYGEN_SHOULD_SKIP_THIS + /*! + \deprecated You should rather use detByLULapack() or detByLU(). + */ + VP_DEPRECATED double detByLUGsl() const + { +#if defined(VISP_HAVE_LAPACK) + return detByLULapack(); +#else + throw(vpException(vpException::fatalError, "Undefined detByLULapack(). Install Lapack 3rd party")); +#endif + } + + /*! + \deprecated You should rather use inverseByLULapack() or inverseByLU(). + */ + VP_DEPRECATED vpMatrix inverseByLUGsl() const + { +#if defined(VISP_HAVE_LAPACK) + return inverseByLULapack(); +#else + throw(vpException(vpException::fatalError, "Undefined inverseByLULapack(). Install Lapack 3rd party")); +#endif + } + + /*! + \deprecated You should rather use inverseByCholeskyLapack() or inverseByCholesky(). + */ + VP_DEPRECATED vpMatrix inverseByCholeskyGsl() const + { +#if defined(VISP_HAVE_LAPACK) + return inverseByCholeskyLapack(); +#else + throw(vpException(vpException::fatalError, "Undefined inverseByCholeskyLapack(). Install Lapack 3rd party")); +#endif + } + + /*! + \deprecated You should rather use inverseByQRLapack() or inverseByQR(). + */ + VP_DEPRECATED vpMatrix inverseByQRGsl() const + { +#if defined(VISP_HAVE_LAPACK) + return inverseByQRLapack(); +#else + throw(vpException(vpException::fatalError, "Undefined inverseByQRLapack(). Install Lapack 3rd party")); +#endif + } + + /*! + \deprecated You should rather use pseudoInverseLapack() or pseudoInverse(). + */ + VP_DEPRECATED vpMatrix pseudoInverseGsl(double svThreshold = 1e-6) const + { +#if defined(VISP_HAVE_LAPACK) + return pseudoInverseLapack(svThreshold); +#else + (void)svThreshold; + throw(vpException(vpException::fatalError, "Undefined pseudoInverseLapack(). Install Lapack 3rd party")); +#endif + } + + /*! + \deprecated You should rather use pseudoInverseLapack() or pseudoInverse(). + */ + VP_DEPRECATED unsigned int pseudoInverseGsl(vpMatrix &Ap, double svThreshold = 1e-6) const + { +#if defined(VISP_HAVE_LAPACK) + return pseudoInverseLapack(Ap, svThreshold); +#else + (void)Ap; + (void)svThreshold; + throw(vpException(vpException::fatalError, "Undefined pseudoInverseLapack(). Install Lapack 3rd party")); +#endif + } + + /*! + \deprecated You should rather use pseudoInverseLapack() or pseudoInverse(). + */ + VP_DEPRECATED unsigned int pseudoInverseGsl(vpMatrix &Ap, vpColVector &sv, double svThreshold = 1e-6) const + { +#if defined(VISP_HAVE_LAPACK) + return pseudoInverseLapack(Ap, sv, svThreshold); +#else + (void)Ap; + (void)sv; + (void)svThreshold; + throw(vpException(vpException::fatalError, "Undefined pseudoInverseLapack(). Install Lapack 3rd party")); +#endif + } + + /*! + \deprecated You should rather use pseudoInverseLapack() or pseudoInverse(). + */ + VP_DEPRECATED unsigned int pseudoInverseGsl(vpMatrix &Ap, vpColVector &sv, double svThreshold, vpMatrix &imA, vpMatrix &imAt, + vpMatrix &kerAt) const + { +#if defined(VISP_HAVE_LAPACK) + return pseudoInverseLapack(Ap, sv, svThreshold, imA, imAt, kerAt); +#else + (void)Ap; + (void)sv; + (void)svThreshold; + (void)imA; + (void)imAt; + (void)kerAt; + throw(vpException(vpException::fatalError, "Undefined pseudoInverseLapack(). Install Lapack 3rd party")); +#endif + } + + /*! + \deprecated You should rather use svdLapack() or svd(). + */ + VP_DEPRECATED void svdGsl(vpColVector &w, vpMatrix &V) + { +#if defined(VISP_HAVE_LAPACK) + svdLapack(w, V); +#else + (void)w; + (void)V; + throw(vpException(vpException::fatalError, "Undefined svdLapack(). Install Lapack 3rd party")); +#endif + } + +#endif // ifndef DOXYGEN_SHOULD_SKIP_THIS + //@} +#endif + +private: + static unsigned int m_lapack_min_size; + static const unsigned int m_lapack_min_size_default; + +#if defined(VISP_HAVE_LAPACK) + static void blas_dgemm(char trans_a, char trans_b, unsigned int M_, unsigned int N_, unsigned int K_, double alpha, + double *a_data, unsigned int lda_, double *b_data, unsigned int ldb_, double beta, + double *c_data, unsigned int ldc_); + static void blas_dgemv(char trans, unsigned int M_, unsigned int N_, double alpha, double *a_data, unsigned int lda_, + double *x_data, int incx_, double beta, double *y_data, int incy_); + static void blas_dsyev(char jobz, char uplo, unsigned int n_, double *a_data, unsigned int lda_, double *w_data, + double *work_data, int lwork_, int &info_); + + unsigned int qrPivotLapack(vpMatrix &Q, vpMatrix &R, vpMatrix &P, bool full, bool squareR, + double tol) const; + +#ifdef VISP_HAVE_GSL + unsigned int qrPivotLapackGSL(vpMatrix &Q, vpMatrix &R, vpMatrix &P, bool full, bool squareR, + double tol) const; +#endif +#endif + + static void computeCovarianceMatrixVVS(const vpHomogeneousMatrix &cMo, const vpColVector &deltaS, const vpMatrix &Ls, + vpMatrix &Js, vpColVector &deltaP); +}; + +////////////////////////////////////////////////////////////////////////// +#if defined(VISP_USE_MSVC) && defined(visp_EXPORTS) +const __declspec(selectany) unsigned int vpMatrix::m_lapack_min_size_default = 0; +__declspec(selectany) unsigned int vpMatrix::m_lapack_min_size = vpMatrix::m_lapack_min_size_default; +#endif + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +VISP_EXPORT +#endif +vpMatrix operator*(const double &x, const vpMatrix &A); + +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMatrixException.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMatrixException.h new file mode 100644 index 00000000..e2edd42c --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMatrixException.h @@ -0,0 +1,104 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Exceptions that can be emitted by the vpMatrix class and its derivatives. + */ + +#ifndef VP_MATRIX_EXCEPTION_H +#define VP_MATRIX_EXCEPTION_H + +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpMatrixException + * \ingroup group_core_debug + * \brief error that can be emitted by the vpMatrix class and its derivatives +*/ +class VISP_EXPORT vpMatrixException : public vpException +{ +public: + /*! + * \brief Lists the possible error than can be emitted while calling + * vpMatrix member + */ + enum errorCodeEnum + { + //! Error returns by a constructor + constructionError, + //! Something is not initialized + notInitializedError, + //! Function not implemented + notImplementedError, + //! Index out of range + outOfRangeError, + //! Iterative algorithm doesn't converge (ex SVD) + convergencyError, + //! Incorrect matrix size + incorrectMatrixSizeError, + //! Forbidden operation + forbiddenOperatorError, + //! Sub operation matrix error + subMatrixError, + //! Matrix operation error + matrixError, + //! Rank deficient + rankDeficient + }; + +public: + /*! + * Constructor. + */ + vpMatrixException(int id, const char *format, ...) + { + this->code = id; + va_list args; + va_start(args, format); + setMessage(format, args); + va_end(args); + } + + /*! + * Constructor. + */ + vpMatrixException(int id, const std::string &msg) : vpException(id, msg) { } + + /*! + * Constructor. + */ + VP_EXPLICIT vpMatrixException(int id) : vpException(id) { } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMeterPixelConversion.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMeterPixelConversion.h new file mode 100644 index 00000000..11d53fe6 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMeterPixelConversion.h @@ -0,0 +1,369 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Meter to pixel conversion. + */ + +/*! + \file vpMeterPixelConversion.h + \brief Meter to pixel conversion. +*/ + +#ifndef VP_METER_PIXEL_CONVERSION_H +#define VP_METER_PIXEL_CONVERSION_H + +#include +#include +#include +#include +#include +#include + +#if defined(VISP_HAVE_OPENCV) +#include +#endif + +BEGIN_VISP_NAMESPACE +/*! + \class vpMeterPixelConversion + + \ingroup group_core_camera + + Various conversion functions to transform primitives (2D ellipse, 2D line, 2D point) from normalized + coordinates in meter in the image plane into pixel coordinates. + + Transformation relies either on ViSP camera parameters implemented in vpCameraParameters or on OpenCV camera parameters + that are set from a projection matrix and a distortion coefficients vector. + +*/ +class VISP_EXPORT vpMeterPixelConversion +{ +public: + /** @name Using ViSP camera parameters */ + //@{ + static void convertEllipse(const vpCameraParameters &cam, const vpSphere &sphere, vpImagePoint ¢er_p, + double &n20_p, double &n11_p, double &n02_p); + static void convertEllipse(const vpCameraParameters &cam, const vpCircle &circle, vpImagePoint ¢er_p, + double &n20_p, double &n11_p, double &n02_p); + static void convertEllipse(const vpCameraParameters &cam, double xc_m, double yc_m, double n20_m, double n11_m, + double n02_m, vpImagePoint ¢er_p, double &n20_p, double &n11_p, double &n02_p); + static void convertLine(const vpCameraParameters &cam, const double &rho_m, const double &theta_m, double &rho_p, + double &theta_p); + + /*! + + Point coordinates conversion from normalized coordinates + \f$(x,y)\f$ in meter in the image plane to pixel coordinates \f$(u,v)\f$ in the image using ViSP camera parameters. + + The used formula depends on the projection model of the camera. To + know the currently used projection model use + vpCameraParameter::get_projModel() + + \param[in] cam : camera parameters. + \param[in] x : input coordinate in meter along image plane x-axis. + \param[in] y : input coordinate in meter along image plane y-axis. + \param[out] u : output coordinate in pixels along image horizontal axis. + \param[out] v : output coordinate in pixels along image vertical axis. + + \f$ u = x*p_x + u_0 \f$ and \f$ v = y*p_y + v_0 \f$ in the case of + perspective projection without distortion. + + \f$ u = x*p_x*(1+k_{ud}*r^2)+u_0 \f$ and \f$ v = y*p_y*(1+k_{ud}*r^2)+v_0 + \f$ with \f$ r^2 = x^2+y^2 \f$ in the case of perspective projection with + distortion. + + In the case of a projection with Kannala-Brandt distortion, refer to + \cite KannalaBrandt. + */ + inline static void convertPoint(const vpCameraParameters &cam, const double &x, const double &y, double &u, double &v) + { + switch (cam.m_projModel) { + case vpCameraParameters::perspectiveProjWithoutDistortion: + convertPointWithoutDistortion(cam, x, y, u, v); + break; + case vpCameraParameters::perspectiveProjWithDistortion: + convertPointWithDistortion(cam, x, y, u, v); + break; + case vpCameraParameters::ProjWithKannalaBrandtDistortion: + convertPointWithKannalaBrandtDistortion(cam, x, y, u, v); + break; + default: { + throw(vpException(vpException::fatalError, "Unsupported camera projection model in vpMeterPixelConversion::convertPoint()")); + } + } + } + + /*! + + Point coordinates conversion from normalized coordinates + \f$(x,y)\f$ in meter in the image plane to pixel coordinates in the image using ViSP camera parameters. + + The used formula depends on the projection model of the camera. To + know the currently used projection model use + vpCameraParameter::get_projModel() + + \param[in] cam : camera parameters. + \param[in] x : input coordinate in meter along image plane x-axis. + \param[in] y : input coordinate in meter along image plane y-axis. + \param[out] iP : output coordinates in pixels. + + In the frame (u,v) the result is given by: + + \f$ u = x*p_x + u_0 \f$ and \f$ v = y*p_y + v_0 \f$ in the case of + perspective projection without distortion. + + \f$ u = x*p_x*(1+k_{ud}*r^2)+u_0 \f$ and \f$ v = y*p_y*(1+k_{ud}*r^2)+v_0 + \f$ with \f$ r^2 = x^2+y^2 \f$ in the case of perspective projection with + distortion. + + In the case of a projection with Kannala-Brandt distortion, refer to + \cite KannalaBrandt. + */ + + inline static void convertPoint(const vpCameraParameters &cam, const double &x, const double &y, vpImagePoint &iP) + { + switch (cam.m_projModel) { + case vpCameraParameters::perspectiveProjWithoutDistortion: + convertPointWithoutDistortion(cam, x, y, iP); + break; + case vpCameraParameters::perspectiveProjWithDistortion: + convertPointWithDistortion(cam, x, y, iP); + break; + case vpCameraParameters::ProjWithKannalaBrandtDistortion: + convertPointWithKannalaBrandtDistortion(cam, x, y, iP); + break; + default: { + throw(vpException(vpException::fatalError, "Unsupported camera projection model in vpMeterPixelConversion::convertPoint()")); + } + } + } + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + /*! + + Point coordinates conversion without distortion from + normalized coordinates \f$(x,y)\f$ in meter to pixel coordinates + \f$(u,v)\f$. + + \f$ u = x*p_x+u_0 \f$ and \f$ v = y*p_y+v_0 \f$ + */ + + inline static void convertPointWithoutDistortion(const vpCameraParameters &cam, const double &x, const double &y, + double &u, double &v) + { + u = (x * cam.m_px) + cam.m_u0; + v = (y * cam.m_py) + cam.m_v0; + } + + /*! + + Point coordinates conversion without distortion from + normalized coordinates \f$(x,y)\f$ in meter to pixel coordinates. + + In the frame (u,v) the result is given by: + + \f$ u = x*p_x+u_0 \f$ and \f$ v = y*p_y+v_0 \f$ + */ + + inline static void convertPointWithoutDistortion(const vpCameraParameters &cam, const double &x, const double &y, + vpImagePoint &iP) + { + iP.set_u((x * cam.m_px) + cam.m_u0); + iP.set_v((y * cam.m_py) + cam.m_v0); + } + + /*! + + Point coordinates conversion with distortion from + normalized coordinates \f$(x,y)\f$ in meter to pixel coordinates + \f$(u,v)\f$. + + \param[in] cam : camera parameters. + \param[in] x : input coordinate in meter along image plane x-axis. + \param[in] y : input coordinate in meter along image plane y-axis. + \param[out] u : output coordinate in pixels along image horizontal axis. + \param[out] v : output coordinate in pixels along image vertical axis. + + \f$ u = x*p_x*(1+k_{ud}*r^2)+u_0 \f$ and + \f$ v = y*p_y*(1+k_{ud}*r^2)+v_0 \f$ + with \f$ r^2 = x^2+y^2 \f$ + */ + inline static void convertPointWithDistortion(const vpCameraParameters &cam, const double &x, const double &y, + double &u, double &v) + { + double r2 = 1. + (cam.m_kud * ((x * x) + (y * y))); + u = cam.m_u0 + (cam.m_px * x * r2); + v = cam.m_v0 + (cam.m_py * y * r2); + } + + /*! + + Point coordinates conversion with distortion from + normalized coordinates \f$(x,y)\f$ in meter to pixel coordinates. + + \param[in] cam : camera parameters. + \param[in] x : input coordinate in meter along image plane x-axis. + \param[in] y : input coordinate in meter along image plane y-axis. + \param[out] iP : output coordinates in pixels. + + In the frame (u,v) the result is given by: + + \f$ u = x*p_x*(1+k_{ud}*r^2)+u_0 \f$ and + \f$ v = y*p_y*(1+k_{ud}*r^2)+v_0 \f$ + with \f$ r^2 = x^2+y^2 \f$ + */ + inline static void convertPointWithDistortion(const vpCameraParameters &cam, const double &x, const double &y, + vpImagePoint &iP) + { + double r2 = 1. + (cam.m_kud * ((x * x) + (y * y))); + iP.set_u(cam.m_u0 + (cam.m_px * x * r2)); + iP.set_v(cam.m_v0 + (cam.m_py * y * r2)); + } + + /*! + Point coordinates conversion with Kannala-Brandt distortion from + normalized coordinates \f$(x,y)\f$ in meter to pixel coordinates + \f$(u,v)\f$. + + \param[in] cam : camera parameters. + \param[in] x : input coordinate in meter along image plane x-axis. + \param[in] y : input coordinate in meter along image plane y-axis. + \param[out] u : output coordinate in pixels along image horizontal axis. + \param[out] v : output coordinate in pixels along image vertical axis. + + \f$ r = sqrt{x^2 + y^2} \f$ + \f$ \theta = \arctan{r} \f$ + Calculate \f$ r_d \f$ knowing distortion coefficients as follows: + \f$ r_d = \theta + k_1 \theta^3 + k_2 \theta^5 + k_3 \theta^7 + k_4 \theta^9 \f$ + \f$ scale = r_d / r \f$ + \f$ x_d = x * scale \f$ + \f$ y_d = y * scale \f$ + \f$ u = x_d*p_x+u_0 \f$ and + \f$ v = y_d*p_y+v_0 \f$ + with \f$ r^2 = x^2+y^2 \f$ + */ + inline static void convertPointWithKannalaBrandtDistortion(const vpCameraParameters &cam, const double &x, + const double &y, double &u, double &v) + { + double r = sqrt(vpMath::sqr(x) + vpMath::sqr(y)); + double theta = atan(r); + const unsigned int index_0 = 0; + const unsigned int index_1 = 1; + const unsigned int index_2 = 2; + const unsigned int index_3 = 3; + + std::vector k = cam.getKannalaBrandtDistortionCoefficients(); + + double theta2 = theta * theta, theta3 = theta2 * theta, theta4 = theta2 * theta2, theta5 = theta4 * theta, + theta6 = theta3 * theta3, theta7 = theta6 * theta, theta8 = theta4 * theta4, theta9 = theta8 * theta; + + double r_d = theta + (k[index_0] * theta3) + (k[index_1] * theta5) + (k[index_2] * theta7) + (k[index_3] * theta9); + + double scale = (std::fabs(r) < std::numeric_limits::epsilon()) ? 1.0 : (r_d / r); + + double x_d = x * scale; + double y_d = y * scale; + + u = (cam.m_px * x_d) + cam.m_u0; + v = (cam.m_py * y_d) + cam.m_v0; + } + + /*! + Point coordinates conversion with Kannala-Brandt distortion from + normalized coordinates \f$(x,y)\f$ in meter to pixel coordinates + \f$(u,v)\f$. + + \param[in] cam : camera parameters. + \param[in] x : input coordinate in meter along image plane x-axis. + \param[in] y : input coordinate in meter along image plane y-axis. + \param[out] iP : output coordinates in pixels. + + \f$ r = sqrt{x^2 + y^2} \f$ + \f$ \theta = \arctan{r} \f$ + Calculate \f$ r_d \f$ knowing distortion coefficients as follows: + \f$ r_d = \theta + k_1 \theta^3 + k_2 \theta^5 + k_3 \theta^7 + k_4 \theta^9 \f$ + \f$ scale = r_d / r \f$ + \f$ x_d = x * scale \f$ + \f$ y_d = y * scale \f$ + \f$ u = x_d*p_x+u_0 \f$ and + \f$ v = y_d*p_y+v_0 \f$ + with \f$ r^2 = x^2+y^2 \f$ + */ + inline static void convertPointWithKannalaBrandtDistortion(const vpCameraParameters &cam, const double &x, + const double &y, vpImagePoint &iP) + { + double r = sqrt(vpMath::sqr(x) + vpMath::sqr(y)); + double theta = atan(r); + const unsigned int index_0 = 0; + const unsigned int index_1 = 1; + const unsigned int index_2 = 2; + const unsigned int index_3 = 3; + + std::vector k = cam.getKannalaBrandtDistortionCoefficients(); + + double theta2 = theta * theta, theta3 = theta2 * theta, theta4 = theta2 * theta2, theta5 = theta4 * theta, + theta6 = theta3 * theta3, theta7 = theta6 * theta, theta8 = theta4 * theta4, theta9 = theta8 * theta; + + double r_d = theta + (k[index_0] * theta3) + (k[index_1] * theta5) + (k[index_2] * theta7) + (k[index_3] * theta9); + + double scale = (std::fabs(r) < std::numeric_limits::epsilon()) ? 1.0 : (r_d / r); + + double x_d = x * scale; + double y_d = y * scale; + + iP.set_u((cam.m_px * x_d) + cam.m_u0); + iP.set_v((cam.m_py * y_d) + cam.m_v0); + } + +#endif // #ifndef DOXYGEN_SHOULD_SKIP_THIS + //@} + +#if defined(VISP_HAVE_OPENCV) && \ + (((VISP_HAVE_OPENCV_VERSION < 0x050000) && defined(HAVE_OPENCV_CALIB3D)) || \ + ((VISP_HAVE_OPENCV_VERSION >= 0x050000) && defined(HAVE_OPENCV_CALIB) && defined(HAVE_OPENCV_3D))) + /** @name Using OpenCV camera parameters */ + //@{ + static void convertEllipse(const cv::Mat &cameraMatrix, const vpCircle &circle, vpImagePoint ¢er, double &n20_p, + double &n11_p, double &n02_p); + static void convertEllipse(const cv::Mat &cameraMatrix, const vpSphere &sphere, vpImagePoint ¢er, double &n20_p, + double &n11_p, double &n02_p); + static void convertEllipse(const cv::Mat &cameraMatrix, double xc_m, double yc_m, double n20_m, double n11_m, + double n02_m, vpImagePoint ¢er_p, double &n20_p, double &n11_p, double &n02_p); + static void convertLine(const cv::Mat &cameraMatrix, const double &rho_m, const double &theta_m, double &rho_p, + double &theta_p); + static void convertPoint(const cv::Mat &cameraMatrix, const cv::Mat &distCoeffs, const double &x, const double &y, + double &u, double &v); + static void convertPoint(const cv::Mat &cameraMatrix, const cv::Mat &distCoeffs, const double &x, const double &y, + vpImagePoint &iP); + //@} +#endif +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMoment.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMoment.h new file mode 100644 index 00000000..df8783af --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMoment.h @@ -0,0 +1,164 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Base for 2D moment descriptor + */ + +/*! + \file vpMoment.h + \brief Base class for all 2D moments. +*/ + +#ifndef _vpMoment_h_ +#define _vpMoment_h_ + +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +class vpMomentDatabase; +class vpMomentObject; + +/*! + * \class vpMoment + * + * \ingroup group_core_moments + * + * \brief This class defines shared methods/attributes for 2D moments. + * + * All moments or combination of moments in the moments module are based on + * this class. A moment uses a vpMomentObject object to access all useful + * information. Moment values are obtained by a 4-step process common for all + * moment types: + * - Declaration. + * \code + * vpMoment moment; + * \endcode + * - Update with object. + * \code + * moment.update(object); + * \endcode + * - Compute the moment value + * \code + * moment.compute(); + * \endcode + * - Access the values: + * \code + * std::vector values = moment.get(); + * \endcode + * + * A moment may also be linked to a vpMomentDatabase. Moments linked to a + * database are able to access each others values. Some moments can be computed + * only if they are linked to a a database containing their dependencies. + * Linking to a database is done using the vpMoment::linkTo(...) method. + * + * There are no constraints about format of the array returned by + * vpMoment::get(); any implementation is fine. + * + * Each moment must have a string name by implementing the char* + * vpMoment::name() method which allows to identify the moment in the database. + * Each moment must also implement a compute method describing how to obtain + * its values from the object. + * + * \attention Order of moment computation DOES matter: when you compute a + * moment using vpMoment::compute(), all moment dependencies must be computed. + * We recall that implemented moments are: + * - vpMomentAlpha + * - vpMomentArea + * - vpMomentAreaNormalized + * - vpMomentBasic + * - vpMomentCentered + * - vpMomentCInvariant + * - vpMomentGravityCenter + * - vpMomentGravityCenterNormalized +*/ +class VISP_EXPORT vpMoment +{ +private: + vpMomentObject *object; + vpMomentDatabase *moments; + std::string m_name; + +protected: + std::vector values; + /*! + * Returns the linked moment database. + * \return the moment database + */ + inline vpMomentDatabase &getMoments() const { return *moments; } + + // private: + //#ifndef DOXYGEN_SHOULD_SKIP_THIS + // vpMoment(const vpMoment &) + // : object(nullptr), moments(nullptr), values() + // { + // throw vpException(vpException::functionNotImplementedError,"Not + // implemented!"); + // } + // vpMoment &operator=(const vpMoment &){ + // throw vpException(vpException::functionNotImplementedError,"Not + // implemented!"); return *this; + // } + //#endif + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpMoment(const vpMoment &) = delete; // non construction-copyable + vpMoment &operator=(const vpMoment &) = delete; // non copyable +#endif + +public: + vpMoment(); + + /*! + Virtual destructor. + */ + virtual ~vpMoment() { } + + /** @name Inherited functionalities from vpMoment */ + //@{ + virtual void compute() = 0; + inline const vpMomentObject &getObject() const { return *object; } + /*! + * Returns all values computed by the moment. + * \return vector of values + */ + const std::vector &get() const { return values; } + void linkTo(vpMomentDatabase &moments); + virtual const std::string name() const = 0; + virtual void printDependencies(std::ostream &os) const; + void update(vpMomentObject &object); + //@} + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpMoment &m); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentAlpha.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentAlpha.h new file mode 100644 index 00000000..6bd2f72d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentAlpha.h @@ -0,0 +1,258 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Alpha moment descriptor for in-plane orientation. + */ + +/*! + \file vpMomentAlpha.h + \brief Alpha moment descriptor for in-plane orientation. +*/ + +#ifndef VP_MOMENT_ALPHA_H +#define VP_MOMENT_ALPHA_H + +#include +#include + +BEGIN_VISP_NAMESPACE + +/*! + * \class vpMomentAlpha + * + * \ingroup group_core_moments + * + * \brief This class defines the orientation of the object inside the plane + * parallel to the object. + * + * In general the value of the moment is computed in \f$ [-\pi/2 ; \pi/2] \f$ + * interval by the formula \f$ \alpha = \frac{1}{2} + * \mathrm{atan2}(2\mu_{11}, \mu_{20}-\mu_{02}) \f$. + * + * To obtain a \f$ [-\pi ; \pi] \f$ precision for non symmetric object, you + * have to specify a reference information. This reference information is an + * alpha computed using the previous formula in \f$ [-\pi/2 ; \pi/2] \f$. + * Obtaining this precision comes from third-order centered moments and this + * reference information. + * + * Therefore there are two modes for vpMomentAlpha and one constructor per + * mode: + * - Reference mode using the empty constructor vpMomentAlpha(): + * The vpMomentAlpha doesn't need any additional information, it will compute + * its values from available moments in \f$ [-\pi/2 ; \pi/2] \f$. + * - Relative mode using non-empty constructor + * vpMomentAlpha(std::vector&, double): The vpMomentAlpha is computed in + * \f$ [-\pi ; \pi] \f$ from the available moments and the reference + * information. By knowing the reference, it may distinguish in-plane rotations + * of \f$ \alpha \f$ from rotations of \f$ \alpha + \pi \f$. + * + * The following code demonstrates a calculation of a reference alpha and then + * uses this alpha to estimate the orientation of the same object after + * performing a 180 degrees rotation. Therefore the first and second alpha differ by 180 degrees. + * + * \code + * #include + * #include + * #include + * #include + * #include + * #include + * + * //generic function for printing + * void print (double i) { std::cout << i << "\t";} + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpPoint p; + * std::vector vec_p; // Vector that contains the vertices of the contour polygon + * p.set_x(1); p.set_y(1); // Coordinates in meters in the image plane (vertex 1) + * vec_p.push_back(p); + * p.set_x(2); p.set_y(2); // Coordinates in meters in the image plane (vertex 2) + * vec_p.push_back(p); + * p.set_x(-3); p.set_y(0); // Coordinates in meters in the image plane (vertex 3) + * vec_p.push_back(p); + * p.set_x(-3); p.set_y(-1); // Coordinates in meters in the image plane (vertex 4) + * vec_p.push_back(p); + * + * //////////////////////////////REFERENCE VALUES//////////////////////////////// + * vpMomentObject objRef(3); // Reference object. Must be of order 3 because we will + * // need the 3rd order centered moments + * + * objRef.setType(vpMomentObject::DENSE_POLYGON); // Object is the inner part of a polygon + * objRef.fromVector(vec_p); // Init the dense object with the polygon + * + * vpMomentDatabase dbRef; // Reference database + * vpMomentGravityCenter gRef; // Declaration of gravity center + * vpMomentCentered mcRef; // Centered moments + * vpMomentAlpha alphaRef; // Declare alpha as reference + * + * gRef.linkTo(dbRef); // Add gravity center to database + * mcRef.linkTo(dbRef); // Add centered moments + * alphaRef.linkTo(dbRef); // Add alpha depending on centered moments + * + * dbRef.updateAll(objRef); // All of the moments must be updated, not just alpha + * + * gRef.compute(); // Compute the moment + * mcRef.compute(); // Compute centered moments AFTER gravity center + * alphaRef.compute(); // Compute alpha AFTER centered moments. + * + * // The order of values in the vector must be as follows: mu30 mu21 mu12 mu03 + * std::vector mu3ref = {mcRef.get(3,0), mcRef.get(2,1), mcRef.get(1,2), mcRef.get(0,3)}; + * + * std::cout << "--- Reference object ---" << std::endl; + * std::cout << "alphaRef=" << vpMath::deg(alphaRef.get()) << " deg" << std::endl << "mu3="; // print reference alpha + * std::for_each (mu3ref.begin(), mu3ref.end(), print); + * std::cout << std::endl; + * + * ////////////CURRENT VALUES (same object rotated 180deg)//////////////// + * vec_p.clear(); + * + * p.set_x(-1); p.set_y(-1); // Coordinates in meters in the image plane (vertex 4) + * vec_p.push_back(p); + * p.set_x(-2); p.set_y(-2); // Coordinates in meters in the image plane (vertex 3) + * vec_p.push_back(p); + * p.set_x(3); p.set_y(-0); // Coordinates in meters in the image plane (vertex 2) + * vec_p.push_back(p); + * p.set_x(3); p.set_y(1); // Coordinates in meters in the image plane (vertex 1) + * vec_p.push_back(p); + * + * vpMomentObject obj(3); // Second object. Order 3 is also required because of the Alpha + * // will compare third-order centered moments to given reference. + * + * obj.setType(vpMomentObject::DENSE_POLYGON); // Object is the inner part of a polygon + * obj.fromVector(vec_p); // Init the dense object with the polygon + * + * vpMomentDatabase db; // Database + * vpMomentGravityCenter g; // Declaration of gravity center + * vpMomentCentered mc; // mc contains centered moments + * vpMomentAlpha alpha(mu3ref, alphaRef.get()); // Declare alpha as relative to a reference + * + * g.linkTo(db); // Add gravity center to database + * mc.linkTo(db); // Add centered moments + * alpha.linkTo(db); // Add alpha depending on centered moments + * + * db.updateAll(obj); // All of the moments must be updated + * + * g.compute(); // Compute the moment + * mc.compute(); // Compute centered moments AFTER gravity center + * alpha.compute(); // Compute alpha AFTER centered moments. + * + * std::cout << "--- current object ---" << std::endl; + * std::cout << "alpha=" << vpMath::deg(alpha.get()) << " deg" << std::endl; + * + * return 0; + * } + * \endcode + * This program outputs: + * \code + * --- Reference object --- + * alphaRef=25.3019 deg + * mu3=1.80552 0.921882 0.385828 0.122449 + * --- current object --- + * alpha=-154.698 deg + * \endcode + * + * There is also testMomentAlpha.cpp example that shows how to compute alpha in the range \f$ [-\pi ; \pi] \f$ + * using arrow images as input. The code is given below: + * \include testMomentAlpha.cpp + * + * From the first image we compute the 3rd order centered moments and the value of the reference alpha + * that is than used to compute the alpha moment in the range \f$ [-\pi ; \pi] \f$. Running this example you will get: + * \code + * alpha expected 0 computed -0.128108 deg + * alpha expected 45 computed 44.8881 deg + * alpha expected 90 computed 89.8719 deg + * alpha expected 135 computed 134.888 deg + * alpha expected 180 computed 179.872 deg + * alpha expected -135 computed -135.112 deg + * alpha expected -90 computed -90.1281 deg + * alpha expected -45 computed -45.1119 deg + * \endcode + * + * Shortcuts for quickly getting those references exist in vpMomentCommon. + * + * This moment depends on vpMomentCentered. +*/ +class VISP_EXPORT vpMomentAlpha : public vpMoment +{ +private: + bool m_isRef; + bool m_symmetric; + std::vector m_mu3Ref; + double m_alphaRef; + double m_symmetricThreshold; + +public: + vpMomentAlpha(); + vpMomentAlpha(const std::vector &mu3_ref, double alpha_ref, double threshold = 1e-6); + + void compute() VP_OVERRIDE; + /*! + * Retrieve the orientation of the object as a single double value. + */ + double get() const { return values[0]; } + /*! + * Moment name. + */ + const std::string name() const VP_OVERRIDE { return "vpMomentAlpha"; } + + /*! + * Returns true if the alpha moment was constructed as a reference with values in \f$ [-\pi/2 ; \pi/2] \f$, false + * otherwise. + */ + inline bool is_ref() const + { + if (m_isRef) + return true; + else + return false; + } + + /*! + * Returns true if the alpha moment is computed on a symmetric object along its two axis. + * Symmetry is computed using 3rd order centered moments \f$\mu_{30},\mu_{21},\mu_{12},\mu_{03}\f$. + */ + inline bool is_symmetric() const + { + if (m_symmetric) + return true; + else + return false; + } + + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpMomentAlpha &v); + void printDependencies(std::ostream &os) const VP_OVERRIDE; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentArea.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentArea.h new file mode 100644 index 00000000..28aae1a3 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentArea.h @@ -0,0 +1,74 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Just the area m00 = mu00 + */ + +#ifndef VP_MOMENT_AREA_H +#define VP_MOMENT_AREA_H + +#include +#include + +BEGIN_VISP_NAMESPACE +class vpMomentObject; +class vpMomentCentered; // Required for discrete case of vpMomentObject + +/*! + * \class vpMomentArea + * + * \ingroup group_core_moments + * + * \brief Class handling the surface moment. + * + * For a dense planar object, the area corresponds to the zero-order moment: + * \f[ a = m_{00} = \mu_{00} \f] + * + * When considering a discrete set of points, the moment \f$ m_{00} \f$ simply + * corresponds to the number of points. Since this is of no use in a servoing + * scheme, this class uses in this case \f$ a = \mu_{20} + \mu_{02} \f$, which is + * invariant to planar translation and rotation. +*/ +class VISP_EXPORT vpMomentArea : public vpMoment +{ +public: + vpMomentArea(); + + /** @name Inherited functionalities from vpMomentArea */ + //@{ + void compute() VP_OVERRIDE; + //! Moment name. + const std::string name() const VP_OVERRIDE { return "vpMomentArea"; } + void printDependencies(std::ostream &os) const VP_OVERRIDE; + //@} + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpMomentArea &m); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentAreaNormalized.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentAreaNormalized.h new file mode 100644 index 00000000..35ddbd01 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentAreaNormalized.h @@ -0,0 +1,191 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * 2D normalized surface moment descriptor (usually described as An) + */ +/*! + * \file vpMomentAreaNormalized.h + * \brief 2D normalized surface moment descriptor (usually described as An). + */ +#ifndef VP_MOMENT_AREA_NORMALIZED_H +#define VP_MOMENT_AREA_NORMALIZED_H + +#include +#include + +BEGIN_VISP_NAMESPACE +class vpMomentObject; +class vpMomentCentered; + +/*! + * \class vpMomentAreaNormalized + * + * \ingroup group_core_moments + * + * \brief Class handling the normalized surface moment that is invariant in + * scale and used to estimate depth. + * + * This moment depends on vpMomentCentered. + * + * The idea behind vpMomentAreaNormalized is described in \cite Tahri05z. + * + * During a visual servoing process, a vpMomentAreaNormalized will converge + * towards the desired depth when the current surface will converge to the + * destination surface. It is defined as follows: \f$ a_n=Z^* + * \sqrt{\frac{a^*}{a}} \f$ where \e a is the current surface and \e a* the + * destination surface. Consequently, the vpMomentAreaNormalized needs to have + * information about the desired depth \e Z* and the desired surface \e a*. + * + * \warning About the order of the object. + * The surface (referred to as \e a in the above paragraph) depends of the + * nature of the object. + * - In case of a continuous object (when vpMomentObject::getType() is + * vpMomentObject::DENSE_FULL_OBJECT or vpMomentObject::DENSE_POLYGON) + * \f$a=m_{00}\f$. + * - In case of a discrete object (when vpMomentObject::getType() is + * vpMomentObject::DISCRETE) \f$a=\mu_{20}+\mu_{02}\f$. + * + * Therefore, a vpMomentObject has to be of minimum order 2 in order to compute + * a vpMomentAreaNormalized moment in the discrete case and of minimum order 0 in + * continuous cases. + * + * This example shows a computation in the discrete case. + * \code + * #include + * #include + * #include + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpPoint p; + * std::vector vec_p; // vector that contains object points + * + * p.set_x(1); p.set_y(1); // coordinates in meters in the image plane of point 1 + * vec_p.push_back(p); + * p.set_x(2); p.set_y(2); // coordinates in meters in the image plane of point 2 + * vec_p.push_back(p); + * + * //////////////////////////////REFERENCE VALUES//////////////////////////////// + * vpMomentObject obj(2); // Object where all the moment defined with + * // i+j <= 2 will be computed below. Order is + * // 2 because in discrete mode, the surface + * // computation is a=mu02+mu02 + * + * obj.setType(vpMomentObject::DISCRETE); // Discrete mode for object + * obj.fromVector(vec_p); // initialize the object with the points coordinates + * + * vpMomentDatabase db; //reference database + * vpMomentGravityCenter g; // declaration of gravity center + * vpMomentCentered mc; // centered moments + * vpMomentAreaNormalized an(2,1); //declare normalized surface with + * //destination depth of 1 meter and + * //destination surface of 2 m2 + * + * g.linkTo(db); //add gravity center to database + * mc.linkTo(db); //add centered moments + * an.linkTo(db); //add alpha depending on centered moments + * + * db.updateAll(obj); // All of the moments must be updated, not just an + * + * g.compute(); // compute the moment + * mc.compute(); //compute centered moments AFTER gravity center + * an.compute(); //compute alpha AFTER centered moments. + * + * std::cout << an << std::endl; + * + * return 0; + * } + * \endcode + * This code produces the following output: + * \code + * An:1.41421 + * \endcode +*/ +class VISP_EXPORT vpMomentAreaNormalized : public vpMoment +{ +private: + double desiredSurface; + double desiredDepth; + +public: + vpMomentAreaNormalized(double a_star, double Z_star); + void compute() VP_OVERRIDE; + + /*! + * Retrieves the desired surface \e a* as specified in the constructor. + */ + double getDesiredArea() const { return desiredSurface; } + + /*! + * Retrieves the desired depth \e Z* as specified in the constructor. + */ + double getDesiredDepth() const { return desiredDepth; } + + /*! + * Set the desired depth \e Z* to a new value than the one specified in the constructor. + * This value has to be set before calling compute(). + */ + void setDesiredDepth(double Z_star) { desiredDepth = Z_star; } + + /*! + * Set the desired area \e a* to a new value than the one specified in the constructor. + * This value has to be set before calling compute(). + */ + void setDesiredArea(double a_star) { desiredSurface = a_star; } + +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) + /*! + * @name Deprecated functions + */ + //@{ + /*! + * \deprecated Use rather getDesiredArea() + * Retrieves the desired surface \e a* as specified in the constructor. + */ + VP_DEPRECATED double getDesiredSurface() const { return desiredSurface; } + //@} +#endif + + /*! + * Moment name. + */ + const std::string name() const VP_OVERRIDE { return "vpMomentAreaNormalized"; } + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpMomentAreaNormalized &v); + void printDependencies(std::ostream &os) const VP_OVERRIDE; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentBasic.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentBasic.h new file mode 100644 index 00000000..95ab10f0 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentBasic.h @@ -0,0 +1,88 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Basic moment descriptor + */ + +/*! + \file vpMomentBasic.h + \brief Basic moment descriptor (also called m). +*/ + +#ifndef VP_MOMENT_BASIC_H +#define VP_MOMENT_BASIC_H + +#include +#include + +BEGIN_VISP_NAMESPACE + +/*! + \class vpMomentBasic + + \ingroup group_core_moments + + \brief This class defines the 2D basic moment \f$m_{ij}\f$. + This class is a wrapper for vpMomentObject which allows to use it as a + moment. This enables all common operations such as adding to a + vpMomentDatabase. + + In the case of a dense object \e O, 2D basic moments are defined by: + \f[m_{ij} = \int \int_{O} x^j y^j dx dy\f] + + In the case of a discrete set of \e n points, the basic moments are defined + by \f[m_{ij} = \sum_{k=1}^{n} x_k^j y_k^j \f] + + The selection of a dense or discrete object is done in vpMomentObject. + The maximum order of vpMomentBasic is the same as that of vpMomentObject + (ie: \f$i+j \leq \f$ vpMomentObject::getOrder). + + Note that in the continuous case, the basic moment \f$m_{00}\f$ corresponds + to the surface \f$a\f$ of the object. In the discrete case, it is the number + of discrete points \f$n\f$. +*/ + +class VISP_EXPORT vpMomentBasic : public vpMoment +{ +public: + vpMomentBasic(); + + void compute() VP_OVERRIDE; + const std::vector &get() const; + double get(unsigned int i, unsigned int j) const; + /*! + Moment name. + */ + const std::string name() const VP_OVERRIDE { return "vpMomentBasic"; } + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpMomentBasic &v); + void printDependencies(std::ostream &os) const VP_OVERRIDE; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentCInvariant.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentCInvariant.h new file mode 100644 index 00000000..9227f23a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentCInvariant.h @@ -0,0 +1,288 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Descriptor for various invariants used to drive space rotations around X and + * Y axis. + */ + +/*! + \file vpMomentCInvariant.h + \brief Descriptor for various invariants used to drive space rotations around + X and Y axis. +*/ +#ifndef VP_MOMENT_CINVARIANT_H +#define VP_MOMENT_CINVARIANT_H + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +class vpMomentCentered; +class vpMomentBasic; + +/*! + \class vpMomentCInvariant + + \ingroup group_core_moments + + This class defines several 2D (translation+rotation+scale) invariants for + both symmetric and non-symmetric objects. These moment-based invariants are + described in the following papers \cite Chaumette04a, \cite Tahri05z. + + The descriptions for the invariants \f$C_1\f$ to \f$C_{10}\f$ can be found + in \cite Chaumette04a and for invariants + \f$P_x\f$,\f$P_y\f$,\f$S_x\f$,\f$S_y\f$ in \cite Tahri05z. + + These invariants are classicaly used in visual servoing to control the + out-of-plane rotations. The C-type or P-type invariants are used for + non-symmetric objects whereas the S-type invariants are used for symmetric + objects. + + For most cases of non-symmetric objects, (\f$C_4\f$,\f$C_6\f$) or + (\f$P_x\f$,\f$P_y\f$) couples are widely used to control x and y rotations. + For symmetric objects \f$S_x\f$ and \f$S_y\f$ are the only choice. + + There are 14 translation+rotation+scale invariants (10 C-type, 2 P-type and + 2 S-type) that can be accessed from by vpMomentCInvariant::get or any of the + get shortcuts. + + The example below shows how to retrieve the \f$C_2\f$ invariant: + \code + #include + #include + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpPoint p; + std::vector vec_p; + + p.set_x(6); p.set_y(-1); // coordinates in meters in the image plane (vertex 1) + vec_p.push_back(p); + p.set_x(2); p.set_y(3); // coordinates in meters in the image plane (vertex 2) + vec_p.push_back(p); + p.set_x(0); p.set_y(1.2); // coordinates in meters in the image plane (vertex 1) + vec_p.push_back(p); + p.set_x(-7); p.set_y(-4); // coordinates in meters in the image plane (vertex 2) + vec_p.push_back(p); + + vpMomentObject obj(5); // Create an image moment object with 5 as maximum order + obj.setType(vpMomentObject::DISCRETE); // Discrete mode for object + obj.fromVector(vec_p); + + //initialisation with default values + vpMomentCommon db(vpMomentCommon::getSurface(obj),vpMomentCommon::getMu3(obj), + vpMomentCommon::getAlpha(obj),1.); + bool success; + + db.updateAll(obj); // Update AND compute all moments + + //get C-invariant + const vpMomentCInvariant& C + = static_cast(db.get("vpMomentCInvariant",success)); + if(success) + std::cout << C.get(1) << std:: endl; // print C2 invariant + else + std::cout << "vpMomentCInvariant not found." << std::endl; + + return 0; + } + \endcode + + vpMomentCInvariant depends on vpMomentCentered (see vpMomentDatabase and vpMomentCommon). +*/ +class VISP_EXPORT vpMomentCInvariant : public vpMoment +{ +private: + std::vector I; + std::vector II; + std::vector c; + std::vector s; + double K; + void computeI(const vpMomentCentered &momentCentered, std::vector &I); + + /* To calculate Sx and Sy from normalized moments */ + void calcSxSy(double &sx, double &sy) const; + void calcSxSyNormalized(double &sx, double &sy) const; + std::vector cn; // same as s above but calculated from normalized moments + std::vector sn; // same as c above but calculated from normalized moments + double In1; // same as I1 in Sx,Sy formulae but calculated from normalized + // moments + bool flg_sxsynormalization_; + +public: + VP_EXPLICIT vpMomentCInvariant(bool flg_sxsynormalization = false); + + /*! + Shorcut for getting the value of \f$C_1\f$. + */ + double C1() const { return values[0]; } + /*! + Shorcut for getting the value of \f$C_2\f$. + */ + double C2() const { return values[1]; } + /*! + Shorcut for getting the value of \f$C_3\f$. + */ + double C3() const { return values[2]; } + /*! + Shorcut for getting the value of \f$C_4\f$. + */ + double C4() const { return values[3]; } + /*! + Shorcut for getting the value of \f$C_5\f$. + */ + double C5() const { return values[4]; } + /*! + Shorcut for getting the value of \f$C_6\f$. + */ + double C6() const { return values[5]; } + /*! + Shorcut for getting the value of \f$C_7\f$. + */ + double C7() const { return values[6]; } + /*! + Shorcut for getting the value of \f$C_8\f$. + */ + double C8() const { return values[7]; } + /*! + Shorcut for getting the value of \f$C_9\f$. + */ + double C9() const { return values[8]; } + /*! + Shorcut for getting the value of \f$C_{10}\f$. + */ + double C10() const { return values[9]; } + + void compute() VP_OVERRIDE; + + /*! + Gets the desired invariant. + \param i given index. For invariants from C1 to C10 the corresponding + index is from 0 to 9. For \f$S_x\f$,\f$S_y\f$ the indexes are 10,11 and + for \f$P_x\f$,\f$P_y\f$ they are 12,13. + */ + double get(unsigned int i) const { return values[i]; } + + /*! + Access to partial invariant c (see [2]). + */ + double getC(unsigned int i) const { return c[i]; } + /*! + Access to partial invariants. The index convention is the same as in [1]. + */ + double getI(unsigned int index) const { return I[index]; } + + /*! + Print the moment invariants used to obtain the actual visual features + */ + void printInvariants(std::ostream &os) const; + + /*! + Access to partial invariant I (see [2]). + */ + double getII(unsigned int i) const { return II[i]; } + /*! + Access to partial invariant K (see [2]). + */ + double getK() const { return K; } + + /*! + Access to partial invariant S (see [2]). + */ + double getS(unsigned int i) const { return s[i]; } + + /*! + Moment name. + */ + const std::string name() const VP_OVERRIDE { return "vpMomentCInvariant"; } + + /*! + Print partial invariant. + */ + void printI(unsigned int index); + + /*! + Shorcut for getting the value of \f$P_x\f$. + */ + double Px() { return values[12]; } + /*! + Shorcut for getting the value of\f$P_y\f$. + */ + double Py() { return values[13]; } + + /*! + Shorcut for getting the value of \f$S_x\f$. + */ + double Sx() const { return values[10]; } + /*! + Shorcut for getting the value of \f$S_y\f$. + */ + double Sy() const { return values[11]; } + + /*! + * Getters for I + * (calculated from normalized 2nd and 3ord order moments) + */ + double getIn1() const { return In1; } + + /*! + * Getter for c + * (calculated from normalized 2nd and 3ord order moments) + */ + double getCN(unsigned int i) const { return cn[i]; } + + /*! + * Getter for s + * (calculated from normalized 2nd and 3ord order moments) + */ + double getSN(unsigned int i) const { return sn[i]; } + + /*! + * To know if Sx and Sy were calculated from normalized moments or not + */ + bool isSxSyfromNormalizedMoments() const { return flg_sxsynormalization_; } + + /*! + * To get all the invariant values as a whole. + */ + inline const std::vector &getMomentVector() const { return values; } + + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpMomentCInvariant &v); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentCentered.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentCentered.h new file mode 100644 index 00000000..f6ae482c --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentCentered.h @@ -0,0 +1,133 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Centered moment descriptor + */ + +/*! + \file vpMomentCentered.h + \brief Centered moment descriptor (also referred as \f$\mu_{ij}\f$). +*/ + +#ifndef VP_MOMENT_CENTERED_H +#define VP_MOMENT_CENTERED_H + +#include +#include + +BEGIN_VISP_NAMESPACE +class vpMomentObject; + +/*! + \class vpMomentCentered + + \ingroup group_core_moments + + \brief This class defines the double-indexed centered moment descriptor + \f$\mu_{ij}\f$. + + In the case of a dense object \e O, centered moments are defined by: + \f[\mu_{ij}= \int \int_{O} (x_k-x_g)^j (y_k-y_g)^j\f] + + In the case of a discrete set of \e n points, centered moments are defined + by: \f[\mu_{ij}= \sum_{k=1}^{n} (x_k-x_g)^j (y_k-y_g)^j\f] + + where \f$(x_g,y_g)\f$ are the coordinates of the center of gravity. + + The centered moments are computed from the object at the highest possible + order. For example if the vpMomentObject is defined up to order 5, + vpMomentCentered will be too. + + Values of vpMomentCentered may be accessed by one of the two + vpMomentCentered::get methods. When using vpMomentCentered::get (), the + format of the return vector is the following: \f$ \mu_{ij} \f$ is stored at + vpMomentCentered::get ()[j* (vpMomentObject::getOrder () +1)+i] + + vpMomentCentered depends on vpMomentGravityCenter. + +*/ +class VISP_EXPORT vpMomentCentered : public vpMoment +{ +public: + vpMomentCentered(); + + void compute() VP_OVERRIDE; + double get(unsigned int i, unsigned int j) const; + + inline const std::vector &get() const; + /*! + Moment name. + */ + inline const std::string name() const VP_OVERRIDE { return "vpMomentCentered"; } + + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpMomentCentered &v); + void printWithIndices(std::ostream &os) const; + void printDependencies(std::ostream &os) const VP_OVERRIDE; + +protected: + void set(unsigned int i, unsigned int j, double value); +}; + +/*! + Returns all centered moment values \f$\mu_{ij}\f$ with \f$i+j \leq order\f$ +where order is the object's order. + + \return Vector of moment values. To access \f$\mu_{ij}\f$, you have to read +vpMomentObject::get()[j*order+i]. + + For example, if the maximal order is 3, the following values are provided: + + \code +u00 u10 u20 u01 u11 u21 u02 u12 u12 u30 u03 + \endcode + + To have a better reading of the moments you can picture them as a triangular + matrix: + + \code +u00 u10 u20 u30 u01 u11 u21 x u02 u12 x x u30 x x x + \endcode + + The moments of the same order are on each of the matrix reverse diagonals. + To access for example to the centered moment \f$\mu_{12}\f$, you should use + this kind of code: + + \code +vpMomentCentered mc; +//[...] +mc.compute(); +double mu12; +mu12 = mc.get()[2*(obj.getOrder()+1)+1]; // i=1 and j=2 +mu12 = mc.get(1,2); // the same + \endcode +*/ +inline const std::vector &vpMomentCentered::get() const { return vpMoment::get(); } +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentCommon.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentCommon.h new file mode 100644 index 00000000..472619ea --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentCommon.h @@ -0,0 +1,148 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Pre-filled moment database with all commonly used moments. + */ + +/*! + \file vpMomentCommon.h + \brief Pre-filled moment database with all commonly used moments. +*/ +#ifndef VP_MOMENT_COMMON_H +#define VP_MOMENT_COMMON_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +BEGIN_VISP_NAMESPACE +class vpMomentObject; + +/*! + \class vpMomentCommon + + \ingroup group_core_moments + + \brief This class initializes and allows access to commonly used moments. + + It is a vpMomentDatabase filled with the following moments: + - vpMomentBasic + - vpMomentGravityCenter + - vpMomentCentered + - vpMomentGravityCenterNormalized + - vpMomentAreaNormalized + - vpMomentCInvariant + - vpMomentAlpha + - vpMomentArea + + There is no need to do the linkTo operations manually nor is it necessary + to care about the order of moment computation. + + This class carries an vpMomentCommon::updateAll() method capable of + updating AND computing moments from an object (see 4-step process in + vpMoment). The moments computed by this class are classical moments used in + moment-based visual servoing. For more information see \cite Tahri05z. + + To initialize this moment set the user needs to compute the following + things: + - the Mu3 value set: set of third-order centered moments computed for a + reference object. (\f$\mu_{ij}$ with $i+j = 3\f$ ). These values allow the + system to save the reference angular position and to perform planar + rotations of more than 180 degrees if needed. + - the destination depth. + - the surface of the destination object in the end of the visual servoing + process. + - the reference alpha: angular position of the object used to obtain the + Mu3 set. + + Shortcuts for each of these prerequisites are provided by this class + except depth (methods vpMomentCommon::getMu3(), + vpMomentCommon::getSurface(), vpMomentCommon::getAlpha()). + + \attention Make sure your object is at least of order 5 when using this + pre-filled database. + +*/ +class VISP_EXPORT vpMomentCommon : public vpMomentDatabase +{ +private: + vpMomentBasic momentBasic; + vpMomentGravityCenter momentGravity; + vpMomentCentered momentCentered; + vpMomentGravityCenterNormalized momentGravityNormalized; + vpMomentAreaNormalized momentSurfaceNormalized; + vpMomentCInvariant *momentCInvariant; + vpMomentAlpha momentAlpha; + vpMomentArea momentArea; + + // private: + //#ifndef DOXYGEN_SHOULD_SKIP_THIS + // vpMomentCommon(const vpMomentCommon &) + // : vpMomentDatabase(), momentBasic(), momentGravity(), + // momentCentered(), momentGravityNormalized(), + // momentSurfaceNormalized(0, 0), momentCInvariant(), momentAlpha(), + // momentArea() + // { + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); + // } + // vpMomentCommon &operator=(const vpMomentCommon &){ + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); return *this; + // } + //#endif + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpMomentCommon(const vpMomentCommon &) = delete; // non construction-copyable + vpMomentCommon &operator=(const vpMomentCommon &) = delete; // non copyable +#endif + +public: + vpMomentCommon(double dstSurface, const std::vector &ref, double refAlpha, double dstZ = 1.0, + bool flg_sxsyfromnormalized = false); + + virtual ~vpMomentCommon() VP_OVERRIDE; + + static double getAlpha(vpMomentObject &object); + static std::vector getMu3(vpMomentObject &object); + static double getSurface(vpMomentObject &object); + + void updateAll(vpMomentObject &object) VP_OVERRIDE; +}; +END_VISP_NAMESPACE +#endif // VPCOMMONMOMENTS_H diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentDatabase.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentDatabase.h new file mode 100644 index 00000000..8499197f --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentDatabase.h @@ -0,0 +1,174 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Pseudo-database used to handle dependencies between moments + */ + +/*! + * \file vpMomentDatabase.h + * \brief Pseudo-database used to handle dependencies between moments. + */ +#ifndef VP_MOMENT_DATABASE_H +#define VP_MOMENT_DATABASE_H + +#include + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +class vpMoment; +class vpMomentObject; + +/*! + * \class vpMomentDatabase + * + * \ingroup group_core_moments + * + * \brief This class allows to register all vpMoments so they can access each + * other according to their dependencies. + * + * Sometimes, a moment needs to have access to other moment's values to be + * computed. For example vpMomentCentered needs additional information about the + * gravity center vpMomentGravityCenter in order to compute the moment's value + * from a vpMomentObject. This gravity center should be stored in a + * vpMomentDatabase where it can be accessed. + * + * All moments in a database can access each other freely at any time. They can + * also verify if a moment is present in the database or not. Here is a example + * of a dependency between two moments using a vpMomentDatabase: + * + * \code + * #include + * #include + * #include + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpPoint p; + * std::vector vec_p; // vector that contains the vertices of the contour polygon + * + * p.set_x(1); p.set_y(1); // coordinates in meters in the image plane (vertex 1) + * vec_p.push_back(p); + * p.set_x(2); p.set_y(2); // coordinates in meters in the image plane (vertex 2) + * vec_p.push_back(p); + * vpMomentObject obj(1); // Create an image moment object with 1 as + * // maximum order (sufficient for gravity center) + * obj.setType(vpMomentObject::DISCRETE); // The object is defined by + * // two discrete points + * obj.fromVector(vec_p); // Init the dense object with the polygon + * + * vpMomentDatabase db; + * vpMomentGravityCenter g; // declaration of gravity center + * vpMomentCentered mc; // mc contains centered moments + * + * g.linkTo(db); //add gravity center to database + * mc.linkTo(db); //centered moments depend on gravity, add them to the + * //database to grant access + * + * db.updateAll(obj); // All of the moments must be updated, not just mc + * + * //There is no global compute method since the order of compute calls + * //depends on the implementation + * g.compute(); // compute the moment + * mc.compute(); //compute centered moments AFTER gravity center + * + * std::cout << "Gravity center: " << g << std:: endl; // print gravity center moment + * std::cout << "Centered moments: " << mc << std:: endl; // print centered moment + * + * return 0; + * } + * \endcode + * + * The following code outputs: + * \code + * Gravity center: + * Xg=1.5, Yg=1.5 + * Centered moments: + * 2 0 + * 0 x + * \endcode + * + * A moment is identified in the database by it's vpMoment::name method. + * Consequently, a database can contain at most one moment of each type. Often it + * is useful to update all moments with the same object. Shortcuts + * (vpMomentDatabase::updateAll) are provided for that matter. +*/ +class VISP_EXPORT vpMomentDatabase +{ +private: +#ifndef DOXYGEN_SHOULD_SKIP_THIS + struct vpCmpStr_t + { + bool operator()(const std::string &a, const std::string &b) const { return std::strcmp(a.c_str(), b.c_str()) < 0; } + }; +#endif + std::map moments; + void add(vpMoment &moment, const std::string &name); + +public: + vpMomentDatabase() : moments() { } +#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98) + vpMomentDatabase(const vpMomentDatabase &) = default; +#endif + virtual ~vpMomentDatabase() { } + +#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98) + vpMomentDatabase &operator=(const vpMomentDatabase &) = default; +#endif + + /** @name Inherited functionalities from vpMomentDatabase */ + //@{ + const vpMoment &get(const std::string &moment_name, bool &found) const; + /*! + * Get the first element in the database. + * May be useful in case an unnamed object is present but is the only element + * in the database. + * + * \return the first element in the database. + */ + vpMoment &get_first() { return *(moments.begin()->second); } + + virtual void updateAll(vpMomentObject &object); + //@} + + friend class vpMoment; + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpMomentDatabase &v); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentGravityCenter.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentGravityCenter.h new file mode 100644 index 00000000..17d1cb3d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentGravityCenter.h @@ -0,0 +1,144 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * 2D Gravity Center moment descriptor (usually described by the pair Xg,Yg) + */ + +/*! + * \file vpMomentGravityCenter.h + * \brief 2D Gravity Center moment descriptor (usually described by the pair + * Xg,Yg). + */ +#ifndef VP_MOMENT_GRAVITY_CENTER_H +#define VP_MOMENT_GRAVITY_CENTER_H + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +class vpMomentObject; + +/*! + * \class vpMomentGravityCenter + * + * \ingroup group_core_moments + * + * \brief Class describing 2D gravity center moment. + * + * This moment can be computed from scratch (no need to compute any different + * moments before computing this). It gives access to both coordinates of the + * gravity center \f$x_g\f$ and \f$y_g\f$. + * + * These coordinates are defined as follows: \f$x_g = \frac{m_{01}}{m_{00}} + * \f$,\f$y_g = \frac{m_{10}}{m_{00}} \f$ + * \code + * #include + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * // Define the contour of an object by a 5 clockwise vertices on a plane + * vpPoint p; + * std::vector vec_p; // vector that contains the vertices of the contour polygon + * + * p.set_x(-0.2); p.set_y(0.1); // coordinates in meters in the image plane (vertex 1) + * vec_p.push_back(p); + * p.set_x(+0.3); p.set_y(0.1); // coordinates in meters in the image plane (vertex 2) + * vec_p.push_back(p); + * p.set_x(+0.2); p.set_y(-0.1); // coordinates in meters in the image plane (vertex 3) + * vec_p.push_back(p); + * p.set_x(-0.2); p.set_y(-0.15); // coordinates in meters in the image plane (vertex 4) + * vec_p.push_back(p); + * p.set_x(-0.2); p.set_y(0.1); // close the contour (vertex 5 = vertex 1) + * vec_p.push_back(p); + * + * vpMomentObject obj(1); // Create an image moment object with 1 as + * // maximum order (because only m00,m01,m10 + * // are needed to compute the gravity center primitive. + * obj.setType(vpMomentObject::DENSE_POLYGON); // The object is defined by a contour polygon + * obj.fromVector(vec_p); // Init the dense object with the polygon + * + * vpMomentGravityCenter g; // declaration of gravity center + * g.update(obj); // specify the object + * g.compute(); // compute the moment + * + * std::cout << "Xg=" << g.getXg() << std::endl; // access to Xg + * std::cout << "Yg=" << g.getYg() << std::endl; // access to Yg + * + * std::cout << g << std:: endl; // print gravity center + * + * return 0; + * } + * \endcode + * + * This example produces the following results: + * \code + * Xg=0.0166667 + * Yg=-0.00833333 + * Xg=0.0166667, Yg=-0.00833333 + * \endcode +*/ + +class VISP_EXPORT vpMomentGravityCenter : public vpMoment +{ +public: + vpMomentGravityCenter(); + + /** @name Inherited functionalities from vpMomentGravityCenter */ + //@{ + void compute() VP_OVERRIDE; + const std::vector &get() const; + /*! + * Shortcut function to retrieve \f$x_g\f$. + * \return The first gravity center coordinate. + */ + double getXg() const { return values[0]; } + /*! + * Shortcut function to retrieve \f$y_g\f$. + * \return The second gravity center coordinate. + */ + double getYg() const { return values[1]; } + /*! + * The class's string name. + */ + const std::string name() const VP_OVERRIDE { return "vpMomentGravityCenter"; } + void printDependencies(std::ostream &os) const VP_OVERRIDE; + //@} + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpMomentGravityCenter &v); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentGravityCenterNormalized.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentGravityCenterNormalized.h new file mode 100644 index 00000000..6efb68c9 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentGravityCenterNormalized.h @@ -0,0 +1,75 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * 2D normalized gravity center moment descriptor (usually described by the + * pair Xn,Yn) + */ + +/*! + \file vpMomentGravityCenterNormalized.h + \brief 2D normalized gravity center moment descriptor (usually described by + the pair Xn,Yn) +*/ +#ifndef VP_MOMENT_GRAVITY_CENTER_NORMALIZED_H +#define VP_MOMENT_GRAVITY_CENTER_NORMALIZED_H + +#include +#include + +BEGIN_VISP_NAMESPACE +class vpMomentObject; + +/*! + * \class vpMomentGravityCenterNormalized + * + * \ingroup group_core_moments + * + * \brief Class describing 2D normalized gravity center moment. + * + * Centered and normalized gravity center moment is defined as follows: + * \f$(x_n,y_n)\f$ where \f$x_n = x_g a_n\f$ and \f$y_n = y_g a_n\f$. + * + * vpMomentGravityCenterNormalized depends on vpMomentAreaNormalized to get + * access to \f$a_n\f$ and on vpMomentGravityCenter to get access to + * \f$(x_g,y_g)\f$ . +*/ +class VISP_EXPORT vpMomentGravityCenterNormalized : public vpMomentGravityCenter +{ +public: + vpMomentGravityCenterNormalized(); + void compute() VP_OVERRIDE; + //! Moment name. + const std::string name() const VP_OVERRIDE { return "vpMomentGravityCenterNormalized"; } + void printDependencies(std::ostream &os) const VP_OVERRIDE; + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpMomentGravityCenterNormalized &v); +}; +END_VISP_NAMESPACE + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentObject.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentObject.h new file mode 100644 index 00000000..7c5242f1 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMomentObject.h @@ -0,0 +1,326 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Object input structure used by moments. + */ + +/*! + \file vpMomentObject.h + \brief Object input structure used by moments. +*/ +#ifndef VP_MOMENT_OBJECT_H +#define VP_MOMENT_OBJECT_H + +#include +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +class vpCameraParameters; + +/*! + \class vpMomentObject + + \ingroup group_core_moments + + \brief Class for generic objects. + + It contains all basic moments often described by \f$m_{ij}\f$ of order + \f$i+j\f$ going from \f$m_{00}\f$ to the order used as parameter in + vpMomentObject() constructor. All other moments implemented in ViSP (gravity + center, alpha orientation, centered moments...) use this moment object as a + combination of its different values. + + When constructing a vpMomentObject() you need first to specify the maximum + used moment order as parameter. + + Then there are three ways to initialize a vpMomentObject. Firstly using + fromImage() you can considerer a dense object \e O defined by an image. + Secondly, as described in fromVector() you can also define a dense object \e O + by a closed contour. In these two cases, 2D basic moments are defined by: + \f[m_{ij} = \int \int_{O} x^i y^j dx dy\f] + + Lastly, as presented in fromVector() you can consider a discrete set of \e n + points. In that last case, the basic moments are defined by \f[m_{ij} = + \sum_{k=1}^{n} x_k^i y_k^j \f] + + With setType() method you can specify the object type. + + The implementation is based on the following references + \cite Steger96, \cite Chaumette04a, \cite Tahri05z, \cite Bakthavatchalam13a. + + \warning Be careful with the object order. When you specify a maximum + order in the vpMomentObject::vpMomentObject constructor (see its detailed + description), it will compute all moment orders up to the order you specified. + If you want to access the values \f$ m_{ij} \f$ with the vpMomentObject::get + method, you can do object.get()[j*(order+1)+i]. + + A few tips about which orders to use in different situations: + - moment based visual servoing: use vpMomentObject(6). This will compute + moment values up to order 6 which will enable vpFeatureMoments up to order 5 + which is the maximum order required for common moments. + - computing gravity center: use vpMomentObject(1). You only need \f$ + m_{00},m_{01},m_{10} \f$. You should compute moments up to order 1. + - computing gravity center interaction matrix with vpFeatureMomentGravityCenter: + use vpMomentObject(2). This will compute moment values till order 2 + since they are needed for the interaction matrix of vpFeatureMoments of order 1. + + The following example shows how to create a moment object from 4 discrete + points locate on a plane one meter in front of the camera. It shows also how + to get the basic moments that are computed and how to compute other classical + moments such as the gravity center or the centered moments. + + \code + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + // Define an object as 4 clockwise points on a plane (Z=0) + std::vector vec_p; // vector that contains the 4 points + + vec_p.push_back( vpPoint(-0.2, 0.1, 0.0) ); // values in meters + vec_p.push_back( vpPoint(+0.3, 0.1, 0.0) ); // values in meters + vec_p.push_back( vpPoint(+0.2,-0.1, 0.0) ); // values in meters + vec_p.push_back( vpPoint(-0.2,-0.15, 0.0) ); // values in meters + + // These points are observed by a camera + vpHomogeneousMatrix cMo(0, 0, 1, 0, 0, 0); // We set the camera to be 1m far the object + // ... update cMo from an image processing + + // Apply the perspective projection to update the points coordinates in the camera plane + for (unsigned int i=0; i moment = obj.get(); + std::cout << std::endl << "Basic moment available (from vector of doubles)" << std::endl; + for(unsigned int k=0; k<=obj.getOrder(); k++) { + for(unsigned int l=0; l<(obj.getOrder()+1)-k; l++) { + std::cout << "m" << l << k << "=" << moment[k*(momobj.getOrder()+1)+ l] << "\t"; + } + std::cout< mu_3 = vpMomentCommon::getMu3(obj); // centered moment up to 3rd order + + std::cout << std::endl << "Common moments computed using basic moments:" << std::endl; + std::cout << "Surface: " << m00 << std::endl; + std::cout << "Alpha: " << alpha << std::endl; + std::cout << "Centered moments (mu03, mu12, mu21, mu30): "; + for(unsigned int i=0; i VISP_CXX_STANDARD_98) + vpMomentObject &operator=(const vpMomentObject &) = default; +#endif + + void fromImage(const vpImage &image, unsigned char threshold, + const vpCameraParameters &cam); // Binary version + void fromImage(const vpImage &image, const vpCameraParameters &cam, vpCameraImgBckGrndType bg_type, + bool normalize_with_pix_size = true); // Photometric version + + void fromVector(std::vector &points); + const std::vector &get() const; + double get(unsigned int i, unsigned int j) const; + + /*! + \return The type of object that is considered. + */ + vpObjectType getType() const { return type; } + + /*! + \return The maximal order. The basic moments \f$m_{ij}\f$ that will be + computed are for \f$i+j \in [0:\mbox{order}]\f$. + */ + unsigned int getOrder() const { return order - 1; } + + // Constructor helpers + void init(unsigned int orderinp); + void init(const vpMomentObject &objin); + + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpMomentObject &v); + /*! + Outputs raw moments in indexed form like m[1,1] = value of moment m11 + \param momobj : A vpMomentObject + \param os : Output stream. + */ + static void printWithIndices(const vpMomentObject &momobj, std::ostream &os); + /*! + Specifies the type of the input data. + \param input_type : An input type. + */ + void setType(vpObjectType input_type) { this->type = input_type; } + + /*! + Converts the raw moments contained in vpMomentObject to a vpMatrix + \param momobj : A vpMomentObject + */ + static vpMatrix convertTovpMatrix(const vpMomentObject &momobj); + +protected: + unsigned int order; + vpObjectType type; + std::vector values; + void set(unsigned int i, unsigned int j, const double &value_ij); + void cacheValues(std::vector &cache, double x, double y); + +private: + void cacheValues(std::vector &cache, double x, double y, double IntensityNormalized); + double calc_mom_polygon(unsigned int p, unsigned int q, const std::vector &points); +}; +END_VISP_NAMESPACE + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMouseButton.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMouseButton.h new file mode 100644 index 00000000..1d382b71 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMouseButton.h @@ -0,0 +1,57 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Color definition. + */ + +#ifndef VP_MOUSE_BUTTON_H +#define VP_MOUSE_BUTTON_H + +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpMouseButton + \ingroup group_gui_display + \brief Class that defines mouse button identifiers. +*/ +class VISP_EXPORT vpMouseButton +{ +public: + typedef enum + { + button1 = 1, /*!< Mouse left button. */ + button2 = 2, /*!< Mouse middle button, or roll. */ + button3 = 3, /*!< Mouse right button. */ + none = 0 /*!< No button. */ + } vpMouseButtonType; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMunkres.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMunkres.h new file mode 100644 index 00000000..5753d218 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMunkres.h @@ -0,0 +1,381 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Class for Munkres Assignment Algorithm. + */ + +#pragma once + +#include + +// Check if std:c++17 or higher. +// Here we cannot use (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17) in the declaration of the class +#if ((__cplusplus >= 201703L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L))) + +// System +#include +#include + +// Internal +#include "vpMath.h" + +BEGIN_VISP_NAMESPACE +/*! + \class vpMunkres + \ingroup group_core_munkres + + Implements the Munkres Assignment Algorithm described [here](https://en.wikipedia.org/wiki/Hungarian_algorithm). + + \note This class is only available with c++17 enabled. + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in using using the Munkres algorithm, you may have a look at:
+ + - \ref tutorial-munkres +*/ +class VISP_EXPORT vpMunkres +{ +public: + template + static std::vector > run(std::vector > costs); + +private: + enum ZERO_T : unsigned int; + enum STEP_T : unsigned int; + + // Init + template static void padCostMatrix(std::vector > &costs); + + // Global helpers + template + static std::optional > findAZero(const std::vector > &costs, + const std::vector &row_cover, + const std::vector &col_cover); + static std::optional findStarInRow(const std::vector > &mask, + const unsigned int &row); + static std::optional findStarInCol(const std::vector > &mask, + const unsigned int &col); + static std::optional findPrimeInRow(const std::vector > &mask, + const unsigned int &row); + template + static Type findSmallest(const std::vector > &costs, const std::vector &row_cover, + const std::vector &col_cover); + + // FSM helpers + static void augmentPath(std::vector > &mask, + const std::vector > &path); + static void clearCovers(std::vector &row_cover, std::vector &col_cover); + static void erasePrimes(std::vector > &mask); + + // FSM + template static STEP_T stepOne(std::vector > &costs); + template + static STEP_T stepTwo(std::vector > &costs, std::vector > &mask, + std::vector &row_cover, std::vector &col_cover); + static STEP_T stepThree(const std::vector > &mask, std::vector &col_cover); + template + static std::tuple > > + stepFour(const std::vector > &costs, std::vector > &mask, + std::vector &row_cover, std::vector &col_cover); + static STEP_T stepFive(std::vector > &mask, const std::pair &path_0, + std::vector &row_cover, std::vector &col_cover); + template + static STEP_T stepSix(std::vector > &costs, const std::vector &row_cover, + const std::vector &col_cover); + +private: + static constexpr auto ZeroEpsilon { 1e-6 }; +}; + +enum vpMunkres::ZERO_T : unsigned int { NA = 0, STARRED = 1, PRIMED = 2 }; + +enum vpMunkres::STEP_T : unsigned int { ENTRY = 0, ONE = 1, TWO = 2, THREE = 3, FOUR = 4, FIVE = 5, SIX = 6, DONE }; + +/*! + * Ensure that the cost matrix is square by the addition of dummy rows/columns. + * + * \param[in,out] costs : Cost matrix. + */ +template inline void vpMunkres::padCostMatrix(std::vector > &costs) +{ + const auto row_input_size = costs.size(); + const auto col_input_size = costs.at(0).size(); + + if (row_input_size > col_input_size) { + for (auto &vec : costs) + vec.resize(row_input_size, 0); + } + + while (costs.size() < col_input_size) { + costs.emplace_back(col_input_size, 0); + } +} + +/*! + * Find a zero in the cost matrix. + * + * \param[in] costs : Cost matrix. + * \param[in] row_cover : Row coverage array. + * \param[in] col_cover : Col coverage array. + * \return Index of the Zero [] or std::nullopt if the cost matrix does not contain a zero. + */ +template +inline std::optional > +vpMunkres::findAZero(const std::vector > &costs, const std::vector &row_cover, + const std::vector &col_cover) +{ + for (auto row = 0u; row < costs.size(); row++) + for (auto col = 0u; col < costs.size(); col++) + if (vpMath::equal(costs.at(row).at(col), static_cast(vpMunkres::ZeroEpsilon)) && !row_cover.at(row) && + !col_cover.at(col)) { + return std::make_optional >(row, col); + } + + return std::nullopt; +} + +/*! + * Find the smallest value of the cost matrix. + * + * \param[in] costs : Cost matrix. + * \param[in] row_cover : Row coverage array. + * \param[in] col_cover : Col coverage array. + * \return Smallest value of the cost matrix. + */ +template +inline Type vpMunkres::findSmallest(const std::vector > &costs, const std::vector &row_cover, + const std::vector &col_cover) +{ + auto minval = std::numeric_limits::max(); + for (auto row = 0u; row < costs.size(); row++) + for (auto col = 0u; col < costs.size(); col++) + if (minval > costs.at(row).at(col) && !row_cover.at(row) && !col_cover.at(col)) { + minval = costs.at(row).at(col); + } + + return minval; +} + +/*! + * For each row of the cost matrix, find the smallest element and subtract it from every element in its row. + * For each col of the cost matrix, find the smallest element and subtract it from every element in its col. + * When finished, Go to Step 2. + * + * \param[in,out] costs : Cost matrix. + * \return Next step. + */ +template inline vpMunkres::STEP_T vpMunkres::stepOne(std::vector > &costs) +{ + // process rows + std::for_each(begin(costs), end(costs), [](auto &cost_row) { + const auto min_in_row = *std::min_element(begin(cost_row), end(cost_row)); + std::transform(begin(cost_row), end(cost_row), begin(cost_row), + [&min_in_row](auto &cost) { return cost - min_in_row; }); + }); + + // process cols + for (auto col = 0u; col < costs.size(); ++col) { + auto minval = std::numeric_limits::max(); + for (const auto &cost_row : costs) { + minval = std::min(minval, cost_row.at(col)); + } + + for (auto &cost_row : costs) { + cost_row.at(col) -= minval; + } + } + + return vpMunkres::STEP_T(2); +} + +/*! + * Find a zero (Z) in the cost matrix. If there is no starred zero in its row or column, star Z. Repeat for each + * element in the cost matrix. + * When finished, Go to Step 3. + * + * \param[in,out] costs : Cost matrix. + * \param[in] mask : Mask matrix. + * \param[in,out] row_cover : Row coverage array. + * \param[in,out] col_cover : Col coverage array. + * \return Next step. + */ +template +inline vpMunkres::STEP_T vpMunkres::stepTwo(std::vector > &costs, + std::vector > &mask, + std::vector &row_cover, std::vector &col_cover) +{ + for (auto row = 0u; row < costs.size(); row++) { + for (auto col = 0u; col < costs.size(); col++) { + if (vpMath::equal(costs.at(row).at(col), static_cast(vpMunkres::ZeroEpsilon)) && !row_cover.at(row) && + !col_cover.at(col)) { + mask.at(row).at(col) = vpMunkres::ZERO_T::STARRED; + row_cover.at(row) = true; + col_cover.at(col) = true; + break; + } + } + } + + clearCovers(row_cover, col_cover); + return vpMunkres::STEP_T(3); +} + +/*! + * Find a noncovered zero and prime it. + * If there is no starred zero in the row containing this primed zero, Go to Step 5. + * Otherwise, cover this row and uncover the column containing the starred zero. Continue in this manner until there + * are no uncovered zeros left. Go to Step 6. + * + * \param[in] costs : Cost matrix. + * \param[in,out] mask : Mask matrix. + * \param[in,out] row_cover : Row coverage array. + * \param[in,out] col_cover : Col coverage array. + * \return Tuple(Next step, pair(path_row_0 path_col_0)). + */ +template +inline std::tuple > > +vpMunkres::stepFour(const std::vector > &costs, std::vector > &mask, + std::vector &row_cover, std::vector &col_cover) +{ + if (const auto zero = findAZero(costs, row_cover, col_cover)) { + const auto [row, col] = *zero; // convenient zero.value() is not working on iOS + mask.at(row).at(col) = vpMunkres::ZERO_T::PRIMED; + + if (const auto star_in_row = findStarInRow(mask, row)) { + row_cover.at(row) = true; + col_cover.at(*star_in_row) = false; + return { vpMunkres::STEP_T(4), std::nullopt }; // Repeat + } + else { + return { vpMunkres::STEP_T(5), std::make_optional >(row, col) }; + } + } + else { + return { vpMunkres::STEP_T(6), std::nullopt }; + } +} + +/*! + * Add the smallest value of the cost matrix to every element of each covered row, and subtract it from every element + * of each uncovered column. Return to Step 4 without altering any stars, primes, or covered lines. + * + * \param[in,out] costs : Cost matrix. + * \param[in] row_cover : Row coverage array. + * \param[in] col_cover : Col coverage array. + * \return Next step. + */ +template +inline vpMunkres::STEP_T vpMunkres::stepSix(std::vector > &costs, const std::vector &row_cover, + const std::vector &col_cover) +{ + const auto minval = findSmallest(costs, row_cover, col_cover); + for (auto row = 0u; row < costs.size(); row++) { + for (auto col = 0u; col < costs.size(); col++) { + if (row_cover.at(row)) { + costs.at(row).at(col) += minval; + } + + if (!col_cover.at(col)) { + costs.at(row).at(col) -= minval; + } + } + } + + return vpMunkres::STEP_T(4); +} + +/*! + * Munkres FSM. + * + * \param[in] costs : Cost matrix. + * \return List of associated pairs []. + */ +template +inline std::vector > vpMunkres::run(std::vector > costs) +{ + const auto original_row_size = static_cast(costs.size()); + const auto original_col_size = static_cast(costs.front().size()); + const size_t sq_size = static_cast(std::max(original_row_size, original_col_size)); + + auto mask = std::vector >(sq_size, std::vector(sq_size, vpMunkres::ZERO_T::NA)); + auto row_cover = std::vector(sq_size, false); + auto col_cover = std::vector(sq_size, false); + + std::optional > path_0 { std::nullopt }; + + auto step { vpMunkres::STEP_T::ENTRY }; + while (step != vpMunkres::STEP_T::DONE) { + switch (step) { + case vpMunkres::STEP_T::ENTRY: + padCostMatrix(costs); + step = vpMunkres::STEP_T(1); + break; + case 1: + step = stepOne(costs); + break; + case 2: + step = stepTwo(costs, mask, row_cover, col_cover); + break; + case 3: + step = stepThree(mask, col_cover); + break; + case 4: + std::tie(step, path_0) = stepFour(costs, mask, row_cover, col_cover); + break; + case 5: + step = stepFive(mask, *path_0, row_cover, col_cover); + break; + case 6: + step = stepSix(costs, row_cover, col_cover); + break; + case vpMunkres::STEP_T::DONE: + default: + break; + } + } + + // Compute the pairs + std::vector > ret {}; + for (auto i = 0u; i < original_row_size; i++) { + if (const auto it = std::find(begin(mask.at(i)), end(mask.at(i)), vpMunkres::ZERO_T::STARRED); + it != end(mask.at(i))) { + if (const unsigned int j = static_cast(std::distance(begin(mask.at(i)), it)); + j < original_col_size) { + ret.emplace_back(i, j); + } + } + } + + return ret; +} +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMutex.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMutex.h new file mode 100644 index 00000000..8874f68f --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpMutex.h @@ -0,0 +1,220 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Mutex protection. + */ + +#ifndef _vpMutex_h_ +#define _vpMutex_h_ + +#include +#include + +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) && (defined(VISP_HAVE_PTHREAD) || (defined(_WIN32) && !defined(WINRT_8_0))) + +#if defined(VISP_HAVE_PTHREAD) +#include +#elif defined(_WIN32) + +// Mute warning with clang-cl +// warning : non-portable path to file ''; specified path differs in case from file name on disk [-Wnonportable-system-include-path] +// warning : non-portable path to file ''; specified path differs in case from file name on disk [-Wnonportable-system-include-path] +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wnonportable-system-include-path" +#endif + +// Include WinSock2.h before windows.h to ensure that winsock.h is not +// included by windows.h since winsock.h and winsock2.h are incompatible +#include +#include + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif +#endif + +#ifdef ENABLE_VISP_NAMESPACE +namespace VISP_NAMESPACE_NAME +{ +#endif +/*! + + \class vpMutex + + \ingroup group_core_threading + \deprecated Use rather std::mutex. + + Class that allows protection by mutex. + + This class implements native pthread functionalities if available, of + native Windows threading capabilities if pthread is not available under + Windows. + + \sa vpScopedLock +*/ +class VP_DEPRECATED vpMutex +{ +public: + vpMutex() : m_mutex() + { +#if defined(VISP_HAVE_PTHREAD) + pthread_mutex_init(&m_mutex, nullptr); +#elif defined(_WIN32) +#ifdef WINRT_8_1 + m_mutex = CreateMutexEx(nullptr, nullptr, 0, nullptr); +#else + m_mutex = CreateMutex(nullptr, // default security attributes + FALSE, // initially not owned + nullptr); // unnamed mutex +#endif + if (m_mutex == nullptr) { + std::cout << "CreateMutex error: " << GetLastError() << std::endl; + return; + } +#endif + } + void lock() + { +#if defined(VISP_HAVE_PTHREAD) + pthread_mutex_lock(&m_mutex); +#elif defined(_WIN32) + DWORD dwWaitResult; +#ifdef WINRT_8_1 + dwWaitResult = WaitForSingleObjectEx(m_mutex, INFINITE, FALSE); +#else + dwWaitResult = WaitForSingleObject(m_mutex, // handle to mutex + INFINITE); // no time-out interval +#endif + if (dwWaitResult == WAIT_FAILED) + std::cout << "lock() error: " << GetLastError() << std::endl; +#endif + } + void unlock() + { +#if defined(VISP_HAVE_PTHREAD) + pthread_mutex_unlock(&m_mutex); +#elif defined(_WIN32) + // Release ownership of the mutex object + if (!ReleaseMutex(m_mutex)) { + // Handle error. + std::cout << "unlock() error: " << GetLastError() << std::endl; + } +#endif + } + + /*! + + \class vpScopedLock + + \ingroup group_core_threading + + \brief Class that allows protection by mutex. + + The following example shows how to use this class to protect a portion of + code from concurrent access. The scope of the mutex lock/unlock is determined + by the constructor/destructor. + + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpMutex mutex; + + { + vpMutex::vpScopedLock lock(mutex); + // shared var to protect + } + } + \endcode + + Without using vpScopedLock, the previous example would become: + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpMutex mutex; + + { + mutex.lock(); + // shared var to protect + mutex.unlock() + } + } + \endcode + + \sa vpMutex + */ + class vpScopedLock + { + private: + vpMutex &_mutex; + + // private: + //#ifndef DOXYGEN_SHOULD_SKIP_THIS + // vpScopedLock &operator=(const vpScopedLock &){ + // throw vpException(vpException::functionNotImplementedError,"Not + // implemented!"); return *this; + // } + //#endif + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpScopedLock &operator=(const vpScopedLock &) = delete; // non copyable +#endif + + public: + //! Constructor that locks the mutex. + vpScopedLock(vpMutex &mutex) : _mutex(mutex) { _mutex.lock(); } + //! Destructor that unlocks the mutex. + virtual ~vpScopedLock() { _mutex.unlock(); } + }; + +private: +#if defined(VISP_HAVE_PTHREAD) + pthread_mutex_t m_mutex; +#elif defined(_WIN32) + HANDLE m_mutex; +#endif +}; +#ifdef ENABLE_VISP_NAMESPACE +} +#endif +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpNetwork.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpNetwork.h new file mode 100644 index 00000000..99d2f1a4 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpNetwork.h @@ -0,0 +1,530 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * TCP Network + */ + +#ifndef VP_NETWORK_H +#define VP_NETWORK_H + +// Specific case for UWP to introduce a workaround +// error C4996: 'gethostbyname': Use getaddrinfo() or GetAddrInfoW() instead or define _WINSOCK_DEPRECATED_NO_WARNINGS to disable deprecated API warnings +#if defined(WINRT) || defined(_WIN32) +#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#endif +#endif + +#include +#include +#include + +#include +#include +#include +#include +#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX +#include +#endif + + // inet_ntop() not supported on win XP +#ifdef VISP_HAVE_FUNC_INET_NTOP + +#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX +#include +#include +#include +#include +#include +#else +#include + +#if defined(__clang__) +// Mute warning : non-portable path to file ''; specified path differs in case from file name on disk [-Wnonportable-system-include-path] +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wnonportable-system-include-path" +#endif + +#include + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +#endif + +#if defined(__APPLE__) && defined(__MACH__) // Apple OSX and iOS (Darwin) +#include // To detect OSX or IOS using TARGET_OS_IPHONE or TARGET_OS_IOS macro +#endif + +BEGIN_VISP_NAMESPACE +/*! + \class vpNetwork + + \ingroup group_core_com_ethernet + + \brief This class represents a Transmission Control Protocol (TCP) network. + + TCP provides reliable, ordered delivery of a stream of bytes from a program + on one computer to another program on another computer. + + \warning This class shouldn't be used directly. You better use vpClient and + vpServer to simulate your network. Some examples are provided in these + classes. + + \sa vpServer + \sa vpNetwork +*/ +class VISP_EXPORT vpNetwork +{ +protected: +#ifndef DOXYGEN_SHOULD_SKIP_THIS + struct vpReceptor + { +#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX + int socketFileDescriptorReceptor; + socklen_t receptorAddressSize; +#else + SOCKET socketFileDescriptorReceptor; + int receptorAddressSize; +#endif + struct sockaddr_in receptorAddress; + std::string receptorIP; + + vpReceptor() : socketFileDescriptorReceptor(0), receptorAddressSize(), receptorAddress(), receptorIP() { } + }; + + struct vpEmitter + { + struct sockaddr_in emitterAddress; +#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX + int socketFileDescriptorEmitter; +#else + SOCKET socketFileDescriptorEmitter; +#endif + vpEmitter() : emitterAddress(), socketFileDescriptorEmitter(0) + { + emitterAddress.sin_family = AF_INET; + emitterAddress.sin_addr.s_addr = INADDR_ANY; + emitterAddress.sin_port = 0; + socketFileDescriptorEmitter = 0; + } + }; +#endif + + //######## PARAMETERS ######## + //# # + //############################ + + vpEmitter emitter; + std::vector receptor_list; + fd_set readFileDescriptor; +#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX + int socketMax; +#else + SOCKET socketMax; +#endif + + // Message Handling + std::vector request_list; + + unsigned int max_size_message; + std::string separator; + std::string beginning; + std::string end; + std::string param_sep; + + std::string currentMessageReceived; + + struct timeval tv; + long tv_sec; + long tv_usec; + + bool verboseMode; + +private: + std::vector privHandleRequests(); + int privHandleFirstRequest(); + + void privReceiveRequest(); + void privReceiveRequestFrom(const unsigned int &receptorEmitting); + int privReceiveRequestOnce(); + int privReceiveRequestOnceFrom(const unsigned int &receptorEmitting); + +public: + vpNetwork(); + vpNetwork(const vpNetwork &network); + virtual ~vpNetwork(); + + vpNetwork &operator=(const vpNetwork &network); + + void addDecodingRequest(vpRequest *); + + int getReceptorIndex(const char *name); + + /*! + Get the Id of the request at the index ind. + + \param ind : Index of the request. + + \return Id of the request. + */ + std::string getRequestIdFromIndex(const int &ind) + { + if (ind >= static_cast(request_list.size()) || ind < 0) { + return ""; + } + return request_list[static_cast(ind)]->getId(); + } + + /*! + Get the maximum size that the emitter can receive (in request mode). + + \sa vpNetwork::setMaxSizeReceivedMessage() + + \return Acutal max size value. + */ + unsigned int getMaxSizeReceivedMessage() { return max_size_message; } + + void print(const char *id = ""); + + template int receive(T *object, const unsigned int &sizeOfObject = sizeof(T)); + template + int receiveFrom(T *object, const unsigned int &receptorEmitting, const unsigned int &sizeOfObject = sizeof(T)); + + std::vector receiveRequest(); + std::vector receiveRequestFrom(const unsigned int &receptorEmitting); + int receiveRequestOnce(); + int receiveRequestOnceFrom(const unsigned int &receptorEmitting); + + std::vector receiveAndDecodeRequest(); + std::vector receiveAndDecodeRequestFrom(const unsigned int &receptorEmitting); + int receiveAndDecodeRequestOnce(); + int receiveAndDecodeRequestOnceFrom(const unsigned int &receptorEmitting); + + void removeDecodingRequest(const char *); + + template int send(T *object, const int unsigned &sizeOfObject = sizeof(T)); + template int sendTo(T *object, const unsigned int &dest, const unsigned int &sizeOfObject = sizeof(T)); + + int sendRequest(vpRequest &req); + int sendRequestTo(vpRequest &req, const unsigned int &dest); + + int sendAndEncodeRequest(vpRequest &req); + int sendAndEncodeRequestTo(vpRequest &req, const unsigned int &dest); + + /*! + Change the maximum size that the emitter can receive (in request mode). + + \sa vpNetwork::getMaxSizeReceivedMessage() + + \param s : new maximum size value. + */ + void setMaxSizeReceivedMessage(const unsigned int &s) { max_size_message = s; } + + /*! + Change the time the emitter spend to check if he receives a message from a + receptor. Initially this value is set to 10usec. + + \sa vpNetwork::setTimeoutUSec() + + \param sec : new value in second. + */ + void setTimeoutSec(const long &sec) { tv_sec = sec; } + + /*! + Change the time the emitter spend to check if he receives a message from a + receptor. Initially this value is set to 10usec. + + \sa vpNetwork::setTimeoutSec() + + \param usec : new value in micro second. + */ + void setTimeoutUSec(const long &usec) { tv_usec = usec; } + + /*! + Set the verbose mode. + + \param mode : Change the verbose mode. True to turn on, False to turn off. + */ + void setVerbose(const bool &mode) { verboseMode = mode; } +}; + +//######## Definition of Template Functions ######## +//# # +//################################################## + +/*! + Receives a object. The size of the object received is assumed to correspond + to the size of the object type. Note that a message received may correspond + to a disconnection signal. + + \warning Using this function means that you know what kind of object you are + suppose to receive, and when you are suppose to receive. If the emitter has + several receptors. It might be a problem, and in that case you better use + the "request" option. + + \sa vpNetwork::receiveRequest() + \sa vpNetwork::receiveRequestOnce() + \sa vpNetwork::receiveAndDecodeRequest() + \sa vpNetwork::receiveAndDecodeRequestOnce() + + \param object : Received object. + \param sizeOfObject : Size of the received object. + + \return the number of bytes received, or -1 if an error occurred. +*/ +template int vpNetwork::receive(T *object, const unsigned int &sizeOfObject) +{ + if (receptor_list.size() == 0) { + if (verboseMode) + vpTRACE("No receptor"); + return -1; + } + + tv.tv_sec = tv_sec; +#ifdef TARGET_OS_IPHONE + tv.tv_usec = static_cast(tv_usec); +#else + tv.tv_usec = tv_usec; +#endif + + FD_ZERO(&readFileDescriptor); + + for (unsigned int i = 0; i < receptor_list.size(); i++) { + FD_SET(static_cast(receptor_list[i].socketFileDescriptorReceptor), &readFileDescriptor); + + if (i == 0) + socketMax = receptor_list[i].socketFileDescriptorReceptor; + + if (socketMax < receptor_list[i].socketFileDescriptorReceptor) + socketMax = receptor_list[i].socketFileDescriptorReceptor; + } + + int value = select(static_cast(socketMax) + 1, &readFileDescriptor, nullptr, nullptr, &tv); + int numbytes = 0; + + if (value == -1) { + if (verboseMode) + vpERROR_TRACE("Select error"); + return -1; + } + else if (value == 0) { + // Timeout + return 0; + } + else { + for (unsigned int i = 0; i < receptor_list.size(); i++) { + if (FD_ISSET(static_cast(receptor_list[i].socketFileDescriptorReceptor), &readFileDescriptor)) { +#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX + numbytes = recv(receptor_list[i].socketFileDescriptorReceptor, (char *)(void *)object, sizeOfObject, 0); +#else + numbytes = recv(static_cast(receptor_list[i].socketFileDescriptorReceptor), (char *)(void *)object, + static_cast(sizeOfObject), 0); +#endif + if (numbytes <= 0) { + std::cout << "Disconnected : " << inet_ntoa(receptor_list[i].receptorAddress.sin_addr) << std::endl; + receptor_list.erase(receptor_list.begin() + static_cast(i)); + return numbytes; + } + + break; + } + } + } + + return numbytes; +} + +/*! + Receives a object from a receptor, by specifying its size or not. + Note that a received message can correspond to a deconnection signal. + + \warning Using this function means that you know what kind of object you are + suppose to receive, and when you are suppose to receive. If the emitter has + several receptors. It might be a problem, and in that case you better use + the "request" mode. + + \sa vpNetwork::getReceptorIndex() + \sa vpNetwork::receiveRequestFrom() + \sa vpNetwork::receiveRequestOnceFrom() + \sa vpNetwork::receiveAndDecodeRequestFrom() + \sa vpNetwork::receiveAndDecodeRequestOnceFrom() + + \param object : Received object. + \param receptorEmitting : Index of the receptor emitting the message. + \param sizeOfObject : Size of the received object. + + \return the number of bytes received, or -1 if an error occurred. +*/ +template +int vpNetwork::receiveFrom(T *object, const unsigned int &receptorEmitting, const unsigned int &sizeOfObject) +{ + if (receptor_list.size() == 0 || receptorEmitting > static_cast(receptor_list.size()) - 1) { + if (verboseMode) + vpTRACE("No receptor at the specified index"); + return -1; + } + + tv.tv_sec = tv_sec; +#ifdef TARGET_OS_IPHONE + tv.tv_usec = static_cast(tv_usec); +#else + tv.tv_usec = tv_usec; +#endif + + FD_ZERO(&readFileDescriptor); + + socketMax = receptor_list[receptorEmitting].socketFileDescriptorReceptor; + FD_SET(static_cast(receptor_list[receptorEmitting].socketFileDescriptorReceptor), &readFileDescriptor); + + int value = select(static_cast(socketMax) + 1, &readFileDescriptor, nullptr, nullptr, &tv); + int numbytes = 0; + + if (value == -1) { + if (verboseMode) + vpERROR_TRACE("Select error"); + return -1; + } + else if (value == 0) { + // timeout + return 0; + } + else { + if (FD_ISSET(static_cast(receptor_list[receptorEmitting].socketFileDescriptorReceptor), &readFileDescriptor)) { +#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX + numbytes = + recv(receptor_list[receptorEmitting].socketFileDescriptorReceptor, (char *)(void *)object, sizeOfObject, 0); +#else + numbytes = recv(static_cast(receptor_list[receptorEmitting].socketFileDescriptorReceptor), + (char *)(void *)object, static_cast(sizeOfObject), 0); +#endif + if (numbytes <= 0) { + std::cout << "Disconnected : " << inet_ntoa(receptor_list[receptorEmitting].receptorAddress.sin_addr) + << std::endl; + receptor_list.erase(receptor_list.begin() + static_cast(receptorEmitting)); + return numbytes; + } + } + } + + return numbytes; +} + +/*! + Send an object. The size of the received object is suppose to be the size of + its type. Note that sending object containing pointers, virtual methods, + etc, won't probably work. + + \warning Using this function means that, in the other side of the network, + it knows what kind of object it is suppose to receive, and when it is + suppose to receive. If the emitter has several receptors. It might be a + problem, and in that case you better use the "request" option. + + \sa vpNetwork::sendTo() + \sa vpNetwork::sendRequest() + \sa vpNetwork::sendRequestTo() + \sa vpNetwork::sendAndEncodeRequest() + \sa vpNetwork::sendAndEncodeRequestTo() + + \param object : Received object. + \param sizeOfObject : Size of the object + + \return The number of bytes sent, or -1 if an error happened. +*/ +template int vpNetwork::send(T *object, const unsigned int &sizeOfObject) +{ + if (receptor_list.size() == 0) { + if (verboseMode) + vpTRACE("No receptor !"); + return 0; + } + + int flags = 0; + //#if ! defined(APPLE) && ! defined(SOLARIS) && ! defined(_WIN32) +#if defined(__linux__) + flags = MSG_NOSIGNAL; // Only for Linux +#endif + +#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX + return sendto(receptor_list[0].socketFileDescriptorReceptor, (const char *)(void *)object, sizeOfObject, flags, + (sockaddr *)&receptor_list[0].receptorAddress, receptor_list[0].receptorAddressSize); +#else + return sendto(receptor_list[0].socketFileDescriptorReceptor, (const char *)(void *)object, static_cast(sizeOfObject), flags, + (sockaddr *)&receptor_list[0].receptorAddress, receptor_list[0].receptorAddressSize); +#endif +} + +/*! + Send an object. The size has to be specified. + + \warning Using this function means that, in the other side of the network, + it knows what kind of object it is suppose to receive, and when it is + suppose to receive. If the emitter has several receptors. It might be a + problem, and in that case you better use the "request" option. + + \sa vpNetwork::getReceptorIndex() + \sa vpNetwork::send() + \sa vpNetwork::sendRequest() + \sa vpNetwork::sendRequestTo() + \sa vpNetwork::sendAndEncodeRequest() + \sa vpNetwork::sendAndEncodeRequestTo() + + \param object : Object to send. + \param dest : Index of the receptor that you are sending the object. + \param sizeOfObject : Size of the object. + + \return The number of bytes sent, or -1 if an error happened. +*/ +template int vpNetwork::sendTo(T *object, const unsigned int &dest, const unsigned int &sizeOfObject) +{ + if (receptor_list.size() == 0 || dest > static_cast(receptor_list.size()) - 1) { + if (verboseMode) + vpTRACE("No receptor at the specified index."); + return 0; + } + + int flags = 0; + //#if ! defined(APPLE) && ! defined(SOLARIS) && ! defined(_WIN32) +#if defined(__linux__) + flags = MSG_NOSIGNAL; // Only for Linux +#endif + +#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX + return sendto(receptor_list[dest].socketFileDescriptorReceptor, (const char *)(void *)object, sizeOfObject, flags, + (sockaddr *)&receptor_list[dest].receptorAddress, receptor_list[dest].receptorAddressSize); +#else + return sendto(receptor_list[dest].socketFileDescriptorReceptor, (const char *)(void *)object, static_cast(sizeOfObject), + flags, (sockaddr *)&receptor_list[dest].receptorAddress, receptor_list[dest].receptorAddressSize); +#endif +} +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpNoise.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpNoise.h new file mode 100644 index 00000000..f61eb99a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpNoise.h @@ -0,0 +1,39 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Generation of random number with uniform and normal probability density. + */ + +#ifndef vpNoise_hh +#define vpNoise_hh + +#include + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpNullptrEmulated.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpNullptrEmulated.h new file mode 100644 index 00000000..8b4031b1 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpNullptrEmulated.h @@ -0,0 +1,64 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#ifndef VP_NULLPTR_EMULATED_H +#define VP_NULLPTR_EMULATED_H + +#include + +// Note that on ubuntu 12.04 __cplusplus is equal to 1 that's why in the next line we consider __cplusplus <= 199711L +// and not __cplusplus == 199711L +#if (!defined(VISP_HAVE_NULLPTR)) && (__cplusplus <= 199711L) + +// Inspired from this thread https://stackoverflow.com/questions/24433436/compile-error-nullptr-undeclared-identifier +// Does the emulation of nullptr when not available with cxx98 +const +class nullptr_t +{ +public: + template + inline operator T *() const // convertible to any type of null non-member pointer... + { + return 0; + } + + template + inline operator T C:: *() const // or any type of null member pointer... + { + return 0; + } + +private: + void operator&() const; // Can't take address of nullptr + +} nullptr = {}; + +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpParticleFilter.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpParticleFilter.h new file mode 100644 index 00000000..30edc32f --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpParticleFilter.h @@ -0,0 +1,811 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Display a point cloud using PCL library. + */ + +#ifndef VP_PARTICLE_FILTER_H +#define VP_PARTICLE_FILTER_H + +#include + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) +#include +#include +#include + +#include // std::function + +#ifdef VISP_HAVE_OPENMP +#include +#endif + +#if defined(__clang__) +// Mute warning : '\tparam' command used in a comment that is not attached to a template declaration [-Wdocumentation] +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +BEGIN_VISP_NAMESPACE +/*! + \class vpParticleFilter + \ingroup group_core_math_tools + \tparam MeasurementsType The class that corresponds to the measurements used to compute + the weights of the Particle Filter + \brief The class permits to use a Particle Filter. + + Be \f$ \textbf{x}_i \in \textit{S} \f$ a particle representing the internal state of the PF, with \f$ i \in {0 \dots N - 1} \f$ + and \f$ \textit{S} \f$ the state space. + To each particle is associated a weight \f$ w_i \f$ that represents its likelihood knowing the measurements and is used + to compute the filtered state \f$ \textbf{x}_{filtered} \in \textit{S} \f$. + + The first step of the PF is the prediction step. During this step, the particles of the PF are projected forward in time. Be + \f$ f(\textbf{x}_i, \Delta t) : \textit{S} \times R \rightarrow \textit{S} \f$ the process function that project the forward in time. + All the particles pass through the function , and some noise \f$ \epsilon \f$ is independently added to each of them to form the new + particles: + + \f[ + \textbf{x}_i(t + \Delta t) = f( \textbf{x}_i(t) , \Delta t ) + \epsilon + \f] + + The second step of the PF is to update the weights \f$ w_i \f$ associated to each particle based on new measurements. + The update is based on the likelihood of a particle based on the measurements \f$ \textbf{z} \in \textit{M} \f$, where + \f$ \textit{M} \f$ is the measurement space. Be \f$ l: \textit{S} \times \textit{M} \rightarrow [0; 1.] \f$ the likelihood function, + we have: + + \f[ + w_i = l(\textbf{x}_i, \textbf{z}) + \f] + + After an update, a check is performed to see if the PF is not degenerated (i.e. if the weights of most particles became very low). + If the PF became degenerated, the particles are resampled depending on a resampling scheme. Different kind of checks + and of resampling algorithms exist in the litterature. In this class, we implemented the Simple Resampling algorithm + in a dedicated method and let to the user the possibility of writing user-defined check and resampling methods. + + Finally, we can compute the new state estimate \f$ \textbf{x}_{filtered} \f$ by performing a weighted mean of the particles + \f$ \textbf{x}_i \f$. Be \f$ \textbf{w} = (w_0 \dots w_{N-1})^T \in R^N \f$, \f$ \textbf{x} = {\textbf{x}_0 \dots \textbf{x}_{N-1}} \in \textit{S}^N \f$ + and \f$ wm: R^N \times \textit{S}^N \rightarrow \textit{S} \f$ the weighted mean function of the state space + \f$ \textit{S} \f$, we have: + + \f[ + \textbf{x}_{filtered} = wm(\textbf{w}, \textbf{x}) + \f] + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in using a Particle Filter in your application, you may have a look at:
+ + - \ref tutorial-pf + - \ref tutorial-pf-curve-fitting +*/ +template +class vpParticleFilter +{ +public: + /** + * \brief Structure of vectors for which the i^th element of the weights vector is + * associated to the i^th element of the particles vectors. + */ + typedef struct vpParticlesWithWeights + { + std::vector m_particles; /*!< Particles vector*/ + std::vector m_weights; /*!< Weights vector.*/ + } vpParticlesWithWeights; + + /** + * \brief Function that computes either the equivalent of an addition in the state space. + * The first argument is the vector to which we must add something + * and the second argument is the thing to be added. The return is the + * result of this operation. + */ + typedef std::function vpStateAddFunction; + + /** + * \brief Process model function, which projects a particle forward in time. + * The first argument is a particle, the second is the period and the return is the + * particle projected in the future. + */ + typedef std::function vpProcessFunction; + + /** + * \brief Command model function, which projects a particle forward in time according to + * the command and its previous state. + * The first argument is the command(s), the second is the particle and the third is the period. + * The return is the updated particle after period seconds. + */ + typedef std::function vpCommandStateFunction; + + /** + * \brief Likelihood function, which evaluates the likelihood of a particle with regard to the measurements. + * The first argument is the particle that is evaluated. + * The second argument is the measurements vector. + * The return is the likelihood of the particle, which equals to 0 when the particle does not match + * at all the measurements and to 1 when it matches completely. + */ + typedef std::function vpLikelihoodFunction; + + /** + * \brief Filter function, which computes the filtered state of the particle filter. + * The first argument is the vector containing the particles, + * the second argument is the associated vector of weights and the third argument is a function + * to perform an addition in the state space. + * The return is the corresponding filtered state. + */ + typedef std::function &, const std::vector &, const vpStateAddFunction &)> vpFilterFunction; + + /** + * \brief Function that takes as argument the number of particles and the vector of weights + * associated to each particle and returns true if the resampling must be performed and false + * otherwise. + */ + typedef std::function &)> vpResamplingConditionFunction; + + /** + * \brief Function that takes as argument the vector of particles and the vector of + * associated weights. It returns a pair < new_vector_particles, new_weights >. + */ + typedef std::function &, const std::vector &)> vpResamplingFunction; + + /** + * \brief Construct a new vpParticleFilter object. + * + * \param[in] N The number of particles. + * \param[in] stdev The standard deviations of the noise, each item correspond to one component of the state. + * \param[in] seed The seed to use to create the noise generators. A negative value makes the seed to + * be based on the current time. + * \param[in] nbThreads The number of threads the user would like to use. Negative value to set the maximum number + * of threads available when using OpenMP. + */ + VP_EXPLICIT vpParticleFilter(const unsigned int &N, const std::vector &stdev, const long &seed = -1, const int &nbThreads = -1); + + inline virtual ~vpParticleFilter() { } + + /** + * \brief Set the guess of the initial state. + * + * \param[in] x0 Guess of the initial state. + * \param[in] f Process model function, which projects the particles forward in time. + * The first argument is a particle, the second is the period and the return is the + * particle projected in the future. + * \param[in] l Likelihood function, that evaluates how much a particle matches the measurements. + * 0 means that the particle does not match the measurement at all and 1 that it maches completely. + * \param[in] checkResamplingFunc The function that returns true when the filter starts to degenerate + * and false otherwise. + * \param[in] resamplingFunc The resampling function that generate new particles and associated weights + * when the filter starts to degenerate. + * \param[in] filterFunc The function to compute the filtered state from the particles and their weights. + * \param[in] addFunc The function that permits to perform an addition in the state space. + */ + void init(const vpColVector &x0, const vpProcessFunction &f, + const vpLikelihoodFunction &l, + const vpResamplingConditionFunction &checkResamplingFunc, const vpResamplingFunction &resamplingFunc, + const vpFilterFunction &filterFunc = weightedMean, + const vpStateAddFunction &addFunc = simpleAdd); + + /** + * \brief Set the guess of the initial state. + * + * \param[in] x0 Guess of the initial state. + * \param[in] bx Process model function, which projects the particles forward in time based on the previous + * state and on the input commands. The first argument is the command vector, the second is a particle and + * the last is the period. The return is the particle projected in the future. + * \param[in] l Likelihood function, that evaluates how much a particle matches the measurements. + * 0 means that the particle does not match the measurement at all and 1 that it maches completely. + * \param[in] checkResamplingFunc The function that returns true when the filter starts to degenerate + * and false otherwise. + * \param[in] resamplingFunc The resampling function that generate new particles and associated weights + * when the filter starts to degenerate. + * \param[in] filterFunc The function to compute the filtered state from the particles and their weights. + * \param[in] addFunc The function that permits to perform an addition in the state space. + */ + void init(const vpColVector &x0, const vpCommandStateFunction &bx, + const vpLikelihoodFunction &l, + const vpResamplingConditionFunction &checkResamplingFunc, const vpResamplingFunction &resamplingFunc, + const vpFilterFunction &filterFunc = weightedMean, + const vpStateAddFunction &addFunc = simpleAdd); + + /** + * \brief Perform first the prediction step and then the update step. + * If needed, resampling will also be performed. + * + * \param[in] z The new measurement. + * \param[in] dt The time in the future we must predict. + * \param[in] u The command(s) given to the system, if the impact of the system is known. + * + * \warning To use the commands, use the dedicated constructor or call + * vpUnscentedKalman::setCommandStateFunction beforehand. In the second case, the process + * function will be ignored. + */ + void filter(const MeasurementsType &z, const double &dt, const vpColVector &u = vpColVector()); + + /** + * \brief Predict the new state based on the last state and how far in time we want to predict. + * + * \param[in] dt The time in the future we must predict. + * \param[in] u The command(s) given to the system, if the impact of the system is known. + * + * \warning To use the commands, use the dedicated constructor or call + * vpUnscentedKalman::setCommandStateFunction beforehand. In the second case, the process + * function will be ignored. + */ + void predict(const double &dt, const vpColVector &u = vpColVector()); + + /** + * \brief Update the weights of the particles based on a new measurement. + * The weights will be normalized (i.e. each weight will be divided by the sum of the weights). + * + * \param[in] z The measurements at the current timestep. + */ + void update(const MeasurementsType &z); + + /** + * \brief Compute the filtered state from the particles and their associated weights. + * + * \return vpColVector The filtered state. + */ + vpColVector computeFilteredState(); + + /** + * \brief Set the process function to use when projecting the particles in the future. + * + * \param f The process function to use. + * + * \warning It will deactive the command function. + */ + inline void setProcessFunction(const vpProcessFunction &f) + { + m_f = f; + m_useCommandStateFunction = false; + m_useProcessFunction = true; + } + + /** + * \brief Set the command function to use when projecting the particles in the future. + * + * \param bx The command function to use. + * + * \warning It will deactivate the process function. + */ + inline void setCommandStateFunction(const vpCommandStateFunction &bx) + { + m_bx = bx; + m_useCommandStateFunction = true; + m_useProcessFunction = false; + } + + /** + * \brief Set the likelihood function that updates the weights of the particles + * based on the new measurements. + * + * \param likelihood The likelihood function. + */ + inline void setLikelihoodFunction(const vpLikelihoodFunction &likelihood) + { + m_likelihood = likelihood; + } + + /** + * \brief Set the filter function that compute the filtered state from the particles + * and their associated weights. + * + * \param filterFunc The filtering function to use. + */ + inline void setFilterFunction(const vpFilterFunction &filterFunc) + { + m_stateFilterFunc = filterFunc; + } + + /** + * \brief Set the function that returns true when the filter starts to degenerate + * and false otherwise. + * + * \param resamplingCondFunc The evaluation function to use. + */ + inline void setCheckResamplingFunction(const vpResamplingConditionFunction &resamplingCondFunc) + { + m_checkIfResample = resamplingCondFunc; + } + + /** + * \brief Set the resampling function that generate new particles and associated weights + * when the filter starts to degenerate. + * + * \param resamplingFunc The resampling function to use. + */ + inline void setResamplingFunction(const vpResamplingFunction &resamplingFunc) + { + m_resampling = resamplingFunc; + } + + /** + * \brief Simple function to compute an addition, which just does \f$ \textbf{res} = \textbf{a} + \textbf{toAdd} \f$ + * + * \param[in] a Vector to which we must add something. + * \param[in] toAdd The something we must add to \b a . + * \return vpColVector \f$ \textbf{res} = \textbf{a} + \textbf{toAdd} \f$ + */ + inline static vpColVector simpleAdd(const vpColVector &a, const vpColVector &toAdd) + { + vpColVector res = a + toAdd; + return res; + } + + /** + * \brief Simple function to compute a weighted mean, which just does + * \f$ \textbf{res} = \sum^{N-1}_{i=0} weights[i] \textbf{particles}[i] \f$ + * + * \param[in] particles Vector that contains all the particles. + * \param[in] weights Vector that contains the weights associated to the particles. + * \param[in] addFunc How to perform the addition. + * \return vpColVector \f$ \textbf{res} = \sum^{N-1}_{i=0} weights[i] \textbf{particles}[i] \f$ + */ + static vpColVector weightedMean(const std::vector &particles, const std::vector &weights, const vpStateAddFunction &addFunc); + + /** + * \brief Returns true if the following condition is fulfilled, or if all the particles diverged: + * \f$ \frac{2}{\sum_i (\frac{w_i}{\sum_j w_j})^2} < N \f$ + * + * \param[in] N The number of particles. + * \param[in] weights The weights associated to each particle. + * \return true Resampling must be performed. + * \return false Resampling is not needed. + */ + static bool simpleResamplingCheck(const unsigned int &N, const std::vector &weights); + + /** + * \brief Function implementing the resampling of a Simple Importance Resampling Particle Filter. + * + * \param[in] particles Vector containing the particles. + * \param[in] weights Vector containing the associated weights. + * \return vpParticlesWithWeights A pair of vector of particles and + * vector of associated weights. + */ + static vpParticlesWithWeights simpleImportanceResampling(const std::vector &particles, const std::vector &weights); + +private: + void initParticles(const vpColVector &x0); +#ifdef VISP_HAVE_OPENMP + void predictMultithread(const double &dt, const vpColVector &u); + void updateMultithread(const MeasurementsType &z); +#endif + + void predictMonothread(const double &dt, const vpColVector &u); + void updateMonothread(const MeasurementsType &z); + + static vpUniRand sampler; + static vpUniRand samplerRandomIdx; + + unsigned int m_N; /*!< Number of particles.*/ + unsigned int m_nbMaxThreads; /*!< Maximum number of threads to use.*/ + std::vector> m_noiseGenerators; /*!< The noise generator adding noise to the particles at each time step.*/ + std::vector m_particles; /*!< The particles.*/ + std::vector m_w; /*!< The weights associated to each particles.*/ + + vpColVector m_Xest; /*!< The estimated (i.e. filtered) state variables.*/ + + vpProcessFunction m_f; /*!< Process model function, which projects the sigma points forward in time.*/ + vpLikelihoodFunction m_likelihood; /*!< Likelihood function, which evaluates how much a particle matches the measurements.*/ + vpCommandStateFunction m_bx; /*!< Function that permits to compute the effect of the commands on the prior, with knowledge of the state.*/ + vpFilterFunction m_stateFilterFunc; /*!< Function to compute a weighted mean in the state space.*/ + vpResamplingConditionFunction m_checkIfResample; /*!< Return true if resampling must be performed, false otherwise.*/ + vpResamplingFunction m_resampling; /*!< Performs resampling, i.e. samples particles and weights when the particle filter degenerates.*/ + vpStateAddFunction m_stateAdd; /*!< Function to performs an addition in the state space.*/ + + bool m_useProcessFunction; /*!< Set to true when the Particle filter should use the process function.*/ + bool m_useCommandStateFunction; /*!< Set to true when the Particle filter should use the command function.*/ +}; + +template +vpUniRand vpParticleFilter::sampler; + +template +vpUniRand vpParticleFilter::samplerRandomIdx; + +template +vpParticleFilter::vpParticleFilter(const unsigned int &N, const std::vector &stdev, const long &seed, const int &nbThreads) + : m_N(N) + , m_particles(N) + , m_w(N, 1./static_cast(N)) + , m_useProcessFunction(false) + , m_useCommandStateFunction(false) +{ +#ifndef VISP_HAVE_OPENMP + m_nbMaxThreads = 1; + if (nbThreads > 1) { + std::cout << "[vpParticleFilter::vpParticleFilter] WARNING: OpenMP is not available, maximum number of threads to use clamped to 1" << std::endl; + } +#else + int maxThreads = omp_get_max_threads(); + if (nbThreads <= 0) { + m_nbMaxThreads = maxThreads; + } + else if (nbThreads > maxThreads) { + m_nbMaxThreads = maxThreads; + std::cout << "[vpParticleFilter::vpParticleFilter] WARNING: maximum number of threads to use clamped to " + << maxThreads << " instead of " << nbThreads << " due to OpenMP restrictions." << std::endl; + std::cout << "[vpParticleFilter::vpParticleFilter] If you want more, consider to use omp_set_num_threads before." << std::endl; + } + else { + m_nbMaxThreads = nbThreads; + } +#endif + // Generating the random generators + unsigned int sizeState = static_cast(stdev.size()); + m_noiseGenerators.resize(m_nbMaxThreads); + unsigned long long seedForGenerator; + if (seed > 0) { + seedForGenerator = seed; + } + else { + seedForGenerator = static_cast(vpTime::measureTimeMicros()); + } + + // Sampler for the simpleImportanceResampling method + sampler.setSeed(seed, 0x123465789ULL); + samplerRandomIdx.setSeed(seed + 4224, 0x123465789ULL); + + vpUniRand seedGenerator(seedForGenerator); + for (unsigned int threadId = 0; threadId < m_nbMaxThreads; ++threadId) { + for (unsigned int stateId = 0; stateId < sizeState; ++stateId) { + m_noiseGenerators[threadId].push_back(vpGaussRand(stdev[stateId], 0., static_cast(seedGenerator.uniform(0., 1e9)))); + } + } +} + +template +void vpParticleFilter::init(const vpColVector &x0, const vpProcessFunction &f, + const vpLikelihoodFunction &l, + const vpResamplingConditionFunction &checkResamplingFunc, const vpResamplingFunction &resamplingFunc, + const vpFilterFunction &filterFunc, const vpStateAddFunction &addFunc) +{ + if (x0.size() != m_noiseGenerators[0].size()) { + throw(vpException(vpException::dimensionError, "X0 does not have the same size than the vector of stdevs used to build the object")); + } + m_f = f; + m_stateFilterFunc = filterFunc; + m_likelihood = l; + m_checkIfResample = checkResamplingFunc; + m_resampling = resamplingFunc; + m_stateAdd = addFunc; + m_useProcessFunction = true; + m_useCommandStateFunction = false; + + // Initialize the different particles + initParticles(x0); +} + +template +void vpParticleFilter::init(const vpColVector &x0, const vpCommandStateFunction &bx, + const vpLikelihoodFunction &l, + const vpResamplingConditionFunction &checkResamplingFunc, const vpResamplingFunction &resamplingFunc, + const vpFilterFunction &filterFunc, const vpStateAddFunction &addFunc) +{ + if (x0.size() != m_noiseGenerators[0].size()) { + throw(vpException(vpException::dimensionError, "X0 does not have the same size than the vector of stdevs used to build the object")); + } + m_bx = bx; + m_stateFilterFunc = filterFunc; + m_likelihood = l; + m_checkIfResample = checkResamplingFunc; + m_resampling = resamplingFunc; + m_stateAdd = addFunc; + m_useProcessFunction = false; + m_useCommandStateFunction = true; + + // Initialize the different particles + initParticles(x0); +} + +template +void vpParticleFilter::filter(const MeasurementsType &z, const double &dt, const vpColVector &u) +{ + predict(dt, u); + update(z); +} + +template +void vpParticleFilter::predict(const double &dt, const vpColVector &u) +{ + if (m_nbMaxThreads == 1) { + predictMonothread(dt, u); + } +#ifdef VISP_HAVE_OPENMP + else { + predictMultithread(dt, u); + } +#endif +} + +template +void vpParticleFilter::update(const MeasurementsType &z) +{ + if (m_nbMaxThreads == 1) { + updateMonothread(z); + } +#ifdef VISP_HAVE_OPENMP + else { + updateMultithread(z); + } +#endif + bool shouldResample = m_checkIfResample(m_N, m_w); + if (shouldResample) { + vpParticlesWithWeights particles_weights = m_resampling(m_particles, m_w); + m_particles = std::move(particles_weights.m_particles); + m_w = std::move(particles_weights.m_weights); + } +} + +template +vpColVector vpParticleFilter::computeFilteredState() +{ + return m_stateFilterFunc(m_particles, m_w, m_stateAdd); +} + +template +vpColVector vpParticleFilter::weightedMean(const std::vector &particles, const std::vector &weights, const vpStateAddFunction &addFunc) +{ + size_t nbParticles = particles.size(); + if (nbParticles == 0) { + throw(vpException(vpException::dimensionError, "No particles to add when computing the mean")); + } + vpColVector res = particles[0] * weights[0]; + for (size_t i = 1; i < nbParticles; ++i) { + res = addFunc(res, particles[i] * weights[i]); + } + return res; +} + +template +bool vpParticleFilter::simpleResamplingCheck(const unsigned int &N, const std::vector &weights) +{ + double sumSquare = 0.; + for (unsigned int i = 0; i < N; ++i) { + sumSquare += weights[i] * weights[i]; + } + if (sumSquare < std::numeric_limits::epsilon()) { + // All the particles diverged + return true; + } + double N_eff = 1.0 / sumSquare; + return (N_eff < (N / 2.0)); +} + +template +typename vpParticleFilter::vpParticlesWithWeights vpParticleFilter::simpleImportanceResampling(const std::vector &particles, const std::vector &weights) +{ + unsigned int nbParticles = static_cast(particles.size()); + double x = 0.; + double sumWeights = 0.; + std::vector idx(nbParticles); + + // Draw indices of the randomly chosen particles from the vector of particles + for (unsigned int i = 0; i < nbParticles; ++i) { + x = sampler(); + sumWeights = 0.0; + int index = samplerRandomIdx.uniform(0, nbParticles); // In case all the weights are null + for (unsigned int j = 0; j < nbParticles; ++j) { + if (x < sumWeights + weights[j]) { + index = j; + break; + } + sumWeights += weights[j]; + } + idx[i] = index; + } + + // Draw the randomly chosen particles corresponding to the indices + vpParticlesWithWeights newParticlesWeights; + newParticlesWeights.m_particles.resize(nbParticles); + for (unsigned int i = 0; i < nbParticles; ++i) { + newParticlesWeights.m_particles[i] = particles[idx[i]]; + } + + // Reinitialize the weights + newParticlesWeights.m_weights.resize(nbParticles, 1.0/ static_cast(nbParticles)); + return newParticlesWeights; +} + +template +void vpParticleFilter::initParticles(const vpColVector &x0) +{ + unsigned int sizeState = x0.size(); + unsigned int chunkSize = m_N / m_nbMaxThreads; + double uniformWeight = 1. / static_cast(m_N); + for (unsigned int i = 0; i < m_nbMaxThreads; ++i) { + unsigned int idStart = chunkSize * i; + unsigned int idStop = chunkSize * (i + 1); + // Last chunk must go until the end + if (i == m_nbMaxThreads - 1) { + idStop = m_N; + } + for (unsigned int id = idStart; id < idStop; ++id) { + // Generating noise + vpColVector noise(sizeState); + for (unsigned int idState = 0; idState < sizeState; ++idState) { + noise[idState] = m_noiseGenerators[i][idState](); + } + // Adding noise to the initial state + m_particles[id] = m_stateAdd(x0, noise); + + // (Re)initializing its weight + m_w[id] = uniformWeight; + } + } +} + +#ifdef VISP_HAVE_OPENMP +template +void vpParticleFilter::predictMultithread(const double &dt, const vpColVector &u) +{ + int iam, nt, ipoints, istart, npoints(m_N); + unsigned int sizeState = m_particles[0].size(); + +#pragma omp parallel default(shared) private(iam, nt, ipoints, istart) num_threads(m_nbMaxThreads) + { + iam = omp_get_thread_num(); + nt = omp_get_num_threads(); + ipoints = npoints / nt; + // size of partition + istart = iam * ipoints; // starting array index + if (iam == nt-1) { + // last thread may do more + ipoints = npoints - istart; + } + + for (int i = istart; i< istart + ipoints; ++i) { + // Updating the particles following the process (or command) function + if (m_useCommandStateFunction) { + m_particles[i] = m_bx(u, m_particles[i], dt); + } + else if (m_useProcessFunction) { + m_particles[i] = m_f(m_particles[i], dt); + } + + // Generating noise to add to the particle + vpColVector noise(sizeState); + for (unsigned int j = 0; j < sizeState; ++j) { + noise[j] = m_noiseGenerators[iam][j](); + } + + // Adding the noise to the particle + m_particles[i] = m_stateAdd(m_particles[i], noise); + } + } +} + +template +double threadLikelihood(const typename vpParticleFilter::vpLikelihoodFunction &likelihood, const std::vector &v_particles, + const MeasurementsType &z, std::vector &w, const int &istart, const int &ipoints) +{ + double sum(0.0); + for (int i = istart; i< istart + ipoints; ++i) { + w[i] = w[i] * likelihood(v_particles[i], z); + sum += w[i]; + } + return sum; +} + +template +void vpParticleFilter::updateMultithread(const MeasurementsType &z) +{ + double sumWeights = 0.0; + int iam, nt, ipoints, istart, npoints(m_N); + vpColVector tempSums(m_nbMaxThreads, 0.0); + // Compute the weights depending on the likelihood of a particle with regard to the measurements +#pragma omp parallel default(shared) private(iam, nt, ipoints, istart) num_threads(m_nbMaxThreads) + { + iam = omp_get_thread_num(); + nt = omp_get_num_threads(); + ipoints = npoints / nt; + // size of partition + istart = iam * ipoints; // starting array index + if (iam == nt-1) { + // last thread may do more + ipoints = npoints - istart; + } + tempSums[iam] = threadLikelihood(m_likelihood, m_particles, z, m_w, istart, ipoints); + } + sumWeights = tempSums.sum(); + + if (sumWeights > std::numeric_limits::epsilon()) { +#pragma omp parallel default(shared) private(iam, nt, ipoints, istart) + { + iam = omp_get_thread_num(); + nt = omp_get_num_threads(); + ipoints = npoints / nt; + // size of partition + istart = iam * ipoints; // starting array index + if (iam == nt-1) { + // last thread may do more + ipoints = npoints - istart; + } + + // Normalize the weights + for (int i = istart; i < istart + ipoints; ++i) { + m_w[i] = m_w[i] / sumWeights; + } + } + } +} +#endif + +template +void vpParticleFilter::predictMonothread(const double &dt, const vpColVector &u) +{ + unsigned int sizeState = m_particles[0].size(); + for (unsigned int i = 0; i < m_N; ++i) { + // Updating the particle following the process (or command) function + if (m_useCommandStateFunction) { + m_particles[i] = m_bx(u, m_particles[i], dt); + } + else if (m_useProcessFunction) { + m_particles[i] = m_f(m_particles[i], dt); + } + else { + throw(vpException(vpException::notInitialized, "vpParticleFilter has not been initialized before calling predict")); + } + + // Generating noise to add to the particle + vpColVector noise(sizeState); + for (unsigned int j = 0; j < sizeState; ++j) { + noise[j] = m_noiseGenerators[0][j](); + } + + // Adding the noise to the particle + m_particles[i] = m_stateAdd(m_particles[i], noise); + } +} + +template +void vpParticleFilter::updateMonothread(const MeasurementsType &z) +{ + double sumWeights = 0.; + // Compute the weights depending on the likelihood of a particle with regard to the measurements + for (unsigned int i = 0; i < m_N; ++i) { + m_w[i] = m_w[i] * m_likelihood(m_particles[i], z); + sumWeights += m_w[i]; + } + + // Normalize the weights + if (sumWeights > std::numeric_limits::epsilon()) { + for (unsigned int i = 0; i < m_N; ++i) { + m_w[i] = m_w[i] / sumWeights; + } + } +} +END_VISP_NAMESPACE + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpPixelMeterConversion.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpPixelMeterConversion.h new file mode 100644 index 00000000..fab0d897 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpPixelMeterConversion.h @@ -0,0 +1,429 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Pixel to meter conversion. + */ + +/*! + \file vpPixelMeterConversion.h + \brief pixel to meter conversion +*/ + +#ifndef VP_PIXEL_METER_CONVERSION_H +#define VP_PIXEL_METER_CONVERSION_H + +#include +#include +#include +#include + +#if defined(VISP_HAVE_OPENCV) +#include +#endif + +BEGIN_VISP_NAMESPACE +/*! + \class vpPixelMeterConversion + + \ingroup group_core_camera + + Various conversion functions to transform primitives (2D line, moments, 2D point) from pixel to normalized + coordinates in meter in the image plane. + + Transformation relies either on ViSP camera parameters implemented in vpCameraParameters or on OpenCV camera parameters + that are set from a projection matrix and a distortion coefficients vector. + +

Tutorials & Examples

+ + Tutorials
+ To see how to use this class, you may have a look at:
+ + - \ref tutorial-pose-estimation + +*/ +class VISP_EXPORT vpPixelMeterConversion +{ +public: + /** @name Using ViSP camera parameters */ + //@{ + static void convertEllipse(const vpCameraParameters &cam, const vpImagePoint ¢er_p, double n20_p, double n11_p, + double n02_p, double &xc_m, double &yc_m, double &n20_m, double &n11_m, double &n02_m); + static void convertLine(const vpCameraParameters &cam, const double &rho_p, const double &theta_p, double &rho_m, + double &theta_m); + + static void convertMoment(const vpCameraParameters &cam, unsigned int order, const vpMatrix &moment_pixel, + vpMatrix &moment_meter); + /*! + Point coordinates conversion from pixel coordinates + \f$(u,v)\f$ to normalized coordinates \f$(x,y)\f$ in meter using ViSP camera parameters. + + The used formula depends on the projection model of the camera. To + know the currently used projection model use + vpCameraParameter::get_projModel() + + \param[in] cam : camera parameters. + \param[in] u : input coordinate in pixels along image horizontal axis. + \param[in] v : input coordinate in pixels along image vertical axis. + \param[out] x : output coordinate in meter along image plane x-axis. + \param[out] y : output coordinate in meter along image plane y-axis. + + \f$ x = (u-u_0)/p_x \f$ and \f$ y = (v-v_0)/p_y \f$ in the case of + perspective projection without distortion. + + \f$ x = (u-u_0)*(1+k_{du}*r^2)/p_x \f$ and + \f$ y = (v-v_0)*(1+k_{du}*r^2)/p_y \f$ + with \f$ r^2=((u - u_0)/p_x)^2+((v-v_0)/p_y)^2 \f$ in the case of + perspective projection with distortion. + + In the case of a projection with Kannala-Brandt distortion, refer to + \cite KannalaBrandt. + */ + inline static void convertPoint(const vpCameraParameters &cam, const double &u, const double &v, double &x, double &y) + { + switch (cam.m_projModel) { + case vpCameraParameters::perspectiveProjWithoutDistortion: + convertPointWithoutDistortion(cam, u, v, x, y); + break; + case vpCameraParameters::perspectiveProjWithDistortion: + convertPointWithDistortion(cam, u, v, x, y); + break; + case vpCameraParameters::ProjWithKannalaBrandtDistortion: + convertPointWithKannalaBrandtDistortion(cam, u, v, x, y); + break; + default: { + throw(vpException(vpException::fatalError, "Unsupported camera projection model in vpPixelMerterConversion::convertPoint()")); + } + } + } + + /*! + Point coordinates conversion from pixel coordinates + Coordinates in pixel to normalized coordinates \f$(x,y)\f$ in meter using ViSP camera parameters. + + The used formula depends on the projection model of the camera. To + know the currently used projection model use + vpCameraParameter::get_projModel() + + \param[in] cam : camera parameters. + \param[in] iP : input coordinates in pixels. + \param[out] x : output coordinate in meter along image plane x-axis. + \param[out] y : output coordinate in meter along image plane y-axis. + + Thanks to the pixel coordinates in the frame (u,v), the meter coordinates + are given by : + + \f$ x = (u-u_0)/p_x \f$ and \f$ y = (v-v_0)/p_y \f$ in the case of + perspective projection without distortion. + + \f$ x = (u-u_0)*(1+k_{du}*r^2)/p_x \f$ and + \f$ y = (v-v_0)*(1+k_{du}*r^2)/p_y \f$ + with \f$ r^2=((u - u_0)/p_x)^2+((v-v_0)/p_y)^2 \f$ in the case of + perspective projection with distortion. + + In the case of a projection with Kannala-Brandt distortion, refer to + \cite KannalaBrandt. + */ + inline static void convertPoint(const vpCameraParameters &cam, const vpImagePoint &iP, double &x, double &y) + { + switch (cam.m_projModel) { + case vpCameraParameters::perspectiveProjWithoutDistortion: + convertPointWithoutDistortion(cam, iP, x, y); + break; + case vpCameraParameters::perspectiveProjWithDistortion: + convertPointWithDistortion(cam, iP, x, y); + break; + case vpCameraParameters::ProjWithKannalaBrandtDistortion: + convertPointWithKannalaBrandtDistortion(cam, iP, x, y); + break; + default: { + throw(vpException(vpException::fatalError, "Unsupported camera projection model in vpPixelMerterConversion::convertPoint()")); + } + } + } + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + /*! + Point coordinates conversion without distortion from pixel + coordinates \f$(u,v)\f$ to normalized coordinates \f$(x,y)\f$ in meter. + + \param[in] cam : camera parameters. + \param[in] u : input coordinate in pixels along image horizontal axis. + \param[in] v : input coordinate in pixels along image vertical axis. + \param[out] x : output coordinate in meter along image plane x-axis. + \param[out] y : output coordinate in meter along image plane y-axis. + + \f$ x = (u-u_0)/p_x \f$ and \f$ y = (v-v_0)/p_y \f$ + */ + inline static void convertPointWithoutDistortion(const vpCameraParameters &cam, const double &u, const double &v, + double &x, double &y) + { + x = (u - cam.m_u0) * cam.m_inv_px; + y = (v - cam.m_v0) * cam.m_inv_py; + } + + /*! + Point coordinates conversion without distortion from pixel + coordinates Coordinates in pixel to normalized coordinates \f$(x,y)\f$ in + meter. + + \param[in] cam : camera parameters. + \param[in] iP : input coordinates in pixels. + \param[out] x : output coordinate in meter along image plane x-axis. + \param[out] y : output coordinate in meter along image plane y-axis. + + Thanks to the pixel coordinates in the frame (u,v), the meter coordinates + are given by : + + \f$ x = (u-u_0)/p_x \f$ and \f$ y = (v-v_0)/p_y \f$ + */ + inline static void convertPointWithoutDistortion(const vpCameraParameters &cam, const vpImagePoint &iP, double &x, + double &y) + { + x = (iP.get_u() - cam.m_u0) * cam.m_inv_px; + y = (iP.get_v() - cam.m_v0) * cam.m_inv_py; + } + + /*! + Point coordinates conversion with distortion from pixel coordinates + \f$(u,v)\f$ to normalized coordinates \f$(x,y)\f$ in meter. + + \param[in] cam : camera parameters. + \param[in] u : input coordinate in pixels along image horizontal axis. + \param[in] v : input coordinate in pixels along image vertical axis. + \param[out] x : output coordinate in meter along image plane x-axis. + \param[out] y : output coordinate in meter along image plane y-axis. + + \f$ x = (u-u_0)*(1+k_{du}*r^2)/p_x \f$ and + \f$ y = (v-v_0)*(1+k_{du}*r^2)/p_y \f$ + with \f$ r^2=((u - u_0)/p_x)^2 + ((v-v_0)/p_y)^2 \f$ + */ + inline static void convertPointWithDistortion(const vpCameraParameters &cam, const double &u, const double &v, + double &x, double &y) + { + double r2 = 1. + (cam.m_kdu * (vpMath::sqr((u - cam.m_u0) * cam.m_inv_px) + vpMath::sqr((v - cam.m_v0) * cam.m_inv_py))); + x = (u - cam.m_u0) * r2 * cam.m_inv_px; + y = (v - cam.m_v0) * r2 * cam.m_inv_py; + } + + /*! + Point coordinates conversion with distortion from pixel coordinates + Coordinates in pixel to normalized coordinates \f$(x,y)\f$ in meter. + + \param[in] cam : camera parameters. + \param[in] iP : input coordinates in pixels. + \param[out] x : output coordinate in meter along image plane x-axis. + \param[out] y : output coordinate in meter along image plane y-axis. + + Thanks to the pixel coordinates in the frame (u,v), the meter coordinates + are given by : + + \f$ x = (u-u_0)*(1+k_{du}*r^2)/p_x \f$ and + \f$ y = (v-v_0)*(1+k_{du}*r^2)/p_y \f$ + with \f$ r^2=((u - u_0)/p_x)^2 + ((v-v_0)/p_y)^2 \f$ + */ + inline static void convertPointWithDistortion(const vpCameraParameters &cam, const vpImagePoint &iP, double &x, + double &y) + { + double r2 = 1. + (cam.m_kdu * (vpMath::sqr((iP.get_u() - cam.m_u0) * cam.m_inv_px) + + vpMath::sqr((iP.get_v() - cam.m_v0) * cam.m_inv_py))); + x = (iP.get_u() - cam.m_u0) * r2 * cam.m_inv_px; + y = (iP.get_v() - cam.m_v0) * r2 * cam.m_inv_py; + } + + /*! + Point coordinates conversion with Kannala-Brandt distortion from pixel + coordinates \f$(u,v)\f$ to normalized coordinates \f$(x,y)\f$ in meter. + + \param[in] cam : Camera parameters. + \param[in] u : Input coordinate in pixels along image horizontal axis. + \param[in] v : Input coordinate in pixels along image vertical axis. + \param[out] x : Output coordinate in meter along image plane x-axis. + \param[out] y : Output coordinate in meter along image plane y-axis. + + \f$ x_d = (u-u_0)/px \f$ + \f$ y_d = (v-v_0)/py \f$ + \f$ r_d = \sqrt{x^2_d + y^2_d} \f$ + Solve for \f$ \theta \f$ knowing that: + \f$ r_d = \theta + k_1 \theta^3 + k_2 \theta^5 + k_3 \theta^7 + k_4 \theta^5 \f$ + Calculate the distortion scale \f$ scale \f$: + \f$ scale = \tan(\theta) / r_d \f$ + \f$ x = x_d * scale \f$ + \f$ y = y_d * scale \f$ + */ + inline static void convertPointWithKannalaBrandtDistortion(const vpCameraParameters &cam, const double &u, + const double &v, double &x, double &y) + { + double x_d = (u - cam.m_u0) / cam.m_px, y_d = (v - cam.m_v0) / cam.m_py; + double scale = 1.0; + double r_d = sqrt(vpMath::sqr(x_d) + vpMath::sqr(y_d)); + const unsigned int index_0 = 0; + const unsigned int index_1 = 1; + const unsigned int index_2 = 2; + const unsigned int index_3 = 3; + const unsigned int val_1 = 1; + const unsigned int val_3 = 3; + const unsigned int val_5 = 5; + const unsigned int val_7 = 7; + const unsigned int val_9 = 9; + const unsigned int val_10 = 10; + + r_d = std::min(std::max(-M_PI, r_d), M_PI); // FOV restricted to 180degrees. + + std::vector k = cam.getKannalaBrandtDistortionCoefficients(); + + const double EPS = 1e-8; + // Use Newton-Raphson method to solve for the angle theta + if (r_d > EPS) { + // compensate distortion iteratively + double theta = r_d; + + for (unsigned int j = 0; j < val_10; ++j) { + double theta2 = theta * theta; + double theta4 = theta2 * theta2; + double theta6 = theta4 * theta2; + double theta8 = theta6 * theta2; + double k0_theta2 = k[index_0] * theta2; + double k1_theta4 = k[index_1] * theta4; + double k2_theta6 = k[index_2] * theta6, + k3_theta8 = k[index_3] * theta8; + /* + // new_theta = theta - theta_fix, theta_fix = f0(theta) / f0'(theta) + */ + double theta_fix = ((theta * (val_1 + k0_theta2 + k1_theta4 + k2_theta6 + k3_theta8)) - r_d) + / (val_1 + (val_3 * k0_theta2) + (val_5 * k1_theta4) + (val_7 * k2_theta6) + (val_9 * k3_theta8)); + theta = theta - theta_fix; + if (fabs(theta_fix) < EPS) { + break; + } + } + + scale = std::tan(theta) / r_d; // Scale of norm of (x,y) and (x_d, y_d) + } + + x = x_d * scale; + y = y_d * scale; + } + + /*! + Point coordinates conversion with Kannala-Brandt distortion from pixel + coordinates in pixel to normalized coordinates \f$(x,y)\f$ in meter. + + \param[in] cam : camera parameters. + \param[in] iP : input coordinates in pixels. + \param[out] x : output coordinate in meter along image plane x-axis. + \param[out] y : output coordinate in meter along image plane y-axis. + + \f$ x_d = (u-u_0)/px \f$ + \f$ y_d = (v-v_0)/py \f$ + \f$ r_d = \sqrt{x^2_d + y^2_d} \f$ + Solve for \f$ \theta \f$ knowing that: + \f$ r_d = \theta + k_1 \theta^3 + k_2 \theta^5 + k_3 \theta^7 + k_4 \theta^5 \f$ + Calculate the distortion scale \f$ scale \f$: + \f$ scale = \tan(\theta) / r_d \f$ + \f$ x = x_d * scale \f$ + \f$ y = y_d * scale \f$ + */ + inline static void convertPointWithKannalaBrandtDistortion(const vpCameraParameters &cam, const vpImagePoint &iP, + double &x, double &y) + { + double x_d = (iP.get_u() - cam.m_u0) / cam.m_px, y_d = (iP.get_v() - cam.m_v0) / cam.m_py; + double scale = 1.0; + double r_d = sqrt(vpMath::sqr(x_d) + vpMath::sqr(y_d)); + const unsigned int index_0 = 0; + const unsigned int index_1 = 1; + const unsigned int index_2 = 2; + const unsigned int index_3 = 3; + const unsigned int val_1 = 1; + const unsigned int val_3 = 3; + const unsigned int val_5 = 5; + const unsigned int val_7 = 7; + const unsigned int val_9 = 9; + const unsigned int val_10 = 10; + + r_d = std::min(std::max(-M_PI, r_d), M_PI); // FOV restricted to 180degrees. + + std::vector k = cam.getKannalaBrandtDistortionCoefficients(); + + const double EPS = 1e-8; + // Use Newton-Raphson method to solve for the angle theta + if (r_d > EPS) { + // compensate distortion iteratively + double theta = r_d; + + for (unsigned int j = 0; j < val_10; ++j) { + double theta2 = theta * theta; + double theta4 = theta2 * theta2; + double theta6 = theta4 * theta2; + double theta8 = theta6 * theta2; + double k0_theta2 = k[index_0] * theta2; + double k1_theta4 = k[index_1] * theta4; + double k2_theta6 = k[index_2] * theta6; + double k3_theta8 = k[index_3] * theta8; + /* + // new_theta = theta - theta_fix, theta_fix = f0(theta) / f0'(theta) + */ + double theta_fix = ((theta * (val_1 + k0_theta2 + k1_theta4 + k2_theta6 + k3_theta8)) - r_d) / + (val_1 + (val_3 * k0_theta2) + (val_5 * k1_theta4) + (val_7 * k2_theta6) + (val_9 * k3_theta8)); + theta = theta - theta_fix; + if (fabs(theta_fix) < EPS) { + break; + } + } + + scale = std::tan(theta) / r_d; // Scale of norm of (x,y) and (x_d, y_d) + } + + x = x_d * scale; + y = y_d * scale; + } +#endif // #ifndef DOXYGEN_SHOULD_SKIP_THIS + //@} + +#if defined(HAVE_OPENCV_IMGPROC) && \ + (((VISP_HAVE_OPENCV_VERSION < 0x050000) && defined(HAVE_OPENCV_CALIB3D)) || ((VISP_HAVE_OPENCV_VERSION >= 0x050000) && defined(HAVE_OPENCV_CALIB) && defined(HAVE_OPENCV_3D))) + + /** @name Using OpenCV camera parameters */ + //@{ + static void convertEllipse(const cv::Mat &cameraMatrix, const cv::Mat &distCoeffs, const vpImagePoint ¢er_p, + double n20_p, double n11_p, double n02_p, double &xc_m, double &yc_m, double &n20_m, + double &n11_m, double &n02_m); + static void convertLine(const cv::Mat &cameraMatrix, const double &rho_p, const double &theta_p, double &rho_m, + double &theta_m); + static void convertMoment(const cv::Mat &cameraMatrix, unsigned int order, const vpMatrix &moment_pixel, + vpMatrix &moment_meter); + static void convertPoint(const cv::Mat &cameraMatrix, const cv::Mat &distCoeffs, const double &u, const double &v, + double &x, double &y); + static void convertPoint(const cv::Mat &cameraMatrix, const cv::Mat &distCoeffs, const vpImagePoint &iP, double &x, + double &y); + //@} +#endif +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpPlane.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpPlane.h new file mode 100644 index 00000000..7b3b40b7 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpPlane.h @@ -0,0 +1,173 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Plane geometrical structure. + */ + +#ifndef VP_PLANE_H +#define VP_PLANE_H + +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE + +/*! + \class vpPlane + + \ingroup group_core_geometry + + \brief This class defines the container for a plane geometrical structure. + + A plane is given by the equation \f$A*X + B*Y + C*Z + D = 0\f$ where + (X,Y,Z) are the coordinates of a point and where \f$[A,B,C]^T\f$ is the normal + vector of the plane. +*/ +class VISP_EXPORT vpPlane +{ +#ifdef VISP_BUILD_DEPRECATED_FUNCTIONS + // for backward compatibility +public: + double A, B, C, D; +#endif + +public: + typedef enum { object_frame, camera_frame } vpPlaneFrame; + vpPlane(); + vpPlane(const vpPlane &P); + vpPlane(double A, double B, double C, double D); + vpPlane(const vpPoint &P, const vpColVector &normal, const vpPlaneFrame &frame = camera_frame); + vpPlane(const vpPoint &P, const vpPoint &Q, const vpPoint &R, const vpPlaneFrame &frame = camera_frame); + + double computeZ(double x, double y) const; + + vpPlane &init(const vpPoint &P, const vpColVector &normal, const vpPlaneFrame &frame = camera_frame); + vpPlane &init(const vpPoint &P, const vpPoint &Q, const vpPoint &R, const vpPlaneFrame &frame = camera_frame); + vpPlane &init(const vpColVector &P, const vpColVector &n); + vpPlane &init(const vpPlane &P); + + // SET the parameter + /*! Set plane parameter A. */ + inline void setA(double a) { this->A = a; } + /*! Set plane parameter B. */ + inline void setB(double b) { this->B = b; } + /*! Set plane parameter C. */ + inline void setC(double c) { this->C = c; } + /*! Set plane parameter D. */ + inline void setD(double d) { this->D = d; } + /*! Set plane parameters A, B, C, D. */ + inline void setABCD(double a, double b, double c, double d) + { + this->A = a; + this->B = b; + this->C = c; + this->D = d; + } + + vpPlane &operator=(const vpPlane &f); + + // GET information + /*! \return The value of the plane parameter A. */ + double getA() const { return A; } + /*! \return The value of the plane parameter B. */ + double getB() const { return B; } + /*! \return The value of the plane parameter C. */ + double getC() const { return C; } + /*! \return The value of the plane parameter D. */ + double getD() const { return D; } + + /*! + + \return Return the four dimension vector \f$[A,B,C,D]^T\f$ + corresponding to the plane parameters. + + */ + inline vpColVector getABCD() const + { + const unsigned int index_0 = 0; + const unsigned int index_1 = 1; + const unsigned int index_2 = 2; + const unsigned int index_3 = 3; + vpColVector n(4); + n[index_0] = A; + n[index_1] = B; + n[index_2] = C; + n[index_3] = D; + + return n; + } + + /*! + + \warning This method is provided for compatibility with the + previous versions. Users should now use getABCD(). + + \return Return the four dimension vector \f$[A,B,C,D]^T\f$ + corresponding to the plane parameters. + + \sa getABCD() + */ + inline vpColVector abcd() const + { + const unsigned int index_0 = 0; + const unsigned int index_1 = 1; + const unsigned int index_2 = 2; + const unsigned int index_3 = 3; + vpColVector n(4); + n[index_0] = A; + n[index_1] = B; + n[index_2] = C; + n[index_3] = D; + + return n; + } + + vpColVector getNormal() const; + void getNormal(vpColVector &n) const; + + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpPlane &p); + + // Operation with Plane + void projectionPointOnPlan(const vpPoint &P, vpPoint &Pproj, const vpPlaneFrame &frame = camera_frame) const; + + double rayIntersection(const vpPoint &M0, const vpPoint &M1, vpColVector &H) const; + + double getIntersection(const vpColVector &M1, vpColVector &H) const; + void changeFrame(const vpHomogeneousMatrix &cMo); + +#ifndef VISP_BUILD_DEPRECATED_FUNCTIONS +private: + double A, B, C, D; +#endif +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpPoint.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpPoint.h new file mode 100644 index 00000000..b48190fa --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpPoint.h @@ -0,0 +1,154 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Point feature. + */ + +/*! + \file vpPoint.h + \brief class that defines what is a point +*/ + +#ifndef VP_POINT_H +#define VP_POINT_H + +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +class vpHomogeneousMatrix; + +/*! + \class vpPoint + \ingroup group_core_geometry + \brief Class that defines a 3D point in the object frame and allows forward projection of a 3D point in the + camera frame and in the 2D image plane by perspective projection. + All the parameters must be set in meter. + + A 3D point has the followings parameters: + - **in the object frame**: the normalized 3D coordinates oX, oY, oZ, oW of the point. These + parameters registered in vpForwardProjection::oP internal 4-dim vector are set using the constructors vpPoint(double + oX, double oY, double oZ), vpPoint(const vpColVector &oP) and vpPoint(const std::vector &oP) or the functions + setWorldCoordinates(double oX, double oY, double oZ), + setWorldCoordinates(const vpColVector &oP) and setWorldCoordinates(const std::vector &oP). + To get theses parameters use get_oP(). + + - **in the camera frame**: the normalized coordinates cX, cY, cZ, 1 of the point. These + parameters registered in vpTracker::cP internal 4-dim vector are computed using + changeFrame(const vpHomogeneousMatrix &cMo, vpColVector &cP) const or changeFrame(const vpHomogeneousMatrix &cMo). + These parameters could be retrieved using get_X(), get_Y() and get_Z(). + To get theses parameters use get_cP(). + + - **in the image plane**: the 2D normalized coordinates (x, y, 1) corresponding + to the perspective projection of the point. These parameters are registered in vpTracker::p internal 3-dim vector and + computed using projection() and projection(const vpColVector &cP, vpColVector &p) const. They could be retrieved using + get_x() and get_y(). They correspond to 2D normalized point parameters with values expressed in meters. To get theses + parameters use get_p(). + +*/ +class VISP_EXPORT vpPoint : public vpForwardProjection +{ + +public: + //! Basic constructor. + vpPoint(); + vpPoint(double oX, double oY, double oZ); + VP_EXPLICIT vpPoint(const vpColVector &oP); + VP_EXPLICIT vpPoint(const std::vector &oP); + +public: + // Compute the 3D coordinates _cP (camera frame) + void changeFrame(const vpHomogeneousMatrix &cMo, vpColVector &cP) const VP_OVERRIDE; + void changeFrame(const vpHomogeneousMatrix &cMo) VP_OVERRIDE; + + void display(const vpImage &I, const vpCameraParameters &cam, const vpColor &color = vpColor::green, + unsigned int thickness = 1) VP_OVERRIDE; + void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &color = vpColor::green, unsigned int thickness = 1) VP_OVERRIDE; + + void display(const vpImage &I, const vpCameraParameters &cam, const vpColor &color = vpColor::green, + unsigned int thickness = 1); + void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &color = vpColor::green, unsigned int thickness = 1); + + vpPoint *duplicate() const VP_OVERRIDE; + + // Get coordinates + double get_X() const; + double get_Y() const; + double get_Z() const; + double get_W() const; + double get_oX() const; + double get_oY() const; + double get_oZ() const; + double get_oW() const; + double get_x() const; + double get_y() const; + double get_w() const; + + void getWorldCoordinates(double &oX, double &oY, double &oZ); + void getWorldCoordinates(vpColVector &oP); + vpColVector getWorldCoordinates(void); + void getWorldCoordinates(std::vector &oP); + + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpPoint &vpp); + + //! Projection onto the image plane of a point. Input: the 3D coordinates in + //! the camera frame _cP, output : the 2D coordinates _p. + + void projection(const vpColVector &_cP, vpColVector &_p) const VP_OVERRIDE; + void projection() VP_OVERRIDE; + + // Set coordinates + void set_X(double cX); + void set_Y(double cY); + void set_Z(double cZ); + void set_W(double cW); + void set_oX(double oX); + void set_oY(double oY); + void set_oZ(double oZ); + void set_oW(double oW); + void set_x(double x); + void set_y(double y); + void set_w(double w); + + void setWorldCoordinates(double oX, double oY, double oZ); + + void setWorldCoordinates(const vpColVector &oP) VP_OVERRIDE; + void setWorldCoordinates(const std::vector &oP); + +protected: + //! Basic construction. + void init() VP_OVERRIDE; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpPolygon.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpPolygon.h new file mode 100644 index 00000000..ea6962aa --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpPolygon.h @@ -0,0 +1,202 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Defines a generic 2D polygon. + */ + +#ifndef VP_POLYGON_H +#define VP_POLYGON_H + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpPolygon + \ingroup group_core_geometry + \brief Defines a generic 2D polygon. + + A polygon is internally represented by N 2D points. + + By default three coordinates in the \f$ (i,j) \f$ frame (see vpImagePoint + class documentation for more details about the frame) are used \f$ (0,0) + \f$, \f$ (1,0) \f$ and \f$ (0,1) \f$. + + The code bellow shows how to manipulate a polygon. + \code + #include + + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + std::vector corners; + + // Initialize the corners vector with 4 points + corners.push_back( vpImagePoint( 50, 100) ); + corners.push_back( vpImagePoint( 50, 300) ); + corners.push_back( vpImagePoint(200, 300) ); + corners.push_back( vpImagePoint(200, 100) ); + + // Initialize a polygon with the corners + vpPolygon polygon(corners); + + // Get the polygon bounding box + vpRect bbox = polygon.getBoundingBox(); + std::cout << "Bounding box: " << bbox.getTopLeft() << " to " + << bbox.getBottomRight() << std::endl; + + // Get the polygon surface and center + std::cout << "Area: " << polygon.getArea() << std::endl; + std::cout << "Center: " << polygon.getCenter() << std::endl; + + // Check if a point is inside the polygon + vpImagePoint ip(550, 200); + std::cout << "The point " << ip << " is " + << (polygon.isInside(ip) ? "inside":"outside") + << " the polygon" << std::endl; + + return 0; + } + \endcode +*/ +class VISP_EXPORT vpPolygon +{ +public: + enum PointInPolygonMethod + { + PnPolySegmentIntersection, /*!< Legacy Point In Polygon test. */ + PnPolyRayCasting /*!< Point In Polygon test using ray casting method + (faster). */ + }; + + vpPolygon(); + VP_EXPLICIT vpPolygon(const std::vector &corners); + VP_EXPLICIT vpPolygon(const std::list &corners); + vpPolygon(const vpPolygon &poly); + virtual ~vpPolygon(); + + vpPolygon &operator=(const vpPolygon &poly); + + vpPolygon &buildFrom(const std::vector &corners, const bool &create_convex_hull = false); + vpPolygon &buildFrom(const std::list &corners, const bool &create_convex_hull = false); + vpPolygon &buildFrom(const std::vector &corners, const vpCameraParameters &cam, + const bool &create_convex_hull = false); + + unsigned int getSize() const; + void initClick(const vpImage &I, unsigned int size = 5, const vpColor &color = vpColor::red, + unsigned int thickness = 1); + void initClick(const vpImage &I, unsigned int size = 5, const vpColor &color = vpColor::red, + unsigned int thickness = 1); + + bool isInside(const vpImagePoint &iP, const PointInPolygonMethod &method = PnPolyRayCasting) const; + + void display(const vpImage &I, const vpColor &color, unsigned int thickness = 1) const; + + /*! + Get the corners of the polygon. + + \return A reference to the corners. + */ + const std::vector &getCorners() const { return _corners; } + + /*! + Return the area of the polygon. + The area is computed when the polygon is built from the corners. + + \return The area of the polygon. + */ + inline double getArea() const { return this->_area; } + + /*! + Return the center of the polygon. + The center is computed when the polygon is built from the corners. + + \return The area of the polygon. + */ + inline vpImagePoint getCenter() const { return this->_center; } + + /*! + Return the bounding box. The bounding box is the smallest rectangle + containing all the polygon. + + \return The bounding box of the polygon. + */ + inline vpRect getBoundingBox() const { return _bbox; } + + //################### + // Static Functions + //################### + static bool isInside(const std::vector &roi, const double &i, const double &j, + const PointInPolygonMethod &method = PnPolyRayCasting); + +protected: + void init(const std::vector &corners); + void init(const std::list &corners); + void updateArea(); + void updateCenter(); + void updateBoundingBox(); + +protected: + //! Collection of image points containing the corners. + std::vector _corners; + //! Center of the polygon. It is automatically computed when the corners are + //! set. + vpImagePoint _center; + //! Area of the polygon. + double _area; + //! Flag set to true when the polygon is a good polygon (ie. it has more + //! than two corners) or false otherwise. + bool _goodPoly; + //! Bounding box containing the polygon. + vpRect _bbox; + +private: + bool testIntersectionSegments(const vpImagePoint &ip1, const vpImagePoint &ip2, const vpImagePoint &ip3, + const vpImagePoint &ip4) const; + void precalcValuesPnPoly(); + + std::vector m_PnPolyConstants; + std::vector m_PnPolyMultiples; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpPolygon3D.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpPolygon3D.h new file mode 100644 index 00000000..cce8073b --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpPolygon3D.h @@ -0,0 +1,274 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Implements a polygon of the model used by the model-based tracker. + */ + +/*! + \file vpPolygon3D.h + \brief Implements a 3D polygon with render functionalities like clipping. +*/ + +#ifndef vpPolygon3D_HH +#define vpPolygon3D_HH + +#include + +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpPolygon3D + \ingroup group_core_geometry + \brief Implements a 3D polygon with render functionalities like clipping. + +*/ +class VISP_EXPORT vpPolygon3D +{ +public: + typedef enum + { + NO_CLIPPING = 0, + NEAR_CLIPPING = 1, + FAR_CLIPPING = 2, + LEFT_CLIPPING = 4, + RIGHT_CLIPPING = 8, + UP_CLIPPING = 16, + DOWN_CLIPPING = 32, + FOV_CLIPPING = 60, + ALL_CLIPPING = 63 + } vpPolygon3DClippingType; + +public: + //! Number of points used to define the polygon. + unsigned int nbpt; + //! Number of corners inside the image during the last call to + //! getNbCornerInsideImage + unsigned int nbCornersInsidePrev; + //! corners in the object frame + vpPoint *p; + //! Region of interest clipped + std::vector > polyClipped; + //! Clipping flag + unsigned int clippingFlag; + //! Distance for near clipping + double distNearClip; + //! Distance for near clipping + double distFarClip; + +private: + bool getClippedPointsFovGeneric(const vpPoint &p1, const vpPoint &p2, vpPoint &p1Clipped, vpPoint &p2Clipped, + unsigned int &p1ClippedInfo, unsigned int &p2ClippedInfo, const vpColVector &normal, + const unsigned int &flag); + + bool getClippedPointsDistance(const vpPoint &p1, const vpPoint &p2, vpPoint &p1Clipped, vpPoint &p2Clipped, + unsigned int &p1ClippedInfo, unsigned int &p2ClippedInfo, const unsigned int &flag, + const double &distance); + +public: + vpPolygon3D(); + vpPolygon3D(const vpPolygon3D &mbtp); + virtual ~vpPolygon3D(); + + void addPoint(unsigned int n, const vpPoint &P); + + void changeFrame(const vpHomogeneousMatrix &cMo); + + void computePolygonClipped(const vpCameraParameters &cam = vpCameraParameters()); + + /*! + Get the clipping used. + + \sa vpPolygon3DClipping + + \return Clipping flags. + */ + inline unsigned int getClipping() const { return clippingFlag; } + + /*! + Get the far distance for clipping. + + \return Far clipping value. + */ + inline double getFarClippingDistance() const { return distFarClip; } + + /*! + Return the number of corners. + + \return number of corner of the face + */ + inline unsigned int getNbPoint() const { return nbpt; } + + /*! + Return the number of corners at the previous computation. + + \return number of corner of the face at the previous computation + */ + inline unsigned int getNbCornerInsidePrevImage() const { return nbCornersInsidePrev; } + + unsigned int getNbCornerInsideImage(const vpImage &I, const vpCameraParameters &cam); + + /*! + Get the near distance for clipping. + + \return Near clipping value. + */ + inline double getNearClippingDistance() const { return distNearClip; } + + vpPoint &getPoint(const unsigned int _index); + + std::vector getRoi(const vpCameraParameters &cam); + + std::vector getRoi(const vpCameraParameters &cam, const vpHomogeneousMatrix &cMo); + + void getRoiClipped(const vpCameraParameters &cam, std::vector &roi); + + void getRoiClipped(const vpCameraParameters &cam, std::vector &roi, const vpHomogeneousMatrix &cMo); + + void getRoiClipped(const vpCameraParameters &cam, std::vector > &roi); + + void getRoiClipped(const vpCameraParameters &cam, std::vector > &roi, + const vpHomogeneousMatrix &cMo); + +#ifdef VISP_BUILD_DEPRECATED_FUNCTIONS + /*! + @name Deprecated functions + */ + //@{ + VP_DEPRECATED void getRoiClipped(std::vector &points); +//@} +#endif + + void getPolygonClipped(std::vector > &poly); + + void getPolygonClipped(std::vector &poly); + + vpPolygon3D &operator=(const vpPolygon3D &mbtp); + + /*! + Specify which clipping to use. + + \sa vpPolygon3DClipping + + \param flags : New clipping flags. + */ + inline void setClipping(const unsigned int &flags) { clippingFlag = flags; } + + /*! + Set the far distance for clipping. + + \param dist : Far clipping value. + */ + inline void setFarClippingDistance(const double &dist) + { + distFarClip = dist; + clippingFlag = (clippingFlag | vpPolygon3D::FAR_CLIPPING); + } + + virtual void setNbPoint(unsigned int nb); + + /*! + Set the near distance for clipping. + + \param dist : Near clipping value. + */ + inline void setNearClippingDistance(const double &dist) + { + distNearClip = dist; + clippingFlag = (clippingFlag | vpPolygon3D::NEAR_CLIPPING); + } + +public: + static void getClippedPolygon(const std::vector &ptIn, std::vector &ptOut, + const vpHomogeneousMatrix &cMo, const unsigned int &clippingFlags, + const vpCameraParameters &cam = vpCameraParameters(), const double &znear = 0.001, + const double &zfar = 100); + static void getMinMaxRoi(const std::vector &roi, int &i_min, int &i_max, int &j_min, int &j_max); + static bool roiInsideImage(const vpImage &I, const std::vector &corners); +}; +END_VISP_NAMESPACE + +#ifdef VISP_HAVE_NLOHMANN_JSON +#include VISP_NLOHMANN_JSON(json.hpp) +#include + +#if defined(__clang__) +// Mute warning : declaration requires an exit-time destructor [-Wexit-time-destructors] +// message : expanded from macro 'NLOHMANN_JSON_SERIALIZE_ENUM' +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +NLOHMANN_JSON_SERIALIZE_ENUM(VISP_NAMESPACE_ADDRESSING vpPolygon3D::vpPolygon3DClippingType, { + {VISP_NAMESPACE_ADDRESSING vpPolygon3D::NO_CLIPPING, "none"}, + {VISP_NAMESPACE_ADDRESSING vpPolygon3D::NEAR_CLIPPING, "near"}, + {VISP_NAMESPACE_ADDRESSING vpPolygon3D::FAR_CLIPPING, "far"}, + {VISP_NAMESPACE_ADDRESSING vpPolygon3D::LEFT_CLIPPING, "left"}, + {VISP_NAMESPACE_ADDRESSING vpPolygon3D::RIGHT_CLIPPING, "right"}, + {VISP_NAMESPACE_ADDRESSING vpPolygon3D::UP_CLIPPING, "up"}, + {VISP_NAMESPACE_ADDRESSING vpPolygon3D::DOWN_CLIPPING, "down"}, + {VISP_NAMESPACE_ADDRESSING vpPolygon3D::FOV_CLIPPING, "fov"}, + {VISP_NAMESPACE_ADDRESSING vpPolygon3D::ALL_CLIPPING, "all"} +}); + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +inline nlohmann::json clippingFlagsToJSON(const unsigned int flags) +{ +#ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; +#endif + constexpr std::array specificFlags = { + vpPolygon3D::ALL_CLIPPING, + vpPolygon3D::FOV_CLIPPING, + vpPolygon3D::NO_CLIPPING + }; + for (const auto f: specificFlags) { + if (flags == f) { + return nlohmann::json::array({ f }); + } + } + return flagsToJSON(flags, { + vpPolygon3D::NEAR_CLIPPING, + vpPolygon3D::FAR_CLIPPING, + vpPolygon3D::LEFT_CLIPPING, + vpPolygon3D::RIGHT_CLIPPING, + vpPolygon3D::UP_CLIPPING, + vpPolygon3D::DOWN_CLIPPING, + }); +} + +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpPoseVector.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpPoseVector.h new file mode 100644 index 00000000..b8f37887 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpPoseVector.h @@ -0,0 +1,346 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Pose object. A pose is a size 6 vector [t, tu]^T where tu is + * a rotation vector (theta u representation) and t is a translation vector. + */ + +/*! + \file vpPoseVector.h + + \brief Pose representation. A pose is a 6 dimension vector [t,tu]^T + where tu is a rotation vector (theta u representation) and t is a + translation vector. +*/ + +#ifndef VP_POSE_VECTOR_H +#define VP_POSE_VECTOR_H + +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +class vpRotationMatrix; +class vpHomogeneousMatrix; +class vpTranslationVector; +class vpThetaUVector; +class vpRowVector; + +/*! + \class vpPoseVector + + \ingroup group_core_transformations + + \brief Implementation of a pose vector and operations on poses. + + The vpPose class implements a complete representation of every rigid motion + in the Euclidean space. + + The vpPose class is derived from vpArray2D. + + The pose is composed of a translation and a rotation + minimally represented by a 6 dimension pose vector as: \f[ ^{a}{\bf + r}_b = [^{a}{\bf t}_{b},\theta {\bf u}]^\top \in R^6\f] + + where \f$ ^{a}{\bf r}_b \f$ is the pose from frame \f$ a \f$ to + frame \f$ b \f$, with \f$ ^{a}{\bf t}_{b} \f$ being the translation + vector between these frames along the x,y,z + axis and \f$\theta \bf u \f$, the axis-angle representation of the + rotation \f$^{a}\bf{R}_{b}\f$ between these frames. + + Translations are expressed in meters, while the angles in the \f$\theta {\bf + u}\f$ axis-angle representation are expressed in radians. + + To know more about the \f$\theta \bf u\f$ rotation representation, + see vpThetaUVector documentation. + + The following code shows how to initialize a pose vector: + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpPoseVector pose; + + pose[0] = 0.1; // tx + pose[1] = 0.2; // ty + pose[2] = 0.3; // tz + + pose[3] = M_PI; // tux + pose[4] = M_PI_2; // tux + pose[5] = M_PI_4; // tuz + + std::cout << "pose vector:\n" << pose << std::endl; + } + \endcode + It produces the following printings: + \code{.unparsed} + pose vector: + 0.1 + 0.2 + 0.3 + 3.141592654 + 1.570796327 + 0.7853981634 + \endcode + The same initialization could be achieved this way: + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpTranslationVector t; + vpThetaUVector tu; + + t << 0.1, 0.2, 0.3; + tu << M_PI, M_PI_2, M_PI_4; + vpPoseVector pose(t, tu); + } + \endcode + If ViSP is build with c++11 support, you could also initialize the vector using: + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpTranslationVector t; + vpThetaUVector tu; + t = { 0.1, 0.2, 0.3 }; + tu = { M_PI, M_PI_2, M_PI_4 }; + vpPoseVector pose(t, tu); + } + \endcode + + JSON serialization + + Since ViSP 3.6.0, if ViSP is build with \ref soft_tool_json 3rd-party we introduce JSON serialization capabilities for vpPoseVector. + The following sample code shows how to save a pose vector in a file named `pose-vector.json` + and reload the values from this JSON file. + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #if defined(VISP_HAVE_NLOHMANN_JSON) + std::string filename = "pose-vector.json"; + { + vpPoseVector pose(0.1, 0.2, 0.3, M_PI, M_PI_2, M_PI_4); + std::ofstream file(filename); + const nlohmann::json j = pose; + file << j; + file.close(); + } + { + std::ifstream file(filename); + const nlohmann::json j = nlohmann::json::parse(file); + vpPoseVector pose; + pose = j; + file.close(); + std::cout << "Read pose vector from " << filename << ":\n" << pose.t() << std::endl; + } + #endif + } + \endcode + If you build and execute the sample code, it will produce the following output: + \code{.unparsed} + Read pose vector from pose-vector.json: + 0.1 0.2 0.3 3.141592654 1.570796327 0.7853981634 + \endcode + + The content of the `pose-vector.json` file is the following: + \code{.unparsed} + $ cat pose-vector.json + {"cols":1,"data":[0.1,0.2,0.3,3.141592653589793,1.5707963267948966,0.7853981633974483],"rows":6,"type":"vpPoseVector"} + \endcode +*/ +class VISP_EXPORT vpPoseVector : public vpArray2D +{ +public: + // constructor + vpPoseVector(); + // constructor from 3 angles (in radian) + vpPoseVector(double tx, double ty, double tz, double tux, double tuy, double tuz); + // constructor convert an homogeneous matrix in a pose + VP_EXPLICIT vpPoseVector(const vpHomogeneousMatrix &M); + // constructor convert a translation and a "thetau" vector into a pose + vpPoseVector(const vpTranslationVector &tv, const vpThetaUVector &tu); + // constructor convert a translation and a rotation matrix into a pose + vpPoseVector(const vpTranslationVector &tv, const vpRotationMatrix &R); + + vpPoseVector &buildFrom(const double &tx, const double &ty, const double &tz, const double &tux, const double &tuy, const double &tuz); + // convert an homogeneous matrix in a pose + vpPoseVector &buildFrom(const vpHomogeneousMatrix &M); + // convert a translation and a "thetau" vector into a pose + vpPoseVector &buildFrom(const vpTranslationVector &tv, const vpThetaUVector &tu); + // convert a translation and a rotation matrix into a pose + vpPoseVector &buildFrom(const vpTranslationVector &tv, const vpRotationMatrix &R); + + void extract(vpRotationMatrix &R) const; + void extract(vpThetaUVector &tu) const; + void extract(vpTranslationVector &tv) const; + void extract(vpQuaternionVector &q) const; + + vpRotationMatrix getRotationMatrix() const; + vpThetaUVector getThetaUVector() const; + vpTranslationVector getTranslationVector() const; + + // Load an homogeneous matrix from a file + void load(std::ifstream &f); + + /*! + Set the value of an element of the pose vector: r[i] = x. + + \param i : Pose vector element index + + \code + // Create a pose vector with translation and rotation set to zero + vpPoseVector r; + + // Initialize the pose vector + r[0] = 1; + r[1] = 2; + r[2] = 3; + r[3] = M_PI; + r[4] = -M_PI; + r[5] = 0; + \endcode + + This code produces the same effect: + \code + vpPoseVector r(1, 2, 3, M_PI, -M_PI, 0); + \endcode + + */ + inline double &operator[](unsigned int i) { return *(data + i); } + /*! + Get the value of an element of the pose vector: x = r[i]. + + \param i : Pose vector element index + + \code + vpPoseVector r(1, 2, 3, M_PI, -M_PI, 0); + + double tx,ty,tz; // Translation + double tux, tuy,tuz; // Theta u rotation + tx = r[0]; + ty = r[1]; + tz = r[2]; + tux = r[3]; + tuy = r[4]; + tuz = r[5]; + \endcode + */ + inline const double &operator[](unsigned int i) const { return *(data + i); } + + // Print a vector [T thetaU] thetaU in degree + void print() const; + int print(std::ostream &s, unsigned int length, char const *intro = nullptr) const; + + /*! + This function is not applicable to a pose vector that is always a + 6-by-1 column vector. + \exception vpException::fatalError When this function is called. + */ + VP_NORETURN void resize(unsigned int nrows, unsigned int ncols, bool flagNullify = true) + { + (void)nrows; + (void)ncols; + (void)flagNullify; + throw(vpException(vpException::fatalError, "Cannot resize a pose vector")); + } + + // Save an homogeneous matrix in a file + void save(std::ofstream &f) const; + void set(double tx, double ty, double tz, double tux, double tuy, double tuz); + vpRowVector t() const; + + std::vector toStdVector() const; + +#ifdef VISP_HAVE_NLOHMANN_JSON +public: + static const std::string jsonTypeName; +private: + friend void to_json(nlohmann::json &j, const vpPoseVector &cam); + friend void from_json(const nlohmann::json &j, vpPoseVector &cam); + // Conversion helper function to avoid circular dependencies and MSVC errors that are not exported in the DLL + void parse_json(const nlohmann::json &j); + void convert_to_json(nlohmann::json &j) const; +public: +#endif + +private: + static const unsigned int constr_value_6; +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) + /*! + @name Deprecated functions + */ + //@{ + /*! + \deprecated Provided only for compat with previous releases. + This function does nothing. + */ + VP_DEPRECATED void init() { } + //@} +#endif +}; + +#ifdef VISP_HAVE_NLOHMANN_JSON +#include VISP_NLOHMANN_JSON(json.hpp) +inline void to_json(nlohmann::json &j, const vpPoseVector &r) +{ + r.convert_to_json(j); +} + +inline void from_json(const nlohmann::json &j, vpPoseVector &r) +{ + r.parse_json(j); +} +#endif +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpQuadProg.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpQuadProg.h new file mode 100644 index 00000000..2f6fcd0e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpQuadProg.h @@ -0,0 +1,164 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Quadratic Programming + */ + +/*! + * \file vpQuadProg.h + * \brief Implementation of Quadratic Program with Active Sets. + */ + +#ifndef _vpQuadProg_h_ +#define _vpQuadProg_h_ + +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpQuadProg + * \ingroup group_core_optim + * \brief This class provides a solver for Quadratic Programs. + * + * The cost function is written under the form \f$ \min ||\mathbf{Q}\mathbf{x} - \mathbf{r}||^2\f$. + * + * If a cost function is written under the canonical form \f$\min \frac{1}{2}\mathbf{x}^T\mathbf{H}\mathbf{x} + + * \mathbf{c}^T\mathbf{x}\f$ then fromCanonicalCost() can be used to retrieve Q and r from H and c. + * + * Equality constraints are solved through projection into the kernel. + * + * Inequality constraints are solved with active sets. + * + * In order to be used sequentially, the decomposition of the equality constraint may be stored. + * The last active set is always stored and used to warm start the next call. + * + * \warning The solvers are only available if c++11 or higher is activated during build. + * Configure ViSP using cmake -DUSE_CXX_STANDARD=11. +*/ +class VISP_EXPORT vpQuadProg +{ +public: +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + /** @name Instantiated solvers */ + //@{ + bool solveQPe(const vpMatrix &Q, const vpColVector &r, vpColVector &x, const double &tol = 1e-6) const; + + bool solveQPi(const vpMatrix &Q, const vpColVector &r, const vpMatrix &C, const vpColVector &d, vpColVector &x, + bool use_equality = false, const double &tol = 1e-6); + + bool solveQP(const vpMatrix &Q, const vpColVector &r, vpMatrix A, vpColVector b, const vpMatrix &C, + const vpColVector &d, vpColVector &x, const double &tol = 1e-6); + //@} + + /** @name Managing sequential calls to solvers */ + //@{ + bool setEqualityConstraint(const vpMatrix &A, const vpColVector &b, const double &tol = 1e-6); + + /*! + * Resets the active set that was found by a previous call to solveQP() or solveQPi(), if any. + */ + void resetActiveSet() { active.clear(); } + //@} + + static void fromCanonicalCost(const vpMatrix &H, const vpColVector &c, vpMatrix &Q, vpColVector &r, + const double &tol = 1e-6); + static bool solveQPe(const vpMatrix &Q, const vpColVector &r, vpMatrix A, vpColVector b, vpColVector &x, + const double &tol = 1e-6); + +protected: + /*! + * Active set from the last call to solveQP() or solveQPi(). Used for warm starting the next call. + */ + std::vector active; + + /*! + * Inactive set from the last call to solveQP() or solveQPi(). Used for warm starting the next call. + */ + std::vector inactive; + + /*! + * Stored particular solution from the last call to setEqualityConstraint(). + */ + vpColVector x1; + + /*! + * Stored projection to the kernel from the last call to setEqualityConstraint(). + */ + vpMatrix Z; + + static vpColVector solveSVDorQR(const vpMatrix &A, const vpColVector &b); + + static bool solveByProjection(const vpMatrix &Q, const vpColVector &r, vpMatrix &A, vpColVector &b, vpColVector &x, + const double &tol = 1e-6); + + /*! + * Performs a dimension check of passed QP matrices and vectors. + * + * If any inconsistency is detected, displays a summary and throws an exception. + * + * \param Q : cost matrix (dimension c x n) + * \param r : cost vector (dimension c) + * \param A : pointer to the equality matrix (if any, dimension m x n) + * \param b : pointer to the equality vector (if any, dimension m) + * \param C : pointer to the inequality matrix (if any, dimension p x n) + * \param d : pointer to the inequality vector (if any, dimension p) + * \param fct : name of the solver that called this function + * + * \return the dimension of the search space. + */ + static unsigned int checkDimensions(const vpMatrix &Q, const vpColVector &r, const vpMatrix *A, const vpColVector *b, + const vpMatrix *C, const vpColVector *d, const std::string fct) + { + // check data consistency + unsigned int n = Q.getCols(); + const bool Ab = (A != nullptr && b != nullptr && A->getRows()); + const bool Cd = (C != nullptr && d != nullptr && C->getRows()); + + if ((Ab && n != A->getCols()) || (Cd && n != C->getCols()) || (Ab && A->getRows() != b->getRows()) || + (Cd && C->getRows() != d->getRows()) || Q.getRows() != r.getRows()) { + std::cout << "vpQuadProg::" << fct << ": wrong dimension\n" + << "Q: " << Q.getRows() << "x" << Q.getCols() << " - r: " << r.getRows() << std::endl; + if (Ab) + std::cout << "A: " << A->getRows() << "x" << A->getCols() << " - b: " << b->getRows() << std::endl; + if (Cd) + std::cout << "C: " << C->getRows() << "x" << C->getCols() << " - d: " << d->getRows() << std::endl; + throw vpMatrixException::dimensionError; + } + return n; + } +#endif +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpQuaternionVector.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpQuaternionVector.h new file mode 100644 index 00000000..690226db --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpQuaternionVector.h @@ -0,0 +1,168 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Quaternion definition. + */ + +/*! + * \file vpQuaternionVector.h + * + * \brief Class that consider the case of a quaternion and basic + * operations on it. + */ + +#ifndef VP_QUATERNION_VECTOR_H +#define VP_QUATERNION_VECTOR_H + +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpQuaternionVector + + \ingroup group_core_transformations + + \brief Implementation of a rotation vector as quaternion angle + minimal representation. + + Defines a quaternion and its basic operations. + + The vpQuaternionVector class is derived from vpRotationVector. + + A quaternion is defined by four double values: \f${\bf q} = (x, y, z, w)\f$. + + This class allows to compute a quaternion from a rotation matrix + using either vpQuaternionVector(const vpRotationMatrix &) constructor + or build() method. + + It also defines common operations on a quaternion such as: + - multiplication (scalar and quaternion) + - addition + - subtraction. + + You can set values accessing each element: + \code + vpQuaternionVector q; + q[0] = x + q[1] = y; + q[2] = z; + q[3] = w; + \endcode + You can also initialize the vector using operator<<(double): + \code + tu << x, y, z, w; + \endcode + Or you can also initialize the vector from a list of doubles if ViSP is build with c++11 enabled: + \code + tu = {x, y, z, w}; + \endcode + + To get the values use: + \code + double x = q[0]; + double y = q[1]; + double z = q[2]; + double w = q[3]; + \endcode + or use getter: + \code + double x = q.x(); + double y = q.y(); + double z = q.z(); + double w = q.w(); + \endcode +*/ +class VISP_EXPORT vpQuaternionVector : public vpRotationVector +{ +public: + vpQuaternionVector(); +#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98) + vpQuaternionVector(const vpQuaternionVector &) = default; + virtual ~vpQuaternionVector() VP_OVERRIDE = default; +#else + virtual ~vpQuaternionVector() { } +#endif + vpQuaternionVector(const double qx, const double qy, const double qz, const double qw); + VP_EXPLICIT vpQuaternionVector(const vpRotationMatrix &R); + VP_EXPLICIT vpQuaternionVector(const vpThetaUVector &tu); + VP_EXPLICIT vpQuaternionVector(const vpColVector &q); + VP_EXPLICIT vpQuaternionVector(const std::vector &q); + + vpQuaternionVector &buildFrom(const double &qx, const double &qy, const double &qz, const double &qw); + vpQuaternionVector &buildFrom(const vpRotationMatrix &R); + vpQuaternionVector &buildFrom(const vpThetaUVector &tu); + vpQuaternionVector &buildFrom(const vpColVector &q); + vpQuaternionVector &buildFrom(const std::vector &q); + + void set(double x, double y, double z, double w); + + const double &x() const; + const double &y() const; + const double &z() const; + const double &w() const; + + double &x(); + double &y(); + double &z(); + double &w(); + + vpQuaternionVector operator+(const vpQuaternionVector &q) const; + vpQuaternionVector operator-(const vpQuaternionVector &q) const; + vpQuaternionVector operator-() const; + vpQuaternionVector operator*(double l) const; + vpQuaternionVector operator*(const vpQuaternionVector &rq) const; + vpQuaternionVector operator/(double l) const; + vpQuaternionVector &operator=(const vpColVector &q); +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpQuaternionVector &operator=(const vpQuaternionVector &) = default; + vpQuaternionVector &operator=(const std::initializer_list &list); +#endif + + vpQuaternionVector conjugate() const; + vpQuaternionVector inverse() const; + double magnitude() const; + void normalize(); + + static double dot(const vpQuaternionVector &q0, const vpQuaternionVector &q1); + + static vpQuaternionVector lerp(const vpQuaternionVector &q0, const vpQuaternionVector &q1, double t); + static vpQuaternionVector nlerp(const vpQuaternionVector &q0, const vpQuaternionVector &q1, double t); + static vpQuaternionVector slerp(const vpQuaternionVector &q0, const vpQuaternionVector &q1, double t); + +private: + static const double minimum; + static const unsigned int constr_val_4; + +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRGBa.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRGBa.h new file mode 100644 index 00000000..7ad32289 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRGBa.h @@ -0,0 +1,339 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * RGBA pixel. + */ + +/*! + \file vpRGBa.h + \brief Define the object vpRGBa that is used to build color + images (it defines a RGB 32 bits structure, fourth byte is not used) +*/ + +#ifndef VP_RGBA_H +#define VP_RGBA_H + +#include + +#include +#include + +#if ((__cplusplus >= 201703L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L))) +#include +#endif + + +BEGIN_VISP_NAMESPACE +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) +template +class vpHSV; +#endif +/*! + \class vpRGBa + + \ingroup group_core_image + + Class that defines a RGBa 32 bits structure that is used to build color + images. RGBa stands for red green blue alpha color space. + + The alpha channel is normally used as an opacity channel. If a pixel has a + value of 0 in its alpha channel, it is fully transparent, whereas a value of + 255 in the alpha channel gives a fully opaque pixel. + + By default the alpha channel is set to vpRGBa::alpha_default. + \sa vpImage +*/ +class VISP_EXPORT vpRGBa +{ +public: + enum AlphaDefault { alpha_default = 255 }; + + /*! + Basic constructor. + + Build a black value. + */ + inline vpRGBa() : R(0), G(0), B(0), A(vpRGBa::alpha_default) { } + + /*! + Constructor. + + Initialize the color with R, G, B, A values. + + \param r : Red value. + \param g : Green value. + \param b : Blue value. + \param a : Additional value. + */ + inline vpRGBa(unsigned char r, unsigned char g, unsigned char b, unsigned char a = vpRGBa::alpha_default) + : R(r), G(g), B(b), A(a) + { } + + /*! + Constructor. + + Initialize all the R, G, B, A components to \e v. + + \param v : Value to set. + */ + VP_EXPLICIT inline vpRGBa(unsigned char v) : R(v), G(v), B(v), A(v) { } + + /*! + Constructor. + + Initialize all the R, G, B, A components to \e v that should be in 0 - 255 range. + + \param v : Value to set. + */ + VP_EXPLICIT inline vpRGBa(unsigned int v) + { + assert(v < 256); + unsigned char v_uc = static_cast(v); + R = v_uc; + G = v_uc; + B = v_uc; + A = v_uc; + } + + /*! + Constructor. + + Initialize all the R, G, B, A components to \e v that should be in 0 - 255 range. + + \param v : Value to set. + */ + VP_EXPLICIT inline vpRGBa(int v) + { + assert(v >=0 && v < 256); + unsigned char v_uc = static_cast(v); + R = v_uc; + G = v_uc; + B = v_uc; + A = v_uc; + } + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) +#ifndef VISP_PYTHON_PREPROCESSOR_RUNNING + /** + * \brief Construct a new vpRGBa object from an vpHSV pbject. + * + * \tparam T The type of the channels of the vpHSV pixels. + * \tparam useFullScale True if vpHSV uses unsigned char and the full range [0; 255], false if vpHSV uses unsigned char and the limited range [0; 180]. + * \param[in] hsv The vpHSV object for which we want to have the corresponding vpRGBa object. + */ + template ::value, int>::type = 0> + VP_EXPLICIT vpRGBa(const vpHSV &hsv) + { + buildFrom(hsv); + } + + template ::value, int>::type = 0> + VP_EXPLICIT vpRGBa(const vpHSV &hsv) + { + buildFrom(hsv); + } +#endif + + /** + * \brief Build a vpRGBa object from a vpHSV object. + * + * \tparam T The type of the channels of the vpHSV pixels. + * \tparam useFullScale True if vpHSV uses unsigned char and the full range [0; 255], false if vpHSV uses unsigned + * char and the limited range [0; 180]. + * \tparam type Enable the method only if T is unsigned char. + * \param[in] other The vpHSV from which we want to build our object. + * \return vpRGBa& The current object after conversion. + */ + template + typename std::enable_if::value &&std::is_same::value, vpRGBa &>::type + buildFrom(const vpHSV &other) + { + vpHSV hsv(other); + buildFrom(hsv); + return *this; + } + + /** + * \brief Build a vpRGBa object from a vpHSV or vpHSV object. + * + * \tparam T The type of the channels of the vpHSV pixels. + * \tparam useFullScale True if vpHSV uses unsigned char and the full range [0; 255], false if vpHSV uses + * unsigned char and the limited range [0; 180]. + * \tparam std::enable_if::value, int>::type Enable the method only if T is a floating + * point number. The "int" is here because we cannot use a floating point type, + * otherwise we get "{float/double} is not a valid type for a template non-type parameter". + * \param[in] hsv The vpHSV from which we want to build our object. + * \return vpRGBa& The current object after conversion. + */ + template + typename std::enable_if::value, vpRGBa &>::type + buildFrom(const vpHSV &hsv) + { + T hue = hsv.H, saturation = hsv.S, value = hsv.V; + T h = hue * static_cast(6.0); + T s = saturation; + T v = value; + + if (vpMath::equal(h, static_cast(6.0), std::numeric_limits::epsilon())) { + h = 0.0; + } + + T f = h - static_cast(h); + T p = v * static_cast(1.0 - s); + T q = v * static_cast(1.0 - (s * f)); + T t = v * static_cast(1.0 - (s * (1.0 - f))); + + const int val_2 = 2; + const int val_3 = 3; + const int val_4 = 4; + switch (static_cast(h)) { + case 0: + hue = v; + saturation = t; + value = p; + break; + + case 1: + hue = q; + saturation = v; + value = p; + break; + + case val_2: + hue = p; + saturation = v; + value = t; + break; + + case val_3: + hue = p; + saturation = q; + value = v; + break; + + case val_4: + hue = t; + saturation = p; + value = v; + break; + + default: // case 5: + hue = v; + saturation = p; + value = q; + break; + } + + R = static_cast(vpMath::round(hue * 255.0)); + G = static_cast(vpMath::round(saturation * 255.0)); + B = static_cast(vpMath::round(value * 255.0)); + A = alpha_default; + return *this; + } +#endif + +/*! + * Copy constructor. + */ +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher + inline vpRGBa(const vpRGBa &v) = default; +#else + inline vpRGBa(const vpRGBa &v) : R(v.R), G(v.G), B(v.B), A(v.A) { } +#endif + + /*! + Create a RGBa value from a 4 dimension column vector. + + R=v[0] + G=v[1] + B=v[2] + A=v[3] + */ + VP_EXPLICIT inline vpRGBa(const vpColVector &v) : R(0), G(0), B(0), A(vpRGBa::alpha_default) { *this = v; } + + // We cannot add here the following destructor without changing the + // hypothesis that the size of this class is 4. With the destructor it + // becomes 16 that does break a lot of things around image conversions + // virtual ~vpRGBa() {}; // Not to implement + + vpRGBa &operator=(const unsigned char &v); + vpRGBa &operator=(const unsigned int &v); + vpRGBa &operator=(const int &v); +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher + vpRGBa &operator=(vpRGBa &&v) = default; + vpRGBa &operator=(const vpRGBa &v) = default; +#else + vpRGBa &operator=(const vpRGBa &v) + { + this->R = v.R; + this->G = v.G; + this->B = v.B; + this->A = v.A; + return *this; + } +#endif + vpRGBa &operator=(const vpColVector &v); + bool operator==(const vpRGBa &v) const; + bool operator!=(const vpRGBa &v) const; + + vpColVector operator-(const vpRGBa &v) const; + vpRGBa operator+(const vpRGBa &v) const; + vpColVector operator-(const vpColVector &v) const; + vpColVector operator+(const vpColVector &v) const; + vpColVector operator*(const float &v) const; + vpColVector operator*(const double &v) const; + + bool operator<(const vpRGBa &v) const; + bool operator>(const vpRGBa &v) const; + + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpRGBa &rgba); + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + /** + * \brief Number of channels a vpRGBa object is made of. + */ + static constexpr unsigned char nbChannels = 4; +#endif +public: + unsigned char R; //!< Red component. + unsigned char G; //!< Green component. + unsigned char B; //!< Blue component. + unsigned char A; //!< Additional component. + + friend VISP_EXPORT vpRGBa operator*(const double &x, const vpRGBa &rgb); +}; + +#if ((__cplusplus >= 201703L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L))) +static_assert(std::is_trivially_assignable_v); +static_assert(std::is_trivially_copyable_v); +#endif + +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRGBf.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRGBf.h new file mode 100644 index 00000000..e90b798c --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRGBf.h @@ -0,0 +1,190 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * 32-bit floating point RGB pixel. + */ + +/*! + \file vpRGBf.h + \brief Define the object vpRGBf that is used to build color + images (it defines a RGB 32-bit floating point structure) +*/ + +#ifndef VP_RGBF_H +#define VP_RGBF_H + +#include +#include + +#ifdef VISP_HAVE_NLOHMANN_JSON +#include VISP_NLOHMANN_JSON(json.hpp) +#endif + +#if ((__cplusplus >= 201703L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L))) +#include +#endif + +BEGIN_VISP_NAMESPACE + +/*! + \class vpRGBf + + \ingroup group_core_image + + Class that defines a RGB 32-bit floating point structure that is used to build color + images. RGB stands for red green blue color space. + + \sa vpRGBa + \sa vpImage +*/ +class VISP_EXPORT vpRGBf +{ +public: + /*! + Basic constructor. + + Build a black value. + */ + inline vpRGBf() : R(0), G(0), B(0) { } + + /*! + Constructor. + + Initialize the color with R, G, B values. + + \param r : Red value. + \param g : Green value. + \param b : Blue value. + */ + inline vpRGBf(float r, float g, float b) + : R(r), G(g), B(b) + { } + + /*! + Constructor. + + Initialize all the R, G, B components to \e v. + + \param v : Value to set. + */ + VP_EXPLICIT inline vpRGBf(float v) : R(v), G(v), B(v) { } + + /*! + Constructor. + + Initialize all the R, G, B components to \e v. + + \param v : Value to set. + */ + VP_EXPLICIT inline vpRGBf(int v) + { + *this = v; + } + + /*! + * Copy constructor. + */ +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher + inline vpRGBf(const vpRGBf &v) = default; +#else + inline vpRGBf(const vpRGBf &v) : R(v.R), G(v.G), B(v.B) { } +#endif + /*! + Create a RGB value from a 3 dimensional column vector. + + R=v[0] + G=v[1] + B=v[2] + */ + VP_EXPLICIT inline vpRGBf(const vpColVector &v) : R(0), G(0), B(0) { *this = v; } + + vpRGBf &operator=(float v); + vpRGBf &operator=(int v); +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher + vpRGBf &operator=(const vpRGBf &v) = default; + vpRGBf &operator=(vpRGBf &&v) = default; +#else + vpRGBf &operator=(const vpRGBf &v) + { + this->R = v.R; + this->G = v.G; + this->B = v.B; + return *this; + } +#endif + vpRGBf &operator=(const vpColVector &v); + bool operator==(const vpRGBf &v) const; + bool operator!=(const vpRGBf &v) const; + + vpColVector operator-(const vpRGBf &v) const; + vpRGBf operator+(const vpRGBf &v) const; + vpColVector operator-(const vpColVector &v) const; + vpColVector operator+(const vpColVector &v) const; + vpColVector operator*(float v) const; + vpColVector operator*(double v) const; + + bool operator<(const vpRGBf &v) const; + bool operator>(const vpRGBf &v) const; + + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpRGBf &rgb); + +public: + float R; //!< Red component. + float G; //!< Green component. + float B; //!< Blue component. + + friend VISP_EXPORT vpRGBf operator*(double x, const vpRGBf &rgb); + friend VISP_EXPORT vpRGBf operator*(float x, const vpRGBf &rgb); +}; + +#if ((__cplusplus >= 201703L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L))) +static_assert(std::is_trivially_assignable_v); +static_assert(std::is_trivially_copyable_v); +#endif + +#ifdef VISP_HAVE_NLOHMANN_JSON + +inline void from_json(const nlohmann::json &j, vpRGBf &c) +{ + c.R = j.at(0); + c.G = j.at(1); + c.B = j.at(2); + +} + +inline void to_json(nlohmann::json &j, const vpRGBf &c) +{ + j = { c.R, c.G, c.B }; +} +#endif + +END_VISP_NAMESPACE + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRansac.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRansac.h new file mode 100644 index 00000000..f7ce7723 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRansac.h @@ -0,0 +1,239 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Ransac robust algorithm. + */ + +/*! + \file vpRansac.h +*/ + +#ifndef VP_RANSAC_H +#define VP_RANSAC_H + +#include +#include +#include +#include +#include // random number generation + +BEGIN_VISP_NAMESPACE +/*! + \class vpRansac + \ingroup group_core_robust + + \brief This class is a generic implementation of the Ransac algorithm. It + cannot be used alone. + + RANSAC is described in \cite Fischler81 and \cite Hartley01a. + + The code of this class is inspired by : + Peter Kovesi + School of Computer Science & Software Engineering + The University of Western Australia + pk at csse uwa edu au + http://www.csse.uwa.edu.au/~pk + + \sa vpHomography +*/ +template class vpRansac +{ +public: + static bool ransac(unsigned int npts, const vpColVector &x, unsigned int s, double t, vpColVector &model, + vpColVector &inliers, int consensus = 1000, double not_used = 0.0, int maxNbumbersOfTrials = 10000, + double *residual = nullptr); +}; + +/*! + \brief + RANSAC - Robustly fits a model to data with the RANSAC algorithm. + + \param[in] npts : The number of data points. + + \param[in] x : Data sets to which we are seeking to fit a model M. It is assumed + that x is of size [d x Npts] where d is the dimensionality of the data and + npts is the number of data points. + + \param[in] s : The minimum number of samples from x required by fitting fn to + fit a model. Value should be greater or equal to 4. + + \param[in] t : The distance threshold between data point and the model used to + decide whether a point is an inlier or not. + + \param[out] M : The model having the greatest number of inliers. + + \param[out] inliers : An array of indices of the elements of x that were the + inliers for the best model. + + \param[in] consensus : Consensus + + \param[in] not_used : Unused parameter. + + \param[in] maxNbumbersOfTrials : Maximum number of trials. Even if a solution is + not found, the method is stopped. + + \param[out] residual : Residual + +*/ + +template +bool vpRansac::ransac(unsigned int npts, const vpColVector &x, unsigned int s, double t, + vpColVector &M, vpColVector &inliers, int consensus, double not_used, + int maxNbumbersOfTrials, double *residual) +{ + /* + // bool isplanar; + // if (s == 4) isplanar = true; + // else isplanar = false; + */ + (void)not_used; + double eps = 1e-6; + double p = 0.99; // Desired probability of choosing at least one sample + // free from outliers + + int maxTrials = maxNbumbersOfTrials; // Maximum number of trials before we give up. + int maxDataTrials = 1000; // Max number of attempts to select a non-degenerate + // data set. + const unsigned int magic_4 = 4; + if (s < magic_4) { + s = magic_4; + } + + // Sentinel value allowing detection of solution failure. + bool solutionFind = false; + vpColVector bestM; + int trialcount = 0; + int bestscore = -1; + double N = 1; // Dummy initialisation for number of trials. + + vpUniRand random(static_cast(time(nullptr))); + vpColVector bestinliers; + unsigned int *ind = new unsigned int[s]; + int ninliers = 0; + + while ((N > trialcount) && (consensus > bestscore) && (trialcount > maxTrials)) { + // Select at random s data points to form a trial model, M. + // In selecting these points we have to check that they are not in + // a degenerate configuration. + + bool degenerate = true; + int count = 1; + + while (degenerate == true) { + // Generate s random indicies in the range 1..npts + for (unsigned int i = 0; i < s; ++i) { + ind[i] = static_cast(ceil(random() * npts)) - 1; + } + + // Test that these points are not a degenerate configuration. + degenerate = vpTransformation::degenerateConfiguration(x, ind); + /* + // degenerate = feval(degenfn, x(:,ind)); + */ + // Safeguard against being stuck in this loop forever + count = count + 1; + + if (count > maxDataTrials) { + delete[] ind; + throw(vpException(vpException::fatalError, "Unable to select a non degenerate data set")); + } + } + // Fit model to this random selection of data points. + vpTransformation::computeTransformation(x, ind, M); + + vpColVector d; + // Evaluate distances between points and model. + vpTransformation::computeResidual(x, M, d); + + // Find the indices of points that are inliers to this model. + if (residual != nullptr) { + *residual = 0.0; + } + ninliers = 0; + for (unsigned int i = 0; i < npts; ++i) { + double resid = fabs(d[i]); + if (resid < t) { + inliers[i] = 1; + ++ninliers; + if (residual != nullptr) { + *residual += fabs(d[i]); + } + } + else { + inliers[i] = 0; + } + } + + if (ninliers > bestscore) // Largest set of inliers so far... + { + bestscore = ninliers; // Record data for this model + bestinliers = inliers; + bestM = M; + solutionFind = true; + + // Update estimate of N, the number of trials to ensure we pick, + // with probability p, a data set with no outliers. + + double fracinliers = static_cast(ninliers) / static_cast(npts); + + double pNoOutliers = 1 - pow(fracinliers, static_cast(s)); + + pNoOutliers = vpMath::maximum(eps, pNoOutliers); // Avoid division by -Inf + pNoOutliers = vpMath::minimum(1 - eps, pNoOutliers); // Avoid division by 0. + N = (log(1 - p) / log(pNoOutliers)); + } + + // Safeguard against being stuck in this loop forever + ++trialcount; + } + if (trialcount > maxTrials) { + std::cout << "Warning: ransac reached the maximum number of " << maxTrials << " trials" << std::endl; + } + if (solutionFind == true) { // We got a solution + M = bestM; + inliers = bestinliers; + } + else { + std::cout << "Warning: ransac was unable to find a useful solution" << std::endl; + M = 0; + } + + if (residual != nullptr) { + if (ninliers > 0) { + *residual /= ninliers; + } + } + + delete[] ind; + + return true; +} +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRect.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRect.h new file mode 100644 index 00000000..f9fdd079 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRect.h @@ -0,0 +1,417 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Defines a rectangle in the plane. + */ + +#ifndef VP_RECT_H +#define VP_RECT_H + +#include +#include +#include // numeric_limits +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE + +/*! + \class vpRect + \ingroup group_core_geometry + \brief Defines a rectangle in the plane. + + A rectangle is internally represented as an upper-left corner and a + width and height, but it is normally expressed as an upper-left + corner and bottom-right corner. + + Note that the size (width and height) of a rectangle might be + different from what you are used to. If the top-left corner and the + bottom-right corner are the same, the height and the width of the + rectangle will both be 1. + + Generally, width = right - left + 1 and height = bottom - top + 1. + + We designed it this way to make it correspond to rectangular + spaces used by drawing functions in which the width and height + denote a number of pixels. For example, drawing a rectangle with + width and height 1 draws a single pixel. + + The default coordinate system has origin (0, 0) in the top-left + corner. The positive direction of the y axis is down, and the + positive x axis is from left to right. + + A vpRect can be constructed with a set of left, top, width and + height double. After creation the dimensions can be changed, + e.g. with setLeft(), setRight(), setTop() and setBottom(), or by + setting sizes, e.g. setWidth(), setHeight() + +*/ +class VISP_EXPORT vpRect +{ +public: + vpRect(); + vpRect(double left, double top, double width, double height); + vpRect(const vpImagePoint &topLeft, double width, double height); + vpRect(const vpImagePoint &topLeft, const vpImagePoint &bottomRight); + vpRect(const vpRect &r); + VP_EXPLICIT vpRect(const std::vector &ip); + + /*! + Returns the area of the rectangle. + */ + inline double getArea() const { return width * height; } + + /*! + Returns the bottom coordinate of the rectangle. + \sa getRight() + */ + inline double getBottom() const { return ((this->top + this->height) - 1.0); } + + /*! + Returns the bottom-left position of the rectangle. + + \sa getTopLeft(), getTopRight(), getBottomRight(), getBottom(), getLeft() + */ + inline vpImagePoint getBottomLeft() const + { + vpImagePoint bottomLeft; + bottomLeft.set_u(getLeft()); + bottomLeft.set_v(getBottom()); + return bottomLeft; + } + + /*! + Returns the bottom-right coordinate of the rectangle. + + \sa getTopLeft(), getTopRight(), getBottomLeft(), getBottom(), getRight() + */ + inline vpImagePoint getBottomRight() const + { + vpImagePoint bottomRight; + bottomRight.set_u(getRight()); + bottomRight.set_v(getBottom()); + + return bottomRight; + } + + /*! + Returns the center point of the rectangle. The center point + coordinates are (\e x, \e y). + + The default coordinate system has origin (0, 0) in the top-left + corner. The positive direction of the y axis is down, and the + positive x axis is from left to right. + + \sa moveCenter() + */ + inline void getCenter(double &x, double &y) const + { + x = (this->left + (this->width / 2.0)) - 0.5; + y = (this->top + (this->height / 2.0)) - 0.5; + } + + /*! + Returns the center point of the rectangle. The center point + coordinates are (\e x, \e y). + + The default coordinate system has origin (0, 0) in the top-left + corner. The positive direction of the y axis is down, and the + positive x axis is from left to right. + + \sa moveCenter() + */ + inline vpImagePoint getCenter() const + { + vpImagePoint center; + center.set_u((this->left + (this->width / 2.0)) - 0.5); + center.set_v((this->top + (this->height / 2.0)) - 0.5); + return center; + } + + /*! + Returns the height of the rectangle. The height includes both the + top and bottom edges, i.e. height = bottom - top + 1. + + \sa getWidth() + */ + inline double getHeight() const { return this->height; } + + /*! + Returns the left coordinate of the rectangle. + + \sa getTopLeft(), getRight() + */ + inline double getLeft() const { return this->left; } + + /*! + Returns the right coordinate of the rectangle. + \sa getLeft() + */ + inline double getRight() const { return ((this->left + this->width) - 1.0); } + + /*! + Returns the size of the rectangle. + \sa getWidth(), getHeight() + */ + inline double getSize() const { return (this->width * this->height); } + + /*! + Returns the top coordinate of the rectangle. + + \sa getTopLeft(), getBottom() + */ + inline double getTop() const { return this->top; } + + /*! + Returns the top-left position of the rectangle. + + \sa getTopRight(), getBottomRight(), getBottomLeft(), getTop(), getLeft() + */ + inline vpImagePoint getTopLeft() const + { + vpImagePoint topLeft; + topLeft.set_u(getLeft()); + topLeft.set_v(getTop()); + return topLeft; + } + + /*! + Returns the top-right position of the rectangle. + + \sa getTopLeft(), getBottomRight(), getBottomLeft(), getTop(), getRight() + */ + inline vpImagePoint getTopRight() const + { + vpImagePoint topRight; + topRight.set_u(getRight()); + topRight.set_v(getTop()); + return topRight; + } + + /*! + Returns the width of the rectangle. The width includes both the + left and right edges, i.e. width = right - left + 1. + + \sa getHeight() + + */ + inline double getWidth() const { return this->width; } + + /*! + Returns true if the point belongs to the rectangle. + */ + bool isInside(const vpImagePoint &ip) const; + + bool operator==(const vpRect &r) const; + bool operator!=(const vpRect &r) const; + + /*! + Intersection operator. + \param r : Rectangle with which the intersection should be calculated. + \return Intersected rectangle or null rectangle if the two rectangles do not + intersect. + */ + inline vpRect &operator&=(const vpRect &r) + { + double x1 = std::max(left, r.left); + double y1 = std::max(top, r.top); + width = std::min(left + width, r.left + r.width) - x1; + height = std::min(top + height, r.top + r.height) - y1; + left = x1; + top = y1; + + if ((width <= 0) || (height <= 0)) { + *this = vpRect(); + } + + return *this; + } + + vpRect &operator=(const vpRect &r); + + /*! + Intersection operator. + \return Intersection rectangle or null rectangle if the two rectangles do not + intersect. + */ + inline vpRect operator&(const vpRect &r) const + { + vpRect a = *this; + return a &= r; + } + + friend VISP_EXPORT bool inRectangle(const vpImagePoint &ip, const vpRect &rect); + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpRect &r); + + void set(double left, double top, double width, double height); + void set(const vpImagePoint &topLeft, double width, double height); + void set(const vpImagePoint &topLeft, const vpImagePoint &bottomRight); + void set(const vpRect &r); + void set(const std::vector &ip); + + /*! + Sets the bottom edge position of the rectangle to pos. May change + the height of the rectangle, but will never change the top edge + position the rectangle. + + \sa setTop() + */ + inline void setBottom(double pos) { this->height = (pos - this->top) + 1.0; } + + /*! + Sets the bottom-right position of the rectangle. Will never change + the top-left position the rectangle. + + \sa setTopLeft() + */ + inline void setBottomRight(const vpImagePoint &bottomRight) + { + this->height = (bottomRight.get_v() - this->top) + 1.0; + this->width = (bottomRight.get_u() - this->left) + 1.0; + } + + /*! + Sets the height of the rectangle to \e h. The top edge is not moved, + but the bottom edge may be moved. + + \sa setWidth() + */ + inline void setHeight(double h) + { + assert(h > 0); + this->height = h; + } + + /*! + Sets the left edge position of the rectangle to pos. May change the right + edge position of the rectangle, but will never change the width of the + rectangle. + + \sa setRight() + */ + inline void setLeft(double pos) { this->left = pos; } + + /*! + Sets the coordinates of the rectangle's top left corner to + (left, top), and its size to (width, height). + + \param l : horizontal position of the rectangle upper/left corner position. + \param t : vertical position of the rectangle upper/left corner position. + \param w : rectangle width. + \param h : rectangle height. + + */ + inline void setRect(double l, double t, double w, double h) + { + this->left = l; + this->top = t; + this->width = w; + this->height = h; + } + + /*! + Sets the right edge position of the rectangle to pos. May change + the width of the rectangle, but will never change the left edge + position of the rectangle. + + \sa setLeft() + */ + inline void setRight(double pos) { this->width = (pos - this->left) + 1.0; } + + /*! + Sets the top edge position of the rectangle to pos. May change the bottom + edge position of the rectangle, but will never change the height of the + rectangle. + + \sa setBottom() + */ + inline void setTop(double pos) { this->top = pos; } + + /*! + Sets the top-left position of the rectangle. May change the bottom + edge position of the rectangle, but will never change the height of the + rectangle. + + \sa setBottomRight() + */ + inline void setTopLeft(const vpImagePoint &topLeft) + { + this->left = topLeft.get_u(); + this->top = topLeft.get_v(); + } + + /*! + Sets the width of the rectangle to \e w. The right edge is changed, + but not the left edge. + + \sa setHeight() + */ + inline void setWidth(double w) + { + assert(w > 0); + this->width = w; + } + + /*! + Sets the center point of the rectangle to (\e x, \e y), leaving + the size unchanged. + + \sa getCenter() + */ + inline void moveCenter(double x, double y) + { + const unsigned int magic_2 = 2; + this->left = (x - (this->width / magic_2)) + 0.5; + this->top = (y - (this->height / magic_2)) + 0.5; + } + + /*! + Center the rectangle to the image point given as parameter, leaving + the size unchanged. + + \sa getCenter() + */ + inline void moveCenter(const vpImagePoint ¢er) + { + const unsigned int magic_2 = 2; + this->left = (center.get_u() - (this->width / magic_2)) + 0.5; + this->top = (center.get_v() - (this->height / magic_2)) + 0.5; + } + +private: + double left; // Upper left corner position along the columns axis + double top; // Upper left corner position along the rows axis + double width; // Rectangle width + double height; // Rectangle height +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRectOriented.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRectOriented.h new file mode 100644 index 00000000..558cad80 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRectOriented.h @@ -0,0 +1,112 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Defines a (possibly oriented) rectangle in the plane. + */ + +#ifndef VP_RECT_ORIENTED_H +#define VP_RECT_ORIENTED_H + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpRectOriented + * \ingroup group_core_geometry + * \brief Defines an oriented rectangle in the plane. +*/ +class VISP_EXPORT vpRectOriented +{ +public: + vpRectOriented(); +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpRectOriented(const vpRectOriented &) = default; +#else + vpRectOriented(const vpRectOriented &rect); +#endif + + vpRectOriented(const vpImagePoint ¢er, double width, double height, double theta = 0); + + VP_EXPLICIT vpRectOriented(const vpRect &rect); + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpRectOriented &operator=(const vpRectOriented &) = default; +#else + vpRectOriented &operator=(const vpRectOriented &rect); +#endif + + vpRectOriented &operator=(const vpRect &rect); + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + VP_EXPLICIT operator vpRect(); +#endif + + void setCenter(const vpImagePoint ¢er); + + void setPoints(const vpImagePoint &topLeft, const vpImagePoint &topRight, const vpImagePoint &bottomLeft, + const vpImagePoint &bottomRight); + + vpImagePoint getCenter() const; + + vpImagePoint getTopLeft() const; + + vpImagePoint getTopRight() const; + + vpImagePoint getBottomLeft() const; + + vpImagePoint getBottomRight() const; + + void setSize(double width, double height); + + double getWidth() const; + + double getHeight() const; + + void setOrientation(double theta); + + double getOrientation() const; + + bool isInside(const vpImagePoint &point) const; + +private: + vpImagePoint m_center; + double m_width; + double m_height; + double m_theta; + vpImagePoint m_topLeft; + vpImagePoint m_topRight; + vpImagePoint m_bottomLeft; + vpImagePoint m_bottomRight; + bool isLeft(const vpImagePoint &pointToTest, const vpImagePoint &point1, const vpImagePoint &point2) const; +}; +END_VISP_NAMESPACE +#endif // _vpRectOriented_h_ diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRequest.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRequest.h new file mode 100644 index 00000000..d05ab6a9 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRequest.h @@ -0,0 +1,241 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Network Request. + */ + +#ifndef VP_REQUEST_H +#define VP_REQUEST_H + +#include +#include +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpRequest + + \ingroup group_core_com_ethernet + + \brief This the request that will transit on the network + + Example request decoding an image on a specific form. + First parameter : Height of the image. + Second parameter : Width of the image. + Thirs parameter : Bitmap of the image (not compress). + + Here is the header of the vpRequestImage class. + + \code + #ifndef vpRequestImage_H + #define vpRequestImage_H + + #include + #include + + class vpRequestImage : public vpRequest + { + private: + vpImage *I; + + public: + vpRequestImage(); + vpRequestImage(vpImage *); + virtual ~vpRequestImage(); + + virtual void encode(); + virtual void decode(); + }; + + #endif + \endcode + + Here is the definition of the vpRequestImage class. + + \code + #include + + vpRequestImage::vpRequestImage(){ + request_id = "image"; + } + + vpRequestImage::vpRequestImage(vpImage *Im){ + request_id = "image"; + I = Im; + } + + vpRequestImage::~vpRequestImage(){} + + void vpRequestImage::encode(){ + clear(); + + unsigned int h = I->getHeight(); + unsigned int w = I->getWidth(); + + addParameterObject(&h); + addParameterObject(&w); + addParameterObject(I->bitmap,h*w*sizeof(unsigned char)); + } + + void vpRequestImage::decode(){ + if(listOfParams.size() == 3){ + unsigned int w, h; + memcpy((void*)&h, (void*)listOfParams[0].c_str(), sizeof(unsigned int)); + memcpy((void*)&w, (void*)listOfParams[1].c_str(), sizeof(unsigned int)); + + I->resize(h,w); + memcpy((void*)I->bitmap,(void*)listOfParams[2].c_str(),w*h*sizeof(unsigned char)); + } + } + \endcode + + \sa vpClient + \sa vpServer + \sa vpNetwork +*/ +class VISP_EXPORT vpRequest +{ +protected: + std::string request_id; + std::vector listOfParams; + +public: + vpRequest(); + vpRequest(const vpRequest &req) + { + *this = req; + } + virtual ~vpRequest() { } + + vpRequest &operator=(const vpRequest &req) + { + request_id = req.request_id; + listOfParams = req.listOfParams; + return *this; + } + + void addParameter(const char *params); + void addParameter(const std::string ¶ms); + void addParameter(const std::vector &listOfparams); + template void addParameterObject(T *params, const int &sizeOfObject = sizeof(T)); + + /*! + Decode the parameters of the request (Funtion that has to be redifined). + + \sa vpRequest::encode() + */ + virtual void decode() = 0; + + /*! + Clear the parameters of the request. + */ + void clear() { listOfParams.clear(); } + + /*! + Encode the parameters of the request (Funtion that has to be redifined). + + \sa vpRequest::decode() + */ + virtual void encode() = 0; + + /*! + Accessor on the parameters. + + \return Parameter at the index i. + */ + inline std::string &operator[](const unsigned int &i) { return listOfParams[i]; } + + /*! + Accessor on the parameters (const). + + \return Parameter at the index i (const). + */ + inline const std::string &operator[](const unsigned int &i) const { return listOfParams[i]; } + + /*! + Get the ID of the request. + + \sa vpRequest::setId() + + \return ID of the request. + */ + std::string getId() const { return request_id; } + + /*! + Change the ID of the request. + + \sa vpRequest::getId() + + \param id : new ID. + */ + void setId(const char *id) { request_id = id; } + + /*! + Get the number of parameters. + + \return Number of parameters. + */ + unsigned int size() const { return static_cast(listOfParams.size()); } +}; + +//######## Definition of Template Functions ######## +//# # +//################################################## + +/*! + Add an object as parameter of the request. + + \warning Only simple object can be sent unless you know its size. + Sending object containing pointers, virtual methods, etc, won't probably + work. Unless the size is well defined... + + \sa vpRequest::addParameter() + + \param params : Object to add. + \param sizeOfObject : Size of the object. +*/ +template void vpRequest::addParameterObject(T *params, const int &sizeOfObject) +{ + if (sizeOfObject != 0) { + char *tempS = new char[sizeOfObject]; + memcpy((void *)tempS, (void *)params, sizeOfObject); + std::string returnVal(tempS, static_cast(sizeOfObject)); + + listOfParams.push_back(returnVal); + + delete[] tempS; + } +} +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRobust.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRobust.h new file mode 100644 index 00000000..6a407662 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRobust.h @@ -0,0 +1,249 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * M-Estimator and various influence function. + */ + +/*! + \file vpRobust.h +*/ + +#ifndef VP_ROBUST_H +#define VP_ROBUST_H + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpRobust + \ingroup group_core_robust + \brief Contains an M-estimator and various influence function. + + This class implements an M-estimator with Tukey, Cauchy or Huber influence function \cite PhDComport + which allow uncertain measures to be less likely considered and in some cases completely + rejected, thus inferring that the data is not normally distributed. + + When using a robust estimate of the mean, it is usual to normalize the distribution to center + the data around zero. In the case of a median operator, the normalized residue is given by: + + \f$\overline{r_i} = r_i - {Med}(r_i) \f$ where \f${Med}(r_i)\f$ is the median value of the residue vector \f$r\f$. + + The Median Absolute Deviation (MAD) representing one standard deviation of the normal distribution is given by: + \f[ \sigma = 1.48 \; {Med}(|\overline{r_i}|) \f] + + This class allows to set the minimum value of \f$ \sigma \f$ using setMinMedianAbsoluteDeviation(). + + This estimated standard deviation \f$\sigma\f$ can accordingly be used with a tuning variable for different + influence functions. + + Let us consider the weight function \f$w(r)\f$ with \f$r\f$ the residual vector of the parameters to estimate. + - Using Huber influence function, with \f$a\f$ a constant such as \f$a=1.21 \; \sigma \f$ we have + \f[ w(r_i) = \left\{ \begin{array}{ll} 1 & + \mbox{if } |r_i| \leq a \\ \frac{a}{|r_i|} & \mbox{else} \end{array} \right. \f] + + - Using Tukey influence function, with \f$b\f$ a constant such as \f$b=4.68 \; \sigma \f$ we have + \f[ w(r_i) = \left\{ \begin{array}{ll} {\left(1 - {\left(\frac{r_i}{b}\right)}^2 \right)}^2 & + \mbox{if } |r_i| \leq b \\ 0 & \mbox{else} \end{array} \right. \f] + + - Using Cauchy influence function, with \f$c\f$ a constant such as \f$c=2.38 \; \sigma \f$ we have + \f[ w(r_i) = \frac{1}{(1 + {(r_i/c)}^2)} \f] + + Given the influence function and the residual vector, the weights are updated in MEstimator(). + +*/ +class VISP_EXPORT vpRobust +{ +public: + //! Enumeration of influence functions + typedef enum + { + TUKEY, //!< Tukey influence function. + CAUCHY, //!< Cauchy influence function. + HUBER //!< Huber influence function. + } vpRobustEstimatorType; + +public: + vpRobust(); + vpRobust(const vpRobust &other); + + //! Destructor + virtual ~vpRobust() { } + + /*! + * Return residual vector Median Absolute Deviation (MAD). + * This value is updated after a call to MEstimator(). It corresponds to + * value of \f$ \sigma = 1.48{Med}(|r_i - {Med}(r_i)|) \f$. + * This value cannot be lower than the min value returned by getMinMedianAbsoluteDeviation() + * or set with setMinMedianAbsoluteDeviation(). + * + * \sa setMinMedianAbsoluteDeviation() + */ + double getMedianAbsoluteDeviation() { return m_mad; } + + /*! + * Return the min value used to threshold residual vector Median Absolute Deviation (MAD). + * This value corresponds to the minimal value of \f$\sigma\f$ computed in MEstimator(). + * + * \sa setMinMedianAbsoluteDeviation() + */ + double getMinMedianAbsoluteDeviation() { return m_mad_min; } + + void MEstimator(const vpRobustEstimatorType method, const vpColVector &residues, vpColVector &weights); + + vpRobust &operator=(const vpRobust &other); +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpRobust &operator=(const vpRobust &&other); +#endif + + /*! + * Set minimal median absolute deviation (MAD) value corresponding to the minimal value of + * \f$\sigma\f$ computed in MEstimator() with + * \f$ \sigma = 1.48{Med}(|r_i - {Med}(r_i)|) \f$. + * \param mad_min : Minimal Median Absolute Deviation value. + * Default value is set to 0.0017 in the default constructor. + * + * \sa getMinMedianAbsoluteDeviation() + */ + inline void setMinMedianAbsoluteDeviation(double mad_min) { m_mad_min = mad_min; } + +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) + /*! + @name Deprecated functions + */ + //@{ + VP_DEPRECATED VP_EXPLICIT vpRobust(unsigned int n_data); + //! Compute the weights according a residue vector and a PsiFunction + void MEstimator(const vpRobustEstimatorType method, const vpColVector &residues, const vpColVector &all_residues, + vpColVector &weights); + /*! + * \deprecated Set iteration. This function is to call before simultMEstimator(). + * \param iter : The first call iter should be set to 0. + */ + VP_DEPRECATED void setIteration(unsigned int iter) { m_iter = iter; } + /*! + \deprecated You should rather use setMinMedianAbsoluteDeviation(). + Set minimal median absolute deviation (MAD) value. + Given the input vector or residual, when MAD(residual) < mad_min + we set MAD(residual) = mad_min. + \param mad_min : Minimal Median Absolute Deviation value. + Default value is set to 0.0017 in the default constructor. + */ + VP_DEPRECATED inline void setThreshold(double mad_min) { m_mad_min = mad_min; } + VP_DEPRECATED vpColVector simultMEstimator(vpColVector &residues); + //@} +#endif +private: + //! Normalized residue + vpColVector m_normres; + //! Sorted normalized Residues + vpColVector m_sorted_normres; + //! Sorted residues + vpColVector m_sorted_residues; + + //! Min admissible value of residual vector Median Absolute Deviation + double m_mad_min; + //! Previous value of residual vector Median Absolute Deviation + double m_mad_prev; +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) + //! Iteration, only used in deprecated simultMEstimator() + unsigned int m_iter; +#endif + //! Size of the containers + unsigned int m_size; + //! Residual vector Median Absolute Deviation + double m_mad; + +private: + //! Resize containers for sort methods + void resize(unsigned int n_data); + + //--------------------------------- + // Partial derivative of loss function with respect to the residue + //--------------------------------- + /** @name PsiFunctions */ + //@{ + //! Tuckey influence function + void psiTukey(double sigma, const vpColVector &x, vpColVector &w); + //! Caucht influence function + void psiCauchy(double sigma, const vpColVector &x, vpColVector &w); + //! Huber influence function + void psiHuber(double sigma, const vpColVector &x, vpColVector &w); + //@} + +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) + double computeNormalizedMedian(vpColVector &all_normres, const vpColVector &residues, const vpColVector &all_residues, + const vpColVector &weights); + //! Calculate various scale estimates + double simultscale(const vpColVector &x); + //! Partial derivative of loss function with respect to the scale + double simult_chi_huber(double x); + + //--------------------------------- + // Constrained Partial derivative of loss function with respect to the scale + //--------------------------------- + /** @name Constrained Chi Functions */ + //@{ + //! Constrained Chi Function + double constrainedChi(vpRobustEstimatorType method, double x); + //! Constrained Chi Tukey Function + double constrainedChiTukey(double x); + //! Constrained Chi Cauchy Function + double constrainedChiCauchy(double x); + //! Constrained Chi Huber Function + double constrainedChiHuber(double x); + //@} + +#if !defined(VISP_HAVE_FUNC_ERFC) && !defined(VISP_HAVE_FUNC_STD_ERFC) + //--------------------------------- + // Mathematic functions used to calculate the Expectation + //--------------------------------- + /** @name Some math function */ + //@{ + double erf(double x); + double gammp(double a, double x); + void gser(double *gamser, double a, double x, double *gln); + void gcf(double *gammcf, double a, double x, double *gln); + double gammln(double xx); + //@} +#endif +#endif + + /** @name Sort function */ + //@{ + //! Sort function using partition method + int partition(vpColVector &a, int l, int r); + //! Sort the vector and select a value in the sorted vector + double select(vpColVector &a, int l, int r, int k); + //@} +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRotationMatrix.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRotationMatrix.h new file mode 100644 index 00000000..fbbf0b6f --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRotationMatrix.h @@ -0,0 +1,240 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Rotation matrix. + */ + +/*! + \file vpRotationMatrix.h + \brief Class that consider the particular case of rotation matrix +*/ + +#ifndef VP_ROTATION_MATRIX_H +#define VP_ROTATION_MATRIX_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpRotationMatrix + + \ingroup group_core_transformations + + \brief Implementation of a rotation matrix and operations on such kind of + matrices. + + The vpRotationMatrix considers the particular case of + a rotation matrix. + + The vpRotationMatrix class is derived from vpArray2D. + + The code below shows how to create a rotation matrix, set the element values and access them: + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpRotationMatrix R; + R[0][0] = 0; R[0][1] = 0; R[0][2] = -1; + R[1][0] = 0; R[1][1] = -1; R[1][2] = 0; + R[2][0] = -1; R[2][1] = 0; R[2][2] = 0; + + std::cout << "R:" << std::endl; + for (unsigned int i = 0; i < R.getRows(); ++i) { + for (unsigned int j = 0; j < R.getCols(); ++j) { + std::cout << R[i][j] << " "; + } + std::cout << std::endl; + } + } + \endcode + Once build, this previous code produces the following output: + \code + R: + 0 0 -1 + 0 -1 0 + -1 0 0 + \endcode + You can also use operator<< to initialize a rotation matrix as previously: + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpRotationMatrix R; + R << 0, 0, -1, 0, -1, 0, -1, 0, 0; + std::cout << "R:\n" << R << std::endl; + } + \endcode + + If ViSP is build with c++11 enabled, you can do the same using: + \code + #include +{ +public: + vpRotationMatrix(); + vpRotationMatrix(const vpRotationMatrix &R); + VP_EXPLICIT vpRotationMatrix(const vpHomogeneousMatrix &M); + VP_EXPLICIT vpRotationMatrix(const vpThetaUVector &r); + VP_EXPLICIT vpRotationMatrix(const vpPoseVector &p); + VP_EXPLICIT vpRotationMatrix(const vpRzyzVector &r); + VP_EXPLICIT vpRotationMatrix(const vpRxyzVector &r); + VP_EXPLICIT vpRotationMatrix(const vpRzyxVector &r); + VP_EXPLICIT vpRotationMatrix(const vpQuaternionVector &q); + VP_EXPLICIT vpRotationMatrix(const vpMatrix &R); + vpRotationMatrix(double tux, double tuy, double tuz); + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + VP_EXPLICIT vpRotationMatrix(const std::initializer_list &list); +#endif + + vpRotationMatrix &buildFrom(const vpHomogeneousMatrix &M); + vpRotationMatrix &buildFrom(const vpThetaUVector &v); + vpRotationMatrix &buildFrom(const vpPoseVector &p); + vpRotationMatrix &buildFrom(const vpRzyzVector &v); + vpRotationMatrix &buildFrom(const vpRxyzVector &v); + vpRotationMatrix &buildFrom(const vpRzyxVector &v); + vpRotationMatrix &buildFrom(const vpQuaternionVector &q); + vpRotationMatrix &buildFrom(const double &tux, const double &tuy, const double &tuz); + + void eye(); + + vpColVector getCol(unsigned int j) const; + vpThetaUVector getThetaUVector(); + + vpRotationMatrix inverse() const; + void inverse(vpRotationMatrix &R) const; + + bool isARotationMatrix(double threshold = 1e-6) const; + + // copy operator from vpRotationMatrix + vpRotationMatrix &operator=(const vpRotationMatrix &R); + // copy operator from vpMatrix (handle with care) + vpRotationMatrix &operator=(const vpMatrix &M); +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpRotationMatrix &operator=(const std::initializer_list &list); +#endif + // operation c = A * b (A is unchanged) + vpTranslationVector operator*(const vpTranslationVector &tv) const; + // operation C = A * B (A is unchanged) + vpRotationMatrix operator*(const vpRotationMatrix &R) const; + // operation C = A * B (A is unchanged) + vpHomogeneousMatrix operator*(const vpHomogeneousMatrix &M) const; + vpMatrix operator*(const vpMatrix &M) const; + // operation v2 = A * v1 (A is unchanged) + vpColVector operator*(const vpColVector &v) const; + vpRotationMatrix operator*(double x) const; + vpRotationMatrix &operator*=(double x); + + vpRotationMatrix &operator<<(double val); + vpRotationMatrix &operator,(double val); + + void orthogonalize(); + + void printVector(); + + /*! + This function is not applicable to a rotation matrix that is always a + 3-by-3 matrix. + \exception vpException::fatalError When this function is called. + */ + VP_NORETURN void resize(unsigned int nrows, unsigned int ncols, bool flagNullify = true) + { + (void)nrows; + (void)ncols; + (void)flagNullify; + throw(vpException(vpException::fatalError, "Cannot resize a rotation matrix")); + } + + // transpose + vpRotationMatrix t() const; + + static vpRotationMatrix mean(const std::vector &vec_M); + static vpRotationMatrix mean(const std::vector &vec_R); + +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) + /*! + @name Deprecated functions + */ + //@{ + /*! + \deprecated Provided only for compat with previous releases. + This function does nothing. + */ + VP_DEPRECATED void init() { } + /*! + \deprecated You should rather use eye(). + */ + VP_DEPRECATED void setIdentity(); + //@} +#endif + +protected: + unsigned int m_index; +private: + static const unsigned int constr_val_3; +}; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +VISP_EXPORT +#endif +VISP_NAMESPACE_ADDRESSING vpRotationMatrix operator*(const double &x, const VISP_NAMESPACE_ADDRESSING vpRotationMatrix &R); +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRotationVector.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRotationVector.h new file mode 100644 index 00000000..4112a313 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRotationVector.h @@ -0,0 +1,167 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Generic rotation vector (cannot be used as is !). + */ + +/*! + \file vpRotationVector.h + \brief class that consider the case of a generic rotation vector + (cannot be used as is !) +*/ + +#ifndef VP_ROTATION_VECTOR_H +#define VP_ROTATION_VECTOR_H + +#include +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE + +class vpRowVector; +class vpColVector; + +/*! + \class vpRotationVector + + \ingroup group_core_transformations + + \brief Implementation of a generic rotation vector. + + Class that consider the case of a generic rotation vector + (cannot be used as is !) consisting in three or four angles. + + The vpRotationVector class is derived from vpArray2D. + The vpRotationVector class is also the base class of specific rotations + vectors such as vpThetaUVector, vpRxyzVector, vpRzyxVector, vpRzyzVector and + vpQuaternionVector. + + The code below shows how this class can be used to manipulate a + vpRxyzVector. + + \code + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpRxyzVector r; // By default initialized to zero + // Rotation around x set to 45 degres converted in radians + r[0] = vpMath::rad(45); + // Rotation around y set to PI radians + r[1] = M_PI; + // Rotation around z set to 0 radians + r[2] = 0; + + std::cout << "Rxyz rotation vector: " << r << std::endl; + + double rx = r[0]; // Get the value of the angle around x axis + double ry = r[1]; // Get the value of the angle around y axis + double rz = r[2]; // Get the value of the angle around z axis + } + \endcode + +*/ + +class VISP_EXPORT vpRotationVector : public vpArray2D +{ +public: + //! Constructor that constructs a 0-size rotation vector. + vpRotationVector() : vpArray2D(), m_index(0) { } + + //! Constructor that constructs a vector of size n and initialize all values + //! to zero. + VP_EXPLICIT vpRotationVector(unsigned int n) : vpArray2D(n, 1), m_index(0) { } + + /*! + Copy operator. + */ + vpRotationVector(const vpRotationVector &v) : vpArray2D(v), m_index(0) { } + + /** @name Inherited functionalities from vpRotationVector */ + //@{ + + /*! + Operator that allows to set the value of an element of the rotation + vector: r[i] = value + */ + inline double &operator[](unsigned int i) { return *(data + i); } + /*! + Operator that allows to get the value of an element of the rotation + vector: value = r[i] + */ + inline const double &operator[](unsigned int i) const { return *(data + i); } + + /*! + Affectation of two vectors. + */ + vpRotationVector &operator=(const vpRotationVector &v) + { + resize(v.size(), 1); + unsigned int v_size = v.size(); + for (unsigned int i = 0; i < v_size; ++i) { + data[i] = v.data[i]; + } + return *this; + } + + vpColVector operator*(double x) const; + + vpRotationVector &operator<<(double val); + vpRotationVector &operator,(double val); + + double sumSquare() const; + + // Transpose of the rotation vector. + vpRowVector t() const; + + std::vector toStdVector() const; + + //@} + +protected: + unsigned int m_index; // index used for operator<< and operator, to fill a vector +}; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +VISP_EXPORT +#endif +VISP_NAMESPACE_ADDRESSING vpColVector operator*(const double &x, const VISP_NAMESPACE_ADDRESSING vpRotationVector &v); +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRowVector.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRowVector.h new file mode 100644 index 00000000..d8af6e54 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRowVector.h @@ -0,0 +1,371 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Operation on row vectors. + */ + +#ifndef VP_ROW_VECTOR_H +#define VP_ROW_VECTOR_H + +#include + +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE + +class vpMatrix; +class vpColVector; + +/*! + \file vpRowVector.h + \brief Definition of row vector class as well + as a set of operations on these vectors. +*/ + +/*! + \class vpRowVector + + \ingroup group_core_matrices + + \brief Implementation of row vector and the associated operations. + + This class provides a data structure for a row vector that contains values + of double. It contains also some functions to achieve a set of operations on + these vectors. + + The vpRowVector class is derived from vpArray2D. + + The code below shows how to create a 3-element row vector of doubles, set the element values and access them: + \code + #include &) + \code + #include +{ +public: + //! Basic constructor that creates an empty 0-size row vector. + vpRowVector() : vpArray2D() { } + //! Construct a row vector of size n. All the elements are initialized to + //! zero. + VP_EXPLICIT vpRowVector(unsigned int n) : vpArray2D(1, n) { } + //! Construct a row vector of size n. Each element is set to \e val. + vpRowVector(unsigned int n, double val) : vpArray2D(1, n, val) { } + //! Copy constructor that allows to construct a row vector from an other + //! one. + vpRowVector(const vpRowVector &v) : vpArray2D(v) { } + vpRowVector(const vpRowVector &v, unsigned int c, unsigned int ncols); + VP_EXPLICIT vpRowVector(const vpMatrix &M); + vpRowVector(const vpMatrix &M, unsigned int i); + VP_EXPLICIT vpRowVector(const std::vector &v); + VP_EXPLICIT vpRowVector(const std::vector &v); +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpRowVector(vpRowVector &&v); + VP_EXPLICIT vpRowVector(const std::initializer_list &list) : vpArray2D(list) { } +#endif + + static vpRowVector view(double *raw_data, unsigned int ncols); + + /*! + Removes all elements from the vector (which are destroyed), + leaving the container with a size of 0. + */ + void clear() + { + if (!isMemoryOwner) { + throw vpException(vpException::fatalError, "Cannot clear a vector view"); + } + if (data != nullptr) { + free(data); + data = nullptr; + } + + if (rowPtrs != nullptr) { + free(rowPtrs); + rowPtrs = nullptr; + } + rowNum = 0; + colNum = 0; + dsize = 0; + } + + std::ostream &cppPrint(std::ostream &os, const std::string &matrixName = "A", bool octet = false) const; + std::ostream &csvPrint(std::ostream &os) const; + + /*! + Convert a column vector containing angles in degrees into radians. + \sa rad2deg() + */ + inline void deg2rad() + { + double d2r = M_PI / 180.0; + + (*this) *= d2r; + } + + /*! + Extract a sub-row vector from a row vector. + \param c : Index of the column corresponding to the first element of the + vector to extract. + \param rowsize : Size of the vector to extract. + \exception vpException::fatalError If the vector to extract is not + contained in the original one. + + \code + vpRowVector r1; + for (unsigned int i=0; i<4; ++i) + r1.stack(i); + // r1 is equal to [0 1 2 3] + vpRowVector r2 = r1.extract(1, 3); + // r2 is equal to [1 2 3] + \endcode + */ + vpRowVector extract(unsigned int c, unsigned int rowsize) const + { + if ((c >= colNum) || ((c + rowsize) > colNum)) { + throw(vpException(vpException::fatalError, + "Cannot extract a (1x%d) row vector from a (1x%d) " + "row vector starting at index %d", + rowsize, colNum, c)); + } + + return vpRowVector(*this, c, rowsize); + } + + double frobeniusNorm() const; + + /*! + * Compute the Hadamard product (element wise vector multiplication). + * + * \param v : Second vector; + * \return v1.hadamard(v2) The kronecker product : + * \f$ v1 \circ v2 = (v1 \circ v2)_{i} = (v1)_{i} (v2)_{i} \f$ + */ + vpRowVector hadamard(const vpRowVector &v) const; + + void init(const vpRowVector &v, unsigned int c, unsigned int ncols); + void insert(unsigned int i, const vpRowVector &v); + + std::ostream &maplePrint(std::ostream &os) const; + std::ostream &matlabPrint(std::ostream &os) const; + + vpRowVector &normalize(); + vpRowVector &normalize(vpRowVector &x) const; + + //! Operator that allows to set a value of an element \f$v_i\f$: v[i] = x + inline double &operator[](unsigned int n) { return *(data + n); } + //! Operator that allows to get the value of an element \f$v_i\f$: x = v[i] + inline const double &operator[](unsigned int n) const { return *(data + n); } + + //! Copy operator. Allow operation such as A = v + vpRowVector &operator=(const vpRowVector &v); + vpRowVector &operator=(const vpMatrix &M); + vpRowVector &operator=(const std::vector &v); + vpRowVector &operator=(const std::vector &v); + vpRowVector &operator=(double x); +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpRowVector &operator=(vpRowVector &&v); + vpRowVector &operator=(const std::initializer_list &list); +#endif + //! Comparison operator. + bool operator==(const vpRowVector &v) const; + bool operator!=(const vpRowVector &v) const; + + double operator*(const vpColVector &x) const; + vpRowVector operator*(const vpMatrix &M) const; + vpRowVector operator*(double x) const; + vpRowVector &operator*=(double x); + + vpRowVector operator/(double x) const; + vpRowVector &operator/=(double x); + + vpRowVector operator+(const vpRowVector &v) const; + vpRowVector &operator+=(vpRowVector v); + + vpRowVector operator-(const vpRowVector &v) const; + vpRowVector &operator-=(vpRowVector v); + vpRowVector operator-() const; + + vpRowVector &operator<<(const vpRowVector &v); + vpRowVector &operator<<(double val); + vpRowVector &operator,(double val); + + int print(std::ostream &s, unsigned int length, char const *intro = nullptr) const; + /*! + Convert a column vector containing angles in radians into degrees. + \sa deg2rad() + */ + inline void rad2deg() + { + double r2d = 180.0 / M_PI; + + (*this) *= r2d; + } + + void reshape(vpMatrix &M, const unsigned int &nrows, const unsigned int &ncols); + vpMatrix reshape(unsigned int nrows, unsigned int ncols); + + /*! Modify the size of the row vector. + \param i : Size of the vector. This value corresponds to the vector number + of columns. + \param flagNullify : If true, set the data to zero. + */ + inline void resize(unsigned int i, bool flagNullify = true) { vpArray2D::resize(1, i, flagNullify); } + + /*! + Resize the row vector to a \e ncols-dimension vector. + This function can only be used with \e nrows = 1. + \param nrows : Vector number of rows. This value should be set to 1. + \param ncols : Vector number of columns. This value corresponds + to the size of the vector. + \param flagNullify : If true, set the data to zero. + \exception vpException::fatalError When \e nrows is not equal to 1. + + */ + void resize(unsigned int nrows, unsigned int ncols, bool flagNullify) + { + if (nrows != 1) { + throw(vpException(vpException::fatalError, + "Cannot resize a row vector to a (%dx%d) dimension " + "vector that has more than one row", + nrows, ncols)); + } + vpArray2D::resize(nrows, ncols, flagNullify); + } + + void stack(double d); + void stack(const vpRowVector &v); + + double sum() const; + double sumSquare() const; + vpColVector t() const; + std::vector toStdVector() const; + vpColVector transpose() const; + void transpose(vpColVector &v) const; + + static double mean(const vpRowVector &v); + static double median(const vpRowVector &v); + static vpRowVector stack(const vpRowVector &A, const vpRowVector &B); + static void stack(const vpRowVector &A, const vpRowVector &B, vpRowVector &C); + static double stdev(const vpRowVector &v, bool useBesselCorrection = false); + +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) + /*! + @name Deprecated functions + */ + //@{ + /*! + \deprecated Provided only for compat with previous releases. + This function does nothing. + */ + VP_DEPRECATED void init() { } + /*! + \deprecated You should rather use stack(const vpRowVector &) + */ + VP_DEPRECATED void stackMatrices(const vpRowVector &r) { stack(r); } + /*! + \deprecated You should rather use stack(const vpRowVector &A, const + vpRowVector &B) + */ + VP_DEPRECATED static vpRowVector stackMatrices(const vpRowVector &A, const vpRowVector &B) { return stack(A, B); } + /*! + \deprecated You should rather use stack(const vpRowVector &A, const + vpRowVector &B, vpRowVector &C) + */ + VP_DEPRECATED static void stackMatrices(const vpRowVector &A, const vpRowVector &B, vpRowVector &C) + { + stack(A, B, C); + } + + /*! + \deprecated You should rather use eye() + */ + VP_DEPRECATED void setIdentity(const double &val = 1.0); + VP_DEPRECATED double euclideanNorm() const; + //@} +#endif +}; + +VISP_EXPORT vpRowVector operator*(const double &x, const vpRowVector &v); + +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRxyzVector.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRxyzVector.h new file mode 100644 index 00000000..d5b7177d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRxyzVector.h @@ -0,0 +1,222 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Rxyz angle parameterization for the rotation. + * Rxyz(phi,theta,psi) = Rot(x,phi)Rot(y,theta)Rot(z,psi). + */ + +/*! + \file vpRxyzVector.h + + \brief Class that consider the case of the Rxyz angle + parameterization for the rotation. + + Rxyz(phi,theta,psi) = Rot(x,phi)Rot(y,theta)Rot(z,psi) + */ + +#ifndef VP_RXYZ_VECTOR_H +#define VP_RXYZ_VECTOR_H + +#include +#include +#include + +BEGIN_VISP_NAMESPACE + +class vpRotationVector; +class vpRotationMatrix; +class vpThetaUVector; + +/*! + \class vpRxyzVector + + \ingroup group_core_transformations + + \brief Implementation of a rotation vector as \f$R(x,y,z)\f$ Euler angle + minimal representation. + + Class that consider the case of the Euler + \f$(\varphi,\theta,\psi)\f$ angle using the x-y-z convention, where + \f$(\varphi,\theta,\psi)\f$ are respectively the rotation angles around the + \f$x\f$, \f$y\f$ and \f$z\f$ axis. + + \f[R_{xyz}(\varphi,\theta,\psi) = R_x(\varphi) \; R_y(\theta) \; R_z(\psi)\f] + + with + + \f[R_{x}(\varphi) = \left( + \begin{array}{ccc} + 1 & 0 & 0 \\ + 0 &\cos \varphi & -\sin\varphi \\ + 0 &\sin \varphi & \cos\varphi \\ + \end{array} + \right) \; + R_{y}(\theta) = \left( + \begin{array}{ccc} + \cos \theta & 0 & \sin\theta\\ + 0 & 1 & 0 \\ + -\sin\theta & 0 &\cos \theta + \end{array} + \right) \; + R_{z}(\psi) = \left( + \begin{array}{ccc} + \cos \psi & -\sin\psi & 0\\ + \sin\psi &\cos \psi& 0 \\ + 0 & 0 & 1 + \end{array} + \right)\f] + + The rotation matrix corresponding to the x-y-z convention is given by: + + \f[ + R_{xyz}(\varphi,\theta,\psi) = \left( + \begin{array}{ccc} + \cos\theta \cos\psi & -\cos\theta \sin\psi & \sin\theta \\ + \sin\varphi \sin\theta \cos\psi + \cos\varphi\sin\psi & -\sin\varphi + \sin\theta \sin\psi +\cos\varphi\cos\psi & -\sin\varphi \cos\theta \\ + -\cos\varphi \sin\theta \cos\psi + \sin\varphi\sin\psi & \cos\varphi + \sin\theta \sin\psi +\sin\varphi\cos\psi & \cos\varphi \cos\theta \end{array} + \right) + \f] + + The vpRxyzVector class is derived from vpRotationVector. + + From the implementation point of view, it is nothing more than an + array of three doubles with values in [rad]. + + You can set values [rad] accessing each element: + \code + vpRxyzVector rxyz; + rxyz[0] = M_PI_4; + rxyz[1] = M_PI_2; + rxyz[2] = M_PI; + \endcode + You can also initialize the vector using operator<<(double): + \code + rxyz << M_PI_4, M_PI_2, M_PI; + \endcode + Or you can also initialize the vector from a list of doubles if ViSP is build with c++11 enabled: + \code + rxyz = {M_PI_4, M_PI_2, M_PI}; + \endcode + + To get the values [rad] use: + \code + double rx = rxyz[0]; + double ry = rxyz[1]; + double rz = rxyz[2]; + \endcode + + The code below shows first how to initialize this representation of + Euler angles, than how to construct a rotation matrix from a + vpRxyzVector and finally how to extract the vpRxyzVector Euler angles + from the build rotation matrix. + + \code + #include + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpRxyzVector rxyz; + + // Initialise the Euler angles + rxyz[0] = vpMath::rad( 45.f); // phi angle in rad around x axis + rxyz[1] = vpMath::rad(-30.f); // theta angle in rad around y axis + rxyz[2] = vpMath::rad( 90.f); // psi angle in rad around z axis + + // Construct a rotation matrix from the Euler angles + vpRotationMatrix R(rxyz); + + // Extract the Euler angles around x,y,z axis from a rotation matrix + rxyz.buildFrom(R); + + // Print the extracted Euler angles. Values are the same than the + // one used for initialization + std::cout << rxyz; + + // Since the rotation vector is 3 values column vector, the + // transpose operation produce a row vector. + vpRowVector rxyz_t = rxyz.t(); + + // Print the transpose row vector + std::cout << rxyz_t << std::endl; + } + \endcode + +*/ + +class VISP_EXPORT vpRxyzVector : public vpRotationVector +{ +public: + vpRxyzVector(); +#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98) + vpRxyzVector(const vpRxyzVector &) = default; + virtual ~vpRxyzVector() VP_OVERRIDE = default; +#else + virtual ~vpRxyzVector() { } +#endif + vpRxyzVector(double phi, double theta, double psi); + + // initialize a Rxyz vector from a rotation matrix + VP_EXPLICIT vpRxyzVector(const vpRotationMatrix &R); + + // initialize a Rxyz vector from a ThetaU vector + VP_EXPLICIT vpRxyzVector(const vpThetaUVector &tu); + VP_EXPLICIT vpRxyzVector(const vpColVector &rxyz); + VP_EXPLICIT vpRxyzVector(const std::vector &rxyz); + + // convert a rotation matrix into Rxyz vector + vpRxyzVector &buildFrom(const vpRotationMatrix &R); + + // convert a ThetaU vector into a Rxyz vector + vpRxyzVector &buildFrom(const vpThetaUVector &tu); + vpRxyzVector &buildFrom(const vpColVector &rxyz); + vpRxyzVector &buildFrom(const std::vector &rxyz); + + vpRxyzVector &buildFrom(const double &phi, const double &theta, const double &psi); + + vpRxyzVector &operator=(const vpColVector &rxyz); + vpRxyzVector &operator=(double x); +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpRxyzVector &operator=(const vpRxyzVector &) = default; + vpRxyzVector &operator=(const std::initializer_list &list); +#endif +private: + static const unsigned int constr_val_3; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRzyxVector.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRzyxVector.h new file mode 100644 index 00000000..d5b252af --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRzyxVector.h @@ -0,0 +1,223 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Rzyx angle parameterization for the rotation. + * Rzyx(phi,theta,psi) = Rot(z,phi)Rot(y,theta)Rot(x,psi) + */ + +/*! + \file vpRzyxVector.h + + \brief class that consider the case of the Rzyx angle + parameterization for the rotation. + + Rzyx(phi,theta,psi) = Rot(z,phi)Rot(y,theta)Rot(x,psi) +*/ + +#ifndef VP_RZYX_VECTOR_H +#define VP_RZYX_VECTOR_H + +#include +#include +#include + +BEGIN_VISP_NAMESPACE + +class vpRotationMatrix; +class vpThetaUVector; + +/*! + \class vpRzyxVector + + \ingroup group_core_transformations + + \brief Implementation of a rotation vector as \f$R(z,y,x)\f$ Euler angle + minimal representation. + + Class that consider the case of the Euler + \f$(\varphi,\theta,\psi)\f$ angle using the z-y-x convention, where + \f$(\varphi,\theta,\psi)\f$ are respectively the rotation angles around the + \f$z\f$, \f$y\f$ and \f$x\f$ axis. + + \f[R_{zyx}(\varphi,\theta,\psi) = R_z(\varphi) \; R_y(\theta) \; R_x(\psi)\f] + + with + + \f[ + R_{z}(\varphi) = \left( + \begin{array}{ccc} + \cos \varphi & -\sin\varphi & 0\\ + \sin\varphi &\cos \varphi& 0 \\ + 0 & 0 & 1 + \end{array} + \right) \; + R_{y}(\theta) = \left( + \begin{array}{ccc} + \cos \theta & 0 & \sin\theta\\ + 0 & 1 & 0 \\ + -\sin\theta & 0 &\cos \theta + \end{array} + \right) \; + R_{x}(\psi) = \left( + \begin{array}{ccc} + 1 & 0 & 0 \\ + 0 &\cos \psi & -\sin\psi \\ + 0 &\sin \psi & \cos\psi \\ + \end{array} + \right) + \f] + + The rotation matrix corresponding to the z-y-x convention is given by: + + \f[ + R_{zyx}(\varphi,\theta,\psi) = \left( + \begin{array}{ccc} + \cos\varphi \cos\theta & -\sin\varphi \cos\psi + + \cos\varphi\sin\theta\sin\psi & \sin\varphi \sin\psi + +\cos\varphi\sin\theta\cos\psi \\ + \sin\varphi \cos\theta & \cos\varphi\cos\psi + \sin\varphi\sin\theta + \sin\psi & -\cos\varphi \sin\psi +\sin\varphi\sin\theta\cos\psi \\ + -\sin\theta & \cos\theta \sin\psi & \cos\theta \cos\psi + \end{array} + \right) + \f] + + The vpRzyxVector class is derived from vpRotationVector. + + From the implementation point of view, it is nothing more than an + array of three doubles with values in [rad]. + + You can set values [rad] accessing each element: + \code + vpRzyxVector rzyx; + rzyx[0] = M_PI_4; + rzyx[1] = M_PI_2; + rzyx[2] = M_PI; + \endcode + You can also initialize the vector using operator<<(double): + \code + rzyx << M_PI_4, M_PI_2, M_PI; + \endcode + Or you can also initialize the vector from a list of doubles if ViSP is build with c++11 enabled: + \code + rzyx = {M_PI_4, M_PI_2, M_PI}; + \endcode + + To get the values [rad] use: + \code + double rz = rzyx[0]; + double ry = rzyx[1]; + double rx = rzyx[2]; + \endcode + + The code below shows first how to initialize this representation of + Euler angles, than how to construct a rotation matrix from a + vpRzyxVector and finally how to extract the vpRzyxVector Euler angles + from the build rotation matrix. + + \code + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpRzyxVector rzyx; + + // Initialise the Euler angles + rzyx[0] = vpMath::rad( 45.f); // phi angle in rad/s around z axis + rzyx[1] = vpMath::rad(-30.f); // theta angle in rad/s around y axis + rzyx[2] = vpMath::rad( 90.f); // psi angle in rad/s around x axis + + // Construct a rotation matrix from the Euler angles + vpRotationMatrix R(rzyx); + + // Extract the Euler angles around z,y,x axis from a rotation matrix + rzyx.buildFrom(R); + + // Print the extracted Euler angles. Values are the same than the + // one used for initialization + std::cout << rzyx; + + // Since the rotation vector is 3 values column vector, the + // transpose operation produce a row vector. + vpRowVector rzyx_t = rzyx.t(); + + // Print the transpose row vector + std::cout << rzyx_t << std::endl; + } + \endcode + +*/ +class VISP_EXPORT vpRzyxVector : public vpRotationVector +{ +public: + vpRzyxVector(); +#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98) + vpRzyxVector(const vpRzyxVector &) = default; + virtual ~vpRzyxVector() VP_OVERRIDE = default; +#else + virtual ~vpRzyxVector() { } +#endif + vpRzyxVector(double phi, double theta, double psi); + + // initialize a Rzyx vector from a rotation matrix + VP_EXPLICIT vpRzyxVector(const vpRotationMatrix &R); + + // initialize a Rzyx vector from a ThetaU vector + VP_EXPLICIT vpRzyxVector(const vpThetaUVector &tu); + VP_EXPLICIT vpRzyxVector(const vpColVector &rzyx); + VP_EXPLICIT vpRzyxVector(const std::vector &rzyx); + + // convert a rotation matrix into Rzyx vector + vpRzyxVector &buildFrom(const vpRotationMatrix &R); + + // convert a ThetaU vector into a Rzyx vector + vpRzyxVector &buildFrom(const vpThetaUVector &R); + vpRzyxVector &buildFrom(const vpColVector &rxyz); + vpRzyxVector &buildFrom(const std::vector &rxyz); + + vpRzyxVector &buildFrom(const double &phi, const double &theta, const double &psi); + + vpRzyxVector &operator=(const vpColVector &rzyx); + vpRzyxVector &operator=(double x); +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpRzyxVector &operator=(const vpRzyxVector &) = default; + vpRzyxVector &operator=(const std::initializer_list &list); +#endif +private: + static const unsigned int constr_val_3; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRzyzVector.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRzyzVector.h new file mode 100644 index 00000000..0f21d693 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpRzyzVector.h @@ -0,0 +1,222 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Euler angles parameterization for the rotation. + * Rzyz(phi,theta,psi) = Rot(z,phi)Rot(y,theta)Rot(z,psi) + */ + +/*! + \file vpRzyzVector.h + \brief class that consider the case of the Rzyz angles parameterization + for the rotation + + Rzyz(phi,theta,psi) = Rot(z,phi)Rot(y,theta)Rot(z,psi) +*/ + +#ifndef VP_RZYZ_VECTOR_H +#define VP_RZYZ_VECTOR_H + +#include +#include +#include + +BEGIN_VISP_NAMESPACE + +class vpRotationMatrix; +class vpThetaUVector; + +/*! + \class vpRzyzVector + + \ingroup group_core_transformations + + \brief Implementation of a rotation vector as \f$R(z,y,z)\f$ Euler angle + minimal representation. + + Class that consider the case of the Euler + \f$(\varphi,\theta,\psi)\f$ angles using the z-y-z convention, where + \f$(\varphi,\theta,\psi)\f$ are respectively the rotation angles + around the \f$z\f$, \f$y\f$ and \f$z\f$ axis. + + \f[R_{zyz}(\varphi,\theta,\psi) = R_z(\varphi) \; R_y(\theta) \; + R_z(\psi)\f] + + with + + \f[ + R_{z}(\varphi) = \left( + \begin{array}{ccc} + \cos \varphi & -\sin\varphi & 0\\ + \sin\varphi &\cos \varphi& 0 \\ + 0 & 0 & 1 + \end{array} + \right) \; + R_{y}(\theta) = \left( + \begin{array}{ccc} + \cos \theta & 0 & \sin\theta\\ + 0 & 1 & 0 \\ + -\sin\theta & 0 &\cos \theta + \end{array} + \right) \; + R_{z}(\psi) = \left( + \begin{array}{ccc} + \cos \psi & -\sin\psi & 0\\ + \sin\psi &\cos \psi& 0 \\ + 0 & 0 & 1 + \end{array} + \right) + \f] + + The rotation matrix corresponding to the z-y-z convention is given by: + + \f[ + R_{zyz}(\varphi,\theta,\psi) = \left( + \begin{array}{ccc} + \cos\varphi \cos\theta \cos\psi - \sin\varphi\sin\psi & -\cos\varphi + \cos\theta \sin\psi -\sin\varphi\cos\psi & \cos\varphi \sin\theta \\ + \sin\varphi \cos\theta \cos\psi + \cos\varphi\sin\psi & -\sin\varphi + \cos\theta \sin\psi +\cos\varphi\cos\psi & \sin\varphi \sin\theta \\ + -\sin\theta \cos\psi & \sin\theta \sin\psi & \cos\theta + \end{array} + \right) + \f] + + The vpRzyzVector class is derived from vpRotationVector. + + From the implementation point of view, it is nothing more than an + array of three doubles with values in [rad]. + + You can set values [rad] accessing each element: + \code + vpRzyzVector rzyz; + rzyz[0] = M_PI_4; + rzyz[1] = M_PI_2; + rzyz[2] = M_PI; + \endcode + You can also initialize the vector using operator<<(double): + \code + rzyz << M_PI_4, M_PI_2, M_PI; + \endcode + Or you can also initialize the vector from a list of doubles if ViSP is build with c++11 enabled: + \code + rzyz = {M_PI_4, M_PI_2, M_PI}; + \endcode + + To get the values [rad] use: + \code + double r1 = rzyz[0]; + double r2 = rzyz[1]; + double r3 = rzyz[2]; + \endcode + + The code below shows first how to initialize this representation of + Euler angles, than how to construct a rotation matrix from a + vpRzyzVector and finally how to extract the vpRzyzVector Euler angles + from the build rotation matrix. + + \code + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpRzyzVector rzyz; + + // Initialise the Euler angles + rzyz[0] = vpMath::rad( 45.f); // phi angle in rad/s around z axis + rzyz[1] = vpMath::rad(-30.f); // theta angle in rad/s around y axis + rzyz[2] = vpMath::rad( 90.f); // psi angle in rad/s around z axis + + // Construct a rotation matrix from the Euler angles + vpRotationMatrix R(rzyz); + + // Extract the Euler angles around z,y,z axis from a rotation matrix + rzyz.buildFrom(R); + + // Print the extracted Euler angles. Values are the same than the + // one used for initialization + std::cout << rzyz; + + // Since the rotation vector is 3 values column vector, the + // transpose operation produce a row vector. + vpRowVector rzyz_t = rzyz.t(); + + // Print the transpose row vector + std::cout << rzyz_t << std::endl; + } + \endcode +*/ +class VISP_EXPORT vpRzyzVector : public vpRotationVector +{ +public: + vpRzyzVector(); +#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98) + vpRzyzVector(const vpRzyzVector &) = default; + virtual ~vpRzyzVector() VP_OVERRIDE = default; +#else + virtual ~vpRzyzVector() { } +#endif + + // initialize a Rzyz vector from a rotation matrix + VP_EXPLICIT vpRzyzVector(const vpRotationMatrix &R); + + // initialize a Rzyz vector from a ThetaU vector + VP_EXPLICIT vpRzyzVector(const vpThetaUVector &tu); + + vpRzyzVector(double phi, double theta, double psi); + VP_EXPLICIT vpRzyzVector(const vpColVector &rzyz); + VP_EXPLICIT vpRzyzVector(const std::vector &rzyz); + + // convert a rotation matrix into Rzyz vector + vpRzyzVector &buildFrom(const vpRotationMatrix &R); + + // convert a ThetaU vector into a Rzyz vector + vpRzyzVector &buildFrom(const vpThetaUVector &R); + vpRzyzVector &buildFrom(const vpColVector &rxyz); + vpRzyzVector &buildFrom(const std::vector &rxyz); + + vpRzyzVector &buildFrom(const double &phi, const double &theta, const double &psi); + + vpRzyzVector &operator=(const vpColVector &rzyz); + vpRzyzVector &operator=(double x); +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpRzyzVector &operator=(const vpRzyzVector &) = default; + vpRzyzVector &operator=(const std::initializer_list &list); +#endif +private: + static const unsigned int constr_val_3; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpScale.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpScale.h new file mode 100644 index 00000000..8a59c840 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpScale.h @@ -0,0 +1,98 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Median Absolute Deviation (MAD), MPDE, Mean shift kernel density + * estimation. + */ + +/*! + \file vpScale.h +*/ + +// ========================================================= +/*! + * \brief Contains various estimators for scale. + * \n Methods : Median Absolute Deviation (MAD), + * MPDE, Mean shift kernel + * density estimation. \author Andrew Comport \date 24/10/03 + */ +// ========================================================== + +#ifndef VP_SCALE_H +#define VP_SCALE_H + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpScale + \ingroup group_core_robust + + \brief Contains different methods for estimating the robust scale of + an error distribution. + + Methods of Median Absolute Deviation and Density Gradient estimation + using the Mean Shift method. + + \author Andrew Comport + \date 24/10/03 +*/ +class VISP_EXPORT vpScale +{ + +private: + double bandwidth; + unsigned int dimension; + +public: + //! Constructor + vpScale(); + vpScale(double kernel_bandwidth, unsigned int dim = 1); +#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98) + vpScale(const vpScale &) = default; + virtual ~vpScale() = default; +#else + virtual ~vpScale() { } +#endif + + double MeanShift(vpColVector &error); + double KernelDensity(vpColVector &error, unsigned int position); + double KernelDensityGradient(vpColVector &error, unsigned int position); + + double KernelDensity_EPANECHNIKOV(vpColVector &X); + double KernelDensityGradient_EPANECHNIKOV(double X, unsigned int n); + +#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98) + vpScale &operator=(const vpScale &) = default; +#endif +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpSerial.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpSerial.h new file mode 100644 index 00000000..ade88891 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpSerial.h @@ -0,0 +1,187 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Serial communication. + */ + +#ifndef VP_SERIAL_H +#define VP_SERIAL_H + +#if !defined(_WIN32) + +#include +#include + +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpSerial + \ingroup group_core_com_serial + + This class allows a serial port communication between 2 devices. + + The following example shows how to write a string on port `/dev/ttyUSB0` using the default constructor: + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #ifndef WIN32 + vpSerial serial("/dev/ttyUSB0"); + serial.write("Hello world"); + #endif + } + \endcode + + \note This class is not implemented on windows-like OS. +*/ +class VISP_EXPORT vpSerial +{ +public: + /*! + * Defines the possible byte sizes for the serial port. + */ + typedef enum + { + fivebits = 5, //!< Data is encoded with 5 bits + sixbits = 6, //!< Data is encoded with 6 bits + sevenbits = 7, //!< Data is encoded with 7 bits + eightbits = 8 //!< Data is encoded with 8 bits + } bytesize_t; + + /*! + * Defines the possible parity types for the serial port. + */ + typedef enum + { + parity_none = 0, //!< No parity check + parity_odd = 1, //!< Check for odd parity + parity_even = 2 //!< Check for even parity + } parity_t; + + /*! + * Defines the possible stopbit types for the serial port. + */ + typedef enum + { + stopbits_one = 1, //!< 1 stop bit is used + stopbits_two = 2, //!< 2 stop bits are used + } stopbits_t; + + /*! + * Defines the possible flowcontrol types for the serial port. + */ + typedef enum + { + flowcontrol_none = 0, //!< No flow control + flowcontrol_software, //!< Software flow control + flowcontrol_hardware //!< Hardware flow control + } flowcontrol_t; + + vpSerial(const std::string &port = "", unsigned long baudrate = 9600, bytesize_t bytesize = eightbits, + parity_t parity = parity_none, stopbits_t stopbits = stopbits_one, + flowcontrol_t flowcontrol = flowcontrol_none); + virtual ~vpSerial(); + + int available(); + void close(); + + /*! + Return the baud rate; 9600, 115200... + \sa setBaudrate() + */ + unsigned long getBaudrate() { return m_baudrate; } + + /*! + Return the byte size. + \sa setBytesize() + */ + bytesize_t getBytesize() { return m_bytesize; } + + /*! + Return the flow control type. + \sa setFlowcontrol() + */ + flowcontrol_t getFlowcontrol() { return m_flowcontrol; } + + /*! + Return parity. + \sa setParity() + */ + parity_t getParity() { return m_parity; } + + /*! + Return the serial port name like `/dev/ttyUSB0`, `/dev/ttySO`, `/dev/ttyAMA0`... + \sa setPort() + */ + std::string getPort() { return m_port; } + + /*! + Return number of stop bits used. + \sa setStopbits() + */ + stopbits_t getStopbits() { return m_stopbits; } + + void open(); + bool read(char *c, long timeout_s); + std::string readline(const std::string &eol); + void setBaudrate(const unsigned long baudrate); + void setBytesize(const bytesize_t &bytesize); + void setFlowcontrol(const flowcontrol_t &flowcontrol); + void setParity(const parity_t &parity); + void setPort(const std::string &port); + void setStopbits(const stopbits_t &stopbits); + void write(const std::string &s); + +private: + void configure(); + + std::string m_port; + int m_fd; + + bool m_is_open; + bool m_xonxoff; + bool m_rtscts; + + unsigned long m_baudrate; + + parity_t m_parity; + bytesize_t m_bytesize; + stopbits_t m_stopbits; + flowcontrol_t m_flowcontrol; +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpServer.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpServer.h new file mode 100644 index 00000000..ede62ce8 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpServer.h @@ -0,0 +1,223 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * TCP Server + */ + +#ifndef VP_SERVER_H +#define VP_SERVER_H + +#include +#include +#include +#include + +// inet_ntop() not supported on win XP +#ifdef VISP_HAVE_FUNC_INET_NTOP + +BEGIN_VISP_NAMESPACE +/*! + \class vpServer + + \ingroup group_core_com_ethernet + + \brief This class represents a Transmission Control Protocol (TCP) server. + + TCP provides reliable, ordered delivery of a stream of bytes from a program + on one computer to another program on another computer. + + Example of server's code, receiving and sending basic message. + It corresponds to the client used in the first example of vpClient class' + documentation. + + \code + #include + #include + + int main(int argc,const char** argv) + { + int port = 35000; + vpServer serv(port); //Launch the server on localhost + serv.start(); + + bool run = true; + int val; + + while(run){ + serv.checkForConnections(); + + if(serv.getNumberOfClients() > 0) + { + // Receiving a value from the first client + if(serv.receive(&val) != sizeof(int)) + std::cout << "Error while receiving" << std::endl; + else + std::cout << "Received : " << val << std::endl; + + val = val+1; + // Sending the new value to the first client + if(serv.send(&val) != sizeof(int)) + std::cout << "Error while sending" << std::endl; + else + std::cout << "Sending : " << val << std::endl; + } + } + + return 0; + } + \endcode + + Example of server's code, receiving a vpImage on request form. + It correspond to the client used in the second example of vpClient class' documentation. + + \code + #include + #include + #include + + #include "vpRequestImage.h" //See vpRequest class documentation + + int main(int argc,const char** argv) + { + int port = 35000; + + std::cout << "Port: " << port << std::endl; + vpServer serv(port); + serv.start(); + + #if defined(VISP_HAVE_X11) + vpDisplayX display; + #elif defined(VISP_HAVE_GDI) //Win32 + vpDisplayGDI display; + #endif + + vpImage I; + + vpRequestImage reqImage(&I); + serv.addDecodingRequest(&reqImage); + + bool run = true; + + while(run){ + serv.checkForConnections(); + + if(serv.getNumberOfClients() > 0) + { + int index = serv.receiveAndDecodeRequestOnce(); + std::string id = serv.getRequestIdFromIndex(index); + + if(id == reqImage.getId()) + { + #if defined(VISP_HAVE_X11) || defined(VISP_HAVE_GDI) + if (! display.isInitialised() ) + display.init(I, -1, -1, "Remote display"); + #endif + + vpDisplay::display(I) ; + vpDisplay::flush(I); + + // A click in the viewer to exit + if ( vpDisplay::getClick(I, false) ) + run = false; + } + } + } + + return 0; + } + \endcode + + \sa vpClient + \sa vpRequest + \sa vpNetwork +*/ +class VISP_EXPORT vpServer : public vpNetwork +{ +private: + //######## PARAMETERS ######## + //# # + //############################ + std::string adress; + int port; + bool started; + unsigned int max_clients; + +public: + vpServer(); + vpServer(const vpServer &server); + VP_EXPLICIT vpServer(const int &port); + vpServer(const std::string &adress_serv, const int &port_serv); + vpServer &operator=(const vpServer &server); + + virtual ~vpServer() VP_OVERRIDE; + + bool checkForConnections(); + + /*! + Check if the server is started. + + \sa vpServer::start() + + \return True if the server is started, false otherwise. + */ + bool isStarted() const { return started; } + + /*! + Get the maximum number of clients that can be connected to the server. + + \sa vpServer::setMaxNumberOfClients() + + \return Maximum number of clients. + */ + unsigned int getMaxNumberOfClients() const { return max_clients; } + + /*! + Get the number of clients connected to the server. + + \return Number of clients connected. + */ + unsigned int getNumberOfClients() const { return static_cast(receptor_list.size()); } + + void print(); + + bool start(); + + /*! + Set the maximum number of clients that can be connected to the server. + + \sa vpServer::getMaxNumberOfClients() + + \param l : Maximum number of clients. + */ + void setMaxNumberOfClients(const unsigned int &l) { max_clients = l; } +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpSphere.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpSphere.h new file mode 100644 index 00000000..5ba502b7 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpSphere.h @@ -0,0 +1,154 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Sphere feature. + */ + +/*! + * \file vpSphere.h + * \brief forward projection of a sphere + */ + +#ifndef VP_SPHERE_H +#define VP_SPHERE_H + +#include +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpSphere + * \ingroup group_core_geometry + * \brief Class that defines a 3D sphere in the object frame and allows forward projection of a 3D sphere in the + * camera frame and in the 2D image plane by perspective projection. + * All the parameters must be set in meter. + * + * A sphere has the followings parameters: + * - **in the object frame**: the 3D coordinates oX, oY, oZ of the center and radius R. These + * parameters registered in vpForwardProjection::oP internal 4-dim vector are set using the constructors vpSphere(double + * oX, double oY, double oZ, double R), vpSphere(const vpColVector &oP) or the functions setWorldCoordinates(double oX, + * double oY, double oZ, double R) and setWorldCoordinates(const vpColVector &oP). To get theses parameters use get_oP(). + * + * - **in the camera frame**: the coordinates cX, cY, cZ of the center and radius R. These + * parameters registered in vpTracker::cP internal 4-dim vector are computed using + * changeFrame(const vpHomogeneousMatrix &cMo, vpColVector &cP) const or changeFrame(const vpHomogeneousMatrix &cMo). + * These parameters could be retrieved using getX(), getY(), getZ() and getR(). + * To get theses parameters use get_cP(). + * + * - **in the image plane**: here we consider the parameters of the ellipse corresponding + * to the perspective projection of the 3D sphere. The parameters are the ellipse centroid (x, y) + * and n20, n11, n02 which are the second order centered moments of + * the ellipse normalized by its area (i.e., such that \f$n_{ij} = \mu_{ij}/a\f$ where + * \f$\mu_{ij}\f$ are the centered moments and a the area). + * These parameters are registered in vpTracker::p internal 5-dim vector and computed using projection() and + * projection(const vpColVector &cP, vpColVector &p) const. They could be retrieved using get_x(), get_y(), get_n20(), + * get_n11() and get_n02(). They correspond to 2D normalized sphere parameters with values expressed in meters. + * To get theses parameters use get_p(). +*/ +class VISP_EXPORT vpSphere : public vpForwardProjection +{ +public: + vpSphere(); + VP_EXPLICIT vpSphere(const vpColVector &oP); + vpSphere(double oX, double oY, double oZ, double R); + + void changeFrame(const vpHomogeneousMatrix &cMo, vpColVector &cP) const VP_OVERRIDE; + void changeFrame(const vpHomogeneousMatrix &cMo) VP_OVERRIDE; + + void display(const vpImage &I, const vpCameraParameters &cam, const vpColor &color = vpColor::green, + unsigned int thickness = 1) VP_OVERRIDE; + void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &color = vpColor::green, unsigned int thickness = 1) VP_OVERRIDE; + + void display(const vpImage &I, const vpCameraParameters &cam, const vpColor &color = vpColor::green, + unsigned int thickness = 1); + void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &color = vpColor::green, unsigned int thickness = 1); + + vpSphere *duplicate() const VP_OVERRIDE; + + double get_x() const { return p[0]; } + double get_y() const { return p[1]; } + + double get_n20() const { const unsigned int index_2 = 2; return p[index_2]; } + double get_n11() const { const unsigned int index_3 = 3; return p[index_3]; } + double get_n02() const { const unsigned int index_4 = 4; return p[index_4]; } + + double getX() const { const unsigned int index_0 = 0; return cP[index_0]; } + double getY() const { const unsigned int index_1 = 1; return cP[index_1]; } + double getZ() const { const unsigned int index_2 = 2; return cP[index_2]; } + double getR() const { const unsigned int index_3 = 3; return cP[index_3]; } + + + void projection() VP_OVERRIDE; + void projection(const vpColVector &cP, vpColVector &p) const VP_OVERRIDE; + void setWorldCoordinates(const vpColVector &oP) VP_OVERRIDE; + + void setWorldCoordinates(double oX, double oY, double oZ, double R); + +public: +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) + /*! + * @name Deprecated functions + */ + //@{ + /*! + * \deprecated You should rather use get_n20(). + * This function is incorrectly named and is confusing since it + * returns second order centered moments of the ellipse normalized + * by its area that corresponds to \f$n_20 = mu_20/a\f$. + */ + VP_DEPRECATED double get_mu20() const { const unsigned int index_2 = 2; return p[index_2]; } + /*! + * \deprecated You should rather use get_n11(). + * This function is incorrectly named and is confusing since it + * returns second order centered moments of the ellipse normalized + * by its area that corresponds to \f$n_11 = mu_11/a\f$. + */ + VP_DEPRECATED double get_mu11() const { const unsigned int index_3 = 3; return p[index_3]; } + /*! + * \deprecated You should rather use get_n02(). + * This function is incorrectly named and is confusing since it + * returns second order centered moments of the ellipse normalized + * by its area that corresponds to \f$n_02 = mu_02/a\f$. + */ + VP_DEPRECATED double get_mu02() const { const unsigned int index_4 = 4; return p[index_4]; } + //@} +#endif +protected: + void init() VP_OVERRIDE; + +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpStatisticalTestAbstract.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpStatisticalTestAbstract.h new file mode 100644 index 00000000..5c78c741 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpStatisticalTestAbstract.h @@ -0,0 +1,273 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +/*! + * \file vpStatisticalTestAbstract.h + * \brief Base class for Statistical Process Control methods implementation. + */ + +#ifndef VP_STATISTICAL_TEST_ABSTRACT_H +#define VP_STATISTICAL_TEST_ABSTRACT_H + +#include +#include +#include +#include + +#include + +BEGIN_VISP_NAMESPACE +/** + * \ingroup group_core_spc + * \brief Base class for methods detecting the drift of the mean of a process. + * + * To detect only downward drifts of the input signal \f$ s(t) \f$ use + * testDownwardMeanDrift().To detect only upward drifts in \f$ s(t) \f$ use + * testUpwardMeanDrift(). To detect both, downward and upward drifts use + * testDownUpwardMeanDrift(). + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using Statistical Process Control methods, you may have a look at:
+ * + * - \ref tutorial-spc +*/ +class VISP_EXPORT vpStatisticalTestAbstract +{ +public: + /** + * \brief Enum that indicates if a drift of the mean occurred. + */ + typedef enum vpMeanDriftType + { + MEAN_DRIFT_NONE = 0, /*!< No mean drift occurred*/ + MEAN_DRIFT_DOWNWARD = 1, /*!< A downward drift of the mean occurred.*/ + MEAN_DRIFT_UPWARD = 2, /*!< An upward drift of the mean occurred.*/ + MEAN_DRIFT_BOTH = 3, /*!< Both an aupward and a downward drifts occurred.*/ + MEAN_DRIFT_COUNT = 4, + MEAN_DRIFT_UNKNOWN = MEAN_DRIFT_COUNT + } vpMeanDriftType; + + /** + * \brief Cast a \b vpMeanDriftType into a string. + * + * \param[in] type The type of mean drift. + * \return std::string The corresponding message. + */ + static std::string vpMeanDriftTypeToString(const vpMeanDriftType &type); + + /** + * \brief Cast a string into a \b vpMeanDriftType. + * + * \param[in] name The name of the mean drift. + * \return vpMeanDriftType The corresponding \b vpMeanDriftType. + */ + static vpMeanDriftType vpMeanDriftTypeFromString(const std::string &name); + + /** + * \brief Get the list of available \b vpMeanDriftType objects that are handled. + * + * \param[in] prefix The prefix that should be placed before the list. + * \param[in] sep The separator between each element of the list. + * \param[in] suffix The suffix that should terminate the list. + * \return std::string The list of handled type of process tests, presented as a string. + */ + static std::string getAvailableMeanDriftType(const std::string &prefix = "<", const std::string &sep = " , ", + const std::string &suffix = ">"); + + /** + * \brief Print the message corresponding to the type of mean drift. + * + * \param[in] type The type of mean drift. + */ + static void print(const vpMeanDriftType &type); + +protected: + bool m_areStatisticsComputed; /*!< Set to true once the mean and the standard deviation are available.*/ + float m_count; /*!< Current number of data used to compute the mean and the standard deviation.*/ + float m_limitDown; /*!< Upper limit for the test signal m_wt.*/ + float m_limitUp; /*!< Lower limit for the test signal m_wt*/ + float m_mean; /*!< Mean of the monitored signal.*/ + unsigned int m_nbSamplesForStatistics; /*!< Number of samples to use to compute the mean and the standard deviation.*/ + float *m_s; /*!< Array that keeps the samples used to compute the mean and standard deviation.*/ + float m_stdev; /*!< Standard deviation of the monitored signal.*/ + float m_stdevmin; /*!< Minimum allowed standard deviation of the monitored signal.*/ + float m_sumForMean; /*!< Sum of the samples used to compute the mean and standard deviation.*/ + + /** + * \brief Detects if a downward mean drift occurred. + * + * \return \b vpMeanDriftType::MEAN_DRIFT_DOWNWARD if a downward mean drift occurred, \b vpMeanDriftType::MEAN_DRIFT_NONE otherwise. + * + * \sa detectUpwardMeanDrift() + */ + virtual vpMeanDriftType detectDownwardMeanDrift() = 0; + + /** + * \brief Detects if a upward mean drift occurred. + * + * \return \b vpMeanDriftType::MEAN_DRIFT_UPWARD if an upward mean drift occurred, \b vpMeanDriftType::MEAN_DRIFT_NONE otherwise. + * + * \sa detectDownwardMeanDrift() + */ + virtual vpMeanDriftType detectUpwardMeanDrift() = 0; + + /** + * \brief Update \b m_s and if enough values are available, compute the mean, the standard + * deviation and the limits. + * + * \param[in] signal The new value of the signal to monitor. + * + * \return true if the statistics have been computed, false if data are missing. + */ + virtual bool updateStatistics(const float &signal); + + /** + * \brief Update the test signals. + * + * \param[in] signal The new value of the signal to monitor. + */ + virtual void updateTestSignals(const float &signal) = 0; +public: + /** + * \brief Construct a new vpStatisticalTestAbstract object. + */ + vpStatisticalTestAbstract(); + + /** + * \brief Construct by copy a new vpStatisticalTestAbstract object. + */ + vpStatisticalTestAbstract(const vpStatisticalTestAbstract &other); + + /** + * \brief Destroy the vpStatisticalTestAbstract object. + */ + virtual ~vpStatisticalTestAbstract(); + + /** + * \brief Get the upper and lower limits of the test signal. + * + * \param[out] limitDown The lower limit. + * \param[out] limitUp The upper limit. + */ + inline void getLimits(float &limitDown, float &limitUp) const + { + limitDown = m_limitDown; + limitUp = m_limitUp; + } + + /** + * \brief Get the mean used as reference. + * + * \return float The mean. + */ + inline float getMean() const + { + return m_mean; + } + + /** + * \brief Get the standard deviation used as reference. + * + * \return float The standard deviation. + */ + inline float getStdev() const + { + return m_stdev; + } + + /** + * \brief (Re)Initialize the algorithm. + */ + void init(); + + /** + * \brief Copy operator of a vpStatisticalTestAbstract. + * + * \param[in] other The vpStatisticalTestAbstract to copy. + * \return vpStatisticalTestAbstract& *this after copy. + */ + vpStatisticalTestAbstract &operator=(const vpStatisticalTestAbstract &other); + + /** + * \brief Set the minimum value of the standard deviation that is expected. + * The computed standard deviation cannot be lower this value if set. + * + * \param[in] stdevmin The minimum value of the standard deviation that is expected. + */ + void setMinStdev(const float &stdevmin) + { + m_stdevmin = stdevmin; + } + + /** + * \brief Set the number of samples required to compute the mean and standard deviation + * of the signal and allocate the memory accordingly. + * + * \param[in] nbSamples The number of samples we want to use. + */ + void setNbSamplesForStat(const unsigned int &nbSamples); + + /** + * \brief Test if a downward or an upward mean drift occurred + * according to the new value of the signal. + * + * \param[in] signal The new value of the signal. + * \return vpMeanDriftType The type of mean drift that occurred. + * + * \sa testDownwardMeanDrift() testUpwardMeanDrift() + */ + vpMeanDriftType testDownUpwardMeanDrift(const float &signal); + + /** + * \brief Test if a downward mean drift occurred + * according to the new value of the signal. + * + * \param[in] signal The new value of the signal. + * \return vpMeanDriftType The type of mean drift that occurred. + * + * \sa testUpwardMeanDrift() + */ + vpMeanDriftType testDownwardMeanDrift(const float &signal); + + /** + * \brief Test if an upward mean drift occurred + * according to the new value of the signal. + * + * \param[in] signal The new value of the signal. + * \return vpMeanDriftType The type of mean drift that occurred. + * + * \sa testDownwardMeanDrift() + */ + vpMeanDriftType testUpwardMeanDrift(const float &signal); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpStatisticalTestEWMA.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpStatisticalTestEWMA.h new file mode 100644 index 00000000..1aeaf4a6 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpStatisticalTestEWMA.h @@ -0,0 +1,174 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +/*! + * \file vpStatisticalTestEWMA.h + * \brief Statistical Process Control Exponentially Weighted Moving Average implementation. + */ + +#ifndef VP_STATISTICAL_TEST_EWMA_H +#define VP_STATISTICAL_TEST_EWMA_H + +#include + +#include + +BEGIN_VISP_NAMESPACE +/** + * \ingroup group_core_spc + * \brief Class that permits to perform Exponentially Weighted Moving Average mean drft tests. + * + * The EWMA test is designed to detect drift in the mean \f$ \mu \f$ + * of an observed signal \f$ s(t) \f$. + * + * The test signal \f$ w(t) \f$ is computed as follow: + * + * \f$ w(0) = \mu \f$ + * + * \f$ w(t) = \alpha s(t) + ( 1 - \alpha ) * w(t-1) \f$ + * + * Be \f$ \sigma \f$ the standard deviation of the input signal \f$ s(t) \f$. + * + * A downward alarm is raised if: + * \f$ w(t) <= \mu - 3 * \sigma * \sqrt{ \frac{\alpha}{2 - \alpha}}\f$ + * + * An upward alarm is raised if: + * \f$ w(t) >= \mu + 3 * \sigma * \sqrt{ \frac{\alpha}{2 - \alpha}}\f$ + * + * To detect only downward drifts of the input signal \f$ s(t) \f$ use + * testDownwardMeanDrift().To detect only upward drifts in \f$ s(t) \f$ use + * testUpwardMeanDrift(). To detect both, downward and upward drifts use + * testDownUpwardMeanDrift(). + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using Statistical Process Control methods, you may have a look at:
+ * + * - \ref tutorial-spc +*/ +class VISP_EXPORT vpStatisticalTestEWMA : public vpStatisticalTestAbstract +{ +protected: + float m_alpha; /*!< Forgetting factor: the higher, the more weight the current signal value has.*/ + float m_wt; /*!< Test signal that permits to raise an alarm.*/ + float m_wtprev; /*!< Previous value of the test signal.*/ + + /** + * \brief Compute the upper and lower limits of the test signal. + */ + virtual void computeDeltaAndLimits(); + + /** + * \brief Detects if a downward mean drift occurred. + * + * \return \b vpMeanDriftType::MEAN_DRIFT_DOWNWARD if a downward mean drift occurred, \b vpMeanDriftType::MEAN_DRIFT_NONE otherwise. + * + * \sa detectUpwardMeanDrift() + */ + virtual vpMeanDriftType detectDownwardMeanDrift() VP_OVERRIDE; + + /** + * \brief Detects if an upward mean drift occurred on the mean. + * + * \return \b vpMeanDriftType::MEAN_DRIFT_UPWARD if an upward mean drift occurred, \b vpMeanDriftType::MEAN_DRIFT_NONE otherwise. + * + * \sa detectDownwardMeanDrift() + */ + virtual vpMeanDriftType detectUpwardMeanDrift() VP_OVERRIDE; + + /** + * \brief Update m_s and if enough values are available, compute the mean, the standard + * deviation and the limits. + * + * \param[in] signal The new value of the signal to monitor. + */ + virtual bool updateStatistics(const float &signal) VP_OVERRIDE; + + /** + * \brief Update the test signals. + * + * \param[in] signal The new value of the signal to monitor. + */ + virtual void updateTestSignals(const float &signal) VP_OVERRIDE; + +public: + /** + * \brief Construct a new vpStatisticalTestEWMA object. + * + * \param[in] alpha The forgetting factor. + */ + vpStatisticalTestEWMA(const float &alpha = 0.1f); + + /** + * \brief Get the forgetting factor of the algorithm. + * + * \return float The forgetting factor. + */ + inline float getAlpha() const + { + return m_alpha; + } + + /** + * \brief Get the current value of the test signal. + * + * \return float The current value of the test signal. + */ + inline float getWt() const + { + return m_wt; + } + + /** + * \brief Initialize the EWMA algorithm. + * + * \param[in] alpha The forgetting factor. + */ + void init(const float &alpha); + + /** + * \brief Initialize the EWMA algorithm. + * + * \param[in] alpha The forgetting factor. + * \param[in] mean The expected mean of the signal to monitor. + * \param[in] stdev The expected standard deviation of the signal to monitor. + */ + void init(const float &alpha, const float &mean, const float &stdev); + + /** + * \brief Set the forgetting factor. + * + * \param[in] alpha The forgetting factor. + */ + void setAlpha(const float &alpha); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpStatisticalTestHinkley.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpStatisticalTestHinkley.h new file mode 100644 index 00000000..bf3ccdd1 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpStatisticalTestHinkley.h @@ -0,0 +1,320 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +/*! + * \file vpStatisticalTestHinkley.h + * \brief Statistical Process Control Hinkley's test implementation. + */ + +#ifndef VP_STATISTICAL_TEST_HINKLEY_H +#define VP_STATISTICAL_TEST_HINKLEY_H + +#include + +#include + +BEGIN_VISP_NAMESPACE +/** + * \ingroup group_core_spc + * \brief This class implements the Hinkley's cumulative sum test. + * + * The Hinkley's cumulative sum test is designed to detect drift in the mean + * of an observed signal \f$ s(t) \f$. It is known to be robust (by + * taking into account all the past of the observed quantity), + * efficient, and inducing a very low computational load. The other + * attractive features of this test are two-fold. First, it can + * straightforwardly and accurately provide the drift instant. Secondly, + * due to its formulation (cumulative sum test), it can simultaneously + * handle both very abrupt and important changes, and gradual smaller + * ones without adapting the involved thresholds. + * + * Two tests are performed in parallel to look for downwards or upwards + * drifts in \f$ s(t) \f$, respectively defined by: + * + * \f[ S_k = \sum_{t=0}^{k} (s(t) - m_0 + \frac{\delta}{2}) \f] + * \f[ M_k = \max_{0 \leq i \leq k} S_i\f] + * \f[ T_k = \sum_{t=0}^{k} (s(t) - m_0 - \frac{\delta}{2}) \f] + * \f[ N_k = \min_{0 \leq i \leq k} T_i\f] + * + * In which \f$m_o\f$ is computed on-line and corresponds to the mean + * of the signal \f$ s(t) \f$ we want to detect a drift. \f$m_o\f$ is + * re-initialized at zero after each drift detection. \f$\delta\f$ + * denotes the drift minimal magnitude that we want to detect and + * \f$\alpha\f$ is a predefined threshold. These values are set by + * default to 0.2 in the default constructor vpStatisticalTestHinkley(). To modify the + * default values use setAlpha() and setDelta() or the + * vpStatisticalTestHinkley(double alpha, double delta) constructor. + * + * A downward drift is detected if \f$ M_k - S_k > \alpha \f$. + * A upward drift is detected if \f$ T_k - N_k > \alpha \f$. + * + * To detect only downward drifts in \f$ s(t) \f$ use + * testDownwardMeanDrift().To detect only upward drifts in \f$ s(t) \f$ use + * testUpwardMeanDrift(). To detect both, downward and upward drifts use + * testDownUpwardMeanDrift(). + * + * If a drift is detected, the drift location is given by the last instant + * \f$k^{'}\f$ when \f$ M_{k^{'}} - S_{k^{'}} = 0 \f$, or \f$ T_{k^{'}} - + * N_{k^{'}} = 0 \f$. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using Statistical Process Control methods, you may have a look at:
+ * + * - \ref tutorial-spc +*/ +class VISP_EXPORT vpStatisticalTestHinkley : public vpStatisticalTestAbstract +{ +protected: + float m_dmin2; /*!< Half of \f$\delta\f$, the drift minimal magnitude that we want to detect.*/ + float m_alpha; /*!< The \f$\alpha\f$ threshold indicating that a mean drift occurs. */ + float m_Sk; /*!< Test signal for downward mean drift.*/ + float m_Mk; /*!< Maximum of the test signal for downward mean drift \f$S_k\f$ .*/ + float m_Tk; /*!< Test signal for upward mean drift.*/ + float m_Nk; /*!< Minimum of the test signal for upward mean drift \f$T_k\f$*/ + bool m_computeDeltaAndAlpha; /*!< If true, compute \f$\delta\f$ and \f$\alpha\f$ from the standard deviation, + the alarm factor and the detection factor.*/ + float m_h; /*!< The alarm factor, that permits to compute \f$\alpha\f$ from the standard deviation of the signal.*/ + float m_k; /*!< The detection factor, that permits to compute \f$\delta\f$ from the standard deviation of the signal.*/ + + /** + * \brief Compute \f$\delta\f$ and \f$\alpha\f$ from the standard deviation of the signal. + */ + virtual void computeAlphaDelta(); + + /** + * \brief Compute the mean value \f$m_0\f$ of the signal. The mean value must be + * computed before the mean drift is estimated on-line. + * + * \param[in] signal The new value of the signal to monitor. + */ + void computeMean(double signal); + + /** + * \brief Compute \f$S_k = \sum_{t=0}^{k} (s(t) - m_0 + \frac{\delta}{2})\f$ + * + * \param[in] signal The new value of the signal to monitor. + */ + void computeSk(double signal); + + /** + * \brief Compute \f$M_k\f$, the maximum value of \f$S_k\f$. + */ + void computeMk(); + + /** + * \brief Compute \f$T_k = \sum_{t=0}^{k} (s(t) - m_0 - \frac{\delta}{2})\f$ + * + * \param[in] signal The new value of the signal to monitor. + */ + void computeTk(double signal); + + /** + * \brief Compute \f$N_k\f$, the minimum value of \f$T_k\f$. + */ + void computeNk(); + + /** + * \brief Detects if a downward mean drift occurred. + * + * \return \b vpMeanDriftType::MEAN_DRIFT_DOWNWARD if a downward mean drift occurred, \b vpMeanDriftType::MEAN_DRIFT_NONE otherwise. + */ + virtual vpMeanDriftType detectDownwardMeanDrift() VP_OVERRIDE; + + /** + * \brief Detects if an upward mean drift occurred on the mean. + * + * \return \b vpMeanDriftType::MEAN_DRIFT_UPWARD if an upward mean drift occurred, \b vpMeanDriftType::MEAN_DRIFT_NONE otherwise. + * + * \sa detectDownwardMeanDrift() + */ + virtual vpMeanDriftType detectUpwardMeanDrift() VP_OVERRIDE; + + /** + * \brief Update m_s and if enough values are available, compute the mean, the standard + * deviation and the limits. + * + * \param[in] signal The new value of the signal to monitor. + */ + virtual bool updateStatistics(const float &signal) VP_OVERRIDE; + + /** + * \brief Update the test signals. + * + * \param[in] signal The new value of the signal to monitor. + */ + virtual void updateTestSignals(const float &signal) VP_OVERRIDE; + +public: + /** + * @brief Construct a new vpStatisticalTestHinkley object. + * Call init() to initialise the Hinkley's test and set \f$\alpha\f$ + * and \f$\delta\f$ to default values. + * + * By default \f$ \delta = 0.2 \f$ and \f$ \alpha = 0.2\f$. Use + * setDelta() and setAlpha() to modify these values. + */ + vpStatisticalTestHinkley(); + + /** + * \brief Call init() to initialise the Hinkley's test and set \f$\alpha\f$ + * and \f$\delta\f$ thresholds. + * \param[in] alpha : \f$\alpha\f$ threshold indicating that a mean drift occurs. + * \param[in] delta : \f$\delta\f$ denotes the drift minimal magnitude that + * we want to detect. + * \param[in] nbSamplesForInit : number of signal samples to initialize the mean of the signal. + * + * \sa setAlpha(), setDelta() + */ + vpStatisticalTestHinkley(const float &alpha, const float &delta, const unsigned int &nbSamplesForInit = 30); + + /** + * \brief Construct a new vpStatisticalTestHinkley object. \f$\alpha\f$ and \f$\delta\f$ will be computed + * from the standard deviation of the signal. + * + * \param[in] h : the alarm factor that permits to compute \f$\alpha\f$ from the standard deviation. + * \param[in] k : the detection factor that permits to compute \f$\delta\f$ from the standard deviation. + * \param[in] computeAlphaDeltaFromStdev : must be equal to true, otherwise throw a vpException. + * \param[in] nbSamplesForInit : number of signal samples to initialize the mean of the signal. + */ + vpStatisticalTestHinkley(const float &h, const float &k, const bool &computeAlphaDeltaFromStdev, const unsigned int &nbSamplesForInit = 30); + + /** + * \brief Construct a new vpStatisticalTestHinkley object. \f$\alpha\f$ and \f$\delta\f$ will be computed + * from the standard deviation of the signal. + * + * \param[in] h : the alarm factor that permits to compute \f$\alpha\f$ from the standard deviation. + * \param[in] k : the detection factor that permits to compute \f$\delta\f$ from the standard deviation. + * \param[in] mean : the expected mean of the signal. + * \param[in] stdev : the expected standard deviation of the signal. + */ + vpStatisticalTestHinkley(const float &h, const float &k, const float &mean, const float &stdev); + + /** + * \brief Get the \f$\alpha\f$ threshold indicating that a mean drift occurs. + * + * \return The \f$\alpha\f$ threshold. + */ + inline float getAlpha() const { return m_alpha; } + + /*! + * \brief Get the test signal for downward mean drift. + * + * \return The value of \f$S_k = \sum_{t=0}^{k} (s(t) - m_0 + \frac{\delta}{2})\f$ . + */ + inline float getSk() const { return m_Sk; } + + /*! + * \brief Get the maximum of the test signal for downward mean drift \f$S_k\f$ . + * + * \return The value of \f$M_k\f$, the maximum value of \f$S_k\f$. + */ + inline float getMk() const { return m_Mk; } + + /*! + * \brief Get the test signal for upward mean drift.. + * + * \return The value of \f$T_k = \sum_{t=0}^{k} (s(t) - m_0 - \frac{\delta}{2})\f$ . + + */ + inline float getTk() const { return m_Tk; } + + /*! + * \brief Get the minimum of the test signal for upward mean drift \f$T_k\f$ + * + * \return The value of \f$N_k\f$, the minimum value of \f$T_k\f$. + */ + inline float getNk() const { return m_Nk; } + + /** + * \brief Initialise the Hinkley's test by setting the mean signal value + * \f$m_0\f$ to zero as well as \f$S_k, M_k, T_k, N_k\f$. + */ + void init(); + + /** + * \brief Call init() to initialise the Hinkley's test and set \f$\alpha\f$ + * and \f$\delta\f$ thresholds. + * + * \param[in] alpha The threshold indicating that a mean drift occurs. + * \param[in] delta The drift minimal magnitude that we want to detect. + * \param[in] nbSamplesForInit : number of signal samples to initialize the mean of the signal. + */ + void init(const float &alpha, const float &delta, const unsigned int &nbSamplesForInit); + + /** + * \brief (Re)Initialize a new vpStatisticalTestHinkley object. \f$\alpha\f$ and \f$\delta\f$ will be computed + * from the standard deviation of the signal. + * + * \param[in] h : the alarm factor that permits to compute \f$\alpha\f$ from the standard deviation. + * \param[in] k : the detection factor that permits to compute \f$\delta\f$ from the standard deviation. + * \param[in] computeAlphaDeltaFromStdev : must be equal to true, otherwise throw a vpException. + * \param[in] nbSamplesForInit : number of signal samples to initialize the mean of the signal. + */ + void init(const float &h, const float &k, const bool &computeAlphaDeltaFromStdev, const unsigned int &nbSamplesForInit); + + /** + * \brief Call init() to initialise the Hinkley's test, set \f$\alpha\f$ + * and \f$\delta\f$ thresholds, and the mean of the signal \f$m_0\f$. + * + * \param[in] alpha The threshold indicating that a mean drift occurs. + * \param[in] delta The drift minimal magnitude that we want to detect. + * \param[in] mean The expected value of the mean. + */ + void init(const float &alpha, const float &delta, const float &mean); + + /** + * \brief (Re)Initialize a new vpStatisticalTestHinkley object. \f$\alpha\f$ and \f$\delta\f$ will be computed + * from the standard deviation of the signal. + * + * \param[in] h : the alarm factor that permits to compute \f$\alpha\f$ from the standard deviation. + * \param[in] k : the detection factor that permits to compute \f$\delta\f$ from the standard deviation. + * \param[in] mean : the expected mean of the signal. + * \param[in] stdev : the expected standard deviation of the signal. + */ + void init(const float &h, const float &k, const float &mean, const float &stdev); + + /** + * \brief Set the drift minimal magnitude that we want to detect. + * + * \param[in] delta The drift magnitude. + */ + void setDelta(const float &delta); + + /** + * \brief The threshold indicating that a mean drift occurs. + * + * \param[in] alpha The threshold. + */ + void setAlpha(const float &alpha); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpStatisticalTestMeanAdjustedCUSUM.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpStatisticalTestMeanAdjustedCUSUM.h new file mode 100644 index 00000000..39868e51 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpStatisticalTestMeanAdjustedCUSUM.h @@ -0,0 +1,267 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +/*! + * \file vpStatisticalTestMeanAdjustedCUSUM.h + * \brief Statistical Process Control mean adjusted CUSUM implementation. + */ + +#ifndef VP_STATISTICAL_TEST_MEAN_ADJUSTED_CUSUM_H +#define VP_STATISTICAL_TEST_MEAN_ADJUSTED_CUSUM_H + +#include + +#include + +BEGIN_VISP_NAMESPACE +/** + * \ingroup group_core_spc + * \brief Class that permits to perform a mean adjusted Cumulative Sum test. + * + * The mean adjusted CUSUM test is designed to detect drift in the mean \f$ \mu \f$ + * of an observed signal \f$ s(t) \f$. + * + * Be \f$ \delta \f$ the amplitude of the mean drift we want to detect. + * Two test signals are computed at each iteration: + * + * \f$ S_-(t) = max\{0, S_-(t-1) - (s(t) - \mu) - \frac{\delta}{2}\} \f$ + * + * \f$ S_+(t) = max\{0, S_+(t-1) + (s(t) - \mu) - \frac{\delta}{2}\} \f$ + * + * A downward alarm is raised if: + * \f$ S_-(t) >= thresh\f$ + * + * An upward alarm is raised if: + * \f$ S_+(t) >= thresh\f$ + * + * To ease the understanding of the detection threshold \f$ \delta \f$ and the + * alarm threshold \f$ thresh \f$, ViSP implemented these two thresholds as + * a multiple of the standard deviation of the signal \f$ \sigma \f$: + * + * \f$ \delta = k \sigma , k \in R^{+*} \f$ + * + * \f$ thresh = h \sigma , h \in R^{+*} \f$ + * + * To have an Average Run Lenght of ~374 samples for a detection threshold \f$ \delta \f$ + * of 1 standard deviation \f$ \sigma \f$, set \f$ h \f$ to 4.76 . + * + * To detect only downward drifts of the input signal \f$ s(t) \f$ use + * testDownwardMeanDrift().To detect only upward drifts in \f$ s(t) \f$ use + * testUpwardMeanDrift(). To detect both, downward and upward drifts use + * testDownUpwardMeanDrift(). + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using Statistical Process Control methods, you may have a look at:
+ * + * - \ref tutorial-spc +*/ +class VISP_EXPORT vpStatisticalTestMeanAdjustedCUSUM : public vpStatisticalTestAbstract +{ +protected: + float m_delta; /*!< Slack of the CUSUM test, i.e. amplitude of mean shift we want to be able to detect.*/ + float m_h; /*!< Alarm factor that permits to determine the limit telling when a mean shift occurs: \f$thresh = h * \sigma \f$ . + To have an Average Run Lenght of ~374 samples for a detection of 1 stdev, set it to 4.76f*/ + float m_half_delta; /*!< Half of the amplitude we want to detect.*/ + float m_k; /*!< Detection factor that permits to determine the slack: \f$\delta = k * \sigma\f$ .*/ + float m_sminus; /*!< Test signal for downward mean shift: \f$ S_-(t) = max\{0, S_-(t-1) - (s(t) - \mu) - \frac{\delta}{2}\} \f$.*/ + float m_splus; /*!< Test signal for upward mean shift: \f$ S_+(t) = max\{0, S_+(t-1) + (s(t) - \mu) - \frac{\delta}{2}\} \f$.*/ + + /** + * \brief Compute the upper and lower limits of the test signal. + */ + virtual void computeDeltaAndLimits(); + + /** + * \brief Detects if a downward mean drift occurred. + * + * \return \b vpMeanDriftType::MEAN_DRIFT_DOWNWARD if a downward mean drift occurred, \b vpMeanDriftType::MEAN_DRIFT_NONE otherwise. + * + * \sa detectUpwardMeanDrift() + */ + virtual vpMeanDriftType detectDownwardMeanDrift() VP_OVERRIDE; + +/** + * \brief Detects if a upward jump occurred on the mean. + * + * \return upwardJump if a upward jump occurred, noJump otherwise. + */ + virtual vpMeanDriftType detectUpwardMeanDrift() VP_OVERRIDE; + + /** + * \brief Update m_s and if enough values are available, compute the mean, the standard + * deviation and the limits. + * + * \param[in] signal The new value of the signal to monitor. + */ + virtual bool updateStatistics(const float &signal) VP_OVERRIDE; + + /** + * \brief Update the test signals. + * + * \param[in] signal The new value of the signal to monitor. + */ + virtual void updateTestSignals(const float &signal) VP_OVERRIDE; + +public: + /** + * \brief Construct a new vpStatisticalTestMeanAdjustedCUSUM object. + * + * \param[in] h The alarm factor that permits to determine when the process is out of control from the standard + * deviation of the signal. + * \param[in] k The detection factor that permits to determine the slack of the CUSUM test, i.e. the + * minimum value of the jumps we want to detect, from the standard deviation of the signal. + * \param[in] nbPtsForStats The number of samples to use to compute the mean and the standard deviation of the signal + * to monitor. + */ + vpStatisticalTestMeanAdjustedCUSUM(const float &h = 4.f, const float &k = 1.f, const unsigned int &nbPtsForStats = 30); + + /** + * \brief Get the slack of the CUSUM test, + * i.e. amplitude of mean shift we want to be able to detect. + * + * \return float The slack of the CUSUM test. + */ + inline float getDelta() const + { + return m_delta; + } + + /** + * \brief Get the alarm factor. + * + * \return float The alarm factor. + */ + inline float getH() const + { + return m_h; + } + + /** + * \brief Get the detection factor. + * + * \return float The detection factor. + */ + inline float getK() const + { + return m_k; + } + + /** + * \brief Get the latest value of the test signal for downward jumps of the mean. + * + * \return float Its latest value. + */ + inline float getTestSignalMinus() const + { + return m_sminus; + } + + /** + * \brief Get the latest value of the test signal for upward jumps of the mean. + * + * \return float Its latest value. + */ + inline float getTestSignalPlus() const + { + return m_splus; + } + + /** + * \brief (Re)Initialize the mean adjusted CUSUM test. + * + * \param[in] h The alarm factor that permits to determine when the process is out of control from the standard + * deviation of the signal. + * \param[in] k The detection factor that permits to determine the slack of the CUSUM test, i.e. the + * minimum value of the jumps we want to detect, from the standard deviation of the signal. + * \param[in] nbPtsForStats The number of points to use to compute the mean and the standard deviation of the signal + */ + void init(const float &h, const float &k, const unsigned int &nbPtsForStats); + + /** + * \brief Initialize the mean adjusted CUSUM test. + * + * \param[in] delta The slack of the CUSUM test, i.e. the minimum value of the jumps we want to detect. + * \param[in] limitDown The lower limit of the CUSUM test, for the downward jumps. + * \param[in] limitUp The upper limit of the CUSUM test, for the upward jumps. + * \param[in] nbPtsForStats The number of points to use to compute the mean and the standard deviation of the signal + * to monitor. + */ + void init(const float &delta, const float &limitDown, const float &limitUp, const unsigned int &nbPtsForStats); + + /** + * \brief Initialize the mean adjusted CUSUM test. + * + * \param[in] h The alarm factor that permits to determine when the process is out of control from the standard + * deviation of the signal. + * \param[in] k The detection factor that permits to determine the slack of the CUSUM test, i.e. the + * minimum value of the jumps we want to detect, from the standard deviation of the signal. + * \param[in] mean The expected mean of the signal to monitor. + * \param[in] stdev The expected standard deviation of the signal to monitor. + */ + void init(const float &h, const float &k, const float &mean, const float &stdev); + + /** + * \brief Initialize the mean adjusted CUSUM test. + * + * \param[in] delta The slack of the CUSUM test, i.e. the minimum value of the jumps we want to detect. + * \param[in] limitDown The lower limit of the CUSUM test, for the downward jumps. + * \param[in] limitUp The upper limit of the CUSUM test, for the upward jumps. + * \param[in] mean The expected mean of the signal to monitor. + * \param[in] stdev The expected standard deviation of the signal to monitor. + */ + void init(const float &delta, const float &limitDown, const float &limitUp, const float &mean, const float &stdev); + + /** + * \brief Set the slack of the CUSUM test, i.e. the minimum value of the jumps we want to detect. + * + * \param[in] delta The slack of the CUSUM test. + */ + inline void setDelta(const float &delta) + { + m_delta = delta; + m_half_delta = 0.5f * delta; + } + + /** + * \brief Set the upward and downward jump limits. + * + * \param[in] limitDown The limit for the downward jumps. + * \param[in] limitUp The limit for the upward jumps. + */ + inline void setLimits(const float &limitDown, const float &limitUp) + { + m_limitDown = limitDown; + m_limitUp = limitUp; + } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpStatisticalTestShewhart.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpStatisticalTestShewhart.h new file mode 100644 index 00000000..2efc72bd --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpStatisticalTestShewhart.h @@ -0,0 +1,232 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +/*! + * \file vpStatisticalTestShewhart.h + * \brief Statistical Process Control Shewhart's test implementation. + */ + +#ifndef VP_STATISTICAL_TEST_SHEWHART_H +#define VP_STATISTICAL_TEST_SHEWHART_H + +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/** + * \ingroup group_core_spc + * \brief Class that permits a Shewhart's test. + * + * Be \f$ s(t) \f$ the signal to monitor, \f$ \mu \f$ and \f$ \sigma \f$ the mean and standard deviation + * of this signal when it is "in control". + * + * A downward alarm is raised if: + * \f$ s(t) >= \mu - 3 \sigma \f$ + * + * An upward alarm is raised if: + * \f$ s(t) >= \mu + 3 \sigma \f$ + * + * Additionally, we can activate the WECO's rules that have been + * proposed by the Western Electric Company to add additional verifications: + * - An alarm is raised if two out of three consecutive points fall beyond the \f$2\sigma\f$-limit, on the same side of the mean \f$ \mu \f$ + * - An alarm is raised if four out of five consecutive points fall beyond the \f$1\sigma\f$-limit, on the same side of the mean \f$ \mu \f$ + * - An alarm is raised if eight consecutive points fall on the same side of the mean \f$ \mu \f$. + * + * The user can decide to use or not the WECO's rules. Additionally, the user can choose which WECO's + * rule(s) to activate. + * + * To detect only downward drifts of the input signal \f$ s(t) \f$ use + * testDownwardMeanDrift().To detect only upward drifts in \f$ s(t) \f$ use + * testUpwardMeanDrift(). To detect both, downward and upward drifts use + * testDownUpwardMeanDrift(). + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using Statistical Process Control methods, you may have a look at:
+ * + * - \ref tutorial-spc +*/ + +class VISP_EXPORT vpStatisticalTestShewhart : public vpStatisticalTestSigma +{ +public: + typedef enum vpWecoRulesAlarm + { + THREE_SIGMA_WECO = 0, /*!< When a \f$ 3\sigma \f$ alarm was raised.*/ + TWO_SIGMA_WECO = 1, /*!< When a \f$ 2\sigma \f$ alarm was raised.*/ + ONE_SIGMA_WECO = 2, /*!< When a \f$ 1\sigma \f$ alarm was raised*/ + SAME_SIDE_WECO = 3, /*!< When a alarm raised when 8 consecutive points lie on the same side of the mean \f$ \mu \f$ was raised.*/ + NONE_WECO = 4, /*!< When no WECO's rule alarm was raised.*/ + COUNT_WECO = 5 /*!< Number of WECO's rules that are implemented.*/ + } vpWecoRulesAlarm; + + static std::string vpWecoRulesAlarmToString(const vpWecoRulesAlarm &alarm); + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17) + VP_ATTRIBUTE_NO_DESTROY static inline const std::vector CONST_ALL_WECO_ACTIVATED = std::vector(COUNT_WECO -1, true); +#else + VP_ATTRIBUTE_NO_DESTROY static const std::vector CONST_ALL_WECO_ACTIVATED; +#endif + static const unsigned int NB_DATA_SIGNAL = 8; + +protected: + unsigned int m_nbDataInBuffer; /*!< Indicate how many data are available in the circular buffer.*/ + float m_signal_vec[NB_DATA_SIGNAL]; /*!< The last values of the signal.*/ + bool m_activateWECOrules; /*!< If true, activate the WECO's rules (NB: it increases the sensitivity of the Shewhart + control chart but the false alarm frequency is also increased.)*/ + std::vector m_activatedWECOrules; /*!< The WECO's rules that are activated. The more are activated, the higher the + sensitivity of the Shewhart control chart is but the higher the false + alarm frequency is.*/ + unsigned int m_idCurrentData; /*!< The index of the current data in m_signal.*/ + vpWecoRulesAlarm m_alarm; /*!< The type of alarm raised due to WECO's rules.*/ + float m_oneSigmaNegLim; /*!< The \f$ \mu - \sigma \f$ threshold.*/ + float m_oneSigmaPosLim; /*!< The \f$ \mu + \sigma \f$ threshold.*/ + float m_twoSigmaNegLim; /*!< The \f$ \mu - 2 \sigma \f$ threshold.*/ + float m_twoSigmaPosLim; /*!< The \f$ \mu + 2 \sigma \f$ threshold.*/ + + /** + * \brief Compute the upper and lower limits of the test signal. + */ + virtual void computeLimits() VP_OVERRIDE; + + /** + * \brief Detects if a downward mean drift occurred. + * + * \return \b vpMeanDriftType::MEAN_DRIFT_DOWNWARD if a downward mean drift occurred, \b vpMeanDriftType::MEAN_DRIFT_NONE otherwise. + * + * \sa detectUpwardMeanDrift() + */ + virtual vpMeanDriftType detectDownwardMeanDrift() VP_OVERRIDE; + /** + * \brief Detects if an upward mean drift occurred on the mean. + * + * \return \b vpMeanDriftType::MEAN_DRIFT_UPWARD if an upward mean drift occurred, \b vpMeanDriftType::MEAN_DRIFT_NONE otherwise. + * + * \sa detectDownwardMeanDrift() + */ + virtual vpMeanDriftType detectUpwardMeanDrift() VP_OVERRIDE; + + /** + * \brief Update m_s and if enough values are available, compute the mean, the standard + * deviation and the limits. + * + * \param[in] signal The new value of the signal to monitor. + */ + virtual bool updateStatistics(const float &signal) VP_OVERRIDE; + + /** + * \brief Update the test signals. + * + * \param[in] signal The new value of the signal to monitor. + */ + virtual void updateTestSignals(const float &signal) VP_OVERRIDE; +public: + /** + * \brief Construct a new vpStatisticalTestShewhart object. + * + * \param[in] activateWECOrules If true, activate the WECO's rules (NB: it increases the sensitivity of the Shewhart + * control chart but the false alarm frequency is also increased.) + * \param[in] activatedRules An array where true means that the corresponding WECO's rule is activated and false means + * that it is not. + * \param[in] nbSamplesForStats The number of samples to compute the statistics of the signal. + */ + vpStatisticalTestShewhart(const bool &activateWECOrules = true, const std::vector &activatedRules = CONST_ALL_WECO_ACTIVATED, const unsigned int &nbSamplesForStats = 30); + + /** + * \brief Construct a new vpStatisticalTestShewhart object. + * + * \param[in] activateWECOrules If true, activate the WECO's rules (NB: it increases the sensitivity of the Shewhart + * control chart but the false alarm frequency is also increased.) + * \param[in] activatedRules An array where true means that the corresponding WECO's rule is activated and false means + * that it is not. + * \param[in] mean The expected mean of the signal. + * \param[in] stdev The expected standard deviation of the signal. + */ + vpStatisticalTestShewhart(const bool &activateWECOrules, const std::vector &activatedRules, const float &mean, const float &stdev); + + /** + * \brief Get the alarm raised by the last test due to the WECO's rules. + * + * \return vpWecoRulesAlarm The type of raised alarm. + */ + vpWecoRulesAlarm getAlarm() const + { + return m_alarm; + } + + /** + * \brief Get the last value of the signal. + * + * \return float The signal. + */ +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + inline virtual float getSignal() const override +#else + inline virtual float getSignal() const +#endif + { + return m_signal_vec[m_idCurrentData]; + } + + /** + * \brief Get the NB_DATA_SIGNAL last signal values, sorted from the latest [0] to the newest [NB_DATA_SIGNAL - 1]. + * + * \return std::vector The last NB_DATA_SIGNAL values. + */ + std::vector getSignals() const; + + /** + * \brief (Re)Initialize the test. + * + * \param[in] activateWECOrules If true, activate the WECO's rules (NB: it increases the sensitivity of the Shewhart + * control chart but the false alarm frequency is also increased.) + * \param[in] activatedRules An array where true means that the corresponding WECO's rule is activated and false means + * that it is not. + * \param[in] nbSamplesForStats The number of samples to compute the statistics of the signal. + */ + void init(const bool &activateWECOrules, const std::vector &activatedRules = CONST_ALL_WECO_ACTIVATED, const unsigned int &nbSamplesForStats = 30); + + /** + * \brief (Re)Initialize the test. + * + * \param[in] activateWECOrules If true, activate the WECO's rules (NB: it increases the sensitivity of the Shewhart + * control chart but the false alarm frequency is also increased.) + * \param[in] activatedRules An array where true means that the corresponding WECO's rule is activated and false means + * that it is not. + * \param[in] mean The expected mean of the signal. + * \param[in] stdev The expected standard deviation of the signal. + */ + void init(const bool &activateWECOrules, const std::vector &activatedRules, const float &mean, const float &stdev); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpStatisticalTestSigma.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpStatisticalTestSigma.h new file mode 100644 index 00000000..5387c21e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpStatisticalTestSigma.h @@ -0,0 +1,165 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +/*! + * \file vpStatisticalTestSigma.h + * \brief Statistical Process Control sigma test implementation. + */ + +#ifndef VP_STATISTICAL_TEST_SIGMA_H +#define VP_STATISTICAL_TEST_SIGMA_H + +#include + +#include + +BEGIN_VISP_NAMESPACE +/** + * \ingroup group_core_spc + * \brief Class that permits a simple test comparing the current value to the + * standard deviation of the signal. + * + * Be \f$ s(t) \f$ the signal to monitor, \f$ \mu \f$ and \f$ \sigma \f$ the mean and standard deviation + * of this signal when it is "in control". + * + * Be \f$ h \f$ a user-defined alarm factor. + * + * A downward alarm is raised if: + * \f$ s(t) >= \mu - h \sigma \f$ + * + * An upward alarm is raised if: + * \f$ s(t) >= \mu - h \sigma \f$ + * + * \f$ h \f$ is often set to 3 if we assume the \f$ s(t) \f$ follows a normal distribution. + * + * To detect only downward drifts of the input signal \f$ s(t) \f$ use + * testDownwardMeanDrift().To detect only upward drifts in \f$ s(t) \f$ use + * testUpwardMeanDrift(). To detect both, downward and upward drifts use + * testDownUpwardMeanDrift(). + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using Statistical Process Control methods, you may have a look at:
+ * + * - \ref tutorial-spc +*/ + +class VISP_EXPORT vpStatisticalTestSigma : public vpStatisticalTestAbstract +{ +protected: + float m_h; /*!< The alarm factor applied to the standard deviation to compute the limits.*/ + float m_signal; /*!< The last value of the signal.*/ + + /** + * \brief Compute the upper and lower limits of the test signal. + */ + virtual void computeLimits(); + + /** + * \brief Detects if a downward mean drift occurred. + * + * \return \b vpMeanDriftType::MEAN_DRIFT_DOWNWARD if a downward mean drift occurred, \b vpMeanDriftType::MEAN_DRIFT_NONE otherwise. + * + * \sa detectUpwardMeanDrift() + */ + virtual vpMeanDriftType detectDownwardMeanDrift() VP_OVERRIDE; + + /** + * \brief Detects if an upward mean drift occurred on the mean. + * + * \return \b vpMeanDriftType::MEAN_DRIFT_UPWARD if an upward mean drift occurred, \b vpMeanDriftType::MEAN_DRIFT_NONE otherwise. + * + * \sa detectDownwardMeanDrift() + */ + virtual vpMeanDriftType detectUpwardMeanDrift() VP_OVERRIDE; + + /** + * \brief Update m_s and if enough values are available, compute the mean, the standard + * deviation and the limits. + * + * \param[in] signal The new value of the signal to monitor. + */ + virtual bool updateStatistics(const float &signal) VP_OVERRIDE; + + /** + * \brief Update the test signals. + * + * \param[in] signal The new value of the signal to monitor. + */ + virtual void updateTestSignals(const float &signal) VP_OVERRIDE; + +public: + /** + * \brief Construct a new vpStatisticalTestSigma object. + * + * \param[in] h The alarm factor applied to the standard deviation to compute the limits. + * \param[in] nbSamplesForStats The number of samples to compute the statistics of the signal. + */ + vpStatisticalTestSigma(const float &h = 3.f, const unsigned int &nbSamplesForStats = 30); + + /** + * \brief Construct a new vpStatisticalTestSigma object. + * + * \param[in] h The alarm factor applied to the standard deviation to compute the limits. + * \param[in] mean The expected mean of the signal. + * \param[in] stdev The expected standard deviation of the signal. + */ + vpStatisticalTestSigma(const float &h, const float &mean, const float &stdev); + + /** + * \brief Get the last value of the signal. + * + * \return float The signal. + */ + inline virtual float getSignal() const + { + return m_signal; + } + + /** + * \brief (Re)Initialize the test. + * + * \param[in] h The alarm factor applied to the standard deviation to compute the limits. + * \param[in] nbSamplesForStats The number of samples to compute the statistics of the signal. + */ + void init(const float &h = 3.f, const unsigned int &nbSamplesForStats = 30); + + /** + * \brief (Re)Initialize the test. + * + * \param[in] h The alarm factor applied to the standard deviation to compute the limits. + * \param[in] mean The expected mean of the signal. + * \param[in] stdev The expected standard deviation of the signal. + */ + void init(const float &h, const float &mean, const float &stdev); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpSubColVector.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpSubColVector.h new file mode 100644 index 00000000..dfcbb30e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpSubColVector.h @@ -0,0 +1,91 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Mask on a vpColVector. + */ + +/*! + * \file vpSubColVector.h + * + * \brief Definition of the vpSubColVector class + */ + +#ifndef VP_SUBCOL_VECTOR_H +#define VP_SUBCOL_VECTOR_H + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpSubColVector + * \ingroup group_core_matrices + * This class provides a mask on a vpColVector. It has internally a + * pointer to the parent vpColVector. + * All properties of vpColVector are available with + * a vpSubColVector. + * + * \sa vpMatrix vpColVector vpRowVector +*/ +class VISP_EXPORT vpSubColVector : public vpColVector +{ +public: + vpSubColVector(); + vpSubColVector(vpColVector &v, const unsigned int &offset, const unsigned int &nrows); + virtual ~vpSubColVector() VP_OVERRIDE; + + void checkParentStatus() const; + + void init(vpColVector &v, const unsigned int &offset, const unsigned int &nrows); + + vpSubColVector &operator=(const vpSubColVector &B); + + vpSubColVector &operator=(const vpPoseVector &p); + vpSubColVector &operator=(const vpRotationVector &rv); + vpSubColVector &operator=(const vpTranslationVector &tv); + + vpSubColVector &operator=(const vpColVector &B); + vpSubColVector &operator=(const vpMatrix &B); + vpSubColVector &operator=(const double &x); + +protected: + //! Number of row of parent vpColVector at initialization + unsigned int m_pRowNum; + //! Parent vpColVector + vpColVector *m_parent; + +private: + //! Copy constructor unavailable + vpSubColVector(const vpSubColVector &v /* m */); + + +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpSubMatrix.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpSubMatrix.h new file mode 100644 index 00000000..c94c8aa0 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpSubMatrix.h @@ -0,0 +1,96 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Mask on a vpMatrix. + */ + +/*! + * \file vpSubMatrix.h + * + * \brief Definition of the vpSubMatrix class + */ + +#ifndef VP_SUB_MATRIX_H +#define VP_SUB_MATRIX_H + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpSubMatrix + * \ingroup group_core_matrices + * \brief Definition of the vpSubMatrix class that provides a mask on a vpMatrix. + * All properties of vpMatrix are available with a vpSubMatrix. + * + * + * \sa vpMatrix vpColVector vpRowVector +*/ +class VISP_EXPORT vpSubMatrix : public vpMatrix +{ + +public: + //! Default constructor + vpSubMatrix(); + //! Constructor + vpSubMatrix(vpMatrix &m, const unsigned int &row, const unsigned int &col, const unsigned int &nrows, + const unsigned int &ncols); + //! Destructor + virtual ~vpSubMatrix() VP_OVERRIDE; + + //! Initialisation of vpMatrix + void init(vpMatrix &m, const unsigned int &row, const unsigned int &col, const unsigned int &nrows, + const unsigned int &ncols); + + //! Check is parent vpRowVector has changed since initialization + void checkParentStatus() const; + + //! Operation such as subA = subB + vpSubMatrix &operator=(const vpSubMatrix &B); + //! Operation such as subA = B + vpSubMatrix &operator=(const vpMatrix &B); + //! Operation such as subA = x + vpSubMatrix &operator=(const double &x); + +protected: + unsigned int pRowNum; + unsigned int pColNum; + vpMatrix *parent; + +private: + //! Eye method unavailable + void eye(unsigned int n); + //! Eye method unavailable + void eye(unsigned int m, unsigned int n); + //! Copy constructor unavailable + vpSubMatrix(const vpSubMatrix &m /* m */); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpSubRowVector.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpSubRowVector.h new file mode 100644 index 00000000..266bb19a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpSubRowVector.h @@ -0,0 +1,85 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Mask on a vpRowVector. + */ + +/*! + * \file vpSubRowVector.h + * + * \brief Definition of the vpSubRowVector class + */ + +#ifndef VP_SUB_ROW_VECTOR_H +#define VP_SUB_ROW_VECTOR_H + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpSubRowVector + * \ingroup group_core_matrices + * This class provides a mask on a vpRowVector. It has internally a + * pointer to the parent vpRowVector. + * All properties of vpRowVector are available with + * a vpSubRowVector. + * + * \sa vpMatrix vpColVector vpRowVector +*/ +class VISP_EXPORT vpSubRowVector : public vpRowVector +{ + +public: + vpSubRowVector(); + vpSubRowVector(vpRowVector &v, const unsigned int &offset, const unsigned int &ncols); + virtual ~vpSubRowVector() VP_OVERRIDE; + + void checkParentStatus() const; + + void init(vpRowVector &v, const unsigned int &offset, const unsigned int &ncols); + + vpSubRowVector &operator=(const vpSubRowVector &B); + vpSubRowVector &operator=(const vpRowVector &B); + vpSubRowVector &operator=(const vpMatrix &B); + vpSubRowVector &operator=(const double &x); + +protected: + //! Number of row of parent vpColVector at initialization + unsigned int m_pColNum; + //! Parent vpColVector + vpRowVector *m_parent; + +private: + //! Copy constructor unavailable + vpSubRowVector(const vpSubRowVector &m /* m */); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpThetaUVector.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpThetaUVector.h new file mode 100644 index 00000000..2f5fa081 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpThetaUVector.h @@ -0,0 +1,240 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Theta U parameterization for the rotation. + */ + +/*! + \file vpThetaUVector.h + \brief class that consider the case of the Theta U parameterization for the + rotation +*/ + +#ifndef VP_THETAU_VECTOR_H +#define VP_THETAU_VECTOR_H + +#include +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE + +class vpHomogeneousMatrix; +class vpRotationMatrix; +class vpPoseVector; +class vpRzyxVector; +class vpRxyzVector; +class vpRzyzVector; +class vpColVector; +class vpRotationVector; +class vpQuaternionVector; + +/*! + \class vpThetaUVector + + \ingroup group_core_transformations + + \brief Implementation of a rotation vector as \f$\theta {\bf u}\f$ + axis-angle minimal representation. + + Class that consider the case of the \f$\theta {\bf u}\f$ + parameterization for the rotation. + + The vpThetaUVector class is derived from vpRotationVector. + + The \f$\theta {\bf u}\f$ representation is one of the minimal + representation of a rotation matrix, where + \f${\bf u} = (u_{x} \; u_{y} \; u_{z})^{\top}\f$ + is a unit vector representing the rotation + axis and \f$\theta\f$ is the rotation angle. + + From the \f$\theta {\bf u}\f$ representation it is possible to build the + rotation matrix \f${\bf R}\f$ using the Rodrigues formula: + + \f[ + {\bf R} = {\bf I}_{3} + (1 - \cos{ \theta}) \; {\bf u u}^{\top} + \sin{ + \theta} \; [{\bf u}]_{\times} \f] + + with \f${\bf I}_{3}\f$ the identity matrix of dimension + \f$3\times3\f$ and \f$[{\bf u}]_{\times}\f$ the skew matrix: + + \f[ + [{\bf u}]_{\times} = \left( + \begin{array}{ccc} + 0 & -u_{z} & u_{y} \\ + u_{z} & 0 & -u_{x} \\ + -u_{y} & u_{x} & 0 + \end{array} + \right) + \f] + + From the implementation point of view, it is nothing more than an + array of three floats with values in [rad]. + + You can set values [rad] accessing each element: + \code + vpThetaUVector tu; + tu[0] = M_PI_4; + tu[1] = M_PI_2; + tu[2] = M_PI; + \endcode + You can also initialize the vector using operator<<(double): + \code + tu << M_PI_4, M_PI_2, M_PI; + \endcode + Or you can also initialize the vector from a list of doubles if ViSP is build with c++11 enabled: + \code + tu = {M_PI_4, M_PI_2, M_PI}; + \endcode + + To get the values [rad] use: + \code + double tux = tu[0]; + double tuy = tu[1]; + double tuz = tu[2]; + \endcode + + The code below shows first how to initialize a \f$\theta {\bf u}\f$ + vector, than how to construct a rotation matrix from a vpThetaUVector + and finally how to extract the theta U angles from the build rotation + matrix. + + \code + #include + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpThetaUVector tu; + + // Initialise the theta U rotation vector + tu[0] = vpMath::rad( 45.f); + tu[1] = vpMath::rad(-30.f); + tu[2] = vpMath::rad( 90.f); + + // Construct a rotation matrix from the theta U angles + vpRotationMatrix R(tu); + + // Extract the theta U angles from a rotation matrix + tu.buildFrom(R); + + // Print the extracted theta U angles. Values are the same than the + // one used for initialization + std::cout << tu; + + // Since the rotation vector is 3 values column vector, the + // transpose operation produce a row vector. + vpRowVector tu_t = tu.t(); + + // Print the transpose row vector + std::cout << tu_t << std::endl; + } + \endcode +*/ +class VISP_EXPORT vpThetaUVector : public vpRotationVector +{ +public: + vpThetaUVector(); +#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98) +vpThetaUVector(const vpThetaUVector &) = default; + virtual ~vpThetaUVector() VP_OVERRIDE = default; +#else + virtual ~vpThetaUVector() { } +#endif + // constructor initialize a Theta U vector from a homogeneous matrix + VP_EXPLICIT vpThetaUVector(const vpHomogeneousMatrix &M); + // constructor initialize a Theta U vector from a pose vector + VP_EXPLICIT vpThetaUVector(const vpPoseVector &p); + // constructor initialize a Theta U vector from a rotation matrix + VP_EXPLICIT vpThetaUVector(const vpRotationMatrix &R); + // constructor initialize a Theta U vector from a RzyxVector + VP_EXPLICIT vpThetaUVector(const vpRzyxVector &rzyx); + // constructor initialize a Theta U vector from a RzyzVector + VP_EXPLICIT vpThetaUVector(const vpRzyzVector &rzyz); + // constructor initialize a Theta U vector from a RxyzVector + VP_EXPLICIT vpThetaUVector(const vpRxyzVector &rxyz); + VP_EXPLICIT vpThetaUVector(const vpQuaternionVector &q); + VP_EXPLICIT vpThetaUVector(const vpColVector &tu); + VP_EXPLICIT vpThetaUVector(const std::vector &tu); + + vpThetaUVector(double tux, double tuy, double tuz); + + // convert an homogeneous matrix into Theta U vector + vpThetaUVector &buildFrom(const vpHomogeneousMatrix &M); + // convert a pose vector into Theta U vector + vpThetaUVector &buildFrom(const vpPoseVector &p); + // convert a rotation matrix into Theta U vector + vpThetaUVector &buildFrom(const vpRotationMatrix &R); + // convert an Rzyx vector into Theta U vector + vpThetaUVector &buildFrom(const vpRzyxVector &rzyx); + // convert an Rzyz vector into Theta U vector + vpThetaUVector &buildFrom(const vpRzyzVector &zyz); + // convert an Rxyz vector into Theta U vector + vpThetaUVector &buildFrom(const vpRxyzVector &xyz); + vpThetaUVector &buildFrom(const vpQuaternionVector &q); + vpThetaUVector &buildFrom(const vpColVector &tu); + vpThetaUVector &buildFrom(const std::vector &tu); + + vpThetaUVector &buildFrom(const double &tux, const double &tuy, const double &tuz); + + // extract the angle and the axis from the ThetaU representation + void extract(double &theta, vpColVector &u) const; + double getTheta() const; + vpColVector getU() const; + + vpThetaUVector &operator=(const vpColVector &tu); + vpThetaUVector &operator=(double x); + vpThetaUVector operator*(const vpThetaUVector &tu_b) const; + +#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98) + vpThetaUVector &operator=(const vpThetaUVector &) = default; +#endif + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpThetaUVector &operator=(const std::initializer_list &list); +#endif + +private: + static const double minimum; + static const unsigned int constr_val_3; + +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpThread.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpThread.h new file mode 100644 index 00000000..8c7b88c7 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpThread.h @@ -0,0 +1,205 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Threading capabilities + */ +#ifndef _vpPthread_h_ +#define _vpPthread_h_ + +#include +#include + +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) && (defined(VISP_HAVE_PTHREAD) || (defined(_WIN32) && !defined(WINRT_8_0))) + +#if defined(VISP_HAVE_PTHREAD) +#include +#include +#elif defined(_WIN32) +// Mute warning with clang-cl +// warning : non-portable path to file ''; specified path differs in case from file name on disk [-Wnonportable-system-include-path] +// warning : non-portable path to file ''; specified path differs in case from file name on disk [-Wnonportable-system-include-path] +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wnonportable-system-include-path" +#endif + +// Include WinSock2.h before windows.h to ensure that winsock.h is not +// included by windows.h since winsock.h and winsock2.h are incompatible +#include +#include + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +#endif + +#ifdef ENABLE_VISP_NAMESPACE +namespace VISP_NAMESPACE_NAME +{ +#endif +/*! + \class vpThread + + \ingroup group_core_threading + + \deprecated Use rather std::thread. + + Class to represent individual threads of execution. + This class implements native pthread functionalities if available, or + native Windows threading capabilities if pthread is not available under + Windows. +*/ +class VP_DEPRECATED vpThread +{ +public: +#if defined(VISP_HAVE_PTHREAD) + typedef void *Args; + typedef void *Return; + typedef void *(*Fn)(Args); + typedef pthread_t Handle; +#elif defined(_WIN32) + typedef LPVOID Args; + typedef DWORD Return; + typedef LPTHREAD_START_ROUTINE Fn; + // typedef DWORD (*Fn)(Args); + typedef HANDLE Handle; +#endif + /*! + Default constructor that does nothing. To attach a function to this + thread of execution you need to call create(). + */ + vpThread() : m_handle(), m_isCreated(false), m_isJoinable(false) { } + + /*! + Construct a thread object that represents a new joinable thread of + execution. The new thread of execution calls \e fn passing \e args as + arguments. + \param fn : A pointer to a function. + \param args : Arguments passed to the call to \e fn (if any). + */ + vpThread(vpThread::Fn fn, vpThread::Args args = nullptr) : m_handle(), m_isCreated(false), m_isJoinable(false) + { + create(fn, args); + } + + /*! + Creates a thread object that represents a new joinable thread of + execution. + \param fn : A pointer to a function. + \param args : Arguments passed to the call to \e fn (if any). + */ + void create(vpThread::Fn fn, vpThread::Args args = nullptr) + { + if (m_isCreated) + throw vpException(vpException::fatalError, "The thread is already created"); +#if defined(VISP_HAVE_PTHREAD) + int err = pthread_create(&m_handle, nullptr, fn, args); + if (err != 0) { + throw vpException(vpException::cannotUseConstructorError, "Can't create thread : %s", strerror(err)); + } +#elif defined(_WIN32) + DWORD dwThreadIdArray; + m_handle = CreateThread(nullptr, // default security attributes + 0, // use default stack size + fn, // thread function name + args, // argument to thread function + 0, // use default creation flags + &dwThreadIdArray); // returns the thread identifier +#endif + + m_isJoinable = true; + } + + /*! + Destroy the thread. + */ + virtual ~vpThread() + { + join(); +#if defined(VISP_HAVE_PTHREAD) +#elif defined(_WIN32) + CloseHandle(m_handle); +#endif + } + + /*! + This function return when the thread execution has completed. + This blocks the execution of the thread that calls this function until + the function called on construction returns (if it hasn't yet). + + After a call to this function, the thread object becomes non-joinable and + can be destroyed safely. + + \sa joinable() + */ + void join() + { + if (m_isJoinable) { +#if defined(VISP_HAVE_PTHREAD) + pthread_join(m_handle, nullptr); +#elif defined(_WIN32) +#if defined(WINRT_8_1) + WaitForSingleObjectEx(m_handle, INFINITE, FALSE); +#else + WaitForSingleObject(m_handle, INFINITE); +#endif +#endif + m_isJoinable = false; + } + } + + /*! + Returns a value used to access implementation-specific information + associated to the thread. + */ + Handle getHandle() { return m_handle; } + + /*! + Returns whether the thread object is joinable. + + A thread object is not joinable in any of these cases: + - if it was default-constructed and create() was not called. + - if join() has been called. + + \sa join() + */ + bool joinable() { return m_isJoinable; } + +protected: + Handle m_handle; //!< Thread handle + bool m_isCreated; //!< Indicates if the thread is created + bool m_isJoinable; //!< Indicates if the thread is joinable +}; +#ifdef ENABLE_VISP_NAMESPACE +} +#endif +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpTime.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpTime.h new file mode 100644 index 00000000..7f3dca6c --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpTime.h @@ -0,0 +1,109 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Time management and measurement. + */ + +/*! + \file vpTime.h + \brief Time management and measurement + */ + +#ifndef VP_TIME_H +#define VP_TIME_H + +#include +#include +#include +#if VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11 +#include +#endif + +#include + +BEGIN_VISP_NAMESPACE +/*! + * \ingroup group_core_time + * \brief Time management and measurement. + * + * The example below shows how to synchronize a loop to a given framerate. + * + * \code + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * double t; + * for ( ; ; ) { + * t = vpTime::measureTimeMs(); + * ... + * vpTime::wait(t, 40); // Loop time is set to 40 ms, ie 25 Hz + * } + * } + * \endcode +*/ + +namespace vpTime +{ +VISP_EXPORT std::string getDateTime(const std::string &format = "%Y/%m/%d %H:%M:%S"); +VISP_EXPORT double getMinTimeForUsleepCall(); +VISP_EXPORT double measureTimeSecond(); +VISP_EXPORT double measureTimeMs(); +VISP_EXPORT double measureTimeMicros(); +VISP_EXPORT void sleepMs(double t); +VISP_EXPORT int wait(double t0, double t); +VISP_EXPORT void wait(double t); +} // namespace vpTime + +class VISP_EXPORT vpChrono +{ +public: + vpChrono(); + + double getDurationMicros(); + double getDurationMs(); + double getDurationSeconds(); + void start(bool reset = true); + void stop(); + +private: + double m_durationMs; +#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98) && (defined(_MSC_VER) && _MSC_VER >= 1900 /* VS2015 */ || !defined(_MSC_VER)) + std::chrono::steady_clock::time_point m_lastTimePoint; +#else + double m_lastTimePoint; +#endif +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpTracker.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpTracker.h new file mode 100644 index 00000000..0ad1c443 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpTracker.h @@ -0,0 +1,109 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Generic tracker. + */ + +/*! + * \file vpTracker.h + * \brief Class that defines what is a generic tracker. + */ + +#ifndef VP_TRACKER_H +#define VP_TRACKER_H + +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpTracker + * \ingroup group_core_trackers + * \brief Class that defines what is a feature generic tracker. + * + * A tracker is able to track features with parameters expressed in: + * - in the camera frame \e cP. These parameters are located in the public + * attribute vpTracker::cP. + * - in the image plane \e p. These parameters are located in the public + * attribute vpTracker::p. They correspond to normalized coordinates + * of the feature expressed in meters. +*/ +class VISP_EXPORT vpTracker +{ +public: + /** @name Public Attributes Inherited from vpTracker */ + //@{ + /*! + * Feature coordinates expressed in the image plane \e p. They correspond + * to 2D normalized coordinates expressed in meters. + */ + vpColVector p; + /*! + * Feature coordinates expressed in the camera frame \e cP. + */ + vpColVector cP; + + /*! + * Flag used to indicate if the feature parameters \e cP expressed + * in the camera frame are available. + */ + bool cPAvailable; + //@} + +public: + //! Default constructor. + vpTracker(); + //! Copy constructor. + vpTracker(const vpTracker &tracker); + //! Destructor. + virtual ~vpTracker() { } + + /** @name Public Member Functions Inherited from vpTracker */ + //@{ + //! Return object parameters expressed in the 2D image plane computed by perspective projection. + vpColVector get_p() const { return p; } + //! Return object parameters expressed in the 3D camera frame. + vpColVector get_cP() const { return cP; } + + //! Copy operator. + vpTracker &operator=(const vpTracker &tracker); + //@} + +protected: + /** @name Protected Member Functions Inherited from vpTracker */ + //@{ + //! Default initialization. + void init(); + //@} +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpTrackingException.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpTrackingException.h new file mode 100644 index 00000000..91b89327 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpTrackingException.h @@ -0,0 +1,94 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Exceptions that can be emitted by the vpTracking class and its derivatives. + */ + +/*! + * \file vpTrackingException.h + * \brief error that can be emitted by the vpTracker class and its derivatives + */ + +#ifndef VP_TRACKING_EXCEPTION_H +#define VP_TRACKING_EXCEPTION_H + +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE + +/*! + * \class vpTrackingException + * \ingroup group_core_debug + * \brief Error that can be emitted by the vpTracker class and its derivatives. +*/ +class VISP_EXPORT vpTrackingException : public vpException +{ +public: + /*! + * \brief Lists the possible error than can be emitted while calling + * vpTracking member + */ + enum errorTrackingCodeEnum + { + featureLostError, //!< Tracker lost feature + // Moving edges + notEnoughPointError, //!< Not enough point to track + initializationError, //!< Tracker initialization error + fatalError //!< Tracker fatal error + }; + +public: + /*! + * Constructor. + */ + vpTrackingException(int id, const char *format, ...) + { + this->code = id; + va_list args; + va_start(args, format); + setMessage(format, args); + va_end(args); + } + + /*! + * Constructor. + */ + vpTrackingException(int id, const std::string &msg) : vpException(id, msg) { } + + /*! + * Constructor. + */ + VP_EXPLICIT vpTrackingException(int id) : vpException(id) { } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpTranslationVector.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpTranslationVector.h new file mode 100644 index 00000000..0333ea70 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpTranslationVector.h @@ -0,0 +1,213 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Translation vector. + */ + +/*! + * \file vpTranslationVector.h + * \brief Class that consider the case of a translation vector. + */ + +#ifndef VP_TRANSLATION_VECTOR_H +#define VP_TRANSLATION_VECTOR_H + +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +class vpMatrix; + +/*! + \class vpTranslationVector + + \ingroup group_core_transformations + + \brief Class that consider the case of a translation vector. + + Let us denote \f$^{a}{\bf t}_{b} = [t_x,t_y,t_z]^\top\f$ the translation + from frame \f$ a \f$ to frame \f$ b \f$. The representation of a + translation is a column vector of dimension 3. + + Translations along x,y,z axis are expressed in meters. + + From the implementation point of view, it is nothing more than an + array of three doubles with values in [meters]. + + You can set values [meters] accessing each element: + \code + vpTranslationVector t; + t[0] = 0; + t[1] = 0.1; + t[2] = 0.5; + \endcode + You can also initialize the vector using operator<<(double): + \code + t << 0, 0.1, 0.5; + \endcode + Or you can also initialize the vector from a list of doubles if ViSP is build with c++11 enabled: + \code + t = {0, 0.1, 0.5}; + \endcode + + To get the values [meters] use: + \code + double tx = t[0]; + double ty = t[1]; + double tz = t[2]; + \endcode + + The code below shows how to use a translation vector to build an + homogeneous matrix. + + \code + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpTranslationVector t; // Translation vector + + // Initialization of the translation vector + t[0] = 0.2; // tx = 0.2 meters + t[1] = -0.1; // ty = -0.1 meters + t[2] = 1.0; // tz = 1 meters + + // Construction of a rotation matrix + vpRotationMatrix R; // Set to identity by default + + // Construction of an homogeneous matrix + vpHomogeneousMatrix M(t, R); + } + \endcode +*/ +class VISP_EXPORT vpTranslationVector : public vpArray2D +{ +public: + /*! + Default constructor. + The translation vector is initialized to zero. + */ + vpTranslationVector() : vpArray2D(3, 1), m_index(0) { } + vpTranslationVector(double tx, double ty, double tz); + vpTranslationVector(const vpTranslationVector &tv); + VP_EXPLICIT vpTranslationVector(const vpHomogeneousMatrix &M); + VP_EXPLICIT vpTranslationVector(const vpPoseVector &p); + VP_EXPLICIT vpTranslationVector(const vpColVector &v); + + vpTranslationVector &buildFrom(const double &tx, const double &ty, const double &tz); + vpTranslationVector &buildFrom(const vpHomogeneousMatrix &M); + vpTranslationVector &buildFrom(const vpPoseVector &p); + vpTranslationVector &buildFrom(const vpColVector &v); + + double frobeniusNorm() const; + + // operators + + // translation vectors additions c = a + b (a, b unchanged) + vpTranslationVector operator+(const vpTranslationVector &tv) const; + vpTranslationVector operator+(const vpColVector &v) const; + // translation vectors subtraction c = a - b (a, b unchanged) + vpTranslationVector operator-(const vpTranslationVector &tv) const; + // negate t = -a (t is unchanged) + vpTranslationVector operator-() const; + vpMatrix operator*(const vpRowVector &v) const; + // b = x * a (x=scalar) + vpTranslationVector operator*(double x) const; + vpTranslationVector &operator*=(double x); + vpTranslationVector operator/(double x) const; + vpTranslationVector &operator/=(double x); + // Copy operator. Allow operation such as A = v + vpTranslationVector &operator=(const vpColVector &tv); + vpTranslationVector &operator=(const vpTranslationVector &tv); + vpTranslationVector &operator=(double x); +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpTranslationVector &operator=(const std::initializer_list &list); +#endif + + //! Operator that allows to set a value of an element \f$t_i\f$: t[i] = x + inline double &operator[](unsigned int n) { return *(data + n); } + //! Operator that allows to get the value of an element \f$t_i\f$: x = t[i] + inline const double &operator[](unsigned int n) const { return *(data + n); } + + vpTranslationVector &operator<<(double val); + vpTranslationVector &operator,(double val); + + /*! + This function is not applicable to a translation vector that is always a + 3-by-1 column vector. + \exception vpException::fatalError When this function is called. + */ + VP_NORETURN void resize(unsigned int nrows, unsigned int ncols, bool flagNullify = true) + { + (void)nrows; + (void)ncols; + (void)flagNullify; + throw(vpException(vpException::fatalError, "Cannot resize a translation vector")); + } + + void set(double tx, double ty, double tz); + + // Skew Symmetric matrix + vpMatrix skew() const; + + double sumSquare() const; + + vpRowVector t() const; + + static vpTranslationVector cross(const vpTranslationVector &a, const vpTranslationVector &b); + static vpTranslationVector mean(const std::vector &vec_M); + static vpTranslationVector mean(const std::vector &vec_t); + static vpMatrix skew(const vpTranslationVector &tv); + static void skew(const vpTranslationVector &tv, vpMatrix &M); + +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) + /*! + @name Deprecated functions + */ + //@{ + VP_DEPRECATED double euclideanNorm() const; + //} +#endif + +protected: + unsigned int m_index; // index used for operator<< and operator, to fill a vector +private: + static const unsigned int constr_val_3; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpTriangle.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpTriangle.h new file mode 100644 index 00000000..9a2e668e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpTriangle.h @@ -0,0 +1,108 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Defines a 2D triangle. + */ + +#ifndef vpTriangle_h +#define vpTriangle_h + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpTriangle + \ingroup group_core_geometry + \brief Defines a 2D triangle. + + A triangle is internally represented by three 2D points. + + By default the three coordinates in the \f$ (i,j) \f$ frame (see + vpImagePoint class documentation for more details about the frame.) are \f$ + (0,0) \f$, \f$ (1,0) \f$ and \f$ (0,1) \f$. +*/ +class VISP_EXPORT vpTriangle +{ +private: + bool goodTriange; + vpImagePoint S1; + double uvinv00; + double uvinv01; + double uvinv10; + double uvinv11; + double ptempo0; + double ptempo1; + double area; + vpImagePoint apex1; + vpImagePoint apex2; + vpImagePoint apex3; + +public: + vpTriangle(); + vpTriangle(const vpTriangle &tri); + virtual ~vpTriangle(); + + vpTriangle &operator=(const vpTriangle &tri); + + vpTriangle(const vpImagePoint &iP1, const vpImagePoint &iP2, const vpImagePoint &iP3); + + vpTriangle &buildFrom(const vpImagePoint &iP1, const vpImagePoint &iP2, const vpImagePoint &iP3); + + bool inTriangle(const vpImagePoint &iP, double threshold = 0.00001); + + /*! + Get the apexes of the triangle. + + \param iP1 : first apex. + \param iP2 : second apex. + \param iP3 : third apex. + */ + void getTriangleApexes(vpImagePoint &iP1, vpImagePoint &iP2, vpImagePoint &iP3) const + { + iP1 = apex1; + iP2 = apex2; + iP3 = apex3; + } + + /*! + Return the area of the triangle. + The area is computed when the triangle is built from three image points. + + \return The area of the triangle. + */ + inline double getArea() const { return this->area; } + +private: + void init(const vpImagePoint &iP1, const vpImagePoint &iP2, const vpImagePoint &iP3); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpUDPClient.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpUDPClient.h new file mode 100644 index 00000000..b3846687 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpUDPClient.h @@ -0,0 +1,223 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * UDP Client + */ + +#ifndef VP_UDP_CLIENT_H +#define VP_UDP_CLIENT_H + +#include + +// inet_ntop() not supported on win XP +#ifdef VISP_HAVE_FUNC_INET_NTOP + +#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX +#include +#include +#include +#else + +#if defined(__clang__) +// Mute warning : non-portable path to file ''; specified path differs in case from file name on disk [-Wnonportable-system-include-path] +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wnonportable-system-include-path" +#endif + +#include + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +#endif + +#include + +#define VP_MAX_UDP_PAYLOAD 508 + +BEGIN_VISP_NAMESPACE +/*! + * \class vpUDPClient + * + * \ingroup group_core_com_ethernet + * + * \brief This class implements a basic (IPv4) User Datagram Protocol (UDP) client. + * + * More information here, + * here + * or + * here: + *
+ * This User Datagram Protocol (UDP) is defined to make available a + * datagram mode of packet-switched computer communication in the + * environment of an interconnected set of computer networks. This + * protocol assumes that the Internet Protocol (IP) [1] is used as the + * underlying protocol. + * + * This protocol provides a procedure for application programs to send + * messages to other programs with a minimum of protocol mechanism. The + * protocol is transaction oriented, and delivery and duplicate protection + * are not guaranteed. Applications requiring ordered reliable delivery of + * streams of data should use the Transmission Control Protocol (TCP) [2]. + *
+ * + * Example of a client's code, sending a basic message and receiving the + * server answer: + * + * \code + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() { + * try { + * std::string servername = "127.0.0.1"; + * unsigned int port = 50037; + * vpUDPClient client(servername, port); + * + * while (true) { + * std::cout << "Enter the message to send:" << std::endl; + * std::string msg = ""; + * std::getline(std::cin, msg); + * if (client.send(msg) != static_cast(msg.size())) + * std::cerr << "Error client.send()!" << std::endl; + * if (client.receive(msg)) + * std::cout << "Receive from the server: " << msg << std::endl; + * } + * return EXIT_SUCCESS; + * } catch (const vpException &e) { + * std::cerr << "Catch an exception: " << e.what() << std::endl; + * return EXIT_FAILURE; + * } + * } + * \endcode + * + * If you want to send a complex data type, you can either send the ASCII + * representation or send directly the byte data. In the last case, you should + * have to handle that both the server and the client have the same data type + * representation. Be careful also with the endianness of the network / host. + * + * Here an example using a structure of data, assuming that both the server and + * the client have the same architecture (probably you should write your own + * serialization / deserialization functions for the data you want to send / + * receive): + * + * \code + * #include + * #include + * #include + * #include + * + * struct vpDataType_t { + * double double_val; + * int int_val; + * vpDataType_t() : double_val(0.0), int_val(0) {} + * vpDataType_t(double dbl, int i) : double_val(dbl), int_val(i) {} + * }; + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() { + * try { + * std::string servername = "127.0.0.1"; + * unsigned int port = 50037; + * vpUDPClient client(servername, port); + * vpDataType_t data_type(1234.56789, 123450); + * char data[sizeof(data_type.double_val)+sizeof(data_type.int_val)]; + * + * memcpy(data, &data_type.double_val, sizeof(data_type.double_val)); + * memcpy(data+sizeof(data_type.double_val), &data_type.int_val, sizeof(data_type.int_val)); + * + * std::string msg(data, sizeof(data_type.double_val)+sizeof(data_type.int_val)); + * if (client.send(msg) != static_cast(sizeof(data_type.double_val)+sizeof(data_type.int_val))) + * std::cerr << "Error client.send()!" << std::endl; + * if (client.receive(msg)) { + * data_type.double_val = *reinterpret_cast(msg.c_str()); + * data_type.int_val + * = *reinterpret_cast(msg.c_str()+sizeof(data_type.double_val)); + * std::cout << "Receive from the server double_val: " << data_type.double_val + * << " ; int_val: " << data_type.int_val << std::endl; + * } + * return EXIT_SUCCESS; + * } catch (const vpException &e) { + * std::cerr << "Catch an exception: " << e.what() << std::endl; + * return EXIT_FAILURE; + * } + * } + * \endcode + * + * \sa vpUDPServer +*/ +class VISP_EXPORT vpUDPClient +{ +public: + vpUDPClient(); + vpUDPClient(const vpUDPClient &client); + vpUDPClient(const std::string &hostname, int port); + virtual ~vpUDPClient(); + + vpUDPClient &operator=(const vpUDPClient &client); + + /** @name Inherited functionalities from vpUDPClient */ + //@{ + void init(const std::string &hostname, int port); + + int receive(std::string &msg, int timeoutMs = 0); + int receive(void *msg, size_t len, int timeoutMs = 0); + int send(const std::string &msg); + int send(const void *msg, size_t len); + //@} + +protected: + bool m_is_init; + +private: + char m_buf[VP_MAX_UDP_PAYLOAD]; + struct sockaddr_in m_serverAddress; + int m_serverLength; +#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX + int m_socketFileDescriptor; +#else + SOCKET m_socketFileDescriptor; + WSADATA m_wsa; +#endif + + void close(); +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpUDPServer.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpUDPServer.h new file mode 100644 index 00000000..6571763d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpUDPServer.h @@ -0,0 +1,241 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * UDP Server + */ + +#ifndef _vpUDPServer_h_ +#define _vpUDPServer_h_ + +#include + +// inet_ntop() not supported on win XP +#ifdef VISP_HAVE_FUNC_INET_NTOP + +#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX +#include +#include +#include +#else + +#if defined(__clang__) +// Mute warning : non-portable path to file ''; specified path differs in case from file name on disk [-Wnonportable-system-include-path] +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wnonportable-system-include-path" +#endif + +#include + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +#endif + +#include + +#define VP_MAX_UDP_PAYLOAD 508 + +BEGIN_VISP_NAMESPACE +/*! + \class vpUDPServer + + \ingroup group_core_com_ethernet + + \brief This class implements a basic (IPv4) User Datagram Protocol (UDP) +server. + + More information here, + here + or here: +
+ This User Datagram Protocol (UDP) is defined to make available a + datagram mode of packet-switched computer communication in the + environment of an interconnected set of computer networks. This + protocol assumes that the Internet Protocol (IP) [1] is used as the + underlying protocol. + + This protocol provides a procedure for application programs to send + messages to other programs with a minimum of protocol mechanism. The + protocol is transaction oriented, and delivery and duplicate protection + are not guaranteed. Applications requiring ordered reliable delivery of + streams of data should use the Transmission Control Protocol (TCP) [2]. +
+ + Example of a server's code, receiving a basic message and sending an + echo message to the client: + + \code + #include + #include + #include + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() { + try { + int port = 50037; + vpUDPServer server(port); + + while (true) { + std::string msg = "", hostInfo = ""; + int res = server.receive(msg, hostInfo, 5000); + if (res) { + std::cout << "Server received: " << msg << " from: " << hostInfo << std::endl; + std::cout << "Reply to the client: Echo: " << msg << std::endl; + + //Get address and port + std::istringstream iss(hostInfo); + std::vector tokens; + std::copy(std::istream_iterator(iss), + std::istream_iterator(), + std::back_inserter(tokens)); + server.send("Echo: " + msg, tokens[1], atoi(tokens[2].c_str())); + } else if (res == 0) { + std::cout << "Receive timeout" << std::endl; + } else { + std::cerr << "Error server.receive()!" << std::endl; + } + } + + return EXIT_SUCCESS; + } catch (const vpException &e) { + std::cerr << "Catch an exception: " << e.what() << std::endl; + return EXIT_FAILURE; + } + } + \endcode + + If you want to send a complex data type, you can either send the ASCII + representation or send directly the byte data. In the last case, you should + have to handle that both the server and the client have the same data type + representation. Be careful also with the endianness of the network / host. + + Here an example using a structure of data, assuming that both the server and + the client have the same architecture (probably you should write your own + serialization / deserialization functions for the data you want to send / + receive): + + \code + #include + #include + #include + #include + #include + #include + #include + + struct vpDataType_t { + double double_val; + int int_val; + + vpDataType_t() : double_val(0.0), int_val(0) {} + vpDataType_t(double dbl, int i) : double_val(dbl), int_val(i) {} + }; + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() { + try { + int port = 50037; + vpUDPServer server(port); + + std::string msg = "", hostInfo = ""; + int res = server.receive(msg, hostInfo); + if (res) { + vpDataType_t data_type; + memcpy(&data_type.double_val, msg.c_str(), sizeof(data_type.double_val)); + memcpy(&data_type.int_val, msg.c_str()+sizeof(data_type.double_val), sizeof(data_type.int_val)); + std::cout << "Server received double_val: " << data_type.double_val << " ; int_val: " + << data_type.int_val << " from: " << hostInfo << std::endl; + + // Get address and port + std::istringstream iss(hostInfo); + std::vector tokens; + std::copy(std::istream_iterator(iss), + std::istream_iterator(), + std::back_inserter(tokens)); + data_type.double_val += 1.5; + data_type.int_val += 2; + char data[sizeof(data_type.double_val)+sizeof(data_type.int_val)]; + memcpy(data, &data_type.double_val, sizeof(data_type.double_val)); + memcpy(data+sizeof(data_type.double_val), &data_type.int_val, sizeof(data_type.int_val)); + msg = std::string(data, sizeof(data_type.double_val)+sizeof(data_type.int_val)); + + server.send(msg, tokens[1], atoi(tokens[2].c_str())); + } + + return EXIT_SUCCESS; + } catch (const vpException &e) { + std::cerr << "Catch an exception: " << e.what() << std::endl; + return EXIT_FAILURE; + } + } + \endcode + + \sa vpUDPServer +*/ +class VISP_EXPORT vpUDPServer +{ +public: + vpUDPServer(int port); + vpUDPServer(const std::string &hostname, int port); + virtual ~vpUDPServer(); + + int receive(std::string &msg, int timeoutMs = 0); + int receive(std::string &msg, std::string &hostInfo, int timeoutMs = 0); + int send(const std::string &msg, const std::string &hostname, int port); + +private: + char m_buf[VP_MAX_UDP_PAYLOAD]; + struct sockaddr_in m_clientAddress; + int m_clientLength; + struct sockaddr_in m_serverAddress; +#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX + int m_socketFileDescriptor; +#else + SOCKET m_socketFileDescriptor; + WSADATA m_wsa; +#endif + + void init(const std::string &hostname, int port); +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpUKSigmaDrawerAbstract.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpUKSigmaDrawerAbstract.h new file mode 100644 index 00000000..013052c4 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpUKSigmaDrawerAbstract.h @@ -0,0 +1,88 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Display a point cloud using PCL library. + */ + +#ifndef VP_UK_SIGMA_DRAWER_ABSTRACT_H +#define VP_UK_SIGMA_DRAWER_ABSTRACT_H + +#include + +#include + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpUKSigmaDrawerAbstract + \ingroup group_core_kalman + This abstract class defines the interface to draw the sigma points for the Unscented Kalman filter. +*/ +class VISP_EXPORT vpUKSigmaDrawerAbstract +{ +public: + /** + * \brief The weights corresponding to the sigma points drawing. + */ + typedef struct vpSigmaPointsWeights + { + std::vector m_wm; /*!< The weights for the computation of the mean.*/ + std::vector m_wc; /*!< The weights for the computation of the covariance.*/ + } vpSigmaPointsWeights; + + inline vpUKSigmaDrawerAbstract(const unsigned int &n) : m_n(n) { } + vpUKSigmaDrawerAbstract(const vpUKSigmaDrawerAbstract &) = default; + virtual ~vpUKSigmaDrawerAbstract() = default; + vpUKSigmaDrawerAbstract &operator=(const vpUKSigmaDrawerAbstract &) = default; + + /** + * \brief Draw the sigma points according to the current mean and covariance of the state + * of the Unscented Kalman filter. + * + * \param[in] mean The current mean of the state of the UKF. + * \param[in] covariance The current process covariance of the UKF. + * @return std::vector The sigma points. + */ + virtual std::vector drawSigmaPoints(const vpColVector &mean, const vpMatrix &covariance) = 0; + + /** + * \brief Computed the weights that correspond to the sigma points that have been drawn. + * + * \return vpSigmaPointsWeights The weights that correspond to the sigma points. + */ + virtual vpSigmaPointsWeights computeWeights() = 0; +protected: + unsigned int m_n; /*!< The size of the state of the UKF.*/ +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpUKSigmaDrawerMerwe.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpUKSigmaDrawerMerwe.h new file mode 100644 index 00000000..d47cf687 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpUKSigmaDrawerMerwe.h @@ -0,0 +1,148 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 202% by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Display a point cloud using PCL library. + */ + +#ifndef VP_UK_SIGMA_DRAWER_MERWE_H +#define VP_UK_SIGMA_DRAWER_MERWE_H + +#include + +#include + +#include +#include + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) +BEGIN_VISP_NAMESPACE +/*! + \class vpUKSigmaDrawerMerwe + \ingroup group_core_kalman + This class defines a class to draw sigma points following the E. A. Wan and R. van der Merwe's method proposed in + \cite Merwe00. + + The method has four parameters: \f$ n \f$, which is the dimension of the input, and \f$ \alpha \f$, \f$ \beta \f$ and + \f$ \kappa \f$, which are three reals. For notational convenience, we define \f$ \lambda = \alpha^2 (n - \kappa) - n \f$. + + Be \f$ \boldsymbol{\mu} \in {R}^n \f$ the mean and \f$ \boldsymbol{\Sigma} \in {R}^{n x n} \f$ the covariance matrix of the + input of the algorithm. The algorithm will draw \f$ 2n + 1 \f$ sigma points \f$ \chi_i \in {R}^n \f$ such as: + + \f[ + \begin{array}{lcl} + \chi_0 &=& \boldsymbol{\mu} \\ + \chi_i &=& \begin{cases} \boldsymbol{\mu} + \left[ \sqrt{(n + \lambda) \boldsymbol{\Sigma}} \right]_i^T & i = 1 .. n \\ + \boldsymbol{\mu} - \left[ \sqrt{(n + \lambda) \boldsymbol{\Sigma}} \right]_{i - n}^T & i = n + 1 .. 2n + \end{cases} + \end{array} + \f] + + where the subscript \f$ i \f$ denotes that we keep the \f$ i^{th} \f$ of the matrix. + + Several definitions of the square root of a matrix exists. We decided to use the following definition: \f$ \textbf{L} \f$ + is the square root of the matrix \f$ \boldsymbol{\Sigma} \f$ if \f$ \boldsymbol{\Sigma} \f$ can be written as: + + \f$ \boldsymbol{\Sigma} = \textbf{L} \textbf{L}^T \f$ + + This definition is favored because it can be computed using the Cholesky's decomposition. + + The computation of the weights that go along the sigma points is the following. The weight used for the + computation of the mean of \f$ \chi_0 \f$ is computed such as: + + \f$ w_0^m = \frac{\lambda}{n + \lambda} \f$ + + The weight used for the computation of the mean of \f$ \chi_0 \f$ is computed such as: + + \f$ w_0^c = \frac{\lambda}{n + \lambda} + 1 - \alpha^2 + \beta \f$ + + The weights for the other sigma points \f$ \chi_1 ... \chi_{2n} \f$ are the same for the mean and covariance + and are computed as follow: + + \f$ w_i^m = w_i^c = \frac{1}{2(n + \lambda)} i = 1..2n \f$ + + \b Note: the weights do not sum to one. Negative values can even be expected. + + \b Additional\b note: the original author recommended to set \f$ \beta = 2 \f$ for Gaussian problems, \f$ \kappa = 3 - n \f$ + and \f$ 0 \leq \alpha \leq 1 \f$, where a larger value for \f$ \alpha \f$ spreads the sigma points further from the mean, + which can be a problem for highly non-linear problems. +*/ +class VISP_EXPORT vpUKSigmaDrawerMerwe : public vpUKSigmaDrawerAbstract +{ +public: + typedef vpUnscentedKalman::vpAddSubFunction vpAddSubFunction; + + /** + * \brief Construct a new vpUKSigmaDrawerMerwe object. + * + * \param[in] n The size of the state vector. + * \param[in] alpha A factor, which should be a real in the interval [0; 1]. The larger alpha is, + * the further the sigma points are spread from the mean. + * \param[in] beta Another factor, which should be set to 2 if the problem is Gaussian. + * \param[in] kappa A third factor, whose value should be set to 3 - n for most problems. + * \param[in] resFunc Residual function expressed in the state space. + * \param[in] addFunc Addition function expressed in the state space. + */ + vpUKSigmaDrawerMerwe(const unsigned int &n, const double &alpha, const double &beta, const double &kappa, + const vpAddSubFunction &resFunc = vpUnscentedKalman::simpleResidual, + const vpAddSubFunction &addFunc = vpUnscentedKalman::simpleAdd); + + /** + * \brief Draw the sigma points according to the current mean and covariance of the state + * of the Unscented Kalman filter. + * + * \param[in] mean The current mean of the state of the UKF. + * \param[in] covariance The current process covariance of the UKF. + * @return std::vector The sigma points. + */ + virtual std::vector drawSigmaPoints(const vpColVector &mean, const vpMatrix &covariance) VP_OVERRIDE; + + /** + * \brief Computed the weights that correspond to the sigma points that have been drawn. + * + * \return vpSigmaPointsWeights The weights that correspond to the sigma points. + */ + virtual vpSigmaPointsWeights computeWeights() VP_OVERRIDE; +protected: + inline void computeLambda() + { + m_lambda = m_alpha * m_alpha * (static_cast(m_n) + m_kappa) - static_cast(m_n); + } + + double m_alpha; /*!< A factor, which should be a real in the interval [0; 1]. The larger alpha is, + the further the sigma points are spread from the mean.*/ + double m_beta; /*!< Another factor, which should be set to 2 if the problem is Gaussian.*/ + double m_kappa; /*!< A third factor, whose value should be set to 3 - n for most problems.*/ + double m_lambda; /*!< \f$ \alpha^2 (n + \kappa) - n \f$, where \f$ n \f$ is the size of the state vector.*/ + vpAddSubFunction m_resFunc; /*!< Residual function expressed in the state space.*/ + vpAddSubFunction m_addFunc; /*!< Addition function expressed in the state space.*/ +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpUniRand.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpUniRand.h new file mode 100644 index 00000000..d17fd59c --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpUniRand.h @@ -0,0 +1,195 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Pseudo random number generator. + */ + +/* + * PCG Random Number Generation for C. + * + * Copyright 2014 Melissa O'Neill + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For additional information about the PCG random number generation scheme, + * including its license and other licensing options, visit + * + * http://www.pcg-random.org + */ + +/* + * This code is derived from the full C implementation, which is in turn + * derived from the canonical C++ PCG implementation. The C++ version + * has many additional features and is preferable if you can use C++ in + * your project. + */ + +#ifndef VP_UNIRAND_H +#define VP_UNIRAND_H + +#include +// Visual Studio 2010 or previous is missing inttypes.h +#if defined(_MSC_VER) && (_MSC_VER < 1700) +typedef unsigned __int64 uint64_t; +typedef unsigned __int32 uint32_t; +#else +#include +#endif + +#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_11) +#include // std::shuffle +#include // std::mt19937 +#include // std::iota +#else +#include // std::time +#include // std::rand, std::srand +#include // std::random_shuffle +#endif + +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpUniRand + + \ingroup group_core_random + \brief Class for generating random numbers with uniform probability density. + + The algorithms and notations used are described in \cite oneill:pcg2014. + + The following example also available in random.cpp shows how to use this class to generate 10 numbers between 0 and 5. + \include random.cpp + + Once build, this previous code should produces an output similar to the following: + \code + 1 + 0.0582619 + 1 + 5.84875 + 1 + 3.86449 + 1 + 0.216396 + 5 + 5.41692 + 1 + 1.65448 + 0 + 3.31304 + 4 + 2.70563 + 0 + 4.86741 + 2 + 5.65826 + Original vector = [ 0 1 2 3 4 5 6 7 8 9 ] + Shuffled vector = [ 2 4 7 8 5 1 3 6 9 0 ] + \endcode +*/ +class VISP_EXPORT vpUniRand +{ +public: + vpUniRand(); + vpUniRand(uint64_t seed, uint64_t seq = 0x123465789ULL); + + double operator()(); + + uint32_t next(); + int uniform(int a, int b); + float uniform(float a, float b); + double uniform(double a, double b); + void setSeed(uint64_t initstate, uint64_t initseq); + + + std::vector sampleWithoutReplacement(size_t count, size_t vectorSize); + + /** + * @brief Create a new vector that is a shuffled version of the \b inputVector. + * + * @tparam T : A class that possesses a copy constructor. + * @param inputVector : The input vector that must be shuffled. It will not be modified. + * @param seed : The seed value. + * @return std::vector A vector containing the same objects than \b inputVector, but that are shuffled. + */ + template + inline static std::vector shuffleVector(const std::vector &inputVector, const int32_t &seed = -1) + { + std::vector shuffled = inputVector; +#if (VISP_CXX_STANDARD <= VISP_CXX_STANDARD_11) + if (seed > 0) { + std::srand(seed); + } + else { + std::srand(std::time(0)); + } + std::random_shuffle(shuffled.begin(), shuffled.end()); +#else + if (seed < 0) { + std::shuffle(shuffled.begin(), shuffled.end(), std::mt19937 { std::random_device{}() }); + } + else { + std::shuffle(shuffled.begin(), shuffled.end(), std::mt19937 { static_cast(seed) }); + } +#endif + return shuffled; + } + +private: + struct vpPcgStateSetSeq64t + { // Internals are *Private*. + uint64_t state; // RNG state. All values are possible. + uint64_t inc; // Controls which RNG sequence (stream) is + // selected. Must *always* be odd. + + vpPcgStateSetSeq64t(uint64_t state_ = 0x853c49e6748fea9bULL, uint64_t inc_ = 0xda3e39cb94b95bdbULL) + : state(state_), inc(inc_) + { } + }; + typedef struct vpPcgStateSetSeq64t pcg32_random_t; + +private: + uint32_t boundedRand(uint32_t bound); + + double m_maxInvDbl; + float m_maxInvFlt; + pcg32_random_t m_rng; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpUnscentedKalman.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpUnscentedKalman.h new file mode 100644 index 00000000..2528c855 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpUnscentedKalman.h @@ -0,0 +1,480 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Display a point cloud using PCL library. + */ + +#ifndef VP_UNSCENTED_KALMAN_H +#define VP_UNSCENTED_KALMAN_H + +#include + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) +#include +#include +#include + +#include // std::function +#include // std::shared_ptr + +BEGIN_VISP_NAMESPACE +/*! + \class vpUnscentedKalman + \ingroup group_core_kalman + This class permits to use Unscented Kalman Filter (UKF) to tackle non-linear problems. Non-linearity + can arise in the process function \f$ f: {R}^n \rightarrow {R}^n \f$, which makes evolve the internal + state \f$ \textbf{x} \in {R}^n \f$ of the UKF over time, or in the measurement function \f$ h: {R}^n \rightarrow {R}^m \f$, + which expresses the internal state of the UKF in the measurement space of dimension \f$ m \f$. + + We will briefly explain the principles of the UKF and the maths behind the wheel. We refer the interested + readers to the [web-book](https://github.com/rlabbe/Kalman-and-Bayesian-Filters-in-Python) by R. Labbe, chapter 10, + for more details. + + The UKF is performed in two steps. First, the prediction step, during which we draw the sigma points \f$ \chi \f$ and compute + their corresponding weights \f$ \textbf{w}^m \in {R}^{2n + 1} \f$ and \f$ \textbf{w}^c \in {R}^{2n + 1} \f$. + Be \f$ \textbf{x} \in {R}^n \f$ the internal state of the UKF and \f$ \textbf{P} \in {R}^{n\text{ x }n} \f$ the process covariance matrix. + We have: + + \f[ + \begin{array}{lcl} + \chi &=& sigma-function(\textbf{x}, \textbf{P}) \\ + \textbf{w}^m, \textbf{w}^c &=& weight-function(n, parameters) + \end{array} + \f] + + There are different ways of drawing the sigma points and associated weights in the litterature, such as the one + proposed by Julier or the one proposed by E. A. Wan and R. van der Merwe. + + Be \f$ \textbf{u} \f$ the vector containing the known commands sent to the system, if any. Then, we pass each sigma + point through the process function \f$ f(\chi, \Delta t) \f$, the command function + \f$ b( \textbf{u}, \Delta t ) \f$ and the command function depending on the state \f$ bx( \textbf{u}, \chi, \Delta t ) \f$ + to project them forward in time, forming the new prior: + + \f$ {Y} = f( \chi , \Delta t ) + b( \textbf{u}, \Delta t ) + bx( \textbf{u}, \chi, \Delta t ) \f$ + + Then, we apply the Unscented Transform to compute the mean \f$ \boldsymbol{\mu} \f$ + and covariance \f$ \overline{\textbf{P}} \f$ of the prior: + + \f[ + \begin{array}{lcl} + \boldsymbol{\mu}, \overline{\textbf{P}} &=& UT({Y}, \textbf{w}^m, \textbf{w}^c, \textbf{Q}) \\ + \boldsymbol{\mu} &=& \sum_{i=0}^{2n} w_i^m {Y}_i \\ + \overline{\textbf{P}} &=& \sum_{i=0}^{2n} ( w_i^c ({Y}_i - \boldsymbol{\mu}) ({Y}_i - \boldsymbol{\mu})^T ) + \textbf{Q} + \end{array} + \f] + + where \f$ \textbf{Q} \f$ is the covariance of the error introduced by the process function. + + The second step is the update step. It is performed in the measurement space, so we must convert the sigma points of + the prior into measurements using the measurement function \f$ h: {R}^n \rightarrow {R}^m \f$: + + \f$ {Z} = h({Y}) \f$ + + Then, we use once again the Unscented Transform to compute the mean \f$ \boldsymbol{\mu}_z \in {R}^m \f$ and the + covariance \f$ \textbf{P}_z \in {R}^{m\text{ x }m} \f$ of these points: + + \f[ + \begin{array}{lcl} + \boldsymbol{\mu}_z, \textbf{P}_z &=& UT({Z}, \textbf{w}^m, \textbf{w}^c, \textbf{R}) \\ + \boldsymbol{\mu}_z &=& \sum_{i=0}^{2n} w_i^m {Z}_i \\ + \textbf{P}_z &=& \sum_{i=0}^{2n} ( w_i^c ({Z}_i - \boldsymbol{\mu}_z) ({Z}_i - \boldsymbol{\mu}_z)^T ) + \textbf{R} + \end{array} + \f] + + where \f$ \textbf{R} \f$ is the measurement covariance matrix. + + Then, we compute the residual \f$ \textbf{y} \f$ of the measurement \f$ \textbf{z} \f$: + + \f$ \textbf{y} = \textbf{z} - \boldsymbol{\mu}_z \f$ + + To compute the Kalman's gain, we first need to compute the cross covariance of the state and the measurements: + + \f$ \textbf{P}_{xy} = \sum_{i=0}^{2n} w_i^c ({Y}_i - \boldsymbol{\mu})({Z}_i - \boldsymbol{\mu}_z)^T \f$ + + The Kalman's gain is then defined as: + + \f$ \textbf{K} = \textbf{P}_{xz} \textbf{P}_z^{-1} \f$ + + Finally, we can compute the new state estimate \f$ \textbf{x} \f$ and the new covariance \f$ \textbf{P} \f$: + + \f[ + \begin{array}{lcl} + \textbf{x} &=& \boldsymbol{\mu} + \textbf{K} \textbf{y} \\ + \textbf{P} &=& \overline{\textbf{P}} - \textbf{K} \textbf{P}_z \textbf{K}^T + \end{array} + \f] + + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in Kalman filtering, you may have a look at:
+ + - \ref tutorial-ukf + +*/ +class VISP_EXPORT vpUnscentedKalman +{ +public: + /** + * \brief Command model function, which projects effect of the command + * on the state. + * The first argument is the command(s), the second is the period and the return is the + * effect of the command on the state after period seconds. + */ + typedef std::function vpCommandOnlyFunction; + + /** + * \brief Command model function, which projects effect of the command + * on the state, when the effect of the command depends on the current state. + * The first argument is the command(s), the second is the state and the third is the period. + * The return is the effect of the command on the state after period seconds. + */ + typedef std::function vpCommandStateFunction; + + /** + * \brief Mean function, which computes the weighted mean of either the prior or the + * prior expressed in the measurement space. + * The first argument is either the prior or the prior expressed in the measurement space + * and the second argument is the associated vector of weights. The return is the + * corresponding mean. + */ + typedef std::function &, const std::vector &)> vpMeanFunction; + + /** + * \brief Measurement function, which converts the prior points in the measurement space. + * The argument is a point of a prior point and the return is its projection in the measurement + * space. + */ + typedef std::function vpMeasurementFunction; + + /** + * \brief Process model function, which projects the sigma points forward in time. + * The first argument is a sigma point, the second is the period and the return is the + * sigma point projected in the future (i.e. a point of the prior). + */ + typedef std::function vpProcessFunction; + + /** + * \brief Function that computes either the equivalent of an addition or the equivalent + * of a subtraction in the state space or in the measurement space. + * The first argument is the vector to which we must add/subtract something + * and the second argument is the thing to be added/subtracted. The return is the + * result of this operation. + */ + typedef std::function vpAddSubFunction; + + /** + * \brief Construct a new vpUnscentedKalman object. + * + * \param[in] Q The covariance introduced by performing the prediction step. + * \param[in] R The covariance introduced by performing the update step. + * \param[in] drawer Object that permits to draw the sigma points. + * \param[in] f Process model function, which projects the sigma points forward in time. + * The first argument is a sigma point, the second is the period and the return is the + * sigma point projected in the future (i.e. a point of the prior). + * \param[in] h Measurement function, which converts the prior points in the measurement space. + * The argument is a point of a prior point and the return is its projection in the measurement + * space. + */ + vpUnscentedKalman(const vpMatrix &Q, const vpMatrix &R, std::shared_ptr &drawer, const vpProcessFunction &f, const vpMeasurementFunction &h); + + /** + * \brief Set the guess of the initial state and covariance. + * + * \param[in] mu0 Guess of the initial state. + * \param[in] P0 Guess of the initial covariance. + */ + void init(const vpColVector &mu0, const vpMatrix &P0); + + /** + * \brief Set the command function to use when computing the prior. + * + * \param b The command function to use. + */ + inline void setCommandOnlyFunction(const vpCommandOnlyFunction &b) + { + m_b = b; + } + + /** + * \brief Set the command function to use when computing the prior. + * + * \param bx The command function to use. + */ + inline void setCommandStateFunction(const vpCommandStateFunction &bx) + { + m_bx = bx; + } + + /** + * \brief Set the measurement mean function to use when computing a mean + * in the measurement space. + * + * \param meanFunc The mean function to use. + */ + inline void setMeasurementMeanFunction(const vpMeanFunction &meanFunc) + { + m_measMeanFunc = meanFunc; + } + + /** + * \brief Set the measurement residual function to use when computing a subtraction + * in the measurement space. + * + * \param measResFunc The residual function to use. + */ + inline void setMeasurementResidualFunction(const vpAddSubFunction &measResFunc) + { + m_measResFunc = measResFunc; + } + + /** + * \brief Set the state addition function to use when computing a addition + * in the state space. + * + * \param stateAddFunc The addition function to use. + */ + inline void setStateAddFunction(const vpAddSubFunction &stateAddFunc) + { + m_stateAddFunction = stateAddFunc; + } + + /** + * \brief Set the state mean function to use when computing a mean + * in the state space. + * + * \param meanFunc The mean function to use. + */ + inline void setStateMeanFunction(const vpMeanFunction &meanFunc) + { + m_stateMeanFunc = meanFunc; + } + + /** + * \brief Set the state residual function to use when computing a subtraction + * in the state space. + * + * \param stateResFunc The residual function to use. + */ + inline void setStateResidualFunction(const vpAddSubFunction &stateResFunc) + { + m_stateResFunc = stateResFunc; + } + + /** + * \brief Permit to change the covariance introduced at each prediction step. + * + * \param[in] Q The process covariance matrix. + */ + inline void setProcessCovariance(const vpMatrix &Q) + { + m_Q = Q; + } + + /** + * \brief Permit to change the covariance introduced at each update step. + * + * \param[in] R The measurement covariance matrix. + */ + inline void setMeasurementCovariance(const vpMatrix &R) + { + m_R = R; + } + + /** + * \brief Perform first the prediction step and then the filtering step. + * + * \param[in] z The new measurement. + * \param[in] dt The time in the future we must predict. + * \param[in] u The command(s) given to the system, if the impact of the system is known. + * + * \warning To use the commands, the method vpUnscentedKalman::setCommandOnlyFunction or + * vpUnscentedKalman::setCommandStateFunction must be called beforehand. + */ + void filter(const vpColVector &z, const double &dt, const vpColVector &u = vpColVector()); + + /** + * \brief Predict the new state based on the last state and how far in time we want to predict. + * + * \param[in] dt The time in the future we must predict. + * \param[in] u The command(s) given to the system, if the impact of the system is known. + * + * \warning To use the commands, the method vpUnscentedKalman::setCommandOnlyFunction or + * vpUnscentedKalman::setCommandStateFunction must be called beforehand. + */ + void predict(const double &dt, const vpColVector &u = vpColVector()); + + /** + * \brief Update the estimate of the state based on a new measurement. + * + * \param[in] z The measurements at the current timestep. + */ + void update(const vpColVector &z); + + /** + * \brief Get the estimated (i.e. filtered) covariance of the state. + * + * \return vpMatrix The filtered covariance matrix. + */ + inline vpMatrix getPest() const + { + return m_Pest; + } + + /** + * \brief Get the predicted covariance of the state, i.e. the covariance of the prior. + * + * \return vpMatrix The predicted covariance matrix. + */ + inline vpMatrix getPpred() const + { + return m_Ppred; + } + + /** + * \brief Get the estimated (i.e. filtered) state. + * + * \return vpColVector The estimated state. + */ + inline vpColVector getXest() const + { + return m_Xest; + } + + /** + * \brief Get the predicted state (i.e. the prior). + * + * \return vpColVector The predicted state. + */ + inline vpColVector getXpred() const + { + return m_mu; + } + + /** + * \brief Simple function to compute an addition, which just does \f$ \textbf{res} = \textbf{a} + \textbf{toAdd} \f$ + * + * \param[in] a Vector to which we must add something. + * \param[in] toAdd The something we must add to \b a . + * \return vpColVector \f$ \textbf{res} = \textbf{a} + \textbf{toAdd} \f$ + */ + inline static vpColVector simpleAdd(const vpColVector &a, const vpColVector &toAdd) + { + vpColVector res = a + toAdd; + return res; + } + + /** + * \brief Simple function to compute a residual, which just does \f$ \textbf{res} = \textbf{a} - \textbf{toSubtract} \f$ + * + * \param[in] a Vector to which we must subtract something. + * \param[in] toSubtract The something we must subtract to \b a . + * \return vpColVector \f$ \textbf{res} = \textbf{a} - \textbf{toSubtract} \f$ + */ + inline static vpColVector simpleResidual(const vpColVector &a, const vpColVector &toSubtract) + { + vpColVector res = a - toSubtract; + return res; + } + + /** + * \brief Simple function to compute a mean, which just does \f$ \boldsymbol{\mu} = \sum_{i} wm_i \textbf{vals}_i \f$ + * + * \param[in] vals Vector containing all the vectors we must compute the mean. + * \param[in] wm The correspond list of weights. + * \return vpColVector \f$ \boldsymbol{\mu} = \sum_{i} wm_i \textbf{vals}_i \f$ + */ + inline static vpColVector simpleMean(const std::vector &vals, const std::vector &wm) + { + size_t nbPoints = vals.size(); + if (nbPoints == 0) { + throw(vpException(vpException::dimensionError, "No points to add when computing the mean")); + } + vpColVector mean = vals[0] * wm[0]; + for (size_t i = 1; i < nbPoints; ++i) { + mean += vals[i] * wm[i]; + } + return mean; + } +private: + bool m_hasUpdateBeenCalled; /*!< Set to true when update is called, reset at the beginning of predict.*/ + vpColVector m_Xest; /*!< The estimated (i.e. filtered) state variables.*/ + vpMatrix m_Pest; /*!< The estimated (i.e. filtered) covariance matrix.*/ + vpMatrix m_Q; /*!< The covariance introduced by performing the prediction step.*/ + std::vector m_chi; /*!< The sigma points.*/ + std::vector m_wm; /*!< The weights for the mean computation.*/ + std::vector m_wc; /*!< The weights for the covariance computation.*/ + std::vector m_Y; /*!< The projection forward in time of the sigma points according to the process model, called the prior.*/ + vpColVector m_mu; /*!< The mean of the prior.*/ + vpMatrix m_Ppred; /*!< The covariance matrix of the prior.*/ + vpMatrix m_R; /*!< The covariance introduced by performing the update step.*/ + std::vector m_Z; /*!< The sigma points of the prior expressed in the measurement space, called the measurement sigma points.*/ + vpColVector m_muz; /*!< The mean of the measurement sigma points.*/ + vpMatrix m_Pz; /*!< The covariance matrix of the measurement sigma points.*/ + vpMatrix m_Pxz; /*!< The cross variance of the state and the measurements.*/ + vpColVector m_y; /*!< The residual.*/ + vpMatrix m_K; /*!< The Kalman gain.*/ + vpProcessFunction m_f; /*!< Process model function, which projects the sigma points forward in time.*/ + vpMeasurementFunction m_h; /*!< Measurement function, which converts the sigma points in the measurement space.*/ + std::shared_ptr m_sigmaDrawer; /*!< Object that permits to draw the sigma points.*/ + vpCommandOnlyFunction m_b; /*!< Function that permits to compute the effect of the commands on the prior, without knowledge of the state.*/ + vpCommandStateFunction m_bx; /*!< Function that permits to compute the effect of the commands on the prior, with knowledge of the state.*/ + vpMeanFunction m_measMeanFunc; /*!< Function to compute a weighted mean in the measurement space.*/ + vpAddSubFunction m_measResFunc; /*!< Function to compute a subtraction in the measurement space.*/ + vpAddSubFunction m_stateAddFunction; /*!< Function to compute an addition in the state space.*/ + vpMeanFunction m_stateMeanFunc; /*!< Function to compute a weighted mean in the state space.*/ + vpAddSubFunction m_stateResFunc; /*!< Function to compute a subtraction in the state space.*/ + + /** + * \brief Structure that stores the results of the unscented transform. + * + */ + typedef struct vpUnscentedTransformResult + { + vpColVector m_mu; /*!< The mean.*/ + vpMatrix m_P; /*!< The covariance matrix.*/ + } vpUnscentedTransformResult; + + /** + * \brief Compute the unscented transform of the sigma points. + * + * \param[in] sigmaPoints The sigma points we consider. + * \param[in] wm The weights to apply for the mean computation. + * \param[in] wc The weights to apply for the covariance computation. + * \param[in] cov The constant covariance matrix to add to the computed covariance matrix. + * \return vpUnscentedTransformResult The mean and covariance of the sigma points. + */ + static vpUnscentedTransformResult unscentedTransform(const std::vector &sigmaPoints, const std::vector &wm, + const std::vector &wc, const vpMatrix &cov, const vpAddSubFunction &resFunc, const vpMeanFunction &meanFunc); +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpVelocityTwistMatrix.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpVelocityTwistMatrix.h new file mode 100644 index 00000000..55412fc3 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpVelocityTwistMatrix.h @@ -0,0 +1,251 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Velocity twist transformation matrix. + */ + +#ifndef VP_VELOCITY_TWIST_MATRIX_H +#define VP_VELOCITY_TWIST_MATRIX_H + +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +class vpColVector; +class vpHomogeneousMatrix; +class vpMatrix; + +/*! + \class vpVelocityTwistMatrix + + \ingroup group_core_transformations + + This class derived from vpArray2D implements the 6 by 6 matrix which + transforms velocities from one frame to another. This matrix is also called + velocity twist transformation matrix. + + The full velocity twist transformation matrix allows to compute the velocity + at point a expressed in frame a knowing its velocity at + point b expressed in frame b. This matrix is defined as: \f[ + ^a{\bf V}_b = \left[\begin{array}{cc} + ^a{\bf R}_b & [^a{\bf t}_b]_\times \; ^a{\bf R}_b\\ + {\bf 0}_{3\times 3} & ^a{\bf R}_b + \end{array} + \right] + \f] + + where \f$ ^a{\bf R}_b \f$ is a rotation matrix and + \f$ ^a{\bf t}_b \f$ is a translation vector. + + There are different ways to initialize such a full velocity twist matrix. The following + example shows how to proceed setting the translation and rotation matrix transformations: + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpTranslationVector cte(0.1, 0.2, 0.3); + vpRotationMatrix cRe( {0, 0, -1, + 0, -1, 0, + -1, 0, 0} ); + + vpVelocityTwistMatrix cVe(cte, cRe); + std::cout << "cVe:\n" << cVe << std::endl; + } + \endcode + It produces the following printings: + \code + cVe: + 0 0 -1 -0.2 0.3 0 + 0 -1 0 0.1 0 -0.3 + -1 0 0 0 -0.1 0.2 + 0 0 0 0 0 -1 + 0 0 0 0 -1 0 + 0 0 0 -1 0 0 + \endcode + + When the point where the velocity is expressed doesn't change, the matrix + becomes block diagonal. It allows than to compute the velocity at point + b expressed in frame a knowing its + velocity at point b expressed in frame b : + \f[ + ^a{\bf V}_b = \left[\begin{array}{cc} + ^a{\bf R}_b & {\bf 0}_{3\times 3} \\ + {\bf 0}_{3\times 3} & ^a{\bf R}_b + \end{array} + \right] + \f] + + To initialize such a velocity twist matrix where translation is not taken into account you + can proceed like in the following code: + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpRotationMatrix cRe( {0, 0, -1, + 0, -1, 0, + -1, 0, 0} ); + + vpVelocityTwistMatrix cVe(cRe); + std::cout << "cVe:\n" << cVe << std::endl; + } + \endcode + It produces the following printings: + \code + cVe: + 0 0 -1 0 0 0 + 0 -1 0 0 0 0 + -1 0 0 0 0 0 + 0 0 0 0 0 -1 + 0 0 0 0 -1 0 + 0 0 0 -1 0 0 + \endcode + + The code below shows how to convert a velocity skew + expressed at the origin of the camera frame into the origin of the fix frame + using the full velocity twist matrix. + + \code + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpVelocityTwistMatrix fVc; // Twist transformation matrix from fix to camera frame + + vpHomogeneousMatrix fMc; // Fix to camera frame transformation + // ... fMc need here to be initialized + + fVc.buildFrom(fMc); + + vpColVector c_v(6); // Velocity in the camera frame: vx,vy,vz,wx,wy,wz + // ... c_v should here have an initial value + + vpColVector f_v(6); // Velocity in the fix frame: vx,vy,vz,wx,wy,wz + + // Compute the velocity in the fix frame + f_v = fVc * c_v; + } + \endcode +*/ +class VISP_EXPORT vpVelocityTwistMatrix : public vpArray2D +{ + friend class vpMatrix; + +public: + // basic constructor + vpVelocityTwistMatrix(); + // copy constructor + vpVelocityTwistMatrix(const vpVelocityTwistMatrix &V); + // constructor from an homogeneous transformation + VP_EXPLICIT vpVelocityTwistMatrix(const vpHomogeneousMatrix &M, bool full = true); + + // Construction from Translation and rotation (matrix parameterization) + vpVelocityTwistMatrix(const vpTranslationVector &t, const vpRotationMatrix &R); + // Construction from Translation and rotation (ThetaU parameterization) + vpVelocityTwistMatrix(const vpTranslationVector &t, const vpThetaUVector &thetau); + vpVelocityTwistMatrix(double tx, double ty, double tz, double tux, double tuy, double tuz); + + VP_EXPLICIT vpVelocityTwistMatrix(const vpRotationMatrix &R); + VP_EXPLICIT vpVelocityTwistMatrix(const vpThetaUVector &thetau); + + vpVelocityTwistMatrix &buildFrom(const vpTranslationVector &t, const vpRotationMatrix &R); + vpVelocityTwistMatrix &buildFrom(const vpTranslationVector &t, const vpThetaUVector &thetau); + vpVelocityTwistMatrix &buildFrom(const vpHomogeneousMatrix &M, bool full = true); + vpVelocityTwistMatrix &buildFrom(const vpRotationMatrix &R); + vpVelocityTwistMatrix &buildFrom(const vpThetaUVector &thetau); + + void extract(vpRotationMatrix &R) const; + void extract(vpTranslationVector &t) const; + + // Basic initialisation (identity) + void eye(); + + vpVelocityTwistMatrix inverse() const; + void inverse(vpVelocityTwistMatrix &V) const; + + vpVelocityTwistMatrix operator*(const vpVelocityTwistMatrix &V) const; + vpMatrix operator*(const vpMatrix &M) const; + vpColVector operator*(const vpColVector &v) const; + + vpVelocityTwistMatrix &operator=(const vpVelocityTwistMatrix &V); + + int print(std::ostream &s, unsigned int length, char const *intro = nullptr) const; + + /*! + This function is not applicable to a velocity twist matrix that is always + a 6-by-6 matrix. \exception vpException::fatalError When this function is + called. + */ + VP_NORETURN void resize(unsigned int nrows, unsigned int ncols, bool flagNullify = true) + { + (void)nrows; + (void)ncols; + (void)flagNullify; + throw(vpException(vpException::fatalError, "Cannot resize a velocity twist matrix")); + } + +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) + /*! + @name Deprecated functions + */ + //@{ + /*! + \deprecated Provided only for compat with previous releases. + This function does nothing. + */ + VP_DEPRECATED void init() { } + /*! + \deprecated You should rather use eye(). + */ + VP_DEPRECATED void setIdentity(); +//@} +#endif +private: + static const unsigned constr_val_6; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpXmlParser.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpXmlParser.h new file mode 100644 index 00000000..a346013a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpXmlParser.h @@ -0,0 +1,323 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Tools to automatize the creation of xml parser based on the libXML2 + */ + +/*! + \file vpXmlParser.h + \brief Tools to simplify the creation of xml parser based on the libXML2 +*/ + +#ifndef VP_XML_PARSER_H +#define VP_XML_PARSER_H + +#include + +#ifdef VISP_HAVE_XML2 + +#include + +#include +#include +#include +#include +#include +#include + +struct _xmlDoc; +typedef _xmlDoc xmlDoc; +typedef xmlDoc *xmlDocPtr; + +struct _xmlNode; +typedef _xmlNode xmlNode; +typedef xmlNode *xmlNodePtr; + +BEGIN_VISP_NAMESPACE +/*! + \class vpXmlParser + + This class intends to simplify the creation of xml parser based on + the libxml2 third party library. + + This class can be useful to manage external data parameters (for example for + configuration of an experiment, ...). + + \warning This class is only available if libxml2 is installed and detected + by ViSP. Installation instructions are provided here + https://visp.inria.fr/3rd_xml2. + + \note After ViSP 3.2.0 release we introduce pugixml built-in 3rd party library + that replaces favorably libxml2. For example vpXmlParserCamera uses now pugixml instead + of inheriting from vpXmlParser. + + In order to use this class, you have to create a new class inheriting from + this one. In the child class, you have to implement the methods: + - writeMainClass() + - readMainClass() + + These two methods depends on the data to parse, and must not be directly + called (they are called from the parse() and the save() methods). + + Following is an example of implementation for the document: + + \code + + 5 + 7 + 3 + + \endcode + + A class to parse this document is declared as follows: + + \code + #include + + class vpDataParser: public vpXmlParser + { + private: + int m_range; + int m_step; + int m_size_filter + public: + typedef enum{ + config, + range, + step, + size_filter + } dataToParse + + vpDataParser(){ + nodeMap["config"] = config; + nodeMap["range"] = range; + nodeMap["step"] = step; + nodeMap["size_filter"] = size_filter; + } + + virtual void writeMainClass(xmlNodePtr node); + virtual void readMainClass(xmlDocPtr doc, xmlNodePtr node); + + // additional methods specific to the data to parse such as: accessors + } + \endcode + + The readMainClass function implementation is: + + \code + void + vpDataParser::readMainClass(xmlDocPtr doc, xmlNodePtr node) + { + for (xmlNodePtr tmpNode = node->xmlChildrenNode; tmpNode != nullptr; tmpNode = tmpNode->next) { + if(tmpNode->type == XML_ELEMENT_NODE) { + + std::map::iterator iter = this->nodeMap.find((char*)tmpNode->name); + if(iter == nodeMap.end()) { + continue; + } + + switch (iter->second){ + case range: + this->m_range = xmlReadIntChild(doc, tmpNode); + break; + case step: + this->m_step = xmlReadIntChild(doc, tmpNode); + break; + case size_filter: + this->m_size_filter = xmlReadIntChild(doc, tmpNode); + break; + default: + std::cout << "problem in the readMainClass (" << iter->second + << " , " << iter->first << " )" << std::endl; break; + } + } + } + } + \endcode + + Data can now be accessed through the internal variables of the class + vpDataParser. + + To store the data in a xml file, the function save has to be called. This + function needs the implementation of the writeMainClass function. + + For example, + + \code + void + vpDataParser::writeMainClass(xmlNodePtr node) + { + xmlWriteIntChild(node, "range", m_range); + xmlWriteIntChild(node, "step", m_step); + xmlWriteIntChild(node, "size_filter", m_size_filter); + } + \endcode + +*/ +class VISP_EXPORT vpXmlParser +{ +protected: + /** @name Protected Member Functions Inherited from vpXmlParser */ + //@{ + /*! + pure virtual method used to read the document. + + As the content of the function depends on the structure of the file to + read, data name, data types and data values, it has to be reimplemented + for every type of filename. + + \param doc : a pointer representing the document + \param node : the root node of the document + */ + virtual void readMainClass(xmlDocPtr doc, xmlNodePtr node) = 0; + + /*! + pure virtual method used to write the document. + + As the content of the function depends on the structure of the file to + read, data name and data types, it has to be reimplemented for every type + of file to parse. + + \param node : the root node of the document + */ + virtual void writeMainClass(xmlNodePtr node) = 0; + + bool xmlReadBoolChild(xmlDocPtr doc, xmlNodePtr node); + char *xmlReadCharChild(xmlDocPtr doc, xmlNodePtr node); + double xmlReadDoubleChild(xmlDocPtr doc, xmlNodePtr node); + float xmlReadFloatChild(xmlDocPtr doc, xmlNodePtr node); + int xmlReadIntChild(xmlDocPtr doc, xmlNodePtr node); + std::string xmlReadStringChild(xmlDocPtr doc, xmlNodePtr node); + unsigned int xmlReadUnsignedIntChild(xmlDocPtr doc, xmlNodePtr node); + + void xmlWriteBoolChild(xmlNodePtr node, const char *label, bool value); + void xmlWriteCharChild(xmlNodePtr node, const char *label, const char *value); + void xmlWriteDoubleChild(xmlNodePtr node, const char *label, double value); + void xmlWriteFloatChild(xmlNodePtr node, const char *label, float value); + void xmlWriteIntChild(xmlNodePtr node, const char *label, int value); + void xmlWriteStringChild(xmlNodePtr node, const char *label, const std::string &value); + void xmlWriteUnsignedIntChild(xmlNodePtr node, const char *label, unsigned int value); + //@} + +protected: + /*! + The map describing the data to parse + */ + std::map nodeMap; + + /*! + The name of the main tag for the file to parse + */ + std::string main_tag; + +public: + /** @name Public Member Functions Inherited from vpXmlParser */ + //@{ + vpXmlParser(); + vpXmlParser(const vpXmlParser &_twin); + virtual ~vpXmlParser(); + + /* virtual */ void parse(const std::string &filename); + /* virtual */ void save(const std::string &filename, bool append = false); + + /*! + Set the map describing the data to parse. This map stores the name of each + node and an associated key used to simplify the parsing of the file. + + If the following file want to be parsed: + + \code + + 5 + 7 + 3 + + \endcode + + The following map has to be declared: + + \code + std::map dataToParse; + dataToParse["config"] = 0; + dataToParse["range"] = 1; + dataToParse["step"] = 2; + dataToParse["size_filter"] = 3; + \endcode + + Or, you can use keyword instead of number as key but it implies to declare + in the child class an enumeration type of the name. For example: + + \code + typedef enum{ + config, + range, + step, + size_filter} data_enum; + + std::map dataToParse; + dataToParse["config"] = config; + dataToParse["range"] = range; + dataToParse["step"] = step; + dataToParse["size_filter"] = size_filter; + \endcode + + \param _map : the map describing the data to parse + */ + void setMap(const std::map &_map) { nodeMap = _map; } + + /*! + set the name of the main tag + + The main tag corresponds to the name of the root node + + \param tag : name of the root node of the document + */ + inline void setMainTag(const std::string &tag) { main_tag = tag; } + //@} + + /** @name Static Public Member Functions Inherited from vpXmlParser */ + //@{ + /*! + As stated in http://xmlsoft.org/html/libxml-parser.html#xmlCleanupParser + to clean up memory allocated by the xml2 library itself, the user should + call xmlCleanupParser() only when the process has finished using the xml2 + library. In case of doubt abstain from calling this function or do it just + before calling exit() to avoid leak reports from valgrind ! That's why in + ViSP the destructor doesn't call xmlCleanupParser(). Rather we provide the + static function vpXmlParser::cleanup() that calls xmlCleanupParser() that + could be called just before exit(). + */ + static void cleanup(); + //@} +}; +END_VISP_NAMESPACE +#endif /* VISP_HAVE_XML2 */ + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpXmlParserCamera.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpXmlParserCamera.h new file mode 100644 index 00000000..cd3f273d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpXmlParserCamera.h @@ -0,0 +1,212 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * XML parser to load and save camera intrinsic parameters. + */ + +/*! + \file vpXmlParserCamera.h + \brief Declaration of the vpXmlParserCamera class. + Class vpXmlParserCamera allowed to load and save intrinsic camera parameters + +*/ + +#ifndef VP_XML_PARSER_CAMERA_H +#define VP_XML_PARSER_CAMERA_H + +#include + +#if defined(VISP_HAVE_PUGIXML) +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpXmlParserCamera + + \ingroup group_core_camera + + \brief XML parser to load and save intrinsic camera parameters. + + To have a complete description of the camera parameters and the + corresponding projection model implemented in ViSP, see + vpCameraParameters. + + Example of an XML file "myXmlFile.xml" containing intrinsic camera + parameters: + + \code + + + + myCamera + 640 + 480 + + perspectiveProjWithoutDistortion + 1129.0 + 1130.6 + 317.9 + 229.1 + + + perspectiveProjWithDistortion + 1089.9 + 1090.1 + 326.1 + 230.5 + -0.196 + 0.204 + + + + \endcode + + Example of loading existing camera parameters from an XML file: + \code + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpCameraParameters cam; // Create a camera parameter container + vpXmlParserCamera p; // Create a XML parser + vpCameraParameters::vpCameraParametersProjType projModel; // Projection model + // Use a perspective projection model without distortion + projModel = vpCameraParameters::perspectiveProjWithoutDistortion; + // Parse the xml file "myXmlFile.xml" to find the intrinsic camera + // parameters of the camera named "myCamera" for the image sizes 640x480, + // for the projection model projModel. The size of the image is optional + // if camera parameters are given only for one image size. + if (p.parse(cam, "myXmlFile.xml", "myCamera", projModel,640,480) != vpXmlParserCamera::SEQUENCE_OK) { + std::cout << "Cannot found myCamera" << std::endl; + } + + // cout the parameters + cam.printParameters(); + + // Get the camera parameters for the model without distortion + double px = cam.get_px(); + double py = cam.get_py(); + double u0 = cam.get_u0(); + double v0 = cam.get_v0(); + + // Now we modify the principal point (u0,v0) for example to add noise + u0 *= 0.9; + v0 *= 0.8; + + // Set the new camera parameters + cam.initPersProjWithoutDistortion(px, py, u0, v0); + + // Save the parameters in a new file "myXmlFileWithNoise.xml" + p.save(cam,"myXmlFileWithNoise.xml",p.getCameraName(),p.getWidth(),p.getHeight()); + } + \endcode + + Example of writing an XML file containing intrinsic camera parameters: + \code + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + // Create a camera parameter container. We want to set these parameters + // for a 320x240 image, and we want to use the perspective projection + // modelization without distortion. + vpCameraParameters cam; + + // Set the principal point coordinates (u0,v0) + double u0 = 162.3; + double v0 = 122.4; + // Set the pixel ratio (px, py) + double px = 563.2; + double py = 564.1; + + // Set the camera parameters for a model without distortion + cam.initPersProjWithoutDistortion(px, py, u0, v0); + + // Create a XML parser + vpXmlParserCamera p; + // Save the camera parameters in an XML file. + if (p.save(cam, "myNewXmlFile.xml", "myNewCamera", 320, 240) != vpXmlParserCamera::SEQUENCE_OK) { + std::cout << "Cannot save camera parameters" << std::endl; + } + } + \endcode +*/ + +class VISP_EXPORT vpXmlParserCamera +{ +public: + enum vpXmlCodeSequenceType { SEQUENCE_OK, SEQUENCE_ERROR }; + + vpXmlParserCamera(); + ~vpXmlParserCamera(); + + // get/set functions + std::string getCameraName() const; + vpCameraParameters getCameraParameters() const; + unsigned int getHeight() const; + unsigned int getSubsampling_width() const; + unsigned int getSubsampling_height() const; + unsigned int getWidth() const; + + int parse(vpCameraParameters &cam, const std::string &filename, const std::string &camera_name, + const vpCameraParameters::vpCameraParametersProjType &projModel, unsigned int image_width = 0, + unsigned int image_height = 0, bool verbose = true); + + int save(const vpCameraParameters &cam, const std::string &filename, const std::string &camera_name, + unsigned int image_width = 0, unsigned int image_height = 0, const std::string &additionalInfo = "", + bool verbose = true); + + void setCameraName(const std::string &name); + void setHeight(unsigned int height); + void setSubsampling_width(unsigned int subsampling); + void setSubsampling_height(unsigned int subsampling); + void setWidth(unsigned int width); + +private: + vpXmlParserCamera(const vpXmlParserCamera &c); // noncopyable + vpXmlParserCamera &operator=(const vpXmlParserCamera &c); // + + // PIMPL idiom + class Impl; + Impl *m_impl; +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpXmlParserHomogeneousMatrix.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpXmlParserHomogeneousMatrix.h new file mode 100644 index 00000000..7c52296d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpXmlParserHomogeneousMatrix.h @@ -0,0 +1,177 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * XML parser to load and save Homogeneous Matrix in a XML file. + */ + +/*! + \file vpXmlParserHomogeneousMatrix.h + \brief Declaration of the vpXmlParserHomogeneousMatrix class. + Class vpXmlParserHomogeneousMatrix allowed to load and save Homogeneous + Matrixes in a file XML + +*/ + +#ifndef VP_XML_PARSER_HOMOGENEOUS_MATRIX_H +#define VP_XML_PARSER_HOMOGENEOUS_MATRIX_H + +#include + +#if defined(VISP_HAVE_PUGIXML) +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpXmlParserHomogeneousMatrix + + \ingroup group_core_transformations + + \brief XML parser to load and save an homogeneous matrix in a file. + + \warning This class is only available if pugixml third-party is successfully +built. + + To have a complete description of the homogeneous matrix implemented in +ViSP, see vpHomogeneousMatrix. + + Example of an XML file "homogeneous_matrixes.xml" containing a Pose vector + that will be converted in an homogeneous matrix: + + \code + + + + + eMc + + + 1.00 + 1.30 + 3.50 + 0.20 0.30 + 0.50 + + + + \endcode + + Example of loading an existing homogeneous matrix from an XML file. + \code + +#include +#include + +#include + +int main(int argc, char* argv[]) +{ + vpHomogeneousMatrix eMc; + + // Create a XML parser + vpXmlParserHomogeneousMatrix p; + + // Define the name of the matrix to load + std::string name = "eMc"; + + if (p.parse(eMc,"homogeneous_matrixes.xml", name) != vpXmlParserHomogeneousMatrix::SEQUENCE_OK) { + std::cout << "Cannot found the Homogeneous matrix named " << name << "." << std::endl; + } + else + std::cout << "Homogeneous matrix " << name <<": " << std::endl << eMc << std::endl; + + return 0; +} + \endcode + + Example of writing an homogenoeus matrix in a XML file. + \note Before writing an homogeneous matrix check if there + is already in the xml file a matrix with the same name. + If you are sure to overwrite it please delete it manually + from the file before. + + \code +#include +#include + +#include + +int main(int argc, char* argv[]) +{ + // Create Pose Vector and convert to homogeneous matrix + vpPoseVector r(1.0,1.3,3.5,0.2,0.3,0.5); + vpHomogeneousMatrix M(r); + + // Create a XML parser + vpXmlParserHomogeneousMatrix p; + + // Define the name of the matrix + std::string name_M = "eMe"; + + // Define name of the file xml to fill + std::string filename = "homogeneous_matrixes.xml"; + + if (p.save(M, filename, name_M) != vpXmlParserHomogeneousMatrix::SEQUENCE_OK) { + std::cout << "Cannot save the Homogeneous matrix" << std::endl; + } + return 0; +} + \endcode +*/ + +class VISP_EXPORT vpXmlParserHomogeneousMatrix +{ +public: + typedef enum { SEQUENCE_OK, SEQUENCE_ERROR } vpXmlCodeSequenceType; + + vpXmlParserHomogeneousMatrix(); + ~vpXmlParserHomogeneousMatrix(); + + // get/set functions + vpHomogeneousMatrix getHomogeneousMatrix() const; + std::string getHomogeneousMatrixName() const; + + int parse(vpHomogeneousMatrix &M, const std::string &filename, const std::string &name); + + int save(const vpHomogeneousMatrix &M, const std::string &filename, const std::string &name); + + void setHomogeneousMatrixName(const std::string &name); + +private: + vpXmlParserHomogeneousMatrix(const vpXmlParserHomogeneousMatrix &hm); // noncopyable + vpXmlParserHomogeneousMatrix &operator=(const vpXmlParserHomogeneousMatrix &); // + + // PIMPL idiom + class Impl; + Impl *m_impl; +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpXmlParserRectOriented.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpXmlParserRectOriented.h new file mode 100644 index 00000000..7e423f04 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/core/vpXmlParserRectOriented.h @@ -0,0 +1,124 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * XML parser to load and save oriented rectangles in a XML file. + */ + +/*! + \file vpXmlParserRectOriented.h + \brief Declaration of the vpXmlParserRectOriented class. + Class vpXmlParserRectOriented allows to load and save oriented rectangles in a file XML +*/ + +#ifndef VP_XML_PARSER_RECT_ORIENTED_H +#define VP_XML_PARSER_RECT_ORIENTED_H + +#include + +#if defined(VISP_HAVE_PUGIXML) +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpXmlParserRectOriented + + \ingroup group_core_geometry + + \brief XML parser to load and save an oriented rectangle in a file. + + The following example shows how to save an oriented rectangle in an xml file: + \code + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpRectOriented rect(vpImagePoint(10, 15), 20, 12, 0.25); + vpXmlParserRectOriented parser; + parser.setRectangle(rect); + std::string filename = "myRectangle.xml"; + parser.save(filename); + return 0; + } + \endcode + + The following example shows how to read an oriented rectangle from an xml file: + \code + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpXmlParserRectOriented parser; + std::string filename = "myRectangle.xml"; + parser.parse(filename); + vpRectOriented rect = parser.getRectangle(); + return 0; + } + \endcode + + \warning This class is only available if pugixml third-party is successfully +built. +*/ + +class VISP_EXPORT vpXmlParserRectOriented +{ +public: + vpXmlParserRectOriented(); + ~vpXmlParserRectOriented(); + + enum vpXmlCodeSequenceType { SEQUENCE_OK, SEQUENCE_ERROR }; + + vpRectOriented getRectangle() const; + + void parse(const std::string &filename); + void save(const std::string &filename, bool append = false); + + void setRectangle(const vpRectOriented &rectangle); + +private: + vpXmlParserRectOriented(const vpXmlParserRectOriented &ro); // noncopyable + vpXmlParserRectOriented &operator=(const vpXmlParserRectOriented &); // + + // PIMPL idiom + class Impl; + Impl *m_impl; +}; +END_VISP_NAMESPACE +#endif +#endif // vpXmlParserRectOriented_h diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/detection/vpDetectorAprilTag.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/detection/vpDetectorAprilTag.h new file mode 100644 index 00000000..325e70b8 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/detection/vpDetectorAprilTag.h @@ -0,0 +1,617 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Base class for AprilTag detection. + */ +#ifndef VP_DETECTOR_APRILTAG_H +#define VP_DETECTOR_APRILTAG_H + +#include + +#include + +#ifdef VISP_HAVE_APRILTAG +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpDetectorAprilTag + * \ingroup group_detection_tag + * Base class for AprilTag detector. This class is a wrapper over + * AprilTag. There + * is no need to download and install AprilTag from source code or from existing + * pre-built packages since the source code is embedded in ViSP. Reference papers + * are AprilTag: A robust and flexible visual fiducial system + * (\cite olson2011tags), AprilTag 2: Efficient and robust fiducial + * detection (\cite wang2016iros) and Flexible Layouts for Fiducial Tags + * (\cite krogius2019iros). + * + * Supported tag families are the following: + * - AprilTag 16h5 + * - AprilTag 25h9 + * - AprilTag 36h10 (deprecated) + * - AprilTag 36h11 + * - AprilTag Circle_21h7 (AprilTag 3) + * - AprilTag Circle_49h12 (AprilTag 3, CMake WITH_APRILTAG_BIG_FAMILY var must be set to true) + * - AprilTag Custom_48h12 (AprilTag 3, CMake WITH_APRILTAG_BIG_FAMILY var must be set to true) + * - AprilTag Standard_41h12 (AprilTag 3, CMake WITH_APRILTAG_BIG_FAMILY var must be set to true) + * - AprilTag Standard_52h13 (AprilTag 3, CMake WITH_APRILTAG_BIG_FAMILY var must be set to true) + * - ArUco 4x4 + * - ArUco 5x5 + * - ArUco 6x6 + * - ArUco 7x7 + * - ArUco MIP_36h12 + * \image html img-apriltag-supported-tags.jpg Supported tags with id 0. + * + * To use this class you can follow \ref tutorial-detection-apriltag. + * + * The detect() function allows to detect multiple tags in an image. Once + * detected, for each tag it is possible to retrieve the location of the corners + * using getPolygon(), the encoded message using getMessage(), the bounding box + * using getBBox() and the center of gravity using getCog(). + * + * If camera parameters and the size of the tag are provided, you can also estimate + * the 3D pose of the tag in terms of position and orientation wrt the camera considering 2 cases: + * 1. If all the tags have the same size use + * detect(const vpImage &, double, const vpCameraParameters &, std::vector &, std::vector *, std::vector *, std::vector *) + * 2. If tag sizes differ, use rather getPose() + * + * \note With ViSP, the size of the tag corresponds to the black part of the tag. Note also that to be detected, + * the black part of the tag must be surrounded by a white border as wide as the black border as in the next image: + * \image html img-apriltag-size.jpg + * + * The following sample code shows how to use this class to detect the location + * of 36h11 AprilTag patterns in an image. + * \code + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * #ifdef VISP_HAVE_APRILTAG + * vpImage I; + * vpImageIo::read(I, "image-tag36h11.pgm"); + * + * vpDetectorAprilTag detector(vpDetectorAprilTag::TAG_36h11); + * + * bool status = detector.detect(I); + * if (status) { + * for(size_t i=0; i < detector.getNbObjects(); ++i) { + * std::cout << "Tag code " << i << ":" << std::endl; + * std::vector p = detector.getPolygon(i); + * for(size_t j=0; j < p.size(); ++j) + * std::cout << " Point " << j << ": " << p[j] << std::endl; + * std::cout << " Message: \"" << detector.getMessage(i) << "\"" << std::endl; + * } + * } + * #endif + * } + * \endcode + * + * The previous example may produce results like: + * \code + * Tag code 0: + * Point 0: 124.008, 442.226 + * Point 1: 194.614, 441.237 + * Point 2: 184.833, 540.386 + * Point 3: 111.948, 533.634 + * Message: "36h11 id: 0" + * Tag code 1: + * Point 0: 245.327, 438.801 + * Point 1: 338.116, 437.221 + * Point 2: 339.341, 553.539 + * Point 3: 238.954, 543.855 + * Message: "36h11 id: 1" + * \endcode + * + * As shown in the next image, two different tag frames could be considered for pose estimation. + * \image html img-tag-frame.jpg Tag 36h11_00000 with location of the 4 corners and tag frame + * There is the function setZAlignedWithCameraAxis() that allows to choose which tag frame has to be considered. + * + * This other example shows how to estimate the 3D pose of 36h11 AprilTag + * patterns considering that all the tags have the same size (in our example 0.053 m). + * Here we consider the default case, when z-camera and z-tag axis are not aligned. + * + * \code + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * #ifdef VISP_HAVE_APRILTAG + * vpImage I; + * vpImageIo::read(I, "image-tag36h11.pgm"); + * + * vpDetectorAprilTag detector(vpDetectorAprilTag::TAG_36h11); + * detector.setZAlignedWithCameraAxis(false); // Default configuration + * std::vector cMo; + * vpCameraParameters cam; + * cam.initPersProjWithoutDistortion(615.1674805, 615.1675415, 312.1889954, 243.4373779); + * double tagSize = 0.053; + * + * bool status = detector.detect(I, tagSize, cam, cMo); + * if (status) { + * for(size_t i=0; i < detector.getNbObjects(); ++i) { + * std::cout << "Tag number " << i << ":" << std::endl; + * std::cout << " Message: \"" << detector.getMessage(i) << "\"" << std::endl; + * std::cout << " Pose: " << vpPoseVector(cMo[i]).t() << std::endl; + * std::size_t tag_id_pos = detector.getMessage(i).find("id: "); + * if (tag_id_pos != std::string::npos) { + * std::string tag_id = detector.getMessage(i).substr(tag_id_pos + 4); + * std::cout << " Tag Id: " << tag_id << std::endl; + * } + * } + * } + * #endif + * } + * \endcode + * The previous example may produce results like: + * \code + * Tag number 0: + * Message: "36h11 id: 0" + * Pose: 0.1015061088 -0.05239057228 0.3549037285 1.991474322 2.04143538 -0.9412360063 + * Tag Id: 0 + * Tag number 1: + * Message: "36h11 id: 1" + * Pose: 0.08951250829 0.02243780207 0.306540622 1.998073197 2.061488008 -0.8699567948 + * Tag Id: 1 + * \endcode + * + * In this other example we estimate the 3D pose of 36h11 AprilTag + * patterns considering that tag 36h11 with id 0 (in that case the tag message is "36h11 id: 0") + * has a size of 0.040 m, while all the others have a size of 0.053m. + * \code + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * #ifdef VISP_HAVE_APRILTAG + * vpImage I; + * vpImageIo::read(I, "image-tag36h11.pgm"); + * + * vpDetectorAprilTag detector(vpDetectorAprilTag::TAG_36h11); + * vpHomogeneousMatrix cMo; + * vpCameraParameters cam; + * cam.initPersProjWithoutDistortion(615.1674805, 615.1675415, 312.1889954, 243.4373779); + * double tagSize_id_0 = 0.04; + * double tagSize_id_others = 0.053; + * + * bool status = detector.detect(I); + * if (status) { + * for(size_t i=0; i < detector.getNbObjects(); ++i) { + * std::cout << "Tag code " << i << ":" << std::endl; + * std::cout << " Message: \"" << detector.getMessage(i) << "\"" << std::endl; + * if (detector.getMessage(i) == std::string("36h11 id: 0")) { + * if (! detector.getPose(i, tagSize_id_0, cam, cMo)) { + * std::cout << "Unable to get tag index " << i << " pose!" << std::endl; + * } + * } + * else { + * if (! detector.getPose(i, tagSize_id_others, cam, cMo)) { + * std::cout << "Unable to get tag index " << i << " pose!" << std::endl; + * } + * } + * std::cout << " Pose: " << vpPoseVector(cMo).t() << std::endl; + * } + * } + * #endif + * } + * \endcode + * With respect to the previous example, this example may now produce a different pose for tag with id 0: + * \code + * Tag code 0: + * Message: "36h11 id: 0" + * Pose: 0.07660838403 -0.03954005455 0.2678518706 1.991474322 2.04143538 -0.9412360063 + * Tag code 1: + * Message: "36h11 id: 1" + * Pose: 0.08951250829 0.02243780207 0.306540622 1.998073197 2.061488008 -0.8699567948 + * \endcode + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you want a detailed explanation on how to use this class, you may have a look at:
+ * + * - \ref tutorial-detection-apriltag +*/ +class VISP_EXPORT vpDetectorAprilTag : public vpDetectorBase +{ +public: + enum vpAprilTagFamily + { + TAG_36h11, ///< AprilTag 36h11 pattern (recommended) + TAG_36h10, ///< DEPRECATED + TAG_36ARTOOLKIT, ///< DEPRECATED AND WILL NOT DETECT ARTOOLKIT TAGS + TAG_25h9, ///< AprilTag 25h9 pattern + TAG_25h7, ///< DEPRECATED AND POOR DETECTION PERFORMANCE + TAG_16h5, ///< AprilTag 16h5 pattern + TAG_CIRCLE21h7, ///< AprilTag Circle21h7 pattern + TAG_CIRCLE49h12, ///< AprilTag Circle49h12 pattern + TAG_CUSTOM48h12, ///< AprilTag Custom48h12 pattern + TAG_STANDARD41h12, ///< AprilTag Standard41h12 pattern + TAG_STANDARD52h13, ///< AprilTag Standard52h13 pattern + TAG_ARUCO_4x4_50, /*!< ArUco 4x4 pattern: 4x4 bits, minimum hamming distance between any two codes = 4, 50 codes.\n + This tag family can produce lots of false detections which can be filtered by setting an + appropriate decision margin, using setAprilTagDecisionMarginThreshold() or + getTagsDecisionMargin(). See \ref apriltag_detection_tips_filter section for more details. */ + TAG_ARUCO_4x4_100, /*!< ArUco 4x4 pattern: 4x4 bits, minimum hamming distance between any two codes = 3, 100 codes.\n + This tag family can produce lots of false detections which can be filtered by setting an + appropriate decision margin, using setAprilTagDecisionMarginThreshold() or + getTagsDecisionMargin(). See \ref apriltag_detection_tips_filter section for more details. */ + TAG_ARUCO_4x4_250, /*!< ArUco 4x4 pattern: 4x4 bits, minimum hamming distance between any two codes = 3, 250 codes.\n + This tag family can produce lots of false detections which can be filtered by setting an + appropriate decision margin, using setAprilTagDecisionMarginThreshold() or + getTagsDecisionMargin(). See \ref apriltag_detection_tips_filter section for more details. */ + TAG_ARUCO_4x4_1000, /*!< ArUco 4x4 pattern: 4x4 bits, minimum hamming distance between any two codes = 2, 1000 codes.\n + This tag family can produce lots of false detections which can be filtered by setting an + appropriate decision margin, using setAprilTagDecisionMarginThreshold() or + getTagsDecisionMargin(). See \ref apriltag_detection_tips_filter section for more details. */ + TAG_ARUCO_5x5_50, /*!< ArUco 5x5 pattern: 5x5 bits, minimum hamming distance between any two codes = 8, 50 codes.\n + This tag family can produce lots of false detections which can be filtered by setting an + appropriate decision margin, using setAprilTagDecisionMarginThreshold() or + getTagsDecisionMargin(). See \ref apriltag_detection_tips_filter section for more details. */ + TAG_ARUCO_5x5_100, /*!< ArUco 5x5 pattern: 5x5 bits, minimum hamming distance between any two codes = 7, 100 codes.\n + This tag family can produce lots of false detections which can be filtered by setting an + appropriate decision margin, using setAprilTagDecisionMarginThreshold() or + getTagsDecisionMargin(). See \ref apriltag_detection_tips_filter section for more details. */ + TAG_ARUCO_5x5_250, /*!< ArUco 5x5 pattern: 5x5 bits, minimum hamming distance between any two codes = 6, 250 codes.\n + This tag family can produce lots of false detections which can be filtered by setting an + appropriate decision margin, using setAprilTagDecisionMarginThreshold() or + getTagsDecisionMargin(). See \ref apriltag_detection_tips_filter section for more details. */ + TAG_ARUCO_5x5_1000, /*!< ArUco 5x5 pattern: 5x5 bits, minimum hamming distance between any two codes = 5, 1000 codes.\n + This tag family can produce lots of false detections which can be filtered by setting an + appropriate decision margin, using setAprilTagDecisionMarginThreshold() or + getTagsDecisionMargin(). See \ref apriltag_detection_tips_filter section for more details. */ + TAG_ARUCO_6x6_50, /*!< ArUco 6x6 pattern: 6x6 bits, minimum hamming distance between any two codes = 13, 50 codes.\n + This tag family can produce lots of false detections which can be filtered by setting an + appropriate decision margin, using setAprilTagDecisionMarginThreshold() or + getTagsDecisionMargin(). See \ref apriltag_detection_tips_filter section for more details. */ + TAG_ARUCO_6x6_100, /*!< ArUco 6x6 pattern: 6x6 bits, minimum hamming distance between any two codes = 12, 100 codes.\n + This tag family can produce lots of false detections which can be filtered by setting an + appropriate decision margin, using setAprilTagDecisionMarginThreshold() or + getTagsDecisionMargin(). See \ref apriltag_detection_tips_filter section for more details. */ + TAG_ARUCO_6x6_250, /*!< ArUco 6x6 pattern: 6x6 bits, minimum hamming distance between any two codes = 11, 250 codes.\n + This tag family can produce lots of false detections which can be filtered by setting an + appropriate decision margin, using setAprilTagDecisionMarginThreshold() or + getTagsDecisionMargin(). See \ref apriltag_detection_tips_filter section for more details. */ + TAG_ARUCO_6x6_1000, /*!< ArUco 6x6 pattern: 6x6 bits, minimum hamming distance between any two codes = 9, 1000 codes.\n + This tag family can produce lots of false detections which can be filtered by setting an + appropriate decision margin, using setAprilTagDecisionMarginThreshold() or + getTagsDecisionMargin(). See \ref apriltag_detection_tips_filter section for more details. */ + TAG_ARUCO_7x7_50, /*!< ArUco 7x7 pattern: 7x7 bits, minimum hamming distance between any two codes = 19, 50 codes.\n + This tag family can produce lots of false detections which can be filtered by setting an + appropriate decision margin, using setAprilTagDecisionMarginThreshold() or + getTagsDecisionMargin(). See \ref apriltag_detection_tips_filter section for more details. */ + TAG_ARUCO_7x7_100, /*!< ArUco 7x7 pattern: 7x7 bits, minimum hamming distance between any two codes = 18, 100 codes.\n + This tag family can produce lots of false detections which can be filtered by setting an + appropriate decision margin, using setAprilTagDecisionMarginThreshold() or + getTagsDecisionMargin(). See \ref apriltag_detection_tips_filter section for more details. */ + TAG_ARUCO_7x7_250, /*!< ArUco 7x7 pattern: 7x7 bits, minimum hamming distance between any two codes = 17, 250 codes.\n + This tag family can produce lots of false detections which can be filtered by setting an + appropriate decision margin, using setAprilTagDecisionMarginThreshold() or + getTagsDecisionMargin(). See \ref apriltag_detection_tips_filter section for more details. */ + TAG_ARUCO_7x7_1000, /*!< ArUco 7x7 pattern: 7x7 bits, minimum hamming distance between any two codes = 14, 1000 codes.\n + This tag family can produce lots of false detections which can be filtered by setting an + appropriate decision margin, using setAprilTagDecisionMarginThreshold() or + getTagsDecisionMargin(). See \ref apriltag_detection_tips_filter section for more details. */ + TAG_ARUCO_MIP_36h12 /*!< ArUco 6x6 pattern: 6x6 bits, minimum hamming distance between any two codes = 12, 250 codes.\n + This is the recommended ArUco tag family by the main ArUco developer, + see this link */ + }; + + enum vpPoseEstimationMethod + { + HOMOGRAPHY, /*!< Pose from homography */ + HOMOGRAPHY_VIRTUAL_VS, /*!< Non linear virtual visual servoing approach + initialized by the homography approach */ + DEMENTHON_VIRTUAL_VS, /*!< Non linear virtual visual servoing approach + initialized by the Dementhon approach */ + LAGRANGE_VIRTUAL_VS, /*!< Non linear virtual visual servoing approach + initialized by the Lagrange approach */ + BEST_RESIDUAL_VIRTUAL_VS, /*!< Non linear virtual visual servoing approach + initialized by the approach that gives the + lowest residual */ + HOMOGRAPHY_ORTHOGONAL_ITERATION /*!< Pose from homography followed by a refinement by Orthogonal Iteration */ + }; + + vpDetectorAprilTag(const vpAprilTagFamily &tagFamily = TAG_36h11, + const vpPoseEstimationMethod &poseEstimationMethod = HOMOGRAPHY_VIRTUAL_VS); + vpDetectorAprilTag(const vpDetectorAprilTag &o); + vpDetectorAprilTag &operator=(vpDetectorAprilTag o); + virtual ~vpDetectorAprilTag() VP_OVERRIDE; + bool detect(const vpImage &I) VP_OVERRIDE; + + bool detect(const vpImage &I, double tagSize, const vpCameraParameters &cam, + std::vector &cMo_vec, std::vector *cMo_vec2 = nullptr, + std::vector *projErrors = nullptr, std::vector *projErrors2 = nullptr); + + void displayFrames(const vpImage &I, const std::vector &cMo_vec, + const vpCameraParameters &cam, double size, const vpColor &color, unsigned int thickness = 1) const; + void displayFrames(const vpImage &I, const std::vector &cMo_vec, + const vpCameraParameters &cam, double size, const vpColor &color, unsigned int thickness = 1) const; + + void displayTags(const vpImage &I, const std::vector > &tagsCorners, + const vpColor &color = vpColor::none, unsigned int thickness = 1) const; + void displayTags(const vpImage &I, const std::vector > &tagsCorners, + const vpColor &color = vpColor::none, unsigned int thickness = 1) const; + + float getAprilTagDecisionMarginThreshold() const; + int getAprilTagHammingDistanceThreshold() const; + bool getPose(size_t tagIndex, double tagSize, const vpCameraParameters &cam, vpHomogeneousMatrix &cMo, + vpHomogeneousMatrix *cMo2 = nullptr, double *projError = nullptr, double *projError2 = nullptr); + + /*! + * Return the pose estimation method. + */ + inline vpPoseEstimationMethod getPoseEstimationMethod() const { return m_poseEstimationMethod; } + + bool getTagImage(vpImage &I, int id); + std::vector > getTagsCorners() const; + std::vector getTagsDecisionMargin() const; + std::vector getTagsHammingDistance() const; + std::vector getTagsId() const; + std::vector > getTagsPoints3D(const std::vector &tagsId, + const std::map &tagsSize) const; + + bool isZAlignedWithCameraAxis() const; + + void setAprilTagDebugOption(bool flag); + void setAprilTagDecisionMarginThreshold(float decisionMarginThreshold); + void setAprilTagDecodeSharpening(double decodeSharpening); + void setAprilTagFamily(const vpAprilTagFamily &tagFamily); + void setAprilTagHammingDistanceThreshold(int hammingDistanceThreshold); + void setAprilTagNbThreads(int nThreads); + void setAprilTagPoseEstimationMethod(const vpPoseEstimationMethod &poseEstimationMethod); + void setAprilTagQuadDecimate(float quadDecimate); + void setAprilTagQuadSigma(float quadSigma); + void setAprilTagRefineEdges(bool refineEdges); + + + /*! Allow to enable the display of overlay tag information in the windows + * (vpDisplay) associated to the input image. */ + inline void setDisplayTag(bool display, const vpColor &color = vpColor::none, unsigned int thickness = 2) + { + m_displayTag = display; + m_displayTagColor = color; + m_displayTagThickness = thickness; + } + + inline friend void swap(vpDetectorAprilTag &o1, vpDetectorAprilTag &o2) + { + using std::swap; + swap(o1.m_impl, o2.m_impl); + } + + void setZAlignedWithCameraAxis(bool zAlignedWithCameraFrame); + +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) + /*! + @name Deprecated functions + */ + //@{ + VP_DEPRECATED void setAprilTagRefinePose(bool refinePose); + VP_DEPRECATED void setAprilTagRefineDecode(bool refineDecode); + //@} +#endif + +protected: + bool m_displayTag; + vpColor m_displayTagColor; + unsigned int m_displayTagThickness; + vpPoseEstimationMethod m_poseEstimationMethod; + vpAprilTagFamily m_tagFamily; + +private: + vpCameraParameters m_defaultCam; + + // PIMPL idiom + class Impl; + Impl *m_impl; +}; + +inline std::ostream &operator<<(std::ostream &os, const vpDetectorAprilTag::vpPoseEstimationMethod &method) +{ + switch (method) { + case vpDetectorAprilTag::HOMOGRAPHY: + os << "HOMOGRAPHY"; + break; + + case vpDetectorAprilTag::HOMOGRAPHY_VIRTUAL_VS: + os << "HOMOGRAPHY_VIRTUAL_VS"; + break; + + case vpDetectorAprilTag::DEMENTHON_VIRTUAL_VS: + os << "DEMENTHON_VIRTUAL_VS"; + break; + + case vpDetectorAprilTag::LAGRANGE_VIRTUAL_VS: + os << "LAGRANGE_VIRTUAL_VS"; + break; + + case vpDetectorAprilTag::BEST_RESIDUAL_VIRTUAL_VS: + os << "BEST_RESIDUAL_VIRTUAL_VS"; + break; + + case vpDetectorAprilTag::HOMOGRAPHY_ORTHOGONAL_ITERATION: + os << "HOMOGRAPHY_ORTHOGONAL_ITERATION"; + break; + + default: + os << "ERROR_UNKNOWN_POSE_METHOD!"; + break; + } + + return os; +} + +inline std::ostream &operator<<(std::ostream &os, const vpDetectorAprilTag::vpAprilTagFamily &tagFamily) +{ + switch (tagFamily) { + case vpDetectorAprilTag::TAG_36h11: + os << "36h11"; + break; + + case vpDetectorAprilTag::TAG_36h10: + os << "36h10"; + break; + + case vpDetectorAprilTag::TAG_36ARTOOLKIT: + os << "36artoolkit"; + break; + + case vpDetectorAprilTag::TAG_25h9: + os << "25h9"; + break; + + case vpDetectorAprilTag::TAG_25h7: + os << "25h7"; + break; + + case vpDetectorAprilTag::TAG_16h5: + os << "16h5"; + break; + + case vpDetectorAprilTag::TAG_CIRCLE21h7: + os << "CIRCLE21h7"; + break; + + case vpDetectorAprilTag::TAG_CIRCLE49h12: + os << "CIRCLE49h12"; + break; + + case vpDetectorAprilTag::TAG_CUSTOM48h12: + os << "CUSTOM48h12"; + break; + + case vpDetectorAprilTag::TAG_STANDARD52h13: + os << "STANDARD52h13"; + break; + + case vpDetectorAprilTag::TAG_STANDARD41h12: + os << "STANDARD41h12"; + break; + + case vpDetectorAprilTag::TAG_ARUCO_4x4_50: + os << "TAG_ARUCO_4x4_50"; + break; + + case vpDetectorAprilTag::TAG_ARUCO_4x4_100: + os << "TAG_ARUCO_4x4_100"; + break; + + case vpDetectorAprilTag::TAG_ARUCO_4x4_250: + os << "TAG_ARUCO_4x4_250"; + break; + + case vpDetectorAprilTag::TAG_ARUCO_4x4_1000: + os << "TAG_ARUCO_4x4_1000"; + break; + + case vpDetectorAprilTag::TAG_ARUCO_5x5_50: + os << "TAG_ARUCO_5x5_50"; + break; + + case vpDetectorAprilTag::TAG_ARUCO_5x5_100: + os << "TAG_ARUCO_5x5_100"; + break; + + case vpDetectorAprilTag::TAG_ARUCO_5x5_250: + os << "TAG_ARUCO_5x5_250"; + break; + + case vpDetectorAprilTag::TAG_ARUCO_5x5_1000: + os << "TAG_ARUCO_5x5_1000"; + break; + + case vpDetectorAprilTag::TAG_ARUCO_6x6_50: + os << "TAG_ARUCO_6x6_50"; + break; + + case vpDetectorAprilTag::TAG_ARUCO_6x6_100: + os << "TAG_ARUCO_6x6_100"; + break; + + case vpDetectorAprilTag::TAG_ARUCO_6x6_250: + os << "TAG_ARUCO_6x6_250"; + break; + + case vpDetectorAprilTag::TAG_ARUCO_6x6_1000: + os << "TAG_ARUCO_6x6_1000"; + break; + + case vpDetectorAprilTag::TAG_ARUCO_7x7_50: + os << "TAG_ARUCO_7x7_50"; + break; + + case vpDetectorAprilTag::TAG_ARUCO_7x7_100: + os << "TAG_ARUCO_7x7_100"; + break; + + case vpDetectorAprilTag::TAG_ARUCO_7x7_250: + os << "TAG_ARUCO_7x7_250"; + break; + + case vpDetectorAprilTag::TAG_ARUCO_7x7_1000: + os << "TAG_ARUCO_7x7_1000"; + break; + + case vpDetectorAprilTag::TAG_ARUCO_MIP_36h12: + os << "TAG_ARUCO_MIP_36h12"; + break; + + default: + os << "UNKNOWN"; + break; + } + + return os; +} + +END_VISP_NAMESPACE + +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/detection/vpDetectorBase.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/detection/vpDetectorBase.h new file mode 100644 index 00000000..436628a1 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/detection/vpDetectorBase.h @@ -0,0 +1,136 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Base class for object detection. + */ + +#ifndef VP_DETECTOR_BASE_H +#define VP_DETECTOR_BASE_H + +#include +#include +#include +#include + +#include +#include +#include + +BEGIN_VISP_NAMESPACE + +/*! + * \class vpDetectorBase + * \ingroup group_detection_barcode group_detection_face + * Base class for object detection. + * + * This class is a generic class that can be used to detect: + * - bar codes like QRcodes of Data matrices using vpDetectorQRCode and vpDetectorDataMatrixCode classes respectively. + * The example given in tutorial-barcode-detector.cpp shows how to detect one or more bar codes in + * an image. In tutorial-barcode-detector-live.cpp you will find an other + * example that shows how to use this class to detect bar codes in images + * acquired by a camera. + * - AprilTags using vpDetectorAprilTag class + * - faces using vpDetectorFace. An example is provided in tutorial-face-detector-live.cpp. +*/ +class VISP_EXPORT vpDetectorBase +{ +public: + /*! + * Default constructor. + */ + vpDetectorBase(); + vpDetectorBase(const vpDetectorBase &o); + /*! + * Default destructor. + */ + virtual ~vpDetectorBase() { } + + /*! + * Detect objects in an image. + * \param I : Image where to detect objects. + * \return true if one or multiple objects are detected, false otherwise. + */ + virtual bool detect(const vpImage &I) = 0; + + /** @name Inherited functionalities from vpDetectorBase */ + //@{ + + /*! + * Return the bounding box of the ith object. + */ + vpRect getBBox(size_t i) const; + + /*! + * Return the center of gravity location of the ith object. + */ + vpImagePoint getCog(size_t i) const; + + /*! + * Returns the contained message of the ith object if there is one. + */ + std::vector &getMessage() { return m_message; } + + /*! + * Returns the contained message of the ith object if there is one. + */ + std::string &getMessage(size_t i); + + /*! + * Return the number of objects that are detected. + */ + size_t getNbObjects() const { return m_nb_objects; } + + /*! + * Returns object container box as a vector of points. + */ + std::vector > &getPolygon() { return m_polygon; } + + /*! + * Returns ith object container box as a vector of points. + */ + std::vector &getPolygon(size_t i); + + /*! + * Set detector timeout in milli-seconds. When set to 0, there is no timeout. + */ + inline void setTimeout(unsigned long timeout_ms) { m_timeout_ms = timeout_ms; } + + //@} + +protected: + std::vector > m_polygon; //!< For each object, defines the polygon that contains the object. + std::vector m_message; //!< Message attached to each object. + size_t m_nb_objects; //!< Number of detected objects. + unsigned long m_timeout_ms; //!< Detection timeout. +}; + +END_VISP_NAMESPACE + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/detection/vpDetectorDNNOpenCV.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/detection/vpDetectorDNNOpenCV.h new file mode 100644 index 00000000..aede6669 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/detection/vpDetectorDNNOpenCV.h @@ -0,0 +1,632 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * DNN object detection using OpenCV DNN module. + */ + +#ifndef VP_DETECTOR_DNN_OPENCV_H +#define VP_DETECTOR_DNN_OPENCV_H + +#include + +// Check if std:c++17 or higher. +// Here we cannot use (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17) in the declaration of the class +#if defined(VISP_HAVE_OPENCV) && (VISP_HAVE_OPENCV_VERSION >= 0x030403) && defined(HAVE_OPENCV_DNN) && \ + ((__cplusplus >= 201703L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L))) + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#ifdef VISP_HAVE_NLOHMANN_JSON +#include VISP_NLOHMANN_JSON(json.hpp) +#endif + +BEGIN_VISP_NAMESPACE +/*! + * \class vpDetectorDNNOpenCV + * \ingroup group_detection_dnn + * This class is a wrapper over the + * OpenCV DNN module and specialized to handle object detection task. + * + * This class supports the following networks dedicated to object detection: + * + * - Faster-RCNN, see usage to detect objects belonging to the COCO dataset using \ref dnn_supported_faster_rcnn network + * - SSD MobileNet, see usage to detect objects belonging to the COCO dataset using \ref dnn_supported_mobilenet_ssd network + * - ResNet 10, see usage for \ref dnn_usecase_face_detection + * - Yolo v3, see usage to detect objects belonging to the COCO dataset using \ref dnn_supported_yolov3 network + * - Yolo v4, see usage to detect objects belonging to the COCO dataset using \ref dnn_supported_yolov4 network + * - Yolo v5, see usage to detect objects belonging to the COCO dataset using \ref dnn_supported_yolov5 network + * - Yolo v7, see usage to detect objects belonging to the COCO dataset using \ref dnn_supported_yolov7 network + * - Yolo v8, see usage to detect objects belonging to the COCO dataset using \ref dnn_supported_yolov8 network + * - Yolo v11, see usage to detect objects belonging to the COCO dataset using \ref dnn_supported_yolov11 network + * - Yolo v12, see usage to detect objects belonging to the COCO dataset using \ref dnn_supported_yolov12 network + * + * This class can be initialized from a JSON file if ViSP has been compiled with NLOHMANN JSON (see \ref soft_tool_json to see how to do it). + * Examples of such JSON files can be found in the tutorial folder. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you want a detailed explanation on how to use this class, you may have a look at:
+ * + * - \ref tutorial-detection-dnn +*/ +class VISP_EXPORT vpDetectorDNNOpenCV +{ +public: + /** + * \enum DNNResultsParsingType + * \brief Enumeration listing the types of DNN for which the \b vpDetectorDNNOpenCV furnishes the methods + * permitting to parse the raw detection data into \b vpDetectorDNNOpenCV::DetectedFeatures2D . + */ + typedef enum DNNResultsParsingType + { + USER_SPECIFIED = 0, /*!< The user will give a pointer towards the parsing method to use to parse the raw data resulting from the detection step.*/ + FASTER_RCNN = 1, /*!< The \b vpDetectorDNNOpenCV object will use the parsing method corresponding to a Faster-RCNN DNN. See \b vpDetectorDNNOpenCV::postProcess_FasterRCNN for more information.*/ + SSD_MOBILENET = 2, /*!< The \b vpDetectorDNNOpenCV object will use the parsing method corresponding to a SSD MobileNet DNN. See \b vpDetectorDNNOpenCV::postProcess_SSD_MobileNet for more information.*/ + RESNET_10 = 3, /*!< The \b vpDetectorDNNOpenCV object will use the parsing method corresponding to a ResNet 10 DNN. See \b vpDetectorDNNOpenCV::postProcess_ResNet_10 for more information.*/ + YOLO_V3 = 4, /*!< The \b vpDetectorDNNOpenCV object will use the parsing method corresponding to a YoloV3 DNN. See \b vpDetectorDNNOpenCV::postProcess_YoloV3_V4 for more information.*/ + YOLO_V4 = 5, /*!< The \b vpDetectorDNNOpenCV object will use the parsing method corresponding to a YoloV4 DNN. See \b vpDetectorDNNOpenCV::postProcess_YoloV3_V4 for more information.*/ + YOLO_V5 = 6, /*!< The \b vpDetectorDNNOpenCV object will use the parsing method corresponding to a YoloV5 DNN. See \b vpDetectorDNNOpenCV::postProcess_YoloV5_V7 for more information.*/ + YOLO_V7 = 7, /*!< The \b vpDetectorDNNOpenCV object will use the parsing method corresponding to a YoloV7 DNN. See \b vpDetectorDNNOpenCV::postProcess_YoloV5_V7 for more information.*/ + YOLO_V8 = 8, /*!< The \b vpDetectorDNNOpenCV object will use the parsing method corresponding to a YoloV8 DNN. See \b vpDetectorDNNOpenCV::postProcess_YoloV8_V11_V12 for more information.*/ + YOLO_V11 = 9, /*!< The \b vpDetectorDNNOpenCV object will use the parsing method corresponding to a YoloV11 DNN. See \b vpDetectorDNNOpenCV::postProcess_YoloV8_V11_V12 for more information.*/ + YOLO_V12 = 10, /*!< The \b vpDetectorDNNOpenCV object will use the parsing method corresponding to a YoloV12 DNN. See \b vpDetectorDNNOpenCV::postProcess_YoloV8_V11_V12 for more information.*/ + COUNT = 11 /*!< The number of parsing method that come along with the \b vpDetectorDNNOpenCV class.*/ + } DNNResultsParsingType; + + typedef struct DetectionCandidates + { + std::vector< float > m_confidences; /*!< Vector containing the detection confidence of each \b vpDetectorDNNOpenCV::DetectionCandidates::m_boxes.*/ + std::vector< cv::Rect > m_boxes; /*!< The bounding box of each detection candidate.*/ + std::vector< int > m_classIds; /*!< The class ID of each detection candidate.*/ + } DetectionCandidates; + + /** + * \class DetectedFeatures2D + * \brief Structure containing the bounding box, expressed in pixels, confidence and class information + * about an object detected in a image. + */ + typedef class DetectedFeatures2D + { + protected: + vpRect m_bbox; /*!< The bounding box of the detected object.*/ + double m_score; /*!< The confidence in the detection.*/ + unsigned int m_cls; /*!< The class ID.*/ + std::optional m_classname; /*!< The class name, if the class names were given to the \b vpDetectorDNNOpenCV::NetConfig used to configure the \b vpDetectorDNNOpenCV object.*/ + public: + /** + * \brief Construct a new Detected Features 2 D object + * + * \param u_min The left coordinate of the bounding box, expressed in pixel. + * \param u_max The right coordinate of the bounding box, expressed in pixel. + * \param v_min The top coordinate of the bounding box, expressed in pixel. + * \param v_max The bottom coordinate of the bounding box, expressed in pixel. + * \param cls The class ID + * \param score The confidence in the detection. + * \param classname The class name, if the class names were given to the \b vpDetectorDNNOpenCV::NetConfig used to configure the \b vpDetectorDNNOpenCV object. + */ + inline explicit DetectedFeatures2D(double u_min, double u_max + , double v_min, double v_max + , unsigned int cls, double score + , const std::optional &classname + ) + : m_bbox(vpImagePoint(v_min, u_min), vpImagePoint(v_max, u_max)) + , m_score(score) + , m_cls(cls) + { + if (classname) { + m_classname = classname; + } + else { + m_classname = std::nullopt; + } + } + + /*! + * Return the bounding box of the detected object. + */ + inline vpRect getBoundingBox() const { return m_bbox; } + /*! + * Return the confidence score of the detected object, a value between 0 and 1. + */ + inline double getConfidenceScore() const { return m_score; } + /*! + * Return the class ID of the detected object. + */ + inline unsigned int getClassId() const { return m_cls; } + /*! + * Return the class name of the detected object. + */ + inline std::optional getClassName() const { return m_classname; } + + template < typename Type > + void display(const vpImage< Type > &img, const vpColor &color = vpColor::blue, unsigned int thickness = 1) const; + + friend vpDetectorDNNOpenCV; + } DetectedFeatures2D; + + /** + * \struct NetConfig + * \brief Structure containing some information required for the configuration of a \b vpDetectorDNNOpenCV object. + */ + typedef class NetConfig + { + private: + float m_confThreshold; /*!< Threshold to filter detections by confidence.*/ + float m_nmsThreshold; /*!< Threshold for Non-Maximum Suppression.*/ + std::vector m_classNames;/*!< Vector containing the names of the different classes the DNN can detect.*/ + cv::Size m_inputSize; /*!< Size of the images the DNN can manipulate. The input images will be resized to match these dimensions.*/ + double m_filterSizeRatio; /*!< Size ratio used by the \b vpDetectorDNNOpenCV::filterDetectionSingleClassInput and \b vpDetectorDNNOpenCV::filterDetectionMultiClassInput methods. + If <= 0., the \b vpDetectorDNNOpenCV::filterDetectionSingleClassInput and \b vpDetectorDNNOpenCV::filterDetectionMultiClassInput methods are not used.*/ + cv::Scalar m_mean; /*!< Values for mean subtraction.*/ + double m_scaleFactor; /*!< Scale factor to normalize pixel values.*/ + bool m_swapRB; /*!< If true, swap R and B for mean subtraction, e.g. when a model has been trained on BGR image format.*/ + DNNResultsParsingType m_parsingMethodType; /*!< Parsing method that should be used to parse the cv::Mat returned by the cv::dnn::Net::forward method.*/ + std::string m_modelFilename; /*!< Path towards the model file.*/ + std::string m_modelConfigFilename; /*!< Path towards the model additional configuration file, e.g. pbtxt file.*/ + std::string m_framework; /*!< Model framework.*/ + +#ifdef VISP_HAVE_NLOHMANN_JSON + /** + * \brief Read the network configuration from JSON. All values are optional and if an argument is not present, + * the default value defined in the constructor is kept + * + * \param j The JSON object, resulting from the parsing of a JSON file. + * \param config The configuration of the network, that will be initialized from the JSON data. + */ + friend inline void from_json(const nlohmann::json &j, NetConfig &config) + { + config.m_confThreshold = j.value("confidenceThreshold", config.m_confThreshold); + if (config.m_confThreshold <= 0) { + throw vpException(vpException::badValue, "Confidence threshold should be > 0"); + } + + config.m_nmsThreshold = j.value("nmsThreshold", config.m_nmsThreshold); + if (config.m_nmsThreshold <= 0) { + throw vpException(vpException::badValue, "Confidence threshold should be > 0"); + } + + config.m_filterSizeRatio = j.value("filterSizeRatio", config.m_filterSizeRatio); + + config.m_classNames = j.value("classNames", config.m_classNames); + + std::pair resolution = j.value("resolution", std::pair(config.m_inputSize.width, config.m_inputSize.height)); + config.m_inputSize.width = resolution.first; + config.m_inputSize.height = resolution.second; + + std::vector v_mean = j.value("mean", std::vector({ config.m_mean[0], config.m_mean[1], config.m_mean[2] })); + if (v_mean.size() != 3) { + throw(vpException(vpException::dimensionError, "Mean should have size = 3")); + } + config.m_mean = cv::Scalar(v_mean[0], v_mean[1], v_mean[2]); + + config.m_scaleFactor = j.value("scale", config.m_scaleFactor); + config.m_swapRB = j.value("swapRB", config.m_swapRB); + config.m_parsingMethodType = dnnResultsParsingTypeFromString(j.value("parsingType", dnnResultsParsingTypeToString(config.m_parsingMethodType))); + config.m_modelFilename = j.value("modelFile", config.m_modelFilename); + config.m_modelConfigFilename = j.value("configurationFile", config.m_modelConfigFilename); + config.m_framework = j.value("framework", config.m_framework); + } + + /** + * \brief Parse a vpDetectorDNNOpenCV::NetConfig into JSON format. + * + * \param j A JSON parser object. + * \param config The vpDetectorDNNOpenCV::NetConfig that must be parsed into JSON format. + */ + friend inline void to_json(nlohmann::json &j, const NetConfig &config) + { + std::pair resolution = { config.m_inputSize.width, config.m_inputSize.height }; + std::vector v_mean = { config.m_mean[0], config.m_mean[1], config.m_mean[2] }; + j = nlohmann::json { + {"confidenceThreshold", config.m_confThreshold } , + {"nmsThreshold" , config.m_nmsThreshold } , + {"filterSizeRatio" , config.m_filterSizeRatio} , + {"classNames" , config.m_classNames } , + {"resolution" , resolution } , + {"mean" , v_mean } , + {"scale" , config.m_scaleFactor } , + {"swapRB" , config.m_swapRB } , + {"parsingType" , dnnResultsParsingTypeToString(config.m_parsingMethodType) }, + {"modelFile" , config.m_modelFilename } , + {"configurationFile" , config.m_modelConfigFilename } , + {"framework" , config.m_framework } + }; + } +#endif + + public: + /** + * \brief Parse the file containing the list of classes the DNN can detect. + * These classes can be written either as a YAML array (i.e. ["classname_0", ... ,"classname_last"]) + * or with one classname by row (without quotes). + * + * For example, in the case of a yaml file called `my-classes.yaml`, one content could be: + \verbatim + $ cat my-classes.yaml + [ "person", "bicycle", "car"] + \endverbatim + * + * And in the case of a text file called `my-classes.txt`, similar content could be: + \verbatim + $ cat my-classes.txt + person + bicycle + car + \endverbatim + * + * \param filename The path towards the file containing the list of classes the DNN can detect. + * \return std::vector The list of classes the DNN can detect. + */ + inline static std::vector parseClassNamesFile(const std::string &filename) + { + std::vector classNames; + std::ifstream ifs(filename); + std::string line; + while (getline(ifs, line)) { + if (line.find("[") == std::string::npos) { + classNames.push_back(line); + } + else { + std::string lineWithoutBracket; + if (line.find("[") != std::string::npos) { + lineWithoutBracket = line.substr(line.find("[") + 1, line.size() - 2); // Remove opening and closing brackets + } + + while (!lineWithoutBracket.empty()) { + std::string className; + auto start_pos = lineWithoutBracket.find("\""); + auto end_pos = lineWithoutBracket.find("\"", start_pos + 1); + className = lineWithoutBracket.substr(start_pos + 1, end_pos - (start_pos + 1)); + if (!className.empty()) { + classNames.push_back(className); + lineWithoutBracket = lineWithoutBracket.substr(end_pos + 1); + } + } + } + } + return classNames; + } + + /** + * \brief Default constructor of the structure \b vpDetectorDNNOpenCV::NetConfig , required for JSON serialization/deserialization. + */ + inline NetConfig() + : m_confThreshold(0.5f) + , m_nmsThreshold(0.4f) + , m_classNames() + , m_inputSize(300, 300) + , m_filterSizeRatio(0.) + , m_mean(127.5, 127.5, 127.5) + , m_scaleFactor(2.0 / 255.0) + , m_swapRB(true) + , m_parsingMethodType(vpDetectorDNNOpenCV::USER_SPECIFIED) + , m_modelFilename() + , m_modelConfigFilename() + , m_framework() + { + + } + + inline NetConfig(const NetConfig &config) + : m_confThreshold(config.m_confThreshold) + , m_nmsThreshold(config.m_nmsThreshold) + , m_classNames(config.m_classNames) + , m_inputSize(config.m_inputSize.width, config.m_inputSize.height) + , m_filterSizeRatio(config.m_filterSizeRatio) + , m_mean(cv::Scalar(config.m_mean[0], config.m_mean[1], config.m_mean[2])) + , m_scaleFactor(config.m_scaleFactor) + , m_swapRB(config.m_swapRB) + , m_parsingMethodType(config.m_parsingMethodType) + , m_modelFilename(config.m_modelFilename) + , m_modelConfigFilename(config.m_modelConfigFilename) + , m_framework(config.m_framework) + { + + } + + /** + * \brief Construct a new Net Config object + * + * \param confThresh The confidence threshold to keep a detection. + * \param nmsThresh The Non-Maximum Suppression threshold to merge overlapping detections. + * \param classNames A vector containing the list of classes the DNN can detect. + * \param dnnInputSize The size of the input that the DNN is expecting. + * \param filterSizeRatio The threshold for the size filter that the user can chose to activate or not (see \b vpDetectorDNNOpenCV::filterDetectionSingleClassInput + * and \b vpDetectorDNNOpenCV::filterDetectionMultiClassInput methods for more information). + * \param mean The mean value we must deduce to each color channel of the image. + * \param scaleFactor The scale factor that will be multiplied to each color channel of the image. + * \param swapRB If true, will swap the red and blue channel of the input image. + * \param parsingType The type of parsing method to use to interpret the DNN raw results. + * \param modelFilename The path towards the DNN weights. + * \param configFilename The path towards the additional DNN configuration file potentially needed. + * \param framework The type of framework used to store the weights of the DNN. + */ + inline NetConfig(float confThresh, const float &nmsThresh, const std::vector &classNames, const cv::Size &dnnInputSize, const double &filterSizeRatio = 0. + , const cv::Scalar &mean = cv::Scalar(127.5, 127.5, 127.5), const double &scaleFactor = 2. / 255., const bool &swapRB = true + , const DNNResultsParsingType &parsingType = vpDetectorDNNOpenCV::USER_SPECIFIED, const std::string &modelFilename = "", const std::string &configFilename = "", const std::string &framework = "") + : m_confThreshold(confThresh) + , m_nmsThreshold(nmsThresh) + , m_classNames(classNames) + , m_inputSize(dnnInputSize) + , m_filterSizeRatio(filterSizeRatio) + , m_mean(mean) + , m_scaleFactor(scaleFactor) + , m_swapRB(swapRB) + , m_parsingMethodType(parsingType) + , m_modelFilename(modelFilename) + , m_modelConfigFilename(configFilename) + , m_framework(framework) + { } + + /** + * \brief Construct a new Net Config object + * + * \param confThresh The confidence threshold to keep a detection. + * \param nmsThresh The Non-Maximum Suppression threshold to merge overlapping detections. + * \param classNamesFile The path towards the file containing the classes names, written as a YAML string array or one class name by line. + * \param dnnInputSize The size of the input that the DNN is expecting. + * \param filterSizeRatio The threshold for the size filter that the user can chose to activate or not (see \b vpDetectorDNNOpenCV::filterDetectionSingleClassInput + * and \b vpDetectorDNNOpenCV::filterDetectionMultiClassInput methods for more information). + * \param mean The mean value we must deduce to each color channel of the image. + * \param scaleFactor The scale factor that will be multiplied to each color channel of the image. + * \param swapRB If true, will swap the red and blue channel of the input image. + * \param parsingType The type of parsing method to use to interpret the DNN raw results. + * \param modelFilename The path towards the DNN weights. + * \param configFilename The path towards the additional DNN configuration file potentially needed. + * \param framework The type of framework used to store the weights of the DNN. + */ + inline NetConfig(const float &confThresh, const float &nmsThresh, const std::string &classNamesFile, const cv::Size &dnnInputSize, const double &filterSizeRatio = 0. + , const cv::Scalar &mean = cv::Scalar(127.5, 127.5, 127.5), const double &scaleFactor = 2. / 255., const bool &swapRB = true + , const DNNResultsParsingType &parsingType = vpDetectorDNNOpenCV::USER_SPECIFIED, const std::string &modelFilename = "", const std::string &configFilename = "", const std::string &framework = "") + : m_confThreshold(confThresh) + , m_nmsThreshold(nmsThresh) + , m_inputSize(dnnInputSize) + , m_filterSizeRatio(filterSizeRatio) + , m_mean(mean) + , m_scaleFactor(scaleFactor) + , m_swapRB(swapRB) + , m_parsingMethodType(parsingType) + , m_modelFilename(modelFilename) + , m_modelConfigFilename(configFilename) + , m_framework(framework) + { + m_classNames = parseClassNamesFile(classNamesFile); + } + + inline std::string toString() const + { + std::string text; + text += "Model : " + m_modelFilename + "\n"; + text += "Type : " + vpDetectorDNNOpenCV::dnnResultsParsingTypeToString(m_parsingMethodType) + "\n"; + text += "Config (optional): " + (m_modelConfigFilename.empty() ? "\"None\"" : m_modelConfigFilename) + "\n"; + text += "Framework (optional): " + (m_framework.empty() ? "\"None\"" : m_framework) + "\n"; + text += "Width x Height : " + std::to_string(m_inputSize.width) + " x " + std::to_string(m_inputSize.height) + "\n"; + text += "Mean RGB : " + std::to_string(m_mean[0]) + " " + std::to_string(m_mean[1]) + " " + std::to_string(m_mean[2]) + "\n"; + text += "Scale : " + std::to_string(m_scaleFactor) + "\n"; + text += "Swap RB? : " + (m_swapRB ? std::string("true") : std::string("false")) + "\n"; + text += "Confidence threshold : " + std::to_string(m_confThreshold) + "\n"; + text += "NMS threshold : " + std::to_string(m_nmsThreshold) + "\n"; + text += "Filter threshold : " + + (m_filterSizeRatio > std::numeric_limits::epsilon() ? std::to_string(m_filterSizeRatio) + : "disabled") + "\n"; + return text; + } + + friend inline std::ostream &operator<<(std::ostream &os, const NetConfig &config) + { + os << config.toString(); + return os; + } + + NetConfig &operator=(const NetConfig &config) + { + m_confThreshold = config.m_confThreshold; + m_nmsThreshold = config.m_nmsThreshold; + m_classNames = config.m_classNames; + m_inputSize = cv::Size(config.m_inputSize.width, config.m_inputSize.height); + m_filterSizeRatio = config.m_filterSizeRatio; + m_mean = cv::Scalar(config.m_mean[0], config.m_mean[1], config.m_mean[2]); + m_scaleFactor = config.m_scaleFactor; + m_swapRB = config.m_swapRB; + m_parsingMethodType = config.m_parsingMethodType; + m_modelFilename = config.m_modelFilename; + m_modelConfigFilename = config.m_modelConfigFilename; + m_framework = config.m_framework; + return *this; + } + + friend vpDetectorDNNOpenCV; + } NetConfig; + + static std::string getAvailableDnnResultsParsingTypes(); + static std::string dnnResultsParsingTypeToString(const DNNResultsParsingType &type); + static DNNResultsParsingType dnnResultsParsingTypeFromString(const std::string &name); + static std::vector parseClassNamesFile(const std::string &filename); + vpDetectorDNNOpenCV(); + vpDetectorDNNOpenCV(const NetConfig &config, const DNNResultsParsingType &typeParsingMethod, void (*parsingMethod)(DetectionCandidates &, std::vector &, const NetConfig &) = postProcess_unimplemented); +#ifdef VISP_HAVE_NLOHMANN_JSON + vpDetectorDNNOpenCV(const std::string &jsonPath, void (*parsingMethod)(DetectionCandidates &, std::vector &, const NetConfig &) = postProcess_unimplemented); + void initFromJSON(const std::string &jsonPath); + void saveConfigurationInJSON(const std::string &jsonPath) const; +#endif + virtual ~vpDetectorDNNOpenCV(); + + virtual bool detect(const vpImage &I, std::vector &output); + virtual bool detect(const vpImage &I, std::map< std::string, std::vector> &output); + virtual bool detect(const vpImage &I, std::vector< std::pair>> &output); + virtual bool detect(const vpImage &I, std::vector &output); + virtual bool detect(const vpImage &I, std::map< std::string, std::vector> &output); + virtual bool detect(const vpImage &I, std::vector< std::pair>> &output); + virtual bool detect(const cv::Mat &I, std::vector &output); + virtual bool detect(const cv::Mat &I, std::map< std::string, std::vector> &output); + virtual bool detect(const cv::Mat &I, std::vector< std::pair>> &output); + + void readNet(const std::string &model, const std::string &config = "", const std::string &framework = ""); + + void setNetConfig(const NetConfig &config); + void setConfidenceThreshold(const float &confThreshold); + void setNMSThreshold(const float &nmsThreshold); + void setDetectionFilterSizeRatio(const double &sizeRatio); + void setInputSize(const int &width, const int &height); + void setMean(const double &meanR, const double &meanG, const double &meanB); + void setPreferableBackend(const int &backendId); + void setPreferableTarget(const int &targetId); + void setScaleFactor(const double &scaleFactor); + void setSwapRB(const bool &swapRB); + void setParsingMethod(const DNNResultsParsingType &typeParsingMethod, void (*parsingMethod)(DetectionCandidates &, std::vector &, const NetConfig &) = postProcess_unimplemented); + inline const NetConfig &getNetConfig() const + { + return m_netConfig; + } + +#ifdef VISP_HAVE_NLOHMANN_JSON + /** + * \brief Read the network configuration from JSON. All values are optional and if an argument is not present, + * the default value defined in the constructor is kept + * + * \param j The JSON object, resulting from the parsing of a JSON file. + * \param network The network, that will be initialized from the JSON data. + */ + friend inline void from_json(const nlohmann::json &j, vpDetectorDNNOpenCV &network) + { + network.m_netConfig = j.value("networkSettings", network.m_netConfig); + } + + /** + * \brief Parse the network configuration into JSON format. + * + * \param j The JSON parser. + * \param network The network we want to parse the configuration. + */ + friend inline void to_json(nlohmann::json &j, const vpDetectorDNNOpenCV &network) + { + j = nlohmann::json { + {"networkSettings", network.m_netConfig} + }; + } +#endif + + friend inline std::ostream &operator<<(std::ostream &os, const vpDetectorDNNOpenCV &network) + { + os << network.m_netConfig; + return os; + } + +protected: +#if (VISP_HAVE_OPENCV_VERSION == 0x030403) + std::vector getOutputsNames(); +#endif + std::vector + filterDetectionSingleClassInput(const std::vector &detected_features, const double minRatioOfAreaOk); + + std::vector + filterDetectionMultiClassInput(const std::vector &detected_features, const double minRatioOfAreaOk); + + std::map> + filterDetectionMultiClassInput(const std::map< std::string, std::vector> &detected_features, const double minRatioOfAreaOk); + + void postProcess(DetectionCandidates &proposals); + + void postProcess_YoloV3_V4(DetectionCandidates &proposals, std::vector &dnnRes, const NetConfig &netConfig); + + void postProcess_YoloV5_V7(DetectionCandidates &proposals, std::vector &dnnRes, const NetConfig &netConfig); + + void postProcess_YoloV8_V11_V12(DetectionCandidates &proposals, std::vector &dnnRes, const NetConfig &netConfig); + + void postProcess_FasterRCNN(DetectionCandidates &proposals, std::vector &dnnRes, const NetConfig &netConfig); + +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) + void postProcess_SSD_MobileNet(DetectionCandidates &proposals, std::vector &dnnRes, const NetConfig &netConfig); +#endif + + void postProcess_ResNet_10(DetectionCandidates &proposals, std::vector &dnnRes, const NetConfig &netConfig); + + static void postProcess_unimplemented(DetectionCandidates &proposals, std::vector &dnnRes, const NetConfig &netConfig); + + //! If true, filter the detections removing the ones for which the bbox does not respect area(bbox) € [mean_class(area) * ratio; mean_class(area) / ratio] + bool m_applySizeFilterAfterNMS; + //! Buffer for the blob in input net + cv::Mat m_blob; + //! Buffer for gray to RGBa image conversion + vpImage m_I_color; + //! Buffer for the input image + cv::Mat m_img; + //! Indices for NMS + std::vector m_indices; + //! DNN network + cv::dnn::Net m_net; + //! Configuration of the DNN + NetConfig m_netConfig; + //! Names of layers with unconnected outputs + std::vector m_outNames; + //! Contains all output blobs for each layer specified in m_outNames + std::vector m_dnnRes; + //! Pointer towards the parsing method, used if \b m_parsingMethodType is equal to \b m_parsingMethodType::USER_SPECIFIED + void (*m_parsingMethod)(DetectionCandidates &, std::vector &, const NetConfig &); +}; + +/*! + * Display the bbox and score of the detected object in an image. + * + * \param[in] img : Image used as background. + * \param[in] color : Color used to draw the CAD model. + * \param[in] thickness : Thickness used to draw the CAD model. + */ +template < typename Type > +inline void +vpDetectorDNNOpenCV::DetectedFeatures2D::display(const vpImage< Type > &img, const vpColor &color, unsigned int thickness) const +{ + vpDisplay::displayRectangle(img, m_bbox, color, false, thickness); + + std::stringstream ss; + if (m_classname) { + ss << *m_classname; + } + else { + ss << m_cls; + } + ss << "(" << std::setprecision(4) << m_score * 100. << "%)"; + vpDisplay::displayText(img, m_bbox.getTopRight(), ss.str(), color); +} +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/detection/vpDetectorDataMatrixCode.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/detection/vpDetectorDataMatrixCode.h new file mode 100644 index 00000000..343779f1 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/detection/vpDetectorDataMatrixCode.h @@ -0,0 +1,124 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Base class for bar code detection. + */ + +#ifndef _vpDetectorDataMatrixCode_h_ +#define _vpDetectorDataMatrixCode_h_ + +#include +#include +#include + +#include + +#ifdef VISP_HAVE_DMTX + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpDetectorDataMatrixCode + * \ingroup group_detection_barcode + * Base class for bar code detector. This class is a wrapper over libdmtx + * available from http://www.libdmtx.org. Installation instructions are + * provided here https://visp.inria.fr/3rd_dmtx. + * + * The detect() function allows to detect multiple QR codes in an image. Once + * detected, for each QR code it is possible to retrieve the location of the + * corners using getPolygon(), the encoded message using getMessage(), the + * bounding box using getBBox() and the center of gravity using getCog(). + * + * The following sample code shows how to use this class to detect QR codes in + * an image. + * \code + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * #ifdef VISP_HAVE_DMTX + * vpImage I; + * vpImageIo::read(I, "bar-code.jpg"); + * + * vpDetectorDataMatrixCode detector; + * + * bool status = detector.detect(I); + * if (status) { + * for(size_t i=0; i < detector.getNbObjects(); i++) { + * std::cout << "Bar code " << i << ":" << std::endl; + * std::vector p = detector.getPolygon(i); + * for(size_t j=0; j < p.size(); j++) + * std::cout << " Point " << j << ": " << p[j] << std::endl; + * std::cout << " Message: \"" << detector.getMessage(i) << "\"" << std::endl; + * } + * } + * #endif + * } + * \endcode + * + * The previous example may produce results like: + * \code + * Bar code 0: + * Point 0: 273.21, 78.9799 + * Point 1: 390.016, 85.1014 + * Point 2: 388.024, 199.185 + * Point 3: 269.23, 192.96 + * Message: "datamatrix 1" + * Bar code 1: + * Point 0: 262.23, 396.404 + * Point 1: 381.041, 402.631 + * Point 2: 378.92, 524.188 + * Point 3: 257.916, 519.962 + * Message: "datamatrix 2" + * \endcode + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in multiple QR codes detection, you may have a look at:
+ * + * - \ref tutorial-detection-barcode + */ + class VISP_EXPORT vpDetectorDataMatrixCode : public vpDetectorBase +{ +public: + vpDetectorDataMatrixCode(); + bool detect(const vpImage &I) VP_OVERRIDE; +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/detection/vpDetectorFace.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/detection/vpDetectorFace.h new file mode 100644 index 00000000..0fea6187 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/detection/vpDetectorFace.h @@ -0,0 +1,117 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Detect faces. + */ + +#ifndef VP_DETECTOR_FACE_H +#define VP_DETECTOR_FACE_H + +#include + +#if defined(VISP_HAVE_OPENCV) && \ + (((VISP_HAVE_OPENCV_VERSION < 0x050000) && defined(HAVE_OPENCV_OBJDETECT)) || \ + ((VISP_HAVE_OPENCV_VERSION >= 0x050000) && defined(HAVE_OPENCV_XOBJDETECT))) + +#include // needed by (std::min) in opencv2/objdetect/objdetect.hpp + +#include +#include + +#if (VISP_HAVE_OPENCV_VERSION < 0x050000) +#include +#elif (VISP_HAVE_OPENCV_VERSION >= 0x050000) +#include +#endif + +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpDetectorFace + * \ingroup group_detection_face + * The vpDetectorFace class is a wrapper over OpenCV Haar cascade face + * detection capabilities. To use this class ViSP should be build against + * OpenCV 2.2.0 or a more recent version. Installation instructions are provided + * here https://visp.inria.fr/3rd_opencv. + * + * The following sample code shows how to use this class to detect the largest + * face in the image. The cascade classifier file + * "haarcascade_frontalface_alt.xml" can be found in ViSP source code or in + * OpenCV. + * \code + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * #if defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_OBJDETECT) + * vpImage I; + * vpDetectorFace face_detector; + * face_detector.setCascadeClassifierFile("haarcascade_frontalface_alt.xml"); + * + * while(1) { + * // Acquire a new image in I + * bool face_found = face_detector.detect(I); + * if (face_found) { + * vpRect face_bbox = face_detector.getBBox(0); // largest face has index 0 + * } + * } + * #endif + * } + * \endcode + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you want a more complete example that works with images acquired from a camera, you may have a look at:
+ * + * - \ref tutorial-detection-face +*/ +class VISP_EXPORT vpDetectorFace : public vpDetectorBase +{ +protected: + std::vector m_faces; //!< Bounding box of each detected face. + cv::CascadeClassifier m_face_cascade; //!< Haar cascade classifier file name. + cv::Mat m_frame_gray; //!< OpenCV image used as input for the face detection. + +public: + vpDetectorFace(); + + bool detect(const vpImage &I) VP_OVERRIDE; + bool detect(const cv::Mat &frame_gray); + void setCascadeClassifierFile(const std::string &filename); +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/detection/vpDetectorQRCode.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/detection/vpDetectorQRCode.h new file mode 100644 index 00000000..5d7d5fd2 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/detection/vpDetectorQRCode.h @@ -0,0 +1,130 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Base class for bar code detection. + */ + +#ifndef _vpDetectorQRCode_h_ +#define _vpDetectorQRCode_h_ + +#include +#include +#include + +#include + +#ifdef VISP_HAVE_ZBAR + +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpDetectorQRCode + * \ingroup group_detection_barcode + * Base class for bar code detector. This class is a wrapper over libzbar + * available from http://zbar.sourceforge.net. Installation instructions are + * provided here https://visp.inria.fr/3rd_zbar. + * + * The detect() function allows to detect multiple QR codes in an image. Once + * detected, for each QR code it is possible to retrieve the location of the + * corners using getPolygon(), the encoded message using getMessage(), the + * bounding box using getBBox() and the center of gravity using getCog(). + * + * The following sample code shows how to use this class to detect QR codes in + * an image. + * \code + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * #ifdef VISP_HAVE_ZBAR + * vpImage I; + * vpImageIo::read(I, "bar-code.jpg"); + * + * vpDetectorQRCode detector; + * + * bool status = detector.detect(I); + * if (status) { + * for(size_t i=0; i < detector.getNbObjects(); i++) { + * std::cout << "Bar code " << i << ":" << std::endl; + * std::vector p = detector.getPolygon(i); + * for(size_t j=0; j < p.size(); j++) + * std::cout << " Point " << j << ": " << p[j] << std::endl; + * std::cout << " Message: \"" << detector.getMessage(i) << "\"" << std::endl; + * } + * } + * #endif + * } + * \endcode + * + * The previous example may produce results like: + * \code + * Bar code 0: + * Point 0: 48, 212 + * Point 1: 57, 84 + * Point 2: 188, 92 + * Point 3: 183, 220 + * Message: "qrcode 2" + * Bar code 1: + * Point 0: 26, 550 + * Point 1: 35, 409 + * Point 2: 174, 414 + * Point 3: 167, 555 + * Message: "qrcode 1" + * \endcode + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in QR code detection, you may have a look at:
+ * + * - \ref tutorial-detection-barcode + * - \ref tutorial-pose-estimation-qrcode +*/ +class VISP_EXPORT vpDetectorQRCode : public vpDetectorBase +{ +protected: + zbar::ImageScanner m_scanner; //!< QR code detector. + +public: + vpDetectorQRCode(); + bool detect(const vpImage &I) VP_OVERRIDE; +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/dnn_tracker/vpMegaPose.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/dnn_tracker/vpMegaPose.h new file mode 100644 index 00000000..39194b0f --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/dnn_tracker/vpMegaPose.h @@ -0,0 +1,262 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * MegaPose wrapper. + */ +#ifndef VP_MEGAPOSE_H +#define VP_MEGAPOSE_H + +#include +#if defined(VISP_HAVE_NLOHMANN_JSON) && defined(VISP_HAVE_THREADS) + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include VISP_NLOHMANN_JSON(json.hpp) + +BEGIN_VISP_NAMESPACE +/** + * \class vpMegaPoseEstimate + * \ingroup module_dnn_tracker + * Result from a pose estimation performed by MegaPose. + * Contains: + * - the estimated pose as a vpHomogeneousMatrix + * - The confidence score between 0-1. + * This score is defined as 1 if the current pose is in the basin of attraction of the true pose + * (whether the MegaPose refiner can converge to the true pose). As such, it should be used to detect divergence. + * - The bounding box of the object in image space. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using tracking an object using Megapose, you may have a look at:
+ * + * - \ref tutorial-tracking-megapose + * - \ref tutorial-megapose-model +*/ +class vpMegaPoseEstimate +{ +public: + vpMegaPoseEstimate() : score(0.f) { } + vpHomogeneousMatrix cTo; + float score; + vpRect boundingBox; +}; + +// Don't use the default ViSP JSON conversion to avoid potential regressions (that wouldn't be detected) +// and very specific parsing on the server side +inline void from_megapose_json(const nlohmann::json &j, vpHomogeneousMatrix &T) +{ + std::vector values = j; + assert(values.size() == 16); + std::copy(values.begin(), values.end(), T.data); +} + +inline void to_megapose_json(nlohmann::json &j, const vpHomogeneousMatrix &T) +{ + std::vector values; + values.reserve(16); + for (unsigned i = 0; i < 16; ++i) { + values.push_back(T.data[i]); + } + j = values; +} + +inline void to_megapose_json(nlohmann::json &j, const vpRect &d) +{ + std::vector values = { + d.getLeft(), d.getTop(), d.getRight(), d.getBottom() + }; + j = values; +} + +inline void from_megapose_json(const nlohmann::json &j, vpRect &d) +{ + std::vector values = j.get>(); + assert((values.size() == 4)); + d.setLeft(values[0]); + d.setTop(values[1]); + d.setRight(values[2]); + d.setBottom(values[3]); +} + +inline void from_json(const nlohmann::json &j, vpMegaPoseEstimate &m) +{ + m.score = j["score"]; + from_megapose_json(j.at("cTo"), m.cTo); + from_megapose_json(j.at("boundingBox"), m.boundingBox); +} + +/** + * \class vpMegaPose + * \ingroup module_dnn_tracker + * Class to communicate with a MegaPose server. + * MegaPose is a deep learning-based method to estimate the 6D pose of novel objects, meaning that it does not require training for a specific object. + * MegaPose is a multistage method and can be used either as a full pose estimation algorithm or as a tracker. + * MegaPose works by render and compare: a synthetized view of an object is compared with a real-world image to see if the poses match. + * Behind the scene, there are two main models: + * - The coarse model: given an image, multiple synthetic view are compared with the "true" object image and a model outputs the probability of a render corresponding to the same object as the true image. This model requires the object to be detected (bounding box) in the image. + * - The refiner model: Given an initial pose estimate, this model predicts a pose displacement to best align render and true image. It is called iteratively, for a fixed number of iterations. + * + * This can be best visualized in the figure below (taken from \cite Labbe2022Megapose): + * \image html megapose_architecture.jpg + * + * For more information on how the model works, see The MegaPose Github page or the paper \cite Labbe2022Megapose. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * For instructions on how to install the Python server and an example usage, have a look at:
+ * + * - \ref tutorial-tracking-megapose +*/ +class VISP_EXPORT vpMegaPose +{ +public: + /** + * Enum to communicate with python server. + * Used to map to a string, which the client and server check to see what message they are getting/expecting. + */ + enum ServerMessage + { + UNKNOWN = 0, + ERR = 1, //! An error occurred server side + OK = 2, //! Server has successfully completed operation, no return value expected + GET_POSE = 3, //! Ask the server to estimate poses + RET_POSE = 4, //! Code sent when server returns pose estimates + GET_VIZ = 5, //! Ask the server for a rendering of the object + RET_VIZ = 6, //! Code sent when server returns the rendering of an object + SET_INTR = 7, //! Set the intrinsics for the MegaPose server + GET_SCORE = 8, //! Ask the server to score a pose estimate + RET_SCORE = 9, //! Code sent when server returns a pose score + SET_SO3_GRID_SIZE = 10, //! Ask the server to set the number of samples for coarse estimation + GET_LIST_OBJECTS = 11, + RET_LIST_OBJECTS = 12, + EXIT = 13 + }; + /** + * Instantiates a connection to a MegaPose server. + * The server should already be started and listening at host:port. + * \param[in] host : The host to connect to (IP address). + * \param[in] port : The port on which the server is listening for incoming connections. + * \param[in] cam : Intrinsics of the camera with which the images sent to the MegaPose server are acquired. + * \param[in] height : Height of the images sent to the server. + * \param[in] width : Width of the images sent to the server. + */ + vpMegaPose(const std::string &host, int port, const vpCameraParameters &cam, unsigned height, unsigned width); + + /** + * Estimate the poses of objects (in the frame of the camera c) with MegaPose. + * The object origins used to estimate the poses are those used by the MegaPose server. + * \param[in] image : The image, acquired by camera c, used to estimate the pose of the objects. + * \param[in] objectNames : Names of the objects for which to estimate the pose. The name of the object should be known by the MegaPose server. + * An object name can appear multiple times if multiple instances of the object are in the image and their pose should estimated. + * \param[in] depth : An optional depth image, that must be aligned with the RGB image. If provided, the MegaPose server should be configure to use the depth. + * Note that the using depth may lead to a noisy estimation and should not always be preferred. + * \param[in] depthToM : A scale factor that is used to convert the depth map into meters. If depth is null, the value is ignored. + * \param[in] detections : The bounding boxes of the objects \e objectNames. Used only for the coarse model, which uses the size of the bounding box to generate initial guesses. + * If specified, should be the same size as \e objectNames. The bounding box at index i will be for the object i. + * \param[in] initial_cTos : An optional initial pose estimate for each object present in the image. If not null, then the MegaPose server only runs the refiner model, + * which is faster but only allows for smaller corrections (depending on the number of refiner iterations). + * If specified, should be the same size as \e objectNames. The initial pose estimate at index i will be for the object at index i. + * \param[in] refinerIterations : Number of MegaPose refiner iterations to be performed. + * + * \return A list of vpMegaPoseEstimate, one for each input object, (same length as \e objectNames) + */ + std::vector estimatePoses(const vpImage &image, const std::vector &objectNames, + const vpImage *const depth = nullptr, const double depthToM = 0.f, + const std::vector *const detections = nullptr, + const std::vector *const initial_cTos = nullptr, + int refinerIterations = -1); + /** + * Score the input poses with MegaPose. The score, between 0 and 1, indicates whether the refiner model can converge to the correct pose. + * As such, it should mainly be used to detect divergence, and not the quality of the pose estimate. + * + * \param[in] image : The input RGB image in which the objects are located. + * \param[in] objectNames : The detected objects. Names should be known by the MegaPose server. + * \param[in] cTos : The object poses to be scored. + * + * \return A list of scores, each between 0 and 1. Returns one score per object (the result has the same length as objectNames) + */ + std::vector scorePoses(const vpImage &image, const std::vector &objectNames, + const std::vector &cTos); + + /** + * Set the camera parameters for the MegaPose server. + * \param[in] cam : The camera intrinsics. + * \param[in] height : The incoming images' height. + * \param[in] width : The incoming images' width. + * Note that the height and width should be equal to the width of the images used in estimatePoses or scorePoses. Otherwise an exception will be thrown. + */ + void setIntrinsics(const vpCameraParameters &cam, unsigned height, unsigned width); + + vpImage viewObjects(const std::vector &objectNames, + const std::vector &poses, const std::string &viewType); + + /** + * Set the number of renders used for coarse pose estimation by MegaPose. + * \param[in] num : The number of renders for full pose estimation by MegaPose. This number should be equal to 72, 512, 576 or 4608. + */ + void setCoarseNumSamples(const unsigned num); + + /** + * @brief Query the server to find the name of all of the objects it knows. + * @return The names of the objects known by the server + */ + std::vector getObjectNames(); + + ~vpMegaPose(); + +private: + // Server connection data + int m_serverSocket; + int m_fd; + + std::mutex m_mutex; // Since client-server communications are synchronous, avoid multiple parallel communications + + void makeMessage(const vpMegaPose::ServerMessage messageType, std::vector &data) const; + std::pair> readMessage() const; + + const static std::unordered_map m_codeMap; + static std::string messageToString(const vpMegaPose::ServerMessage messageType); + static vpMegaPose::ServerMessage stringToMessage(const std::string &s); +}; + +END_VISP_NAMESPACE +#endif // VISP_HAVE_NLOHMANN_JSON +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/dnn_tracker/vpMegaPoseTracker.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/dnn_tracker/vpMegaPoseTracker.h new file mode 100644 index 00000000..5dd9f7ed --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/dnn_tracker/vpMegaPoseTracker.h @@ -0,0 +1,171 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Tracker based on MegaPose. + */ + +#ifndef _vpMegaPoseTracker_h_ +#define _vpMegaPoseTracker_h_ + +#include +#if defined(VISP_HAVE_NLOHMANN_JSON) && defined(VISP_HAVE_THREADS) + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/** + * \class vpMegaPoseTracker + * \ingroup module_dnn_tracker + * + * @brief A simplified interface to track a single object with MegaPose. + * This tracker works asynchronously: A call to init or track will not stop the current thread. Rather, an std::future object is returned, and its result should be acquired when it is ready. + * + * To instantiate and use the tracker: + * + * \code{.cpp} + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpCameraParameters cam; + * cam.initPersProjWithoutDistortion(500.0, 500.0, 320.0, 240.0); + * std::shared_ptr megapose; + * try { + * megapose = std::make_shared("127.0.0.1", 5555, cam, 480, 640); + * } + * catch (vpException &e) { + * throw vpException(vpException::ioError, "Could not connect to MegaPose server."); + * } + * vpMegaPoseTracker megaposeTracker(megapose, "my_object_name", 1); + * //... + * vpRect detection; + * vpImage I(480, 640); + * // Perform object detection for init + * // detection = ...; + * // Acquire Image I + * // I = ...; + * std::future futurePoseEstimate; + * + * futurePoseEstimate = megaposeTracker.init(I, detection); + * // Do something else + * vpMegaPoseEstimate estimate = futurePoseEstimate.get(); // Block and await result + * bool callMegapose = true; // True when we should call megapose + * while(true) { // Run continuously, update results when MegaPose returns new results + * // I = ...; + * if (!callMegapose && futurePosEstimate.wait_for(std::chrono::milliseconds(0)) == std::future_status::ready) { + * // process result + * callMegapose = true; + * } + * if (callMegapose) { + * futurePoseEstimate = megaposeTracker.track(I); + * callMegapose = false; + * } + * } + * } + * \endcode + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using tracking an object using Megapose, you may have a look at:
+ * + * - \ref tutorial-tracking-megapose + * - \ref tutorial-megapose-model +*/ +class VISP_EXPORT vpMegaPoseTracker +{ +public: + /** + * @brief Construct a new MegaPose tracker. + * + * @param[in] megapose : A valid connection to the MegaPose server. + * @param[in] objectLabel : The name of the object to track. + * @param[in] refinerIterations : Number of refiner iterations to perform every time init or track is called. Impacts performance. + */ + vpMegaPoseTracker(std::shared_ptr megapose, const std::string &objectLabel, const int refinerIterations) : + m_megapose(megapose), m_objectLabel(objectLabel), m_refinerIterations(refinerIterations), m_initialized(false) + { } + /** + * @brief Initialize tracking. Performs a full object pose estimation with megapose. + * + * This is slower than tracking. + * + * Requires a bounding box of the object to track. + * + * @param[in] I : The image in which the object is located. + * @param[in] bb : The bounding box of the object. + * @return A future object that will contain the result of the pose estimation. + */ + std::future init(const vpImage &I, const vpRect &bb); + /** + * @brief Initialize tracking from an initial pose. The initial pose should be in the neighborhood of the true pose. + * The pose should be expressed in the camera frame. + * This method will call MegaPose to correct the initial estimate and initialize the tracking. + * + * @param[in] I : The image in which the object is located. + * @param[in] cTo : An initial, coarse, estimate of the object pose. + * @return A future object that will contain the result of the pose estimation. + */ + std::future init(const vpImage &I, const vpHomogeneousMatrix &cTo); + + + /** + * @brief Track the object in the image. Requires the tracker to be initialized by calling init. + * + * @param[in] I : The image containing the object to track. + * @return A future object that will contain the result of the pose estimation. + */ + std::future track(const vpImage &I); + + /** + * @brief Update the current pose estimate with a new one, provided by an external source. + * No operation (such as init or track) should be running. + * + * @param[in] cTo : The new pose estimate. + */ + void updatePose(const vpHomogeneousMatrix &cTo); + +private: + std::shared_ptr m_megapose; + vpMegaPoseEstimate m_poseEstimate; + std::string m_objectLabel; + int m_refinerIterations; + bool m_initialized; +}; +END_VISP_NAMESPACE +#endif // VISP_HAVE_NLOHMANN_JSON + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpColorBlindFriendlyPalette.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpColorBlindFriendlyPalette.h new file mode 100644 index 00000000..b8276621 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpColorBlindFriendlyPalette.h @@ -0,0 +1,198 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Palette of colors that is said to be color-blind people friendly. + * This palette has been found on https://jfly.uni-koeln.de/color/#see + */ + +#ifndef _vpColorBlindFliendlyPalette_h_ +#define _vpColorBlindFliendlyPalette_h_ + +#include + +#include +#include + +#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98) +/** + * \brief Class that furnishes a set of colors that color blind people + * should be able to distinguish one from another. + */ +BEGIN_VISP_NAMESPACE +class VISP_EXPORT vpColorBlindFriendlyPalette +{ +public: + /** + * \brief Enum that list the different available colors. + */ + typedef enum class Palette + { + Black = 0, /*!< Color that looks like black for non-colorblind people.*/ + Orange = 1, /*!< Color that looks like orange for non-colorblind people.*/ + SkyBlue = 2, /*!< Color that looks like sky blue for non-colorblind people.*/ + Green = 3, /*!< Color that looks like green for non-colorblind people.*/ + Yellow = 4, /*!< Color that looks like yellow for non-colorblind people.*/ + Blue = 5, /*!< Color that looks like blue for non-colorblind people.*/ + Vermillon = 6, /*!< Color that looks like vermilion for non-colorblind people.*/ + Purple = 7, /*!< Color that looks like purple for non-colorblind people.*/ + COUNT = 8 /*!< Number of colors the palette proposes.*/ + }Palette; + + static std::vector s_paletteNames; /*!< Vector that lists the names of the different available colors.*/ + + /** + * \brief Construct a new vp Color Blind Friendly Palette. + * The default value \b vpColorBlindFriendlyPalette::Palette::COUNT is affected. + * + */ + vpColorBlindFriendlyPalette(); + + /** + * \brief Construct a new vp Color Blind Friendly Palette object. + * + * \param colorID \b vpColorBlindFriendlyPalette::Palette that permits to determine the RGB values + * that we must affect to it. + */ + vpColorBlindFriendlyPalette(const Palette &colorID); + + /** + * \brief Construct a new vp Color Blind Friendly Palette object from the name of the color. + * \b WARNING: if the color is not found, it will be set to \b vpColorBlindFriendlyPalette::Palette::COUNT. + * + * \param nameColor + */ + vpColorBlindFriendlyPalette(const std::string &nameColor); + + /** + * @brief Get the \b vpColorBlindFriendlyPalette::Palette the object corresponds to. + * + * @return Palette Return \b vpColorBlindFriendlyPalette::_colorID. + */ + Palette get_colorID() const; + + /** + * \brief Cast a \b vpColorBlindFriendlyPalette in a \b vpColor object. + * A \b vpColorBlindFriendlyPalette::Palette::COUNT object is set as white (255, 255, 255). + * + * \return vpColor The \b vpColor corresponding to the RGB values of the \b vpColorBlindFriendlyPalette . + */ + vpColor to_vpColor() const; + + /** + * \brief Cast a \b vpColorBlindFriendlyPalette in a vector {R, G, B}. + * A \b vpColorBlindFriendlyPalette::Palette::COUNT object is set as white, i.e. {255, 255, 255}. + * + * \return std::vector A vector containing the R, g, B components (in his order), expressed + * as an unsigned char (so between 0 and 255). + */ + std::vector to_RGB() const; + + /** + * \brief Cast the object in a vector of doubles that belong to the range [0; 1]. The + * initial R, G, B values are divided by 255. + * For instance a pixel whose RGB values would be {0; 127.5; 255.} would be transformed in + * a vector {0.; 0.5; 1.0}. + * + * \return std::vector R, G, B values normalized in doubles in the range [0; 1.] by + * dividing the original values by 255. + */ + std::vector to_colorRatio() const; + + /** + * \brief Set the fromString object + * + * \param nameColor + * \return true if a match was found and it was possible to set \b _colorID correctly. + * \return false if the input string \b nameColor did not match any known names. + */ + bool set_fromString(const std::string &nameColor); + + /** + * \brief Get the name of the \b vpColorBlindFriendlyPalette object. + * + * \return std::string The name of the color. + */ + std::string to_string() const; + + /** + * \brief Get the list of available colors names. + * + * \param prefix Optional prefix that will be written before starting the list. + * \param separator Optional separator between each member of the list. + * \param suffix Optional suffix that will be written after ending the list. + * \return std::string The list of names of the available colors. + */ + static std::string getAvailableColorsNames(const std::string &prefix = "", const std::string &separator = " ", const std::string &suffix = ""); +private: + static std::vector s_palette; /*!< A vector that contains the \b vpColor corresponding to the \b vpColorBlindFriendlyPalette in terms of R, g, B values.*/ + + /** + * \brief Cast \b vpColorBlindFriendlyPalette::Palette enum value into an unsigned int. + * + * \param colorID A \b vpColorBlindFriendlyPalette::Palette enum + * value we are interested in knowing the value, expressed as unsigned int. + * \return unsigned int that matches the value of \b colorID . + */ + static unsigned int to_uint(const Palette &colorID); + + /** + * \brief Get the name that corresponds to \b colorID . + * + * \param colorID A \b vpColorBlindFriendlyPalette::Palette enum + * value we are interested in knowing the name. + * \return std::string The corresponding name. + */ + static std::string to_string(const Palette &colorID); + + Palette m_colorID; /*!< The ID of the color in the \b vpColorBlindFriendlyPalette::Palette.*/ +}; +END_VISP_NAMESPACE + +/** + * \brief Permit to display in a \b std::ostream the name of the \b color. + * + * \param os The stream in which we want to write the name of the palette. + * \param color The color we are interested in displaying the name. + * \return std::ostream& The stream, in which the name of the \b color has been written. + */ + std::ostream &operator<<(std::ostream &os, const VISP_NAMESPACE_ADDRESSING vpColorBlindFriendlyPalette &color); + + /** + * \brief Permits to initialize a \b vpColorBlindFriendlyPalette by reading its name in + * an \b std::istream. + * + * \param is A stream from which we will read the name of \b color in order to initialized it. + * \param color The color we want to initialized. + * \return std::istream& The \b is input, from which we have read the name of \b color. + */ +std::istream &operator>>(std::istream &is, VISP_NAMESPACE_ADDRESSING vpColorBlindFriendlyPalette &color); + +#endif +#endif // _vpColorBlindFliendlyPalette_h_ diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpD3DRenderer.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpD3DRenderer.h new file mode 100644 index 00000000..47417ea3 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpD3DRenderer.h @@ -0,0 +1,212 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * D3D renderer for windows 32 display + */ + +#ifndef VP_D3D_RENDERER_H +#define VP_D3D_RENDERER_H + +#include + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +#if (defined(VISP_HAVE_D3D9)) + + +// Mute warning with clang-cl +// warning : non-portable path to file ''; specified path differs in case from file name on disk [-Wnonportable-system-include-path] +// warning : non-portable path to file ''; specified path differs in case from file name on disk [-Wnonportable-system-include-path] +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wnonportable-system-include-path" +#endif + +// Include WinSock2.h before windows.h to ensure that winsock.h is not +// included by windows.h since winsock.h and winsock2.h are incompatible +#include +#include +#include +#include + + +#include + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +#include + +BEGIN_VISP_NAMESPACE + +/*! + * \class vpD3DRenderer.h + * + * \brief Display under windows using Direct3D9. + * Is used by vpDisplayD3D to do the drawing. +*/ +class VISP_EXPORT vpD3DRenderer : public vpWin32Renderer +{ + IDirect3D9 *pD3D; + + // The d3d device we will be working with. + IDirect3DDevice9 *pd3dDevice; + + // Sprite used to render the texture. + ID3DXSprite *pSprite; + + // The system memory texture : + // The one we will be drawing on. + IDirect3DTexture9 *pd3dText; + + // The video memory texture : + // The one we will use for display. + IDirect3DTexture9 *pd3dVideoText; + + // The texture's width. + unsigned int textWidth; + + // The window's handle. + HWND hWnd; + + // Colors for overlay drawn with d3d directly. + unsigned long colors[vpColor::id_unknown]; + + // Colors for overlay drawn with GDI. + COLORREF colorsGDI[vpColor::id_unknown]; + + // Font used for text drawing. + HFONT hFont; + +public: + bool init(HWND hwnd, unsigned int width, unsigned int height); + bool render(); + + vpD3DRenderer(); + virtual ~vpD3DRenderer() VP_OVERRIDE; + + void setImg(const vpImage &im); + void setImg(const vpImage &im); + void setImgROI(const vpImage &im, const vpImagePoint &iP, unsigned int width, unsigned int height); + void setImgROI(const vpImage &im, const vpImagePoint &iP, unsigned int width, unsigned int height); + + void setPixel(const vpImagePoint &iP, const vpColor &color); + + void drawLine(const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color, unsigned int thickness, + int style = PS_SOLID); + + void drawRect(const vpImagePoint &topLeft, unsigned int width, unsigned int height, const vpColor &color, + bool fill = false, unsigned int thickness = 1); + + void clear(const vpColor &color); + + void drawCircle(const vpImagePoint ¢er, unsigned int radius, const vpColor &color, bool fill = false, + unsigned int thickness = 1); + + void drawText(const vpImagePoint &ip, const char *text, const vpColor &color); + + void drawCross(const vpImagePoint &ip, unsigned int size, const vpColor &color, unsigned int thickness = 1); + + void drawArrow(const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color, unsigned int w, unsigned int h, + unsigned int thickness = 1); + + void getImage(vpImage &I) VP_OVERRIDE; + +private: + void initView(float, float); + + /*! + Sub function for circle drawing. + Circle drawing is based on Bresenham 's circle algorithm. + */ + void subDrawCircle(int i, int j, int x, int y, vpColor col, unsigned char *buf, unsigned int pitch, unsigned int maxX, + unsigned int maxY); + + void convert(const vpImage &I, unsigned char *imBuffer, unsigned int pitch); + void convert(const vpImage &I, unsigned char *imBuffer, unsigned int pitch); + void convertROI(const vpImage &I, unsigned char *imBuffer, unsigned int pitch, int i_min, int j_min, + int i_max, int j_max); + void convertROI(const vpImage &I, unsigned char *imBuffer, unsigned int pitch, int i_min, int j_min, + int i_max, int j_max); + + /*! + Safe useful inline function to set a pixel in a texture buffer. + \param buf The texture's buffer. + \param pitch The image pitch. + \param x The x-coordinate of the pixel (in the locked rectangle base) + \param y The y-coordinate of the pixel (in the locked rectangle base) + \param color The color of the pixel. + \param maxX The maximum x value (equals to the width of the locked + rectangle). \param maxY The maximum y value (equals to the height of the + locked rectangle). + + */ + inline void setBufferPixel(unsigned char *buf, unsigned int pitch, int x, int y, const vpColor &color, + unsigned int maxX, unsigned int maxY) + { + unsigned long c; + if (color.id < vpColor::id_unknown) + c = colors[color.id]; + else { + c = D3DCOLOR_ARGB(0xFF, color.R, color.G, color.B); + } + + if (x >= 0 && y >= 0 && x <= static_cast(maxX) && y <= static_cast(maxY)) + *(unsigned long *)(buf + (y * pitch) + (x << 2)) = c; // colors[color]; + } + /*! + Unsafe useful inline function to set a pixel in a texture buffer. + \param buf The texture's buffer. + \param pitch The image pitch. + \param x The x-coordinate of the pixel (in the locked rectangle base) + \param y The y-coordinate of the pixel (in the locked rectangle base) + \param color The color of the pixel. + + */ + inline void setBufferPixel(unsigned char *buf, unsigned int pitch, int x, int y, const vpColor &color) + { + unsigned long c; + if (color.id < vpColor::id_unknown) + c = colors[color.id]; + else { + c = D3DCOLOR_ARGB(0xFF, color.R, color.G, color.B); + } + + *(unsigned long *)(buf + (y * pitch) + (x << 2)) = c; // colors[color]; + } + + unsigned int supPowerOf2(unsigned int n); +}; +#endif + +END_VISP_NAMESPACE + +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayD3D.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayD3D.h new file mode 100644 index 00000000..2925185b --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayD3D.h @@ -0,0 +1,121 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Windows 32 display using D3D + */ + +#ifndef VP_DISPLAY_D3D_H +#define VP_DISPLAY_D3D_H + +#include +#include +#if (defined(VISP_HAVE_D3D9)) +#include + +BEGIN_VISP_NAMESPACE + +/*! + * \class vpDisplayD3D + * + * \ingroup group_gui_display + * + * \brief Display for windows using Direct3D 3rd party. Thus to enable this + * class Direct3D should be installed. Installation instructions are provided + * here https://visp.inria.fr/3rd_d3d/ + * + * Direct3D is part of the DirectX API available under Windows + * operating systems. + * + * \warning Requires DirectX9 SDK to compile and DirectX9 DLLs to run. + * + * The example below shows how to display an image with this video device. + * \code + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * #if defined(VISP_HAVE_D3D9) + * vpImage I; // Grey level image + * + * // Read an image in PGM P5 format + * vpImageIo::read(I, "C:/Temp/visp-images/Klimt/Klimt.pgm"); + * + * vpDisplayD3D d; + * + * // Initialize the display with the image I. Display and image are + * // now link together. + * d.init(I); + * + * // Specify the window location + * vpDisplay::setWindowPosition(I, 400, 100); + * + * // Set the display window title + * vpDisplay::setTitle(I, "My Direct 3D display"); + * + * // Set the display background with image I content + * vpDisplay::display(I); + * + * // Draw a red rectangle in the display overlay (foreground) + * vpImagePoint topLeftCorner; + * topLeftCorner.set_i(10); + * topLeftCorner.set_j(20); + * vpDisplay::displayRectangle(I, topLeftCorner, 100, 20, vpColor::red, true); + * + * // Flush the foreground and background display + * vpDisplay::flush(I); + * + * // Wait for a click in the display window + * vpDisplay::getClick(I); + * #endif + * } + * \endcode +*/ +class VISP_EXPORT vpDisplayD3D : public vpDisplayWin32 +{ +public: + vpDisplayD3D(); + vpDisplayD3D(int winx, int winy, const std::string &title = ""); + vpDisplayD3D(vpImage &I, vpScaleType type); + vpDisplayD3D(vpImage &I, int winx = -1, int winy = -1, const std::string &title = "", + vpScaleType type = SCALE_DEFAULT); + vpDisplayD3D(vpImage &I, vpScaleType type); + vpDisplayD3D(vpImage &I, int winx = -1, int winy = -1, const std::string &title = "", + vpScaleType type = SCALE_DEFAULT); + +}; + +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayFactory.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayFactory.h new file mode 100644 index 00000000..430dbb07 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayFactory.h @@ -0,0 +1,309 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Display Factory + */ + +#ifndef VP_DISPLAY_FACTORY_H +#define VP_DISPLAY_FACTORY_H + +#include +#include +#include +#include +#include +#include +#include + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) +#include +#endif + +#if defined(__clang__) +// Mute warning : '\tparam' command used in a comment that is not attached to a template declaration [-Wdocumentation] +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +BEGIN_VISP_NAMESPACE +/** + * \ingroup group_gui_display +*/ +namespace vpDisplayFactory +{ +/** + * \brief Return a newly allocated vpDisplay specialization + * if a GUI library is available or nullptr otherwise. + * + * \return A newly allocated vpDisplay specialization + * if a GUI library is available or nullptr otherwise. + */ +inline vpDisplay *allocateDisplay() +{ +#if defined(VISP_HAVE_DISPLAY) +#ifdef VISP_HAVE_X11 + return new vpDisplayX(); +#elif defined(VISP_HAVE_D3D9) + return new vpDisplayD3D(); +#elif defined(VISP_HAVE_GDI) + return new vpDisplayGDI(); +#elif defined(VISP_HAVE_GTK) + return new vpDisplayGTK(); +#elif defined(HAVE_OPENCV_HIGHGUI) + return new vpDisplayOpenCV(); +#endif +#else + return nullptr; +#endif +} + +/** + * \brief Return a newly allocated vpDisplay specialization initialized with \b I + * if a GUI library is available or nullptr otherwise. + * + * \tparam T : Any type that an image can handle and that can be displayed. + * \param[in] I : The image the display must be initialized with. + * \param[in] winx : The horizontal position of the display on the screen. + * \param[in] winy : The vertical position of the display on the screen. + * \param[in] title : The title of the display. + * \param[in] scaleType : If this parameter is set to: + * - vpDisplay::SCALE_AUTO, the display size is adapted to ensure the image is fully displayed in the screen; + * - vpDisplay::SCALE_DEFAULT or vpDisplay::SCALE_1, the display size is the same than the image size. + * - vpDisplay::SCALE_2, the display size is down scaled by 2 along the lines and the columns. + * - vpDisplay::SCALE_3, the display size is down scaled by 3 along the lines and the columns. + * - vpDisplay::SCALE_4, the display size is down scaled by 4 along the lines and the columns. + * - vpDisplay::SCALE_5, the display size is down scaled by 5 along the lines and the columns. + * + * \return A newly allocated vpDisplay specialization initialized with \b I + * if a GUI library is available or nullptr otherwise. + * \warning The user must free the memory when the display is not used anymore. + */ +template +vpDisplay *allocateDisplay(vpImage &I, const int winx = -1, const int winy = -1, const std::string &title = "", + const vpDisplay::vpScaleType &scaleType = vpDisplay::SCALE_DEFAULT) +{ +#if defined(VISP_HAVE_DISPLAY) +#ifdef VISP_HAVE_X11 + return new vpDisplayX(I, winx, winy, title, scaleType); +#elif defined(VISP_HAVE_GDI) + return new vpDisplayGDI(I, winx, winy, title, scaleType); +#elif defined(HAVE_OPENCV_HIGHGUI) + return new vpDisplayOpenCV(I, winx, winy, title, scaleType); +#elif defined(VISP_HAVE_GTK) + return new vpDisplayGTK(I, winx, winy, title, scaleType); +#elif defined(VISP_HAVE_D3D9) + return new vpDisplayD3D(I, winx, winy, title, scaleType); +#endif +#else + (void)I; + (void)winx; + (void)winy; + (void)title; + (void)scaleType; + return nullptr; +#endif +} + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) +/** + * \brief Return a smart pointer vpDisplay specialization + * if a GUI library is available or nullptr otherwise. + * + * \return A smart pointer pointing to a vpDisplay specialization + * if a GUI library is available or nullptr otherwise. + */ +inline std::shared_ptr createDisplay() +{ +#if defined(VISP_HAVE_DISPLAY) +#ifdef VISP_HAVE_X11 + return std::make_shared(); +#elif defined(VISP_HAVE_GDI) + return std::make_shared(); +#elif defined(HAVE_OPENCV_HIGHGUI) + return std::make_shared(); +#elif defined(VISP_HAVE_GTK) + return std::make_shared(); +#elif defined(VISP_HAVE_D3D9) + return std::make_shared(); +#endif +#else + return std::shared_ptr(nullptr); +#endif +} + +/** + * \brief Return a smart pointer vpDisplay specialization initialized with \b I + * if a GUI library is available or nullptr otherwise. + * + * \tparam T : Any type that an image can handle and that can be displayed. + * \param[in] I : The image the display must be initialized with. + * \param[in] winx : The horizontal position of the display on the screen. + * \param[in] winy : The vertical position of the display on the screen. + * \param[in] title : The title of the display. + * \param[in] scaleType : If this parameter is set to: + * - vpDisplay::SCALE_AUTO, the display size is adapted to ensure the image is fully displayed in the screen; + * - vpDisplay::SCALE_DEFAULT or vpDisplay::SCALE_1, the display size is the same than the image size. + * - vpDisplay::SCALE_2, the display size is down scaled by 2 along the lines and the columns. + * - vpDisplay::SCALE_3, the display size is down scaled by 3 along the lines and the columns. + * - vpDisplay::SCALE_4, the display size is down scaled by 4 along the lines and the columns. + * - vpDisplay::SCALE_5, the display size is down scaled by 5 along the lines and the columns. + * + * \return A smart pointer pointing to a vpDisplay specialization initialized with \b I + * if a GUI library is available or nullptr otherwise. + */ +template +std::shared_ptr createDisplay(vpImage &I, const int winx = -1, const int winy = -1, + const std::string &title = "", + const vpDisplay::vpScaleType &scaleType = vpDisplay::SCALE_DEFAULT) +{ +#if defined(VISP_HAVE_DISPLAY) +#ifdef VISP_HAVE_X11 + return std::make_shared(I, winx, winy, title, scaleType); +#elif defined(VISP_HAVE_GDI) + return std::make_shared(I, winx, winy, title, scaleType); +#elif defined(HAVE_OPENCV_HIGHGUI) + return std::make_shared(I, winx, winy, title, scaleType); +#elif defined(VISP_HAVE_GTK) + return std::make_shared(I, winx, winy, title, scaleType); +#elif defined(VISP_HAVE_D3D9) + return std::make_shared(I, winx, winy, title, scaleType); +#endif +#else + (void)I; + (void)winx; + (void)winy; + (void)title; + (void)scaleType; + return nullptr; + return std::shared_ptr(nullptr); +#endif +} + +namespace impl +{ +struct GridSettings +{ + unsigned int rows; + unsigned int cols; + unsigned int startY; + unsigned int startX; + unsigned int paddingX; + unsigned int paddingY; +}; + +inline void makeDisplayGridHelper(std::vector> &res, const GridSettings &settings, + unsigned int currRow, unsigned int currCol, + unsigned int currentPixelX, unsigned int currentPixelY, + unsigned int maxRowHeightPixel) +{ + if (currRow != (settings.rows - 1) && (currCol != settings.cols - 1)) { + throw vpException(vpException::dimensionError, "Too few images for the grid size"); + } + (void)res; + (void)settings; + (void)currRow; + (void)currCol; + (void)currentPixelX; + (void)currentPixelY; + (void)maxRowHeightPixel; +} + +template +void makeDisplayGridHelper(std::vector> &res, const GridSettings &settings, + unsigned int currRow, unsigned int currCol, + unsigned int currentPixelX, unsigned int currentPixelY, + const unsigned int maxRowHeightPixel, + const std::string &name, vpImage &I, Args&... args) +{ + if (currRow >= settings.rows) { + throw vpException(vpException::dimensionError, "Too many images for the grid size"); + } + if (currCol == settings.cols) { + makeDisplayGridHelper(res, settings, currRow + 1, 0, settings.startX, + currentPixelY + maxRowHeightPixel + settings.paddingY, 0, name, I, args...); + } + else { + std::shared_ptr display = vpDisplayFactory::createDisplay(I, currentPixelX, currentPixelY, name); + vpDisplay::display(I); + vpDisplay::flush(I); + res.push_back(display); + makeDisplayGridHelper(res, settings, currRow, currCol + 1, currentPixelX + I.getWidth() + settings.paddingX, + currentPixelY, std::max(maxRowHeightPixel, I.getHeight()), args...); + } +} +} + +/** + * \brief Create a grid of displays, given a set of images. + * All the displays will be initialized in the correct location with the content of the associated image and name. + * All the images should have been initialized before with the correct resolution. + * The display creation and image association will follow a row major order. + * + * \tparam Args A sequence of display name (const std::string&) and ViSP image. + * The name should always come before the image. The image can be vpImage or vpImage + * \param rows Number of rows in the grid + * \param cols Number of columns in the grid + * \param startX The starting left position of the grid + * \param startY The starting top localization of the grid + * \param paddingX Horizontal padding between windows + * \param paddingY Vertical padding between windows + * \param args The name => image => name sequence + * \return std::vector> The allocated displays. + * + * \throws If the grid dimensions and number of images do not match + * + */ +template +std::vector> makeDisplayGrid(unsigned int rows, unsigned int cols, + unsigned int startX, unsigned int startY, + unsigned int paddingX, unsigned int paddingY, + Args&... args) +{ + std::vector> res; + impl::GridSettings settings; + settings.rows = rows; + settings.cols = cols; + settings.paddingX = paddingX; + settings.paddingY = paddingY; + settings.startX = startX; + settings.startY = startY; + makeDisplayGridHelper(res, settings, 0, 0, settings.startX, settings.startY, 0, args...); + return res; +} +#endif +} + +END_VISP_NAMESPACE + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayGDI.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayGDI.h new file mode 100644 index 00000000..4f196589 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayGDI.h @@ -0,0 +1,144 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Windows 32 display using GDI + */ + +#ifndef VP_DISPLAY_GDI_H +#define VP_DISPLAY_GDI_H + +#include +#include + +#if (defined(VISP_HAVE_GDI)) + +#include + +BEGIN_VISP_NAMESPACE + +/*! + * \class vpDisplayGDI + * + * \ingroup group_gui_display + * + * \brief Display for windows using GDI (available on any windows 32 platform). + * + * GDI stands for Graphics Device Interface and is a core component of + * Microsoft Windows operating systems used for displaying graphics in a window. + * + * The example below shows how to display an image with this video device. + * \code + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * #if defined(VISP_HAVE_GDI) + * vpImage I; // Grey level image + * + * // Read an image in PGM P5 format + * #ifdef _WIN32 + * vpImageIo::read(I, "C:/Temp/visp-images/Klimt/Klimt.pgm"); + * #else + * vpImageIo::read(I, "/local/soft/ViSP/ViSP-images/Klimt/Klimt.pgm"); + * #endif + * + * vpDisplayGDI d; + * + * // Initialize the display with the image I. Display and image are + * // now link together. + * d.init(I); + * + * // Specify the window location + * vpDisplay::setWindowPosition(I, 400, 100); + * + * // Set the display window title + * vpDisplay::setTitle(I, "My GDI display"); + * + * // Set the display background with image I content + * vpDisplay::display(I); + * + * // Draw a red rectangle in the display overlay (foreground) + * vpDisplay::displayRectangle(I, 10, 10, 100, 20, vpColor::red, true); + * + * // Draw a red rectangle in the display overlay (foreground) + * vpImagePoint topLeftCorner; + * topLeftCorner.set_i(50); + * topLeftCorner.set_j(10); + * vpDisplay::displayRectangle(I, topLeftCorner, 100, 20, vpColor::green, true); + * + * // Flush the foreground and background display + * vpDisplay::flush(I); + * + * // Get non blocking keyboard events + * std::cout << "Check keyboard events..." << std::endl; + * char key[10]; + * bool ret; + * for (int i=0; i< 200; ++i) { + * bool ret = vpDisplay::getKeyboardEvent(I, key, false); + * if (ret) + * std::cout << "keyboard event: key: " << "\"" << key << "\"" << std::endl; + * vpTime::wait(40); + * } + * + * // Get a blocking keyboard event + * std::cout << "Wait for a keyboard event..." << std::endl; + * ret = vpDisplay::getKeyboardEvent(I, key, true); + * std::cout << "keyboard event: " << ret << std::endl; + * if (ret) + * std::cout << "key: " << "\"" << key << "\"" << std::endl; + * + * // Wait for a click in the display window + * std::cout << "Wait for a button click..." << std::endl; + * vpDisplay::getClick(I); + * #endif + * } + * \endcode + */ + class VISP_EXPORT vpDisplayGDI : public vpDisplayWin32 +{ +public: + vpDisplayGDI(); + vpDisplayGDI(int winx, int winy, const std::string &title = ""); + vpDisplayGDI(vpImage &I, vpScaleType type); + vpDisplayGDI(vpImage &I, int winx = -1, int winy = -1, const std::string &title = "", + vpScaleType type = SCALE_DEFAULT); + vpDisplayGDI(vpImage &I, vpScaleType type); + vpDisplayGDI(vpImage &I, int winx = -1, int winy = -1, const std::string &title = "", + vpScaleType type = SCALE_DEFAULT); +}; + +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayGTK.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayGTK.h new file mode 100644 index 00000000..1bb8ba00 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayGTK.h @@ -0,0 +1,238 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Image display. + */ + +#ifndef VP_DISPLAY_GTK_H +#define VP_DISPLAY_GTK_H + +#include +#include +#if (defined(VISP_HAVE_GTK)) +#include + +BEGIN_VISP_NAMESPACE + +/*! + * \file vpDisplayGTK.h + * \brief Define the GTK console to display images. + */ + +/*! + * \class vpDisplayGTK + * + * \ingroup group_gui_display + * + * \brief The vpDisplayGTK allows to display image using the GTK 3rd party + * library. Thus to enable this class GTK should be installed. Installation + * instructions are provided here https://visp.inria.fr/3rd_gtk. + * + * The example below shows how to display an image with this video device. + * \code + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * #if defined(VISP_HAVE_GTK) + * vpImage I; // Grey level image + * + * // Read an image in PGM P5 format + * #ifdef _WIN32 + * vpImageIo::read(I, "C:/Temp/visp-images/Klimt/Klimt.pgm"); + * #else + * vpImageIo::read(I, "/local/soft/ViSP/ViSP-images/Klimt/Klimt.pgm"); + * #endif + * + * vpDisplayGTK d; + * + * // Initialize the display with the image I. Display and image are + * // now link together. + * d.init(I); + * + * // Specify the window location + * vpDisplay::setWindowPosition(I, 400, 100); + * + * // Set the display window title + * vpDisplay::setTitle(I, "My GTK display"); + * + * // Set the display background with image I content + * vpDisplay::display(I); + * + * // Draw a red rectangle in the display overlay (foreground) + * vpDisplay::displayRectangle(I, 10, 10, 100, 20, vpColor::red, true); + * + * // Draw a red rectangle in the display overlay (foreground) + * vpImagePoint topLeftCorner; + * topLeftCorner.set_i(50); + * topLeftCorner.set_j(10); + * vpDisplay::displayRectangle(I, topLeftCorner, 100, 20, vpColor::green, true); + * + * // Flush the foreground and background display + * vpDisplay::flush(I); + * + * // Get non blocking keyboard events + * std::cout << "Check keyboard events..." << std::endl; + * char key[10]; + * bool ret; + * for (int i=0; i< 200; ++i) { + * bool ret = vpDisplay::getKeyboardEvent(I, key, false); + * if (ret) + * std::cout << "keyboard event: key: " << "\"" << key << "\"" << std::endl; + * vpTime::wait(40); + * } + * + * // Get a blocking keyboard event + * std::cout << "Wait for a keyboard event..." << std::endl; + * ret = vpDisplay::getKeyboardEvent(I, key, true); + * std::cout << "keyboard event: " << ret << std::endl; + * if (ret) + * std::cout << "key: " << "\"" << key << "\"" << std::endl; + * + * // Wait for a click in the display window + * std::cout << "Wait for a button click..." << std::endl; + * vpDisplay::getClick(I); + * #endif + * } + * \endcode +*/ +class VISP_EXPORT vpDisplayGTK : public vpDisplay +{ +private: + typedef enum + { + id_black = 0, + id_white, + id_lightGray, + id_gray, + id_darkGray, + id_lightRed, + id_red, + id_darkRed, + id_lightGreen, + id_green, + id_darkGreen, + id_lightBlue, + id_blue, + id_darkBlue, + id_yellow, + id_cyan, + id_orange, + id_purple, + id_npredefined // Number of predefined colors + } vpColorIdentifier; + +public: + vpDisplayGTK(); + vpDisplayGTK(int win_x, int win_y, const std::string &win_title = ""); + vpDisplayGTK(vpImage &I, vpScaleType type); + vpDisplayGTK(vpImage &I, int win_x = -1, int win_y = -1, const std::string &win_title = "", + vpScaleType type = SCALE_DEFAULT); + vpDisplayGTK(vpImage &I, vpScaleType type); + vpDisplayGTK(vpImage &I, int win_x = -1, int win_y = -1, const std::string &win_title = "", + vpScaleType type = SCALE_DEFAULT); + + virtual ~vpDisplayGTK() VP_OVERRIDE; + + void getImage(vpImage &I) VP_OVERRIDE; + unsigned int getScreenDepth(); + unsigned int getScreenHeight() VP_OVERRIDE; + void getScreenSize(unsigned int &screen_width, unsigned int &screen_height) VP_OVERRIDE; + unsigned int getScreenWidth() VP_OVERRIDE; + + void init(vpImage &I, int win_x = -1, int win_y = -1, const std::string &win_title = "") VP_OVERRIDE; + void init(vpImage &I, int win_x = -1, int win_y = -1, const std::string &win_title = "") VP_OVERRIDE; + void init(unsigned int win_width, unsigned int win_height, int win_x = -1, int win_y = -1, + const std::string &win_title = "") VP_OVERRIDE; + +protected: + void setFont(const std::string &fontname) VP_OVERRIDE; + void setTitle(const std::string &win_title) VP_OVERRIDE; + void setWindowPosition(int win_x, int win_y) VP_OVERRIDE; + + void clearDisplay(const vpColor &color = vpColor::white) VP_OVERRIDE; + + void closeDisplay() VP_OVERRIDE; + + void displayArrow(const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color = vpColor::white, + unsigned int w = 4, unsigned int h = 2, unsigned int thickness = 1) VP_OVERRIDE; + + void displayCircle(const vpImagePoint ¢er, unsigned int radius, const vpColor &color, bool fill = false, + unsigned int thickness = 1) VP_OVERRIDE; + void displayCross(const vpImagePoint &ip, unsigned int size, const vpColor &color, unsigned int thickness = 1) VP_OVERRIDE; + void displayDotLine(const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color, + unsigned int thickness = 1) VP_OVERRIDE; + + void displayImage(const vpImage &I) VP_OVERRIDE; + void displayImage(const vpImage &I) VP_OVERRIDE; + void displayImage(const unsigned char *I); + + void displayImageROI(const vpImage &I, const vpImagePoint &iP, unsigned int width, + unsigned int height) VP_OVERRIDE; + void displayImageROI(const vpImage &I, const vpImagePoint &iP, unsigned int width, unsigned int height) VP_OVERRIDE; + + void displayLine(const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color, unsigned int thickness = 1) VP_OVERRIDE; + + void displayPoint(const vpImagePoint &ip, const vpColor &color, unsigned int thickness = 1) VP_OVERRIDE; + void displayRectangle(const vpImagePoint &topLeft, unsigned int width, unsigned int height, const vpColor &color, + bool fill = false, unsigned int thickness = 1) VP_OVERRIDE; + void displayRectangle(const vpImagePoint &topLeft, const vpImagePoint &bottomRight, const vpColor &color, + bool fill = false, unsigned int thickness = 1) VP_OVERRIDE; + void displayRectangle(const vpRect &rectangle, const vpColor &color, bool fill = false, unsigned int thickness = 1) VP_OVERRIDE; + + void displayText(const vpImagePoint &ip, const std::string &text, const vpColor &color = vpColor::green) VP_OVERRIDE; + + void flushDisplay() VP_OVERRIDE; + void flushDisplayROI(const vpImagePoint &iP, unsigned int width, unsigned int height) VP_OVERRIDE; + + bool getClick(bool blocking = true) VP_OVERRIDE; + bool getClick(vpImagePoint &ip, bool blocking = true) VP_OVERRIDE; + bool getClick(vpImagePoint &ip, vpMouseButton::vpMouseButtonType &button, bool blocking = true) VP_OVERRIDE; + bool getClickUp(vpImagePoint &ip, vpMouseButton::vpMouseButtonType &button, bool blocking = true) VP_OVERRIDE; + bool getKeyboardEvent(bool blocking = true) VP_OVERRIDE; + bool getKeyboardEvent(std::string &key, bool blocking = true) VP_OVERRIDE; + bool getPointerMotionEvent(vpImagePoint &ip) VP_OVERRIDE; + bool getPointerPosition(vpImagePoint &ip) VP_OVERRIDE; + +private: + // Implementation + class Impl; + Impl *m_impl; +}; + +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayOpenCV.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayOpenCV.h new file mode 100644 index 00000000..c594b00a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayOpenCV.h @@ -0,0 +1,220 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Image display. + */ + +#ifndef VP_DISPLAY_OPENCV_H +#define VP_DISPLAY_OPENCV_H + +#include +#include + +#if defined(HAVE_OPENCV_HIGHGUI) + +#include + +#include +#include + +BEGIN_VISP_NAMESPACE + +/*! + * \file vpDisplayOpenCV.h + * \brief Define the OpenCV console to display images. + */ + +/*! + * \class vpDisplayOpenCV + * + * \ingroup group_gui_display + * + * \brief The vpDisplayOpenCV allows to display image using the OpenCV library. + * Thus to enable this class OpenCV should be installed. Installation + * instructions are provided here https://visp.inria.fr/3rd_opencv. + * + * \warning Since ViSP 3.3.1 or higher we introduce the alpha channel support for color + * transparency. This new feature is only supported yet using vpDisplayOpenCV. See vpColor + * header documentation and displayOpenCV.cpp example for usage displaying filled + * transparent circles and rectangles. + * + * The example below shows how to display an image with this video device. + * \code + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * #if defined(VISP_HAVE_OPENCV) + * vpImage I; // Grey level image + * + * // Read an image in PGM P5 format + * vpImageIo::read(I, "/local/soft/ViSP/ViSP-images/Klimt/Klimt.pgm"); + * + * vpDisplayOpenCV d; + * + * // Initialize the display with the image I. Display and image are + * // now link together. + * d.init(I); + * + * // Specify the window location + * vpDisplay::setWindowPosition(I, 400, 100); + * + * // Set the display window title + * vpDisplay::setTitle(I, "My OpenCV display"); + * + * // Set the display background with image I content + * vpDisplay::display(I); + * + * // Draw a red rectangle in the display overlay (foreground) + * vpDisplay::displayRectangle(I, 10, 10, 100, 20, vpColor::red, true); + * + * // Draw a red rectangle in the display overlay (foreground) + * vpImagePoint topLeftCorner; + * topLeftCorner.set_i(10); + * topLeftCorner.set_j(50); + * vpDisplay::displayRectangle(I, topLeftCorner, 100, 20, vpColor::green, true); + * + * // Flush the foreground and background display + * vpDisplay::flush(I); + * + * // Get non blocking keyboard events + * std::cout << "Check keyboard events..." << std::endl; + * char key[10]; + * bool ret; + * for (int i=0; i< 200; ++i) { + * bool ret = vpDisplay::getKeyboardEvent(I, key, false); + * if (ret) + * std::cout << "keyboard event: key: " << "\"" << key << "\"" << std::endl; + * vpTime::wait(40); + * } + * + * // Get a blocking keyboard event + * std::cout << "Wait for a keyboard event..." << std::endl; + * ret = vpDisplay::getKeyboardEvent(I, key, true); + * std::cout << "keyboard event: " << ret << std::endl; + * if (ret) + * std::cout << "key: " << "\"" << key << "\"" << std::endl; + * + * // Wait for a click in the display window + * std::cout << "Wait for a button click..." << std::endl; + * vpDisplay::getClick(I); + * #endif + * } + * \endcode +*/ +class VISP_EXPORT vpDisplayOpenCV : public vpDisplay +{ +public: + vpDisplayOpenCV(); + vpDisplayOpenCV(int winx, int winy, const std::string &title = ""); + vpDisplayOpenCV(vpImage &I, vpScaleType type); + vpDisplayOpenCV(vpImage &I, int winx = -1, int winy = -1, const std::string &title = "", + vpScaleType type = SCALE_DEFAULT); + vpDisplayOpenCV(vpImage &I, vpScaleType type); + vpDisplayOpenCV(vpImage &I, int winx = -1, int winy = -1, const std::string &title = "", + vpScaleType type = SCALE_DEFAULT); + vpDisplayOpenCV(const vpDisplayOpenCV &display); + virtual ~vpDisplayOpenCV() VP_OVERRIDE; + vpDisplayOpenCV &operator=(const vpDisplayOpenCV &display); + + void getImage(vpImage &I) VP_OVERRIDE; + unsigned int getScreenHeight() VP_OVERRIDE; + void getScreenSize(unsigned int &width, unsigned int &height) VP_OVERRIDE; + unsigned int getScreenWidth() VP_OVERRIDE; + + void init(vpImage &I, int winx = -1, int winy = -1, const std::string &title = "") VP_OVERRIDE; + void init(vpImage &I, int winx = -1, int winy = -1, const std::string &title = "") VP_OVERRIDE; + void init(unsigned int width, unsigned int height, int winx = -1, int winy = -1, const std::string &title = "") VP_OVERRIDE; + +protected: + void setFont(const std::string &font) VP_OVERRIDE; + void setTitle(const std::string &title) VP_OVERRIDE; + void setWindowPosition(int winx, int winy) VP_OVERRIDE; + + void clearDisplay(const vpColor &color = vpColor::white) VP_OVERRIDE; + + void closeDisplay() VP_OVERRIDE; + + void displayArrow(const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color = vpColor::white, + unsigned int w = 4, unsigned int h = 2, unsigned int thickness = 1) VP_OVERRIDE; + + void displayCircle(const vpImagePoint ¢er, unsigned int radius, const vpColor &color, bool fill = false, + unsigned int thickness = 1) VP_OVERRIDE; + void displayCross(const vpImagePoint &ip, unsigned int size, const vpColor &color, unsigned int thickness = 1) VP_OVERRIDE; + void displayDotLine(const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color, + unsigned int thickness = 1) VP_OVERRIDE; + + void displayImage(const vpImage &I) VP_OVERRIDE; + void displayImage(const vpImage &I) VP_OVERRIDE; + void displayImage(const unsigned char *I); + + void displayImageROI(const vpImage &I, const vpImagePoint &iP, unsigned int width, + unsigned int height) VP_OVERRIDE; + void displayImageROI(const vpImage &I, const vpImagePoint &iP, unsigned int width, unsigned int height) VP_OVERRIDE; + + void displayLine(const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color, unsigned int thickness = 1) VP_OVERRIDE; + void displayPoint(const vpImagePoint &ip, const vpColor &color, unsigned int thickness = 1) VP_OVERRIDE; + + void displayRectangle(const vpImagePoint &topLeft, unsigned int width, unsigned int height, const vpColor &color, + bool fill = false, unsigned int thickness = 1) VP_OVERRIDE; + void displayRectangle(const vpImagePoint &topLeft, const vpImagePoint &bottomRight, const vpColor &color, + bool fill = false, unsigned int thickness = 1) VP_OVERRIDE; + void displayRectangle(const vpRect &rectangle, const vpColor &color, bool fill = false, unsigned int thickness = 1) VP_OVERRIDE; + + void displayText(const vpImagePoint &ip, const std::string &text, const vpColor &color = vpColor::green) VP_OVERRIDE; + + void flushDisplay() VP_OVERRIDE; + void flushDisplayROI(const vpImagePoint &iP, unsigned int width, unsigned int height) VP_OVERRIDE; + + bool getClick(bool blocking = true) VP_OVERRIDE; + bool getClick(vpImagePoint &ip, bool blocking = true) VP_OVERRIDE; + bool getClick(vpImagePoint &ip, vpMouseButton::vpMouseButtonType &button, bool blocking = true) VP_OVERRIDE; + bool getClickUp(vpImagePoint &ip, vpMouseButton::vpMouseButtonType &button, bool blocking = true) VP_OVERRIDE; + + bool getKeyboardEvent(bool blocking = true) VP_OVERRIDE; + bool getKeyboardEvent(std::string &key, bool blocking = true) VP_OVERRIDE; + bool getPointerMotionEvent(vpImagePoint &ip) VP_OVERRIDE; + bool getPointerPosition(vpImagePoint &ip) VP_OVERRIDE; + +private: + // Implementation + class Impl; + Impl *m_impl; +}; + +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayPCL.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayPCL.h new file mode 100644 index 00000000..1800a773 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayPCL.h @@ -0,0 +1,268 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Display a point cloud using PCL library. + */ + +#ifndef VP_DISPLAY_PCL_H +#define VP_DISPLAY_PCL_H + +#include + +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_VISUALIZATION) && defined(VISP_HAVE_THREADS) + +#include +#include +#include + +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpDisplayPCL + \ingroup group_gui_plotter + This class enables real time plotting of 3D point clouds. It relies on the PCL library. + To see how to install PCL library, please refer to the \ref soft_tool_pcl section. + + \warning MacOS currently can only use the monothread method `display`, otherwise they get + the error `uncaught exception 'NSInternalInconsistencyException', reason: 'NSWindow should only be instantiated on the main thread!'` + + \warning On Ubuntu 22.04 and 24.04, you can face the following error: + ``` + Thread 2 "tutorial-pcl-vi" received signal SIGSEGV, Segmentation fault. + 0x00007ffff7304b10 in _XEventsQueued () from /lib/x86_64-linux-gnu/libX11.so.6 + 0x00007ffff7304b10 in _XEventsQueued () at /lib/x86_64-linux-gnu/libX11.so.6 + 0x00007ffff72f11a1 in XPending () at /lib/x86_64-linux-gnu/libX11.so.6 + 0x00007fffecf65b8f in vtkXRenderWindowInteractor::StartEventLoop() () at /lib/x86_64-linux-gnu/libvtkRenderingUI-9.1.so.1 + 0x00007ffff6ee3f8c in pcl::visualization::PCLVisualizer::spinOnce(int, bool) () at /lib/x86_64-linux-gnu/libpcl_visualization.so.1.12 + 0x00007ffff7fa5c49 in vpPclVisualizer::loopThread() (this=0x7fffffffd720) at /usr/include/c++/11/bits/shared_ptr_base.h:1295 + ``` + This is a [known compatibility issue](https://github.com/PointCloudLibrary/pcl/issues/5237) between PCL library and VTK library. + + The vpDisplayPCL can be used in monothread mode, or you may try to install PCL from source and then recompile + ViSP. + + \warning When the PCL library relies on the VTK library and X11 library as backends for the viewer, you + may face the following error if you create either two viewers in threaded mode or one viewer in + threaded mode and another one in the main thread: + ``` + X Error of failed request: BadAccess (attempt to access private resource denied) + Major opcode of failed request: 152 (GLX) + Minor opcode of failed request: 5 (X_GLXMakeCurrent) + Serial number of failed request: 407 + Current serial number in output stream: 407 + ``` + It seems to be due to the fact that the X server resources are reserved by one thread, preventing any other thread to + access them. + +

Tutorials & Examples

+ + Tutorials
+ If you want detailed explanation on how to use this class, you may have a look at:
+ + - \ref tutorial-display-pcl + - \ref tutorial-hsv-segmentation-pcl +*/ +class VISP_EXPORT vpDisplayPCL +{ +public: + vpDisplayPCL(int posx = 0, int posy = 0, const std::string &window_name = ""); + vpDisplayPCL(unsigned int width, unsigned int height, int posx = 0, int posy = 0, const std::string &window_name = ""); + ~vpDisplayPCL(); + + void setVerbose(bool verbose); + void display(const bool &blocking = false); + void startThread(const bool &colorThread = false); + void startThread(std::mutex &mutex, pcl::PointCloud::Ptr pointcloud, const std::string &name = "", const vpColor &color = vpColor::red); + void startThread(std::mutex &mutex, pcl::PointCloud::Ptr pointcloud); + void addPointCloud(std::mutex &mutex, pcl::PointCloud::Ptr pointcloud, const std::string &name = "", const vpColor &color = vpColor::red); + void addPointCloud(std::mutex &mutex, pcl::PointCloud::Ptr pointcloud); + void setPosition(int posx, int posy); + void setWindowName(const std::string &window_name); + void stop(); +private: + void run(); + void runColor(); + void createViewer(); + void insertLegend(const size_t &id); + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +/** + * \brief Basic structure to handle the point clouds to display. + */ + typedef struct PointCloudHandling + { + bool m_do_init; //!< Managed internally. True to insert for the first time the point cloud in the viewer, false to update the point cloud instead. + std::string m_name; //!< The name of the handled point cloud. It is used both by the viewer as unique identifier and in the legend. + static unsigned int s_nb; //!< The number of point clouds that are handled. + + /** + * \brief Create a unique name for the point cloud. + * + * \return std::string The unique name. + */ + std::string createName() + { + std::stringstream ss; + ss << "Point cloud " << s_nb; + return ss.str(); + } + + /** + * \brief Initializes the point cloud handler + * + * \param[in] name Its name, used as ID and potentially in the legend. + * If empty, it will be automatically generated. + */ + void init(const std::string &name) + { + if (name.empty()) { + m_name = createName(); + } + else { + m_name = name; + } + m_do_init = true; + ++s_nb; + } + + /** + * \brief Initialize the point cloud handler. + * Its name, used as ID and potentially in the legend, will be automatically generated. + */ + void init() + { + init(createName()); + } + + /** + * \brief Construct a new Point Cloud Handling object. + * Its name, used as ID and potentially in the legend, will be automatically generated. + */ + PointCloudHandling() + { + init(); + } + + /** + * \brief Construct a new Point Cloud Handling object + * + * \param[in] name Name used as ID and potentially in the legend. + */ + PointCloudHandling(const std::string &name) + { + init(name); + } + } PointCloudHandling; + + typedef struct XYZPointCloudHandling : public PointCloudHandling + { + pcl::PointCloud::Ptr m_pcl; //!< Pointer towards the textureless point cloud. + vpColor m_color; //!< Monochromic color to render the point cloud. + + /** + * \brief Construct a new XYZPointCloudHandling object + * + * \param[in] pcl Pointer towards the textureless point cloud. + * \param[in] color Monochromic color to render the point cloud. + */ + XYZPointCloudHandling(pcl::PointCloud::Ptr pcl, const vpColor &color) + : PointCloudHandling() + , m_pcl(pcl) + , m_color(color) + { } + + /** + * \brief Construct a new XYZPointCloudHandling object + * + * \param[in] pcl Pointer towards the textureless point cloud. + * \param[in] name Name of the point cloud, used as ID by the viewer and inserted in the legend. + * \param[in] color Monochromic color to render the point cloud. + */ + XYZPointCloudHandling(pcl::PointCloud::Ptr pcl, const std::string &name, const vpColor &color) + : PointCloudHandling(name) + , m_pcl(pcl) + , m_color(color) + { } + } XYZPointCloudHandling; + + /** + * \brief Structure that handles textured point cloud. + */ + typedef struct ColoredPointCloudHandling : public PointCloudHandling + { + pcl::PointCloud::Ptr m_pcl; //!< Pointer towards the textured point cloud + + /** + * \brief Construct a new Colored Point Cloud Handling object + * + * \param[in] pcl Pointer towards the textured point cloud + */ + ColoredPointCloudHandling(pcl::PointCloud::Ptr pcl) + : PointCloudHandling() + , m_pcl(pcl) + { } + + /** + * \brief Construct a new Colored Point Cloud Handling object + * + * \param[in] pcl Pointer towards the textured point cloud + * \param[in] name Name of the point cloud, used as ID by the viewer. + */ + ColoredPointCloudHandling(pcl::PointCloud::Ptr pcl, const std::string &name) + : PointCloudHandling(name) + , m_pcl(pcl) + { } + } ColoredPointCloudHandling; +#endif + + bool m_stop; + bool m_thread_running; //!< Set to true once the startThread method is called. + bool m_verbose; + std::thread m_thread; //!< Non-blocking display thread. + std::mutex m_mutex_vector; //!< Mutex to protect the vectors of point clouds + std::vector> mv_xyz_pcl; //!< Storage for textureless point clouds + associated mutex + std::vector> mv_xyz_handlers; //!< Mono-color handlers for textureless point clouds + std::vector> mv_colored_pcl; //!< Storage for textured point clouds + associated mutex + std::vector> mv_color_handlers; //!< Color handlers for textured point clouds + unsigned int m_width; + unsigned int m_height; + int m_posx; + int m_posy; + std::string m_window_name; + pcl::visualization::PCLVisualizer::Ptr m_viewer; +}; +END_VISP_NAMESPACE +#endif + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayWin32.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayWin32.h new file mode 100644 index 00000000..bb1689ff --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayWin32.h @@ -0,0 +1,211 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Windows 32 display base class + */ + +#ifndef VP_DISPLAY_WIN32_H +#define VP_DISPLAY_WIN32_H + +#include +#include + +#if (defined(VISP_HAVE_GDI) || defined(VISP_HAVE_D3D9)) + +#include + +#include + +// Mute warning with clang-cl +// warning : non-portable path to file ''; specified path differs in case from file name on disk [-Wnonportable-system-include-path] +// warning : non-portable path to file ''; specified path differs in case from file name on disk [-Wnonportable-system-include-path] +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wnonportable-system-include-path" +#endif + +// Include WinSock2.h before windows.h to ensure that winsock.h is not +// included by windows.h since winsock.h and winsock2.h are incompatible +#include +#include +#include +#include +#include + +#include + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +BEGIN_VISP_NAMESPACE + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +/*! + * Used to pass parameters to the window's thread. +*/ +struct threadParam +{ + //! Pointer to the display associated with the window. + vpDisplayWin32 *vpDisp; + + //! X position of the window. + int x; + + //! Y position of the window. + int y; + + //! Width of the window's client area. + unsigned int w; + + //! Height of the window's client area. + unsigned int h; + + //! Title of the window. + std::string title; +}; +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ + +/*! + * \class vpDisplayWin32 + * + * \brief Base abstract class for Windows 32 displays. + * Implements the window creation in a separate thread + * and the associated event handling functions for + * Windows 32 displays. + * Uses calls to a renderer to do some display. + * (i.e. all display methods are implemented in the renderer) +*/ +class VISP_EXPORT vpDisplayWin32 : public vpDisplay +{ +protected: + //! Maximum delay for window initialization + static const int MAX_INIT_DELAY; + + //! Handle of the window's thread. + HANDLE hThread; + + //! Id of the window's thread. + DWORD threadId; + + //! Initialization status. + bool iStatus; + + //! The window. + vpWin32Window window; + + //! + RECT roi; + + //! Function used to launch the window in a thread. + friend void vpCreateWindow(threadParam *param); + +public: + VP_EXPLICIT vpDisplayWin32(vpWin32Renderer *rend = nullptr); + + vpDisplayWin32(vpImage &I, int winx = -1, int winy = -1, const std::string &title = ""); + + vpDisplayWin32(vpImage &I, int winx = -1, int winy = -1, const std::string &title = ""); + + vpDisplayWin32(const vpDisplayWin32 &display); + virtual ~vpDisplayWin32() VP_OVERRIDE; + vpDisplayWin32 &operator=(const vpDisplayWin32 &display); + + void clearDisplay(const vpColor &color = vpColor::white) VP_OVERRIDE; + void closeDisplay() VP_OVERRIDE; + void displayImage(const vpImage &I) VP_OVERRIDE; + void displayImage(const vpImage &I) VP_OVERRIDE; + + void displayImageROI(const vpImage &I, const vpImagePoint &iP, unsigned int width, + unsigned int height) VP_OVERRIDE; + void displayImageROI(const vpImage &I, const vpImagePoint &iP, unsigned int width, unsigned int height) VP_OVERRIDE; + + void flushDisplay() VP_OVERRIDE; + void flushDisplayROI(const vpImagePoint &iP, unsigned int width, unsigned int height) VP_OVERRIDE; + + void getImage(vpImage &I) VP_OVERRIDE; + unsigned int getScreenHeight() VP_OVERRIDE; + void getScreenSize(unsigned int &width, unsigned int &height) VP_OVERRIDE; + unsigned int getScreenWidth() VP_OVERRIDE; + + void init(vpImage &I, int winx = -1, int winy = -1, const std::string &title = "") VP_OVERRIDE; + void init(vpImage &I, int winx = -1, int winy = -1, const std::string &title = "") VP_OVERRIDE; + void init(unsigned int width, unsigned int height, int winx = -1, int winy = -1, const std::string &title = "") VP_OVERRIDE; + + void setFont(const std::string &fontname) VP_OVERRIDE; + void setDownScalingFactor(unsigned int scale) VP_OVERRIDE + { + window.setScale(scale); + m_scale = scale; + } + void setDownScalingFactor(vpScaleType scaleType) VP_OVERRIDE { m_scaleType = scaleType; } + void setTitle(const std::string &windowtitle) VP_OVERRIDE; + void setWindowPosition(int winx, int winy) VP_OVERRIDE; + +protected: + void displayArrow(const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color = vpColor::white, + unsigned int w = 4, unsigned int h = 2, unsigned int thickness = 1) VP_OVERRIDE; + + void displayCircle(const vpImagePoint ¢er, unsigned int radius, const vpColor &color, bool fill = false, + unsigned int thickness = 1) VP_OVERRIDE; + + void displayCross(const vpImagePoint &ip, unsigned int size, const vpColor &color, unsigned int thickness = 1) VP_OVERRIDE; + + void displayDotLine(const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color, + unsigned int thickness = 1) VP_OVERRIDE; + + void displayLine(const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color, unsigned int thickness = 1) VP_OVERRIDE; + + void displayPoint(const vpImagePoint &ip, const vpColor &color, unsigned int thickness = 1) VP_OVERRIDE; + + void displayRectangle(const vpImagePoint &topLeft, unsigned int width, unsigned int height, const vpColor &color, + bool fill = false, unsigned int thickness = 1) VP_OVERRIDE; + void displayRectangle(const vpImagePoint &topLeft, const vpImagePoint &bottomRight, const vpColor &color, + bool fill = false, unsigned int thickness = 1) VP_OVERRIDE; + void displayRectangle(const vpRect &rectangle, const vpColor &color, bool fill = false, unsigned int thickness = 1) VP_OVERRIDE; + + void displayText(const vpImagePoint &ip, const std::string &text, const vpColor &color = vpColor::green) VP_OVERRIDE; + + bool getClick(bool blocking = true) VP_OVERRIDE; + bool getClick(vpImagePoint &ip, bool blocking = true) VP_OVERRIDE; + bool getClick(vpImagePoint &ip, vpMouseButton::vpMouseButtonType &button, bool blocking = true) VP_OVERRIDE; + bool getClickUp(vpImagePoint &ip, vpMouseButton::vpMouseButtonType &button, bool blocking = true) VP_OVERRIDE; + + bool getKeyboardEvent(bool blocking = true) VP_OVERRIDE; + bool getKeyboardEvent(std::string &key, bool blocking) VP_OVERRIDE; + bool getPointerMotionEvent(vpImagePoint &ip) VP_OVERRIDE; + bool getPointerPosition(vpImagePoint &ip) VP_OVERRIDE; + + void waitForInit(); +}; + +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayX.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayX.h new file mode 100644 index 00000000..2172b15c --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpDisplayX.h @@ -0,0 +1,236 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Image display. + */ + +/*! + * \file vpDisplayX.h + * \brief Define the X11 console to display images. + */ + +#ifndef VP_DISPLAY_X_H +#define VP_DISPLAY_X_H + +#include +#include + +#ifdef VISP_HAVE_X11 +#include +#include + +BEGIN_VISP_NAMESPACE + +/*! + * \class vpDisplayX + * + * \ingroup group_gui_display + * + * \brief Use the X11 console to display images on unix-like OS. + * Thus to enable this class X11 should be installed. Installation + * instructions are provided here https://visp.inria.fr/3rd_x11. + * + * This class define the X11 console to display images + * It also define method to display some geometric feature (point, line, + * circle) in the image. + * + * The example below shows how to display an image with this video device. + * \code + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpImage I; // Grey level image + * + * // Read an image in PGM P5 format + * vpImageIo::read(I, "/local/soft/ViSP/ViSP-images/Klimt/Klimt.pgm"); + * + * #if defined(VISP_HAVE_X11) + * vpDisplayX d; + * + * // Initialize the display with the image I. Display and image are + * // now link together. + * d.init(I); + * #endif + * + * // Specify the window location + * vpDisplay::setWindowPosition(I, 400, 100); + * + * // Set the display window title + * vpDisplay::setTitle(I, "My X11 display"); + * + * // Set the display background with image I content + * vpDisplay::display(I); + * + * // Draw a red rectangle in the display overlay (foreground) + * vpDisplay::displayRectangle(I, 10, 10, 100, 20, vpColor::red, true); + * + * // Draw a red rectangle in the display overlay (foreground) + * vpImagePoint topLeftCorner; + * topLeftCorner.set_i(50); + * topLeftCorner.set_j(10); + * vpDisplay::displayRectangle(I, topLeftCorner, 100, 20, vpColor::green, true); + * + * // Flush the foreground and background display + * vpDisplay::flush(I); + * + * // Get non blocking keyboard events + * std::cout << "Check keyboard events..." << std::endl; + * char key[10]; + * bool ret; + * for (int i=0; i< 200; ++i) { + * bool ret = vpDisplay::getKeyboardEvent(I, key, false); + * if (ret) + * std::cout << "keyboard event: key: " << "\"" << key << "\"" << std::endl; + * vpTime::wait(40); + * } + * + * // Get a blocking keyboard event + * std::cout << "Wait for a keyboard event..." << std::endl; + * ret = vpDisplay::getKeyboardEvent(I, key, true); + * std::cout << "keyboard event: " << ret << std::endl; + * if (ret) + * std::cout << "key: " << "\"" << key << "\"" << std::endl; + * + * // Wait for a click in the display window + * std::cout << "Wait for a button click..." << std::endl; + * vpDisplay::getClick(I); + * } + * \endcode +*/ +class VISP_EXPORT vpDisplayX : public vpDisplay +{ + // private: + //#ifndef DOXYGEN_SHOULD_SKIP_THIS + // vpDisplayX(const vpDisplayX &) + // : vpDisplay(), display(nullptr), window(), Ximage(nullptr), lut(), + // context(), screen(), event(), pixmap(), + // x_color(nullptr), screen_depth(8), xcolor(), values(), + // ximage_data_init(false), RMask(0), GMask(0), BMask(0), RShift(0), + // GShift(0), BShift(0) + // { + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); + // } + // vpDisplayX &operator=(const vpDisplayX &){ + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); return *this; + // } + //#endif + +public: + vpDisplayX(); + vpDisplayX(int winx, int winy, const std::string &title = ""); + vpDisplayX(vpImage &I, vpScaleType type); + vpDisplayX(vpImage &I, int winx = -1, int winy = -1, const std::string &title = "", + vpScaleType type = SCALE_DEFAULT); + vpDisplayX(vpImage &I, vpScaleType type); + vpDisplayX(vpImage &I, int winx = -1, int winy = -1, const std::string &title = "", + vpScaleType type = SCALE_DEFAULT); + + virtual ~vpDisplayX() VP_OVERRIDE; + + void getImage(vpImage &I) VP_OVERRIDE; + unsigned int getScreenDepth(); + unsigned int getScreenHeight() VP_OVERRIDE; + void getScreenSize(unsigned int &width, unsigned int &height) VP_OVERRIDE; + unsigned int getScreenWidth() VP_OVERRIDE; + + void init(vpImage &I, int win_x = -1, int win_y = -1, const std::string &win_title = "") VP_OVERRIDE; + void init(vpImage &I, int win_x = -1, int win_y = -1, const std::string &win_title = "") VP_OVERRIDE; + void init(unsigned int win_width, unsigned int win_height, int win_x = -1, int win_y = -1, + const std::string &win_title = "") VP_OVERRIDE; + +protected: + void clearDisplay(const vpColor &color = vpColor::white) VP_OVERRIDE; + + void closeDisplay() VP_OVERRIDE; + + void displayArrow(const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color = vpColor::white, + unsigned int w = 4, unsigned int h = 2, unsigned int thickness = 1) VP_OVERRIDE; + + void displayCircle(const vpImagePoint ¢er, unsigned int radius, const vpColor &color, bool fill = false, + unsigned int thickness = 1) VP_OVERRIDE; + void displayCross(const vpImagePoint &ip, unsigned int size, const vpColor &color, unsigned int thickness = 1) VP_OVERRIDE; + void displayDotLine(const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color, + unsigned int thickness = 1) VP_OVERRIDE; + + void displayImage(const vpImage &I) VP_OVERRIDE; + void displayImage(const vpImage &I) VP_OVERRIDE; + void displayImage(const unsigned char *I); + + void displayImageROI(const vpImage &I, const vpImagePoint &iP, unsigned int width, + unsigned int height) VP_OVERRIDE; + void displayImageROI(const vpImage &I, const vpImagePoint &iP, unsigned int width, unsigned int height) VP_OVERRIDE; + + void displayLine(const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color, unsigned int thickness = 1) VP_OVERRIDE; + void displayPoint(const vpImagePoint &ip, const vpColor &color, unsigned int thickness = 1) VP_OVERRIDE; + + void displayRectangle(const vpImagePoint &topLeft, unsigned int width, unsigned int height, const vpColor &color, + bool fill = false, unsigned int thickness = 1) VP_OVERRIDE; + void displayRectangle(const vpImagePoint &topLeft, const vpImagePoint &bottomRight, const vpColor &color, + bool fill = false, unsigned int thickness = 1) VP_OVERRIDE; + void displayRectangle(const vpRect &rectangle, const vpColor &color, bool fill = false, unsigned int thickness = 1) VP_OVERRIDE; + + void displayText(const vpImagePoint &ip, const std::string &text, const vpColor &color = vpColor::green) VP_OVERRIDE; + + void flushDisplay() VP_OVERRIDE; + void flushDisplayROI(const vpImagePoint &iP, unsigned int width, unsigned int height) VP_OVERRIDE; + + bool getClick(bool blocking = true) VP_OVERRIDE; + bool getClick(vpImagePoint &ip, bool blocking = true) VP_OVERRIDE; + bool getClick(vpImagePoint &ip, vpMouseButton::vpMouseButtonType &button, bool blocking = true) VP_OVERRIDE; + bool getClickUp(vpImagePoint &ip, vpMouseButton::vpMouseButtonType &button, bool blocking = true) VP_OVERRIDE; + + bool getKeyboardEvent(bool blocking = true) VP_OVERRIDE; + bool getKeyboardEvent(std::string &key, bool blocking = true) VP_OVERRIDE; + + bool getPointerMotionEvent(vpImagePoint &ip) VP_OVERRIDE; + bool getPointerPosition(vpImagePoint &ip) VP_OVERRIDE; + + void setFont(const std::string &font) VP_OVERRIDE; + void setTitle(const std::string &title) VP_OVERRIDE; + void setWindowPosition(int win_x, int win_y) VP_OVERRIDE; + +private: + // Implementation + class Impl; + Impl *m_impl; +}; + +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpGDIRenderer.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpGDIRenderer.h new file mode 100644 index 00000000..ed33ec62 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpGDIRenderer.h @@ -0,0 +1,152 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * GDI renderer for windows 32 display + */ + +#ifndef VP_GDI_RENDERER_H +#define VP_GDI_RENDERER_H + +#include + +#if (defined(VISP_HAVE_GDI)) + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +// Mute warning with clang-cl +// warning : non-portable path to file ''; specified path differs in case from file name on disk [-Wnonportable-system-include-path] +// warning : non-portable path to file ''; specified path differs in case from file name on disk [-Wnonportable-system-include-path] +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wnonportable-system-include-path" +#endif + +// Include WinSock2.h before windows.h to ensure that winsock.h is not +// included by windows.h since winsock.h and winsock2.h are incompatible +#include +#include + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +#include +#include +#include +#include + +#include + +BEGIN_VISP_NAMESPACE + +class VISP_EXPORT vpGDIRenderer : public vpWin32Renderer +{ + // the handle of the associated window + HWND m_hWnd; + + // the bitmap object to display + HBITMAP m_bmp; + + // colors for overlay + COLORREF m_colors[vpColor::id_unknown]; + + // font used to draw text + HFONT m_hFont; + + // used to ensure that only one thread at a time is accessing bmp + CRITICAL_SECTION m_criticalSection; + + unsigned int m_bmp_width; + unsigned int m_bmp_height; + +public: + double timelost; + vpGDIRenderer(); + virtual ~vpGDIRenderer() VP_OVERRIDE; + +#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98) + vpGDIRenderer(const vpGDIRenderer &) = default; + vpGDIRenderer &operator=(const vpGDIRenderer &) = default; +#endif + + bool init(HWND hWnd, unsigned int width, unsigned int height) VP_OVERRIDE; + + bool render() VP_OVERRIDE; + + void setImg(const vpImage &I) VP_OVERRIDE; + void setImg(const vpImage &I) VP_OVERRIDE; + void setImgROI(const vpImage &I, const vpImagePoint &iP, unsigned int width, unsigned int height) VP_OVERRIDE; + void setImgROI(const vpImage &I, const vpImagePoint &iP, unsigned int width, unsigned int height) VP_OVERRIDE; + + void setPixel(const vpImagePoint &iP, const vpColor &color) VP_OVERRIDE; + + void drawLine(const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color, unsigned int thickness, + int style = PS_SOLID) VP_OVERRIDE; + + void drawRect(const vpImagePoint &topLeft, unsigned int width, unsigned int height, const vpColor &color, + bool fill = false, unsigned int thickness = 1) VP_OVERRIDE; + + void clear(const vpColor &color) VP_OVERRIDE; + + void drawCircle(const vpImagePoint ¢er, unsigned int radius, const vpColor &color, bool fill = false, + unsigned int thickness = 1) VP_OVERRIDE; + + void drawText(const vpImagePoint &ip, const char *text, const vpColor &color) VP_OVERRIDE; + + void drawCross(const vpImagePoint &ip, unsigned int size, const vpColor &color, unsigned int thickness = 1) VP_OVERRIDE; + + void drawArrow(const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color, unsigned int w, unsigned int h, + unsigned int thickness = 1) VP_OVERRIDE; + + void getImage(vpImage &I) VP_OVERRIDE; + +private: + // updates the renderer hbitmaps. + bool updateBitmap(HBITMAP &hBmp, unsigned char *imBuffer, unsigned int w, unsigned int h); + // updates the renderer hbitmaps. + bool updateBitmapROI(unsigned char *imBuffer, int i_min, int j_min, int w, int h); + + // converts a vpImage into a HBITMAP . + void convert(const vpImage &I, HBITMAP &hBmp); + + // converst a vpImage into a HBITMAP . + void convert(const vpImage &I, HBITMAP &hBmp); + + // converts a vpImage into a HBITMAP . + void convertROI(const vpImage &I, const vpImagePoint &iP, unsigned int width, unsigned int height); + + // converst a vpImage into a HBITMAP . + void convertROI(const vpImage &I, const vpImagePoint &iP, unsigned int width, unsigned int height); +}; + +END_VISP_NAMESPACE +#endif +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpPclViewer.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpPclViewer.h new file mode 100644 index 00000000..638c397f --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpPclViewer.h @@ -0,0 +1,257 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * Description: + * Real-time 3D point clouds plotter based on the PCL library. + */ + +#ifndef VP_PCL_VIEWER_H +#define VP_PCL_VIEWER_H + +#include + +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_VISUALIZATION) && defined(VISP_HAVE_PCL_IO) && defined(VISP_HAVE_THREADS) +// System +#include +#include + +// ViSP +#include + +// PCL +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpPclViewer + \ingroup group_gui_plotter + This class enables real time plotting of 3D point clouds. It relies on the PCL library. To see how to install PCL library, + please refer to the \ref soft_tool_pcl section. + You can either plot in a non-blocking threaded manner or in a blocking manner. + The point clouds can be displayed using their original colors, or using uniform colors to match + a legend. + Additionally, it is possible to use confidence weights to hide points that are not trusted. These + weights can result from a robust estimation using for instance the vpRobust class. + + \warning If you face some runtime errors with the following class, please refer to the \ref pcl_viewer_known_issues + section to see if it is a known problem. + + \sa \ref tutorial-display-pcl +*/ +class VISP_EXPORT VP_DEPRECATED vpPclViewer +{ +public: + /** + * @brief Structure that contains all the required parameters to display a legend on the viewer. + */ + typedef struct legendParams + { + std::string m_text; /*!< The text of the legend.*/ + unsigned int m_posU; /*!< The position of the legend on the u-axis of the screen.*/ + unsigned int m_posV; /*!< The position of the legend on the v-axis of the screen.*/ + unsigned int m_size; /*!< The size of the legend.*/ + double m_rRatio; /*!< The red ratio of the legend.*/ + double m_gRatio; /*!< The green ratio of the legend.*/ + double m_bRatio; /*!< The blue ratio of the legend.*/ + }legendParams; + + /** + * @brief Construct a new vpPclViewer object. + * + * @param title The title of the window of the viewer. + * @param width The width of the window of the viewer. + * @param height The height of the window of the viewer. + * @param posU The position on the horizontal axis of the screen of the window of the viewer. + * @param posV The position on the vertical axis of the screen of the window of the viewer. + * @param outFolder If different from the empty string, the point clouds will be saved in this folder. + * @param ignoreThreshold A point for which the weight is below this threshold will be displayed in black. + */ + vpPclViewer(const std::string &title, const int &width = 640, const int &height = 480, const int &posU = 720, const int &posV = 560, const std::string &outFolder = std::string(), const double &ignoreThreshold = 0.95); + ~vpPclViewer(); + + /** + * @brief Set the name of the PCL viewer window. + * + * @param nameWindow The name of the PCL viewer window. + */ + void setNameWindow(const std::string &nameWindow); + + /** + * @brief Set the path to the output folder. + * If different from the empty string, the point clouds will be saved in this folder. + * @param outputFolder The path towards to the output folder, or empty if the point clouds must not be saved. + */ + void setOutFolder(const std::string &outputFolder); + + /** + * @brief Set the threshold below which a point must be displayed in black. + * + * @param thresh The threshold below which a point must be displayed in black. + */ + void setIgnoreThreshold(const double &thresh); + + /** + * @brief Add a surface to the list of point clouds known by the viewer. + * + * @param surface The surface that must be knwon to be displayed by the PCL viewer. + * @param name The name of the surface that will be displayed in the legend. If empty, it will be automatically generated. + * @param v_color A vector containing the 3 RGB values. + * @return unsigned int The ID by which is known the point cloud by the viewer. + */ + unsigned int addSurface(const pcl::PointCloud::Ptr &surface, const std::string &name = "", + const std::vector &v_color = std::vector()); + + /** + * @brief Add a surface to the list of point clouds known by the viewer. + * The points whose weights are below the \b vpPclViewer::s_ignoreThresh wil be displayed in black. + * + * @param surface The surface that must be knwon to be displayed by the PCL viewer. + * @param weights The confidence weights of each points. Must be between 0 and 1. + * @param name The name of the surface that will be displayed in the legend. If empty, it will be automatically generated. + * @param v_color A vector containing the 3 RGB values. + * @return unsigned int The ID by which is known the point cloud by the viewer. + */ + unsigned int addSurface(const pcl::PointCloud::Ptr &surface, const vpColVector &weights, + const std::string &name = "", + const std::vector &v_color = std::vector()); + + /** + * @brief Update the surface known by \b id by the viewer. + * + * @param surface The updated surface. + * @param id The ID of the surface that must be updated. + * @param hasToKeepColor If true, will be displayed in its original color. Otherwise, will be displayed in its default color. + */ + void updateSurface(const pcl::PointCloud::Ptr &surface, const unsigned int &id, + const bool &hasToKeepColor = false); + + /** + * @brief Update the surface known by \b id by the viewer. + * + * @param surface The updated surface. + * @param id The ID of the surface that must be updated. + * @param weights The confidence weights of each points. + * @param hasToKeepColor If true, will be displayed in its original color. Otherwise, will be displayed in its default color. + */ + void updateSurface(const pcl::PointCloud::Ptr &surface, const unsigned int &id, + const vpColVector &weights, const bool &hasToKeepColor = false); + + /** + * @brief Blocking-mode display of the viewer. + * + * @param blocking: True if we must wait for the user to press 'Q' to leave the method, false otherwise. + */ + void display(const bool &blocking = false); + + /** + * @brief Start the drawing thread that permits to have a non-blocking display. + */ + void launchThread(); + + /** + * @brief Stop the drawing thread that permits to have a non-blocking display. + */ + void stopThread(); + +protected: + /** + * @brief Method to update a point cloud known by the viewer when the drawing thread is running. + * The updated surface will be drawn with the default color that was affected to it. + * + * @param surface The updated surface. + * @param id The ID of the point cloud that must be updated. + */ + void threadUpdateSurface(const pcl::PointCloud::Ptr &surface, const unsigned int &id); + + /** + * @brief Method to update a point cloud known by the viewer when the drawing thread is running. + * The updated surface will be drawn with the color it contains. + * + * @param surface The updated surface. + * @param id The ID of the point cloud that must be updated. + */ + void threadUpdateSurfaceOriginalColor(const pcl::PointCloud::Ptr &surface, const unsigned int &id); + + /** + * @brief Method to update a point cloud known by the viewer when the drawing thread is running. + * The updated surface will be drawn with the default color that was affected to it. + * + * @param surface The updated surface. + * @param id The ID of the point cloud that must be updated. + * @param weights The confidence weights of each point. Must be between 0 and 1. + */ + void threadUpdateSurface(const pcl::PointCloud::Ptr &surface, const unsigned int &id, + const vpColVector &weights); + + /** + * @brief Method to update a point cloud known by the viewer when the drawing thread is running. + * The updated surface will be drawn with the color it contains. + * + * @param surface The updated surface. + * @param id The ID of the point cloud that must be updated. + * @param weights The confidence weights of each point. Must be between 0 and 1. + */ + void threadUpdateSurfaceOriginalColor(const pcl::PointCloud::Ptr &surface, const unsigned int &id, + const vpColVector &weights); + + + /** + * @brief Internal method that is called by \b vpPclViewer::launchThread to launch the + * drawing thread. + * + * @param p_viewer The pointer of the \b vpPclViewer object that will run the thread (is equal to \b this ). + */ + static void runThread(vpPclViewer *p_viewer); + + /** + * @brief The internal loop of the non-blocking drawing thread. + * + */ + void loopThread(); + + std::vector::Ptr> m_vPointClouds; /*!< The list of point clouds known by the viewer.*/ + std::vector> m_vhandler; /*!< The list of color handlers.*/ + pcl::visualization::PCLVisualizer::Ptr mp_viewer; /*!< The PCL viewer permitting the display.*/ + int m_width; /*!< The width of the window.*/ + int m_height; /*!< The height of the window.*/ + int m_posU; /*!< The position along the horizontal axis of the screen of the window.*/ + int m_posV; /*!< The position along the vertical axis of the screen of the window.*/ + double m_ignoreThresh; /*!< The minimum value of the confidence weight of a point to allow it to be displayed.*/ + std::vector m_vmeshid; /*!< The list of the point cloud names, for the legend.*/ + std::vector m_vlegends; /*!< The list of the legend items.*/ + std::vector m_vpmutex; /*!< The list of mutexes protecting the point clouds from data race when using the drawing thread.*/ + std::vector m_vweights; /*!< The list of confidence weights of each point cloud.*/ + std::thread m_threadDisplay; /*!< The non-blocking drawing thread.*/ + bool m_hasToRun; /*!< If true, the drawing thread is running. Otherwise, it is stopped.*/ + std::string m_title; /*!< The title of the viewer window.*/ + bool m_hasToSavePCDs; /*!< If true, the point clouds will be saved at each iteration of the drawing thread.*/ + std::string m_outFolder; /*!< If non empty, the path to the folders where the point clouds will be saved.*/ +}; +END_VISP_NAMESPACE +#endif // #if defined(VISP_HAVE_PCL) +#endif // _vpPclVisualizer_h_ diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpPlot.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpPlot.h new file mode 100644 index 00000000..8c387f47 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpPlot.h @@ -0,0 +1,208 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Plot curves. + */ + +/*! + \file vpPlot.h + \brief Plot curves. +*/ + +#ifndef VP_PLOT_H +#define VP_PLOT_H + +#include +#include +#include + +#if defined(VISP_HAVE_DISPLAY) + +BEGIN_VISP_NAMESPACE + +/*! + * \class vpPlot + * \ingroup group_gui_plotter + * + * \brief This class enables real time drawing of 2D or 3D graphics. An + * instance of the class open a window which contains between 1 and 4 + * graphics. Each one contains a desired number of curves. + * + * \warning This class is only available if one of the display functionalities + * (X11, GDI, GTK, OpenCV or Direct3D) is available. In visp3/core/vpConfig.h + * header file, you should have VISP_HAVE_DISPLAY define. + * + * The example below shows how to use the vpPlot class. + * + * \code + * #include + * + * int main () + * { + * #if defined(VISP_HAVE_DISPLAY) + * // Create a window (700 by 700) at position (100, 200) with two graphics + * vpPlot A(2, 700, 700, 100, 200, "Curves..."); + * + * // The first graphic contains 1 curve and the second graphic contains 2 curves + * A.initGraph(0,1); A.initGraph(1,2); + * + * // The color of the curve in the first graphic is red + * A.setColor(0,0,vpColor::red); + * // The first curve in the second graphic is green + * A.setColor(1,0,vpColor::green); + * // The second curve in the second graphic is blue + * A.setColor(1,1,vpColor::blue); + * + * // Add the point (0,0) in the first graphic + * A.plot(0,0,0,0); + * + * // Add the point (0,1) to the first curve of the second graphic + * A.plot(1,0,0,1); + * + * // Add the point (0,2) to the second curve of the second graphic + * A.plot(1,1,0,2); + * + * for (int i = 0; i < 50; ++i) { + * // Add the point (i,sin(i*pi/10) in the first graphic + * A.plot(0,0,i,sin(i*M_PI/10)); + * + * // Add the point (i,1) to the first curve of the second graphic + * A.plot(1,0,i,1); + * + * // Add the point (i,2) to the second curve of the second graphic + * A.plot(1,1,i,2); + * } + * + * return 0; + * #endif + * } + * \endcode + * + *

Tutorials & Examples

+ * + * Tutorials
+ * Another example that shows how to use this class + * to plot in real-time some curves during an image-based visual servo is explained in:
+ * + * - \ref tutorial-plotter +*/ +class VISP_EXPORT vpPlot +{ +public: + vpImage I; + +private: + vpDisplay *display; + + unsigned int graphNbr; + vpPlotGraph *graphList; + + unsigned int margei; + unsigned int margej; + + float factori; + float factorj; + + // private: + //#ifndef DOXYGEN_SHOULD_SKIP_THIS + // vpPlot(const vpPlot &) + // : I(), display(nullptr), graphNbr(0), graphList(nullptr), margei(0), + // margej(0), + // factori(0), factorj(0) + // { + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); + // } + // vpPlot &operator=(const vpPlot &){ + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); return *this; + // } + //#endif + +public: + vpPlot(); + vpPlot(unsigned int nbGraph, unsigned int height = 700, unsigned int width = 700, int x = -1, int y = -1, + const std::string &title = ""); + virtual ~vpPlot(); + void closeDisplay(); + void getPixelValue(bool block); + void init(unsigned int nbGraph, unsigned int height = 700, unsigned int width = 700, int x = -1, int y = -1, + const std::string &title = ""); + void initGraph(unsigned int graphNum, unsigned int curveNbr); + + void initRange(unsigned int graphNum, double xmin, double xmax, double ymin, double ymax); + void initRange(unsigned int graphNum, double xmin, double xmax, double ymin, double ymax, double zmin, double zmax); + void navigate(void); + + void plot(unsigned int graphNum, unsigned int curveNum, double x, double y); + void plot(unsigned int graphNum, double x, const vpColVector &v_y); + void plot(unsigned int graphNum, double x, const vpRowVector &v_y); + void plot(unsigned int graphNum, double x, const vpPoseVector &v_y); + void plot(unsigned int graphNum, double x, const vpTranslationVector &v_y); + void plot(unsigned int graphNum, double x, const vpRotationVector &v_y); + vpMouseButton::vpMouseButtonType plot(unsigned int graphNum, unsigned int curveNum, double x, double y, double z); + vpMouseButton::vpMouseButtonType plot(unsigned int graphNum, double x, const vpColVector &v_y, + const vpColVector &v_z); + + void resetPointList(unsigned int graphNum); + void resetPointList(unsigned int graphNum, unsigned int curveNum); + + void saveData(unsigned int graphNum, const std::string &dataFile, const std::string &title_prefix = ""); + void setColor(unsigned int graphNum, unsigned int curveNum, vpColor color); + void setGraphThickness(unsigned int graphNum, unsigned int thickness); + void setGridThickness(unsigned int graphNum, unsigned int thickness); + + /*! + * Set the font of the characters. The display should be initialized before. + * + * To know which font are available, on Unix you can use xfontsel or xlsfonts + * utilities. + */ + void setFont(const std::string &font) + { + if (display->isInitialised()) + vpDisplay::setFont(I, font.c_str()); + } + void setLegend(unsigned int graphNum, unsigned int curveNum, const std::string &legend); + void setTitle(unsigned int graphNum, const std::string &title); + void setUnitX(unsigned int graphNum, const std::string &unitx); + void setUnitY(unsigned int graphNum, const std::string &unity); + void setUnitZ(unsigned int graphNum, const std::string &unitz); + void setThickness(unsigned int graphNum, unsigned int curveNum, unsigned int thickness); + +private: + void initNbGraph(unsigned int nbGraph); + void displayGrid(); +}; + +END_VISP_NAMESPACE +#endif + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpPlotCurve.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpPlotCurve.h new file mode 100644 index 00000000..c616da76 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpPlotCurve.h @@ -0,0 +1,86 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Define a curve for the vpPlot class. + */ + +#ifndef VP_PLOT_CURVE_H +#define VP_PLOT_CURVE_H + +#include + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +#include +#include + +#include +#include + +#include +#include + +#include + +#if defined(VISP_HAVE_DISPLAY) + +BEGIN_VISP_NAMESPACE + +class vpPlotCurve +{ +public: + //! Different styles to plot the curve. + typedef enum { point, line, dashed_line, marker } vpCurveStyle; + vpColor color; + vpCurveStyle curveStyle; + unsigned int thickness; + unsigned int nbPoint; + vpImagePoint lastPoint; + std::list pointListx; + std::list pointListy; + std::list pointListz; + std::string legend; + double xmin; + double xmax; + double ymin; + double ymax; + +public: + vpPlotCurve(); + virtual ~vpPlotCurve(); + void plotPoint(const vpImage &I, const vpImagePoint &iP, double x, double y); + void plotList(const vpImage &I, double xorg, double yorg, double zoomx, double zoomy); +}; + + +END_VISP_NAMESPACE +#endif +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpPlotGraph.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpPlotGraph.h new file mode 100644 index 00000000..23117b88 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpPlotGraph.h @@ -0,0 +1,225 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Define a graph for the vpPlot class. + */ + +#ifndef VP_PLOT_GRAPH_H +#define VP_PLOT_GRAPH_H + +#include + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +#include +#include + +#include +#include + +#include +#include + +#include +#include + +#if defined(VISP_HAVE_DISPLAY) + +BEGIN_VISP_NAMESPACE + +class vpPlotGraph +{ +public: + double xorg; + double yorg; + double zoomx; + double zoomy; + double xmax; + double ymax; + double xmin; + double ymin; + double xdelt; + double ydelt; + bool gridx; + bool gridy; + vpColor gridColor; + std::string title; + std::string unitx; + std::string unity; + unsigned int curveNbr; + vpPlotCurve *curveList; + bool scaleInitialized; + bool firstPoint; + + int nbDivisionx; + int nbDivisiony; + + // Graph complet + vpImagePoint topLeft; + unsigned int width; + unsigned int height; + vpRect graphZone; + + // Zone d'affichage + vpImagePoint dTopLeft; + unsigned int dWidth; + unsigned int dHeight; + vpRect dGraphZone; + + // Zone d'affichage + vpImagePoint dTopLeft3D; + // int dWidth; + // int dHeight; + vpRect dGraphZone3D; + + // 3D part + vpCameraParameters cam; + vpHomogeneousMatrix cMo; + vpHomogeneousMatrix cMf; + double w_xval; + double w_xsize; + double w_yval; + double w_ysize; + double w_zval; + double w_zsize; + double ptXorg; + double ptYorg; + double ptZorg; + double zoomx_3D; + double zoomy_3D; + double zoomz_3D; + + int nbDivisionz; + + double zorg; + double zoomz; + double zmax; + double zmin; + double zdelt; + std::string unitz; + vpImagePoint old_iPr; + vpImagePoint old_iPz; + bool blockedr; + bool blockedz; + bool blocked; + + unsigned int epsi; + unsigned int epsj; + + bool dispUnit; + bool dispTitle; + bool dispLegend; + + unsigned int gridThickness; + + // private: + //#ifndef DOXYGEN_SHOULD_SKIP_THIS + // vpPlotGraph(const vpPlotGraph &) + // : xorg(0.), yorg(0.), zoomx(1.), zoomy(1.), xmax(10), ymax(10), + // xmin(0), ymin(-10), + // xdelt(1), ydelt(1), gridx(true), gridy(true), gridColor(), + // curveNbr(1), curveList(nullptr), scaleInitialized(false), + // firstPoint(true), nbDivisionx(10), nbDivisiony(10), topLeft(), + // width(0), height(0), graphZone(), dTopLeft(), dWidth(0), + // dHeight(0), dGraphZone(), dTopLeft3D(), dGraphZone3D(), cam(), + // cMo(), cMf(), w_xval(0), w_xsize(0), w_yval(0), w_ysize(0), + // w_zval(0), w_zsize(0), ptXorg(0), ptYorg(0), ptZorg(0), + // zoomx_3D(1.), zoomy_3D(1.), zoomz_3D(1.), nbDivisionz(10), + // zorg(1.), zoomz(1.), zmax(10), zmin(-10), zdelt(1), old_iPr(), + // old_iPz(), blockedr(false), blockedz(false), blocked(false), + // epsi(5), epsj(6), dispUnit(false), dispTitle(false), + // dispLegend(false), gridThickness(1) + // { + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); + // } + // vpPlotGraph &operator=(const vpPlotGraph &){ + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); return *this; + // } + //#endif + +public: + vpPlotGraph(); + virtual ~vpPlotGraph(); + + bool check3Dline(vpImagePoint &iP1, vpImagePoint &iP2); + bool check3Dpoint(vpImagePoint &iP); + void clearGraphZone(vpImage &I); + void computeGraphParameters(); + void computeGraphParameters3D(); + + void initGraph(unsigned int nbCurve); + void initSize(vpImagePoint topLeft, unsigned int width, unsigned int height, unsigned int margei, + unsigned int margej); + void initScale(vpImage &I, double xmin, double xmax, int nbDivx, double ymin, double ymax, int nbDivy, + bool gx, bool gy); + void initScale(vpImage &I, double xmin, double xmax, int nbDivx, double ymin, double ymax, int nbDivy, + double zmin, double zmax, int nbDivz, bool gx, bool gy); + + void displayGrid(vpImage &I); + void displayUnit(vpImage &I); + void displayTitle(vpImage &I); + void displayLegend(vpImage &I); + void displayGrid3D(vpImage &I); + + void findPose(); + + bool getPixelValue(vpImage &I, vpImagePoint &iP); + + bool move(const vpImage &I, vpMouseButton::vpMouseButtonType &button); + vpHomogeneousMatrix navigation(const vpImage &I, bool &changed, vpMouseButton::vpMouseButtonType &b); + + void plot(vpImage &I, unsigned int curveNb, double x, double y); + vpMouseButton::vpMouseButtonType plot(vpImage &I, unsigned int curveNb, double x, double y, double z); + void replot(vpImage &I); + void replot3D(vpImage &I); + + void rescalex(unsigned int side, double extremity); + void rescaley(unsigned int side, double extremity); + void rescalez(unsigned int side, double extremity); + // void rescale(double &min, double &max, double &delta, int nbDiv, int side); + void resetPointList(unsigned int curveNum); + + void setCurveColor(unsigned int curveNum, const vpColor &color); + void setCurveThickness(unsigned int curveNum, unsigned int thickness); + void setGridThickness(unsigned int thickness) { this->gridThickness = thickness; } + void setLegend(unsigned int curveNum, const std::string &legend); + void setTitle(const std::string &title); + void setUnitX(const std::string &unitx); + void setUnitY(const std::string &unity); + void setUnitZ(const std::string &unitz); +}; + + +END_VISP_NAMESPACE +#endif +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpProjectionDisplay.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpProjectionDisplay.h new file mode 100644 index 00000000..50aeaf55 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpProjectionDisplay.h @@ -0,0 +1,133 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Interface with the image for feature display. + */ + +#ifndef VP_PROJECTION_DISPLAY_H +#define VP_PROJECTION_DISPLAY_H + +/*! + \file vpProjectionDisplay.h + \brief interface with the image for feature display +*/ + +#include +#if defined(VISP_HAVE_DISPLAY) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +BEGIN_VISP_NAMESPACE + +/*! + \class vpProjectionDisplay + \ingroup group_gui_projection + \brief interface with the image for feature display +*/ +class VISP_EXPORT vpProjectionDisplay +{ +private: + vpImage Icam; + vpImage Iext; + +#if defined(VISP_HAVE_X11) + vpDisplayX dIcam; + vpDisplayX dIext; +#elif defined(VISP_HAVE_GTK) + vpDisplayGTK dIcam; + vpDisplayGTK dIext; +#elif defined(VISP_HAVE_GDI) + vpDisplayGDI dIcam; + vpDisplayGDI dIext; +#elif defined(HAVE_OPENCV_HIGHGUI) + vpDisplayOpenCV dIcam; + vpDisplayOpenCV dIext; +#elif defined(VISP_HAVE_D3D9) + vpDisplayD3D dIcam; + vpDisplayD3D dIext; +#endif +public: + void init(); + void init(int select); + void close(); + static int internalView() { return 0x01; } + static int externalView() { return 0x02; } + + /*! Default constructor. */ + vpProjectionDisplay() + : Icam(), Iext(), +#if defined(VISP_HAVE_DISPLAY) + dIcam(), dIext(), +#endif + listFp(), o(), x(), y(), z(), traj() + { + init(); + } + VP_EXPLICIT vpProjectionDisplay(int select) + : Icam(), Iext(), +#if defined(VISP_HAVE_DISPLAY) + dIcam(), dIext(), +#endif + listFp(), o(), x(), y(), z(), traj() + { + init(select); + } + + void insert(vpForwardProjection &fp); + void display(vpImage &I, const vpHomogeneousMatrix &cextMo, const vpHomogeneousMatrix &cMo, + const vpCameraParameters &cam, const vpColor &color, const bool &displayTraj = false, + unsigned int thickness = 1); + void displayCamera(vpImage &I, const vpHomogeneousMatrix &cextMo, const vpHomogeneousMatrix &cMo, + const vpCameraParameters &cam, unsigned int thickness = 1); + +private: + std::list listFp; + vpPoint o; + vpPoint x; + vpPoint y; + vpPoint z; + vpMatrix traj; +}; + + +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpWin32API.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpWin32API.h new file mode 100644 index 00000000..c5adb1bb --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpWin32API.h @@ -0,0 +1,78 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * GDI renderer for windows 32 display + */ + +#ifndef VP_WIN32_API_H +#define VP_WIN32_API_H + +#include + +#if (defined(VISP_HAVE_GDI) || defined(VISP_HAVE_D3D9)) +// Mute warning with clang-cl +// warning : non-portable path to file ''; specified path differs in case from file name on disk [-Wnonportable-system-include-path] +// warning : non-portable path to file ''; specified path differs in case from file name on disk [-Wnonportable-system-include-path] +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wnonportable-system-include-path" +#endif + +// Include WinSock2.h before windows.h to ensure that winsock.h is not +// included by windows.h since winsock.h and winsock2.h are incompatible +#include +#include + +#include + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +BEGIN_VISP_NAMESPACE + +DWORD vpProcessErrors(const std::string &api_name); +void vpSelectObject(HWND hWnd, HDC hDC, HDC hDCMem, HGDIOBJ h); +void vpPrepareImageWithPen(CRITICAL_SECTION *CriticalSection, HWND hWnd, HBITMAP bmp, COLORREF color, + unsigned int thickness, int style, HDC &hDCScreen, HDC &hDCMem, HPEN &hPen); +void vpEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection); +void vpLeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection); +BOOL vpReleaseSemaphore(HANDLE hSemaphore, LONG IReleaseCount, LPLONG lpPreviousCount); +BOOL vpLineTo(HDC hdc, int nXEnd, int nYEnd); +BOOL vpMoveToEx(HDC hdc, int X, int Y, LPPOINT lpPoint); +BOOL vpBitBlt(HDC hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, HDC hdcSrc, int nXSrc, int nYSrc, + DWORD dwRop); +BOOL vpInvalidateRect(HWND hWnd, const RECT *lpRect, BOOL bErase); +COLORREF vpSetPixel(HDC hdc, int X, int Y, COLORREF crColor); +HBITMAP vpCreateBitmap(int nWidth, int nHeight, UINT cPlanes, UINT cBitsPerPel, const VOID *lpvBits); + +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpWin32Renderer.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpWin32Renderer.h new file mode 100644 index 00000000..7131ef8d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpWin32Renderer.h @@ -0,0 +1,213 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Windows 32 renderer base class + */ + +#ifndef VP_WIN32_RENDERER_H +#define VP_WIN32_RENDERER_H + +#include + +#if (defined(VISP_HAVE_GDI) || defined(VISP_HAVE_D3D9)) + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +#include +#include + +// Mute warning with clang-cl +// warning : non-portable path to file ''; specified path differs in case from file name on disk [-Wnonportable-system-include-path] +// warning : non-portable path to file ''; specified path differs in case from file name on disk [-Wnonportable-system-include-path] +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wnonportable-system-include-path" +#endif + +// Include WinSock2.h before windows.h to ensure that winsock.h is not +// included by windows.h since winsock.h and winsock2.h are incompatible +#include +#include + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +BEGIN_VISP_NAMESPACE + +class VISP_EXPORT vpWin32Renderer +{ + +protected: + // the size of the display + unsigned int m_rwidth; + unsigned int m_rheight; + unsigned int m_rscale; + +public: + /*! + * Default constructor; + */ + vpWin32Renderer() : m_rwidth(0), m_rheight(0), m_rscale(1) { } + + /*! + * Copy constructor; + */ + vpWin32Renderer(const vpWin32Renderer &renderer) + { + *this = renderer; + } + + /*! + * Destructor. + */ + virtual ~vpWin32Renderer() { } + + /*! + * Copy operator; + */ + vpWin32Renderer &operator=(const vpWin32Renderer &renderer) + { + m_rwidth = renderer.m_rwidth; + m_rheight = renderer.m_rheight; + m_rscale = renderer.m_rscale; + return *this; + } + + //! Inits the display . + virtual bool init(HWND hWnd, unsigned int w, unsigned int h) = 0; + + //! Renders the image. + virtual bool render() = 0; + + /*! + Sets the image to display. + \param im The image to display. + */ + virtual void setImg(const vpImage &im) = 0; + virtual void setImg(const vpImage &im) = 0; + virtual void setImgROI(const vpImage &im, const vpImagePoint &iP, unsigned int width, + unsigned int height) = 0; + virtual void setImgROI(const vpImage &im, const vpImagePoint &iP, unsigned int width, + unsigned int height) = 0; + + /*! + Sets the pixel at (x,y). + \param iP The coordinates of the pixel. + \param color The color of the pixel. + */ + virtual void setPixel(const vpImagePoint &iP, const vpColor &color) = 0; + + void setScale(unsigned int scale) { m_rscale = scale; } + void setHeight(unsigned int height) { m_rheight = height; } + void setWidth(unsigned int width) { m_rwidth = width; } + + /*! + Draws a line. + \param ip1 it's starting point coordinates + \param ip2 it's ending point coordinates + \param color the line's color + \param thickness line thickness + \param style style of the line + */ + virtual void drawLine(const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color, unsigned int thickness, + int style = PS_SOLID) = 0; + + /*! + Draws a rectangle. + \param topLeft it's top left point coordinates + \param width width of the rectangle + \param height height of the rectangle + \param color The rectangle's color + \param fill True if it is a filled rectangle + \param thickness line thickness + */ + virtual void drawRect(const vpImagePoint &topLeft, unsigned int width, unsigned int height, const vpColor &color, + bool fill = false, unsigned int thickness = 1) = 0; + + /*! + Clears the image to color c. + \param color The color used to fill the image. + */ + virtual void clear(const vpColor &color) = 0; + + /*! + Draws a circle. + \param center its center point coordinates + \param radius The circle's radius + \param color The circle's color + \param fill When true fill the circle with the given color + \param thickness Drawing thickness + */ + virtual void drawCircle(const vpImagePoint ¢er, unsigned int radius, const vpColor &color, bool fill, + unsigned int thickness = 1) = 0; + + /*! + Draws some text. + \param ip it's top left point coordinates + \param text The string to display + \param color The text's color + */ + virtual void drawText(const vpImagePoint &ip, const char *text, const vpColor &color) = 0; + + /*! + Draws a cross. + \param ip it's center point coordinates + \param size Size of the cross + \param color The cross' color + \param thickness Thickness of the drawing + */ + virtual void drawCross(const vpImagePoint &ip, unsigned int size, const vpColor &color, + unsigned int thickness = 1) = 0; + + /*! + Draws an arrow. + \param[in] ip1 It's starting point coordinates. + \param[in] ip2 It's ending point coordinates. + \param[in] color The line's color. + \param[in] w Arrow width. + \param[in] h Arrow height. + \param[in] thickness Thickness of the drawing + */ + virtual void drawArrow(const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color, unsigned int w, + unsigned int h, unsigned int thickness) = 0; + + /*! + Gets the currently displayed image. + \param I Image returned. + */ + virtual void getImage(vpImage &I) = 0; +}; + + +END_VISP_NAMESPACE +#endif +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpWin32Window.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpWin32Window.h new file mode 100644 index 00000000..d80cd41b --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/gui/vpWin32Window.h @@ -0,0 +1,152 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Windows 32 display's window class + */ + +#ifndef VP_WIN32_WINDOW_H +#define VP_WIN32_WINDOW_H + +#include + +#if (defined(VISP_HAVE_GDI) || defined(VISP_HAVE_D3D9)) + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +// Mute warning with clang-cl +// warning : non-portable path to file ''; specified path differs in case from file name on disk [-Wnonportable-system-include-path] +// warning : non-portable path to file ''; specified path differs in case from file name on disk [-Wnonportable-system-include-path] +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wnonportable-system-include-path" +#endif + +// Include WinSock2.h before windows.h to ensure that winsock.h is not +// included by windows.h since winsock.h and winsock2.h are incompatible +#include +#include +#include +#include +#include + +#include + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +BEGIN_VISP_NAMESPACE + +// ViSP-defined messages for window's callback function +#define vpWM_GETCLICK WM_USER + 1 +#define vpWM_DISPLAY WM_USER + 2 +#define vpWM_GETCLICKUP WM_USER + 3 +#define vpWM_CLOSEDISPLAY WM_USER + 4 +#define vpWM_GETPOINTERMOTIONEVENT WM_USER + 5 +#define vpWM_DISPLAY_ROI WM_USER + 6 + +// No specific mouse button query +#define vpNO_BUTTON_QUERY -1 + +class vpDisplayWin32; + +class VISP_EXPORT vpWin32Window +{ +private: + HINSTANCE hInst; + + //! Window's handle + HWND hWnd; + + //! Window is initialized + bool initialized; + //! Handle for the initialization semaphore + HANDLE semaInit; + + //! Handle for the getClick semaphore + HANDLE semaClick; + //! Handle for the getClickUp semaphore + HANDLE semaClickUp; + //! Handle for the keyborad event semaphore + HANDLE semaKey; + //! Handle for the mouse event semaphore + HANDLE semaMove; + + //! X coordinate of the click + int clickX; + int clickXUp; + //! Y coordinate of the click + int clickY; + int clickYUp; + //! X coordinate of the mouse + int coordX; + //! Y coordinate of the mouse + int coordY; + // Keyboard key + char lpString[10]; + //! Button used for the click + vpMouseButton::vpMouseButtonType clickButton; + vpMouseButton::vpMouseButtonType clickButtonUp; + + //! True if the window's class has already been registered + static bool registered; + + //! The renderer used by the window + vpWin32Renderer *renderer; + +public: + VP_EXPLICIT vpWin32Window(vpWin32Renderer *rend = nullptr); + vpWin32Window(const vpWin32Window &window); + virtual ~vpWin32Window(); + vpWin32Window &operator=(const vpWin32Window &window); + + HWND getHWnd() { return hWnd; } + + //! Returns true if the window is initialized + bool isInitialized() { return initialized; } + + //! Initialize the window + void initWindow(const char *title, int posx, int posy, unsigned int w, unsigned int h); + + void setScale(unsigned int scale) { renderer->setScale(scale); } + + // Friend classes + friend class vpDisplayWin32; + friend class vpDisplayD3D; + friend class vpDisplayGDI; + + //! The message loop + friend LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); +}; + +END_VISP_NAMESPACE +#endif +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/imgproc/vpCircleHoughTransform.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/imgproc/vpCircleHoughTransform.h new file mode 100644 index 00000000..08d30dbb --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/imgproc/vpCircleHoughTransform.h @@ -0,0 +1,1447 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#ifndef VP_CIRCLE_HOUGH_TRANSFORM_H +#define VP_CIRCLE_HOUGH_TRANSFORM_H + +// System includes +#include +#include + +// ViSP includes +#include +#include +#include +#include +#include + +// 3rd parties inclue +#ifdef VISP_HAVE_NLOHMANN_JSON +#include VISP_NLOHMANN_JSON(json.hpp) +#endif + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17) +#include +#endif + +BEGIN_VISP_NAMESPACE + +/** + * \ingroup group_hough_transform + * \brief Class that permits to detect 2D circles in a image using + * the gradient-based Circle Hough transform. + * Please find more information on the algorithm + * [here](https://theailearner.com/tag/hough-gradient-method-opencv/) + * +*/ +class VISP_EXPORT vpCircleHoughTransform +{ +public: + /** + * \brief Class that gather the algorithm parameters. + */ + class vpCircleHoughTransformParams + { + public: + /** + * \brief Construct a new vpCircleHoughTransformParams object with default parameters. + */ + vpCircleHoughTransformParams() + : m_filteringAndGradientType(vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) + , m_gaussianStdev(1.f) + , m_lowerCannyThresh(-1.f) + , m_upperCannyThresh(-1.f) + , m_edgeMapFilteringNbIter(1) + , m_cannyBackendType(vpImageFilter::CANNY_OPENCV_BACKEND) + , m_lowerCannyThreshRatio(0.6f) + , m_upperCannyThreshRatio(0.8f) + , m_centerXlimits(std::pair(std::numeric_limits::min(), std::numeric_limits::max())) + , m_centerYlimits(std::pair(std::numeric_limits::min(), std::numeric_limits::max())) + , m_minRadius(0.f) + , m_maxRadius(1000.f) + , m_centerMinThresh(50.f) + , m_expectedNbCenters(-1) + , m_circleProbaThresh(0.9f) + , m_circlePerfectness(0.9f) + , m_circleVisibilityRatioThresh(0.1f) + , m_recordVotingPoints(false) + , m_centerMinDist(15.f) + , m_mergingRadiusDiffThresh(1.5f * m_centerMinDist) + { + const unsigned int gaussianKernelSize_default = 5; + const unsigned int gradientFilterKernelSize_default = 3; + const unsigned int dilatationKernelSize_default = 3; + const unsigned int averagingWindowSize_default = 5; + + m_gaussianKernelSize = gaussianKernelSize_default; + m_gradientFilterKernelSize = gradientFilterKernelSize_default; + m_dilatationKernelSize = dilatationKernelSize_default; + m_averagingWindowSize = averagingWindowSize_default; + } + + /** + * \brief Construct a new vpCircleHoughTransformParams object. + * + * \param[in] gaussianKernelSize Size of the Gaussian filter kernel used to smooth the input image. Must be an odd number. + * \param[in] gaussianStdev Standard deviation of the Gaussian filter. + * \param[in] gradientFilterKernelSize Size of the Sobel or Scharr kernels used to compute the gradients. Must be an odd number. + * \param[in] lowerCannyThresh The lower threshold for the Canny operator. Values lower than this value are rejected. + A negative value makes the algorithm compute this threshold and the lower one automatically. + * \param[in] upperCannyThresh The upper threshold for the Canny operator. Only values greater than this value are marked as an edge. + A negative value makes the algorithm compute this threshold and the lower one automatically. + * \param[in] edgeMapFilterNbIter Number of 8-neighbor connectivity filtering iterations to apply to the edge map. + * \param[in] centerXlimits Minimum and maximum position on the horizontal axis of the center of the circle we want to detect. + * \param[in] centerYlimits Minimum and maximum position on the vertical axis of the center of the circle we want to detect. + * \param[in] minRadius Minimum radius of the circles we want to detect. + * \param[in] maxRadius Maximum radius of the circles we want to detect. + * \param[in] dilatationKernelSize Kernel size of the dilatation that is performed to detect the maximum number of votes for the center candidates. + * \param[in] averagingWindowSize Size of the averaging window around the maximum number of votes to compute the + center candidate such as it is the barycenter of the window. Must be odd. + * \param[in] centerThresh Minimum number of votes a point must exceed to be considered as center candidate. + * \param[in] circleProbabilityThresh Probability threshold in order to keep a circle candidate. + * \param[in] circlePerfectness The threshold for the colinearity between the gradient of a point + and the radius it would form with a center candidate to be able to vote. + The formula to get the equivalent angle is: `angle = acos(circle_perfectness)`. + * \param[in] centerMinDistThresh Two circle candidates whose centers are closer than this threshold are considered for merging. + * \param[in] mergingRadiusDiffThresh Maximum radius difference between two circle candidates to consider merging them. + * \param[in] filteringAndGradientMethod The choice of the filter and gradient operator to apply before the edge + * detection step. + * \param[in] backendType Permits to choose the backend used to compute the edge map. + * \param[in] lowerCannyThreshRatio If the thresholds must be computed,the lower threshold will be equal to the upper + * threshold times \b lowerThresholdRatio . + * \param[in] upperCannyThreshRatio If the thresholds must be computed,the upper threshold will be equal to the value + * such as the number of pixels of the image times \b upperThresholdRatio have an absolute gradient lower than the + * upper threshold. + * \param[in] expectedNbCenters Expected number of centers in the image. If the number is negative, all the centers + * are kept. Otherwise, maximum up to this number of centers are kept. + * \param[in] recordVotingPoints If true, the edge-map points having voted for each circle will be stored. + * \param[in] visibilityRatioThresh Visibility threshold: which minimum ratio of the circle must be visible in order to keep a circle candidate. + */ + vpCircleHoughTransformParams( + const int &gaussianKernelSize + , const float &gaussianStdev + , const int &gradientFilterKernelSize + , const float &lowerCannyThresh + , const float &upperCannyThresh + , const int &edgeMapFilterNbIter + , const std::pair ¢erXlimits + , const std::pair ¢erYlimits + , const float &minRadius + , const float &maxRadius + , const int &dilatationKernelSize + , const int &averagingWindowSize + , const float ¢erThresh + , const float &circleProbabilityThresh + , const float &circlePerfectness + , const float ¢erMinDistThresh + , const float &mergingRadiusDiffThresh + , const vpImageFilter::vpCannyFilteringAndGradientType &filteringAndGradientMethod = vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING + , const vpImageFilter::vpCannyBackendType &backendType = vpImageFilter::CANNY_OPENCV_BACKEND + , const float &lowerCannyThreshRatio = 0.6f + , const float &upperCannyThreshRatio = 0.8f + , const int &expectedNbCenters = -1 + , const bool &recordVotingPoints = false + , const float &visibilityRatioThresh = 0.1f + ) + : m_filteringAndGradientType(filteringAndGradientMethod) + , m_gaussianKernelSize(gaussianKernelSize) + , m_gaussianStdev(gaussianStdev) + , m_gradientFilterKernelSize(gradientFilterKernelSize) + , m_lowerCannyThresh(lowerCannyThresh) + , m_upperCannyThresh(upperCannyThresh) + , m_edgeMapFilteringNbIter(edgeMapFilterNbIter) + , m_cannyBackendType(backendType) + , m_lowerCannyThreshRatio(lowerCannyThreshRatio) + , m_upperCannyThreshRatio(upperCannyThreshRatio) + , m_centerXlimits(centerXlimits) + , m_centerYlimits(centerYlimits) + , m_minRadius(std::min(minRadius, maxRadius)) + , m_maxRadius(std::max(minRadius, maxRadius)) + , m_dilatationKernelSize(dilatationKernelSize) + , m_averagingWindowSize(averagingWindowSize) + , m_centerMinThresh(centerThresh) + , m_expectedNbCenters(expectedNbCenters) + , m_circleProbaThresh(circleProbabilityThresh) + , m_circlePerfectness(circlePerfectness) + , m_circleVisibilityRatioThresh(visibilityRatioThresh) + , m_recordVotingPoints(recordVotingPoints) + , m_centerMinDist(centerMinDistThresh) + , m_mergingRadiusDiffThresh(mergingRadiusDiffThresh) + { } + + /** + * \brief Get the size of the Gaussian filter kernel used to smooth the input image. + * + * \return int The size of the kernel. + */ + inline int getGaussianKernelSize() const + { + return m_gaussianKernelSize; + } + + /** + * \brief Get the standard deviation of the Gaussian filter. + * + * \return float The standard deviation. + */ + inline float getGaussianStdev() const + { + return m_gaussianStdev; + } + + /** + * \brief Get the size of the gradient kernel filters used to compute the gradients. + * + * \return int The size of the kernel. + */ + inline int getGradientKernelSize() const + { + return m_gradientFilterKernelSize; + } + + /** + * \brief Get the lower threshold for the Canny operator. Values lower than this value are rejected. + * A negative value means that the algorithm computes the lower threshold automatically. + * + * \return float The lower Canny threshold. + */ + inline float getLowerCannyThreshold() const + { + return m_lowerCannyThresh; + } + + /** + * \brief Get the upper threshold for the Canny operator. Values lower than this value are rejected. + * A negative value means that the algorithm computes the lower and upper thresholds automatically. + * + * \return float The upper Canny threshold. + */ + inline float getUpperCannyThreshold() const + { + return m_upperCannyThresh; + } + + /** + * \brief Get the number of iterations of 8-neighbor connectivity filtering to apply to the edge map. + * + * \return int The number of iterations. + */ + inline int getEdgeMapFilteringNbIter() const + { + return m_edgeMapFilteringNbIter; + } + + /** + * \brief Get the minimum and maximum position on the horizontal axis of the center of the circle we want to detect. + * + * \return std::pair The min and max x positions. + */ + inline std::pair getCenterXLimits() const + { + return m_centerXlimits; + } + + /** + * \brief Get the minimum and maximum position on the vertical axis of the center of the circle we want to detect. + * + * \return std::pair The min and max y positions. + */ + inline std::pair getCenterYLimits() const + { + return m_centerYlimits; + } + + /** + * \brief Get the minimum radius of the circles we want to detect. + * + * \return float The radius min. + */ + inline float getMinRadius() const + { + return m_minRadius; + } + + /** + * \brief Get the maximum radius of the circles we want to detect. + * + * \return float The radius max. + */ + inline float getMaxRadius() const + { + return m_maxRadius; + } + + /** + * \brief Get the kernel size of the dilatation that is performed to detect the maximum number of votes + * for the center candidates. + * + * \return int The kernel size. + */ + inline int getDilatationKernelSize() const + { + return m_dilatationKernelSize; + } + + /** + * \brief Get the size of the averaging window around the maximum number of votes to compute the + * center candidate such as it is the barycenter of the window. + * + * \return int The size of the averaging window. + */ + inline int getAveragingWindowSize() const + { + return m_averagingWindowSize; + } + + /** + * \brief Get the minimum number of votes a point must exceed to be considered as center candidate. + * + * \return float The threshold. + */ + inline float getCenterMinThreshold() const + { + return m_centerMinThresh; + } + + /** + * \brief Get the expected number of centers in the image. If the number is negative, all the centers + * are kept. Otherwise, maximum up to this number of centers are kept. + * + * \return int The expected number of centers. + */ + inline int getExpectedNbCenters() const + { + return m_expectedNbCenters; + } + + /** + * \brief Get the probability threshold in order to keep a circle candidate. + * + * \return float The threshold. + */ + inline float getProbabilityThreshold() const + { + return m_circleProbaThresh; + } + + /** + * \brief Get the visibility ratio threshold in order to keep a circle candidate. + * + * \return float The threshold. + */ + inline float getVisibilityRatioThreshold() const + { + return m_circleVisibilityRatioThresh; + } + + /** + * \brief Get the threshold for the colinearity between the gradient of a point + * and the radius it would form with a center candidate to be able to vote. + * The formula to get the equivalent angle is: `angle = acos(circle_perfectness)`. + * + * \return float The threshold. + */ + inline float getCirclePerfectness() const + { + return m_circlePerfectness; + } + + /** + * \brief Get the boolean indicating if we have to record the edge-map points having voted for the circles. + * + * \return bool True if we have to record the voting points. + */ + inline bool getRecordVotingPoints() const + { + return m_recordVotingPoints; + } + + /** + * \brief Get the Maximum distance between two circle candidates centers to consider merging them. + * + * \return float The maximum distance between two centers. + */ + inline float getCenterMinDist() const + { + return m_centerMinDist; + } + + /** + * \brief Get the Maximum radius difference between two circle candidates to consider merging them. + * + * @return float The merging radius difference. + */ + inline float getMergingRadiusDiff() const + { + return m_mergingRadiusDiffThresh; + } + + /** + * Create a string with all the Hough transform parameters. + */ + std::string toString() const + { + std::stringstream txt; + txt << "Hough Circle Transform Configuration:\n"; + txt << "\tFiltering + gradient operators = " << vpImageFilter::vpCannyFiltAndGradTypeToStr(m_filteringAndGradientType) << "\n"; + txt << "\tGaussian filter kernel size = " << m_gaussianKernelSize << "\n"; + txt << "\tGaussian filter standard deviation = " << m_gaussianStdev << "\n"; + txt << "\tGradient filter kernel size = " << m_gradientFilterKernelSize << "\n"; + txt << "\tCanny backend = " << vpImageFilter::vpCannyBackendTypeToString(m_cannyBackendType) << "\n"; + txt << "\tCanny edge filter thresholds = [" << m_lowerCannyThresh << " ; " << m_upperCannyThresh << "]\n"; + txt << "\tCanny edge filter thresholds ratio (for auto-thresholding) = [" << m_lowerCannyThreshRatio << " ; " << m_upperCannyThreshRatio << "]\n"; + txt << "\tEdge map 8-neighbor connectivity filtering number of iterations = " << m_edgeMapFilteringNbIter << "\n"; + txt << "\tCenter horizontal position limits: min = " << m_centerXlimits.first << "\tmax = " << m_centerXlimits.second << "\n"; + txt << "\tCenter vertical position limits: min = " << m_centerYlimits.first << "\tmax = " << m_centerYlimits.second << "\n"; + txt << "\tRadius limits: min = " << m_minRadius << "\tmax = " << m_maxRadius << "\n"; + txt << "\tKernel size of the dilatation filter = " << m_dilatationKernelSize << "\n"; + txt << "\tAveraging window size for center detection = " << m_averagingWindowSize << "\n"; + txt << "\tCenters votes threshold = " << m_centerMinThresh << "\n"; + txt << "\tExpected number of centers = "; + if (m_expectedNbCenters > 0) { + txt << m_expectedNbCenters; + } + else { + txt << "no limits"; + } + txt << "\n"; + txt << "\tCircle probability threshold = " << m_circleProbaThresh << "\n"; + txt << "\tCircle visibility ratio threshold = " << m_circleVisibilityRatioThresh << "\n"; + txt << "\tCircle perfectness threshold = " << m_circlePerfectness << "\n"; + txt << "\tRecord voting points = "; + txt << (m_recordVotingPoints ? std::string("true") : std::string("false")) << "\n"; + txt << "\tCenters minimum distance = " << m_centerMinDist << "\n"; + txt << "\tRadius difference merging threshold = " << m_mergingRadiusDiffThresh << "\n"; + return txt.str(); + } + + // // Configuration from files +#ifdef VISP_HAVE_NLOHMANN_JSON + /** + * \brief Create a new vpCircleHoughTransformParams from a JSON file. + * + * \param[in] jsonFile The path towards the JSON file. + * \return vpCircleHoughTransformParams The corresponding vpCircleHoughTransformParams object. + */ + inline static vpCircleHoughTransformParams createFromJSON(const std::string &jsonFile) + { + using json = nlohmann::json; + + std::ifstream file(jsonFile); + if (!file.good()) { + std::stringstream ss; + ss << "Problem opening file " << jsonFile << ". Make sure it exists and is readable" << std::endl; + throw vpException(vpException::ioError, ss.str()); + } + json j; + try { + j = json::parse(file); + } + catch (json::parse_error &e) { + std::stringstream msg; + msg << "Could not parse JSON file : \n"; + + msg << e.what() << std::endl; + msg << "Byte position of error: " << e.byte; + throw vpException(vpException::ioError, msg.str()); + } + vpCircleHoughTransformParams params = j; // Call from_json(const json& j, vpDetectorDNN& *this) to read json + file.close(); + return params; + } + + /** + * \brief Save the configuration of the detector in a JSON file + * described by the path \b jsonPath. Throw a \b vpException + * is the file cannot be created. + * + * \param[in] jsonPath The path towards the JSON output file. + */ + inline void saveConfigurationInJSON(const std::string &jsonPath) const + { + using json = nlohmann::json; + std::ofstream file(jsonPath); + const json j = *this; + const int indent = 4; + file << j.dump(indent); + file.close(); + } + + /** + * \brief Read the detector configuration from JSON. All values are optional and if an argument is not present, + * the default value defined in the constructor is kept + * + * \param[in] j : The JSON object, resulting from the parsing of a JSON file. + * \param[out] params : The circle Hough transform parameters that will be initialized from the JSON data. + */ + friend inline void from_json(const nlohmann::json &j, vpCircleHoughTransformParams ¶ms) + { + std::string filteringAndGradientName = vpImageFilter::vpCannyFiltAndGradTypeToStr(params.m_filteringAndGradientType); + filteringAndGradientName = j.value("filteringAndGradientType", filteringAndGradientName); + params.m_filteringAndGradientType = vpImageFilter::vpCannyFiltAndGradTypeFromStr(filteringAndGradientName); + + params.m_gaussianKernelSize = j.value("gaussianKernelSize", params.m_gaussianKernelSize); + const int checkEvenModulo = 2; + if ((params.m_gaussianKernelSize % checkEvenModulo) != 1) { + throw vpException(vpException::badValue, "Gaussian Kernel size should be odd."); + } + + params.m_gaussianStdev = j.value("gaussianStdev", params.m_gaussianStdev); + if (params.m_gaussianStdev <= 0) { + throw vpException(vpException::badValue, "Standard deviation should be > 0"); + } + + params.m_gradientFilterKernelSize = j.value("gradientFilterKernelSize", params.m_gradientFilterKernelSize); + if ((params.m_gradientFilterKernelSize % checkEvenModulo) != 1) { + throw vpException(vpException::badValue, "Gradient filter kernel (Sobel or Scharr) size should be odd."); + } + + std::string cannyBackendName = vpImageFilter::vpCannyBackendTypeToString(params.m_cannyBackendType); + cannyBackendName = j.value("cannyBackendType", cannyBackendName); + params.m_cannyBackendType = vpImageFilter::vpCannyBackendTypeFromString(cannyBackendName); + params.m_lowerCannyThresh = j.value("lowerCannyThresh", params.m_lowerCannyThresh); + params.m_lowerCannyThreshRatio = j.value("lowerThresholdRatio", params.m_lowerCannyThreshRatio); + params.m_upperCannyThresh = j.value("upperCannyThresh", params.m_upperCannyThresh); + params.m_upperCannyThreshRatio = j.value("upperThresholdRatio", params.m_upperCannyThreshRatio); + params.m_edgeMapFilteringNbIter = j.value("edgeMapFilteringNbIter", params.m_edgeMapFilteringNbIter); + + params.m_centerXlimits = j.value("centerXlimits", params.m_centerXlimits); + params.m_centerYlimits = j.value("centerYlimits", params.m_centerYlimits); + std::pair radiusLimits = j.value("radiusLimits", std::pair(params.m_minRadius, params.m_maxRadius)); + params.m_minRadius = std::min(radiusLimits.first, radiusLimits.second); + params.m_maxRadius = std::max(radiusLimits.first, radiusLimits.second); + + params.m_dilatationKernelSize = j.value("dilatationKernelSize", params.m_dilatationKernelSize); + params.m_averagingWindowSize = j.value("averagingWindowSize", params.m_averagingWindowSize); + if ((params.m_averagingWindowSize <= 0) || ((params.m_averagingWindowSize % checkEvenModulo) == 0)) { + throw vpException(vpException::badValue, "Averaging window size must be positive and odd."); + } + + params.m_centerMinThresh = j.value("centerThresh", params.m_centerMinThresh); + if (params.m_centerMinThresh <= 0) { + throw vpException(vpException::badValue, "Votes thresholds for center detection must be positive."); + } + + params.m_expectedNbCenters = j.value("expectedNbCenters", params.m_expectedNbCenters); + + + params.m_circleProbaThresh = j.value("circleProbabilityThreshold", params.m_circleProbaThresh); + params.m_circleVisibilityRatioThresh = j.value("circleVisibilityRatioThreshold", params.m_circleVisibilityRatioThresh); + + params.m_circlePerfectness = j.value("circlePerfectnessThreshold", params.m_circlePerfectness); + + if ((params.m_circlePerfectness <= 0) || (params.m_circlePerfectness > 1)) { + throw vpException(vpException::badValue, "Circle perfectness must be in the interval ] 0; 1]."); + } + + params.m_recordVotingPoints = j.value("recordVotingPoints", params.m_recordVotingPoints); + + params.m_centerMinDist = j.value("centerMinDistance", params.m_centerMinDist); + if (params.m_centerMinDist <= 0) { + throw vpException(vpException::badValue, "Centers minimum distance threshold must be positive."); + } + + params.m_mergingRadiusDiffThresh = j.value("mergingRadiusDiffThresh", params.m_mergingRadiusDiffThresh); + if (params.m_mergingRadiusDiffThresh <= 0) { + throw vpException(vpException::badValue, "Radius difference merging threshold must be positive."); + } + } + + /** + * \brief Parse a vpCircleHoughTransform into JSON format. + * + * \param[out] j : A JSON parser object. + * \param[in] params : The circle Hough transform parameters that will be serialized in the json object. + */ + friend inline void to_json(nlohmann::json &j, const vpCircleHoughTransformParams ¶ms) + { + std::pair radiusLimits = { params.m_minRadius, params.m_maxRadius }; + + j = nlohmann::json { + {"filteringAndGradientType", vpImageFilter::vpCannyFiltAndGradTypeToStr(params.m_filteringAndGradientType)}, + {"gaussianKernelSize", params.m_gaussianKernelSize}, + {"gaussianStdev", params.m_gaussianStdev}, + {"gradientFilterKernelSize", params.m_gradientFilterKernelSize}, + {"cannyBackendType", vpImageFilter::vpCannyBackendTypeToString(params.m_cannyBackendType)}, + {"lowerCannyThresh", params.m_lowerCannyThresh}, + {"lowerThresholdRatio", params.m_lowerCannyThreshRatio}, + {"upperCannyThresh", params.m_upperCannyThresh}, + {"upperThresholdRatio", params.m_upperCannyThreshRatio}, + {"edgeMapFilteringNbIter", params.m_edgeMapFilteringNbIter}, + {"centerXlimits", params.m_centerXlimits}, + {"centerYlimits", params.m_centerYlimits}, + {"radiusLimits", radiusLimits}, + {"dilatationKernelSize", params.m_dilatationKernelSize}, + {"averagingWindowSize", params.m_averagingWindowSize}, + {"centerThresh", params.m_centerMinThresh}, + {"expectedNbCenters", params.m_expectedNbCenters}, + {"circleProbabilityThreshold", params.m_circleProbaThresh}, + {"circleVisibilityRatioThreshold", params.m_circleVisibilityRatioThresh}, + {"circlePerfectnessThreshold", params.m_circlePerfectness}, + {"recordVotingPoints", params.m_recordVotingPoints}, + {"centerMinDistance", params.m_centerMinDist}, + {"mergingRadiusDiffThresh", params.m_mergingRadiusDiffThresh} }; + } +#endif + friend VISP_EXPORT bool operator==(const vpImageCircle &a, const vpImageCircle &b); + + private: + // // Filtering + gradient operators to use + vpImageFilter::vpCannyFilteringAndGradientType m_filteringAndGradientType; /*!< Permits to choose the filtering + + gradient operators to use.*/ + + // // Gaussian smoothing attributes + int m_gaussianKernelSize; /*!< Size of the Gaussian filter kernel used to smooth the input image. + Must be an odd number.*/ + float m_gaussianStdev; /*!< Standard deviation of the Gaussian filter.*/ + + // // Gradient computation attributes + int m_gradientFilterKernelSize; /*!< Size of the Sobel or Scharr kernels used to compute the gradients. Must be an odd number.*/ + + // // Edge detection attributes + float m_lowerCannyThresh; /*!< The lower threshold for the Canny operator. Values lower than this value are rejected. + A negative value makes the algorithm compute the lower threshold automatically.*/ + float m_upperCannyThresh; /*!< The upper threshold for the Canny operator. Only values greater than this value are marked as an edge. + A negative value makes the algorithm compute the upper and lower thresholds automatically.*/ + int m_edgeMapFilteringNbIter; /*!< Number of iterations of 8-neighbor connectivity filtering to apply to the edge map*/ + vpImageFilter::vpCannyBackendType m_cannyBackendType; /*!< Permits to choose the backend used to compute the edge map.*/ + float m_lowerCannyThreshRatio; /*!< The ratio of the upper threshold the lower threshold must be equal to. + It is used only if the user asks to compute the Canny thresholds.*/ + float m_upperCannyThreshRatio; /*!< The ratio of pixels whose absolute gradient Gabs is lower or equal to define + the upper threshold. It is used only if the user asks to compute the Canny thresholds.*/ + + // // Center candidates computation attributes + std::pair m_centerXlimits; /*!< Minimum and maximum position on the horizontal axis of the center of the circle we want to detect.*/ + std::pair m_centerYlimits; /*!< Minimum and maximum position on the vertical axis of the center of the circle we want to detect.*/ + float m_minRadius; /*!< Minimum radius of the circles we want to detect.*/ + float m_maxRadius; /*!< Maximum radius of the circles we want to detect.*/ + int m_dilatationKernelSize; /*!< Kernel size of the dilatation that is performed to detect the maximum number of votes for the center candidates.*/ + int m_averagingWindowSize; /*!< Size of the averaging window around the maximum number of votes to compute the + center candidate such as it is the barycenter of the window. Must be odd.*/ + float m_centerMinThresh; /*!< Minimum number of votes a point must exceed to be considered as center candidate.*/ + int m_expectedNbCenters; /*!< Expected number of different centers in the image. If negative, all candidates centers + are kept, otherwise only up to this number are kept.*/ + + // // Circle candidates computation attributes + float m_circleProbaThresh; /*!< Probability threshold in order to keep a circle candidate.*/ + float m_circlePerfectness; /*!< The threshold for the colinearity between the gradient of a point + and the radius it would form with a center candidate to be able to vote. + The formula to get the equivalent angle is: `angle = acos(circle_perfectness)`. */ + float m_circleVisibilityRatioThresh; /*!< Visibility ratio threshold: minimum ratio of the circle must be visible in order to keep a circle candidate.*/ + bool m_recordVotingPoints; /*!< If true, the edge-map points having voted for each circle will be stored.*/ + + // // Circle candidates merging attributes + float m_centerMinDist; /*!< Maximum distance between two circle candidates centers to consider merging them.*/ + float m_mergingRadiusDiffThresh; /*!< Maximum radius difference between two circle candidates to consider merging them.*/ + + friend class vpCircleHoughTransform; + }; + +#ifdef VISP_BUILD_DEPRECATED_FUNCTIONS + typedef vpCircleHoughTransformParams vpCircleHoughTransformParameters; +#endif + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + /** + * \brief Data storage for the computation of the center candidates. + */ + typedef struct vpCenterVotes + { + std::pair m_position; + float m_votes; + } vpCenterVotes; +#endif + +/** + * \brief Construct a new vpCircleHoughTransform object with default parameters. + */ + vpCircleHoughTransform(); + + /** + * \brief Construct a new vpCircleHoughTransform object + * from a \b vpCircleHoughTransformParams object. + * \param[in] algoParams The parameters of the Circle Hough Transform. + */ + VP_EXPLICIT vpCircleHoughTransform(const vpCircleHoughTransformParams &algoParams); + + /** + * \brief Destroy the vp Circle Hough Transform object + */ + virtual ~vpCircleHoughTransform(); + + /** @name Detection methods */ + //@{ +#ifdef HAVE_OPENCV_CORE + /** + * \brief Perform Circle Hough Transform to detect the circles in an OpenCV image. + * + * \param[in] cv_I : The input gray scale image. + * \return std::vector The list of 2D circles detected in the image. + */ + virtual std::vector detect(const cv::Mat &cv_I); +#endif + + /** + * \brief Convert the input image in a gray-scale image and then + * perform Circle Hough Transform to detect the circles in it + * + * \param[in] I The input color image. + * \return std::vector The list of 2D circles detected in the image. + */ + virtual std::vector detect(const vpImage &I); + + /** + * \brief Perform Circle Hough Transform to detect the circles in a gray-scale image + * + * \param[in] I The input gray scale image. + * \return std::vector The list of 2D circles detected in the image. + */ + virtual std::vector detect(const vpImage &I); + + /** + * \brief Perform Circle Hough Transform to detect the circles in in a gray-scale image. + * Get only the \b nbCircles circles having the greatest number of votes. + * + * \param[in] I The input gray scale image. + * \param[in] nbCircles The number of circles we want to get. If negative, all the circles will be + * returned, sorted such as result[0] has the highest number of votes and result[end -1] the lowest. + * \return std::vector The list of 2D circles with the most number + * of votes detected in the image. + */ + virtual std::vector detect(const vpImage &I, const int &nbCircles); + + /*! + * \brief Compute the mask containing pixels that voted for the \b detections. + * \param[in] I The image for which we want to have the information. + * \param[in] detections Vector containing the list of vpImageCircle for which we want to know the voting points. + * \param[out] mask Optional mask where pixels to exclude have a value set to false. + * \param[out] opt_votingPoints Optional vector of pairs of pixel coordinates that voted for the \b detections. + */ +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17) + void computeVotingMask(const vpImage &I, const std::vector &detections, + std::optional< vpImage > &mask, std::optional>>> &opt_votingPoints) const; +#else + void computeVotingMask(const vpImage &I, const std::vector &detections, + vpImage **mask, std::vector > > **opt_votingPoints) const; +#endif + +/** @name Configuration from files */ +//@{ +#ifdef VISP_HAVE_NLOHMANN_JSON + /** + * \brief Construct a new vpCircleHoughTransform object configured according to + * the JSON file whose path is \b jsonPath. Throw a \b vpException error if the file + * does not exist. + * \param[in] jsonPath The path towards the JSON configuration file. + */ + VP_EXPLICIT vpCircleHoughTransform(const std::string &jsonPath); + + /** + * \brief Initialize all the algorithm parameters using the JSON file + * whose path is \b jsonPath. Throw a \b vpException error if the file + * does not exist. + * + * \param[in] jsonPath The path towards the JSON configuration file. + */ + virtual void initFromJSON(const std::string &jsonPath); + + /** + * \brief Save the configuration of the detector in a JSON file + * described by the path \b jsonPath. Throw a \b vpException + * is the file cannot be created. + * + * \param[in] jsonPath The path towards the JSON output file. + */ + virtual void saveConfigurationInJSON(const std::string &jsonPath) const; + + /** + * \brief Read the detector configuration from JSON. All values are optional and if an argument is not present, + * the default value defined in the constructor is kept + * + * \param[in] j The JSON object, resulting from the parsing of a JSON file. + * \param[out] detector The detector, that will be initialized from the JSON data. + */ + friend inline void from_json(const nlohmann::json &j, vpCircleHoughTransform &detector) + { + detector.m_algoParams = j; + } + + /** + * \brief Parse a vpCircleHoughTransform into JSON format. + * + * \param[out] j A JSON parser object. + * \param[in] detector The vpCircleHoughTransform that must be parsed into JSON format. + */ + friend inline void to_json(nlohmann::json &j, const vpCircleHoughTransform &detector) + { + j = detector.m_algoParams; + } +#endif + //@} + + /** @name Setters */ + //@{ + /** + * \brief Initialize all the algorithm parameters. + * + * \param[in] algoParams The algorithm parameters. + */ + void init(const vpCircleHoughTransformParams &algoParams); + + /** + * \brief Permits to choose the filtering + gradient operators to use. + * + * \param[in] type The type of filtering + gradient operators to use. + */ + inline void setFilteringAndGradientType(const vpImageFilter::vpCannyFilteringAndGradientType &type) + { + m_algoParams.m_filteringAndGradientType = type; + m_cannyVisp.setFilteringAndGradientType(type); + initGradientFilters(); + } + + /** + * \brief Set the parameters of the Gaussian filter, that permits to blur the + * gradients of the image. + * + * \param[in] kernelSize The size of the Gaussian kernel. Must be an odd value. + * \param[in] stdev The standard deviation of the Gaussian function. + */ + inline void setGaussianParameters(const int &kernelSize, const float &stdev) + { + m_algoParams.m_gaussianKernelSize = kernelSize; + m_algoParams.m_gaussianStdev = stdev; + + const unsigned int checkEvenModulo = 2; + if ((m_algoParams.m_gaussianKernelSize % checkEvenModulo) != 1) { + throw vpException(vpException::badValue, "Gaussian Kernel size should be odd."); + } + + if (m_algoParams.m_gaussianStdev <= 0) { + throw vpException(vpException::badValue, "Standard deviation should be > 0"); + } + + initGaussianFilters(); + } + + /** + * \brief Set the parameters of the gradient filter (Sobel or Scharr) kernel size filters. + * + * \param[in] apertureSize The size of the gradient filters kernel. Must be an odd value. + */ + inline void setGradientFilterAperture(const unsigned int &apertureSize) + { + m_algoParams.m_gradientFilterKernelSize = apertureSize; + + const unsigned int checkEvenModulo = 2; + if ((m_algoParams.m_gradientFilterKernelSize % checkEvenModulo) != 1) { + throw vpException(vpException::badValue, "Gradient filter (Sobel or Scharr) Kernel size should be odd."); + } + + initGradientFilters(); + } + + /** + * \brief Set the backend to use to perform the Canny edge detection. + * + * \param[in] type The backend that must be used. + */ + inline void setCannyBackend(const vpImageFilter::vpCannyBackendType &type) + { + m_algoParams.m_cannyBackendType = type; + } + + /*! + * Set the threshold for the Canny operator. + * Only value greater than this value are marked as an edge. + * If negative, the threshold is automatically computed. + * \param[in] lowerCannyThreshold : Canny filter lower threshold. When set to -1 (default), compute + * automatically this threshold. + * \param[in] upperCannyThreshold : Canny filter upper threshold. When set to -1 (default), compute + * automatically this threshold. + */ + inline void setCannyThreshold(const float &lowerCannyThreshold, const float &upperCannyThreshold) + { + m_algoParams.m_lowerCannyThresh = lowerCannyThreshold; + m_algoParams.m_upperCannyThresh = upperCannyThreshold; + } + + /** + * \brief Set the Canny thresholds ratio that are used to automatically compute the Canny thresholds + * in case the user asks to. + * + * \sa \ref vpCircleHoughTransform::setCannyThreshold "vpCircleHoughTransform::setCannyThreshold(const float&, const float&)" + * + * \param[in] lowerThreshRatio The ratio of the upper threshold the lower threshold will be equal to. + * \param[in] upperThreshRatio The ratio of pixels that must have a gradient lower than the upper threshold. + */ + inline void setCannyThresholdRatio(const float &lowerThreshRatio, const float &upperThreshRatio) + { + m_algoParams.m_lowerCannyThreshRatio = lowerThreshRatio; + m_algoParams.m_upperCannyThreshRatio = upperThreshRatio; + m_cannyVisp.setCannyThresholdsRatio(lowerThreshRatio, upperThreshRatio); + } + + /*! + * Set circles center min distance. + * Change this value to detect circles with different distances to each other. + * + * \param[in] center_min_dist : Center min distance in pixels. + */ + inline void setCircleCenterMinDist(const float ¢er_min_dist) + { + m_algoParams.m_centerMinDist = center_min_dist; + + if (m_algoParams.m_centerMinDist <= 0) { + throw vpException(vpException::badValue, "Circles center min distance must be positive."); + } + } + + /*! + * Set circles center min and max location in the image. + * If one value is equal to \b std::numeric_limits::min or + * \b std::numeric_limits::max(), the algorithm will set it + * either to -maxRadius or +maxRadius depending on if + * it is the lower or upper limit that is missing. + * + * \param[in] center_min_x : Center min location on the horizontal axis, expressed in pixels. + * \param[in] center_max_x : Center max location on the horizontal axis, expressed in pixels. + * \param[in] center_min_y : Center min location on the vertical axis, expressed in pixels. + * \param[in] center_max_y : Center max location on the vertical axis, expressed in pixels. + */ + void setCircleCenterBoundingBox(const int ¢er_min_x, const int ¢er_max_x, + const int ¢er_min_y, const int ¢er_max_y) + { + m_algoParams.m_centerXlimits.first = center_min_x; + m_algoParams.m_centerXlimits.second = center_max_x; + m_algoParams.m_centerYlimits.first = center_min_y; + m_algoParams.m_centerYlimits.second = center_max_y; + } + + /*! + * Set circles min radius. + * \param[in] circle_min_radius : Min radius in pixels. + */ + inline void setCircleMinRadius(const float &circle_min_radius) + { + m_algoParams.m_minRadius = circle_min_radius; + } + + /*! + * Set circles max radius. + * \param[in] circle_max_radius : Max radius in pixels. + */ + inline void setCircleMaxRadius(const float &circle_max_radius) + { + m_algoParams.m_maxRadius = circle_max_radius; + } + + /*! + * \brief Set circles perfectness, which corresponds to the threshold of the colinearity + * between the gradient of a point and the radius it would form with a center candidate + * to be able to vote. + * The formula to get the equivalent angle is: `angle = acos(circle_perfectness)`. + * \param[in] circle_perfectness : Circle perfectness. Value between 0 and 1. A perfect circle has value 1. + */ + void setCirclePerfectness(const float &circle_perfectness) + { + m_algoParams.m_circlePerfectness = circle_perfectness; + if ((m_algoParams.m_circlePerfectness <= 0) || (m_algoParams.m_circlePerfectness > 1)) { + throw vpException(vpException::badValue, "Circle perfectness must be in the interval ] 0; 1]."); + } + } + + /** + * \brief Set the parameters of the computation of the circle center candidates. + * + * \param[in] dilatationSize Kernel size of the dilatation operation used to detect the maxima in the center accumulator. + * \param[in] centerThresh Minimum number of votes a point must exceed to be considered as center candidate. + * \param[in] averagingWindowSize Size of the averaging window around the maximum number of votes to compute the + center candidate such as it is the barycenter of the window. Must be odd. + * \param[in] expectedNbCenters Expected number of centers in the image. If the number is negative, all the centers + * are kept. Otherwise, maximum up to this number of centers are kept. + */ + inline void setCenterComputationParameters(const int &dilatationSize, const float ¢erThresh, + const int &averagingWindowSize = 5, const int expectedNbCenters = -1) + { + m_algoParams.m_dilatationKernelSize = dilatationSize; + m_algoParams.m_centerMinThresh = centerThresh; + m_algoParams.m_averagingWindowSize = averagingWindowSize; + m_algoParams.m_expectedNbCenters = expectedNbCenters; + + const int minDilatationKernel = 3; + const unsigned int checkEvenModulo = 2; + if (m_algoParams.m_dilatationKernelSize < minDilatationKernel) { + throw vpException(vpException::badValue, "Dilatation kernel size for center detection must be greater or equal to 3."); + } + else if ((m_algoParams.m_dilatationKernelSize % checkEvenModulo) == 0) { + throw vpException(vpException::badValue, "Dilatation kernel size for center detection must be odd."); + } + + if (m_algoParams.m_centerMinThresh <= 0.f) { + throw vpException(vpException::badValue, "Votes thresholds for center detection must be positive."); + } + + if ((m_algoParams.m_averagingWindowSize <= 0) || ((m_algoParams.m_averagingWindowSize % checkEvenModulo) == 0)) { + throw vpException(vpException::badValue, "Averaging window size must be positive and odd."); + } + } + + /** + * \brief Set the parameters of the computation of the circle radius candidates. + * + * \param[in] radiusRatioThresh Minimum number of votes per radian a radius candidate RC_ij of a center candidate CeC_i must have in order that the circle of center CeC_i and radius RC_ij must be considered as circle candidate. + */ + inline void setRadiusRatioThreshold(const float &radiusRatioThresh) + { + m_algoParams.m_circleProbaThresh = radiusRatioThresh; + + if (m_algoParams.m_circleProbaThresh <= 0) { + throw vpException(vpException::badValue, "Radius ratio threshold must be > 0."); + } + } + + /** + * \brief Set the radius merging threshold used during the merging step in order + * to merge the circles that are similar. + * + * \param[in] radiusDifferenceThresh Maximum radius difference between two circle candidates to consider merging them. + */ + inline void setRadiusMergingThresholds(const float &radiusDifferenceThresh) + { + m_algoParams.m_mergingRadiusDiffThresh = radiusDifferenceThresh; + + if (m_algoParams.m_mergingRadiusDiffThresh <= 0) { + throw vpException(vpException::badValue, "Radius difference merging threshold must be positive."); + } + } + + /*! + * \brief Set the mask that permits to ignore some pixels when performing the circle detection. + * + * \param[in] mask A boolean image where pixels set to true means that the pixel + * must be considered and set to false means that the pixel must be ignored. + */ + inline void setMask(const vpImage &mask) + { + mp_mask = &mask; + } + + /*! + * \brief Set the mask that permits to ignore some pixels when performing the circle detection. + * + * \param[in] mask Either a boolean image where pixels set to true means that the pixel + * must be considered and set to false means that the pixel must be ignored, or nullptr + * to deactivate the mask. + */ + inline void setMask(const vpImage *mask) + { + mp_mask = mask; + } + + /*! + * \brief Permits to either activate or deactivate the memorization + * of the points that voted for the detected circles. + * + * \param[in] record True to activate the feature, false to deactivate it. + */ + inline void setRecordVotingPoints(const bool &record) + { + m_algoParams.m_recordVotingPoints = record; + } + //@} + + /** @name Getters */ + //@{ + /** + * \brief Get the list of Center Candidates, stored as pair + * + * \return std::vector > The list of Center Candidates, stored as pair + */ + inline std::vector > getCenterCandidatesList() const + { + return m_centerCandidatesList; + } + + /** + * \brief Get the number of votes of each Center Candidates. + * + * \return std::vector The number of votes of each Center Candidates, ordered in the same way than \b m_centerCandidatesList. + */ + inline std::vector getCenterCandidatesVotes() const + { + return m_centerVotes; + } + + /** + * \brief Get the Circle Candidates before merging step. + * + * \return std::vector The list of circle candidates + * that were obtained before the merging step. + */ + inline std::vector getCircleCandidates() const + { + return m_circleCandidates; + } + + /** + * \brief Get the probabilities of the Circle Candidates. + * + * \return std::vector The votes accumulator. + */ + inline std::vector getCircleCandidatesProbabilities() const + { + return m_circleCandidatesProbabilities; + } + + /** + * \brief Get the votes of the circle candidates. + * + * \return std::vector The votes of the circle candidates. + */ + inline std::vector getCircleCandidatesVotes() const + { + return m_circleCandidatesVotes; + } + + /** + * \brief Get the gradient along the horizontal axis of the image. + * + * \return vpImage The gradient along the horizontal axis of the image. + */ + inline vpImage getGradientX() const + { + return m_dIx; + } + + /** + * \brief Get the gradient along the vertical axis of the image. + * + * \return vpImage The gradient along the vertical axis of the image. + */ + inline vpImage getGradientY() const + { + return m_dIy; + } + + /** + * \brief Get the Edge Map computed thanks to the Canny edge filter. + * + * \return vpImage The edge map computed during the edge detection step. + */ + inline vpImage getEdgeMap() const + { + return m_edgeMap; + } + + /*! + * Get internal Canny filter upper threshold. When value is equal to -1 (default), it means that the threshold is computed + * automatically. + */ + inline float getCannyThreshold() const + { + return m_algoParams.m_upperCannyThresh; + } + + /*! + * Get circles center min distance in pixels. + */ + inline float getCircleCenterMinDist() const + { + return m_algoParams.m_centerMinDist; + } + + /*! + * Get circles min radius in pixels. + */ + inline float getCircleMinRadius() const + { + return m_algoParams.m_minRadius; + } + + /*! + * Get circles max radius in pixels. + */ + inline float getCircleMaxRadius() const + { + return m_algoParams.m_maxRadius; + } + + /*! + * Get the probabilities of the detections that are outputed by vpCircleHoughTransform::detect() + */ + inline std::vector getDetectionsProbabilities() const + { + return m_finalCirclesProbabilities; + } + + /*! + * Get the number of votes for the detections that are outputed by vpCircleHoughTransform::detect() + */ + inline std::vector getDetectionsVotes() const + { + return m_finalCircleVotes; + } + + /*! + * Get the points that voted for the detections that are outputed by vpCircleHoughTransform::detect(). + */ + inline std::vector > > getDetectionsVotingPoints() const + { + if (!m_algoParams.m_recordVotingPoints) { + throw(vpException(vpException::fatalError, "Asking voting points when it was not asked to remember them.")); + } + return m_finalCirclesVotingPoints; + } + + /*! + * Returns true if it was asked to record the points that voted for the detections. + */ + inline bool getRecordVotingPoints() const + { + return m_algoParams.getRecordVotingPoints(); + } + //@} + + /*! + * Create a string with all Hough transform parameters. + */ + std::string toString() const; + + /*! + * Create a ostream with all Hough transform parameters. + */ + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpCircleHoughTransform &detector); + + static const unsigned char edgeMapOn; + static const unsigned char edgeMapOff; + +protected: + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + /** + * \brief Data storage for the computation of the barycenter of center candidates. + */ + typedef struct vpCentersBarycenter + { + std::pair m_position; /*!< The position of the barycenter.*/ + float m_totalVotes; /*!< The cumulated number of votes accross all the centers voting for this barycenter.*/ + float m_nbElectors; /*!< The number of center candidates that have voted for this barycenter.*/ + }vpCentersBarycenter; + + /** + * \brief Store the coordinates for single step of update of the center candidates accumulator. + */ + typedef struct vpCoordinatesForAccumStep + { + float x_orig; + float y_orig; + int x; + int y; + }vpCoordinatesForAccumStep; + + /** + * \brief Data required to update the center candidates accumulator along the + * gradient direction. + */ + typedef struct vpDataForAccumLoop + { + unsigned int r; /*!< The row of the edge-point of interest.*/ + unsigned int c; /*!< The column of the edge-point of interest.*/ + float minRadius; /*!< The minimum radius of the searched circle.s.*/ + float maxRadius; /*!< The maximum radius of the searched circle.s.*/ + float minimumXpositionFloat; /*!< The minimum x-axis position of the center in the image.*/ + float minimumYpositionFloat; /*!< The minimum y-axis position of the center in the image.*/ + float maximumXpositionFloat; /*!< The maximum x-axis position of the center in the image.*/ + float maximumYpositionFloat; /*!< The maximum y-axis position of the center in the image.*/ + int offsetX; /*!< The offset to map the accumulator indices with the minimum x-axis value.*/ + int offsetY; /*!< The offset to map the accumulator indices with the minimum y-axis value.*/ + int accumulatorWidth; /*!< The width of the accumulator.*/ + int accumulatorHeight; /*!< The height of the accumulator.*/ + }vpDataForAccumLoop; +#endif + + /** + * \brief Initialize the Gaussian filters used to blur the image and + * compute the gradient images. + */ + virtual void initGaussianFilters(); + + /** + * \brief Initialize the gradient filters used to compute the gradient images. + */ + virtual void initGradientFilters(); + + /** + * \brief Perform Gaussian smoothing on the input image to reduce the noise + * that would perturbate the edge detection. + * Then, compute the x-gradient and y-gradient of the input images. + * + * \param[in] I The input gray scale image. + */ + virtual void computeGradients(const vpImage &I); + + /** + * \brief Perform edge detection based on the computed gradients. + * Stores the edge points and the edge points connectivity. + * + * \param[in] I The input gray scale image. + */ + virtual void edgeDetection(const vpImage &I); + + /** + * \brief Filter the edge map in order to remove isolated edge points. + */ + virtual void filterEdgeMap(); + + /** + * \brief Determine the image points that are circle center candidates. + * Increment the center accumulator based on the edge points and gradient information. + * Perform thresholding to keep only the center candidates that exceed the threshold. + */ + virtual void computeCenterCandidates(); + + void updateAccumulator(const vpCoordinatesForAccumStep &coord, const vpDataForAccumLoop &data, vpImage &accum, bool &hasToStop); + + /** + * \brief Update the center accumulator along the positive and negative gradient direction + * starting from an edge-point of interest. + * + * \param[in] data The data required for the algorithm. + * \param[out] sx The gradient along x. + * \param[out] sy The gradient along y. + * \param[out] centersAccum The center candidates accumulator. + */ + void updateAccumAlongGradientDir(const vpDataForAccumLoop &data, float &sx, float &sy, vpImage ¢ersAccum); + + /** + * \brief Voting for points in both direction of the gradient. + * + * \param[in] data The data required for the algorithm. + * \param[in] centersAccum The center candidates accumulator. + */ + virtual void workOnAccumulator(vpDataForAccumLoop &data, vpImage ¢ersAccum); + + /** + * \brief Aggregate center candidates that are close to each other. + * \param[in] peak_positions_votes Vector containing raw center candidates. + */ + virtual void filterCenterCandidates(const std::vector &peak_positions_votes); + + /** + * \brief Look in the list containing the raw center candidates if one is closed to the center candidate + * that is currently of interest. + * + * \param[in] idPeak The ID of the center candidate that is currently of interest. + * \param[in] nbPeaks The number of center candidates. + * \param[in] squared_distance_max The maximum squared distance between to center candidates to merge them. + * \param[in] peak_positions_votes The list containing the raw center candidates. + * \param[out] has_been_merged Vector indicating if the center candidates have already been merged. + * \return vpCentersBarycenter The barycenter between the center candidate that is currently of interest and similar center candidates. + */ + virtual vpCentersBarycenter mergeSimilarCenters(const unsigned int &idPeak, const unsigned int &nbPeaks, const float &squared_distance_max, const std::vector &peak_positions_votes, std::vector &has_been_merged); + + /** + * \brief Compute the probability of \b circle given the number of pixels voting for + * it \b nbVotes. + * The probability is defined as the ratio of \b nbVotes by the theoretical number of + * pixel that should be visible in the image. + * + * \param[in] circle The circle for which we want to evaluate the probability. + * \param[in] nbVotes The number of visible pixels of the given circle. + * \return float The probability of the circle. + */ + virtual float computeCircleProbability(const vpImageCircle &circle, const unsigned int &nbVotes); + + /** + * \brief For each center candidate CeC_i, do: + * - For each edge point EP_j, compute the distance d_ij = distance(CeC_i; EP_j) + * - Determine to which radius candidate bin RCB_k the distance d_ij belongs to + * - Increment the radius candidate accumulator accum_rc[CeC_i][RCB_k] + * - If accum_rc[CeC_i][RCB_k] > radius_count_thresh, add the circle candidate (CeC_i, RCB_k) + * to the list of circle candidates + */ + virtual void computeCircleCandidates(); + + /** + * \brief For each circle candidate CiC_i, check if similar circles have also been detected and if so merges them. + */ + virtual void mergeCircleCandidates(); + + /** + * \brief For each circle candidate CiC_i do: + * - For each other circle candidate CiC_j do: + * +- Compute the similarity between CiC_i and CiC_j + * +- If the similarity exceeds a threshold, merge the circle candidates CiC_i and CiC_j and remove CiC_j of the list + * - Add the circle candidate CiC_i to the final list of detected circles + * \param[out] circleCandidates List of circle candidates in which we want to merge the similar circles. + * \param[out] circleCandidatesVotes List of votes of the circle candidates. + * \param[out] circleCandidatesProba List of probabilities of the circle candidates. + * \param[out] votingPoints List of edge-map points having voted of the circle candidates. + */ + virtual void mergeCandidates(std::vector &circleCandidates, std::vector &circleCandidatesVotes, + std::vector &circleCandidatesProba, std::vector > > &votingPoints); + + vpCircleHoughTransformParams m_algoParams; /*!< Attributes containing all the algorithm parameters.*/ + // // Gaussian smoothing attributes + vpArray2D m_fg; + + // // Gradient computation attributes + const vpImage *mp_mask; /*!< Mask that permits to avoid to compute gradients on some regions of the image.*/ + vpArray2D m_gradientFilterX; /*!< Contains the coefficients of the gradient kernel along the X-axis*/ + vpArray2D m_gradientFilterY; /*!< Contains the coefficients of the gradient kernel along the Y-axis*/ + vpImage m_dIx; /*!< Gradient along the x-axis of the input image.*/ + vpImage m_dIy; /*!< Gradient along the y-axis of the input image.*/ + + // // Edge detection attributes + vpCannyEdgeDetection m_cannyVisp; /*!< Edge detector ViSP implementation, used if ViSP has not been compiled with OpenCV imgproc module*/ + vpImage m_edgeMap; /*!< Edge map resulting from the edge detection algorithm.*/ + + // // Center candidates computation attributes + std::vector > m_edgePointsList; /*!< Vector that contains the list of edge points, to make faster some parts of the algo. They are stored as pair .*/ + std::vector > m_centerCandidatesList; /*!< Vector that contains the list of center candidates. They are stored as pair .*/ + std::vector m_centerVotes; /*!< Number of votes for the center candidates that are kept.*/ + + // // Circle candidates computation attributes + std::vector m_circleCandidates; /*!< List of the candidate circles.*/ + std::vector m_circleCandidatesProbabilities; /*!< Probabilities of each candidate circle that is kept.*/ + std::vector m_circleCandidatesVotes; /*!< Number of pixels voting for each candidate circle that is kept.*/ + std::vector > > m_circleCandidatesVotingPoints; /*!< Points that voted for each circle candidate.*/ + + // // Circle candidates merging attributes + std::vector m_finalCircles; /*!< List of the final circles, i.e. the ones resulting from the merge of the circle candidates.*/ + std::vector m_finalCirclesProbabilities; /*!< Probabilities of each final circle, i.e. resulting from the merge of the circle candidates.*/ + std::vector m_finalCircleVotes; /*!< Number of votes for the final circles.*/ + std::vector > > m_finalCirclesVotingPoints; /*!< Points that voted for each final circle.*/ +}; + +END_VISP_NAMESPACE + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/imgproc/vpContours.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/imgproc/vpContours.h new file mode 100644 index 00000000..fd11a5ab --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/imgproc/vpContours.h @@ -0,0 +1,351 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Basic contours extraction based on the orignal work of + * Sina Samangooei (ss@ecs.soton.ac.uk). + */ +/** + * Copyright (c) 2011, The University of Southampton and the individual + * contributors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * 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. + * + * * Neither the name of the University of Southampton 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 OWNER 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. + */ + +/*! + \file vpContours.h + \brief Basic contours extraction. +*/ + +#ifndef VP_CONTOURS_H +#define VP_CONTOURS_H + +#include +#include +#include + +namespace VISP_NAMESPACE_NAME +{ + +/*! + * Possible directions to find a contour. + */ +typedef enum +{ + NORTH, //!< North direction + NORTH_EAST, //!< North-East direction + EAST, //!< East direction + SOUTH_EAST, //!< South-East direction + SOUTH, //!< South direction + SOUTH_WEST, //!< South-West direction + WEST, //!< West direction + NORTH_WEST, //!< North-West direction + LAST_DIRECTION //!< Number of possible directions +} vpDirectionType; + +/*! + * Direction object. + */ +class vpDirection +{ +public: + //! Direction + vpDirectionType m_direction; + + //! Pixel increment along x to reach a given direction + int m_dirx[8]; + + //! Pixel increment along y to reach a given direction + int m_diry[8]; + + /*! + * Default constructor. + */ + vpDirection() + { + const unsigned int dir0 = 0, dir1 = 1, dir2 = 2, dir3 = 3; + const unsigned int dir4 = 4, dir5 = 5, dir6 = 6, dir7 = 7; + m_direction = NORTH; + + m_dirx[dir0] = 0; + m_dirx[dir1] = 1; + m_dirx[dir2] = 1; + m_dirx[dir3] = 1; + m_dirx[dir4] = 0; + m_dirx[dir5] = -1; + m_dirx[dir6] = -1; + m_dirx[dir7] = -1; + + m_diry[dir0] = -1; + m_diry[dir1] = -1; + m_diry[dir2] = 0; + m_diry[dir3] = 1; + m_diry[dir4] = 1; + m_diry[dir5] = 1; + m_diry[dir6] = 0; + m_diry[dir7] = -1; + } + + /*! + * Turn clockwise to find the next pixel along the contour. + * @return Direction to take. + */ + vpDirection clockwise() + { + vpDirection direction; + int directionSize = LAST_DIRECTION; + direction.m_direction = static_cast((static_cast(m_direction) + 1) % directionSize); + + return direction; + } + + /*! + * Turn counter clockwise to find the next pixel along the contour. + * @return Direction to take. + */ + vpDirection counterClockwise() + { + vpDirection direction; + int directionSize = static_cast(LAST_DIRECTION); + int idx = VISP_NAMESPACE_ADDRESSING vpMath::modulo(static_cast(m_direction) - 1, directionSize); + direction.m_direction = static_cast(idx); + + return direction; + } + + /*! + * Get the next point coordinate along the contour. + * @param I Image to process. + * @param point Current point coordinate. + * @return Next point coordinate along the contour. + */ + VISP_NAMESPACE_ADDRESSING vpImagePoint active(const VISP_NAMESPACE_ADDRESSING vpImage &I, const VISP_NAMESPACE_ADDRESSING vpImagePoint &point) + { + int yy = static_cast(point.get_i() + m_diry[static_cast(m_direction)]); + int xx = static_cast(point.get_j() + m_dirx[static_cast(m_direction)]); + + if ((xx < 0) || (xx >= static_cast(I.getWidth())) || (yy < 0) || (yy >= static_cast(I.getHeight()))) { + return VISP_NAMESPACE_ADDRESSING vpImagePoint(-1, -1); + } + + int pixel = I[yy][xx]; + return pixel != 0 ? VISP_NAMESPACE_ADDRESSING vpImagePoint(yy, xx) : VISP_NAMESPACE_ADDRESSING vpImagePoint(-1, -1); + } +}; + +/*! + * Type of contour. + */ +typedef enum +{ + CONTOUR_OUTER, /*!< Outer contour. */ + CONTOUR_HOLE /*!< Hole contour. */ +} vpContourType; + +/*! + * Type of contour retrieval. + */ +typedef enum +{ + CONTOUR_RETR_TREE, /*!< Retrieve all the contours with the hierarchy stored + in a tree. */ + CONTOUR_RETR_LIST, /*!< Retrieve all the contours without any hierarchy. */ + CONTOUR_RETR_EXTERNAL /*!< Retrieve only external contours. */ +} vpContourRetrievalType; + +/*! + * Structure associated to a contour. + */ +struct vpContour +{ + //! Children contour + std::vector m_children; + //! Contour type + vpContourType m_contourType; + //! Parent contour + vpContour *m_parent; + //! Vector of points belonging to the contour + std::vector m_points; + + /*! + * Default constructor. + */ + vpContour() : m_children(), m_contourType(CONTOUR_HOLE), m_parent(nullptr), m_points() { } + + /*! + * Constructor of a given contour type. + */ + VP_EXPLICIT vpContour(const vpContourType &type) : m_children(), m_contourType(type), m_parent(nullptr), m_points() { } + + /*! + * Copy constructor. + */ + vpContour(const vpContour &contour) + : m_children(), m_contourType(contour.m_contourType), m_parent(nullptr), m_points(contour.m_points) + { + + // Copy the underlying contours + std::vector::const_iterator contour_m_children_end = contour.m_children.end(); + for (std::vector::const_iterator it = contour.m_children.begin(); it != contour_m_children_end; + ++it) { + vpContour *copy = new vpContour(**it); + copy->m_parent = this; + m_children.push_back(copy); + } + } + + /*! + * Destructor. + */ + virtual ~vpContour() + { + std::vector::iterator m_children_end = m_children.end(); + for (std::vector::iterator it = m_children.begin(); it != m_children_end; ++it) { + (*it)->m_parent = nullptr; + if (*it != nullptr) { + delete *it; + *it = nullptr; + } + } + } + + /*! + * Copy operator. + */ + vpContour &operator=(const vpContour &other) + { + m_contourType = other.m_contourType; + + if (m_parent == nullptr) { + // We are a root or an uninitialized contour so delete everything + std::vector::iterator m_children_end = m_children.end(); + for (std::vector::iterator it = m_children.begin(); it != m_children_end; ++it) { + (*it)->m_parent = nullptr; + if (*it != nullptr) { + delete *it; + *it = nullptr; + } + } + } + else { + // Make the current contour the root contour + // to avoid problem when deleting + m_parent = nullptr; + } + + m_children.clear(); + std::vector::const_iterator other_m_children_end = other.m_children.end(); + for (std::vector::const_iterator it = other.m_children.begin(); it != other_m_children_end; ++it) { + vpContour *copy = new vpContour(**it); + copy->m_parent = this; + m_children.push_back(copy); + } + + return *this; + } + + /*! + * Set parent contour. + */ + void setParent(vpContour *parent) + { + m_parent = parent; + + if (parent != nullptr) { + parent->m_children.push_back(this); + } + } +}; + +/*! + * \ingroup group_imgproc_contours + * + * Draw the input contours on the binary image. + * + * \param I : Grayscale image where we want to draw the input contours. + * \param contours : Detected contours. + * \param grayValue : Drawing grayscale color. + */ +VISP_EXPORT void drawContours(VISP_NAMESPACE_ADDRESSING vpImage &I, const std::vector > &contours, + unsigned char grayValue = 255); + +/*! + * \ingroup group_imgproc_contours + * + * Draw the input contours on the color image. + * + * \param I : Color image where we want to draw the input contours. + * \param contours : Detected contours. + * \param color : Drawing color. + */ +VISP_EXPORT void drawContours(VISP_NAMESPACE_ADDRESSING vpImage &I, const std::vector > &contours, + const VISP_NAMESPACE_ADDRESSING vpColor &color); + +/*! + * \ingroup group_imgproc_contours + * + * Extract contours from a binary image. + * + * \param I_original : Input binary image (0 means background, 1 means + * foreground, other values are not allowed). + * \param contours : Detected contours. + * \param contourPts : List of contours, each contour contains a list of contour points. + * \param retrievalMode : Contour retrieval mode. + */ +VISP_EXPORT void findContours(const VISP_NAMESPACE_ADDRESSING vpImage &I_original, vpContour &contours, + std::vector > &contourPts, + const vpContourRetrievalType &retrievalMode = CONTOUR_RETR_TREE); + +} // namespace + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/imgproc/vpImgproc.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/imgproc/vpImgproc.h new file mode 100644 index 00000000..d4b44101 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/imgproc/vpImgproc.h @@ -0,0 +1,655 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Static functions for basic image processing functions. + */ + +/*! + \file vpImgproc.h + \brief Basic image processing functions. +*/ + +#ifndef VP_IMGPROC_H +#define VP_IMGPROC_H + +#include +#include +#include +#include + +namespace VISP_NAMESPACE_NAME +{ +#define USE_OLD_FILL_HOLE 0 + +/*! + * Retinex level that allows to specifies distribution + * of the Gaussian blurring kernel sizes for scale division values > 2. + */ +enum RETINEX_LEVEL +{ + RETINEX_UNIFORM = 0, //!< Tends to treat all image intensities similarly. + RETINEX_LOW = 1, //!< Enhances dark regions of the image. + RETINEX_HIGH = 2 //!< Enhances the bright regions of the image +}; + +/*! + * Automatic thresholding method. + */ +typedef enum +{ + AUTO_THRESHOLD_HUANG, /*!< Huang L.-K. and Wang M.-J.J. (1995) "Image + Thresholding by Minimizing the Measures of + Fuzziness" Pattern Recognition, 28(1): 41-51 + \cite Huang_imagethresholding */ + AUTO_THRESHOLD_INTERMODES, /*!< Prewitt, JMS & Mendelsohn, ML (1966), "The + analysis of cell images", Annals of the New + York Academy of Sciences 128: 1035-1053 + \cite NYAS:NYAS1035 */ + AUTO_THRESHOLD_ISODATA, /*!< Ridler, TW & Calvard, S (1978), "Picture + thresholding using an iterative selection + method", IEEE Transactions on Systems, Man and + Cybernetics 8: 630-632 \cite article4310039, */ + AUTO_THRESHOLD_MEAN, /*!< Glasbey, CA (1993), "An analysis of + histogram-based thresholding algorithms", CVGIP: + Graphical Models and Image Processing 55: 532-537 + \cite Glasbey:1993:AHT:167725.167747 */ + AUTO_THRESHOLD_OTSU, /*!< Otsu, N (1979), "A threshold selection method from + gray-level histograms", IEEE Trans. Sys., Man., + Cyber. 9: 62-66, doi:10.1109/TSMC.1979.4310076 + \cite article4310076 */ + AUTO_THRESHOLD_TRIANGLE /*!< Zack GW, Rogers WE, Latt SA (1977), "Automatic + measurement of sister chromatid exchange + frequency", J. Histochem. Cytochem. 25 (7): + 741–53, PMID 70454 \cite doi:10.1177/25.7.70454 + */ +} vpAutoThresholdMethod; + +/** + * \brief Gamma Correction automatic methods. + */ +typedef enum vpGammaMethod +{ + GAMMA_MANUAL = 0, /*!< User-defined constant positive gamma factor.*/ + GAMMA_LOG_BASED = 1, /*!< Scott, J & Pusateri M (2009), "Towards Real-time Hardware + Gamma Correction for Dynamic Contrast Enhancement" + IEEE Applied Imagery Pattern Recognition Workshop (AIPR 2009) \cite Scott2009TowardsRH */ + GAMMA_NONLINEAR_BASED = 2, /*!< Shi, Y et al. (2007), "Reducing Illumination Based On Nonlinear Gamma Correction", + International Conference on Image Processing \cite Shi2007ReducingIB */ + GAMMA_CDF_BASED = 3, /*!< Huang, SC et al. (2013),"Efficient Contrast Enhancement Using Adaptive + Gamma Correction With Weighting Distribution", + IEEE Trans. on Image Processing, VOL. 22, NO. 3, MARCH 2013. \cite Huang2013EfficientCE */ + GAMMA_CLASSIFICATION_BASED = 4, /*!< Rahman, S et al. (2016), "An adaptive gamma correction for image + enhancement", EURASIP Journal on Image and Video Processing \cite Rahman2016AnAG */ + GAMMA_SPATIAL_VARIANT_BASED = 5, /*!< Lee, S et al. (2010), "A Space-Variant Luminance Map based + Color Image Enhancement", + IEEE Trans. on Consumer Electronics, Vol. 56, No. 4, November 2010. \cite Lee2010ASL */ + GAMMA_METHOD_COUNT = 6 +} vpGammaMethod; + +/** + * \brief Get the list of available vpGammaMethod. + * + * \param[in] pref The prefix of the list. + * \param[in] sep The separator between two elements of the list. + * \param[in] suf The suffix of the list. + * \return std::string The list of available items. + */ +VISP_EXPORT std::string vpGammaMethodList(const std::string &pref = "<", const std::string &sep = " , ", + const std::string &suf = ">"); + +/** + * \brief Cast a \b vpGammaMethod into a string, to know its name. + * + * \param[in] type The type that must be casted into a string. + * \return std::string The corresponding name. + */ +VISP_EXPORT std::string vpGammaMethodToString(const vpGammaMethod &type); + +/** + * \brief Cast a string into a \b vpGammaMethod. + * + * \param[in] name The name of the backend. + * \return vpGammaMethod The corresponding enumeration value. + */ +VISP_EXPORT vpGammaMethod vpGammaMethodFromString(const std::string &name); + +/** + * \brief How to handle color images when applying Gamma Correction. + */ +typedef enum vpGammaColorHandling +{ + GAMMA_RGB = 0, /*!< Gamma correction is apply to Red, Blue and Green channels individually.*/ + GAMMA_HSV = 1, /*!< The input image is converted into HSV space, Gamma Correction is applied to Value channel and + then the image is converted back into RGBa space.*/ + GAMMA_COLOR_HANDLING_COUNT = 2 +} vpGammaColorHandling; + +/** + * \brief Get the list of available vpGammaColorHandling. + * + * \param[in] pref The prefix of the list. + * \param[in] sep The separator between two elements of the list. + * \param[in] suf The suffix of the list. + * \return std::string The list of available items. + */ +VISP_EXPORT std::string vpGammaColorHandlingList(const std::string &pref = "<", const std::string &sep = " , ", + const std::string &suf = ">"); + +/** + * \brief Cast a \b vpGammaColorHandling into a string, to know its name. + * + * \param[in] type The type that must be casted into a string. + * \return std::string The corresponding name. + */ +VISP_EXPORT std::string vpGammaColorHandlingToString(const vpGammaColorHandling &type); + +/** + * \brief Cast a string into a \b vpGammaColorHandling. + * + * \param[in] name The name of the backend. + * \return vpGammaColorHandling The corresponding enumeration value. + */ +VISP_EXPORT vpGammaColorHandling vpGammaColorHandlingFromString(const std::string &name); + +/*! + * \ingroup group_imgproc_brightness + * + * Adjust the brightness of a grayscale image such as the new intensity is + * alpha x old_intensity + beta. + * + * \param I : The grayscale image to adjust the brightness. + * \param alpha : Multiplication coefficient. + * \param beta : Constant value added to the old intensity. + */ +VISP_EXPORT void adjust(VISP_NAMESPACE_ADDRESSING vpImage &I, double alpha, double beta); + +/*! + * \ingroup group_imgproc_brightness + * + * Adjust the brightness of a grayscale image such as the new intensity is + * alpha x old_intensity + beta. + * + * \param I1 : The original grayscale image. + * \param I2 : The grayscale image after adjusting pixel intensities. + * \param alpha : Multiplication coefficient. + * \param beta : Constant value added to the old intensity. + */ +VISP_EXPORT void adjust(const VISP_NAMESPACE_ADDRESSING vpImage &I1, VISP_NAMESPACE_ADDRESSING vpImage &I2, double alpha, double beta); + +/*! + * \ingroup group_imgproc_brightness + * + * Adjust the brightness of a color image such as the new intensity is alpha x + * old_intensity + beta. + * + * \param I : The color image to adjust the brightness. + * \param alpha : Multiplication coefficient. + * \param beta : Constant value added to the old intensity. + */ +VISP_EXPORT void adjust(VISP_NAMESPACE_ADDRESSING vpImage &I, const double alpha, double beta); + +/*! + * \ingroup group_imgproc_brightness + * + * Adjust the brightness of a color image such as the new intensity is alpha x + * old_intensity + beta. + * + * \param I1 : The original color image. + * \param I2 : The color image after adjusting pixel intensities. + * \param alpha : Multiplication coefficient. + * \param beta : Constant value added to the old intensity. + */ +VISP_EXPORT void adjust(const VISP_NAMESPACE_ADDRESSING vpImage &I1, VISP_NAMESPACE_ADDRESSING vpImage &I2, double alpha, double beta); + +/*! + * \ingroup group_imgproc_brightness + * + * Adjust the contrast of a grayscale image locally using the Contrast Limited + * Adaptive Histogram Equalization method. The limit parameter allows to + * limit the slope of the transformation function to prevent the + * over amplification of noise. This method is a transcription of the CLAHE + * ImageJ plugin code by Stephan Saalfeld. + * + * \param I1 : The first grayscale image. + * \param I2 : The second grayscale image after application of the CLAHE + * method. + * \param blockRadius : The size (2*blockRadius+1) of the local region + * around a pixel for which the histogram is equalized. This size should be + * larger than the size of features to be preserved. + * \param bins : The number + * of histogram bins used for histogram equalization (between 1 and 256). The + * number of histogram bins should be smaller than the number of pixels in a + * block. + * \param slope : Limits the contrast stretch in the intensity transfer + * function. Very large values will let the histogram equalization do whatever + * it wants to do, that is result in maximal local contrast. The value 1 will + * result in the original image. + * \param fast : Use the fast but less accurate + * version of the filter. The fast version does not evaluate the intensity + * transfer function for each pixel independently but for a grid of adjacent + * boxes of the given block size only and interpolates for locations in + * between. + */ +VISP_EXPORT void clahe(const VISP_NAMESPACE_ADDRESSING vpImage &I1, VISP_NAMESPACE_ADDRESSING vpImage &I2, int blockRadius = 150, + int bins = 256, float slope = 3.0f, bool fast = true); + +/*! + * \ingroup group_imgproc_brightness + * + * Adjust the contrast of a color image locally using the Contrast Limited + * Adaptive Histogram Equalization method. The limit parameter allows to + * limit the slope of the transformation function to prevent the + * over amplification of noise. This method is a transcription of the CLAHE + * ImageJ plugin code by Stephan Saalfeld. + * + * \param I1 : The first color image. + * \param I2 : The second color image after application of the CLAHE method. + * \param blockRadius : The size (2*blockRadius+1) of the local region around a + * pixel for which the histogram is equalized. This size should be larger than + * the size of features to be preserved. + * \param bins : The number of histogram + * bins used for histogram equalization (between 1 and 256). The number of + * histogram bins should be smaller than the number of pixels in a block. + * \param slope : Limits the contrast stretch in the intensity transfer + * function. Very large values will let the histogram equalization do whatever + * it wants to do, that is result in maximal local contrast. The value 1 will + * result in the original image. + * \param fast : Use the fast but less accurate + * version of the filter. The fast version does not evaluate the intensity + * transfer function for each pixel independently but for a grid of adjacent + * boxes of the given block size only and interpolates for locations in + * between. +*/ +VISP_EXPORT void clahe(const VISP_NAMESPACE_ADDRESSING vpImage &I1, VISP_NAMESPACE_ADDRESSING vpImage &I2, int blockRadius = 150, int bins = 256, + float slope = 3.0f, bool fast = true); + +/*! + * \ingroup group_imgproc_histogram + * + * Adjust the contrast of a grayscale image by performing an histogram + * equalization. The intensity distribution is redistributed over the full [0 - + * 255] range such as the cumulative histogram distribution becomes linear. + * + * \param I : The grayscale image to apply histogram equalization. + * \param p_mask : If set, a boolean mask to take into account only the points for which the mask is true. + */ +VISP_EXPORT void equalizeHistogram(VISP_NAMESPACE_ADDRESSING vpImage &I, const VISP_NAMESPACE_ADDRESSING vpImage *p_mask = nullptr); + +/*! + * \ingroup group_imgproc_histogram + * + * Adjust the contrast of a grayscale image by performing an histogram + * equalization. The intensity distribution is redistributed over the full [0 - + * 255] range such as the cumulative histogram distribution becomes linear. + * + * \param I1 : The first grayscale image. + * \param I2 : The second grayscale image after histogram equalization. + * \param p_mask : If set, a boolean mask to take into account only the points for which the mask is true. + */ +VISP_EXPORT void equalizeHistogram(const VISP_NAMESPACE_ADDRESSING vpImage &I1, VISP_NAMESPACE_ADDRESSING vpImage &I2, + const VISP_NAMESPACE_ADDRESSING vpImage *p_mask = nullptr); + +/*! + * \ingroup group_imgproc_histogram + * + * Adjust the contrast of a color image by performing an histogram + * equalization. The intensity distribution is redistributed over the full [0 - + * 255] range such as the cumulative histogram distribution becomes linear. The + * alpha channel is ignored / copied from the source alpha channel. + * + * \param I : The color image to apply histogram equalization. + * \param useHSV : If true, the histogram equalization is performed on the + * value channel (in HSV space), otherwise the histogram equalization is + * performed independently on the RGB channels. + */ +VISP_EXPORT void equalizeHistogram(VISP_NAMESPACE_ADDRESSING vpImage &I, bool useHSV = false); + +/*! + * \ingroup group_imgproc_histogram + * + * Adjust the contrast of a color image by performing an histogram + * equalization. The intensity distribution is redistributed over the full [0 - + * 255] range such as the cumulative histogram distribution becomes linear. The + * alpha channel is ignored / copied from the source alpha channel. + * + * \param I1 : The first color image. + * \param I2 : The second color image after histogram equalization. + * \param useHSV : If true, the histogram equalization is performed on the + * value channel (in HSV space), otherwise the histogram equalization is + * performed independently on the RGB channels. + */ +VISP_EXPORT void equalizeHistogram(const VISP_NAMESPACE_ADDRESSING vpImage &I1, VISP_NAMESPACE_ADDRESSING vpImage &I2, bool useHSV = false); + +/*! + * \ingroup group_imgproc_gamma + * + * Perform a gamma correction on a grayscale image. + * + * \param[in,out] I : The grayscale image to apply gamma correction. + * \param[in] gamma : Gamma value. If equals to -1, use automatic Gamma correction based on a non-linear + * technique. If equals to -2, use automatic Gamma correction based on a logarithmic technique. + * If equals to -3, uses automatic Gamma correction based on classification. If equals to -4, uses automatic Gamma + * correction based on probabilistic. + * \param[in] method : The method to use: either \b GAMMA_MANUAL if the user wants to use a positive constant \b gamma + * factor, or one of the automatic method if \b gamma is negative. + * \param[in] p_mask : If different from nullptr, permits to indicate which points must be taken into account by setting + * them to true. + */ +VISP_EXPORT void gammaCorrection(VISP_NAMESPACE_ADDRESSING vpImage &I, const float &gamma, const vpGammaMethod &method = GAMMA_MANUAL, + const VISP_NAMESPACE_ADDRESSING vpImage *p_mask = nullptr); + +/*! + * \ingroup group_imgproc_gamma + * + * Perform a gamma correction on a grayscale image. + * + * \param[in] I1 : The first grayscale image. + * \param[out] I2 : The second grayscale image after gamma correction. + * \param[in] gamma : Gamma value. If equals to -1, use automatic Gamma correction based on a non-linear + * technique. If equals to -2, use automatic Gamma correction based on a logarithmic technique. + * If equals to -3, uses automatic Gamma correction based on classification. If equals to -4, uses automatic Gamma + * correction based on probabilistic. + * \param[in] method : The method to use: either \b GAMMA_MANUAL if the user wants to use a positive constant \b gamma + * factor, or one of the automatic method if \b gamma is negative. + * \param[in] p_mask : If different from nullptr, permits to indicate which points must be taken into account by setting + * them to true. + */ +VISP_EXPORT void gammaCorrection(const VISP_NAMESPACE_ADDRESSING vpImage &I1, VISP_NAMESPACE_ADDRESSING vpImage &I2, const float &gamma, + const vpGammaMethod &method = GAMMA_MANUAL, const VISP_NAMESPACE_ADDRESSING vpImage *p_mask = nullptr); + +/*! + * \ingroup group_imgproc_gamma + * + * Perform a gamma correction on a color image. + * + * \param[in,out] I : The color image to apply gamma correction. + * \param[in] gamma : Gamma value. + * \param[in] colorHandling : How to handle the colors of the image. + * \param[in] method : The method to use: either \b GAMMA_MANUAL if the user wants to use a positive constant \b gamma factor, + * or one of the automatic method if \b gamma is negative. + * \param[in] p_mask : If different from nullptr, permits to indicate which points must be taken into account by setting + * them to true. + */ +VISP_EXPORT void gammaCorrection(VISP_NAMESPACE_ADDRESSING vpImage &I, const float &gamma, const vpGammaColorHandling &colorHandling = GAMMA_RGB, + const vpGammaMethod &method = GAMMA_MANUAL, const VISP_NAMESPACE_ADDRESSING vpImage *p_mask = nullptr); + +/*! + * \ingroup group_imgproc_gamma + * + * Perform a gamma correction on a color image. + * + * \param[in] I1 : The first color image. + * \param[out] I2 : The second color image after gamma correction. + * \param[in] gamma : Gamma value. + * \param[in] colorHandling : How to handle the colors of the image. + * \param[in] method : The method to use: either \b GAMMA_MANUAL if the user wants to use a positive constant \b gamma factor, + * or one of the automatic method if \b gamma is negative. + * \param[in] p_mask : If different from nullptr, permits to indicate which points must be taken into account by setting + * them to true. + */ +VISP_EXPORT void gammaCorrection(const VISP_NAMESPACE_ADDRESSING vpImage &I1, VISP_NAMESPACE_ADDRESSING vpImage &I2, const float &gamma, + const vpGammaColorHandling &colorHandling = GAMMA_RGB, + const vpGammaMethod &method = GAMMA_MANUAL, const VISP_NAMESPACE_ADDRESSING vpImage *p_mask = nullptr); + +/*! + * \ingroup group_imgproc_retinex + * + * Apply the Retinex algorithm (the input image is modified). + * \param I : The color image after application of the Retinex technique. + * \param scale : Specifies the depth of the retinex effect. Minimum value is + * 16, a value providing gross, unrefined filtering. Maximum value is 250. + * Optimal and default value is 240. + * \param scaleDiv : Specifies the number of + * iterations of the multi scale filter. Values larger than 2 exploit the + * "multiscale" nature of the algorithm. + * \param level : Specifies distribution + * of the Gaussian blurring kernel sizes for Scale division values > 2: + * - 0, tends to treat all image intensities similarly, + * - 1, enhances dark regions of the image, + * - 2, enhances the bright regions of the image. + * \param dynamic : Adjusts the color of the result. Large values produce less + * saturated images. + * \param kernelSize : Kernel size for the gaussian blur + * operation. If -1, the kernel size is calculated from the image size. + */ +VISP_EXPORT void retinex(VISP_NAMESPACE_ADDRESSING vpImage &I, int scale = 240, int scaleDiv = 3, int level = RETINEX_UNIFORM, + double dynamic = 1.2, int kernelSize = -1); + +/*! + * \ingroup group_imgproc_retinex + * + * Apply the Retinex algorithm. + * \param I1 : The input color image. + * \param I2 : The output color image after application of the Retinex technique. + * \param scale : Specifies the depth of the retinex effect. Minimum + * value is 16, a value providing gross, unrefined filtering. Maximum value is + * 250. Optimal and default value is 240. + * \param scaleDiv : Specifies the + * number of iterations of the multiscale filter. Values larger than 2 exploit + * the "multiscale" nature of the algorithm. + * \param level : Specifies distribution of the Gaussian blurring kernel sizes + * for Scale division values > 2: + * - 0, tends to treat all image intensities similarly, + * - 1, enhances dark regions of the image, + * - 2, enhances the bright regions of the image. + * \param dynamic : Adjusts the color of the result. Large values produce less + * saturated images. + * \param kernelSize : Kernel size for the gaussian blur + * operation. If -1, the kernel size is calculated from the image size. + */ +VISP_EXPORT void retinex(const VISP_NAMESPACE_ADDRESSING vpImage &I1, VISP_NAMESPACE_ADDRESSING vpImage &I2, int scale = 240, int scaleDiv = 3, + int level = RETINEX_UNIFORM, double dynamic = 1.2, int kernelSize = -1); + +/*! + * \ingroup group_imgproc_contrast + * + * Stretch the contrast of a grayscale image. + * + * \param I : The grayscale image to stretch the contrast. +*/ +VISP_EXPORT void stretchContrast(VISP_NAMESPACE_ADDRESSING vpImage &I); + +/*! + * \ingroup group_imgproc_contrast + * + * Stretch the contrast of a grayscale image. + * + * \param I1 : The first input grayscale image. + * \param I2 : The second output grayscale image. + */ +VISP_EXPORT void stretchContrast(const VISP_NAMESPACE_ADDRESSING vpImage &I1, VISP_NAMESPACE_ADDRESSING vpImage &I2); + +/*! + * \ingroup group_imgproc_contrast + * + * Stretch the contrast of a color image. + * + * \param I : The color image to stretch the contrast. + */ +VISP_EXPORT void stretchContrast(VISP_NAMESPACE_ADDRESSING vpImage &I); + +/*! + * \ingroup group_imgproc_contrast + * + * Stretch the contrast of a color image. + * + * \param I1 : The first input color image. + * \param I2 : The second output color image. + */ +VISP_EXPORT void stretchContrast(const VISP_NAMESPACE_ADDRESSING vpImage &I1, VISP_NAMESPACE_ADDRESSING vpImage &I2); + +/*! + * \ingroup group_imgproc_contrast + * + * Stretch the contrast of a color image in the HSV color space. + * The saturation and value components are stretch so the hue is preserved. + * + * \param I : The color image to stretch the contrast in the HSV color space. + */ +VISP_EXPORT void stretchContrastHSV(VISP_NAMESPACE_ADDRESSING vpImage &I); + +/*! + * \ingroup group_imgproc_contrast + * + * Stretch the contrast of a color image in the HSV color space. + * The saturation and value components are stretch so the hue is preserved. + * + * \param I1 : The first input color image. + * \param I2 : The second output color image. + */ +VISP_EXPORT void stretchContrastHSV(const VISP_NAMESPACE_ADDRESSING vpImage &I1, VISP_NAMESPACE_ADDRESSING vpImage &I2); + +/*! + * \ingroup group_imgproc_sharpening + * + * Sharpen a grayscale image using the unsharp mask technique. + * + * \param I : The grayscale image to sharpen. + * \param sigma : Standard deviation for Gaussian kernel. + * \param weight : Weight (between [0 - 1[) for the sharpening process. + */ +VISP_EXPORT void unsharpMask(VISP_NAMESPACE_ADDRESSING vpImage &I, float sigma, double weight = 0.6); + +/*! + * \ingroup group_imgproc_sharpening + * + * Sharpen a grayscale image using the unsharp mask technique. + * + * \param I : The input grayscale image. + * \param Ires : The output grayscale image. + * \param sigma : Standard deviation for Gaussian kernel. + * \param weight : Weight (between [0 - 1[) for the sharpening process. + */ +VISP_EXPORT void unsharpMask(const VISP_NAMESPACE_ADDRESSING vpImage &I, VISP_NAMESPACE_ADDRESSING vpImage &Ires, float sigma, + double weight = 0.6); + +/*! + * \ingroup group_imgproc_sharpening + * + * Sharpen a color image using the unsharp mask technique. + * + * \param I : The color image to sharpen. + * \param sigma : Standard deviation for Gaussian kernel. + * \param weight : Weight (between [0 - 1[) for the sharpening process. + */ +VISP_EXPORT void unsharpMask(VISP_NAMESPACE_ADDRESSING vpImage &I, float sigma, double weight = 0.6); + +/*! + * \ingroup group_imgproc_sharpening + * + * Sharpen a color image using the unsharp mask technique. + * + * \param I : The input color image. + * \param Ires : The output color image. + * \param sigma : Standard deviation for Gaussian kernel. + * \param weight : Weight (between [0 - 1[) for the sharpening process. + */ +VISP_EXPORT void unsharpMask(const VISP_NAMESPACE_ADDRESSING vpImage &I, VISP_NAMESPACE_ADDRESSING vpImage &Ires, float sigma, double weight = 0.6); + +/*! + * \ingroup group_imgproc_connected_components + * + * Perform connected components detection. + * + * \param I : Input image (0 means background). + * \param labels : Label image that contain for each position the component label. + * \param nbComponents : Number of connected components. + * \param connexity : Type of connexity. + */ +VISP_EXPORT void connectedComponents(const VISP_NAMESPACE_ADDRESSING vpImage &I, VISP_NAMESPACE_ADDRESSING vpImage &labels, int &nbComponents, + const VISP_NAMESPACE_ADDRESSING vpImageMorphology::vpConnexityType &connexity = VISP_NAMESPACE_ADDRESSING vpImageMorphology::CONNEXITY_4); + +/*! + * \ingroup group_imgproc_morph + * + * Fill the holes in a binary image. + * + * \param I : Input binary image (0 means background, 255 means foreground). + */ +VISP_EXPORT void fillHoles(VISP_NAMESPACE_ADDRESSING vpImage &I +#if USE_OLD_FILL_HOLE + , + const VISP_NAMESPACE_ADDRESSING vpImageMorphology::vpConnexityType &connexity = VISP_NAMESPACE_ADDRESSING vpImageMorphology::CONNEXITY_4 +#endif +); + +/*! + * \ingroup group_imgproc_connected_components + * + * Perform the flood fill algorithm. + * + * \param I : Input image to flood fill. + * \param seedPoint : Seed position in the image. + * \param oldValue : Old value to replace. + * \param newValue : New value to flood fill. + * \param connexity : Type of connexity. + */ +VISP_EXPORT void floodFill(VISP_NAMESPACE_ADDRESSING vpImage &I, const VISP_NAMESPACE_ADDRESSING vpImagePoint &seedPoint, const unsigned char oldValue, + const unsigned char newValue, + const VISP_NAMESPACE_ADDRESSING vpImageMorphology::vpConnexityType &connexity = VISP_NAMESPACE_ADDRESSING vpImageMorphology::CONNEXITY_4); + +/*! + * \ingroup group_imgproc_morph + * + * Perform morphological reconstruction of the image \a marker under the image + * \a mask. Definition from Gleb V. Tcheslavsk: > The morphological + * reconstruction by dilation of a grayscale image \f$ g \f$ by a grayscale + * marker image \f$ f \f$ > is defined as the geodesic dilation of \f$ f \f$ + * with respect to \f$ g \f$ repeated (iterated) until stability is reached: + * \f[ + * R_{g}^{D} \left ( f \right ) = D_{g}^{\left ( k \right )} \left ( f \right + * ) \f] with \f$ k \f$ such that: \f$ D_{g}^{\left ( k \right )} \left ( f + * \right ) = D_{g}^{\left ( k+1 \right )} \left ( f \right ) \f$ + * + * \param marker : Grayscale image marker. + * \param mask : Grayscale image mask. + * \param h_kp1 : Image morphologically reconstructed. + * \param connexity : Type of connexity. + */ +VISP_EXPORT void reconstruct(const VISP_NAMESPACE_ADDRESSING vpImage &marker, const VISP_NAMESPACE_ADDRESSING vpImage &mask, + VISP_NAMESPACE_ADDRESSING vpImage &h_kp1 /*alias I */, + const VISP_NAMESPACE_ADDRESSING vpImageMorphology::vpConnexityType &connexity = VISP_NAMESPACE_ADDRESSING vpImageMorphology::CONNEXITY_4); + +/*! + * \ingroup group_imgproc_threshold + * + * Automatic thresholding. + * + * \param I : Input grayscale image. + * \param method : Automatic thresholding method. + * \param backgroundValue : Value to set to the background. + * \param foregroundValue : Value to set to the foreground. + */ +VISP_EXPORT unsigned char autoThreshold(VISP_NAMESPACE_ADDRESSING vpImage &I, const vpAutoThresholdMethod &method, + const unsigned char backgroundValue = 0, + const unsigned char foregroundValue = 255); +} // namespace + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpDiskGrabber.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpDiskGrabber.h new file mode 100644 index 00000000..1556abd9 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpDiskGrabber.h @@ -0,0 +1,289 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Disk framegrabber. + */ + +/*! + * \file vpDiskGrabber.h + * \brief Class to load image sequence from the disk. + */ +#ifndef VP_DISK_GRABBER_H +#define VP_DISK_GRABBER_H + +#include + +#include +#include +#include + +BEGIN_VISP_NAMESPACE + +/*! + * \class vpDiskGrabber + * + * \ingroup group_io_video + * + * \brief Class to grab (ie. read) images from the disk. + * + * Defined a virtual video device. "Grab" the images from the disk. + * Derived from the vpFrameGrabber class. + * + * \sa vpFrameGrabber + * + * Here an example of capture from the directory + * "/local/soft/ViSP/ViSP-images/cube". We want to acquire 10 images + * from the first named "image.0001.pgm" by steps of 2. + * + * \code + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main(){ + * vpImage I; // Grey level image + * + * // Declare a framegrabber able to read a sequence of successive + * // images from the disk + * vpDiskGrabber g; + * + * // Set the path to the directory containing the sequence + * g.setDirectory("/local/soft/ViSP/ViSP-images/cube"); + * // Set the image base name. The directory and the base name constitute + * // the constant part of the full filename + * g.setBaseName("image."); + * // Set the step between two images of the sequence + * g.setStep(2); + * // Set the number of digits to build the image number + * g.setNumberOfZero(4); + * // Set the first frame number of the sequence + * g.setImageNumber(1); + * // Set the image file extension + * g.setExtension("pgm"); + * + * // Open the framegrabber by loading the first image of the sequence + * g.open(I) ; + * + * unsigned int cpt = 1; + * // this is the loop over the image sequence + * while(cpt ++ < 10) + * { + * // read the image and then increment the image counter so that the next + * // call to acquire(I) will get the next image + * g.acquire(I) ; + * } + * } + * \endcode +*/ +class VISP_EXPORT vpDiskGrabber : public vpFrameGrabber +{ +private: + long m_image_number; //!< id of the current image to be read + long m_image_number_next; //!< id of the next image to be read + long m_image_step; //!< increment between two image id + unsigned int m_number_of_zero; //!< number of zero in the image name + //!< (image.00000.pgm) + + std::string m_directory; //!< image location + std::string m_base_name; //!< image base name + std::string m_extension; //!< image extension + + bool m_use_generic_name; + std::string m_generic_name; + std::string m_image_name; + +public: + /*! + * Default constructor. + */ + vpDiskGrabber(); + + /*! + * Copy constructor. + */ + vpDiskGrabber(const vpDiskGrabber &grabber); + + /*! + * Constructor that takes a generic image sequence as input. + */ + VP_EXPLICIT vpDiskGrabber(const std::string &genericName); + + /*! + * Destructor. + * In fact nothing to destroy... + */ + virtual ~vpDiskGrabber() VP_OVERRIDE { } + + /*! + * Copy operator. + */ + vpDiskGrabber &operator=(const vpDiskGrabber &grabber); + + /*! + * Constructor. + * + * \param dir : Location of the image sequence. + * \param basename : Base name of each image. + * \param number : Initial image number. + * \param step : Increment between two images. + * \param noz : Number of zero to code the image number. + * \param ext : Extension of the image file. + */ + VP_EXPLICIT vpDiskGrabber(const std::string &dir, const std::string &basename, long number, int step, unsigned int noz, + const std::string &ext); + + /*! + * Acquire an image reading the next image from the disk. + * After this call, the image number is incremented considering the step. + * + * \param I : The image read from a file. + */ + void acquire(vpImage &I) VP_OVERRIDE; + + /*! + * Acquire an image reading the next image from the disk. + * After this call, the image number is incremented considering the step. + * + * \param I : The image read from a file. + */ + void acquire(vpImage &I) VP_OVERRIDE; + + /*! + * Acquire an image reading the next image from the disk. + * After this call, the image number is incremented considering the step. + * + * \param I : The image read from a file. + */ + void acquire(vpImage &I); + + /*! + * Acquire an image reading the image with number \e img_number from the disk. + * After this call, the image number is incremented considering the step. + * + * \param I : The image read from a file. + * \param image_number : The number of the desired image. + */ + void acquire(vpImage &I, long image_number); + + /*! + * Acquire an image reading the image with number \e img_number from the disk. + * After this call, the image number is incremented considering the step. + * + * \param I : The image read from a file. + * \param image_number : The number of the desired image. + */ + void acquire(vpImage &I, long image_number); + + /*! + * Acquire an image reading the pfm image with number \e img_number from the + * disk. After this call, the image number is incremented considering the step. + * + * \param I : The image read from a file. + * \param image_number : The number of the desired image. + */ + void acquire(vpImage &I, long image_number); + + /*! + * Not useful. + * + * Does nothing. Here for compatibility issue with the vpFrameGrabber class. + */ + void close() VP_OVERRIDE { } + + /*! + * Return the current image number. + */ + inline long getImageNumber() const { return m_image_number; } + + /*! + * Return the name of the file in which the last frame was read. + */ + inline std::string getImageName() const { return m_image_name; } + + /*! + * Read the first image of the sequence. + * The image number is not incremented. + */ + void open(vpImage &I) VP_OVERRIDE; + + /*! + * Read the first image of the sequence. + * The image number is not incremented. + */ + void open(vpImage &I) VP_OVERRIDE; + + /*! + * Read the first image of the sequence. + * The image number is not incremented. + */ + void open(vpImage &I); + + /*! + * Set the image base name. + */ + void setBaseName(const std::string &name) { m_base_name = name; } + + /*! + * Set the main directory name (ie location of the image sequence). + */ + void setDirectory(const std::string &dir) { m_directory = dir; } + + /*! + * Set the image extension. + */ + void setExtension(const std::string &ext) { m_extension = ext; } + + /*! + * Set the image generic name like `image-%04d.png`. + */ + void setGenericName(const std::string &genericName); + + /*! + * Set the number of the image to be read. + */ + void setImageNumber(long number); + + /*! + * Set the step between two images. + */ + void setNumberOfZero(unsigned int noz) { m_number_of_zero = noz; } + + /*! + * Set the step between two images. + */ + void setStep(long step) { m_image_step = step; } +}; + +END_VISP_NAMESPACE + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpImageIo.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpImageIo.h new file mode 100644 index 00000000..d1337373 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpImageIo.h @@ -0,0 +1,198 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Read/write images. + */ + +/*! + \file vpImageIo.h + \brief Read/write images +*/ + +#ifndef VP_IMAGE_IO_H +#define VP_IMAGE_IO_H + +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE + +/*! + \class vpImageIo + + \ingroup group_io_image + + \brief Read/write images with various image format. + + This class has its own implementation of PGM and PPM images read/write. + + This class may benefit from optional 3rd parties: + - libpng: If installed this optional 3rd party is used to read/write PNG + images. Installation instructions are provided here + https://visp.inria.fr/3rd_png. + - libjpeg: If installed this optional 3rd party is used to read/write JPEG + images. Installation instructions are provided here + https://visp.inria.fr/3rd_jpeg. + - OpenCV: If installed this optional 3rd party is used to read/write other + image formats TIFF, BMP, DIB, PBM, RASTER, JPEG2000. If libpng or libjpeg is + not installed OpenCV is also used to consider these image formats. + Installation instructions are provided here https://visp.inria.fr/3rd_opencv. + + The code below shows how to convert an PPM P6 image file format into + a PGM P5 image file format. The extension of the filename is here + used in read() and write() functions to set the image file format + (".pgm" for PGM P5 and ".ppm" for PPM P6). + + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpImage I; + #if defined(_WIN32) + std::string filename("C:/Temp/visp-images/Klimt/Klimt.ppm"); + #else // UNIX + std::string filename("/local/soft/ViSP/ViSP-images/Klimt/Klimt.ppm"); + #endif + + vpImageIo::read(I, filename); // Convert the color image in a gray level image + vpImageIo::write(I, "Klimt.pgm"); // Write the image in a PGM P5 image file format + } + \endcode + + This other example available in tutorial-image-reader.cpp shows how to + read/write jpeg images. It supposes that `libjpeg` is installed. + + \include tutorial-image-reader.cpp +*/ + +class VISP_EXPORT vpImageIo +{ +private: + typedef enum + { + FORMAT_PGM, + FORMAT_PPM, + FORMAT_JPEG, + FORMAT_PNG, + // Formats supported by opencv + FORMAT_TIFF, + FORMAT_BMP, + FORMAT_DIB, + FORMAT_PBM, + FORMAT_RASTER, + FORMAT_JPEG2000, + FORMAT_EXR, + FORMAT_PFM, + FORMAT_UNKNOWN + } vpImageFormatType; + + static vpImageFormatType getFormat(const std::string &filename); + +public: + //! Image IO backend for only jpeg and png formats image loading and saving + enum vpImageIoBackendType + { + IO_DEFAULT_BACKEND, //!< Default backend + IO_SYSTEM_LIB_BACKEND, //!< Use system libraries like libpng or libjpeg-turbo + IO_OPENCV_BACKEND, //!< Use OpenCV imgcodecs module + IO_SIMDLIB_BACKEND, //!< Use embedded simd library + IO_STB_IMAGE_BACKEND //!< Use embedded stb_image library + }; + + static void read(vpImage &I, const std::string &filename, int backend = IO_DEFAULT_BACKEND); + static void read(vpImage &I, const std::string &filename, int backend = IO_DEFAULT_BACKEND); + static void read(vpImage &I, const std::string &filename); + + static void write(const vpImage &I, const std::string &filename, int backend = IO_DEFAULT_BACKEND); + static void write(const vpImage &I, const std::string &filename, int backend = IO_DEFAULT_BACKEND); + + static void readPFM(vpImage &I, const std::string &filename); + static void readPFM_HDR(vpImage &I, const std::string &filename); + static void readPFM_HDR(vpImage &I, const std::string &filename); + + static void readPGM(vpImage &I, const std::string &filename); + static void readPGM(vpImage &I, const std::string &filename); + + static void readPPM(vpImage &I, const std::string &filename); + static void readPPM(vpImage &I, const std::string &filename); + + static void readJPEG(vpImage &I, const std::string &filename, int backend = IO_DEFAULT_BACKEND); + static void readJPEG(vpImage &I, const std::string &filename, int backend = IO_DEFAULT_BACKEND); + + static void readPNG(vpImage &I, const std::string &filename, int backend = IO_DEFAULT_BACKEND); + static void readPNG(vpImage &I, const std::string &filename, int backend = IO_DEFAULT_BACKEND); + + static void readEXR(vpImage &I, const std::string &filename, int backend = IO_DEFAULT_BACKEND); + static void readEXR(vpImage &I, const std::string &filename, int backend = IO_DEFAULT_BACKEND); + + static void writePFM(const vpImage &I, const std::string &filename); + static void writePFM_HDR(const vpImage &I, const std::string &filename); + static void writePFM_HDR(const vpImage &I, const std::string &filename); + + static void writePGM(const vpImage &I, const std::string &filename); + static void writePGM(const vpImage &I, const std::string &filename); + static void writePGM(const vpImage &I, const std::string &filename); + + static void writePPM(const vpImage &I, const std::string &filename); + static void writePPM(const vpImage &I, const std::string &filename); + + static void writeJPEG(const vpImage &I, const std::string &filename, int backend = IO_DEFAULT_BACKEND, + int quality = 90); + static void writeJPEG(const vpImage &I, const std::string &filename, int backend = IO_DEFAULT_BACKEND, + int quality = 90); + + static void writePNG(const vpImage &I, const std::string &filename, int backend = IO_DEFAULT_BACKEND); + static void writePNG(const vpImage &I, const std::string &filename, int backend = IO_DEFAULT_BACKEND); + + static void writeEXR(const vpImage &I, const std::string &filename, int backend = IO_DEFAULT_BACKEND); + static void writeEXR(const vpImage &I, const std::string &filename, int backend = IO_DEFAULT_BACKEND); + + static void readPNGfromMem(const std::vector &buffer, vpImage &I, + int backend = IO_DEFAULT_BACKEND); + static void readPNGfromMem(const std::vector &buffer, vpImage &I, + int backend = IO_DEFAULT_BACKEND); + + static void writePNGtoMem(const vpImage &I, std::vector &buffer, + int backend = IO_DEFAULT_BACKEND); + static void writePNGtoMem(const vpImage &I, std::vector &buffer, + int backend = IO_DEFAULT_BACKEND, bool saveAlpha = false); +}; + +END_VISP_NAMESPACE + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpImageQueue.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpImageQueue.h new file mode 100644 index 00000000..37b9a96c --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpImageQueue.h @@ -0,0 +1,292 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Image queue for storage helper. + */ + +#ifndef VP_IMAGE_QUEUE_H +#define VP_IMAGE_QUEUE_H + +#include + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) && defined(VISP_HAVE_THREADS) + +#include +#include +#include +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +/*! + \class vpImageQueue + + \ingroup group_io_image + + Create a queue containing images and optional additional strings that could be useful to save additional information + like the timestamp. + + This call is to use with vpImageStorageWorker. + +*/ +template class vpImageQueue +{ +public: + struct vpCancelled_t + { }; + + /*! + * Queue (FIFO) constructor. By default the max queue size is set to 1024*8. + * + * \param[in] seqname : Generic sequence name like `"folder/I%04d.png"`. If this name contains a parent folder, it + * will be created. + * \param[in] record_mode : 0 to record a sequence of images, 1 to record single images. + */ + vpImageQueue(const std::string &seqname, int record_mode) + : m_cancelled(false), m_cond(), m_queue_image(), m_queue_data(), m_maxQueueSize(1024 * 8), m_mutex(), + m_seqname(seqname), m_recording_mode(record_mode), m_start_recording(false), m_directory_to_create(false), + m_recording_trigger(false) + { + m_directory = vpIoTools::getParent(seqname); + if (!m_directory.empty()) { + if (!vpIoTools::checkDirectory(m_directory)) { + m_directory_to_create = true; + } + } + m_text_record_mode = + std::string("Record mode: ") + (m_recording_mode ? std::string("single") : std::string("continuous")); + } + + /*! + * Emit cancel signal. + */ + void cancel() + { + std::lock_guard lock(m_mutex); + std::cout << "Wait to finish saving images..." << std::endl; + m_cancelled = true; + m_cond.notify_all(); + } + + /*! + * Return record mode; 0 when recording a sequence of images, 1 when recording recording single imagess. + */ + int getRecordingMode() const { return m_recording_mode; } + + /*! + * Return recording trigger indicating if recording is started. + */ + bool getRecordingTrigger() const { return m_recording_trigger; } + + /*! + * Return generic name of the sequence of images. + */ + std::string getSeqName() const { return m_seqname; } + + /*! + * Pop the image to save from the queue (FIFO). + * + * \param[out] I : Image to record. + * \param[out] data : Data to record. + * + */ + void pop(vpImage &I, std::string &data) + { + std::unique_lock lock(m_mutex); + + while (m_queue_image.empty()) { + if (m_cancelled) { + throw vpCancelled_t(); + } + + m_cond.wait(lock); + + if (m_cancelled) { + throw vpCancelled_t(); + } + } + + I = m_queue_image.front(); + + m_queue_image.pop(); + + if (!m_queue_data.empty()) { + data = m_queue_data.front(); + m_queue_data.pop(); + } + } + + /*! + * Push data to save in the queue (FIFO). + * + * \param[in] I : Image to record. + * \param[in] data : Data to record. + */ + void push(const vpImage &I, std::string *data) + { + std::lock_guard lock(m_mutex); + + m_queue_image.push(I); + + if (data != nullptr) { + m_queue_data.push(*data); + } + + // Pop extra data in the queue + while (m_queue_image.size() > m_maxQueueSize) { + m_queue_image.pop(); + } + + if (data != nullptr) { + while (m_queue_data.size() > m_maxQueueSize) { + m_queue_data.pop(); + } + } + + m_cond.notify_one(); + } + + /*! + * Record helper that display information in the windows associated to the image, pop current image and additional + * data in the queue. + * \param[in] I : Image to record. + * \param[in] data : Data to record. Set to nullptr when no additional data have to be considered. + * \param[in] trigger_recording : External trigger to start data saving. + * \param[in] disable_left_click : Disable left click usage to trigger data saving. + * \return true when the used asked to quit using a right click in the display window. + */ + bool record(const vpImage &I, std::string *data = nullptr, bool trigger_recording = false, + bool disable_left_click = false) + { + if (I.display) { + if (!m_seqname.empty()) { + if (!disable_left_click) { + if (!m_recording_mode) { // continuous + if (m_start_recording) { + vpDisplay::displayText(I, 20 * vpDisplay::getDownScalingFactor(I), + 10 * vpDisplay::getDownScalingFactor(I), "Left click: stop recording", + vpColor::red); + } + else { + vpDisplay::displayText(I, 20 * vpDisplay::getDownScalingFactor(I), + 10 * vpDisplay::getDownScalingFactor(I), "Left click: start recording", + vpColor::red); + } + } + else { + vpDisplay::displayText(I, 20 * vpDisplay::getDownScalingFactor(I), 10 * vpDisplay::getDownScalingFactor(I), + "Left click: record image", vpColor::red); + } + } + vpDisplay::displayText(I, 40 * vpDisplay::getDownScalingFactor(I), 10 * vpDisplay::getDownScalingFactor(I), + "Right click: quit", vpColor::red); + } + else { + vpDisplay::displayText(I, 20 * vpDisplay::getDownScalingFactor(I), 10 * vpDisplay::getDownScalingFactor(I), + "Click to quit", vpColor::red); + } + + if (!m_seqname.empty()) { + vpDisplay::displayText(I, 60 * vpDisplay::getDownScalingFactor(I), 10 * vpDisplay::getDownScalingFactor(I), + m_text_record_mode, vpColor::red); + } + vpMouseButton::vpMouseButtonType button; + if (vpDisplay::getClick(I, button, false)) { + if (!m_seqname.empty()) { // Recording requested + if (button == vpMouseButton::button1 && !disable_left_click) { // enable/disable recording + m_start_recording = !m_start_recording; + } + else if (button == vpMouseButton::button3) { // quit + return true; + } + } + else { // any button to quit + return true; + } + } + } + else if (!m_seqname.empty()) { + m_start_recording = true; + } + + if (trigger_recording) { + m_start_recording = true; + } + + m_recording_trigger = m_start_recording; + + if (m_start_recording) { + + if (m_directory_to_create) { + std::cout << "Create directory \"" << m_directory << "\"" << std::endl; + vpIoTools::makeDirectory(m_directory); + m_directory_to_create = false; + } + + push(I, data); + + if (m_recording_mode == 1) { // single shot mode + m_start_recording = false; + } + } + return false; + } + + /*! + * Set queue size. + * \param[in] max_queue_size : Queue size. + */ + void setMaxQueueSize(const size_t max_queue_size) { m_maxQueueSize = max_queue_size; } + +private: + bool m_cancelled; + std::condition_variable m_cond; + std::queue > m_queue_image; + std::queue m_queue_data; + size_t m_maxQueueSize; + std::mutex m_mutex; + std::string m_seqname; + std::string m_directory; + int m_recording_mode; + bool m_start_recording; + std::string m_text_record_mode; + bool m_directory_to_create; + bool m_recording_trigger; +}; + +#endif // DOXYGEN_SHOULD_SKIP_THIS +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpImageStorageWorker.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpImageStorageWorker.h new file mode 100644 index 00000000..14787e45 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpImageStorageWorker.h @@ -0,0 +1,138 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Image storage helper. + */ + +#ifndef VP_IMAGE_STORAGE_WORKER_H +#define VP_IMAGE_STORAGE_WORKER_H + +#include + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) && defined(VISP_HAVE_THREADS) + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpImageStorageWorker + + \ingroup group_io_image + + Save data contained in an vpImageQueue. + +*/ +template class vpImageStorageWorker +{ +public: + /*! + * Constructor. + * \param[in] queue : A reference to a queue. + */ + vpImageStorageWorker(vpImageQueue &queue) + : m_queue(queue), m_dataname(""), m_cpt(1), m_ofs_data(), m_data_file_created(false) + { + m_seqname = queue.getSeqName(); + m_record_mode = queue.getRecordingMode(); + } + + /*! + * Thread main loop that save the images and additional data. + */ + void run() + { + try { + vpImage I; + std::string data; + + for (;;) { + m_queue.pop(I, data); + + // Save image + std::string filename = vpIoTools::formatString(m_seqname, m_cpt); + + if (m_record_mode > 0) { // Single image + std::cout << "Save image: " << filename << std::endl; + } + else if (m_cpt == 1) { + std::cout << "Started sequence saving: " << m_seqname << std::endl; + } + vpImageIo::write(I, filename); + + if (!data.empty()) { + if (!m_data_file_created) { + std::string parent = vpIoTools::getParent(m_seqname); + if (!parent.empty()) { + m_dataname = vpIoTools::getParent(m_seqname) + "/"; + } + m_dataname += vpIoTools::getNameWE(m_seqname); + m_dataname += ".txt"; + + std::cout << "Create data file: " << m_dataname << std::endl; + m_ofs_data.open(m_dataname); + + m_data_file_created = true; + } + m_ofs_data << vpIoTools::getName(filename) << " " << data << std::endl; + } + + m_cpt++; + } + } + catch (const vpImageQueue::vpCancelled_t &) { + std::cout << "Receive cancel during color image saving." << std::endl; + if (m_data_file_created) { + std::cout << "Close data file: " << m_dataname << std::endl; + m_ofs_data.close(); + } + } + catch (const vpImageQueue::vpCancelled_t &) { + std::cout << "Receive cancel during gray image saving." << std::endl; + if (m_data_file_created) { + std::cout << "Close data file: " << m_dataname << std::endl; + m_ofs_data.close(); + } + } + } + +private: + vpImageQueue &m_queue; + std::string m_seqname; + std::string m_dataname; + int m_record_mode; + unsigned int m_cpt; + std::ofstream m_ofs_data; + bool m_data_file_created; +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpJsonArgumentParser.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpJsonArgumentParser.h new file mode 100644 index 00000000..db75fda6 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpJsonArgumentParser.h @@ -0,0 +1,291 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * An argument parser that can both use JSON files and command line arguments as inputs. + */ + +#ifndef VP_JSON_ARGUMENT_PARSER_H +#define VP_JSON_ARGUMENT_PARSER_H + +#include + +#if defined(VISP_HAVE_NLOHMANN_JSON) +#include VISP_NLOHMANN_JSON(json.hpp) +#include +#include + + +/** +* @brief Convert a command line argument to a json representation. By default, will call the parsing function of the JSON library +* +* @param arg the argument, represented as a string +* @return json the json representation of the argument +*/ + +template +nlohmann::json convertCommandLineArgument(const std::string &arg) +{ + nlohmann::json j = nlohmann::json::parse(arg); + return j; +} + +/** + * @brief Specialization of command line parsing for strings: a shell may eat the quotes, which would be necessary for JSON parsing to work. + * This function thus directly converts the string to a JSON representation: no parsing is performed. + * @param arg the string argument + * @return nlohmann::json The JSON representation of the string + */ +template<> +nlohmann::json convertCommandLineArgument(const std::string &arg) +{ + nlohmann::json j = arg; + return j; +} + +BEGIN_VISP_NAMESPACE + +/*! + \class vpJsonArgumentParser + \ingroup module_io_cmd_parser + \brief Command line argument parsing with support for JSON files. + If a JSON file is supplied, it is parsed and command line arguments take precedence over values given in the file. + + \warning To be used, this class requires the 3rd party JSON library to be installed and enabled when installing ViSP. + + This argument parser can take any number and type of arguments, as long they can be serialized to and from JSON. + + A very basic program that uses both a JSON file and command line arguments can be found below: + \code{.cpp} + #include + #include + int main(int argc, char* argv[]) + { + double d = 1.0; + std::string s = "Default"; + + vpJsonArgumentParser parser("Example program for arguments with vpJsonArgumentParser", "config", "/"); + + parser.add_argument("scalar", d, true, "An important value: must be defined by the user") + .add_argument("string", s, false, "An optional value: if left unspecified, will default to its initialized value (\"Default\")") + .parse(argc, argv); + + std::cout << "Scalar = " << d << std::endl; + std::cout << "String = " << s << std::endl; + } + \endcode + Compiling this sample and calling the program with the arguments from the command line would yield: + \code{.sh} + $ ./program scalar 2.0 string "A new value" + Scalar = 2.0 + String = a new value + $ ./program scalar 2.0 + Scalar = 2.0 + String = default + \endcode + Here the arguments are specified from the command line. Since the "string" argument is optional, it does not have to be specified. + + For programs with more arguments it is helpful to use a JSON file that contains a base configuration. For the program above, a JSON file could look like: + \code{.json} + { + "scalar": 3.0, + "string": "Some base value" + } + \endcode + we could then call the program with: + \code{.sh} + $ ./program config my_settings.json + Scalar = 3.0 + String = Some base value + \endcode + + The values contained in the JSON file can be overridden with command line arguments + \code{.sh} + $ ./program config my_settings.json scalar 5 + Scalar = 5.0 + String = Some base value + \endcode + + The program can also be called with the "-h" or "--help" argument to display the help associated to the arguments, as well as an example json configuration file +*/ +class VISP_EXPORT vpJsonArgumentParser +{ +public: + + enum vpJsonArgumentType + { + WITH_FIELD = 0, + FLAG = 1 + }; + + /** + * @brief Create a new argument parser, that can take into account both a JSON configuration file and command line arguments. + * + * @param description Description of the program tied to this parser + * @param jsonFileArgumentName Name of the argument that points to the JSON file to load + * @param nestSeparator Delimiter that is used map a nested json object to a command line argument. For example, with a delimiter set to "/", the command line argument "a/b" will map to the json key "b" in the following json document: + * \code + * { + * "a": { + * "b": 10.0 + * }, + * "otherArgument": false + * } + * \endcode + * + */ + vpJsonArgumentParser(const std::string &description, const std::string &jsonFileArgumentName, const std::string &nestSeparator); + + /** + * @brief Generate a help message, containing the description of the arguments, their default value and whether they are required or not. + * This message also contains an example json file, generated from the default values of the arguments. + * This method is called when running the program with the "-h" or "--help" arguments. + * + * @return The help message + */ + std::string help() const; + + /** + * @brief Add an argument that can be provided by the user, either via command line or through the json file. + * + * @tparam T Type of the argument to pass. + * The methods from_json(const nlohmann::json&, T&) and to_json(nlohmann::json&, const T&) must be defined. + * This is the case for most basic types or stl containers. For your own types, you should define the method. + * @param name Name of the parameter that will be used to look up the argument values when parsing command line arguments or the json file. + * This name may contain the nestSeparator, in which case the look up in the JSON file will seek a nested object to parse. + * @param parameter Reference where the parsed value will be stored. It is modified when calling parse. + * @param required Whether this argument is required. If it is, it should be specified either through command line or through the json file. + * If not, then you should take special care to initialize \p parameter with a sensible value. + * @param help The description of the argument. + * @return vpJsonArgumentParser& returns self, allowing argument definition chaining + */ + template + vpJsonArgumentParser &addArgument(const std::string &name, T ¶meter, const bool required = true, const std::string &help = "No description") + { + m_argumentType[name] = WITH_FIELD; + const auto getter = [name, this](nlohmann::json &j, bool create) -> nlohmann::json * { + size_t pos = 0; + nlohmann::json *f = &j; + std::string token; + std::string name_copy = name; + + while ((pos = name_copy.find(m_nestSeparator)) != std::string::npos) { + token = name_copy.substr(0, pos); + + name_copy.erase(0, pos + m_nestSeparator.length()); + if (create && !f->contains(token)) { + (*f)[token] = {}; + } + else if (!f->contains(token)) { + return nullptr; + } + f = &(f->at(token)); + } + if (create && !f->contains(name_copy)) { + (*f)[name_copy] = {}; + } + else if (!f->contains(name_copy)) { + return nullptr; + } + f = &(f->at(name_copy)); + return f; + }; + + m_parsers[name] = [¶meter, required, getter, name](nlohmann::json &j) { + const nlohmann::json *field = getter(j, false); + const bool fieldHasNoValue = field == nullptr || (field != nullptr && field->is_null()); + if (required && fieldHasNoValue) { + std::stringstream ss; + ss << "Argument " << name << " is required, but no value was provided" << std::endl; + throw vpException(vpException::badValue, ss.str()); + } + else if (!fieldHasNoValue) { + field->get_to(parameter); + } + }; + + m_updaters[name] = [getter](nlohmann::json &j, const std::string &s) { + nlohmann::json *field = getter(j, true); + *field = convertCommandLineArgument(s); + }; + + m_helpers[name] = [help, parameter, required]() -> std::string { + std::stringstream ss; + nlohmann::json repr = parameter; + ss << help << std::endl << "Default: " << repr; + if (required) { + ss << std::endl << "Required"; + } + else { + ss << std::endl << "Optional"; + } + return ss.str(); + }; + + nlohmann::json *exampleField = getter(m_exampleJson, true); + *exampleField = parameter; + + return *this; + } + + /** + * @brief Add an argument that acts as a flag when specified on the command line. + * When this flag is specified, the boolean passed in argument will be inverted. + * + * @param name Name of the flag. + * @param parameter The boolean to modify when the flag is specified + * @param help The description of the argument. + * @return vpJsonArgumentParser& returns self, allowing argument definition chaining + */ + vpJsonArgumentParser &addFlag(const std::string &name, bool ¶meter, const std::string &help = "No description"); + + /** + * @brief Parse the arguments. + * + * @param argc Number of arguments (including program name) + * @param argv Arguments + */ + void parse(int argc, const char *argv[]); + +private: + std::string m_description; // Program description + std::string m_jsonFileArgumentName; // Name of the argument that points to the json file: ./program --config settings.json. Here jsonFileArgumentName == "--config" + std::string m_nestSeparator; // JSON nesting delimiter character. Used to access JSON nested objects from a single string + std::map> m_parsers; // Functions that update the variables with the values contained in the JSON document (should be used after calling updaters) + std::map m_argumentType; // Update the base json document with command line arguments + std::map> m_updaters; // Update the base json document with command line arguments + std::map> m_helpers; // Functions that output the usage and description of command line arguments: used when the help flag is given as argument + nlohmann::json m_exampleJson; // Example JSON argument file: displayed when user calls for help +}; + +END_VISP_NAMESPACE + +#endif // VISP_HAVE_NLOHMANN_JSON + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpKeyboard.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpKeyboard.h new file mode 100644 index 00000000..953d98f7 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpKeyboard.h @@ -0,0 +1,102 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Keyboard management. + */ + +#ifndef vpKeyboard_h +#define vpKeyboard_h + +/*! + \file vpKeyboard.h + \brief Keyboard management under unix. +*/ + +#include + +#include + +#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) +#include +#include +#include +#endif // defined UNIX + +BEGIN_VISP_NAMESPACE +/*! + + \class vpKeyboard + \ingroup group_io_keyboard + \brief Keyboard management under unix (Linux or OSX). This class is not + available under windows. + + Gets a key from the keyboard without waiting for the "Enter" key. + \warning The key that was pressed is only detected if the terminal where the + binary was launched is active. + + \code + vpKeyboard keyboard; // Turn on keyboard raw mode + int c; + while (...) { + ... + if (keyboard.kbhit()) { // Detect if a key was pressed + c = keyboard.getchar (void); // Get the pressed key + if (c == 'q' || c == 'Q') { + break; // Quit the while() + } + ... + } + } + + // Keyboard raw mode is turned off by the vpKeyboard destructor + \endcode + +*/ +class VISP_EXPORT vpKeyboard +{ + +public: + vpKeyboard(); + virtual ~vpKeyboard(); + int kbhit(); + int getchar(); + +#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) +private: + void init(); + void end(); + + void setRawMode(bool active); + + struct termios initial_settings, new_settings; +#endif // defined UNIX +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpParallelPort.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpParallelPort.h new file mode 100644 index 00000000..ddaa2527 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpParallelPort.h @@ -0,0 +1,103 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Parallel port management. + */ + +#ifndef vpParallelPort_h +#define vpParallelPort_h + +/*! + \file vpParallelPort.h + \brief Parallel port management under unix. +*/ + +#include + +#ifdef VISP_HAVE_PARPORT + +#include +#include +#include +#include +#include +#include + +#include + +BEGIN_VISP_NAMESPACE +/*! + + \class vpParallelPort + \ingroup group_io_parallel_port + \brief Parallel port management under unix. + + The code below shows how to send a data over the parallel port. + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #ifdef VISP_HAVE_PARPORT + vpParallelPort parport; + + unsigned char data = 5; // 0x00000101 = 5 in decimal + parport.sendData(data); // D0 and D2 are set to logical level 1 + #endif + } + \endcode + +*/ +class VISP_EXPORT vpParallelPort +{ + +public: + vpParallelPort(); + virtual ~vpParallelPort(); + + void sendData(unsigned char &data); + unsigned char getData(); + +private: + void open(); + void close(); + +private: + int fd; // parallel port descriptor + std::string device; +}; +END_VISP_NAMESPACE +#endif + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpParallelPortException.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpParallelPortException.h new file mode 100644 index 00000000..27c34d6e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpParallelPortException.h @@ -0,0 +1,93 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Exceptions that can be emitted by the vpParallelPort class and its + * derivates. + */ + +#ifndef _vpParallelPortException_h_ +#define _vpParallelPortException_h_ + +/*! + * \file vpParallelPortException.h + * \brief Error that can be emitted by the vpParallelPort class and its + * derivates. + */ + +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpParallelPortException + * + * \brief Error that can be emitted by the vpParallelPort class and its + * derivates. +*/ +class VISP_EXPORT vpParallelPortException : public vpException +{ +public: + /*! + * \brief Lists the possible errors than can be emitted while calling + * vpParallelPort member + */ + enum error + { + opening, /*!< Cannot access to the parallel port device. */ + closing /*!< Cannot close the parallel port device. */ + }; + +public: + /*! + * Constructor. + */ + vpParallelPortException(int id, const char *format, ...) + { + this->code = id; + va_list args; + va_start(args, format); + setMessage(format, args); + va_end(args); + } + + /*! + * Constructor. + */ + vpParallelPortException(int id, const std::string &msg) : vpException(id, msg) { } + + /*! + * Constructor. + */ + VP_EXPLICIT vpParallelPortException(int id) : vpException(id) { } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpParseArgv.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpParseArgv.h new file mode 100644 index 00000000..a9952b56 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpParseArgv.h @@ -0,0 +1,206 @@ +/* + * Declarations for Tk-related things that are visible + * outside of the Tk module itself. + * + * Copyright 1989-1992 Regents of the University of California. + * Permission to use, copy, modify, and distribute this + * software and its documentation for any purpose and without + * fee is hereby granted, provided that the above copyright + * notice appear in all copies. The University of California + * makes no representations about the suitability of this + * software for any purpose. It is provided "as is" without + * express or implied warranty. + * + * This file has been modified to be used only for argv parsing without + * reference to tk, tcl or X11. Base on tk.h from tk2.3 + * + * Modifications by Peter Neelin (November 27, 1992) + * Modifications by Fabien Spindler (June 20, 2006) + */ + +/*! + \file vpParseArgv.h + \brief Command line argument parsing. +*/ + +#ifndef VP_PARSE_ARGV_H +#define VP_PARSE_ARGV_H + +#include +#include + +BEGIN_VISP_NAMESPACE + +/*! + \class vpParseArgv + \ingroup module_io_cmd_parser + \brief Command line argument parsing. + + The code below shows a first way to parse command line arguments + using vpParseArgv class. It allows to specify an option + name with more than one character. + + \code + #include + #include + #include + + // Usage : [-bool] [-int ] [-long ] + // [-float ] [-double ] [-string ] [-h] + int main(int argc, const char ** argv) + { + // Variables to set by command line parsing + bool b_val = false; + int i_val = 10; + long l_val = 123456; + float f_val = 0.1f; + double d_val = M_PI; + char *s_val; + + // Parse the command line to set the variables + vpParseArgv::vpArgvInfo argTable[] = + { + {"-bool", vpParseArgv::ARGV_CONSTANT_BOOL, 0, (char *) &b_val, + "Flag enabled."}, + {"-int", vpParseArgv::ARGV_INT, (char*) nullptr, (char *) &i_val, + "An integer value."}, + {"-long", vpParseArgv::ARGV_LONG, (char*) nullptr, (char *) &l_val, + "An integer value."}, + {"-float", vpParseArgv::ARGV_FLOAT, (char*) nullptr, (char *) &f_val, + "A float value."}, + {"-double", vpParseArgv::ARGV_DOUBLE, (char*) nullptr, (char *) &d_val, + "A double value."}, + {"-string", vpParseArgv::ARGV_STRING, (char*) nullptr, (char *) &s_val, + "A string value."}, + {"-h", vpParseArgv::ARGV_HELP, (char*) nullptr, (char *) nullptr, + "Print the help."}, + {(char*) nullptr, vpParseArgv::ARGV_END, (char*) nullptr, (char*) nullptr, (char*) nullptr} } ; + + // Read the command line options + if(vpParseArgv::parse(&argc, argv, argTable, + vpParseArgv::ARGV_NO_LEFTOVERS | + vpParseArgv::ARGV_NO_ABBREV | + vpParseArgv::ARGV_NO_DEFAULTS)) { + return (false); + } + + // b_val, i_val, l_val, f_val, d_val, s_val may have new values + } + \endcode + + The code below shows an other way to parse command line arguments using + vpParseArgv class. Here command line options are only one character long. + \code + #include + #include + #include + #include + + // List of allowed command line options + #define GETOPTARGS "bi:l:f:d:h" // double point mean here that the preceding option request an argument + + // Usage : [-b] [-i ] [-l ] + // [-f ] [-d ] [-s ] [-h] + int main(int argc, const char ** argv) + { + // Variables to set by command line parsing + bool b_val = false; + int i_val = 10; + long l_val = 123456; + float f_val = 0.1f; + double d_val = M_PI; + std::string s_val; + + // Parse the command line to set the variables + const char *optarg; + int c; + while ((c = vpParseArgv::parse(argc, argv, GETOPTARGS, &optarg)) > 1) { + + switch (c) { + case 'b': b_val = true; break; + case 'i': i_val = atoi(optarg); break; + case 'l': l_val = atol(optarg); break; + case 'f': f_val = static_cast(atof(optarg)); break; + case 'd': d_val = atof(optarg); break; + case 's': s_val = std::string(optarg); break; + case 'h': printf("Usage: ...\n"); return EXIT_SUCCESS; break; + + default: + printf("Usage: ...\n"); return EXIT_SUCCESS; break; + } + } + if ((c == 1) || (c == -1)) { + // standalone param or error + printf("Usage: ...\n"); + return EXIT_FAILURE; + } + + // b_val, i_val, l_val, f_val, d_val, s_val may have new values + } + \endcode + +*/ + +class VISP_EXPORT vpParseArgv +{ +public: + /*! + Legal values for the type field of a vpArgvInfo. + */ + typedef enum + { + ARGV_CONSTANT, ///< Stand alone argument. Same as vpParseArgv::ARGV_CONSTANT_INT. + ARGV_CONSTANT_INT, ///< Stand alone argument associated to an int var that is set to 1. + ARGV_CONSTANT_BOOL, ///< Stand alone argument associated to a bool var that is set to true. + ARGV_INT, ///< Argument is associated to an int. + ARGV_LONG, ///< Argument is associated to a long. + ARGV_STRING, ///< Argument is associated to a char * string. + ARGV_REST, + ARGV_FLOAT, ///< Argument is associated to a float. + ARGV_DOUBLE, ///< Argument is associated to a double. + ARGV_FUNC, + ARGV_GENFUNC, + ARGV_HELP, ///< Argument is for help displaying. + ARGV_END ///< End of the argument list. + } vpArgvType; + + /*! + Flag bits. + */ + typedef enum + { + ARGV_NO_DEFAULTS = 0x1, ///< No default options like -help. + ARGV_NO_LEFTOVERS = 0x2, ///< Print an error message if an option is not in the argument list. + ARGV_NO_ABBREV = 0x4, ///< No abrevation. Print an error message if an option is abrevated (ie "-i" in place of + ///< "-int" which is requested). + ARGV_DONT_SKIP_FIRST_ARG = 0x8, ///< Don't skip first argument. + ARGV_NO_PRINT = 0x10 ///< No printings. + } vpArgvFlags; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + /*! + + Structure used to specify how to handle argv options. + */ + typedef struct + { + const char *key; ///< The key string that flags the option in the argv array. + vpArgvType type; ///< Indicates option type. + const char *src; ///< Value to be used in setting dst; usage depends on type. + const char *dst; ///< Address of value to be modified; usage depends on type. + const char *help; ///< Documentation message describing this option. + } vpArgvInfo; +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ + +public: + static vpArgvInfo defaultTable[2]; + static bool parse(int *argcPtr, const char **argv, vpArgvInfo *argTable, int flags); + static int parse(int argc, const char **argv, const char *validOpts, const char **param); + +private: + static void printUsage(vpArgvInfo *argTable, int flags); +}; + +END_VISP_NAMESPACE + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpVideoReader.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpVideoReader.h new file mode 100644 index 00000000..25c83fe1 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpVideoReader.h @@ -0,0 +1,400 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Read videos and sequences of images . + */ + +/*! + \file vpVideoReader.h + \brief Read videos and image sequences +*/ + +#ifndef VP_VIDEO_READER_H +#define VP_VIDEO_READER_H + +#include + +#include +#include + +#if defined(VISP_HAVE_OPENCV) && (VISP_HAVE_OPENCV_VERSION < 0x030000) && defined(HAVE_OPENCV_HIGHGUI) +#include +#elif defined(VISP_HAVE_OPENCV) && (VISP_HAVE_OPENCV_VERSION >= 0x030000) && defined(HAVE_OPENCV_VIDEOIO) +#include +#endif + +BEGIN_VISP_NAMESPACE + +/*! + * \class vpVideoReader + * + * \ingroup group_io_video + * + * \brief Class that enables to manipulate easily a video file or a sequence of + * images. As it inherits from the vpFrameGrabber Class, it can be used like an + * other frame grabber class. + * + * This class has its own implementation to read a sequence of PGM and PPM + * images. + * + * This class may benefit from optional 3rd parties: + * - libpng: If installed this optional 3rd party is used to read a sequence of + * PNG images. Installation instructions are provided here + * https://visp.inria.fr/3rd_png. + * - libjpeg: If installed this optional 3rd party is used to read a sequence + * of JPEG images. Installation instructions are provided here + * https://visp.inria.fr/3rd_jpeg. + * - OpenCV: If installed this optional 3rd party is used to read a sequence of + * images where images could be in TIFF, BMP, DIB, PBM, RASTER, JPEG2000 format. + * If libpng or libjpeg is not installed, OpenCV is also used to consider these + * image formats. OpenCV allows also to consider AVI, MPEG, MPEG4, MOV, OGV, WMV, + * FLV, MKV video formats. Installation instructions are provided here + * https://visp.inria.fr/3rd_opencv. + * + * The following example available in tutorial-video-reader.cpp shows how this + * class is really easy to use. It enables to read a video file named + * video.mpeg. + * \include tutorial-video-reader.cpp + * + * As shown in the next example, this class allows also to access to a specific + * frame. But be careful, for video files, the getFrame() method is not precise + * and returns the nearest intra key frame from the expected frame. You can use + * the getFrame() method to position the reader in the video and then use the + * acquire() method to get the following frames one by one. + * \code + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * #ifdef VISP_HAVE_OPENCV + * vpImage I; + * + * vpVideoReader reader; + * + * // Initialize the reader. + * reader.setFileName("video.mpeg"); + * reader.open(I); + * + * // Read the nearest key frame from the 3th frame + * reader.getFrame(I, 2); + * + * // After positioning the video reader use acquire to read the video frame by frame + * reader.acquire(I); + * + * return 0; + * #endif + * } + * \endcode + * + * The other following example explains how to use the class to read a + * sequence of images. The images are stored in the folder "./image" and are + * named "image0000.jpeg", "image0001.jpeg", "image0002.jpeg", ... As explained + * in setFirstFrameIndex() and setLastFrameIndex() it is also possible to set + * the first and last image numbers to read a portion of the sequence. If these + * two functions are not used, first and last image numbers are set automatically + * to match the first and image images of the sequence. + * + * \code + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpImage I; + * + * vpVideoReader reader; + * + * // Initialize the reader. + * reader.setFileName("./image/image%04d.jpeg"); + * reader.setFirstFrameIndex(10); + * reader.setLastFrameIndex(20); + * reader.open(I); + * + * while (! reader.end() ) + * reader.acquire(I); + * + * return 0; + * } + * \endcode + * + * Note that it is also possible to access to a specific frame using getFrame(). + * \code + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpImage I; + * + * vpVideoReader reader; + * + * // Initialize the reader. + * reader.setFileName("./image/image%04d.jpeg"); + * reader.open(I); + * + * // Read the 3th frame + * reader.getFrame(I,2); + * + * return 0; + * } + * \endcode +*/ + +class VISP_EXPORT vpVideoReader : public vpFrameGrabber +{ +private: + //! To read sequences of images + vpDiskGrabber *m_imSequence; +#if defined(VISP_HAVE_OPENCV) && \ + (((VISP_HAVE_OPENCV_VERSION < 0x030000) && defined(HAVE_OPENCV_HIGHGUI)) || \ + ((VISP_HAVE_OPENCV_VERSION >= 0x030000) && defined(HAVE_OPENCV_VIDEOIO))) + //! To read video files with OpenCV + cv::VideoCapture m_capture; + cv::Mat m_frame; + bool m_lastframe_unknown; +#endif + //! Types of available formats + typedef enum + { + FORMAT_PGM, + FORMAT_PPM, + FORMAT_JPEG, + FORMAT_PNG, + // Formats supported by opencv + FORMAT_TIFF, + FORMAT_BMP, + FORMAT_DIB, + FORMAT_PBM, + FORMAT_RASTER, + FORMAT_JPEG2000, + // Video format + FORMAT_AVI, + FORMAT_MPEG, + FORMAT_MPEG4, + FORMAT_MTS, + FORMAT_MOV, + FORMAT_OGV, + FORMAT_WMV, + FORMAT_FLV, + FORMAT_MKV, + FORMAT_UNKNOWN + } vpVideoFormatType; + + //! Video's format which has to be read + vpVideoFormatType m_formatType; + + //! Path to the video + std::string m_videoName; + std::string m_frameName; + //! Indicates if the path to the video is set. + bool m_initFileName; + //! Indicates if the video is "open". + bool m_isOpen; + //! Count the frame number when the class is used as a grabber. + long m_frameCount; // Index of the next image + //! The first frame index + long m_firstFrame; + //! The last frame index + long m_lastFrame; + bool m_firstFrameIndexIsSet; + bool m_lastFrameIndexIsSet; + //! The frame step + long m_frameStep; + double m_frameRate; + +public: + vpVideoReader(); + vpVideoReader(const vpVideoReader &reader); + virtual ~vpVideoReader() VP_OVERRIDE; + vpVideoReader &operator=(const vpVideoReader &reader); + + void acquire(vpImage &I) VP_OVERRIDE; + void acquire(vpImage &I) VP_OVERRIDE; + void close() VP_OVERRIDE { } + + /*! + * \return true if the end of the sequence is reached. + */ + inline bool end() + { + if (m_frameStep > 0) { + if (m_frameCount + m_frameStep > m_lastFrame) + return true; + } + else if (m_frameStep < 0) { + if (m_frameCount + m_frameStep < m_firstFrame) + return true; + } + return false; + } + bool getFrame(vpImage &I, long frame); + bool getFrame(vpImage &I, long frame); + + /*! + * Return the frame rate in Hz used to encode the video stream. + * + * If the video is a sequence of images, return -1. + */ + double getFramerate() + { + if (!m_isOpen) { + getProperties(); + } + return m_frameRate; + } + + /*! + * Get the frame index of the current image. This index is updated at each + * call of the acquire method. It can be used to detect the end of a file + * (comparison with getLastFrameIndex()). + * + * \return Returns the current frame index. + * + * \sa end() + */ + inline long getFrameIndex() const { return m_frameCount; } + + /*! + * Return the name of the file in which the last frame was read. + */ + inline std::string getFrameName() const { return m_frameName; } + + /*! + * Gets the first frame index. + * + * \return Returns the first frame index. + */ + inline long getFirstFrameIndex() + { + if (!m_isOpen) { + getProperties(); + } + return m_firstFrame; + } + + /*! + * Gets the last frame index. + * + * \return Returns the last frame index. + */ + inline long getLastFrameIndex() + { + if (!m_isOpen) { + getProperties(); + } + return m_lastFrame; + } + + /*! + * Gets the frame step. + * + * \return Returns the frame step value. + */ + inline long getFrameStep() const { return m_frameStep; } + + bool isVideoFormat() const; + void open(vpImage &I) VP_OVERRIDE; + void open(vpImage &I) VP_OVERRIDE; + + vpVideoReader &operator>>(vpImage &I); + vpVideoReader &operator>>(vpImage &I); + + /*! + * Reset the frame counter and sets it to the first image index. + * + * By default the first frame index is set to 0. + * + * This method is useful if you use the class like a frame grabber (ie with + * the acquire method). + */ + inline void resetFrameCounter() { m_frameCount = m_firstFrame; } + void setFileName(const std::string &filename); + + /*! + * Enables to set the first frame index if you want to use the class like a + * grabber (ie with the acquire method). + * + * \param first_frame : The first frame index. + * + * \sa setLastFrameIndex() + */ + inline void setFirstFrameIndex(const long first_frame) + { + m_firstFrameIndexIsSet = true; + m_firstFrame = first_frame; + } + + /*! + * Enables to set the last frame index. + * + * \param last_frame : The last frame index. + * + * \sa setFirstFrameIndex() + */ + inline void setLastFrameIndex(const long last_frame) + { + this->m_lastFrameIndexIsSet = true; + m_lastFrame = last_frame; + } + + /*! + * Sets the frame step index. + * The default frame step is 1 + * + * \param frame_step : The frame index step. + * + * \sa setFrameStep() + */ + inline void setFrameStep(const long frame_step) { m_frameStep = frame_step; } + +private: + vpVideoFormatType getFormat(const std::string &filename) const; + static std::string getExtension(const std::string &filename); + void findFirstFrameIndex(); + void findLastFrameIndex(); + bool isImageExtensionSupported() const; + bool isVideoExtensionSupported() const; + bool checkImageNameFormat(const std::string &format) const; + void getProperties(); +}; + +END_VISP_NAMESPACE + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpVideoWriter.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpVideoWriter.h new file mode 100644 index 00000000..bea2b28b --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/io/vpVideoWriter.h @@ -0,0 +1,286 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Write videos and sequences of images. + */ + +/*! + \file vpVideoWriter.h + \brief Write videos and sequences of images. +*/ + +#ifndef VP_VIDEO_WRITER_H +#define VP_VIDEO_WRITER_H + +#include + +#include + +#if defined(VISP_HAVE_OPENCV) && \ + (((VISP_HAVE_OPENCV_VERSION < 0x030000) && defined(HAVE_OPENCV_HIGHGUI)) || \ + ((VISP_HAVE_OPENCV_VERSION >= 0x030000) && defined(HAVE_OPENCV_VIDEOIO))) + +#if (VISP_HAVE_OPENCV_VERSION < 0x030000) && defined(HAVE_OPENCV_HIGHGUI) +#include // for cv::VideoCapture +#elif (VISP_HAVE_OPENCV_VERSION >= 0x030000) && defined(HAVE_OPENCV_VIDEOIO) +#include +#endif + +#endif + +BEGIN_VISP_NAMESPACE + +/*! + \class vpVideoWriter + + \ingroup group_io_video + + \brief Class that enables to write easily a video file or a sequence of images. + + This class has its own implementation to write a sequence of PGM and PPM images. + + This class may benefit from optional 3rd parties: + - libpng: If installed this optional 3rd party is used to write a sequence + of PNG images. Installation instructions are provided here + https://visp.inria.fr/3rd_png. + - libjpeg: If installed this optional 3rd party is used to write a sequence + of JPEG images. Installation instructions are provided here + https://visp.inria.fr/3rd_jpeg. + - OpenCV: If installed this optional 3rd party is used to write a sequence + of images where images could be in TIFF, BMP, DIB, PBM, RASTER, JPEG2000 + format. If libpng or libjpeg is not installed, OpenCV is also used to consider + these image formats. OpenCV allows also to consider AVI, MPEG, MPEG4, MOV, + OGV, WMV, FLV, MKV video formats. Installation instructions are provided here + https://visp.inria.fr/3rd_opencv. + + The following example available in tutorial-video-recorder.cpp shows how + this class can be used to record a video from a camera by default in an mpeg + file. + + \include tutorial-video-recorder.cpp + + The following example shows also how this class can be used to write an + image sequence. The images are stored in the folder "./image" and are named + "image0000.jpeg", "image0001.jpeg", "image0002.jpeg", ... + + \code + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpImage I; + + vpVideoWriter writer; + + //Initialize the writer. + writer.setFileName("./image/image%04d.jpeg"); + + writer.open(I); + + for ( ; ; ) + { + //Here the code to capture or create an image and stores it in I. + + //Save the image + writer.saveFrame(I); + } + + writer.close(); + + return 0; + } + \endcode + + The other following example explains how to use the class to write directly + an mpeg file. + + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #if defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_VIDEOIO) && defined(HAVE_OPENCV_HIGHGUI) + vpImage I; + + vpVideoWriter writer; + + // Set up the framerate to 30Hz. Default is 25Hz. + writer.setFramerate(30); + + #if VISP_HAVE_OPENCV_VERSION >= 0x030000 + writer.setCodec(cv::VideoWriter::fourcc('P', 'I', 'M', '1')); + #else + writer.setCodec(CV_FOURCC('P', 'I', 'M', '1')); + #endif + + writer.setFileName("./test.mpeg"); + + writer.open(I); + + for (; ; ) { + // Here the code to capture or create an image and store it in I. + + // Save the image + writer.saveFrame(I); + } + + writer.close(); + #endif + return 0; + } + \endcode +*/ + +class VISP_EXPORT vpVideoWriter +{ +private: +#if defined(VISP_HAVE_OPENCV) && \ + (((VISP_HAVE_OPENCV_VERSION < 0x030000) && defined(HAVE_OPENCV_HIGHGUI)) || \ + ((VISP_HAVE_OPENCV_VERSION >= 0x030000) && defined(HAVE_OPENCV_VIDEOIO))) + cv::VideoWriter m_writer; + int m_fourcc; + double m_framerate; +#endif + //! Types of available formats + typedef enum + { + FORMAT_PGM, + FORMAT_PPM, + FORMAT_JPEG, + FORMAT_PNG, + FORMAT_AVI, + FORMAT_MPEG, + FORMAT_MPEG4, + FORMAT_MOV, + FORMAT_UNKNOWN + } vpVideoFormatType; + + //! Video's format which has to be written + vpVideoFormatType m_formatType; + + //! Path to the video or image sequence + std::string m_videoName; + std::string m_frameName; + + //! Indicates if the path to the image sequence is set. + bool m_initFileName; + + //! Indicates if the video is "open". + bool m_isOpen; + + //! Count the frame number. + int m_frameCount; + + //! The first frame index. + int m_firstFrame; + + //! Size of the frame + unsigned int m_width; + unsigned int m_height; + + int m_frameStep; + +public: + vpVideoWriter(); + virtual ~vpVideoWriter(); + + void close(); + + /*! + Gets the current frame index. + + \return Returns the current frame index. + */ + inline unsigned int getCurrentFrameIndex() const { return static_cast(m_frameCount); } + /*! + * Return the name of the file in which the last frame was saved. + */ + inline std::string getFrameName() const { return m_frameName; } + + void open(vpImage &I); + void open(vpImage &I); + /*! + Reset the frame counter and sets it to the first image index. + + By default the first frame index is set to 0. + */ + inline void resetFrameCounter() { m_frameCount = m_firstFrame; } + + void saveFrame(vpImage &I); + void saveFrame(vpImage &I); + +#if defined(VISP_HAVE_OPENCV) && \ + (((VISP_HAVE_OPENCV_VERSION < 0x030000) && defined(HAVE_OPENCV_HIGHGUI)) || \ + ((VISP_HAVE_OPENCV_VERSION >= 0x030000) && defined(HAVE_OPENCV_VIDEOIO))) + inline void setCodec(const int fourcc_codec) { m_fourcc = fourcc_codec; } +#endif + + void setFileName(const std::string &filename); + void setFirstFrameIndex(int first_frame); + + /*! + * Sets the framerate in Hz of the video when encoding. + * + * \param framerate : The expected framerate. + * + * By default the framerate is set to 25Hz. + * + * \note Framerate can only be set when OpenCV > 2.1.0. + */ +#if defined(VISP_HAVE_OPENCV) && \ + (((VISP_HAVE_OPENCV_VERSION < 0x030000) && defined(HAVE_OPENCV_HIGHGUI)) || \ + ((VISP_HAVE_OPENCV_VERSION >= 0x030000) && defined(HAVE_OPENCV_VIDEOIO))) + inline void setFramerate(const double framerate) { m_framerate = framerate; } +#else + inline void setFramerate(const double dummy) { (void)dummy; } +#endif + /*! + * Set frame step between 2 successive images when a sequence of images is considered. + * \param frame_step : Step between 2 successive images. The default value is 1. + */ + inline void setFrameStep(const int frame_step) { m_frameStep = frame_step; } + +private: + vpVideoFormatType getFormat(const std::string &filename); + static std::string getExtension(const std::string &filename); +}; + +END_VISP_NAMESPACE + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/klt/vpKltOpencv.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/klt/vpKltOpencv.h new file mode 100644 index 00000000..2788bdeb --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/klt/vpKltOpencv.h @@ -0,0 +1,452 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Wrapper for the KLT (Kanade-Lucas-Tomasi) feature tracker implemented + * with opencv. + */ + +/*! + \file vpKltOpencv.h + + \brief Wrapper for the KLT (Kanade-Lucas-Tomasi) feature tracker + implemented with opencv. +*/ + +#ifndef VP_KLT_OPENCV_H +#define VP_KLT_OPENCV_H + +#include + +#if defined(HAVE_OPENCV_HIGHGUI) && defined(HAVE_OPENCV_IMGPROC) && defined(HAVE_OPENCV_VIDEO) + +#include +#include + +#include +#include +#include + +#if defined(VISP_HAVE_NLOHMANN_JSON) +#include VISP_NLOHMANN_JSON(json.hpp) +#endif + +BEGIN_VISP_NAMESPACE +/*! + * \class vpKltOpencv + * + * \ingroup module_klt + * + * \brief Wrapper for the KLT (Kanade-Lucas-Tomasi) feature tracker + * implemented in OpenCV. Thus to enable this class OpenCV should be installed. + * Installation instructions are provided here + * https://visp.inria.fr/3rd_opencv. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * + * The following example available in tutorial-klt-tracker.cpp shows how to use + * the main functions of the class. + * + * \include tutorial-klt-tracker.cpp + * + * A line by line explanation is provided in \ref tutorial-tracking-keypoint. +*/ +class VISP_EXPORT vpKltOpencv +{ +public: + /*! + * Default constructor. + */ + vpKltOpencv(); + /*! + * Copy constructor. + */ + vpKltOpencv(const vpKltOpencv ©); + /*! + * Destructor. + */ + virtual ~vpKltOpencv(); + + /*! + * Add a keypoint at the end of the feature list. The id of the feature is set + * to ensure that it is unique. + * + * \param x : Coordinates along x-axis of the feature in the image. + * \param y : Coordinates along y-axis of the feature in the image. + */ + void addFeature(const float &x, const float &y); + + /*! + * Add a keypoint at the end of the feature list. + * + * \warning This function doesn't ensure that the id of the feature is unique. + * You should rather use addFeature(const float &, const float &) or + * addFeature(const cv::Point2f &). + * + * \param id : Feature id. Should be unique + * \param x : Coordinates along x-axis of the feature in the image. + * \param y : Coordinates along y-axis of the feature in the image. + */ + void addFeature(const long &id, const float &x, const float &y); + + /*! + * Add a keypoint at the end of the feature list. The id of the feature is set + * to ensure that it is unique. + * \param f : Coordinates of the feature in the image. + */ + void addFeature(const cv::Point2f &f); + + /*! + * Display features position and id. + * + * \param I : Image used as background. Display should be initialized on it. + * \param color : Color used to display the features. + * \param thickness : Thickness of the drawings. + */ + void display(const vpImage &I, const vpColor &color = vpColor::red, unsigned int thickness = 1) const; + /*! + * Display features list. + * + * \param I : The image used as background. + * \param features : Vector of features. + * \param color : Color used to display the points. + * \param thickness : Thickness of the points. + */ + static void display(const vpImage &I, const std::vector &features, + const vpColor &color = vpColor::green, unsigned int thickness = 1); + /*! + * Display features list. + * + * \param I : The image used as background. + * \param features : Vector of features. + * \param color : Color used to display the points. + * \param thickness : Thickness of the points. + */ + static void display(const vpImage &I, const std::vector &features, + const vpColor &color = vpColor::green, unsigned int thickness = 1); + /*! + * Display features list with ids. + * + * \param I : The image used as background. + * \param features : Vector of features. + * \param featuresid : Vector of ids corresponding to the features. + * \param color : Color used to display the points. + * \param thickness : Thickness of the points + */ + static void display(const vpImage &I, const std::vector &features, + const std::vector &featuresid, const vpColor &color = vpColor::green, + unsigned int thickness = 1); + /*! + * Display features list with ids. + * + * \param I : The image used as background. + * \param features : Vector of features. + * \param featuresid : Vector of ids corresponding to the features. + * \param color : Color used to display the points. + * \param thickness : Thickness of the points + */ + static void display(const vpImage &I, const std::vector &features, + const std::vector &featuresid, const vpColor &color = vpColor::green, + unsigned int thickness = 1); + + //! Get the size of the averaging block used to track the features. + int getBlockSize() const { return m_blockSize; } + /*! + * Get the 'index'th feature image coordinates. Beware that + * getFeature(i,...) may not represent the same feature before and + * after a tracking iteration (if a feature is lost, features are + * shifted in the array). + * + * \param index : Index of feature. + * \param id : id of the feature. + * \param x : x coordinate. + * \param y : y coordinate. + */ + void getFeature(const int &index, long &id, float &x, float &y) const; + //! Get the list of current features. + std::vector getFeatures() const { return m_points[1]; } + // CvPoint2D32f* getFeatures() const {return features;} + //! Get the unique id of each feature. + std::vector getFeaturesId() const { return m_points_id; } + // long* getFeaturesId() const {return featuresid;} + //! Get the free parameter of the Harris detector. + double getHarrisFreeParameter() const { return m_harris_k; } + //! Get the list of lost feature + // bool *getListOfLostFeature() const { return lostDuringTrack; } + //! Get the maximum number of features to track in the image. + int getMaxFeatures() const { return m_maxCount; } + //! Get the minimal Euclidean distance between detected corners during + //! initialization. + double getMinDistance() const { return m_minDistance; } + //! Get the number of current features + int getNbFeatures() const { return static_cast(m_points[1].size()); } + //! Get the number of previous features. + int getNbPrevFeatures() const { return static_cast(m_points[0].size()); } + // void getPrevFeature(int index, int &id, float &x, float &y) const; + //! Get the list of previous features + std::vector getPrevFeatures() const { return m_points[0]; } + // CvPoint2D32f* getPrevFeatures() const {return prev_features;} + //! Get the list of features id + // long* getPrevFeaturesId() const {return prev_featuresid;} + //! Get the maximal pyramid level. + int getPyramidLevels() const { return m_pyrMaxLevel; } + //! Get the parameter characterizing the minimal accepted quality of image + //! corners. + double getQuality() const { return m_qualityLevel; } + //! Get the window size used to refine the corner locations. + int getWindowSize() const { return m_winSize; } + + /*! + * Initialise the tracking by extracting KLT keypoints on the provided image. + * + * \param I : Grey level image used as input. This image should have only 1 channel. + * \param mask : Image mask used to restrict the keypoint detection + * area. If mask is nullptr, all the image will be considered. + * + * \exception vpTrackingException::initializationError : If the image I is not + * initialized, or if the image or the mask have bad coding format. + */ + void initTracking(const cv::Mat &I, const cv::Mat &mask = cv::Mat()); + + /*! + * Set the points that will be used as initialization during the next call to + * track(). + * + * \param I : Input image. + * \param pts : Vector of points that should be tracked. + */ + void initTracking(const cv::Mat &I, const std::vector &pts); + + /*! + * Set the points that will be used as initialization during the next call to + * track(). + * + * \param I : Input image. + * \param pts : Vector of points that should be tracked. + * \param ids : Corresponding point ids. + */ + void initTracking(const cv::Mat &I, const std::vector &pts, const std::vector &ids); + + /*! + * Copy operator. + */ + vpKltOpencv &operator=(const vpKltOpencv ©); + + /*! + * Track KLT keypoints using the iterative Lucas-Kanade method with pyramids. + * + * \param I : Input image. + */ + void track(const cv::Mat &I); + + /*! + * Set the size of the averaging block used to track the features. + * + * \warning The input is a signed integer to be compatible with OpenCV. + * However, it must be a positive integer. + * + * \param blockSize : Size of an average block for computing a derivative + * covariance matrix over each pixel neighborhood. Default value is set to 3. + */ + void setBlockSize(int blockSize) { m_blockSize = blockSize; } + + /*! + * Set the free parameter of the Harris detector. + * + * \param harris_k : Free parameter of the Harris detector. Default value is + * set to 0.04. + */ + void setHarrisFreeParameter(double harris_k) { m_harris_k = harris_k; } + + /*! + * Set the points that will be used as initial guess during the next call to + * track(). A typical usage of this function is to predict the position of the + * features before the next call to track(). + * + * \param guess_pts : Vector of points that should be tracked. The size of this + * vector should be the same as the one returned by getFeatures(). If this is + * not the case, an exception is returned. Note also that the id of the points + * is not modified. + * + * \sa initTracking() + */ + void setInitialGuess(const std::vector &guess_pts); + + /*! + * Set the points that will be used as initial guess during the next call to + * track(). A typical usage of this function is to predict the position of the + * features before the next call to track(). + * + * \param init_pts : Initial points (could be obtained from getPrevFeatures() + * or getFeatures()). + * \param guess_pts : Prediction of the new position of the + * initial points. The size of this vector must be the same as the size of the + * vector of initial points. + * \param fid : Identifiers of the initial points. + * + * \sa getPrevFeatures(),getPrevFeaturesId + * \sa getFeatures(), getFeaturesId + * \sa initTracking() + */ + void setInitialGuess(const std::vector &init_pts, const std::vector &guess_pts, + const std::vector &fid); + /*! + * Set the maximum number of features to track in the image. + * + * \param maxCount : Maximum number of features to detect and track. Default + * value is set to 500. + */ + void setMaxFeatures(int maxCount) { m_maxCount = maxCount; } + + /*! + * Set the minimal Euclidean distance between detected corners during + * initialization. + * + * \param minDistance : Minimal possible Euclidean distance between the + * detected corners. Default value is set to 15. + */ + void setMinDistance(double minDistance) { m_minDistance = minDistance; } + + /*! + * Set the minimal eigen value threshold used to reject a point during the + * tracking. + * + * \param minEigThreshold : Minimal eigen value threshold. Default + * value is set to 1e-4. + */ + void setMinEigThreshold(double minEigThreshold) { m_minEigThreshold = minEigThreshold; } + + /*! + * Set the maximal pyramid level. If the level is zero, then no pyramid is + * computed for the optical flow. + * + * \param pyrMaxLevel : 0-based maximal pyramid level number; if set to 0, + * pyramids are not used (single level), if set to 1, two levels are used, and + * so on. Default value is set to 3. + */ + void setPyramidLevels(int pyrMaxLevel) { m_pyrMaxLevel = pyrMaxLevel; } + + /*! + * Set the parameter characterizing the minimal accepted quality of image + * corners. + * + * \param qualityLevel : Quality level parameter. Default value is set to 0.01. + * The parameter value is multiplied by the best corner quality measure, which + * is the minimal eigenvalue or the Harris function response. The corners with + * the quality measure less than the product are rejected. For example, if the + * best corner has the quality measure = 1500, and the qualityLevel=0.01, then + * all the corners with the quality measure less than 15 are rejected. + */ + void setQuality(double qualityLevel) { m_qualityLevel = qualityLevel; } + + //! Does nothing. Just here for compat with previous releases that use + //! OpenCV C api to do the tracking. + void setTrackerId(int tid) { (void)tid; } + + /*! + * Set the parameter indicating whether to use a Harris detector or + * the minimal eigenvalue of gradient matrices for corner detection. + * \param useHarrisDetector : If 1 (default value), use the Harris detector. If + * 0 use the eigenvalue. + */ + void setUseHarris(int useHarrisDetector) { m_useHarrisDetector = useHarrisDetector; } + + /*! + * Set the window size used to refine the corner locations. + * + * \param winSize : Half of the side length of the search window. Default value + * is set to 10. For example, if \e winSize=5 , then a 5*2+1 \f$\times\f$ 5*2+1 + * = 11 \f$\times\f$ 11 search window is used. + */ + void setWindowSize(int winSize) { m_winSize = winSize; } + + /*! + * Remove the feature with the given index as parameter. + * + * \param index : Index of the feature to remove. + */ + void suppressFeature(const int &index); + +#ifdef VISP_HAVE_NLOHMANN_JSON + friend void to_json(nlohmann::json &j, const vpKltOpencv &array); + friend void from_json(const nlohmann::json &j, vpKltOpencv &array); +#endif + +protected: + cv::Mat m_gray; //!< Gray image + cv::Mat m_prevGray; //!< Previous gray image + std::vector m_points[2]; //!< Previous [0] and current [1] keypoint location + std::vector m_points_id; //!< Keypoint id + int m_maxCount; //!< Max number of keypoints + cv::TermCriteria m_termcrit; //!< Term criteria + int m_winSize; //!< Window criteria + double m_qualityLevel; //!< Quality level + double m_minDistance; //!< Mins distance between keypoints + double m_minEigThreshold; //!< Min eigen threshold + double m_harris_k; //!< Harris parameter + int m_blockSize; //!< Block size + int m_useHarrisDetector; //!< true to use Harris detector + int m_pyrMaxLevel; //!< Pyramid max level + long m_next_points_id; //!< Id for the newt keypoint + bool m_initial_guess; //!< true when initial guess is provided +}; + +#ifdef VISP_HAVE_NLOHMANN_JSON +inline void to_json(nlohmann::json &j, const vpKltOpencv &klt) +{ + j = nlohmann::json { + {"maxFeatures", klt.getMaxFeatures()}, + {"windowSize", klt.getWindowSize()}, + {"quality", klt.getQuality()}, + {"minDistance", klt.getMinDistance()}, + {"useHarris", klt.m_useHarrisDetector}, + {"harris", klt.getHarrisFreeParameter()}, + {"blockSize", klt.getBlockSize()}, + {"pyramidLevels", klt.getPyramidLevels()} + }; +} + +inline void from_json(const nlohmann::json &j, vpKltOpencv &klt) +{ + klt.setMaxFeatures(j.value("maxFeatures", 10000)); + klt.setWindowSize(j.value("windowSize", 5)); + klt.setQuality(j.value("quality", 0.01)); + klt.setMinDistance(j.value("minDistance", 5)); + klt.setUseHarris(j.value("useHarris", 1)); + klt.setHarrisFreeParameter(j.value("harris", 0.01)); + klt.setBlockSize(j.value("blockSize", 3)); + klt.setPyramidLevels(j.value("pyramidLevels", 3)); +} +#endif + +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbDepthDenseTracker.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbDepthDenseTracker.h new file mode 100644 index 00000000..23f5ea80 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbDepthDenseTracker.h @@ -0,0 +1,193 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Model-based tracker using depth dense features. + */ + +#ifndef VP_MBT_DEPTH_DENSE_TRACKER_H +#define VP_MBT_DEPTH_DENSE_TRACKER_H + +#include +#include +#include +#include +#include + +#if DEBUG_DISPLAY_DEPTH_DENSE +#include +#endif + +BEGIN_VISP_NAMESPACE +/** + * \brief Model-based tracker using depth dense features. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using a MBT tracker in your applications, you may have a look at:
+ * + * - \ref tutorial-tracking-mb-generic + * - \ref tutorial-tracking-mb-generic-stereo + * - \ref tutorial-tracking-mb-generic-rgbd + * - \ref tutorial-tracking-mb-generic-apriltag-live + * - \ref tutorial-mb-generic-json + * - \ref tutorial-tracking-mb-generic-rgbd-Blender +*/ +class VISP_EXPORT vpMbDepthDenseTracker : public virtual vpMbTracker +{ +public: + vpMbDepthDenseTracker(); + vpMbDepthDenseTracker(const vpMbDepthDenseTracker &tracker); + virtual ~vpMbDepthDenseTracker() VP_OVERRIDE; + vpMbDepthDenseTracker &operator=(const vpMbDepthDenseTracker &tracker); + + virtual void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false) VP_OVERRIDE; + + virtual void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false) VP_OVERRIDE; + + virtual inline vpColVector getError() const VP_OVERRIDE { return m_error_depthDense; } + + virtual std::vector > getModelForDisplay(unsigned int width, unsigned int height, + const vpHomogeneousMatrix &cMo, + const vpCameraParameters &cam, + bool displayFullModel = false) VP_OVERRIDE; + + virtual inline vpColVector getRobustWeights() const VP_OVERRIDE { return m_w_depthDense; } + + virtual void init(const vpImage &I) VP_OVERRIDE; + + virtual void loadConfigFile(const std::string &configFile, bool verbose = true) VP_OVERRIDE; + + void reInitModel(const vpImage &I, const std::string &cad_name, const vpHomogeneousMatrix &cMo, + bool verbose = false); +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_COMMON) + void reInitModel(const pcl::PointCloud::ConstPtr &point_cloud, const std::string &cad_name, + const vpHomogeneousMatrix &cMo, bool verbose = false); +#endif + + virtual void resetTracker() VP_OVERRIDE; + + virtual void setCameraParameters(const vpCameraParameters &camera) VP_OVERRIDE; + + virtual void setDepthDenseFilteringMaxDistance(double maxDistance); + virtual void setDepthDenseFilteringMethod(int method); + virtual void setDepthDenseFilteringMinDistance(double minDistance); + virtual void setDepthDenseFilteringOccupancyRatio(double occupancyRatio); + + inline void setDepthDenseSamplingStep(unsigned int stepX, unsigned int stepY) + { + if (stepX == 0 || stepY == 0) { + std::cerr << "stepX and stepY must be greater than zero!" << std::endl; + return; + } + + m_depthDenseSamplingStepX = stepX; + m_depthDenseSamplingStepY = stepY; + } + + virtual void setOgreVisibilityTest(const bool &v) VP_OVERRIDE; + + virtual void setPose(const vpImage &I, const vpHomogeneousMatrix &cdMo) VP_OVERRIDE; + virtual void setPose(const vpImage &I_color, const vpHomogeneousMatrix &cdMo) VP_OVERRIDE; +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_COMMON) + virtual void setPose(const pcl::PointCloud::ConstPtr &point_cloud, const vpHomogeneousMatrix &cdMo); +#endif + + virtual void setScanLineVisibilityTest(const bool &v) VP_OVERRIDE; + + void setUseDepthDenseTracking(const std::string &name, const bool &useDepthDenseTracking); + + virtual void testTracking() VP_OVERRIDE; + + virtual void track(const vpImage &) VP_OVERRIDE; + virtual void track(const vpImage &) VP_OVERRIDE; +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_COMMON) + virtual void track(const pcl::PointCloud::ConstPtr &point_cloud); +#endif + virtual void track(const std::vector &point_cloud, unsigned int width, unsigned int height); + +protected: + //! Set of faces describing the object used only for display with scan line. + vpMbHiddenFaces m_depthDenseHiddenFacesDisplay; + //! List of current active (visible and features extracted) faces + std::vector m_depthDenseListOfActiveFaces; + //! Nb features + unsigned int m_denseDepthNbFeatures; + //! List of faces + std::vector m_depthDenseFaces; + //! Sampling step in x-direction + unsigned int m_depthDenseSamplingStepX; + //! Sampling step in y-direction + unsigned int m_depthDenseSamplingStepY; + //! (s - s*) + vpColVector m_error_depthDense; + //! Interaction matrix + vpMatrix m_L_depthDense; + //! Tukey M-Estimator + vpMbtTukeyEstimator m_robust_depthDense; + //! Robust weights + vpColVector m_w_depthDense; + //! Weighted error + vpColVector m_weightedError_depthDense; +#if DEBUG_DISPLAY_DEPTH_DENSE + vpDisplay *m_debugDisp_depthDense; + vpImage m_debugImage_depthDense; +#endif + + void addFace(vpMbtPolygon &polygon, bool alreadyClose); + + void computeVisibility(unsigned int width, unsigned int height); + + void computeVVS(); + virtual void computeVVSInit() VP_OVERRIDE; + virtual void computeVVSInteractionMatrixAndResidu() VP_OVERRIDE; + virtual void computeVVSWeights(); + using vpMbTracker::computeVVSWeights; + + virtual void initCircle(const vpPoint &p1, const vpPoint &p2, const vpPoint &p3, double radius, int idFace = 0, + const std::string &name = "") VP_OVERRIDE; + + virtual void initCylinder(const vpPoint &p1, const vpPoint &p2, double radius, int idFace = 0, + const std::string &name = "") VP_OVERRIDE; + + virtual void initFaceFromCorners(vpMbtPolygon &polygon) VP_OVERRIDE; + + virtual void initFaceFromLines(vpMbtPolygon &polygon) VP_OVERRIDE; + +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_COMMON) + void segmentPointCloud(const pcl::PointCloud::ConstPtr &point_cloud); +#endif + void segmentPointCloud(const std::vector &point_cloud, unsigned int width, unsigned int height); + void segmentPointCloud(const vpMatrix &point_cloud, unsigned int width, unsigned int height); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbDepthNormalTracker.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbDepthNormalTracker.h new file mode 100644 index 00000000..6e479e35 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbDepthNormalTracker.h @@ -0,0 +1,205 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Model-based tracker using depth normal features. + */ + +#ifndef VP_MB_DEPTH_NORMAL_TRACKER_H +#define VP_MB_DEPTH_NORMAL_TRACKER_H + +#include +#include +#include +#include + +#if DEBUG_DISPLAY_DEPTH_NORMAL +#include +#endif + +BEGIN_VISP_NAMESPACE +/** + * \brief Model-based tracker using depth normal features. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using a MBT tracker in your applications, you may have a look at:
+ * + * - \ref tutorial-tracking-mb-generic + * - \ref tutorial-tracking-mb-generic-stereo + * - \ref tutorial-tracking-mb-generic-rgbd + * - \ref tutorial-tracking-mb-generic-apriltag-live + * - \ref tutorial-mb-generic-json + * - \ref tutorial-tracking-mb-generic-rgbd-Blender +*/ +class VISP_EXPORT vpMbDepthNormalTracker : public virtual vpMbTracker +{ +public: + vpMbDepthNormalTracker(); + virtual ~vpMbDepthNormalTracker() VP_OVERRIDE; + + virtual void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false) VP_OVERRIDE; + + virtual void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false) VP_OVERRIDE; + + virtual inline vpMbtFaceDepthNormal::vpFeatureEstimationType getDepthFeatureEstimationMethod() const + { + return m_depthNormalFeatureEstimationMethod; + } + + virtual inline vpColVector getError() const VP_OVERRIDE { return m_error_depthNormal; } + + virtual std::vector > getModelForDisplay(unsigned int width, unsigned int height, + const vpHomogeneousMatrix &cMo, + const vpCameraParameters &cam, + bool displayFullModel = false) VP_OVERRIDE; + + virtual inline vpColVector getRobustWeights() const VP_OVERRIDE { return m_w_depthNormal; } + + virtual void init(const vpImage &I) VP_OVERRIDE; + + virtual void loadConfigFile(const std::string &configFile, bool verbose = true) VP_OVERRIDE; + + void reInitModel(const vpImage &I, const std::string &cad_name, const vpHomogeneousMatrix &cMo, + bool verbose = false); +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_COMMON) + void reInitModel(const pcl::PointCloud::ConstPtr &point_cloud, const std::string &cad_name, + const vpHomogeneousMatrix &cMo, bool verbose = false); +#endif + + virtual void resetTracker() VP_OVERRIDE; + + virtual void setCameraParameters(const vpCameraParameters &camera) VP_OVERRIDE; + + virtual void setDepthNormalFaceCentroidMethod(const vpMbtFaceDepthNormal::vpFaceCentroidType &method); + + virtual void setDepthNormalFeatureEstimationMethod(const vpMbtFaceDepthNormal::vpFeatureEstimationType &method); + + virtual void setDepthNormalPclPlaneEstimationMethod(int method); + + virtual void setDepthNormalPclPlaneEstimationRansacMaxIter(int maxIter); + + virtual void setDepthNormalPclPlaneEstimationRansacThreshold(double thresold); + + virtual void setDepthNormalSamplingStep(unsigned int stepX, unsigned int stepY); + + // virtual void setDepthNormalUseRobust(bool use); + + virtual void setOgreVisibilityTest(const bool &v) VP_OVERRIDE; + + virtual void setPose(const vpImage &I, const vpHomogeneousMatrix &cdMo) VP_OVERRIDE; + virtual void setPose(const vpImage &I_color, const vpHomogeneousMatrix &cdMo) VP_OVERRIDE; +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_COMMON) + virtual void setPose(const pcl::PointCloud::ConstPtr &point_cloud, const vpHomogeneousMatrix &cdMo); +#endif + + virtual void setScanLineVisibilityTest(const bool &v) VP_OVERRIDE; + + void setUseDepthNormalTracking(const std::string &name, const bool &useDepthNormalTracking); + + virtual void testTracking() VP_OVERRIDE; + + virtual void track(const vpImage &) VP_OVERRIDE; + virtual void track(const vpImage &I_color) VP_OVERRIDE; +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_SEGMENTATION) && defined(VISP_HAVE_PCL_FILTERS) && defined(VISP_HAVE_PCL_COMMON) + virtual void track(const pcl::PointCloud::ConstPtr &point_cloud); +#endif + virtual void track(const std::vector &point_cloud, unsigned int width, unsigned int height); + +protected: + //! Method to estimate the desired features + vpMbtFaceDepthNormal::vpFeatureEstimationType m_depthNormalFeatureEstimationMethod; + //! Set of faces describing the object used only for display with scan line. + vpMbHiddenFaces m_depthNormalHiddenFacesDisplay; + //! List of current active (visible and with features extracted) faces + std::vector m_depthNormalListOfActiveFaces; + //! List of desired features + std::vector m_depthNormalListOfDesiredFeatures; + //! List of faces + std::vector m_depthNormalFaces; + //! PCL plane estimation method + int m_depthNormalPclPlaneEstimationMethod; + //! PCL RANSAC maximum number of iterations + int m_depthNormalPclPlaneEstimationRansacMaxIter; + //! PCL RANSAC threshold + double m_depthNormalPclPlaneEstimationRansacThreshold; + //! Sampling step in x-direction + unsigned int m_depthNormalSamplingStepX; + //! Sampling step in y-direction + unsigned int m_depthNormalSamplingStepY; + //! If true, use Tukey robust M-Estimator + bool m_depthNormalUseRobust; + //! (s - s*) + vpColVector m_error_depthNormal; + //! Display features + std::vector > m_featuresToBeDisplayedDepthNormal; + //! Interaction matrix + vpMatrix m_L_depthNormal; + //! Robust + vpRobust m_robust_depthNormal; + //! Robust weights + vpColVector m_w_depthNormal; + //! Weighted error + vpColVector m_weightedError_depthNormal; +#if DEBUG_DISPLAY_DEPTH_NORMAL + vpDisplay *m_debugDisp_depthNormal; + vpImage m_debugImage_depthNormal; +#endif + + void addFace(vpMbtPolygon &polygon, bool alreadyClose); + + void computeVisibility(unsigned int width, unsigned int height); + + void computeVVS(); + virtual void computeVVSInit() VP_OVERRIDE; + virtual void computeVVSInteractionMatrixAndResidu() VP_OVERRIDE; + + virtual std::vector > getFeaturesForDisplayDepthNormal(); + + virtual void initCircle(const vpPoint &p1, const vpPoint &p2, const vpPoint &p3, double radius, int idFace = 0, + const std::string &name = "") VP_OVERRIDE; + + virtual void initCylinder(const vpPoint &p1, const vpPoint &p2, double radius, int idFace = 0, + const std::string &name = "") VP_OVERRIDE; + + virtual void initFaceFromCorners(vpMbtPolygon &polygon) VP_OVERRIDE; + + virtual void initFaceFromLines(vpMbtPolygon &polygon) VP_OVERRIDE; + +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_SEGMENTATION) && defined(VISP_HAVE_PCL_FILTERS) && defined(VISP_HAVE_PCL_COMMON) + void segmentPointCloud(const pcl::PointCloud::ConstPtr &point_cloud); +#endif + void segmentPointCloud(const std::vector &point_cloud, unsigned int width, unsigned int height); + void segmentPointCloud(const vpMatrix &point_cloud, unsigned int width, unsigned int height); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbEdgeKltTracker.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbEdgeKltTracker.h new file mode 100644 index 00000000..ad3c8aac --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbEdgeKltTracker.h @@ -0,0 +1,375 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Hybrid tracker based on edges (vpMbt) and points of interests (KLT) + */ + +/*! + * \file vpMbEdgeKltTracker.h + * \brief Hybrid tracker based on edges (vpMbt) and points of interests (KLT) + */ + +#ifndef _vpMbEdgeKltTracker_h_ +#define _vpMbEdgeKltTracker_h_ + +#include + +#if defined(VISP_HAVE_MODULE_KLT) && defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) && defined(HAVE_OPENCV_VIDEO) + +#include +#include +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpMbEdgeKltTracker + * \ingroup group_mbt_trackers + * \warning This class is deprecated for user usage. You should rather use the high level + * vpMbGenericTracker class. + * \warning This class is only available if OpenCV is installed, and used. + * + * \brief Hybrid tracker based on moving-edges and keypoints tracked using KLT + * tracker. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using a MBT tracker in your applications, you may have a look at:
+ * + * - \ref tutorial-tracking-mb-generic + * - \ref tutorial-tracking-mb-generic-stereo + * - \ref tutorial-tracking-mb-generic-rgbd + * - \ref tutorial-tracking-mb-generic-apriltag-live + * - \ref tutorial-mb-generic-json + * - \ref tutorial-tracking-mb-generic-rgbd-Blender + * + * Deprecated tutorials
+ * The \ref tutorial-tracking-mb-deprecated is a good starting point to use this class. + * + * The tracker requires the knowledge of the 3D model that could be provided in + * a vrml or in a cao file. The cao format is described in loadCAOModel(). It may + * also use an xml file used to tune the behavior of the tracker and an init file + * used to compute the pose at the very first image. + * + * The following code shows the simplest way to use the tracker. The \ref + * tutorial-tracking-mb-deprecated is also a good starting point to use this class. + * + * \code + * #include + * #include + * #include + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * #if defined VISP_HAVE_OPENCV + * vpMbEdgeKltTracker tracker; // Create an hybrid model based tracker. + * vpImage I; + * vpHomogeneousMatrix cMo; // Pose computed using the tracker. + * vpCameraParameters cam; + * + * // Acquire an image + * vpImageIo::read(I, "cube.pgm"); + * + * #if defined(VISP_HAVE_X11) + * vpDisplayX display; + * display.init(I,100,100,"Mb Hybrid Tracker"); + * #endif + * + * tracker.loadConfigFile("cube.xml"); // Load the configuration of the tracker + * // Load the 3d model in cao format. No 3rd party library is required + * tracker.loadModel("cube.cao"); + * // Get the camera parameters used by the tracker (from the configuration file). + * tracker.getCameraParameters(cam); + * // Initialise manually the pose by clicking on the image points associated to the 3d points contained in the + * // cube.init file. + * tracker.initClick(I, "cube.init"); + * + * while(true){ + * // Acquire a new image + * vpDisplay::display(I); + * tracker.track(I); // Track the object on this image + * tracker.getPose(cMo); // Get the pose + * + * tracker.display(I, cMo, cam, vpColor::darkRed, 1); // Display the model at the computed pose. + * vpDisplay::flush(I); + * } + * + * return 0; + * #endif + * } + * \endcode + * + * The tracker can also be used without display, in that case the initial pose + * must be known (object always at the same initial pose for example) or + * computed using another method: + * + * \code + * #include + * #include + * #include + * #include + * #include + * + * int main() + * { + * #if defined VISP_HAVE_OPENCV + * vpMbEdgeKltTracker tracker; // Create an hybrid model based tracker. + * vpImage I; + * vpHomogeneousMatrix cMo; // Pose used in entry (has to be defined), then computed using the tracker. + * + * //acquire an image + * vpImageIo::read(I, "cube.pgm"); // Example of acquisition + * + * tracker.loadConfigFile("cube.xml"); // Load the configuration of the tracker + * // load the 3d model, to read .wrl model coin is required, if coin is not installed .cao file can be used. + * tracker.loadModel("cube.cao"); + * tracker.initFromPose(I, cMo); // initialise the tracker with the given pose. + * + * while(true){ + * // acquire a new image + * tracker.track(I); // track the object on this image + * tracker.getPose(cMo); // get the pose + * } + * + * return 0; + * #endif + * } + * \endcode + * + * Finally it can be used not to track an object but just to display a model at + * a given pose: + * + * \code + * #include + * #include + * #include + * #include + * #include + * #include + * + * int main() + * { + * #if defined VISP_HAVE_OPENCV + * vpMbEdgeKltTracker tracker; // Create an hybrid model based tracker. + * vpImage I; + * vpHomogeneousMatrix cMo; // Pose used to display the model. + * vpCameraParameters cam; + * + * // Acquire an image + * vpImageIo::read(I, "cube.pgm"); + * + * #if defined(VISP_HAVE_X11) + * vpDisplayX display; + * display.init(I,100,100,"Mb Hybrid Tracker"); + * #endif + * + * tracker.loadConfigFile("cube.xml"); // Load the configuration of the tracker + * tracker.getCameraParameters(cam); // Get the camera parameters used by the tracker (from the configuration file). + * // load the 3d model, to read .wrl model coin is required, if coin is not installed .cao file can be used. + * tracker.loadModel("cube.cao"); + * + * while(true){ + * // acquire a new image + * // Get the pose using any method + * vpDisplay::display(I); + * tracker.display(I, cMo, cam, vpColor::darkRed, 1, true); // Display the model at the computed pose. + * vpDisplay::flush(I); + * } + * + * #endif + * + * return 0; + * } + * \endcode + * +*/ +class VISP_EXPORT vpMbEdgeKltTracker : +#if defined(VISP_HAVE_MODULE_KLT) && defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) && defined(HAVE_OPENCV_VIDEO) + public vpMbKltTracker, +#endif + public vpMbEdgeTracker +{ +protected: + //! The threshold used in the robust estimation of KLT. + double m_thresholdKLT; + //! The threshold used in the robust estimation of MBT. + double m_thresholdMBT; + //! The maximum iteration of the virtual visual servoing stage. + unsigned int m_maxIterKlt; + //! Robust weights for Edge + vpColVector m_w_mbt; + //! Robust weights for KLT + vpColVector m_w_klt; + //! (s - s*) + vpColVector m_error_hybrid; + //! Robust weights + vpColVector m_w_hybrid; + +public: + vpMbEdgeKltTracker(); + virtual ~vpMbEdgeKltTracker(); + + virtual void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false) VP_OVERRIDE; + virtual void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false) VP_OVERRIDE; + + virtual inline vpColVector getError() const VP_OVERRIDE { return m_error_hybrid; } + + virtual std::vector > getModelForDisplay(unsigned int width, unsigned int height, + const vpHomogeneousMatrix &cMo, + const vpCameraParameters &cam, + bool displayFullModel = false) VP_OVERRIDE; + + virtual inline vpColVector getRobustWeights() const VP_OVERRIDE { return m_w_hybrid; } + + /*! + * Get the near distance for clipping. + * + * \return Near clipping value. + */ + virtual inline double getNearClippingDistance() const VP_OVERRIDE { return vpMbKltTracker::getNearClippingDistance(); } + + virtual void loadConfigFile(const std::string &configFile, bool verbose = true) VP_OVERRIDE; + + void reInitModel(const vpImage &I, const std::string &cad_name, const vpHomogeneousMatrix &cMo, + bool verbose = false, const vpHomogeneousMatrix &T = vpHomogeneousMatrix()) VP_OVERRIDE; + void resetTracker() VP_OVERRIDE; + + virtual void setCameraParameters(const vpCameraParameters &cam) VP_OVERRIDE; + + /*! + * Specify which clipping to use. + * + * \sa vpMbtPolygonClipping + * + * \param flags : New clipping flags. + */ + virtual void setClipping(const unsigned int &flags) VP_OVERRIDE { vpMbEdgeTracker::setClipping(flags); } + + /*! + * Set the far distance for clipping. + * + * \param dist : Far clipping value. + */ + virtual void setFarClippingDistance(const double &dist) VP_OVERRIDE { vpMbEdgeTracker::setFarClippingDistance(dist); } + + /*! + * Set the near distance for clipping. + * + * \param dist : Near clipping value. + */ + virtual void setNearClippingDistance(const double &dist) VP_OVERRIDE { vpMbEdgeTracker::setNearClippingDistance(dist); } + + /*! + * Use Ogre3D for visibility tests + * + * \warning This function has to be called before the initialization of the + * tracker. + * + * \param v : True to use it, False otherwise + */ + virtual void setOgreVisibilityTest(const bool &v) VP_OVERRIDE + { + vpMbTracker::setOgreVisibilityTest(v); +#ifdef VISP_HAVE_OGRE + faces.getOgreContext()->setWindowName("MBT Hybrid"); +#endif + } + + /*! + * Use Scanline algorithm for visibility tests + * + * \param v : True to use it, False otherwise + */ + virtual void setScanLineVisibilityTest(const bool &v) VP_OVERRIDE + { + vpMbEdgeTracker::setScanLineVisibilityTest(v); + vpMbKltTracker::setScanLineVisibilityTest(v); + } + + virtual void setPose(const vpImage &I, const vpHomogeneousMatrix &cdMo) VP_OVERRIDE; + virtual void setPose(const vpImage &I_color, const vpHomogeneousMatrix &cdMo) VP_OVERRIDE; + + /*! + * Set if the projection error criteria has to be computed. + * + * \param flag : True if the projection error criteria has to be computed, + * false otherwise + */ + virtual void setProjectionErrorComputation(const bool &flag) VP_OVERRIDE + { + vpMbEdgeTracker::setProjectionErrorComputation(flag); + } + + virtual void testTracking() VP_OVERRIDE { } + virtual void track(const vpImage &I) VP_OVERRIDE; + virtual void track(const vpImage &I_color) VP_OVERRIDE; + +protected: + virtual void computeVVS(const vpImage &I, const unsigned int &nbInfos, unsigned int &nbrow, + unsigned int lvl = 0, double *edge_residual = nullptr, double *klt_residual = nullptr); + virtual void computeVVSInit() VP_OVERRIDE; + virtual void computeVVSInteractionMatrixAndResidu() VP_OVERRIDE; + using vpMbTracker::computeCovarianceMatrixVVS; + using vpMbTracker::computeVVSPoseEstimation; + + virtual void init(const vpImage &I) VP_OVERRIDE; + virtual void initCircle(const vpPoint &, const vpPoint &, const vpPoint &, double r, int idFace = 0, + const std::string &name = "") VP_OVERRIDE; + virtual void initCylinder(const vpPoint &, const vpPoint &, double r, int idFace, const std::string &name = "") VP_OVERRIDE; + virtual void initFaceFromCorners(vpMbtPolygon &polygon) VP_OVERRIDE; + virtual void initFaceFromLines(vpMbtPolygon &polygon) VP_OVERRIDE; + unsigned int initMbtTracking(unsigned int level = 0); + + bool postTracking(const vpImage &I, vpColVector &w_mbt, vpColVector &w_klt, unsigned int lvl = 0); + bool postTracking(const vpImage &I_color, vpColVector &w_mbt, vpColVector &w_klt, unsigned int lvl = 0); + void postTrackingMbt(vpColVector &w, unsigned int level = 0); + + unsigned int trackFirstLoop(const vpImage &I, vpColVector &factor, unsigned int lvl = 0); + void trackSecondLoop(const vpImage &I, vpMatrix &L, vpColVector &_error, + const vpHomogeneousMatrix &cMo, unsigned int lvl = 0); +}; +END_VISP_NAMESPACE +#endif + +#endif // VISP_HAVE_OPENCV diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbEdgeTracker.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbEdgeTracker.h new file mode 100644 index 00000000..385cedea --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbEdgeTracker.h @@ -0,0 +1,547 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Make the complete tracking of an object by using its CAD model + */ + +/*! + * \file vpMbEdgeTracker.h + * \brief Make the complete tracking of an object by using its CAD model. + */ + +#ifndef vpMbEdgeTracker_HH +#define vpMbEdgeTracker_HH + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#if defined(VISP_HAVE_COIN3D) +// Inventor includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#if defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) +#include +#if (VISP_HAVE_OPENCV_VERSION < 0x050000) +#include +#endif +#endif + +BEGIN_VISP_NAMESPACE + +/*! + * \class vpMbEdgeTracker + * \ingroup group_mbt_trackers + * \brief Make the complete tracking of an object by using its CAD model. + * \warning This class is deprecated for user usage. You should rather use the high level + * vpMbGenericTracker class. + * + * This class allows to track an object or a scene given its 3D model. A + * video can be found on YouTube \e https://www.youtube.com/watch?v=UK10KMMJFCI + * The \ref tutorial-tracking-mb-deprecated is also a good starting point to use this class. + * + * The tracker requires the knowledge of the 3D model that could be provided in + * a vrml or in a cao file. The cao format is described in loadCAOModel(). It may + * also use an xml file used to tune the behavior of the tracker and an init file + * used to compute the pose at the very first image. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using a MBT tracker in your applications, you may have a look at:
+ * + * - \ref tutorial-tracking-mb-generic + * - \ref tutorial-tracking-mb-generic-stereo + * - \ref tutorial-tracking-mb-generic-rgbd + * - \ref tutorial-tracking-mb-generic-apriltag-live + * - \ref tutorial-mb-generic-json + * - \ref tutorial-tracking-mb-generic-rgbd-Blender + * + * Deprecated examples
+ * The following code shows the simplest way to use the tracker. + * + * \code + * #include + * #include + * #include + * #include + * #include + * #include + * + * int main() + * { + * vpMbEdgeTracker tracker; // Create a model based tracker. + * vpImage I; + * vpHomogeneousMatrix cMo; // Pose computed using the tracker. + * vpCameraParameters cam; + * + * // Acquire an image + * vpImageIo::read(I, "cube.pgm"); + * + * #if defined(VISP_HAVE_X11) + * vpDisplayX display; + * display.init(I,100,100,"Mb Edge Tracker"); + * #endif + * + * tracker.loadConfigFile("cube.xml"); // Load the configuration of the tracker + * tracker.getCameraParameters(cam); // Get the camera parameters used by the tracker (from the configuration file). + * tracker.loadModel("cube.cao"); // Load the 3d model in cao format. No 3rd party library is required + * // Initialise manually the pose by clicking on the image points associated to the 3d points contained in the + * // cube.init file. + * tracker.initClick(I, "cube.init"); + * + * while(true){ + * // Acquire a new image + * vpDisplay::display(I); + * tracker.track(I); // Track the object on this image + * tracker.getPose(cMo); // Get the pose + * + * tracker.display(I, cMo, cam, vpColor::darkRed, 1); // Display the model at the computed pose. + * vpDisplay::flush(I); + * } + * + * return 0; + * } + * \endcode + * + * For application with large inter-images displacement, multi-scale tracking + * is also possible, by setting the number of scales used and by activating (or + * not) them using a vector of booleans, as presented in the following code: + * + * \code + * ... + * vpHomogeneousMatrix cMo; // Pose computed using the tracker. + * vpCameraParameters cam; + * + * std::vector< bool > scales(3); //Three scales used + * scales.push_back(true); //First scale : active + * scales.push_back(false); //Second scale (/2) : not active + * scales.push_back(true); //Third scale (/4) : active + * tracker.setScales(scales); // Set active scales for multi-scale tracking + * + * tracker.loadConfigFile("cube.xml"); // Load the configuration of the tracker + * tracker.getCameraParameters(cam); // Get the camera parameters used by the tracker (from the configuration file). + * ... + * \endcode + * + * The tracker can also be used without display, in that case the initial pose + * must be known (object always at the same initial pose for example) or + * computed using another method: + * + * \code + * #include + * #include + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpMbEdgeTracker tracker; // Create a model based tracker. + * vpImage I; + * vpHomogeneousMatrix cMo; // Pose used in entry (has to be defined), then computed using the tracker. + * + * //acquire an image + * vpImageIo::read(I, "cube.pgm"); // Example of acquisition + * + * tracker.loadConfigFile("cube.xml"); // Load the configuration of the tracker + * // load the 3d model, to read .wrl model coin is required, if coin is not installed .cao file can be used. + * tracker.loadModel("cube.cao"); + * tracker.initFromPose(I, cMo); // initialize the tracker with the given pose. + * + * while(true){ + * // acquire a new image + * tracker.track(I); // track the object on this image + * tracker.getPose(cMo); // get the pose + * } + * + * return 0; + * } + * \endcode + * + * Finally it can be used not to track an object but just to display a model at + * a given pose: + * + * \code + * #include + * #include + * #include + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpMbEdgeTracker tracker; // Create a model based tracker. + * vpImage I; + * vpHomogeneousMatrix cMo; // Pose used to display the model. + * vpCameraParameters cam; + * + * // Acquire an image + * vpImageIo::read(I, "cube.pgm"); + * + * #if defined(VISP_HAVE_X11) + * vpDisplayX display; + * display.init(I,100,100,"Mb Edge Tracker"); + * #endif + * + * tracker.loadConfigFile("cube.xml"); // Load the configuration of the tracker + * tracker.getCameraParameters(cam); // Get the camera parameters used by the tracker (from the configuration file). + * // load the 3d model, to read .wrl model coin is required, if coin is not installed + * // .cao file can be used. + * tracker.loadModel("cube.cao"); + * + * while(true){ + * // acquire a new image + * // Get the pose using any method + * vpDisplay::display(I); + * tracker.display(I, cMo, cam, vpColor::darkRed, 1, true); // Display the model at the computed pose. + * vpDisplay::flush(I); + * } + * + * return 0; + * } + $ \endcode +*/ +class VISP_EXPORT vpMbEdgeTracker : public virtual vpMbTracker +{ +protected: + //! The moving edges parameters. + vpMe me; + //! Vector of list of all the lines tracked (each line is linked to a list + //! of moving edges). Each element of the vector is for a scale (element 0 = + //! level 0 = no subsampling). + std::vector > lines; + + //! Vector of the tracked circles. + std::vector > circles; + + //! Vector of the tracked cylinders. + std::vector > cylinders; + + //! Index of the polygon to add, and total number of polygon extracted so + //! far. + unsigned int nline; + + //! Index of the circle to add, and total number of circles extracted so + //! far. + unsigned int ncircle; + + //! Index of the cylinder to add, and total number of cylinders extracted so + //! far. + unsigned int ncylinder; + + //! Number of polygon (face) currently visible. + unsigned int nbvisiblepolygone; + + //! Percentage of good points over total number of points below which + //! tracking is supposed to have failed. + double percentageGdPt; + + //! Vector of scale level to use for the multi-scale tracking. + std::vector scales; + + //! Pyramid of image associated to the current image. This pyramid is + //! computed in the init() and in the track() methods. + std::vector *> Ipyramid; + + //! Current scale level used. This attribute must not be modified outside of + //! the downScale() and upScale() methods, as it used to specify to some + //! methods which set of distanceLine use. + unsigned int scaleLevel; + + //! Number of features used in the computation of the projection error + unsigned int nbFeaturesForProjErrorComputation; + + /// Edge VVS variables + vpColVector m_factor; + vpRobust m_robustLines; + vpRobust m_robustCylinders; + vpRobust m_robustCircles; + vpColVector m_wLines; + vpColVector m_wCylinders; + vpColVector m_wCircles; + vpColVector m_errorLines; + vpColVector m_errorCylinders; + vpColVector m_errorCircles; + //! Interaction matrix + vpMatrix m_L_edge; + //! (s - s*) + vpColVector m_error_edge; + //! Robust weights + vpColVector m_w_edge; + //! Weighted error + vpColVector m_weightedError_edge; + //! Robust + vpRobust m_robust_edge; + //! Display features + std::vector > m_featuresToBeDisplayedEdge; +public: + vpMbEdgeTracker(); + virtual ~vpMbEdgeTracker() VP_OVERRIDE; + + /** @name Inherited functionalities from vpMbEdgeTracker */ + //@{ + + virtual void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false) VP_OVERRIDE; + virtual void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false) VP_OVERRIDE; + + void getLline(std::list &linesList, unsigned int level = 0) const; + void getLcircle(std::list &circlesList, unsigned int level = 0) const; + void getLcylinder(std::list &cylindersList, unsigned int level = 0) const; + + virtual std::vector > getModelForDisplay(unsigned int width, unsigned int height, + const vpHomogeneousMatrix &cMo, + const vpCameraParameters &cam, + bool displayFullModel = false) VP_OVERRIDE; + + /*! + * Get the moving edge parameters. + * + * \param p_me [out] : an instance of the moving edge parameters used by the + * tracker. + */ + virtual inline void getMovingEdge(vpMe &p_me) const { p_me = this->me; } + /*! + * Get the moving edge parameters. + * + * \return an instance of the moving edge parameters used by the tracker. + */ + virtual inline vpMe getMovingEdge() const { return this->me; } + + virtual unsigned int getNbPoints(unsigned int level = 0) const; + + /*! + * Return the scales levels used for the tracking. + * + * \return The scales levels used for the tracking. + */ + std::vector getScales() const { return scales; } + /*! + * \return The threshold value between 0 and 1 over good moving edges ratio. + * It allows to decide if the tracker has enough valid moving edges to + * compute a pose. 1 means that all moving edges should be considered as + * good to have a valid pose, while 0.1 means that 10% of the moving edge + * are enough to declare a pose valid. + * + * \sa setGoodMovingEdgesRatioThreshold() + */ + inline double getGoodMovingEdgesRatioThreshold() const { return percentageGdPt; } + + virtual inline vpColVector getError() const VP_OVERRIDE { return m_error_edge; } + + virtual inline vpColVector getRobustWeights() const VP_OVERRIDE { return m_w_edge; } + + virtual void loadConfigFile(const std::string &configFile, bool verbose = true) VP_OVERRIDE; + + virtual void reInitModel(const vpImage &I, const std::string &cad_name, const vpHomogeneousMatrix &cMo, + bool verbose = false, const vpHomogeneousMatrix &T = vpHomogeneousMatrix()); + void resetTracker() VP_OVERRIDE; + + /*! + * Set the camera parameters. + * + * \param cam : The new camera parameters. + */ + virtual void setCameraParameters(const vpCameraParameters &cam) VP_OVERRIDE + { + m_cam = cam; + + for (unsigned int i = 0; i < scales.size(); i += 1) { + if (scales[i]) { + for (std::list::const_iterator it = lines[i].begin(); it != lines[i].end(); ++it) { + (*it)->setCameraParameters(m_cam); + } + + for (std::list::const_iterator it = cylinders[i].begin(); it != cylinders[i].end(); + ++it) { + (*it)->setCameraParameters(m_cam); + } + + for (std::list::const_iterator it = circles[i].begin(); it != circles[i].end(); ++it) { + (*it)->setCameraParameters(m_cam); + } + } + } + } + + virtual void setClipping(const unsigned int &flags) VP_OVERRIDE; + + virtual void setFarClippingDistance(const double &dist) VP_OVERRIDE; + + virtual void setNearClippingDistance(const double &dist) VP_OVERRIDE; + + /*! + * Use Ogre3D for visibility tests + * + * \warning This function has to be called before the initialization of the + * tracker. + * + * \param v : True to use it, False otherwise + */ + virtual void setOgreVisibilityTest(const bool &v) VP_OVERRIDE + { + vpMbTracker::setOgreVisibilityTest(v); +#ifdef VISP_HAVE_OGRE + faces.getOgreContext()->setWindowName("MBT Edge"); +#endif + } + + /*! + * Use Scanline algorithm for visibility tests + * + * \param v : True to use it, False otherwise + */ + virtual void setScanLineVisibilityTest(const bool &v) VP_OVERRIDE + { + vpMbTracker::setScanLineVisibilityTest(v); + + for (unsigned int i = 0; i < scales.size(); i += 1) { + if (scales[i]) { + for (std::list::const_iterator it = lines[i].begin(); it != lines[i].end(); ++it) { + (*it)->useScanLine = v; + } + } + } + } + + /*! + * Set the threshold value between 0 and 1 over good moving edges ratio. It + * allows to decide if the tracker has enough valid moving edges to compute + * a pose. 1 means that all moving edges should be considered as good to + * have a valid pose, while 0.1 means that 10% of the moving edge are enough + * to declare a pose valid. + * + * \param threshold : Value between 0 and 1 that corresponds to the ratio of + * good moving edges that is necessary to consider that the estimated pose + * is valid. Default value is 0.4. + * + * \sa getGoodMovingEdgesRatioThreshold() + */ + void setGoodMovingEdgesRatioThreshold(double threshold) { percentageGdPt = threshold; } + + void setMovingEdge(const vpMe &me); + + virtual void setPose(const vpImage &I, const vpHomogeneousMatrix &cdMo) VP_OVERRIDE; + virtual void setPose(const vpImage &I_color, const vpHomogeneousMatrix &cdMo) VP_OVERRIDE; + + void setScales(const std::vector &_scales); + + void setUseEdgeTracking(const std::string &name, const bool &useEdgeTracking); + + virtual void track(const vpImage &I) VP_OVERRIDE; + virtual void track(const vpImage &I) VP_OVERRIDE; + //@} + +protected: + /** @name Protected Member Functions Inherited from vpMbEdgeTracker */ + //@{ + void addCircle(const vpPoint &P1, const vpPoint &P2, const vpPoint &P3, double r, int idFace = -1, + const std::string &name = ""); + void addCylinder(const vpPoint &P1, const vpPoint &P2, double r, int idFace = -1, const std::string &name = ""); + void addLine(vpPoint &p1, vpPoint &p2, int polygon = -1, std::string name = ""); + void addPolygon(vpMbtPolygon &p); + + void cleanPyramid(std::vector *> &_pyramid); + void computeProjectionError(const vpImage &_I); + + void computeVVS(const vpImage &_I, unsigned int lvl); + void computeVVSFirstPhase(const vpImage &I, unsigned int iter, double &count, unsigned int lvl = 0); + void computeVVSFirstPhaseFactor(const vpImage &I, unsigned int lvl = 0); + void computeVVSFirstPhasePoseEstimation(unsigned int iter, bool &isoJoIdentity); + virtual void computeVVSInit() VP_OVERRIDE; + virtual void computeVVSInteractionMatrixAndResidu() VP_OVERRIDE; + virtual void computeVVSInteractionMatrixAndResidu(const vpImage &I); + virtual void computeVVSWeights(); + using vpMbTracker::computeVVSWeights; + + void displayFeaturesOnImage(const vpImage &I); + void displayFeaturesOnImage(const vpImage &I); + void downScale(const unsigned int _scale); + virtual std::vector > getFeaturesForDisplayEdge(); + virtual void init(const vpImage &I) VP_OVERRIDE; + virtual void initCircle(const vpPoint &p1, const vpPoint &p2, const vpPoint &p3, double radius, int idFace = 0, + const std::string &name = "") VP_OVERRIDE; + virtual void initCylinder(const vpPoint &p1, const vpPoint &p2, double radius, int idFace = 0, + const std::string &name = "") VP_OVERRIDE; + virtual void initFaceFromCorners(vpMbtPolygon &polygon) VP_OVERRIDE; + virtual void initFaceFromLines(vpMbtPolygon &polygon) VP_OVERRIDE; + unsigned int initMbtTracking(unsigned int &nberrors_lines, unsigned int &nberrors_cylinders, + unsigned int &nberrors_circles); + void initMovingEdge(const vpImage &I, const vpHomogeneousMatrix &_cMo, const bool &useInitRange = true); + void initPyramid(const vpImage &_I, std::vector *> &_pyramid); + void reInitLevel(const unsigned int _lvl); + void reinitMovingEdge(const vpImage &I, const vpHomogeneousMatrix &_cMo); + void removeCircle(const std::string &name); + void removeCylinder(const std::string &name); + void removeLine(const std::string &name); + void resetMovingEdge(); + virtual void testTracking() VP_OVERRIDE; + void trackMovingEdge(const vpImage &I); + void updateMovingEdge(const vpImage &I); + void updateMovingEdgeWeights(); + void upScale(const unsigned int _scale); + void visibleFace(const vpImage &_I, const vpHomogeneousMatrix &_cMo, bool &newvisibleline); + //@} +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbGenericTracker.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbGenericTracker.h new file mode 100644 index 00000000..cc1f5f6c --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbGenericTracker.h @@ -0,0 +1,1073 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Generic model-based tracker. + */ + +/*! + * \file vpMbGenericTracker.h + *\brief Generic model-based tracker + */ + +#ifndef VP_MB_GENERIC_TRACKER_H +#define VP_MB_GENERIC_TRACKER_H + +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpMbGenericTracker + * \ingroup group_mbt_trackers + * \brief Real-time 6D object pose tracking using its CAD model. + * + * The tracker requires the knowledge of the 3D model that could be provided in + * a vrml or in a cao file. The cao format is described in loadCAOModel(). It may + * also use an xml file used to tune the behavior of the tracker and an init file + * used to compute the pose at the very first image. + * + * This class allows tracking an object or a scene given its 3D model. More information in \cite Trinh18a. + * A lot of videos can be found on YouTube VispTeam channel. + * + * \htmlonly + * + * + * + * + * \endhtmlonly + * + *

Tutorials & Examples

+ * + * Tutorials
+ * + * The \ref tutorial-tracking-mb-generic is a good starting point to use this + * class. If you want to track an object with a stereo camera refer to + * \ref tutorial-tracking-mb-generic-stereo. If you want rather use a RGB-D camera and exploit + * the depth information, you may see \ref tutorial-tracking-mb-generic-rgbd. + * There is also \ref tutorial-detection-object that shows how to initialize the tracker from + * an initial pose provided by a detection algorithm. + * + * JSON serialization + * + * Since ViSP 3.6.0, if ViSP is build with \ref soft_tool_json 3rd-party we introduce JSON serialization capabilities for vpMbGenericTracker. + * The following sample code shows how to save a model-based tracker settings in a file named `mbt.json` + * and reload the values from this JSON file. + * \code + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * #if defined(VISP_HAVE_NLOHMANN_JSON) + * std::string filename = "mbt-generic.json"; + * { + * vpMbGenericTracker mbt; + * mbt.saveConfigFile(filename); + * } + * { + * vpMbGenericTracker mbt; + * bool verbose = false; + * std::cout << "Read model-based tracker settings from " << filename << std::endl; + * mbt.loadConfigFile(filename, verbose); + * } + * #endif + * } + * \endcode + * If you build and execute the sample code, it will produce the following output: + * \code{.unparsed} + * Read model-based tracker settings from mbt-generic.json + * \endcode + * + * The content of the `mbt.json` file is the following: + * \code{.unparsed} + * $ cat mbt-generic.json + * { + * "referenceCameraName": "Camera", + * "trackers": { + * "Camera": { + * "angleAppear": 89.0, + * "angleDisappear": 89.0, + * "camTref": { + * "cols": 4, + * "data": [ + * 1.0, + * 0.0, + * 0.0, + * 0.0, + * 0.0, + * 1.0, + * 0.0, + * 0.0, + * 0.0, + * 0.0, + * 1.0, + * 0.0, + * 0.0, + * 0.0, + * 0.0, + * 1.0 + * ], + * "rows": 4, + * "type": "vpHomogeneousMatrix" + * }, + * "camera": { + * "model": "perspectiveWithoutDistortion", + * "px": 600.0, + * "py": 600.0, + * "u0": 192.0, + * "v0": 144.0 + * }, + * "clipping": { + * "far": 100.0, + * "flags": [ + * "none" + * ], + * "near": 0.001 + * }, + * "display": { + * "features": false, + * "projectionError": false + * }, + * "edge": { + * "maskSign": 0, + * "maskSize": 5, + * "minSampleStep": 4.0, + * "mu": [ + * 0.5, + * 0.5 + * ], + * "nMask": 180, + * "ntotalSample": 0, + * "pointsToTrack": 500, + * "range": 4, + * "sampleStep": 10.0, + * "strip": 2, +* "thresholdType": "normalized", + * "threshold": 20.0 + * }, + * "lod": { + * "minLineLengthThresholdGeneral": 50.0, + * "minPolygonAreaThresholdGeneral": 2500.0, + * "useLod": false + * }, + * "type": [ + * "edge" + * ], + * "visibilityTest": { + * "ogre": false, + * "scanline": false + * } + * } + * }, + * "version": "1.0" + * } + * \endcode +*/ +class VISP_EXPORT vpMbGenericTracker : public vpMbTracker +{ +public: + enum vpTrackerType + { + EDGE_TRACKER = 1 << 0, /*!< Model-based tracking using moving edges features. */ +#if defined(VISP_HAVE_MODULE_KLT) && (defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) && defined(HAVE_OPENCV_VIDEO)) + KLT_TRACKER = 1 << 1, /*!< Model-based tracking using KLT features. */ +#endif + DEPTH_NORMAL_TRACKER = 1 << 2, /*!< Model-based tracking using depth normal features. */ + DEPTH_DENSE_TRACKER = 1 << 3 /*!< Model-based tracking using depth dense features. */ + }; + + vpMbGenericTracker(); + vpMbGenericTracker(unsigned int nbCameras, int trackerType = EDGE_TRACKER); + VP_EXPLICIT vpMbGenericTracker(const std::vector &trackerTypes); + vpMbGenericTracker(const std::vector &cameraNames, const std::vector &trackerTypes); + + virtual ~vpMbGenericTracker() VP_OVERRIDE; + + virtual double computeCurrentProjectionError(const vpImage &I, const vpHomogeneousMatrix &_cMo, + const vpCameraParameters &_cam) VP_OVERRIDE; + virtual double computeCurrentProjectionError(const vpImage &I, const vpHomogeneousMatrix &_cMo, + const vpCameraParameters &_cam); + + virtual void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false) VP_OVERRIDE; + virtual void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false) VP_OVERRIDE; + + virtual void display(const vpImage &I1, const vpImage &I2, + const vpHomogeneousMatrix &c1Mo, const vpHomogeneousMatrix &c2Mo, const vpCameraParameters &cam1, + const vpCameraParameters &cam2, const vpColor &color, unsigned int thickness = 1, + bool displayFullModel = false); + virtual void display(const vpImage &I1, const vpImage &I2, const vpHomogeneousMatrix &c1Mo, + const vpHomogeneousMatrix &c2Mo, const vpCameraParameters &cam1, const vpCameraParameters &cam2, + const vpColor &color, unsigned int thickness = 1, bool displayFullModel = false); + + virtual void display(const std::map *> &mapOfImages, + const std::map &mapOfCameraPoses, + const std::map &mapOfCameraParameters, const vpColor &col, + unsigned int thickness = 1, bool displayFullModel = false); + virtual void display(const std::map *> &mapOfImages, + const std::map &mapOfCameraPoses, + const std::map &mapOfCameraParameters, const vpColor &col, + unsigned int thickness = 1, bool displayFullModel = false); + + virtual std::vector getCameraNames() const; + + using vpMbTracker::getCameraParameters; + virtual void getCameraParameters(vpCameraParameters &camera) const VP_OVERRIDE; + virtual void getCameraParameters(vpCameraParameters &cam1, vpCameraParameters &cam2) const; + virtual void getCameraParameters(std::map &mapOfCameraParameters) const; + + virtual std::map getCameraTrackerTypes() const; + + using vpMbTracker::getClipping; + virtual void getClipping(unsigned int &clippingFlag1, unsigned int &clippingFlag2) const; + virtual void getClipping(std::map &mapOfClippingFlags) const; + + virtual inline vpColVector getError() const VP_OVERRIDE { return m_error; } + + virtual vpMbHiddenFaces &getFaces() VP_OVERRIDE; + virtual vpMbHiddenFaces &getFaces(const std::string &cameraName); + +#if defined(VISP_HAVE_MODULE_KLT) && defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) && defined(HAVE_OPENCV_VIDEO) + virtual std::list &getFeaturesCircle(); + virtual std::list &getFeaturesKltCylinder(); + virtual std::list &getFeaturesKlt(); +#endif + + virtual std::vector > getFeaturesForDisplay(); + virtual void getFeaturesForDisplay(std::map > > &mapOfFeatures); + + virtual double getGoodMovingEdgesRatioThreshold() const; + +#if defined(VISP_HAVE_MODULE_KLT) && defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) && defined(HAVE_OPENCV_VIDEO) + virtual std::vector getKltImagePoints() const; + virtual std::map getKltImagePointsWithId() const; + + virtual unsigned int getKltMaskBorder() const; + virtual int getKltNbPoints() const; + + virtual vpKltOpencv getKltOpencv() const; + virtual void getKltOpencv(vpKltOpencv &klt1, vpKltOpencv &klt2) const; + virtual void getKltOpencv(std::map &mapOfKlts) const; + +#if defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) && defined(HAVE_OPENCV_VIDEO) + virtual std::vector getKltPoints() const; +#endif + + virtual double getKltThresholdAcceptation() const; +#endif + + virtual void getLcircle(std::list &circlesList, unsigned int level = 0) const; + virtual void getLcircle(const std::string &cameraName, std::list &circlesList, + unsigned int level = 0) const; + virtual void getLcylinder(std::list &cylindersList, unsigned int level = 0) const; + virtual void getLcylinder(const std::string &cameraName, std::list &cylindersList, + unsigned int level = 0) const; + virtual void getLline(std::list &linesList, unsigned int level = 0) const; + virtual void getLline(const std::string &cameraName, std::list &linesList, + unsigned int level = 0) const; + + virtual std::vector > getModelForDisplay(unsigned int width, unsigned int height, + const vpHomogeneousMatrix &cMo, + const vpCameraParameters &cam, + bool displayFullModel = false) VP_OVERRIDE; + virtual void getModelForDisplay(std::map > > &mapOfModels, + const std::map &mapOfwidths, + const std::map &mapOfheights, + const std::map &mapOfcMos, + const std::map &mapOfCams, + bool displayFullModel = false); + + virtual vpMe getMovingEdge() const; + virtual void getMovingEdge(vpMe &me1, vpMe &me2) const; + virtual void getMovingEdge(std::map &mapOfMovingEdges) const; + + /*! + * Return the number of depth dense features taken into account in the virtual visual-servoing scheme. + */ + virtual inline unsigned int getNbFeaturesDepthDense() const { return m_nb_feat_depthDense; } + + /*! + * Return the number of depth normal features features taken into account in the virtual visual-servoing scheme. + */ + virtual inline unsigned int getNbFeaturesDepthNormal() const { return m_nb_feat_depthNormal; } + + /*! + * Return the number of moving-edges features taken into account in the virtual visual-servoing scheme. + * + * This function is similar to getNbPoints(). + */ + virtual inline unsigned int getNbFeaturesEdge() const { return m_nb_feat_edge; } + + /*! + * Return the number of klt keypoints features taken into account in the virtual visual-servoing scheme. + */ + virtual inline unsigned int getNbFeaturesKlt() const { return m_nb_feat_klt; } + + virtual unsigned int getNbPoints(unsigned int level = 0) const; + virtual void getNbPoints(std::map &mapOfNbPoints, unsigned int level = 0) const; + + virtual unsigned int getNbPolygon() const VP_OVERRIDE; + virtual void getNbPolygon(std::map &mapOfNbPolygons) const; + + virtual vpMbtPolygon *getPolygon(unsigned int index) VP_OVERRIDE; + virtual vpMbtPolygon *getPolygon(const std::string &cameraName, unsigned int index); + + virtual std::pair, std::vector > > + getPolygonFaces(bool orderPolygons = true, bool useVisibility = true, bool clipPolygon = false) VP_OVERRIDE; + virtual void getPolygonFaces(std::map > &mapOfPolygons, + std::map > > &mapOfPoints, + bool orderPolygons = true, bool useVisibility = true, bool clipPolygon = false); + + using vpMbTracker::getPose; + virtual void getPose(vpHomogeneousMatrix &cMo) const VP_OVERRIDE; + virtual void getPose(vpHomogeneousMatrix &c1Mo, vpHomogeneousMatrix &c2Mo) const; + virtual void getPose(std::map &mapOfCameraPoses) const; + + virtual std::string getReferenceCameraName() const; + + virtual inline vpColVector getRobustWeights() const VP_OVERRIDE { return m_w; } + + virtual int getTrackerType() const; + + virtual void init(const vpImage &I) VP_OVERRIDE; + +#ifdef VISP_HAVE_MODULE_GUI + using vpMbTracker::initClick; + virtual void initClick(const vpImage &I1, const vpImage &I2, + const std::string &initFile1, const std::string &initFile2, bool displayHelp = false, + const vpHomogeneousMatrix &T1 = vpHomogeneousMatrix(), + const vpHomogeneousMatrix &T2 = vpHomogeneousMatrix()); + virtual void initClick(const vpImage &I_color1, const vpImage &I_color2, const std::string &initFile1, + const std::string &initFile2, bool displayHelp = false, + const vpHomogeneousMatrix &T1 = vpHomogeneousMatrix(), + const vpHomogeneousMatrix &T2 = vpHomogeneousMatrix()); + + virtual void + initClick(const std::map *> &mapOfImages, + const std::map &mapOfInitFiles, bool displayHelp = false, + const std::map &mapOfT = std::map()); + virtual void + initClick(const std::map *> &mapOfImages, + const std::map &mapOfInitFiles, bool displayHelp = false, + const std::map &mapOfT = std::map()); +#endif + + using vpMbTracker::initFromPoints; + virtual void initFromPoints(const vpImage &I1, const vpImage &I2, + const std::string &initFile1, const std::string &initFile2); + virtual void initFromPoints(const vpImage &I_color1, const vpImage &I_color2, + const std::string &initFile1, const std::string &initFile2); + + virtual void initFromPoints(const std::map *> &mapOfImages, + const std::map &mapOfInitPoints); + virtual void initFromPoints(const std::map *> &mapOfColorImages, + const std::map &mapOfInitPoints); + + using vpMbTracker::initFromPose; + virtual void initFromPose(const vpImage &I, const vpHomogeneousMatrix &cMo) VP_OVERRIDE; + virtual void initFromPose(const vpImage &I1, const vpImage &I2, + const std::string &initFile1, const std::string &initFile2); + virtual void initFromPose(const vpImage &I_color1, const vpImage &I_color2, + const std::string &initFile1, const std::string &initFile2); + + virtual void initFromPose(const std::map *> &mapOfImages, + const std::map &mapOfInitPoses); + virtual void initFromPose(const std::map *> &mapOfColorImages, + const std::map &mapOfInitPoses); + + virtual void initFromPose(const vpImage &I1, const vpImage &I2, + const vpHomogeneousMatrix &c1Mo, const vpHomogeneousMatrix &c2Mo); + virtual void initFromPose(const vpImage &I_color1, const vpImage &I_color2, + const vpHomogeneousMatrix &c1Mo, const vpHomogeneousMatrix &c2Mo); + + virtual void initFromPose(const std::map *> &mapOfImages, + const std::map &mapOfCameraPoses); + virtual void initFromPose(const std::map *> &mapOfColorImages, + const std::map &mapOfCameraPoses); + + virtual void loadConfigFile(const std::string &configFile, bool verbose = true) VP_OVERRIDE; + virtual void loadConfigFile(const std::string &configFile1, const std::string &configFile2, bool verbose = true); + virtual void loadConfigFile(const std::map &mapOfConfigFiles, bool verbose = true); + +#ifdef VISP_HAVE_NLOHMANN_JSON + virtual void saveConfigFile(const std::string &settingsFile) const; +#endif + + virtual void loadModel(const std::string &modelFile, bool verbose = false, + const vpHomogeneousMatrix &T = vpHomogeneousMatrix()) VP_OVERRIDE; + virtual void loadModel(const std::string &modelFile1, const std::string &modelFile2, bool verbose = false, + const vpHomogeneousMatrix &T1 = vpHomogeneousMatrix(), + const vpHomogeneousMatrix &T2 = vpHomogeneousMatrix()); + + virtual void + loadModel(const std::map &mapOfModelFiles, bool verbose = false, + const std::map &mapOfT = std::map()); + + virtual void reInitModel(const vpImage &I, const std::string &cad_name, const vpHomogeneousMatrix &cMo, + bool verbose = false, const vpHomogeneousMatrix &T = vpHomogeneousMatrix()); + virtual void reInitModel(const vpImage &I_color, const std::string &cad_name, const vpHomogeneousMatrix &cMo, + bool verbose = false, const vpHomogeneousMatrix &T = vpHomogeneousMatrix()); + + virtual void reInitModel(const vpImage &I1, const vpImage &I2, + const std::string &cad_name1, const std::string &cad_name2, const vpHomogeneousMatrix &c1Mo, + const vpHomogeneousMatrix &c2Mo, bool verbose = false, + const vpHomogeneousMatrix &T1 = vpHomogeneousMatrix(), + const vpHomogeneousMatrix &T2 = vpHomogeneousMatrix()); + virtual void reInitModel(const vpImage &I_color1, const vpImage &I_color2, + const std::string &cad_name1, const std::string &cad_name2, const vpHomogeneousMatrix &c1Mo, + const vpHomogeneousMatrix &c2Mo, bool verbose = false, + const vpHomogeneousMatrix &T1 = vpHomogeneousMatrix(), + const vpHomogeneousMatrix &T2 = vpHomogeneousMatrix()); + + virtual void + reInitModel(const std::map *> &mapOfImages, + const std::map &mapOfModelFiles, + const std::map &mapOfCameraPoses, bool verbose = false, + const std::map &mapOfT = std::map()); + virtual void + reInitModel(const std::map *> &mapOfColorImages, + const std::map &mapOfModelFiles, + const std::map &mapOfCameraPoses, bool verbose = false, + const std::map &mapOfT = std::map()); + + virtual void resetTracker() VP_OVERRIDE; + + virtual void setAngleAppear(const double &a) VP_OVERRIDE; + virtual void setAngleAppear(const double &a1, const double &a2); + virtual void setAngleAppear(const std::map &mapOfAngles); + + virtual void setAngleDisappear(const double &a) VP_OVERRIDE; + virtual void setAngleDisappear(const double &a1, const double &a2); + virtual void setAngleDisappear(const std::map &mapOfAngles); + + virtual void setCameraParameters(const vpCameraParameters &camera) VP_OVERRIDE; + virtual void setCameraParameters(const vpCameraParameters &camera1, const vpCameraParameters &camera2); + virtual void setCameraParameters(const std::map &mapOfCameraParameters); + + virtual void setCameraTransformationMatrix(const std::string &cameraName, + const vpHomogeneousMatrix &cameraTransformationMatrix); + virtual void + setCameraTransformationMatrix(const std::map &mapOfTransformationMatrix); + + virtual void setClipping(const unsigned int &flags) VP_OVERRIDE; + virtual void setClipping(const unsigned int &flags1, const unsigned int &flags2); + virtual void setClipping(const std::map &mapOfClippingFlags); + + virtual void setDepthDenseFilteringMaxDistance(double maxDistance); + virtual void setDepthDenseFilteringMethod(int method); + virtual void setDepthDenseFilteringMinDistance(double minDistance); + virtual void setDepthDenseFilteringOccupancyRatio(double occupancyRatio); + virtual void setDepthDenseSamplingStep(unsigned int stepX, unsigned int stepY); + + virtual void setDepthNormalFaceCentroidMethod(const vpMbtFaceDepthNormal::vpFaceCentroidType &method); + virtual void setDepthNormalFeatureEstimationMethod(const vpMbtFaceDepthNormal::vpFeatureEstimationType &method); + virtual void setDepthNormalPclPlaneEstimationMethod(int method); + virtual void setDepthNormalPclPlaneEstimationRansacMaxIter(int maxIter); + virtual void setDepthNormalPclPlaneEstimationRansacThreshold(double threshold); + virtual void setDepthNormalSamplingStep(unsigned int stepX, unsigned int stepY); + + virtual void setDisplayFeatures(bool displayF) VP_OVERRIDE; + + virtual void setFarClippingDistance(const double &dist) VP_OVERRIDE; + virtual void setFarClippingDistance(const double &dist1, const double &dist2); + virtual void setFarClippingDistance(const std::map &mapOfClippingDists); + + virtual void setFeatureFactors(const std::map &mapOfFeatureFactors); + + virtual void setGoodMovingEdgesRatioThreshold(double threshold); + +#ifdef VISP_HAVE_OGRE + virtual void setGoodNbRayCastingAttemptsRatio(const double &ratio) VP_OVERRIDE; + virtual void setNbRayCastingAttemptsForVisibility(const unsigned int &attempts) VP_OVERRIDE; +#endif + +#if defined(VISP_HAVE_MODULE_KLT) && defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) && defined(HAVE_OPENCV_VIDEO) + virtual void setKltMaskBorder(const unsigned int &e); + virtual void setKltMaskBorder(const unsigned int &e1, const unsigned int &e2); + virtual void setKltMaskBorder(const std::map &mapOfErosions); + + virtual void setKltOpencv(const vpKltOpencv &t); + virtual void setKltOpencv(const vpKltOpencv &t1, const vpKltOpencv &t2); + virtual void setKltOpencv(const std::map &mapOfKlts); + + virtual void setKltThresholdAcceptation(double th); +#endif + + virtual void setLod(bool useLod, const std::string &name = "") VP_OVERRIDE; + + virtual void setMask(const vpImage &mask) VP_OVERRIDE; + + virtual void setMinLineLengthThresh(double minLineLengthThresh, const std::string &name = "") VP_OVERRIDE; + virtual void setMinPolygonAreaThresh(double minPolygonAreaThresh, const std::string &name = "") VP_OVERRIDE; + + virtual void setMovingEdge(const vpMe &me); + virtual void setMovingEdge(const vpMe &me1, const vpMe &me2); + virtual void setMovingEdge(const std::map &mapOfMe); + + virtual void setNearClippingDistance(const double &dist) VP_OVERRIDE; + virtual void setNearClippingDistance(const double &dist1, const double &dist2); + virtual void setNearClippingDistance(const std::map &mapOfDists); + + virtual void setOgreShowConfigDialog(bool showConfigDialog) VP_OVERRIDE; + virtual void setOgreVisibilityTest(const bool &v) VP_OVERRIDE; + + virtual void setOptimizationMethod(const vpMbtOptimizationMethod &opt) VP_OVERRIDE; + + virtual void setPose(const vpImage &I, const vpHomogeneousMatrix &cdMo) VP_OVERRIDE; + virtual void setPose(const vpImage &I_color, const vpHomogeneousMatrix &cdMo) VP_OVERRIDE; + + virtual void setPose(const vpImage &I1, const vpImage &I2, + const vpHomogeneousMatrix &c1Mo, const vpHomogeneousMatrix &c2Mo); + virtual void setPose(const vpImage &I_color1, const vpImage &I_color2, + const vpHomogeneousMatrix &c1Mo, const vpHomogeneousMatrix &c2Mo); + + virtual void setPose(const std::map *> &mapOfImages, + const std::map &mapOfCameraPoses); + virtual void setPose(const std::map *> &mapOfColorImages, + const std::map &mapOfCameraPoses); + + virtual void setProjectionErrorComputation(const bool &flag) VP_OVERRIDE; + + virtual void setProjectionErrorDisplay(bool display) VP_OVERRIDE; + virtual void setProjectionErrorDisplayArrowLength(unsigned int length) VP_OVERRIDE; + virtual void setProjectionErrorDisplayArrowThickness(unsigned int thickness) VP_OVERRIDE; + + virtual void setReferenceCameraName(const std::string &referenceCameraName); + + virtual void setScanLineVisibilityTest(const bool &v) VP_OVERRIDE; + + virtual void setTrackerType(int type); + virtual void setTrackerType(const std::map &mapOfTrackerTypes); + + virtual void setUseDepthDenseTracking(const std::string &name, const bool &useDepthDenseTracking); + virtual void setUseDepthNormalTracking(const std::string &name, const bool &useDepthNormalTracking); + virtual void setUseEdgeTracking(const std::string &name, const bool &useEdgeTracking); +#if defined(VISP_HAVE_MODULE_KLT) && defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) && defined(HAVE_OPENCV_VIDEO) + virtual void setUseKltTracking(const std::string &name, const bool &useKltTracking); +#endif + + virtual void testTracking() VP_OVERRIDE; + + virtual void track(const vpImage &I) VP_OVERRIDE; + virtual void track(const vpImage &I_color) VP_OVERRIDE; + + virtual void track(const vpImage &I1, const vpImage &I2); + virtual void track(const vpImage &I_color1, const vpImage &I_color2); + + virtual void track(std::map *> &mapOfImages); + virtual void track(std::map *> &mapOfColorImages); + +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_SEGMENTATION) && defined(VISP_HAVE_PCL_FILTERS) && defined(VISP_HAVE_PCL_COMMON) + virtual void track(std::map *> &mapOfImages, + std::map::ConstPtr> &mapOfPointClouds); + virtual void track(std::map *> &mapOfColorImages, + std::map::ConstPtr> &mapOfPointClouds); +#endif + + virtual void track(std::map *> &mapOfImages, + std::map *> &mapOfPointClouds, + std::map &mapOfPointCloudWidths, + std::map &mapOfPointCloudHeights); + virtual void track(std::map *> &mapOfColorImages, + std::map *> &mapOfPointClouds, + std::map &mapOfPointCloudWidths, + std::map &mapOfPointCloudHeights); + + virtual void track(std::map *> &mapOfImages, + std::map &mapOfPointClouds, + std::map &mapOfPointCloudWidths, + std::map &mapOfPointCloudHeights); + virtual void track(std::map *> &mapOfColorImages, + std::map &mapOfPointClouds, + std::map &mapOfPointCloudWidths, + std::map &mapOfPointCloudHeights); + +protected: + virtual void computeProjectionError(); + + virtual void computeVVS(std::map *> &mapOfImages); + + virtual void computeVVSInit() VP_OVERRIDE; + virtual void computeVVSInit(std::map *> &mapOfImages); + virtual void computeVVSInteractionMatrixAndResidu() VP_OVERRIDE; + virtual void computeVVSInteractionMatrixAndResidu(std::map *> &mapOfImages, + std::map &mapOfVelocityTwist); + using vpMbTracker::computeVVSWeights; + virtual void computeVVSWeights(); + + virtual void initCircle(const vpPoint &p1, const vpPoint &p2, const vpPoint &p3, double radius, int idFace = 0, + const std::string &name = "") VP_OVERRIDE; + + virtual void initCylinder(const vpPoint &p1, const vpPoint &p2, double radius, int idFace = 0, + const std::string &name = "") VP_OVERRIDE; + + virtual void initFaceFromCorners(vpMbtPolygon &polygon) VP_OVERRIDE; + + virtual void initFaceFromLines(vpMbtPolygon &polygon) VP_OVERRIDE; + + virtual void loadConfigFileXML(const std::string &configFile, bool verbose = true); +#ifdef VISP_HAVE_NLOHMANN_JSON + virtual void loadConfigFileJSON(const std::string &configFile, bool verbose = true); +#endif + +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_SEGMENTATION) && defined(VISP_HAVE_PCL_FILTERS) && defined(VISP_HAVE_PCL_COMMON) + virtual void preTracking(std::map *> &mapOfImages, + std::map::ConstPtr> &mapOfPointClouds); +#endif + virtual void preTracking(std::map *> &mapOfImages, + std::map *> &mapOfPointClouds, + std::map &mapOfPointCloudWidths, + std::map &mapOfPointCloudHeights); + virtual void preTracking(std::map *> &mapOfImages, + std::map &mapOfPointClouds, + std::map &mapOfPointCloudWidths, + std::map &mapOfPointCloudHeights); + +private: + class TrackerWrapper : public vpMbEdgeTracker, +#if defined(VISP_HAVE_MODULE_KLT) && defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) && defined(HAVE_OPENCV_VIDEO) + public vpMbKltTracker, +#endif + public vpMbDepthNormalTracker, + public vpMbDepthDenseTracker + { + friend class vpMbGenericTracker; +#ifdef VISP_HAVE_NLOHMANN_JSON + friend void to_json(nlohmann::json &j, const TrackerWrapper &t); + friend void from_json(const nlohmann::json &j, TrackerWrapper &t); +#endif + + public: + //! (s - s*) + vpColVector m_error; + //! Interaction matrix + vpMatrix m_L; + //! Type of the tracker (a combination of the above) + int m_trackerType; + //! Robust weights + vpColVector m_w; + //! Weighted error + vpColVector m_weightedError; + + TrackerWrapper(); + explicit TrackerWrapper(int trackerType); + + virtual inline vpColVector getError() const VP_OVERRIDE { return m_error; } + + virtual inline vpColVector getRobustWeights() const VP_OVERRIDE { return m_w; } + + virtual inline int getTrackerType() const { return m_trackerType; } + + virtual void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false) VP_OVERRIDE; + virtual void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false) VP_OVERRIDE; + + virtual std::vector > getFeaturesForDisplay(); + + virtual std::vector > getModelForDisplay(unsigned int width, unsigned int height, + const vpHomogeneousMatrix &cMo, + const vpCameraParameters &cam, + bool displayFullModel = false) VP_OVERRIDE; + + virtual void init(const vpImage &I) VP_OVERRIDE; + + virtual void loadConfigFile(const std::string &configFile, bool verbose = true) VP_OVERRIDE; + + virtual void reInitModel(const vpImage &I, const std::string &cad_name, + const vpHomogeneousMatrix &cMo, bool verbose = false, + const vpHomogeneousMatrix &T = vpHomogeneousMatrix()) VP_OVERRIDE; + virtual void reInitModel(const vpImage &I_color, const std::string &cad_name, + const vpHomogeneousMatrix &cMo, bool verbose = false, + const vpHomogeneousMatrix &T = vpHomogeneousMatrix()); + + virtual void resetTracker() VP_OVERRIDE; + + virtual void setCameraParameters(const vpCameraParameters &camera) VP_OVERRIDE; + + virtual void setClipping(const unsigned int &flags) VP_OVERRIDE; + + virtual void setFarClippingDistance(const double &dist) VP_OVERRIDE; + + virtual void setNearClippingDistance(const double &dist) VP_OVERRIDE; + + virtual void setOgreVisibilityTest(const bool &v) VP_OVERRIDE; + + virtual void setPose(const vpImage &I, const vpHomogeneousMatrix &cdMo) VP_OVERRIDE; + virtual void setPose(const vpImage &I_color, const vpHomogeneousMatrix &cdMo) VP_OVERRIDE; + + virtual void setProjectionErrorComputation(const bool &flag) VP_OVERRIDE; + + virtual void setScanLineVisibilityTest(const bool &v) VP_OVERRIDE; + + virtual void setTrackerType(int type); + + virtual void testTracking() VP_OVERRIDE; + + virtual void track(const vpImage &I) VP_OVERRIDE; + virtual void track(const vpImage &I_color) VP_OVERRIDE; +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_SEGMENTATION) && defined(VISP_HAVE_PCL_FILTERS) && defined(VISP_HAVE_PCL_COMMON) + // Fix error: using declaration ‘using vpMbDepthDenseTracker::setPose’ conflicts with a previous + // using declaration that occurs with g++ 4.6.3 on Ubuntu 12.04 +#if !((__GNUC__ == 4) && (__GNUC_MINOR__ == 6)) + using vpMbDepthNormalTracker::track; +#endif + using vpMbDepthDenseTracker::track; + using vpMbEdgeTracker::track; + virtual void track(const vpImage *const ptr_I, const pcl::PointCloud::ConstPtr &point_cloud); +#endif + + protected: + virtual void computeVVS(const vpImage *const ptr_I); + virtual void computeVVSInit() VP_OVERRIDE; + virtual void computeVVSInit(const vpImage *const ptr_I); + virtual void computeVVSInteractionMatrixAndResidu() VP_OVERRIDE; + using vpMbEdgeTracker::computeVVSInteractionMatrixAndResidu; + virtual void computeVVSInteractionMatrixAndResidu(const vpImage *const ptr_I); + using vpMbTracker::computeVVSWeights; + virtual void computeVVSWeights() VP_OVERRIDE; + + virtual void initCircle(const vpPoint &p1, const vpPoint &p2, const vpPoint &p3, double radius, int idFace = 0, + const std::string &name = "") VP_OVERRIDE; + + virtual void initCylinder(const vpPoint &p1, const vpPoint &p2, double radius, int idFace = 0, + const std::string &name = "") VP_OVERRIDE; + + virtual void initFaceFromCorners(vpMbtPolygon &polygon) VP_OVERRIDE; + virtual void initFaceFromLines(vpMbtPolygon &polygon) VP_OVERRIDE; + + virtual void initMbtTracking(const vpImage *const ptr_I); + +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_SEGMENTATION) && defined(VISP_HAVE_PCL_FILTERS) && defined(VISP_HAVE_PCL_COMMON) + virtual void postTracking(const vpImage *const ptr_I, + const pcl::PointCloud::ConstPtr &point_cloud); + virtual void preTracking(const vpImage *const ptr_I, + const pcl::PointCloud::ConstPtr &point_cloud); +#endif + virtual void postTracking(const vpImage *const ptr_I = nullptr, const unsigned int pointcloud_width = 0, + const unsigned int pointcloud_height = 0); + virtual void preTracking(const vpImage *const ptr_I = nullptr, + const std::vector *const point_cloud = nullptr, + const unsigned int pointcloud_width = 0, const unsigned int pointcloud_height = 0); + virtual void preTracking(const vpImage *const ptr_I = nullptr, + const vpMatrix *const point_cloud = nullptr, + const unsigned int pointcloud_width = 0, const unsigned int pointcloud_height = 0); + + virtual void reInitModel(const vpImage *const I, const vpImage *const I_color, + const std::string &cad_name, const vpHomogeneousMatrix &cMo, bool verbose = false, + const vpHomogeneousMatrix &T = vpHomogeneousMatrix()); + +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_SEGMENTATION) && defined(VISP_HAVE_PCL_FILTERS) && defined(VISP_HAVE_PCL_COMMON) + // Fix error: using declaration ‘using vpMbDepthDenseTracker::track’ conflicts with a previous + // using declaration that occurs with g++ 4.6.3 on Ubuntu 12.04 +#if !((__GNUC__ == 4) && (__GNUC_MINOR__ == 6)) + using vpMbDepthNormalTracker::setPose; +#endif + using vpMbDepthDenseTracker::setPose; +#endif +#if defined(VISP_HAVE_MODULE_KLT) && defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) && defined(HAVE_OPENCV_VIDEO) + virtual void setPose(const vpImage *I, const vpImage *I_color, + const vpHomogeneousMatrix &cdMo) VP_OVERRIDE; +#else + virtual void setPose(const vpImage *I, const vpImage *I_color, + const vpHomogeneousMatrix &cdMo); +#endif + }; +#ifdef VISP_HAVE_NLOHMANN_JSON + friend void to_json(nlohmann::json &j, const TrackerWrapper &t); + friend void from_json(const nlohmann::json &j, TrackerWrapper &t); +#endif + +protected: + //! (s - s*) + vpColVector m_error; + //! Interaction matrix + vpMatrix m_L; + //! Map of camera transformation matrix between the current camera frame to + //! the reference camera frame (cCurrent_M_cRef) + std::map m_mapOfCameraTransformationMatrix; + //! Ponderation between each feature type in the VVS stage + std::map m_mapOfFeatureFactors; + //! Map of Model-based trackers, key is the name of the camera, value is the + //! tracker + std::map m_mapOfTrackers; + //! Percentage of good points over total number of points below which + //! tracking is supposed to have failed (only for Edge tracking). + double m_percentageGdPt; + //! Name of the reference camera + std::string m_referenceCameraName; + //! Threshold below which the weight associated to a point to consider this + //! one as an outlier (only for KLT tracking). + double m_thresholdOutlier; + //! Robust weights + vpColVector m_w; + //! Weighted error + vpColVector m_weightedError; + + //! Number of moving-edges features + unsigned int m_nb_feat_edge; + //! Number of klt features + unsigned int m_nb_feat_klt; + //! Number of depth normal features + unsigned int m_nb_feat_depthNormal; + //! Number of depth dense features + unsigned int m_nb_feat_depthDense; +}; + +#ifdef VISP_HAVE_NLOHMANN_JSON + +#define MBT_JSON_SETTINGS_VERSION "1.0" + +#if defined(__clang__) +// Mute warning : declaration requires an exit-time destructor [-Wexit-time-destructors] +// message : expanded from macro 'NLOHMANN_JSON_SERIALIZE_ENUM' +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +// Serialize tracker type enumeration +#if defined(VISP_HAVE_MODULE_KLT) && defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) && defined(HAVE_OPENCV_VIDEO) +NLOHMANN_JSON_SERIALIZE_ENUM(vpMbGenericTracker::vpTrackerType, { + {vpMbGenericTracker::EDGE_TRACKER, "edge"}, + {vpMbGenericTracker::KLT_TRACKER, "klt"}, + {vpMbGenericTracker::DEPTH_DENSE_TRACKER, "depthDense"}, + {vpMbGenericTracker::DEPTH_NORMAL_TRACKER, "depthNormal"} + }); +#else +NLOHMANN_JSON_SERIALIZE_ENUM(vpMbGenericTracker::vpTrackerType, { + {vpMbGenericTracker::EDGE_TRACKER, "edge"}, + {vpMbGenericTracker::DEPTH_DENSE_TRACKER, "depthDense"}, + {vpMbGenericTracker::DEPTH_NORMAL_TRACKER, "depthNormal"} +}); +#endif + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +/** +* @brief Serialize a tracker wrapper's settings into a JSON representation. +* \sa from_json for more details on what is serialized +* @param j The modified json object. +* @param t The tracker to serialize. +*/ +inline void to_json(nlohmann::json &j, const vpMbGenericTracker::TrackerWrapper &t) +{ + // Common tracker attributes + VP_ATTRIBUTE_NO_DESTROY const static std::vector trackerTypes = { + vpMbGenericTracker::EDGE_TRACKER, + #if defined(VISP_HAVE_MODULE_KLT) && defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) && defined(HAVE_OPENCV_VIDEO) + vpMbGenericTracker::KLT_TRACKER, + #endif + vpMbGenericTracker::DEPTH_DENSE_TRACKER, + vpMbGenericTracker::DEPTH_NORMAL_TRACKER + }; + j = nlohmann::json { + {"camera", t.m_cam}, + {"type", flagsToJSON(t.m_trackerType, trackerTypes)}, + {"angleAppear", vpMath::deg(t.getAngleAppear())}, + {"angleDisappear", vpMath::deg(t.getAngleDisappear())}, + {"lod", { + {"useLod", t.useLodGeneral}, + {"minLineLengthThresholdGeneral", t.minLineLengthThresholdGeneral}, + {"minPolygonAreaThresholdGeneral", t.minPolygonAreaThresholdGeneral} + }}, + {"display", { + {"features", t.displayFeatures}, + {"projectionError", t.m_projectionErrorDisplay} + }}, + {"visibilityTest", { + {"ogre", t.useOgre}, + {"scanline", t.useScanLine} + }}, + {"clipping", { + {"flags", clippingFlagsToJSON(t.getClipping())}, + {"near", t.getNearClippingDistance()}, + {"far", t.getFarClippingDistance()}, + }} + }; + //Check tracker type: for each type, add settings to json if the tracker t does use the features + //Edge tracker settings + if (t.m_trackerType & vpMbGenericTracker::EDGE_TRACKER) { + j["edge"] = t.me; + } + //KLT tracker settings +#if defined(VISP_HAVE_MODULE_KLT) && defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) && defined(HAVE_OPENCV_VIDEO) + if (t.m_trackerType & vpMbGenericTracker::KLT_TRACKER) { + nlohmann::json klt = t.tracker; + klt["maskBorder"] = t.maskBorder; + j["klt"] = klt; + } +#endif + //Depth normal settings + if (t.m_trackerType & vpMbGenericTracker::DEPTH_NORMAL_TRACKER) { + j["normals"] = nlohmann::json { + {"featureEstimationMethod", t.m_depthNormalFeatureEstimationMethod}, + {"pcl", { + {"method", t.m_depthNormalPclPlaneEstimationMethod}, + {"ransacMaxIter", t.m_depthNormalPclPlaneEstimationRansacMaxIter}, + {"ransacThreshold", t.m_depthNormalPclPlaneEstimationRansacThreshold} + }}, + {"sampling", { + {"x", t.m_depthNormalSamplingStepX}, + {"y", t.m_depthNormalSamplingStepY} + }} + }; + } + //Depth dense settings + if (t.m_trackerType & vpMbGenericTracker::DEPTH_DENSE_TRACKER) { + j["dense"] = { + {"sampling", { + {"x", t.m_depthDenseSamplingStepX}, + {"y", t.m_depthDenseSamplingStepY} + }} + }; + } +} +/** + * @brief Load configuration settings from a JSON object for a tracker wrapper. + * + * The settings must at the minimum contain the camera parameters #vpCameraParameters::from_json and the type of the tracker. + * + * The type of the tracker is serialized as a combination of flags of type vpMbGenericTracker::vpTrackerType: + * \code{.json} + * "type" : ["edge", "klt"] // for a tracker that uses edges and KLT point as features + * "type": ["depthDense", "depthNormal"] // for a tracker that operates on the depth map using normal + * // and the dense depth map features + * \endcode + * + * Then for each used type of feature that is used, the corresponding settings are deserialized. + * + * The settings may also contain settings about clipping, LOD or face tracking. + * + * \sa to_json + * @param j The JSON object containing the settings + * @param t The tracker wrapper for which to load settings + */ +inline void from_json(const nlohmann::json &j, vpMbGenericTracker::TrackerWrapper &t) +{ + t.setCameraParameters(j.at("camera")); + t.setTrackerType(flagsFromJSON(j.at("type"))); + //Load base settings + if (j.contains("angleAppear")) { + t.setAngleAppear(vpMath::rad(static_cast(j.at("angleAppear")))); + } + if (j.contains("angleDisappear")) { + t.setAngleDisappear(vpMath::rad(static_cast(j.at("angleDisappear")))); + } + if (j.contains("clipping")) { + const nlohmann::json clipping = j["clipping"]; + t.setNearClippingDistance(clipping.value("near", t.getNearClippingDistance())); + t.setFarClippingDistance(clipping.value("far", t.getFarClippingDistance())); + if (clipping.contains("flags")) { + t.setClipping(flagsFromJSON(clipping.at("flags"))); + } + } + if (j.contains("lod")) { + const nlohmann::json lod = j["lod"]; + t.useLodGeneral = lod.value("useLod", t.useLodGeneral); + t.minLineLengthThresholdGeneral = lod.value("minLineLengthThresholdGeneral", t.minLineLengthThresholdGeneral); + t.minPolygonAreaThresholdGeneral = lod.value("minPolygonAreaThresholdGeneral", t.minPolygonAreaThresholdGeneral); + t.applyLodSettingInConfig = false; + if (t.getNbPolygon() > 0) { + t.applyLodSettingInConfig = true; + t.setLod(t.useLodGeneral); + t.setMinLineLengthThresh(t.minLineLengthThresholdGeneral); + t.setMinPolygonAreaThresh(t.minPolygonAreaThresholdGeneral); + } + } + if (j.contains("display")) { + const nlohmann::json displayJson = j["display"]; + t.setDisplayFeatures(displayJson.value("features", t.displayFeatures)); + t.setProjectionErrorDisplay(displayJson.value("projectionError", t.m_projectionErrorDisplay)); + } + if (j.contains("visibilityTest")) { + const nlohmann::json visJson = j["visibilityTest"]; + t.setOgreVisibilityTest(visJson.value("ogre", t.useOgre)); + t.setScanLineVisibilityTest(visJson.value("scanline", t.useScanLine)); + } + + //Check tracker type: for each type, load settings for this specific tracker type + //Edge tracker settings + if (t.m_trackerType & vpMbGenericTracker::EDGE_TRACKER) { + from_json(j.at("edge"), t.me); + t.setMovingEdge(t.me); + } + //KLT tracker settings +#if defined(VISP_HAVE_MODULE_KLT) && defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) && defined(HAVE_OPENCV_VIDEO) + if (t.m_trackerType & vpMbGenericTracker::KLT_TRACKER) { + const nlohmann::json klt = j.at("klt"); + t.tracker = klt; + t.setMaskBorder(klt.value("maskBorder", t.maskBorder)); + t.faces.getMbScanLineRenderer().setMaskBorder(t.maskBorder); + } +#else + if (j.contains("klt")) { + std::cerr << "Trying to load a KLT tracker, but the ViSP dependency requirements are not met. Ignoring." << std::endl; + } +#endif + //Depth normal settings + if (t.m_trackerType & vpMbGenericTracker::DEPTH_NORMAL_TRACKER) { + const nlohmann::json n = j.at("normals"); + t.setDepthNormalFeatureEstimationMethod(n.at("featureEstimationMethod")); + if (n.contains("pcl")) { + const nlohmann::json pcl = n["pcl"]; + t.setDepthNormalPclPlaneEstimationMethod(pcl.at("method")); + t.setDepthNormalPclPlaneEstimationRansacMaxIter(pcl.at("ransacMaxIter")); + t.setDepthNormalPclPlaneEstimationRansacThreshold(pcl.at("ransacThreshold")); + } + if (n.contains("sampling")) { + const nlohmann::json sampling = n.at("sampling"); + t.setDepthNormalSamplingStep(sampling.at("x"), sampling.at("y")); + } + } + //Depth Dense settings + if (t.m_trackerType & vpMbGenericTracker::DEPTH_DENSE_TRACKER) { + const nlohmann::json dense = j.at("dense"); + if (dense.contains("sampling")) { + const nlohmann::json sampling = dense.at("sampling"); + t.setDepthDenseSamplingStep(sampling.at("x"), sampling.at("y")); + } + } + } + +#endif + +END_VISP_NAMESPACE + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbHiddenFaces.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbHiddenFaces.h new file mode 100644 index 00000000..6a7583b2 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbHiddenFaces.h @@ -0,0 +1,966 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Generic model based tracker. This class declares the methods to implement + * in order to have a model based tracker. + */ + +#ifndef VP_MB_HIDDEN_FACES_H +#define VP_MB_HIDDEN_FACES_H + +#include +#include +#include +#include +#include +#include +#include + +#ifdef VISP_HAVE_OGRE +#include +#endif + +#include +#include + +BEGIN_VISP_NAMESPACE +template class vpMbHiddenFaces; + +// Forward declaration to have the operator in the global namespace +template void swap(vpMbHiddenFaces &first, vpMbHiddenFaces &second); +END_VISP_NAMESPACE + +BEGIN_VISP_NAMESPACE +/*! + * \class vpMbHiddenFaces + * + * \brief Implementation of the polygons management for the model-based + * trackers. + * + * \ingroup group_mbt_faces + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using a MBT tracker in your applications, you may have a look at:
+ * + * - \ref tutorial-tracking-mb-generic + * - \ref tutorial-tracking-mb-generic-stereo + * - \ref tutorial-tracking-mb-generic-rgbd + * - \ref tutorial-tracking-mb-generic-apriltag-live + * - \ref tutorial-mb-generic-json + * - \ref tutorial-tracking-mb-generic-rgbd-Blender +*/ +template class vpMbHiddenFaces +{ +private: + //! List of polygons + std::vector Lpol; + //! Number of visible polygon + unsigned int nbVisiblePolygon; + vpMbScanLine scanlineRender; + +#ifdef VISP_HAVE_OGRE + vpImage ogreBackground; + bool ogreInitialised; + unsigned int nbRayAttempts; + double ratioVisibleRay; + vpAROgre *ogre; + std::vector lOgrePolygons; + bool ogreShowConfigDialog; +#endif + + unsigned int setVisiblePrivate(const vpHomogeneousMatrix &cMo, const double &angleAppears, + const double &angleDisappears, bool &changed, bool useOgre = false, + bool not_used = false, unsigned int width = 0, unsigned int height = 0, + const vpCameraParameters &cam = vpCameraParameters()); + +public: + vpMbHiddenFaces(); + virtual ~vpMbHiddenFaces(); + vpMbHiddenFaces(const vpMbHiddenFaces ©); + vpMbHiddenFaces &operator=(vpMbHiddenFaces other); + friend void swap(vpMbHiddenFaces &first, vpMbHiddenFaces &second); + + void addPolygon(PolygonType *p); + + bool computeVisibility(const vpHomogeneousMatrix &cMo, const double &angleAppears, const double &angleDisappears, + bool &changed, bool useOgre, bool not_used, unsigned int width, unsigned int height, + const vpCameraParameters &cam, const vpTranslationVector &cameraPos, unsigned int index); + + void computeClippedPolygons(const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam); + + void computeScanLineRender(const vpCameraParameters &cam, const unsigned int &w, const unsigned int &h); + + void computeScanLineQuery(const vpPoint &a, const vpPoint &b, std::vector > &lines, + const bool &displayResults = false); + + vpMbScanLine &getMbScanLineRenderer() { return scanlineRender; } + +#ifdef VISP_HAVE_OGRE + void displayOgre(const vpHomogeneousMatrix &cMo); +#endif + + /*! + * Get the list of polygons. + * + * \return Mbt Klt polygons list. + */ + std::vector &getPolygon() { return Lpol; } + +#ifdef VISP_HAVE_OGRE + void initOgre(const vpCameraParameters &cam = vpCameraParameters()); +#endif + + /*! + * Get the number of visible polygons. + * + * \return number of visible polygons. + */ + unsigned int getNbVisiblePolygon() const { return nbVisiblePolygon; } + +#ifdef VISP_HAVE_OGRE + /*! + * Get the number of rays that will be sent toward each polygon for + * visibility test. Each ray will go from the optic center of the camera to a + * random point inside the considered polygon. + * + * \sa getGoodNbRayCastingAttemptsRatio() + * + * \return Number of rays sent. + */ + unsigned int getNbRayCastingAttemptsForVisibility() { return nbRayAttempts; } + + /*! + * Get the Ogre3D Context. + * + * \return A pointer on a vpAROgre instance. + */ + vpAROgre *getOgreContext() { return ogre; } + + /*! + * Get the ratio of visibility attempts that has to be successful to consider + * a polygon as visible. + * + * \sa getNbRayCastingAttemptsForVisibility() + * + * \return Ratio of successful attempts that has to be considered. Value will + * be between 0.0 (0%) and 1.0 (100%). + */ + double getGoodNbRayCastingAttemptsRatio() { return ratioVisibleRay; } +#endif + + bool isAppearing(unsigned int i) { return Lpol[i]->isAppearing(); } + +#ifdef VISP_HAVE_OGRE + /*! + * Tell whether if Ogre Context is initialised or not. + * + * \return True if it does, false otherwise. + */ + bool isOgreInitialised() { return ogreInitialised; } +#endif + + /*! + * Check if the polygon at position i in the list is visible. + * + * \param i : TPosition in the list. + * + * \return Return true if the polygon is visible. + */ + bool isVisible(unsigned int i) { return Lpol[i]->isVisible(); } + +#ifdef VISP_HAVE_OGRE + bool isVisibleOgre(const vpTranslationVector &cameraPos, const unsigned int &index); +#endif + + //! Operator[] as modifier. + inline PolygonType *operator[](unsigned int i) { return Lpol[i]; } + //! Operator[] as reader. + inline const PolygonType *operator[](unsigned int i) const { return Lpol[i]; } + + void reset(); + +#ifdef VISP_HAVE_OGRE + /*! + * Set the background size (by default it is 640x480). + * The background size has to match with the size of the image that you are + * using for the tracking. + * + * \warning This function has to be called before initOgre(). + * + * \param h : Height of the background + * \param w : Width of the background + */ + void setBackgroundSizeOgre(const unsigned int &h, const unsigned int &w) + { + ogreBackground = vpImage(h, w, 0); + } + + /*! + * Set the number of rays that will be sent toward each polygon for + * visibility test. Each ray will go from the optic center of the camera to a + * random point inside the considered polygon. + * + * \sa setGoodNbRayCastingAttemptsRatio(const double &) + * + * \param attempts Number of rays to be sent. + */ + void setNbRayCastingAttemptsForVisibility(const unsigned int &attempts) { nbRayAttempts = attempts; } + + /*! + * Set the ratio of visibility attempts that has to be successful to consider + * a polygon as visible. + * + * \sa setNbRayCastingAttemptsForVisibility(const unsigned int &) + * + * \param ratio : Ratio of successful attempts that has to be considered. + * Value has to be between 0.0 (0%) and 1.0 (100%). + */ + void setGoodNbRayCastingAttemptsRatio(const double &ratio) + { + ratioVisibleRay = ratio; + if (ratioVisibleRay > 1.0) + ratioVisibleRay = 1.0; + if (ratioVisibleRay < 0.0) + ratioVisibleRay = 0.0; + } + /*! + * Enable/Disable the appearance of Ogre config dialog on startup. + * + * \warning This method has only effect when Ogre is used and Ogre visibility + * test is enabled using setOgreVisibilityTest() with true parameter. + * + * \param showConfigDialog : if true, shows Ogre dialog window (used to set + * Ogre rendering options) when Ogre visibility is enabled. By default, this + * functionality is turned off. + */ + inline void setOgreShowConfigDialog(bool showConfigDialog) { ogreShowConfigDialog = showConfigDialog; } +#endif + + unsigned int setVisible(unsigned int width, unsigned int height, const vpCameraParameters &cam, + const vpHomogeneousMatrix &cMo, const double &angle, bool &changed); + unsigned int setVisible(unsigned int width, unsigned int height, const vpCameraParameters &cam, + const vpHomogeneousMatrix &cMo, const double &angleAppears, const double &angleDisappears, + bool &changed); + unsigned int setVisible(const vpHomogeneousMatrix &cMo, const double &angleAppears, const double &angleDisappears, + bool &changed); + +#ifdef VISP_HAVE_OGRE + unsigned int setVisibleOgre(unsigned int width, unsigned int height, const vpCameraParameters &cam, + const vpHomogeneousMatrix &cMo, const double &angleAppears, const double &angleDisappears, + bool &changed); + unsigned int setVisibleOgre(const vpHomogeneousMatrix &cMo, const double &angleAppears, const double &angleDisappears, + bool &changed); +#endif + /*! + * Get the number of polygons. + * + * \return Size of the list. + */ + inline unsigned int size() const { return static_cast(Lpol.size()); } +}; + +/*! + * Basic constructor. + */ +template +vpMbHiddenFaces::vpMbHiddenFaces() : Lpol(), nbVisiblePolygon(0), scanlineRender() +{ +#ifdef VISP_HAVE_OGRE + ogreInitialised = false; + nbRayAttempts = 1; + ratioVisibleRay = 1.0; + ogreShowConfigDialog = false; + ogre = new vpAROgre(); + ogreBackground = vpImage(480, 640, 0); +#endif +} + +/*! + * Basic destructor. + */ +template vpMbHiddenFaces::~vpMbHiddenFaces() +{ + for (unsigned int i = 0; i < Lpol.size(); i++) { + if (Lpol[i] != nullptr) { + delete Lpol[i]; + } + Lpol[i] = nullptr; + } + Lpol.resize(0); + +#ifdef VISP_HAVE_OGRE + if (ogre != nullptr) { + delete ogre; + ogre = nullptr; + } + + // This is already done by calling "delete ogre" + // for(unsigned int i = 0 ; i < lOgrePolygons.size() ; i++){ + // if (lOgrePolygons[i]!=nullptr){ + // delete lOgrePolygons[i]; + // } + // lOgrePolygons[i] = nullptr; + // } + + lOgrePolygons.resize(0); +#endif +} + +/*! + * \relates vpMbHiddenFaces + */ +template +vpMbHiddenFaces::vpMbHiddenFaces(const vpMbHiddenFaces ©) + : Lpol(), nbVisiblePolygon(copy.nbVisiblePolygon), scanlineRender(copy.scanlineRender) +#ifdef VISP_HAVE_OGRE + , + ogreBackground(copy.ogreBackground), ogreInitialised(copy.ogreInitialised), nbRayAttempts(copy.nbRayAttempts), + ratioVisibleRay(copy.ratioVisibleRay), ogre(nullptr), lOgrePolygons(), ogreShowConfigDialog(copy.ogreShowConfigDialog) +#endif +{ + // Copy the list of polygons + for (unsigned int i = 0; i < copy.Lpol.size(); i++) { + PolygonType *poly = new PolygonType(*copy.Lpol[i]); + Lpol.push_back(poly); + } +} + +template void swap(vpMbHiddenFaces &first, vpMbHiddenFaces &second) +{ + using std::swap; + swap(first.Lpol, second.Lpol); + swap(first.nbVisiblePolygon, second.nbVisiblePolygon); + swap(first.scanlineRender, second.scanlineRender); +#ifdef VISP_HAVE_OGRE + swap(first.ogreInitialised, second.ogreInitialised); + swap(first.nbRayAttempts, second.nbRayAttempts); + swap(first.ratioVisibleRay, second.ratioVisibleRay); + swap(first.ogreShowConfigDialog, second.ogreShowConfigDialog); + swap(first.ogre, second.ogre); + swap(first.ogreBackground, second.ogreBackground); +#endif +} + +/*! + * Copy assignment operator. + */ +template +vpMbHiddenFaces &vpMbHiddenFaces::operator=(vpMbHiddenFaces other) +{ + swap(*this, other); + + return *this; +} + +/*! + * Add a polygon to the list of polygons. + * + * \param p : The polygon to add. + */ +template void vpMbHiddenFaces::addPolygon(PolygonType *p) +{ + PolygonType *p_new = new PolygonType; + p_new->index = p->index; + p_new->setNbPoint(p->nbpt); + p_new->isvisible = p->isvisible; + p_new->useLod = p->useLod; + p_new->minLineLengthThresh = p->minLineLengthThresh; + p_new->minPolygonAreaThresh = p->minPolygonAreaThresh; + p_new->setName(p->name); + p_new->hasOrientation = p->hasOrientation; + + for (unsigned int i = 0; i < p->nbpt; i++) + p_new->p[i] = p->p[i]; + Lpol.push_back(p_new); +} + +/*! + * Reset the hidden faces (remove the list of PolygonType). + */ +template void vpMbHiddenFaces::reset() +{ + nbVisiblePolygon = 0; + for (unsigned int i = 0; i < Lpol.size(); i++) { + if (Lpol[i] != nullptr) { + delete Lpol[i]; + } + Lpol[i] = nullptr; + } + Lpol.resize(0); + +#ifdef VISP_HAVE_OGRE + if (ogre != nullptr) { + delete ogre; + ogre = nullptr; + } + + // This is already done by calling "delete ogre" + // for(unsigned int i = 0 ; i < lOgrePolygons.size() ; i++){ + // if (lOgrePolygons[i]!=nullptr){ + // delete lOgrePolygons[i]; + // } + // lOgrePolygons[i] = nullptr; + // } + + lOgrePolygons.resize(0); + + ogreInitialised = false; + nbRayAttempts = 1; + ratioVisibleRay = 1.0; + ogre = new vpAROgre(); + ogreBackground = vpImage(480, 640); +#endif +} + +/*! + * Compute the clipped points of the polygons that have been added via + * addPolygon(). + * + * \param cMo : Pose that will be used to clip the polygons. + * \param cam : Camera parameters that will be used to clip the polygons. + */ +template +void vpMbHiddenFaces::computeClippedPolygons(const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam) +{ + for (unsigned int i = 0; i < Lpol.size(); i++) { + // For fast result we could just clip visible polygons. + // However clipping all of them gives us the possibility to return more + // information in the scanline visibility results + // if(Lpol[i]->isVisible()) + { + Lpol[i]->changeFrame(cMo); + Lpol[i]->computePolygonClipped(cam); + } + } +} + +/*! + * Render the scene in order to perform, later via computeScanLineQuery(), + * visibility tests. + * + * \param cam : Camera parameters that will be used to render the scene. + * \param w : Width of the render window. + * \param h : Height of the render window. + */ +template +void vpMbHiddenFaces::computeScanLineRender(const vpCameraParameters &cam, const unsigned int &w, + const unsigned int &h) +{ + std::vector > > polyClipped(Lpol.size()); + std::vector > *> listPolyClipped; + std::vector listPolyIndices; + + for (unsigned int i = 0; i < Lpol.size(); i++) { + // For fast result we could just use visible polygons. + // However using all of them gives us the possibility to return more + // information in the scanline visibility results + // if(Lpol[i]->isVisible()) + { + polyClipped[i].clear(); + Lpol[i]->getPolygonClipped(polyClipped[i]); + if (polyClipped[i].size() != 0) { + listPolyClipped.push_back(&polyClipped[i]); + listPolyIndices.push_back(Lpol[i]->getIndex()); + } + } + } + + scanlineRender.drawScene(listPolyClipped, listPolyIndices, cam, w, h); +} + +/*! + * Compute scanline visibility results for a line. + * + * \warning computeScanLineRender() function has to be called before + * + * \param a : First point of the line. + * \param b : Second point of the line. + * \param lines : Result of the scanline visibility. List of the visible parts of the line. + * \param displayResults : True if the results have to be displayed. False otherwise. + */ +template +void vpMbHiddenFaces::computeScanLineQuery(const vpPoint &a, const vpPoint &b, + std::vector > &lines, + const bool &displayResults) +{ + scanlineRender.queryLineVisibility(a, b, lines, displayResults); +} + +/*! + * Compute the number of visible polygons. + * + * \param cMo : The pose of the camera + * \param angleAppears : Angle used to test the appearance of a face + * \param angleDisappears : Angle used to test the disappearance of a face + * \param changed : True if a face appeared, disappeared or too many points have been lost. False otherwise + * \param useOgre : True if a Ogre is used to test the visibility, False otherwise + * \param not_used : Unused parameter. + * \param width : Image width. + * \param height : Image height. + * \param cam : Camera parameters. + * + * \return Return the number of visible polygons + */ +template +unsigned int vpMbHiddenFaces::setVisiblePrivate(const vpHomogeneousMatrix &cMo, const double &angleAppears, + const double &angleDisappears, bool &changed, bool useOgre, + bool not_used, unsigned int width, unsigned int height, + const vpCameraParameters &cam) +{ + nbVisiblePolygon = 0; + changed = false; + + vpTranslationVector cameraPos; + + if (useOgre) { +#ifdef VISP_HAVE_OGRE + cMo.inverse().extract(cameraPos); + ogre->renderOneFrame(ogreBackground, cMo); +#else + vpTRACE("ViSP doesn't have Ogre3D, simple visibility test used"); +#endif + } + + for (unsigned int i = 0; i < Lpol.size(); i++) { + // std::cout << "Calling poly: " << i << std::endl; + if (computeVisibility(cMo, angleAppears, angleDisappears, changed, useOgre, not_used, width, height, cam, cameraPos, + i)) + nbVisiblePolygon++; + } + return nbVisiblePolygon; +} + +/*! + * Compute the visibility of a given face index. + * + * \param[in] cMo : The pose of the camera + * \param[in] angleAppears : Angle used to test the appearance of a face + * \param[in] angleDisappears : Angle used to test the disappearance of a face + * \param[in] changed : True if a face appeared, disappeared or too many points have been lost. False otherwise. + * \param[in] useOgre : True if a Ogre is used to test the visibility, False otherwise. + * \param[in] not_used : Unused parameter. + * \param[in] width : Image width. + * \param[in] height : Image height. + * \param[in] cam : Camera parameters. + * \param[in] cameraPos : Position of the camera. Used only when Ogre is used as 3rd party. + * \param[in] index : Index of the face to consider. + * + * \return Return true if the face is visible. + */ +template +bool vpMbHiddenFaces::computeVisibility(const vpHomogeneousMatrix &cMo, const double &angleAppears, + const double &angleDisappears, bool &changed, bool useOgre, + bool not_used, unsigned int width, unsigned int height, + const vpCameraParameters &cam, + const vpTranslationVector &cameraPos, unsigned int index) +{ + (void)not_used; + unsigned int i = index; + Lpol[i]->changeFrame(cMo); + Lpol[i]->isappearing = false; + + // Commented because we need to compute visibility + // even when dealing with line in level of detail case + /*if(Lpol[i]->getNbPoint() <= 2) + { + Lpol[i]->isvisible = true; + } + else*/ + { + if (Lpol[i]->isVisible()) { + bool testDisappear = false; + // unsigned int nbCornerInsidePrev = 0; + + if (!testDisappear) { + if (useOgre) +#ifdef VISP_HAVE_OGRE + testDisappear = + ((!Lpol[i]->isVisible(cMo, angleDisappears, true, cam, width, height)) || !isVisibleOgre(cameraPos, i)); +#else + { + (void)cameraPos; // Avoid warning + testDisappear = (!Lpol[i]->isVisible(cMo, angleDisappears, false, cam, width, height)); + } +#endif + else + testDisappear = (!Lpol[i]->isVisible(cMo, angleDisappears, false, cam, width, height)); + } + + // test if the face is still visible + if (testDisappear) { + // std::cout << "Face " << i << " disappears" << + // std::endl; + changed = true; + Lpol[i]->isvisible = false; + } + else { + // nbVisiblePolygon++; + Lpol[i]->isvisible = true; + + // if(nbCornerInsidePrev > Lpol[i]->getNbCornerInsidePrevImage()) + // changed = true; + } + } + else { + bool testAppear = true; + + if (testAppear) { + if (useOgre) +#ifdef VISP_HAVE_OGRE + testAppear = + ((Lpol[i]->isVisible(cMo, angleAppears, true, cam, width, height)) && isVisibleOgre(cameraPos, i)); +#else + testAppear = (Lpol[i]->isVisible(cMo, angleAppears, false, cam, width, height)); +#endif + else + testAppear = (Lpol[i]->isVisible(cMo, angleAppears, false, cam, width, height)); + } + + if (testAppear) { + // std::cout << "Face " << i << " appears" << std::endl; + Lpol[i]->isvisible = true; + changed = true; + // nbVisiblePolygon++; + } + else { + // std::cout << "Problem" << std::endl; + Lpol[i]->isvisible = false; + } + } + } + // std::cout << "Nombre de polygones visibles: " << nbVisiblePolygon << + // std::endl; + return Lpol[i]->isvisible; +} + +/*! + * Compute the number of visible polygons. + * + * \param width : Image width used to check if the region of interest is inside the image. + * \param height : Image height used to check if the region of interest is inside the image. + * \param cam : Camera parameters. + * \param cMo : The pose of the camera. + * \param angle : Angle used to test the appearance and disappearance of a face. + * \param changed : True if a face appeared, disappeared or too many points have been lost. False otherwise. + * + * \return Return the number of visible polygons + */ +template +unsigned int vpMbHiddenFaces::setVisible(unsigned int width, unsigned int height, + const vpCameraParameters &cam, const vpHomogeneousMatrix &cMo, + const double &angle, bool &changed) +{ + return setVisible(width, height, cam, cMo, angle, angle, changed); +} + +/*! + * Compute the number of visible polygons. + * + * \param width : Image width used to check if the region of interest is inside the image. + * \param height : Image height used to check if the region of interest is inside the image. + * \param cam : Camera parameters. + * \param cMo : The pose of the camera. + * \param changed : True if a face appeared, disappeared or too many points have been lost. False otherwise. + * \param angleAppears : Angle used to test the appearance of a face. + * \param angleDisappears : Angle used to test the disappearance of a face. + * + * \return Return the number of visible polygons + */ +template +unsigned int vpMbHiddenFaces::setVisible(unsigned int width, unsigned int height, + const vpCameraParameters &cam, const vpHomogeneousMatrix &cMo, + const double &angleAppears, const double &angleDisappears, + bool &changed) +{ + return setVisiblePrivate(cMo, angleAppears, angleDisappears, changed, false, true, width, height, cam); +} + +/*! + * Compute the number of visible polygons. + * + * \param cMo : The pose of the camera + * \param angleAppears : Angle used to test the appearance of a face + * \param angleDisappears : Angle used to test the disappearance of a face + * \param changed : True if a face appeared, disappeared or too many points have been lost. False otherwise + * + * \return Return the number of visible polygons + */ +template +unsigned int vpMbHiddenFaces::setVisible(const vpHomogeneousMatrix &cMo, const double &angleAppears, + const double &angleDisappears, bool &changed) +{ + return setVisiblePrivate(cMo, angleAppears, angleDisappears, changed, false); +} + +#ifdef VISP_HAVE_OGRE +/*! + * Initialise the ogre context for face visibility tests. + * + * \param cam : Camera parameters. + */ +template void vpMbHiddenFaces::initOgre(const vpCameraParameters &cam) +{ + ogreInitialised = true; + ogre->setCameraParameters(cam); + ogre->setShowConfigDialog(ogreShowConfigDialog); + ogre->init(ogreBackground, false, true); + + for (unsigned int n = 0; n < Lpol.size(); n++) { + Ogre::ManualObject *manual = ogre->getSceneManager()->createManualObject(Ogre::StringConverter::toString(n)); + + manual->begin("BaseWhiteNoLighting", Ogre::RenderOperation::OT_LINE_STRIP); + for (unsigned int i = 0; i < Lpol[n]->nbpt; i++) { + manual->position((Ogre::Real)Lpol[n]->p[i].get_oX(), (Ogre::Real)Lpol[n]->p[i].get_oY(), + (Ogre::Real)Lpol[n]->p[i].get_oZ()); + manual->colour(1.0, 1.0, 1.0); + manual->index(i); + } + + manual->index(0); + manual->end(); + + ogre->getSceneManager()->getRootSceneNode()->createChildSceneNode()->attachObject(manual); + + lOgrePolygons.push_back(manual); + } +} + +/*! + * Update the display in Ogre Window. + * + * \param cMo : Pose used to display. + */ +template void vpMbHiddenFaces::displayOgre(const vpHomogeneousMatrix &cMo) +{ + if (ogreInitialised && !ogre->isWindowHidden()) { + for (unsigned int i = 0; i < Lpol.size(); i++) { + if (Lpol[i]->isVisible()) { + lOgrePolygons[i]->setVisible(true); + } + else + lOgrePolygons[i]->setVisible(false); + } + ogre->display(ogreBackground, cMo); + } +} + +/*! + * Compute the number of visible polygons through Ogre3D. + * + * \param width : Image width used to check if the region of interest is inside the image. + * \param height : Image height used to check if the region of interest is inside the image. + * \param cam : Camera parameters. + * \param cMo : The pose of the camera. + * \param changed : True if a face appeared, disappeared or too many points have been lost. False otherwise. + * \param angleAppears : Angle used to test the appearance of a face. + * \param angleDisappears : Angle used to test the disappearance of a face. + * + * \return Return the number of visible polygons + */ +template +unsigned int vpMbHiddenFaces::setVisibleOgre(unsigned int width, unsigned int height, + const vpCameraParameters &cam, const vpHomogeneousMatrix &cMo, + const double &angleAppears, const double &angleDisappears, + bool &changed) +{ + return setVisiblePrivate(cMo, angleAppears, angleDisappears, changed, true, true, width, height, cam); +} + +/*! + * Compute the number of visible polygons through Ogre3D. + * + * \param cMo : The pose of the camera + * \param angleAppears : Angle used to test the appearance of a face + * \param angleDisappears : Angle used to test the disappearance of a face + * \param changed : True if a face appeared, disappeared or too many points have been lost. False otherwise + * + * \return Return the number of visible polygons + */ +template +unsigned int vpMbHiddenFaces::setVisibleOgre(const vpHomogeneousMatrix &cMo, const double &angleAppears, + const double &angleDisappears, bool &changed) +{ + return setVisiblePrivate(cMo, angleAppears, angleDisappears, changed, true); +} + +/*! + * Test the visibility of a polygon through Ogre3D via RayCasting. + * + * \param cameraPos : Position of the camera in the 3D world. + * \param index : Index of the polygon. + * + * \return Return true if the polygon is visible, False otherwise. + */ +template +bool vpMbHiddenFaces::isVisibleOgre(const vpTranslationVector &cameraPos, const unsigned int &index) +{ + Ogre::Vector3 camera((Ogre::Real)cameraPos[0], (Ogre::Real)cameraPos[1], (Ogre::Real)cameraPos[2]); + if (!ogre->getCamera()->isVisible(lOgrePolygons[index]->getBoundingBox())) { + lOgrePolygons[index]->setVisible(false); + Lpol[index]->isvisible = false; + return false; + } + + // Get the center of gravity + bool visible = false; + unsigned int nbVisible = 0; + + for (unsigned int i = 0; i < nbRayAttempts; i++) { + Ogre::Vector3 origin(0, 0, 0); + Ogre::Real totalFactor = 0.0f; + + for (unsigned int j = 0; j < Lpol[index]->getNbPoint(); j++) { + Ogre::Real factor = 1.0f; + + if (nbRayAttempts > 1) { + int r = rand() % 101; + + if (r != 0) + factor = ((Ogre::Real)r) / 100.0f; + } + + Ogre::Vector3 tmp((Ogre::Real)Lpol[index]->getPoint(j).get_oX(), (Ogre::Real)Lpol[index]->getPoint(j).get_oY(), + (Ogre::Real)Lpol[index]->getPoint(j).get_oZ()); + tmp *= factor; + origin += tmp; + totalFactor += factor; + } + + origin /= totalFactor; + + Ogre::Vector3 direction = origin - camera; + Ogre::Real distanceCollision = direction.length(); + + direction.normalise(); + Ogre::RaySceneQuery *mRaySceneQuery = ogre->getSceneManager()->createRayQuery(Ogre::Ray(camera, direction)); + mRaySceneQuery->setSortByDistance(true); + + Ogre::RaySceneQueryResult &result = mRaySceneQuery->execute(); + Ogre::RaySceneQueryResult::iterator it = result.begin(); + + // while(it != result.end()){ + // std::cout << it->movable->getName() << "(" << it->distance<< ") : + // " << std::flush; it++; + // } + // std::cout << std::endl; + // it = result.begin(); + + if (it != result.end()) + if (it->movable->getName().find("SimpleRenderable") != Ogre::String::npos) // Test if the ogreBackground is + // intersect in first + ++it; + + double distance; + // In a case of a two-axis aligned segment, ray collision is not always + // working. + if (Lpol[index]->getNbPoint() == 2 && + (((std::fabs(Lpol[index]->getPoint(0).get_oX() - Lpol[index]->getPoint(1).get_oX()) < + std::numeric_limits::epsilon()) + + (std::fabs(Lpol[index]->getPoint(0).get_oY() - Lpol[index]->getPoint(1).get_oY()) < + std::numeric_limits::epsilon()) + + (std::fabs(Lpol[index]->getPoint(0).get_oZ() - Lpol[index]->getPoint(1).get_oZ()) < + std::numeric_limits::epsilon())) >= 2)) { + if (it != result.end()) { + if (it->movable->getName() == Ogre::StringConverter::toString(index)) { + nbVisible++; + } + else { + distance = it->distance; + // Cannot use epsilon for comparison as ray length is slightly + // different from the collision distance returned by + // Ogre::RaySceneQueryResult. + if (distance > distanceCollision || std::fabs(distance - distanceCollision) < + 1e-6 /*std::fabs(distance) * std::numeric_limits::epsilon()*/) + nbVisible++; + } + } + else + nbVisible++; // Collision not detected but present. + } + else { + if (it != result.end()) { + distance = it->distance; + double distancePrev = distance; + + // std::cout << "For " << Ogre::StringConverter::toString(index) << ": + // " << it->movable->getName() << " / " << std::flush; + + if (it->movable->getName() == Ogre::StringConverter::toString(index)) { + nbVisible++; + } + else { + ++it; + while (it != result.end()) { + distance = it->distance; + + if (std::fabs(distance - distancePrev) < + 1e-6 /*std::fabs(distance) * std::numeric_limits::epsilon()*/) { + // std::cout << it->movable->getName() << " / " << std::flush; + if (it->movable->getName() == Ogre::StringConverter::toString(index)) { + nbVisible++; + break; + } + ++it; + distancePrev = distance; + } + else + break; + } + } + } + } + + ogre->getSceneManager()->destroyQuery(mRaySceneQuery); + } + + if ((static_cast(nbVisible)) / (static_cast(nbRayAttempts)) > ratioVisibleRay || + std::fabs((static_cast(nbVisible)) / (static_cast(nbRayAttempts)) - ratioVisibleRay) < + ratioVisibleRay * std::numeric_limits::epsilon()) + visible = true; + else + visible = false; + + if (visible) { + lOgrePolygons[index]->setVisible(true); + Lpol[index]->isvisible = true; + } + else { + lOgrePolygons[index]->setVisible(false); + Lpol[index]->isvisible = false; + } + + return Lpol[index]->isvisible; +} + +#endif // VISP_HAVE_OGRE +END_VISP_NAMESPACE +#endif // vpMbHiddenFaces diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbKltTracker.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbKltTracker.h new file mode 100644 index 00000000..ad85cdd2 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbKltTracker.h @@ -0,0 +1,503 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Model based tracker using only KLT + */ + +/*! + * \file vpMbKltTracker.h + * \brief Model based tracker using only KLT + */ + +#ifndef _vpMbKltTracker_h_ +#define _vpMbKltTracker_h_ + +#include + +#if defined(VISP_HAVE_MODULE_KLT) && defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) && defined(HAVE_OPENCV_VIDEO) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpMbKltTracker + * \ingroup group_mbt_trackers + * \warning This class is deprecated for user usage. You should rather use the high level + * vpMbGenericTracker class. + * \warning This class is only available if OpenCV is installed, and used. + * + * \brief Model based tracker using only KLT. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using a MBT tracker in your applications, you may have a look at:
+ * + * - \ref tutorial-tracking-mb-generic + * - \ref tutorial-tracking-mb-generic-stereo + * - \ref tutorial-tracking-mb-generic-rgbd + * - \ref tutorial-tracking-mb-generic-apriltag-live + * - \ref tutorial-mb-generic-json + * - \ref tutorial-tracking-mb-generic-rgbd-Blender + * + * Derpecated tutorials
+ * The \ref tutorial-tracking-mb-deprecated is a good starting point to use this class. + * + * The tracker requires the knowledge of the 3D model that could be provided in + * a vrml or in a cao file. The cao format is described in loadCAOModel(). It may + * also use an xml file used to tune the behavior of the tracker and an init file + * used to compute the pose at the very first image. + * + * The following code shows the simplest way to use the tracker. The \ref + * tutorial-tracking-mb-deprecated is also a good starting point to use this class. + * + * \code + * #include + * #include + * #include + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * #if defined VISP_HAVE_OPENCV + * vpMbKltTracker tracker; // Create a model based tracker via KLT points. + * vpImage I; + * vpHomogeneousMatrix cMo; // Pose computed using the tracker. + * vpCameraParameters cam; + * + * // Acquire an image + * vpImageIo::read(I, "cube.pgm"); + * + * #if defined(VISP_HAVE_X11) + * vpDisplayX display; + * display.init(I,100,100,"Mb Klt Tracker"); + * #endif + * + * tracker.loadConfigFile("cube.xml"); // Load the configuration of the tracker + * tracker.getCameraParameters(cam); // Get the camera parameters used by the tracker (from the configuration file). + * tracker.loadModel("cube.cao"); // Load the 3d model in cao format. No 3rd party library is required + * // Initialise manually the pose by clicking on the image points associated to the 3d points contained in the + * // cube.init file. + * tracker.initClick(I, "cube.init"); + * + * while(true){ + * // Acquire a new image + * vpDisplay::display(I); + * tracker.track(I); // Track the object on this image + * tracker.getPose(cMo); // Get the pose + * + * tracker.display(I, cMo, cam, vpColor::darkRed, 1); // Display the model at the computed pose. + * vpDisplay::flush(I); + * } + * + * return 0; + * #endif + * } + * \endcode + * + * The tracker can also be used without display, in that case the initial pose + * must be known (object always at the same initial pose for example) or + * computed using another method: + * + * \code + * #include + * #include + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * #if defined VISP_HAVE_OPENCV + * vpMbKltTracker tracker; // Create a model based tracker via Klt Points. + * vpImage I; + * vpHomogeneousMatrix cMo; // Pose used in entry (has to be defined), then computed using the tracker. + * + * //acquire an image + * vpImageIo::read(I, "cube.pgm"); // Example of acquisition + * + * tracker.loadConfigFile("cube.xml"); // Load the configuration of the tracker + * // load the 3d model, to read .wrl model coin is required, if coin is not installed .cao file can be used. + * tracker.loadModel("cube.cao"); + * tracker.initFromPose(I, cMo); // initialize the tracker with the given pose. + * + * while(true){ + * // acquire a new image + * tracker.track(I); // track the object on this image + * tracker.getPose(cMo); // get the pose + * } + * + * return 0; + * #endif + * } + * \endcode + * + * Finally it can be used not to track an object but just to display a model at + * a given pose: + * + * \code + * #include + * #include + * #include + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * #if defined VISP_HAVE_OPENCV + * vpMbKltTracker tracker; // Create a model based tracker via Klt Points. + * vpImage I; + * vpHomogeneousMatrix cMo; // Pose used to display the model. + * vpCameraParameters cam; + * + * // Acquire an image + * vpImageIo::read(I, "cube.pgm"); + * + * #if defined(VISP_HAVE_X11) + * vpDisplayX display; + * display.init(I,100,100,"Mb Klt Tracker"); + * #endif + * + * tracker.loadConfigFile("cube.xml"); // Load the configuration of the tracker + * tracker.getCameraParameters(cam); // Get the camera parameters used by the tracker (from the configuration file). + * // load the 3d model, to read .wrl model coin is required, if coin is not installed .cao file can be used. + * tracker.loadModel("cube.cao"); + * + * while(true){ + * // acquire a new image + * // Get the pose using any method + * vpDisplay::display(I); + * tracker.display(I, cMo, cam, vpColor::darkRed, 1, true); // Display the model at the computed pose. + * vpDisplay::flush(I); + * } + * + * return 0; + * #endif + * } + * \endcode +*/ +class VISP_EXPORT vpMbKltTracker : public virtual vpMbTracker +{ +protected: + //! Temporary OpenCV image for fast conversion. + cv::Mat cur; + //! Initial pose. + vpHomogeneousMatrix c0Mo; + //! Flag to specify whether the init method is called the first or not + //! (specific calls to realize in this case). + bool firstInitialisation; + //! Erosion of the mask + unsigned int maskBorder; + //! Threshold below which the weight associated to a point to consider this + //! one as an outlier. + double threshold_outlier; + //! Percentage of good points, according to the initial number, that must + //! have the tracker. + double percentGood; + //! The estimated displacement of the pose between the current instant and + //! the initial position. + vpHomogeneousMatrix ctTc0; + //! Points tracker. + vpKltOpencv tracker; + //! + std::list kltPolygons; + //! + std::list kltCylinders; + //! Vector of the circles used here only to display the full model. + std::list circles_disp; + //! + unsigned int m_nbInfos; + //! + unsigned int m_nbFaceUsed; + //! Interaction matrix + vpMatrix m_L_klt; + //! (s - s*) + vpColVector m_error_klt; + //! Robust weights + vpColVector m_w_klt; + //! Weighted error + vpColVector m_weightedError_klt; + //! Robust + vpRobust m_robust_klt; + //! Display features + std::vector > m_featuresToBeDisplayedKlt; + +public: + vpMbKltTracker(); + virtual ~vpMbKltTracker(); + + /** @name Inherited functionalities from vpMbKltTracker */ + //@{ + + void addCircle(const vpPoint &p1, const vpPoint &p2, const vpPoint &p3, double radius, const std::string &name = ""); + virtual void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false) VP_OVERRIDE; + virtual void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false) VP_OVERRIDE; + + /*! Return the address of the circle feature list. */ + virtual std::list &getFeaturesCircle() { return circles_disp; } + /*! Return the address of the cylinder feature list. */ + virtual std::list &getFeaturesKltCylinder() { return kltCylinders; } + /*! Return the address of the Klt feature list. */ + virtual std::list &getFeaturesKlt() { return kltPolygons; } + + /*! + * Get the current list of KLT points. + * + * \return the list of KLT points through vpKltOpencv. + */ + inline std::vector getKltPoints() const { return tracker.getFeatures(); } + + std::vector getKltImagePoints() const; + + std::map getKltImagePointsWithId() const; + + /*! + * Get the klt tracker at the current state. + * + * \return klt tracker. + */ + inline vpKltOpencv getKltOpencv() const { return tracker; } + + /*! + * Get the erosion of the mask used on the Model faces. + * + * \return The erosion. + */ + inline unsigned int getKltMaskBorder() const { return maskBorder; } + + /*! + * Get the current number of klt points. + * + * \return the number of features + */ + inline int getKltNbPoints() const { return tracker.getNbFeatures(); } + + /*! + * Get the threshold for the acceptation of a point. + * + * \return threshold_outlier : Threshold for the weight below which a point + * is rejected. + */ + inline double getKltThresholdAcceptation() const { return threshold_outlier; } + + virtual inline vpColVector getError() const VP_OVERRIDE { return m_error_klt; } + + virtual inline vpColVector getRobustWeights() const VP_OVERRIDE { return m_w_klt; } + + virtual std::vector > getModelForDisplay(unsigned int width, unsigned int height, + const vpHomogeneousMatrix &cMo, + const vpCameraParameters &cam, + bool displayFullModel = false) VP_OVERRIDE; + + virtual void loadConfigFile(const std::string &configFile, bool verbose = true) VP_OVERRIDE; + + virtual void reInitModel(const vpImage &I, const std::string &cad_name, const vpHomogeneousMatrix &cMo, + bool verbose = false, const vpHomogeneousMatrix &T = vpHomogeneousMatrix()); + void resetTracker() VP_OVERRIDE; + + void setCameraParameters(const vpCameraParameters &cam) VP_OVERRIDE; + + /*! + * Set the erosion of the mask used on the Model faces. + * + * \param e : The desired erosion. + */ + inline void setKltMaskBorder(const unsigned int &e) + { + maskBorder = e; + // if(useScanLine) + faces.getMbScanLineRenderer().setMaskBorder(maskBorder); + } + + virtual void setKltOpencv(const vpKltOpencv &t); + + /*! + * Set the threshold for the acceptation of a point. + * + * \param th : Threshold for the weight below which a point is rejected. + */ + inline void setKltThresholdAcceptation(double th) { threshold_outlier = th; } + + /*! + * Use Ogre3D for visibility tests + * + * \warning This function has to be called before the initialization of the + * tracker. + * + * \param v : True to use it, False otherwise + */ + virtual void setOgreVisibilityTest(const bool &v) VP_OVERRIDE + { + vpMbTracker::setOgreVisibilityTest(v); +#ifdef VISP_HAVE_OGRE + faces.getOgreContext()->setWindowName("MBT Klt"); +#endif + } + + /*! + * Use Scanline algorithm for visibility tests + * + * \param v : True to use it, False otherwise + */ + virtual void setScanLineVisibilityTest(const bool &v) VP_OVERRIDE + { + vpMbTracker::setScanLineVisibilityTest(v); + + for (std::list::const_iterator it = kltPolygons.begin(); it != kltPolygons.end(); ++it) + (*it)->useScanLine = v; + } + + virtual void setPose(const vpImage &I, const vpHomogeneousMatrix &cdMo) VP_OVERRIDE; + virtual void setPose(const vpImage &I_color, const vpHomogeneousMatrix &cdMo) VP_OVERRIDE; + + /*! + * Set if the projection error criteria has to be computed. + * + * \param flag : True if the projection error criteria has to be computed, + * false otherwise + */ + virtual void setProjectionErrorComputation(const bool &flag) VP_OVERRIDE + { + if (flag) + std::cerr << "This option is not yet implemented in vpMbKltTracker, " + "projection error computation set to false." + << std::endl; + } + + void setUseKltTracking(const std::string &name, const bool &useKltTracking); + + virtual void testTracking() VP_OVERRIDE; + virtual void track(const vpImage &I) VP_OVERRIDE; + virtual void track(const vpImage &I_color) VP_OVERRIDE; + + /*! + @name Deprecated functions + */ + //@{ + + /*! + * Get the erosion of the mask used on the Model faces. + * \deprecated Use rather getkltMaskBorder() + * + * \return The erosion. + */ + /* VP_DEPRECATED */ inline unsigned int getMaskBorder() const { return maskBorder; } + + /*! + * Get the current number of klt points. + * \deprecated Use rather getKltNbPoints() + * + * \return the number of features + */ + /* VP_DEPRECATED */ inline int getNbKltPoints() const { return tracker.getNbFeatures(); } + + /*! + * Get the threshold for the acceptation of a point. + * \deprecated Use rather getKltThresholdAcceptation() + * + * \return threshold_outlier : Threshold for the weight below which a point + * is rejected. + */ + /* VP_DEPRECATED */ inline double getThresholdAcceptation() const { return threshold_outlier; } + + /*! + * Set the erosion of the mask used on the Model faces. + * + * \param e : The desired erosion. + */ + /* VP_DEPRECATED */ inline void setMaskBorder(const unsigned int &e) + { + maskBorder = e; + // if(useScanLine) + faces.getMbScanLineRenderer().setMaskBorder(maskBorder); + } + + /*! + * Set the threshold for the acceptation of a point. + * \deprecated Use rather setKltThresholdAcceptation() + * + * \param th : Threshold for the weight below which a point is rejected. + */ + /* VP_DEPRECATED */ inline void setThresholdAcceptation(double th) { threshold_outlier = th; } + + //@} + +protected: + /** @name Protected Member Functions Inherited from vpMbKltTracker */ + //@{ + void computeVVS(); + virtual void computeVVSInit() VP_OVERRIDE; + virtual void computeVVSInteractionMatrixAndResidu() VP_OVERRIDE; + + virtual std::vector > getFeaturesForDisplayKlt(); + + virtual void init(const vpImage &I) VP_OVERRIDE; + virtual void initFaceFromCorners(vpMbtPolygon &polygon) VP_OVERRIDE; + virtual void initFaceFromLines(vpMbtPolygon &polygon) VP_OVERRIDE; + virtual void initCircle(const vpPoint &, const vpPoint &, const vpPoint &, double, int, const std::string &name = "") VP_OVERRIDE; + virtual void initCylinder(const vpPoint &, const vpPoint &, double, int, const std::string &name = "") VP_OVERRIDE; + + void preTracking(const vpImage &I); + bool postTracking(const vpImage &I, vpColVector &w); + virtual void reinit(const vpImage &I); + virtual void setPose(const vpImage *I, const vpImage *I_color, + const vpHomogeneousMatrix &cdMo); + //@} +}; +END_VISP_NAMESPACE +#endif +#endif // VISP_HAVE_OPENCV diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbScanLine.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbScanLine.h new file mode 100644 index 00000000..f90af8cd --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbScanLine.h @@ -0,0 +1,215 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Compute the visibility of 3D polygons already transformed in the camera frame. + */ + +#ifndef VP_MB_SCANLINE_H +#define VP_MB_SCANLINE_H + +#include +#include // numeric_limits +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +//#define DEBUG_DISP // Uncomment to get visibility debug display + +#if defined(DEBUG_DISP) +#include +#endif + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +BEGIN_VISP_NAMESPACE + +/*! + \class vpMbScanLine + + \ingroup group_mbt_faces + +

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using a MBT tracker in your applications, you may have a look at:
+ * + * - \ref tutorial-tracking-mb-generic + * - \ref tutorial-tracking-mb-generic-stereo + * - \ref tutorial-tracking-mb-generic-rgbd + * - \ref tutorial-tracking-mb-generic-apriltag-live + * - \ref tutorial-mb-generic-json + * - \ref tutorial-tracking-mb-generic-rgbd-Blender +*/ +class VISP_EXPORT vpMbScanLine +{ +public: + //! Structure to define if a scanline intersection is a starting or an + //! ending point of a polygon, or just a single line intersection. + typedef enum { START = 1, END = 0, POINT = 2 } vpMbScanLineType; + + //! Structure to define a scanline edge (basically a pair of (X,Y,Z) + //! vectors). + typedef std::pair vpMbScanLineEdge; + + //! Structure to define a scanline intersection. + struct vpMbScanLineSegment + { + vpMbScanLineSegment() : type(START), edge(), p(0), P1(0), P2(0), Z1(0), Z2(0), ID(0), b_sample_Y(false) { } + vpMbScanLineType type; + vpMbScanLineEdge edge; + double p; // This value can be either x or y-coordinate value depending if + // the structure is used in X or Y-axis scanlines computation. + double P1, P2; // Same comment as previous value. + double Z1, Z2; + int ID; + bool b_sample_Y; + }; + + //! vpMbScanLineEdge Comparator. + struct vpMbScanLineEdgeComparator + { + inline bool operator()(const vpMbScanLineEdge &l0, const vpMbScanLineEdge &l1) const + { + for (unsigned int i = 0; i < 3; ++i) + if (l0.first[i] < l1.first[i]) + return true; + else if (l0.first[i] > l1.first[i]) + return false; + for (unsigned int i = 0; i < 3; ++i) + if (l0.second[i] < l1.second[i]) + return true; + else if (l0.second[i] > l1.second[i]) + return false; + return false; + } + }; + + //! vpMbScanLineSegment Comparators. + struct vpMbScanLineSegmentComparator + { + inline bool operator()(const vpMbScanLineSegment &a, const vpMbScanLineSegment &b) const + { + // return a.p == b.p ? a.type < b.type : a.p < b.p; + return (std::fabs(a.p - b.p) <= std::numeric_limits::epsilon()) ? a.type < b.type : a.p < b.p; + } + + inline bool operator()(const std::pair &a, + const std::pair &b) const + { + return a.first < b.first; + } + }; + +private: + unsigned int w, h; + vpCameraParameters K; + unsigned int maskBorder; + vpImage mask; + vpImage primitive_ids; + std::map, vpMbScanLineEdgeComparator> visibility_samples; + double depthTreshold; + +public: +#if (defined(VISP_HAVE_X11) || defined(VISP_HAVE_GDI)) && defined(DEBUG_DISP) + vpDisplay *dispMaskDebug; + vpDisplay *dispLineDebug; + vpImage linedebugImg; +#endif + + vpMbScanLine(); + vpMbScanLine(const vpMbScanLine &scanline); + virtual ~vpMbScanLine(); + vpMbScanLine &operator=(const vpMbScanLine &scanline); + + void drawScene(const std::vector > *> &polygons, + std::vector listPolyIndices, const vpCameraParameters &K, unsigned int w, unsigned int h); + + /*! + If there is one polygon behind another, + this threshold defines the minimum distance between both polygons to still + consider the one behind as visible. + + \return Current Threshold. + */ + double getDepthTreshold() { return depthTreshold; } + unsigned int getMaskBorder() { return maskBorder; } + const vpImage &getMask() const { return mask; } + const vpImage &getPrimitiveIDs() const { return primitive_ids; } + + void queryLineVisibility(const vpPoint &a, const vpPoint &b, std::vector > &lines, + const bool &displayResults = false); + + /*! + If there is one polygon behind another, + this threshold defines the minimum distance between both polygons to still + consider the one behind as visible. + + \param treshold : New Threshold. + */ + void setDepthTreshold(const double &treshold) { depthTreshold = treshold; } + void setMaskBorder(const unsigned int &mb) { maskBorder = mb; } + +private: + void createScanLinesFromLocals(std::vector > &scanlines, + std::vector > &localScanlines, + const unsigned int &size); + + void drawLineY(const vpColVector &a, const vpColVector &b, const vpMbScanLineEdge &line_ID, const int ID, + std::vector > &scanlines); + + void drawLineX(const vpColVector &a, const vpColVector &b, const vpMbScanLineEdge &line_ID, const int ID, + std::vector > &scanlines); + + void drawPolygonY(const std::vector > &polygon, const int ID, + std::vector > &scanlines); + + void drawPolygonX(const std::vector > &polygon, const int ID, + std::vector > &scanlines); + + // Static functions + static vpMbScanLineEdge makeMbScanLineEdge(const vpPoint &a, const vpPoint &b); + static void createVectorFromPoint(const vpPoint &p, vpColVector &v, const vpCameraParameters &K); + static double getAlpha(double x, double X0, double Z0, double X1, double Z1); + static double mix(double a, double b, double alpha); + static vpPoint mix(const vpPoint &a, const vpPoint &b, double alpha); + static double norm(const vpPoint &a, const vpPoint &b); +}; +END_VISP_NAMESPACE +#endif // doxygen should skip this + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbTracker.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbTracker.h new file mode 100644 index 00000000..2f5e012f --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbTracker.h @@ -0,0 +1,932 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Generic model based tracker. This class declares the methods to implement + * in order to have a model based tracker. + */ + +/*! + \file vpMbTracker.h + \brief Generic model based tracker. +*/ +#ifndef vpMbTracker_hh +#define vpMbTracker_hh + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef VISP_HAVE_COIN3D +// Work around to avoid type redefinition int8_t with Coin +// #if defined(_WIN32) && defined(VISP_HAVE_OGRE) && (_MSC_VER >= 1600) // +// Visual Studio 2010 +// #define HAVE_INT8_T 1 +// #endif + +// Inventor includes +#include +#include +#include +#endif + +BEGIN_VISP_NAMESPACE +/*! + \class vpMbTracker + \ingroup group_mbt_trackers + \brief Main methods for a model-based tracker. + + This class provides the main methods for a model based tracker. This pure + virtual class must be used in inheritance for a tracker that compute the + interaction matrix and the residu vector using a defined information (edge, + points of interest, patch, ...) + + This class intends to define a common basis for object tracking. This is + realised by implementing the main functions: + - init() : Initialisation of the tracker (it includes re-initialisation). + This method is called at the end of the initClick() method. + - initFaceFromCorners() : Initialisation of the lines that has to be tracked. + - track() : Tracking on the current image + - testTracking() : Test the tracking. This method throws exception if the tracking failed. + - display() : Display the model and eventually other information. + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in using a MBT tracker in your applications, you may have a look at:
+ + - \ref tutorial-tracking-mb-generic + - \ref tutorial-tracking-mb-generic-stereo + - \ref tutorial-tracking-mb-generic-rgbd + - \ref tutorial-tracking-mb-generic-apriltag-live + - \ref tutorial-mb-generic-json + - \ref tutorial-tracking-mb-generic-rgbd-Blender +*/ +class VISP_EXPORT vpMbTracker +{ +public: + typedef enum { GAUSS_NEWTON_OPT = 0, LEVENBERG_MARQUARDT_OPT = 1 } vpMbtOptimizationMethod; + +protected: + //! The camera parameters. + vpCameraParameters m_cam; + //! The current pose. + vpHomogeneousMatrix m_cMo; + //! The Degrees of Freedom to estimate + vpMatrix oJo; + //! Boolean to know if oJo is identity (for fast computation) + bool m_isoJoIdentity; + //! The name of the file containing the model (it is used to create a file + //! name.0.pos used to store the compute pose in the initClick method). + std::string modelFileName; + //! Flag used to ensure that the CAD model is loaded before the + //! initialisation. + bool modelInitialised; + //! Filename used to save the initial pose computed using the initClick() + //! method. It is also used to read a previous pose in the same method. + std::string poseSavingFilename; + //! Flag used to specify if the covariance matrix has to be computed or not. + bool computeCovariance; + //! Covariance matrix + vpMatrix covarianceMatrix; + //! Flag used to specify if the gradient error criteria has to be computed + //! or not. + bool computeProjError; + //! Error angle between the gradient direction of the model features + //! projected at the resulting pose and their normal. + double projectionError; + //! If true, the features are displayed. + bool displayFeatures; + //! Optimization method used + vpMbtOptimizationMethod m_optimizationMethod; + + //! Set of faces describing the object. + vpMbHiddenFaces faces; + //! Angle used to detect a face appearance + double angleAppears; + //! Angle used to detect a face disappearance + double angleDisappears; + //! Distance for near clipping + double distNearClip; + //! Distance for near clipping + double distFarClip; + //! Flags specifying which clipping to used + unsigned int clippingFlag; + //! Use Ogre3d for global visibility tests + bool useOgre; + bool ogreShowConfigDialog; + //! Use Scanline for global visibility tests + bool useScanLine; + //! Number of points in init file + unsigned int m_nbInitPoints; + //! Max allowed number of points in init file + unsigned int m_maxInitPoints; + //! Number of points in CAO model + unsigned int nbPoints; + //! Number of lines in CAO model + unsigned int nbLines; + //! Number of polygon lines in CAO model + unsigned int nbPolygonLines; + //! Number of polygon points in CAO model + unsigned int nbPolygonPoints; + //! Number of cylinders in CAO model + unsigned int nbCylinders; + //! Number of circles in CAO model + unsigned int nbCircles; + //! True if LOD mode is enabled + bool useLodGeneral; + //! True if the CAO model is loaded before the call to loadConfigFile, + //! (deduced by the number of polygons) + bool applyLodSettingInConfig; + //! Minimum line length threshold for LOD mode (general setting) + double minLineLengthThresholdGeneral; + //! Minimum polygon area threshold for LOD mode (general setting) + double minPolygonAreaThresholdGeneral; + //! Map with [map.first]=parameter_names and [map.second]=type (string, + //! number or boolean) + std::map mapOfParameterNames; + //! If true, compute the interaction matrix at each iteration of the + //! minimization. Otherwise, compute it only on the first iteration + bool m_computeInteraction; + //! Gain of the virtual visual servoing stage + double m_lambda; + //! Maximum number of iterations of the virtual visual servoing stage + unsigned int m_maxIter; + //! Epsilon threshold to stop the VVS optimization loop + double m_stopCriteriaEpsilon; + //! Initial Mu for Levenberg Marquardt optimization loop + double m_initialMu; + + //! Distance line primitives for projection error + std::vector m_projectionErrorLines; + //! Distance cylinder primitives for projection error + std::vector m_projectionErrorCylinders; + //! Distance circle primitive for projection error + std::vector m_projectionErrorCircles; + //! Set of faces describing the object, used for projection error + vpMbHiddenFaces m_projectionErrorFaces; + bool m_projectionErrorOgreShowConfigDialog; + //! Moving-Edges parameters for projection error + vpMe m_projectionErrorMe; + //! Kernel size used to compute the gradient orientation + unsigned int m_projectionErrorKernelSize; + //! Sobel kernel in X + vpMatrix m_SobelX; + //! Sobel kernel in Y + vpMatrix m_SobelY; + //! Display gradient and model orientation for projection error computation + bool m_projectionErrorDisplay; + //! Length of the arrows used to show the gradient and model orientation + unsigned int m_projectionErrorDisplayLength; + //! Thickness of the arrows used to show the gradient and model orientation + unsigned int m_projectionErrorDisplayThickness; + //! Camera parameters used for projection error computation + vpCameraParameters m_projectionErrorCam; + //! Mask used to disable tracking on a part of image + const vpImage *m_mask; + //! Grayscale image buffer, used when passing color images + vpImage m_I; + //! Flag that indicates that SoDB::init(); was called + bool m_sodb_init_called; + //! Random number generator used in vpMbtDistanceLine::buildFrom() + vpUniRand m_rand; + +public: + vpMbTracker(); + vpMbTracker(const vpMbTracker &tracker); + virtual ~vpMbTracker(); + vpMbTracker &operator=(const vpMbTracker &tracker); + + /** @name Inherited functionalities from vpMbTracker */ + virtual double computeCurrentProjectionError(const vpImage &I, const vpHomogeneousMatrix &_cMo, + const vpCameraParameters &_cam); + + /*! Return the angle used to test polygons appearance. */ + virtual inline double getAngleAppear() const { return angleAppears; } + + /*! Return the angle used to test polygons disappearance. */ + virtual inline double getAngleDisappear() const { return angleDisappears; } + + /*! + Get the camera parameters. + + \param cam : copy of the camera parameters used by the tracker. + */ + virtual void getCameraParameters(vpCameraParameters &cam) const { cam = m_cam; } + + /*! + Get the clipping used and defined in + vpPolygon3D::vpMbtPolygonClippingType. + + \return Clipping flags. + */ + virtual inline unsigned int getClipping() const { return clippingFlag; } + + /*! + Get the covariance matrix. This matrix is only computed if + setCovarianceComputation() is turned on. + + \sa setCovarianceComputation() + */ + + virtual vpMatrix getCovarianceMatrix() const + { + if (!computeCovariance) { + // vpTRACE("Warning : The covariance matrix has not been computed. + // See setCovarianceComputation() to do it."); + std::cerr << "Warning : The covariance matrix has not been computed. " + "See setCovarianceComputation() to do it." + << std::endl; + } + + return covarianceMatrix; + } + + /*! + Get the initial value of mu used in the Levenberg Marquardt optimization + loop. + + \return the initial mu value. + */ + virtual inline double getInitialMu() const { return m_initialMu; } + + /*! + Get the value of the gain used to compute the control law. + + \return the value for the gain. + */ + virtual inline double getLambda() const { return m_lambda; } + + /*! + Get the maximum number of iterations of the virtual visual servoing stage. + + \return the number of iteration + */ + virtual inline unsigned int getMaxIter() const { return m_maxIter; } + + /*! + Get the error angle between the gradient direction of the model features + projected at the resulting pose and their normal. The error is expressed + in degree between 0 and 90. This value is computed if + setProjectionErrorComputation() is turned on. + + \return the value for the error. + + \sa setProjectionErrorComputation() + */ + virtual double getProjectionError() const { return projectionError; } + + virtual vpColVector getEstimatedDoF() const; + + /*! + Return the error vector \f$(s-s^*)\f$ reached after the virtual visual + servoing process used to estimate the pose. + + The following example shows how to use this function to compute the norm + of the residual and the norm of the residual normalized by the number of + features that are tracked: + \code + tracker.track(I); std::cout << "Residual: " << sqrt( (tracker.getError()).sumSquare()) << std::endl; + std::cout << "Residual normalized: " + << sqrt( (tracker.getError()).sumSquare())/tracker.getError().size() << std::endl; + \endcode + + \sa getRobustWeights() + */ + virtual vpColVector getError() const = 0; + + /*! Return a reference to the faces structure. */ + virtual inline vpMbHiddenFaces &getFaces() { return faces; } + + /*! + Get the far distance for clipping. + + \return Far clipping value. + */ + virtual inline double getFarClippingDistance() const { return distFarClip; } + + /*! + Return the weights vector \f$w_i\f$ computed by the robust scheme. + + The following example shows how to use this function to compute the norm + of the weighted residual and the norm of the weighted residual normalized + by the sum of the weights associated to the features that are tracked: + \code + tracker.track(I); + vpColVector w = tracker.getRobustWeights(); + vpColVector e = tracker.getError(); + vpColVector we(w.size()); + for(unsigned int i=0; i(faces.size()); } + + /*! + Get the near distance for clipping. + + \return Near clipping value. + */ + virtual inline double getNearClippingDistance() const { return distNearClip; } + + /*! + Get the optimization method used during the tracking. + 0 = Gauss-Newton approach. + 1 = Levenberg-Marquardt approach. + + \return Optimization method. + */ + virtual inline vpMbtOptimizationMethod getOptimizationMethod() const { return m_optimizationMethod; } + + /*! + Return the polygon (face) "index". + + \exception vpException::dimensionError if index does not represent a good + polygon. + + \param index : Index of the polygon to return. + \return Pointer to the polygon index. + */ + virtual inline vpMbtPolygon *getPolygon(unsigned int index) + { + if (index >= static_cast(faces.size())) { + throw vpException(vpException::dimensionError, "index out of range"); + } + + return faces[index]; + } + + virtual std::pair, std::vector > > + getPolygonFaces(bool orderPolygons = true, bool useVisibility = true, bool clipPolygon = false); + + /*! + Get the current pose between the object and the camera. + cMo is the matrix which can be used to express + coordinates from the object frame to camera frame. + + \param cMo : the pose + */ + virtual inline void getPose(vpHomogeneousMatrix &cMo) const { cMo = m_cMo; } + + /*! + Get the current pose between the object and the camera. + cMo is the matrix which can be used to express + coordinates from the object frame to camera frame. + + \return the current pose + */ + virtual inline vpHomogeneousMatrix getPose() const { return m_cMo; } + + virtual inline double getStopCriteriaEpsilon() const { return m_stopCriteriaEpsilon; } + + // initializer + +#ifdef VISP_HAVE_MODULE_GUI + virtual void initClick(const vpImage &I, const std::string &initFile, bool displayHelp = false, + const vpHomogeneousMatrix &T = vpHomogeneousMatrix()); + virtual void initClick(const vpImage &I_color, const std::string &initFile, bool displayHelp = false, + const vpHomogeneousMatrix &T = vpHomogeneousMatrix()); + + virtual void initClick(const vpImage &I, const std::vector &points3D_list, + const std::string &displayFile = ""); + virtual void initClick(const vpImage &I_color, const std::vector &points3D_list, + const std::string &displayFile = ""); +#endif + + virtual void initFromPoints(const vpImage &I, const std::string &initFile); + virtual void initFromPoints(const vpImage &I_color, const std::string &initFile); + + virtual void initFromPoints(const vpImage &I, const std::vector &points2D_list, + const std::vector &points3D_list); + virtual void initFromPoints(const vpImage &I_color, const std::vector &points2D_list, + const std::vector &points3D_list); + + virtual void initFromPose(const vpImage &I, const std::string &initFile); + virtual void initFromPose(const vpImage &I_color, const std::string &initFile); + + virtual void initFromPose(const vpImage &I, const vpHomogeneousMatrix &cMo); + virtual void initFromPose(const vpImage &I_color, const vpHomogeneousMatrix &cMo); + + virtual void initFromPose(const vpImage &I, const vpPoseVector &cPo); + virtual void initFromPose(const vpImage &I_color, const vpPoseVector &cPo); + + virtual void loadModel(const std::string &modelFile, bool verbose = false, + const vpHomogeneousMatrix &od_M_o = vpHomogeneousMatrix()); + + /*! + Set the angle used to test polygons appearance. + If the angle between the normal of the polygon and the line going + from the camera to the polygon center has a value lower than + this parameter, the polygon is considered as appearing. + The polygon will then be tracked. + + \param a : new angle in radian. + */ + virtual inline void setAngleAppear(const double &a) { angleAppears = a; } + + /*! + Set the angle used to test polygons disappearance. + If the angle between the normal of the polygon and the line going + from the camera to the polygon center has a value greater than + this parameter, the polygon is considered as disappearing. + The tracking of the polygon will then be stopped. + + \param a : new angle in radian. + */ + virtual inline void setAngleDisappear(const double &a) { angleDisappears = a; } + + /*! + Set the camera parameters. + + \param cam : The new camera parameters. + */ + virtual void setCameraParameters(const vpCameraParameters &cam) { m_cam = cam; } + + virtual void setClipping(const unsigned int &flags); + + /*! + Set if the covariance matrix has to be computed. + + \param flag : True if the covariance has to be computed, false otherwise. + If computed its value is available with getCovarianceMatrix() + + \sa getCovarianceMatrix() + */ + virtual void setCovarianceComputation(const bool &flag) { computeCovariance = flag; } + + /*! + Enable to display the features. By features, we meant the moving edges + (ME) and the klt points if used. + + Note that if present, the moving edges can be displayed with different + colors: + - If green : The ME is a good point. + - If blue : The ME is removed because of a contrast problem during the + tracking phase. + - If purple : The ME is removed because of a threshold problem during the + tracking phase. + - If red : The ME is removed because it is rejected by the robust approach + in the virtual visual servoing scheme. + + \param displayF : set it to true to display the features. + */ + virtual void setDisplayFeatures(bool displayF) { displayFeatures = displayF; } + + virtual void setEstimatedDoF(const vpColVector &v); + + virtual void setFarClippingDistance(const double &dist); + + /*! + Set the initial value of mu for the Levenberg Marquardt optimization loop. + + \param mu : initial mu. + */ + virtual inline void setInitialMu(double mu) { m_initialMu = mu; } + + /*! + Set the value of the gain used to compute the control law. + + \param gain : the desired value for the gain. + */ + virtual inline void setLambda(double gain) { m_lambda = gain; } + + virtual void setLod(bool useLod, const std::string &name = ""); + + /*! + Set the maximum iteration of the virtual visual servoing stage. + + \param max : the desired number of iteration + */ + virtual inline void setMaxIter(unsigned int max) { m_maxIter = max; } + + virtual void setMinLineLengthThresh(double minLineLengthThresh, const std::string &name = ""); + + virtual void setMinPolygonAreaThresh(double minPolygonAreaThresh, const std::string &name = ""); + + virtual void setNearClippingDistance(const double &dist); + + /*! + * Set the optimization method used during the tracking. + * + * \param opt : Optimization method to use. + */ + virtual inline void setOptimizationMethod(const vpMbtOptimizationMethod &opt) { m_optimizationMethod = opt; } + + void setProjectionErrorMovingEdge(const vpMe &me); + + void setProjectionErrorKernelSize(const unsigned int &size); + + virtual void setMask(const vpImage &mask) { m_mask = &mask; } + + /*! + Set the minimal error (previous / current estimation) to determine if + there is convergence or not. + + \param eps : Epsilon threshold. + */ + virtual inline void setStopCriteriaEpsilon(const double eps) { m_stopCriteriaEpsilon = eps; } + + /*! + Set if the projection error criteria has to be computed. This criteria + could be used to detect the quality of the tracking. It computes an angle + between 0 and 90 degrees that is available with getProjectionError(). + Closer to 0 is the value, better is the tracking. + + \param flag : True if the projection error criteria has to be computed, + false otherwise. + + \sa getProjectionError() + */ + virtual void setProjectionErrorComputation(const bool &flag) { computeProjError = flag; } + + /*! + * Display or not gradient and model orientation when computing the projection error. + */ + virtual void setProjectionErrorDisplay(bool display) { m_projectionErrorDisplay = display; } + + /*! + * Arrow length used to display gradient and model orientation for projection error computation. + */ + virtual void setProjectionErrorDisplayArrowLength(unsigned int length) + { + m_projectionErrorDisplayLength = length; + } + + /*! + * Arrow thickness used to display gradient and model orientation for projection error computation. + */ + virtual void setProjectionErrorDisplayArrowThickness(unsigned int thickness) + { + m_projectionErrorDisplayThickness = thickness; + } + + /*! + * Allows to enable global scanline visibility test for all the faces. + * @param v : When true, enables scanline visibility test. + */ + virtual void setScanLineVisibilityTest(const bool &v) { useScanLine = v; } + + virtual void setOgreVisibilityTest(const bool &v); + + void savePose(const std::string &filename) const; + +#ifdef VISP_HAVE_OGRE + /*! + Set the ratio of visibility attempts that has to be successful to consider + a polygon as visible. + + \sa setNbRayCastingAttemptsForVisibility(const unsigned int &) + + \param ratio : Ratio of successful attempts that has to be considered. + Value has to be between 0.0 (0%) and 1.0 (100%). + */ + virtual void setGoodNbRayCastingAttemptsRatio(const double &ratio) { faces.setGoodNbRayCastingAttemptsRatio(ratio); } + /*! + Set the number of rays that will be sent toward each polygon for + visibility test. Each ray will go from the optic center of the camera to a + random point inside the considered polygon. + + \sa setGoodNbRayCastingAttemptsRatio(const unsigned int &) + + \param attempts Number of rays to be sent. + */ + virtual void setNbRayCastingAttemptsForVisibility(const unsigned int &attempts) + { + faces.setNbRayCastingAttemptsForVisibility(attempts); + } +#endif + + /*! + Enable/Disable the appearance of Ogre config dialog on startup. + + \warning This method has only effect when Ogre is used and Ogre visibility + test is enabled using setOgreVisibilityTest() with true parameter. + + \param showConfigDialog : if true, shows Ogre dialog window (used to set + Ogre rendering options) when Ogre visibility is enabled. By default, this + functionality is turned off. + */ + inline virtual void setOgreShowConfigDialog(bool showConfigDialog) { ogreShowConfigDialog = showConfigDialog; } + + /*! + Set the filename used to save the initial pose computed using the + initClick() method. It is also used to read a previous pose in the same + method. If the file is not set then, the initClick() method will create a + .0.pos file in the root directory. This directory is the path to the file + given to the method initClick() used to know the coordinates in the object + frame. + + \param filename : The new filename. + */ + inline void setPoseSavingFilename(const std::string &filename) { poseSavingFilename = filename; } + + /* PURE VIRTUAL METHODS */ + + /*! + Display the 3D model at a given position using the given camera parameters + on a grey level image. + + \param I : The image. + \param cMo : Pose used to project the 3D model into the image. + \param cam : The camera parameters. + \param col : The desired color. + \param thickness : The thickness of the lines. + \param displayFullModel : If true, the full model is displayed (even the + non visible surfaces). + */ + virtual void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false) = 0; + /*! + Display the 3D model at a given position using the given camera parameters + on a color (RGBa) image. + + \param I : The image. + \param cMo : Pose used to project the 3D model into the image. + \param cam : The camera parameters. + \param col : The desired color. + \param thickness : The thickness of the lines. + \param displayFullModel : If true, the full model is displayed (even the + non visible surfaces). + */ + virtual void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false) = 0; + + virtual std::vector > getModelForDisplay(unsigned int width, unsigned int height, + const vpHomogeneousMatrix &cMo, + const vpCameraParameters &cam, + bool displayFullModel = false) = 0; + + /*! + Initialise the tracking. + + \param I : Input image. + */ + virtual void init(const vpImage &I) = 0; + + /*! + Load a config file to parameterise the behavior of the tracker. + + Virtual method to adapt to each tracker. + + \param configFile : An xml config file to parse. + \param verbose : verbose flag. + */ + virtual void loadConfigFile(const std::string &configFile, bool verbose = true); + + /*! + Reset the tracker. + */ + virtual void resetTracker() = 0; + + /*! + Set the pose to be used in entry of the next call to the track() function. + This pose will be just used once. + + \warning This function has to be called after the initialisation of the + tracker. + + \param I : grayscale image corresponding to the desired pose. + \param cdMo : Pose to affect. + */ + virtual void setPose(const vpImage &I, const vpHomogeneousMatrix &cdMo) = 0; + + /*! + Set the pose to be used in entry of the next call to the track() function. + This pose will be just used once. + + \warning This function has to be called after the initialisation of the + tracker. + + \param I_color : color image corresponding to the desired pose. + \param cdMo : Pose to affect. + */ + virtual void setPose(const vpImage &I_color, const vpHomogeneousMatrix &cdMo) = 0; + + /*! + Test the quality of the tracking. + + \throw vpException if the test fail. + */ + virtual void testTracking() = 0; + + /*! + Track the object in the given image + + \param I : The current image. + */ + virtual void track(const vpImage &I) = 0; + + /*! + Track the object in the given image + + \param I : The current image. + */ + virtual void track(const vpImage &I) = 0; + +protected: + /** @name Protected Member Functions Inherited from vpMbTracker */ + void addPolygon(const std::vector &corners, int idFace = -1, const std::string &polygonName = "", + bool useLod = false, double minPolygonAreaThreshold = 2500.0, double minLineLengthThreshold = 50.0); + void addPolygon(const vpPoint &p1, const vpPoint &p2, const vpPoint &p3, double radius, int idFace = -1, + const std::string &polygonName = "", bool useLod = false, double minPolygonAreaThreshold = 2500.0); + void addPolygon(const vpPoint &p1, const vpPoint &p2, int idFace = -1, const std::string &polygonName = "", + bool useLod = false, double minLineLengthThreshold = 50); + void addPolygon(const std::vector > &listFaces, int idFace = -1, + const std::string &polygonName = "", bool useLod = false, double minLineLengthThreshold = 50); + + void addProjectionErrorCircle(const vpPoint &P1, const vpPoint &P2, const vpPoint &P3, double r, int idFace = -1, + const std::string &name = ""); + void addProjectionErrorCylinder(const vpPoint &P1, const vpPoint &P2, double r, int idFace = -1, + const std::string &name = ""); + void addProjectionErrorLine(vpPoint &p1, vpPoint &p2, int polygon = -1, std::string name = ""); + + void addProjectionErrorPolygon(const std::vector &corners, int idFace = -1, + const std::string &polygonName = "", bool useLod = false, + double minPolygonAreaThreshold = 2500.0, const double minLineLengthThreshold = 50.0); + void addProjectionErrorPolygon(const vpPoint &p1, const vpPoint &p2, const vpPoint &p3, double radius, + int idFace = -1, const std::string &polygonName = "", bool useLod = false, + double minPolygonAreaThreshold = 2500.0); + void addProjectionErrorPolygon(const vpPoint &p1, const vpPoint &p2, int idFace = -1, + const std::string &polygonName = "", bool useLod = false, + double minLineLengthThreshold = 50); + void addProjectionErrorPolygon(const std::vector > &listFaces, int idFace = -1, + const std::string &polygonName = "", bool useLod = false, + double minLineLengthThreshold = 50); + + void createCylinderBBox(const vpPoint &p1, const vpPoint &p2, const double &radius, + std::vector > &listFaces); + + virtual void computeCovarianceMatrixVVS(const bool isoJoIdentity, const vpColVector &w_true, + const vpHomogeneousMatrix &cMoPrev, const vpMatrix &L_true, + const vpMatrix &LVJ_true, const vpColVector &error); + + void computeJTR(const vpMatrix &J, const vpColVector &R, vpColVector &JTR) const; + + double computeProjectionErrorImpl(const vpImage &I, const vpHomogeneousMatrix &_cMo, + const vpCameraParameters &_cam, unsigned int &nbFeatures); + + virtual void computeVVSCheckLevenbergMarquardt(unsigned int iter, vpColVector &error, const vpColVector &m_error_prev, + const vpHomogeneousMatrix &cMoPrev, double &mu, + bool &reStartFromLastIncrement, vpColVector *const w = nullptr, + const vpColVector *const m_w_prev = nullptr); + virtual void computeVVSInit() = 0; + virtual void computeVVSInteractionMatrixAndResidu() = 0; + virtual void computeVVSPoseEstimation(const bool isoJoIdentity, unsigned int iter, vpMatrix &L, vpMatrix <L, + vpColVector &R, const vpColVector &error, vpColVector &error_prev, + vpColVector <R, double &mu, vpColVector &v, const vpColVector *const w = nullptr, + vpColVector *const m_w_prev = nullptr); + virtual void computeVVSWeights(vpRobust &robust, const vpColVector &error, vpColVector &w); + +#ifdef VISP_HAVE_COIN3D + virtual void extractGroup(SoVRMLGroup *sceneGraphVRML2, vpHomogeneousMatrix &transform, int &idFace); + virtual void extractFaces(SoVRMLIndexedFaceSet *face_set, vpHomogeneousMatrix &transform, int &idFace, + const std::string &polygonName = ""); + virtual void extractLines(SoVRMLIndexedLineSet *line_set, int &idFace, const std::string &polygonName = ""); + virtual void extractCylinders(SoVRMLIndexedFaceSet *face_set, vpHomogeneousMatrix &transform, int &idFace, + const std::string &polygonName = ""); +#endif + + vpPoint getGravityCenter(const std::vector &_pts) const; + + /*! + Add a circle to track. With the center of the circle we have 3 points defining the plane that contains the circle. + To be visible, the plane defined by the 3 points p1, p2, p3 should have its normal going toward the camera. + + \param p1 : Center of the circle, considered as the first point on the plane containing the circle. + \param p2 : Second point on the plane containing the circle. + \param p3 : Third point on the plane containing the circle. + \param radius : Radius of the circle. + \param idFace : Index of the face associated to the circle to handle visibility test. + \param name : The optional name of the circle. + */ + virtual void initCircle(const vpPoint &p1, const vpPoint &p2, const vpPoint &p3, double radius, int idFace = 0, + const std::string &name = "") = 0; + +#ifdef VISP_HAVE_MODULE_GUI + virtual void initClick(const vpImage *const I, const vpImage *const I_color, + const std::string &initFile, bool displayHelp = false, + const vpHomogeneousMatrix &od_M_o = vpHomogeneousMatrix()); + + virtual void initClick(const vpImage *const I, const vpImage *const I_color, + const std::vector &points3D_list, const std::string &displayFile = ""); +#endif + + virtual void initFromPoints(const vpImage *const I, const vpImage *const I_color, + const std::string &initFile); + + virtual void initFromPoints(const vpImage *const I, const vpImage *const I_color, + const std::vector &points2D_list, + const std::vector &points3D_list); + + virtual void initFromPose(const vpImage *const I, const vpImage *const I_color, + const std::string &initFile); + + /*! + Add a cylinder to track from two points on the axis (defining the length + of the cylinder) and its radius. + + \param p1 : First point on the axis. + \param p2 : Second point on the axis. + \param radius : Radius of the cylinder. + \param idFace : Id of the face associated to the cylinder. + \param name : Name of the cylinder. + */ + virtual void initCylinder(const vpPoint &p1, const vpPoint &p2, double radius, int idFace = 0, + const std::string &name = "") = 0; + + /*! + Add the lines to track from the polygon description. If the polygon has + only two points, it defines a single line that is always visible. If it + has three or more corners, it defines a face. In that case the visibility + of the face is computed in order to track the corresponding lines only if + the face is visible. + + The id of the polygon is supposed to be set prior calling this function. + + \param polygon : The polygon describing the set of lines that has to be + tracked. + */ + virtual void initFaceFromCorners(vpMbtPolygon &polygon) = 0; + virtual void initFaceFromLines(vpMbtPolygon &polygon) = 0; + + void initProjectionErrorCircle(const vpPoint &p1, const vpPoint &p2, const vpPoint &p3, double radius, int idFace = 0, + const std::string &name = ""); + void initProjectionErrorCylinder(const vpPoint &p1, const vpPoint &p2, double radius, int idFace = 0, + const std::string &name = ""); + void initProjectionErrorFaceFromCorners(vpMbtPolygon &polygon); + void initProjectionErrorFaceFromLines(vpMbtPolygon &polygon); + + virtual void loadVRMLModel(const std::string &modelFile); + virtual void loadCAOModel(const std::string &modelFile, std::vector &vectorOfModelFilename, + int &startIdFace, bool verbose = false, bool parent = true, + const vpHomogeneousMatrix &T = vpHomogeneousMatrix()); + void loadInitFile(const std::string &initFile, std::vector &vectorOfInitFilename, + bool parent, const vpHomogeneousMatrix &T, std::vector &P); + void projectionErrorInitMovingEdge(const vpImage &I, const vpHomogeneousMatrix &_cMo); + void projectionErrorResetMovingEdges(); + void projectionErrorVisibleFace(unsigned int width, unsigned int height, const vpHomogeneousMatrix &_cMo); + + void removeCommentsAndEmptyLines(std::ifstream &fileId); + + std::map parseParameters(std::string &endLine); + + bool samePoint(const vpPoint &P1, const vpPoint &P2) const; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtDistanceCircle.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtDistanceCircle.h new file mode 100644 index 00000000..a6cd239c --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtDistanceCircle.h @@ -0,0 +1,270 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Manage a circle used in the model-based tracker. + */ + +/*! + \file vpMbtDistanceCircle.h + \brief Manage a circle used in the model-based tracker. +*/ + +#ifndef vpMbtDistanceCircle_HH +#define vpMbtDistanceCircle_HH + +#include +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpMbtDistanceCircle + + \brief Manage a circle used in the model-based tracker. + + \ingroup group_mbt_features + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in using a MBT tracker in your applications, you may have a look at:
+ + - \ref tutorial-tracking-mb-generic + - \ref tutorial-tracking-mb-generic-stereo + - \ref tutorial-tracking-mb-generic-rgbd + - \ref tutorial-tracking-mb-generic-apriltag-live + - \ref tutorial-mb-generic-json + - \ref tutorial-tracking-mb-generic-rgbd-Blender +*/ +class VISP_EXPORT vpMbtDistanceCircle +{ +private: + std::string name; + unsigned int index; + vpCameraParameters cam; + vpMe *me; + // double alpha; + double wmean; + vpFeatureEllipse featureEllipse; + //! Polygon describing the circle bbox + // vpMbtPolygon poly; + bool isTrackedCircle; + +public: + //! The moving edge containers + vpMbtMeEllipse *meEllipse; + + //! The circle to track + vpCircle *circle; + + //! The radius of the circle + double radius; + + //! The center of the circle + vpPoint *p1; + //! A point on the plane containing the circle + vpPoint *p2; + //! An other point on the plane containing the circle + vpPoint *p3; + + //! The interaction matrix + vpMatrix L; + //! The error vector + vpColVector error; + //! The number of moving edges + unsigned int nbFeature; + //! Indicates if the circle has to be reinitialized + bool Reinit; + //! Pointer to the list of faces + vpMbHiddenFaces *hiddenface; + //! Index of the faces which contain the line + int index_polygon; + //! Indicates if the circle is visible or not + bool isvisible; + + // private: + //#ifndef DOXYGEN_SHOULD_SKIP_THIS + // vpMbtDistanceCircle(const vpMbtDistanceCircle &) + // : name(), index(0), cam(), me(nullptr), wmean(1), + // featureEllipse(), isTrackedCircle(true), meEllipse(nullptr), + // circle(nullptr), radius(0.), p1(nullptr), p2(nullptr), p3(nullptr), + // L(), error(), nbFeature(0), Reinit(false), + // hiddenface(nullptr), index_polygon(-1), isvisible(false) + // { + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); + // } + // vpMbtDistanceCircle &operator=(const vpMbtDistanceCircle &){ + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); return *this; + // } + //#endif + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpMbtDistanceCircle(const vpMbtDistanceCircle &) = delete; // non construction-copyable + vpMbtDistanceCircle &operator=(const vpMbtDistanceCircle &) = delete; // non copyable +#endif + +public: + vpMbtDistanceCircle(); + virtual ~vpMbtDistanceCircle(); + + void buildFrom(const vpPoint &_p1, const vpPoint &_p2, const vpPoint &_p3, double r); + + void computeInteractionMatrixError(const vpHomogeneousMatrix &cMo); + + void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false); + void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false); + void displayMovingEdges(const vpImage &I); + void displayMovingEdges(const vpImage &I); + + /*! + Get the camera parameters. + + \param camera : The vpCameraParameters used to store the camera parameters. + */ + inline void getCameraParameters(vpCameraParameters &camera) { camera = this->cam; } + + /*! + Get the index of the circle. + + \return Return the index of the line. + */ + inline unsigned int getIndex() { return index; } + + /*! + Get the mean weight of the circle. The mean weight is computed thanks to + the weight of each moving edge. Those weights are computed by the robust + estimation method used during the virtual visual servoing. + + \return The mean weight of the circle. + */ + inline double getMeanWeight() const { return wmean; } + + std::vector > getFeaturesForDisplay(); + + std::vector getModelForDisplay(const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + bool displayFullModel = false); + + /*! + Get the name of the circle. + + \return Return the name of the circle. + */ + inline std::string getName() const { return name; } + + void initInteractionMatrixError(); + + bool initMovingEdge(const vpImage &I, const vpHomogeneousMatrix &cMo, bool doNotTrack, + const vpImage *mask = nullptr, const int &initRange = 1U); + + /*! + Return if the circle is used for tracking. + + \return True if it is used, False otherwise. + */ + inline bool isTracked() const { return isTrackedCircle; } + + /*! + Check if the circle is visible in the image or not. + + \return Return true if the circle is visible + */ + inline bool isVisible() const { return isvisible; } + + void reinitMovingEdge(const vpImage &I, const vpHomogeneousMatrix &cMo, + const vpImage *mask = nullptr); + + /*! + Set the camera parameters. + \param camera : The camera parameters. + */ + inline void setCameraParameters(const vpCameraParameters &camera) { this->cam = camera; } + + /*! + Set if the circle has to considered during tracking phase. + + \param track : True if the circle has to be tracked, False otherwise. + */ + inline void setTracked(const bool &track) { this->isTrackedCircle = track; } + + /*! + Set the index of the circle. + + \param i : The index number + */ + inline void setIndex(unsigned int i) { index = i; } + + /*! + Set the mean weight of the circle. + + \param _wmean : The mean weight of the circle. + */ + inline void setMeanWeight(double _wmean) { this->wmean = _wmean; } + + void setMovingEdge(vpMe *Me); + + /*! + Set the name of the circle. + + \param circle_name : The name of the circle. + */ + inline void setName(const std::string &circle_name) { this->name = circle_name; } + + /*! + Set the name of the circle. + + \param circle_name : The name of the circle. + */ + inline void setName(const char *circle_name) { this->name = std::string(circle_name); } + + /*! + Set a boolean parameter to indicates if the circle is visible in the image + or not. + + \param _isvisible : Set to true if the circle is visible + */ + inline void setVisible(bool _isvisible) { isvisible = _isvisible; } + + void trackMovingEdge(const vpImage &I, const vpHomogeneousMatrix &cMo); + + void updateMovingEdge(const vpImage &I, const vpHomogeneousMatrix &cMo); + +private: + void project(const vpHomogeneousMatrix &cMo); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtDistanceCylinder.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtDistanceCylinder.h new file mode 100644 index 00000000..c0166ebd --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtDistanceCylinder.h @@ -0,0 +1,302 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Manage a cylinder used in the model-based tracker. + * + * Authors: + * Romain Tallonneau + * Bertrand Delabarre + */ + +/*! + \file vpMbtDistanceCylinder.h + \brief Manage a cylinder used in the model-based tracker. +*/ + +#ifndef vpMbtDistanceCylinder_HH +#define vpMbtDistanceCylinder_HH + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpMbtDistanceCylinder + + \brief Manage a cylinder used in the model-based tracker. + + \ingroup group_mbt_features + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in using a MBT tracker in your applications, you may have a look at:
+ + - \ref tutorial-tracking-mb-generic + - \ref tutorial-tracking-mb-generic-stereo + - \ref tutorial-tracking-mb-generic-rgbd + - \ref tutorial-tracking-mb-generic-apriltag-live + - \ref tutorial-mb-generic-json + - \ref tutorial-tracking-mb-generic-rgbd-Blender +*/ +class VISP_EXPORT vpMbtDistanceCylinder +{ +private: + std::string name; + unsigned int index; + vpCameraParameters cam; + vpMe *me; + double wmean1; + double wmean2; + vpFeatureLine featureline1; + vpFeatureLine featureline2; + bool isTrackedCylinder; + +public: + //! The moving edge containers (first line of the cylinder) + vpMbtMeLine *meline1; + //! The moving edge containers (second line of the cylinder) + vpMbtMeLine *meline2; + + //! The upper circle limiting the cylinder + vpCircle *cercle1; + //! The lower circle limiting the cylinder + vpCircle *cercle2; + + //! The radius of the cylinder + double radius; + + //! The first extremity on the axe + vpPoint *p1; + //! The second extremity on the axe + vpPoint *p2; + + //! The interaction matrix + vpMatrix L; + //! The error vector + vpColVector error; + //! The number of moving edges + unsigned int nbFeature; + //! The number of moving edges on line 1 + unsigned int nbFeaturel1; + //! The number of moving edges on line 2 + unsigned int nbFeaturel2; + //! Indicates if the line has to be reinitialized + bool Reinit; + //! The cylinder + vpCylinder *c; + + //! Pointer to the list of faces + vpMbHiddenFaces *hiddenface; + //! Index of the face which contains the cylinder + int index_polygon; + //! Indicates if the cylinder is visible or not + bool isvisible; + + // private: + //#ifndef DOXYGEN_SHOULD_SKIP_THIS + // vpMbtDistanceCylinder(const vpMbtDistanceCylinder &) + // : name(), index(0), cam(), me(nullptr), wmean1(1), wmean2(1), + // featureline1(), featureline2(), isTrackedCylinder(true), + // meline1(nullptr), meline2(nullptr), cercle1(nullptr), cercle2(nullptr), + // radius(0), p1(nullptr), p2(nullptr), L(), error(), nbFeature(0), + // nbFeaturel1(0), nbFeaturel2(0), Reinit(false), c(nullptr), + // hiddenface(nullptr), index_polygon(-1), isvisible(false) + // { + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); + // } + // vpMbtDistanceCylinder &operator=(const vpMbtDistanceCylinder &){ + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); return *this; + // } + //#endif + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpMbtDistanceCylinder(const vpMbtDistanceCylinder &) = delete; // non construction-copyable + vpMbtDistanceCylinder &operator=(const vpMbtDistanceCylinder &) = delete; // non copyable +#endif + +public: + vpMbtDistanceCylinder(); + virtual ~vpMbtDistanceCylinder(); + + void buildFrom(const vpPoint &_p1, const vpPoint &_p2, double r); + + void computeInteractionMatrixError(const vpHomogeneousMatrix &cMo, const vpImage &I); + + void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false); + void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false); + void displayMovingEdges(const vpImage &I); + void displayMovingEdges(const vpImage &I); + + /*! + Get the camera parameters. + + \param camera : The vpCameraParameters used to store the camera parameters. + */ + inline void getCameraParameters(vpCameraParameters &camera) { camera = this->cam; } + + /*! + Get the index of the cylinder. + + \return Return the index of the line. + */ + inline unsigned int getIndex() { return index; } + + /*! + Get the mean weight of the first line. The mean weight is computed thanks + to the weight of each moving edge. Those weights are computed by the robust + estimation method used during the virtual visual servoing. + + \return The mean weight of the first line. + */ + inline double getMeanWeight1() const { return wmean1; } + + /*! + Get the mean weight of the second line. The mean weight is computed thanks + to the weight of each moving edge. Those weights are computed by the robust + estimation method used during the virtual visual servoing. + + \return The mean weight of the second line. + */ + inline double getMeanWeight2() const { return wmean2; } + + std::vector > getFeaturesForDisplay(); + + std::vector > getModelForDisplay(unsigned int width, unsigned int height, + const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + bool displayFullModel = false); + + /*! + Get the name of the cylinder. + + \return Return the name of the cylinder + */ + inline std::string getName() const { return name; } + + void initInteractionMatrixError(); + + bool initMovingEdge(const vpImage &I, const vpHomogeneousMatrix &cMo, bool doNotTrack, + const vpImage *mask = nullptr, const int &initRange = 0); + + /*! + Return if the cylinder is used for tracking. + + \return True if it is used, False otherwise. + */ + inline bool isTracked() const { return isTrackedCylinder; } + + /*! + Check if the cylinder is visible in the image or not. + + \return Return true if the cylinder is visible + */ + inline bool isVisible() const { return isvisible; } + + void reinitMovingEdge(const vpImage &I, const vpHomogeneousMatrix &cMo, + const vpImage *mask = nullptr); + + /*! + Set the camera parameters. + \param camera : The camera parameters. + */ + inline void setCameraParameters(const vpCameraParameters &camera) { this->cam = camera; } + + /*! + Set if the cylinder has to considered during tracking phase. + + \param track : True if the cylinder has to be tracked, False otherwise. + */ + inline void setTracked(const bool &track) { this->isTrackedCylinder = track; } + + /*! + Set the index of the cylinder. + + \param i : The index number + */ + inline void setIndex(unsigned int i) { index = i; } + + /*! + Set the mean weight of the first line. + + \param wmean : The mean weight of the first line. + */ + inline void setMeanWeight1(double wmean) { this->wmean1 = wmean; } + + /*! + Set the mean weight of the second line. + + \param wmean : The mean weight of the second line. + */ + inline void setMeanWeight2(double wmean) { this->wmean2 = wmean; } + + void setMovingEdge(vpMe *Me); + + /*! + Set the name of the cylinder. + + \param cyl_name : The name of the cylinder. + */ + inline void setName(const std::string &cyl_name) { this->name = cyl_name; } + + /*! + Set the name of the cylinder. + + \param cyl_name : The name of the cylinder + */ + inline void setName(const char *cyl_name) { this->name = std::string(cyl_name); } + + /*! + Set a boolean parameter to indicates if the cylinder is visible in the + image or not. + + \param _isvisible : Set to true if the cylinder is visible + */ + inline void setVisible(bool _isvisible) { isvisible = _isvisible; } + + void trackMovingEdge(const vpImage &I, const vpHomogeneousMatrix &cMo); + + void updateMovingEdge(const vpImage &I, const vpHomogeneousMatrix &cMo); + +private: + void project(const vpHomogeneousMatrix &cMo); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtDistanceKltCylinder.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtDistanceKltCylinder.h new file mode 100644 index 00000000..be418afb --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtDistanceKltCylinder.h @@ -0,0 +1,234 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Klt cylinder, containing points of interest. + */ + +#ifndef vpMbtDistanceKltCylinder_h +#define vpMbtDistanceKltCylinder_h + +#include + +#if defined(VISP_HAVE_MODULE_KLT) && defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) && defined(HAVE_OPENCV_VIDEO) + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpMbtDistanceKltCylinder + + \brief Implementation of a polygon of the model containing points of + interest. It is used by the model-based tracker KLT, and hybrid. + + \warning This class is only available if OpenCV is installed, and used. + + \ingroup group_mbt_features + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in using a MBT tracker in your applications, you may have a look at:
+ + - \ref tutorial-tracking-mb-generic + - \ref tutorial-tracking-mb-generic-stereo + - \ref tutorial-tracking-mb-generic-rgbd + - \ref tutorial-tracking-mb-generic-apriltag-live + - \ref tutorial-mb-generic-json + - \ref tutorial-tracking-mb-generic-rgbd-Blender +*/ +class VISP_EXPORT vpMbtDistanceKltCylinder +{ +private: + //! Pose at initialisation + vpHomogeneousMatrix c0Mo; + //! First extremity of the cylinder (used for display) + vpPoint p1Ext; + //! Second extremity of the cylinder (used for display) + vpPoint p2Ext; + //! Cylinder + vpCylinder cylinder; + //! The upper circle limiting the cylinder (used for display) + vpCircle circle1; + //! The lower circle limiting the cylinder (used for display) + vpCircle circle2; + //! Initial points and their ID + std::map initPoints; + //! Initial points and their ID + std::map initPoints3D; + //! Current points and their ID + std::map curPoints; + //! Current points ID and their indexes + std::map curPointsInd; + //! number of points detected + unsigned int nbPointsCur; + //! initial number of points + unsigned int nbPointsInit; + //! Minimal number of points to be tracked + unsigned int minNbPoint; + //! Boolean to know if there is enough point to be tracked + bool enoughPoints; + //! Camera parameters + vpCameraParameters cam; + //! Boolean to specify if the klt points have to be tracked or not + bool isTrackedKltCylinder; + +public: + //! Pointer to the polygon that define a face + std::vector listIndicesCylinderBBox; + //! Pointer to the list of faces + vpMbHiddenFaces *hiddenface; + //! Use scanline rendering + bool useScanLine; + +private: + double computeZ(const double &x, const double &y); + bool isTrackedFeature(int id); + + // private: + //#ifndef DOXYGEN_SHOULD_SKIP_THIS + // vpMbtDistanceKltCylinder(const vpMbtDistanceKltCylinder &) + // : c0Mo(), p1Ext(), p2Ext(), cylinder(), circle1(), circle2(), + // initPoints(), initPoints3D(), curPoints(), curPointsInd(), + // nbPointsCur(0), nbPointsInit(0), minNbPoint(4), + // enoughPoints(false), cam(), isTrackedKltCylinder(true), + // listIndicesCylinderBBox(), hiddenface(nullptr), useScanLine(false) + // { + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); + // } + // vpMbtDistanceKltCylinder &operator=(const vpMbtDistanceKltCylinder &){ + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); return *this; + // } + //#endif + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpMbtDistanceKltCylinder(const vpMbtDistanceKltCylinder &) = delete; // non construction-copyable + vpMbtDistanceKltCylinder &operator=(const vpMbtDistanceKltCylinder &) = delete; // non copyable +#endif + +public: + vpMbtDistanceKltCylinder(); + virtual ~vpMbtDistanceKltCylinder(); + + void buildFrom(const vpPoint &p1, const vpPoint &p2, const double &r); + + unsigned int computeNbDetectedCurrent(const vpKltOpencv &_tracker); + void computeInteractionMatrixAndResidu(const vpHomogeneousMatrix &cMc0, vpColVector &_R, vpMatrix &_J); + + void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false); + void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false); + + void displayPrimitive(const vpImage &_I); + void displayPrimitive(const vpImage &_I); + + /*! + Get the camera parameters of the face. + + \return cam : the camera parameters of the face. + */ + inline vpCameraParameters &getCameraParameters() { return cam; } + + inline std::map &getCurrentPoints() { return curPoints; } + + inline std::map &getCurrentPointsInd() { return curPointsInd; } + + inline vpCylinder getCylinder() const { return cylinder; } + + /*! + Get the number of point that was belonging to the face at the + initialisation + + \return the number of initial point. + + \sa getCurrentNumberPoints() + */ + inline unsigned int getInitialNumberPoint() const { return nbPointsInit; } + /*! + Get the number of points detected in the last image. + + \warning To have the real number of points, the function + computeNbDetectedCurrent() must be called first. + + \return the number of points detected in the current image. + + \sa getInitialNumberPoint() + */ + inline unsigned int getCurrentNumberPoints() const { return nbPointsCur; } + + std::vector > getFeaturesForDisplay(); + + std::vector > getModelForDisplay(const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam); + + inline bool hasEnoughPoints() const { return enoughPoints; } + + /*! + Return if the klt cylinder is used for tracking. + + \return True if it is used, False otherwise. + */ + inline bool isTracked() const { return isTrackedKltCylinder; } + + void init(const vpKltOpencv &_tracker, const vpHomogeneousMatrix &cMo); + + void removeOutliers(const vpColVector &weight, const double &threshold_outlier); + + /*! + Set the camera parameters + + \param _cam : the new camera parameters + */ + virtual inline void setCameraParameters(const vpCameraParameters &_cam) { cam = _cam; } + + /*! + Set if the klt cylinder has to be considered during tracking phase. + + \param track : True if is has to be tracked, False otherwise. + */ + inline void setTracked(const bool &track) { this->isTrackedKltCylinder = track; } + + void updateMask(cv::Mat &mask, unsigned char _nb = 255, unsigned int _shiftBorder = 0); +}; +END_VISP_NAMESPACE +#endif + +#endif // VISP_HAVE_OPENCV diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtDistanceKltPoints.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtDistanceKltPoints.h new file mode 100644 index 00000000..e5d9dff2 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtDistanceKltPoints.h @@ -0,0 +1,234 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Klt polygon, containing points of interest. + */ + +#ifndef _vpMbtDistanceKltPoints_h_ +#define _vpMbtDistanceKltPoints_h_ + +#include + +#if defined(VISP_HAVE_MODULE_KLT) && defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) && defined(HAVE_OPENCV_VIDEO) + +#include + +#include +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpMbtDistanceKltPoints + + \brief Implementation of a polygon of the model containing points of + interest. It is used by the model-based tracker KLT, and hybrid. + + \warning This class is only available if OpenCV is installed, and used. + + \ingroup group_mbt_features + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in using a MBT tracker in your applications, you may have a look at:
+ + - \ref tutorial-tracking-mb-generic + - \ref tutorial-tracking-mb-generic-stereo + - \ref tutorial-tracking-mb-generic-rgbd + - \ref tutorial-tracking-mb-generic-apriltag-live + - \ref tutorial-mb-generic-json + - \ref tutorial-tracking-mb-generic-rgbd-Blender +*/ +class VISP_EXPORT vpMbtDistanceKltPoints +{ +private: + //! the homography in meter + vpMatrix H; + //! normal to the initial plane + vpColVector N; + //! current normal + vpColVector N_cur; + //! inverse of the distance between the plane and the camera at the initial + //! position (speed up computation) + double invd0; + //! cRc0_0n (temporary variable to speed up the computation) + vpColVector cRc0_0n; + //! Initial points and their ID + std::map initPoints; + //! Current points and their ID + std::map curPoints; + //! Current points ID and their indexes + std::map curPointsInd; + //! number of points detected + unsigned int nbPointsCur; + //! initial number of points + unsigned int nbPointsInit; + //! Minimal number of points to be tracked + unsigned int minNbPoint; + //! Boolean to know if there is enough point to be tracked + bool enoughPoints; + //! current camera to plane distance to speed up the computation + double dt; + //! distance between the plane and the camera at the initial position + double d0; + //! Camera parameters + vpCameraParameters cam; + //! Boolean to specify if the klt points have to be tracked or not + bool isTrackedKltPoints; + +public: + //! Pointer to the polygon that define a face + vpMbtPolygon *polygon; + //! Pointer to the list of faces + vpMbHiddenFaces *hiddenface; + //! Use scanline rendering + bool useScanLine; + +private: + double compute_1_over_Z(double x, double y); + void computeP_mu_t(double x_in, double y_in, double &x_out, double &y_out, const vpMatrix &cHc0); + bool isTrackedFeature(int id); + + // private: + //#ifndef DOXYGEN_SHOULD_SKIP_THIS + // vpMbtDistanceKltPoints(const vpMbtDistanceKltPoints &) + // : H(), N(), N_cur(), invd0(1.), cRc0_0n(), initPoints(), + // curPoints(), curPointsInd(), + // nbPointsCur(0), nbPointsInit(0), minNbPoint(4), + // enoughPoints(false), dt(1.), d0(1.), cam(), + // isTrackedKltPoints(true), polygon(nullptr), hiddenface(nullptr), + // useScanLine(false) + // { + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); + // } + // vpMbtDistanceKltPoints &operator=(const vpMbtDistanceKltPoints &){ + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); return *this; + // } + //#endif + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpMbtDistanceKltPoints(const vpMbtDistanceKltPoints &) = delete; // non construction-copyable + vpMbtDistanceKltPoints &operator=(const vpMbtDistanceKltPoints &) = delete; // non copyable +#endif + +public: + vpMbtDistanceKltPoints(); + virtual ~vpMbtDistanceKltPoints(); + + unsigned int computeNbDetectedCurrent(const vpKltOpencv &_tracker, const vpImage *mask = nullptr); + void computeHomography(const vpHomogeneousMatrix &_cTc0, vpHomography &cHc0); + void computeInteractionMatrixAndResidu(vpColVector &_R, vpMatrix &_J); + + void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false); + void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false); + + void displayPrimitive(const vpImage &_I); + void displayPrimitive(const vpImage &_I); + + std::vector > getFeaturesForDisplay(); + + std::vector > getModelForDisplay(const vpCameraParameters &cam, bool displayFullModel = false); + + /*! + Get the camera parameters of the face. + + \return cam : the camera parameters of the face. + */ + inline vpCameraParameters &getCameraParameters() { return cam; } + + inline vpColVector getCurrentNormal() const { return N_cur; } + + inline std::map &getCurrentPoints() { return curPoints; } + + inline std::map &getCurrentPointsInd() { return curPointsInd; } + + /*! + Get the number of point that was belonging to the face at the + initialisation + + \return the number of initial point. + + \sa getCurrentNumberPoints() + */ + inline unsigned int getInitialNumberPoint() const { return nbPointsInit; } + /*! + Get the number of points detected in the last image. + + \warning To have the real number of points, the function + computeNbDetectedCurrent() must be called first. + + \return the number of points detected in the current image. + + \sa getInitialNumberPoint() + */ + inline unsigned int getCurrentNumberPoints() const { return nbPointsCur; } + + inline bool hasEnoughPoints() const { return enoughPoints; } + + void init(const vpKltOpencv &_tracker, const vpImage *mask = nullptr); + + /*! + Return if the klt points are used for tracking. + + \return True if it is used, False otherwise. + */ + inline bool isTracked() const { return isTrackedKltPoints; } + + void removeOutliers(const vpColVector &weight, const double &threshold_outlier); + + /*! + Set the camera parameters + + \param _cam : the new camera parameters + */ + virtual inline void setCameraParameters(const vpCameraParameters &_cam) { cam = _cam; } + + /*! + Set if the klt points have to considered during tracking phase. + + \param track : True if they have to be tracked, False otherwise. + */ + inline void setTracked(const bool &track) { this->isTrackedKltPoints = track; } + + void updateMask(cv::Mat &mask, unsigned char _nb = 255, unsigned int _shiftBorder = 0); +}; +END_VISP_NAMESPACE +#endif + +#endif // VISP_HAVE_OPENCV diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtDistanceLine.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtDistanceLine.h new file mode 100644 index 00000000..00d3dee4 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtDistanceLine.h @@ -0,0 +1,286 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Manage the line of a polygon used in the model-based tracker. + * + * Authors: + * Romain Tallonneau + */ + +/*! + \file vpMbtDistanceLine.h + \brief Manage the line of a polygon used in the model-based tracker. +*/ + +#ifndef vpMbtDistanceLine_HH +#define vpMbtDistanceLine_HH + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpMbtDistanceLine + + \brief Manage the line of a polygon used in the model-based tracker. + + \ingroup group_mbt_features + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in using a MBT tracker in your applications, you may have a look at:
+ + - \ref tutorial-tracking-mb-generic + - \ref tutorial-tracking-mb-generic-stereo + - \ref tutorial-tracking-mb-generic-rgbd + - \ref tutorial-tracking-mb-generic-apriltag-live + - \ref tutorial-mb-generic-json + - \ref tutorial-tracking-mb-generic-rgbd-Blender +*/ +class VISP_EXPORT vpMbtDistanceLine +{ +private: + std::string name; + unsigned int index; + vpCameraParameters cam; + vpMe *me; + bool isTrackedLine; + bool isTrackedLineWithVisibility; + double wmean; + vpFeatureLine featureline; + //! Polygon describing the line + vpMbtPolygon poly; + +public: + //! Use scanline rendering + bool useScanLine; + //! The moving edge container + // vpMbtMeLine *meline; + std::vector meline; + //! The 3D line + vpLine *line; + //! The first extremity + vpPoint *p1; + //! The second extremity + vpPoint *p2; + //! The interaction matrix + vpMatrix L; + //! The error vector + vpColVector error; + //! The number of moving edges + // unsigned int nbFeature; + std::vector nbFeature; + //! The number of moving edges + unsigned int nbFeatureTotal; + //! Indicates if the line has to be reinitialized + bool Reinit; + //! Pointer to the list of faces + vpMbHiddenFaces *hiddenface; + //! Index of the faces which contain the line + std::list Lindex_polygon; + //! Vector of bool associated with Lindex_polygon to know if + //! Lindex_polygon[i] is tracked + std::vector Lindex_polygon_tracked; + //! Indicates if the line is visible or not + bool isvisible; + + // private: + //#ifndef DOXYGEN_SHOULD_SKIP_THIS + // vpMbtDistanceLine(const vpMbtDistanceLine &) + // : name(), index(0), cam(), me(nullptr), isTrackedLine(true), + // isTrackedLineWithVisibility(true), + // wmean(1), featureline(), poly(), useScanLine(false), meline(), + // line(nullptr), p1(nullptr), p2(nullptr), L(), error(), nbFeature(), + // nbFeatureTotal(0), Reinit(false), hiddenface(nullptr), + // Lindex_polygon(), Lindex_polygon_tracked(), isvisible(false) + // { + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); + // } + // vpMbtDistanceLine &operator=(const vpMbtDistanceLine &){ + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); return *this; + // } + //#endif + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpMbtDistanceLine(const vpMbtDistanceLine &) = delete; // non construction-copyable + vpMbtDistanceLine &operator=(const vpMbtDistanceLine &) = delete; // non copyable +#endif + +public: + vpMbtDistanceLine(); + virtual ~vpMbtDistanceLine(); + + void addPolygon(const int &index); + + void buildFrom(vpPoint &_p1, vpPoint &_p2, vpUniRand &rand_gen); + + bool closeToImageBorder(const vpImage &I, const unsigned int threshold); + void computeInteractionMatrixError(const vpHomogeneousMatrix &cMo); + + void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false); + void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false); + void displayMovingEdges(const vpImage &I); + void displayMovingEdges(const vpImage &I); + + /*! + Get the camera parameters. + + \param camera : The vpCameraParameters used to store the camera parameters. + */ + inline void getCameraParameters(vpCameraParameters &camera) const { camera = this->cam; } + + /*! + Get the index of the line. + + \return Return the index of the line. + */ + inline unsigned int getIndex() const { return index; } + + /*! + Get the mean weight of the line. The mean weight is computed thanks to the + weight of each moving edge. Those weights are computed by the robust + estimation method used during the virtual visual servoing. + + \return The mean weight of the line. + */ + inline double getMeanWeight() const { return wmean; } + + std::vector > getFeaturesForDisplay(); + + std::vector > getModelForDisplay(unsigned int width, unsigned int height, + const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + bool displayFullModel = false); + + /*! + Get the name of the line. + + \return Return the name of the line + */ + inline std::string getName() const { return name; } + + /*! + Get the polygon associated to the line. + + \return poly. + */ + inline vpMbtPolygon &getPolygon() { return poly; } + + void initInteractionMatrixError(); + + bool initMovingEdge(const vpImage &I, const vpHomogeneousMatrix &cMo, bool doNotTrack, + const vpImage *mask = nullptr, const int &initRange = 0); + + /*! + Return if the line is used for tracking. + + \return True if it is used, False otherwise. + */ + inline bool isTracked() const { return isTrackedLineWithVisibility; } + + /*! + Check if the line is visible in the image or not. + + \return Return true if the line is visible + */ + inline bool isVisible() const { return isvisible; } + + void reinitMovingEdge(const vpImage &I, const vpHomogeneousMatrix &cMo, + const vpImage *mask = nullptr); + + /*! + Set the camera parameters. + \param camera : The camera parameters. + */ + inline void setCameraParameters(const vpCameraParameters &camera) { this->cam = camera; } + + /*! + Set the index of the line. + + \param i : The index number + */ + inline void setIndex(unsigned int i) { index = i; } + + /*! + Set the mean weight of the line. + + \param w_mean : The mean weight of the line. + */ + inline void setMeanWeight(double w_mean) { this->wmean = w_mean; } + + void setMovingEdge(vpMe *Me); + + /*! + Set the name of the line. + + \param line_name : The name of the line. + */ + inline void setName(const std::string &line_name) { this->name = line_name; } + + /*! + Set the name of the line. + + \param line_name : The name of the line. + */ + inline void setName(const char *line_name) { this->name = std::string(line_name); } + + void setTracked(const std::string &name, const bool &track); + + /*! + Set a boolean parameter to indicates if the line is visible in the image + or not. + + \param _isvisible : Set to true if the line is visible + */ + void setVisible(bool _isvisible) { isvisible = _isvisible; } + + void trackMovingEdge(const vpImage &I); + + void updateMovingEdge(const vpImage &I, const vpHomogeneousMatrix &cMo); + + void updateTracked(); + +private: + void project(const vpHomogeneousMatrix &cMo); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtFaceDepthDense.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtFaceDepthDense.h new file mode 100644 index 00000000..b286cca5 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtFaceDepthDense.h @@ -0,0 +1,260 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Manage depth dense features for a particular face. + */ + +#ifndef VP_MBT_FACE_DEPTH_DENSE_H +#define VP_MBT_FACE_DEPTH_DENSE_H + +#include + +#include +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_COMMON) +#include +#include +#endif + +#include +#include +#include + +#define DEBUG_DISPLAY_DEPTH_DENSE 0 + +BEGIN_VISP_NAMESPACE +/** + * \ingroup group_mbt_faces + * \brief Manage depth dense features for a particular face. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using a MBT tracker in your applications, you may have a look at:
+ * + * - \ref tutorial-tracking-mb-generic + * - \ref tutorial-tracking-mb-generic-stereo + * - \ref tutorial-tracking-mb-generic-rgbd + * - \ref tutorial-tracking-mb-generic-apriltag-live + * - \ref tutorial-mb-generic-json + * - \ref tutorial-tracking-mb-generic-rgbd-Blender + */ + class VISP_EXPORT vpMbtFaceDepthDense +{ +public: + enum vpDepthDenseFilteringType + { + NO_FILTERING = 0, ///< Face is used if visible + DEPTH_OCCUPANCY_RATIO_FILTERING = 1 << 1, ///< Face is used if there is + ///< enough depth information in + ///< the face polygon + MIN_DISTANCE_FILTERING = 1 << 2, ///< Face is used if the camera position + ///< is farther than the threshold + MAX_DISTANCE_FILTERING = 1 << 3 ///< Face is used if the camera position + ///< is closer than the threshold + }; + + //! Camera intrinsic parameters + vpCameraParameters m_cam; + //! Flags specifying which clipping to used + unsigned int m_clippingFlag; + //! Distance for near clipping + double m_distFarClip; + //! Distance for near clipping + double m_distNearClip; + //! Pointer to the list of faces + vpMbHiddenFaces *m_hiddenFace; + //! Plane equation described in the object frame + vpPlane m_planeObject; + //! Polygon defining the face + vpMbtPolygon *m_polygon; + //! Scan line visibility + bool m_useScanLine; + + vpMbtFaceDepthDense(); + vpMbtFaceDepthDense(const vpMbtFaceDepthDense &mbt_face); + virtual ~vpMbtFaceDepthDense(); + vpMbtFaceDepthDense &operator=(const vpMbtFaceDepthDense &mbt_face); + + void addLine(vpPoint &p1, vpPoint &p2, vpMbHiddenFaces *const faces, vpUniRand &rand_gen, + int polygon = -1, std::string name = ""); + +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_COMMON) + bool computeDesiredFeatures(const vpHomogeneousMatrix &cMo, + const pcl::PointCloud::ConstPtr &point_cloud, unsigned int stepX, + unsigned int stepY +#if DEBUG_DISPLAY_DEPTH_DENSE + , + vpImage &debugImage, std::vector > &roiPts_vec +#endif + , + const vpImage *mask = nullptr); +#endif + bool computeDesiredFeatures(const vpHomogeneousMatrix &cMo, unsigned int width, unsigned int height, + const std::vector &point_cloud, unsigned int stepX, unsigned int stepY +#if DEBUG_DISPLAY_DEPTH_DENSE + , + vpImage &debugImage, std::vector > &roiPts_vec +#endif + , + const vpImage *mask = nullptr); + bool computeDesiredFeatures(const vpHomogeneousMatrix &cMo, unsigned int width, unsigned int height, + const vpMatrix &point_cloud, unsigned int stepX, unsigned int stepY +#if DEBUG_DISPLAY_DEPTH_DENSE + , + vpImage &debugImage, std::vector > &roiPts_vec +#endif + , + const vpImage *mask = nullptr); + + void computeInteractionMatrixAndResidu(const vpHomogeneousMatrix &cMo, vpMatrix &L, vpColVector &error); + + void computeVisibility(); + void computeVisibilityDisplay(); + + void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false); + void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false); + + void displayFeature(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + double scale = 0.05, unsigned int thickness = 1); + void displayFeature(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + double scale = 0.05, unsigned int thickness = 1); + + std::vector > getModelForDisplay(unsigned int width, unsigned int height, + const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + bool displayFullModel = false); + + inline unsigned int getNbFeatures() const { return static_cast(m_pointCloudFace.size() / 3); } + + inline bool isTracked() const { return m_isTrackedDepthDenseFace; } + + inline bool isVisible() const { return m_polygon->isvisible; } + + void setCameraParameters(const vpCameraParameters &camera); + + void setScanLineVisibilityTest(bool v); + + inline void setDepthDenseFilteringMaxDistance(double maxDistance) { m_depthDenseFilteringMaxDist = maxDistance; } + + inline void setDepthDenseFilteringMethod(int method) { m_depthDenseFilteringMethod = method; } + + inline void setDepthDenseFilteringMinDistance(double minDistance) { m_depthDenseFilteringMinDist = minDistance; } + + inline void setDepthDenseFilteringOccupancyRatio(double occupancyRatio) + { + if (occupancyRatio < 0.0 || occupancyRatio > 1.0) { + std::cerr << "occupancyRatio < 0.0 || occupancyRatio > 1.0" << std::endl; + } + else { + m_depthDenseFilteringOccupancyRatio = occupancyRatio; + } + } + + inline void setTracked(bool tracked) { m_isTrackedDepthDenseFace = tracked; } + +private: + class PolygonLine + { + public: + //! The first extremity + vpPoint *m_p1; + //! The second extremity + vpPoint *m_p2; + //! Polygon describing the line + vpMbtPolygon m_poly; + //! The first extremity clipped in the image frame + vpImagePoint m_imPt1; + //! The second extremity clipped in the image frame + vpImagePoint m_imPt2; + + PolygonLine() : m_p1(nullptr), m_p2(nullptr), m_poly(), m_imPt1(), m_imPt2() { } + + PolygonLine(const PolygonLine &polyLine) + : m_p1(nullptr), m_p2(nullptr), m_poly(polyLine.m_poly), m_imPt1(polyLine.m_imPt1), m_imPt2(polyLine.m_imPt2) + { + m_p1 = &m_poly.p[0]; + m_p2 = &m_poly.p[1]; + } + + PolygonLine &operator=(PolygonLine other) + { + swap(*this, other); + + return *this; + } + + void swap(PolygonLine &first, PolygonLine &second) + { + using std::swap; + swap(first.m_p1, second.m_p1); + swap(first.m_p2, second.m_p2); + swap(first.m_poly, second.m_poly); + swap(first.m_imPt1, second.m_imPt1); + swap(first.m_imPt2, second.m_imPt2); + } + }; + +protected: + //! Method to use to consider or not the face + int m_depthDenseFilteringMethod; + //! Maximum distance threshold + double m_depthDenseFilteringMaxDist; + //! Minimum distance threshold + double m_depthDenseFilteringMinDist; + //! Ratio between available depth points and theoretical number of points + double m_depthDenseFilteringOccupancyRatio; + //! Flag to define if the face should be tracked or not + bool m_isTrackedDepthDenseFace; + //! Visibility flag + bool m_isVisible; + std::vector m_listOfFaceLines; + //! Plane equation described in the camera frame and updated with the + //! current pose + vpPlane m_planeCamera; + //! List of depth points inside the face + std::vector m_pointCloudFace; + //! Polygon lines used for scan-line visibility + std::vector m_polygonLines; + +protected: + void computeROI(const vpHomogeneousMatrix &cMo, unsigned int width, unsigned int height, + std::vector &roiPts +#if DEBUG_DISPLAY_DEPTH_DENSE + , + std::vector > &roiPts_vec +#endif + , + double &distanceToFace); + + bool samePoint(const vpPoint &P1, const vpPoint &P2) const; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtFaceDepthNormal.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtFaceDepthNormal.h new file mode 100644 index 00000000..3cbbaf48 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtFaceDepthNormal.h @@ -0,0 +1,371 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Manage depth normal features for a particular face. + */ + +#ifndef VP_MBT_FACE_DEPTH_NORMAL_H +#define VP_MBT_FACE_DEPTH_NORMAL_H + +#include + +#include +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_COMMON) && defined(VISP_HAVE_PCL_SEGMENTATION) && defined(VISP_HAVE_PCL_FILTERS) +#include +#include +#endif + +#include +#include +#include + +#define DEBUG_DISPLAY_DEPTH_NORMAL 0 + +BEGIN_VISP_NAMESPACE +/** + * \ingroup group_mbt_faces + * \brief Manage depth normal features for a particular face. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using a MBT tracker in your applications, you may have a look at:
+ * + * - \ref tutorial-tracking-mb-generic + * - \ref tutorial-tracking-mb-generic-stereo + * - \ref tutorial-tracking-mb-generic-rgbd + * - \ref tutorial-tracking-mb-generic-apriltag-live + * - \ref tutorial-mb-generic-json + * - \ref tutorial-tracking-mb-generic-rgbd-Blender +*/ +class VISP_EXPORT vpMbtFaceDepthNormal +{ +public: + /*! + * How to compute the centroid of a face using depth feature. + */ + enum vpFaceCentroidType + { + GEOMETRIC_CENTROID, ///< Compute the geometric centroid + MEAN_CENTROID ///< Compute the mean centroid + }; + + /*! + * How to estimate the normal of a face using depth feature. + */ + enum vpFeatureEstimationType + { + ROBUST_FEATURE_ESTIMATION = 0, //!< Robust scheme to estimate the normal of the plane + ROBUST_SVD_PLANE_ESTIMATION = 1, //!< Use SVD and robust scheme to estimate the normal of the plane +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_COMMON) && defined(VISP_HAVE_PCL_SEGMENTATION) && defined(VISP_HAVE_PCL_FILTERS) + PCL_PLANE_ESTIMATION = 2 //!< Use PCL to estimate the normal of the plane +#endif + }; + + //! Camera intrinsic parameters + vpCameraParameters m_cam; + //! Flags specifying which clipping to used + unsigned int m_clippingFlag; + //! Distance for near clipping + double m_distFarClip; + //! Distance for near clipping + double m_distNearClip; + //! Pointer to the list of faces + vpMbHiddenFaces *m_hiddenFace; + //! Plane equation described in the object frame + vpPlane m_planeObject; + //! Polygon defining the face + vpMbtPolygon *m_polygon; + //! Scan line visibility + bool m_useScanLine; + + vpMbtFaceDepthNormal(); + vpMbtFaceDepthNormal(const vpMbtFaceDepthNormal &mbt_face); + virtual ~vpMbtFaceDepthNormal(); + vpMbtFaceDepthNormal &operator=(const vpMbtFaceDepthNormal &mbt_face); + + void addLine(vpPoint &p1, vpPoint &p2, vpMbHiddenFaces *const faces, vpUniRand &rand_gen, + int polygon = -1, std::string name = ""); + +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_COMMON) && defined(VISP_HAVE_PCL_SEGMENTATION) && defined(VISP_HAVE_PCL_FILTERS) + bool computeDesiredFeatures(const vpHomogeneousMatrix &cMo, unsigned int width, unsigned int height, + const pcl::PointCloud::ConstPtr &point_cloud, + vpColVector &desired_features, unsigned int stepX, unsigned int stepY +#if DEBUG_DISPLAY_DEPTH_NORMAL + , + vpImage &debugImage, std::vector > &roiPts_vec +#endif + , + const vpImage *mask = nullptr); +#endif + bool computeDesiredFeatures(const vpHomogeneousMatrix &cMo, unsigned int width, unsigned int height, + const std::vector &point_cloud, vpColVector &desired_features, + unsigned int stepX, unsigned int stepY +#if DEBUG_DISPLAY_DEPTH_NORMAL + , + vpImage &debugImage, std::vector > &roiPts_vec +#endif + , + const vpImage *mask = nullptr); + bool computeDesiredFeatures(const vpHomogeneousMatrix &cMo, unsigned int width, unsigned int height, + const vpMatrix &point_cloud, vpColVector &desired_features, + unsigned int stepX, unsigned int stepY +#if DEBUG_DISPLAY_DEPTH_NORMAL + , + vpImage &debugImage, std::vector > &roiPts_vec +#endif + , + const vpImage *mask = nullptr); + + void computeInteractionMatrix(const vpHomogeneousMatrix &cMo, vpMatrix &L, vpColVector &features); + + void computeVisibility(); + void computeVisibilityDisplay(); + + bool planeIsInvalid(const vpHomogeneousMatrix &cMo, double maxAngle); + + void computeNormalVisibility(double nx, double ny, double nz, const vpColVector ¢roid_point, + vpColVector &face_normal); +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_COMMON) && defined(VISP_HAVE_PCL_SEGMENTATION) && defined(VISP_HAVE_PCL_FILTERS) + void computeNormalVisibility(float nx, float ny, float nz, const pcl::PointXYZ ¢roid_point, + pcl::PointXYZ &face_normal); +#endif + void computeNormalVisibility(double nx, double ny, double nz, const vpHomogeneousMatrix &cMo, + const vpCameraParameters &camera, vpColVector &correct_normal, vpPoint ¢roid); + + void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false); + void display(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + const vpColor &col, unsigned int thickness = 1, bool displayFullModel = false); + + void displayFeature(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + double scale = 0.05, unsigned int thickness = 1); + void displayFeature(const vpImage &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + double scale = 0.05, unsigned int thickness = 1); + + std::vector > getFeaturesForDisplay(const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + double scale = 0.05); + + std::vector > getModelForDisplay(unsigned int width, unsigned int height, + const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + bool displayFullModel = false); + + inline bool isTracked() const { return m_isTrackedDepthNormalFace; } + + inline bool isVisible() const { return m_polygon->isvisible; } + + void setCameraParameters(const vpCameraParameters &camera); + + inline void setFaceCentroidMethod(const vpFaceCentroidType &method) { m_faceCentroidMethod = method; } + + inline void setFeatureEstimationMethod(const vpFeatureEstimationType &method) { m_featureEstimationMethod = method; } + + inline void setPclPlaneEstimationMethod(int method) { m_pclPlaneEstimationMethod = method; } + + inline void setPclPlaneEstimationRansacMaxIter(int maxIter) { m_pclPlaneEstimationRansacMaxIter = maxIter; } + + inline void setPclPlaneEstimationRansacThreshold(double threshold) + { + m_pclPlaneEstimationRansacThreshold = threshold; + } + + void setScanLineVisibilityTest(bool v); + + inline void setTracked(bool tracked) { m_isTrackedDepthNormalFace = tracked; } + +private: + class PolygonLine + { + public: + //! The first extremity + vpPoint *m_p1; + //! The second extremity + vpPoint *m_p2; + //! Polygon describing the line + vpMbtPolygon m_poly; + //! The first extremity clipped in the image frame + vpImagePoint m_imPt1; + //! The second extremity clipped in the image frame + vpImagePoint m_imPt2; + + PolygonLine() : m_p1(nullptr), m_p2(nullptr), m_poly(), m_imPt1(), m_imPt2() { } + + PolygonLine(const PolygonLine &polyLine) + : m_p1(nullptr), m_p2(nullptr), m_poly(polyLine.m_poly), m_imPt1(polyLine.m_imPt1), m_imPt2(polyLine.m_imPt2) + { + m_p1 = &m_poly.p[0]; + m_p2 = &m_poly.p[1]; + } + + PolygonLine &operator=(PolygonLine other) + { + swap(*this, other); + + return *this; + } + + void swap(PolygonLine &first, PolygonLine &second) + { + using std::swap; + swap(first.m_p1, second.m_p1); + swap(first.m_p2, second.m_p2); + swap(first.m_poly, second.m_poly); + swap(first.m_imPt1, second.m_imPt1); + swap(first.m_imPt2, second.m_imPt2); + } + }; + + template class Mat33 + { + public: + std::vector data; + + Mat33() : data(9) { } + + inline T operator[](const size_t i) const { return data[i]; } + + inline T &operator[](const size_t i) { return data[i]; } + + Mat33 inverse() const + { + // determinant + T det = data[0] * (data[4] * data[8] - data[7] * data[5]) - data[1] * (data[3] * data[8] - data[5] * data[6]) + + data[2] * (data[3] * data[7] - data[4] * data[6]); + T invdet = 1 / det; + + Mat33 minv; + minv[0] = (data[4] * data[8] - data[7] * data[5]) * invdet; + minv[1] = (data[2] * data[7] - data[1] * data[8]) * invdet; + minv[2] = (data[1] * data[5] - data[2] * data[4]) * invdet; + minv[3] = (data[5] * data[6] - data[3] * data[8]) * invdet; + minv[4] = (data[0] * data[8] - data[2] * data[6]) * invdet; + minv[5] = (data[3] * data[2] - data[0] * data[5]) * invdet; + minv[6] = (data[3] * data[7] - data[6] * data[4]) * invdet; + minv[7] = (data[6] * data[1] - data[0] * data[7]) * invdet; + minv[8] = (data[0] * data[4] - data[3] * data[1]) * invdet; + + return minv; + } + }; + +protected: + //! True if the face should be considered by the tracker + bool m_faceActivated; + //! Method to compute the face centroid for the current features + vpFaceCentroidType m_faceCentroidMethod; + //! Desired centroid (computed from the sensor) + vpPoint m_faceDesiredCentroid; + //! Face (normalized) normal (computed from the sensor) + vpPoint m_faceDesiredNormal; + //! Method to estimate the desired features + vpFeatureEstimationType m_featureEstimationMethod; + //! + bool m_isTrackedDepthNormalFace; + //! + bool m_isVisible; + //! + std::vector m_listOfFaceLines; + //! Plane equation described in the camera frame and updated with the + //! current pose + vpPlane m_planeCamera; + //! PCL plane estimation method + int m_pclPlaneEstimationMethod; + //! PCL pane estimation max number of iterations + int m_pclPlaneEstimationRansacMaxIter; + //! PCL plane estimation RANSAC threshold + double m_pclPlaneEstimationRansacThreshold; + //! + std::vector m_polygonLines; + +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_COMMON) && defined(VISP_HAVE_PCL_SEGMENTATION) && defined(VISP_HAVE_PCL_FILTERS) + bool computeDesiredFeaturesPCL(const pcl::PointCloud::ConstPtr &point_cloud_face, + vpColVector &desired_features, vpColVector &desired_normal, + vpColVector ¢roid_point); +#endif + void computeDesiredFeaturesRobustFeatures(const std::vector &point_cloud_face_custom, + const std::vector &point_cloud_face, const vpHomogeneousMatrix &cMo, + vpColVector &desired_features, vpColVector &desired_normal, + vpColVector ¢roid_point); + void computeDesiredFeaturesSVD(const std::vector &point_cloud_face, const vpHomogeneousMatrix &cMo, + vpColVector &desired_features, vpColVector &desired_normal, + vpColVector ¢roid_point); + void computeDesiredNormalAndCentroid(const vpHomogeneousMatrix &cMo, const vpColVector &desired_normal, + const vpColVector ¢roid_point); + + bool computePolygonCentroid(const std::vector &points, vpPoint ¢roid); + + void computeROI(const vpHomogeneousMatrix &cMo, unsigned int width, unsigned int height, + std::vector &roiPts +#if DEBUG_DISPLAY_DEPTH_NORMAL + , + std::vector > &roiPts_vec +#endif + ); + + void estimateFeatures(const std::vector &point_cloud_face, const vpHomogeneousMatrix &cMo, + vpColVector &x_estimated, std::vector &weights); + + void estimatePlaneEquationSVD(const std::vector &point_cloud_face, const vpHomogeneousMatrix &cMo, + vpColVector &plane_equation_estimated, vpColVector ¢roid); + + bool samePoint(const vpPoint &P1, const vpPoint &P2) const; +}; +END_VISP_NAMESPACE + +#ifdef VISP_HAVE_NLOHMANN_JSON +#include VISP_NLOHMANN_JSON(json.hpp) + +#if defined(__clang__) +// Mute warning : declaration requires an exit-time destructor [-Wexit-time-destructors] +// message : expanded from macro 'NLOHMANN_JSON_SERIALIZE_ENUM' +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_COMMON) && defined(VISP_HAVE_PCL_SEGMENTATION) && defined(VISP_HAVE_PCL_FILTERS) +NLOHMANN_JSON_SERIALIZE_ENUM(VISP_NAMESPACE_ADDRESSING vpMbtFaceDepthNormal::vpFeatureEstimationType, { + {VISP_NAMESPACE_ADDRESSING vpMbtFaceDepthNormal::ROBUST_FEATURE_ESTIMATION, "robust"}, + {VISP_NAMESPACE_ADDRESSING vpMbtFaceDepthNormal::ROBUST_SVD_PLANE_ESTIMATION, "robustSVD"}, + {VISP_NAMESPACE_ADDRESSING vpMbtFaceDepthNormal::PCL_PLANE_ESTIMATION, "pcl"} +}); +#else +NLOHMANN_JSON_SERIALIZE_ENUM(VISP_NAMESPACE_ADDRESSING vpMbtFaceDepthNormal::vpFeatureEstimationType, { + {VISP_NAMESPACE_ADDRESSING vpMbtFaceDepthNormal::ROBUST_FEATURE_ESTIMATION, "robust"}, + {VISP_NAMESPACE_ADDRESSING vpMbtFaceDepthNormal::ROBUST_SVD_PLANE_ESTIMATION, "robustSVD"} +}); +#endif + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +#endif + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtMeEllipse.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtMeEllipse.h new file mode 100644 index 00000000..ccf7cde4 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtMeEllipse.h @@ -0,0 +1,92 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Moving edges. + */ + +/*! + * \file vpMbtMeEllipse.h + * \brief Moving edges on an ellipse + */ + +#ifndef VP_MBT_ME_ELLIPSE_H +#define VP_MBT_ME_ELLIPSE_H + +#include +#include + +#include +#include + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +BEGIN_VISP_NAMESPACE +/*! + * \class vpMbtMeEllipse + * \ingroup group_mbt_features + * + * \brief Class that tracks an ellipse moving edges with specific capabilities for + * model-based tracking. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using a MBT tracker in your applications, you may have a look at:
+ * + * - \ref tutorial-tracking-mb-generic + * - \ref tutorial-tracking-mb-generic-stereo + * - \ref tutorial-tracking-mb-generic-rgbd + * - \ref tutorial-tracking-mb-generic-apriltag-live + * - \ref tutorial-mb-generic-json + * - \ref tutorial-tracking-mb-generic-rgbd-Blender +*/ +class VISP_EXPORT vpMbtMeEllipse : public vpMeEllipse +{ +public: + using vpMeTracker::display; + + void computeProjectionError(const vpImage &_I, double &_sumErrorRad, unsigned int &_nbFeatures, + const vpMatrix &SobelX, const vpMatrix &SobelY, bool display, unsigned int length, + unsigned int thickness); + + void initTracking(const vpImage &I, const vpImagePoint &ic, double n20_p, double n11_p, double n02_p, + bool doNotTrack, vpImagePoint *pt1 = nullptr, const vpImagePoint *pt2 = nullptr); + + void track(const vpImage &I); + void updateParameters(const vpImage &I, const vpImagePoint ¢er_p, double n20_p, double n11_p, + double n02_p); + +private: + void reSample(const vpImage &I); + void sample(const vpImage &I, bool doNotTrack = false) VP_OVERRIDE; + void suppressPoints(); +}; +END_VISP_NAMESPACE +#endif // #ifndef DOXYGEN_SHOULD_SKIP_THIS +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtMeLine.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtMeLine.h new file mode 100644 index 00000000..5b2e7f8d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtMeLine.h @@ -0,0 +1,129 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Implementation of a line used by the model-based tracker. + */ + +/*! + * \file vpMbtMeLine.h + * \brief Implementation of a line used by the model-based tracker. + */ + +#ifndef VP_MBT_ME_LINE_H +#define VP_MBT_ME_LINE_H + +#include +#include +#include +#include + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +BEGIN_VISP_NAMESPACE +/*! + * \class vpMbtMeLine + * \brief Implementation of a line used by the model-based tracker. + * \ingroup group_mbt_features + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using a MBT tracker in your applications, you may have a look at:
+ * + * - \ref tutorial-tracking-mb-generic + * - \ref tutorial-tracking-mb-generic-stereo + * - \ref tutorial-tracking-mb-generic-rgbd + * - \ref tutorial-tracking-mb-generic-apriltag-live + * - \ref tutorial-mb-generic-json + * - \ref tutorial-tracking-mb-generic-rgbd-Blender +*/ +class VISP_EXPORT vpMbtMeLine : public vpMeLine +{ +public: + int imin, imax; + int jmin, jmax; + double expecteddensity; + +public: + vpMbtMeLine(); + vpMbtMeLine(const vpMbtMeLine &meline); + vpMbtMeLine &operator=(const vpMbtMeLine &meline); + + void computeProjectionError(const vpImage &_I, double &_sumErrorRad, unsigned int &_nbFeatures, + const vpMatrix &SobelX, const vpMatrix &SobelY, bool display, unsigned int length, + unsigned int thickness); + + void display(const vpImage & /* I */, const vpColor &/* col */, unsigned int /* thickness */) { } + using vpMeTracker::display; + + /*! + * Get the a coefficient of the line corresponding to \f$ i \; cos(\theta) + j + * \; sin(\theta) - \rho = 0 \f$ + * + * \return : The a coefficient of the moving edge + */ + inline double get_a() const { return m_a; } + + /*! + * Get the a coefficient of the line corresponding to \f$ i \; cos(\theta) + j + * \; sin(\theta) - \rho = 0 \f$ + * + * \return : The b coefficient of the moving edge + */ + inline double get_b() const { return m_b; } + + /*! + * Get the a coefficient of the line corresponding to \f$ i \; cos(\theta) + j + * \; sin(\theta) - \rho = 0 \f$ + * + * \return : The c coefficient of the moving edge + */ + inline double get_c() const { return m_c; } + + void initTracking(const vpImage &I, const vpImagePoint &ip1, const vpImagePoint &ip2, double rho, + double theta, bool doNoTrack); + + void track(const vpImage &I); + + void updateParameters(const vpImage &I, double rho, double theta); + void updateParameters(const vpImage &I, const vpImagePoint &ip1, const vpImagePoint &ip2, double rho, + double theta); + +private: + virtual unsigned int seekExtremities(const vpImage &I) VP_OVERRIDE; + + void suppressPoints(const vpImage &I); + // void reSample(const vpImage &image); + void reSample(const vpImage &image, const vpImagePoint &ip1, const vpImagePoint &ip2); +}; +END_VISP_NAMESPACE +#endif // #ifndef DOXYGEN_SHOULD_SKIP_THIS + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtPolygon.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtPolygon.h new file mode 100644 index 00000000..c5a18483 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtPolygon.h @@ -0,0 +1,173 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Implements a polygon of the model used by the model-based tracker. + */ + +/*! + * \file vpMbtPolygon.h + *\brief Implements a polygon of the model used by the model-based tracker. + */ + +#ifndef vpMbtPolygon_HH +#define vpMbtPolygon_HH + +#include +#include +#include +#include +#include + +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpMbtPolygon + * + * \brief Implementation of a polygon of the model used by the model-based + * tracker. + * + * \ingroup group_mbt_faces + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using a MBT tracker in your applications, you may have a look at:
+ * + * - \ref tutorial-tracking-mb-generic + * - \ref tutorial-tracking-mb-generic-stereo + * - \ref tutorial-tracking-mb-generic-rgbd + * - \ref tutorial-tracking-mb-generic-apriltag-live + * - \ref tutorial-mb-generic-json + * - \ref tutorial-tracking-mb-generic-rgbd-Blender +*/ +class VISP_EXPORT vpMbtPolygon : public vpPolygon3D +{ +public: + //! Index of the polygon. Cannot be unsigned int because default value is + //! -1. + int index; + //! flag to specify whether the face is visible or not + bool isvisible; + //! flag to specify whether the face is appearing or not + bool isappearing; + //! Flag to specify if the visibility of the polygon depends also of the + //! current level of detail (LOD) + bool useLod; + //! Threshold for minimum line length in pixel to consider if the line is + //! visible or not in LOD case + double minLineLengthThresh; + //! Threshold for minimum polygon area in pixel to consider if the polygon + //! is visible or not in LOD case + double minPolygonAreaThresh; + //! Name of the polygon + std::string name; + //! Boolean that specify if the polygon has an orientation or not (mainly + //! used for cylinders) + bool hasOrientation; + +public: + vpMbtPolygon(); + vpMbtPolygon(const vpMbtPolygon &mbtp); + + /*! + * Get the index of the face. + * + * \return index : the index of the face. + */ + inline int getIndex() const { return index; } + + /*! + * Get the name of the face. + * + * \return Name of the face. + */ + inline std::string getName() const { return name; } + + inline bool isAppearing() const { return isappearing; } + inline bool isPolygonOriented() { return hasOrientation; } + virtual bool isVisible(const vpHomogeneousMatrix &cMo, double alpha, const bool &modulo = false, + const vpCameraParameters &cam = vpCameraParameters(), unsigned int width = 0, + unsigned int height = 0); + bool isVisible() const { return isvisible; } + + vpMbtPolygon &operator=(const vpMbtPolygon &mbtp); + + /*! + * Set the index of the face. + * + * \param i : the new index of the face. + */ + virtual inline void setIndex(int i) { index = i; } + + // Due to a doxygen warning include the sample code in the doc, we remove + // the inline and put the doc in the *.cpp file + void setLod(bool use_lod); + /*! + * Set the threshold for the minimum line length to be considered as visible + * in the LOD (level of detail) case. This threshold is only used when + * setLoD() is turned on. + * + * \param min_line_length : threshold for the minimum line length in pixel. + * When a single line that doesn't belong to a face is considered by the + * tracker, this line is tracked only if its length in pixel is greater than + * \e min_line_length. + * + * \sa setLoD() + */ + inline void setMinLineLengthThresh(double min_line_length) { this->minLineLengthThresh = min_line_length; } + /*! + * Set the minimum polygon area to be considered as visible in the LOD (level + * of detail) case. This threshold is only used when setLoD() is turned on. + * + * \param min_polygon_area : threshold for the minimum polygon area in pixel. + * When a face is considered by the tracker, this face is tracked only if its + * area in pixel is greater than \e min_polygon_area. + * + * \sa setLoD() + */ + inline void setMinPolygonAreaThresh(double min_polygon_area) { this->minPolygonAreaThresh = min_polygon_area; } + + /*! + * Set the name of the face. + * + * \param face_name : name of the face. + */ + inline void setName(const std::string &face_name) { this->name = face_name; } + + /*! + * Set if the polygon is oriented or not. + * + * \param oriented : True if the polygon is oriented, false otherwise. + */ + inline void setIsPolygonOriented(const bool &oriented) { this->hasOrientation = oriented; } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtTukeyEstimator.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtTukeyEstimator.h new file mode 100644 index 00000000..ecdbd0dd --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtTukeyEstimator.h @@ -0,0 +1,498 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Tukey M-estimator. + */ + +#ifndef VP_MBT_TUKEY_ESTIMATOR_H +#define VP_MBT_TUKEY_ESTIMATOR_H + +#include +#include +#include + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +BEGIN_VISP_NAMESPACE +/** + * \brief Tukey M-estimator. + * + * \tparam T + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using a MBT tracker in your applications, you may have a look at:
+ * + * - \ref tutorial-tracking-mb-generic + * - \ref tutorial-tracking-mb-generic-stereo + * - \ref tutorial-tracking-mb-generic-rgbd + * - \ref tutorial-tracking-mb-generic-apriltag-live + * - \ref tutorial-mb-generic-json + * - \ref tutorial-tracking-mb-generic-rgbd-Blender + */ + template class vpMbtTukeyEstimator +{ +public: + void MEstimator(const std::vector &residues, std::vector &weights, T NoiseThreshold); + void MEstimator(const vpColVector &residues, vpColVector &weights, double NoiseThreshold); + +private: + T getMedian(std::vector &vec); + void MEstimator_impl(const std::vector &residues, std::vector &weights, T NoiseThreshold); + void MEstimator_impl_simd(const std::vector &residues, std::vector &weights, T NoiseThreshold); + void psiTukey(const T sig, std::vector &x, std::vector &weights); + void psiTukey(const T sig, std::vector &x, vpColVector &weights); + + std::vector m_normres; + std::vector m_residues; +}; +END_VISP_NAMESPACE +#endif //#ifndef DOXYGEN_SHOULD_SKIP_THIS + +/* + * The code bellow previously in vpMbtTuckeyEstimator.cpp produced + * a link issue with MinGW-W64 x86_64-8.1.0-posix-seh-rt_v6-rev0 (g++ 8.1.0) + * libvisp_mbt.so.3.1.0: undefined reference to + * `vpMbtTukeyEstimator::MEstimator(std::vector > const&, std::vector + * >&, double)' + * Note that with the previous MinGW-W64 version x86_64-7.3.0-posix-seh-rt_v6-rev0 (g++ 7.3.0) + * the build succeed. + * + * To remove this link issue the solution was to move the content of vpMbtTuckeyEstimator.cpp + * before remove. + */ +#include +#include +#include + +#include + +#define USE_TRANSFORM 1 +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) && USE_TRANSFORM +#define HAVE_TRANSFORM 1 +#include +#endif + +#if defined __SSE2__ || defined _M_X64 || (defined _M_IX86_FP && _M_IX86_FP >= 2) +#include +#define VISP_HAVE_SSE2 1 + +#if defined __SSE3__ || (defined _MSC_VER && _MSC_VER >= 1500) +#include +#define VISP_HAVE_SSE3 1 +#endif +#if defined __SSSE3__ || (defined _MSC_VER && _MSC_VER >= 1500) +#include +#define VISP_HAVE_SSSE3 1 +#endif +#endif + +#if defined _WIN32 && defined(_M_ARM64) +# define _ARM64_DISTINCT_NEON_TYPES +# include +# include +# define VISP_HAVE_NEON 1 +#elif (defined(__ARM_NEON__) || defined (__ARM_NEON)) && defined(__aarch64__) +# include +# define VISP_HAVE_NEON 1 +#endif + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +#if HAVE_TRANSFORM + namespace +{ +// Check if std:c++14 or higher +#if ((__cplusplus >= 201402L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201402L))) +auto AbsDiff = [](const auto &a, const auto &b) { return std::fabs(a - b); }; +#else +template struct AbsDiff : public std::binary_function +{ + T operator()(const T a, const T b) const { return std::fabs(a - b); } +}; +#endif +} // namespace +#endif + +BEGIN_VISP_NAMESPACE +template class vpMbtTukeyEstimator; +template class vpMbtTukeyEstimator; + +#if VISP_HAVE_SSSE3 +namespace +{ +inline __m128 abs_ps(__m128 x) +{ + static const __m128 sign_mask = _mm_set1_ps(-0.f); // -0.f = 1 << 31 + return _mm_andnot_ps(sign_mask, x); +} +} // namespace +#endif + +template T vpMbtTukeyEstimator::getMedian(std::vector &vec) +{ + // Not the exact median when even number of elements + size_t index = static_cast(ceil(static_cast(vec.size()) / 2.0)) - 1; + std::nth_element(vec.begin(), vec.begin() + static_cast(index), vec.end()); + return vec[index]; +} + +// Without MEstimator_impl, error with g++4.6, ok with gcc 5.4.0 +// Ubuntu-12.04-Linux-i386-g++4.6-Dyn-RelWithDebInfo-dc1394-v4l2-X11-OpenCV2.3.1-lapack-gsl-Coin-jpeg-png-xml-pthread-OpenMP-dmtx-zbar-Wov-Weq-Moment: +// libvisp_mbt.so.3.1.0: undefined reference to +// `vpMbtTukeyEstimator::MEstimator(std::vector > const&, std::vector +// >&, double)' +template +void vpMbtTukeyEstimator::MEstimator_impl(const std::vector &residues, std::vector &weights, + const T NoiseThreshold) +{ + if (residues.empty()) { + return; + } + + m_residues = residues; + + T med = getMedian(m_residues); + m_normres.resize(residues.size()); + +#if HAVE_TRANSFORM +// Check if std:c++14 or higher +#if ((__cplusplus >= 201402L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201402L))) + std::transform(residues.begin(), residues.end(), m_normres.begin(), std::bind(AbsDiff, std::placeholders::_1, med)); +#else + std::transform(residues.begin(), residues.end(), m_normres.begin(), + std::bind(AbsDiff(), std::placeholders::_1, med)); +#endif +#else + for (size_t i = 0; i < m_residues.size(); i++) { + m_normres[i] = (std::fabs(residues[i] - med)); + } +#endif + + m_residues = m_normres; + T normmedian = getMedian(m_residues); + + // 1.48 keeps scale estimate consistent for a normal probability dist. + T sigma = static_cast(1.4826) * normmedian; // median Absolute Deviation + + // Set a minimum threshold for sigma + // (when sigma reaches the level of noise in the image) + if (sigma < NoiseThreshold) { + sigma = NoiseThreshold; + } + + psiTukey(sigma, m_normres, weights); +} + +template <> +inline void vpMbtTukeyEstimator::MEstimator_impl_simd(const std::vector &residues, + std::vector &weights, + float NoiseThreshold) +{ +#if VISP_HAVE_SSSE3 || VISP_HAVE_NEON + if (residues.empty()) { + return; + } + + m_residues = residues; + + float med = getMedian(m_residues); + m_normres.resize(residues.size()); + + size_t i = 0; +#if VISP_HAVE_SSSE3 + __m128 med_128 = _mm_set_ps1(med); +#else + float32x4_t med_128 = vdupq_n_f32(med); +#endif + + if (m_residues.size() >= 4) { + for (i = 0; i <= m_residues.size() - 4; i += 4) { +#if VISP_HAVE_SSSE3 + __m128 residues_128 = _mm_loadu_ps(residues.data() + i); + _mm_storeu_ps(m_normres.data() + i, abs_ps(_mm_sub_ps(residues_128, med_128))); +#else + float32x4_t residues_128 = vld1q_f32(residues.data() + i); + vst1q_f32(m_normres.data() + i, vabsq_f32(vsubq_f32(residues_128, med_128))); +#endif + } + } + + for (; i < m_residues.size(); i++) { + m_normres[i] = (std::fabs(residues[i] - med)); + } + + m_residues = m_normres; + float normmedian = getMedian(m_residues); + + // 1.48 keeps scale estimate consistent for a normal probability dist. + float sigma = 1.4826f * normmedian; // median Absolute Deviation + + // Set a minimum threshold for sigma + // (when sigma reaches the level of noise in the image) + if (sigma < NoiseThreshold) { + sigma = NoiseThreshold; + } + + psiTukey(sigma, m_normres, weights); +#else + (void)residues; + (void)weights; + (void)NoiseThreshold; +#endif +} + +/*! + * \relates vpMbtTukeyEstimator + */ +template <> +inline void vpMbtTukeyEstimator::MEstimator_impl_simd(const std::vector &residues, + std::vector &weights, + double NoiseThreshold) +{ +#if VISP_HAVE_SSSE3 || VISP_HAVE_NEON + if (residues.empty()) { + return; + } + + m_residues = residues; + + double med = getMedian(m_residues); + m_normres.resize(residues.size()); + +#if HAVE_TRANSFORM +// Check if std:c++14 or higher +#if ((__cplusplus >= 201402L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201402L))) + std::transform(residues.begin(), residues.end(), m_normres.begin(), std::bind(AbsDiff, std::placeholders::_1, med)); +#else + std::transform(residues.begin(), residues.end(), m_normres.begin(), + std::bind(AbsDiff(), std::placeholders::_1, med)); +#endif +#else + for (size_t i = 0; i < m_residues.size(); i++) { + m_normres[i] = (std::fabs(residues[i] - med)); + } +#endif + + m_residues = m_normres; + double normmedian = getMedian(m_residues); + + // 1.48 keeps scale estimate consistent for a normal probability dist. + double sigma = 1.4826 * normmedian; // median Absolute Deviation + + // Set a minimum threshold for sigma + // (when sigma reaches the level of noise in the image) + if (sigma < NoiseThreshold) { + sigma = NoiseThreshold; + } + + psiTukey(sigma, m_normres, weights); +#else + (void)residues; + (void)weights; + (void)NoiseThreshold; +#endif +} + +/*! + * \relates vpMbtTukeyEstimator + */ +template <> +inline void vpMbtTukeyEstimator::MEstimator(const std::vector &residues, std::vector &weights, + float NoiseThreshold) +{ +#if defined(VISP_HAVE_SIMDLIB) + bool checkSimd = vpCPUFeatures::checkSSSE3() || vpCPUFeatures::checkNeon(); +#else + bool checkSimd = vpCPUFeatures::checkSSSE3(); +#endif +#if !VISP_HAVE_SSSE3 && !VISP_HAVE_NEON + checkSimd = false; +#endif + + if (checkSimd) + MEstimator_impl_simd(residues, weights, NoiseThreshold); + else + MEstimator_impl(residues, weights, NoiseThreshold); +} + +/*! + * \relates vpMbtTukeyEstimator + */ +template <> +inline void vpMbtTukeyEstimator::MEstimator(const std::vector &residues, std::vector &weights, + double NoiseThreshold) +{ +#if defined(VISP_HAVE_SIMDLIB) + bool checkSimd = vpCPUFeatures::checkSSSE3() || vpCPUFeatures::checkNeon(); +#else + bool checkSimd = vpCPUFeatures::checkSSSE3(); +#endif +#if !VISP_HAVE_SSSE3 && !VISP_HAVE_NEON + checkSimd = false; +#endif + + if (checkSimd) + MEstimator_impl_simd(residues, weights, NoiseThreshold); + else + MEstimator_impl(residues, weights, NoiseThreshold); +} + +/*! + * Consider Tukey influence function. + */ +template void vpMbtTukeyEstimator::psiTukey(const T sig, std::vector &x, vpColVector &weights) +{ + double C = static_cast(sig) * 4.6851; + + // Here we consider that sig cannot be equal to 0 + for (unsigned int i = 0; i < static_cast(x.size()); i++) { + double xi = static_cast(x[i]) / C; + xi *= xi; + + if (xi > 1.) { + weights[i] = 0; + } + else { + xi = 1 - xi; + xi *= xi; + weights[i] = xi; + } + } +} + +/*! + * \relates vpMbtTukeyEstimator + */ +template <> +inline void vpMbtTukeyEstimator::MEstimator(const vpColVector &residues, vpColVector &weights, + double NoiseThreshold) +{ + if (residues.size() == 0) { + return; + } + + m_residues.resize(0); + m_residues.reserve(residues.size()); + m_residues.insert(m_residues.end(), &residues.data[0], &residues.data[residues.size()]); + + double med = getMedian(m_residues); + + m_normres.resize(residues.size()); + for (size_t i = 0; i < m_residues.size(); i++) { + m_normres[i] = std::fabs(residues[static_cast(i)] - med); + } + + m_residues = m_normres; + double normmedian = getMedian(m_residues); + + // 1.48 keeps scale estimate consistent for a normal probability dist. + double sigma = 1.4826 * normmedian; // median Absolute Deviation + + // Set a minimum threshold for sigma + // (when sigma reaches the level of noise in the image) + if (sigma < NoiseThreshold) { + sigma = NoiseThreshold; + } + + psiTukey(sigma, m_normres, weights); +} + +/*! + * \relates vpMbtTukeyEstimator + */ +template <> +inline void vpMbtTukeyEstimator::MEstimator(const vpColVector &residues, vpColVector &weights, + double NoiseThreshold) +{ + if (residues.size() == 0) { + return; + } + + m_residues.resize(0); + m_residues.reserve(residues.size()); + for (unsigned int i = 0; i < residues.size(); i++) { + m_residues.push_back(static_cast(residues[i])); + } + + float med = getMedian(m_residues); + + m_normres.resize(residues.size()); + for (size_t i = 0; i < m_residues.size(); i++) { + m_normres[i] = static_cast(std::fabs(static_cast(residues[static_cast(i)]) - med)); + } + + m_residues = m_normres; + float normmedian = getMedian(m_residues); + + // 1.48 keeps scale estimate consistent for a normal probability dist. + float sigma = 1.4826f * normmedian; // median Absolute Deviation + float noise_threshold = static_cast(NoiseThreshold); + + // Set a minimum threshold for sigma + // (when sigma reaches the level of noise in the image) + if (sigma < noise_threshold) { + sigma = noise_threshold; + } + + psiTukey(sigma, m_normres, weights); +} + +/*! + * Consider Tukey influence function. + */ +template void vpMbtTukeyEstimator::psiTukey(const T sig, std::vector &x, std::vector &weights) +{ + T C = static_cast(4.6851) * sig; + weights.resize(x.size()); + T one = static_cast(1.); + + // Here we consider that sig cannot be equal to 0 + for (size_t i = 0; i < x.size(); i++) { + T xi = x[i] / C; + xi *= xi; + + if (xi > one) { + weights[i] = 0; + } + else { + xi = 1 - xi; + xi *= xi; + weights[i] = xi; + } + } +} +END_VISP_NAMESPACE +#endif //#ifndef DOXYGEN_SHOULD_SKIP_THIS + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtXmlGenericParser.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtXmlGenericParser.h new file mode 100644 index 00000000..42e1dd39 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/mbt/vpMbtXmlGenericParser.h @@ -0,0 +1,180 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Load XML Parameter for Model Based Tracker. + */ + +/*! + * \file vpMbtXmlGenericParser.h + * \brief Parse an Xml file to extract configuration parameters of a mbtConfig + * object. + */ + +#ifndef _vpMbtXmlGenericParser_h_ +#define _vpMbtXmlGenericParser_h_ + +#include + +#if defined(VISP_HAVE_PUGIXML) +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpMbtXmlGenericParser + \brief Parse an Xml file to extract configuration parameters of a mbtConfig + object. + \ingroup group_mbt_xml_parser + + Data parser for the model-based tracker. + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in using a MBT tracker in your applications, you may have a look at:
+ + - \ref tutorial-tracking-mb-generic + - \ref tutorial-tracking-mb-generic-stereo + - \ref tutorial-tracking-mb-generic-rgbd + - \ref tutorial-tracking-mb-generic-apriltag-live + - \ref tutorial-mb-generic-json + - \ref tutorial-tracking-mb-generic-rgbd-Blender + +*/ +class VISP_EXPORT vpMbtXmlGenericParser +{ +public: + enum vpParserType + { + EDGE_PARSER = 1 << 0, /*!< Parser for model-based tracking using moving + edges features. */ + KLT_PARSER = 1 << 1, /*!< Parser for model-based tracking using KLT features. */ + DEPTH_NORMAL_PARSER = 1 << 2, /*!< Parser for model-based tracking using + depth normal features. */ + DEPTH_DENSE_PARSER = 1 << 3, /*!< Parser for model-based tracking using + depth dense features. */ + PROJECTION_ERROR_PARSER = 0 /*!< Parser for projection error computation parameters. */ + }; + +public: + vpMbtXmlGenericParser(int type = EDGE_PARSER); + virtual ~vpMbtXmlGenericParser(); + + double getAngleAppear() const; + double getAngleDisappear() const; + + void getCameraParameters(vpCameraParameters &cam) const; + + void getEdgeMe(vpMe &ecm) const; + + unsigned int getDepthDenseSamplingStepX() const; + unsigned int getDepthDenseSamplingStepY() const; + + vpMbtFaceDepthNormal::vpFeatureEstimationType getDepthNormalFeatureEstimationMethod() const; + int getDepthNormalPclPlaneEstimationMethod() const; + int getDepthNormalPclPlaneEstimationRansacMaxIter() const; + double getDepthNormalPclPlaneEstimationRansacThreshold() const; + unsigned int getDepthNormalSamplingStepX() const; + unsigned int getDepthNormalSamplingStepY() const; + + double getFarClippingDistance() const; + bool getFovClipping() const; + + unsigned int getKltBlockSize() const; + double getKltHarrisParam() const; + unsigned int getKltMaskBorder() const; + unsigned int getKltMaxFeatures() const; + double getKltMinDistance() const; + unsigned int getKltPyramidLevels() const; + double getKltQuality() const; + unsigned int getKltWindowSize() const; + + bool getLodState() const; + double getLodMinLineLengthThreshold() const; + double getLodMinPolygonAreaThreshold() const; + + double getNearClippingDistance() const; + + void getProjectionErrorMe(vpMe &me) const; + + unsigned int getProjectionErrorKernelSize() const; + + bool hasFarClippingDistance() const; + bool hasNearClippingDistance() const; + + void parse(const std::string &filename); + + void setAngleAppear(const double &aappear); + void setAngleDisappear(const double &adisappear); + + void setCameraParameters(const vpCameraParameters &cam); + + void setDepthDenseSamplingStepX(unsigned int stepX); + void setDepthDenseSamplingStepY(unsigned int stepY); + + void setDepthNormalFeatureEstimationMethod(const vpMbtFaceDepthNormal::vpFeatureEstimationType &method); + void setDepthNormalPclPlaneEstimationMethod(int method); + void setDepthNormalPclPlaneEstimationRansacMaxIter(int maxIter); + void setDepthNormalPclPlaneEstimationRansacThreshold(double threshold); + void setDepthNormalSamplingStepX(unsigned int stepX); + void setDepthNormalSamplingStepY(unsigned int stepY); + + void setEdgeMe(const vpMe &ecm); + + void setFarClippingDistance(const double &fclip); + + void setKltBlockSize(const unsigned int &bs); + void setKltHarrisParam(const double &hp); + void setKltMaskBorder(const unsigned int &mb); + void setKltMaxFeatures(const unsigned int &mF); + void setKltMinDistance(const double &mD); + void setKltPyramidLevels(const unsigned int &pL); + void setKltQuality(const double &q); + void setKltWindowSize(const unsigned int &w); + + void setNearClippingDistance(const double &nclip); + + void setProjectionErrorMe(const vpMe &me); + void setProjectionErrorKernelSize(const unsigned int &size); + + void setVerbose(bool verbose); + +private: + vpMbtXmlGenericParser(const vpMbtXmlGenericParser &); // noncopyable + vpMbtXmlGenericParser &operator=(const vpMbtXmlGenericParser &); // + + // PIMPL idiom + class Impl; + Impl *m_impl; +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpMe.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpMe.h new file mode 100644 index 00000000..209330c6 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpMe.h @@ -0,0 +1,698 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Moving edges. + */ + +/*! + * \file vpMe.h + * \brief Moving edges + */ + +#ifndef VP_ME_H +#define VP_ME_H + +#include +#include +#include +#include + +#ifdef VISP_HAVE_NLOHMANN_JSON +#include VISP_NLOHMANN_JSON(json.hpp) +#endif + +BEGIN_VISP_NAMESPACE +/*! + * \class vpMe + * \ingroup module_me + * + * This class defines predetermined masks for moving-edges (ME) sites and holds moving edges + * tracking parameters. + * + * To know more about moving-edges and their usage for object tracking, follow \ref tutorial-tracking-me. + * + * JSON serialization + * + * Since ViSP 3.6.0, if ViSP is build with \ref soft_tool_json 3rd-party we introduce JSON serialization capabilities for vpMe. + * The following sample code shows how to save moving-edges settings in a file named `me.json` + * and reload the values from this JSON file. + * \code + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * #if defined(VISP_HAVE_NLOHMANN_JSON) + * std::string filename = "me.json"; + * { + * vpMe me; + * me.setLikelihoodThresholdType(vpMe::NORMALIZED_THRESHOLD); + * me.setThreshold(20); // Value in range [0 ; 255] + * me.setThresholdMarginRatio(-1.); // Deactivate automatic thresholding + * me.setMinThreshold(-1.); // Deactivate automatic thresholding + * me.setMaskNumber(180); + * me.setMaskSign(0); + * me.setMu1(0.5); + * me.setMu2(0.5); + * me.setNbTotalSample(0); + * me.setPointsToTrack(200); + * me.setRange(5); + * me.setStrip(2); + * + * std::ofstream file(filename); + * const nlohmann::json j = me; + * file << j; + * file.close(); + * } + * { + * std::ifstream file(filename); + * const nlohmann::json j = nlohmann::json::parse(file); + * vpMe me; + * me = j; + * file.close(); + * std::cout << "Read moving-edges settings from " << filename << ":" << std::endl; + * me.print(); + * } + * #endif + * } + * \endcode + * If you build and execute the sample code, it will produce the following output: + * \code{.unparsed} + * Read moving-edges settings from me.json: + * + * Moving edges settings + * + * Size of the convolution masks....5x5 pixels + * Number of masks..................180 + * Query range +/- J................5 pixels + * Likelihood threshold type........normalized + * Likelihood threshold.............20 + * Likelihood margin ratio..........unused + * Minimum likelihood threshold.....unused + * Contrast tolerance +/-...........50% and 50% + * Sample step......................10 pixels + * Strip............................2 pixels + * Min sample step..................4 pixels + * \endcode + * + * The content of the `me.json` file is the following: + * \code{.unparsed} + * $ cat me.json + * {"maskSign":0,"maskSize":5,"minSampleStep":4.0,"mu":[0.5,0.5],"nMask":180,"ntotalSample":0,"pointsToTrack":200, + * "range":5,"sampleStep":10.0,"strip":2,"threshold":20.0,"thresholdMarginRatio":-1.0,"minThreshold":-1.0,"thresholdType":"normalized"} + * \endcode + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using a ME tracker in your application, you may have a look at:
+ * + * - \ref tutorial-tracking-me +*/ +class VISP_EXPORT vpMe +{ +public: + /*! + * Type of likelihood threshold to use. + */ + typedef enum + { + //! Old likelihood ratio threshold (to be avoided). + OLD_THRESHOLD = 0, + //! Easy-to-use normalized likelihood threshold corresponding to the minimal luminance contrast to consider + //! with values in [0 ; 255]. + NORMALIZED_THRESHOLD = 1 + } vpLikelihoodThresholdType; + +public: + /*! + * Default constructor. + */ + vpMe(); + + /*! + * Copy constructor. + */ + vpMe(const vpMe &me); + + /*! + * Destructor. + */ + virtual ~vpMe(); + + /*! + * Copy operator. + */ + vpMe &operator=(const vpMe &me); + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + /*! + * Move operator. + */ + vpMe &operator=(const vpMe &&me); +#endif + + /*! + * Check sample step wrt min value. + * \param[in,out] sample_step : When this value is lower than the min sample step value, + * it is modified to the min sample step value. + */ + void checkSamplestep(double &sample_step) + { + if (sample_step < m_min_samplestep) { + sample_step = m_min_samplestep; + } + } + + /*! + * Return the angle step. + * + * \return Value of angle step. + */ + inline unsigned int getAngleStep() const { return m_anglestep; } + + /*! + * Return the range used during the initialization step. A negative value means + * that the default value is used by the different ME primitives. + */ + inline int getInitRange() const { return m_init_range; } + + /*! + * Get the matrix of the mask. + * + * \return the value of mask. + */ + inline vpMatrix *getMask() const { return m_mask; } + + /*! + * Return the number of mask applied to determine the object contour. The + * number of mask determines the precision of the normal of the edge for + * every sample. If precision is 2deg, then there are 360/2 = 180 masks. + * + * \return the current number of mask. + */ + inline unsigned int getMaskNumber() const { return m_mask_number; } + + /*! + * Return the mask sign. + * + * \return Value of mask_sign. + */ + inline int getMaskSign() const { return m_mask_sign; } + + /*! + * Return the actual mask size (in pixel) used to compute the image gradient + * and determine the object contour. The mask size defines the size of the + * convolution mask used to detect an edge. + * + * \return the current mask size. + */ + inline unsigned int getMaskSize() const { return m_mask_size; } + + /*! + * Get the minimum allowed sample step. Useful to specify a lower bound when + * the sample step is changed. + * + * \return Value of min sample step. + */ + inline double getMinSampleStep() const { return m_min_samplestep; } + + /*! + * Get the minimum image contrast allowed to detect a contour. + * + * \return Value of mu1. + */ + inline double getMu1() const { return m_mu1; } + + /*! + * Get the maximum image contrast allowed to detect a contour. + * + * \return Value of mu2. + */ + inline double getMu2() const { return m_mu2; } + + /*! + * Get how many discretized points are used to track the feature. + * + * \return Value of ntotal_sample. + */ + inline int getNbTotalSample() const { return m_ntotal_sample; } + + /*! + * Return the number of points to track. + * + * \return Value of points_to_track. + */ + inline int getPointsToTrack() const { return m_points_to_track; } + + /*! + * Return the seek range on both sides of the reference pixel. + * + * \return Value of range. + */ + inline unsigned int getRange() const { return m_range; } + + /*! + * Get the minimum distance in pixel between two discretized points. + * + * \return Value of sample_step. + */ + inline double getSampleStep() const { return m_sample_step; } + + /*! + * Get the number of pixels that are ignored around the image borders. + * + * \return the value of strip. + */ + inline int getStrip() const { return m_strip; } + + /*! + * Return the likelihood threshold used to determine if the moving edge is valid or not. + * + * \return Value of the likelihood threshold. + * + * \sa setThreshold(), getLikelihoodThresholdType(), setLikelihoodThresholdType() + */ + inline double getThreshold() const { return m_threshold; } + + /*! + * Return the ratio of the initial contrast to use to initialize the contrast threshold of the \b vpMeSite. + * + * \return Value of the likelihood threshold ratio, between 0 and 1. A negative value means it is not activated. + * + * \sa setThresholdMarginRatio(), setMinThreshold(), getMinThreshold(), getLikelihoodThresholdType(), setLikelihoodThresholdType() + */ + inline double getThresholdMarginRatio() const { return m_thresholdMarginRatio; } + + /*! + * Return the minimum contrast threshold of the \b vpMeSite that can be used when using the + * automatic threshold computation. + * + * \return Value of the minimum contrast threshold. + * + * \sa setThresholdMarginRatio(), getThresholdMarginRatio(), setMinThreshold(), getLikelihoodThresholdType(), setLikelihoodThresholdType() + */ + inline double getMinThreshold() const { return m_minThreshold; } + + /*! + * \brief Indicates if the contrast threshold of the vpMeSite is automatically computed. + * + * \return true The contrast threshold of the vpMeSite is automatically computed. + * \return false The vpMe::m_threshold is used as a global threshold. + */ + inline bool getUseAutomaticThreshold() const { return m_useAutomaticThreshold; } + + /*! + * Return the selected choice for the likelihood threshold. + * + * \return The likelihood threshold type to consider. + * + * \sa setLikelihoodThresholdType(), setThreshold(), getThreshold() + */ + inline vpLikelihoodThresholdType getLikelihoodThresholdType() const { return m_likelihood_threshold_type; } + + /*! + * Initialise the array of matrices with the defined size and the number of + * matrices to create. + */ + void initMask(); // convolution masks - offset computation + + /*! + * Print using std::cout moving edges settings. + */ + void print(); + + /*! + * Set the angle step. + * + * \param anglestep : New angle step value. + */ + void setAngleStep(const unsigned int &anglestep) { m_anglestep = anglestep; } + + /*! + * Set the range used during the initialization step. A negative value means + * that the default value is used by the different ME primitives. + * + * \param initRange : New range, or a negative value to use the default ranges. + */ + inline void setInitRange(const int &initRange) { m_init_range = initRange; } + + /*! + * Set the number of mask applied to determine the object contour. The number + * of mask determines the precision of the normal of the edge for every + * sample. If precision is 2deg, then there are 360/2 = 180 masks. + * + * \param mask_number : The number of mask. + */ + void setMaskNumber(const unsigned int &mask_number); + + /*! + * Set the mask sign. + * + * \param mask_sign : New mask sign. + */ + void setMaskSign(const int &mask_sign) { m_mask_sign = mask_sign; } + + /*! + * Set the mask size (in pixel) used to compute the image gradient and + * determine the object contour. The mask size defines the size of the + * convolution mask used to detect an edge. + * + * \param mask_size : New mask size. + */ + void setMaskSize(const unsigned int &mask_size); + + /*! + * Set the minimum allowed sample step. Useful to specify a lower bound when + * the sample step is changed. + * + * \param min_samplestep : New minimum sample step. + */ + void setMinSampleStep(const double &min_samplestep) { m_min_samplestep = min_samplestep; } + + /*! + * Set the minimum image contrast allowed to detect a contour. + * + * \param mu_1 : new mu1. + */ + void setMu1(const double &mu_1) { this->m_mu1 = mu_1; } + + /*! + * Set the maximum image contrast allowed to detect a contour. + * + * \param mu_2 : New mu2. + */ + void setMu2(const double &mu_2) { this->m_mu2 = mu_2; } + + /*! + * Set how many discretized points are used to track the feature. + * + * \param ntotal_sample : New total number of sample. + */ + void setNbTotalSample(const int &ntotal_sample) { m_ntotal_sample = ntotal_sample; } + + /*! + * Set the number of points to track. + * + * \param points_to_track : New number of points to track. + * + * \warning This method is useful only for the vpMeNurbsTracker. + */ + void setPointsToTrack(const int &points_to_track) { m_points_to_track = points_to_track; } + + /*! + * Set the seek range on both sides of the reference pixel. + * + * \param range : New range. + */ + void setRange(const unsigned int &range) { m_range = range; } + + /*! + * Set the minimum distance in pixel between two discretized points. + * + * \param sample_step : New sample_step. + */ + void setSampleStep(const double &sample_step) { m_sample_step = sample_step; } + + /*! + * Set the number of pixels that are ignored around the image borders. + * + * \param strip : New strip. + */ + void setStrip(const int &strip) { m_strip = strip; } + + /*! + * Set the likelihood threshold used to determined if the moving edge is valid or not. + * + * \param threshold : Threshold to consider. Two different cases need to be considered depending on the likelihood threshold type that + * can be set using setLikelihoodThresholdType() or get using getLikelihoodThresholdType(). The default likelihood threshold type + * is set to OLD_THRESHOLD to keep compatibility with ViSP previous releases, but it is recommended to use rather the NORMALIZED_THRESHOLD + * type like in the following sample code. When doing so, the threshold is more easy to set since it corresponds to the minimal luminance + * contrast to consider with values in range [0 ; 255]. + * + * \code + * vpMe me; + * me.setLikelihoodThresholdType(NORMALIZED_THRESHOLD); + * me.setThreshold(20); // Value in range [0 ; 255] + * me.setThresholdMarginRatio(-1.); // Deactivate automatic thresholding + * me.setMinThreshold(-1.); // Deactivate automatic thresholding + * \endcode + * + * When the likelihood threshold type is set by default to OLD_THRESHOLD like in the next example, values of the likelihood threshold + * depends on the minimal luminance contrast to consider and the mask size that can be set using setMaskSize() and retrieved using getMaskSize(). + * \code + * vpMe me; // By default the constructor set the threshold type to OLD_THRESHOLD + * me.setThreshold(10000); // Value that depends on the minimal luminance contrast to consider and the mask size. + * me.setThresholdMarginRatio(-1.); // Deactivate automatic thresholding + * me.setMinThreshold(-1.); // Deactivate automatic thresholding + * \endcode + * The previous sample code is similar to the next one: + * \code + * vpMe me; + * me.setLikelihoodThresholdType(OLD_THRESHOLD); + * me.setThreshold(10000); // Value that depends on the minimal luminance contrast to consider and the mask size. + * me.setThresholdMarginRatio(-1.); // Deactivate automatic thresholding + * me.setMinThreshold(-1.); // Deactivate automatic thresholding + * \endcode + * \sa getThreshold(), getLikelihoodThresholdType() + */ + void setThreshold(const double &threshold) { m_threshold = threshold; } + + /*! + * Set the the ratio of the initial contrast to use to initialize the contrast threshold of the \b vpMeSite. + * + * \param thresholdMarginRatio Value of the likelihood threshold ratio, between 0 and 1. A negative value means that this + * the automatic thresholding is not activated. The threshold of a vpMeSite is computed from the convolution value + * computed when initializing the vpMeTracker multiplied by this ratio. + * + * \sa getThresholdMarginRatio(), setMinThreshold(), getMinThreshold(), getLikelihoodThresholdType(), setLikelihoodThresholdType() + */ + inline void setThresholdMarginRatio(const double &thresholdMarginRatio) + { + if (thresholdMarginRatio > 1.) { + throw(vpException(vpException::badValue, "Threshold margin ratio must be between 0 and 1 if you want to use automatic threshold computation, or negative otherwise")); + } + m_thresholdMarginRatio = thresholdMarginRatio; + m_useAutomaticThreshold = (m_thresholdMarginRatio > 0) && (m_minThreshold > 0); + } + + /*! + * Set the minimum value of the contrast threshold of the \b vpMeSite. + * + * \param minThreshold Minimum value of the contrast threshold. A negative value means that automatic + * thresholding is not activated. + * + * \sa getMinThreshold(), setThresholdMarginRatio(), getThresholdMarginRatio(), getLikelihoodThresholdType(), setLikelihoodThresholdType() + */ + inline void setMinThreshold(const double &minThreshold) + { + m_minThreshold = minThreshold; + m_useAutomaticThreshold = (m_thresholdMarginRatio > 0) && (m_minThreshold > 0); + } + + /*! + * Set the likelihood threshold type used to determine if the moving edge is valid or not. + * + * \param likelihood_threshold_type : Likelihood threshold type. It is recommended to use NORMALIZED_THRESHOLD and set the threshold + * using setThreshold() with a value corresponding to the minimal luminance contrast to consider that can handle values in range [0 ; 255]. + * + * \sa setThreshold() + */ + void setLikelihoodThresholdType(const vpLikelihoodThresholdType likelihood_threshold_type) { m_likelihood_threshold_type = likelihood_threshold_type; } + +private: + vpLikelihoodThresholdType m_likelihood_threshold_type; //!< Likelihood threshold type + //! Old likelihood ratio threshold (to be avoided) or easy-to-use normalized threshold: minimal contrast + double m_threshold; + double m_thresholdMarginRatio; //!< The ratio of the initial contrast to use to initialize the contrast threshold of the vpMeSite. + double m_minThreshold; //!< The minimum moving-edge threshold in grey level used when the contrast threshold of the vpMeSites is automatically computed. + bool m_useAutomaticThreshold; //!< Set to true if the user wants to automatically compute the vpMeSite contrast thresholds, false if the user wants to use a global threshold. + double m_mu1; //!< Contrast continuity parameter (left boundary) + double m_mu2; //!< Contrast continuity parameter (right boundary) + double m_min_samplestep; + unsigned int m_anglestep; + int m_mask_sign; + int m_init_range; //! Seek range during the initialization step - a negative value leads to using the default values. + unsigned int m_range; //! Seek range - on both sides of the reference pixel + double m_sample_step; //! Distance between sampled points in pixels + int m_ntotal_sample; + int m_points_to_track; //!< Expected number of points to track + //! Convolution masks' size in pixels (masks are square) + unsigned int m_mask_size; + //! The number of convolution masks available for tracking ; defines resolution + unsigned int m_mask_number; + //! Strip: defines a "security strip" such that when seeking extremities + //! cannot return a new extremity which is too close to the frame borders + int m_strip; + vpMatrix *m_mask; //!< Array of matrices defining the different masks (one for every angle step). + +#ifdef VISP_HAVE_NLOHMANN_JSON + /*! + * @brief Convert a vpMe object to a JSON representation. + * + * @param j : Resulting json object. + * @param me : The object to convert. + */ + friend void to_json(nlohmann::json &j, const vpMe &me); + + /** + * @brief Retrieve a vpMe object from a JSON representation + * + * JSON content (key: type): + * - thresholdType: either "old" or "normalized", vpMe::getLikelihoodThresholdType() + * - threshold: double, vpMe::setThreshold() + * - thresholdMarginRatio: double, vpMe::setThresholdMarginRatio() + * - minThreshold: double, vpMe::setMinThreshold() + * - mu : [double, double], vpMe::setMu1, vpMe::setMu2() + * - minSampleStep: double, vpMe::setMinSampleStep() + * - angleStep: double, vpMe::setAngleStep() + * - sampleStep: double, vpMe::setSampleStep() + * - range: int, vpMe::setRange() + * - ntotal_sample: int, vpMe::setNbTotalSample() + * - pointsToTrack: int, vpMe::setPointsToTrack() + * - maskSize: int, vpMe::setMaskSize() + * - nMask: int, vpMe::setMaskNumber() + * - maskSign: int, vpMe::setMaskSign() + * - strip: int, vpMe::setStrip() + * + * Example: + * \code{.json} + * { + * "angleStep": 1, + * "maskSign": 0, + * "maskSize": 5, + * "minSampleStep": 4.0, + * "mu": [ + * 0.5, + * 0.5 + * ], + * "nMask": 180, + * "ntotal_sample": 0, + * "pointsToTrack": 500, + * "range": 7, + * "sampleStep": 4.0, + * "strip": 2, + * "thresholdType": "normalized", + * "threshold": 20.0, + * "thresholdMarginRatio": 0.75, + * "minThreshold": 20.0, + * } + * \endcode + * + * @param j JSON representation to convert + * @param me converted object + */ + friend void from_json(const nlohmann::json &j, vpMe &me); +#endif +}; + +#ifdef VISP_HAVE_NLOHMANN_JSON + +#if defined(__clang__) +// Mute warning : declaration requires an exit-time destructor [-Wexit-time-destructors] +// message : expanded from macro 'NLOHMANN_JSON_SERIALIZE_ENUM' +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +NLOHMANN_JSON_SERIALIZE_ENUM(vpMe::vpLikelihoodThresholdType, { + {vpMe::vpLikelihoodThresholdType::OLD_THRESHOLD, "old"}, + {vpMe::vpLikelihoodThresholdType::NORMALIZED_THRESHOLD, "normalized"} +}); + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +inline void to_json(nlohmann::json &j, const vpMe &me) +{ + j = { + {"thresholdType", me.getLikelihoodThresholdType()}, + {"threshold", me.getThreshold()}, + {"thresholdMarginRatio", me.getThresholdMarginRatio()}, + {"minThreshold", me.getMinThreshold()}, + {"mu", {me.getMu1(), me.getMu2()}}, + {"minSampleStep", me.getMinSampleStep()}, + {"sampleStep", me.getSampleStep()}, + {"initRange", me.getInitRange()}, + {"range", me.getRange()}, + {"ntotalSample", me.getNbTotalSample()}, + {"pointsToTrack", me.getPointsToTrack()}, + {"maskSize", me.getMaskSize()}, + {"nMask", me.getMaskNumber()}, + {"maskSign", me.getMaskSign()}, + {"strip", me.getStrip()} + }; +} + +inline void from_json(const nlohmann::json &j, vpMe &me) +{ + if (j.contains("thresholdType")) { + me.setLikelihoodThresholdType(j.value("thresholdType", me.getLikelihoodThresholdType())); + } + me.setThreshold(j.value("threshold", me.getThreshold())); + me.setThresholdMarginRatio(j.value("thresholdMarginRatio", me.getThresholdMarginRatio())); + me.setMinThreshold(j.value("minThreshold", me.getMinThreshold())); + + if (j.contains("mu")) { + std::vector mus = j.at("mu").get>(); + assert((mus.size() == 2)); + me.setMu1(mus[0]); + me.setMu2(mus[1]); + } + me.setMinSampleStep(j.value("minSampleStep", me.getMinSampleStep())); + me.setSampleStep(j.value("sampleStep", me.getSampleStep())); + me.setRange(j.value("range", me.getRange())); + me.setInitRange(j.value("initRange", me.getInitRange())); + me.setNbTotalSample(j.value("ntotalSample", me.getNbTotalSample())); + me.setPointsToTrack(j.value("pointsToTrack", me.getPointsToTrack())); + me.setMaskSize(j.value("maskSize", me.getMaskSize())); + me.setMaskSign(j.value("maskSign", me.getMaskSign())); + me.setStrip(j.value("strip", me.getStrip())); + if (j.contains("angleStep") && j.contains("nMask")) { + std::cerr << "both angle step and number of masks are defined, number of masks will take precedence" << std::endl; + me.setMaskNumber(j["nMask"]); + } + else if (j.contains("angleStep")) { + me.setAngleStep(j["angleStep"]); + } + else if (j.contains("nMask")) { + me.setMaskNumber(j["nMask"]); + } + me.initMask(); +} + +#endif + +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpMeEllipse.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpMeEllipse.h new file mode 100644 index 00000000..f9454634 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpMeEllipse.h @@ -0,0 +1,648 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +/*! + * \file vpMeEllipse.h + * \brief Moving edges on an ellipse + */ + +#ifndef VP_ME_ELLIPSE_H +#define VP_ME_ELLIPSE_H + +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17) +#include +#endif + +BEGIN_VISP_NAMESPACE + +/*! + * \class vpMeEllipse + * \ingroup module_me + * + * \brief Class that tracks an ellipse or a circle using moving edges. + * + * In this class, an ellipse is defined as the set of image points \f$ (u,v) \f$ + * (for more information about the image frame see the vpImagePoint + * documentation) that satisfy the homogeneous equation: + * + * \f[ K_0 u^2 + K_1 v^2 + 2K_2 u v + 2K_3 u + 2K_4 v + K5 = 0 \f] + * with \f$K_0 = n_{02}\f$, \f$K_1 = n_{20}\f$, \f$K_2 = -n_{11}\f$, etc. + * (see Eq. (25) of Chaumette TRO 2004 paper \cite Chaumette04a) + * + * If \f$ K_0 = K1 \f$ and \f$ K_2 = 0 \f$, the ellipse is a circle. + * + * The six parameters are stored in the private attribute K. + * + * An ellipse is also represented thanks to five parameters that are + * the center \f$ (uc,vc) \f$ of the ellipse and either: + * - three normalized moments \f$ n_{ij} \f$ of order 2, + * - or the semi major axis \f$ A \f$, the semi minor + * axis \f$ B \f$ and the ellipse orientation \f$ E \in [-\pi/2;\pi/2] \f$ + * defined by the angle between the major axis and the u axis of the image frame. + * + * For more details, see \cite Chaumette04a. + * The following figure illustrates these parameters. + * + * \image html vpMeEllipse.gif + * + *

Tutorials & Examples

+ * + * Tutorials
+ * + * The example below available in tutorial-me-ellipse-tracker.cpp and described + * in \ref tutorial-tracking-me, section \ref tracking_me_ellipse and section \ref tracking_me_circle shows how to + * use this class. + * + * \include tutorial-me-ellipse-tracker.cpp + * +*/ +class VISP_EXPORT vpMeEllipse : public vpMeTracker +{ +public: + /*! + * Basic constructor that calls the constructor of the class vpMeTracker. + */ + vpMeEllipse(); + + /*! + * Copy constructor. + */ + vpMeEllipse(const vpMeEllipse &me_ellipse); + + /*! + * Destructor. + */ + virtual ~vpMeEllipse() VP_OVERRIDE; + + /*! + * Copy operator. + */ + vpMeEllipse &operator=(const vpMeEllipse &me_ellipse); + + /*! + * Display the ellipse or arc of ellipse + * + * \warning To effectively display the ellipse a call to vpDisplay::flush() is needed. + * + * \param I : Image in which the ellipse appears. + * \param col : Color of the displayed ellipse. + * \param thickness : Thickness of the drawing. + */ + void display(const vpImage &I, const vpColor &col, unsigned int thickness = 1); + + /*! + * Display the ellipse or arc of ellipse + * + * \warning To effectively display the ellipse a call to vpDisplay::flush() is needed. + * + * \param I : Image in which the ellipse appears. + * \param col : Color of the displayed ellipse. + * \param thickness : Thickness of the drawing. + */ + void display(const vpImage &I, const vpColor &col, unsigned int thickness = 1); + + /*! + * Gets the second order normalized centered moment \f$ n_{ij} \f$ + * as a 3-dim vector containing \f$ n_{20}, n_{11}, n_{02} \f$ + * such as \f$ n_{ij} = \mu_{ij}/m_{00} \f$ + * + * \return The 3-dim vector containing \f$ n_{20}, n_{11}, n_{02} \f$. + * + * \sa getCenter(), get_ABE(), getArea() + */ + inline vpColVector get_nij() const + { + vpColVector nij(3); + const unsigned int index_0 = 0; + const unsigned int index_1 = 1; + const unsigned int index_2 = 2; + nij[index_0] = m_n20; + nij[index_1] = m_n11; + nij[index_2] = m_n02; + + return nij; + } + + /*! + * Gets the ellipse parameters as a 3-dim vector containing \f$ A, B, E \f$. + * + * \return The 3-dim vector containing \f$ A, B, E \f$ corresponding respectively to + * the semi major axis, the semi minor axis and the angle in rad made by the major axis + * and the u axis of the image frame \f$ (u,v) \f$, \f$ e \in [-\pi/2;pi/2] \f$. + * + * \sa getCenter(), get_nij(), getArea() + */ + inline vpColVector get_ABE() const + { + vpColVector ABE(3); + const unsigned int index_0 = 0; + const unsigned int index_1 = 1; + const unsigned int index_2 = 2; + ABE[index_0] = m_a; + ABE[index_1] = m_b; + ABE[index_2] = m_e; + + return ABE; + } + + /*! + * Gets the area of the ellipse corresponding also to the zero + * order moment of the ellipse. + * + * \return The ellipse area. + * + * \sa getCenter(), get_nij(), get_ABE() + */ + inline double getArea() const { return m_m00; } + + /*! + * Gets the center of the ellipse. + * + * \sa get_nij(), get_ABE(), getArea() + */ + inline vpImagePoint getCenter() const { return m_iPc; } + + /*! + * \return Expected number of moving edges to track along the ellipse. + */ + unsigned int getExpectedDensity() const { return m_expectedDensity; } + + /*! + * \return Number of valid edges tracked along the ellipse. + */ + unsigned int getNumberOfGoodPoints() const { return m_numberOfGoodPoints; } + + /*! + * Gets the first endpoint of the ellipse arc (corresponding to alpha1, + * not alphamin) when an arc is tracked. + * + * \sa getSecondEndpoint() + */ + inline vpImagePoint getFirstEndpoint() const { return m_iP1; } + + /*! + * Gets the highest \f$ alpha \f$ angle of the moving edges tracked + * \f$ \alpha_{max} \in [\alpha_min;\alpha_2] \f$. + * + * \sa getSmallestAngle() + */ + inline double getHighestAngle() const { return m_alphamax; } + + /*! + * Gets the second endpoint of the ellipse arc (corresponding to alpha2, + * not alphamax) when an arc is tracked. + * + * \sa getFirstEndpoint() + */ + inline vpImagePoint getSecondEndpoint() const { return m_iP2; } + + /*! + * Gets the smallest \f$ alpha \f$ angle of the moving edges tracked + * \f$ \alpha_{min} \in [\alpha_1;\alpha_2] \f$. + * + * \sa getHighestAngle() + */ + inline double getSmallestAngle() const { return m_alphamin; } + + /*! + * Initialize the tracking of an ellipse or an arc of an ellipse when \e trackArc is set to true. + * Ask the user to click on five points located on the ellipse to be tracked. + * + * \warning The points should be selected as far as possible from each other. + * When an arc of an ellipse is tracked, it is recommended to select the 5 points clockwise. + * + * \param I : Image in which the ellipse appears. + * \param trackCircle : When true, track a circle, when false track an ellipse. + * \param trackArc : When true track an arc of the ellipse/circle. In that case, first and + * last points specify the extremities of the arc (clockwise). + * When false track the complete ellipse/circle. + */ + void initTracking(const vpImage &I, bool trackCircle = false, bool trackArc = false); + + /*! + * Initialize the tracking of an ellipse or an arc of an ellipse when \e trackArc is set to true. + * If \b ips is set, use the contained points to initialize the ME if there are some, or initialize + * by clicks the ME and \b ips will contained the clicked points. + * If \b ips is not set, call the method vpMeEllipse::initTracking(const vpImage&, bool, bool). + * + * \sa vpMeEllipse::initTracking() + * + * \warning The points should be selected as far as possible from each other. + * When an arc of an ellipse is tracked, it is recommended to select the 5 points clockwise. + * + * \param I : Image in which the ellipse appears. + * \param opt_ips: If set, either a vector that contains the vpImagePoint to use or will be filled with the clicked + * points. + * \param trackCircle : When true, track a circle, when false track an ellipse. + * \param trackArc : When true track an arc of the ellipse/circle. In that case, first and + * last points specify the extremities of the arc (clockwise). + * When false track the complete ellipse/circle. + */ +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17) + void initTracking(const vpImage &I, std::optional> &opt_ips, bool trackCircle = false, bool trackArc = false); +#else + void initTracking(const vpImage &I, std::vector *opt_ips, bool trackCircle = false, bool trackArc = false); +#endif + +/*! + * Initialize the tracking of an ellipse/circle or an arc of an ellipse/circle when \e trackArc is set to true. + * The ellipse/circle is defined thanks to a vector of image points. + * + * \warning It is mandatory to use at least five image points to estimate the + * ellipse parameters while three points are needed to estimate the circle parameters. + * \warning The image points should be selected as far as possible from each other. + * When an arc of an ellipse/circle is tracked, it is recommended to select the 5/3 points clockwise. + * + * \param I : Image in which the ellipse/circle appears. + * \param iP : A vector of image points belonging to the ellipse/circle edge used to + * initialize the tracking. + * \param trackCircle : When true, track a circle, when false track an ellipse. + * \param trackArc : When true track an arc of the ellipse/circle. In that case, first and + * last points specify the extremities of the arc (clockwise). + * When false track the complete ellipse/circle. + */ + void initTracking(const vpImage &I, const std::vector &iP, bool trackCircle = false, + bool trackArc = false); + + /*! + * Initialize the tracking of an ellipse/circle or an arc of an ellipse/circle when arc extremities are given. + * The ellipse/circle is defined by the vector containing the coordinates of its center and the three second order + * centered normalized moments \f$ n_ij \f$. Without setting the arc extremities with + * parameters \e pt1 and \e pt2, the complete ellipse/circle is considered. When extremities + * are set, we consider an ellipse/circle arc defined clockwise from first extremity to second extremity. + * + * \param I : Image in which the ellipse appears. + * \param param : Vector with the five parameters \f$(u_c, v_c, n_{20}, n_{11}, n_{02})\f$ defining the ellipse + * (expressed in pixels). + * \param pt1 : Image point defining the first extremity of the arc or nullptr to track a complete ellipse. + * \param pt2 : Image point defining the second extremity of the arc or nullptr to track a complete ellipse. + * \param trackCircle : When true enable tracking of a circle, when false the tracking of an ellipse. + */ + void initTracking(const vpImage &I, const vpColVector ¶m, vpImagePoint *pt1 = nullptr, + const vpImagePoint *pt2 = nullptr, bool trackCircle = false); + + /*! + * Print the parameters \f$ K = {K_0, ..., K_5} \f$, the coordinates of the + * ellipse center, the normalized moments, and the A, B, E parameters. + */ + void printParameters() const; + + /*! + * Set the two endpoints of the ellipse arc when an arc is tracked. + */ + void setEndpoints(const vpImagePoint &pt1, const vpImagePoint &pt2) + { + m_iP1 = pt1; + m_iP2 = pt2; + } + + /*! + * Set the threshold for the weights in the robust estimation of the + * ellipse parameters. + * If the weight of a point is below this threshold, this one is removed from + * the list of tracked meSite. + * Value must be between 0 (never rejected) and 1 (always rejected). + * + * \param threshold : The new value of the threshold. + */ + void setThresholdRobust(double threshold) + { + if (threshold < 0) { + m_thresholdWeight = 0; + } + else if (threshold > 1) { + m_thresholdWeight = 1; + } + else { + m_thresholdWeight = threshold; + } + } + + /*! + * Track a set of MEs along an ellipse or a circle. + * The number of valid tracked MEs is obtained from getNumberOfGoodPoints(). + */ + void track(const vpImage &I); + + /*! + * Display the ellipse or the arc of ellipse thanks to the ellipse parameters. + * + * \param I : The image used as background. + * \param center : Center of the ellipse. + * \param A : Semi major axis of the ellipse. + * \param B : Semi minor axis of the ellipse. + * \param E : Angle made by the major axis and the u axis of the image frame + * \f$ (u,v) \f$ (in rad). + * \param smallalpha : Smallest \f$ alpha \f$ angle in rad (0 for a complete ellipse). + * \param highalpha : Highest \f$ alpha \f$ angle in rad (2 \f$ \Pi \f$ for a complete ellipse). + * \param color : Color used to display the ellipse. + * \param thickness : Thickness of the drawings. + * + * \sa vpDisplay::displayEllipse() + */ + static void displayEllipse(const vpImage &I, const vpImagePoint ¢er, const double &A, const double &B, + const double &E, const double &smallalpha, const double &highalpha, + const vpColor &color = vpColor::green, unsigned int thickness = 1); + + /*! + * Display the ellipse or the arc of ellipse thanks to the ellipse parameters. + * + * \param I : The image used as background. + * \param center : Center of the ellipse + * \param A : Semi-major axis of the ellipse. + * \param B : Semi-minor axis of the ellipse. + * \param E : Angle made by the major axis and the u axis of the image frame + * \f$ (u,v) \f$ (in rad) + * \param smallalpha : Smallest \f$ alpha \f$ angle in rad (0 for a complete ellipse) + * \param highalpha : Highest \f$ alpha \f$ angle in rad (\f$ 2 \Pi \f$ for a complete ellipse) + * \param color : Color used to display th lines. + * \param thickness : Thickness of the drawings. + * + * \sa vpDisplay::displayEllipse() + */ + static void displayEllipse(const vpImage &I, const vpImagePoint ¢er, const double &A, const double &B, + const double &E, const double &smallalpha, const double &highalpha, + const vpColor &color = vpColor::green, unsigned int thickness = 1); + +#ifdef VISP_BUILD_DEPRECATED_FUNCTIONS + // Marked deprecated since they override vpMeTracker::display(). Warning detected by mingw64 + /*! + * \deprecated Use rather displayEllipse(). + */ + VP_DEPRECATED static void display(const vpImage &I, const vpImagePoint ¢er, const double &A, const double &B, + const double &E, const double &smallalpha, const double &highalpha, + const vpColor &color = vpColor::green, unsigned int thickness = 1); + + /*! + * \deprecated Use rather displayEllipse(). + */ + VP_DEPRECATED static void display(const vpImage &I, const vpImagePoint ¢er, const double &A, const double &B, + const double &E, const double &smallalpha, const double &highalpha, + const vpColor &color = vpColor::green, unsigned int thickness = 1); +#endif + +protected: + /*! Parameters of the ellipse satisfying the homogeneous equation : + * \f[ K_0 u^2 + K_1 v^2 + 2K_2 uv + 2K_3u + 2K_4v + K5 = 0 \f] + */ + vpColVector m_K; + //! The coordinates of the ellipse center. + vpImagePoint m_iPc; + //! \f$ a \f$ is the semi major axis of the ellipse. + double m_a; + //! \f$ b \f$ is the semi minor axis of the ellipse. + double m_b; + /*! \f$ e \in [-\pi/2;\pi/2] \f$ is the angle made by the major axis + * and the u axis of the image frame \f$ (u,v) \f$. + */ + double m_e; + + /*! The coordinates of the first endpoint of the ellipse arc + * corresponding to angle \f$ \alpha_1 \f$ + */ + vpImagePoint m_iP1; + /*! The coordinates of the second endpoint of the ellipse arc + * corresponding to angle \f$ \alpha_2 \f$ + */ + vpImagePoint m_iP2; + /*! The angle \f$ \alpha_1 \in [-\pi;\pi] \f$ on the ellipse corresponding + * to the first endpoint. Its value is 0 for tracking a complete ellipse + */ + double m_alpha1; + /*! The angle \f$ \alpha_2 \in [\alpha_1;\alpha_1+2\pi]\f$ on the ellipse + * corresponding to the second endpoint. Its value is \f$ 2 \pi \f$ for + * tracking a complete ellipse + */ + double m_alpha2; + //! Value of cos(e). + double m_ce; + //! Value of sin(e). + double m_se; + //! Stores the value in increasing order of the \f$ alpha \f$ angle on the ellipse for each vpMeSite. + std::list m_angleList; + //! Ellipse area + double m_m00; + + //! Threshold on the weights for the robust least square. + double m_thresholdWeight; + + /*! The smallest angle \f$ \alpha_{min} \in [\alpha_1;\alpha_2]\f$ + * of the current moving edge list + */ + double m_alphamin; + /*! The highest angle \f$ \alpha_{max} \in [\alpha_{min};\alpha_2]\f$ + * of the current moving edge list + */ + double m_alphamax; + //! Value of u coordinate of iPc + double m_uc; + //! Value of v coordinate of iPc + double m_vc; + //! Second order centered and normalized moments \f$ n_{20} \f$ + double m_n20; + //! Second order centered and normalized moments \f$ n_{11} \f$ + double m_n11; + //! Second order centered and normalized moments \f$ n_{02} \f$ + double m_n02; + //! Expected number of points to track along the ellipse. + unsigned int m_expectedDensity; + //! Number of correct points tracked along the ellipse. + unsigned int m_numberOfGoodPoints; + //! Track a circle (true) or an ellipse (false). + bool m_trackCircle; + //! Track an arc of ellipse/circle (true) or a complete one (false). + bool m_trackArc; + //! Epsilon value used to check if arc angles are the same + double m_arcEpsilon; + + /*! + * Computes the length of the semi major axis \f$ a \f$, the length of the + * semi minor axis \f$ b \f$, and \f$ e \f$ that is the angle + * made by the major axis and the u axis of the image frame \f$ (u,v) \f$. + * They are computed from the normalized moments $ \f$ n_{ij} \f$. + */ + void computeAbeFromNij(); + + /*! + * Compute the angle of a point on the ellipse wrt the ellipse major axis. + * \param pt : Image point on the ellipse. + * \return The computed angle. + */ + double computeAngleOnEllipse(const vpImagePoint &pt) const; + + /*! + * Computes the parameters \f$ K = {K_0, ..., K_5} \f$ from the center of + * the ellipse and the normalized moments \f$ n_{ij} \f$. The parameters + * \f$ K \f$ are such that \f$ K0 = n02, K1 = n20 \f$, etc. as in Eq (25) + * of Chaumette 2004 TRO paper. + */ + void computeKiFromNij(); + + /*! + * Computes the normalized moments \f$ n_{ij} \f$ from the \f$ A, B, E \f$ + * parameters as in Eq (24) of Chaumette 2004 TRO paper after simplifications + * to deal with the case cos(e) = 0.0 + */ + void computeNijFromAbe(); + + /*! + * Compute the coordinates of a point on an ellipse from its angle with respect + * to the main orientation of the ellipse. + * + * \param angle : Angle on the ellipse with respect to its major axis. + * \param iP : Image point on the ellipse. + */ + void computePointOnEllipse(const double angle, vpImagePoint &iP); + + /*! + * Computes the \f$ \theta \f$ angle that represents the angle between the + * tangent to the curve and the u axis. This angle is used for tracking the + * vpMeSite. + * + * \param iP : The point belonging to the ellipse where the angle is computed. + */ + double computeTheta(const vpImagePoint &iP) const; + + /*! + * Computes the \f$ \theta \f$ angle that represents the angle between the + * tangent to the curve and the u axis. This angle is used for tracking the + * vpMeSite. + * + * \param[in] u : The coordinate along the x-axis (horizontal) of the point belonging to the ellipse where the angle is + * computed. + * \param[in] v : The coordinate along the y-axis (vertical) of the point belonging to the ellipse where the angle is + * computed. + */ + double computeTheta(double u, double v) const; + + /*! + * Computes the coordinates of the ellipse center, the normalized + * moments \f$ n_{ij} \f$, the length of the semi major axis \f$ a \f$, the + * length of the semi minor axis \f$ b \f$, and \f$ e \f$ that is the angle + * made by the major axis and the u axis of the image frame \f$ (u,v) \f$. + * + * All those computations are made from the parameters \f$ K ={K_0, ..., K_5} \f$ + * so that \f$ K_0 u^2 + K1 v^2 + 2 K2 u v + 2 K3 u + 2 K4 v + K5 = 0 \f$. + */ + void getParameters(); + + /*! + * Least squares method to compute the circle/ ellipse to which the points belong. + * + * \param I : Image in which the circle/ellipse appears (useful just to get + * its number of rows and columns... + * \param iP : A vector of points belonging to the circle/ellipse. + */ + void leastSquare(const vpImage &I, const std::vector &iP); + + /*! + * Robust least squares method to compute the ellipse to which the vpMeSite + * belong. Manage also the lists of vpMeSite and corresponding angles, + * and update the expected density of points. + * + * \param I : Image where tracking is done (useful just to get its number + * of rows and columns... + */ + unsigned int leastSquareRobust(const vpImage &I); + + /*! + * Robust least squares method to compute the ellipse to which the vpMeSite + * belong, when we track a circle. Manage also the lists of vpMeSite and corresponding angles, + * and update the expected density of points. + * + * \param um Half width of the image. + * \param vm Half height of the image. + * \param k Count the number of tracked MEs. + * \param w Weights computed by robust estimation. + */ + void leastSquareRobustCircle(const double &um, const double &vm, unsigned int &k, vpColVector &w); + + /*! + * Robust least squares method to compute the ellipse to which the vpMeSite + * belong, when we track an ellipse. Manage also the lists of vpMeSite and corresponding angles, + * and update the expected density of points. + * + * \param um Half width of the image. + * \param vm Half height of the image. + * \param k Count the number of tracked MEs. + * \param w Weights computed by robust estimation. + */ + void leastSquareRobustEllipse(const double &um, const double &vm, unsigned int &k, vpColVector &w); + + /*! + * Seek along the ellipse or arc of ellipse its two extremities to try + * recovering lost points. Try also to complete the parts with no tracked points. + * + * \param I : Image in which the ellipse appears. + * + * \return The function returns the number of points added to the list. + * + * \exception vpTrackingException::initializationError : Moving edges not + * initialized. + */ + unsigned int plugHoles(const vpImage &I); + + /*! + * Construct a list of vpMeSite moving edges at a particular sampling + * step between the two extremities. The two extremities are defined by + * the points with the smallest and the biggest \f$ alpha \f$ angle. + * + * \param I : Image in which the ellipse appears. + * \param doNotTrack : If true, moving-edges are not tracked. + * + * \exception vpTrackingException::initializationError : Moving edges not + * initialized. + */ + virtual void sample(const vpImage &I, bool doNotTrack = false) VP_OVERRIDE; + + /*! + * Compute the \f$ theta \f$ angle for each vpMeSite. + * + * \note The \f$ theta \f$ angle is useful during the tracking part. + */ + void updateTheta(); +}; + +END_VISP_NAMESPACE + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpMeLine.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpMeLine.h new file mode 100644 index 00000000..9c2a22eb --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpMeLine.h @@ -0,0 +1,580 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Moving edges. + */ + +/*! + * \file vpMeLine.h + * \brief Moving edges on a line +*/ + +#ifndef VP_ME_LINE_H +#define VP_ME_LINE_H + +#include +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE + +/*! + * \class vpMeLine + * + * \ingroup module_me + * + * \brief Class that tracks in an image a line moving edges. + * + * In this class the line is defined by its equation in the \f$ (i,j) = + * (line,column) \f$ image plane. Two kinds of parametrization are available to + * describe a 2D line. The first one corresponds to the following + * equation + * + * \f[ ai + bj + c = 0 \f] + * + * where \f$ i \f$ and \f$ j \f$ are the coordinates of the points + * belonging to the line. The line features are \f$ (a, b, c) \f$. + * + * The second way to write the line equation is to consider polar coordinates + * \f[ i \; cos(\theta) + j \; sin(\theta) - \rho = 0 \f] + * + * where \f$ i \f$ and \f$ j \f$ are still the coordinates of the + * points belonging to the line. But now the line features are \f$ + * (\rho, \theta) \f$. The computation of \f$ \rho \f$ and \f$ \theta + * \f$ is easy thanks to \f$ (a, b, c) \f$. + * + * \f[ \theta = arctan(b/a) \f] + * \f[ \rho = -c/\sqrt{a^2+b^2} \f] + * + * The value of \f$ \theta \f$ is between \f$ -\pi \f$ and \f$ \pi + * \f$. And the value of \f$ \rho \f$ can be positive or negative. The + * conventions to find the right values of the two features are + * illustrated in the following pictures. + * + * \image html vpMeLine.gif + * \image latex vpMeLine.ps * width=10cm + * + * The angle \f$\theta\f$ is computed thanks to the direction of the + * arrow. The arrow points to the side of the line which is darker. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * + * The example below available in tutorial-me-line-tracker.cpp and described + * in \ref tutorial-tracking-me shows how to use this class. + * + * \include tutorial-me-line-tracker.cpp + * + * The code below shows how to use this class. + * \code + * #include + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpImage I(240, 320); + * + * // Fill the image with a black rectangle + * I = 0u; + * for (int i = 100; i < 180; i ++) { + * for (int j = 120; j < 250; j ++) { + * I[i][j] = 255; + * } + * } + * + * // Set the moving-edges tracker parameters + * vpMe me; + * me.setRange(25); + * me.setLikelihoodThresholdType(vpMe::NORMALIZED_THRESHOLD); + * me.setThreshold(20); + * me.setSampleStep(10); + * + * // Initialize the moving-edges line tracker parameters + * vpMeLine line; + * line.setMe(&me); + * + * // Initialize the location of the vertical line to track + * vpImagePoint ip1, ip2; // Two points belonging to the line to track + * ip1.set_i( 120 ); + * ip1.set_j( 119 ); + * ip2.set_i( 170 ); + * ip2.set_j( 122 ); + * + * line.initTracking(I, ip1, ip2); + * + * while ( 1 ) + * { + * // ... Here the code to read or grab the next image. + * + * // Track the line. + * line.track(I); + * } + * return 0; + * } + * \endcode + * + * \note It is possible to display the line as an overlay. For that you + * must use the display function of the class vpMeLine. +*/ +class VISP_EXPORT vpMeLine : public vpMeTracker +{ +public: + /*! + * Basic constructor that calls the constructor of the class vpMeTracker. + */ + vpMeLine(); + + /*! + * Copy constructor. + */ + vpMeLine(const vpMeLine &meline); + + /*! + * Destructor. + */ + virtual ~vpMeLine() VP_OVERRIDE; + + /*! + * Copy operator. + */ + vpMeLine &operator=(const vpMeLine &meline); + + /*! + * Display me line. + * + * \warning To effectively display the line a call to vpDisplay::flush() is needed. + * + * \param I : Image in which the line appears. + * \param color : Color of the displayed line. Note that a moving edge + * that is considered as an outlier is displayed in green. + * \param thickness : Drawings thickness. + */ + void display(const vpImage &I, const vpColor &color, unsigned int thickness = 1); + + /*! + * Display me line. + * + * \warning To effectively display the line a call to vpDisplay::flush() is needed. + * + * \param I : Image in which the line appears. + * \param color : Color of the displayed line. Note that a moving edge + * that is considered as an outlier is displayed in green. + * \param thickness : Drawings thickness. + */ + void display(const vpImage &I, const vpColor &color, unsigned int thickness = 1); + + /*! + * Gets the parameters a,b,c of the line with equation a*x + b*y + c = 0 as a + * 3-dim vector. + * + * \return 3-dim vector containing a, b and c line parameters. + */ + vpColVector get_ABC() const + { + vpColVector abc(3); + abc[0] = m_a; + abc[1] = m_b; + abc[2] = m_c; + return abc; + } + + /*! + * Get the extremities of the line. These two points strictly belong to + * the line. They are the projection of m_Pext[2] on the line + * + * \param ip1 : Coordinates of the first extremity. + * \param ip2 : Coordinates of the second extremity. + */ + void getExtremities(vpImagePoint &ip1, vpImagePoint &ip2) const; + + /*! + * Returns the value of \f$\rho\f$, the distance between the origin and the point on the line + * that belongs to the normal to the line passing through the origin. + * + * Depending on the convention described at the beginning of this + * class, \f$\rho\f$ is signed. + * + * \sa getTheta(), getRhoTheta() + */ + inline double getRho() const + { + return m_rho; + } + + /*! + * Returns the value of \f$\rho, \theta\f$ as a 2-dim vector. + * - \f$\rho\f$ is the distance between the origin and the point on the line + * that belongs to the normal to the line passing through the origin. + * - \f$\theta\f$ is the angle in radian between the vertical axis and the + * normal to the line. + * + * Depending on the convention described at the beginning of this + * class, \f$\rho\f$ is signed. + * + * \sa getRho(), getTheta() + */ + inline vpColVector getRhoTheta() const + { + vpColVector rho_theta(2); + rho_theta[0] = m_rho; + rho_theta[1] = m_theta; + return rho_theta; + } + + /*! + * Returns the value of the angle \f$\theta\f$ between \f$-\pi\f$ and \f$\pi\f$. + * + * \sa getRho(), getRhoTheta() + */ + inline double getTheta() const + { + return m_theta; + } + + /*! + * Initialization of the tracking. Ask the user to click on two points + * from the line to track. + * + * \param I : Image in which the line appears. + */ + void initTracking(const vpImage &I); + + /*! + * Initialization of the tracking. The line is defined thanks to the + * coordinates of two points. + * + * \param I : Image in which the line appears. + * \param ip1 : Coordinates of the first point. + * \param ip2 : Coordinates of the second point. + */ + void initTracking(const vpImage &I, const vpImagePoint &ip1, const vpImagePoint &ip2); + + /*! + * Track the line in the image I. + * + * \param I : Image in which the line appears. + */ + void track(const vpImage &I); + + /*! + * Display of a moving line thanks to its equation parameters and its + * extremities. + * + * \param I : The image used as background. + * \param PExt1 : First extremity + * \param PExt2 : Second extremity + * \param A : Parameter a of the line equation a*i + b*j + c = 0 + * \param B : Parameter b of the line equation a*i + b*j + c = 0 + * \param C : Parameter c of the line equation a*i + b*j + c = 0 + * \param color : Color used to display the line. + * \param thickness : Thickness of the line. + */ + static void displayLine(const vpImage &I, const vpMeSite &PExt1, const vpMeSite &PExt2, const double &A, + const double &B, const double &C, const vpColor &color = vpColor::green, + unsigned int thickness = 1); + + /*! + * Display of a moving line thanks to its equation parameters and its + * extremities. + * + * \param I : The image used as background. + * \param PExt1 : First extremity + * \param PExt2 : Second extremity + * \param A : Parameter a of the line equation a*i + b*j + c = 0 + * \param B : Parameter b of the line equation a*i + b*j + c = 0 + * \param C : Parameter c of the line equation a*i + b*j + c = 0 + * \param color : Color used to display the line. + * \param thickness : Thickness of the line. + */ + static void displayLine(const vpImage &I, const vpMeSite &PExt1, const vpMeSite &PExt2, const double &A, + const double &B, const double &C, const vpColor &color = vpColor::green, + unsigned int thickness = 1); + + /*! + * Display of a moving line thanks to its equation parameters and its + * extremities with all the site list. + * + * \param I : The image used as background. + * \param PExt1 : First extremity + * \param PExt2 : Second extremity + * \param site_list : vpMeSite list + * \param A : Parameter a of the line equation a*i + b*j + c = 0 + * \param B : Parameter b of the line equation a*i + b*j + c = 0 + * \param C : Parameter c of the line equation a*i + b*j + c = 0 + * \param color : Color used to display the line. + * \param thickness : Thickness of the line. + */ + static void displayLine(const vpImage &I, const vpMeSite &PExt1, const vpMeSite &PExt2, + const std::list &site_list, const double &A, const double &B, const double &C, + const vpColor &color = vpColor::green, unsigned int thickness = 1); + + /*! + * Display of a moving line thanks to its equation parameters and its + * extremities with all the site list. + * + * \param I : The image used as background. + * \param PExt1 : First extremity + * \param PExt2 : Second extremity + * \param site_list : vpMeSite list + * \param A : Parameter a of the line equation a*i + b*j + c = 0 + * \param B : Parameter b of the line equation a*i + b*j + c = 0 + * \param C : Parameter c of the line equation a*i + b*j + c = 0 + * \param color : Color used to display the line. + * \param thickness : Thickness of the line. + */ + static void displayLine(const vpImage &I, const vpMeSite &PExt1, const vpMeSite &PExt2, + const std::list &site_list, const double &A, const double &B, const double &C, + const vpColor &color = vpColor::green, unsigned int thickness = 1); + + /*! + * Computes the intersection point of two lines. The result is given in + * the (i,j) frame. + * + * \param line1 : The first line. + * \param line2 : The second line. + * \param iP : The coordinates of the intersection point. + * + * \return Returns a boolean value which depends on the computation + * success. True means that the computation ends successfully. + */ + static bool intersection(const vpMeLine &line1, const vpMeLine &line2, vpImagePoint &iP); + + static void project(double a, double b, double c, const vpMeSite &P, vpImagePoint &iP); + +protected: + void computeDelta(double &delta, double i1, double j1, double i2, double j2); + + /*! + * Compute the two parameters \f$(\rho, \theta)\f$ of the line in the (i,j) + * frame using all the MEs for disambiguating. + */ + void computeRhoTheta(); + + /*! + * Least squares method used to make the tracking more robust. It + * ensures that the points taken into account to compute the right + * equation belong to the line. + * + * \param I : Image in which the line appears. + */ + void leastSquare(const vpImage &I); + + void normalizeAngle(double &delta); + + /*! + * Seek along the line defined by its equation to add points + * in the detected holes. Useful in case of temporary occlusion + * + * \param I : Image in which the line appears. + * + * \return The function returns the number of points added to the list. + * + * \exception vpTrackingException::initializationError : Moving edges not + * initialized. + */ + unsigned int plugHoles(const vpImage &I); + + /*! + * Resample the line if the number of sample is less than 80% of the + * expected value. + * + * \note The expected value is computed thanks to the length of the + * line and the parameter which indicates the number of pixel between + * two points (vpMe::sample_step). + * + * \param I : Image in which the line appears. + */ + void reSample(const vpImage &I); + + /*! + * Construct a list of vpMeSite moving edges at a particular sampling + * step between the two extremities of the line. + * + * \param I : Image in which the line appears. + * \param doNotTrack : Inherited parameter, not used. + * + * \exception vpTrackingException::initializationError : Moving edges not + * initialized. + */ + virtual void sample(const vpImage &I, bool doNotTrack = false) VP_OVERRIDE; + + /*! + * Try to add points at both extremities of the line + * + * \param I : Image in which the line appears. + * + * \return The function returns the number of points added to the list. + * + * \exception vpTrackingException::initializationError : Moving edges not + * initialized. + */ + virtual unsigned int seekExtremities(const vpImage &I); + + /*! + * Seek in the list of good points its two extremities m_PExt[2] + * These extremities are not strictly on the line + * + */ + void setExtremities(); + + /*! + * Set the alpha value of the different vpMeSite to the value of delta. + */ + void updateDelta(); + +private: + static void update_indices(double theta, int incr, int i, int j, int &i1, int &i2, int &j1, int &j2); + +protected: + vpMeSite m_PExt[2]; //!< Both extremities of good points in the list. + //!< These extremities are not strictly on the line + + double m_rho; //!< rho parameter of the line + double m_theta; //!< theta parameter of the line + double m_delta; //!< Angle in rad between the extremities + int m_sign; //!< Sign + + double m_a; //!< Parameter a of the line equation a*i + b*j + c = 0 + double m_b; //!< Parameter b of the line equation a*i + b*j + c = 0 + double m_c; //!< Parameter c of the line equation a*i + b*j + c = 0 + +#ifdef VISP_BUILD_DEPRECATED_FUNCTIONS +public: + /*! + * @name Deprecated functions + */ + //@{ + + /*! + * \deprecated This method is deprecated and should no more be used. Instead of computing + * the sign of rho from the intensity of the middle point of the line, the algorithm was + * modified to consider all the points to be more robust to occlusion of the middle point. + * + * This method allows to disable/enable the calculation of the sign of the rho attribute + * from the intensity of the central point of the line. When enabled, it allows to distinguish + * between a black/white edge and a white/black edge, but it can cause problems + * for example during a visual-servo, when this point can be occluded. + * + * \param unused : This parameter is unused. + */ + VP_DEPRECATED inline void setRhoSignFromIntensity(bool unused) + { + (void)unused; + } + + /*! + * \deprecated This method is deprecated and should no more be used. Instead of computing + * the sign of rho from the intensity of the middle point of the line, the algorithm was + * modified to consider all the points to be more robust to occlusion of the middle point. + * + * This method allows to turn off the computation of the sign of the rho + * attribute based on the intensity near the middle point of the line. This + * is usually done to distinguish between a black/white and a white/black + * edge but it may be source of problem (ex. for a servoing example) when + * this point can be occluded. + * + * \param unused : This parameter is unused. + */ + VP_DEPRECATED inline void computeRhoSignFromIntensity(bool unused) + { + (void)unused; + } + + /*! + * \deprecated Gets parameter a of the line equation a*i + b*j + c = 0 + * You should rather use get_ABC(). + */ + VP_DEPRECATED inline double getA() const { return m_a; } + + /*! + * \deprecated Gets parameter b of the line equation a*i + b*j + c = 0 + * You should rather use get_ABC(). + */ + VP_DEPRECATED inline double getB() const { return m_b; } + + /*! + * \deprecated Gets parameter c of the line equation a*i + b*j + c = 0 + * You should rather use get_ABC(). + */ + VP_DEPRECATED inline double getC() const { return m_c; } + + /*! + * \deprecated Gets the equation parameters of the line + * You should rather use get_ABC(). + */ + VP_DEPRECATED void getEquationParam(double &A, double &B, double &C) const + { + A = m_a; + B = m_b; + C = m_c; + } + + /*! + * \deprecated Use rather displayLine(). + */ + VP_DEPRECATED static void display(const vpImage &I, const vpMeSite &PExt1, const vpMeSite &PExt2, const double &A, + const double &B, const double &C, const vpColor &color = vpColor::green, + unsigned int thickness = 1); + + /*! + * \deprecated Use rather displayLine(). + */ + VP_DEPRECATED static void display(const vpImage &I, const vpMeSite &PExt1, const vpMeSite &PExt2, const double &A, + const double &B, const double &C, const vpColor &color = vpColor::green, + unsigned int thickness = 1); + + /*! + * \deprecated Use rather displayLine(). + */ + VP_DEPRECATED static void display(const vpImage &I, const vpMeSite &PExt1, const vpMeSite &PExt2, + const std::list &site_list, const double &A, const double &B, const double &C, + const vpColor &color = vpColor::green, unsigned int thickness = 1); + + /*! + * \deprecated Use rather displayLine(). + */ + VP_DEPRECATED static void display(const vpImage &I, const vpMeSite &PExt1, const vpMeSite &PExt2, + const std::list &site_list, const double &A, const double &B, const double &C, + const vpColor &color = vpColor::green, unsigned int thickness = 1); + //@} +#endif +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpMeNurbs.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpMeNurbs.h new file mode 100644 index 00000000..23939998 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpMeNurbs.h @@ -0,0 +1,353 @@ +/* + * + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Moving edges. + */ + +/*! + * \file vpMeNurbs.h + * \brief Moving edges on a form represented by a NURBS (Non Uniform Rational + * B-Spline) + */ + +#ifndef vpMeNurbs_HH +#define vpMeNurbs_HH + +#include +#include +#include +#include + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpMeNurbs + * + * \ingroup module_me + * + * \brief Class that tracks in an image a edge defined by a Nurbs. + * + * The advantage of this class is that it enables to track an edge whose + * equation is not known in advance. At each iteration, the Nurbs corresponding + * to the edge is computed. + * + * It is possible to have a direct access to the nurbs. It is indeed a public + * parameter. + * + * The code below shows how to use this class. + * \code + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpImage I(240, 320); + * + * // Fill the image with a black rectangle + * I = 0u; + * for (int i = 100; i < 180; i ++) { + * for (int j = 0; j < 320; j ++) { + * I[i][j] = 255; + * } + * } + * + * // Set the moving-edges tracker parameters + * vpMe me; + * me.setRange(25); + * me.setPointsToTrack(20); + * me.setLikelihoodThresholdType(vpMe::NORMALIZED_THRESHOLD); + * me.setThreshold(20); + * me.setSampleStep(10); + * + * // Initialize the moving-edges tracker parameters + * vpMeNurbs meNurbs; + * meNurbs.setNbControlPoints(4); + * meNurbs.setMe(&me); + * + * // Initialize the location of the edge to track (here a horizontal line + * std::list ipList; //List of points belonging to the edge + * ipList.push_back(vpImagePoint(110,119)); + * ipList.push_back(vpImagePoint(140,119)); + * ipList.push_back(vpImagePoint(160,119)); + * ipList.push_back(vpImagePoint(170,119)); + * + * meNurbs.initTracking(I, ipList); + * + * while ( 1 ) + * { + * // ... Here the code to read or grab the next image. + * + * // Track the line. + * meNurbs.track(I); + * } + * return 0; + * } + * \endcode + * + * \note It is possible to display the nurbs as an overlay. For that you + * must use the display function of the class vpMeNurbs. + * + * \note In case of an edge which is not smooth, it can be interesting to use + * the canny detection to find the extremities. In this case, use the method + * setEnableCannyDetection to enable it. + * + * \warning : This function requires OpenCV. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using a ME tracker in your application, you may have a look at:
+ * + * - \ref tutorial-tracking-me +*/ +class VISP_EXPORT vpMeNurbs : public vpMeTracker +{ +#ifdef VISP_BUILD_DEPRECATED_FUNCTIONS +public: +#else +private: +#endif + //! The Nurbs which represents the tracked edge. + vpNurbs nurbs; + +private: + //! Size of the nurbs + double dist; + //! Number of control points used to compute the Nurbs. + unsigned int nbControlPoints; + //! counter used to trigger the canny edge detection at the beginning of the + //! Nurbs. + int beginPtFound; + //! counter used to trigger the canny edge detection at the end of the + //! Nurbs. + int endPtFound; + //! True if the canny detection has to be used during the extremities + //! search. + bool enableCannyDetection; + //! First canny threshold + float cannyTh1; + //! Second canny threshold + float cannyTh2; + +public: + /*! + * Basic constructor that calls the constructor of the class vpMeTracker. + */ + vpMeNurbs(); + + /*! + * Copy constructor. + */ + vpMeNurbs(const vpMeNurbs &menurbs); + + /*! + * Sets the number of control points used to compute the Nurbs. + * + * \param nb_point : The number of control points used to compute the Nurbs. + */ + void setNbControlPoints(unsigned int nb_point) { this->nbControlPoints = nb_point; } + + /*! + * Enables or disables the canny detection used during the extremities + * search. + * + * \param enable_canny : if true it enables the canny detection. + */ + void setEnableCannyDetection(const bool enable_canny) { this->enableCannyDetection = enable_canny; } + + /*! + * Enables to set the two thresholds use by the canny detection. + * + * \param th1 : The first threshold; + * \param th2 : The second threshold; + */ + void setCannyThreshold(float th1, float th2) + { + this->cannyTh1 = th1; + this->cannyTh2 = th2; + } + + /*! + * Initialization of the tracking. Ask the user to click left on several points + * along the edge to track and click right at the end. + * + * \param I : Image in which the edge appears. + */ + void initTracking(const vpImage &I); + + /*! + * Initialization of the tracking. The Nurbs is initialized thanks to the + * list of vpImagePoint. + * + * \param I : Image in which the edge appears. + * \param ptList : List of point to initialize the Nurbs. + */ + void initTracking(const vpImage &I, const std::list &ptList); + + /*! + * Track the edge in the image I. + * + * \param I : Image in which the edge appears. + */ + void track(const vpImage &I); + + /*! + * Construct a list of vpMeSite moving edges at a particular sampling + * step between the two extremities of the nurbs. + * + * \param I : Image in which the edge appears. + * \param doNotTrack : Inherited parameter, not used. + */ + virtual void sample(const vpImage &I, bool doNotTrack = false); + + /*! + * Resample the edge if the number of sample is less than 70% of the + * expected value. + * + * \note The expected value is computed thanks to the length of the + * nurbs and the parameter which indicates the number of pixel between + * two points (vpMe::sample_step). + * + * \param I : Image in which the edge appears. + */ + void reSample(const vpImage &I); + + /*! + * Set the alpha value (normal to the edge at this point) + * of the different vpMeSite to a value computed thanks to the nurbs. + */ + void updateDelta(); + + /*! + * Seek along the edge defined by the nurbs, the two extremities of + * the edge. This function is useful in case of translation of the + * edge. + * + * \param I : Image in which the edge appears. + */ + void seekExtremities(const vpImage &I); + + /*! + * Seek the extremities of the edge thanks to a canny edge detection. + * The edge detection enable to find the points belonging to the edge. + * The any vpMeSite are initialized at this points. + * + * This method is useful when the edge is not smooth. + * + * \note To use the canny detection, OpenCV has to be installed. + * + * \param I : Image in which the edge appears. + */ + void seekExtremitiesCanny(const vpImage &I); + + /*! + * Suppression of the points which: + * + * - belong no more to the edge. + * - which are to closed to another point. + */ + void suppressPoints(); + + /*! + * Suppress vpMeSites if they are too close to each other. + * + * The goal is to keep the order of the vpMeSites in the list. + */ + void supressNearPoints(); + + /*! + * Resample a part of the edge if two vpMeSite are too far from each other. + * In this case the method try to initialize any vpMeSite between the two + * points. + * + * \param I : Image in which the edge appears. + */ + void localReSample(const vpImage &I); + + /*! + Gets the nurbs; + */ + inline vpNurbs getNurbs() const { return nurbs; } + + /*! + * Display edge. + * + * \warning To effectively display the edge a call to + * vpDisplay::flush() is needed. + * + * \param I : Image in which the edge appears. + * \param color : Color of the displayed line. + * \param thickness : Drawings thickness. + */ + void display(const vpImage &I, const vpColor &color, unsigned int thickness = 1); + +private: + bool computeFreemanChainElement(const vpImage &I, vpImagePoint &iP, unsigned int &element); + + bool hasGoodLevel(const vpImage &I, const vpImagePoint &iP) const; + + bool isInImage(const vpImage &I, const vpImagePoint &iP) const; + + void computeFreemanParameters(unsigned int element, vpImagePoint &diP); + + bool farFromImageEdge(const vpImage &I, const vpImagePoint &iP); + +public: + /*! + * Display of a moving nurbs. + * + * \param I : The image used as background. + * \param n : Nurbs to display + * \param color : Color used to display the nurbs. + * \param thickness : Drawings thickness. + */ + static void display(const vpImage &I, vpNurbs &n, const vpColor &color = vpColor::green, unsigned int thickness = 1); + + /*! + * Display of a moving nurbs. + * + * \param I : The image used as background. + * \param n : Nurbs to display + * \param color : Color used to display the nurbs. + * \param thickness : Drawings thickness. + */ + static void display(const vpImage &I, vpNurbs &n, const vpColor &color = vpColor::green, unsigned int thickness = 1); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpMeSite.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpMeSite.h new file mode 100644 index 00000000..649c92cd --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpMeSite.h @@ -0,0 +1,456 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Moving edges. + */ + +/*! + * \file vpMeSite.h + * \brief Moving edges + */ + +#ifndef VP_ME_SITE_H +#define VP_ME_SITE_H + +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE + +/*! + * \class vpMeSite + * \ingroup module_me + * + * \brief Performs search in a given direction(normal) for a given + * distance(pixels) for a given 'site'. Gives the most likely site + * given the probability from an ME mask + * + * - Bug fix: rewrote application of masks to use the temporal + * information instead of applying both temporal masks to the same + * image. ie: spatial -> spatio/temporal + * + * - Added new tracking function to choose the most similar edge + * amongst all edges found. + * + * - sample step. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using a ME tracker in your application, you may have a look at:
+ * + * - \ref tutorial-tracking-me +*/ +class VISP_EXPORT vpMeSite +{ +public: + /*! + * Type moving-edges site of display. + */ + typedef enum + { + NONE, //!< Not displayed + RANGE, //!< + RESULT, //!< + RANGE_RESULT //!< + } vpMeSiteDisplayType; + + /*! + * Moving-edge site state + */ + typedef enum + { + NO_SUPPRESSION = 0, ///< Point successfully tracked. + CONTRAST = 1, ///< Point not tracked due to a contrast problem, but retained in the ME list. +#ifdef VISP_BUILD_DEPRECATED_FUNCTIONS + CONSTRAST = CONTRAST, ///< Deprecated. Point not tracked due to a likelihood problem, but retained in the ME list. Use instead CONTRAST. +#endif + THRESHOLD = 2, ///< Point not tracked due to the likelihood that is below the threshold, but retained in the ME list. + M_ESTIMATOR = 3, ///< Point detected as an outlier during virtual visual-servoing. + TOO_NEAR = 4, ///< Point not tracked anymore, since too near from its neighbor. + UNKNOW = 5, ///< Reserved. + OUTSIDE_ROI_MASK = 6 ///< Point is outside the region of interest mask, but retained in the ME list. + } vpMeSiteState; + + //! Integer coordinate along i of a site + int m_i; + //! Integer coordinates along j of a site + int m_j; + //! Subpixel coordinates along i of a site + double m_ifloat; + //! Subpixel coordinates along j of a site + double m_jfloat; + //! Mask sign + int m_mask_sign; + //! Angle of tangent at site + double m_alpha; + //! Convolution of Site in previous image + double m_convlt; + //! Convolution of Site in previous image + double m_normGradient; + //! Uncertainty of point given as a probability between 0 and 1 + double m_weight; + //! Old likelihood ratio threshold (to be avoided) or easy-to-use normalized threshold: minimal contrast + double m_contrastThreshold; + +public: + /*! + * Default constructor. + */ + vpMeSite(); + + /*! + * Constructor from pixel coordinates. + */ + vpMeSite(const double &ip, const double &jp); + + /*! + * Copy constructor. + */ + vpMeSite(const vpMeSite &mesite); + + /*! + * Destructor. + */ + virtual ~vpMeSite() { } + + /*! + * Compute convolution. + */ + double convolution(const vpImage &ima, const vpMe *me); + + /*! + * Compute convolution for a given mask_index. + */ + double convolution(const vpImage &ima, const vpMe &me, const unsigned int mask_index); + + /*! + * Compute the index mask in [0:179] for convolution. + */ + unsigned int computeMaskIndex(const double alpha, const vpMe &me); + + /*! + * Display moving edges in image I. + * @param I : Input image. + */ + void display(const vpImage &I) const; + + /*! + * Display moving edges in image I. + * @param I : Input image. + */ + void display(const vpImage &I) const; + + /*! + * Get the angle of tangent at site. + * + * \return value of alpha + */ + inline double getAlpha() const { return m_alpha; } + + /*! + * Return site weight or uncertainty as a probability between 0 and 1. + */ + inline double getWeight() const { return m_weight; } + + /*! + * Construct and return the list of vpMeSite along the normal to the contour, + * in the given range. + * \pre : Subpixel coordinates (ifloat, jfloat) and the direction of the normal (alpha) have to be set. + * \param I : Image in which the display is performed. + * \param range : +/- the range within which the pixel's correspondent will be sought. + * \return Pointer to the list of query sites + */ + vpMeSite *getQueryList(const vpImage &I, const int &range) const; + + /*! + * Return integer coordinate along i (rows). + * \sa get_ifloat() + */ + inline int get_i() const { return m_i; } + + /*! + * Return integer coordinate along j (columns). + * \sa get_jfloat() + */ + inline int get_j() const { return m_j; } + + /*! + * Return subpixel coordinate along i (rows). + * \sa get_i() + */ + inline double get_ifloat() const { return m_ifloat; } + + /*! + * Return subpixel coordinate along j (columns). + * \sa get_j() + */ + inline double get_jfloat() const { return m_jfloat; } + + /*! + * Return the convolution index in [0:179]. + * \sa computeMaskIndex() + */ + inline unsigned int getIndex() const { return m_index_prev; } + + /*! + * Initialize moving-edge site with default parameters. + */ + void init(); + + /*! + * Initialize moving-edge site parameters. + */ + void init(const double &ip, const double &jp, const double &alphap); + + /*! + * Initialize moving-edge site parameters. + */ + void init(const double &ip, const double &jp, const double &alphap, const double &convltp); + + /*! + * Initialize moving-edge site parameters. + */ + void init(const double &ip, const double &jp, const double &alphap, const double &convltp, const int &sign); + + /*! + * Initialize moving-edge site parameters. + */ + void init(const double &ip, const double &jp, const double &alphap, const double &convltp, const int &sign, const double &contrastThreshold); + + /*! + * Specific function for moving-edges. + * + * \warning To display the moving edges graphics a call to vpDisplay::flush() is needed after this function. + * \param[in] I : Input image. + * \param[in] me : Pointer to the moving-edges settings. + * \param[in] test_contrast : When true tracking is based on contrast. The retained moving-edge is the one with + * similar contrast. When false, the tracking is based on the likelihood. The retained moving-edge is the one with + * the maximum likelihood. + */ + void track(const vpImage &I, const vpMe *me, const bool &test_contrast = true); + + /*! + * Similar to the track() function, but stores the best numCandidates hypotheses in `outputHypotheses`. + * The best matching hypotheses (if it is not suppressed) is assigned to *this* and is stored as the first + * element of `outputHypotheses`. + * The hypotheses are sorted from best to worst match in the vector. + * A match may be in the vector but mark as suppressed. If this is undesired, you should filter them afterwards. + * + * \throws If `numCandidates` is superior to me.getRange() * 2 + 1. + * + * \warning To display the moving edges graphics a call to vpDisplay::flush() is needed after this function. + */ + void trackMultipleHypotheses(const vpImage &I, const vpMe &me, const bool &test_contrast, + std::vector &outputHypotheses, const unsigned numCandidates); + + /*! + * Set the angle of tangent at site. + * + * \param a : new value of alpha + */ + void setAlpha(const double &a) { m_alpha = a; } + + /*! + * Display selector. + */ + void setDisplay(vpMeSiteDisplayType select) { m_selectDisplay = select; } + + /*! + * Set the state of the site. + * + * \param flag : flag corresponding to vpMeSiteState + * + * \sa vpMeSiteState + */ + void setState(const vpMeSiteState &flag) + { + m_state = flag; + } + + /*! + * Get the state of the site. + * + * \return flag corresponding to vpMeSiteState + */ + inline vpMeSiteState getState() const { return m_state; } + + /*! + * Set the weight or uncertainty of the site. + * + * \param weight : New value of weight as a probability between 0 and 1. + */ + void setWeight(const double &weight) { m_weight = weight; } + + /*! + * Set the contrast threshold of the site. + * If the \b vpMe::m_useAutomaticThreshold is set to false, the contrast threshold is set to the global + * value retrieved using vpMe::getThreshold(). This value can be set using vpMe::setThreshold(). + * Otherwise, the contrast threshold will be set to the highest value + * between \b thresh and the minimum value set by vpMe::setMinThreshold() that could be retrieved using + * vpMe::getMinThreshold(). + * + * \param[in] thresh : new value of contrast threshold + * \param[in] me : moving-edge parameters + */ + void setContrastThreshold(const double &thresh, const vpMe &me) + { + double threshold; + if (me.getUseAutomaticThreshold()) { + threshold = std::max(thresh, me.getMinThreshold()); + } + else { + threshold = me.getThreshold(); + } + + m_contrastThreshold = threshold; + } + + /*! + * Get the contrast threshold of the site. + * + * \return value of the contrast threshold of the site. + */ + inline double getContrastThreshold() const { return m_contrastThreshold; } + + + /*! + * Get the final computed likelihood threshold value, depending on the likelihood threshold type and ME settings. + * + * \return value of the contrast threshold of the site. + */ + inline double computeFinalThreshold(const vpMe &me) const + { + const double threshold = getContrastThreshold(); + if (me.getLikelihoodThresholdType() == vpMe::NORMALIZED_THRESHOLD) { + return 2.0 * threshold; + } + else { + const double n_d = me.getMaskSize(); + return threshold / (100.0 * n_d * trunc(n_d / 2.0)); + } + } + + /*! + * Copy operator. + */ + vpMeSite &operator=(const vpMeSite &m); + + /*! + * Comparison operator. + */ + int operator!=(const vpMeSite &m); + + /*! + * ostream operator. + */ + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, vpMeSite &vpMeS); + + // Static functions + /*! + Compute the square root distance between two moving-edges sites + \f$ |S1 - S2| = \sqrt{(i_1-i_2)^2+(j_1-j_2)^2} \f$ + + \param S1 : First site + \param S2 : Second site + + \return the distance between the two sites. + + \sa sqrDistance() + */ + static double distance(const vpMeSite &S1, const vpMeSite &S2) + { + return (sqrt(sqrDistance(S1, S2))); + } + + /*! + Compute the square distance between two moving-edges sites + \f$ |S1 - S2| = (i_1-i_2)^2+(j_1-j_2)^2 \f$ + + \param S1 : First site + \param S2 : Second site + + \return The square distance between the two sites. + + \sa distance() + */ + static double sqrDistance(const vpMeSite &S1, const vpMeSite &S2) + { + return (vpMath::sqr(S1.m_ifloat - S2.m_ifloat) + vpMath::sqr(S1.m_jfloat - S2.m_jfloat)); + } + + /*! + * Display the moving edge site with a color corresponding to their state. + * + * - If green : The vpMeSite is a good point. + * - If blue : The point is removed because of the vpMeSite tracking phase (contrast problem). + * - If purple : The point is removed because of the vpMeSite tracking phase (threshold problem). + * - If red : The point is removed because of the robust method in the virtual visual servoing (M-Estimator problem). + * - If cyan : The point is outside the mask, see vpMeTracker::setMask(). + * - Yellow otherwise. + * + * \param I : The image. + * \param i : Pixel i of the site. + * \param j : Pixel j of the site. + * \param state : State of the site. + */ + static void display(const vpImage &I, const double &i, const double &j, + const vpMeSiteState &state = NO_SUPPRESSION); + + /*! + * Display the moving edge site with a color corresponding to their state. + * + * - If green : The vpMeSite is a good point. + * - If blue : The point is removed because of the vpMeSite tracking phase (contrast problem). + * - If purple : The point is removed because of the vpMeSite tracking phase (threshold problem). + * - If red : The point is removed because of the robust method in the virtual visual servoing (M-Estimator problem). + * - If cyan : The point is outside the mask, see vpMeTracker::setMask(). + * - Yellow otherwise + * + * \param I : The image. + * \param i : Pixel i of the site. + * \param j : Pixel j of the site. + * \param state : State of the site. + */ + static void display(const vpImage &I, const double &i, const double &j, + const vpMeSiteState &state = NO_SUPPRESSION); + +private: + vpMeSiteDisplayType m_selectDisplay; //!< Display selector + vpMeSiteState m_state; //!< Site state + unsigned int m_index_prev; //!< previous convolution index +}; + +END_VISP_NAMESPACE + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpMeTracker.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpMeTracker.h new file mode 100644 index 00000000..f248eff1 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpMeTracker.h @@ -0,0 +1,323 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Moving edges. + */ + +/*! + * \file vpMeTracker.h + * \brief Contains abstract elements for a Distance to Feature type feature. + */ + +#ifndef VP_ME_TRACKER_H +#define VP_ME_TRACKER_H + +#include +#include +#include +#include + +#include +#include +#include + +BEGIN_VISP_NAMESPACE + +/*! + * \class vpMeTracker + * + * \ingroup module_me + * \brief Contains abstract elements for a Distance to Feature type feature. + * + * 2D state = list of points, 3D state = feature + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using a ME tracker in your application, you may have a look at:
+ * + * - \ref tutorial-tracking-me +*/ +class VISP_EXPORT vpMeTracker : public vpTracker +{ +public: + /*! + * Default constructor. + */ + vpMeTracker(); + + /*! + * Copy constructor. + */ + vpMeTracker(const vpMeTracker &meTracker); + + /*! + * Destructor. + */ + virtual ~vpMeTracker() VP_OVERRIDE; + + /** @name Public Member Functions Inherited from vpMeTracker */ + //@{ + + /*! + * Display the moving edge sites with a color corresponding to their state. + * + * - If green : The vpMeSite is a good point. + * - If blue : The point is removed because of the vpMeSite tracking phase (contrast problem). + * - If purple : The point is removed because of the vpMeSite tracking phase (threshold problem). + * - If red : The point is removed because of the robust method in the virtual visual servoing (M-Estimator problem). + * - If cyan : The point is removed because it's too close to another. + * - Yellow otherwise. + * + * \param[in] I : The image. + */ + void display(const vpImage &I); + + /*! + * Display the moving edge sites with a color corresponding to their state. + * + * - If green : The vpMeSite is a good point. + * - If blue : The point is removed because of the vpMeSite tracking phase (contrast problem). + * - If purple : The point is removed because of the vpMeSite tracking phase (threshold problem). + * - If red : The point is removed because of the robust method in the virtual visual servoing (M-Estimator problem). + * - If cyan : The point is removed because it's too close to another. + * - Yellow otherwise. + * + * \param[in] I : The image. + */ + void display(const vpImage &I); + + /*! + * Displays the status of moving edge sites + * + * \param[in] I : The image. + * \param[in] w : vector + * \param[in] index_w : index + */ + void display(const vpImage &I, vpColVector &w, unsigned int &index_w); + + /*! + * Test whether the pixel is inside the region of interest mask. Mask values that are set to true + * are considered in the tracking. + * + * \param[in] mask : Mask corresponding to the region of interest in the image or nullptr if not wanted. + * Mask values that are set to true are considered in the tracking. To disable a pixel, set false. + * \param[in] i : Pixel coordinate along the rows. + * \param[in] j : Pixel coordinate along the columns. + */ + static bool inRoiMask(const vpImage *mask, unsigned int i, unsigned int j); + + /*! + * Test whether the moving-edge (ME) is inside the mask of ME candidates for the initialization. + * Mask values that are set to true and their 8 neighbors are considered for the initialization. + * + * \param[in] meMaskCandidates : Mask corresponding the ME location in the image or nullptr if not wanted. + * Mask values that are set to true are considered for the initialization. To disable a pixel, set false. + * \param[in] i : ME coordinate along the rows. + * \param[in] j : ME coordinate along the columns. + */ + static bool inMeMaskCandidates(const vpImage *meMaskCandidates, unsigned int i, unsigned int j); + + /*! + * Return the moving edges initialisation parameters. + * + * \return Moving Edges. + */ + inline vpMe *getMe() { return m_me; } + + /*! + * Return the list of moving edges + * + * \return List of Moving Edges. + */ + inline std::list &getMeList() { return m_meList; } + + /*! + * Return the list of moving edges + * + * \return List of Moving Edges. + */ + inline std::list getMeList() const { return m_meList; } + + /*! + * Return the number of points that has not been suppressed. + * + * \return Number of good points. + */ + inline int getNbPoints() const { return m_nGoodElement; } + + /*! + * Initialize the tracker. + */ + void init(); + + /*! + * Virtual function that is called by lower classes vpMeEllipse, vpMeLine + * and vpMeNurbs. + * + * \exception vpTrackingException::initializationError : Moving edges not initialized. + */ + void initTracking(const vpImage &I); + + /*! + * Return number of moving-edges that are tracked. + */ + unsigned int numberOfSignal(); + + /*! + * Copy operator. + */ + vpMeTracker &operator=(vpMeTracker &meTracker); + + /*! + * Check if a pixel i,j is out of the image. + * \param[in] i : Pixel coordinate along the image rows. + * \param[in] j : Pixel coordinates along the image columns. + * \param[in] border : Number of pixels along the image border to exclude. When border is set to 0, consider the complete image. + * \param[in] nrows : Image number of rows. + * \param[in] ncols : Image number of cols. + * \return true when the pixel is inside the image minus the border size, false otherwise. + */ + bool outOfImage(int i, int j, int border, int nrows, int ncols); + + /*! + * Check if a pixel i,j is out of the image. + * \param[in] iP : Pixel coordinates. + * \param[in] border : Number of pixels along the image border to exclude. When border is set to 0, consider the complete image. + * \param[in] nrows : Image number of rows. + * \param[in] ncols : Image number of cols. + * \return true when the pixel is inside the image minus the border size, false otherwise. + */ + bool outOfImage(const vpImagePoint &iP, int border, int nrows, int ncols); + + /*! + * Reset the tracker by removing all the moving edges. + */ + void reset(); + + /*! + * Sample pixels at a given interval. + */ + virtual void sample(const vpImage &image, bool doNotTrack = false) = 0; + + /*! + * Set type of moving-edges display. + * @param select : Display type selector. + */ + void setDisplay(vpMeSite::vpMeSiteDisplayType select) { m_selectDisplay = select; } + + /*! + * Set the mask. + * + * \param mask : Mask. + */ + virtual void setMask(const vpImage &mask) { m_mask = &mask; } + + /*! + * Set the mask of candidates points for initialization. + * + * \param[in] maskCandidates : Pointer towards the mask of candidates points for initialization. + */ + virtual void setMaskCandidates(const vpImage *maskCandidates) { m_maskCandidates = maskCandidates; } + + /*! + * Set the moving edges initialisation parameters. + * + * \param[in] me : Moving Edges. + */ + void setMe(vpMe *me) { m_me = me; } + + /*! + * Set the list of moving edges. + * + * \param[in] meList : List of Moving Edges. + */ + void setMeList(const std::list &meList) { m_meList = meList; } + + /*! + * Return the total number of moving-edges. + */ + unsigned int totalNumberOfSignal(); + + /*! + * Track moving-edges. + * + * \param[in] I : Image. + * + * \exception vpTrackingException::initializationError : Moving edges not initialized. + */ + void track(const vpImage &I); + //@} + +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) + /*! + @name Deprecated functions + */ + //@{ + /*! + * \deprecated You should rather use inRoiMask(). + * Test whether the pixel is inside the region of interest mask. Mask values that are set to true + * are considered in the tracking. + * + * \param[in] mask : Mask corresponding to the region of interest in the image or nullptr if not wanted. + * Mask values that are set to true are considered in the tracking. To disable a pixel, set false. + * \param[in] i : Pixel coordinate along the rows. + * \param[in] j : Pixel coordinate along the columns. + */ + VP_DEPRECATED static bool inMask(const vpImage *mask, unsigned int i, unsigned int j) + { + return inRoiMask(mask, i, j); + } + //@} +#endif + +protected: + /** @name Protected Attributes Inherited from vpMeTracker */ + //@{ + //! Tracking dependent variables/functions + //! List of tracked moving edges points. + std::list m_meList; + //! Moving edges initialisation parameters + vpMe *m_me; + //! Number of good moving-edges that are tracked + int m_nGoodElement; + //! Mask used to disable tracking on a part of image + const vpImage *m_mask; + //! Mask used to determine candidate points for initialization in an image + const vpImage *m_maskCandidates; + //! Moving-edges display type + vpMeSite::vpMeSiteDisplayType m_selectDisplay; + //@} + +}; + +END_VISP_NAMESPACE + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpNurbs.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpNurbs.h new file mode 100644 index 00000000..4704e29e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/me/vpNurbs.h @@ -0,0 +1,528 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * This class implements the Non Uniform Rational B-Spline (NURBS) + */ + +#ifndef vpNurbs_H +#define vpNurbs_H + +/*! + * \file vpNurbs.h + * \brief Class that provides tools to compute and manipulate a Non Uniform + * Rational B-Spline curve. + */ + +#include +#include +#include +#include +#include +#include + +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpNurbs + * \ingroup module_me + * + * \brief Class that provides tools to compute and manipulate a Non Uniform + * Rational B-Spline curve. + * + * The different parameters are : + * + * - The knot vector \f$ U = {u_0, ... , u_m} \f$ where the knots \f$ u_i, i = + * 0, ...,m \f$ are real number such as \f$ u_i < u_{i+1} i = 0, ...,m \f$. To + * define a curve, the knot vector is such as : \f$ U = {a , ... , a, u_{p+1} , + * ... , u_{m-p-1} , b , ... , b} \f$ where \f$ a \f$ and \f$ b \f$ are real + * numbers and p is the degree of the B-Spline basis functions. + * + * - The B-Spline basis functions \f$ N_{i,p} \f$ defined as : + * \f[ N_{i,0}(u) = \left\{\begin{array}{cc} + * 1 & \mbox{if } u_i \leq u_{i+1} \\ 0 & else + * \end{array}\right.\f] + * + * \f[ N_{i,p}(u) = + * \frac{u-u_i}{u_{i+p}-u_i}N_{i,p-1}(u)+\frac{u_{i+p+1}-u}{u_{i+p+1}-u_{i+1}}N_{i+1,p-1}(u)\f] + * + * where \f$ i = 0 , ... , m-1 \f$ and p is the degree of the B-Spline basis + * functions. + * + * - The control points \f$ {P_i} \f$ which are defined by the coordinates \f$ + * (i,j) \f$ of a point in an image. + * + * - The weight \f$ {w_i} \f$ associated to each control points. The weights + * value is upper than 0. + * + * It is possible to compute the coordinates of a point corresponding to the + * knots \f$ u \f$ (\f$ u \in [u_0,u_m]\f$) thanks to the formula : \f[ C(u) = + * \frac{\sum_{i=0}^n (N_{i,p}(u)w_iP_i)}{\sum_{i=0}^n (N_{i,p}(u)w_i)}\f] + * + * You can find much more information about the B-Splines and the + * implementation of all the methods in the Nurbs Book. +*/ +class VISP_EXPORT vpNurbs : public vpBSpline +{ +protected: + //! Vector which contains the weights associated to each control Points + std::vector weights; + +protected: + /*! + * This function is used in the computeCurveDersPoint method. + * + * Compute the kth derivatives of \f$ C(u) \f$ for \f$ k = 0, ... , l_{der} + * \f$. + * + * The formula used is the following : + * + * \f[ C^{(k)}(u) = \sum_{i=0}^n (N_{i,p}^{(k)}(u)Pw_i) \f] + * + * where \f$ i \f$ is the knot interval number in which \f$ u \f$ lies, \f$ p + * \f$ is the degree of the NURBS basis function and \f$ Pw_i = (P_i w_i) \f$ + * contains the control points and the associated weights. + * + * \param l_u : A real number which is between the extremities of the knot + * vector. + * \param l_i : the number of the knot interval in which \f$ l_u \f$ + * lies. + * \param l_p : Degree of the NURBS basis functions. + * \param l_der : The last derivative to be computed. + * \param l_knots : The knot vector. + * \param l_controlPoints : the list of control points. + * \param l_weights : the list of weights. + * + * \return a matrix of size (l_der+1)x3 containing the coordinates \f$ + * C^{(k)}(u) \f$ for \f$ k = 0, ... , l_{der} \f$. The kth derivative is in + * the kth line of the matrix. For each lines the first and the second column + * corresponds to the coordinates (i,j) of the point and the third column + * corresponds to the associated weight. + */ + static vpMatrix computeCurveDers(double l_u, unsigned int l_i, unsigned int l_p, unsigned int l_der, + std::vector &l_knots, std::vector &l_controlPoints, + std::vector &l_weights); + + /*! + * This function is used in the computeCurveDersPoint method. + * + * Compute the kth derivatives of \f$ C(u) \f$ for \f$ k = 0, ... , der \f$. + * + * The formula used is the following : + * + * \f[ C^{(k)}(u) = \sum_{i=0}^n (N_{i,p}^{(k)}(u)Pw_i) \f] + * + * where \f$ i \f$ is the knot interval number in which \f$ u \f$ lies, \f$ p + * \f$ is the degree of the NURBS basis function and \f$ Pw_i = (P_i w_i) \f$ + * contains the control points and the associated weights. + * + * \param u : A real number which is between the extremities of the knot vector + * \param der : The last derivative to be computed. + * + * \return a matrix of size (l_der+1)x3 containing the coordinates \f$ + * C^{(k)}(u) \f$ for \f$ k = 0, ... , der \f$. The kth derivative is in the + * kth line of the matrix. For each lines the first and the second column + * corresponds to the coordinates (i,j) of the point and the third column + * corresponds to the associated weight. + */ + vpMatrix computeCurveDers(double u, unsigned int der); + +public: + /*! + * Basic constructor. + * + * The degree \f$ p \f$ of the NURBS basis functions is set to 3 to + * compute cubic NURBS. + */ + vpNurbs(); + + /*! + * Copy constructor. + */ + vpNurbs(const vpNurbs &nurbs); + + + /*! + * Gets all the weights relative to the control points. + * + * \param list [out] : A std::list containing weights relative to the + * control points. + */ + inline void get_weights(std::list &list) const + { + list.clear(); + for (unsigned int i = 0; i < weights.size(); i++) + list.push_back(*(&(weights[0]) + i)); + } + + /*! + * Sets all the knots. + * + * \param list : A std::list containing the value of the knots. + */ + inline void set_weights(const std::list &list) + { + weights.clear(); + for (std::list::const_iterator it = list.begin(); it != list.end(); ++it) { + weights.push_back(*it); + } + } + + /*! + * Compute the coordinates of a point \f$ C(u) = \frac{\sum_{i=0}^n + * (N_{i,p}(u)w_iP_i)}{\sum_{i=0}^n (N_{i,p}(u)w_i)} \f$ corresponding to the + * knot \f$ u \f$. + * + * \param l_u : A real number which is between the extremities of the knot + * vector. + * \param l_i : the number of the knot interval in which \f$ l_u \f$ + * lies. + * \param l_p : Degree of the NURBS basis functions. + * \param l_knots : The knot vector. + * \param l_controlPoints : the list of control points. + * \param l_weights : the list of weights. + * + * \return The coordinates of a point corresponding to the knot \f$ u \f$. + */ + static vpImagePoint computeCurvePoint(double l_u, unsigned int l_i, unsigned int l_p, std::vector &l_knots, + std::vector &l_controlPoints, std::vector &l_weights); + + /*! + * Compute the coordinates of a point \f$ C(u) = \frac{\sum_{i=0}^n + * (N_{i,p}(u)w_iP_i)}{\sum_{i=0}^n (N_{i,p}(u)w_i)} \f$ corresponding to the + * knot \f$ u \f$. + * + * \param u : A real number which is between the extremities of the knot vector + * + * return the coordinates of a point corresponding to the knot \f$ u \f$. + */ + vpImagePoint computeCurvePoint(double u); + + /*! + * Compute the kth derivatives of \f$ C(u) \f$ for \f$ k = 0, ... , l_{der} + * \f$. + * + * To see how the derivatives are computed refers to the Nurbs book. + * + * \param l_u : A real number which is between the extremities of the knot + * vector. + * \param l_i : the number of the knot interval in which \f$ l_u \f$ + * lies. + * \param l_p : Degree of the NURBS basis functions. + * \param l_der : The last derivative to be computed. + * \param l_knots : The knot vector. + * \param l_controlPoints : the list of control points. + * \param l_weights : the list of weights. + * + * \return an array of size l_der+1 containing the coordinates \f$ C^{(k)}(u) + * \f$ for \f$ k = 0, ... , l_{der} \f$. The kth derivative is in the kth cell + * of the array. + */ + static vpImagePoint *computeCurveDersPoint(double l_u, unsigned int l_i, unsigned int l_p, unsigned int l_der, + std::vector &l_knots, std::vector &l_controlPoints, + std::vector &l_weights); + + /*! + * Compute the kth derivatives of \f$ C(u) \f$ for \f$ k = 0, ... , l_{der} + * \f$. + * + * To see how the derivatives are computed refers to the Nurbs book. + * + * \param u : A real number which is between the extremities of the knot vector + * \param der : The last derivative to be computed. + * + * \return an array of size l_der+1 containing the coordinates \f$ C^{(k)}(u) + * \f$ for \f$ k = 0, ... , der \f$. The kth derivative is in the kth cell of + * the array. + */ + vpImagePoint *computeCurveDersPoint(double u, unsigned int der); + + /*! + * Insert \f$ l_r \f$ times a knot in the \f$ l_k \f$ th interval of the knot + * vector. The inserted knot \f$ l_u \f$ has multiplicity \f$ l_s \f$. + * + * Of course the knot vector changes. But The list of control points and the + * list of the associated weights change too. + * + * \param l_u : A real number which is between the extremities of the knot + * vector and which has to be inserted. + * \param l_k : The number of the knot interval in which \f$ l_u \f$ lies. + * \param l_s : Multiplicity of \f$ l_u \f$ + * \param l_r : Number of times \f$ l_u \f$ has to be inserted. + * \param l_p : Degree of the NURBS basis functions. + * \param l_knots : The knot vector + * \param l_controlPoints : the list of control points. + * \param l_weights : the list of weights. + */ + static void curveKnotIns(double l_u, unsigned int l_k, unsigned int l_s, unsigned int l_r, unsigned int l_p, + std::vector &l_knots, std::vector &l_controlPoints, + std::vector &l_weights); + + /*! + * Insert \f$ r \f$ times a knot in the \f$ k \f$ th interval of the knot + * vector. The inserted knot \f$ u \f$ has multiplicity \f$ s \f$. + * + * Of course the knot vector changes. But The list of control points and the + * list of the associated weights change too. + * + * \param u : A real number which is between the extremities of the knot vector + * and which has to be inserted. + * \param s : Multiplicity of \f$ l_u \f$. + * \param r : Number of times \f$ l_u \f$ has to be inserted. + */ + void curveKnotIns(double u, unsigned int s = 0, unsigned int r = 1); + + /*! + * Insert \f$ l_r \f$ knots in the knot vector. + * + * Of course the knot vector changes. But The list of control points and the + * list of the associated weights change too. + * + * \param l_x : Several real numbers which are between the extremities of the + * knot vector and which have to be inserted. + * \param l_r : Number of knot in the array \f$ l_x \f$. + * \param l_p : Degree of the NURBS basis functions. + * \param l_knots : The knot vector + * \param l_controlPoints : the list of control points. + * \param l_weights : the list of weights. + */ + static void refineKnotVectCurve(double *l_x, unsigned int l_r, unsigned int l_p, std::vector &l_knots, + std::vector &l_controlPoints, std::vector &l_weights); + + /*! + * Insert \f$ r \f$ knots in the knot vector. + * + * Of course the knot vector changes. But The list of control points and the + * list of the associated weights change too. + * + * \param x : Several real numbers which are between the extremities of the + * knot vector and which have to be inserted. \param r : Number of knot in the + * array \f$ l_x \f$. + */ + void refineKnotVectCurve(double *x, unsigned int r); + + /*! + * Remove \f$ l_num \f$ times the knot \f$ l_u \f$ from the knot vector. The + * removed knot \f$ l_u \f$ is the \f$ l_r \f$ th vector in the knot vector. + * + * Of course the knot vector changes. But The list of control points and the + * list of the associated weights change too. + * + * \param l_u : A real number which is between the extremities of the knot + * vector and which has to be removed. + * \param l_r : Index of \f$ l_u \f$ in the knot vector. + * \param l_num : Number of times \f$ l_u \f$ has to be removed. + * \param l_TOL : A parameter which has to be computed. + * \param l_s : Multiplicity of \f$ l_u \f$. + * \param l_p : Degree of the NURBS basis functions. + * \param l_knots : The knot vector + * \param l_controlPoints : the list of control points. + * \param l_weights : the list of weights. + * + * \return The number of time that l_u was removed. + * + * \f$ l_{TOL} = \frac{dw_{min}}{1+|P|_{max}} \f$ + * + * where \f$ w_{min} \f$ is the minimal weight on the original curve, \f$ + * |P|_{max} \f$ is the maximum distance of any point on the original curve + * from the origin and \f$ d \f$ is the desired bound on deviation. + */ + static unsigned int removeCurveKnot(double l_u, unsigned int l_r, unsigned int l_num, double l_TOL, unsigned int l_s, + unsigned int l_p, std::vector &l_knots, + std::vector &l_controlPoints, std::vector &l_weights); + + /*! + * Remove \f$ num \f$ times the knot \f$ u \f$ from the knot vector. The + * removed knot \f$ u \f$ is the \f$ r \f$ th vector in the knot vector. + * + * Of course the knot vector changes. But The list of control points and the + * list of the associated weights change too. + * + * \param l_u : A real number which is between the extremities of the knot vector + * and which has to be removed. + * \param l_r : Index of \f$ l_u \f$ in the knot vector. + * \param l_num : Number of times \f$ l_u \f$ has to be removed. + * \param l_TOL : A parameter which has to be computed. + * + * \return The number of time that l_u was removed. + * + * \f$ TOL = \frac{dw_{min}}{1+|P|_{max}} \f$ + * + * where \f$ w_{min} \f$ is the minimal weight on the original curve, \f$ + * |P|_{max} \f$ is the maximum distance of any point on the original curve + * from the origin and \f$ d \f$ is the desired bound on deviation. + */ + unsigned int removeCurveKnot(double l_u, unsigned int l_r, unsigned int l_num, double l_TOL); + + /*! + * Method which enables to compute a NURBS curve passing through a set of data + * points. + * + * The result of the method is composed by a knot vector, a set of control + * points and a set of associated weights. + * + * \param l_crossingPoints : The list of data points which have to be interpolated. + * \param l_p : Degree of the NURBS basis functions. This value need to be > 0. + * \param l_knots : The knot vector. + * \param l_controlPoints : The list of control points. + * \param l_weights : the list of weights. + */ + static void globalCurveInterp(std::vector &l_crossingPoints, unsigned int l_p, + std::vector &l_knots, std::vector &l_controlPoints, + std::vector &l_weights); + + /*! + * Method which enables to compute a NURBS curve passing through a set of data + * points. + * + * The result of the method is composed by a knot vector, a set of control + * points and a set of associated weights. + * + * \param l_crossingPoints : The list of data points which have to be + * interpolated. + */ + void globalCurveInterp(vpList &l_crossingPoints); + + /*! + * Method which enables to compute a NURBS curve passing through a set of data + * points. + * + * The result of the method is composed by a knot vector, a set of control + * points and a set of associated weights. + * + * \param l_crossingPoints : The list of data points which have to be + * interpolated. + */ + void globalCurveInterp(const std::list &l_crossingPoints); + + /*! + * Method which enables to compute a NURBS curve passing through a set of data + * points. + * + * The result of the method is composed by a knot vector, a set of control + * points and a set of associated weights. + * + * \param l_crossingPoints : The list of data points which have to be + * interpolated. + */ + void globalCurveInterp(const std::list &l_crossingPoints); + + /*! + * Method which enables to compute a NURBS curve passing through a set of data + * points. + * + * The result of the method is composed by a knot vector, a set of control + * points and a set of associated weights. + */ + void globalCurveInterp(); + + /*! + * Method which enables to compute a NURBS curve approximating a set of data + * points. + * + * The data points are approximated thanks to a least square method. + * + * The result of the method is composed by a knot vector, a set of control + * points and a set of associated weights. + * + * \param l_crossingPoints : The list of data points which have to be interpolated. + * \param l_p : Degree of the NURBS basis functions. + * \param l_n : The desired number of control points. l_n must be under or equal to the + * number of data points. + * \param l_knots : The knot vector. + * \param l_controlPoints : the list of control points. + * \param l_weights : the list of weights. + */ + static void globalCurveApprox(std::vector &l_crossingPoints, unsigned int l_p, unsigned int l_n, + std::vector &l_knots, std::vector &l_controlPoints, + std::vector &l_weights); + /*! + * Method which enables to compute a NURBS curve approximating a set of + * data points. + * + * The data points are approximated thanks to a least square method. + * + * The result of the method is composed by a knot vector, a set of + * control points and a set of associated weights. + * + * \param l_crossingPoints : The list of data points which have to be + * interpolated. + * + * \param n : The desired number of control points. This parameter \e n + * must be under or equal to the number of data points. + */ + void globalCurveApprox(vpList &l_crossingPoints, unsigned int n); + + /*! + * Method which enables to compute a NURBS curve approximating a set of data + * points. + * + * The data points are approximated thanks to a least square method. + * + * The result of the method is composed by a knot vector, a set of control + * points and a set of associated weights. + * + * \param l_crossingPoints : The list of data points which have to be + * interpolated. + * \param n : The desired number of control points. The parameter + * \e n must be under or equal to the number of data points. + */ + void globalCurveApprox(const std::list &l_crossingPoints, unsigned int n); + + /*! + * Method which enables to compute a NURBS curve approximating a set of + * data points. + * + * The data points are approximated thanks to a least square method. + * + * The result of the method is composed by a knot vector, a set of + * control points and a set of associated weights. + * + * \param l_crossingPoints : The list of data points which have to be + * interpolated. + * + * \param n : The desired number of control points. This parameter \e n + * must be under or equal to the number of data points. + */ + void globalCurveApprox(const std::list &l_crossingPoints, unsigned int n); + + /*! + * Method which enables to compute a NURBS curve approximating a set of data + * points. + * + * The data points are approximated thanks to a least square method. + * + * The result of the method is composed by a knot vector, a set of control + * points and a set of associated weights. + */ + void globalCurveApprox(unsigned int n); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpAfma6.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpAfma6.h new file mode 100644 index 00000000..e908db40 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpAfma6.h @@ -0,0 +1,216 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Interface for the Irisa's Afma6 robot. + */ + +/*! + + \file vpAfma6.h + + Modelization of Irisa's gantry robot named Afma6. + +*/ + +#ifndef _vpAfma6_h +#define _vpAfma6_h + +#include + +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + + \class vpAfma6 + + \ingroup group_robot_real_gantry group_robot_simu_gantry + + \brief Modelization of Irisa's gantry robot named Afma6. + + In this modelization, different frames have to be considered. + + - \f$ {\cal F}_f \f$: the reference frame, also called world frame + + - \f$ {\cal F}_e \f$: the end-effector frame located at the intersection of + the 3 rotations. + + - \f$ {\cal F}_c \f$: the camera or tool frame, with \f$^f{\bf M}_c = ^f{\bf + M}_e \; ^e{\bf M}_c \f$ where \f$ ^e{\bf M}_c \f$ is the result of + a calibration stage. We can also consider a custom tool TOOL_CUSTOM and + set this tool during robot initialisation or using set_eMc(). + +*/ +class VISP_EXPORT vpAfma6 +{ +public: +#ifdef VISP_HAVE_AFMA6_DATA + //! File where constant parameters in relation with the robot are stored: + //! joint max, min, coupling factor between 4 ant 5 joint, distance between + //! 5 and 6 joint, transformation eMc between end-effector and camera frame. + static const std::string CONST_AFMA6_FILENAME; + static const std::string CONST_EMC_CCMOP_WITHOUT_DISTORTION_FILENAME; + static const std::string CONST_EMC_CCMOP_WITH_DISTORTION_FILENAME; + static const std::string CONST_EMC_GRIPPER_WITHOUT_DISTORTION_FILENAME; + static const std::string CONST_EMC_GRIPPER_WITH_DISTORTION_FILENAME; + static const std::string CONST_EMC_VACUUM_WITHOUT_DISTORTION_FILENAME; + static const std::string CONST_EMC_VACUUM_WITH_DISTORTION_FILENAME; + static const std::string CONST_EMC_INTEL_D435_WITHOUT_DISTORTION_FILENAME; + static const std::string CONST_EMC_INTEL_D435_WITH_DISTORTION_FILENAME; + static const std::string CONST_EMC_GENERIC_WITHOUT_DISTORTION_FILENAME; + static const std::string CONST_EMC_GENERIC_WITH_DISTORTION_FILENAME; + static const std::string CONST_CAMERA_AFMA6_FILENAME; +#endif + /*! + Name of the camera attached to the CCMOP tool + (vpAfma6ToolType::TOOL_CCMOP). + */ + static const char *const CONST_CCMOP_CAMERA_NAME; + /*! + Name of the camera attached to the 2 fingers gripper tool + (vpAfma6ToolType::TOOL_GRIPPER). + */ + static const char *const CONST_GRIPPER_CAMERA_NAME; + /*! + Name of the camera attached to the vacuum gripper tool + (vpAfma6ToolType::TOOL_VACUUM). + */ + static const char *const CONST_VACUUM_CAMERA_NAME; + /*! + Name of the generic camera attached to the robot hand + (vpAfma6ToolType::TOOL_GENERIC_CAMERA). + */ + static const char *const CONST_GENERIC_CAMERA_NAME; + + /*! + Name of the Intel D435 camera attached to the robot hand + (vpAfma6ToolType::TOOL_INTEL_D435_CAMERA). + */ + static const char *const CONST_INTEL_D435_CAMERA_NAME; + + //! List of possible tools that can be attached to the robot end-effector. + typedef enum + { + TOOL_CCMOP, /*!< Pneumatic CCMOP gripper. */ + TOOL_GRIPPER, /*!< Pneumatic gripper with 2 fingers. */ + TOOL_VACUUM, /*!< Pneumatic vaccum gripper. */ + TOOL_GENERIC_CAMERA, /*!< A generic camera. */ + TOOL_INTEL_D435_CAMERA, /*!< Intel D435 camera */ + TOOL_CUSTOM /*!< A user defined tool. */ + } vpAfma6ToolType; + + //! Default tool attached to the robot end effector + static const vpAfma6ToolType defaultTool; + +public: + vpAfma6(); + /*! Destructor that does nothing. */ + virtual ~vpAfma6() { } + + /** @name Inherited functionalities from vpAfma6 */ + //@{ + void init(void); + void init(const std::string &camera_extrinsic_parameters); + void init(const std::string &camera_extrinsic_parameters, const std::string &camera_intrinsic_parameters); + void init(vpAfma6::vpAfma6ToolType tool, const std::string &filename); + void init(vpAfma6::vpAfma6ToolType tool, const vpHomogeneousMatrix &eMc_); + void + init(vpAfma6::vpAfma6ToolType tool, + vpCameraParameters::vpCameraParametersProjType projModel = vpCameraParameters::perspectiveProjWithoutDistortion); + + vpHomogeneousMatrix getForwardKinematics(const vpColVector &q) const; + int getInverseKinematics(const vpHomogeneousMatrix &fMc, vpColVector &q, const bool &nearest = true, + const bool &verbose = false) const; + + vpHomogeneousMatrix get_eMc() const; + vpHomogeneousMatrix get_fMc(const vpColVector &q) const; + void get_fMe(const vpColVector &q, vpHomogeneousMatrix &fMe) const; + void get_fMc(const vpColVector &q, vpHomogeneousMatrix &fMc) const; + + void get_cMe(vpHomogeneousMatrix &cMe) const; + void get_cVe(vpVelocityTwistMatrix &cVe) const; + void get_eJe(const vpColVector &q, vpMatrix &eJe) const; + void get_fJe(const vpColVector &q, vpMatrix &fJe) const; + + //! Get the current tool type + vpAfma6ToolType getToolType() const { return tool_current; } + //! Get the current camera model projection type + vpCameraParameters::vpCameraParametersProjType getCameraParametersProjType() const { return projModel; } + + void getCameraParameters(vpCameraParameters &cam, const unsigned int &image_width, + const unsigned int &image_height) const; + void getCameraParameters(vpCameraParameters &cam, const vpImage &I) const; + void getCameraParameters(vpCameraParameters &cam, const vpImage &I) const; + + vpColVector getJointMin() const; + vpColVector getJointMax() const; + double getCoupl56() const; + double getLong56() const; + + void parseConfigFile(const std::string &filename); + + virtual void set_eMc(const vpHomogeneousMatrix &eMc); + //@} + + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpAfma6 &afma6); + +protected: + /** @name Protected Member Functions Inherited from vpAfma6 */ + //@{ + //! Set the current tool type + void setToolType(vpAfma6::vpAfma6ToolType tool) { tool_current = tool; } + //@} + +public: + static const unsigned int njoint; ///< Number of joint. + +protected: + double _coupl_56; // coupling between join 5 and 6 + double _long_56; // distance between join 5 and 6 + double _joint_max[6]; // Maximal value of the joints + double _joint_min[6]; // Minimal value of the joints + // Minimal representation of _eMc + vpTranslationVector _etc; // meters + vpRxyzVector _erc; // radian + + vpHomogeneousMatrix _eMc; // Camera extrinsic parameters: effector to camera + +protected: + //! Current tool in use + vpAfma6ToolType tool_current; + // Used projection model + vpCameraParameters::vpCameraParametersProjType projModel; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpBiclops.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpBiclops.h new file mode 100644 index 00000000..a2172052 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpBiclops.h @@ -0,0 +1,325 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Interface for the Biclops robot. + */ + +#ifndef VP_BICLOPS_H +#define VP_BICLOPS_H + +#include + +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpBiclops + * + * \ingroup group_robot_real_ptu + * + * \brief Jacobian, geometric model functionalities... for Biclops, pan, tilt + * head. + * + * Two different Denavit-Hartenberg representations of the robot are + * implemented. As mentioned in vpBiclops::DenavitHartenbergModel they differ + * in the orientation of the tilt axis. Use setDenavitHartenbergModel() to + * select the representation. + * + * See http://www.traclabs.com/tracbiclops.htm for more details concerning the + * hardware. + * +*/ +class VISP_EXPORT vpBiclops +{ +public: + /*! + * Two different Denavit-Hartenberg representations of the robot are + * implemented. As you can see in the next image, they differ in the orientation of the tilt axis. + * + * \image html img-biclops-frames.jpg Biclops PT models + * + * The first representation, vpBiclops::DH1 is given by: + * + * | Joint | \f$a_i\f$ | \f$d_i\f$ | \f$\alpha_i\f$ | \f$\theta_i\f$ | + * | :---: | :-------: | :-------: | -------------: | ----------------: | + * | 1 | 0 | 0 | \f$-\pi/2\f$ | \f$q_1\f$ | + * | 2 | 0 | 0 | \f$ \pi/2\f$ | \f$q_2 + \pi/2\f$ | + * + * The second one, vpBiclops::DH2 is given by: + * + * | Joint | \f$a_i\f$ | \f$d_i\f$ | \f$\alpha_i\f$ | \f$\theta_i\f$ | + * | :---: | :-------: | :-------: | -------------: | ----------------: | + * | 1 | 0 | 0 | \f$ \pi/2\f$ | \f$q_1\f$ | + * | 2 | 0 | 0 | \f$-\pi/2\f$ | \f$q_2 - \pi/2\f$ | + * + * where \f$q_1, q_2\f$ are respectively the pan and tilt joint + * positions. + * + * In those representations, the pan is oriented from left to right, while + * the tilt is oriented + * - in vpBiclops::DH1 from down to top, + * - in vpBiclops::DH2 from top to down. + */ + typedef enum + { + DH1, //!< First Denavit-Hartenberg representation. + DH2 //!< Second Denavit-Hartenberg representation. + } DenavitHartenbergModel; + +public: + static const unsigned int ndof; //!< Number of dof + + // Geometric model + static const float h; //!< Vertical offset from last joint to camera frame used in set_cMe() + static const float panJointLimit; //!< Pan axis +/- joint limit in rad + static const float tiltJointLimit; //!< Tilt axis +/- joint limit in rad + static const float speedLimit; //!< Pan and tilt axis max velocity in rad/s to perform a displacement + +protected: + DenavitHartenbergModel m_dh_model; //!< Denavit-Hartenberg model + vpHomogeneousMatrix m_cMe; //!< Camera frame to PT end-effector frame transformation + +public: + /*! + * Default constructor. Call init() that sets vpBiclops::DH1 Denavit-Hartenberg model. + */ + vpBiclops(void); + + /*! + * Destructor that does nothing. + */ + virtual ~vpBiclops() { } + + /** @name Inherited functionalities from vpBiclops */ + //@{ + + /*! + * Initialization. + * - By default vpBiclops::DH1 Denavit-Hartenberg model is selected. + * - Initialize also the default \f${^c}{\bf M}_e\f$ transformation calling set_cMe(). + * \f[ + * {^c}{\bf M}_e = \left( + * \begin{matrix} + * 0 & 1 & 0 & 0 \\ + * -1 & 0 & 0 & h \\ + * 0 & 0 & 1 & 0 \\ + * 0 & 0 & 0 & 1 + * \end{matrix} + * \right) + * \f] + */ + void init(void); + + /*! + * Compute the direct geometric model of the camera: fMc + * + * \warning Provided for compatibility with previous versions. Use rather + * get_fMc(const vpColVector &, vpHomogeneousMatrix &). + * + * \param q : Joint position for pan and tilt axis. + * + * \param fMc : Homogeneous matrix corresponding to the direct geometric model + * of the camera. Describes the transformation between the robot reference + * frame (called fixed) and the camera frame. + * + * \sa get_fMc(const vpColVector &, vpHomogeneousMatrix &) + */ + void computeMGD(const vpColVector &q, vpHomogeneousMatrix &fMc) const; + + /*! + * Return the direct geometric model of the camera: fMc + * + * \warning Provided for compatibility with previous versions. Use rather + * get_fMc(const vpColVector &). + * + * \param q : Joint position for pan and tilt axis. + * + * \return fMc, the homogeneous matrix corresponding to the direct geometric + * model of the camera. Describes the transformation between the robot + * reference frame (called fixed) and the camera frame. + * + * \sa get_fMc(const vpColVector &) + */ + vpHomogeneousMatrix computeMGD(const vpColVector &q) const; + + /*! + * Compute the direct geometric model of the camera in terms of pose vector. + * + * \warning Provided for compatibility with previous versions. Use rather + * get_fMc(const vpColVector &, vpPoseVector &). + * + * \param q : Joint position for pan and tilt axis. + * + * \param fPc : Pose vector corresponding to the transformation between the + * robot reference frame (called fixed) and the camera frame. + * + * \sa get_fMc(const vpColVector &, vpPoseVector &) + */ + void computeMGD(const vpColVector &q, vpPoseVector &fPc) const; + + /*! + * Return the transformation \f${^c}{\bf M}_e\f$ between the camera frame and + * the end effector frame. + */ + vpHomogeneousMatrix get_cMe() const { return m_cMe; } + + /*! + * Get the twist matrix corresponding to the transformation between the + * camera frame and the end effector frame. The end effector frame is located + * on the tilt axis. + * + * \param[out] cVe : Twist transformation between camera and end effector frame to + * express a velocity skew from end effector frame in camera frame. + */ + void get_cVe(vpVelocityTwistMatrix &cVe) const; + + /*! + * Compute the direct geometric model of the camera: fMc + * + * \param[in] q : Joint position for pan and tilt axis. + * + * \param[out] fMc : Homogeneous matrix corresponding to the direct geometric model + * of the camera. Describes the transformation between the robot reference + * frame (called fixed) and the camera frame. + */ + void get_fMc(const vpColVector &q, vpHomogeneousMatrix &fMc) const; + + /*! + * Compute the direct geometric model of the camera in terms of pose vector. + * + * \param[in] q : Joint position for pan and tilt axis. + * + * \param[out] fPc : Pose vector corresponding to the direct geometric model + * of the camera. Describes the transformation between the robot reference + * frame (called fixed) and the camera frame. + */ + void get_fMc(const vpColVector &q, vpPoseVector &fPc) const; + + /*! + * Return the direct geometric model of the camera: fMc + * + * \param[in] q : Joint position for pan and tilt axis. + * + * \return fMc, the homogeneous matrix corresponding to the direct geometric + * model of the camera. Describes the transformation between the robot + * reference frame (called fixed) and the camera frame. + */ + vpHomogeneousMatrix get_fMc(const vpColVector &q) const; + + /*! + * Return the direct geometric model of the end effector: fMe + * + * \param[in] q : Joint position for pan and tilt axis. + * + * \return fMe, the homogeneous matrix corresponding to the direct geometric + * model of the end effector. Describes the transformation between the robot + * reference frame (called fixed) and the end effector frame. + */ + vpHomogeneousMatrix get_fMe(const vpColVector &q) const; + + /*! + * Get the robot jacobian expressed in the end-effector frame. + * + * \warning Re is not the embedded camera frame. It corresponds to the frame + * associated to the tilt axis (see also get_cMe). + * + * \param[in] q : Joint position for pan and tilt axis. + * + * \param[out] eJe : Jacobian between end effector frame and end effector frame (on + * tilt axis). + */ + void get_eJe(const vpColVector &q, vpMatrix &eJe) const; + + /*! + * Get the robot jacobian expressed in the robot reference frame + * + * \param[in] q : Joint position for pan and tilt axis. + * + * \param[out] fJe : Jacobian between reference frame (or fix frame) and end + * effector frame (on tilt axis). + */ + void get_fJe(const vpColVector &q, vpMatrix &fJe) const; + + /*! + * Return the Denavit-Hartenberg representation used to model the head. + * \sa vpBiclops::DenavitHartenbergModel + */ + inline vpBiclops::DenavitHartenbergModel getDenavitHartenbergModel() const { return m_dh_model; } + + /*! + * Set the default homogeneous matrix corresponding to the transformation + * between the camera frame and the end effector frame. The end effector frame + * is located on the tilt axis. + * + * \f[ + * {^c}{\bf M}_e = \left( + * \begin{matrix} + * 0 & 1 & 0 & 0 \\ + * -1 & 0 & 0 & h \\ + * 0 & 0 & 1 & 0 \\ + * 0 & 0 & 0 & 1 + * \end{matrix} + * \right) + * \f] + */ + void set_cMe(); + + /*! + * Set the transformation between the camera frame and the end effector + * frame. + */ + void set_cMe(const vpHomogeneousMatrix &cMe) { m_cMe = cMe; } + + /*! + * Set the Denavit-Hartenberg representation used to model the head. + * + * \param[in] dh_model : Denavit-Hartenberg model. \sa vpBiclops::DenavitHartenbergModel + */ + inline void setDenavitHartenbergModel(vpBiclops::DenavitHartenbergModel dh_model = vpBiclops::DH1) + { + m_dh_model = dh_model; + } + +//@} + +/*! + * Set output stream with Biclops parameters. + * @param os : Output stream. + * @param dummy : Not used. + * @return Output stream with the Biclops parameters. + */ + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpBiclops &dummy); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpImageSimulator.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpImageSimulator.h new file mode 100644 index 00000000..1cbdae6d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpImageSimulator.h @@ -0,0 +1,321 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: Class which enables to project an image in the 3D space + * and get the view of a virtual camera. + * + * + * Authors: + * Amaury Dame + */ + +#ifndef vpImageSimulator_h +#define vpImageSimulator_h + +/*! + \class vpImageSimulator + + \ingroup group_robot_image_simu + + \brief Class which enables to project an image in the 3D space and get the + view of a virtual camera. + + The image is represented by a rectangular image whose corners coordinates + are known in the 3D frame linked to the 3D rectangle. + + The 3D rectangle is positionned relative to a virtual camera (represented by + its intrinsic parameters). Indeed, the pose \f$ cMt \f$ has to be given by + the user of the class. + + And finally, the view of the virtual camera is given by the geImage() + method. + + You can use a colored or a gray scaled image. + + To avoid the aliasing especially when the camera is very near from the image + plane, a bilinear interpolation can be done for every pixels which have to + be filled in. By default this functionality is not used because it consumes + lot of time. + + The following example explain how to use the class. + + \code + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpImage Icamera(480,640,vpRGBa(0)); + vpImage Iimage(60,60); + + // Initialise the image which will be projected into the image Icamera + vpRGBa colorb(0,0,255); + vpRGBa colorw(255,255,255); + vpRGBa colorr(255,0,0); + for(int i = 0; i < 60; i++) { + for(int j = 0; j < 20; j++) + Iimage[i][j] = colorb; + for(int j = 20; j < 40; j++) + Iimage[i][j] = colorw; + for(int j = 40; j < 60; j++) + Iimage[i][j] = colorr; + } + + // Initialise the 3D coordinates of the Iimage corners + vpColVector X[4]; + for (int i = 0; i < 4; i++) X[i].resize(3); + // Top left corner + X[0][0] = -1; + X[0][1] = -1; + X[0][2] = 0; + + // Top right corner + X[1][0] = 1; + X[1][1] = -1; + X[1][2] = 0; + + // Bottom right corner + X[2][0] = 1; + X[2][1] = 1; + X[2][2] = 0; + + //Bottom left corner + X[3][0] = -1; + X[3][1] = 1; + X[3][2] = 0; + + vpImageSimulator sim; + sim.init(Iimage, X); + + sim.setCameraPosition(vpHomogeneousMatrix(0,0,5,vpMath::rad(60),vpMath::rad(0),0)); + + vpCameraParameters cam(868.0, 869.0, 320, 240); + + sim.getImage(Icamera,cam); + + return 0; + } + \endcode + + Tutorials
+ If you are interested in projecting an image in the 3D space and getting the + view of a virtual camera, you may have a look at:
+ + - \ref tutorial-simu-image +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +class VISP_EXPORT vpImageSimulator +{ +public: + typedef enum { COLORED, GRAY_SCALED } vpColorPlan; + + typedef enum { SIMPLE, BILINEAR_INTERPOLATION } vpInterpolationType; + +private: + vpColVector X[4]; + vpHomogeneousMatrix cMt; + vpColVector X2[4]; + std::vector pt; + std::vector ptClipped; + + vpInterpolationType interp; + + // normal repere objet + vpColVector normal_obj; + // normal repere cam + vpColVector normal_Cam; + // normal repere cam sous forme de pointeur sinon vpColVector prend trop de + // temps + double *normal_Cam_optim; + + // distance du plan au centre de camera + double distance; + double visible_result; + bool visible; + + // point 3D du plan definissant ou sera le pixel (0,0) de l'image + double *X0_2_optim; + + // variable pour intersection algebre : vecteurs de la base 2D et leur norme + double frobeniusNorm_u, fronbniusNorm_v; + + // 2 vecteur 3D (typiquement deux cotes du plan) qui definissent le repere + // 2D u,v de l'image + vpColVector vbase_u, vbase_v; + // version pointeur des vecteurs de base + double *vbase_u_optim; + double *vbase_v_optim; + + // allocation memoire du point 3D d'intersection entre le vecteur + // (centre_cam - point_plan_image) et le plan + double *Xinter_optim; + + // triangles de projection du plan + std::vector listTriangle; + + // image de texture + vpColorPlan colorI; + vpImage Ig; + vpImage Ic; + + vpRect rect; + bool cleanPrevImage; + bool setBackgroundTexture; // flag set when the background is to a texture + // using setBackGroundTexture() + vpColor bgColor; + + vpColVector focal; + + // boolean to tell if the points in the camera frame have to be clipped + bool needClipping; + +public: + VP_EXPLICIT vpImageSimulator(const vpColorPlan &col = COLORED); + vpImageSimulator(const vpImageSimulator &text); + virtual ~vpImageSimulator(); + + vpImageSimulator &operator=(const vpImageSimulator &sim); + + // creation du plan a partir de ses coordonnees 3D ds repere objet et de son + // image texture + void init(const vpImage &I, vpColVector *X); + void init(const vpImage &I, vpColVector *X); +#ifdef VISP_HAVE_MODULE_IO + void init(const char *file_image, vpColVector *X); +#endif + void init(const vpImage &I, const std::vector &X); + void init(const vpImage &I, const std::vector &X); +#ifdef VISP_HAVE_MODULE_IO + void init(const char *file_image, const std::vector &X); +#endif + + // projection du plan par cMo => creation des deux triangles definissant + // projection du plan sur plan image (coord en metre) + void setCameraPosition(const vpHomogeneousMatrix &cMt); + + void setInterpolationType(const vpInterpolationType interplt) { this->interp = interplt; } + + void getImage(vpImage &I, const vpCameraParameters &cam); + void getImage(vpImage &I, const vpCameraParameters &cam); + + void getImage(vpImage &I, vpImage &Isrc, const vpCameraParameters &cam); + void getImage(vpImage &I, vpImage &Isrc, const vpCameraParameters &cam); + + void getImage(vpImage &I, const vpCameraParameters &cam, vpMatrix &zBuffer); + void getImage(vpImage &I, const vpCameraParameters &cam, vpMatrix &zBuffer); + + static void getImage(vpImage &I, std::list &list, const vpCameraParameters &cam); + static void getImage(vpImage &I, std::list &list, const vpCameraParameters &cam); + + std::vector get3DcornersTextureRectangle(); + + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpImageSimulator & /*ip*/); + + /*! + As it can be time consuming to reset all the image to a default baground + value, this function enable to reset only the pixel which changed the + previous time. + + By default this functionality is disabled. and the background color is + white. + + \param clean : Enable the reset method. + \param color : Color of the back ground. + */ + void setCleanPreviousImage(const bool &clean, const vpColor &color = vpColor::white) + { + cleanPrevImage = clean; + bgColor = color; + } + + /*! + This function allows to set the background to a texture instead of the + default black background. + + \param Iback : Image/Texture for the background + */ + inline void setBackGroundTexture(const vpImage &Iback) + { + setBackgroundTexture = true; + Ig = Iback; + } + +private: + void initPlan(vpColVector *X); + + // result = plan est visible. + // ie: un plan est oriente dans si normal_plan.focal < 0 => plan est visible + // sinon invisible. + bool isVisible() { return visible; } + + // function that project a point x,y on the plane, return true if the + // projection is on the limited plane + // and in this case return the corresponding image pixel Ipixelplan + bool getPixel(const vpImagePoint &iP, unsigned char &Ipixelplan); + bool getPixel(const vpImagePoint &iP, vpRGBa &Ipixelplan); + bool getPixel(vpImage &Isrc, const vpImagePoint &iP, unsigned char &Ipixelplan); + bool getPixel(vpImage &Isrc, const vpImagePoint &iP, vpRGBa &Ipixelplan); + bool getPixelDepth(const vpImagePoint &iP, double &Zpixelplan); + bool getPixelVisibility(const vpImagePoint &iP, double &Zpixelplan); + + // operation 3D de base : + void project(const vpColVector &_vin, const vpHomogeneousMatrix &_cMt, vpColVector &_vout); + // donne coordonnes homogenes de _v; + void getHomogCoord(const vpColVector &_v, vpColVector &_vH); + // donne coordonnes _v en fction coord homogenes _vH; + void getCoordFromHomog(const vpColVector &_vH, vpColVector &_v); + + void getRoi(const unsigned int &Iwidth, const unsigned int &Iheight, const vpCameraParameters &cam, + const std::vector &point, vpRect &rect); +}; +END_VISP_NAMESPACE +#endif + +/* + * Local variables: + * c-basic-offset: 2 + * End: + */ diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpPioneer.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpPioneer.h new file mode 100644 index 00000000..4b533535 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpPioneer.h @@ -0,0 +1,144 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Common features for Pioneer unicycle mobile robots. + */ +#ifndef VPPIONEER_H +#define VPPIONEER_H + +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpPioneer + * + * \ingroup group_robot_real_unicycle group_robot_simu_unicycle + * + * \brief Generic functions for Pioneer mobile robots. + * + * This class provides common features for Pioneer mobile robots. + * This robot has two control velocities \f$(v_x, w_z)\f$, the translational + * and rotational velocities of the mobile platform respectively. + * + * The figure below shows the position of the frames that are used to model the + * robot. The end effector frame is here located at the middle point between + * the two wheels. + * + * \image html pioneer.png + * + * The robot jacobian at the end effector frame, the point located at the + * middle between the two wheels is given by: + * + * \f[ + * {^e}{\bf J}_e = \left(\begin{array}{cc} + * 1 & 0 \\ + * 0 & 0 \\ + * 0 & 0 \\ + * 0 & 0 \\ + * 0 & 0 \\ + * 0 & 1 \\ + * \end{array} + * \right) + * \f] + * + * Considering \f$(v_x, w_z)\f$, it is possible to compute \f$\bf v\f$ the six + * dimension velocity skew expressed at the end effector frame by: + * + * \f[ + * {\bf v} = {^e}{\bf J}_e \; + * \left(\begin{array}{c} + * v_x \\ + * w_z \\ + * \end{array} + * \right) + * \f]. +*/ +class VISP_EXPORT vpPioneer : public vpUnicycle +{ +public: + /*! + * Create a default Pioneer robot. + */ + vpPioneer() : vpUnicycle() + { + set_cMe(); + set_eJe(); + } + +private: + /*! + * Set the transformation between the camera frame and the mobile platform + * end effector frame. + */ + void set_cMe() + { + // Position of mobile platform end effector frame in the camera frame + double l = 0.13; // distance between the camera frame and the mobile robot frame + vpTranslationVector cte; // meters + vpRxyzVector cre; // radian + cte.set(0, 0, -l); + cre.buildFrom(vpMath::rad(90.), 0, vpMath::rad(90.)); + cMe_.buildFrom(cte, vpRotationMatrix(cre)); + } + + /*! + * Set the robot jacobian at the end effector frame, the point located at the + * middle between the two wheels. + * + * Considering \f${\bf v} = {^e}{\bf J}_e \; [v_x, w_z]\f$ with + * \f$(v_x, w_z)\f$ respectively the translational and rotational control + * velocities of the mobile robot and \f$\bf v\f$ the six dimension velocity + * skew expressed at the end effector frame, the robot jacobian is given by: + * + * \f[ + * {^e}{\bf J}_e = \left(\begin{array}{cc} + * 1 & 0 \\ + * 0 & 0 \\ + * 0 & 0 \\ + * 0 & 0 \\ + * 0 & 0 \\ + * 0 & 1 \\ + * \end{array} + * \right) + * \f] + */ + void set_eJe() + { + eJe_.resize(6, 2); // pioneer jacobian expressed at point M + eJe_ = 0; + eJe_[0][0] = 1; // vx + eJe_[5][1] = 1; // wz + } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpPioneerPan.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpPioneerPan.h new file mode 100644 index 00000000..6d8ad824 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpPioneerPan.h @@ -0,0 +1,228 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Common features for Pioneer unicycle mobile robots. + */ +#ifndef VPPIONEERPAN_H +#define VPPIONEERPAN_H + +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpPioneerPan + * + * \ingroup group_robot_real_unicycle group_robot_simu_unicycle + * + * \brief Generic functions for Pioneer mobile robots equipped with a pan head. + * + * This class provides common features for Pioneer mobile robots equipped with a + * pan head. + * + * This robot has three control velocities \f$(v_x, w_z, \dot{q_1})\f$, the + * translational and rotational velocities of the mobile platform, the pan head + * velocity respectively. + * + * The figure below shows the position of the frames that are used to model the + * robot. The end effector frame is here located at the pan axis. + * + * \image html pioneer-pan.png + * + * Considering + * \f[{\bf v} = {^e}{\bf J}_e \; + * \left(\begin{array}{c} + * v_x \\ + * w_z \\ + * \dot{q_1} \\ + * \end{array} + * \right) + * \f] + * with + * \f$(v_x, w_z)\f$ respectively the translational and rotational control + * velocities of the mobile platform, \f$\dot{q_1}\f$ the joint velocity of the + * pan head and \f$\bf v\f$ the six dimension velocity skew expressed at point + * E in frame E, the robot jacobian is given by: + * + * \f[ + * {^e}{\bf J}_e = \left(\begin{array}{ccc} + * c_1 & -c_1*p_y - s_1*p_x & 0 \\ + * 0 & 0 & 0 \\ + * s_1 & -s_1*p_y + c_1*p_x & 0 \\ + * 0 & 0 & 0 \\ + * 0 & -1 & 1 \\ + * 0 & 0 & 0 \\ + * \end{array} + * \right) + * \f] + * + * with \f$p_x, p_y\f$ the position of the head base frame in the mobile + * platform frame located at the middle point between the two wheels. +*/ +class VISP_EXPORT vpPioneerPan : public vpUnicycle +{ +public: + /*! + * Create a pioneer mobile robot equipped with a pan head. + */ + vpPioneerPan() : mMp_(), pMe_() + { + double q = 0; // Initial position of the pan axis + set_mMp(); + set_pMe(q); + set_cMe(); + set_eJe(q); + } + + /** @name Inherited functionalities from vpPioneerPan */ + //@{ + + /*! + * Set the robot jacobian expressed at point E the end effector frame located + * on the pan head. + * + * Considering \f${\bf v} = {^e}{\bf J}_e \; [v_x, w_z, \dot{q_1}]\f$ with + * \f$(v_x, w_z)\f$ respectively the translational and rotational control + * velocities of the mobile platform, \f$\dot{q_1}\f$ the joint velocity of + * the pan head and \f$\bf v\f$ the six dimension velocity skew expressed at + * point E in frame E, the robot jacobian is given by: + * + * \f[ + * {^e}{\bf J}_e = \left(\begin{array}{ccc} + * c_1 & -c_1*p_y - s_1*p_x & 0 \\ + * 0 & 0 & 0 \\ + * s_1 & -s_1*p_y + c_1*p_x & 0 \\ + * 0 & 0 & 0 \\ + * 0 & -1 & 1 \\ + * 0 & 0 & 0 \\ + * \end{array} + * \right) + * \f] + * + * with \f$p_x, p_y\f$ the position of the head base frame in the mobile + * platform frame located at the middle point between the two wheels. + */ + void set_eJe(double q_pan) + { + double px = mMp_[0][3]; + double py = mMp_[1][3]; + double c1 = cos(q_pan); + double s1 = sin(q_pan); + + eJe_.resize(6, + 3); // robot jacobian expressed at the pan head end effector + + eJe_ = 0; + eJe_[0][0] = c1; + eJe_[0][1] = -c1 * py - s1 * px; + + eJe_[2][0] = s1; + eJe_[2][1] = -s1 * py + c1 * px; + + eJe_[4][1] = -1; + eJe_[4][2] = 1; + } + //@} + +protected: + /** @name Protected Member Functions Inherited from vpPioneerPan */ + //@{ + /*! + * Set the transformation between the camera frame and the pan head end + * effector frame. + */ + void set_cMe() + { + // Position of pan head end effector frame in the camera frame + double cx = 0; + double cy = -0.065; // distance between camera and tilt axis + double cz = 0; + vpTranslationVector etc(cx, cy, cz); + vpRotationMatrix eRc; + eRc[0][0] = eRc[1][1] = eRc[2][2] = 0; + eRc[0][2] = 1; + eRc[1][1] = 1; + eRc[2][0] = -1; + + vpHomogeneousMatrix eMc; + eMc.buildFrom(etc, eRc); + + cMe_ = eMc.inverse(); + } + + /*! + * Set the transformation between the mobile platform frame + * located at the middle point between the two wheels and the base frame of + * the pan head. + */ + void set_mMp() + { + // Position of the pan head in the mobile platform frame + double px = 0.103; // distance between the pan frame and the robot frame + double py = 0; + double pz = 0.27; + vpTranslationVector mtp; + mtp.set(px, py, pz); + + vpRotationMatrix mRp; // set to Identity + mRp[1][1] = mRp[2][2] = -1.; + + mMp_.insert(mtp); + mMp_.insert(mRp); + } + + /*! + * Set the transformation between the pan head reference frame and the + * end-effector frame. + * + * \param q : Position in rad of the pan axis. + */ + void set_pMe(const double q) + { + vpRotationMatrix pRe; + pRe[0][0] = cos(q); + pRe[0][2] = pRe[1][0] = sin(q); + pRe[1][1] = pRe[2][2] = 0.; + pRe[2][1] = 1.; + pRe[1][2] = -pRe[0][0]; + + pMe_.insert(pRe); + } + //@} + +protected: + vpHomogeneousMatrix mMp_; // constant + vpHomogeneousMatrix pMe_; // depends on q pan +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpPololu.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpPololu.h new file mode 100644 index 00000000..54e2ed31 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpPololu.h @@ -0,0 +1,334 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Common features for Pololu Maestro Servo Motor. + */ + +#ifndef _vpPololu_h_ +#define _vpPololu_h_ + +#include + +#if defined(VISP_HAVE_POLOLU) && defined(VISP_HAVE_THREADS) + +#include +#include +#include + +class RPMSerialInterface; + +BEGIN_VISP_NAMESPACE +/*! + * \class vpPololu + * \ingroup group_robot_real_arm + * + * \brief Interface for the Pololu Maestro USB Servo Controllers. + * + * See https://www.pololu.com/category/102/maestro-usb-servo-controllers for more details. + * + * This class give a position and velocity control for the servo motors plugged into the board on a given channel. + * If you want to control two or more servo motors, you need to instanciate a new object for each additional + * servo motor. An example is given in the vpRobotPololuPtu class that allows to control a pan-tilt unit with + * two servo motors. + * + * It implements a velocity controller that runs in a separate thread. + * + * - A servomotor can be position-controlled by giving it position commands in pwm units using setPwmPosition() + * or in angles expressed in radians using setAngularPosition(). + * - It can be also velocity-controller by giving it velocity commands in pwm units using setPwmVelocity() or in ras/s + * using setAngularVelocity(). + * + * The conversion between pwm units and radians positions is done using radToPwm() or pwmToRad(). + * For velocity control conversion from pwm units and rad/s is done using speedToRadS() and radSToSpeed(). + * + * Each servo has a pwm position range that could be retrieved using calibrate() and set using set using setPwmRange(). + * + * It is the user responsability to set the corresponding angular range using setAngularRange(). +*/ +class VISP_EXPORT vpPololu +{ +public: + /*! + * Default constructor. + * + * \param[in] verbose : When true enable verbose mode. + * + * You need to call connect() to setup the serial link with the Pololu board. + */ + vpPololu(bool verbose = false); + + /*! + * Constructor that enables the serial link with the Pololu board calling internally connect(). + * The velocity controller thread is created in the constructor and will run independently of the rest of the class. + * You can set velocity commands using setPwmVelocity() or setAngularVelocity(). + * + * \param[in] device : Serial device name to dial with Pololu board. + * \param[in] baudrate : Baudrate used to dial with Pololu board. Note that this parameter is only used on Windows. + * \param[in] channel : Channel to which the servo is connected to the Pololu board. + * \param[in] verbose : When true enable verbose mode. + */ + vpPololu(const std::string &device, int baudrate = 38400, int channel = 0, bool verbose = false); + + /*! + * Destructor. + */ + virtual ~vpPololu(); + + /*! + * Move servo motor to minimal pwm position and then to maximal pwm position + * to retrieve min and max pwm values. + * + * \param[out] pwm_min : Min position (pwm). + * \param[out] pwm_max : Max position (pwm). + */ + void calibrate(unsigned short &pwm_min, unsigned short &pwm_max); + + /*! + * Open a connection with the Pololu board. + * + * \param[in] device : Serial device name to dial with Pololu board. + * \param[in] baudrate : Baudrate used to dial with Pololu board. Note that this parameter is only used on Windows. + * \param[in] channel : Channel to which the servo is connected to the Pololu board. + */ + void connect(const std::string &device, int baudrate, int channel); + + /*! + * Check if the serial connection is still up. + * + * \return true is the connection is enabled, false if the board is not connected. + */ + bool connected() const; + + /*! + * Return angular position in rad. + * + * \return Current position in rad. + */ + float getAngularPosition() const; + + /*! + * Return PWM position. + * + * \return Current PWM position. + */ + unsigned short getPwmPosition() const; + + /*! + * Get min, max and range for angle cmd. + * + * \param[out] minAngle : Min range value for angle control. + * + * \param[out] maxAngle : Max range value for angle control. + * + * \sa setAngularRange() + */ + void getRangeAngles(float &minAngle, float &maxAngle) const; + + /*! + * Get min, max range for PWM cmd. + * + * \param[out] min : Min value for PWM control. + * + * \param[out] max : Max value for PWM control. + * + * \sa setPwmRange() + */ + void getRangePwm(unsigned short &min, unsigned short &max); + + /*! + * Set the position to reach in angle. + * + * \param[in] pos_rad : Position to reach in radians. + * + * \param[in] vel_rad_s : Velocity to use for the positioning in rad/s. Default is '0' and will use + * maximum speed. + */ + void setAngularPosition(float pos_rad, float vel_rad_s = 0.f); + + /*! + * Set min and max axis angles range in rad. + * + * \param[in] min_angle : Min value for angle (rad). + * + * \param[in] max_angle : Max value for angle (rad). + * + * \sa getRangeAngles() + */ + inline void setAngularRange(float min_angle, float max_angle) + { + m_min_angle = min_angle; + m_max_angle = max_angle; + m_range_angle = m_max_angle - m_min_angle; + } + + /*! + * Set the angular velocity of the motor movements in rad/s. + * + * \param[in] vel_rad_s : Velocity to apply for movement in rad/s. + */ + void setAngularVelocity(float vel_rad_s); + + /*! + * Set the position to reach in PWM. + * + * \param[in] pos_pwm : Position in PWM to reach. + * + * \param[in] speed_pwm : Speed to use for movement in units of (0.25 us)/(10 ms). Default is 0, maximum speed. + * + * \exception When PWM out of range. + */ + void setPwmPosition(unsigned short pos_pwm, unsigned short speed_pwm = 0); + + /*! + * Set min, max PWM cmd. + * + * \param[in] min_pwm : Min value for PWM control. + * + * \param[in] max_pwm : Max value for PWM control. + * + * \sa getRangePwm() + */ + inline void setPwmRange(unsigned short min_pwm, unsigned short max_pwm) + { + m_min_pwm = min_pwm; + m_max_pwm = max_pwm; + m_range_pwm = m_max_pwm - m_min_pwm; + } + + /*! + * Set the pwm velocity of the motor movements. The motor will move to the edge of the + * range at the given speed. + * + * \param[in] pwm_vel : PWM velocity to use for movement in units of (0.25 us)/(10 ms). When set to 0, will use the + * maximum speed. + */ + void setPwmVelocity(short pwm_vel); + + /*! + * Enable/disable verbose mode. + * + * \param[in] verbose : Set to true to enable verbose mode, false otherwise. + */ + void setVerbose(bool verbose) + { + m_verbose = verbose; + } + + /*! + * Stop the velocity command thread. + */ + void stopVelocityCmd(); + + /*! + * @name Public Member Functions for Conversion + */ + //@{ + /*! + * Convert a PWM value to an angle in radians. + * + * \param[in] pwm : PWM value. + * + * \return Corresponding angle value in radian for the PWM. + * + * \sa radToPwm() + */ + float pwmToRad(unsigned short pwm) const; + + /*! + * Convert angles in radians to PWM for servo commands. + * + * \param angle : Angle in radian to convert. + * + * \return Corresponding PWM value for the angle. + * + * \sa pwmToRad() + */ + unsigned short radToPwm(float angle) const; + + /*! + * Convert deg/s speed into Pololu's speed. + * + * \param speed_rad_s : Speed converted to rad/s. + * + * \return Signed speed in units of (0.25 us)/(10 ms). + * + * \sa speedToRadS() + */ + short radSToSpeed(float speed_rad_s) const; + + /*! + * Convert Pololu's pwm velocity to rad/s velocity. + * + * \param[in] speed : Signed speed in units of (0.25 us)/(10 ms). + * + * \return Speed converted to rad/s. + * + * \sa radSToSpeed() + */ + float speedToRadS(short speed) const; + //@} + +private: + static RPMSerialInterface *m_interface; // Only one interface should be used even when controlling multiple servos + static int m_nb_servo; // Object counter to handel serial interface destruction + + int m_channel; + bool m_apply_velocity_cmd; + bool m_stop_velocity_cmd_thread; + + unsigned short m_vel_speed; //!< PWM speed to in velocity control + unsigned short m_vel_target_position; //!< Min or max PWM target position to reach in velocity control + + unsigned short m_vel_speed_prev; //!< Previous PWM speed to in velocity control + unsigned short m_vel_target_position_prev; //!< Previous Min or max PWM target position to reach in velocity control + + std::mutex m_mutex_velocity_cmd; + + // ranges + unsigned short m_min_pwm = 4095; + unsigned short m_max_pwm = 7905; + unsigned short m_range_pwm = m_max_pwm - m_min_pwm; + float m_min_angle = -40; + float m_max_angle = 40; + float m_range_angle = abs(m_min_angle) + abs(m_max_angle); + + bool m_verbose; + + /*! + * Thread use for Velocity control. This thread is launch in the constructor of the object and, unless crashes, will + * run until the process is ended. If the m_apply_velocity_cmd is set to TRUE, by invoking the setPwmVelocity method, the + * motor will go to the edge of the motor range using the speed set in setPwmVelocity. The velocity command can be + * stopped invoking the stopVelocityCmd() method. + */ + void VelocityCmdThread(); +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpPtu46.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpPtu46.h new file mode 100644 index 00000000..b7410988 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpPtu46.h @@ -0,0 +1,102 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Interface for the ptu-46 robot. + */ + +#ifndef _vpPtu46_h_ +#define _vpPtu46_h_ + +/* ----------------------------------------------------------------------- */ +/* --- INCLUDES -------------------------------------------------------- */ +/* --------------------------------------------------------------------- */ + +/* --- GENERAL --- */ +#include + +/* --- ViSP --- */ +#include +#include +#include +#include + +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + + \class vpPtu46 + + \ingroup group_robot_real_ptu + + \brief Jacobian, geometric model functionalities... for ptu46, pan, tilt + head from Directed Perception. + + See http://www.DPerception.com for more details. + +*/ + +class VISP_EXPORT vpPtu46 +{ + +public: /* Constants */ + /** Nombre d'articulations du robot. */ + static const unsigned int ndof; /*!< Number of dof */ + + /** Geometric model */ + static const float L; + static const float h; + +public: /* Methodes publiques */ + vpPtu46(void); + /*! Destructor that does nothing. */ + virtual ~vpPtu46() { } + + /** @name Inherited functionalities from vpPtu46 */ + //@{ + void init(void); + + void computeMGD(const vpColVector &q, vpHomogeneousMatrix &fMc) const; + vpHomogeneousMatrix computeMGD(const vpColVector &q) const; + void computeMGD(const vpColVector &q, vpPoseVector &r) const; + + void get_cMe(vpHomogeneousMatrix &_cMe) const; + void get_cVe(vpVelocityTwistMatrix &_cVe) const; + void get_eJe(const vpColVector &q, vpMatrix &eJe) const; + void get_fJe(const vpColVector &q, vpMatrix &fJe) const; + + //@} + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpPtu46 &constant); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpQbDevice.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpQbDevice.h new file mode 100644 index 00000000..eeed4868 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpQbDevice.h @@ -0,0 +1,116 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Interface for the qb robotics devices. + */ + +#ifndef _vpQbDevice_h_ +#define _vpQbDevice_h_ + +#include +#if defined(VISP_HAVE_QBDEVICE) && defined(VISP_HAVE_THREADS) + +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + + \class vpQbDevice + + \ingroup group_robot_haptic + + Interface for qbrobotics devices. + + See https://qbrobotics.com/ for more details. + + This class was tested with the [qbSoftHand](https://qbrobotics.com/products/qb-softhand/). + +*/ +class VISP_EXPORT vpQbDevice +{ +public: + vpQbDevice(); + virtual ~vpQbDevice(); + + /** @name Inherited public functionalities from vpQbDevice */ + double getCurrentMax() const; + std::vector getPositionLimits() const; + void setMaxRepeats(const int &max_repeats); + //@} + +protected: + /** @name Inherited protected functionalities from vpQbDevice */ + //@{ + virtual int activate(const int &id, const bool &command, const int &max_repeats); + virtual int activate(const int &id, const int &max_repeats); + virtual bool close(const std::string &serial_port); + virtual int deactivate(const int &id, const int &max_repeats); + + virtual int getCurrents(const int &id, const int &max_repeats, std::vector ¤ts); + + virtual int getInfo(const int &id, const int &max_repeats, std::string &info); + virtual int getMeasurements(const int &id, const int &max_repeats, std::vector ¤ts, + std::vector &positions); + + virtual int getParameters(const int &id, std::vector &limits, std::vector &resolutions); + virtual int getPositions(const int &id, const int &max_repeats, std::vector &positions); + virtual int getSerialPortsAndDevices(const int &max_repeats); + + virtual bool init(const int &id); + + virtual int isActive(const int &id, const int &max_repeats, bool &status); + int isConnected(const int &id, const int &max_repeats); + virtual bool isInConnectedSet(const int &id); + virtual bool isInOpenMap(const std::string &serial_port); + bool isReliable(int const &failures, int const &max_repeats); + virtual int open(const std::string &serial_port); + + virtual int setCommandsAndWait(const int &id, const int &max_repeats, std::vector &commands); + virtual int setCommandsAsync(const int &id, std::vector &commands); + //@} + +private: + vpQbDevice(const vpQbDevice &); // noncopyable + vpQbDevice &operator=(const vpQbDevice &); // + + // Implementation + class Impl; + Impl *m_impl; + +protected: + int m_max_repeats; //!< Max number of trials to send a command. + bool m_init_done; //!< Flag used to indicate if the device is initialized. +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpQbSoftHand.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpQbSoftHand.h new file mode 100644 index 00000000..867bcbb1 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpQbSoftHand.h @@ -0,0 +1,105 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Interface for the qb robotics qbSoftHand device. + */ + +#ifndef _vpQbSoftHand_h_ +#define _vpQbSoftHand_h_ + +#include +#if defined(VISP_HAVE_QBDEVICE) && defined(VISP_HAVE_THREADS) + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpQbSoftHand + * + * \ingroup group_robot_haptic + * + * Interface for qbSoftHand [device](https://qbrobotics.com/products/qb-softhand/). + * + * See https://qbrobotics.com/ for more details. + * + * \note Before using this class under Linux (Ubuntu, Debian, Fedora...) it is mandatory to add + * user to the dialout group. To do so, you must execute: + * \code + * $ sudo adduser user_name dialout + * \endcode + * otherwise you will get an error: + * \code + * vpQbDevice fails while opening [/dev/ttyUSB0] and sets errno [Permission denied]. + * \endcode + * + * The following example shows how to close and open the SoftHand with a given speed factor and stiffness used to stop + * the command applied to the motors when the measured current is larger than the stiffness multiplied by the maximum + * allowed current that can be applied to the motors. + * + * \code + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpQbSoftHand qbsofthand; + * + * vpColVector q(1); + * + * double speed_factor = 0.5; // half speed + * double stiffness = 0.7; // 70% of the max allowed current supported by the motors + * std::cout << "** Close the hand with blocking positioning function" << std::endl; + * q[0] = 1; + * qbsofthand.setPosition(q, speed_factor, stiffness); + * + * std::cout << "** Open the hand with blocking positioning function" << std::endl; + * q[0] = 0; + * qbsofthand.setPosition(q, speed_factor, stiffness); + * } + * \endcode +*/ +class VISP_EXPORT vpQbSoftHand : public vpQbDevice +{ +public: + vpQbSoftHand(); + + void getCurrent(vpColVector ¤t, const int &id = 1); + void getPosition(vpColVector &position, const int &id = 1); + + void setPosition(const vpColVector &position, const int &id = 1); + void setPosition(const vpColVector &position, double speed_factor, double stiffness, const int &id = 1); +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpReflexTakktile2.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpReflexTakktile2.h new file mode 100644 index 00000000..61f90a92 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpReflexTakktile2.h @@ -0,0 +1,159 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Interface for the Reflex Takktile 2 hand from Right Hand Robotics. + */ + +#ifndef _vpReflexTakktile2_h_ +#define _vpReflexTakktile2_h_ + +#include +#ifdef VISP_HAVE_TAKKTILE2 + +#include +#include + +#include + +BEGIN_VISP_NAMESPACE +/*! + + \class vpReflexTakktile2 + + \ingroup group_robot_haptic + + Right Hand Robotics Reflex Takktile 2 hand controller. + + See https://www.labs.righthandrobotics.com/docs for more details. + + This class was tested with the [Reflex Takktile2 hand](https://www.labs.righthandrobotics.com/reflexhand). + + To communicate by Ethernet with the hand we recommend to setup computer network with a static configuration: + \verbatim + IP: 10.1.1.10 + Gateway: 254.0.0.0 + \endverbatim + +*/ +class VISP_EXPORT vpReflexTakktile2 +{ +public: + class HandInfo + { + public: + std::vector proximal; + std::vector distal_approx; + std::vector > pressure; + std::vector > contact; + + std::vector joint_angle; + std::vector raw_angle; + std::vector velocity; + std::vector load; + std::vector voltage; + + std::vector temperature; + std::vector error_state; + + HandInfo(); + ~HandInfo() { } + + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const HandInfo &hand); + }; + + vpReflexTakktile2(); + virtual ~vpReflexTakktile2(); + + void calibrate(); + + void disableTorque(); + + HandInfo getHandInfo(); + + int getNumFingers() const; + int getNumSensorsPerFinger() const; + int getNumServos() const; + + vpColVector getPosition() const; + vpColVector getVelocity() const; + + void open(); + + /*! + * Set yaml file used to calibrate fingers. + * \param finger_file_name : Finger calibration file. + */ + void setFingerConfigFile(const std::string &finger_file_name) { m_finger_file_name = finger_file_name; } + + /*! + * Set yaml file used to define motor constants. + * \param motor_file_name : Motor constant file. + */ + void setMotorConfigFile(const std::string &motor_file_name) { m_motor_file_name = motor_file_name; } + + /*! + * Set network interface name. + * \param network_interface : Network interface name used to communicate with the Right Hand. + * Use `ifconfig` command to know the name of this interface. + */ + void setNetworkInterface(const std::string &network_interface = "eth0") { m_network_interface = network_interface; } + + void setPosition(const vpColVector &targets); + + /*! + * Set yaml file used to calibrate tactile sensors. + * \param tactile_file_name : Tactile calibration file. + */ + void setTactileConfigFile(const std::string &tactile_file_name) { m_tactile_file_name = tactile_file_name; } + + void setTactileThreshold(int threshold); + void setTactileThreshold(const std::vector &thresholds); + + void setPositioningVelocity(const vpColVector &targets); + void setVelocityUntilAnyContact(const vpColVector &targets); + void setVelocityUntilEachContact(const vpColVector &targets); + + void wait(int milliseconds); + +protected: + std::string m_network_interface; + std::string m_finger_file_name; + std::string m_tactile_file_name; + std::string m_motor_file_name; + HandInfo m_hand_info; + +private: + // Implementation + class Impl; + Impl *m_impl; +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRingLight.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRingLight.h new file mode 100644 index 00000000..792da049 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRingLight.h @@ -0,0 +1,128 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Ring light management. + */ + +/*! + \file vpRingLight.h + \brief Ring light management under unix. +*/ + +#ifndef vpRingLight_h +#define vpRingLight_h + +#include + +#if defined(VISP_HAVE_MODULE_IO) && defined(VISP_HAVE_PARPORT) + +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + + \class vpRingLight + \ingroup group_robot_real_gantry + \brief Ring light management under unix. + + \warning This class works only at Irisa with the Edixia's ring light system. + + Here is an example showing how to synchronise the framegrabbing with + the lighting system. + + \code + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #if defined(VISP_HAVE_PARPORT) && defined(VISP_HAVE_DC1394) + vp1394TwoGrabber g; // Firewire framegrabber based on libdc1394-2.x third party lib + vpImage I; + + vpRingLight light; // Open the device to access to the ring light. + + for (int i=0; i < 10; i++) { + light.pulse(); // Send a pulse to the lighting system + g.acquire(I); // Acquire an image + } + #endif + } + \endcode + + Here is an example showing how to turn on the lighting during 10 seconds. + \code + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #ifdef VISP_HAVE_PARPORT + vpRingLight light; // Open the device to access to the ring light. + + int nsec = 10; // Time to wait in seconds + light.on(); // Turn the ring light on + vpTime::wait(nsec * 1000); // Wait 10 s + light.off(); // and then turn the ring light off + #endif + } + \endcode +*/ +class VISP_EXPORT vpRingLight +{ + +public: + vpRingLight(); + virtual ~vpRingLight(); + + void pulse(); + void pulse(double time); + void on(); + void off(); + +private: + vpParallelPort parport; +}; +END_VISP_NAMESPACE +#endif + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobot.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobot.h new file mode 100644 index 00000000..e50d0ff1 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobot.h @@ -0,0 +1,184 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Generic virtual robot. + */ + +/*! + \file vpRobot.h + \brief class that defines a generic virtual robot +*/ + +#ifndef vpRobot_H +#define vpRobot_H + +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpRobot + \ingroup group_robot_real_gantry group_robot_real_arm group_robot_real_ptu group_robot_real_unicycle + group_robot_real_template + \brief Class that defines a generic virtual robot. +*/ +class VISP_EXPORT vpRobot +{ +public: + /*! + Robot control states. + */ + typedef enum + { + STATE_STOP, //!< Stops robot motion especially in velocity and acceleration control. + STATE_VELOCITY_CONTROL, //!< Initialize the velocity controller. + STATE_POSITION_CONTROL, //!< Initialize the position controller. + STATE_ACCELERATION_CONTROL, //!< Initialize the acceleration controller. + STATE_FORCE_TORQUE_CONTROL //!< Initialize the force/torque controller. + } vpRobotStateType; + + /*! + Robot control frames. + */ + typedef enum + { + REFERENCE_FRAME, /*!< Corresponds to a fixed reference frame + attached to the robot structure. */ + ARTICULAR_FRAME, /*!< Corresponds to the joint state. This value is deprecated. + You should rather use vpRobot::JOINT_STATE. */ + JOINT_STATE = ARTICULAR_FRAME, /*!< Corresponds to the joint state. */ + END_EFFECTOR_FRAME, /*!< Corresponds to robot end-effector frame. */ + CAMERA_FRAME, /*!< Corresponds to a frame attached to the + camera mounted on the robot end-effector. */ + TOOL_FRAME = CAMERA_FRAME, /*!< Corresponds to a frame attached to the + tool (camera, gripper...) mounted on the robot end-effector. This value is equal to vpRobot::CAMERA_FRAME. */ + MIXT_FRAME /*!< Corresponds to a "virtual" frame where + translations are expressed in the reference frame, and + rotations in the camera frame.*/ + } vpControlFrameType; + +private: /* Membres privees */ + vpRobot::vpRobotStateType stateRobot; + vpRobot::vpControlFrameType frameRobot; + +protected: + double maxTranslationVelocity; + static const double maxTranslationVelocityDefault; // = 0.2; + double maxRotationVelocity; + static const double maxRotationVelocityDefault; // = 0.7; + + //! number of degrees of freedom + int nDof; + //! robot Jacobian expressed in the end-effector frame + vpMatrix eJe; + //! is the robot Jacobian expressed in the end-effector frame available + int eJeAvailable; + //! robot Jacobian expressed in the robot reference frame available + vpMatrix fJe; + //! is the robot Jacobian expressed in the robot reference frame available + int fJeAvailable; + + int areJointLimitsAvailable; + double *qmin; + double *qmax; + + bool verbose_; + +public: + vpRobot(void); + vpRobot(const vpRobot &robot); + virtual ~vpRobot(); + + /** @name Inherited functionalities from vpRobot */ + //@{ + + //---------- Jacobian ----------------------------- + //! Get the robot Jacobian expressed in the end-effector frame + virtual void get_eJe(vpMatrix &_eJe) = 0; + //! Get the robot Jacobian expressed in the robot reference (or world) + //! frame. + virtual void get_fJe(vpMatrix &_fJe) = 0; + + //! Get a displacement (frame as to ve specified) between two successive + //! position control. + virtual void getDisplacement(const vpRobot::vpControlFrameType frame, vpColVector &q) = 0; + + double getMaxTranslationVelocity(void) const; + double getMaxRotationVelocity(void) const; + + /*! + * Return robot degrees of freedom number. + */ + int getNDof() const + { + return nDof; + } + + //! Get the robot position (frame has to be specified). + virtual void getPosition(const vpRobot::vpControlFrameType frame, vpColVector &position) = 0; + + // Return the robot position (frame has to be specified). + vpColVector getPosition(const vpRobot::vpControlFrameType frame); + virtual vpRobotStateType getRobotState(void) const { return stateRobot; } + + virtual void init() = 0; + + vpRobot &operator=(const vpRobot &robot); + + void setMaxRotationVelocity(double maxVr); + void setMaxTranslationVelocity(double maxVt); + //! Set a displacement (frame has to be specified) in position control. + virtual void setPosition(const vpRobot::vpControlFrameType frame, const vpColVector &q) = 0; + virtual vpRobotStateType setRobotState(const vpRobot::vpRobotStateType newState); + + //! Set the velocity (frame has to be specified) that will be applied to the + //! velocity controller. + virtual void setVelocity(const vpRobot::vpControlFrameType frame, const vpColVector &vel) = 0; + inline void setVerbose(bool verbose) { verbose_ = verbose; } + + //@} + + /** @name Static Public Member Functions inherited from vpRobot */ + //@{ + static vpColVector saturateVelocities(const vpColVector &v_in, const vpColVector &v_max, bool verbose = false); + //@} + +protected: + /** @name Protected Member Functions Inherited from vpRobot */ + //@{ + vpControlFrameType setRobotFrame(vpRobot::vpControlFrameType newFrame); + vpControlFrameType getRobotFrame(void) const { return frameRobot; } + //@} +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotAfma6.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotAfma6.h new file mode 100644 index 00000000..25b475fd --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotAfma6.h @@ -0,0 +1,321 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Interface for the Irisa's Afma6 robot controlled by an Adept MotionBlox. + */ + +#ifndef vpRobotAfma6_h +#define vpRobotAfma6_h + +#include + +#ifdef VISP_HAVE_AFMA6 + +#include +#include + +#include +#include +#include +#include +#include + +// low level controller api +extern "C" { +#include "irisa_Afma6.h" +#include "trycatch.h" +} + +BEGIN_VISP_NAMESPACE +/*! + \class vpRobotAfma6 + + \ingroup group_robot_real_gantry + + \brief Control of Irisa's gantry robot named Afma6. + + Implementation of the vpRobot class in order to control Irisa's + Afma6 robot. This robot is a gantry robot with six degrees of + freedom manufactured in 1992 by the french Afma-Robots company. In + 2008, the low level controller change for a more recent Adept + technology based on the MotionBlox controller. A firewire camera is + mounted on the end-effector to allow eye-in-hand visual + servoing. The control of this camera is achieved by the + vp1394TwoGrabber class. A ring light is attached around the + camera. The control of this ring light is possible throw the + vpRingLight class. A CCMOP gripper is also mounted on the + end-effector. The pneumatic control of this gripper is possible + throw the openGripper() or closeGripper() member functions. + + This class allows to control the Afma6 gantry robot in position + and velocity: + - in the joint space (vpRobot::ARTICULAR_FRAME), + - in the fixed reference frame (vpRobot::REFERENCE_FRAME), + - in the camera frame (vpRobot::CAMERA_FRAME), + - or in a mixed frame (vpRobot::MIXT_FRAME) where translations are expressed + in the reference frame and rotations in the camera frame. + + End-effector frame (vpRobot::END_EFFECTOR_FRAME) is not implemented. + + All the translations are expressed in meters for positions and m/s + for the velocities. Rotations are expressed in radians for the + positions, and rad/s for the rotation velocities. + + The direct and inverse kinematics models are implemented in the + vpAfma6 class. + + \warning A Ctrl-C, a segmentation fault or other system errors are + catched by this class to stop the robot. + + To communicate with the robot, you may first create an instance of this + class by calling the default constructor: + + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #ifdef VISP_HAVE_AFMA6 + vpRobotAfma6 robot; + #endif + } + \endcode + + This initialize the robot kinematics with the eMc extrinsic camera + parameters obtained with a projection model without distortion. To + set the robot kinematics with the eMc matrix obtained with a camera + perspective model including distortion you need to initialize the + robot with: + + \code + // Set the extrinsic camera parameters obtained with a perspective + // projection model including a distortion parameter + robot.init(vpAfma6::TOOL_CCMOP, vpCameraParameters::perspectiveProjWithDistortion); + \endcode + + You can get the intrinsic camera parameters of the image I + acquired with the camera, with: + + \code + vpCameraParameters cam; + robot.getCameraParameters(cam, I); + // In cam, you get the intrinsic parameters of the projection model + // with distortion. + \endcode + + To control the robot in position, you may set the controller + to position control and than send the position to reach in a specific + frame like here in the joint space: + + \code + vpColVector q(6); + // Set a joint position + q[0] = 0.1; // x axis, in meter + q[1] = 0.2; // y axis, in meter + q[2] = 0.3; // z axis, in meter + q[3] = M_PI/8; // rotation around A axis, in rad + q[4] = M_PI/4; // rotation around B axis, in rad + q[5] = M_PI; // rotation around C axis, in rad + + // Initialize the controller to position control + robot.setRobotState(vpRobot::STATE_POSITION_CONTROL); + + // Moves the robot in the joint space + robot.setPosition(vpRobot::ARTICULAR_FRAME, q); + \endcode + + The robot moves to the specified position with the default + positioning velocity vpRobotAfma6::defaultPositioningVelocity. The + setPositioningVelocity() method allows to change the maximal + velocity used to reach the desired position. + + \code + // Set the max velocity to 40% + robot.setPositioningVelocity(40); + + // Moves the robot in the joint space + robot.setPosition(vpRobot::ARTICULAR_FRAME, q); + \endcode + + To control the robot in velocity, you may set the controller to + velocity control and than send the velocities. To end the velocity + control and stop the robot you have to set the controller to the + stop state. Here is an example of a velocity control in the joint + space: + + \code + vpColVector qvel(6); + // Set a joint velocity + qvel[0] = 0.1; // x axis, in m/s + qvel[1] = 0.2; // y axis, in m/s + qvel[2] = 0; // z axis, in m/s + qvel[3] = M_PI/8; // rotation around A axis, in rad/s + qvel[4] = 0; // rotation around B axis, in rad/s + qvel[5] = 0; // rotation around C axis, in rad/s + + // Initialize the controller to position control + robot.setRobotState(vpRobot::STATE_VELOCITY_CONTROL); + + while (...) { + // Apply a velocity in the joint space + robot.setVelocity(vpRobot::ARTICULAR_FRAME, qvel); + + // Compute new velocities qvel... + } + + // Stop the robot + robot.setRobotState(vpRobot::STATE_STOP) + \endcode + + There is also possible to measure the robot current position with + getPosition() method and the robot current velocities with the getVelocity() + method. + + For convenience, there is also the ability to read/write joint + positions from a position file with readPosFile() and writePosFile() + methods. +*/ +class VISP_EXPORT vpRobotAfma6 : public vpAfma6, public vpRobot +{ + +private: /* Not allowed functions. */ + /*! + Copy constructor not allowed. + */ + vpRobotAfma6(const vpRobotAfma6 &robot); + +private: /* Attributs prives. */ + /** \brief Vrai ssi aucun objet de la classe vpRobotAfma6 n'existe. + * + * Il ne peut exister simultanement qu'un seul objet de la classe + * vpRobotAfma6, car il correspond a un seul robot AFMA6. Creer + * simultanement deux objets peut engendrer des conflits. Le constructeur + * lance une erreur si le champ n'est pas FAUX puis positionne le champ + * a VRAI. Seul le destructeur repositionne le champ a FAUX, ce qui + * alors la creation d'un nouvel objet. + */ + static bool robotAlreadyCreated; + + double positioningVelocity; + + // Variables used to compute the measured velocities (see getVelocity() ) + vpColVector q_prev_getvel; + vpHomogeneousMatrix fMc_prev_getvel; + double time_prev_getvel; + bool first_time_getvel; + + // Variables used to compute the measured displacement (see + // getDisplacement() ) + vpColVector q_prev_getdis; + bool first_time_getdis; + vpHomogeneousMatrix fMc_prev_getdis; + +public: /* Constantes */ + /* Vitesse maximale par default lors du positionnement du robot. + * C'est la valeur a la construction de l'attribut prive \a + * positioningVelocity. Cette valeur peut etre changee par la fonction + * #setPositioningVelocity. + */ + static const double defaultPositioningVelocity; // = 20.0; + +public: /* Methode publiques */ + VP_EXPLICIT vpRobotAfma6(bool verbose = true); + virtual ~vpRobotAfma6(void); + + bool checkJointLimits(vpColVector &jointsStatus); + + void closeGripper(); + + void getDisplacement(vpRobot::vpControlFrameType frame, vpColVector &displacement); + + void getPosition(const vpRobot::vpControlFrameType frame, vpColVector &position) VP_OVERRIDE; + void getPosition(const vpRobot::vpControlFrameType frame, vpColVector &position, double ×tamp); + void getPosition(const vpRobot::vpControlFrameType frame, vpPoseVector &position); + void getPosition(const vpRobot::vpControlFrameType frame, vpPoseVector &position, double ×tamp); + + double getPositioningVelocity(void); + bool getPowerState(); + double getTime() const; + + void getVelocity(const vpRobot::vpControlFrameType frame, vpColVector &velocity); + void getVelocity(const vpRobot::vpControlFrameType frame, vpColVector &velocity, double ×tamp); + + vpColVector getVelocity(const vpRobot::vpControlFrameType frame); + vpColVector getVelocity(const vpRobot::vpControlFrameType frame, double ×tamp); + + void get_cMe(vpHomogeneousMatrix &_cMe) const; + void get_cVe(vpVelocityTwistMatrix &_cVe) const; + void get_eJe(vpMatrix &_eJe) VP_OVERRIDE; + void get_fJe(vpMatrix &_fJe) VP_OVERRIDE; + + void init(void); + void init(vpAfma6::vpAfma6ToolType tool, const vpHomogeneousMatrix &eMc); + void init(vpAfma6::vpAfma6ToolType tool, const std::string &filename); + void + init(vpAfma6::vpAfma6ToolType tool, + vpCameraParameters::vpCameraParametersProjType projModel = vpCameraParameters::perspectiveProjWithoutDistortion); + + void move(const std::string &filename); + void move(const std::string &filename, double velocity); + + void openGripper(); + + void powerOn(); + void powerOff(); + + static bool readPosFile(const std::string &filename, vpColVector &q); + static bool savePosFile(const std::string &filename, const vpColVector &q); + + /* --- POSITIONNEMENT --------------------------------------------------- */ + void setPosition(const vpRobot::vpControlFrameType frame, const vpPoseVector &pose); + void setPosition(const vpRobot::vpControlFrameType frame, const vpColVector &position) VP_OVERRIDE; + void setPosition(const vpRobot::vpControlFrameType frame, double pos1, double pos2, double pos3, double pos4, + double pos5, double pos6); + void setPosition(const std::string &filename); + void setPositioningVelocity(double velocity); + void set_eMc(const vpHomogeneousMatrix &eMc); + + /* --- ETAT ------------------------------------------------------------- */ + + vpRobot::vpRobotStateType setRobotState(vpRobot::vpRobotStateType newState); + + /* --- VITESSE ---------------------------------------------------------- */ + + void setVelocity(const vpRobot::vpControlFrameType frame, const vpColVector &velocity) VP_OVERRIDE; + + void stopMotion(); +}; +END_VISP_NAMESPACE +#endif +#endif /* #ifndef vpRobotAfma6_h */ diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotBebop2.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotBebop2.h new file mode 100644 index 00000000..7f817c7e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotBebop2.h @@ -0,0 +1,257 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Interface for the Irisa's Afma6 robot. + * + * Authors: + * Gatien Gaumerais + */ + +#ifndef _vpRobotBebop2_h_ +#define _vpRobotBebop2_h_ + +#include + +#if defined(VISP_HAVE_ARSDK) && defined(VISP_HAVE_THREADS) + +#include + +extern "C" { +#include // For drone control +#include // For semaphore + +#ifdef VISP_HAVE_FFMPEG +#include // For H264 video decoding +#include // For rescaling decoded frames +#endif +} + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpRobotBebop2 + + \ingroup group_robot_real_drone + + Interface for Parrot ARSDK3, allowing to control the Bebop 2 drone and get images from the camera (if ViSP was built + with FFMpeg support). + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in using performing visual servoing using a Bebop 2 drone, you may have a look at:
+ * - \ref tutorial-bebop2-vs +*/ +class VISP_EXPORT vpRobotBebop2 +{ +public: + vpRobotBebop2(bool verbose = false, bool setDefaultSettings = true, std::string ipAddress = "192.168.42.1", + int discoveryPort = 44444); + virtual ~vpRobotBebop2(); + + //! @name Drone networking information + //@{ + std::string getIpAddress(); + int getDiscoveryPort(); + //@} + + //! @name General drone information + //@{ + void doFlatTrim(); + unsigned int getBatteryLevel(); + void setVerbose(bool verbose); + void resetAllSettings(); + //@} + + //! @name Drone state checking + //@{ + bool isFlying(); + bool isHovering(); + bool isLanded(); + bool isRunning(); + bool isStreaming(); + //@} + + //*** Motion commands ***// + //! @name Motion commands and parameters + //@{ + void cutMotors(); + double getMaxTilt(); + void setMaxTilt(double maxTilt); + void setPitch(int value); + void setPosition(float dX, float dY, float dZ, float dPsi, bool blocking); + void setPosition(const vpHomogeneousMatrix &M, bool blocking); + void setRoll(int value); + void setVelocity(const vpColVector &vel, double delta_t); + void setVerticalSpeed(int value); + void setYawSpeed(int value); + void stopMoving(); + void takeOff(bool blocking = true); + //@} + static void land(); + //*** ***// + + //*** Streaming commands ***// +#ifdef VISP_HAVE_FFMPEG + /** @name Streaming commands and parameters (only available if ViSP was built with FFMpeg support) */ + //@{ + void getGrayscaleImage(vpImage &I); + void getRGBaImage(vpImage &I); + int getVideoHeight(); + int getVideoWidth(); + void setExposure(float expo); + void setStreamingMode(int mode); + void setVideoResolution(int mode); + void setVideoStabilisationMode(int mode); + void startStreaming(); + void stopStreaming(); + //@} +#endif + //*** ***// + + //*** Camera control commands ***// + /** @name Camera control commands and parameters */ + //@{ + double getCameraHorizontalFOV() const; + double getCurrentCameraPan() const; + double getMaxCameraPan() const; + double getMinCameraPan() const; + double getCurrentCameraTilt() const; + double getMaxCameraTilt() const; + double getMinCameraTilt() const; + void setCameraOrientation(double tilt, double pan, bool blocking = false); + void setCameraPan(double pan, bool blocking = false); + void setCameraTilt(double tilt, bool blocking = false); + //@} + //*** ***// + +private: + //*** Attributes ***// + std::string m_ipAddress; ///< Ip address of the drone to discover on the network + int m_discoveryPort; ///< Port of the drone to discover on the network + + ARSAL_Sem_t m_stateSem; ///< Semaphore + struct sigaction m_sigAct; ///< Signal handler + +#ifdef VISP_HAVE_FFMPEG + AVCodecContext *m_codecContext; ///< Codec context for video stream decoding + AVPacket *m_packet; ///< Packed used to send data to the decoder + AVFrame *m_picture; ///< Frame used to receive data from the decoder + std::mutex m_bgr_picture_mutex; ///< Mutex to protect m_bgr_picture + AVFrame *m_bgr_picture; ///< Frame used to store rescaled frame received from the decoder + SwsContext *m_img_convert_ctx; ///< Used to rescale frame received from the decoder + uint8_t *m_buffer; ///< Buffer used to fill frame arrays + + bool m_videoDecodingStarted; ///< Used to know if the drone is currently streaming and decoding its camera video feed + + int m_videoWidth; ///< Width of the video streamed from the camera + int m_videoHeight; ///< Height of the video streamed from the camera +#endif + + static bool m_running; ///< Used for checking if the drone is running ie if successfully connected and ready to + ///< receive commands + + bool m_exposureSet; ///< Used to know if exposure compensation has been set + bool m_flatTrimFinished; ///< Used to know when the drone has finished a flat trim + bool m_relativeMoveEnded; ///< Used to know when the drone has ended a relative move + bool m_videoResolutionSet; ///< Used to know if video resolution has been set + bool m_streamingStarted; ///< Used to know if the streaming has been started + bool m_streamingModeSet; ///< Used to know if the streaming mode has been set + bool m_settingsReset; ///< Used to know when the drone a finished the settings reset + + bool m_update_codec_params; ///< Used to know if codec parameters need to be updated + std::vector m_codec_params_data; ///< Used to store codec parameters when they need to be updated + + unsigned int m_batteryLevel; ///< Percentage of battery remaining + double m_maxTilt; ///< Max pitch and roll value of the drone + + double m_cameraHorizontalFOV; ///< Camera horizontal FOV + + double m_currentCameraTilt; ///< Current tilt of the camera + double m_minCameraTilt; ///< Minimum possible tilt of the camera + double m_maxCameraTilt; ///< Maximum possible tilt of the camera + + double m_currentCameraPan; ///< Current pan of the camera + double m_minCameraPan; ///< Minimum possible tilt of the camera + double m_maxCameraPan; ///< Maximum possible tilt of the camera + + static ARCONTROLLER_Device_t *m_deviceController; ///< Used for drone control + + eARCONTROLLER_ERROR m_errorController; ///< Used for error handling + eARCONTROLLER_DEVICE_STATE m_deviceState; ///< Used to store device state + //*** ***// + + [[noreturn]] static void sighandler(int signo); + + eARCOMMANDS_ARDRONE3_PILOTINGSTATE_FLYINGSTATECHANGED_STATE getFlyingState(); + eARCOMMANDS_ARDRONE3_MEDIASTREAMINGSTATE_VIDEOENABLECHANGED_ENABLED getStreamingState(); + + //*** Setup functions ***// + void cleanUp(); + ARDISCOVERY_Device_t *discoverDrone(); + void createDroneController(ARDISCOVERY_Device_t *discoveredDrone); + void setupCallbacks(); + void startController(); + +#ifdef VISP_HAVE_FFMPEG + //*** Video streaming functions ***// + void initCodec(); + void cleanUpCodec(); + + void startVideoDecoding(); + void stopVideoDecoding(); + void computeFrame(ARCONTROLLER_Frame_t *frame); + //*** ***// +#endif + + //*** Callbacks ***// + static void stateChangedCallback(eARCONTROLLER_DEVICE_STATE newState, eARCONTROLLER_ERROR error, void *customData); +#ifdef VISP_HAVE_FFMPEG + static eARCONTROLLER_ERROR decoderConfigCallback(ARCONTROLLER_Stream_Codec_t codec, void *customData); + static eARCONTROLLER_ERROR didReceiveFrameCallback(ARCONTROLLER_Frame_t *frame, void *customData); +#endif + + static void cmdBatteryStateChangedRcv(ARCONTROLLER_DICTIONARY_ELEMENT_t *elementDictionary, vpRobotBebop2 *drone); + static void cmdCameraOrientationChangedRcv(ARCONTROLLER_DICTIONARY_ELEMENT_t *elementDictionary, + vpRobotBebop2 *drone); + static void cmdCameraSettingsRcv(ARCONTROLLER_DICTIONARY_ELEMENT_t *elementDictionary, vpRobotBebop2 *drone); + static void cmdExposureSetRcv(ARCONTROLLER_DICTIONARY_ELEMENT_t *elementDictionary, vpRobotBebop2 *drone); + static void cmdMaxPitchRollChangedRcv(ARCONTROLLER_DICTIONARY_ELEMENT_t *elementDictionary, vpRobotBebop2 *drone); + static void cmdRelativeMoveEndedRcv(ARCONTROLLER_DICTIONARY_ELEMENT_t *elementDictionary, vpRobotBebop2 *drone); + static void commandReceivedCallback(eARCONTROLLER_DICTIONARY_KEY commandKey, + ARCONTROLLER_DICTIONARY_ELEMENT_t *elementDictionary, void *customData); + //*** ***// +}; +END_VISP_NAMESPACE +#endif //#ifdef VISP_HAVE_ARSDK +#endif //#ifndef _vpRobotBebop2_h_ diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotBiclops.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotBiclops.h new file mode 100644 index 00000000..14f2b89a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotBiclops.h @@ -0,0 +1,454 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Interface for the Biclops robot. + */ + +#ifndef _vpRobotBiclops_h_ +#define _vpRobotBiclops_h_ + +#include + +#if defined(VISP_HAVE_BICLOPS) && defined(VISP_HAVE_THREADS) + +/* ------------------------------------------------------------------------ */ +/* --- INCLUDES ----------------------------------------------------------- */ +/* ------------------------------------------------------------------------ */ + +/* --- GENERAL --- */ +#include +#include +#include + +/* --- ViSP --- */ +#include +#include +#include +#include + +/* ------------------------------------------------------------------------ */ +/* --- CLASS -------------------------------------------------------------- */ +/* ------------------------------------------------------------------------ */ + +BEGIN_VISP_NAMESPACE +/*! + * \class vpRobotBiclops + * + * \ingroup group_robot_real_ptu + * + * \brief Interface for the Biclops, pan, tilt head control. + * + * Two different models are proposed and can be set using vpBiclops::DenavitHartenbergModel. + * The vpBiclops::DH1 and vpBiclops::DH2 model differ in the orientation of the tilt axis. + * The following image gives the location of the end-effector frame and a potential camera frame. + * + * \image html img-biclops-frames.jpg Biclops PT models + * + * See http://www.traclabs.com/biclopspt.html for more details. + * + * This class provide a position and a speed control interface for the Biclops + * head. To manage the Biclops joint limits in speed control, a control loop is + * running in a separate thread implemented in vpRobotBiclopsSpeedControlLoop(). + * + * \warning Velocity control mode is not exported from the top-level Biclops + * API class provided by Traclabs. That means that there is no protection in + * this mode to prevent an axis from striking its hard limit. In position mode, + * Traclabs put soft limits in that keep any command from driving to a position + * too close to the hard limits. In velocity mode this protection does not + * exist in the current API. + * + * \warning With the understanding that hitting the hard limits at full + * speed/power can damage the unit, damage due to velocity mode commanding is + * under user responsibility. +*/ +class VISP_EXPORT vpRobotBiclops : public vpBiclops, public vpRobot +{ +public: + static const double defaultPositioningVelocity; + + /*! + * Default constructor. + * + * Does nothing more than setting the default configuration file + * to `/usr/share/BiclopsDefault.cfg`. + * + * As shown in the following example, the turret need to be initialized + * using init() function. + * + * \code + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * #ifdef VISP_HAVE_BICLOPS + * vpRobotBiclops robot; // Use the default config file in /usr/share/BiclopsDefault.cfg + * + * // Initialize the head + * robot.init(); + * + * // Move the robot to a specified pan and tilt + * robot.setRobotState(vpRobot::STATE_POSITION_CONTROL) ; + * vpColVector q(2); + * q[0] = vpMath::rad(20); // pan + * q[1] = vpMath::rad(40); // tilt + * robot.setPosition(vpRobot::JOINT_STATE, q); + * #endif + * return 0; + * } + * \endcode + */ + vpRobotBiclops(); + + /*! + * Constructor that initialize the Biclops pan, tilt head by reading the + * configuration file provided by Traclabs + * and do the homing sequence. + * + * The following example shows how to use the constructor. + * + * \code + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * #ifdef VISP_HAVE_BICLOPS + * // Specify the config file location and initialize the turret + * vpRobotBiclops robot("/usr/share/BiclopsDefault.cfg"); + * + * // Move the robot to a specified pan and tilt + * robot.setRobotState(vpRobot::STATE_POSITION_CONTROL); + * + * vpColVector q(2); + * q[0] = vpMath::rad(-20); // pan + * q[1] = vpMath::rad(10); // tilt + * robot.setPosition(vpRobot::JOINT_STATE, q); + * #endif + * return 0; + * } + * \endcode + */ + VP_EXPLICIT vpRobotBiclops(const std::string &filename); + + /*! + * Destructor. + * Wait the end of the control thread. + */ + virtual ~vpRobotBiclops(); + + /*! + * Set the Biclops config filename. + * Check if the config file exists and initialize the head. + * + * \exception vpRobotException::constructionError If the config file cannot be + * opened. + */ + void init() VP_OVERRIDE; + + /*! + * Get the homogeneous matrix corresponding to the transformation between the + * camera frame and the end effector frame. The end effector frame is located + * on the tilt axis. + * + * \param cMe : Homogeneous matrix between camera and end effector frame. + */ + void get_cMe(vpHomogeneousMatrix &cMe) const; + + /*! + * Get the twist matrix corresponding to the transformation between the + * camera frame and the end effector frame. The end effector frame is located + * on the tilt axis. + * + * \param cVe : Twist transformation between camera and end effector frame to + * express a velocity skew from end effector frame in camera frame. + */ + void get_cVe(vpVelocityTwistMatrix &cVe) const; + + /*! + * Get the robot jacobian expressed in the end-effector frame. + * + * \warning Re is not the embedded camera frame. It corresponds to the frame + * associated to the tilt axis (see also get_cMe). + * + * \param eJe : Jacobian between end effector frame and end effector frame (on + * tilt axis). + */ + void get_eJe(vpMatrix &eJe) VP_OVERRIDE; + + /*! + * Get the robot jacobian expressed in the robot reference frame + * + * \param fJe : Jacobian between reference frame (or fix frame) and end + * effector frame (on tilt axis). + */ + void get_fJe(vpMatrix &fJe) VP_OVERRIDE; + + /*! + * Get the robot displacement since the last call of this method. + * + * \warning The first call of this method gives not a good value for the + * displacement. + * + * \param frame The frame in which the measured displacement is expressed. + * + * \param d The displacement: + * + * - In joint state, the dimension of q is 2 (the number of axis of the robot) + * with respectively d[0] (pan displacement), d[1] (tilt displacement). + * + * - In camera frame, the dimension of d is 6 (tx, ty, ty, tux, tuy, tuz). + * Translations are expressed in meters, rotations in radians with the theta U + * representation. + * + * \exception vpRobotException::wrongStateError If a not supported frame type + * is given. + */ + void getDisplacement(const vpRobot::vpControlFrameType frame, vpColVector &d) VP_OVERRIDE; + + /*! + * Return the position of each axis. + * - In positioning control mode, call vpRobotBiclopsController::getPosition() + * - In speed control mode, call vpRobotBiclopsController::getActualPosition() + * + * \param frame : Control frame. This Biclops head can only be controlled in + * joint state. + * + * \param q : The position of the axis in radians. + * + * \exception vpRobotException::wrongStateError : If a not supported frame type + * is given. + */ + void getPosition(const vpRobot::vpControlFrameType frame, vpColVector &q) VP_OVERRIDE; + + /*! + * Get the velocity in % used for a position control. + * + * \return Positioning velocity in [0, 100.0]. The + * maximum positioning velocity is given vpBiclops::speedLimit. + */ + double getPositioningVelocity(void); + + /*! + * Get the joint velocity. + * + * \param frame : Control frame. This head can only be controlled in joint state. + * + * \param q_dot : The measured joint velocity in rad/s. + * + * \exception vpRobotException::wrongStateError : If a not supported frame type + * is given. + */ + void getVelocity(const vpRobot::vpControlFrameType frame, vpColVector &q_dot); + + /*! + * Return the joint velocity. + * + * \param frame : Control frame. This head can only be controlled in joint state. + * + * \return The measured joint velocity in rad/s. + * + * \exception vpRobotException::wrongStateError : If a not supported frame type + * is given. + */ + vpColVector getVelocity(const vpRobot::vpControlFrameType frame); + + /*! + * Get joint positions from the position file. + * + * \param filename : Position file. + * + * \param q : The joint positions read in the file. + * + * \code + * # Example of Biclops position file + * # The axis positions must be preceeded by R: + * # First value : pan joint position in degrees + * # Second value: tilt joint position in degrees + * R: 15.0 5.0 + * \endcode + * + * \return true if a position was found, false otherwise. + */ + bool readPositionFile(const std::string &filename, vpColVector &q); + + /*! + * Set the Biclops config filename. + */ + void setConfigFile(const std::string &filename = "/usr/share/BiclopsDefault.cfg"); + + /*! + * Move the robot in position control. + * + * \warning This method is blocking. That mean that it waits the end of the + * positioning. + * + * \param frame : Control frame. This Biclops head can only be controlled in + * joint state. + * + * \param q : The joint position to set for each axis in radians. + * + * \exception vpRobotException::wrongStateError : If a not supported frame + * type is given. + */ + void setPosition(const vpRobot::vpControlFrameType frame, const vpColVector &q) VP_OVERRIDE; + + /*! + * Move the robot in position control. + * + * \warning This method is blocking. That mean that it wait the end of the + * positioning. + * + * \param frame : Control frame. This Biclops head can only be controlled in + * joint state. + * + * \param q1 : The pan joint position to set in radians. + * \param q2 : The tilt joint position to set in radians. + * + * \exception vpRobotException::wrongStateError : If a not supported frame + * type is given. + */ + void setPosition(const vpRobot::vpControlFrameType frame, const double &q1, const double &q2); + + /*! + * Read the content of the position file and moves the head to joint + * positions. + * + * \param filename : Position filename + * + * \exception vpRobotException::readingParametersError : If the joint + * positions cannot be read from file. + * + * \sa readPositionFile() + */ + void setPosition(const std::string &filename); + + /*! + * Set the velocity used for a position control. + * + * \param velocity : Velocity in % of the maximum velocity between [0,100]. The + * maximum velocity is given vpBiclops::speedLimit. + */ + void setPositioningVelocity(double velocity); + + /*! + * Change the state of the robot either to stop them, or to set position or + * speed control. + */ + vpRobot::vpRobotStateType setRobotState(const vpRobot::vpRobotStateType newState) VP_OVERRIDE; + + /*! + * Send a velocity on each axis. + * + * \param frame : Control frame. This Biclops head can only be controlled in + * joint state. Be aware, the camera frame (vpRobot::CAMERA_FRAME), the reference + * frame (vpRobot::REFERENCE_FRAME), end-effector frame (vpRobot::END_EFFECTOR_FRAME) + * and the mixt frame (vpRobot::MIXT_FRAME) are not implemented. + * + * \param q_dot : The desired joint velocities for each axis in rad/s. \f$ \dot + * {r} = [\dot{q}_1, \dot{q}_2]^t \f$ with \f$ \dot{q}_1 \f$ the pan of the + * camera and \f$ \dot{q}_2\f$ the tilt of the camera. + * + * \exception vpRobotException::wrongStateError : If a the robot is not + * configured to handle a velocity. The robot can handle a velocity only if the + * velocity control mode is set. For that, call setRobotState( + * vpRobot::STATE_VELOCITY_CONTROL) before setVelocity(). + * + * \exception vpRobotException::wrongStateError : If a not supported frame type + * (vpRobot::CAMERA_FRAME, vpRobot::REFERENCE_FRAME, vpRobot::END_EFFECTOR_FRAME + * or vpRobot::MIXT_FRAME) is given. + * + * \warning Velocities could be saturated if one of them exceed the maximal + * authorized speed (see vpRobot::maxRotationVelocity). + */ + void setVelocity(const vpRobot::vpControlFrameType frame, const vpColVector &q_dot) VP_OVERRIDE; + + /*! + * Halt all the axis. + */ + void stopMotion(); + + /* + * Control loop to manage the Biclops joint limits in speed control. + * + * This control loop is running in a separate thread in order to detect each 5 + * ms joint limits during the speed control. If a joint limit is detected the + * axis should be halted. + * + * \warning Velocity control mode is not exported from the top-level Biclops + * API class provided by Traclabs. That means that there is no protection in + * this mode to prevent an axis from striking its hard limit. In position mode, + * Traclabs put soft limits in that keep any command from driving to a position + * too close to the hard limits. In velocity mode this protection does not + * exist in the current API. + * + * \warning With the understanding that hitting the hard limits at full + * speed/power can damage the unit, damage due to velocity mode commanding is + * under user responsibility. + */ + static void vpRobotBiclopsSpeedControlLoop(void *arg); + +private: + std::thread m_control_thread; + + std::string m_configfile; // Biclops config file + + class vpRobotBiclopsController; + vpRobotBiclopsController *m_controller; + + double m_positioningVelocity; + vpColVector m_q_previous; + + // private: + //#ifndef DOXYGEN_SHOULD_SKIP_THIS + // /*! \brief No copy constructor allowed. */ + // vpRobotBiclops(const vpRobotBiclops &) + // : vpBiclops(), vpRobot(), m_control_thread(), m_controller(), + // m_positioningVelocity(0), m_q_previous() + // { + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); + // } + // vpRobotBiclops &operator=(const vpRobotBiclops &){ + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); return *this; + // } + //#endif +}; +END_VISP_NAMESPACE +#endif /* #ifndef _vpRobotBiclops_h_ */ + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotCamera.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotCamera.h new file mode 100644 index 00000000..112e75a1 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotCamera.h @@ -0,0 +1,140 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Defines the simplest robot : a free flying camera. + */ + +/*! + \file vpRobotCamera.h + \brief class that defines the simplest robot : a free flying camera +*/ + +#ifndef vpRobotCamera_H +#define vpRobotCamera_H + +#include + +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) + +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpRobotCamera + \ingroup group_robot_simu_camera + + \deprecated This class is deprecated since ViSP 3.0.0. + + \brief Class that defines the simplest robot: a free flying camera. We + recommend to use vpSimulatorCamera instead. + + This free flying camera has 6 dof; 3 in translation and 3 in rotation. + It evolves as a gantry robot with respect to a world frame. This class + is similar to vpSimulatorCamera class except that here the position of the + robot is provided as the transformation from camera frame to world frame; cMw. + Since the position of the camera frame evolves, this representation is less + intuitive than the one implemented in vpSimulatorCamera where the + transformation from world to camera frame is considered; wMc. + + For this particular simulated robot, the end-effector and camera frame are + confused. That means that the cMe transformation is equal to identity. + + The robot jacobian expressed in the end-effector frame + \f$ {^e}{\bf J}_e \f$ is also set to identity (see get_eJe()). + + The following code shows how to control this robot in position and velocity. + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpHomogeneousMatrix cMw; + vpRobotCamera robot; + + robot.getPosition(cMw); // Position of the world frame in the camera frame + std::cout << "Default position of the world frame in the camera frame cMw:\n" << cMw << std::endl; + + cMw[2][3] = 1.; // World frame is 1 meter along z axis in front of the camera frame + robot.setPosition(cMw); // Set the new position of the world frame in the camera frame + std::cout << "New position of the world frame in the camera frame cMw:\n" << cMw << std::endl; + + robot.setSamplingTime(0.100); // Modify the default sampling time to 0.1 second + robot.setMaxTranslationVelocity(1.); // vx, vy and vz max set to 1 m/s + robot.setMaxRotationVelocity(vpMath::rad(90)); // wx, wy and wz max set to 90 deg/s + + vpColVector v(6); + v = 0; + v[2] = 1.; // set v_z to 1 m/s + robot.setVelocity(vpRobot::CAMERA_FRAME, v); + // The robot has moved from 0.1 meters along the z axis + robot.getPosition(cMw); // Position of the world frame in the camera frame + std::cout << "New position of the camera cMw:\n" << cMw << std::endl; + } + \endcode + +*/ +class VISP_EXPORT vpRobotCamera : public vpRobotSimulator +{ +protected: + vpHomogeneousMatrix cMw_; // camera to world + +public: + vpRobotCamera(); + + /** @name Inherited functionalities from vpRobotCamera */ + //@{ + void get_cVe(vpVelocityTwistMatrix &cVe) const; + void get_eJe(vpMatrix &eJe) VP_OVERRIDE; + + void getPosition(vpHomogeneousMatrix &cMw) const; + void getPosition(const vpRobot::vpControlFrameType frame, vpColVector &q) VP_OVERRIDE; + + void setPosition(const vpHomogeneousMatrix &cMw); + void setVelocity(const vpRobot::vpControlFrameType frame, const vpColVector &v) VP_OVERRIDE; + //@} + +private: + void init() VP_OVERRIDE; + + // Non implemented virtual pure functions + void get_fJe(vpMatrix & /*_fJe */) VP_OVERRIDE { } + void getDisplacement(const vpRobot::vpControlFrameType /* frame */, vpColVector & /* q */) VP_OVERRIDE { } + void setPosition(const vpRobot::vpControlFrameType /* frame */, const vpColVector & /* q */) VP_OVERRIDE { } +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotException.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotException.h new file mode 100644 index 00000000..4320022b --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotException.h @@ -0,0 +1,121 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Exception that can be emitted by the vpRobot class and its derivatives. + */ + +/*! + * \file vpRobotException.h + * \brief error that can be emitted by the vpRobot class and its derivatives + */ + +#ifndef _vpRobotException_h_ +#define _vpRobotException_h_ + +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpRobotException + * \brief Error that can be emitted by the vpRobot class and its derivatives. +*/ +class VISP_EXPORT vpRobotException : public vpException +{ +public: + /*! + * \brief Lists the possible error than can be emitted while calling + * vpRobot member + */ + enum errorRobotCodeEnum + { + + //! Error from constructor + constructionError, + + //! Not unique robot object construction + uniqueRobotError, + + //! Wrong robot state + wrongStateError, + + //! Cannot change robot state + stateModificationError, + + //! Unable to communicate + communicationError, + + //! Error thrown by the low level sdk + lowLevelError, + + //! Cannot parse parameters + readingParametersError, + + //! Cannot initialize the robot + notInitializedError, + + //! Functionality not implemented + notImplementedError, + + //! Position is out of range + positionOutOfRangeError, + + //! Signal exception returned after SIGINT (CTRL-C), SIGBUS, SIGSEGV, SIGSEGV (CTRL-\), SIGQUIT. + signalException + }; + +public: + /*! + * Constructor. + */ + vpRobotException(int id, const char *format, ...) + { + this->code = id; + va_list args; + va_start(args, format); + setMessage(format, args); + va_end(args); + } + + /*! + * Constructor. + */ + vpRobotException(int id, const std::string &msg) : vpException(id, msg) { } + + /*! + * Constructor. + */ + VP_EXPLICIT vpRobotException(int id) : vpException(id) { } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotFlirPtu.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotFlirPtu.h new file mode 100644 index 00000000..c9c52951 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotFlirPtu.h @@ -0,0 +1,174 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Interface for Flir Ptu Cpi robot. + */ + +/*! + \file vpRobotFlirPtu.h + Interface for Flir Ptu Cpi robot. +*/ + +#ifndef vpRobotFlirPtu_h +#define vpRobotFlirPtu_h + +#include + +#ifdef VISP_HAVE_FLIR_PTU_SDK + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpRobotFlirPtu + \ingroup group_robot_real_arm + Interface for FLIR pan-tilt units compatible with FLIR PTU-SDK. + + \note We strongly recommend to communicate with the PTU using network interface. We experienced communication issues + using serial communication. + + \warning On Unix-like OS, if you experienced the following error when running servoFlirPtu.cpp: + \code + Failed to open /dev/ttyUSB0: Permission denied. + \endcode + 1. Add users to the "dialout" group: + \code + $ sudo adduser dialout + \endcode + 2. Reboot + + \warning Again on Unix-like OS, if you experienced the following error during ViSP build: + \code + /sdk-x.y.z/libcpi.a(cerial.o): relocation R_X86_64_PC32 against symbol `serposix' can not be used when + making a shared object; recompile with -fPIC \endcode + 1. Enter FLIR PTU SDK folder and modify `config.mk` to add `-fPIC` build flag + \code + $ cat /sdk-x.y.y/config.mk + CFLAGS=-g -Wall -Werror -DLITTLE_ENDIAN -O2 -fPIC + \endcode + 2. Rebuild PTU-SDK + \code + $ cd /sdk-x.y.y + $ make clean + $ make + \endcode + 3. Rebuild ViSP + \code + $ cd $VISP_WS/visp-build + $ make -j4 + \endcode + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in using performing visual servoing using a FLIR pan-tilt unit, you may have a look at:
+ * - \ref tutorial-flir-ptu-vs +*/ +class VISP_EXPORT vpRobotFlirPtu : public vpRobot +{ +public: + vpRobotFlirPtu(); + virtual ~vpRobotFlirPtu(); + + void connect(const std::string &portname, int baudrate = 9600); + void disconnect(); + + void get_eJe(vpMatrix &eJe) VP_OVERRIDE; + vpMatrix get_eJe(); + void get_fJe(vpMatrix &fJe) VP_OVERRIDE; + vpMatrix get_fJe(); + vpMatrix get_fMe(); + + /*! + Return constant transformation between end-effector and tool frame. + If your tool is a camera, this transformation is obtained by hand-eye calibration. + */ + vpHomogeneousMatrix get_eMc() const { return m_eMc; } + vpVelocityTwistMatrix get_cVe() const; + + void getDisplacement(const vpRobot::vpControlFrameType frame, vpColVector &q) VP_OVERRIDE; + + std::string getNetworkIP(); + std::string getNetworkGateway(); + std::string getNetworkHostName(); + + void getPosition(const vpRobot::vpControlFrameType frame, vpColVector &q) VP_OVERRIDE; + vpColVector getPanPosLimits(); + vpColVector getTiltPosLimits(); + vpColVector getPanTiltVelMax(); + + void reset(); + + /*! + Set constant transformation between end-effector and tool frame. + If your tool is a camera, this transformation is obtained by hand-eye calibration. + */ + void set_eMc(vpHomogeneousMatrix &eMc) { m_eMc = eMc; } + void setPosition(const vpRobot::vpControlFrameType frame, const vpColVector &q) VP_OVERRIDE; + void setPanPosLimits(const vpColVector &pan_limits); + void setTiltPosLimits(const vpColVector &tilt_limits); + + void setPositioningVelocity(double velocity); + vpRobot::vpRobotStateType setRobotState(vpRobot::vpRobotStateType newState); + void setVelocity(const vpRobot::vpControlFrameType frame, const vpColVector &vel) VP_OVERRIDE; + void stopMotion(); + + static void emergencyStop(int signo); + +protected: + void init(); + void getLimits(); + void getJointPosition(vpColVector &q); + void setCartVelocity(const vpRobot::vpControlFrameType frame, const vpColVector &v); + void setJointVelocity(const vpColVector &qdot); + +private: + double tics2deg(int axis, int tics); + double tics2rad(int axis, int tics); + int rad2tics(int axis, double rad); + +protected: + vpHomogeneousMatrix m_eMc; //!< Constant transformation between end-effector and tool (or camera) frame + + struct cerial *m_cer; + uint16_t m_status; + std::vector m_pos_max_tics; //!< Pan min/max position in robot tics unit + std::vector m_pos_min_tics; //!< Tilt min/max position in robot tics unit + std::vector m_vel_max_tics; //!< Pan/tilt max velocity in robot tics unit + std::vector m_res; //!< Pan/tilt tic resolution in deg + bool m_connected; + int m_njoints; + double m_positioning_velocity; +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotFranka.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotFranka.h new file mode 100644 index 00000000..ac51a982 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotFranka.h @@ -0,0 +1,372 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Interface for the Franka robot. + */ + +#ifndef VP_ROBOT_FRANKA_H +#define VP_ROBOT_FRANKA_H + +#include + +#if defined(VISP_HAVE_FRANKA) && defined(VISP_HAVE_THREADS) + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpRobotFranka + * + * \ingroup group_robot_real_arm + * + * This class is a wrapper over the [libfranka](https://github.com/frankaemika/libfranka) + * component part of the [Franka Control Interface](https://frankaemika.github.io/docs/) (FCI). + * + * Before using vpRobotFranka follow the + * [installation instructions](https://frankaemika.github.io/docs/installation.html#) to install + * libfranka. We suggest to + * [build libfranka from source](https://frankaemika.github.io/docs/installation.html#building-libfranka) + * if you are not using ROS. + * + * Moreover, you need also to setup a real-time kernel following these + * [instructions](https://frankaemika.github.io/docs/installation.html#setting-up-the-real-time-kernel). + * + * Up to now, this class provides the following capabilities to: + * - move to a given joint position using setPosition() that is blocking and that returns only when the robot + * has reached the desired position. + * \code + * vpRobotFranka robot("192.168.1.1"); + * + * vpColVector q_d(7); + * q_d[3] = -M_PI_2; + * q_d[5] = M_PI_2; + * q_d[6] = M_PI_4; + * std::cout << "Move to joint position: " << q_d.t() << std::endl; + * robot.setPosition(vpRobot::JOINT_STATE, q_d); + * \endcode + * - move applying a joint velocity using setVelocity(). This function is non-blocking. + * \code + * vpRobotFranka robot("192.168.1.1"); + * + * robot.setRobotState(vpRobot::STATE_VELOCITY_CONTROL); + * + * vpColVector dq_d(7, 0); + * dq_d[4] = vpMath::rad(-20.); + * dq_d[6] = vpMath::rad(20.); + * while(1) { + * robot.setVelocity(vpRobot::JOINT_STATE, dq_d); + * ... + * } + * \endcode + * - move applying a cartesian velocity to the end-effector using setVelocity(). This function is non-blocking. + * \code + * vpRobotFranka robot("192.168.1.1"); + * + * vpColVector ve_d(6); + * ve_d[2] = 0.02; // vz = 2 cm/s goes down + * + * while(1) { + * robot.setVelocity(vpRobot::END_EFFECTOR_FRAME, ve_d); + * ... + * } + * \endcode + * - move applying a cartesian velocity to the camera frame (or a given tool frame) using setVelocity(). + * The camera frame (or a tool frame) location wrt the end-effector is set using set_eMc(). This function is + * non-blocking. + * \code + * vpRobotFranka robot("192.168.1.1"); + * vpHomogeneousMatrix eMc; // Position of the camera wrt the end-effector + * // update eMc + * robot.set_eMc(eMc); + * + * vpColVector vc_d(6); + * vc_d[2] = 0.02; // vz = 2 cm/s is along the camera optical axis + * + * while(1) { + * robot.setVelocity(vpRobot::CAMERA_FRAME, vc_d); + * ... + * } + * \endcode + * If the tool attached to the end-effector is not a camera, you can do exactly the same using: + * \code + * vpRobotFranka robot("192.168.1.1"); + * vpHomogeneousMatrix eMt; + * // update eMt, the position of the tool wrt the end-effector frame + * robot.set_eMc(eMt); + * + * vpColVector vt_d(6); + * vt_d[2] = 0.02; // vt = 2 cm/s is along tool z axis + * + * while(1) { + * robot.setVelocity(vpRobot::TOOL_FRAME, vt_d); + * ... + * } + * \endcode + * + * - get the joint position using getPosition() + * \code + * vpRobotFranka robot("192.168.1.1"); + * + * vpColVector q; + * while(1) { + * robot.getPosition(vpRobot::JOINT_STATE, q); + * ... + * } + * \endcode + * - get the cartesian end-effector position using getPosition(). This function is non-blocking. + * \code + * vpRobotFranka robot("192.168.1.1"); + * + * vpPoseVector wPe; + * vpHomogeneousMatrix wMe; + * while(1) { + * robot.getPosition(vpRobot::END_EFFECTOR_FRAME, wPe); + * wMe.buildFrom(wPe); + * ... + * } + * \endcode + * - get the cartesian camera (or tool) frame position using getPosition(). This function is non-blocking. + * \code + * vpRobotFranka robot("192.168.1.1"); + * vpHomogeneousMatrix eMc; + * // update eMc, the position of the camera wrt the end-effector frame + * robot.set_eMc(eMc); + * + * vpPoseVector wPc; + * vpHomogeneousMatrix wMc; + * while(1) { + * robot.getPosition(vpRobot::CAMERA_FRAME, wPc); + * wMc.buildFrom(wPc); + * ... + * } + * \endcode + * If the tool attached to the end-effector is not a camera, you can do exactly the same using: + * \code + * vpRobotFranka robot("192.168.1.1"); + * vpHomogeneousMatrix eMt; + * // update eMt, the position of the tool wrt the end-effector frame + * robot.set_eMc(eMt); + * + * vpPoseVector wPt; + * vpHomogeneousMatrix wMt; + * while(1) { + * robot.getPosition(vpRobot::TOOL_FRAME, wPt); + * wMt.buildFrom(wPt); + * ... + * } + * \endcode + * + * What is not implemented is: + * - move to a given cartesian end-effector position + * - gripper controller + * - force/torque feedback and control + * + * Known issues: + * - sometimes the joint to joint trajectory generator provided by Franka complains about discontinuities. + * + * We provide also the getHandler() function that allows to access to the robot handler and call the native + * [libfranka API](https://frankaemika.github.io/libfranka/index.html) functionalities: + * \code + * vpRobotFranka robot("192.168.1.1"); + * + * franka::Robot *handler = robot.getHandler(); + * + * // Get end-effector cartesian position + * std::array pose = handler->readOnce().O_T_EE; + * \endcode + * + *

Tutorials & Examples

+ + Tutorials
+ If you are interested in using performing visual servoing using a Franka robot, you may have a look at:
+ * - \ref tutorial-franka-pbvs + * - \ref tutorial-franka-ibvs +*/ +class VISP_EXPORT vpRobotFranka : public vpRobot +{ +private: + /*! + * Copy constructor not allowed. + */ + vpRobotFranka(const vpRobotFranka &robot); + /*! + * This function is not implemented. + */ + void getDisplacement(const vpRobot::vpControlFrameType, vpColVector &) VP_OVERRIDE { } + + void init(); + + franka::Robot *m_handler; //!< Robot handler + franka::Gripper *m_gripper; //!< Gripper handler + franka::Model *m_model; + double m_positioningVelocity; + + // Velocity controller + std::thread m_velControlThread; + std::atomic_bool m_velControlThreadIsRunning; + std::atomic_bool m_velControlThreadStopAsked; + std::array m_dq_des; // Desired joint velocity + vpColVector m_v_cart_des; // Desired cartesian velocity either in reference, end-effector, camera, or tool frame + + // Force/torque controller + std::thread m_ftControlThread; + std::atomic_bool m_ftControlThreadIsRunning; + std::atomic_bool m_ftControlThreadStopAsked; + std::array m_tau_J_des; // Desired joint torques + vpColVector m_ft_cart_des; // Desired cartesian force/torque either in reference, end-effector, camera, or tool frame + + std::array m_q_min; // Joint min position + std::array m_q_max; // Joint max position + std::array m_dq_max; // Joint max velocity + std::array m_ddq_max; // Joint max acceleration + + franka::RobotState m_robot_state; // Robot state protected by mutex + std::mutex m_mutex; // Mutex to protect m_robot_state + + vpHomogeneousMatrix m_eMc; + std::string m_log_folder; + std::string m_franka_address; + +public: + /** + * Used to decide whether to enforce realtime mode for a control loop thread. + * + * \note Wrapper over franka::RealtimeConfig mainly for python bindings. + */ + enum class vpRealtimeConfig { kEnforce, kIgnore }; + + vpRobotFranka(); + + vpRobotFranka(const std::string &franka_address, + franka::RealtimeConfig realtime_config = franka::RealtimeConfig::kEnforce); + vpRobotFranka(const std::string &franka_address, vpRealtimeConfig realtime_config); + + virtual ~vpRobotFranka(); + + void connect(const std::string &franka_address, + franka::RealtimeConfig realtime_config = franka::RealtimeConfig::kEnforce); + void connect(const std::string &franka_address, vpRealtimeConfig realtime_config); + + vpHomogeneousMatrix get_fMe(const vpColVector &q); + vpHomogeneousMatrix get_fMc(const vpColVector &q); + vpHomogeneousMatrix get_eMc() const; + + void get_eJe(vpMatrix &eJe) VP_OVERRIDE; + void get_eJe(const vpColVector &q, vpMatrix &eJe); + void get_fJe(vpMatrix &fJe) VP_OVERRIDE; + void get_fJe(const vpColVector &q, vpMatrix &fJe); + + void getCoriolis(vpColVector &coriolis); + void getForceTorque(const vpRobot::vpControlFrameType frame, vpColVector &force); + + void getGravity(vpColVector &gravity); + + franka::RobotState getRobotInternalState(); + + /*! + * Get gripper handler to access native libfranka functions. + * + * \return Robot handler if it exists, an exception otherwise. + */ + franka::Gripper *getGripperHandler() + { + if (!m_gripper) { + throw(vpException(vpException::fatalError, "Cannot get Franka gripper handler: gripper is not connected")); + } + + return m_gripper; + } + + /*! + * Get robot handler to access native libfranka functions. + * + * \return Robot handler if it exists, an exception otherwise. + */ + franka::Robot *getHandler() + { + if (!m_handler) { + throw(vpException(vpException::fatalError, "Cannot get Franka robot handler: robot is not connected")); + } + + return m_handler; + } + + vpColVector getJointMin() const; + vpColVector getJointMax() const; + + void getMass(vpMatrix &mass); + + void getPosition(const vpRobot::vpControlFrameType frame, vpColVector &position) VP_OVERRIDE; + void getPosition(const vpRobot::vpControlFrameType frame, vpPoseVector &pose); + + void getVelocity(const vpRobot::vpControlFrameType frame, vpColVector &d_position); + + int gripperClose(); + int gripperGrasp(double grasping_width, double force = 60.); + int gripperGrasp(double grasping_width, double speed, double force); + void gripperHoming(); + int gripperMove(double width); + int gripperOpen(); + void gripperRelease(); + + void move(const std::string &filename, double velocity_percentage = 10.); + + bool readPosFile(const std::string &filename, vpColVector &q); + bool savePosFile(const std::string &filename, const vpColVector &q); + + void set_eMc(const vpHomogeneousMatrix &eMc); + void setForceTorque(const vpRobot::vpControlFrameType frame, const vpColVector &ft, const double &filter_gain = 0.1, + const bool &activate_pi_controller = false); + void setLogFolder(const std::string &folder); + void setPosition(const vpRobot::vpControlFrameType frame, const vpColVector &position) VP_OVERRIDE; + void setPositioningVelocity(double velocity); + + vpRobot::vpRobotStateType setRobotState(vpRobot::vpRobotStateType newState); + void setVelocity(const vpRobot::vpControlFrameType frame, const vpColVector &vel) VP_OVERRIDE; + + void stopMotion(); +}; +END_VISP_NAMESPACE +#endif +#endif // #ifndef __vpRobotFranka_h_ diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotKinova.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotKinova.h new file mode 100644 index 00000000..31174e6d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotKinova.h @@ -0,0 +1,206 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Interface for Kinova Jaco robot. + */ + +/*! + + \file vpRobotKinova.h + + Interface for Kinova robot using Jaco SDK. + +*/ + +#ifndef vpRobotKinova_h +#define vpRobotKinova_h + +#include + +#ifdef VISP_HAVE_JACOSDK + +#include + +#ifdef __linux__ +#include +#include +#include +#include +#include +#include +#elif _WIN32 +#include +#include + +#if defined(__clang__) +// Mute warning : non-portable path to file ''; specified path differs in case from file name on disk [-Wnonportable-system-include-path] +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wnonportable-system-include-path" +#endif + +#include + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +// Mute warning with clang-cl +// warning : non-portable path to file ''; specified path differs in case from file name on disk [-Wnonportable-system-include-path] +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wnonportable-system-include-path" +#endif + +#include + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +#include +#include +#endif + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + + \class vpRobotKinova + + \ingroup group_robot_real_arm + + Interface for Kinova Jaco2 robot. + + This class is a wrapper over Kinova Jaco SDK that could be downloaded from Kinova Robotics + software resources + by following the link under `Gen2 7 DoF > SDK 1.5.1`. + + It allows to control Kinova Jaco2 robot Gen 2 with 7 DoF, 6 DoF and 4 DoF. + + To select the degrees of freedom corresponding to your robot use setDoF(). + +*/ +class VISP_EXPORT vpRobotKinova : public vpRobot +{ +public: + typedef enum { CMD_LAYER_USB, CMD_LAYER_ETHERNET, CMD_LAYER_UNSET } CommandLayer; + + vpRobotKinova(); + virtual ~vpRobotKinova() VP_OVERRIDE; + + int connect(); + + void get_eJe(vpMatrix &eJe) VP_OVERRIDE; + void get_fJe(vpMatrix &fJe) VP_OVERRIDE; + + /*! + * Return constant transformation between end-effector and tool frame. + * If your tool is a camera, this transformation is obtained by hand-eye calibration. + */ + vpHomogeneousMatrix get_eMc() const { return m_eMc; } + + int getActiveDevice() const { return m_active_device; } + int getNumDevices() const { return m_devices_count; } + void getDisplacement(const vpRobot::vpControlFrameType frame, vpColVector &q) VP_OVERRIDE; + void getPosition(const vpRobot::vpControlFrameType frame, vpColVector &position) VP_OVERRIDE; + void getPosition(const vpRobot::vpControlFrameType frame, vpPoseVector &pose); + + void homing(); + + /*! + * Set constant transformation between end-effector and tool frame. + * If your tool is a camera, this transformation is obtained by hand-eye calibration. + */ + void set_eMc(vpHomogeneousMatrix &eMc) { m_eMc = eMc; } + void setActiveDevice(int device); + /*! + * Set command layer indicating if the robot is controlled throw USB or Ethernet. + * \param[in] command_layer : Layer used to control the robot. + */ + void setCommandLayer(CommandLayer command_layer) { m_command_layer = command_layer; } + void setDoF(unsigned int dof); + void setPosition(const vpRobot::vpControlFrameType frame, const vpColVector &q) VP_OVERRIDE; + /*! + * \param[in] plugin_location: Path to Jaco SDK plugins (ie. `Kinova.API.USBCommandLayerUbuntu.so` on + * unix-like platform or `CommandLayerWindows.dll` on Windows platform). By default this location is empty, + * meaning that we suppose that the plugins are located in the same folder as the binary that want to use + * them. + */ + void setPluginLocation(const std::string &plugin_location) { m_plugin_location = plugin_location; } + void setVelocity(const vpRobot::vpControlFrameType frame, const vpColVector &vel) VP_OVERRIDE; + /*! + * Enable or disable verbose mode to print to stdout additional information. + * \param[in] verbose : true to enable verbose, false to disable. By default verbose + * mode is disabled. + */ + void setVerbose(bool verbose) { m_verbose = verbose; } + +protected: + void closePlugin(); + void getJointPosition(vpColVector &q); + void init(); + void loadPlugin(); + void setCartVelocity(const vpRobot::vpControlFrameType frame, const vpColVector &v); + void setJointVelocity(const vpColVector &qdot); + +protected: + vpHomogeneousMatrix m_eMc; //!< Constant transformation between end-effector and tool (or camera) frame + std::string m_plugin_location; + bool m_verbose; + bool m_plugin_loaded; + int m_devices_count; + KinovaDevice *m_devices_list; + int m_active_device; + CommandLayer m_command_layer; + +#ifdef __linux__ + void *m_command_layer_handle; //!< A handle to the API. +#elif _WIN32 + HINSTANCE m_command_layer_handle; //!< A handle to the API. +#endif + +private: + int (*KinovaCloseAPI)(); + int (*KinovaGetAngularCommand)(AngularPosition &); + int (*KinovaGetCartesianCommand)(CartesianPosition &); + int (*KinovaGetDevices)(KinovaDevice devices[MAX_KINOVA_DEVICE], int &result); + int (*KinovaInitFingers)(); + int (*KinovaInitAPI)(); + int (*KinovaMoveHome)(); + int (*KinovaSendBasicTrajectory)(TrajectoryPoint command); + int (*KinovaSetActiveDevice)(KinovaDevice device); + int (*KinovaSetAngularControl)(); + int (*KinovaSetCartesianControl)(); +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotMavsdk.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotMavsdk.h new file mode 100644 index 00000000..2f694303 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotMavsdk.h @@ -0,0 +1,165 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Interface to mavlink compatible controller using mavsdk 3rd party + */ + +#ifndef VP_ROBOT_MAVSDK_H +#define VP_ROBOT_MAVSDK_H + +#include + +// Check if std:c++17 or higher. +// Here we cannot use (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17) in the declaration of the class +#if defined(VISP_HAVE_MAVSDK) && ((__cplusplus >= 201703L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L))) \ + && defined(VISP_HAVE_THREADS) + +#include +#include +#include +#include +#include + +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpRobotMavsdk + * + * \ingroup group_robot_real_drone + * + * Interface for [Mavlink](https://mavlink.io/en/) allowing to control drones or rovers using a MavLink compatible + * controller such a Pixhawk running PX4 or Ardupilot. + * + * This class needs cxx17 or more recent standard enabled during ViSP cmake configuration. + * + * This class is enabled when [MavSDK C++](https://github.com/mavlink/MAVSDK) is installed and detected by ViSP during + * cmake configuration step. + * + * \note The body frame associated to the vehicle controlled through MavLink is supposed to be Front-Right-Down (FRD) + * respectively for X-Y-Z. + * + * \image html img-pixhawk-frames.jpg + * + * 1. This class was tested to control a quadcopter equipped with a Pixhawk running PX4 firmware connected to a Jetson + * TX2. + * + * We provide a set of tests if you want to have a try on your flying vehicle: + * - testPixhawkDroneTakeoff.cpp + * - testPixhawkDronePositionAbsoluteControl.cpp + * - testPixhawkDronePositionRelativeControl.cpp + * - testPixhawkDroneVelocityControl.cpp + * - testPixhawkDroneKeyboard.cpp + * + * We provide also this \ref tutorial-pixhawk-vs. + * + * 2. This class was also tested to control an AION ROBOTICS rover equipped with a Pixhawk running Ardupilot firmware + * directly connected by serial to a laptop running Ubuntu 22.04. + * + * If you want to have a try you may see: + * - testPixhawkRoverVelocityControl.cpp + * + *

Tutorials & Examples

+ + Tutorials
+ If you are interested in using performing visual servoing such a drone, you may have a look at:
+ * - \ref tutorial-pixhawk-vs + */ + class VISP_EXPORT vpRobotMavsdk +{ +public: + vpRobotMavsdk(); + vpRobotMavsdk(const std::string &connection_info); + virtual ~vpRobotMavsdk(); + + //! \name Robot connection + //@{ + void connect(const std::string &connection_info); + //@} + + //! \name General robot information + //@{ + float getBatteryLevel() const; + void getPosition(float &ned_north, float &ned_east, float &ned_down, float &ned_yaw) const; + void getPosition(vpHomogeneousMatrix &ned_M_frd) const; + std::tuple getHome() const; + std::string getAddress() const; + bool isRunning() const; + //@} + + //! \name Robot commands + //@{ + bool arm(); + bool disarm(); + void doFlatTrim(); + bool hasFlyingCapability(); + bool holdPosition(); + bool kill(); + bool land(); + bool releaseControl(); + bool sendMocapData(const vpHomogeneousMatrix &enu_M_flu, int display_fps = 1); + void setAutoLand(bool auto_land); + bool setForwardSpeed(double body_frd_vx); + bool setLateralSpeed(double body_frd_vy); + bool setGPSGlobalOrigin(double latitude, double longitude, double altitude); + void setPositioningIncertitude(float position_incertitude, float yaw_incertitude); + bool setPosition(float ned_north, float ned_east, float ned_down, float ned_yaw, bool blocking = true, + int timeout_sec = 10); + bool setPosition(const vpHomogeneousMatrix &ned_M_frd, bool blocking = true, int timeout_sec = 10); + bool setPositionRelative(float ned_delta_north, float ned_delta_east, float ned_delta_down, float ned_delta_yaw, + bool blocking = true, int timeout_sec = 10); + bool setPositionRelative(const vpHomogeneousMatrix &delta_frd_M_frd, bool blocking = true, int timeout_sec = 10); + bool setVelocity(const vpColVector &frd_vel_cmd); + bool setVerticalSpeed(double body_frd_vz); + bool setYawSpeed(double body_frd_wz); + void setTakeOffAlt(double altitude); + void setVerbose(bool verbose); + bool stopMoving(); + bool takeControl(); + bool takeOff(bool interactive = true, int timeout_sec = 10, bool use_gps = false); + bool takeOff(bool interactive, double takeoff_altitude, int timeout_sec = 10, bool use_gps = false); + //@} + +private: + //*** Setup functions ***// + void cleanUp(); + void createDroneController(); + void setupCallbacks(); + void startController(); + + vpRobotMavsdk(const vpRobotMavsdk &); // noncopyable + vpRobotMavsdk &operator=(const vpRobotMavsdk &); // + + class vpRobotMavsdkImpl; + vpRobotMavsdkImpl *m_impl; +}; +END_VISP_NAMESPACE +#endif // #ifdef VISP_HAVE_MAVSDK +#endif // #ifndef vpRobotMavsdk_h_ diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotPioneer.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotPioneer.h new file mode 100644 index 00000000..2ac5d65b --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotPioneer.h @@ -0,0 +1,137 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Interface for Pioneer mobile robots based on Aria 3rd party library. + */ +#ifndef VPROBOTPIONEER_H +#define VPROBOTPIONEER_H + +#include + +#ifdef VISP_HAVE_PIONEER + +#include + +// Warning: vpMath.h included from vpRobot.h or vpPioneer.h should be +// included after Aria.h to avoid the build issue: +// "/usr/include/Aria/ariaUtil.h:732:21: error: ‘isfinite’ was not declared +// in this scope" +// This error is due to cmath header included from vpMath.h that makes +// isfinite() ambiguous between ::isfinite() and std::isfinite() +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpRobotPioneer + * + * \ingroup group_robot_real_unicycle + * + * \brief Interface for Pioneer mobile robots based on Aria 3rd party library. + * + * This class provides a position and speed control interface for Pioneer + * mobile robots. It inherits from the Aria ArRobot class. For more information + * about the model of the robot, see vpPioneer documentation. +*/ +class VISP_EXPORT vpRobotPioneer : public vpRobot, public vpPioneer, public ArRobot +{ +private: /* Not allowed functions. */ + /*! + * Copy constructor not allowed. + */ + vpRobotPioneer(const vpRobotPioneer &robot); + +public: + vpRobotPioneer(); + virtual ~vpRobotPioneer() VP_OVERRIDE; + + /*! + * Get the robot Jacobian expressed at point E, the point located at the + * middle between the two wheels. + * + * \param eJe : Robot jacobian such as \f$(v_x, w_z) = {^e}{\bf J}e \; {\bf + * v}\f$ with \f$(v_x, w_z)\f$ respectively the translational and rotational + * control velocities of the mobile robot, \f$\bf v\f$ the six dimension + * velocity skew, and where + * + * \sa get_eJe() + */ + void get_eJe(vpMatrix &eJe) VP_OVERRIDE { eJe = vpUnicycle::get_eJe(); } + +private: // Set as private since not implemented + /*! + * Get the robot Jacobian expressed in the robot reference (or world) frame. + * \warning Not implemented. + */ + void get_fJe(vpMatrix & /*fJe*/) VP_OVERRIDE { } + + /*! + * Get a displacement (frame as to ve specified) between two successive + * position control. + * \warning Not implemented. + */ + void getDisplacement(const vpRobot::vpControlFrameType /*frame*/, vpColVector & /*q*/) { } + +public: + void getVelocity(const vpRobot::vpControlFrameType frame, vpColVector &velocity); + vpColVector getVelocity(const vpRobot::vpControlFrameType frame); + +private: // Set as private since not implemented + /*! + * Get the robot position (frame has to be specified). + * \warning Not implemented. + */ + void getPosition(const vpRobot::vpControlFrameType /*frame*/, vpColVector & /*q*/) { } + +public: + void init(); + +private: // Set as private since not implemented + /*! + * Set a displacement (frame has to be specified) in position control. + * \warning Not implemented. + */ + void setPosition(const vpRobot::vpControlFrameType /*frame*/, const vpColVector & /*q*/) VP_OVERRIDE { } + +public: + void setVelocity(const vpRobot::vpControlFrameType frame, const vpColVector &vel) VP_OVERRIDE; + + /*! + * Enable or disable sonar device usage. + */ + void useSonar(bool usage) { this->comInt(ArCommands::SONAR, usage); } + +protected: + bool isInitialized; +}; +END_VISP_NAMESPACE +#endif + +#endif // VPROBOTPIONEER_H diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotPololuPtu.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotPololuPtu.h new file mode 100644 index 00000000..014eb956 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotPololuPtu.h @@ -0,0 +1,273 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Common features for Pololu Maestro PanTiltUnit. + */ + +#ifndef _vpRobotPololuPtu_h_ +#define _vpRobotPololuPtu_h_ + +#include + +#if defined(VISP_HAVE_POLOLU) && defined(VISP_HAVE_THREADS) + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpRobotPololuPtu + * \ingroup group_robot_real_arm + * + * \brief Interface for the Pololu Maestro pan-tilt unit using two servo motors. + * + * See https://www.pololu.com/category/102/maestro-usb-servo-controllers for more details. + * + * This class handle the vpPololu class in a higher level and allows to control + * the pan-tilt unit using position or velocity commands. + * + * The corresponding Denavit-Hartenberg representations of the PTU is the following: + * + * | Joint | \f$a_i\f$ | \f$d_i\f$ | \f$\alpha_i\f$ | \f$\theta_i\f$ | + * | :---: | :-------: | :-------: | -------------: | ----------------: | + * | 1 | 0 | 0 | \f$ \pi/2\f$ | \f$q_1\f$ | + * | 2 | 0 | 0 | \f$-\pi/2\f$ | \f$q_2 - \pi/2\f$ | +*/ +class VISP_EXPORT vpRobotPololuPtu : public vpRobot +{ +public: + /*! + * Default constructor. + * + * \param[in] device : Name of the serial interface used for communication. + * \param[in] baudrate : Baudrate used for the serial communication. Note that this parameter is only used on Windows. + * \param[in] verbose : When true, enable verbose mode. + */ + vpRobotPololuPtu(const std::string &device = "/dev/ttyACM0", int baudrate = 9600, bool verbose = false); + + /*! + * Destructor that stops the movements. + */ + ~vpRobotPololuPtu() VP_OVERRIDE; + + /*! + * Get the robot jacobian expressed in the end-effector frame. + * + * \warning End-effector frame is not the embedded camera frame. It corresponds to the frame + * associated to the tilt axis (see also get_cMe). + * + * \param[out] eJe : Jacobian between end effector frame and end effector frame (on + * tilt axis). + */ + void get_eJe(vpMatrix &eJe) VP_OVERRIDE; + + /*! + * Get the robot jacobian expressed in the end-effector frame. + * + * \warning End-effector frame is not the embedded camera frame. It corresponds to the frame + * associated to the tilt axis (see also get_cMe). + * + * \param[in] q : Joint positions to consider [rad]. + * + * \param[out] eJe : Jacobian between end effector frame and end effector frame (on + * tilt axis). + */ + void get_eJe(const vpColVector &q, vpMatrix &eJe) const; + + /*! + * Get the robot jacobian expressed in the robot reference frame. + * + * \param[out] fJe : Jacobian between reference frame (or fix frame) and end + * effector frame (on tilt axis). + */ + void get_fJe(vpMatrix &fJe) VP_OVERRIDE; + + /*! + * Get the robot jacobian expressed in the robot reference frame. + * + * \param[in] q : Joint positions to consider [rad]. + * + * \param[out] fJe : Jacobian between reference frame (or fix frame) and end + * effector frame (on tilt axis). + */ + void get_fJe(const vpColVector &q, vpMatrix &fJe) const; + + /*! + * Return the minimul angular velocity in rad/s that could be applied to move the motors. + * It corresponds to 1 pwm converted in rad/s. + */ + float getAngularVelocityResolution() const; + + /*! + * Return the position of each joint. + * + * \param[in] frame : Control frame. This PTU can only be controlled in + * joint state. + * + * \param[out] q : The position of the joints in radians. + * + * \exception vpRobotException::wrongStateError : If a not supported frame type + * is given. + */ + void getPosition(const vpRobot::vpControlFrameType frame, vpColVector &q) VP_OVERRIDE; + + /*! + * Get the percentage of the maximum velocity applied to move the PTU in position. + * + * \return Positioning velocity percentage in [0, 100.0]. The + * maximum positioning velocity is given vpRobot::getMaxRotationVelocity(). + * + * \sa setPositioningVelocityPercentage() + */ + float getPositioningVelocityPercentage() const + { + return m_positioning_velocity_percentage; + } + + /*! + * Move the robot to a given joint position. + * + * \warning This method is blocking. That mean that it waits the end of the + * positioning. + * + * \param[in] frame : Control frame. This PTU can only be controlled in + * joint state. + * + * \param[in] q : The joint position to set for each axis in radians. + * + * \exception vpRobotException::wrongStateError : If a not supported frame + * type is given. + */ + void setPosition(const vpRobot::vpControlFrameType frame, const vpColVector &q) VP_OVERRIDE; + + /*! + * Set the percentage of the maximum velocity applied to move the PTU in position. + * + * \param[in] positioning_velocity_percentage : Percentage between [0,100] of the maximum velocity. The + * maximum positioning velocity is given vpRobot::getMaxRotationVelocity(). + * + * \sa getPositioningVelocityPercentage() + */ + void setPositioningVelocityPercentage(float positioning_velocity_percentage) + { + m_positioning_velocity_percentage = positioning_velocity_percentage; + } + + /*! + * Enable/disable verbose mode. + * + * \param[in] verbose : Set to true to enable verbose mode, false otherwise. + */ + void setVerbose(bool verbose) + { + m_verbose = verbose; + } + + /*! + * Send a velocity on each axis. + * + * \param[in] frame : Control frame. This Biclops head can only be controlled in + * joint state. Be aware, the camera frame (vpRobot::CAMERA_FRAME), the reference + * frame (vpRobot::REFERENCE_FRAME), end-effector frame (vpRobot::END_EFFECTOR_FRAME) + * and the mixt frame (vpRobot::MIXT_FRAME) are not implemented. + * + * \param[in] q_dot : The desired joint velocities for each axis in rad/s. \f$ \dot + * {r} = [\dot{q}_1, \dot{q}_2]^t \f$ with \f$ \dot{q}_1 \f$ the pan of the + * camera and \f$ \dot{q}_2\f$ the tilt of the camera. + * + * \exception vpRobotException::wrongStateError : If a the robot is not + * configured to handle a velocity. The robot can handle a velocity only if the + * velocity control mode is set. For that, call setRobotState( + * vpRobot::STATE_VELOCITY_CONTROL) before setVelocity(). + * + * \exception vpRobotException::wrongStateError : If a not supported frame type + * (vpRobot::CAMERA_FRAME, vpRobot::REFERENCE_FRAME, vpRobot::END_EFFECTOR_FRAME + * or vpRobot::MIXT_FRAME) is given. + * + * \warning Velocities could be saturated if one of them exceed the maximal + * authorized speed (see vpRobot::maxRotationVelocity). + */ + void setVelocity(const vpRobot::vpControlFrameType frame, const vpColVector &q_dot) VP_OVERRIDE; + + /*! + * Stop the velocity command. + */ + void stopVelocity(); + + /*! + * Change the state of the robot either to stop them, or to set position or + * speed control. + */ + vpRobot::vpRobotStateType setRobotState(const vpRobot::vpRobotStateType newState) VP_OVERRIDE; + +private: + /*! + * Initialize the robot. + * + * \exception vpRobotException::constructionError If the config file cannot be + * opened. + */ + void init() VP_OVERRIDE { } + + /*! + * Get the robot displacement since the last call of this method. + * + * \warning The first call of this method gives not a good value for the + * displacement. + * + * \param[in] frame The frame in which the measured displacement is expressed. + * + * \param[out] d The displacement: + * + * - In joint state, the dimension of q is 2 (the number of axis of the robot) + * with respectively d[0] (pan displacement), d[1] (tilt displacement). + * + * - In camera frame, the dimension of d is 6 (tx, ty, ty, tux, tuy, tuz). + * Translations are expressed in meters, rotations in radians with the theta U + * representation. + * + * \exception vpRobotException::wrongStateError If a not supported frame type + * is given. + */ + void getDisplacement(const vpRobot::vpControlFrameType frame, vpColVector &d) VP_OVERRIDE + { + (void)frame; + (void)d; + }; + + vpPololu m_pan; + vpPololu m_tilt; + float m_positioning_velocity_percentage; + + bool m_verbose; +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotPtu46.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotPtu46.h new file mode 100644 index 00000000..041485f5 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotPtu46.h @@ -0,0 +1,126 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Interface for the ptu-46 robot. + */ + +#include +#ifdef VISP_HAVE_PTU46 + +#ifndef _vpRobotPtu46_h_ +#define _vpRobotPtu46_h_ + +/* ------------------------------------------------------------------------ */ +/* --- INCLUDES ----------------------------------------------------------- */ +/* ------------------------------------------------------------------------ */ + +/* --- GENERAL --- */ +#include +#include + +/* --- ViSP --- */ +#include +#include +#include +#include +#include + +#include // Contrib for Ptu-46 robot + +/* ------------------------------------------------------------------------ */ +/* --- CLASS ------------------------------------------------------------- */ +/* ------------------------------------------------------------------------ */ +BEGIN_VISP_NAMESPACE +/*! + + \class vpRobotPtu46 + + \ingroup group_robot_real_ptu + + \brief Interface for the Directed Perception ptu-46 pan, tilt head . + + See http://www.DPerception.com for more details. + + This class provide a position and a speed control interface for the ptu-46 + head. + +*/ +class VISP_EXPORT vpRobotPtu46 : public vpPtu46, public vpRobot +{ + +private: + /*! \brief No copy constructor allowed. */ + vpRobotPtu46(const vpRobotPtu46 &ass); + + /*! Object to control. This is a contribution. */ + Ptu ptu; + +private: + static bool robotAlreadyCreated; + double positioningVelocity; + int velocityMesureTempo; + std::string device; + +public: + static const double defaultPositioningVelocity; + + VP_EXPLICIT vpRobotPtu46(const std::string &device = "/dev/ttyS0"); + VP_EXPLICIT vpRobotPtu46(vpRobotPtu46 *pub); + virtual ~vpRobotPtu46(void); + + void get_cMe(vpHomogeneousMatrix &_cMe) const; + void get_cVe(vpVelocityTwistMatrix &_cVe) const; + void get_eJe(vpMatrix &_eJe) VP_OVERRIDE; + void get_fJe(vpMatrix &_fJe) VP_OVERRIDE; + + void getDisplacement(vpRobot::vpControlFrameType frame, vpColVector &q); + void getPosition(const vpRobot::vpControlFrameType frame, vpColVector &q) VP_OVERRIDE; + double getPositioningVelocity(void); + void getVelocity(const vpRobot::vpControlFrameType frame, vpColVector &q_dot); + vpColVector getVelocity(const vpRobot::vpControlFrameType frame); + + void init(void); + + bool readPositionFile(const std::string &filename, vpColVector &q); + + void setPosition(const vpRobot::vpControlFrameType frame, const vpColVector &q) VP_OVERRIDE; + void setPosition(const vpRobot::vpControlFrameType frame, const double &q1, const double &q2); + void setPosition(const char *filename); + void setPositioningVelocity(double velocity); + vpRobot::vpRobotStateType setRobotState(vpRobot::vpRobotStateType newState); + + void setVelocity(const vpRobot::vpControlFrameType frame, const vpColVector &q_dot) VP_OVERRIDE; + + void stopMotion(); +}; +END_VISP_NAMESPACE +#endif /* #ifndef _vpRobotPtu46_h_ */ + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotSimulator.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotSimulator.h new file mode 100644 index 00000000..dd86b54f --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotSimulator.h @@ -0,0 +1,85 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Basic class used to make robot simulators. + */ + +/*! + * \file vpRobotSimulator.h + * \brief Basic class used to make robot simulators. + */ + +#ifndef vpRobotSimulator_HH +#define vpRobotSimulator_HH + +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpRobotSimulator + * + * \ingroup group_robot_simu_gantry group_robot_simu_arm + * group_robot_simu_unicycle \ingroup group_robot_simu_camera + * + * \brief This class aims to be a basis used to create all the + * robot simulators. +*/ +class VISP_EXPORT vpRobotSimulator : public vpRobot +{ +protected: + double delta_t_; // sampling time in second + +public: + vpRobotSimulator(); + + /** @name Inherited functionalities from vpRobotSimulator */ + //@{ + /*! + * Return the sampling time. + * + * \return Sampling time in second used to compute the robot displacement + * from the velocity applied to the robot during this time. + */ + inline double getSamplingTime() const { return (this->delta_t_); } + + /*! + * Set the sampling time. + * + * \param delta_t : Sampling time in second used to compute the robot + * displacement from the velocity applied to the robot during this time. + */ + virtual inline void setSamplingTime(const double &delta_t) { this->delta_t_ = delta_t; } + //@} +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotTemplate.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotTemplate.h new file mode 100644 index 00000000..7cefec2e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotTemplate.h @@ -0,0 +1,89 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Defines a robot just to show which function you must implement. + */ + +/*! + * \file vpRobotTemplate.h + * Defines a robot just to show which function you must implement. + */ + +#ifndef vpRobotTemplate_h +#define vpRobotTemplate_h + +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpRobotTemplate + * \ingroup group_robot_real_template + * \brief Class that defines a robot just to show which function you must implement. +*/ +class VISP_EXPORT vpRobotTemplate : public vpRobot +{ +public: + vpRobotTemplate(); + virtual ~vpRobotTemplate() VP_OVERRIDE; + + void get_eJe(vpMatrix &eJe_) VP_OVERRIDE; + void get_fJe(vpMatrix &fJe_) VP_OVERRIDE; + + /*! + * Return constant transformation between end-effector and tool frame. + * If your tool is a camera, this transformation is obtained by hand-eye calibration. + */ + vpHomogeneousMatrix get_eMc() const { return m_eMc; } + + void getDisplacement(const vpRobot::vpControlFrameType frame, vpColVector &q) VP_OVERRIDE; + void getPosition(const vpRobot::vpControlFrameType frame, vpColVector &q) VP_OVERRIDE; + + /*! + * Set constant transformation between end-effector and tool frame. + * If your tool is a camera, this transformation is obtained by hand-eye calibration. + */ + void set_eMc(vpHomogeneousMatrix &eMc) { m_eMc = eMc; } + void setPosition(const vpRobot::vpControlFrameType frame, const vpColVector &q) VP_OVERRIDE; + void setVelocity(const vpRobot::vpControlFrameType frame, const vpColVector &vel) VP_OVERRIDE; + +protected: + void init() VP_OVERRIDE; + void getJointPosition(vpColVector &q); + void setCartVelocity(const vpRobot::vpControlFrameType frame, const vpColVector &v); + void setJointVelocity(const vpColVector &qdot); + +protected: + vpHomogeneousMatrix m_eMc; //!< Constant transformation between end-effector and tool (or camera) frame +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotUniversalRobots.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotUniversalRobots.h new file mode 100644 index 00000000..a6b46139 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotUniversalRobots.h @@ -0,0 +1,145 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Interface for Universal Robot. + */ + +#ifndef VP_ROBOT_UNIVERSAL_ROBOTS_H +#define VP_ROBOT_UNIVERSAL_ROBOTS_H + +#include + +#if defined(VISP_HAVE_UR_RTDE) + +#include + +#include +#include + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpRobotUniversalRobots + * + * \ingroup group_robot_real_arm + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using performing visual servoing using a robot from + * Universal Robots, you may have a look at:
+ * - \ref tutorial-universal-robot-ibvs + * - \ref tutorial-universal-robot-pbvs + */ + class VISP_EXPORT vpRobotUniversalRobots : public vpRobot +{ +private: // Not allowed functions + /*! + Copy constructor not allowed. + */ + vpRobotUniversalRobots(const vpRobotUniversalRobots &robot); + +public: + vpRobotUniversalRobots(); + vpRobotUniversalRobots(const std::string &ur_address); + virtual ~vpRobotUniversalRobots(); + + void connect(const std::string &ur_address); + void disconnect(); + + /*! + * Return handler to RTDEReceiveInterface. + */ + std::shared_ptr getRTDEReceiveInterfaceHandler() const { return m_rtde_receive; } + + /*! + * Return handler to RTDEControlInterface. + */ + std::shared_ptr getRTDEControlInterfaceHandler() const { return m_rtde_control; } + + /*! + * Return handler to DashboardClient. + */ + std::shared_ptr getDashboardClientHandler() const { return m_db_client; } + + vpHomogeneousMatrix get_fMe(); + vpHomogeneousMatrix get_fMe(const vpColVector &q); + vpHomogeneousMatrix get_fMc(); + vpHomogeneousMatrix get_eMc() const; + + void getForceTorque(const vpRobot::vpControlFrameType frame, vpColVector &force); + std::string getPolyScopeVersion(); + void getPosition(const vpRobot::vpControlFrameType frame, vpColVector &position) VP_OVERRIDE; + void getPosition(const vpRobot::vpControlFrameType frame, vpPoseVector &pose); + int getRobotMode() const; + std::string getRobotModel() const; + + void move(const std::string &filename, double velocity_percentage = 10.); + + bool readPosFile(const std::string &filename, vpColVector &q); + bool savePosFile(const std::string &filename, const vpColVector &q); + + void setPosition(const vpRobot::vpControlFrameType frame, const vpColVector &position) VP_OVERRIDE; + void setPosition(const vpRobot::vpControlFrameType frame, const vpPoseVector &pose); + void setPositioningVelocity(double velocity); + + vpRobot::vpRobotStateType setRobotState(vpRobot::vpRobotStateType newState); + void setVelocity(const vpRobot::vpControlFrameType frame, const vpColVector &vel) VP_OVERRIDE; + + void set_eMc(const vpHomogeneousMatrix &eMc); + + void stopMotion(); + +private: + // Not implemented yet + void get_eJe(vpMatrix &) VP_OVERRIDE { } + void get_fJe(vpMatrix &) VP_OVERRIDE { } + void getDisplacement(const vpRobot::vpControlFrameType, vpColVector &) VP_OVERRIDE { } + +protected: + void init(); + + std::shared_ptr m_rtde_receive; + std::shared_ptr m_rtde_control; + std::shared_ptr m_db_client; + vpHomogeneousMatrix m_eMc; + double m_positioningVelocity; + double m_max_joint_speed; + double m_max_joint_acceleration; + double m_max_linear_speed; + double m_max_linear_acceleration; + vpRobot::vpControlFrameType m_vel_control_frame; +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotViper650.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotViper650.h new file mode 100644 index 00000000..27dfa812 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotViper650.h @@ -0,0 +1,497 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Interface for the Irisa's Viper S650 robot controlled by an Adept + *MotionBlox. + */ + +#ifndef vpRobotViper650_h +#define vpRobotViper650_h + +#include + +#ifdef VISP_HAVE_VIPER650 + +#include +#include + +#include +#include +#include +#include + +// low level controller api +extern "C" { +#include "irisa_Viper650.h" +#include "trycatch.h" +} + +BEGIN_VISP_NAMESPACE +/*! + \class vpRobotViper650 + + \ingroup group_robot_real_arm + + \brief Control of Irisa's Viper S650 robot named Viper650. + + Implementation of the vpRobot class in order to control Irisa's + Viper650 robot. This robot is an ADEPT six degrees of freedom arm. + A firewire camera is mounted on the end-effector to allow + eye-in-hand visual servoing. The control of this camera is achieved + by the vp1394TwoGrabber class. + + The model of the robot is the following: + \image html model-viper.png Model of the Viper 650 robot. + + The non modified Denavit-Hartenberg representation of the robot is + given in the table below, where \f$q_1^*, \ldots, q_6^*\f$ + are the variable joint positions. + + \f[ + \begin{tabular}{|c|c|c|c|c|} + \hline + Joint & $a_i$ & $d_i$ & $\alpha_i$ & $\theta_i$ \\ + \hline + 1 & $a_1$ & $d_1$ & $-\pi/2$ & $q_1^*$ \\ + 2 & $a_2$ & 0 & 0 & $q_2^*$ \\ + 3 & $a_3$ & 0 & $-\pi/2$ & $q_3^* - \pi$ \\ + 4 & 0 & $d_4$ & $\pi/2$ & $q_4^*$ \\ + 5 & 0 & 0 & $-\pi/2$ & $q_5^*$ \\ + 6 & 0 & 0 & 0 & $q_6^*-\pi$ \\ + 7 & 0 & $d_6$ & 0 & 0 \\ + \hline + \end{tabular} + \f] + + In this modelization, different frames have to be considered. + + - \f$ {\cal F}_f \f$: the reference frame, also called world frame + + - \f$ {\cal F}_w \f$: the wrist frame located at the intersection of + the last three rotations, with \f$ ^f{\bf M}_w = ^0{\bf M}_6 \f$ + + - \f$ {\cal F}_e \f$: the end-effector frame located at the interface of the + two tool changers, with \f$^f{\bf M}_e = 0{\bf M}_7 \f$ + + - \f$ {\cal F}_c \f$: the camera or tool frame, with \f$^f{\bf M}_c = ^f{\bf + M}_e \; ^e{\bf M}_c \f$ where \f$ ^e{\bf M}_c \f$ is the result of + a calibration stage. We can also consider a custom tool + vpViper650::TOOL_CUSTOM and set this during robot initialisation or using + set_eMc(). + + - \f$ {\cal F}_s \f$: the force/torque sensor frame, with \f$d7=0.0666\f$. + + This class allows to control the Viper650 arm robot in position + and velocity: + - in the joint space (vpRobot::ARTICULAR_FRAME), + - in the fixed reference frame \f$ {\cal F}_f \f$ + (vpRobot::REFERENCE_FRAME), + - in the camera or tool frame \f$ {\cal F}_c \f$ (vpRobot::CAMERA_FRAME), + - or in a mixed frame (vpRobot::MIXT_FRAME) where translations are expressed + in the reference frame \f$ {\cal F}_f \f$ and rotations in the camera or + tool frame \f$ {\cal F}_c \f$ . + + End-effector frame (vpRobot::END_EFFECTOR_FRAME) is not implemented. + + All the translations are expressed in meters for positions and m/s + for the velocities. Rotations are expressed in radians for the + positions, and rad/s for the rotation velocities. + + The direct and inverse kinematics models are implemented in the + vpViper650 class. + + \warning A Ctrl-C, a segmentation fault or other system errors are + catched by this class to stop the robot. + + To communicate with the robot, you may first create an instance of this + class by calling the default constructor: + + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #ifdef VISP_HAVE_VIPER650 + vpRobotViper650 robot; + #endif + } + \endcode + + This initialize the robot kinematics with the \f$^e{\bf M}_c\f$ + extrinsic camera parameters obtained with a projection model without + distortion. To set the robot kinematics with the \f$^e{\bf M}_c\f$ + transformation obtained with a camera perspective model including + distortion you need to initialize the robot with: + + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #ifdef VISP_HAVE_VIPER650 + vpRobotViper650 robot; + + // Set the extrinsic camera parameters obtained with a perspective + // projection model including a distortion parameter + robot.init(vpViper650::TOOL_MARLIN_F033C_CAMERA, + vpCameraParameters::perspectiveProjWithDistortion); + #endif + } + \endcode + + You can get the intrinsic camera parameters of an image + acquired by the camera attached to the robot, with: + + \code + #include + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #if defined(VISP_HAVE_VIPER650) && defined(VISP_HAVE_DC1394) + vpImage I; + vp1394TwoGrabber g; + g.acquire(I); + + vpRobotViper650 robot; + + // ... + + vpCameraParameters cam; + robot.getCameraParameters(cam, I); + // In cam, you get the intrinsic parameters of the projection model + // with distortion. + #endif + } + \endcode + + To control the robot in position, you may set the controller + to position control and than send the position to reach in a specific + frame like here in the joint space: + + \code + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #ifdef VISP_HAVE_VIPER650 + vpRobotViper650 robot; + + vpColVector q(6); + // Set a joint position + q[0] = vpMath::rad(10); // Joint 1 position, in rad + q[1] = 0.2; // Joint 2 position, in rad + q[2] = 0.3; // Joint 3 position, in rad + q[3] = M_PI/8; // Joint 4 position, in rad + q[4] = M_PI/4; // Joint 5 position, in rad + q[5] = M_PI; // Joint 6 position, in rad + + // Initialize the controller to position control + robot.setRobotState(vpRobot::STATE_POSITION_CONTROL); + + // Moves the robot in the joint space + robot.setPosition(vpRobot::ARTICULAR_FRAME, q); + #endif + } + \endcode + + The robot moves to the specified position with the default + positioning velocity vpRobotViper650::defaultPositioningVelocity. The + setPositioningVelocity() method allows to change the maximal + velocity used to reach the desired position. + + \code + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #ifdef VISP_HAVE_VIPER650 + vpRobotViper650 robot; + + vpColVector q(6); + // Set q[i] with i in [0:5] + + // Initialize the controller to position control + robot.setRobotState(vpRobot::STATE_POSITION_CONTROL); + + // Set the max velocity to 40% + robot.setPositioningVelocity(40); + + // Moves the robot in the joint space + robot.setPosition(vpRobot::ARTICULAR_FRAME, q); + #endif + } + \endcode + + To control the robot in velocity, you may set the controller to + velocity control and than send the velocities. To end the velocity + control and stop the robot you have to set the controller to the + stop state. Here is an example of a velocity control in the joint + space: + + \code + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #ifdef VISP_HAVE_VIPER650 + vpRobotViper650 robot; + + vpColVector qvel(6); + // Set a joint velocity + qvel[0] = 0.1; // Joint 1 velocity in rad/s + qvel[1] = vpMath::rad(15); // Joint 2 velocity in rad/s + qvel[2] = 0; // Joint 3 velocity in rad/s + qvel[3] = M_PI/8; // Joint 4 velocity in rad/s + qvel[4] = 0; // Joint 5 velocity in rad/s + qvel[5] = 0; // Joint 6 velocity in rad/s + + // Initialize the controller to position control + robot.setRobotState(vpRobot::STATE_VELOCITY_CONTROL); + + for ( ; ; ) { + // Apply a velocity in the joint space + robot.setVelocity(vpRobot::ARTICULAR_FRAME, qvel); + + // Compute new velocities qvel... + } + + // Stop the robot + robot.setRobotState(vpRobot::STATE_STOP); + #endif + } + \endcode + + It is also possible to specify the position of a custom tool cartesian + frame. To this end this frame is to specify with respect of the end effector + frame in \f$^e {\bf M}_c\f$ transformation. This could be done by initializing + the robot thanks to init(vpViper650::vpToolType, const vpHomogeneousMatrix &) + or init(vpViper650::vpToolType, const std::string &) or using set_eMc(). The + following example illustrates this use case: + + \code + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #ifdef VISP_HAVE_VIPER650 + vpRobotViper650 robot; + + // Set the transformation between the end-effector frame + // and the tool frame. + vpHomogeneousMatrix eMc(0.001, 0.0, 0.1, 0.0, 0.0, M_PI/2); + + robot.init(vpViper650::TOOL_CUSTOM, eMc); + #endif + } + \endcode + + It is also possible to measure the robot current position with + getPosition() method and the robot current velocities with the getVelocity() + method. + + For convenience, there is also the ability to read/write joint + positions from a position file with readPosFile() and savePosFile() + methods. +*/ +class VISP_EXPORT vpRobotViper650 : public vpViper650, public vpRobot +{ + +public: /* Constantes */ + /*! \enum vpControlModeType Control mode. */ + typedef enum + { + AUTO, //!< Automatic control mode (default). + MANUAL, //!< Manual control mode activated when the dead man switch is in + //!< use. + ESTOP //!< Emergency stop activated. + } vpControlModeType; + + /* Max velocity used during robot control in position. + * this value could be changed using setPositioningVelocity(). + */ + static const double m_defaultPositioningVelocity; // = 20.0; + +private: /* Not allowed functions. */ + /*! + Copy constructor not allowed. + */ + vpRobotViper650(const vpRobotViper650 &robot); + +private: /* Attributs prives. */ + /** \brief Vrai ssi aucun objet de la classe vpRobotViper650 n'existe. + * + * Il ne peut exister simultanement qu'un seul objet de la classe + * vpRobotViper650, car il correspond a un seul robot AFMA6. Creer + * simultanement deux objets peut engendrer des conflits. Le constructeur + * lance une erreur si le champ n'est pas FAUX puis positionne le champ + * a VRAI. Seul le destructeur repositionne le champ a FAUX, ce qui + * alors la creation d'un nouvel objet. + */ + static bool m_robotAlreadyCreated; + + double m_positioningVelocity; + + // Variables used to compute the measured velocities (see getVelocity() ) + vpColVector m_q_prev_getvel; + vpHomogeneousMatrix m_fMc_prev_getvel; + vpHomogeneousMatrix m_fMe_prev_getvel; + double m_time_prev_getvel; + bool m_first_time_getvel; + + // Variables used to compute the measured displacement (see getDisplacement() ) + vpColVector m_q_prev_getdis; + bool m_first_time_getdis; + vpControlModeType m_controlMode; + +public: /* Methode publiques */ + VP_EXPLICIT vpRobotViper650(bool verbose = true); + virtual ~vpRobotViper650(void); + + // Force/Torque control + void biasForceTorqueSensor() const; + + void closeGripper() const; + + void disableJoint6Limits() const; + void enableJoint6Limits() const; + + /*! + \return The control mode indicating if the robot is in automatic, + manual (usage of the dead man switch) or emergnecy stop mode. + */ + vpControlModeType getControlMode() const { return m_controlMode; } + + void getDisplacement(vpRobot::vpControlFrameType frame, vpColVector &displacement); + void getForceTorque(vpColVector &H) const; + vpColVector getForceTorque() const; + + double getMaxRotationVelocityJoint6() const; + + void getPosition(const vpRobot::vpControlFrameType frame, vpColVector &position); + void getPosition(const vpRobot::vpControlFrameType frame, vpColVector &position, double ×tamp); + void getPosition(const vpRobot::vpControlFrameType frame, vpPoseVector &position); + void getPosition(const vpRobot::vpControlFrameType frame, vpPoseVector &position, double ×tamp); + + double getPositioningVelocity(void) const; + bool getPowerState() const; + + double getTime() const; + void getVelocity(const vpRobot::vpControlFrameType frame, vpColVector &velocity); + void getVelocity(const vpRobot::vpControlFrameType frame, vpColVector &velocity, double ×tamp); + + vpColVector getVelocity(const vpRobot::vpControlFrameType frame); + vpColVector getVelocity(const vpRobot::vpControlFrameType frame, double ×tamp); + + void get_cMe(vpHomogeneousMatrix &cMe) const; + void get_cVe(vpVelocityTwistMatrix &cVe) const; + void get_eJe(vpMatrix &eJe) VP_OVERRIDE; + void get_fJe(vpMatrix &fJe) VP_OVERRIDE; + + void init(void); + void + init(vpViper650::vpToolType tool, + vpCameraParameters::vpCameraParametersProjType projModel = vpCameraParameters::perspectiveProjWithoutDistortion); + void init(vpViper650::vpToolType tool, const std::string &filename); + void init(vpViper650::vpToolType tool, const vpHomogeneousMatrix &eMc_); + + void move(const std::string &filename); + + void openGripper(); + + void powerOn(); + void powerOff(); + + static bool readPosFile(const std::string &filename, vpColVector &q); + static bool savePosFile(const std::string &filename, const vpColVector &q); + + void set_eMc(const vpHomogeneousMatrix &eMc_); + void set_eMc(const vpTranslationVector &etc_, const vpRxyzVector &erc_); + + void setMaxRotationVelocity(double w_max); + void setMaxRotationVelocityJoint6(double w6_max); + + // Position control + void setPosition(const vpRobot::vpControlFrameType frame, const vpColVector &position) VP_OVERRIDE; + void setPosition(const vpRobot::vpControlFrameType frame, double pos1, double pos2, double pos3, double pos4, + double pos5, double pos6); + void setPosition(const std::string &filename); + void setPositioningVelocity(double velocity); + + // State + vpRobot::vpRobotStateType setRobotState(vpRobot::vpRobotStateType newState); + // Velocity control + void setVelocity(const vpRobot::vpControlFrameType frame, const vpColVector &velocity) VP_OVERRIDE; + + void stopMotion(); + +private: + double m_maxRotationVelocity_joint6; +}; +END_VISP_NAMESPACE +#endif +#endif /* #ifndef vpRobotViper650_h */ diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotViper850.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotViper850.h new file mode 100644 index 00000000..c5d7df71 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotViper850.h @@ -0,0 +1,506 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Interface for the Irisa's Viper S850 robot controlled by an Adept + *MotionBlox. + */ + +#ifndef vpRobotViper850_h +#define vpRobotViper850_h + +#include + +#ifdef VISP_HAVE_VIPER850 + +#include +#include + +#include +#include +#include +#include + +// low level controller api +extern "C" { +#include "irisa_Viper850.h" +#include "trycatch.h" +} + +// If USE_ATI_DAQ defined, use DAQ board instead of serial connexion to +// acquire data using comedi +#define USE_ATI_DAQ + +#ifdef USE_ATI_DAQ +#include +#endif + +BEGIN_VISP_NAMESPACE +/*! + \class vpRobotViper850 + + \ingroup group_robot_real_arm + + \brief Control of Irisa's Viper S850 robot named Viper850. + + Implementation of the vpRobot class in order to control Irisa's + Viper850 robot. This robot is an ADEPT six degrees of freedom arm. + A firewire camera is mounted on the end-effector to allow + eye-in-hand visual servoing. The control of this camera is achieved + by the vp1394TwoGrabber class. + + The model of the robot is the following: + \image html model-viper.png Model of the Viper 850 robot. + + The non modified Denavit-Hartenberg representation of the robot is + given in the table below, where \f$q_1^*, \ldots, q_6^*\f$ + are the variable joint positions. + + \f[ + \begin{tabular}{|c|c|c|c|c|} + \hline + Joint & $a_i$ & $d_i$ & $\alpha_i$ & $\theta_i$ \\ + \hline + 1 & $a_1$ & $d_1$ & $-\pi/2$ & $q_1^*$ \\ + 2 & $a_2$ & 0 & 0 & $q_2^*$ \\ + 3 & $a_3$ & 0 & $-\pi/2$ & $q_3^* - \pi$ \\ + 4 & 0 & $d_4$ & $\pi/2$ & $q_4^*$ \\ + 5 & 0 & 0 & $-\pi/2$ & $q_5^*$ \\ + 6 & 0 & 0 & 0 & $q_6^*-\pi$ \\ + 7 & 0 & $d_6$ & 0 & 0 \\ + \hline + \end{tabular} + \f] + + In this modelization, different frames have to be considered. + + - \f$ {\cal F}_f \f$: the reference frame, also called world frame + + - \f$ {\cal F}_w \f$: the wrist frame located at the intersection of + the last three rotations, with \f$ ^f{\bf M}_w = ^0{\bf M}_6 \f$ + + - \f$ {\cal F}_e \f$: the end-effector frame located at the interface of the + two tool changers, with \f$^f{\bf M}_e = 0{\bf M}_7 \f$ + + - \f$ {\cal F}_c \f$: the camera or tool frame, with \f$^f{\bf M}_c = ^f{\bf + M}_e \; ^e{\bf M}_c \f$ where \f$ ^e{\bf M}_c \f$ is the result of + a calibration stage. We can also consider a custom tool + vpViper850::TOOL_CUSTOM and set this during robot initialisation or using + set_eMc(). + + - \f$ {\cal F}_s \f$: the force/torque sensor frame, with \f$d7=0.0666\f$. + + This class allows to control the Viper650 arm robot in position + and velocity: + - in the joint space (vpRobot::ARTICULAR_FRAME), + - in the fixed reference frame \f$ {\cal F}_f \f$ (vpRobot::REFERENCE_FRAME), + - in the camera or tool frame \f$ {\cal F}_c \f$ (vpRobot::CAMERA_FRAME), + - or in a mixed frame (vpRobot::MIXT_FRAME) where translations are expressed + in the reference frame \f$ {\cal F}_f \f$ and rotations in the camera or + tool frame \f$ {\cal F}_c \f$ . + + End-effector frame (vpRobot::END_EFFECTOR_FRAME) is not implemented. + + All the translations are expressed in meters for positions and m/s + for the velocities. Rotations are expressed in radians for the + positions, and rad/s for the rotation velocities. + + The direct and inverse kinematics models are implemented in the + vpViper850 class. + + \warning A Ctrl-C, a segmentation fault or other system errors are + catched by this class to stop the robot. + + To communicate with the robot, you may first create an instance of this + class by calling the default constructor: + + \code + #include + + int main() + { + #ifdef VISP_HAVE_VIPER850 + vpRobotViper850 robot; + #endif + } + \endcode + + This initialize the robot kinematics with the \f$^e{\bf M}_c\f$ + extrinsic camera parameters obtained with a projection model without + distortion. To set the robot kinematics with the \f$^e{\bf M}_c\f$ + transformation obtained with a camera perspective model including + distortion you need to initialize the robot with: + + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #ifdef VISP_HAVE_VIPER850 + vpRobotViper850 robot; + + // Set the extrinsic camera parameters obtained with a perspective + // projection model including a distortion parameter + robot.init(vpViper850::TOOL_MARLIN_F033C_CAMERA, vpCameraParameters::perspectiveProjWithDistortion); + #endif + } + \endcode + + You can get the intrinsic camera parameters of an image + acquired by the camera attached to the robot, with: + + \code + #include + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #if defined(VISP_HAVE_VIPER850) && defined(VISP_HAVE_DC1394) + vpImage I; + vp1394TwoGrabber g; + g.acquire(I); + + vpRobotViper850 robot; + + // ... + + vpCameraParameters cam; + robot.getCameraParameters(cam, I); + // In cam, you get the intrinsic parameters of the projection model + // with distortion. + #endif + } + \endcode + + To control the robot in position, you may set the controller + to position control and than send the position to reach in a specific + frame like here in the joint space: + + \code + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #ifdef VISP_HAVE_VIPER850 + vpRobotViper850 robot; + + vpColVector q(6); + // Set a joint position + q[0] = vpMath::rad(10); // Joint 1 position, in rad + q[1] = 0.2; // Joint 2 position, in rad + q[2] = 0.3; // Joint 3 position, in rad + q[3] = M_PI/8; // Joint 4 position, in rad + q[4] = M_PI/4; // Joint 5 position, in rad + q[5] = M_PI; // Joint 6 position, in rad + + // Initialize the controller to position control + robot.setRobotState(vpRobot::STATE_POSITION_CONTROL); + + // Moves the robot in the joint space + robot.setPosition(vpRobot::ARTICULAR_FRAME, q); + #endif + } + \endcode + + The robot moves to the specified position with the default + positioning velocity vpRobotViper850::m_defaultPositioningVelocity. The + setPositioningVelocity() method allows to change the maximal + velocity used to reach the desired position. + + \code + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #ifdef VISP_HAVE_VIPER850 + vpRobotViper850 robot; + + vpColVector q(6); + // Set q[i] with i in [0:5] + + // Initialize the controller to position control + robot.setRobotState(vpRobot::STATE_POSITION_CONTROL); + + // Set the max velocity to 40% + robot.setPositioningVelocity(40); + + // Moves the robot in the joint space + robot.setPosition(vpRobot::ARTICULAR_FRAME, q); + #endif + } + \endcode + + To control the robot in velocity, you may set the controller to + velocity control and than send the velocities. To end the velocity + control and stop the robot you have to set the controller to the + stop state. Here is an example of a velocity control in the joint + space: + + \code + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #ifdef VISP_HAVE_VIPER850 + vpRobotViper850 robot; + + vpColVector qvel(6); + // Set a joint velocity + qvel[0] = 0.1; // Joint 1 velocity in rad/s + qvel[1] = vpMath::rad(15); // Joint 2 velocity in rad/s + qvel[2] = 0; // Joint 3 velocity in rad/s + qvel[3] = M_PI/8; // Joint 4 velocity in rad/s + qvel[4] = 0; // Joint 5 velocity in rad/s + qvel[5] = 0; // Joint 6 velocity in rad/s + + // Initialize the controller to position control + robot.setRobotState(vpRobot::STATE_VELOCITY_CONTROL); + + for ( ; ; ) { + // Apply a velocity in the joint space + robot.setVelocity(vpRobot::ARTICULAR_FRAME, qvel); + + // Compute new velocities qvel... + } + + // Stop the robot + robot.setRobotState(vpRobot::STATE_STOP); + #endif + } + \endcode + + It is also possible to specify the position of a custom tool cartesian + frame. To this end this frame is to specify with respect of the end effector + frame in \f$^e {\bf M}_c\f$ transformation. This could be done by initializing + the robot thanks to init(vpViper850::vpToolType, const vpHomogeneousMatrix &) + or init(vpViper850::vpToolType, const std::string &) or using set_eMc(). The + following example illustrates this usecase: + + \code + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #ifdef VISP_HAVE_VIPER850 + vpRobotViper850 robot; + + // Set the transformation between the end-effector frame + // and the tool frame. + vpHomogeneousMatrix eMc(0.001, 0.0, 0.1, 0.0, 0.0, M_PI/2); + + robot.init(vpViper850::TOOL_CUSTOM, eMc); + #endif + } + \endcode + + It is also possible to measure the robot current position with + getPosition() method and the robot current velocities with the getVelocity() + method. + + For convenience, there is also the ability to read/write joint + positions from a position file with readPosFile() and savePosFile() + methods. +*/ +class VISP_EXPORT vpRobotViper850 : public vpViper850, public vpRobot +{ + +public: /* Constantes */ + /*! \enum vpControlModeType Control mode. */ + typedef enum + { + AUTO, //!< Automatic control mode (default). + MANUAL, //!< Manual control mode activated when the dead man switch is in + //!< use. + ESTOP //!< Emergency stop activated. + } vpControlModeType; + + /* Max velocity used during robot control in position. + * this value could be changed using setPositioningVelocity(). + */ + static const double m_defaultPositioningVelocity; // = 20.0; + +private: /* Not allowed functions. */ + /*! + Copy constructor not allowed. + */ + vpRobotViper850(const vpRobotViper850 &robot); + +private: /* Attributs prives. */ + /** \brief Vrai ssi aucun objet de la classe vpRobotViper850 n'existe. + * + * Il ne peut exister simultanement qu'un seul objet de la classe + * vpRobotViper850, car il correspond a un seul robot AFMA6. Creer + * simultanement deux objets peut engendrer des conflits. Le constructeur + * lance une erreur si le champ n'est pas FAUX puis positionne le champ + * a VRAI. Seul le destructeur repositionne le champ a FAUX, ce qui + * alors la creation d'un nouvel objet. + */ + static bool m_robotAlreadyCreated; + + double m_positioningVelocity; + + // Variables used to compute the measured velocities (see getVelocity() ) + vpColVector m_q_prev_getvel; + vpHomogeneousMatrix m_fMc_prev_getvel; + vpHomogeneousMatrix m_fMe_prev_getvel; + double m_time_prev_getvel; + bool m_first_time_getvel; + + // Variables used to compute the measured displacement (see + // getDisplacement() ) + vpColVector m_q_prev_getdis; + bool m_first_time_getdis; + vpControlModeType m_controlMode; + +#if defined(USE_ATI_DAQ) && defined(VISP_HAVE_COMEDI) + vpForceTorqueAtiSensor m_ati; +#endif + +public: /* Methode publiques */ + VP_EXPLICIT vpRobotViper850(bool verbose = true); + virtual ~vpRobotViper850(void); + + // Force/Torque control + void biasForceTorqueSensor(); + + void closeGripper() const; + + void disableJoint6Limits() const; + void enableJoint6Limits() const; + + void getDisplacement(vpRobot::vpControlFrameType frame, vpColVector &displacement); + /*! + \return The control mode indicating if the robot is in automatic, + manual (usage of the dead man switch) or emergnecy stop mode. + */ + vpControlModeType getControlMode() const { return m_controlMode; } + + void getForceTorque(vpColVector &H) const; + vpColVector getForceTorque() const; + + double getMaxRotationVelocityJoint6() const; + void getPosition(const vpRobot::vpControlFrameType frame, vpColVector &position) VP_OVERRIDE; + void getPosition(const vpRobot::vpControlFrameType frame, vpColVector &position, double ×tamp); + void getPosition(const vpRobot::vpControlFrameType frame, vpPoseVector &position); + void getPosition(const vpRobot::vpControlFrameType frame, vpPoseVector &position, double ×tamp); + + double getPositioningVelocity(void) const; + bool getPowerState() const; + + void getVelocity(const vpRobot::vpControlFrameType frame, vpColVector &velocity); + void getVelocity(const vpRobot::vpControlFrameType frame, vpColVector &velocity, double ×tamp); + + vpColVector getVelocity(const vpRobot::vpControlFrameType frame); + vpColVector getVelocity(const vpRobot::vpControlFrameType frame, double ×tamp); + + double getTime() const; + + void get_cMe(vpHomogeneousMatrix &cMe) const; + void get_cVe(vpVelocityTwistMatrix &cVe) const; + void get_eJe(vpMatrix &eJe) VP_OVERRIDE; + void get_fJe(vpMatrix &fJe) VP_OVERRIDE; + + void init(void); + void + init(vpViper850::vpToolType tool, + vpCameraParameters::vpCameraParametersProjType projModel = vpCameraParameters::perspectiveProjWithoutDistortion); + void init(vpViper850::vpToolType tool, const std::string &filename); + void init(vpViper850::vpToolType tool, const vpHomogeneousMatrix &eMc_); + + void move(const std::string &filename); + + void openGripper(); + + void powerOn(); + void powerOff(); + + static bool readPosFile(const std::string &filename, vpColVector &q); + static bool savePosFile(const std::string &filename, const vpColVector &q); + + void set_eMc(const vpHomogeneousMatrix &eMc_); + void set_eMc(const vpTranslationVector &etc_, const vpRxyzVector &erc_); + + void setMaxRotationVelocity(double w_max); + void setMaxRotationVelocityJoint6(double w6_max); + + // Position control + void setPosition(const vpRobot::vpControlFrameType frame, const vpColVector &position) VP_OVERRIDE; + void setPosition(const vpRobot::vpControlFrameType frame, double pos1, double pos2, double pos3, double pos4, + double pos5, double pos6); + void setPosition(const std::string &filename); + void setPositioningVelocity(double velocity); + + // State + vpRobot::vpRobotStateType setRobotState(vpRobot::vpRobotStateType newState); + + // Velocity control + void setVelocity(const vpRobot::vpControlFrameType frame, const vpColVector &velocity) VP_OVERRIDE; + + void stopMotion(); + void unbiasForceTorqueSensor(); + +private: + double maxRotationVelocity_joint6; +}; +END_VISP_NAMESPACE +#endif +#endif /* #ifndef vpRobotViper850_h */ diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotWireFrameSimulator.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotWireFrameSimulator.h new file mode 100644 index 00000000..c040e610 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpRobotWireFrameSimulator.h @@ -0,0 +1,415 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Basic class used to make robot simulators. + */ + +/*! + * \file vpRobotWireFrameSimulator.h + * \brief Basic class used to make robot simulators. + */ + +#ifndef VP_ROBOT_WIREFRAME_SIMULATOR_H +#define VP_ROBOT_WIREFRAME_SIMULATOR_H + +#include + +#if defined(VISP_HAVE_MODULE_GUI) && defined(VISP_HAVE_THREADS) + +#include // std::fabs +#include // numeric_limits + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpRobotWireFrameSimulator + * + * \ingroup group_robot_simu_gantry group_robot_simu_arm + * + * \brief This class aims to be a basis used to create all the + * simulators of robots. + * + * Thus in this class you will find all the parameters and methods + * which are necessary to create a simulator. Several methods are pure + * virtual. In this case it means that they are specific to the each + * robot, for example the computation of the geometrical model. + * + * \warning This class uses threading capabilities. Thus on Unix-like + * platforms, the libpthread third-party library need to be + * installed. On Windows, we use the native threading capabilities. +*/ +class VISP_EXPORT vpRobotWireFrameSimulator : protected vpWireFrameSimulator, public vpRobotSimulator +{ +public: + vpImage I; + + typedef enum { MODEL_3D, MODEL_DH } vpDisplayRobotType; + +protected: + /*! cpu time at the begining of the robot's movement*/ + double tcur; + /*! cpu time at the end of the last robot's movement*/ + double tprev; + + /*! Contains the 3D model of the robot's arms*/ + Bound_scene *robotArms; + + /*! Size of the fMi table*/ + unsigned int size_fMi; + /*! Table containing all the homogeneous matrices between the reference + frame of the robot and the frames you used to compute the Denavit-Hartenberg + representation + + If you use a camera at the end of the effector, the last homogeneous matrix + has to be the one between the reference frame and the camera frame (fMc)*/ + vpHomogeneousMatrix *fMi; + + /*! The articular coordinates*/ + vpColVector artCoord; + /*! The articular velocity*/ + vpColVector artVel; + /*! The velocity in the current frame (articular, camera or reference)*/ + vpColVector velocity; + + std::thread *m_thread; + + std::mutex m_mutex_fMi; + std::mutex m_mutex_eMc; + std::mutex m_mutex_artVel; + std::mutex m_mutex_artCoord; + std::mutex m_mutex_velocity; + std::mutex m_mutex_display; + std::mutex m_mutex_robotStop; + std::mutex m_mutex_frame; + std::mutex m_mutex_setVelocityCalled; + std::mutex m_mutex_scene; + + bool displayBusy; + + /*! True if the robot has to be stopped*/ + bool robotStop; + /*! True if one of the joint reach the limit*/ + bool jointLimit; + /*! Index of the joint which is in limit*/ + unsigned int jointLimitArt; + /*! True if the singularity are automatically managed */ + bool singularityManagement; + + /*! External camera parameters*/ + vpCameraParameters cameraParam; + +#if defined(VISP_HAVE_X11) + vpDisplayX display; +#elif defined(VISP_HAVE_GDI) + vpDisplayGDI display; +#elif defined(HAVE_OPENCV_HIGHGUI) + vpDisplayOpenCV display; +#elif defined(VISP_HAVE_D3D9) + vpDisplayD3D display; +#elif defined(VISP_HAVE_GTK) + vpDisplayGTK display; +#endif + + vpDisplayRobotType displayType; + + bool displayAllowed; + //! Flag used to force the sampling time in the thread computing the robot's + //! displacement to a constant value (\e samplingTime). It may be useful if + //! the main thread (computing the features) is very time consuming. False + //! by default. + bool constantSamplingTimeMode; + + //! Flag used to specify to the thread managing the robot displacements that + //! the setVelocity() method has been called. + bool setVelocityCalled; + +public: + vpRobotWireFrameSimulator(); + VP_EXPLICIT vpRobotWireFrameSimulator(bool display); + + /** @name Inherited functionalities from vpRobotWireFrameSimulator */ + //@{ + /*! + * Get the parameters of the virtual external camera. + * + * \return It returns the camera parameters. + */ + vpCameraParameters getExternalCameraParameters() const + { + // if(px_ext != 1 && py_ext != 1) + // we assume px_ext and py_ext > 0 + if ((std::fabs(px_ext - 1.) > vpMath::maximum(px_ext, 1.) * std::numeric_limits::epsilon()) && + (std::fabs(py_ext - 1) > vpMath::maximum(py_ext, 1.) * std::numeric_limits::epsilon())) + return vpCameraParameters(px_ext, py_ext, I.getWidth() / 2, I.getHeight() / 2); + else { + unsigned int size = vpMath::minimum(I.getWidth(), I.getHeight()) / 2; + return vpCameraParameters(size, size, I.getWidth() / 2, I.getHeight() / 2); + } + } + /*! + * Get the external camera's position relative to the the world reference + * frame. + * + * \return the main external camera position relative to the the world + * reference frame. + */ + vpHomogeneousMatrix getExternalCameraPosition() const + { + return this->vpWireFrameSimulator::getExternalCameraPosition(); + } + + void getInternalView(vpImage &I); + void getInternalView(vpImage &I); + + vpHomogeneousMatrix get_cMo(); + /*! + * Get the pose between the object and the fixed world frame. + * + * \return The pose between the object and the fixed world frame. + */ + vpHomogeneousMatrix get_fMo() const { return fMo; } + + /* Display functions */ + void initScene(const vpSceneObject &obj, const vpSceneDesiredObject &desiredObject); + void initScene(const char *obj, const char *desiredObject); + void initScene(const vpSceneObject &obj); + void initScene(const char *obj); + + /*! + * Set the color used to display the camera in the external view. + * + * \param col : The desired color. + */ + void setCameraColor(const vpColor &col) { camColor = col; } + + /*! + * Set the flag used to force the sampling time in the thread computing the + * robot's displacement to a constant value; see setSamplingTime(). It may be + * useful if the main thread (computing the features) is very time consuming. + * False by default. + * + * \param _constantSamplingTimeMode : The new value of the + * constantSamplingTimeMode flag. + */ + inline void setConstantSamplingTimeMode(const bool _constantSamplingTimeMode) + { + constantSamplingTimeMode = _constantSamplingTimeMode; + } + + /*! + * Set the color used to display the object at the current position in the + * robot's camera view. + * + * \param col : The desired color. + */ + void setCurrentViewColor(const vpColor &col) { curColor = col; } + + /*! + * Set the color used to display the object at the desired position in the + * robot's camera view. + * + * \param col : The desired color. + */ + void setDesiredViewColor(const vpColor &col) { desColor = col; } + + /*! + * Set the desired position of the robot's camera relative to the object. + * + * \param cdMo_ : The desired pose of the camera. + */ + void setDesiredCameraPosition(const vpHomogeneousMatrix &cdMo_) + { + this->vpWireFrameSimulator::setDesiredCameraPosition(cdMo_); + } + + /*! + * Set the way to draw the robot. Depending on what you choose you can + * display a 3D wireframe model or a set of lines linking the frames used to + * compute the geometrical model. + * + * \param dispType : Type of display. Can be MODEL_3D or MODEL_DH. + */ + inline void setDisplayRobotType(const vpDisplayRobotType dispType) { displayType = dispType; } + /*! + * Set the external camera point of view. + * + * \param camMf_ : The pose of the external camera relative to the world + * reference frame. + */ + void setExternalCameraPosition(const vpHomogeneousMatrix &camMf_) + { + this->vpWireFrameSimulator::setExternalCameraPosition(camMf_); + } + /*! + * Specify the thickness of the graphics drawings. + */ + void setGraphicsThickness(unsigned int thickness) { this->thickness_ = thickness; } + + /*! + * Set the sampling time. + * + * \param delta_t : Sampling time in second used to compute the robot + * displacement from the velocity applied to the robot during this time. + * + * Since the wireframe simulator is threaded, the sampling time is set to + * vpTime::getMinTimeForUsleepCall() / 1000 seconds. + */ + inline void setSamplingTime(const double &delta_t) VP_OVERRIDE + { + if (delta_t < (vpTime::getMinTimeForUsleepCall() * 1e-3)) { + this->delta_t_ = (vpTime::getMinTimeForUsleepCall() * 1e-3); + } + else { + this->delta_t_ = delta_t; + } + } + /*! + * Set the parameter which enable or disable the singularity management. + */ + void setSingularityManagement(bool sm) { singularityManagement = sm; } + + /*! + * Activates extra printings when the robot reaches joint limits... + */ + void setVerbose(bool verbose) { this->verbose_ = verbose; } + + /*! + * Set the pose between the object and the fixed world frame. + * + * \param fMo_ : The pose between the object and the fixed world frame. + */ + void set_fMo(const vpHomogeneousMatrix &fMo_) { this->fMo = fMo_; } + //@} + +protected: +/** @name Protected Member Functions Inherited from vpRobotWireFrameSimulator + */ +//@{ + /*! + * Function used to launch the thread which moves the robot. + */ + static void launcher(vpRobotWireFrameSimulator &simulator) + { + simulator.updateArticularPosition(); + } + + /*! + * Method launched by the thread to compute the position of the robot in the + * articular frame. + */ + virtual void updateArticularPosition() = 0; + /*! Method used to check if the robot reached a joint limit. */ + virtual int isInJointLimit() = 0; + /*! Compute the articular velocity relative to the velocity in another + * frame. */ + virtual void computeArticularVelocity() = 0; + + /* Display functions */ + void initDisplay() { } + virtual void initArms() = 0; + + vpColVector get_artCoord() + { + m_mutex_artCoord.lock(); + vpColVector artCoordTmp(6); + artCoordTmp = artCoord; + m_mutex_artCoord.unlock(); + return artCoordTmp; + } + void set_artCoord(const vpColVector &coord) + { + m_mutex_artCoord.lock(); + artCoord = coord; + m_mutex_artCoord.unlock(); + } + + vpColVector get_artVel() + { + m_mutex_artVel.lock(); + vpColVector artVelTmp(artVel); + m_mutex_artVel.unlock(); + return artVelTmp; + } + void set_artVel(const vpColVector &vel) + { + m_mutex_artVel.lock(); + artVel = vel; + m_mutex_artVel.unlock(); + } + + vpColVector get_velocity() + { + m_mutex_velocity.lock(); + vpColVector velocityTmp = velocity; + m_mutex_velocity.unlock(); + return velocityTmp; + } + void set_velocity(const vpColVector &vel) + { + m_mutex_velocity.lock(); + velocity = vel; + m_mutex_velocity.unlock(); + } + + void set_displayBusy(const bool &status) + { + m_mutex_display.lock(); + displayBusy = status; + m_mutex_display.unlock(); + } + bool get_displayBusy() + { + m_mutex_display.lock(); + bool status = displayBusy; + if (!displayBusy) + displayBusy = true; + m_mutex_display.unlock(); + return status; + } + + /*! Get a table of poses between the reference frame and the frames you used + * to compute the Denavit-Hartenberg representation */ + virtual void get_fMi(vpHomogeneousMatrix *fMit) = 0; + //@} +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpSimulatorAfma6.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpSimulatorAfma6.h new file mode 100644 index 00000000..ba465f8b --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpSimulatorAfma6.h @@ -0,0 +1,278 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Class which provides a simulator for the robot Afma6. + */ + +/*! + * \file vpSimulatorAfma6.h + * \brief Class which provides a simulator for the robot Afma6. + */ + +#ifndef vpSimulatorAfma6_HH +#define vpSimulatorAfma6_HH + +#include +#include +#include + +#include + +#if defined(VISP_HAVE_MODULE_GUI) && defined(VISP_HAVE_THREADS) + +BEGIN_VISP_NAMESPACE +/*! + * \class vpSimulatorAfma6 + * + * \ingroup group_robot_simu_gantry + * + * \brief Simulator of Irisa's gantry robot named Afma6. + * + * Implementation of the vpRobotWireFrameSimulator class in order to simulate + * Irisa's Afma6 robot. This robot is a gantry robot with six degrees of freedom + * manufactured in 1992 by the french Afma-Robots company. + * + * \warning This class uses threading capabilities. Thus on Unix-like + * platforms, the libpthread third-party library need to be + * installed. On Windows, we use the native threading capabilities. + * + * This class allows to control the Afma6 gantry robot in position + * and velocity: + * - in the joint space (vpRobot::ARTICULAR_FRAME), + * - in the fixed reference frame (vpRobot::REFERENCE_FRAME), + * - in the camera frame (vpRobot::CAMERA_FRAME), + * - or in a mixed frame (vpRobot::MIXT_FRAME) where translations are expressed + * in the reference frame and rotations in the camera frame. + * + * End-effector frame (vpRobot::END_EFFECTOR_FRAME) is not implemented. + * + * All the translations are expressed in meters for positions and m/s + * for the velocities. Rotations are expressed in radians for the + * positions, and rad/s for the rotation velocities. + * + * The direct and inverse kinematics models are implemented in the + * vpAfma6 class. + * + * To control the robot in position, you may set the controller + * to position control and then send the position to reach in a specific + * frame like here in the joint space: + * + * \code + * #include + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpSimulatorAfma6 robot; + * + * robot.init(vpAfma6::TOOL_CCMOP, vpCameraParameters::perspectiveProjWithoutDistortion); + * + * vpColVector q(6); + * // Set a joint position + * q[0] = 0.1; // Joint 1 position, in meter + * q[1] = 0.2; // Joint 2 position, in meter + * q[2] = 0.3; // Joint 3 position, in meter + * q[3] = M_PI/8; // Joint 4 position, in rad + * q[4] = M_PI/4; // Joint 5 position, in rad + * q[5] = M_PI; // Joint 6 position, in rad + * + * // Initialize the controller to position control + * robot.setRobotState(vpRobot::STATE_POSITION_CONTROL); + * + * // Moves the robot in the joint space + * robot.setPosition(vpRobot::ARTICULAR_FRAME, q); + * + * return 0; + * } + * \endcode + * + * To control the robot in velocity, you may set the controller to + * velocity control and then send the velocities. To end the velocity + * control and stop the robot you have to set the controller to the + * stop state. Here is an example of a velocity control in the joint + * space: + * + * \code + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpSimulatorAfma6 robot; + * + * robot.init(vpAfma6::TOOL_GRIPPER, vpCameraParameters::perspectiveProjWithoutDistortion); + * + * vpColVector qvel(6); + * // Set a joint velocity + * qvel[0] = 0.1; // Joint 1 velocity in m/s + * qvel[1] = 0.1; // Joint 2 velocity in m/s + * qvel[2] = 0.1; // Joint 3 velocity in m/s + * qvel[3] = M_PI/8; // Joint 4 velocity in rad/s + * qvel[4] = 0; // Joint 5 velocity in rad/s + * qvel[5] = 0; // Joint 6 velocity in rad/s + * + * // Initialize the controller to position control + * robot.setRobotState(vpRobot::STATE_VELOCITY_CONTROL); + * + * for ( ; ; ) { + * // Apply a velocity in the joint space + * robot.setVelocity(vpRobot::ARTICULAR_FRAME, qvel); + * + * // Compute new velocities qvel... + * } + * + * // Stop the robot + * robot.setRobotState(vpRobot::STATE_STOP); + * + * return 0; + * } + * \endcode + * + * It is also possible to measure the robot current position with + * getPosition() method and the robot current velocities with the getVelocity() + * method. + * + * For convenience, there is also the ability to read/write joint + * positions from a position file with readPosFile() and savePosFile() + * methods. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * To know how this class can be used to achieve a visual servoing simulation, + * you can follow the tutorial:
+ * + * - \ref tutorial-ibvs. +*/ +class VISP_EXPORT vpSimulatorAfma6 : public vpRobotWireFrameSimulator, public vpAfma6 +{ +public: + static const double defaultPositioningVelocity; + +private: + vpColVector q_prev_getdis; + bool first_time_getdis; + + double positioningVelocity; + + vpColVector zeroPos; + vpColVector reposPos; + + bool toolCustom; + std::string arm_dir; + +public: + vpSimulatorAfma6(); + VP_EXPLICIT vpSimulatorAfma6(bool display); + virtual ~vpSimulatorAfma6() VP_OVERRIDE; + + void getCameraParameters(vpCameraParameters &cam, const unsigned int &image_width, const unsigned int &image_height); + void getCameraParameters(vpCameraParameters &cam, const vpImage &I); + void getCameraParameters(vpCameraParameters &cam, const vpImage &I); + void getDisplacement(const vpRobot::vpControlFrameType frame, vpColVector &displacement) VP_OVERRIDE; + void getPosition(const vpRobot::vpControlFrameType frame, vpColVector &q) VP_OVERRIDE; + void getPosition(const vpRobot::vpControlFrameType frame, vpColVector &q, double ×tamp); + void getPosition(const vpRobot::vpControlFrameType frame, vpPoseVector &position); + void getPosition(const vpRobot::vpControlFrameType frame, vpPoseVector &position, double ×tamp); + double getPositioningVelocity(void) { return positioningVelocity; } + void getVelocity(const vpRobot::vpControlFrameType frame, vpColVector &q); + void getVelocity(const vpRobot::vpControlFrameType frame, vpColVector &q, double ×tamp); + vpColVector getVelocity(const vpRobot::vpControlFrameType frame); + vpColVector getVelocity(const vpRobot::vpControlFrameType frame, double ×tamp); + + void get_cMe(vpHomogeneousMatrix &cMe); + void get_cVe(vpVelocityTwistMatrix &cVe); + void get_eJe(vpMatrix &eJe) VP_OVERRIDE; + void get_fJe(vpMatrix &fJe) VP_OVERRIDE; + + void + init(vpAfma6::vpAfma6ToolType tool, + vpCameraParameters::vpCameraParametersProjType projModel = vpCameraParameters::perspectiveProjWithoutDistortion); + bool initialiseCameraRelativeToObject(const vpHomogeneousMatrix &cMo); + void initialiseObjectRelativeToCamera(const vpHomogeneousMatrix &cMo); + + void move(const char *filename); + + static bool readPosFile(const std::string &filename, vpColVector &q); + static bool savePosFile(const std::string &filename, const vpColVector &q); + void setCameraParameters(const vpCameraParameters &cam); + void setJointLimit(const vpColVector &limitMin, const vpColVector &limitMax); + + void setPosition(const vpRobot::vpControlFrameType frame, const vpColVector &q) VP_OVERRIDE; + void setPosition(const vpRobot::vpControlFrameType frame, double pos1, double pos2, double pos3, double pos4, + double pos5, double pos6); + void setPosition(const char *filename); + void setPositioningVelocity(double vel) { positioningVelocity = vel; } + bool setPosition(const vpHomogeneousMatrix &cdMo, vpImage *Iint = nullptr, const double &errMax = 0.001); + vpRobot::vpRobotStateType setRobotState(const vpRobot::vpRobotStateType newState) VP_OVERRIDE; + + void setVelocity(const vpRobot::vpControlFrameType frame, const vpColVector &velocity) VP_OVERRIDE; + + void stopMotion(); + +protected: + /** @name Protected Member Functions Inherited from vpSimulatorAfma6 */ + //@{ + void computeArticularVelocity() VP_OVERRIDE; + void compute_fMi(); + void findHighestPositioningSpeed(vpColVector &q); + void getExternalImage(vpImage &I); + inline void get_fMi(vpHomogeneousMatrix *fMit) VP_OVERRIDE + { + m_mutex_fMi.lock(); + for (int i = 0; i < 8; i++) { + fMit[i] = fMi[i]; + } + + m_mutex_fMi.unlock(); + } + void init() VP_OVERRIDE; + void initArms() VP_OVERRIDE; + void initDisplay(); + int isInJointLimit() VP_OVERRIDE; + bool singularityTest(const vpColVector &q, vpMatrix &J); + void updateArticularPosition() VP_OVERRIDE; + //@} +}; +END_VISP_NAMESPACE +#endif + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpSimulatorCamera.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpSimulatorCamera.h new file mode 100644 index 00000000..5efe2e42 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpSimulatorCamera.h @@ -0,0 +1,142 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Defines the simplest robot : a free flying camera. + */ + +/*! + * \file vpSimulatorCamera.h + * \brief class that defines the simplest robot : a free flying camera + */ + +#ifndef vpSimulatorCamera_H +#define vpSimulatorCamera_H + +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpSimulatorCamera + * + * \ingroup group_robot_simu_camera + * + * \brief Class that defines the simplest robot: a free flying camera. + * + * This free flying camera has 6 dof; 3 in translation and 3 in rotation. + * It evolves as a gentry robot with respect to a world frame. This class + * is similar to vpRobotCamera class except that here the position of the robot + * is provided as the transformation from world frame to camera frame; wMc. + * This representation is more intuitive than the one implemented in + * vpRobotCamera where the transformation from camera to world frame is + * considered; cMw. + * + * For this particular simulated robot, the end-effector and camera frame are + * confused. That means that the cMe transformation is equal to identity. + * + * The robot jacobian expressed in the end-effector frame + * \f$ {^e}{\bf J}_e \f$ is also set to identity (see get_eJe()). + * + * The following code shows how to control this robot in position and velocity. + * \code + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpHomogeneousMatrix wMc; + * vpSimulatorCamera robot; + * + * robot.getPosition(wMc); // Position of the camera in the world frame + * std::cout << "Default position of the camera in the world frame wMc:\n" << wMc << std::endl; + * + * wMc[2][3] = 1.; // Camera frame is 1 meter along z axis in front of the world frame + * robot.setPosition(wMc); // Set the new position of the camera in the world frame + * std::cout << "New position of the camera in the world frame wMc:\n" << wMc << std::endl; + * + * robot.setSamplingTime(0.100); // Modify the default sampling time to 0.1 second + * robot.setMaxTranslationVelocity(1.); // vx, vy and vz max set to 1 m/s + * robot.setMaxRotationVelocity(vpMath::rad(90)); // wx, wy and wz max set to 90 deg/s + * + * vpColVector v(6); + * v = 0; + * v[2] = 1.; // set v_z to 1 m/s + * robot.setVelocity(vpRobot::CAMERA_FRAME, v); + * // The robot has moved from 0.1 meters along the z axis + * robot.getPosition(wMc); // Position of the camera in the world frame + * std::cout << "New position of the camera wMc:\n" << wMc << std::endl; + * } + * \endcode + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in achieving a visual servoing simulation, you may have a look at:
+ * + * - \ref tutorial-ibvs. +*/ +class VISP_EXPORT vpSimulatorCamera : public vpRobotSimulator +{ +protected: + vpHomogeneousMatrix wMc_; // world to camera + +public: + vpSimulatorCamera(); + +public: + /** @name Inherited functionalities from vpSimulatorCamera */ + //@{ + void get_cVe(vpVelocityTwistMatrix &cVe) const; + void get_eJe(vpMatrix &eJe) VP_OVERRIDE; + + vpHomogeneousMatrix getPosition() const; + void getPosition(vpHomogeneousMatrix &wMc) const; + void getPosition(const vpRobot::vpControlFrameType frame, vpColVector &q) VP_OVERRIDE; + void setPosition(const vpHomogeneousMatrix &wMc); + void setVelocity(const vpRobot::vpControlFrameType frame, const vpColVector &vel) VP_OVERRIDE; + //@} + +private: + void init() VP_OVERRIDE; + + // Non implemented virtual pure functions + void get_fJe(vpMatrix & /*_fJe */) VP_OVERRIDE { } + void getDisplacement(const vpRobot::vpControlFrameType /* frame */, vpColVector & /* q */) VP_OVERRIDE { } + void setPosition(const vpRobot::vpControlFrameType /* frame */, const vpColVector & /* q */) VP_OVERRIDE { } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpSimulatorPioneer.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpSimulatorPioneer.h new file mode 100644 index 00000000..6aef779b --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpSimulatorPioneer.h @@ -0,0 +1,144 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Pioneer mobile robot simulator without display. + */ + +#ifndef vpSimulatorPioneer_H +#define vpSimulatorPioneer_H + +/*! + * \file vpSimulatorPioneer.h + * \brief class that defines the Pioneer mobile robot simulator equipped with a + * static camera. + */ + +#include +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpSimulatorPioneer + * + * \ingroup group_robot_simu_unicycle + * + * \brief Class that defines the Pioneer mobile robot simulator equipped with a + * static camera. + * + * It intends to simulate the mobile robot described in vpPioneer class. + * This robot has 2 dof: \f$(v_x, w_z)\f$, the translational and + * rotational velocities that are applied at point E. + * + * The robot position evolves with respect to a world frame; wMc. When a new + * joint velocity is applied to the robot using setVelocity(), the position of + * the camera wrt the world frame is updated. + * + * \image html pioneer.png + * + * The following code shows how to control this robot in position and velocity. + * \code + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpHomogeneousMatrix wMc; + * vpSimulatorPioneer robot; + * + * robot.getPosition(wMc); // Position of the camera in the world frame + * std::cout << "Default position of the camera in the world frame wMc:\n" << wMc << std::endl; + * + * robot.setSamplingTime(0.100); // Modify the default sampling time to 0.1 second + * robot.setMaxTranslationVelocity(1.); // vx max set to 1 m/s + * robot.setMaxRotationVelocity(vpMath::rad(90)); // wz max set to 90 deg/s + * + * vpColVector v(2); // we control vx and wz dof + * v = 0; + * v[0] = 1.; // set vx to 1 m/s + * robot.setVelocity(vpRobot::ARTICULAR_FRAME, v); + * // The robot has moved from 0.1 meters along the z axis + * robot.getPosition(wMc); // Position of the camera in the world frame + * std::cout << "New position of the camera wMc:\n" << wMc << std::endl; + * } + * \endcode + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in simulating a Pioneer robot, you may have a look at:
+ * + * - \ref tutorial-simu-robot-pioneer +*/ +class VISP_EXPORT vpSimulatorPioneer : public vpPioneer, public vpRobotSimulator +{ + +protected: + // world to camera + vpHomogeneousMatrix wMc_; + // world to end effector frame which is also the mobile + // robot frame located between the two wheels + vpHomogeneousMatrix wMe_; + // cMe_ is a protected member of vpUnicycle + + double xm_; + double ym_; + double theta_; + +public: + vpSimulatorPioneer(); + +public: + /** @name Inherited functionalities from vpSimulatorPioneer */ + //@{ + void get_eJe(vpMatrix &eJe) VP_OVERRIDE; + + void getPosition(vpHomogeneousMatrix &wMc) const; + void getPosition(const vpRobot::vpControlFrameType frame, vpColVector &q) VP_OVERRIDE; + void setVelocity(const vpRobot::vpControlFrameType frame, const vpColVector &vel) VP_OVERRIDE; + //@} + +private: + void init() VP_OVERRIDE; + + // Non implemented virtual pure functions + void get_fJe(vpMatrix & /*_fJe */) VP_OVERRIDE { } + void getDisplacement(const vpRobot::vpControlFrameType /* frame */, vpColVector & /* q */) VP_OVERRIDE { } + void setPosition(const vpRobot::vpControlFrameType /* frame */, const vpColVector & /* q */) VP_OVERRIDE { } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpSimulatorPioneerPan.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpSimulatorPioneerPan.h new file mode 100644 index 00000000..1b23252c --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpSimulatorPioneerPan.h @@ -0,0 +1,141 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Pioneer mobile robot equipped with a pan head simulator without display. + */ + +/*! + * \file vpSimulatorPioneerPan.h + * \brief class that defines the Pioneer mobile robot simulator equipped + * with a camera able to move in pan. + */ + +#ifndef vpSimulatorPioneerPan_H +#define vpSimulatorPioneerPan_H + +#include +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpSimulatorPioneerPan + * + * \ingroup group_robot_simu_unicycle + * + * \brief Class that defines the Pioneer mobile robot simulator equipped + * with a camera able to move in pan. + * + * It intends to simulate the mobile robot described in vpPioneerPan class. + * This robot has 3 dof: \f$(v_x, w_z, \dot{q_1})\f$, the translational and + * rotational velocities of the mobile platform, the pan head velocity + * respectively. + * + * The robot position evolves with respect to a world frame; wMc. When a new + * joint velocity is applied to the robot using setVelocity(), the position of + * the camera wrt the world frame is updated. + * + * \image html pioneer-pan.png + * + * The following code shows how to control this robot in position and velocity. + * \code + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpHomogeneousMatrix wMc; + * vpSimulatorPioneerPan robot; + * + * robot.getPosition(wMc); // Position of the camera in the world frame + * std::cout << "Default position of the camera in the world frame wMc:\n" << wMc << std::endl; + * + * robot.setSamplingTime(0.100); // Modify the default sampling time to 0.1 second + * robot.setMaxTranslationVelocity(1.); // vx max set to 1 m/s + * robot.setMaxRotationVelocity(vpMath::rad(90)); // wz max set to 90 deg/s + * + * vpColVector v(3); // we control vx, wz and q_pan + * v = 0; + * v[0] = 1.; // set vx to 1 m/s + * robot.setVelocity(vpRobot::ARTICULAR_FRAME, v); + * // The robot has moved from 0.1 meters along the z axis + * robot.getPosition(wMc); // Position of the camera in the world frame + * std::cout << "New position of the camera wMc:\n" << wMc << std::endl; + * } + * \endcode + * + * The usage of this class is also highlighted in \ref tutorial-simu-robot-pioneer. +*/ +class VISP_EXPORT vpSimulatorPioneerPan : public vpPioneerPan, public vpRobotSimulator +{ + +protected: + //! robot / camera location in the world frame + vpHomogeneousMatrix wMc_; // world to camera + vpHomogeneousMatrix wMm_; // world to mobile robot frame located between the two wheels + // mMp_ mobile robot to pan frame is a protected member of vpPioneerPan + // pMe_ pan head to end effector frame is a protected member of vpPioneerPan + // cMe_ is a protected member of vpUnicycle + + double xm_; + double ym_; + double theta_; + double q_pan_; + +public: + vpSimulatorPioneerPan(); + +public: + /** @name Inherited functionalities from vpSimulatorPioneerPan */ + //@{ + void get_eJe(vpMatrix &eJe) VP_OVERRIDE; + + void getPosition(vpHomogeneousMatrix &wMc) const; + void getPosition(const vpRobot::vpControlFrameType frame, vpColVector &q) VP_OVERRIDE; + void setVelocity(const vpRobot::vpControlFrameType frame, const vpColVector &vel) VP_OVERRIDE; + //@} + +private: + void init() VP_OVERRIDE; + + // Non implemented virtual pure functions + void get_fJe(vpMatrix & /*_fJe */) VP_OVERRIDE { } + void getDisplacement(const vpRobot::vpControlFrameType /* frame */, vpColVector & /* q */) VP_OVERRIDE { } + void setPosition(const vpRobot::vpControlFrameType /* frame */, const vpColVector & /* q */) VP_OVERRIDE { } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpSimulatorViper850.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpSimulatorViper850.h new file mode 100644 index 00000000..fe88ca38 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpSimulatorViper850.h @@ -0,0 +1,304 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Class which provides a simulator for the robot Viper850. + */ + +/*! + * \file vpSimulatorViper850.h + * \brief Class which provides a simulator for the robot Viper850.. + */ + +#ifndef vpSimulatorViper850_HH +#define vpSimulatorViper850_HH + +#include +#include +#if defined(VISP_HAVE_MODULE_GUI) && defined(VISP_HAVE_THREADS) + +#include + +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpSimulatorViper850 + * + * \ingroup group_robot_simu_arm + * + * \brief Simulator of Irisa's Viper S850 robot named Viper850. + * + * Implementation of the vpRobotWireFrameSimulator class in order to simulate + * Irisa's Viper850 robot. This robot is an ADEPT six degrees of freedom arm. + * + * \warning This class uses threading capabilities. Thus on Unix-like + * platforms, the libpthread third-party library need to be + * installed. On Windows, we use the native threading capabilities. + * + * This class allows to control the Viper850 arm robot in position + * and velocity: + * - in the joint space (vpRobot::ARTICULAR_FRAME), + * - in the fixed reference frame (vpRobot::REFERENCE_FRAME), + * - in the camera frame (vpRobot::CAMERA_FRAME), + * - or in a mixed frame (vpRobot::MIXT_FRAME) where translations are expressed + * in the reference frame and rotations in the camera frame. + * + * End-effector frame (vpRobot::END_EFFECTOR_FRAME) is not implemented. + * + * All the translations are expressed in meters for positions and m/s + * for the velocities. Rotations are expressed in radians for the + * positions, and rad/s for the rotation velocities. + * + * The direct and inverse kinematics models are implemented in the + * vpViper850 class. + * + * To control the robot in position, you may set the controller + * to position control and then send the position to reach in a specific + * frame like here in the joint space: + * + * \code + * #include + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpSimulatorViper850 robot; + * + * vpColVector q(6); + * // Set a joint position + * q[0] = vpMath::rad(10); // Joint 1 position, in rad + * q[1] = 0.2; // Joint 2 position, in rad + * q[2] = 0.3; // Joint 3 position, in rad + * q[3] = M_PI/8; // Joint 4 position, in rad + * q[4] = M_PI/4; // Joint 5 position, in rad + * q[5] = M_PI; // Joint 6 position, in rad + * + * // Initialize the controller to position control + * robot.setRobotState(vpRobot::STATE_POSITION_CONTROL); + * + * // Moves the robot in the joint space + * robot.setPosition(vpRobot::ARTICULAR_FRAME, q); + * + * return 0; + * } + * \endcode + * + * The robot moves to the specified position with the default + * positioning velocity vpRobotViper850::defaultPositioningVelocity. The + * setPositioningVelocity() method allows to change the maximal + * velocity used to reach the desired position. + * + * \code + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpSimulatorViper850 robot; + * + * vpColVector q(6); + * // Set q[i] with i in [0:5] + * + * // Initialize the controller to position control + * robot.setRobotState(vpRobot::STATE_POSITION_CONTROL); + * + * // Set the max velocity to 40% + * robot.setPositioningVelocity(40); + * + * // Moves the robot in the joint space + * robot.setPosition(vpRobot::ARTICULAR_FRAME, q); + * + * return 0; + * } + * \endcode + * + * To control the robot in velocity, you may set the controller to + * velocity control and then send the velocities. To end the velocity + * control and stop the robot you have to set the controller to the + * stop state. Here is an example of a velocity control in the joint + * space: + * + * \code + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpSimulatorViper850 robot; + * + * vpColVector qvel(6); + * // Set a joint velocity + * qvel[0] = 0.1; // Joint 1 velocity in rad/s + * qvel[1] = vpMath::rad(15); // Joint 2 velocity in rad/s + * qvel[2] = 0; // Joint 3 velocity in rad/s + * qvel[3] = M_PI/8; // Joint 4 velocity in rad/s + * qvel[4] = 0; // Joint 5 velocity in rad/s + * qvel[5] = 0; // Joint 6 velocity in rad/s + * + * // Initialize the controller to position control + * robot.setRobotState(vpRobot::STATE_VELOCITY_CONTROL); + * + * for ( ; ; ) { + * // Apply a velocity in the joint space + * robot.setVelocity(vpRobot::ARTICULAR_FRAME, qvel); + * + * // Compute new velocities qvel... + * } + * + * // Stop the robot + * robot.setRobotState(vpRobot::STATE_STOP); + * + * return 0; + * } + * \endcode + * + * It is also possible to measure the robot current position with + * getPosition() method and the robot current velocities with the getVelocity() + * method. + * + * For convenience, there is also the ability to read/write joint + * positions from a position file with readPosFile() and savePosFile() + * methods. + * + * To know how this class can be used to achieve a visual servoing simulation, + * you can follow the \ref tutorial-ibvs. +*/ +class VISP_EXPORT vpSimulatorViper850 : public vpRobotWireFrameSimulator, public vpViper850 +{ +public: + static const double defaultPositioningVelocity; + +private: + vpColVector q_prev_getdis; + bool first_time_getdis; + + double positioningVelocity; + + vpColVector zeroPos; + vpColVector reposPos; + + bool toolCustom; + std::string arm_dir; + +public: + vpSimulatorViper850(); + VP_EXPLICIT vpSimulatorViper850(bool display); + virtual ~vpSimulatorViper850() VP_OVERRIDE; + + void getCameraParameters(vpCameraParameters &cam, const unsigned int &image_width, const unsigned int &image_height); + void getCameraParameters(vpCameraParameters &cam, const vpImage &I); + void getCameraParameters(vpCameraParameters &cam, const vpImage &I); + + void getDisplacement(const vpRobot::vpControlFrameType frame, vpColVector &displacement) VP_OVERRIDE; + + void getPosition(const vpRobot::vpControlFrameType frame, vpColVector &q) VP_OVERRIDE; + void getPosition(const vpRobot::vpControlFrameType frame, vpColVector &q, double ×tamp); + void getPosition(const vpRobot::vpControlFrameType frame, vpPoseVector &position); + void getPosition(const vpRobot::vpControlFrameType frame, vpPoseVector &position, double ×tamp); + double getPositioningVelocity(void) { return positioningVelocity; } + + void getVelocity(const vpRobot::vpControlFrameType frame, vpColVector &q); + void getVelocity(const vpRobot::vpControlFrameType frame, vpColVector &q, double ×tamp); + vpColVector getVelocity(const vpRobot::vpControlFrameType frame); + vpColVector getVelocity(const vpRobot::vpControlFrameType frame, double ×tamp); + + void get_cMe(vpHomogeneousMatrix &cMe); + void get_cVe(vpVelocityTwistMatrix &cVe); + void get_eJe(vpMatrix &eJe) VP_OVERRIDE; + void get_fJe(vpMatrix &fJe) VP_OVERRIDE; + + void + init(vpViper850::vpToolType tool, + vpCameraParameters::vpCameraParametersProjType projModel = vpCameraParameters::perspectiveProjWithoutDistortion); + bool initialiseCameraRelativeToObject(const vpHomogeneousMatrix &cMo); + void initialiseObjectRelativeToCamera(const vpHomogeneousMatrix &cMo); + + void move(const char *filename); + + static bool readPosFile(const std::string &filename, vpColVector &q); + static bool savePosFile(const std::string &filename, const vpColVector &q); + + void setCameraParameters(const vpCameraParameters &cam); + void setJointLimit(const vpColVector &limitMin, const vpColVector &limitMax); + void setPosition(const vpRobot::vpControlFrameType frame, const vpColVector &q) VP_OVERRIDE; + void setPosition(const vpRobot::vpControlFrameType frame, double pos1, double pos2, double pos3, double pos4, + double pos5, double pos6); + void setPosition(const char *filename); + void setPositioningVelocity(double vel) { positioningVelocity = vel; } + vpRobot::vpRobotStateType setRobotState(const vpRobot::vpRobotStateType newState) VP_OVERRIDE; + + void setVelocity(const vpRobot::vpControlFrameType frame, const vpColVector &velocity) VP_OVERRIDE; + + void stopMotion(); + +protected: + /** @name Protected Member Functions Inherited from vpSimulatorViper850 */ + //@{ + void computeArticularVelocity() VP_OVERRIDE; + void compute_fMi(); + void findHighestPositioningSpeed(vpColVector &q); + void getExternalImage(vpImage &I); + + inline void get_fMi(vpHomogeneousMatrix *fMit) VP_OVERRIDE + { + m_mutex_fMi.lock(); + for (int i = 0; i < 8; i++) { + fMit[i] = fMi[i]; + } + m_mutex_fMi.unlock(); + } + void init() VP_OVERRIDE; + void initArms() VP_OVERRIDE; + void initDisplay(); + int isInJointLimit() VP_OVERRIDE; + bool singularityTest(const vpColVector &q, vpMatrix &J); + void updateArticularPosition() VP_OVERRIDE; + //@} +}; +END_VISP_NAMESPACE +#endif + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpUnicycle.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpUnicycle.h new file mode 100644 index 00000000..75522f3a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpUnicycle.h @@ -0,0 +1,120 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Common features for unicycle mobile robots. + */ +#ifndef VPUNICYCLE_H +#define VPUNICYCLE_H + +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpUnicycle + * + * \ingroup group_robot_simu_unicycle + * + * \brief Generic functions for unicycle mobile robots. + * + * This class provides common features for unicycle mobile robots. +*/ +class VISP_EXPORT vpUnicycle +{ +public: + /*! + * Default constructor that does nothing. + */ + vpUnicycle() : cMe_(), eJe_() { } + + /** @name Inherited functionalities from vpUnicycle */ + //@{ + /*! + * Return the transformation \f${^c}{\bf M}_e\f$ between the camera frame + * and the mobile robot end effector frame. + */ + vpHomogeneousMatrix get_cMe() const { return cMe_; } + + /*! + * Return the twist transformation from camera frame to the mobile robot + * end effector frame. This transformation allows to compute a velocity + * expressed in the end effector frame into the camera frame. + */ + vpVelocityTwistMatrix get_cVe() const + { + vpVelocityTwistMatrix cVe; + cVe.buildFrom(cMe_); + return cVe; + } + + /*! + * Return the twist transformation from camera frame to the mobile robot + * end effector frame. This transformation allows to compute a velocity + * expressed in the end effector frame into the camera frame. + * + * \sa get_cVe() + */ + void get_cVe(vpVelocityTwistMatrix &cVe) const { cVe = get_cVe(); } + + /*! + * Return the robot jacobian \f${^e}{\bf J}_e\f$ expressed in the end + * effector frame. + * + * \return The robot jacobian such as \f${\bf v} = {^e}{\bf J}_e \; \dot{\bf + * q}\f$ with \f$\dot{\bf q} = (v_x, w_z)\f$ the robot control velocities and + * \f$\bf v\f$ the six dimension velocity skew. + */ + vpMatrix get_eJe() const { return eJe_; } + + /*! + * Set the transformation between the camera frame and the end effector + * frame. + */ + void set_cMe(const vpHomogeneousMatrix &cMe) { cMe_ = cMe; } + + /*! + * Set the robot jacobian \f${^e}{\bf J}_e\f$ expressed in the end effector + * frame. + * + * \param eJe : The robot jacobian to set such as \f${\bf v} = {^e}{\bf J}_e + * \; \dot{\bf q}\f$ with \f$\dot{\bf q} = (v_x, w_z)\f$ the robot control + * velocities and \f$\bf v\f$ the six dimension velocity skew. + */ + void set_eJe(const vpMatrix &eJe) { eJe_ = eJe; } + //@} + +protected: + vpHomogeneousMatrix cMe_; // Camera frame to mobile platform frame + vpMatrix eJe_; // Robot jacobian +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpViper.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpViper.h new file mode 100644 index 00000000..4fa9e72e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpViper.h @@ -0,0 +1,175 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Interface for a generic ADEPT Viper (either 650 or 850) robot. + */ + +/*! + + \file vpViper.h + + Modelization of the ADEPT Viper 650 or 850 robot. + +*/ + +#ifndef VP_VIPER_H +#define VP_VIPER_H + +#include +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + + \class vpViper + + \ingroup group_robot_real_arm group_robot_simu_arm + + \brief Modelization of the ADEPT Viper robot + + This robot has six degrees of freedom. The model of the robot is the + following: \image html model-viper.png Model of the Viper 850 robot. + + The non modified Denavit-Hartenberg representation of the robot is + given in the table below, where \f$q_1^*, \ldots, q_6^*\f$ + are the variable joint positions. + + \f[ + \begin{tabular}{|c|c|c|c|c|} + \hline + Joint & $a_i$ & $d_i$ & $\alpha_i$ & $\theta_i$ \\ + \hline + 1 & $a_1$ & $d_1$ & $-\pi/2$ & $q_1^*$ \\ + 2 & $a_2$ & 0 & 0 & $q_2^*$ \\ + 3 & $a_3$ & 0 & $-\pi/2$ & $q_3^* - \pi$ \\ + 4 & 0 & $d_4$ & $\pi/2$ & $q_4^*$ \\ + 5 & 0 & 0 & $-\pi/2$ & $q_5^*$ \\ + 6 & 0 & 0 & 0 & $q_6^*-\pi$ \\ + 7 & 0 & $d_6$ & 0 & 0 \\ + \hline + \end{tabular} + \f] + + In this modelization, different frames have to be considered. + + - \f$ {\cal F}_f \f$: the reference frame, also called world frame + + - \f$ {\cal F}_w \f$: the wrist frame located at the intersection of + the last three rotations, with \f$ ^f{\bf M}_w = ^0{\bf M}_6 \f$ + + - \f$ {\cal F}_e \f$: the end-effector frame located at the interface of the + two tool changers, with \f$^f{\bf M}_e = 0{\bf M}_7 \f$ + + - \f$ {\cal F}_c \f$: the camera or tool frame, with \f$^f{\bf M}_c = ^f{\bf + M}_e \; ^e{\bf M}_c \f$ where \f$ ^e{\bf M}_c \f$ is the result of + a calibration stage. We can also consider a custom tool TOOL_CUSTOM and + set this during robot initialisation or using set_eMc(). + + - \f$ {\cal F}_s \f$: the force/torque sensor frame, with \f$d7=0.0666\f$. + + The forward kinematics of the robot is implemented in get_fMw(), + get_fMe() and get_fMc(). + + The robot forward jacobian used to compute the cartesian velocities + from joint ones is given and implemented in get_fJw(), get_fJe() and + get_eJe(). + +*/ +class VISP_EXPORT vpViper +{ +public: + vpViper(); + vpViper(const vpViper &viper); + virtual ~vpViper() { } + vpViper &operator=(const vpViper &viper); + + /** @name Inherited functionalities from vpViper */ + //@{ + vpHomogeneousMatrix getForwardKinematics(const vpColVector &q) const; + unsigned int getInverseKinematicsWrist(const vpHomogeneousMatrix &fMw, vpColVector &q, + const bool &verbose = false) const; + unsigned int getInverseKinematics(const vpHomogeneousMatrix &fMc, vpColVector &q, const bool &verbose = false) const; + vpHomogeneousMatrix get_fMc(const vpColVector &q) const; + void get_fMw(const vpColVector &q, vpHomogeneousMatrix &fMw) const; + void get_wMe(vpHomogeneousMatrix &wMe) const; + void get_eMc(vpHomogeneousMatrix &eMc) const; + void get_eMs(vpHomogeneousMatrix &eMs) const; + void get_fMe(const vpColVector &q, vpHomogeneousMatrix &fMe) const; + void get_fMc(const vpColVector &q, vpHomogeneousMatrix &fMc) const; + + void get_cMe(vpHomogeneousMatrix &cMe) const; + void get_cVe(vpVelocityTwistMatrix &cVe) const; + void get_fJw(const vpColVector &q, vpMatrix &fJw) const; + void get_fJe(const vpColVector &q, vpMatrix &fJe) const; + void get_eJe(const vpColVector &q, vpMatrix &eJe) const; + + virtual void set_eMc(const vpHomogeneousMatrix &eMc_); + virtual void set_eMc(const vpTranslationVector &etc_, const vpRxyzVector &erc_); + + vpColVector getJointMin() const; + vpColVector getJointMax() const; + double getCoupl56() const; + //@} + + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpViper &viper); + +private: + bool convertJointPositionInLimits(unsigned int joint, const double &q, double &q_mod, + const bool &verbose = false) const; + +public: + static const unsigned int njoint; ///< Number of joint. + +protected: + vpHomogeneousMatrix eMc; //!< End effector to camera transformation + // Minimal representation of eMc + vpTranslationVector etc; // meters + vpRxyzVector erc; // radian + + // Denavit-Hartenberg parameters + double a1, d1; //!< for joint 1 + double a2; //!< for joint 2 + double a3; //!< for joint 3 + double d4; //!< for joint 4 + double d6; //!< for joint 6 + double d7; //!< for force/torque location + double c56; //!< Mechanical coupling between joint 5 and joint 6 + + // Software joint limits in radians + vpColVector joint_max; // Maximal value of the joints + vpColVector joint_min; // Minimal value of the joints +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpViper650.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpViper650.h new file mode 100644 index 00000000..c6afe4b5 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpViper650.h @@ -0,0 +1,171 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Interface for the ADEPT Viper 650 robot. + */ + +/*! + * \file vpViper650.h + * + * Modelization of the ADEPT Viper 650 robot. + */ + +#ifndef vpViper650_h +#define vpViper650_h + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpViper650 + * + * \ingroup group_robot_real_arm + * + * \brief Modelization of the ADEPT Viper 650 robot. + * + * The model of the robot is the following: + * \image html model-viper.png Model of the Viper 650 robot. + * + * The non modified Denavit-Hartenberg representation of the robot is + * given in the table below, where \f$q_1^*, \ldots, q_6^*\f$ + * are the variable joint positions. + * + * \f[ + * \begin{tabular}{|c|c|c|c|c|} + * \hline + * Joint & $a_i$ & $d_i$ & $\alpha_i$ & $\theta_i$ \\ + * \hline + * 1 & $a_1$ & $d_1$ & $-\pi/2$ & $q_1^*$ \\ + * 2 & $a_2$ & 0 & 0 & $q_2^*$ \\ + * 3 & $a_3$ & 0 & $-\pi/2$ & $q_3^* - \pi$ \\ + * 4 & 0 & $d_4$ & $\pi/2$ & $q_4^*$ \\ + * 5 & 0 & 0 & $-\pi/2$ & $q_5^*$ \\ + * 6 & 0 & 0 & 0 & $q_6^*-\pi$ \\ + * 7 & 0 & $d_6$ & 0 & 0 \\ + * \hline + * \end{tabular} + * \f] + * + * In this modelization, different frames have to be considered. + * + * - \f$ {\cal F}_f \f$: the reference frame, also called world frame + * + * - \f$ {\cal F}_w \f$: the wrist frame located at the intersection of + * the last three rotations, with \f$ ^f{\bf M}_w = ^0{\bf M}_6 \f$ + * + * - \f$ {\cal F}_e \f$: the end-effector frame located at the interface of the + * two tool changers, with \f$^f{\bf M}_e = 0{\bf M}_7 \f$ + * + * - \f$ {\cal F}_c \f$: the camera or tool frame, with \f$^f{\bf M}_c = ^f{\bf + * M}_e \; ^e{\bf M}_c \f$ where \f$ ^e{\bf M}_c \f$ is the result of + * a calibration stage. We can also consider a custom tool TOOL_CUSTOM and + * set this tool during robot initialisation or using set_eMc(). + * + * - \f$ {\cal F}_s \f$: the force/torque sensor frame, with \f$d7=0.0666\f$. +*/ +class VISP_EXPORT vpViper650 : public vpViper +{ +public: +#ifdef VISP_HAVE_VIPER650_DATA + //! Files where constant transformation between end-effector and camera frame + //! are stored. + static const std::string CONST_EMC_MARLIN_F033C_WITHOUT_DISTORTION_FILENAME; + static const std::string CONST_EMC_MARLIN_F033C_WITH_DISTORTION_FILENAME; + static const std::string CONST_EMC_PTGREY_FLEA2_WITHOUT_DISTORTION_FILENAME; + static const std::string CONST_EMC_PTGREY_FLEA2_WITH_DISTORTION_FILENAME; + static const std::string CONST_EMC_SCHUNK_GRIPPER_WITHOUT_DISTORTION_FILENAME; + static const std::string CONST_EMC_SCHUNK_GRIPPER_WITH_DISTORTION_FILENAME; + static const std::string CONST_EMC_GENERIC_WITHOUT_DISTORTION_FILENAME; + static const std::string CONST_EMC_GENERIC_WITH_DISTORTION_FILENAME; + static const std::string CONST_CAMERA_FILENAME; +#endif + /*! + Name of the camera attached to the end-effector. + */ + static const char *const CONST_MARLIN_F033C_CAMERA_NAME; + static const char *const CONST_PTGREY_FLEA2_CAMERA_NAME; + static const char *const CONST_SCHUNK_GRIPPER_CAMERA_NAME; + static const char *const CONST_GENERIC_CAMERA_NAME; + + //! List of possible tools that can be attached to the robot end-effector. + typedef enum + { + TOOL_MARLIN_F033C_CAMERA, /*!< Marlin F033C camera. */ + TOOL_PTGREY_FLEA2_CAMERA, /*!< Point Grey Flea 2 camera. */ + TOOL_SCHUNK_GRIPPER_CAMERA, /*!< Camera attached to the Schunk gripper. */ + TOOL_GENERIC_CAMERA, /*!< A generic camera. */ + TOOL_CUSTOM /*!< A user defined tool. */ + } vpToolType; + + //! Default tool attached to the robot end effector + static const vpToolType defaultTool; + + vpViper650(); + + /** @name Inherited functionalities from vpViper650 */ + //@{ + void init(void); + void init(const std::string &camera_extrinsic_parameters); + void + init(vpViper650::vpToolType tool, + vpCameraParameters::vpCameraParametersProjType projModel = vpCameraParameters::perspectiveProjWithoutDistortion); + void init(vpViper650::vpToolType tool, const std::string &filename); + void init(vpViper650::vpToolType tool, const vpHomogeneousMatrix &eMc_); + + //! Get the current camera model projection type + vpCameraParameters::vpCameraParametersProjType getCameraParametersProjType() const { return projModel; } + + void getCameraParameters(vpCameraParameters &cam, const unsigned int &image_width, + const unsigned int &image_height) const; + void getCameraParameters(vpCameraParameters &cam, const vpImage &I) const; + void getCameraParameters(vpCameraParameters &cam, const vpImage &I) const; + + //! Get the current tool type + vpToolType getToolType() const { return tool_current; } + + void parseConfigFile(const std::string &filename); + //@} + +protected: + /** @name Protected Member Functions Inherited from vpViper650 */ + //@{ + //! Set the current tool type + void setToolType(vpViper650::vpToolType tool) { tool_current = tool; } + //@} + +protected: + //! Current tool in use + vpToolType tool_current; + // Used projection model + vpCameraParameters::vpCameraParametersProjType projModel; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpViper850.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpViper850.h new file mode 100644 index 00000000..4d468564 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpViper850.h @@ -0,0 +1,171 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Interface for the ADEPT Viper 850 robot. + */ + +/*! + * \file vpViper850.h + * + * Modelization of the ADEPT Viper 850 robot. + */ + +#ifndef vpViper850_h +#define vpViper850_h + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpViper850 + * + * \ingroup group_robot_real_arm group_robot_simu_arm + * + * \brief Modelization of the ADEPT Viper 850 robot. + * + * The model of the robot is the following: + * \image html model-viper.png Model of the Viper 850 robot. + * + * The non modified Denavit-Hartenberg representation of the robot is + * given in the table below, where \f$q_1^*, \ldots, q_6^*\f$ + * are the variable joint positions. + * + * \f[ + * \begin{tabular}{|c|c|c|c|c|} + * \hline + * Joint & $a_i$ & $d_i$ & $\alpha_i$ & $\theta_i$ \\ + * \hline + * 1 & $a_1$ & $d_1$ & $-\pi/2$ & $q_1^*$ \\ + * 2 & $a_2$ & 0 & 0 & $q_2^*$ \\ + * 3 & $a_3$ & 0 & $-\pi/2$ & $q_3^* - \pi$ \\ + * 4 & 0 & $d_4$ & $\pi/2$ & $q_4^*$ \\ + * 5 & 0 & 0 & $-\pi/2$ & $q_5^*$ \\ + * 6 & 0 & 0 & 0 & $q_6^*-\pi$ \\ + * 7 & 0 & $d_6$ & 0 & 0 \\ + * \hline + * \end{tabular} + * \f] + * + * In this modelization, different frames have to be considered. + * + * - \f$ {\cal F}_f \f$: the reference frame, also called world frame + * + * - \f$ {\cal F}_w \f$: the wrist frame located at the intersection of + * the last three rotations, with \f$ ^f{\bf M}_w = ^0{\bf M}_6 \f$ + * + * - \f$ {\cal F}_e \f$: the end-effector frame located at the interface of the + * two tool changers, with \f$^f{\bf M}_e = 0{\bf M}_7 \f$ + * + * - \f$ {\cal F}_c \f$: the camera or tool frame, with \f$^f{\bf M}_c = ^f{\bf + * M}_e \; ^e{\bf M}_c \f$ where \f$ ^e{\bf M}_c \f$ is the result of + * a calibration stage. We can also consider a custom tool TOOL_CUSTOM and + * set this during robot initialisation or using set_eMc(). + * + * - \f$ {\cal F}_s \f$: the force/torque sensor frame, with \f$d7=0.0666\f$. +*/ +class VISP_EXPORT vpViper850 : public vpViper +{ +public: +#ifdef VISP_HAVE_VIPER850_DATA + //! Files where constant transformation between end-effector and camera frame + //! are stored. + static const std::string CONST_EMC_MARLIN_F033C_WITHOUT_DISTORTION_FILENAME; + static const std::string CONST_EMC_MARLIN_F033C_WITH_DISTORTION_FILENAME; + static const std::string CONST_EMC_PTGREY_FLEA2_WITHOUT_DISTORTION_FILENAME; + static const std::string CONST_EMC_PTGREY_FLEA2_WITH_DISTORTION_FILENAME; + static const std::string CONST_EMC_SCHUNK_GRIPPER_WITHOUT_DISTORTION_FILENAME; + static const std::string CONST_EMC_SCHUNK_GRIPPER_WITH_DISTORTION_FILENAME; + static const std::string CONST_EMC_GENERIC_WITHOUT_DISTORTION_FILENAME; + static const std::string CONST_EMC_GENERIC_WITH_DISTORTION_FILENAME; + static const std::string CONST_CAMERA_FILENAME; +#endif + /*! + Name of the camera attached to the end-effector. + */ + static const char *const CONST_MARLIN_F033C_CAMERA_NAME; + static const char *const CONST_PTGREY_FLEA2_CAMERA_NAME; + static const char *const CONST_SCHUNK_GRIPPER_CAMERA_NAME; + static const char *const CONST_GENERIC_CAMERA_NAME; + + //! List of possible tools that can be attached to the robot end-effector. + typedef enum + { + TOOL_MARLIN_F033C_CAMERA, /*!< Marlin F033C camera. */ + TOOL_PTGREY_FLEA2_CAMERA, /*!< Point Grey Flea 2 camera. */ + TOOL_SCHUNK_GRIPPER_CAMERA, /*!< Camera attached to the Schunk gripper. */ + TOOL_GENERIC_CAMERA, /*!< A generic camera. */ + TOOL_CUSTOM /*!< A user defined tool. */ + } vpToolType; + + //! Default tool attached to the robot end effector + static const vpToolType defaultTool; + + vpViper850(); + + /** @name Inherited functionalities from vpViper850 */ + //@{ + void init(void); + void init(const std::string &camera_extrinsic_parameters); + void + init(vpViper850::vpToolType tool, + vpCameraParameters::vpCameraParametersProjType projModel = vpCameraParameters::perspectiveProjWithoutDistortion); + void init(vpViper850::vpToolType tool, const std::string &filename); + void init(vpViper850::vpToolType tool, const vpHomogeneousMatrix &eMc_); + + //! Get the current camera model projection type + vpCameraParameters::vpCameraParametersProjType getCameraParametersProjType() const { return projModel; } + + void getCameraParameters(vpCameraParameters &cam, const unsigned int &image_width, + const unsigned int &image_height) const; + void getCameraParameters(vpCameraParameters &cam, const vpImage &I) const; + void getCameraParameters(vpCameraParameters &cam, const vpImage &I) const; + + //! Get the current tool type + vpToolType getToolType() const { return tool_current; } + + void parseConfigFile(const std::string &filename); + //@} + +protected: + /** @name Protected Member Functions Inherited from vpViper650 */ + //@{ + //! Set the current tool type + void setToolType(vpViper850::vpToolType tool) { tool_current = tool; } + //@} + +protected: + //! Current tool in use + vpToolType tool_current; + // Used projection model + vpCameraParameters::vpCameraParametersProjType projModel; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpVirtuose.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpVirtuose.h new file mode 100644 index 00000000..ab443a0e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpVirtuose.h @@ -0,0 +1,232 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: Class which enables to project an image in the 3D space + * and get the view of a virtual camera. + */ + +/*! + \file vpVirtuose.h + \brief Wrapper over Haption Virtuose SDK to control haptic devices. +*/ + +#ifndef _vpVirtuose_h_ +#define _vpVirtuose_h_ + +#include +#include +#include +#include + +#ifdef VISP_HAVE_VIRTUOSE + +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpVirtuose + \ingroup group_robot_haptic + + This class was tested with Haption (http://www.haption.com) Virtuose 6D haptic device. + + The class vpVirtuose allows to work with the original Virtuose API inside + ViSP. The Virtuose API supports the following devices: + - Virtuose 6D35-45 + - Virtuose 3D35-40 + - Virtuose 3D10-20 + - Virtuose Desktop + - Virtuose Inca + + Not all Virtuose API function are implemented in the class. + Original Virtuose API functions need to be called with a VirtContext object, + provided by the function getHandler(). + + The Virtuose library implements different control modes that could be set + using setCommandType(). The choice of the control mode depends on the + application. The following is the description of the main control modes as + described in the Virtuose API documentation. + + 1. Force/position control (impedance mode): the application sends forces and + torques to the device and reads the position and speed of the end-effector + frame. + 2. Position/force control (admittance mode): this advanced control mode + allows direct coupling with virtual objects; in that case, the application + sends the position and speed of the center of the object to the device, and + reads the forces and torques to be applied to the object for dynamic + integration. Stiffness and damping are calculated by the embedded software, + knowing the mass and inertia of the object, in order to ensure control stability. + 3. Position/force with virtual guides: this is the same as above, with + addition of virtual guides (e.g. fixed translation, fixed rotation, etc.). + + The Virtuose library defines the following reference frames: + 1. The environment frame, corresponding to the origin of the virtual scene; + it is specified by the software application independently of the Virtuose API. + 2. The observation frame, corresponding generally to the position of the + camera; it is defined with respect to environment frame. This frame location + could be set using setObservationFrame(). + 3. The base frame, representing the center of the haptic device; it is + defined with respect to the observation frame. This frame location could be + set using setBaseFrame(). + 4. The tool frame corresponds to the base of the tool fixed at the end of + the haptic device, and is defined with respect to the environment frame. + 5. The end-effector (avatar) frame corresponds to the position of the user + hand on the device, taking into account the geometry of the tool, and is + defined with respect to tool frame. + + The position of the following frames can be defined only once using the API: + base frame (with respect to the observation frame) thanks to setBaseFrame() + and end-effector frame (with respect to the tool frame). + + The position of the observation frame (with respect to the environment + frame) can be modified dynamically using setObservationFrame(). + + The position of the tool frame (with respect to the environment frame) + cannot be modified. + + All values used in the Virtuose API are expressed in physical units using + metric conventions: + - Durations in seconds (s) + - Dimensions in meters (m) + - Angles in radians (rad) + - Linear velocities in meters per second (m.s -1 ) + - Angular velocities in radians per second (rad.s -1 ) + - Forces in Newtons (N) + - Torques in Newton-meters (N.m) + - Masses in kilograms (kg) + - Inertia components in kg.m2 + + The following sample code shows how to connect to the haptic device to get + its current joint position: + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpVirtuose virtuose; + virtuose.init(); + vpColVector q = virtuose.getArticularPosition(); + std::cout << "Joint position: " << q.t() << std::endl; + } + \endcode +*/ +class VISP_EXPORT vpVirtuose +{ +public: + vpVirtuose(); + virtual ~vpVirtuose(); + + void addForce(vpColVector &force); + void close(); + void enableForceFeedback(int enable); + + vpColVector getArticularPosition() const; + vpColVector getArticularVelocity() const; + vpPoseVector getAvatarPosition() const; + vpPoseVector getBaseFrame() const; + VirtCommandType getCommandType() const; + bool getDeadMan() const; + bool getEmergencyStop() const; + unsigned int getJointsNumber() const; + vpColVector getForce() const; + VirtContext getHandler(); + vpPoseVector getObservationFrame() const; + vpPoseVector getPhysicalPosition() const; + vpColVector getPhysicalVelocity() const; + vpPoseVector getPosition() const; + bool getPower() const; + vpColVector getVelocity() const; + + void init(); + + void setArticularForce(const vpColVector &articularForce); + void setArticularPosition(const vpColVector &articularPosition); + void setArticularVelocity(const vpColVector &articularVelocity); + void setBaseFrame(const vpPoseVector &position); + void setCommandType(const VirtCommandType &type); + void setForce(const vpColVector &force); + void setForceFactor(const float &forceFactor); + void setIndexingMode(const VirtIndexingType &type); + void setIpAddressAndPort(const std::string &ip, int port); + + void setObservationFrame(const vpPoseVector &position); + void setPeriodicFunction(VirtPeriodicFunction CallBackVirt); + void setPosition(vpPoseVector &position); + void setPowerOff(); + void setPowerOn(); + void setSaturation(const float &forceLimit, const float &torqueLimit); + void setTimeStep(const float &timeStep); + void setVelocity(vpColVector &velocity); + void setVelocityFactor(const float &velocityFactor); + /*! + * Enable/disable verbose mode. + * \param mode : true to enable, false to disable verbose. + */ + void setVerbose(bool mode) { m_verbose = mode; } + + void startPeriodicFunction(); + void stopPeriodicFunction(); + +#ifdef VISP_BUILD_DEPRECATED_FUNCTIONS + /*! + @name Deprecated functions + */ + //@{ + /*! + * \deprecated You should rather use setIpAddressAndPort() that is more explicit. + * + * Set haptic device ip address and port. Default value is + * "localhost#5000". + * + * \sa setIpAddressAndPort() + */ + VP_DEPRECATED inline void setIpAddress(const std::string &ip_port) { m_ip_port = ip_port; } + //@} +#endif + +protected: + VirtContext m_virtContext; + std::string m_ip_port; + bool m_verbose; + int m_apiMajorVersion; + int m_apiMinorVersion; + int m_ctrlMajorVersion; + int m_ctrlMinorVersion; + VirtCommandType m_typeCommand; + VirtIndexingType m_indexType; + bool m_is_init; + float m_period; + unsigned int m_njoints; +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpWireFrameSimulator.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpWireFrameSimulator.h new file mode 100644 index 00000000..b2406fa3 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpWireFrameSimulator.h @@ -0,0 +1,618 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Wire frame simulator + */ + +/*! + \file vpWireFrameSimulator.h + \brief Implementation of a wire frame simulator. +*/ + +#ifndef vpWireFrameSimulator_HH +#define vpWireFrameSimulator_HH + +#include // std::fabs +#include +#include // numeric_limits +#include +#include +#include + +#include +#include +#include +#include +#ifdef VISP_BUILD_DEPRECATED_FUNCTIONS +#include +#endif +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpWireFrameSimulator + + \ingroup group_robot_simu_gantry group_robot_simu_arm + + \brief Implementation of a wire frame simulator. Compared to the vpSimulator + class, it does not require third party libraries to be used. + + The simulator uses several frames to display the scene in the image. There + are: + + - The world frame : This is a fixed frame used to compute the position of + the other frames against each other. By default this frame is positionned at + the same location as the initial object frame position. + + - The object frame : It is the frame linked to the object. + + - The camera frame : It is the frame linked to the main camera. + + - The external camera frame : It is the frame which corresponds to one + external point of view. + + The most used homogeneous matrices which describes the displacement between + two frames are : + + - fMo which is the displacement between the world frame and the object + frame. + + - cMo which is the displacement between the main camera frame and the object + frame. + + - cextMf which is the displacement between one external camera frame and the + world frame. + + The following picture summarize all the previous information. + + \image html vpWireFrameSimulator.jpeg + \image latex vpWireFrameSimulator.ps + + The simulator uses .bnd or .wrl files as 3D scene descriptors. Several + scenes can be found in the data folder which is in the directory where you + build ViSP. + + You can move the main external view while clicking in the image. The left + click enables to turn, the middle button enables to zoom and the left to + translate along x and y. + + The simulator is able to take into account to camera parameters. You can set + the internal and external cameras parameters thanks to a vpCameraParameters. + + The following example shows how it is easy to use. + + \code + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpWireFrameSimulator sim; + + vpImage Iint(480, 640, vpRGBa(255)); + vpImage Iext(480, 640, vpRGBa(255)); + + //Set the type of scene to use + sim.initScene(vpWireFrameSimulator::PLATE, vpWireFrameSimulator::D_STANDARD); + + //Set the initial pose of the camera + sim.setCameraPositionRelObj(vpHomogeneousMatrix(0, 0, 0.5, vpMath::rad(0), vpMath::rad(10), 0)); + //Set the desired pose of the camera (for the internal view) + sim.setDesiredCameraPosition(vpHomogeneousMatrix(0.0, 0, 0.5, 0, 0, 0)); + //Set the pose of the reference frame (for the external view) + sim.setExternalCameraPosition(vpHomogeneousMatrix(0.1, 0, 0.2, 0, 0, 0)); + + //Set the camera parameters + vpCameraParameters camera(1000, 1000, 320, 240); + sim.setInternalCameraParameters(camera); + sim.setExternalCameraParameters(camera); + + //Get the internal view + sim.getInternalImage(Iint); + + //Get the external view + sim.getExternalImage(Iext); + + return 0; + } + \endcode + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in using this class to achieve a visual + servoing simulation, you may have a look at:
+ + - \ref tutorial-ibvs. +*/ + +class VISP_EXPORT vpWireFrameSimulator +{ +public: + /*! + Type of scene used to display the object at the current position. + */ + typedef enum + { + THREE_PTS, //!< A 40cm by 40cm plate with 3 points at coordinates (0,0,0), + //!< (0.1,0,0), (0,0.1,0). Each point is represented by a + //!< circle with 2cm radius. + CUBE, //!< A 12.5cm size cube. + PLATE, //!< A 40cm by 40cm plate with 4 points at coordinates + //!< (-0.1,-0.1,0), (0.1,-0.1,0), (0.1,0.1,0), (0.1,0.1,0). Each + //!< point is represented by a circle with 2cm radius. + SMALL_PLATE, //!< 4 points at coordinates (-0.03,-0.03,0), (0.03,-0.03,0), + //!< (0.03,0.03,0), (0.03,0.03,0). Each point is represented + //!< by a circle with 1cm radius. + RECTANGLE, //!< A 40cm by 40cm plate with 4 points at coordinates + //!< (-0.07,-0.05,0), (0.07,0.05,0), (0.07,-0.05,0), + //!< (-0.07,-0.05,0). Each point is represented by a circle + //!< with 2cm radius. + SQUARE_10CM, //!< A 40cm by 40cm plate with 4 points at coordinates + //!< (-0.05,0.05,0), (0.05,0.05,0), (0.05,-0.05,0), + //!< (-0.05,-0.05,0). Each point is represented by a circle + //!< with 2cm radius. + DIAMOND, //!< A 40cm by 40cm plate with 4 points at coordinates + //!< (0,-0.1,0), (0.1,0,0), (0,0.1,0), (-0.1,0,0). Each point is + //!< represented by a circle with 2cm radius. + TRAPEZOID, //!< A 40cm by 40cm plate with 4 points at coordinates + //!< (-0.025,-0.05,0), (-0.075,0.05,0), (0.075,0.05,0), + //!< (0.025,-0.05,0). Each point is represented by a circle + //!< with 2cm radius. + THREE_LINES, //!< Three parallel lines with equation y=-5, y=0, y=5. + ROAD, //!< Three parallel lines representing a road. + TIRE, //!< A tire represented by 2 circles with radius 10cm and 15cm. + PIPE, //!< A pipe represented by a cylinder of 25 cm length and 15cm + //!< radius. + CIRCLE, //!< A 10cm radius circle. + SPHERE, //!< A 15cm radius sphere. + CYLINDER, //!< A cylinder of 80cm length and 10cm radius. + PLAN, //!< A plane represented by a 56cm by 56cm plate with a grid of 49 + //!< squares inside. + POINT_CLOUD, //!< A plate with 8 points at coordinates (0.05,0,0), + //!< (0.15,0.05,0), (0.2,0.2,0), (-0.05,0.2,0), + //!< (-0.15,-0.1,0), (-0.1,-0.1,0), (-0.05,0.05,0) and + //!< (0.5,0,0). ach point is represented by a circle with 2cm + //!< radius. + } vpSceneObject; + + /*! + Type of scene used to display the object at the desired pose (in the + internal view). + + - D_STANDARD will use the vpSceneObject used to be the object at the + current position. + - D_OUTIL will display a tool which is attached to the camera. + */ + typedef enum + { + D_STANDARD, //!< The object displayed at the desired position is the same + //!< than the scene object defined in vpSceneObject. + D_CIRCLE, //!< The object displayed at the desired position is a circle. + D_TOOL //!< A cylindrical tool is attached to the camera. + } vpSceneDesiredObject; + + typedef enum { CT_LINE, CT_POINT } vpCameraTrajectoryDisplayType; + +protected: + Bound_scene scene; + Bound_scene desiredScene; + Bound_scene camera; + std::list objectImage; + + vpHomogeneousMatrix fMo; + vpHomogeneousMatrix fMc; + vpHomogeneousMatrix camMf; + vpHomogeneousMatrix refMo; + vpHomogeneousMatrix cMo; + vpHomogeneousMatrix cdMo; + + vpSceneObject object; + vpSceneDesiredObject desiredObject; + + vpColor camColor; + vpColor camTrajColor; + vpColor curColor; + vpColor desColor; + + bool sceneInitialized; + + bool displayCameraTrajectory; + std::list cameraTrajectory; + std::list poseList; + std::list fMoList; + unsigned int nbrPtLimit; + + vpImagePoint old_iPr; + vpImagePoint old_iPz; + vpImagePoint old_iPt; + bool blockedr; + bool blockedz; + bool blockedt; + bool blocked; + + vpHomogeneousMatrix camMf2; + vpHomogeneousMatrix f2Mf; + + double px_int; + double py_int; + double px_ext; + double py_ext; + + bool displayObject; + bool displayDesiredObject; + bool displayCamera; + bool displayImageSimulator; + + float cameraFactor; + + vpCameraTrajectoryDisplayType camTrajType; + + bool extCamChanged; + + vpHomogeneousMatrix rotz; + + unsigned int thickness_; + +private: + std::string scene_dir; + +public: + vpWireFrameSimulator(); + virtual ~vpWireFrameSimulator(); + + /** @name Inherited functionalities from vpWireFrameSimulator */ + //@{ + /*! + Delete the history of the main camera position which are displayed in + the external views. + */ + inline void deleteCameraPositionHistory() + { + cameraTrajectory.clear(); + poseList.clear(); + fMoList.clear(); + } + + void displayTrajectory(const vpImage &I, const std::list &list_cMo, + const std::list &list_fMo, const vpHomogeneousMatrix &camMf); + void displayTrajectory(const vpImage &I, const std::list &list_cMo, + const std::list &list_fMo, const vpHomogeneousMatrix &camMf); + + /*! + Get the parameters of the virtual external camera. + + \param I : The image used to display the view of the camera. + + \return It returns the camera parameters. + */ + vpCameraParameters getExternalCameraParameters(const vpImage &I) const + { + // if(px_ext != 1 && py_ext != 1) + // we assume px_ext and py_ext > 0 + if ((std::fabs(px_ext - 1.) > vpMath::maximum(px_ext, 1.) * std::numeric_limits::epsilon()) && + (std::fabs(py_ext - 1) > vpMath::maximum(py_ext, 1.) * std::numeric_limits::epsilon())) + return vpCameraParameters(px_ext, py_ext, I.getWidth() / 2, I.getHeight() / 2); + else { + unsigned int size = vpMath::minimum(I.getWidth(), I.getHeight()) / 2; + return vpCameraParameters(size, size, I.getWidth() / 2, I.getHeight() / 2); + } + } + /*! + Get the parameters of the virtual external camera. + + \param I : The image used to display the view of the camera. + + \return It returns the camera parameters. + */ + vpCameraParameters getExternalCameraParameters(const vpImage &I) const + { + // if(px_ext != 1 && py_ext != 1) + // we assume px_ext and py_ext > 0 + if ((std::fabs(px_ext - 1.) > vpMath::maximum(px_ext, 1.) * std::numeric_limits::epsilon()) && + (std::fabs(py_ext - 1) > vpMath::maximum(py_ext, 1.) * std::numeric_limits::epsilon())) + return vpCameraParameters(px_ext, py_ext, I.getWidth() / 2, I.getHeight() / 2); + else { + unsigned int size = vpMath::minimum(I.getWidth(), I.getHeight()) / 2; + return vpCameraParameters(size, size, I.getWidth() / 2, I.getHeight() / 2); + } + } + /*! + Get the main external camera's position relative to the the world + reference frame. + + \return the main external camera position relative to the the world + reference frame. + */ + inline vpHomogeneousMatrix getExternalCameraPosition() const { return rotz * camMf; } + + void getExternalImage(vpImage &I); + void getExternalImage(vpImage &I, const vpHomogeneousMatrix &camMf); + void getExternalImage(vpImage &I); + void getExternalImage(vpImage &I, const vpHomogeneousMatrix &camMf); + + /*! + Get the parameters of the virtual internal camera. + + \param I : The image used to display the view of the camera. + + \return It returns the camera parameters. + */ + vpCameraParameters getInternalCameraParameters(const vpImage &I) const + { + // if(px_int != 1 && py_int != 1) + // we assume px_int and py_int > 0 + if ((std::fabs(px_int - 1.) > vpMath::maximum(px_int, 1.) * std::numeric_limits::epsilon()) && + (std::fabs(py_int - 1) > vpMath::maximum(py_int, 1.) * std::numeric_limits::epsilon())) + return vpCameraParameters(px_int, py_int, I.getWidth() / 2, I.getHeight() / 2); + else { + unsigned int size = vpMath::minimum(I.getWidth(), I.getHeight()) / 2; + return vpCameraParameters(size, size, I.getWidth() / 2, I.getHeight() / 2); + } + } + /*! + Get the parameters of the virtual internal camera. + + \param I : The image used to display the view of the camera. + + \return It returns the camera parameters. + */ + vpCameraParameters getInternalCameraParameters(const vpImage &I) const + { + // if(px_int != 1 && py_int != 1) + // we assume px_int and py_int > 0 + if ((std::fabs(px_int - 1.) > vpMath::maximum(px_int, 1.) * std::numeric_limits::epsilon()) && + (std::fabs(py_int - 1) > vpMath::maximum(py_int, 1.) * std::numeric_limits::epsilon())) + return vpCameraParameters(px_int, py_int, I.getWidth() / 2, I.getHeight() / 2); + else { + unsigned int size = vpMath::minimum(I.getWidth(), I.getHeight()) / 2; + return vpCameraParameters(size, size, I.getWidth() / 2, I.getHeight() / 2); + } + } + + void getInternalImage(vpImage &I); + void getInternalImage(vpImage &I); + + /*! + Get the pose between the object and the camera. + + \return The pose between between the object and the camera. + */ + vpHomogeneousMatrix get_cMo() const { return rotz * cMo; } + + /*! + Get the homogeneous matrices cMo stored to display the camera + trajectory. + + \param cMo_history : The list of the homogeneous matrices cMo. + */ + void get_cMo_History(std::list &cMo_history) + { + cMo_history.clear(); + for (std::list::const_iterator it = poseList.begin(); it != poseList.end(); ++it) { + cMo_history.push_back(rotz * (*it)); + } + } + + /*! + Get the pose between the object and the fixed world frame. + + \return The pose between the object and the fixed world frame. + */ + vpHomogeneousMatrix get_fMo() const { return fMo; } + + /*! + Get the homogeneous matrices fMo stored to display the camera + trajectory. + + \param fMo_history : The list of the homogeneous matrices fMo. + */ + void get_fMo_History(std::list &fMo_history) { fMo_history = fMoList; } + + void initScene(const vpSceneObject &obj, const vpSceneDesiredObject &desiredObject); + void initScene(const char *obj, const char *desiredObject); + void initScene(const vpSceneObject &obj); + void initScene(const char *obj); + + void initScene(const vpSceneObject &obj, const vpSceneDesiredObject &desiredObject, + const std::list &imObj); + void initScene(const char *obj, const char *desiredObject, const std::list &imObj); + void initScene(const vpSceneObject &obj, const std::list &imObj); + void initScene(const char *obj, const std::list &imObj); + + /*! + Set the color used to display the camera in the external view. + + \param col : The desired color. + */ + void setCameraColor(const vpColor &col) { camColor = col; } + /*! + Set the transformation between the camera frame and the object frame. + + \param cMo_ : The pose of the object in the camera frame. + */ + void setCameraPositionRelObj(const vpHomogeneousMatrix &cMo_) + { + this->cMo = rotz * cMo_; + fMc = fMo * this->cMo.inverse(); + } + + /*! + Set the position of the the world reference frame relative to the + camera. + + \param fMc_ : The pose of the camera. + */ + void setCameraPositionRelWorld(const vpHomogeneousMatrix &fMc_) + { + this->fMc = fMc_ * rotz; + cMo = this->fMc.inverse() * fMo; + } + + /*! + Set the parameter which enables to choose the size of the main camera in + the external camera views. By default this parameter is set to 1. + + \param factor : The ration for the camera size. + */ + inline void setCameraSizeFactor(float factor) { cameraFactor = factor; } + + /*! + Set the color used to display the camera trajectory in the external + view. + + \param col : The desired color. + */ + void setCameraTrajectoryColor(const vpColor &col) { camTrajColor = col; } + + /*! + Set the way to display the history of the main camera trajectory in the + main external view. The choice is given between displaying lines and + points. + + \param camTraj_type : The chosen way to display the camera trajectory. + */ + inline void setCameraTrajectoryDisplayType(const vpCameraTrajectoryDisplayType &camTraj_type) + { + this->camTrajType = camTraj_type; + } + + /*! + Set the color used to display the object at the current position. + + \param col : The desired color. + */ + void setCurrentViewColor(const vpColor &col) { curColor = col; } + /*! + Set the transformation between the desired position of the camera and the object. + + \param cdMo_ : The position of the object in the desired camera frame + */ + void setDesiredCameraPosition(const vpHomogeneousMatrix &cdMo_) { this->cdMo = rotz * cdMo_; } + /*! + Set the color used to display the object at the desired position. + + \param col : The desired color. + */ + void setDesiredViewColor(const vpColor &col) { desColor = col; } + /*! + Enable or disable the displaying of the camera trajectory in the main + external camera view. + + By default the trajectory is displayed. + + \param do_display : Set to true to display the camera trajectory. + */ + void setDisplayCameraTrajectory(const bool &do_display) { this->displayCameraTrajectory = do_display; } + + /*! + Set the internal camera parameters. + + \param cam : The desired camera parameters. + */ + inline void setExternalCameraParameters(const vpCameraParameters &cam) + { + px_ext = cam.get_px(); + py_ext = cam.get_py(); + } + /*! + Set the external camera point of view. + + \param cam_Mf : The pose of the external camera relative to the world + reference frame. + */ + void setExternalCameraPosition(const vpHomogeneousMatrix &cam_Mf) + { + this->camMf = rotz * cam_Mf; + vpTranslationVector T; + this->camMf.extract(T); + this->camMf2.buildFrom(0, 0, T[2], 0, 0, 0); + f2Mf = camMf2.inverse() * this->camMf; + extCamChanged = true; + } + + /*! + Specify the thickness of the graphics drawings. + */ + void setGraphicsThickness(unsigned int thickness) { this->thickness_ = thickness; } + + /*! + Set the internal camera parameters. + + \param cam : The desired camera parameters. + */ + inline void setInternalCameraParameters(const vpCameraParameters &cam) + { + px_int = cam.get_px(); + py_int = cam.get_py(); + } + + /*! + Set the maximum number of main camera's positions which are stored. + Those position can be displayed in the external camera field of view. By + default this parameter is set to 1000. + + \param nbPt : The desired number of position which are saved. + */ + inline void setNbPtTrajectory(unsigned int nbPt) { nbrPtLimit = nbPt; } + + /*! + Set the pose between the object and the fixed world frame. + + \param fMo_ : The pose between the object and the fixed world frame. + */ + void set_fMo(const vpHomogeneousMatrix &fMo_) { this->fMo = fMo_; /*this->cMo = fMc.inverse()*fMo;*/ } + //@} + +protected: + /** @name Protected Member Functions Inherited from vpWireFrameSimulator */ + //@{ + void display_scene(Matrix mat, Bound_scene &sc, const vpImage &I, const vpColor &color); + void display_scene(Matrix mat, Bound_scene &sc, const vpImage &I, const vpColor &color); + vpHomogeneousMatrix navigation(const vpImage &I, bool &changed); + vpHomogeneousMatrix navigation(const vpImage &I, bool &changed); + vpImagePoint projectCameraTrajectory(const vpImage &I, const vpHomogeneousMatrix &cMo, + const vpHomogeneousMatrix &fMo); + vpImagePoint projectCameraTrajectory(const vpImage &I, const vpHomogeneousMatrix &cMo, + const vpHomogeneousMatrix &fMo); + vpImagePoint projectCameraTrajectory(const vpImage &I, const vpHomogeneousMatrix &cMo, + const vpHomogeneousMatrix &fMo, const vpHomogeneousMatrix &cMf); + vpImagePoint projectCameraTrajectory(const vpImage &I, const vpHomogeneousMatrix &cMo, + const vpHomogeneousMatrix &fMo, const vpHomogeneousMatrix &cMf); + //@} +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpWireFrameSimulatorTypes.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpWireFrameSimulatorTypes.h new file mode 100644 index 00000000..e049114b --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/robot/vpWireFrameSimulatorTypes.h @@ -0,0 +1,130 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Wire frame simulator + */ + +#ifndef vpWireFrameSimulatorTypes_h +#define vpWireFrameSimulatorTypes_h + +#include + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +BEGIN_VISP_NAMESPACE +typedef unsigned short Index; +typedef char Type; +typedef float Matrix[4][4]; + +/* + * MATRIX + * ______ + * + * Matrice homogene ou non. + * | Rotation | 0 | + * Matrice non homogene = | 3x3 | 0 | + * |-------------| 0 | + * | Translation | 1 | + */ +typedef float Matrix[4][4]; + +#define DEFAULT_VSIZE 4 + +/* + * Vertex_list : + * Pour optimiser l'allocation et la liberation memoire d'une liste de + * sommets: si (nbr > DEFAULT_VSIZE) | alors ptr est alloue et libere + * dynamiquement | sinon ptr = tbl fsi; + */ +typedef struct +{ + Index nbr; /* nombre de sommets */ + Index *ptr; /* liste dynamique */ + Index tbl[DEFAULT_VSIZE]; +} Vertex_list; + +typedef struct +{ + unsigned is_polygonal : 1; /* face polygonale */ + unsigned is_visible : 1; /* face affichable */ +#ifdef face_edge + Edge_list edge; /* liste d'aretes */ +#endif // face_edge + Vertex_list vertex; /* liste de sommets */ +#ifdef face_normal + Vector normal; /* vecteur normal */ +#endif // face_normal +} Face; + +typedef struct +{ + Index nbr; /* nombre de faces */ + Face *ptr; /* liste dynamique */ +} Face_list; + +typedef struct +{ + float x, y, z; +} Point3f; + +typedef struct +{ + Index nbr; /* nombre de points */ + Point3f *ptr; /* liste dynamique */ +} Point3f_list; + +typedef struct +{ + unsigned is_display : 1; /* surface affichable */ + unsigned is_polygonal : 1; /* surface polyedrique */ + Type type; /* type de la primitive */ +#ifdef face_edge + Edge_list edge; /* liste d'aretes */ +#endif // face_edge + Face_list face; /* liste de faces */ + Point3f_list point; /* points aux sommets */ +#ifdef face_normal + Vector_list normal; /* normales aux sommets */ +#endif // face_normal +} Bound; + +typedef struct +{ + Index nbr; /* nombre de surfaces */ + Bound *ptr; /* liste dynamique */ +} Bound_list; + +typedef struct +{ + char *name; /* nom de la scene */ + Bound_list bound; /* liste de surfaces */ +} Bound_scene; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vp1394CMUGrabber.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vp1394CMUGrabber.h new file mode 100644 index 00000000..2e45c350 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vp1394CMUGrabber.h @@ -0,0 +1,351 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Firewire cameras video capture based on CMU 1394 Digital Camera SDK. + */ + +/*! + \file vp1394CMUGrabber.h + \brief Firewire cameras video capture based on CMU 1394 Digital Camera SDK. +*/ + +#ifndef VP_1394_CMU_GRABBER_H +#define VP_1394_CMU_GRABBER_H + +#include + +#ifdef VISP_HAVE_CMU1394 + +// Include WinSock2.h before windows.h to ensure that winsock.h is not +// included by windows.h since winsock.h and winsock2.h are incompatible +#include <1394Camera.h> // CMU library + +// Mute warning with clang-cl +// warning : non-portable path to file ''; specified path differs in case from file name on disk [-Wnonportable-system-include-path] +// warning : non-portable path to file ''; specified path differs in case from file name on disk [-Wnonportable-system-include-path] +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wnonportable-system-include-path" +#endif + +#include +#include + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vp1394CMUGrabber + + \ingroup group_sensor_camera + + \brief Firewire cameras video capture based on CMU 1394 Digital Camera SDK. + + Thus to be enabled, this class needs the optional CMU 1394 3rd party. +Installation instruction are provided here https://visp.inria.fr/3rd_cmu1394. + + This block is based on the CMU 1394 Digital Camera SDK. The CMU 1394 +Digital Camera Driver must be installed (go to +http://www.cs.cmu.edu/~iwan/1394/index.html to download it). + - Parameters: + - Camera index (0, 1, 2, ... or 10). First camera has index 0. + - Image format + - Frame rate. Real frame rate depends on device capacities. + - Control : shutter speed and gain can be internally set, but it is +possible to set manually them. + * Exposure time register value. Real exposure time depends on device +capacities. + * Gain register value. Real gain depends on device capacities. + + This first example available in tutorial-grabber-CMU1394.cpp shows how to +grab and display images from a firewire camera under Windows. + + \include tutorial-grabber-CMU1394.cpp + + This other example shows how to consider more than one firewire camera, and +how to grab and display images from the first camera found on the bus. + + \code + #include + + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #if defined(VISP_HAVE_CMU1394) + std::cout << "ViSP Image acquisition example" << std::endl; + + vpImage I; + vp1394CMUGrabber g; + + if( g.getNumberOfConnectedCameras() > 1 ) + std::cout << "There are " << g.getNumberOfConnectedCameras() << " connected cameras." << std::endl; + if( g.getNumberOfConnectedCameras() == 1 ) + std::cout << "There is " << g.getNumberOfConnectedCameras() << " connected camera." << std::endl; + else + std::cout << "There is no connected camera." << std::endl; + + // Setting camera parameters manually + g.selectCamera(0); + g.setGain(0); + g.setShutter(2000); + g.setFramerate(3); // 15 FPS + g.setVideoMode(0, 5); // 640x480 - MONO + + g.acquire(I); + + // Display camera description + g.displayCameraDescription(0); + g.displayCameraModel(); + std::cout << "Height: " << g.getHeight() << " Width: " << g.getWidth() << std::endl; + + vpDisplayOpenCV d(I); + vpDisplay::display(I); + + for(;;) + { + g.acquire(I); + vpDisplay::display(I); + vpDisplay::flush(I); + if (vpDisplay::getClick(I, false)) // a click to exit + break; + } + + g.close(); + #endif + std::cout << "ViSP exiting..." < &I); + + // Acquire one frame in a color image. + void acquire(vpImage &I); + + // Stop the acquisition. + void close(); + + // Display information about the camera on the standard output. + void displayCameraDescription(int cam_id); + + // Display camera model on the standard output. Call it after open the + // grabber. + void displayCameraModel(); + + // Get the video framerate + int getFramerate(); + + // Get the gain min and max values. + void getGainMinMax(unsigned short &min, unsigned short &max); + + // Get the number of connected cameras. + int getNumberOfConnectedCameras() const; + + // Get the shutter min and max values. + void getShutterMinMax(unsigned short &min, unsigned short &max); + + //! Get the video color coding format. + vpColorCodingType getVideoColorCoding() const + { + vpColorCodingType color = vp1394CMUGrabber::UNKNOWN; + if (_format == 0) { + switch (_mode) { + case 0: + color = vp1394CMUGrabber::YUV444; + break; + case 1: + color = vp1394CMUGrabber::YUV422; + break; + case 2: + color = vp1394CMUGrabber::YUV411; + break; + case 3: + color = vp1394CMUGrabber::YUV422; + break; + case 4: + color = vp1394CMUGrabber::RGB8; + break; + case 5: + color = vp1394CMUGrabber::MONO8; + break; + case 6: + color = vp1394CMUGrabber::MONO16; + break; + } + } + else if (_format == 1) { + switch (_mode) { + case 0: + color = vp1394CMUGrabber::YUV422; + break; + case 1: + color = vp1394CMUGrabber::RGB8; + break; + case 2: + color = vp1394CMUGrabber::MONO8; + break; + case 3: + color = vp1394CMUGrabber::YUV422; + break; + case 4: + color = vp1394CMUGrabber::RGB8; + break; + case 5: + color = vp1394CMUGrabber::MONO8; + break; + case 6: + color = vp1394CMUGrabber::MONO16; + break; + case 7: + color = vp1394CMUGrabber::MONO16; + break; + } + } + else if (_format == 2) { + switch (_mode) { + case 0: + color = vp1394CMUGrabber::YUV422; + break; + case 1: + color = vp1394CMUGrabber::RGB8; + break; + case 2: + color = vp1394CMUGrabber::MONO8; + break; + case 3: + color = vp1394CMUGrabber::YUV422; + break; + case 4: + color = vp1394CMUGrabber::RGB8; + break; + case 5: + color = vp1394CMUGrabber::MONO8; + break; + case 6: + color = vp1394CMUGrabber::MONO16; + break; + case 7: + color = vp1394CMUGrabber::MONO16; + break; + } + } + + return color; + } + + // Initialization of the grabber using a greyscale image. + void open(vpImage &I); + + // Initialization of the grabber using a color image. + void open(vpImage &I); + + vp1394CMUGrabber &operator>>(vpImage &I); + vp1394CMUGrabber &operator>>(vpImage &I); + + // Select the camera on the bus. Call it before open the grabber. + void selectCamera(int cam_id); + + // Enable auto gain + void setAutoGain(); + + // Enable auto shutter + void setAutoShutter(); + + // Set the gain and the shutter values. Call it before open the grabber + void setControl(unsigned short gain, unsigned short shutter); + + // Set the frame rate. Call it before open the grabber. + void setFramerate(unsigned long fps); + + // Set the shutter value. Call it before open the grabber + void setShutter(unsigned short shutter); + + // Set the gain value. Call it before open the grabber + void setGain(unsigned short gain); + + // Set the video format and mode. Call it before open the grabber. + void setVideoMode(unsigned long format, unsigned long mode); + +private: + void initCamera(); +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vp1394TwoGrabber.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vp1394TwoGrabber.h new file mode 100644 index 00000000..77ce0aec --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vp1394TwoGrabber.h @@ -0,0 +1,471 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Firewire cameras video capture. + */ + +/*! + \file vp1394TwoGrabber.h + \brief class for firewire cameras video capture using libdc1394-2.x api + + \warning This class is a wrapper over libdc1394-2 optional 3rd party. + + Thus to be enabled, this class needs the optional libdc1394-2 3rd party. + Installation instruction are provided here https://visp.inria.fr/3rd_dc1394. + + vp1394TwoGrabber was tested with the following cameras: + - Marlin F033C, F131B + - PointGrey Dragonfly 2, Flea 2 + - APPLE iSight extern webcam + +*/ + +#ifndef vp1394TwoGrabber_h +#define vp1394TwoGrabber_h + +#include + +#include + +#if defined(VISP_HAVE_DC1394) + +/* + * Interface with libdc1394 2.x + */ +#include + +#include +#include +#include + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vp1394TwoGrabber + + \ingroup group_sensor_camera + + \brief Class for firewire ieee1394 video devices using libdc1394-2.x api. + + + Needs libraw1394-1.2.0 and libdc1394-2.0.0 or more recent versions + available on http://sourceforge.net. + + This class was tested with Marlin F033C and F131B cameras and with + Point Grey Dragonfly 2, Flea 2 and Flea 3 cameras. + + This grabber allows single or multi camera acquisition. + + - Here you will find an example of single capture from the first camera + found on the bus. This example is available in tutorial-grabber-1394.cpp: + \include tutorial-grabber-1394.cpp + A line by line explanation of this example is provided in \ref + tutorial-grabber. An other example that shows how to use format 7 and the + auto-shutter is provided in vp1394TwoGrabber() constructor: + + - If more than one camera is connected, it is also possible to select a + specific camera by its GUID: + \code + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #if defined(VISP_HAVE_DC1394) + vpImage I; // Create a gray level image container + bool reset = false; // Disable bus reset during construction + vp1394TwoGrabber g(reset); // Create a grabber based on libdc1394-2.x third party lib + + unsigned int ncameras; // Number of cameras on the bus + ncameras = g.getNumCameras(); + std::cout << ncameras << " cameras found:" << std::endl; + + for(unsigned int i=0; i< ncameras; i++) + { + g.setCamera(i); + uint64_t guid = g.getGuid(); + printf("camera %d with guid 0x%lx\n", i, (long unsigned int)guid); + } + + // produce: + // 2 cameras found: + // camera 0 with guid 0xb09d01009b329c + // camera 1 with guid 0xb09d01007e0ee7 + g.setCamera( (uint64_t)0xb09d01009b329cULL ); + + printf("Use camera with GUID: 0x%lx\n", (long unsigned int)g.getGuid()); + g.acquire(I); // Acquire an image from the camera with GUID 0xb09d01009b329c + + vpImageIo::write(I, "image.pgm"); // Write image on the disk + #endif + } + \endcode + + - Here an example of multi camera capture. An other example is available in setCamera(): + \code + #include + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #if defined(VISP_HAVE_DC1394) + bool reset = false; // Disable bus reset during construction + vp1394TwoGrabber g(reset); // Creation of a grabber instance based on libdc1394-2.x third party lib. + unsigned int ncameras; // Number of cameras on the bus + ncameras = g.getNumCameras(); + + // Create an image container for each camera + vpImage *I = new vpImage [ncameras]; + char filename[FILENAME_MAX]; + + // If the first camera supports vpVIDEO_MODE_640x480_YUV422 video mode + g.setCamera(0); + g.setVideoMode(vp1394TwoGrabber::vpVIDEO_MODE_640x480_YUV422); + + // If the second camera support 30 fps acquisition + g.setCamera(1); + g.setFramerate(vp1394TwoGrabber::vpFRAMERATE_30); + + // Acquire an image from each camera + for (unsigned int camera=0; camera < ncameras; camera ++) { + g.setCamera(camera); + g.acquire(I[camera]); + std::stringstream ss; + ss << image-cam << camera << ".pgm"; + vpImageIo::write(I[camera], ss.str()); + } + delete [] I; + #endif + } + \endcode + +*/ +class VISP_EXPORT vp1394TwoGrabber : public vpFrameGrabber +{ + +public: + static const char *strVideoMode[DC1394_VIDEO_MODE_NUM]; + static const char *strFramerate[DC1394_FRAMERATE_NUM]; + static const char *strColorCoding[DC1394_COLOR_CODING_NUM]; + + /*! + Enumeration of iso speed. See libdc1394 2.x header file + dc1394/control.h + */ + typedef enum + { + vpISO_SPEED_100 = DC1394_ISO_SPEED_100, + vpISO_SPEED_200 = DC1394_ISO_SPEED_200, + vpISO_SPEED_400 = DC1394_ISO_SPEED_400, + vpISO_SPEED_800 = DC1394_ISO_SPEED_800, + vpISO_SPEED_1600 = DC1394_ISO_SPEED_1600, + vpISO_SPEED_3200 = DC1394_ISO_SPEED_3200 + } vp1394TwoIsoSpeedType; + + /*! + Enumeration of video modes. See libdc1394 2.x header file + dc1394/control.h + */ + typedef enum + { + vpVIDEO_MODE_160x120_YUV444 = DC1394_VIDEO_MODE_160x120_YUV444, + vpVIDEO_MODE_320x240_YUV422 = DC1394_VIDEO_MODE_320x240_YUV422, + vpVIDEO_MODE_640x480_YUV411 = DC1394_VIDEO_MODE_640x480_YUV411, + vpVIDEO_MODE_640x480_YUV422 = DC1394_VIDEO_MODE_640x480_YUV422, + vpVIDEO_MODE_640x480_RGB8 = DC1394_VIDEO_MODE_640x480_RGB8, + vpVIDEO_MODE_640x480_MONO8 = DC1394_VIDEO_MODE_640x480_MONO8, + vpVIDEO_MODE_640x480_MONO16 = DC1394_VIDEO_MODE_640x480_MONO16, + vpVIDEO_MODE_800x600_YUV422 = DC1394_VIDEO_MODE_800x600_YUV422, + vpVIDEO_MODE_800x600_RGB8 = DC1394_VIDEO_MODE_800x600_RGB8, + vpVIDEO_MODE_800x600_MONO8 = DC1394_VIDEO_MODE_800x600_MONO8, + vpVIDEO_MODE_1024x768_YUV422 = DC1394_VIDEO_MODE_1024x768_YUV422, + vpVIDEO_MODE_1024x768_RGB8 = DC1394_VIDEO_MODE_1024x768_RGB8, + vpVIDEO_MODE_1024x768_MONO8 = DC1394_VIDEO_MODE_1024x768_MONO8, + vpVIDEO_MODE_800x600_MONO16 = DC1394_VIDEO_MODE_800x600_MONO16, + vpVIDEO_MODE_1024x768_MONO16 = DC1394_VIDEO_MODE_1024x768_MONO16, + vpVIDEO_MODE_1280x960_YUV422 = DC1394_VIDEO_MODE_1280x960_YUV422, + vpVIDEO_MODE_1280x960_RGB8 = DC1394_VIDEO_MODE_1280x960_RGB8, + vpVIDEO_MODE_1280x960_MONO8 = DC1394_VIDEO_MODE_1280x960_MONO8, + vpVIDEO_MODE_1600x1200_YUV422 = DC1394_VIDEO_MODE_1600x1200_YUV422, + vpVIDEO_MODE_1600x1200_RGB8 = DC1394_VIDEO_MODE_1600x1200_RGB8, + vpVIDEO_MODE_1600x1200_MONO8 = DC1394_VIDEO_MODE_1600x1200_MONO8, + vpVIDEO_MODE_1280x960_MONO16 = DC1394_VIDEO_MODE_1280x960_MONO16, + vpVIDEO_MODE_1600x1200_MONO16 = DC1394_VIDEO_MODE_1600x1200_MONO16, + vpVIDEO_MODE_EXIF = DC1394_VIDEO_MODE_EXIF, + vpVIDEO_MODE_FORMAT7_0 = DC1394_VIDEO_MODE_FORMAT7_0, + vpVIDEO_MODE_FORMAT7_1 = DC1394_VIDEO_MODE_FORMAT7_1, + vpVIDEO_MODE_FORMAT7_2 = DC1394_VIDEO_MODE_FORMAT7_2, + vpVIDEO_MODE_FORMAT7_3 = DC1394_VIDEO_MODE_FORMAT7_3, + vpVIDEO_MODE_FORMAT7_4 = DC1394_VIDEO_MODE_FORMAT7_4, + vpVIDEO_MODE_FORMAT7_5 = DC1394_VIDEO_MODE_FORMAT7_5, + vpVIDEO_MODE_FORMAT7_6 = DC1394_VIDEO_MODE_FORMAT7_6, + vpVIDEO_MODE_FORMAT7_7 = DC1394_VIDEO_MODE_FORMAT7_7 + } vp1394TwoVideoModeType; + + /*! + Enumeration of framerates. See libdc1394 2.x header file + dc1394/control.h + */ + typedef enum + { + vpFRAMERATE_1_875 = DC1394_FRAMERATE_1_875, + vpFRAMERATE_3_75 = DC1394_FRAMERATE_3_75, + vpFRAMERATE_7_5 = DC1394_FRAMERATE_7_5, + vpFRAMERATE_15 = DC1394_FRAMERATE_15, + vpFRAMERATE_30 = DC1394_FRAMERATE_30, + vpFRAMERATE_60 = DC1394_FRAMERATE_60, + vpFRAMERATE_120 = DC1394_FRAMERATE_120, + vpFRAMERATE_240 = DC1394_FRAMERATE_240 + } vp1394TwoFramerateType; + + /*! + Enumeration of color codings. See libdc1394 2.x header file + dc1394/control.h + */ + typedef enum + { + vpCOLOR_CODING_MONO8 = DC1394_COLOR_CODING_MONO8, + vpCOLOR_CODING_YUV411 = DC1394_COLOR_CODING_YUV411, + vpCOLOR_CODING_YUV422 = DC1394_COLOR_CODING_YUV422, + vpCOLOR_CODING_YUV444 = DC1394_COLOR_CODING_YUV444, + vpCOLOR_CODING_RGB8 = DC1394_COLOR_CODING_RGB8, + vpCOLOR_CODING_MONO16 = DC1394_COLOR_CODING_MONO16, + vpCOLOR_CODING_RGB16 = DC1394_COLOR_CODING_RGB16, + vpCOLOR_CODING_MONO16S = DC1394_COLOR_CODING_MONO16S, + vpCOLOR_CODING_RGB16S = DC1394_COLOR_CODING_RGB16S, + vpCOLOR_CODING_RAW8 = DC1394_COLOR_CODING_RAW8, + vpCOLOR_CODING_RAW16 = DC1394_COLOR_CODING_RAW16 + } vp1394TwoColorCodingType; + + /*! + Enumeration of the parameters that can be modified. See libdc1394 2.x + header file dc1394/control.h + */ + typedef enum + { + vpFEATURE_BRIGHTNESS = DC1394_FEATURE_BRIGHTNESS, + vpFEATURE_EXPOSURE = DC1394_FEATURE_EXPOSURE, + vpFEATURE_SHARPNESS = DC1394_FEATURE_SHARPNESS, + // vpFEATURE_WHITE_BALANCE = DC1394_FEATURE_WHITE_BALANCE, + vpFEATURE_HUE = DC1394_FEATURE_HUE, + vpFEATURE_SATURATION = DC1394_FEATURE_SATURATION, + vpFEATURE_GAMMA = DC1394_FEATURE_GAMMA, + vpFEATURE_SHUTTER = DC1394_FEATURE_SHUTTER, + vpFEATURE_GAIN = DC1394_FEATURE_GAIN, + vpFEATURE_IRIS = DC1394_FEATURE_IRIS, + // vpFEATURE_FOCUS = DC1394_FEATURE_FOCUS, + // vpFEATURE_TEMPERATURE = DC1394_FEATURE_TEMPERATURE, + // vpFEATURE_TRIGGER = DC1394_FEATURE_TRIGGER, + // vpFEATURE_TRIGGER_DELAY = DC1394_FEATURE_TRIGGER_DELAY, + // vpFEATURE_WHITE_SHADING = DC1394_FEATURE_WHITE_SHADING, + // vpFEATURE_FRAME_RATE = DC1394_FEATURE_FRAME_RATE, + // vpFEATURE_ZOOM = DC1394_FEATURE_ZOOM, + // vpFEATURE_PAN = DC1394_FEATURE_PAN, + // vpFEATURE_TILT = DC1394_FEATURE_TILT, + // vpFEATURE_OPTICAL_FILTER = DC1394_FEATURE_OPTICAL_FILTER, + // vpFEATURE_CAPTURE_SIZE = DC1394_FEATURE_CAPTURE_SIZE, + // vpFEATURE_CAPTURE_QUALITY = DC1394_FEATURE_CAPTURE_QUALITY + } vp1394TwoParametersType; + +private: + /*! + Control structure of the values that can be modified during the execution. + */ + typedef struct + { + uint32_t brightness; + uint32_t exposure; + uint32_t sharpness; + // uint32_t whiteBalance; + uint32_t hue; + uint32_t saturation; + uint32_t gamma; + uint32_t shutter; + uint32_t gain; + uint32_t iris; + // uint32_t focus; + // uint32_t temperature ; + // uint32_t trigger ; + // uint32_t triggerDelay ; + // uint32_t whiteShadding ; + // uint32_t frameRate ; + // uint32_t zoom; + // uint32_t pan; + // uint32_t tilt ; + // uint32_t opticalFilter ; + // uint32_t captureSize; + // uint32_t captureQuality ; + } vpDc1394TwoCameraParametersData; + + // private: + //#ifndef DOXYGEN_SHOULD_SKIP_THIS + // vp1394TwoGrabber(const vp1394TwoGrabber &) + // : camera(nullptr), cameras(nullptr), num_cameras(0), camera_id(0), + // verbose(false), camIsOpen(nullptr), + // num_buffers(4), // ring buffer size + // isDataModified(nullptr), initialShutterMode(nullptr), dataCam(nullptr) + // #ifdef VISP_HAVE_DC1394_CAMERA_ENUMERATE // new API > + // libdc1394-2.0.0-rc7 , d(nullptr), + // list(nullptr) + // #endif + // { + // throw vpException(vpException::functionNotImplementedError,"Not + // implemented!"); + // } + // vp1394TwoGrabber &operator=(const vp1394TwoGrabber &){ + // throw vpException(vpException::functionNotImplementedError,"Not + // implemented!"); return *this; + // } + //#endif + +public: + VP_EXPLICIT vp1394TwoGrabber(bool reset = true); + virtual ~vp1394TwoGrabber(); + + void acquire(vpImage &I); + void acquire(vpImage &I, uint64_t ×tamp, uint32_t &id); + void acquire(vpImage &I); + void acquire(vpImage &I, uint64_t ×tamp, uint32_t &id); + + void close(); + + static std::string colorCoding2string(vp1394TwoColorCodingType colorcoding); + + dc1394video_frame_t *dequeue(); + dc1394video_frame_t *dequeue(vpImage &I); + dc1394video_frame_t *dequeue(vpImage &I, uint64_t ×tamp, uint32_t &id); + dc1394video_frame_t *dequeue(vpImage &I); + dc1394video_frame_t *dequeue(vpImage &I, uint64_t ×tamp, uint32_t &id); + void enqueue(dc1394video_frame_t *frame); + + static std::string framerate2string(vp1394TwoFramerateType fps); + + void getAutoGain(unsigned int &minvalue, unsigned int &maxvalue); + void getAutoShutter(unsigned int &minvalue, unsigned int &maxvalue); + + uint64_t getCamera(); + void getCamera(uint64_t &camera); + void getColorCoding(vp1394TwoColorCodingType &coding); + uint32_t getColorCodingSupported(vp1394TwoVideoModeType videomode, std::list &codings); + void getFramerate(vp1394TwoFramerateType &fps); + uint32_t getFramerateSupported(vp1394TwoVideoModeType videomode, std::list &fps); + uint64_t getGuid(); + void getGuid(uint64_t &guid); + void getHeight(unsigned int &height); + unsigned int getHeight(); + void getNumCameras(unsigned int &ncameras) const; + unsigned int getNumCameras() const; + unsigned int getParameterValue(vp1394TwoParametersType param); + unsigned int getRingBufferSize() const; + void getVideoMode(vp1394TwoVideoModeType &videomode); + uint32_t getVideoModeSupported(std::list &videomodes); + void getWidth(unsigned int &width); + unsigned int getWidth(); + + bool isColor(); + bool isColorCodingSupported(vp1394TwoVideoModeType videomode, vp1394TwoColorCodingType coding); + bool isFramerateSupported(vp1394TwoVideoModeType videomode, vp1394TwoFramerateType fps); + bool isVideoModeSupported(vp1394TwoVideoModeType videomode); + bool isVideoModeFormat7(vp1394TwoVideoModeType videomode); + + void open(vpImage &I); + void open(vpImage &I); + + vp1394TwoGrabber &operator>>(vpImage &I); + vp1394TwoGrabber &operator>>(vpImage &I); + + void printCameraInfo(); + + void resetBus(); + + void setAutoGain(bool enable = true); + void setAutoGain(unsigned int minvalue, unsigned int maxvalue); + void setAutoShutter(bool enable = true); + void setAutoShutter(unsigned int minvalue, unsigned int maxvalue); + void setCamera(uint64_t camera); + void setColorCoding(vp1394TwoColorCodingType coding); + void setFormat7ROI(unsigned int left = 0, unsigned int top = 0, unsigned int width = 0, unsigned int height = 0); + void setFramerate(vp1394TwoFramerateType fps); + void setIsoTransmissionSpeed(vp1394TwoIsoSpeedType isospeed); + void setPanControl(unsigned int panControlValue); + void setParameterValue(vp1394TwoParametersType param, unsigned int val); + void setRingBufferSize(unsigned int size); + void setVideoMode(vp1394TwoVideoModeType videomode); + + static vp1394TwoVideoModeType string2videoMode(std::string videomode); + static vp1394TwoFramerateType string2framerate(std::string fps); + static vp1394TwoColorCodingType string2colorCoding(std::string colorcoding); + + static std::string videoMode2string(vp1394TwoVideoModeType videomode); + +private: + void open(); + void initialize(bool reset); + void setCapture(dc1394switch_t _switch); + void setTransmission(dc1394switch_t _switch); + inline void updateDataCamToStruct(); + inline void updateDataStructToCam(); + +private: + dc1394camera_t *camera, **cameras; + unsigned int num_cameras; + unsigned int camera_id; + + bool verbose; + bool *camIsOpen; + unsigned int num_buffers; + + /* parameters for the cameras */ + bool *isDataModified; + dc1394feature_mode_t *initialShutterMode; + vpDc1394TwoCameraParametersData *dataCam; + +#ifdef VISP_HAVE_DC1394_CAMERA_ENUMERATE + dc1394_t *d; + dc1394camera_list_t *list; +#endif +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpComedi.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpComedi.h new file mode 100644 index 00000000..b3380455 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpComedi.h @@ -0,0 +1,182 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * ATI Force torque interface. + */ +#ifndef _vpComedi_h_ +#define _vpComedi_h_ + +#include + +#ifdef VISP_HAVE_COMEDI + +#include + +#include + +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpComedi + + \ingroup group_sensor_ft + + Interface for data acquisition devices supported by Comedi. Comedi is a + linux control and measurement device interface. For more information see + http://www.comedi.org. + + This class was tested with ATI Gamma 65-SI FT sensor connected to a + National Instrument NI DAQmx PCI-6220 board. + + \warning If you experience an exception like + \code + Could not open device /dev/comedi0 + \endcode + you may set up udev permissions for Comedi device files. Once Comedi is + installed you also need to make sure that the user has appropriate permissions + for accessing the Comedi device files. That is you need to be able to have + read and write access to the /dev/comedi* files. One elegant way to achieve + this to create a new group and tell udev to add the Comedi device files to + this group. To this end: + 1. Login as root + 2. Create a new group "iocard": + \code + $ addgroup --system iocard + \endcode + 3. Add udev rules to the /etc/udev/rules.d directory: + \code + $ echo 'KERNEL=="comedi*", MODE="0660", GROUP="iocard"' > /etc/udev/rules.d/95-comedi.rules + \endcode + 4. Add users to the "iocard" group: + \code + $ adduser iocard + \endcode + 5. Reboot + + The following example shows how to run an synchronous data acquisition at + 500 Hz, calling getPhyData() each 2 ms: + + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpComedi comedi; + comedi.setDevice("/dev/comedi0"); + comedi.setChannelNumbers(6); // to read a F/T tensor + comedi.open(); + + for(unsigned int i=0; i < 500; i++) { + std::cout << "Physical data (in " << comedi.getPhyDataUnits() << "): " << comedi.getPhyData().t() << std::endl; + vpTime::wait(2); + } + comedi.close(); + } + \endcode +*/ +class VISP_EXPORT vpComedi +{ +public: + vpComedi(); + virtual ~vpComedi(); + + //! @name Inherited functionalities from vpComedi + //@{ + void close(); + //! Get native Comedi handler. + comedi_t *getHandler() const { return m_handler; } + //! Get max data per channel. The returned vector is of dimension the number + //! of channels. + std::vector getMaxData() const { return m_maxdata; } + //! Get number of channels. + unsigned int getNChannel() const { return m_nchannel; } + + vpColVector getPhyData() const; + std::string getPhyDataUnits() const; + + //! Get data range information per channel. The returned vector is of + //! dimension the number of channels. + std::vector getRangeInfo() const { return m_range_info; } + //! Get comedi subdevice. + unsigned int getSubDevice() const { return m_subdevice; } + + void open(); + + /*! + Set analog reference type. + \param aref : The aref parameter specifies an analog reference to use: + AREF_GROUND, AREF_COMMON, AREF_DIFF, or AREF_OTHER. + */ + void setAnalogReference(const unsigned int &aref) { m_aref = aref; } + + /*! + Number of channels to read from sensor. For a 6-dim force/torque sensor + use 6. + */ + void setChannelNumbers(const unsigned int &nchannel) { m_nchannel = nchannel; } + + //! Set comedi device name. Default value is /dev/comedi0. + void setDevice(const std::string &device) { m_device = device; } + + /*! + Set the range parameter that is the zero-based index of one of the gain + ranges supported by the channel. This is a number from 0 to N-1 where N + is the number of ranges supported by the channel. + */ + void setRange(const unsigned int &range) { m_range = range; } + //! Set comedi analog input subdevice. + void setSubDevice(const unsigned int &subdevice) { m_subdevice = subdevice; } + //@} + +protected: + std::vector getRawData() const; + +protected: + //! @name Protected Member Functions Inherited from vpComedi + //@{ + std::string m_device; /*!< Comedi device name (default: /dev/comedi0) */ + comedi_t *m_handler; /*!< Comedi handler */ + unsigned int m_subdevice; /*!< Input subdevice */ + unsigned int m_range; /*!< Range of a channel */ + unsigned int m_aref; /*!< Analog reference */ + unsigned int m_nchannel; /*!< Number of channels */ + std::vector m_range_info; /*!< Range information */ + std::vector m_maxdata; /*!< Max data value */ + std::vector m_chanlist; /*!< Channel list */ + //@} +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpDirectShowDevice.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpDirectShowDevice.h new file mode 100644 index 00000000..17a29912 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpDirectShowDevice.h @@ -0,0 +1,81 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * DirectShow device description. + * + * Authors: + * Bruno Renier + */ + +#ifndef vpDirectShowDevice_hh +#define vpDirectShowDevice_hh + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +#include +#if (defined(VISP_HAVE_DIRECTSHOW)) + +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +class VISP_EXPORT vpDirectShowDevice +{ + + std::string name; // the device's name + std::string desc; // the device's description + std::string devPath; // the device's device path (unique) + + bool inUse; // true if the device is already used by a grabber + +public: + vpDirectShowDevice() : inUse(false) { } + VP_EXPLICIT vpDirectShowDevice(const CComPtr &moniker) : inUse(false) { init(moniker); } + + bool init(const CComPtr &moniker); + + bool getState() { return inUse; } + void setInUse() { inUse = true; } + void resetInUse() { inUse = false; } + + std::string &getName() { return name; } + std::string &getDesc() { return desc; } + std::string &getDevPath() { return devPath; } + + bool operator==(vpDirectShowDevice &dev); + + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, vpDirectShowDevice &dev); +}; +END_VISP_NAMESPACE +#endif +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpDirectShowGrabber.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpDirectShowGrabber.h new file mode 100644 index 00000000..085a3a75 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpDirectShowGrabber.h @@ -0,0 +1,114 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * DirectShow framegrabber. + * + * Authors: + * Bruno Renier + * Anthony Saunier + */ + +#ifndef vpDirectShowGrabber_hh +#define vpDirectShowGrabber_hh + +#include +#if (defined(VISP_HAVE_DIRECTSHOW)) + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +class vpDirectShowGrabberImpl; + +/*! + \class vpDirectShowGrabber + \brief class for windows direct show devices + + This class' goal is to totally hide the implementation of the framegrabber + from the rest of the application. This is necessary to avoid conflicts + between dshow's Qedit.h and Direct3D's includes. + + \ingroup group_sensor_camera +*/ + +class VISP_EXPORT vpDirectShowGrabber : public vpFrameGrabber +{ + vpDirectShowGrabberImpl *grabber; + +public: + unsigned int getHeight(); + unsigned int getWidth(); + + vpDirectShowGrabber(); + virtual ~vpDirectShowGrabber(); + + void open(); + void open(vpImage &I); + void open(vpImage &I); + + void acquire(vpImage &I); + void acquire(vpImage &I); + + void close(); + + // get the number of capture devices + unsigned int getDeviceNumber(); + + // change the capture device + bool setDevice(unsigned int id); + + // displays a list of available devices + void displayDevices(); + + // set image size + bool setImageSize(unsigned int width, unsigned int height); + + // set source framerate + bool setFramerate(double framerate); + + // set capture format + bool setFormat(unsigned int width, unsigned int height, double framerate); + + // get capture format + void getFormat(unsigned int &width, unsigned int &height, double &framerate); + + // Get the available capture formats + bool getStreamCapabilities(); + + // Set capture MediaType + bool setMediaType(int mediaTypeID); + + // Get current capture MediaType + int getMediaType(); +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpDirectShowGrabberImpl.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpDirectShowGrabberImpl.h new file mode 100644 index 00000000..29366414 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpDirectShowGrabberImpl.h @@ -0,0 +1,214 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * DirectShow framegrabber Implementation. + */ + +#ifndef vpDirectShowGrabberImpl_hh +#define vpDirectShowGrabberImpl_hh + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +#include +#if (defined(VISP_HAVE_DIRECTSHOW)) + +#include +#include +#include + +#include +#include + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpDirectShowGrabberImpl + \brief class for windows direct show devices - implementation + + This class uses CComPtr which is the best way to be sure that all the + interfaces are released sooner or later. + +*/ +class VISP_EXPORT vpDirectShowGrabberImpl : public vpFrameGrabber +{ + static const int MAX_DELAY = 10000; + static const int MAX_DEVICES = 10; + +public: + /*! + Enumeration of video subtypes. + */ + /* + typedef enum { + //Known RGB formats + vpMEDIASUBTYPE_ARGB32 = MEDIASUBTYPE_ARGB32, + vpMEDIASUBTYPE_RGB32 = MEDIASUBTYPE_RGB32, + vpMEDIASUBTYPE_RGB24 = MEDIASUBTYPE_RGB24, + vpMEDIASUBTYPE_RGB555 = MEDIASUBTYPE_RGB555, + vpMEDIASUBTYPE_RGB565 = MEDIASUBTYPE_RGB565, + vpMEDIASUBTYPE_RGB8 = MEDIASUBTYPE_RGB8, + vpMEDIASUBTYPE_RGB4 = MEDIASUBTYPE_RGB4, + vpMEDIASUBTYPE_RGB1 = MEDIASUBTYPE_RGB1, + //Known YUV formats + vpMEDIASUBTYPE_AYUV = MEDIASUBTYPE_AYUV, + vpMEDIASUBTYPE_UYVY = MEDIASUBTYPE_UYVY, + vpMEDIASUBTYPE_Y411 = MEDIASUBTYPE_Y411, + vpMEDIASUBTYPE_Y41P = MEDIASUBTYPE_Y41P, + vpMEDIASUBTYPE_Y211 = MEDIASUBTYPE_Y211, + vpMEDIASUBTYPE_YUY2 = MEDIASUBTYPE_YUY2, + vpMEDIASUBTYPE_YVYU = MEDIASUBTYPE_YVYU, + vpMEDIASUBTYPE_YUYV = MEDIASUBTYPE_YUYV, + vpMEDIASUBTYPE_IF09 = MEDIASUBTYPE_IF09, + vpMEDIASUBTYPE_IYUV = MEDIASUBTYPE_IYUV, + vpMEDIASUBTYPE_YV12 = MEDIASUBTYPE_YV12, + vpMEDIASUBTYPE_YVU9 = MEDIASUBTYPE_YVU9 + } vpDirectShowMediaSubtype; + */ + + vpDirectShowGrabberImpl(); + virtual ~vpDirectShowGrabberImpl(); + + void open(); + void open(vpImage &I); + void open(vpImage &I); + + void acquire(vpImage &I); + void acquire(vpImage &I); + + void close(); + + /*! + Gets the number of capture devices + */ + unsigned int getDeviceNumber() { return nbDevices; } + + // change the capture device + bool setDevice(unsigned int id); + + // displays a list of available devices + void displayDevices(); + + // set image size + bool setImageSize(unsigned int width, unsigned int height); + + // set capture framerate + bool setFramerate(double framerate); + + // set capture format + bool setFormat(unsigned int width, unsigned int height, double framerate); + + // get capture format + void getFormat(unsigned int &width, unsigned int &height, double &framerate); + + // set capture MediaType + bool setMediaType(int mediaTypeID); + + // get current capture MediaType + int getMediaType(); + + // Get the available capture formats + bool getStreamCapabilities(); + +private: + CComPtr pGraph; // our DS filter graph + + CComPtr pBuild; // the interface to the capture graph builder + // used to build the filter graph + + CComPtr pCapSource; // the capture source filter + + CComPtr pGrabberI; // the sample grabber's interface and filter + CComPtr pGrabberFilter; + + CComPtr pControl; // The DS filter graph control interface + CComPtr pEvent; // The DS filter graph event interface + + vpDirectShowSampleGrabberI sgCB; // Interface used to implement the frame grabber callback + + HRESULT hr; // contains the result of the last operation + + static vpDirectShowDevice *deviceList; // This contains the list of the available capture devices + // it is shared by all the DirectShow Grabbers + + static unsigned int nbDevices; // the number of available devices + int currentDevice; // the number of the current device + + // flag to manage CoInitialize() and CoUnInitialze() + bool initCo; + // setup the directshow filtergraph with the first available device + bool initDirectShow(); + + // enumerates the different video inputs + bool enumerate(CComPtr &ppVideoInputEnum); + + // selects a random video input from the enumeration and returns the + // associated filter + bool selectRandomSource(CComPtr &ppVideoInputEnum, CComPtr &pCapSource); + + // creates the filter graph + bool createGraph(); + + // creates the sample grabber + bool createSampleGrabber(CComPtr &ppGrabberFilter); + + // checks the capture filter's media type and sets flags as needed + bool checkSourceType(CComPtr &pCapSourcePin); + + // connects the filters as needed + bool connectSourceToGrabber(CComPtr &pCapSource, CComPtr &pGrabberFilter); + + // used to convert HRESULT-associated error message to a string + void HRtoStr(std::string &str); + + // create the list of the available devices + bool createDeviceList(CComPtr &ppVideoInputEnum); + + // get the n-th device if it is available + bool getDevice(unsigned int n, CComPtr &ppCapSource); + + // get the first available device if any + unsigned int getFirstUnusedDevice(CComPtr &ppDevice); + + // removes all the filters in the graph + bool removeAll(); + + // Deletes an allocated AM_MEDIA_TYPE structure, including the format block + void MyDeleteMediaType(AM_MEDIA_TYPE *pmt); + + // Frees the format block in an AM_MEDIA_TYPE structure + void MyFreeMediaType(AM_MEDIA_TYPE &mt); +}; +END_VISP_NAMESPACE +#endif +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpDirectShowSampleGrabberI.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpDirectShowSampleGrabberI.h new file mode 100644 index 00000000..8fa9703c --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpDirectShowSampleGrabberI.h @@ -0,0 +1,101 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * DirectShow framegrabber callback. + * + * Authors: + * Bruno Renier + */ + +#ifndef vpDirectShowSampleGrabberI_hh +#define vpDirectShowSampleGrabberI_hh + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +#include +#if (defined(VISP_HAVE_DIRECTSHOW)) + +#include +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + This class is needed in order to implement a callback function + associated with the grabber +*/ +class VISP_EXPORT vpDirectShowSampleGrabberI : public ISampleGrabberCB +{ + vpDirectShowSampleGrabberI(); + virtual ~vpDirectShowSampleGrabberI(); + + // needed by the interface + STDMETHODIMP_(ULONG) AddRef() { return 1; } + STDMETHODIMP_(ULONG) Release() { return 2; } + + STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject); + + // not implemented + STDMETHODIMP SampleCB(double Time, IMediaSample *pSample) { return E_NOTIMPL; } + + // our callback function + STDMETHODIMP BufferCB(double Time, BYTE *pBuffer, long BufferLen); + +private: + // the currently connected media type + AM_MEDIA_TYPE connectedMediaType; + + // true if the source media type is not a standard one + bool specialMediaType; + // true if the image needs to be flipped (only for special media types) + bool invertedSource; + + // booleans used to signal a demand from acquire + bool acqGrayDemand; + bool acqRGBaDemand; + + // pointer on the image to fill during the next callback if there has been a + // demand + vpImage *rgbaIm; + vpImage *grayIm; + + // semaphore used to synchronize the productor (callback) and the consumer + // (acquire) + HANDLE copySem; + + friend class vpDirectShowGrabberImpl; +}; +END_VISP_NAMESPACE +#endif +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpFlyCaptureGrabber.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpFlyCaptureGrabber.h new file mode 100644 index 00000000..4b5290d9 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpFlyCaptureGrabber.h @@ -0,0 +1,246 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: Class which enables to project an image in the 3D space + * and get the view of a virtual camera. + */ + +/*! + \file vpFlyCaptureGrabber.h + \brief Wrapper over PointGrey FlyCapture SDK to capture images from + PointGrey cameras. +*/ + +#ifndef _vpFlyCaptureGrabber_h_ +#define _vpFlyCaptureGrabber_h_ + +#include +#include +#include + +#ifdef VISP_HAVE_FLYCAPTURE + +#include + +BEGIN_VISP_NAMESPACE + +/*! + \class vpFlyCaptureGrabber + \ingroup group_sensor_camera + + Allows to grab images from a PointGrey camera using FlyCapture SDK. + + To use this class install first FlyCapture SDK https://www.ptgrey.com/flycapture-sdk. + Installation instructions are provide here https://visp.inria.fr/3rd_flycapture. + + \note To install FlyCapture SDK on linux follow https://www.ptgrey.com/tan/10548. + \note For specific details about using FlyCapture and Linux with a USB 3.0 camera, see + http://www.ptgrey.com/KB/10685. + + \warning With Ubuntu 16.04 or more recent versions, if you get issues during grabbing like: + \code + Grab loop had an error: There is an image consistency issue with this image. + \endcode + follow instruction provide + [here](https://www.flir.fr/support-center/iis/machine-vision/knowledge-base/lost-ethernet-data-packets-on-linux-systems) + to increase receive buffer size. + + Once installed configure ViSP using cmake to detect FlyCapture SDK and build + ViSP to include FlyCapture SDK support. + + This class was tested under Ubuntu and Windows with the following cameras: + - Flea3 USB 3.0 cameras (FL3-U3-32S2M-CS, FL3-U3-13E4C-C) + - Flea2 firewire camera (FL2-03S2C) + - Dragonfly2 firewire camera (DR2-COL) + + The following example shows how to use this class to capture images + from the first camera that is found. + \code + #include + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #if defined(VISP_HAVE_FLYCAPTURE) + try { + int nframes = 100; + vpImage I; + char filename[FILENAME_MAX]; + vpFlyCaptureGrabber g; + std::cout << "Number of cameras detected: " << g.getNumCameras() << std::endl; + + g.setCameraIndex(0); // Default camera is the first on the bus + g.getCameraInfo(std::cout); + g.open(I); + + for(int i=0; i< nframes; ++i) { + g.acquire(I); + std::string filename = vpIoTools::formatString("image%04d.png", i); + vpImageIo::write(I, filename); + } + #endif + } + \endcode + + If more than one camera is detected, you can use setCamera(const unsigned int &) + to select the camera of interest. + + It is also possible to capture images from multiple cameras. The following + example shows how to capture simultaneously images from multiple cameras. + + \code + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #if defined(VISP_HAVE_FLYCAPTURE) + int nframes = 100; + char filename[FILENAME_MAX]; + unsigned int numCameras = vpFlyCaptureGrabber::getNumCameras(); + + std::cout << "Number of cameras detected: " << numCameras << std::endl; + + vpFlyCaptureGrabber *g = new vpFlyCaptureGrabber [numCameras]; + std::vector< vpImage > I(numCameras); + + for(unsigned int cam=0; cam < numCameras; cam++) { + g[cam].setCameraIndex(cam); // Default camera is the first on the bus + g[cam].getCameraInfo(std::cout); + g[cam].open(I[cam]); + } + + for(int i=0; i< nframes; i++) { + for(unsigned int cam=0; cam < numCameras; cam++) { + g[cam].acquire(I[cam]); + snprintf(filename, FILENAME_MAX, "image-camera%d-%04d.pgm", cam, i); + vpImageIo::write(I[cam], filename); + } + } + delete [] g; + #endif + } + \endcode +*/ +class VISP_EXPORT vpFlyCaptureGrabber : public vpFrameGrabber +{ +public: + vpFlyCaptureGrabber(); + virtual ~vpFlyCaptureGrabber(); + + void acquire(vpImage &I); + void acquire(vpImage &I, FlyCapture2::TimeStamp ×tamp); + void acquire(vpImage &I); + void acquire(vpImage &I, FlyCapture2::TimeStamp ×tamp); + + void close(); + void connect(); + void disconnect(); + + float getBrightness(); + std::ostream &getCameraInfo(std::ostream &os); // Cannot be const since + // FlyCapture2::Camera::GetCameraInfo() + // isn't + FlyCapture2::Camera *getCameraHandler(); + /*! Return the index of the active camera. */ + unsigned int getCameraIndex() const { return m_index; } + bool getCameraPower(); + static unsigned int getCameraSerial(unsigned int index); + float getExposure(); + float getFrameRate(); + float getGain(); + static unsigned int getNumCameras(); + unsigned int getSharpness(); + float getShutter(); + + bool isCameraPowerAvailable(); + //! Return true if the camera is connected. + bool isConnected() const { return m_connected; } + //! Return true if the camera capture is started. + bool isCaptureStarted() const { return m_capture; } + bool isFormat7Supported(FlyCapture2::Mode format7_mode); + bool isVideoModeAndFrameRateSupported(FlyCapture2::VideoMode video_mode, FlyCapture2::FrameRate frame_rate); + void open(vpImage &I); + void open(vpImage &I); + + vpFlyCaptureGrabber &operator>>(vpImage &I); + vpFlyCaptureGrabber &operator>>(vpImage &I); + + float setBrightness(bool brightness_auto, float brightness_value = 0); + void setCameraIndex(unsigned int index); + void setCameraPower(bool on); + void setCameraSerial(unsigned int serial); + float setExposure(bool exposure_on, bool exposure_auto, float exposure_value = 0); + float setGain(bool gain_auto, float gain_value = 0); + void setFormat7VideoMode(FlyCapture2::Mode format7_mode, FlyCapture2::PixelFormat pixel_format, unsigned int width, + unsigned int height); + float setFrameRate(float frame_rate); + unsigned int setSharpness(bool sharpness_on, bool sharpness_auto, unsigned int sharpness_value = 0); + float setShutter(bool auto_shutter, float shutter_ms = 10); + void setVideoModeAndFrameRate(FlyCapture2::VideoMode video_mode, FlyCapture2::FrameRate frame_rate); + + void startCapture(); + void stopCapture(); + +protected: + typedef enum + { + ABS_VALUE, //!< Consider FlyCapture2::Property::absValue + VALUE_A, //!< Consider FlyCapture2::Property::valueA + } PropertyValue; + std::pair centerRoi(unsigned int size, unsigned int max_size, unsigned int step); + FlyCapture2::Property getProperty(FlyCapture2::PropertyType prop_type); + FlyCapture2::PropertyInfo getPropertyInfo(FlyCapture2::PropertyType prop_type); + void open(); + void setProperty(const FlyCapture2::PropertyType &prop_type, bool on, bool auto_on, float value, + PropertyValue prop_value = ABS_VALUE); + +protected: + FlyCapture2::Camera m_camera; //!< Pointer to each camera + FlyCapture2::PGRGuid m_guid; //!< Active camera guid + unsigned int m_index; //!< Active camera index + unsigned int m_numCameras; //!< Number of connected cameras + FlyCapture2::Image m_rawImage; //!< Image buffer + bool m_connected; //!< true if camera connected + bool m_capture; //!< true is capture started +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpForceTorqueAtiNetFTSensor.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpForceTorqueAtiNetFTSensor.h new file mode 100644 index 00000000..6f13a893 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpForceTorqueAtiNetFTSensor.h @@ -0,0 +1,165 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * ATI Force torque interface. + */ +#ifndef _vpForceTorqueAtiNetFTSensor_h_ +#define _vpForceTorqueAtiNetFTSensor_h_ + +#include + +#include + +#include +#include + +// Make vpForceTorqueAtiNetFTSensor available only if inet_ntop() used to +// communicate by UDP with the sensor through vpUDPClient is available; inet_ntop() +// is not supported on win XP +#ifdef VISP_HAVE_FUNC_INET_NTOP + +BEGIN_VISP_NAMESPACE +/*! + * \class vpForceTorqueAtiNetFTSensor + * + * \ingroup group_sensor_ft + * + * Interface for ATI force/torque sensor using [Net F/T](https://www.ati-ia.com/products/ft/ft_NetFT.aspx) over UDP. + * + * The Network Force/Torque (Net F/T) sensor system measures six components of force and torque (Fx, Fy, Fz, Tx, Ty, Tz). + * The Net F/T provides an EtherNet/IP communication interface and is compatible with standard Ethernet. The Net + * F/T system is available with any of ATI transducer models. The Net F/T's web browser interface allows for easy + * configuration and set up via the Ethernet connection present on all NetBox models. + * + * This class was tested with ATI Nano 43 F/T sensor connected to a NetBox. To use this class, you don't need to install + * any specific third-party. + * + * To use this class, connect an Ethernet cable to the NetBox. The default IP address of the Net F/T is: 192.168.1.1. + * The default Ethernet port is 49152. + * You can use your favorite web browser on http://192.168.1.1 to modify Net F/T sensor settings and select sensor + * calibration configuration. + * + * The following example shows how to use this class to get F/T measurements. + * \code + * #include + * + * #include + * + * int main(int argc, char **argv) + * { + * vpForceTorqueAtiNetFTSensor ati_net_ft("192.168.1.1", 49152); + * + * ati_net_ft.startStreaming(); + * ati_net_ft.bias(); + * + * while (1) { + * double t = vpTime::measureTimeMs(); + * if (ati_net_ft.waitForNewData()) { + * vpColVector ft = ati_net_ft.getForceTorque(); + * std::cout << "F/T: " << ft.t() << std::endl; + * } + * std::cout << "Loop time: " << vpTime::measureTimeMs() - t << " ms" << std::endl; + * } + * } + * \endcode + * + * It produces the following output: + * \code + * F/T: -0.00150018 0.0030764 -0.00791356 -8.22294e-06 4.18799e-05 1.078288e-05 + * Loop time: 0.03393554688 ms + * ... + * \endcode + * where 3 first values are forces Fx, Fy, Fz in N and the 3 last are torques Tx, Ty, Tz in Nm. +*/ +class VISP_EXPORT vpForceTorqueAtiNetFTSensor : public vpUDPClient +{ +public: + vpForceTorqueAtiNetFTSensor(); + vpForceTorqueAtiNetFTSensor(const std::string &hostname, int port); + virtual ~vpForceTorqueAtiNetFTSensor() VP_OVERRIDE; + + void bias(unsigned int n_counts = 50); + /*! + * \return Counts per force used to tranform measured data in N. + * \sa getCountsPerTorque(), getForceTorque() + */ + inline unsigned long getCountsPerForce() const { return m_counts_per_force; } + /*! + * \return Counts per torque used to tranform measured data in Nm. + * \sa getCountsPerForce(), getForceTorque() + */ + inline unsigned long getCountsPerTorque() const { return m_counts_per_torque; } + /*! + * \return Data counter. Each call to waitForNewData() will increment data counter when a new data is received. + */ + inline unsigned long getDataCounter() const { return m_data_count; } + /*! + * \return Scaling factor to transform measured data in user units (N and Nm). + * \sa getCountsPerForce(), getCountsPerTorque(), getForceTorque() + */ + inline unsigned long getScalingFactor() const { return m_scaling_factor; } + vpColVector getForceTorque() const; + /*! + * Set counts per force value. Default value is 1000000. + * \param counts : Counts per force. + * \sa setCountsPerTorque(), setScalingFactor() + */ + inline void setCountsPerForce(unsigned long counts) { m_counts_per_force = counts; } + /*! + * Set counts per torque value. Default value is 1000000000. + * \param counts : Counts per torque. + * \sa setCountsPerForce(), setScalingFactor() + */ + inline void setCountsPerTorque(unsigned long counts) { m_counts_per_torque = counts; } + /*! + * Set scaling factor. Default value is 1. + * \param scaling_factor : scaling factor. + * \sa setCountsPerForce(), setCountsPerTorque() + */ + inline void setScalingFactor(unsigned long scaling_factor) { m_scaling_factor = scaling_factor; } + bool startStreaming(); + void stopStreaming(); + + void unbias(); + bool waitForNewData(unsigned int timeout = 50); + +protected: + unsigned long m_counts_per_force; + unsigned long m_counts_per_torque; + unsigned long m_scaling_factor; + vpColVector m_ft_bias; + unsigned long m_data_count; + unsigned long m_data_count_prev; + vpColVector m_ft; + bool m_is_streaming_started; +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpForceTorqueAtiSensor.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpForceTorqueAtiSensor.h new file mode 100644 index 00000000..0f6695e0 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpForceTorqueAtiSensor.h @@ -0,0 +1,119 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * ATI Force torque interface. + */ +#ifndef _vpForceTorqueAtiSensor_h_ +#define _vpForceTorqueAtiSensor_h_ + +#include + +#if defined(VISP_HAVE_ATIDAQ) && defined(VISP_HAVE_COMEDI) + +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpForceTorqueAtiSensor + * + * \ingroup group_sensor_ft + * + * Interface for ATI force/torque sensor. This class works only under + * linux-like OS. It requires Comedi 3rd party. Installation instructions are + * provided here https://visp.inria.fr/3rd_comedi. + * + * Comedi is the linux control and measurement device interface. For more + * information see http://www.comedi.org. + * + * This class was tested with ATI Gamma 65-SI FT sensor connected to a + * National Instrument NI DAQmx PCI-6220 board. + * + * Synchronous F/T data acquisition is performed using getForceTorque(). The + * call to the function blocks until the whole acquisition has finished. + * + * The following example shows how to get single measures from an ATI F/T + * device each 10 ms (100 Hz). + * \code + * #include + * #include + * + * int main(int argc, char** argv) + * { + * vpForceTorqueAtiSensor ati; + * ati.setCalibrationFile("FT12345.cal"); + * ati.open(); + * ati.bias(); + * for(unsigned int i=0; i < 20; i++) { + * std::cout << "F/T: " << ati.getForceTorque().t() << std::endl; + * vpTime::wait(10); + * } + * ati.close(); + * #endif + * } + * \endcode +*/ +class VISP_EXPORT vpForceTorqueAtiSensor : public vpComedi +{ +public: + vpForceTorqueAtiSensor(); + virtual ~vpForceTorqueAtiSensor() VP_OVERRIDE; + + void bias(); + void close(); + + /*! + * Return the calibration file location specified using + * setCalibrationFile(). \sa setCalibrationFile() + */ + std::string getCalibrationFile() const { return m_calibfile; } + vpColVector getForceTorque() const; + std::string getForceUnits() const; + std::string getTorqueUnits() const; + + void open(); + + void setCalibrationFile(const std::string &calibfile, unsigned short index = 1); + void unbias(); + + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpForceTorqueAtiSensor &ati); + +protected: + std::string m_calibfile; //!< ATI calibration file FT*.cal + unsigned short m_index; //!< Index of calibration in file (default: 1) + unsigned short m_num_axes; //!< Number of axis or gages available from the sensor + unsigned short m_num_channels; //!< Number of channels available from the sensor + vpColVector m_sample_bias; //!< Sample value used for bias +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpForceTorqueIitSensor.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpForceTorqueIitSensor.h new file mode 100644 index 00000000..e7dd5ef0 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpForceTorqueIitSensor.h @@ -0,0 +1,191 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Wrapper over IIT force-torque sensor. + * + * Authors: + * Alexander Oliva + */ + +/*! + \file vpForceTorqueIitSensor.h + \brief Wrapper over IIT force-torque sensor. +*/ + +#ifndef vpForceTorqueIitSensor_h +#define vpForceTorqueIitSensor_h + +#include + +#if defined(VISP_HAVE_FT_IIT_SDK) && defined(VISP_HAVE_THREADS) + +#include +#include + +#include + +#include + +BEGIN_VISP_NAMESPACE + +/*! + \class vpForceTorqueIitSensor + \ingroup group_sensor_ft + + This class is a wrapper over six axis load measurement (forces and torques) from IIT (ISTITUTO ITALIANO DI TECNOLOGIA) + sell by [Alberobotics](https://alberobotics.it/). + + The following example shows how to use this class to stream force-torque data at 1kHz and print a measurement each + second. + \code + #include + + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #ifdef VISP_HAVE_FT_IIT_SDK + vpForceTorqueIitSensor iit_ft; + + if ( ! iit_ft.connected() ) { + std::cout << "Unable to connect to IIT force-torque sensor" << std::endl; + return EXIT_SUCCESS; + } + + iit_ft.bias(); + iit_ft.startStreaming(); + vpColVector ft; + for(int i=0; i < 10; i++) { + ft = iit_ft.getForceTorque(); + std::cout << ft.t() << std::endl; + vpTime::sleepMs(1000); + } + iit_ft.stopStreaming(); + + #else + std::cout << "ViSP is not build with IIT force-torque SDK support" << std::endl; + #endif + return EXIT_SUCCESS; + } + \endcode + + Once build, in the same folder as the binary you should find a configuration file named `configurationSettings.ini`. + - Such a file is provided in `$VISP_WS/modules/sensor/test/force-torque/configurationSettings.ini` with default + settings. + \include configurationSettings.ini + - To modify default settings you need first to identify your Ethernet link: + \code + $ ifconfig + enp0s31f6: flags=4163 mtu 1500 + inet 192.168.100.100 netmask 255.255.255.0 broadcast 192.168.100.255 + \endcode + - and then modify accordingly the following lines: + \verbatim + LOCAL_IFACE_IP = 192.168.100.100 ; Host Computer's local interface IP + LOCAL_IFACE = enp0s31f6 ; Host Computer's local interface name (it is required only in Linux) + \endverbatim + - The sensor default IP is set to `192.168.1.1`. Browsing to this address with Firefox allows to modify it's default + IP address. If the new sensor IP address is for example `192.168.100.10`, you need to modify the following line: + \verbatim + USE_DEFAULT_SETTINGS = false ; If "true", the library will use the + ; DEFAULT_SETTINGS. Note that the sensor + ; needs to be in the "DEF" IP position. + ; If "false", the library will use the + ; USER_SETTINGS to initialize the communication + ; with the sensor. + [USER_SETTINGS] ; USER SETTINGS FOR USE IN SINGLE_SENSOR_MODE when USE_DEFAULT_SETTINGS=false + USER_IP = 192.168.100.10 ; User Sensor IP + \endverbatim + + When running a binary that uses vpForceTorqueIitSensor class, + - if you experience the following error: + \code + $ cd $VISP_WS/modules/sensor + $ ./testForceTorqueIitSensor-simple + ./testForceTorqueIitSensor-simple: error while loading shared libraries: libftSensorLib.so.0.0.1: cannot open shared + object file: No such file or directory + \endcode + - it means that you need to add the location of the library in `LD_LIBRARY_PATH` environment variable. This could be + achieved running: + \code + $ export + LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$VISP_WS/3rdparty/FT_SDK_01_4/linux/ubuntu16.04/ftSensorLibReleaseExamples/library/bin/lin-x86_64/release + \endcode + where `$VISP_WS/3rdparty/FT_SDK_01_4` contains IIT SDK. + + To configure the sensor, you may access the sensor through the web interface using your favorite browser. + \image html vpForceTorqueIitSensor-ethernet.png + +*/ +class VISP_EXPORT vpForceTorqueIitSensor +{ +public: + vpForceTorqueIitSensor(); + ~vpForceTorqueIitSensor(); + + void bias(); + bool connected(int timeout_ms = 0) const; + vpColVector getForceTorque(bool filtered = false); + + void startStreaming(); + void stopStreaming(); + +protected: + void acquisitionLoop(); + void close(); + void join(); + +protected: + ftSensorLib m_ftLib; + int m_numSensorsInLib; + + vpColVector m_ft; + vpColVector m_ft_filt; + + ftSensorsConnected m_ftSensorsData {}; + + std::atomic m_acquisitionEnabled; + std::atomic m_dataValid; + bool m_connected; + + std::thread m_acquisitionThread; + std::chrono::time_point m_timeCur, m_timePrev; + + std::mutex m_mutex; + int m_warmupMilliseconds; +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpKinect.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpKinect.h new file mode 100644 index 00000000..7b5f2da4 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpKinect.h @@ -0,0 +1,182 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * API for using a Microsoft Kinect device + * Requires libfreenect as a third party library + */ + +#ifndef VP_KINECT_H +#define VP_KINECT_H + +#include +// Note that libfreenect needs libusb-1.0 and libpthread +#if defined(VISP_HAVE_LIBFREENECT_AND_DEPENDENCIES) && defined(VISP_HAVE_THREADS) + +#include +#include +#include + +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + + \class vpKinect + + \ingroup group_sensor_rgbd + + \brief Driver for the Kinect-1 device. + + To be enabled this class requires libfreenect 3rd party. Installation + instructions are provided here https://visp.inria.fr/3rd_freenect. + + The following example shows how to use this class to acquire data + (depth map and color image) from a Kinect. + + \code + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() { + #ifdef VISP_HAVE_LIBFREENECT_AND_DEPENDENCIES + // Init Kinect device + #ifdef VISP_HAVE_LIBFREENECT_OLD + // This is the way to initialize Freenect with an old version of libfreenect + // package under ubuntu lucid 10.04 + Freenect::Freenect freenect; + vpKinect * kinect = &freenect.createDevice(0); + #else + Freenect::Freenect freenect; + vpKinect * kinect = &freenect.createDevice(0); + #endif + kinect->start(); // Start acquisition thread + + // Set tilt angle + float angle = -5; + kinect->setTiltDegrees(angle); + + vpImage I(480,640); + vpImage Irgb(480,640); + vpImage dmap(480,640); + + // Acquisition loop + for (int i=0; i<100; i++) + { + kinect->getDepthMap(dmap,I); + kinect->getRGB(Irgb); + } + kinect->stop(); // Stop acquisition thread + #endif + return 0; + } + \endcode +*/ +class VISP_EXPORT vpKinect : public Freenect::FreenectDevice +{ + // private: + //#ifndef DOXYGEN_SHOULD_SKIP_THIS + // vpKinect(const vpKinect &); // Not implemented! + // vpKinect &operator=(const vpKinect &){ + // throw vpException(vpException::functionNotImplementedError,"Not + // implemented!"); return *this; + // } + //#endif + +public: + /*! + Depth map resolution. + */ + typedef enum + { + DMAP_LOW_RES, /*!< Depth map has a resolution of 320 by 240. */ + DMAP_MEDIUM_RES /*!< Depth map has a resolution of 640 by 480. */ + } vpDMResolution; + + vpKinect(freenect_context *ctx, int index); + virtual ~vpKinect(); + + void start(vpKinect::vpDMResolution res = DMAP_LOW_RES); + void stop(); + + bool getDepthMap(vpImage &map); + bool getDepthMap(vpImage &map, vpImage &Imap); + bool getRGB(vpImage &IRGB); + + inline void getIRCamParameters(vpCameraParameters &cam) const { cam = IRcam; } + inline void getRGBCamParameters(vpCameraParameters &cam) const { cam = RGBcam; } + inline void setIRCamParameters(const vpCameraParameters &cam) { IRcam = cam; } + inline void setRGBCamParameters(const vpCameraParameters &cam) { RGBcam = cam; } + + void warpRGBFrame(const vpImage &Irgb, const vpImage &Idepth, + vpImage &IrgbWarped); // warp the RGB image into + // the Depth camera frame + +private: + //! Instantiation of Freenect virtual functions + // Do not call directly even in child + void VideoCallback(void *rgb, uint32_t timestamp); + + // Do not call directly even in child + void DepthCallback(void *depth, uint32_t timestamp); + +private: + std::mutex m_rgb_mutex; + std::mutex m_depth_mutex; + + vpCameraParameters RGBcam, IRcam; // intrinsic parameters of the two cameras + vpHomogeneousMatrix rgbMir; // Transformation from IRcam coordinate frame to + // RGBcam coordinate frame. + vpHomogeneousMatrix irMrgb; // Transformation from RGBcam coordinate frame + // to IRcam coordinate frame . + vpDMResolution DMres; + unsigned int hd; // height of the depth map + unsigned int wd; // width of the depth map + + // Access protected by a mutex: + vpImage dmap; + vpImage IRGB; + bool m_new_rgb_frame; + bool m_new_depth_map; + bool m_new_depth_image; + unsigned int height; // height of the rgb image + unsigned int width; // width of the rgb image +}; +END_VISP_NAMESPACE +#endif + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpLaserScan.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpLaserScan.h new file mode 100644 index 00000000..231a8fed --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpLaserScan.h @@ -0,0 +1,127 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Laser scan data structure. + */ + +/*! + \file vpLaserScan.h + + \brief Implements a laser scan data structure. + +*/ + +#ifndef vpLaserScan_h +#define vpLaserScan_h + +#include +#include "visp3/sensor/vpScanPoint.h" + +#include + +BEGIN_VISP_NAMESPACE +/*! + + \class vpLaserScan + \ingroup group_sensor_laserscanner + + \brief Implements a laser scan data structure that contains + especially the list of scanned points that have been recorded for + this laser scan. + + Other data as the start/stop angle, the start/end timestamp are + also considered. +*/ +class /*VISP_EXPORT*/ vpLaserScan +{ +public: + /*! Default constructor that initialize all the internal variable to zero. + */ + vpLaserScan() + : listScanPoints(), startTimestamp(0), endTimestamp(0), measurementId(0), numSteps(0), startAngle(0), stopAngle(0), + numPoints(0) + { } + /*! Copy constructor. */ + vpLaserScan(const vpLaserScan &scan) + : listScanPoints(scan.listScanPoints), startTimestamp(0), endTimestamp(0), measurementId(0), numSteps(0), + startAngle(0), stopAngle(0), numPoints(0) + { + startTimestamp = scan.startTimestamp; + endTimestamp = scan.endTimestamp; + measurementId = scan.measurementId; + numSteps = scan.numSteps; + startAngle = scan.startAngle; + stopAngle = scan.stopAngle; + numPoints = scan.numPoints; + } + /*! Default destructor that does nothing. */ + virtual ~vpLaserScan() { } + /*! Add the scan point at the end of the list. */ + inline void addPoint(const vpScanPoint &p) { listScanPoints.push_back(p); } + /*! Drop the list of points. */ + inline void clear() { listScanPoints.clear(); } + /*! Get the list of points. */ + inline std::vector getScanPoints() { return listScanPoints; } + +#if VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11 + vpLaserScan &operator=(const vpLaserScan &) = default; +#endif + + /*! Specifies the id of former measurements and increases with every measurement. */ + inline void setMeasurementId(const unsigned short &id) { this->measurementId = id; } + /*! Start time of measurement. */ + inline void setStartTimestamp(const double &start_timestamp) { this->startTimestamp = start_timestamp; } + /*! End time of measurement. */ + inline void setEndTimestamp(const double &end_timestamp) { this->endTimestamp = end_timestamp; } + /*! Angular steps per scanner rotation. */ + inline void setNumSteps(const unsigned short &num_steps) { this->numSteps = num_steps; } + /*! Start angle of the measurement in angular steps. */ + inline void setStartAngle(const short &start_angle) { this->startAngle = start_angle; } + /*! Stop angle of the measurement in angular steps. */ + inline void setStopAngle(const short &stop_angle) { this->stopAngle = stop_angle; } + /*! Number of measured points of the measurement. */ + inline void setNumPoints(const unsigned short &num_points) { this->numPoints = num_points; } + /*! Return the measurement start time. */ + inline double getStartTimestamp() { return startTimestamp; } + /*! Return the measurement end time. */ + inline double getEndTimestamp() { return endTimestamp; } + +private: + std::vector listScanPoints; + double startTimestamp; + double endTimestamp; + unsigned short measurementId; + unsigned short numSteps; + short startAngle; + short stopAngle; + unsigned short numPoints; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpLaserScanner.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpLaserScanner.h new file mode 100644 index 00000000..41dfdd54 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpLaserScanner.h @@ -0,0 +1,76 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Generic laser scanner. + */ + +/*! + + \file vpLaserScanner.h + + \brief Implements a generic laser scanner. + */ + +#ifndef VP_LASER_SCANNER_H +#define VP_LASER_SCANNER_H + +#include + +BEGIN_VISP_NAMESPACE +/*! + + \class vpLaserScanner + \ingroup group_sensor_laserscanner + + \brief Class that defines a generic laser scanner. +*/ +class /*VISP_EXPORT*/ vpLaserScanner +{ +public: + /*! Default constructor that initialize all the internal variable to zero. + */ + vpLaserScanner() : ip("null"), port(0) { } + /*! Copy constructor. */ + vpLaserScanner(const vpLaserScanner &scanner) : ip(scanner.ip), port(scanner.port) { } + /*! Default destructor that does nothing. */ + virtual ~vpLaserScanner() { } + + /*! Set the Ethernet address of the laser. */ + void setIpAddress(std::string ip_address) { this->ip = ip_address; } + + /*! Set the communication port. */ + void setPort(int com_port) { this->port = com_port; } + +protected: + std::string ip; + int port; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpMocap.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpMocap.h new file mode 100644 index 00000000..a7eee409 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpMocap.h @@ -0,0 +1,107 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Generic motion capture. + */ +#ifndef vpMocap_h +#define vpMocap_h + +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpMocap + * \ingroup group_sensor_mocap + * Generic motion capture wrapper. +*/ +class VISP_EXPORT vpMocap +{ +public: + /*! + * Default constructor that turns off the verbose mode. + */ + vpMocap() : m_verbose(false), m_serverAddr() { } + /*! + * Destructor. + */ + virtual ~vpMocap() { } + + /*! + * Close connexion with the motion capture device. + */ + virtual void close() = 0; + + /*! + * Connect to the motion capture device which address is specified using setServerAddress(). + * + * \sa close(), setServerAddress() + */ + virtual bool connect() = 0; + + /*! + * Get the bodies pose. + * \param[out] bodies_pose : Map of bodies pose. + * \param[in] all_bodies : When true consider all the bodies, even those that are not visible and that may contain nan + * in their pose. \return true when success, false otherwise. + */ + virtual bool getBodiesPose(std::map &bodies_pose, bool all_bodies = false) = 0; + + /*! + * Get the pose of a specific body. + * \param[in] body_name : Name of the body to consider. + * \param[out] body_pose : Body pose. + * \return true when success, false otherwise. + */ + virtual bool getSpecificBodyPose(const std::string &body_name, vpHomogeneousMatrix &body_pose) = 0; + + /*! + * Set mocap server address. + * \param[in] serverAddr : Server address. + * + * \sa connect() + */ + virtual inline void setServerAddress(const std::string &serverAddr) { m_serverAddr = serverAddr; } + + /*! + * Enable or disable verbose mode. + * \param[in] verbose : When true enable verbose mode, otherwise disable verbose mode. + */ + virtual inline void setVerbose(bool verbose) { m_verbose = verbose; } + +protected: + bool m_verbose; + std::string m_serverAddr; +}; +END_VISP_NAMESPACE +#endif // vpMocap_h diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpMocapQualisys.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpMocapQualisys.h new file mode 100644 index 00000000..e4235020 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpMocapQualisys.h @@ -0,0 +1,71 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Motion capture using Qualisys device. + */ +#ifndef vpMocapQualisys_h +#define vpMocapQualisys_h + +#include + +#ifdef VISP_HAVE_QUALISYS + +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpMocapQualisys + * \ingroup group_sensor_mocap + * Qualisys motion capture wrapper. +*/ +class VISP_EXPORT vpMocapQualisys : public vpMocap +{ +public: + vpMocapQualisys(); + virtual ~vpMocapQualisys(); + + void close(); + bool connect(); + + bool getBodiesPose(std::map &bodies_pose, bool all_bodies = false); + bool getSpecificBodyPose(const std::string &body_name, vpHomogeneousMatrix &body_pose); + void setServerAddress(const std::string &serverAddr); + void setVerbose(bool verbose); + +private: + vpMocapQualisys(const vpMocapQualisys &); // noncopyable + vpMocapQualisys &operator=(const vpMocapQualisys &); // + + class vpMocapQualisysImpl; + vpMocapQualisysImpl *m_impl; +}; +END_VISP_NAMESPACE +#endif +#endif // vpMocapQualisys_h diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpMocapVicon.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpMocapVicon.h new file mode 100644 index 00000000..d41fb478 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpMocapVicon.h @@ -0,0 +1,72 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Motion capture using Vicon device. + */ +#ifndef vpMocapVicon_h +#define vpMocapVicon_h + +#include + +#ifdef VISP_HAVE_VICON + +#include + +BEGIN_VISP_NAMESPACE + +/*! + *\class vpMocapVicon + * \ingroup group_sensor_mocap + * Vicon motion capture wrapper. +*/ +class VISP_EXPORT vpMocapVicon : public vpMocap +{ +public: + vpMocapVicon(); + virtual ~vpMocapVicon(); + + void close(); + bool connect(); + + bool getBodiesPose(std::map &bodies_pose, bool all_bodies = false); + bool getSpecificBodyPose(const std::string &body_name, vpHomogeneousMatrix &body_pose); + void setServerAddress(const std::string &serverAddr); + void setVerbose(bool verbose); + +private: + vpMocapVicon(const vpMocapVicon &); // noncopyable + vpMocapVicon &operator=(const vpMocapVicon &); // + + class vpMocapViconImpl; + vpMocapViconImpl *m_impl; +}; +END_VISP_NAMESPACE +#endif +#endif // vpMocapVicon_h diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpOccipitalStructure.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpOccipitalStructure.h new file mode 100644 index 00000000..ce741fac --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpOccipitalStructure.h @@ -0,0 +1,369 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * libStructure interface. + */ + +#ifndef VP_OCCIPITAL_STRUCTURE_H +#define VP_OCCIPITAL_STRUCTURE_H + +#include + +#if defined(VISP_HAVE_OCCIPITAL_STRUCTURE) && (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) && defined(VISP_HAVE_THREADS) +#include +#include + +#include + +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_COMMON) +#include +#endif + +#include +#include +#include + +BEGIN_VISP_NAMESPACE + /*! + \class vpOccipitalStructure + + \ingroup group_sensor_rgbd + + This class provides a wrapper over the Occipital Structure SDK + library https://structure.io/developers. It allows to capture + data from the Occipital Structure Core camera. + + \note Supported devices for Occipital Structure SDK 0.9: + - Occipital Structure Core. + + The usage of vpOccipitalStructure class is enabled when libStructure 3rd party is + successfully installed. The following tutorials explain how to proceed: + - \ref tutorial-install-ubuntu + - \ref tutorial-install-win10-msvc16 + - \ref tutorial-install-osx-homebrew + + Moreover, if Point Cloud Library (PCL) 3rd party is installed, we also + propose interfaces to retrieve point cloud as pcl::PointCloud + or pcl::PointCloud data structures. + + \warning Notice that the usage of this class requires compiler and library + support for the ISO C++ 2011 standard. This support is enabled by default + in ViSP when supported by the compiler. Hereafter we give an example of a + CMakeLists.txt file that allows to build `sample-structure-core.cpp` that + uses vpOccipitalStructure class. + + \code + cmake_minimum_required(VERSION 3.10) + + project(sample) + + find_package(VISP REQUIRED) + include_directories(${VISP_INCLUDE_DIRS}) + + add_executable(sample-structure-core sample-structure-core.cpp) + target_link_libraries(sample-structure-core ${VISP_LIBRARIES}) + \endcode + + To acquire images from the Structure Core color camera and convert them into grey + level images, a good starting is to use the following code that corresponds to + the content of `sample-structure-core.cpp`: + + \code + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpOccipitalStructure sc; + ST::CaptureSessionSettings settings; + settings.source = ST::CaptureSessionSourceId::StructureCore; + settings.structureCore.visibleEnabled = true; + + sc.open(settings); + + vpImage I(sc.getHeight(vpOccipitalStructure::visible), sc.getWidth(vpOccipitalStructure::visible)); + #ifdef VISP_HAVE_X11 + vpDisplayX d(I); + #elif defined(VISP_HAVE_GDI) + vpDisplayGDI d(I); + #endif + + while (true) { + sc.acquire(I); + vpDisplay::display(I); + vpDisplay::flush(I); + if (vpDisplay::getClick(I, false)) + break; + } + return 0; + } + \endcode + + If you want to acquire color images, in the previous sample replace: + \code + vpImage I(sc.getHeight(vpOccipitalStructure::visible), sc.getWidth(vpOccipitalStructure::visible)); + \endcode + by + \code + vpImage I(sc.getHeight(vpOccipitalStructure::visible), sc.getWidth(vpOccipitalStructure::visible)); + \endcode + + If you are interested in the point cloud and if ViSP is build with PCL + support, you can start from the following example where we use PCL library to + visualize the point cloud + + \code + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpOccipitalStructure sc; + ST::CaptureSessionSettings settings; + settings.source = ST::CaptureSessionSourceId::StructureCore; + settings.structureCore.visibleEnabled = true; + settings.applyExpensiveCorrection = true; // Apply a correction and clean filter to the depth before streaming. + + sc.open(settings); + // Calling these 2 functions to set internal variables. + sc.getCameraParameters(vpOccipitalStructure::visible); + sc.getCameraParameters(vpOccipitalStructure::depth); + + pcl::PointCloud::Ptr pointcloud(new pcl::PointCloud); + + sc.acquire(nullptr, nullptr, nullptr, pointcloud); + + pcl::visualization::PCLVisualizer::Ptr viewer (new pcl::visualization::PCLVisualizer ("3D Viewer")); + pcl::visualization::PointCloudColorHandlerRGBField rgb(pointcloud); + viewer->setBackgroundColor(0, 0, 0); + viewer->initCameraParameters(); + viewer->setCameraPosition(0, 0, -0.5, 0, -1, 0); + + while (true) { + sc.acquire(nullptr, nullptr, nullptr, pointcloud); + + static bool update = false; + if (!update) { + viewer->addPointCloud (pointcloud, rgb, "sample cloud"); + viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "sample cloud"); + update = true; + } else { + viewer->updatePointCloud (pointcloud, rgb, "sample cloud"); + } + + viewer->spinOnce(30); + } + return 0; + } + \endcode + + References to `ST::CaptureSession` and `ST::CaptureSessionSettings` can be retrieved + but `sc.open()` must be called before: + \code + ST::CaptureSession &getCaptureSession(); + ST::CaptureSessionSettings &getCaptureSessionSettings(); + \endcode + + */ + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + struct SessionDelegate : ST::CaptureSessionDelegate +{ + std::mutex m_sampleLock; + std::condition_variable cv_sampleLock; + + ST::ColorFrame m_visibleFrame; + ST::DepthFrame m_depthFrame; + ST::InfraredFrame m_infraredFrame; + ST::AccelerometerEvent m_accelerometerEvent; + ST::GyroscopeEvent m_gyroscopeEvent; + ST::StructureCoreCameraType m_cameraType; + ST::CaptureSessionUSBVersion m_USBVersion; + std::string m_serialNumber; + + ~SessionDelegate() { } + + void captureSessionEventDidOccur(ST::CaptureSession *session, ST::CaptureSessionEventId event) VP_OVERRIDE + { + switch (event) { + case ST::CaptureSessionEventId::Booting: + break; + case ST::CaptureSessionEventId::Connected: + printf("Starting streams...\n"); + session->startStreaming(); + // The following wait function will let the capture session load correctly. + vpTime::wait(1000); + + // Getting details about capture session. + // (USB Version, Serial Number of the camera connected, Camera Monochorme/Color) + m_USBVersion = session->USBVersion(); + m_serialNumber = session->sensorInfo().serialNumber; + m_cameraType = session->getCameraType(); + break; + case ST::CaptureSessionEventId::Disconnected: + break; + case ST::CaptureSessionEventId::Error: + throw vpException(vpException::fatalError, "Capture session error"); + break; + default: + printf("Capture session event unhandled\n"); + } + } + + void captureSessionDidOutputSample(ST::CaptureSession *, const ST::CaptureSessionSample &sample) VP_OVERRIDE + { + // acquire sampleLock mutex. + std::lock_guard u(m_sampleLock); + + // Perform the modification needed on the shared variables. + if (sample.visibleFrame.isValid()) + m_visibleFrame = sample.visibleFrame; + + if (sample.depthFrame.isValid()) + m_depthFrame = sample.depthFrame; + + if (sample.infraredFrame.isValid()) + m_infraredFrame = sample.infraredFrame; + + if (sample.type == ST::CaptureSessionSample::Type::AccelerometerEvent) + m_accelerometerEvent = sample.accelerometerEvent; + + if (sample.type == ST::CaptureSessionSample::Type::GyroscopeEvent) + m_gyroscopeEvent = sample.gyroscopeEvent; + + // If any thread is waiting on `cv_sampleLock`, the following instruction will unblock it. + // In our case, `open()` and `acquire()` will be blocked on `cv_sampleLock`. + cv_sampleLock.notify_one(); + } +}; +#endif // DOXYGEN_SHOULD_SKIP_THIS + +class VISP_EXPORT vpOccipitalStructure +{ +public: + typedef enum + { + visible, //!< Visible stream + depth, //!< Depth stream + infrared, //!< Infrared stream + imu //!< IMU stream + } vpOccipitalStructureStream; + + vpOccipitalStructure(); + ~vpOccipitalStructure(); + + void acquire(vpImage &gray, bool undistorted = false, double *ts = nullptr); + void acquire(vpImage &rgb, bool undistorted = false, double *ts = nullptr); + + void acquire(vpImage *rgb, vpImage *depth, vpColVector *acceleration_data = nullptr, + vpColVector *gyroscope_data = nullptr, bool undistorted = false, double *ts = nullptr); + void acquire(vpImage *gray, vpImage *depth, vpColVector *acceleration_data = nullptr, + vpColVector *gyroscope_data = nullptr, bool undistorted = false, double *ts = nullptr); + + void acquire(unsigned char *const data_image, unsigned char *const data_depth, + std::vector *const data_pointCloud = nullptr, unsigned char *const data_infrared = nullptr, + vpColVector *acceleration_data = nullptr, vpColVector *gyroscope_data = nullptr, bool undistorted = true, + double *ts = nullptr); + +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_COMMON) + void acquire(unsigned char *const data_image, unsigned char *const data_depth, + std::vector *const data_pointCloud, pcl::PointCloud::Ptr &pointcloud, + unsigned char *const data_infrared = nullptr, vpColVector *acceleration_data = nullptr, + vpColVector *gyroscope_data = nullptr, bool undistorted = true, double *ts = nullptr); + void acquire(unsigned char *const data_image, unsigned char *const data_depth, + std::vector *const data_pointCloud, pcl::PointCloud::Ptr &pointcloud, + unsigned char *const data_infrared = nullptr, vpColVector *acceleration_data = nullptr, + vpColVector *gyroscope_data = nullptr, bool undistorted = true, double *ts = nullptr); +#endif + + void getIMUVelocity(vpColVector *imu_vel, double *ts); + void getIMUAcceleration(vpColVector *imu_acc, double *ts); + void getIMUData(vpColVector *imu_vel, vpColVector *imu_acc, double *ts = nullptr); + + bool open(const ST::CaptureSessionSettings &settings); + void close(); + + /*! + Get camera type: Color or Monochrome. + */ + ST::StructureCoreCameraType getCameraType() const { return m_delegate.m_cameraType; } + + ST::CaptureSessionUSBVersion getUSBVersion() const { return m_delegate.m_USBVersion; } + std::string getSerialNumber() const { return m_delegate.m_serialNumber; } + ST::CaptureSession &getCaptureSession() { return m_captureSession; } + ST::CaptureSessionSettings &getCaptureSessionSettings() { return m_captureSessionSettings; } + + unsigned int getWidth(vpOccipitalStructureStream stream_type); + unsigned int getHeight(vpOccipitalStructureStream stream_type); + + // Returns depth in millimeters at (x,y) if it exists, NAN otherwise. + float getDepth(int x, int y); + + vpPoint unprojectPoint(int row, int col); + + vpHomogeneousMatrix getTransform(const vpOccipitalStructureStream from, const vpOccipitalStructureStream to); + + ST::Intrinsics getIntrinsics(const vpOccipitalStructureStream stream_type) const; + + vpCameraParameters getCameraParameters( + const vpOccipitalStructureStream stream_type, + vpCameraParameters::vpCameraParametersProjType type = vpCameraParameters::perspectiveProjWithoutDistortion); + + void saveDepthImageAsPointCloudMesh(std::string &filename); + +protected: + bool m_init; + float m_invalidDepthValue; + float m_maxZ; + + ST::CaptureSession m_captureSession; + ST::CaptureSessionSettings m_captureSessionSettings; + SessionDelegate m_delegate; + vpCameraParameters m_visible_camera_parameters, m_depth_camera_parameters; + + void getPointcloud(std::vector &pointcloud); +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_COMMON) + void getPointcloud(pcl::PointCloud::Ptr &pointcloud); + void getColoredPointcloud(pcl::PointCloud::Ptr &pointcloud); +#endif +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpPylonFactory.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpPylonFactory.h new file mode 100644 index 00000000..56e9e596 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpPylonFactory.h @@ -0,0 +1,92 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: Factory class used to create vpPylonGrabber instances. + * + * Authors: + * Wenfeng CAI + */ + +/*! + \file vpPylonFactory.h + \brief Description: Factory class used to create vpPylonGrabber + instances. +*/ + +#ifndef _vpPylonFactory_h_ +#define _vpPylonFactory_h_ + +#include + +#include + +#ifdef VISP_HAVE_PYLON + +BEGIN_VISP_NAMESPACE +/*! + \brief Factory singleton class to create vpPylonGrabber subclass + instances. + + \ingroup group_sensor_camera + + Use vpPylonFactory::instance() to get the singleton instance. This + class can also help to initialize and terminate pylon runtime system. + + Example code. + \code + vpPylonFactory &factory = vpPylonFactory::instance(); + vpPylonGrabber *g = factory.createPylonGrabber(vpPylonFactory::BASLER_GIGE); + \endcode +*/ +class VISP_EXPORT vpPylonFactory +{ +public: + static vpPylonFactory &instance(); + + /*! Device class of cameras. + */ + enum DeviceClass + { + BASLER_GIGE, //!< Basler GigE camera. + BASLER_USB //!< Basler USB camera. + }; + + vpPylonGrabber *createPylonGrabber(DeviceClass dev_class); + +private: + //! Default constructor. + vpPylonFactory() { } + vpPylonFactory(vpPylonFactory const &); + void operator=(vpPylonFactory const &); + + Pylon::PylonAutoInitTerm m_autoInitTerm; //!< Auto initialize and terminate object for pylon SDK. +}; +END_VISP_NAMESPACE +#endif // #ifdef VISP_HAVE_PYLON +#endif // #ifndef _vpPylonFactory_h_ diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpPylonGrabber.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpPylonGrabber.h new file mode 100644 index 00000000..fcda80fc --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpPylonGrabber.h @@ -0,0 +1,395 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: Classes allow to grab images from a Basler camera using + * Pylon SDK. + * + * Authors: + * Wenfeng CAI + */ + +/*! + \file vpPylonGrabber.h + \brief Wrapper over Basler Pylon SDK to capture images from Basler + cameras. +*/ + +#ifndef _vpPylonGrabber_h_ +#define _vpPylonGrabber_h_ + +#include +#include +#include + +#ifdef VISP_HAVE_PYLON + +// Work around: /usr/X11R6/include/X11/X.h:115:30: note: expanded from macro +// 'None' +#ifdef None +#undef None +#endif +// Work around: /usr/X11R6/include/X11/Xlib.h:83:16: note: expanded from +// macro 'Status' +#ifdef Status +#undef Status +#endif + +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpPylonGrabber + \ingroup group_sensor_camera + + Allows to grab images from a Basler camera using Pylon SDK. This is an + abstract base class which can't be instantiated. Use vpPylonFactory + functions to create class instances instead. Currently only GigE and + USB3 cameras are supported. + + To get a vpPylonGrabber, use the following code. + \code + vpPylonFactory &factory = vpPylonFactory::instance(); + vpPylonGrabber *g = factory.createPylonGrabber(vpPylonFactory::BASLER_GIGE); + \endcode + + To use this class install first Pylon SDK\n + https://www.baslerweb.com/en/support/downloads/software-downloads/#type=pylonsoftware;version=all + \n Installation instructions included. + + Once installed, configure ViSP using cmake to detect Pylon SDK and + build ViSP to include Pylon SDK support. + + This class was tested under Ubuntu with pylon 5.0.9 and the following + cameras: + - acA640-90gm + - acA1600-60gm + + This class is inspired by vpFlyCaptureGrabber with much simplified methods. +*/ +class VISP_EXPORT vpPylonGrabber : public vpFrameGrabber +{ +public: + /*! + Default destructor. + */ + virtual ~vpPylonGrabber() { } + + /*! Valid values for user set names. + */ + enum UserSetName + { + USERSET_DEFAULT, //!< The default user set. + USERSET_USERSET1, //!< User set 1. + USERSET_USERSET2, //!< User set 2. + USERSET_USERSET3, //!< User set 3. + USERSET_UNKNOWN //!< User set not supported. + }; + + /*! + Acquire a gray level image from the active camera. + + \param I : Image data structure (8 bits image). + */ + virtual void acquire(vpImage &I) = 0; + /*! + Acquire a color image from the active camera. + + \param I : Image data structure (RGBa image). + */ + virtual void acquire(vpImage &I) = 0; + + /*! + \brief Stop active camera capturing images and disconnect the active + camera. + + If you want to use again this camera, you may call + setCameraIndex(const unsigned int &) and open(vpImage + &) or open(vpImage &) to connect again the camera. + */ + virtual void close() = 0; + /*! + Connect the active camera. + + \sa disconnect() + */ + virtual void connect() = 0; + /*! + Disconnect the active camera. + + \sa connect() + */ + virtual void disconnect() = 0; + + /*! + Return blacklevel value in % or raw value. + If the camera doesn't support blacklevel property, return an exception. + + According to SFNC (standard feature naming convention) of GenICam + standard, Black level is used instead of brightness. + + See "Terminology Changes" section of the page: + https://www.ptgrey.com/kb/11020?countryid=237 + \sa setBlackLevel() + */ + virtual float getBlackLevel() = 0; + /*! + Print to the output stream active camera information (serial number, + camera model, camera vendor, sensor, resolution, firmware version, + ...). + */ + virtual std::ostream &getCameraInfo(std::ostream &os) = 0; + /*! + Return the handler to the active camera or nullptr if the camera is not + connected. This function was designed to provide a direct access to + the Pylon SDK to get access to advanced functionalities that are not + implemented in this class. + */ + virtual Pylon::CInstantCamera *getCameraHandler() = 0; + /*! Return the index of the active camera. */ + virtual unsigned int getCameraIndex() const = 0; + /*! + Return the serial id of a camera with \e index. + \param index : Camera index. + + \sa setCameraSerial() + */ + virtual std::string getCameraSerial(unsigned int index) = 0; + /*! + Return exposure value in ms. + If the camera doesn't support exposure property, return an exception. + + According to SFNC (standard feature naming convention) of GenICam + standard, Exposure is used and deprecates shutter. + + See "Terminology Changes" section of the page: + https://www.ptgrey.com/kb/11020?countryid=237 + \sa setExposure() + */ + virtual float getExposure() = 0; + /*! + Return camera capture framerate. + If the camera doesn't support framerate property, return an exception. + + \sa setFrameRate() + */ + virtual float getFrameRate() = 0; + /*! + Return camera gain value in dB or raw value. + If the camera doesn't support gain property, return an exception. + + \sa setGain() + */ + virtual float getGain() = 0; + /*! + \brief Get the number of cameras of specific subclasses. GigE, USB, + etc. + \return Return the number of cameras connected on the bus GigE or USB. + */ + virtual unsigned int getNumCameras() = 0; + /*! + Return gamma correction value. + If the camera doesn't support gamma property, return an exception. + + \sa setGamma() + */ + virtual float getGamma() = 0; + /*! + \brief Loads the selected configuration into the camera's volatile + memory and makes it the active configuration set. + + \param user_set See vpPylonGrabber::UserSetName for valid values. + \return true for finished, false otherwise. + + \sa saveUserSet() + */ + virtual bool loadUserSet(UserSetName user_set) = 0; + /*! + \brief Gets the configuration set being used as the default startup + set. + + \sa setUserSetDefault() + */ + virtual UserSetName getUserSetDefault() = 0; + + //! Return true if the camera is connected. + virtual bool isConnected() const = 0; + //! Return true if the camera capture is started. + virtual bool isCaptureStarted() const = 0; + /*! + Connect to the active camera, start capture and retrieve an image. + \param I : Captured image. + */ + virtual void open(vpImage &I) = 0; + /*! + Connect to the active camera, start capture and retrieve an image. + \param I : Captured image. + */ + virtual void open(vpImage &I) = 0; + + /*! + Operator that allows to capture a grey level image. + \param I : The captured image. + */ + virtual vpPylonGrabber &operator>>(vpImage &I) = 0; + /*! + Operator that allows to capture a color image. + \param I : The captured image. + */ + virtual vpPylonGrabber &operator>>(vpImage &I) = 0; + + /*! + Set camera blacklevel mode and parameter. + \param blacklevel_value : This is the level of black in an image. A + high blacklevel will result in a low amount of black in the image. + + \return The measured blacklevel after applying the new setting. + + According to SFNC (standard feature naming convention) of GenICam + standard, Black level is used instead of brightness. + + See "Terminology Changes" section of the page: + https://www.ptgrey.com/kb/11020?countryid=237 + \sa getBlackLevel() + */ + virtual float setBlackLevel(float blacklevel_value) = 0; + /*! + If multiples cameras are connected on the bus, select the camera to + dial with. + + \param index : Current camera index, a value comprised between 0 (the + first camera found on the bus) and the number of cameras found and + returned by getNumCameras() minus 1. + + If two cameras are connected on the bus, setting \e index to one + allows to communicate with the second one. This identifier is not + unique. That is why, it is also possible to select a camera by its + serial number, which is unique using setCameraSerial(). + + \exception vpException::badValue : If the index is greater or equal to + the number of cameras connected to the bus. + */ + virtual void setCameraIndex(unsigned int index) = 0; + /*! + Set the current camera from its unique serial identifier. + \param serial : Camera serial string. + + \sa getCameraSerial() + */ + virtual void setCameraSerial(const std::string &serial) = 0; + /*! + Set camera exposure mode and parameter. + + \param exposure_on : If true turn exposure on, otherwise turn off. + \param exposure_auto : If true set auto exposure, if false set manual + exposure applying \e exposure_value parameter. + \param exposure_value : This is the exposure value in ms. + + \return The measured exposure time in ms after applying the new setting. + + According to SFNC (standard feature naming convention) of GenICam + standard, Exposure is used and deprecates shutter. + + See "Terminology Changes" section of the page: + https://www.ptgrey.com/kb/11020?countryid=237 + \sa getExposure() + */ + virtual float setExposure(bool exposure_on, bool exposure_auto, float exposure_value) = 0; + /*! + Set camera gain mode and value. + \param gain_auto : If true set auto gain, if false set manual gain + applying \e gain_value parameter. + \param gain_value : The amount of amplification that is applied to a + pixel in manual mode. An increase in gain can result in an increase in + noise. + + \return The measured gain after applying the new setting. + + \sa getGain() + */ + virtual float setGain(bool gain_auto, float gain_value) = 0; + /*! + Set camera frame rate. + \param frame_rate : Camera frame rate (fps) to set. + \return The camera current framerate. + + \sa getFramerate() + */ + virtual float setFrameRate(float frame_rate) = 0; + /*! + Set camera gamma correction mode and parameter. + + \param gamma_on : If true turn gamma correction on, otherwise turn off. + \param gamma_value : Parameter used to perform gamma correction of + pixel intensity. + + \return The measured gamma correction value after applying the new + setting. + + \sa getGamma() + */ + virtual float setGamma(bool gamma_on, float gamma_value = 1) = 0; + /*! + \brief Saves the current active configuration set into the selected + user set. + + \param user_set See vpPylonGrabber::UserSetName for valid values. + \param set_default Whether to set the configuration set to be used as + the default startup set. + \return true for finished, false otherwise. + + \sa loadUserSet() + */ + virtual bool saveUserSet(UserSetName user_set, bool set_default = false) = 0; + /*! + \brief Sets the configuration set to be used as the default startup + set. + + \param user_set See vpPylonGrabber::UserSetName for valid values. + \return true for finished, false otherwise. + + \sa getUserSetDefault() + */ + virtual bool setUserSetDefault(UserSetName user_set) = 0; + + /*! + Start active camera capturing images. + + \sa stopCapture() + */ + virtual void startCapture() = 0; + /*! + Stop active camera capturing images. + + \sa startCapture() + */ + virtual void stopCapture() = 0; +}; +END_VISP_NAMESPACE +#endif // #ifdef VISP_HAVE_PYLON +#endif // #ifndef _vpPylonGrabber_h_ diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpRealSense2.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpRealSense2.h new file mode 100644 index 00000000..1c94372d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpRealSense2.h @@ -0,0 +1,432 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * librealSense2 interface. + */ + +#ifndef _vpRealSense2_h_ +#define _vpRealSense2_h_ + +#include + +#if defined(VISP_HAVE_REALSENSE2) && (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + +#include +#include + +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_COMMON) +#include +#endif + +#include +#include + +BEGIN_VISP_NAMESPACE + /*! + \class vpRealSense2 + + \ingroup group_sensor_rgbd + + This class provides a lightweight wrapper over the Intel librealsense2 + library https://github.com/IntelRealSense/librealsense. It allows to capture + data from the Intel RealSense cameras. + + \note Supported devices for Intel® RealSense™ SDK 2.0: + - Intel® RealSense™ Camera D400-Series + - Intel® RealSense™ Developer Kit SR300 + - Intel® RealSense™ Tracking Camera T265 (librealsense2 version > 2.31.0) + + The usage of vpRealSense2 class is enabled when librealsense2 3rd party is + successfully installed. + + Moreover, if Point Cloud Library (PCL) 3rd party is installed, we also + propose interfaces to retrieve point cloud as pcl::PointCloud + or pcl::PointCloud data structures. + + \warning Notice that the usage of this class requires compiler and library + support for the ISO C++ 2011 standard. This support is enabled by default + in ViSP when supported by the compiler. Hereafter we give an example of a + CMakeLists.txt file that allows to build sample-realsense.cpp that + uses vpRealSense2 class. + + \code + 10) + + project(sample) + + find_package(VISP REQUIRED) + include_directories(${VISP_INCLUDE_DIRS}) + + add_executable(sample-realsense sample-realsense.cpp) + target_link_libraries(sample-realsense ${VISP_LIBRARIES}) + \endcode + + To acquire images from the RealSense color camera and convert them into grey + level images, a good starting is to use the following code that corresponds to + the content of sample-realsense.cpp: + + \code + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpRealSense2 rs; + rs.open(); + + vpImage I(rs.getIntrinsics(RS2_STREAM_COLOR).height, rs.getIntrinsics(RS2_STREAM_COLOR).width); + #ifdef VISP_HAVE_X11 + vpDisplayX d(I); + #elif defined(VISP_HAVE_GDI) + vpDisplayGDI d(I); + #endif + + while (true) { + rs.acquire(I); + vpDisplay::display(I); + vpDisplay::flush(I); + if (vpDisplay::getClick(I, false)) + break; + } + return 0; + } + \endcode + + If you want to acquire color images, in the previous sample replace: + \code + vpImage I(rs.getIntrinsics(RS2_STREAM_COLOR).height, rs.getIntrinsics(RS2_STREAM_COLOR).width); + \endcode + by + \code + vpImage I(rs.getIntrinsics(RS2_STREAM_COLOR).height, rs.getIntrinsics(RS2_STREAM_COLOR).width); + \endcode + + If you are interested in the point cloud and if ViSP is build with PCL + support, you can start from the following example where we use PCL library to + visualize the point cloud + + \code + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpRealSense2 rs; + rs.open(); + + pcl::PointCloud::Ptr pointcloud(new pcl::PointCloud); + + rs.acquire(nullptr, nullptr, nullptr, pointcloud); + + pcl::visualization::PCLVisualizer::Ptr viewer (new pcl::visualization::PCLVisualizer ("3D Viewer")); + pcl::visualization::PointCloudColorHandlerRGBField rgb(pointcloud); + viewer->setBackgroundColor(0, 0, 0); + viewer->initCameraParameters(); + viewer->setCameraPosition(0, 0, -0.5, 0, -1, 0); + + while (true) { + rs.acquire(nullptr, nullptr, nullptr, pointcloud); + + static bool update = false; + if (!update) { + viewer->addPointCloud (pointcloud, rgb, "sample cloud"); + viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "sample cloud"); + update = true; + } else { + viewer->updatePointCloud (pointcloud, rgb, "sample cloud"); + } + + viewer->spinOnce(30); + } + return 0; + } + \endcode + + If you want to change the default stream parameters, refer to the + librealsense2 `rs2::config` documentation. The following code allows to + capture the color stream in 1920x1080 at 30 Hz: + + \code + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpRealSense2 rs; + rs2::config config; + config.enable_stream(RS2_STREAM_COLOR, 1920, 1080, RS2_FORMAT_RGBA8, 30); + config.enable_stream(RS2_STREAM_DEPTH, 640, 480, RS2_FORMAT_Z16, 30); + config.enable_stream(RS2_STREAM_INFRARED, 640, 480, RS2_FORMAT_Y8, 30); + rs.open(config); + + vpImage Ic(rs.getIntrinsics(RS2_STREAM_COLOR).height, rs.getIntrinsics(RS2_STREAM_COLOR).width); + vpImage Ii(rs.getIntrinsics(RS2_STREAM_INFRARED).height, + rs.getIntrinsics(RS2_STREAM_INFRARED).width); + + #ifdef VISP_HAVE_X11 + vpDisplayX dc(Ic, 0, 0, "Color"); + vpDisplayX di(Ii, 100, 100, "Infrared"); + #elif defined(VISP_HAVE_GDI) + vpDisplayGDI dc(Ic, 0, 0, "Color"); + vpDisplayGDI di(Ii, 100, 100, "Infrared"); + #endif + + while (true) { + rs.acquire((unsigned char *) Ic.bitmap, nullptr, nullptr, Ii.bitmap); + vpDisplay::display(Ic); + vpDisplay::display(Ii); + vpDisplay::flush(Ic); + vpDisplay::flush(Ii); + if (vpDisplay::getClick(Ic, false) || vpDisplay::getClick(Ii, false)) + break; + } + return 0; + } + \endcode + + This other example shows how to get depth stream aligned on color stream: + \code + #include + #include + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpRealSense2 rs; + rs2::config config; + config.enable_stream(RS2_STREAM_COLOR, 640, 480, RS2_FORMAT_RGBA8, 30); + config.enable_stream(RS2_STREAM_DEPTH, 640, 480, RS2_FORMAT_Z16, 30); + config.enable_stream(RS2_STREAM_INFRARED, 640, 480, RS2_FORMAT_Y8, 30); + rs.open(config); + + vpImage Ic(rs.getIntrinsics(RS2_STREAM_COLOR).height, rs.getIntrinsics(RS2_STREAM_COLOR).width); + vpImage Id_raw(rs.getIntrinsics(RS2_STREAM_DEPTH).height, rs.getIntrinsics(RS2_STREAM_DEPTH).width); + vpImage Id(rs.getIntrinsics(RS2_STREAM_DEPTH).height, rs.getIntrinsics(RS2_STREAM_DEPTH).width); + + #ifdef VISP_HAVE_X11 + vpDisplayX dc(Ic, 0, 0, "Color"); + vpDisplayX dd(Id, 100, 100, "Depth aligned to color"); + #elif defined(VISP_HAVE_GDI) + vpDisplayGDI dc(Ic, 0, 0, "Color"); + vpDisplayGDI dd(Id, 100, 100, "Depth aligned to color"); + #endif + + rs2::align align_to(RS2_STREAM_COLOR); + while (true) { + rs.acquire((unsigned char *) Ic.bitmap, (unsigned char *) Id_raw.bitmap, nullptr, nullptr, &align_to); + vpImageConvert::createDepthHistogram(Id_raw, Id); + vpDisplay::display(Ic); + vpDisplay::display(Id); + vpDisplay::flush(Ic); + vpDisplay::flush(Id); + if (vpDisplay::getClick(Ic, false) || vpDisplay::getClick(Id, false)) + break; + } + return 0; + } + \endcode + + References to `rs2::pipeline_profile` and `rs2::pipeline` can be retrieved + with (`rs.open() must be called before`): + \code + rs2::pipeline_profile& profile = rs.getPipelineProfile(); + rs2::pipeline& pipeline = rs.getPipeline(); + \endcode + + Information about the sensor can be printed with: + \code + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpRealSense2 rs; + rs.open(); + std::cout << "RealSense sensor characteristics: \n" << rs << std::endl; + + return 0; + } + \endcode + + It is also possible to use several RealSense sensors at the same time. In that case, you need to create a + vpRealSense2 object for each device and use vpRealSense2::enable_device(const std::string &serial_number) + to select the device explicitly by its serial number. An example is provided + in tutorial-grabber-multiple-realsense.cpp. + + \note Additional information can be found in the + [librealsense wiki](https://github.com/IntelRealSense/librealsense/wiki/). +*/ +class VISP_EXPORT vpRealSense2 +{ +public: + vpRealSense2(); + virtual ~vpRealSense2(); + + void acquire(vpImage &grey, double *ts = nullptr); + void acquire(vpImage &color, double *ts = nullptr); + void acquire(unsigned char *const data_image, unsigned char *const data_depth, + std::vector *const data_pointCloud, unsigned char *const data_infrared, + rs2::align *const align_to = nullptr, double *ts = nullptr); + void acquire(unsigned char *const data_image, unsigned char *const data_depth, + std::vector *const data_pointCloud, unsigned char *const data_infrared1, + unsigned char *const data_infrared2, rs2::align *const align_to, double *ts = nullptr); +#if (RS2_API_VERSION > ((2 * 10000) + (31 * 100) + 0)) + void acquire(vpImage *left, vpImage *right, double *ts = nullptr); + void acquire(vpImage *left, vpImage *right, vpHomogeneousMatrix *cMw, + vpColVector *odo_vel, vpColVector *odo_acc, unsigned int *confidence = nullptr, double *ts = nullptr); + void acquire(vpImage *left, vpImage *right, vpHomogeneousMatrix *cMw, + vpColVector *odo_vel, vpColVector *odo_acc, vpColVector *imu_vel, vpColVector *imu_acc, + unsigned int *tracker_confidence = nullptr, double *ts = nullptr); +#endif + +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_COMMON) + void acquire(unsigned char *const data_image, unsigned char *const data_depth, + std::vector *const data_pointCloud, pcl::PointCloud::Ptr &pointcloud, + unsigned char *const data_infrared = nullptr, rs2::align *const align_to = nullptr, double *ts = nullptr); + void acquire(unsigned char *const data_image, unsigned char *const data_depth, + std::vector *const data_pointCloud, pcl::PointCloud::Ptr &pointcloud, + unsigned char *const data_infrared1, unsigned char *const data_infrared2, rs2::align *const align_to, + double *ts = nullptr); + + void acquire(unsigned char *const data_image, unsigned char *const data_depth, + std::vector *const data_pointCloud, pcl::PointCloud::Ptr &pointcloud, + unsigned char *const data_infrared = nullptr, rs2::align *const align_to = nullptr, double *ts = nullptr); + void acquire(unsigned char *const data_image, unsigned char *const data_depth, + std::vector *const data_pointCloud, pcl::PointCloud::Ptr &pointcloud, + unsigned char *const data_infrared1, unsigned char *const data_infrared2, rs2::align *const align_to, + double *ts = nullptr); +#endif + + void close(); + + vpCameraParameters getCameraParameters( + const rs2_stream &stream, + vpCameraParameters::vpCameraParametersProjType type = vpCameraParameters::perspectiveProjWithDistortion, + int index = -1) const; + + float getDepthScale(); + +#if (RS2_API_VERSION > ((2 * 10000) + (31 * 100) + 0)) + void getIMUAcceleration(vpColVector *imu_acc, double *ts); + void getIMUData(vpColVector *imu_vel, vpColVector *imu_acc, double *ts); + void getIMUVelocity(vpColVector *imu_vel, double *ts); +#endif + + rs2_intrinsics getIntrinsics(const rs2_stream &stream, int index = -1) const; + + //! Get the value used when the pixel value (u, v) in the depth map is + //! invalid for the point cloud. For instance, the Point Cloud Library (PCL) + //! uses NAN values for points where the depth is invalid. + inline float getInvalidDepthValue() const { return m_invalidDepthValue; } + + //! Get the maximum Z value (used to discard bad reconstructed depth for + //! pointcloud). + inline float getMaxZ() const { return m_max_Z; } + +#if (RS2_API_VERSION > ((2 * 10000) + (31 * 100) + 0)) + unsigned int getOdometryData(vpHomogeneousMatrix *cMw, vpColVector *odo_vel, vpColVector *odo_acc, double *ts = nullptr); +#endif + + //! Get a reference to `rs2::pipeline`. + rs2::pipeline &getPipeline() { return m_pipe; } + + //! Get a reference to `rs2::pipeline_profile`. + rs2::pipeline_profile &getPipelineProfile() { return m_pipelineProfile; } + + std::string getProductLine(); + + std::string getSensorInfo(); + + vpHomogeneousMatrix getTransformation(const rs2_stream &from, const rs2_stream &to, int from_index = -1) const; + + bool open(const rs2::config &cfg = rs2::config()); + bool open(const rs2::config &cfg, std::function &callback); + + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpRealSense2 &rs); + + //! Set the value used when the pixel value (u, v) in the depth map is + //! invalid for the point cloud. For instance, the Point Cloud Library (PCL) + //! uses NAN values for points where the depth is invalid. + inline void setInvalidDepthValue(float value) { m_invalidDepthValue = value; } + + //! Set the maximum Z value (used to discard bad reconstructed depth for + //! pointcloud). + inline void setMaxZ(const float maxZ) { m_max_Z = maxZ; } + +protected: + float m_depthScale; + float m_invalidDepthValue; + float m_max_Z; + rs2::pipeline m_pipe; + rs2::pipeline_profile m_pipelineProfile; + rs2::pointcloud m_pointcloud; + rs2::points m_points; + vpTranslationVector m_pos; + vpQuaternionVector m_quat; + vpRotationMatrix m_rot; + std::string m_product_line; + bool m_init; + + void getColorFrame(const rs2::frame &frame, vpImage &color); + void getGreyFrame(const rs2::frame &frame, vpImage &grey); + void getNativeFrameData(const rs2::frame &frame, unsigned char *const data); + void getPointcloud(const rs2::depth_frame &depth_frame, std::vector &pointcloud); +#if defined(VISP_HAVE_PCL) && defined(VISP_HAVE_PCL_COMMON) + void getPointcloud(const rs2::depth_frame &depth_frame, pcl::PointCloud::Ptr &pointcloud); + void getPointcloud(const rs2::depth_frame &depth_frame, const rs2::frame &color_frame, + pcl::PointCloud::Ptr &pointcloud); +#endif +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpScanPoint.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpScanPoint.h new file mode 100644 index 00000000..ce0d2f83 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpScanPoint.h @@ -0,0 +1,253 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Single laser scanner point. + */ + +/*! + \file vpScanPoint.h + + \brief Implements a single laser scanner point. +*/ + +#ifndef vpScanPoint_h +#define vpScanPoint_h + +#include +#include + +#include // std::fabs +#include // numeric_limits +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + + \class vpScanPoint + \ingroup group_sensor_laserscanner + + \brief Class that defines a single laser scanner point. + + This class stores data of a single scan point as: + + - cartesian coordinates in the 3D space that are available throw + getX(), getY() and getZ() methods. + + - polar coordinates that are the native data provided by a laser + scanner. By polar coordinates we mean here the radial distance and the + horizontal angle of a point in the scanner layer and an additional + vertical angle that gives the orientation of the layer. + +*/ +class /* VISP_EXPORT */ vpScanPoint // Note that here VISP_EXPORT should not + // be added since this class is complete + // inline +{ +public: + /*! Default constructor. */ + inline vpScanPoint() : rDist(0), hAngle(0), vAngle(0) { } + /*! Copy constructor. */ + inline vpScanPoint(const vpScanPoint &scanpoint) : rDist(0), hAngle(0), vAngle(0) + { + this->rDist = scanpoint.rDist; + this->hAngle = scanpoint.hAngle; + this->vAngle = scanpoint.vAngle; + } + /*! + Set the polar point coordinates. + \param r_dist : Radial distance in meter. + \param h_angle : Horizontal angle in radian. + \param v_angle : Vertical angle in radian. + */ + inline vpScanPoint(double r_dist, double h_angle, double v_angle) : rDist(r_dist), hAngle(h_angle), vAngle(v_angle) + { + this->rDist = r_dist; + this->hAngle = h_angle; + this->vAngle = v_angle; + } + /*! Destructor that does nothing. */ + inline virtual ~vpScanPoint() { } + /*! + Set the polar point coordinates. + \param r_dist : Radial distance in meter. + \param h_angle : Horizontal angle in radian. + \param v_angle : Vertical angle in radian. + */ + inline void setPolar(double r_dist, double h_angle, double v_angle) + { + this->rDist = r_dist; + this->hAngle = h_angle; + this->vAngle = v_angle; + } + /*! + Return the radial distance in meter. + */ + inline double getRadialDist() const { return (this->rDist); } + /*! + Returns the polar elevation (vertical) angle in radian. + */ + inline double getVAngle() const { return (this->vAngle); } + /*! + Returns the polar elevation (vertical) angle in radian. + */ + inline double getHAngle() const { return (this->hAngle); } + /*! + Returns the cartesian x coordinate. + + The x and y axis define an horizontal plane, where x is oriented + positive in front of the laser while y on the left side. + + */ + inline double getX() const { return (rDist * cos(this->hAngle) * cos(this->vAngle)); } + /*! + Returns the cartesian y coordinate. + + The x and y axis define an horizontal plane, where x is oriented + positive in front of the laser while y on the left side. + + */ + inline double getY() const { return (rDist * sin(this->hAngle)); } + /*! + Returns the cartesian z coordinate. + + The z axis is vertical and oriented in direction of the sky. + + */ + inline double getZ() const { return (rDist * cos(this->hAngle) * sin(this->vAngle)); } + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpScanPoint &operator=(const vpScanPoint &) = default; +#endif + + friend inline std::ostream &operator<<(std::ostream &s, const vpScanPoint &p); + + /*! + + Returns true if sp1 and sp2 are equal; otherwire returns false. + + */ + friend inline bool operator==(const vpScanPoint &sp1, const vpScanPoint &sp2) + { + double rd1 = sp1.getRadialDist(); + double ha1 = sp1.getHAngle(); + double va1 = sp1.getVAngle(); + double rd2 = sp2.getRadialDist(); + double ha2 = sp2.getHAngle(); + double va2 = sp2.getVAngle(); + + return ((std::fabs(rd1 - rd2) <= std::fabs(vpMath::maximum(rd1, rd2)) * std::numeric_limits::epsilon()) && + (std::fabs(ha1 - ha2) <= std::fabs(vpMath::maximum(ha1, ha2)) * std::numeric_limits::epsilon()) && + (std::fabs(va1 - va2) <= std::fabs(vpMath::maximum(va1, va2)) * std::numeric_limits::epsilon())); + } + + /*! + + Returns true if sp1 and sp2 are different; otherwire returns false. + + */ + friend inline bool operator!=(const vpScanPoint &sp1, const vpScanPoint &sp2) + { + // return ( ( sp1.getRadialDist() != sp2.getRadialDist() ) + // || ( sp1.getHAngle() != sp2.getHAngle() ) + // || ( sp1.getVAngle() != sp2.getVAngle() ) ); + double rd1 = sp1.getRadialDist(); + double ha1 = sp1.getHAngle(); + double va1 = sp1.getVAngle(); + double rd2 = sp2.getRadialDist(); + double ha2 = sp2.getHAngle(); + double va2 = sp2.getVAngle(); + return ((std::fabs(rd1 - rd2) > std::fabs(vpMath::maximum(rd1, rd2)) * std::numeric_limits::epsilon()) || + (std::fabs(ha1 - ha2) <= std::fabs(vpMath::maximum(ha1, ha2)) * std::numeric_limits::epsilon()) || + (std::fabs(va1 - va2) <= std::fabs(vpMath::maximum(va1, va2)) * std::numeric_limits::epsilon())); + } + +private: + double rDist; + double hAngle; + double vAngle; +}; + +/*! + \relates vpScanPoint + + Print the values of the scan point on the output stream. Data are + separated by a white space. Data that are print are first the + polar coordinates, than the cartesian coordinates: + - the radial distance in meter + - the horizontal angle in radian + - the vertical angle in radian + - the cartesian X coordinate + - the cartesian Y coordinate + - the cartesian Z coordinate + + The following code + + \code + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + vpScanPoint p; + double radialDistance = 3; // 3 meters + double horizontalAngle = 1.12; // 1.12 radian + double verticalAngle = 0; // 0 radian for a horizontal layer + + p.setPolar(radialDistance, horizontalAngle, verticalAngle); + + std::cout << p << std::endl; + } + \endcode + will produce the prints + \code + "3 1.12 0 1.307047339 2.700301327 0" + \endcode + + */ +inline std::ostream &operator<<(std::ostream &s, const vpScanPoint &p) +{ + std::ios_base::fmtflags original_flags = s.flags(); + + s.precision(10); + s << p.getRadialDist() << " " << p.getHAngle() << " " << p.getVAngle() << " " << p.getX() << " " << p.getY() << " " + << p.getZ(); + + s.setf(original_flags); // restore s to standard state + + return s; +} +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpSickLDMRS.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpSickLDMRS.h new file mode 100644 index 00000000..bfcd2ddb --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpSickLDMRS.h @@ -0,0 +1,166 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Sick LD-MRS laser driver. + */ + +/*! + * \file vpSickLDMRS.h + * + * \brief Driver for the Sick LD-MRS laser scanner. + */ + +#ifndef vpSickLDMRS_h +#define vpSickLDMRS_h + +#include + +#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpSickLDMRS + * + * \ingroup group_sensor_laserscanner + * + * \brief Driver for the Sick LD-MRS laser scanner. + * + * \warning For the moment, this driver works only on UNIX platform. + * + * The code below shows how the four laser scan provided by the Sick + * LD-MRS could be acquired. + * + * \code + * #include "visp3/sensor/vpSickLDMRS.h" + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * #if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || + * (defined(__APPLE__) && defined(__MACH__))) // UNIX std::string ip = + * "131.254.12.119"; + * + * vpSickLDMRS laser; + * laser.setIpAddress(ip); + * laser.setup(); + * + * vpLaserScan laserscan[4]; + * for ( ; ; ) { + * // Get the measured points in the four layers + * laser.measure(laserscan); + * + * // Prints all the measured points + * for (int layer=0; layer<4; layer++) { + * std::vector pointsInLayer = laserscan[layer].getScanPoints(); vpScanPoint p; + * + * for (unsigned int i=0; i < pointsInLayer.size(); i++) { + * std::cout << pointsInLayer[i] << std::endl; + * } + * } + * } + * #endif + * } + * \endcode +*/ +class VISP_EXPORT vpSickLDMRS : public vpLaserScanner +{ +public: + enum MagicWord + { + MagicWordC2 = 0xAFFEC0C2 ///< The magic word that allows to identify the + ///< messages that are sent by the Sick LD-MRS. + }; + enum DataType + { + MeasuredData = 0x2202 ///< Flag to indicate that the body of a message + ///< contains measured data. + }; + vpSickLDMRS(); + + /*! Copy constructor. */ + vpSickLDMRS(const vpSickLDMRS &sick) + : vpLaserScanner(sick), socket_fd(-1), body(nullptr), vAngle(), time_offset(0), isFirstMeasure(true), + maxlen_body(104000) + { + *this = sick; + }; + virtual ~vpSickLDMRS() VP_OVERRIDE; + + /*! Copy operator. */ + vpSickLDMRS &operator=(const vpSickLDMRS &sick) + { + if (this != &sick) { + socket_fd = sick.socket_fd; + vAngle = sick.vAngle; + time_offset = sick.time_offset; + isFirstMeasure = sick.isFirstMeasure; + maxlen_body = sick.maxlen_body; + if (body) + delete[] body; + body = new unsigned char[104000]; + memcpy(body, sick.body, maxlen_body); + } + return (*this); + }; + + bool setup(const std::string &ip, int port); + bool setup(); + bool measure(vpLaserScan laserscan[4]); + +protected: +#if defined(_WIN32) + SOCKET socket_fd; +#else + int socket_fd; +#endif + unsigned char *body; + vpColVector vAngle; // constant vertical angle for each layer + double time_offset; + bool isFirstMeasure; + size_t maxlen_body; +}; +END_VISP_NAMESPACE +#endif + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpUeyeGrabber.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpUeyeGrabber.h new file mode 100644 index 00000000..dca1c8b0 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpUeyeGrabber.h @@ -0,0 +1,124 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * IDS uEye interface. + */ + +#ifndef _vpUeyeGrabber_h_ +#define _vpUeyeGrabber_h_ + +#include +#include + +#ifdef VISP_HAVE_UEYE + +BEGIN_VISP_NAMESPACE +/*! + * \class vpUeyeGrabber + * \ingroup group_sensor_camera + * + * Allows to grab images from an IDS camera using uEye SDK. + * + * This class was tested with IDS UI328xCP-C and UI328xCP-C camera models. + * + * By default, the first connected camera is the active one. + * The following code shows how to get information about all the connected cameras + * (index, id, model, serial number): + * \snippet tutorial-grabber-ids-ueye.cpp List camera info + * + * If multiple cameras are connected, to select a specific one use the following code: + * \snippet tutorial-grabber-ids-ueye.cpp Active camera info + * + * Create an image container, either for gray level or color images: + * \snippet tutorial-grabber-ids-ueye.cpp Create image + * + * Then call open() to connect the active camera: + * \snippet tutorial-grabber-ids-ueye.cpp Open connection + * + * From here you can modify camera default settings, + * - either by loading camera parameters from a config file created by `ueyedemo` binary: + * \snippet tutorial-grabber-ids-ueye.cpp Load settings from file + * - either using setter like setColorMode(), setExposure(), setFrameRate(), setGain(), setSubsampling(), + * setWhiteBalance() + * + * Depending on the settings, you may update image container size, especially if you want to create a window to display + * the image: + * \snippet tutorial-grabber-ids-ueye.cpp Update image size + * + * Now you can create an infinite loop to grab images: + * \code + * while (1) { + * g.acquire(I); + * } + * \endcode +*/ +class VISP_EXPORT vpUeyeGrabber +{ +public: + vpUeyeGrabber(); + virtual ~vpUeyeGrabber(); + + void acquire(vpImage &I, double *timestamp_camera = nullptr, std::string *timestamp_system = nullptr); + void acquire(vpImage &I, double *timestamp_camera = nullptr, std::string *timestamp_system = nullptr); + + std::string getActiveCameraModel() const; + std::string getActiveCameraSerialNumber() const; + + std::vector getCameraIDList() const; + std::vector getCameraModelList() const; + std::vector getCameraSerialNumberList() const; + double getFramerate() const; + unsigned int getFrameHeight() const; + unsigned int getFrameWidth() const; + + bool isConnected() const; + void loadParameters(const std::string &filename); + void open(vpImage &I); + void open(vpImage &I); + + bool setActiveCamera(unsigned int cam_index); + bool setColorMode(const std::string &color_mode); + bool setExposure(bool auto_exposure, double exposure_ms = -1); + bool setFrameRate(bool auto_frame_rate, double manual_frame_rate_hz = -1); + bool setGain(bool auto_gain, int master_gain = -1, bool gain_boost = false); + void setSubsampling(int factor); + void setVerbose(bool verbose); + void setWhiteBalance(bool auto_wb); + +private: + vpUeyeGrabber(const vpUeyeGrabber &); // noncopyable + vpUeyeGrabber &operator=(const vpUeyeGrabber &); // + + class vpUeyeGrabberImpl; + vpUeyeGrabberImpl *m_impl; +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpV4l2Grabber.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpV4l2Grabber.h new file mode 100644 index 00000000..904f25b8 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/sensor/vpV4l2Grabber.h @@ -0,0 +1,359 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Video for linux two framegrabber. + */ + +/*! + \file vpV4l2Grabber.h + \brief class for the Video For Linux 2 video device framegrabbing. +*/ + +#ifndef vpV4l2Grabber_hh +#define vpV4l2Grabber_hh + +#include + +#ifdef VISP_HAVE_V4L2 + +#include // Video For Linux Two interface +#include // Video For Linux Two interface +#include + +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpV4l2Grabber + + \ingroup group_sensor_camera + + \brief Class that is a wrapper over the Video4Linux2 (V4L2) driver. + + Thus to be enabled, this class needs the optional V4L2 3rd party. + Installation instruction are provided here https://visp.inria.fr/3rd_v4l2. + + Information about Video4Linux can be found on + http://linuxtv.org/v4lwiki/index.php/Main_Page + + This class was tested with a Pinnacle PCTV Studio/Rave board but + also with the following webcams (Logitech QuickCam Vision Pro 9000, + Logitech QuickCam Orbit AF, Logitech QuickCam IM (V-USB39), Dell latitude + E6400 internal webcam). + + If the grabbing fail with a webcam, it means probably that you don't + have the read/write permission on the /dev/video%%d device. You can + set the right permissions by "sudo chmod a+rw /dev/video*". + + If the grabbing fails when the camera is attached to a bttv PCI + card, it means potentially that you have not configured the linux + bttv kernel module according to your board. + + For that, depending on your linux distribution check the card id in + - /usr/share/doc/kernel-doc-2.4.20/video4linux/bttv/CARDLIST + - or /usr/share/doc/kernel-doc-2.6.20/Documentation/video4linux/CARDLIST.bttv + + For example, the card id of a Pinnacle PCTV Studio/Rave board is 39. + Once this id is determined, you have to set the bttv driver with, by adding + \verbatim + options bttv card=39 + \endverbatim + in one of theses files : + - /etc/modules.conf + - or /etc/modprobe.conf + + This first example available in tutorial-grabber-v4l2.cpp shows how to grab + and display images from an usb camera. + \include tutorial-grabber-v4l2.cpp + + This other example shows how to use this grabber with an analogic camera + attached to a bttv PCI card. + \code + #include + #include + + #ifdef ENABLE_VISP_NAMESPACE + using namespace VISP_NAMESPACE_NAME; + #endif + + int main() + { + #if defined(VISP_HAVE_V4L2) + vpImage I; + vpV4l2Grabber g; + g.setInput(2); // Input 2 on the board + g.setFramerate(vpV4l2Grabber::framerate_25fps); // 25 fps + g.setWidth(768); // Acquired images are 768 width + g.setHeight(576); // Acquired images are 576 height + g.setNBuffers(3); // 3 ring buffers to ensure real-time acquisition + g.open(I); // Open the grabber + + g.acquire(I); // Acquire a 768x576 grey image + vpImageIo::write(I, "image.pgm"); // Save the image on the disk + #endif + } + \endcode + + \sa vpFrameGrabber +*/ +class VISP_EXPORT vpV4l2Grabber : public vpFrameGrabber +{ +public: + static const unsigned int DEFAULT_INPUT; + static const unsigned int DEFAULT_SCALE; + static const __u32 MAX_INPUTS; + static const __u32 MAX_NORM; + static const __u32 MAX_FORMAT; + static const unsigned int MAX_CTRL; + static const unsigned int MAX_BUFFERS; + static const unsigned int FRAME_SIZE; + + /*! \enum vpV4l2FramerateType + Frame rate type for capture. + */ + typedef enum + { + framerate_50fps, //!< 50 frames per second + framerate_25fps //!< 25 frames per second + } vpV4l2FramerateType; + + /*! \enum vpV4l2FrameFormatType + Frame format type for capture. + */ + typedef enum + { + V4L2_FRAME_FORMAT, /*!< a field only */ + V4L2_IMAGE_FORMAT /*!< an interlaced image */ + } vpV4l2FrameFormatType; + + /*! \enum vpV4l2PixelFormatType + Pixel format type for capture. + */ + typedef enum + { + V4L2_GREY_FORMAT, /*!< 8 Greyscale */ + V4L2_RGB24_FORMAT, /*!< 24 RGB-8-8-8 */ + V4L2_RGB32_FORMAT, /*!< 32 RGB-8-8-8-8 */ + V4L2_BGR24_FORMAT, /*!< 24 BGR-8-8-8 */ + V4L2_YUYV_FORMAT, /*!< 16 YUYV 4:2:2 */ + V4L2_MAX_FORMAT + } vpV4l2PixelFormatType; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + struct ng_video_fmt + { + unsigned int pixelformat; /* VIDEO_* */ + unsigned int width; + unsigned int height; + unsigned int bytesperline; /* zero for compressed formats */ + }; + + struct ng_video_buf + { + struct ng_video_fmt fmt; + size_t size; + unsigned char *data; + int refcount; + }; +#endif + + // private: + //#ifndef DOXYGEN_SHOULD_SKIP_THIS + // vpV4l2Grabber(const vpV4l2Grabber &) + // : fd(-1), device(), cap(), streamparm(), inp(nullptr), std(nullptr), + // fmt(nullptr), ctl(nullptr), + // fmt_v4l2(), fmt_me(), reqbufs(), buf_v4l2(nullptr), buf_me(nullptr), + // queue(0), waiton_cpt(0), index_buffer(0), m_verbose(false), + // m_nbuffers(3), field(0), streaming(false), + // m_input(vpV4l2Grabber::DEFAULT_INPUT), + // m_framerate(vpV4l2Grabber::framerate_25fps), + // m_frameformat(vpV4l2Grabber::V4L2_FRAME_FORMAT), + // m_pixelformat(vpV4l2Grabber::V4L2_YUYV_FORMAT) + // { + // throw vpException(vpException::functionNotImplementedError,"Not + // implemented!"); + // } + // vpV4l2Grabber &operator=(const vpV4l2Grabber &){ + // throw vpException(vpException::functionNotImplementedError,"Not + // implemented!"); return *this; + // } + //#endif + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpV4l2Grabber(const vpV4l2Grabber &) = delete; // non construction-copyable + vpV4l2Grabber &operator=(const vpV4l2Grabber &) = delete; // non copyable +#endif + +public: + vpV4l2Grabber(); + VP_EXPLICIT vpV4l2Grabber(bool verbose); + vpV4l2Grabber(unsigned input, unsigned scale = vpV4l2Grabber::DEFAULT_SCALE); + vpV4l2Grabber(vpImage &I, unsigned input, unsigned scale = vpV4l2Grabber::DEFAULT_SCALE); + vpV4l2Grabber(vpImage &I, unsigned input, unsigned scale = vpV4l2Grabber::DEFAULT_SCALE); + virtual ~vpV4l2Grabber(); + +public: + void open(vpImage &I); + void open(vpImage &I); + + void acquire(vpImage &I); + void acquire(vpImage &I, const vpRect &roi); + void acquire(vpImage &I, struct timeval ×tamp, const vpRect &roi = vpRect()); + void acquire(vpImage &I); + void acquire(vpImage &I, const vpRect &roi); + void acquire(vpImage &I, struct timeval ×tamp, const vpRect &roi = vpRect()); + bool getField(); + vpV4l2FramerateType getFramerate(); + /*! + + Get the pixel format used for capture. + + \return Camera pixel format coding. + + */ + inline vpV4l2PixelFormatType getPixelFormat() { return (this->m_pixelformat); } + + vpV4l2Grabber &operator>>(vpImage &I); + vpV4l2Grabber &operator>>(vpImage &I); + + /*! + Activates the verbose mode to print additional information on stdout. + \param verbose : If true activates the verbose mode. + */ + void setVerboseMode(bool verbose) { this->m_verbose = verbose; } + void setFramerate(vpV4l2FramerateType framerate); + + void setInput(unsigned input = vpV4l2Grabber::DEFAULT_INPUT); + + /*! + Set image width to acquire. + + */ + inline void setWidth(unsigned w) { this->width = w; } + /*! + Set image height to acquire. + + */ + inline void setHeight(unsigned h) { this->height = h; } + + void setScale(unsigned scale = vpV4l2Grabber::DEFAULT_SCALE); + + /*! + + Set the number of buffers required for streaming data. + + For non real-time applications the number of buffers should be set to 1. For + real-time applications to reach 25 fps or 50 fps a good compromise is to set + the number of buffers to 3. + + \param nbuffers : Number of ring buffers. + + */ + inline void setNBuffers(unsigned nbuffers) { this->m_nbuffers = nbuffers; } + + /*! + Set the device name. + + \param devname : Device name (like /dev/video0). + + */ + inline void setDevice(const std::string &devname) { device = devname; } + /*! + + Set the pixel format for capture. If the specified pixel format is + out of range, we set the V4L2_RGB24_FORMAT. + + \param pixelformat : Camera pixel format coding. + + */ + inline void setPixelFormat(vpV4l2PixelFormatType pixelformat) + { + this->m_pixelformat = pixelformat; + if (this->m_pixelformat >= V4L2_MAX_FORMAT) + this->m_pixelformat = V4L2_RGB24_FORMAT; + } + + void close(); + +private: + void setFormat(); + /*! + Set the frame format. + + \param frameformat : + - vpV4l2Grabber::V4L2_FRAME_FORMAT: capture alternate fields (or frames), + - vpV4l2Grabber::V4L2_IMAGE_FORMAT: capture interlaced images. + */ + inline void setFrameFormat(vpV4l2FrameFormatType frameformat) { this->m_frameformat = frameformat; } + void open(); + void getCapabilities(); + void startStreaming(); + void stopStreaming(); + unsigned char *waiton(__u32 &index, struct timeval ×tamp); + int queueBuffer(); + void queueAll(); + void printBufInfo(struct v4l2_buffer buf); + + int fd; + std::string device; + /* device descriptions */ + struct v4l2_capability cap; + struct v4l2_streamparm streamparm; + struct v4l2_input *inp; //[vpV4l2Grabber::MAX_INPUTS]; + struct v4l2_standard *std; //[vpV4l2Grabber::MAX_NORM]; + struct v4l2_fmtdesc *fmt; //[vpV4l2Grabber::MAX_FORMAT]; + struct v4l2_queryctrl *ctl; //[vpV4l2Grabber::MAX_CTRL*2]; + + /* capture */ + struct v4l2_format fmt_v4l2; + struct ng_video_fmt fmt_me; + struct v4l2_requestbuffers reqbufs; + struct v4l2_buffer *buf_v4l2; //[vpV4l2Grabber::MAX_BUFFERS]; + struct ng_video_buf *buf_me; //[vpV4l2Grabber::MAX_BUFFERS]; + unsigned int queue; + unsigned int waiton_cpt; + __u32 index_buffer; //!< index of the buffer in use + + bool m_verbose; + unsigned m_nbuffers; + unsigned int field; + bool streaming; + + unsigned m_input; + vpV4l2FramerateType m_framerate; + vpV4l2FrameFormatType m_frameformat; + vpV4l2PixelFormatType m_pixelformat; +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTracker.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTracker.h new file mode 100644 index 00000000..2140bf04 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTracker.h @@ -0,0 +1,307 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Template tracker. + * + * Authors: + * Amaury Dame + * Aurelien Yol + */ +/*! + \file vpTemplateTracker.h + \brief +*/ + +#ifndef vpTemplateTracker_hh +#define vpTemplateTracker_hh + +#include + +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpTemplateTracker + \ingroup group_tt_tracker + + This class allows to instantiate a template tracker using image registration + algorithms \cite Dame10c \cite Dame11c. + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in the Template Tracker (TT), you may have a look at:
+ + - \ref tutorial-tracking-tt +*/ +class VISP_EXPORT vpTemplateTracker +{ +protected: + // traitement pyramidal + unsigned int nbLvlPyr; // If = 1, disable pyramidal usage + unsigned int l0Pyr; + bool pyrInitialised; + // For evolRMS computation + double evolRMS; + std::vector x_pos; + std::vector y_pos; + double evolRMS_eps; + + vpTemplateTrackerPoint *ptTemplate; + vpTemplateTrackerPoint **ptTemplatePyr; + bool ptTemplateInit; + unsigned int templateSize; + unsigned int *templateSizePyr; + bool *ptTemplateSelect; + bool **ptTemplateSelectPyr; + bool ptTemplateSelectInit; + unsigned int templateSelectSize; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + vpTemplateTrackerPointSuppMIInv *ptTemplateSupp; // pour inverse et compo + vpTemplateTrackerPointSuppMIInv **ptTemplateSuppPyr; // pour inverse et compo +#endif + + vpTemplateTrackerPointCompo *ptTemplateCompo; // pour ESM + vpTemplateTrackerPointCompo **ptTemplateCompoPyr; // pour ESM + vpTemplateTrackerZone *zoneTracked; + vpTemplateTrackerZone *zoneTrackedPyr; + + vpImage *pyr_IDes; + + vpMatrix H; + vpMatrix Hdesire; + vpMatrix *HdesirePyr; + vpMatrix HLM; + vpMatrix HLMdesire; + vpMatrix *HLMdesirePyr; + vpMatrix HLMdesireInverse; + vpMatrix *HLMdesireInversePyr; + vpColVector G; + + double gain; + double thresholdGradient; + bool costFunctionVerification; + bool blur; + bool useBrent; + unsigned int nbIterBrent; + unsigned int taillef; + double *fgG; + double *fgdG; + double ratioPixelIn; + int mod_i; + int mod_j; // variable de sampling de zone de reference + unsigned int nbParam; + double lambdaDep; + unsigned int iterationMax; + // pour BFGS + unsigned int iterationGlobale; + // diverge is set to true if there is no more point in the tracked area + bool diverge; + unsigned int nbIteration; + bool useCompositionnal; + bool useInverse; + + vpTemplateTrackerWarp *Warp; + // Parametre de deplacement + vpColVector p; + vpColVector dp; + + // temporary values for warping + vpColVector X1; + vpColVector X2; + // temporary derivative matrix + vpMatrix dW; + + vpImage BI; + vpImage dIx; + vpImage dIy; + vpTemplateTrackerZone zoneRef_; // Reference zone + +public: + //! Default constructor. + vpTemplateTracker() + : nbLvlPyr(0), l0Pyr(0), pyrInitialised(false), ptTemplate(nullptr), ptTemplatePyr(nullptr), ptTemplateInit(false), + templateSize(0), templateSizePyr(nullptr), ptTemplateSelect(nullptr), ptTemplateSelectPyr(nullptr), + ptTemplateSelectInit(false), templateSelectSize(0), ptTemplateSupp(nullptr), ptTemplateSuppPyr(nullptr), + ptTemplateCompo(nullptr), ptTemplateCompoPyr(nullptr), zoneTracked(nullptr), zoneTrackedPyr(nullptr), pyr_IDes(nullptr), H(), + Hdesire(), HdesirePyr(nullptr), HLM(), HLMdesire(), HLMdesirePyr(nullptr), HLMdesireInverse(), + HLMdesireInversePyr(nullptr), G(), gain(0), thresholdGradient(0), costFunctionVerification(false), blur(false), + useBrent(false), nbIterBrent(0), taillef(0), fgG(nullptr), fgdG(nullptr), ratioPixelIn(0), mod_i(0), mod_j(0), + nbParam(), lambdaDep(0), iterationMax(0), iterationGlobale(0), diverge(false), nbIteration(0), + useCompositionnal(false), useInverse(false), Warp(nullptr), p(), dp(), X1(), X2(), dW(), BI(), dIx(), dIy(), + zoneRef_() + { } + VP_EXPLICIT vpTemplateTracker(vpTemplateTrackerWarp *_warp); + virtual ~vpTemplateTracker(); + + void display(const vpImage &I, const vpColor &col = vpColor::green, unsigned int thickness = 3); + void display(const vpImage &I, const vpColor &col = vpColor::green, unsigned int thickness = 3); + + bool getDiverge() const { return diverge; } + vpColVector getdp() { return dp; } + vpColVector getG() const { return G; } + vpMatrix getH() const { return H; } + unsigned int getNbParam() const { return nbParam; } + unsigned int getNbIteration() const { return nbIteration; } + vpColVector getp() const { return p; } + double getRatioPixelIn() const { return ratioPixelIn; } + + /*! + + \return The pointer to the warper. + */ + vpTemplateTrackerWarp *getWarp() const { return Warp; } + + /*! + Return the reference template zone. + */ + vpTemplateTrackerZone getZoneRef() const { return zoneRef_; } + + void initClick(const vpImage &I, bool delaunay = false); + void initFromPoints(const vpImage &I, const std::vector &v_ip, bool delaunay = false); + void initFromZone(const vpImage &I, const vpTemplateTrackerZone &zone); + + void resetTracker(); + + void setBlur(bool b) { blur = b; } + void setCostFunctionVerification(bool b) { costFunctionVerification = b; } + void setGain(double g) { gain = g; } + void setGaussianFilterSize(unsigned int new_taill); + void setHDes(vpMatrix &tH) + { + Hdesire = tH; + vpMatrix::computeHLM(Hdesire, lambdaDep, HLMdesire); + HLMdesireInverse = HLMdesire.inverseByLU(); + } + /*! + Set the maximum number of iteration of the estimation scheme. + \param n : Maximum number of iterations to stop the estimation scheme. A + typical value is around 100. + */ + void setIterationMax(const unsigned int &n) { iterationMax = n; } + /*! + Set the convergence gain used in the estimation scheme. + \param l : Gain. A typical value is 0.001. + */ + void setLambda(double l) { lambdaDep = l; } + void setNbIterBrent(const unsigned int &b) { nbIterBrent = b; } + void setp(const vpColVector &tp) + { + p = tp; + diverge = false; + iterationGlobale = 0; + } + /*! + Set the number of pyramid levels used in the multi-resolution scheme. + If \e nlevels > 1, the tracker uses a pyramidal approach. + + \param nlevels : Number of pyramid levels. Algorithm starts at level + nlevels-1. + \param level_to_stop : Last level of the pyramid that will be + considered. Lowest level is zero. + */ + void setPyramidal(unsigned int nlevels = 2, unsigned int level_to_stop = 1) + { + nbLvlPyr = nlevels; + l0Pyr = level_to_stop; + if (l0Pyr >= nlevels) { + std::cout << "Warning: level_to_stop: " << level_to_stop << " higher than level_to_start: " << nlevels - 1 + << " (nlevels-1)" << std::endl; + std::cout << "Level to stop put to: " << nlevels - 1 << std::endl; + l0Pyr = nlevels - 1; + } + } + /*! + Set the pixel sampling parameters along the rows and the columns. + \param sample_i : Sampling factor along the rows. + If 1 all the lines are considered. If 2, consider one line over two. + + \param sample_j : Sampling factor along the columns. + If 1 all the columns are considered. If 2, consider one column over two. + */ + void setSampling(int sample_i, int sample_j) + { + mod_i = sample_i; + mod_j = sample_j; + } + void setThresholdGradient(double threshold) { thresholdGradient = threshold; } + /*! + Set the threshold used to stop optimization loop. + When the residual difference between two successive iterations becomes lower than the threshold we stop + optimization loop. + + \note Increasing the default value allows to speed up the tracking. + + \param threshold : Threshold used to stop optimization. Default value is set to 1e-4. + */ + void setThresholdResidualDifference(double threshold) { evolRMS_eps = threshold; } + + /*! By default Brent usage is disabled. */ + void setUseBrent(bool b) { useBrent = b; } + + void track(const vpImage &I); + void trackRobust(const vpImage &I); + +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) + /*! + @name Deprecated functions + */ + //@{ + /*! + \deprecated This function is deprecated and the value set is no more used. + Use rather setThresholdResidualDerivative() + \param threshold : Unused value. + */ + VP_DEPRECATED void setThresholdRMS(double threshold) { (void)threshold; } + //@} +#endif + +protected: + void computeEvalRMS(const vpColVector &p); + void computeOptimalBrentGain(const vpImage &I, vpColVector &tp, double tMI, vpColVector &direction, + double &alpha); + virtual double getCost(const vpImage &I, const vpColVector &tp) = 0; + void getGaussianBluredImage(const vpImage &I) { vpImageFilter::filter(I, BI, fgG, taillef); } + virtual void initHessienDesired(const vpImage &I) = 0; + virtual void initHessienDesiredPyr(const vpImage &I); + void initPosEvalRMS(const vpColVector &p); + virtual void initPyramidal(unsigned int nbLvl, unsigned int l0); + void initTracking(const vpImage &I, vpTemplateTrackerZone &zone); + virtual void initTrackingPyr(const vpImage &I, vpTemplateTrackerZone &zone); + virtual void trackNoPyr(const vpImage &I) = 0; + virtual void trackPyr(const vpImage &I); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerBSpline.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerBSpline.h new file mode 100644 index 00000000..c9b011c6 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerBSpline.h @@ -0,0 +1,71 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Template tracker. + * + * Authors: + * Amaury Dame + * Aurelien Yol + */ +/*! + \file vpTemplateTrackerBSpline.h + \brief +*/ + +#ifndef vpTemplateTrackerBSpline_hh +#define vpTemplateTrackerBSpline_hh + +#include +#include +#include + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +BEGIN_VISP_NAMESPACE +/** + \ingroup group_tt_tracker + \brief + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in the Template Tracker (TT), you may have a look at:
+ + - \ref tutorial-tracking-tt + */ + class VISP_EXPORT vpTemplateTrackerBSpline +{ +public: + static double Bspline4(double diff); + + static double getSubPixBspline4(const vpImage &I, double r, double t); +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerHeader.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerHeader.h new file mode 100644 index 00000000..5ca6f289 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerHeader.h @@ -0,0 +1,138 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Template tracker. + * + * Authors: + * Amaury Dame + * Aurelien Yol + */ +/*! + \file vpTemplateTrackerHeader.h + \brief +*/ + +#ifndef vpTemplateTrackerHeader_hh +#define vpTemplateTrackerHeader_hh + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \struct vpTemplateTrackerZPoint + \ingroup group_tt_tools + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in the Template Tracker (TT), you may have a look at:
+ + - \ref tutorial-tracking-tt +*/ +struct vpTemplateTrackerZPoint +{ + int x, y; + + vpTemplateTrackerZPoint() : x(0), y(0) { } +}; +/*! + \struct vpTemplateTrackerDPoint + \ingroup group_tt_tools + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in the Template Tracker (TT), you may have a look at:
+ + - \ref tutorial-tracking-tt +*/ +struct vpTemplateTrackerDPoint +{ + double x, y; + + vpTemplateTrackerDPoint() : x(0), y(0) { } +}; +/*! + \struct vpTemplateTrackerPoint + \ingroup group_tt_tools + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in the Template Tracker (TT), you may have a look at:
+ + - \ref tutorial-tracking-tt +*/ +struct vpTemplateTrackerPoint +{ + int x, y; + double dx, dy; + double val; + double *dW; + double *HiG; + + vpTemplateTrackerPoint() : x(0), y(0), dx(0), dy(0), val(0), dW(nullptr), HiG(nullptr) { } +}; +/*! + \struct vpTemplateTrackerPointCompo + \ingroup group_tt_tools + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in the Template Tracker (TT), you may have a look at:
+ + - \ref tutorial-tracking-tt +*/ +struct vpTemplateTrackerPointCompo +{ + double *dW; + vpTemplateTrackerPointCompo() : dW(nullptr) { } +}; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +struct vpTemplateTrackerPointSuppMIInv +{ + double et; + int ct; + double *BtInit; + double *Bt; + double *dBt; + double *d2W; + double *d2Wx; + double *d2Wy; + vpTemplateTrackerPointSuppMIInv() : et(0), ct(0), BtInit(nullptr), Bt(nullptr), dBt(nullptr), d2W(nullptr), d2Wx(nullptr), d2Wy(nullptr) + { } +}; +#endif // DOXYGEN_SHOULD_SKIP_THIS +END_VISP_NAMESPACE + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerSSD.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerSSD.h new file mode 100644 index 00000000..74bb3542 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerSSD.h @@ -0,0 +1,88 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Template tracker. + * + * Authors: + * Amaury Dame + * Aurelien Yol + */ +/*! + \file vpTemplateTrackerSSD.h + \brief +*/ + +#ifndef vpTemplateTrackerSSD_hh +#define vpTemplateTrackerSSD_hh + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpTemplateTrackerSSD + \ingroup group_tt_tracker + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in the Template Tracker (TT), you may have a look at:
+ + - \ref tutorial-tracking-tt +*/ +class VISP_EXPORT vpTemplateTrackerSSD : public vpTemplateTracker +{ +protected: + vpRowVector DI; + vpRowVector temp; + +protected: + double getCost(const vpImage &I, const vpColVector &tp); + double getCost(const vpImage &I) { return getCost(I, p); } + virtual void initHessienDesired(const vpImage &I) = 0; + virtual void trackNoPyr(const vpImage &I) = 0; + +public: + VP_EXPLICIT vpTemplateTrackerSSD(vpTemplateTrackerWarp *warp); + + double getSSD(const vpImage &I, const vpColVector &tp); + void setGain(double g) { gain = g; } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerSSDESM.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerSSDESM.h new file mode 100644 index 00000000..1de79fb9 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerSSDESM.h @@ -0,0 +1,80 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Template tracker. + * + * Authors: + * Amaury Dame + * Aurelien Yol + */ +/*! + \file vpTemplateTrackerSSDESM.h + \brief +*/ + +#ifndef vpTemplateTrackerSSDESM_hh +#define vpTemplateTrackerSSDESM_hh + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpTemplateTrackerSSDESM + \ingroup group_tt_tracker + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in the Template Tracker (TT), you may have a look at:
+ + - \ref tutorial-tracking-tt +*/ +class VISP_EXPORT vpTemplateTrackerSSDESM : public vpTemplateTrackerSSD +{ +protected: + bool compoInitialised; + vpMatrix HDir; + vpMatrix HInv; + vpMatrix HLMDir; + vpMatrix HLMInv; + vpColVector GDir; + vpColVector GInv; + +protected: + void initHessienDesired(const vpImage &I); + void initCompInverse(const vpImage &I); + void trackNoPyr(const vpImage &I); + +public: + VP_EXPLICIT vpTemplateTrackerSSDESM(vpTemplateTrackerWarp *warp); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerSSDForwardAdditional.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerSSDForwardAdditional.h new file mode 100644 index 00000000..011d5399 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerSSDForwardAdditional.h @@ -0,0 +1,84 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Template tracker. + * + * Authors: + * Amaury Dame + * Aurelien Yol + */ +/*! + \file vpTemplateTrackerSSDForwardAdditional.h + \brief +*/ + +#ifndef vpTemplateTrackerSSDForwardAdditional_hh +#define vpTemplateTrackerSSDForwardAdditional_hh + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \ingroup group_tt_tracker + The algorithm implemented in this class is described in \cite Baker04a and + \cite Marchand16a. + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in the Template Tracker (TT), you may have a look at:
+ + - \ref tutorial-tracking-tt +*/ +class VISP_EXPORT vpTemplateTrackerSSDForwardAdditional : public vpTemplateTrackerSSD +{ +public: + /*! Minimization method. */ + typedef enum { USE_NEWTON, USE_LMA, USE_GRADIENT, USE_QUASINEWTON } vpMinimizationTypeSSDForwardAdditional; + +private: + vpMinimizationTypeSSDForwardAdditional minimizationMethod; + // valeur pour calculer Quasi_Newton + vpColVector p_prec; + vpColVector G_prec; + vpMatrix KQuasiNewton; + +protected: + void initHessienDesired(const vpImage & /*I*/) { } + void trackNoPyr(const vpImage &I); + +public: + VP_EXPLICIT vpTemplateTrackerSSDForwardAdditional(vpTemplateTrackerWarp *warp); + + void setMinimizationMethod(vpMinimizationTypeSSDForwardAdditional method) { minimizationMethod = method; } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerSSDForwardCompositional.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerSSDForwardCompositional.h new file mode 100644 index 00000000..ea970eff --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerSSDForwardCompositional.h @@ -0,0 +1,75 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Template tracker. + * + * Authors: + * Amaury Dame + * Aurelien Yol + */ +/*! + \file vpTemplateTrackerSSDForwardCompositional.h + \brief +*/ + +#ifndef vpTemplateTrackerSSDForwardCompositional_hh +#define vpTemplateTrackerSSDForwardCompositional_hh + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \ingroup group_tt_tracker + The algorithm implemented in this class is described in \cite Baker04a and + \cite Marchand16a. + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in the Template Tracker(TT), you may have a look at:
+ + - \ref tutorial-tracking-tt +*/ +class VISP_EXPORT vpTemplateTrackerSSDForwardCompositional : public vpTemplateTrackerSSD +{ +protected: + bool compoInitialised; + +protected: + void initHessienDesired(const vpImage &I); + void initCompo(const vpImage &I); + void trackNoPyr(const vpImage &I); + +public: + VP_EXPLICIT vpTemplateTrackerSSDForwardCompositional(vpTemplateTrackerWarp *warp); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerSSDInverseCompositional.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerSSDInverseCompositional.h new file mode 100644 index 00000000..4d743cc2 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerSSDInverseCompositional.h @@ -0,0 +1,83 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Template tracker. + * + * Authors: + * Amaury Dame + * Aurelien Yol + */ +/*! + \file vpTemplateTrackerSSDInverseCompositional.h + \brief +*/ +#ifndef vpTemplateTrackerSSDInverseCompositional_hh +#define vpTemplateTrackerSSDInverseCompositional_hh + +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \ingroup group_tt_tracker + The algorithm implemented in this class is described in \cite Baker04a and + \cite Marchand16a. + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in the Template Tracker (TT), you may have a look at:
+ + - \ref tutorial-tracking-tt +*/ +class VISP_EXPORT vpTemplateTrackerSSDInverseCompositional : public vpTemplateTrackerSSD +{ +protected: + bool compoInitialised; + vpMatrix HInv; + vpMatrix HCompInverse; + bool useTemplateSelect; // use only the strong gradient pixels to compute the Jabocian + +protected: + void initHessienDesired(const vpImage &I); + void initCompInverse(const vpImage &I); + void trackNoPyr(const vpImage &I); + +public: + VP_EXPLICIT vpTemplateTrackerSSDInverseCompositional(vpTemplateTrackerWarp *warp); + + /*! Use only the strong gradient pixels to compute the Jabobian. By default + * this feature is disabled. */ + void setUseTemplateSelect(bool b) { useTemplateSelect = b; } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerTriangle.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerTriangle.h new file mode 100644 index 00000000..db394795 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerTriangle.h @@ -0,0 +1,177 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Template tracker. + * + * Authors: + * Amaury Dame + * Aurelien Yol + */ +/*! + \file vpTemplateTrackerTriangle.h + \brief +*/ + +#ifndef vpTemplateTrackerTriangle_hh +#define vpTemplateTrackerTriangle_hh + +#include +#include + +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpTemplateTrackerTriangle + \ingroup group_tt_tools + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in the Template Tracker (TT), you may have a look at:
+ + - \ref tutorial-tracking-tt +*/ +class VISP_EXPORT vpTemplateTrackerTriangle +{ +protected: + double minx_temp; + double miny_temp; + vpTemplateTrackerDPoint C1; //! Corner 1 + vpTemplateTrackerDPoint C2; //! Corner 2 + vpTemplateTrackerDPoint C3; //! Corner 3 + + double l_t; + double h_t; + + bool not_good; + double uvinv00; + double uvinv01; + double uvinv10; + double uvinv11; + double marge_triangle; + double area; + +private: + vpColVector getCorner1() const; + vpColVector getCorner2() const; + vpColVector getCorner3() const; + +public: + vpTemplateTrackerTriangle(); + vpTemplateTrackerTriangle(const vpTemplateTrackerTriangle &T); + vpTemplateTrackerTriangle(const vpColVector &c1, const vpColVector &c2, const vpColVector &c3); + vpTemplateTrackerTriangle(const vpImagePoint &c1, const vpImagePoint &c2, const vpImagePoint &c3); + vpTemplateTrackerTriangle(int x1, int y1, int x2, int y2, int x3, int y3); + vpTemplateTrackerTriangle(double x1, double y1, double x2, double y2, double x3, double y3); + + /*! + Return the area of the triangle. + + \return The area of the triangle. + */ + inline double getArea() const { return this->area; } + + vpTemplateTrackerTriangle getPyramidDown() const; + void getCorners(vpColVector &c1, vpColVector &c2, vpColVector &c3) const; + void getCorners(vpImagePoint &c1, vpImagePoint &c2, vpImagePoint &c3) const; + void getCorners(std::vector &c) const; + + /*! + Returns the coordinates of a triangle corner. + \param i : Allowed values are 0, 1 or 2. + \return + - if i = 0, return corner 1 coordinates, + - if i = 1, return corner 2 coordinates, + - if i = 2, return corner 3 coordinates. + + The coordinates are returned as a 2 dimension vector (x,y). + */ + vpColVector getCorner(unsigned int i) const + { + assert(i < 3); + if (i == 0) + return getCorner1(); + else if (i == 1) + return getCorner2(); + else /*if(i==2)*/ + return getCorner3(); + }; + /*! + Returns the coordinates of a triangle corner. + \param i : Allowed values are 0, 1 or 2. + - if i = 0, return corner 1 coordinates, + - if i = 1, return corner 2 coordinates, + - if i = 2, return corner 3 coordinates. + \param x : The coordinate of the corner along horizontal axis. + \param y : The coordinate of the corner along vertical axis. + */ + void getCorner(unsigned int i, double &x, double &y) const + { + assert(i < 3); + if (i == 0) { + x = C1.x; + y = C1.y; + } + else if (i == 1) { + x = C2.x; + y = C2.y; + } + else /*if(i==2)*/ { + x = C3.x; + y = C3.y; + } + }; + + double getMaxx() const; + double getMaxy() const; + double getMinx() const; + double getMiny() const; + + void getSize(double &w, double &h) const; + void getSize(int &w, int &h) const; + + void init(const vpColVector &c1, const vpColVector &c2, const vpColVector &c3); + void init(const vpImagePoint &c1, const vpImagePoint &c2, const vpImagePoint &c3); + void init(int x1, int y1, int x2, int y2, int x3, int y3); + void init(double x1, double y1, double x2, double y2, double x3, double y3); + bool inTriangle(const vpImagePoint &ip) const; + bool inTriangle(const int &i, const int &j) const; + bool inTriangle(const double &i, const double &j) const; + + vpTemplateTrackerTriangle &operator=(const vpTemplateTrackerTriangle &T); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarp.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarp.h new file mode 100644 index 00000000..33f9e3ef --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarp.h @@ -0,0 +1,266 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Template tracker. + * + * Authors: + * Amaury Dame + * Aurelien Yol + */ +/*! + \file vpTemplateTrackerWarp.h + \brief +*/ + +#ifndef vpTemplateTrackerWarp_hh +#define vpTemplateTrackerWarp_hh + +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpTemplateTrackerWarp + \ingroup group_tt_warp + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in the Template Tracker (TT), you may have a look at:
+ + - \ref tutorial-tracking-tt +*/ +class VISP_EXPORT vpTemplateTrackerWarp +{ +protected: + double denom; //!< Internal value used by homography warp model. + unsigned int nbParam; //!< Number of parameters used to model warp transformation. + +public: + /*! + * Default constructor. + */ + vpTemplateTrackerWarp() : denom(1.), nbParam(0) { } + /*! + * Destructor. + */ + virtual ~vpTemplateTrackerWarp() { } + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + virtual void computeCoeff(const vpColVector &p) = 0; + virtual void computeDenom(vpColVector &vX, const vpColVector &ParamM) = 0; +#endif + + /*! + * Compute the derivative matrix of the warping function at point \f$X=(u,v)\f$ according to the model parameters: + * \f[ + * \frac{\partial M}{\partial p}(X_1, X_2, p) + * \f] + * \param X1 : Vector corresponding to the coordinates \f$(u_1, v_1)\f$ of the point to + * consider in the derivative computation. + * \param X2 : Vector corresponding to the coordinates \f$(u_1, v_1)\f$ of the point to + * consider in the derivative computation. + * \param p : Vector that contains the parameters of the warping function. + * \param dM : Resulting warping model derivative returned as a matrix. + */ + virtual void dWarp(const vpColVector &X1, const vpColVector &X2, const vpColVector &p, vpMatrix &dM) = 0; + + /*! + * Compute the compositionnal derivative matrix of the warping function according to the model parameters. + * \param X1 : Point to consider in the derivative computation. + * \param X2 : Point to consider in the derivative computation. + * \param p : Vector that contains the parameters of the warping function. + * \param dwdp0 : Derivative matrix of the warping function according to + * the initial warping function parameters (p=0). + * \param dM : Resulting warping model compositionnal derivative returned as a 2-by-3 matrix. + */ + virtual void dWarpCompo(const vpColVector &X1, const vpColVector &X2, const vpColVector &p, const double *dwdp0, + vpMatrix &dM) = 0; + + /*! + * Compute the derivative of the image with relation to the warping function parameters. + * \param v : Coordinate (along the image rows axis) of the point to consider in the image. + * \param u : Coordinate (along the image columns axis) of the point to consider in the image. + * \param dv : Derivative on the v-axis (along the rows) of the point (u,v). + * \param du : Derivative on the u-axis (along the columns) of the point (u,v). + * \param dIdW : Resulting derivative matrix (image according to the warping function). + */ + virtual void getdW0(const int &v, const int &u, const double &dv, const double &du, double *dIdW) = 0; + + /*! + * Compute the derivative of the warping model \f$M\f$ according to the initial parameters \f$p_0\f$ + * at point \f$X=(u,v)\f$: + * \f[ + * \frac{\partial M}{\partial p}(X, p_0) + * \f] + * + * \param v : Coordinate (along the image rows axis) of the point X(u,v) to consider in the image. + * \param u : Coordinate (along the image columns axis) of the point X(u,v) to consider in the image. + * \param dIdW : Resulting 2-by-3 derivative matrix. + */ + virtual void getdWdp0(const int &v, const int &u, double *dIdW) = 0; + + /*! + * Compute inverse of the RT warping transformation. + * \param p : Vector that contains the parameters corresponding + * to the transformation to inverse. + * \param p_inv : Vector that contains the parameters of the inverse transformation \f$ {M(p)}^{-1}\f$. + */ + virtual void getParamInverse(const vpColVector &p, vpColVector &p_inv) const = 0; + + /*! + * Get the parameters of the warping function one level down + * where image size is divided by two along the lines and the columns. + * \param p : Vector that contains the current parameters of the warping function. + * \param p_down : Vector that contains the resulting parameters one level down. + */ + virtual void getParamPyramidDown(const vpColVector &p, vpColVector &p_down) = 0; + + /*! + * Get the parameters of the warping function one level up + * where image size is multiplied by two along the lines and the columns. + * \param p : Vector that contains the current parameters of the warping function. + * \param p_up : Vector that contains the resulting parameters one level up. + */ + virtual void getParamPyramidUp(const vpColVector &p, vpColVector &p_up) = 0; + + /*! + Tells if the warping function is ESM compatible. + + \return true if it is ESM compatible, false otherwise. + */ + virtual bool isESMcompatible() const = 0; + + /*! + * Compute the RT transformation resulting from the composition of two other RT transformations. + * \param p1 : Vector that contains the parameters corresponding + * to first transformation. + * \param p2 : Vector that contains the parameters corresponding + * to second transformation. + * \param p12 : Vector that contains the resulting transformation \f$ p_{12} = p_1 \circ p_2\f$. + */ + virtual void pRondp(const vpColVector &p1, const vpColVector &p2, vpColVector &p12) const = 0; + + /*! + * Warp point \f$X_1=(u_1,v_1)\f$ using the transformation model with parameters \f$p\f$. + * \f[X_2 = {^2}M_1(p) * X_1\f] + * \param v1 : Coordinate (along the image rows axis) of the point \f$X_1=(u_1,v_1)\f$ to warp. + * \param u1 : Coordinate (along the image columns axis) of the point \f$X_1=(u_1,v_1)\f$ to warp. + * \param v2 : Coordinate of the warped point \f$X_2=(u_2,v_2)\f$ along the image rows axis. + * \param u2 : Coordinate of the warped point \f$X_2=(u_2,v_2)\f$ along the image column axis. + * \param p : Vector that contains the parameters of the transformation. + */ + virtual void warpX(const int &v1, const int &u1, double &v2, double &u2, const vpColVector &p) = 0; + + /*! + * Warp point \f$X_1=(u_1,v_1)\f$ using the RT transformation model. + * \f[X_1 = M(p) * X_2\f] + * \param X1 : Vector corresponding to the coordinates \f$(u_1, v_1)\f$ of the point to warp. + * \param X2 : Vector corresponding to the coordinates \f$(u_2, v_2)\f$ of the warped point. + * \param p : Vector that contains the parameters of the RT transformation. + */ + virtual void warpX(const vpColVector &X1, vpColVector &X2, const vpColVector &p) = 0; + + /*! + * Warp a point X1 with the inverse transformation \f$M\f$. + * \f[ X_2 = {\left( {^1}M_2 \right) }^{-1} \; X_1\f] + * \param X1 : Vector corresponding to the coordinates (u,v) of the point to warp. + * \param X2 : Vector corresponding to the coordinates (u,v) of the warped point. + * \param p : Parameters corresponding to the warping RT model \f${^1}M_2\f$. + */ + virtual void warpXInv(const vpColVector &X1, vpColVector &X2, const vpColVector &p) = 0; + + /** @name Inherited functionalities from vpTemplateTrackerWarp */ + //@{ +#ifndef DOXYGEN_SHOULD_SKIP_THIS + void findWarp(const double *ut0, const double *vt0, const double *u, const double *v, int nb_pt, vpColVector &p); +#endif + /*! + Compute the distance between a zone and its associated warped zone. + + \param Z : Zone to consider. + \param p : Parameters of the warping function. + */ + double getDistanceBetweenZoneAndWarpedZone(const vpTemplateTrackerZone &Z, const vpColVector &p); + + /*! + Get the number of parameters of the warping function. + + \return Number of parameters. + */ + unsigned int getNbParam() const { return nbParam; } + + /*! + Set the number of parameters of the warping function. + + \param nb : New number of parameters. + */ + void setNbParam(unsigned int nb) { nbParam = nb; } + + /*! + Warp a list of points. + + \param ut0 : List of u coordinates of the points. + \param vt0 : List of v coordinates of the points. + \param nb_pt : Number of points to consider. + \param p : Parameters of the warp. + \param u : Resulting u coordinates. + \param v : resulting v coordinates. + */ + void warp(const double *ut0, const double *vt0, int nb_pt, const vpColVector &p, double *u, double *v); + + /*! + Warp a triangle and store the result in a new zone. + + \param in : Triangle to warp. + \param p : Parameters of the warping function. These parameters are + estimated by the template tracker and returned using + vpTemplateTracker::getp(). \param out : Resulting triangle. + */ + void warpTriangle(const vpTemplateTrackerTriangle &in, const vpColVector &p, vpTemplateTrackerTriangle &out); + + /*! + Warp a zone and store the result in a new zone. + + \param in : Zone to warp. + \param p : Parameters of the warping function. These parameters are + estimated by the template tracker and returned using + vpTemplateTracker::getp(). + \param out : Resulting zone. + */ + void warpZone(const vpTemplateTrackerZone &in, const vpColVector &p, vpTemplateTrackerZone &out); + //@} +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarpAffine.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarpAffine.h new file mode 100644 index 00000000..13695cf0 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarpAffine.h @@ -0,0 +1,106 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Template tracker. + * + * Authors: + * Amaury Dame + * Aurelien Yol + */ +/*! + \file vpTemplateTrackerWarpAffine.h + \brief Affine warping function: w(X)=AX+b with: A=[[1+p0, p2], [p1, 1+p3]] + and b= [p4, p5]] +*/ + +#ifndef vpTemplateTrackerWarpAffine_hh +#define vpTemplateTrackerWarpAffine_hh + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpTemplateTrackerWarpAffine + \ingroup group_tt_warp + + This class consider the affine warping model \f$M\f$ with parameters \f$p=(a_1, a_2, a_3, a_4, a_5, a_6)\f$ such as + \f[M(p) = \left[ + \begin{array}{ccc} + a_1 + 1 & a_3 & a_5 \\ + a_2 & a_4 + 1 & a_6 + \end{array} + \right] + \f] + + We recall that u axis is the image horizontal axis, and v axis is the image vertical axis. A point (u,v) with + coordinates (0,0) is located in the top left image corner. + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in the Template Tracker (TT), you may have a look at:
+ + - \ref tutorial-tracking-tt +*/ +class VISP_EXPORT vpTemplateTrackerWarpAffine : public vpTemplateTrackerWarp +{ +public: + vpTemplateTrackerWarpAffine(); + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + void computeCoeff(const vpColVector &) { } + void computeDenom(vpColVector &, const vpColVector &) { } +#endif + + void dWarp(const vpColVector &X, const vpColVector &, const vpColVector &, vpMatrix &dM); + void dWarpCompo(const vpColVector &, const vpColVector &, const vpColVector &p, const double *dwdp0, vpMatrix &dM); + + void getdW0(const int &v, const int &u, const double &dv, const double &du, double *dIdW); + void getdWdp0(const int &v, const int &u, double *dIdW); + + void getParamInverse(const vpColVector &p, vpColVector &p_inv) const; + void getParamPyramidDown(const vpColVector &p, vpColVector &p_down); + void getParamPyramidUp(const vpColVector &p, vpColVector &p_up); + + /*! + * Tells if the warping function is ESM compatible. + * \return false. Affine model is not compatible with ESM. + */ + bool isESMcompatible() const { return false; } + + void pRondp(const vpColVector &p1, const vpColVector &p2, vpColVector &p12) const; + + void warpX(const vpColVector &X1, vpColVector &X2, const vpColVector &p); + void warpX(const int &v1, const int &u1, double &v2, double &u2, const vpColVector &p); + void warpXInv(const vpColVector &X1, vpColVector &X2, const vpColVector &p); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarpHomography.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarpHomography.h new file mode 100644 index 00000000..0988b9a3 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarpHomography.h @@ -0,0 +1,113 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Template tracker. + * + * Authors: + * Amaury Dame + * Aurelien Yol + */ +/*! + \file vpTemplateTrackerWarpHomography.h + \brief warping function of an homography: the homography is directly defined + by the diplacement parameter: H=[[1+p0, p3, p6], [p1, 1+p4, p7], [p2, p5, 1]] +*/ + +#ifndef vpTemplateTrackerWarpHomography_hh +#define vpTemplateTrackerWarpHomography_hh + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpTemplateTrackerWarpHomography + \ingroup group_tt_warp + + This class consider the homography warping model \f$M\f$ with parameters \f$p=(h_1, h_2, h_3, h_4, h_5, h_6, h_7, + h_8)\f$ such as \f[M(p) = \left[ \begin{array}{ccc} + h_1 + 1 & h_4 & h_7 \\ + h_2 & h_5 + 1 & h_8 \\ + h_3 & h_6 & 1 + \end{array} + \right] + \f] + + We recall that u axis is the image horizontal axis, and v axis is the image vertical axis. A point (u,v) with + coordinates (0,0) is located in the top left image corner. + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in the Template Tracker (TT), you may have a look at:
+ + - \ref tutorial-tracking-tt +*/ +class VISP_EXPORT vpTemplateTrackerWarpHomography : public vpTemplateTrackerWarp +{ +public: + vpTemplateTrackerWarpHomography(); + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + void computeCoeff(const vpColVector &) { } +#endif + + void computeDenom(vpColVector &X, const vpColVector &p); + + void dWarp(const vpColVector &, const vpColVector &X, const vpColVector &, vpMatrix &dW); + void dWarpCompo(const vpColVector &X, const vpColVector &, const vpColVector &p, const double *dwdp0, vpMatrix &dW); + + void getdW0(const int &v, const int &u, const double &dv, const double &du, double *dIdW); + void getdWdp0(const int &v, const int &u, double *dIdW); + + vpHomography getHomography(const vpColVector &ParamM) const; + + void getParam(const vpHomography &H, vpColVector &p) const; + void getParam(const vpMatrix &H, vpColVector &p) const; + + void getParamInverse(const vpColVector &p, vpColVector &p_inv) const; + void getParamPyramidDown(const vpColVector &p, vpColVector &p_down); + void getParamPyramidUp(const vpColVector &p, vpColVector &p_up); + + /*! + * Tells if the warping function is ESM compatible. + * \return false. Homography model is not compatible with ESM. + */ + bool isESMcompatible() const { return false; } + + void pRondp(const vpColVector &p1, const vpColVector &p2, vpColVector &p12) const; + + void warpX(const vpColVector &X1, vpColVector &X2, const vpColVector &p); + void warpX(const int &v1, const int &u1, double &v2, double &u2, const vpColVector &p); + void warpXInv(const vpColVector &X1, vpColVector &X2, const vpColVector &p); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarpHomographySL3.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarpHomographySL3.h new file mode 100644 index 00000000..2860f4e2 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarpHomographySL3.h @@ -0,0 +1,105 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Template tracker. + */ + +/*! + *\file vpTemplateTrackerWarpHomographySL3.h + *\brief warping function of an homography: the homography is defined on the + *sl3 lie algebra H=exp(Sum(p[i]* A_i)) A_i is the basis of the SL3 Algebra + */ + +#ifndef vpTemplateTrackerWarpHomographySL3_hh +#define vpTemplateTrackerWarpHomographySL3_hh + +#include + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpTemplateTrackerWarpHomographySL3 + * \ingroup group_tt_warp + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in the Template Tracker (TT), you may have a look at:
+ * + * - \ref tutorial-tracking-tt +*/ +class VISP_EXPORT vpTemplateTrackerWarpHomographySL3 : public vpTemplateTrackerWarp +{ +protected: + vpMatrix G; + vpMatrix dGx; + std::vector A; + +public: + vpTemplateTrackerWarpHomographySL3(); + + void computeCoeff(const vpColVector &p); + void computeDenom(vpColVector &X, const vpColVector &); + + void dWarp(const vpColVector &X1, const vpColVector &X2, const vpColVector &, vpMatrix &dW); + void dWarpCompo(const vpColVector &, const vpColVector &X, const vpColVector &, const double *dwdp0, vpMatrix &dW); + + void findWarp(const double *ut0, const double *vt0, const double *u, const double *v, int nb_pt, vpColVector &p); + + void getdW0(const int &v, const int &u, const double &dv, const double &du, double *dIdW); + void getdWdp0(const int &v, const int &u, double *dIdW); + void getdWdp0(const double &v, const double &u, double *dIdW); + + vpHomography getHomography() const; + + void getParamInverse(const vpColVector &p, vpColVector &p_inv) const; + void getParamPyramidDown(const vpColVector &p, vpColVector &p_down); + void getParamPyramidUp(const vpColVector &p, vpColVector &p_up); + + /*! + * Tells if the warping function is ESM compatible. + * \return true. Homography SL3 model is compatible with ESM. + */ + bool isESMcompatible() const { return true; } + + void pRondp(const vpColVector &p1, const vpColVector &p2, vpColVector &p12) const; + + void warpX(const vpColVector &X1, vpColVector &X2, const vpColVector &); + void warpX(const int &v1, const int &u1, double &v2, double &u2, const vpColVector &); + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + void warpXInv(const vpColVector &, vpColVector &, const vpColVector &) { } +#endif +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarpRT.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarpRT.h new file mode 100644 index 00000000..c07dc0dd --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarpRT.h @@ -0,0 +1,105 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Template tracker. + * + * Authors: + * Amaury Dame + * Aurelien Yol + */ +/*! + \file vpTemplateTrackerWarpRT.h + \brief sRt warping function +*/ + +#ifndef vpTemplateTrackerWarpRT_hh +#define vpTemplateTrackerWarpRT_hh + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpTemplateTrackerWarpRT + \ingroup group_tt_warp + + This class consider the RT (rotation, translation) warping model \f$M\f$ with parameters \f$p=(\theta, t_u, t_v)\f$ + such as \f[M(p) = \left[ \begin{array}{ccc} + \cos(\theta) & -\sin(\theta) & t_u \\ + \sin(\theta) & \cos(\theta) & t_v + \end{array} + \right] + \f] + with \f$\theta\f$ the rotation, and \f$t_u, t_v\f$ the translation along u and v axis in the image. + + We recall that u axis is the image horizontal axis, and v axis is the image vertical axis. A point (u,v) with + coordinates (0,0) is located in the top left image corner. + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in the Template Tracker (TT), you may have a look at:
+ + - \ref tutorial-tracking-tt +*/ +class VISP_EXPORT vpTemplateTrackerWarpRT : public vpTemplateTrackerWarp +{ +public: + vpTemplateTrackerWarpRT(); + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + void computeCoeff(const vpColVector &) { } + void computeDenom(vpColVector &, const vpColVector &) { } +#endif + + void dWarp(const vpColVector &X, const vpColVector &, const vpColVector &p, vpMatrix &dM); + void dWarpCompo(const vpColVector &, const vpColVector &, const vpColVector &p, const double *dwdp0, vpMatrix &dM); + + void getdW0(const int &v, const int &u, const double &dv, const double &du, double *dIdW); + void getdWdp0(const int &v, const int &u, double *dIdW); + + void getParamInverse(const vpColVector &p, vpColVector &p_inv) const; + void getParamPyramidDown(const vpColVector &p, vpColVector &p_down); + void getParamPyramidUp(const vpColVector &p, vpColVector &p_up); + + /*! + * Tells if the warping function is ESM compatible. + * \return false. RT model is not compatible with ESM. + */ + bool isESMcompatible() const { return false; } + + void pRondp(const vpColVector &p1, const vpColVector &p2, vpColVector &p12) const; + + void warpX(const vpColVector &X1, vpColVector &X2, const vpColVector &p); + void warpX(const int &v1, const int &u1, double &v2, double &u2, const vpColVector &p); + void warpXInv(const vpColVector &X1, vpColVector &X2, const vpColVector &p); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarpSRT.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarpSRT.h new file mode 100644 index 00000000..71337cef --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarpSRT.h @@ -0,0 +1,105 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Template tracker. + * + * Authors: + * Amaury Dame + * Aurelien Yol + */ +/*! + \file vpTemplateTrackerWarpSRT.h + \brief sRt warping function +*/ + +#ifndef vpTemplateTrackerWarpSRT_hh +#define vpTemplateTrackerWarpSRT_hh + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpTemplateTrackerWarpSRT + \ingroup group_tt_warp + + This class consider the SRT (scale, rotation, translation) warping model \f$M\f$ with parameters \f$p=(s, \theta, t_u, + t_v)\f$ such as \f[M(p) = \left[ \begin{array}{ccc} + s * \cos(\theta) & - s * \sin(\theta) & t_u \\ + s * \sin(\theta) & s * \cos(\theta) & t_v + \end{array} + \right] + \f] + with \f$s\f$ the scale, \f$\theta\f$ the rotation, and \f$t_u, t_v\f$ the translation along u and v axis in the image. + + We recall that u axis is the image horizontal axis, and v axis is the image vertical axis. A point (u,v) with + coordinates (0,0) is located in the top left image corner. + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in the Template Tracker (TT), you may have a look at:
+ + - \ref tutorial-tracking-tt +*/ +class VISP_EXPORT vpTemplateTrackerWarpSRT : public vpTemplateTrackerWarp +{ +public: + vpTemplateTrackerWarpSRT(); + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + void computeCoeff(const vpColVector &) { } + void computeDenom(vpColVector &, const vpColVector &) { } +#endif + + void dWarp(const vpColVector &X, const vpColVector &, const vpColVector &p, vpMatrix &dM); + void dWarpCompo(const vpColVector &, const vpColVector &, const vpColVector &p, const double *dwdp0, vpMatrix &dM); + + void getdW0(const int &v, const int &u, const double &dv, const double &du, double *dIdW); + void getdWdp0(const int &v, const int &u, double *dIdW); + + void getParamInverse(const vpColVector &p, vpColVector &p_inv) const; + void getParamPyramidDown(const vpColVector &p, vpColVector &p_down); + void getParamPyramidUp(const vpColVector &p, vpColVector &p_up); + + /*! + * Tells if the warping function is ESM compatible. + * \return false. SRT model is not compatible with ESM. + */ + bool isESMcompatible() const { return false; } + + void pRondp(const vpColVector &p1, const vpColVector &p2, vpColVector &p12) const; + + void warpX(const vpColVector &X1, vpColVector &X2, const vpColVector &p); + void warpX(const int &v1, const int &u1, double &v2, double &u2, const vpColVector &p); + void warpXInv(const vpColVector &X1, vpColVector &X2, const vpColVector &p); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarpTranslation.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarpTranslation.h new file mode 100644 index 00000000..be8adda6 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerWarpTranslation.h @@ -0,0 +1,106 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Template tracker. + * + * Authors: + * Amaury Dame + * Aurelien Yol + */ +/*! + \file vpTemplateTrackerWarpTranslation.h + \brief Translation warping function: w(X)=X+b with: b= [p0, p1]] +*/ + +#ifndef vpTemplateTrackerWarpTranslation_hh +#define vpTemplateTrackerWarpTranslation_hh + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpTemplateTrackerWarpTranslation + \ingroup group_tt_warp + + This class consider the translation warping model \f$M\f$ with parameters \f$p=(t_u, t_v)\f$ such as + \f[M(p) = \left[ + \begin{array}{c} + t_u \\ + t_v + \end{array} + \right] + \f] + with \f$t_u, t_v\f$ the translation along u and v axis in the image. + + We recall that u axis is the image horizontal axis, and v axis is the image vertical axis. A point (u,v) with + coordinates (0,0) is located in the top left image corner. + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in the Template Tracker (TT), you may have a look at:
+ + - \ref tutorial-tracking-tt +*/ +class VISP_EXPORT vpTemplateTrackerWarpTranslation : public vpTemplateTrackerWarp +{ +public: + vpTemplateTrackerWarpTranslation(); + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + void computeCoeff(const vpColVector &) { } + void computeDenom(vpColVector &, const vpColVector &) { } +#endif + + void dWarp(const vpColVector &, const vpColVector &, const vpColVector &, vpMatrix &dM); + void dWarpCompo(const vpColVector &, const vpColVector &, const vpColVector &, const double *dwdp0, vpMatrix &dM); + + void getdW0(const int &, const int &, const double &dv, const double &du, double *dIdW); + void getdWdp0(const int &, const int &, double *dIdW); + + void getParamInverse(const vpColVector &p, vpColVector &p_inv) const; + void getParamPyramidDown(const vpColVector &p, vpColVector &p_down); + void getParamPyramidUp(const vpColVector &p, vpColVector &p_up); + + /*! + * Tells if the warping function is ESM compatible. + * \return true. This model is compatible with ESM. + */ + bool isESMcompatible() const { return true; } + + void pRondp(const vpColVector &p1, const vpColVector &p2, vpColVector &p12) const; + + void warpX(const vpColVector &X1, vpColVector &X2, const vpColVector &p); + void warpX(const int &v1, const int &u1, double &v2, double &u2, const vpColVector &p); + void warpXInv(const vpColVector &X1, vpColVector &X2, const vpColVector &p); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerZNCC.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerZNCC.h new file mode 100644 index 00000000..8b83b639 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerZNCC.h @@ -0,0 +1,93 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Template tracker. + * + * Authors: + * Amaury Dame + * Aurelien Yol + */ +/*! + \file vpTemplateTrackerZNCC.h + \brief +*/ + +#ifndef vpTemplateTrackerZNCC_hh +#define vpTemplateTrackerZNCC_hh + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define APPROX_NCC + +BEGIN_VISP_NAMESPACE +/*! + \class vpTemplateTrackerZNCC + \ingroup group_tt_tracker + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in the Template Tracker (TT), you may have a look at:
+ + - \ref tutorial-tracking-tt +*/ +class VISP_EXPORT vpTemplateTrackerZNCC : public vpTemplateTracker +{ +protected: + vpRowVector DI; + vpRowVector temp; + +protected: + double getCost(const vpImage &I, const vpColVector &tp); + double getCost(const vpImage &I) + { + vpColVector tp; + return getCost(I, tp); + } + virtual void initHessienDesired(const vpImage &I) = 0; + virtual void trackNoPyr(const vpImage &I) = 0; + +public: + VP_EXPLICIT vpTemplateTrackerZNCC(vpTemplateTrackerWarp *warp); + + void setGain(double _gain) { gain = _gain; } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerZNCCForwardAdditional.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerZNCCForwardAdditional.h new file mode 100644 index 00000000..694e6a61 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerZNCCForwardAdditional.h @@ -0,0 +1,72 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Template tracker. + * + * Authors: + * Amaury Dame + * Aurelien Yol + */ +/*! + \file vpTemplateTrackerZNCCForwardAdditional.h + \brief +*/ + +#ifndef vpTemplateTrackerZNCCForwardAdditional_hh +#define vpTemplateTrackerZNCCForwardAdditional_hh + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpTemplateTrackerZNCCForwardAdditional + \ingroup group_tt_tracker + The algorithm implemented in this class is described in \cite Irani98a and + \cite Marchand16a. + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in the Template Tracker (TT), you may have a look at:
+ + - \ref tutorial-tracking-tt +*/ +class VISP_EXPORT vpTemplateTrackerZNCCForwardAdditional : public vpTemplateTrackerZNCC +{ +protected: + void initHessienDesired(const vpImage &I); + void trackNoPyr(const vpImage &I); + +public: + VP_EXPLICIT vpTemplateTrackerZNCCForwardAdditional(vpTemplateTrackerWarp *warp); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerZNCCInverseCompositional.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerZNCCInverseCompositional.h new file mode 100644 index 00000000..87a3b7cb --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerZNCCInverseCompositional.h @@ -0,0 +1,79 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Template tracker. + * + * Authors: + * Amaury Dame + * Aurelien Yol + */ +/*! + \file vpTemplateTrackerZNCCInverseCompositional.h + \brief +*/ + +#ifndef vpTemplateTrackerZNCCInverseCompositional_hh +#define vpTemplateTrackerZNCCInverseCompositional_hh + +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpTemplateTrackerZNCCInverseCompositional + \ingroup group_tt_tracker + The algorithm implemented in this class is described in \cite Irani98a and + \cite Marchand16a. + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in the Template Tracker (TT), you may have a look at:
+ + - \ref tutorial-tracking-tt + */ + class VISP_EXPORT vpTemplateTrackerZNCCInverseCompositional : public vpTemplateTrackerZNCC +{ +protected: + bool compoInitialised; + vpColVector moydIrefdp; + +protected: + void initCompInverse(const vpImage &I); + void initHessienDesired(const vpImage &I); + void trackNoPyr(const vpImage &I); + +public: + VP_EXPLICIT vpTemplateTrackerZNCCInverseCompositional(vpTemplateTrackerWarp *warp); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerZone.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerZone.h new file mode 100644 index 00000000..aaaeaf44 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt/vpTemplateTrackerZone.h @@ -0,0 +1,130 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Template tracker. + * + * Authors: + * Amaury Dame + * Aurelien Yol + */ +#ifndef vpTemplateTrackerZone_hh +#define vpTemplateTrackerZone_hh + +#include + +#include +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpTemplateTrackerZone + \ingroup group_tt_tools + A zone is defined by a set of triangles defined as + vpTemplateTrackerTriangle. + + A zone can be initialized either by user interaction using mouse click in a + display device throw initClick(), or by a list of points throw + initFromPoints(). + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in the Template Tracker (TT), you may have a look at:
+ + - \ref tutorial-tracking-tt +*/ +class VISP_EXPORT vpTemplateTrackerZone +{ +protected: + std::vector Zone; //!< Vector of triangles that defines the zone. + int min_x; //!< Bounding box parameter + int min_y; //!< Bounding box parameter + int max_x; //!< Bounding box parameter + int max_y; //!< Bounding box parameter + +public: + vpTemplateTrackerZone(); + vpTemplateTrackerZone(const vpTemplateTrackerZone &z); + virtual ~vpTemplateTrackerZone(); + + // add a triangle to the zone + void add(const vpTemplateTrackerTriangle &t); + void clear(); + void copy(const vpTemplateTrackerZone &z); + + // display the area on an image + void display(const vpImage &I, const vpColor &col = vpColor::green, unsigned int thickness = 3); + void display(const vpImage &I, const vpColor &col = vpColor::green, unsigned int thickness = 3); + + // colorie le tieme triangle + void fillTriangle(vpImage &I, unsigned int id, unsigned char gray_level); + + double getArea() const; + vpImagePoint getCenter() const; + vpImagePoint getCenter(int borne_x, int borne_y) const; + // get bounds of the area + int getMaxx() const; + int getMaxy() const; + int getMinx() const; + int getMiny() const; + vpRect getBoundingBox() const; + + /*! Return the number of triangles that define the zone. \sa getTriangle() + */ + unsigned int getNbTriangle() const { return static_cast(Zone.size()); } + vpTemplateTrackerZone getPyramidDown() const; + // renvoie le ieme triangle de la zone + void getTriangle(unsigned int i, vpTemplateTrackerTriangle &T) const; + vpTemplateTrackerTriangle getTriangle(unsigned int i) const; + + // create an area by clicking on an image + void initClick(const vpImage &I, bool delaunay = false); + // create an area with a pointer of integer that describes a series of + // triangles: + // *pt= t0.S1.x,t0.S1.y,t0.S2.x,t0.S2.y,t0.S3.x,t0.S3.y, t1.S1.x ... + void initFromPoints(const vpImage &I, const std::vector &ip, bool delaunay = false); + + // check if a point is in the area + bool inZone(const int &i, const int &j) const; + bool inZone(const double &i, const double &j) const; + // check if a point is in the area and return the corresponding triangle + // id_triangle where the point is. + bool inZone(const int &i, const int &j, unsigned int &id_triangle) const; + bool inZone(const double &i, const double &j, unsigned int &id_triangle) const; + + vpTemplateTrackerZone &operator=(const vpTemplateTrackerZone &z); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/tt_mi/vpTemplateTrackerMI.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt_mi/vpTemplateTrackerMI.h new file mode 100644 index 00000000..75c6e029 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt_mi/vpTemplateTrackerMI.h @@ -0,0 +1,182 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Example of template tracking. + */ +#ifndef vpTemplateTrackerMI_hh +#define vpTemplateTrackerMI_hh + +#include + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpTemplateTrackerMI + * \ingroup group_tt_mi_tracker + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in the Template Tracker based on Mutual Information (TT-MI), you may have a look at:
+ * + * - \ref tutorial-tracking-tt +*/ +class VISP_EXPORT vpTemplateTrackerMI : public vpTemplateTracker +{ +public: + /*! Hessian approximation. */ + typedef enum + { + HESSIAN_NONSECOND = -1, + HESSIAN_0, + HESSIAN_d2I, + HESSIAN_YOUCEF, + HESSIAN_EXACT, + HESSIAN_NEW + } vpHessienApproximationType; + + /*! Hessian computation. */ + typedef enum { USE_HESSIEN_NORMAL, USE_HESSIEN_DESIRE, USE_HESSIEN_BEST_COND } vpHessienType; + + /*! Hessian computation. */ + typedef enum { BSPLINE_THIRD_ORDER = 3, BSPLINE_FOURTH_ORDER = 4 } vpBsplineType; + +protected: + vpHessienType hessianComputation; + vpHessienApproximationType ApproxHessian; + double lambda; + + double *temp; + double *Prt; + double *dPrt; + double *Pt; + double *Pr; + double *d2Prt; + double *PrtTout; + double *dprtemp; + + double *PrtD; + double *dPrtD; + int influBspline; + + int bspline; + // Nombre de couleur concidere dans l'histogramme + int Nc; + int Ncb; + + vpImage d2Ix; + vpImage d2Iy; + vpImage d2Ixy; + + double MI_preEstimation; + double MI_postEstimation; + + double NMI_preEstimation; + double NMI_postEstimation; + + vpMatrix covarianceMatrix; + bool computeCovariance; + + // Internal vars for computeHessienNormalized() + std::vector m_du; + std::vector m_dv; + std::vector m_A; + std::vector m_dB; + std::vector > m_d2u; + std::vector > m_d2v; + std::vector > m_dA; + +protected: + void computeGradient(); + void computeHessien(vpMatrix &H); + void computeHessienNormalized(vpMatrix &H); + void computeMI(double &MI); + void computeProba(int &nbpoint); + + double getCost(const vpImage &I, const vpColVector &tp) VP_OVERRIDE; + double getCost(const vpImage &I) { return getCost(I, p); } + double getNormalizedCost(const vpImage &I, const vpColVector &tp); + double getNormalizedCost(const vpImage &I) { return getNormalizedCost(I, p); } + void zeroProbabilities(); + + // private: + //#ifndef DOXYGEN_SHOULD_SKIP_THIS + // vpTemplateTrackerMI(const vpTemplateTrackerMI &) + // : vpTemplateTracker(), hessianComputation(USE_HESSIEN_NORMAL), + // ApproxHessian(HESSIAN_0), lambda(0), + // temp(nullptr), Prt(nullptr), dPrt(nullptr), Pt(nullptr), Pr(nullptr), d2Prt(nullptr), + // PrtTout(nullptr), dprtemp(nullptr), PrtD(nullptr), dPrtD(nullptr), + // influBspline(0), bspline(0), Nc(0), Ncb(0), d2Ix(), d2Iy(), d2Ixy(), + // MI_preEstimation(0), MI_postEstimation(0), NMI_preEstimation(0), + // NMI_postEstimation(0), covarianceMatrix(), computeCovariance(false) + // { + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); + // } + // vpTemplateTrackerMI &operator=(const vpTemplateTrackerMI &){ + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); return *this; + // } + //#endif + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpTemplateTrackerMI(const vpTemplateTrackerMI &) = delete; // non construction-copyable + vpTemplateTrackerMI &operator=(const vpTemplateTrackerMI &) = delete; // non copyable +#endif + +public: + //! Default constructor. + vpTemplateTrackerMI() + : vpTemplateTracker(), hessianComputation(USE_HESSIEN_NORMAL), ApproxHessian(HESSIAN_0), lambda(0), temp(nullptr), + Prt(nullptr), dPrt(nullptr), Pt(nullptr), Pr(nullptr), d2Prt(nullptr), PrtTout(nullptr), dprtemp(nullptr), PrtD(nullptr), dPrtD(nullptr), + influBspline(0), bspline(0), Nc(0), Ncb(0), d2Ix(), d2Iy(), d2Ixy(), MI_preEstimation(0), MI_postEstimation(0), + NMI_preEstimation(0), NMI_postEstimation(0), covarianceMatrix(), computeCovariance(false), m_du(), m_dv(), m_A(), + m_dB(), m_d2u(), m_d2v(), m_dA() + { } + VP_EXPLICIT vpTemplateTrackerMI(vpTemplateTrackerWarp *_warp); + virtual ~vpTemplateTrackerMI() VP_OVERRIDE; + vpMatrix getCovarianceMatrix() const { return covarianceMatrix; } + double getMI() const { return MI_postEstimation; } + double getMI(const vpImage &I, int &nc, const int &bspline, vpColVector &tp); + double getMI256(const vpImage &I, const vpColVector &tp); + double getNMI() const { return NMI_postEstimation; } + // initialisation du Hessien en position desiree + void setApprocHessian(vpHessienApproximationType approx) { ApproxHessian = approx; } + void setCovarianceComputation(const bool &flag) { computeCovariance = flag; } + void setHessianComputation(vpHessienType type) { hessianComputation = type; } + void setBspline(const vpBsplineType &newbs); + void setLambda(double _l) { lambda = _l; } + void setNc(int newNc); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/tt_mi/vpTemplateTrackerMIBSpline.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt_mi/vpTemplateTrackerMIBSpline.h new file mode 100644 index 00000000..9a21b4ff --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt_mi/vpTemplateTrackerMIBSpline.h @@ -0,0 +1,132 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Template tracker. + * + * Authors: + * Amaury Dame + * Aurelien Yol + */ +/*! + \file vpTemplateTrackerMIBSpline.h + \brief +*/ + +#ifndef vpTemplateTrackerMIBSpline_hh +#define vpTemplateTrackerMIBSpline_hh + +#include + +#include +#include +#include +#include + +#include + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +BEGIN_VISP_NAMESPACE +/** + * \ingroup group_tt_mi_tracker + * \brief + * + *

Tutorials & Examples

+ + Tutorials
+ If you are interested in the Template Tracker based on Mutual Information (TT-MI), you may have a look at:
+ + - \ref tutorial-tracking-tt + */ + class VISP_EXPORT vpTemplateTrackerMIBSpline +{ +public: + static void PutPVBsplineD(double *Prt, int cr, double er, int ct, double et, int Nc, double val, const int °re); + static void PutPVBsplineD3(double *Prt, int cr, double er, int ct, double et, int Nc, double val); + static void PutPVBsplineD4(double *Prt, int cr, double er, int ct, double et, int Nc, double val); + + static void PutTotPVBspline(double *Prt, int cr, double &er, int ct, double &et, int Nc, double *val, + unsigned int &NbParam, int °ree); + static void PutTotPVBspline(double *Prt, double *dPrt, double *d2Prt, int cr, double &er, int ct, double &et, int Ncb, + double *val, unsigned int &NbParam, int °ree); + static void PutTotPVBspline3(double *Prt, int cr, double &er, int ct, double &et, int Nc, double *val, + unsigned int &NbParam); + static void PutTotPVBspline3(double *Prt, double *dPrt, double *d2Prt, int cr, double &er, int ct, double &et, + int Ncb, double *val, unsigned int &NbParam); + static void PutTotPVBspline4(double *Prt, int cr, double er, int ct, double et, int Nc, double *val, + unsigned int &NbParam); + static void PutTotPVBspline4(double *Prt, double *dPrt, double *d2Prt, int cr, double er, int ct, double et, int Ncb, + double *val, unsigned int &NbParam); + + // AY Optimisation + static void PutTotPVBspline3(double *Prt, double &er, double *et, unsigned int NbParam); + static void PutTotPVBspline4(double *Prt, double &er, double *et, unsigned int NbParam); + // + + static void PutTotPVBsplineNoSecond(double *Prt, int &cr, double &er, int &ct, double &et, int &Nc, double *val, + unsigned int &NbParam, int °ree); + static void PutTotPVBsplineNoSecond(double *Prt, double *dPrt, int &cr, double &er, int &ct, double &et, int &Ncb, + double *val, unsigned int &NbParam, int °ree); + static void PutTotPVBspline3NoSecond(double *Prt, int &cr, double &er, int &ct, double &et, int &Nc, double *val, + unsigned int &NbParam); + static void PutTotPVBspline3NoSecond(double *Prt, double *dPrt, int &cr, double &er, int &ct, double &et, int &Ncb, + double *val, unsigned int &NbParam); + static void PutTotPVBspline4NoSecond(double *Prt, int &cr, double &er, int &ct, double &et, int &Nc, double *val, + unsigned int &NbParam); + static void PutTotPVBspline4NoSecond(double *Prt, double *dPrt, int &cr, double &er, int &ct, double &et, int &Ncb, + double *val, unsigned int &NbParam); + + static void PutTotPVBsplinePrtTout(double *Prt, int &cr, double &er, int &ct, double &et, int &Nc, + unsigned int &NbParam, int °ree); + static void PutTotPVBspline3PrtTout(double *Prt, int &cr, double &er, int &ct, double &et, int &Nc, + unsigned int &NbParam); + static void PutTotPVBspline4PrtTout(double *Prt, int &cr, double &er, int &ct, double &et, int &Nc, + unsigned int &NbParam); + + static void PutTotPVBsplinePrt(double *Prt, int &cr, double &er, int &ct, double &et, int &Ncb, unsigned int &NbParam, + int °ree); + static void PutTotPVBspline3Prt(double *Prt, int &cr, double &er, int &ct, double &et, int &Ncb); + static void PutTotPVBspline4Prt(double *Prt, int &cr, double &er, int &ct, double &et, int &Ncb); + + static double Bspline3(double diff); + static double Bspline4i(double diff, int &interv); + + static double dBspline3(double diff); + static double dBspline4(double diff); + + static double d2Bspline3(double diff); + static double d2Bspline4(double diff); + + static void computeProbabilities(double *Prt, int &cr, double &er, int &ct, double &et, int &Nc, double *dW, + unsigned int &NbParam, int &bspline, + vpTemplateTrackerMI::vpHessienApproximationType &approx, bool use_hessien_des); +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/tt_mi/vpTemplateTrackerMIESM.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt_mi/vpTemplateTrackerMIESM.h new file mode 100644 index 00000000..df00eaa1 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt_mi/vpTemplateTrackerMIESM.h @@ -0,0 +1,120 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Example of template tracking. + * + * Authors: + * Amaury Dame + * Aurelien Yol + */ +#ifndef vpTemplateTrackerMIESM_hh +#define vpTemplateTrackerMIESM_hh + +#include +#include +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpTemplateTrackerMIESM + \ingroup group_tt_mi_tracker + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in the Template Tracker based on Mutual Information (TT-MI), you may have a look at:
+ + - \ref tutorial-tracking-tt +*/ +class VISP_EXPORT vpTemplateTrackerMIESM : public vpTemplateTrackerMI +{ +public: + /*! Minimization method. */ + typedef enum + { + USE_NEWTON, // not used + USE_LMA, // not used + USE_GRADIENT, + USE_QUASINEWTON // not used => see default equivalence + } vpMinimizationTypeMIESM; + +protected: + vpMinimizationTypeMIESM minimizationMethod; + bool CompoInitialised; + vpMatrix HDirect; + vpMatrix HInverse; + vpMatrix HdesireDirect; + vpMatrix HdesireInverse; + vpColVector GDirect; + vpColVector GInverse; + +protected: + void initCompInverse(); + void initHessienDesired(const vpImage &I); + void trackNoPyr(const vpImage &I); + + // private: + //#ifndef DOXYGEN_SHOULD_SKIP_THIS + // vpTemplateTrackerMIESM(const vpTemplateTrackerMIESM &) + // : vpTemplateTrackerMI(), minimizationMethod(USE_NEWTON), + // CompoInitialised(false), + // HDirect(), HInverse(), HdesireDirect(), HdesireInverse(), GDirect(), + // GInverse() + // { + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); + // } + // vpTemplateTrackerMIESM &operator=(const vpTemplateTrackerMIESM &){ + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); return *this; + // } + //#endif + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpTemplateTrackerMIESM(const vpTemplateTrackerMIESM &) = delete; // non construction-copyable + vpTemplateTrackerMIESM &operator=(const vpTemplateTrackerMIESM &) = delete; // non copyable +#endif + +public: + //! Default constructor. + vpTemplateTrackerMIESM() + : vpTemplateTrackerMI(), minimizationMethod(USE_NEWTON), CompoInitialised(false), HDirect(), HInverse(), + HdesireDirect(), HdesireInverse(), GDirect(), GInverse() + { } + VP_EXPLICIT vpTemplateTrackerMIESM(vpTemplateTrackerWarp *_warp); + + void setMinimizationMethod(vpMinimizationTypeMIESM method) { minimizationMethod = method; } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/tt_mi/vpTemplateTrackerMIForwardAdditional.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt_mi/vpTemplateTrackerMIForwardAdditional.h new file mode 100644 index 00000000..21e86b26 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt_mi/vpTemplateTrackerMIForwardAdditional.h @@ -0,0 +1,89 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Example of template tracking. + * + * Authors: + * Amaury Dame + * Aurelien Yol + */ +#ifndef vpTemplateTrackerMIForwardAdditional_hh +#define vpTemplateTrackerMIForwardAdditional_hh + +#include + +#include +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpTemplateTrackerMIForwardAdditional + \ingroup group_tt_mi_tracker + The algorithm implemented in this class is described in \cite Dame12a and + \cite Marchand16a. + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in the Template Tracker based on Mutual Information (TT-MI), you may have a look at:
+ + - \ref tutorial-tracking-tt +*/ +class VISP_EXPORT vpTemplateTrackerMIForwardAdditional : public vpTemplateTrackerMI +{ +public: + /*! Minimization method. */ + typedef enum { USE_NEWTON, USE_LMA, USE_GRADIENT, USE_QUASINEWTON } vpMinimizationTypeMIForwardAdditional; + +private: + vpMinimizationTypeMIForwardAdditional minimizationMethod; + // valeur pour calculer Quasi_Newton + vpColVector p_prec; + vpColVector G_prec; + vpMatrix KQuasiNewton; + +protected: + void initHessienDesired(const vpImage &I); + void trackNoPyr(const vpImage &I); + +public: + //! Default constructor. + vpTemplateTrackerMIForwardAdditional() + : vpTemplateTrackerMI(), minimizationMethod(USE_NEWTON), p_prec(), G_prec(), KQuasiNewton() + { } + VP_EXPLICIT vpTemplateTrackerMIForwardAdditional(vpTemplateTrackerWarp *_warp); + void setMinimizationMethod(vpMinimizationTypeMIForwardAdditional method) { minimizationMethod = method; } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/tt_mi/vpTemplateTrackerMIForwardCompositional.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt_mi/vpTemplateTrackerMIForwardCompositional.h new file mode 100644 index 00000000..e2aa1f25 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt_mi/vpTemplateTrackerMIForwardCompositional.h @@ -0,0 +1,77 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Example of template tracking. + * + * Authors: + * Amaury Dame + * Aurelien Yol + */ +#ifndef vpTemplateTrackerMIForwardCompositional_hh +#define vpTemplateTrackerMIForwardCompositional_hh + +#include + +#include +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpTemplateTrackerMIForwardCompositional + \ingroup group_tt_mi_tracker + The algorithm implemented in this class is described in \cite Dame12a and + \cite Marchand16a. + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in the Template Tracker based on Mutual Information (TT-MI), you may have a look at:
+ + - \ref tutorial-tracking-tt +*/ +class VISP_EXPORT vpTemplateTrackerMIForwardCompositional : public vpTemplateTrackerMI +{ +protected: + bool CompoInitialised; + +protected: + void initCompo(); + void initHessienDesired(const vpImage &I); + void trackNoPyr(const vpImage &I); + +public: + VP_EXPLICIT vpTemplateTrackerMIForwardCompositional(vpTemplateTrackerWarp *_warp); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/tt_mi/vpTemplateTrackerMIInverseCompositional.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt_mi/vpTemplateTrackerMIInverseCompositional.h new file mode 100644 index 00000000..72bd9628 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/tt_mi/vpTemplateTrackerMIInverseCompositional.h @@ -0,0 +1,103 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Example of template tracking. + * + * Authors: + * Amaury Dame + * Aurelien Yol + */ +#ifndef vpTemplateTrackerMIInverseCompositional_hh +#define vpTemplateTrackerMIInverseCompositional_hh + +#include + +#include +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpTemplateTrackerMIInverseCompositional + \ingroup group_tt_mi_tracker + The algorithm implemented in this class is described in \cite Dame12a and + \cite Marchand16a. + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in the Template Tracker based on Mutual Information (TT-MI), you may have a look at:
+ + - \ref tutorial-tracking-tt +*/ +class VISP_EXPORT vpTemplateTrackerMIInverseCompositional : public vpTemplateTrackerMI +{ +public: + /*! Minimization method. */ + typedef enum { USE_NEWTON, USE_LMA, USE_GRADIENT, USE_QUASINEWTON } vpMinimizationTypeMIInverseCompositional; + +private: + vpMinimizationTypeMIInverseCompositional minimizationMethod; + bool CompoInitialised; + bool useTemplateSelect; // use only the strong gradient pixels to compute + // the Jabocian + // valeur pour calculer Quasi_Newton + vpColVector p_prec; + vpColVector G_prec; + vpMatrix KQuasiNewton; + + // bool useAYOptim; + +public: // AY Optimisation + void initTemplateRefBspline(unsigned int ptIndex, double &et); + +protected: + void initCompInverse(const vpImage &I); + void initHessienDesired(const vpImage &I); + void trackNoPyr(const vpImage &I); + +public: + //! Default constructor. + vpTemplateTrackerMIInverseCompositional() + : vpTemplateTrackerMI(), minimizationMethod(USE_LMA), CompoInitialised(false), useTemplateSelect(false), p_prec(), + G_prec(), KQuasiNewton() + { } + VP_EXPLICIT vpTemplateTrackerMIInverseCompositional(vpTemplateTrackerWarp *_warp); + + /*! Use only the strong gradient pixels to compute the Jabobian. By default + * this feature is disabled. */ + void setUseTemplateSelect(bool b) { useTemplateSelect = b; } + void setMinimizationMethod(vpMinimizationTypeMIInverseCompositional method) { minimizationMethod = method; } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpBasicKeyPoint.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpBasicKeyPoint.h new file mode 100644 index 00000000..2b125240 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpBasicKeyPoint.h @@ -0,0 +1,279 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Key point used in matching algorithm. + */ + +#ifndef VP_BASIC_KEYPOINT_H +#define VP_BASIC_KEYPOINT_H + +/*! + * \file vpBasicKeyPoint.h + * \brief Class that defines what is a keypoint. + */ + +#include +#include +#include +#include +#include +#include + +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpBasicKeyPoint + * \ingroup group_vision_keypoints + * + * \brief class that defines what is a keypoint. This class provides + * all the basic elements to implement classes which aims to match + * points from an image to another. +*/ +class VISP_EXPORT vpBasicKeyPoint +{ +public: + /*! + * Basic constructor. + */ + vpBasicKeyPoint() + : m_referenceImagePointsList(), m_currentImagePointsList(), m_matchedReferencePoints(), m_reference_computed(false) + { } + + /*! + * Destructor. + */ + virtual ~vpBasicKeyPoint(); + + /*! + * Build reference. + */ + virtual unsigned int buildReference(const vpImage &I) = 0; + + /*! + * Build reference. + */ + virtual unsigned int buildReference(const vpImage &I, const vpImagePoint &iP, unsigned int height, + unsigned int width) = 0; + + /*! + * Build reference. + */ + virtual unsigned int buildReference(const vpImage &I, const vpRect &rectangle) = 0; + + /*! + * Match keypoints. + */ + virtual unsigned int matchPoint(const vpImage &I) = 0; + + /*! + * Match keypoints. + */ + virtual unsigned int matchPoint(const vpImage &I, const vpImagePoint &iP, unsigned int height, + unsigned int width) = 0; + + /*! + * Match keypoints. + */ + virtual unsigned int matchPoint(const vpImage &I, const vpRect &rectangle) = 0; + + /*! + * Display keypoints. + */ + virtual void display(const vpImage &Iref, const vpImage &Icurrent, unsigned int size = 3) = 0; + + /*! + * Display keypoints. + */ + virtual void display(const vpImage &Icurrent, unsigned int size = 3, const vpColor &color = vpColor::green) = 0; + + /*! + * Indicate wether the reference has been built or not. + * + * \return True if the reference of the current instance has been built. + */ + bool referenceBuilt() const { return m_reference_computed; } + + /*! + * Get the pointer to the complete list of reference points. The pointer is + * const. Thus the points can not be modified + * + * \return The pointer to the complete list of reference points. + */ + inline const vpImagePoint *getAllPointsInReferenceImage() { return &m_referenceImagePointsList[0]; } + + /*! + * Get the nth reference point. This point is copied in the vpImagePoint + * instance given in argument. + * + * \param index : The index of the desired reference point. The index must be + * between 0 and the number of reference points - 1. + * \param referencePoint : + * The coordinates of the desired reference point are copied there. + */ + inline void getReferencePoint(unsigned int index, vpImagePoint &referencePoint) + { + if (index >= m_referenceImagePointsList.size()) { + vpTRACE("Index of the reference point out of range"); + throw(vpException(vpException::fatalError, "Index of the reference point out of range")); + } + + referencePoint.set_ij(m_referenceImagePointsList[index].get_i(), m_referenceImagePointsList[index].get_j()); + } + + /*! + * Get the nth couple of reference point and current point which have been + * matched. These points are copied in the vpImagePoint instances given in + * argument. + * + * \param index : The index of the desired couple of reference point and + * current point. The index must be between 0 and the number of matched + * points - 1. + * \param referencePoint : The coordinates of the desired reference point are copied here. + * \param currentPoint : The coordinates of the desired current point are copied here. + */ + inline void getMatchedPoints(unsigned int index, vpImagePoint &referencePoint, vpImagePoint ¤tPoint) + { + if (index >= m_matchedReferencePoints.size()) { + vpTRACE("Index of the matched points out of range"); + throw(vpException(vpException::fatalError, "Index of the matched points out of range")); + } + referencePoint.set_ij(m_referenceImagePointsList[m_matchedReferencePoints[index]].get_i(), + m_referenceImagePointsList[m_matchedReferencePoints[index]].get_j()); + currentPoint.set_ij(m_currentImagePointsList[index].get_i(), m_currentImagePointsList[index].get_j()); + } + + /*! + * Get the nth matched reference point index in the complete list of + * reference point. + * + * In the code below referencePoint1 and referencePoint2 correspond to the + * same matched reference point. + * + * \code + * vpKeyPoint keypoint; + * + * // Here the code to compute the reference points and the current points. + * vpImagePoint referencePoint1; + * vpImagePoint currentPoint; + * keypoint.getMatchedPoints(1, referencePoint1, currentPoint); //Get the first matched points + * + * vpImagePoint referencePoint2; + * const vpImagePoint* referencePointsList = keypoint.getAllPointsInReferenceImage(); + * // Get the first matched reference point index in the complete reference point list + * int index = keypoint.getIndexInAllReferencePointList(1); + * // Get the first matched reference point + * referencePoint2 = referencePointsList[index]; + * \endcode + */ + inline unsigned int getIndexInAllReferencePointList(unsigned int indexInMatchedPointList) + { + if (indexInMatchedPointList >= m_matchedReferencePoints.size()) { + vpTRACE("Index of the matched reference point out of range"); + throw(vpException(vpException::fatalError, "Index of the matched reference point out of range")); + } + return m_matchedReferencePoints[indexInMatchedPointList]; + } + + /*! + * Get the number of reference points. + * + * \return the number of reference points. + */ + inline unsigned int getReferencePointNumber() const { return static_cast(m_referenceImagePointsList.size()); } + + /*! + * Get the number of matched points. + * + * \return the number of matched points. + */ + inline unsigned int getMatchedPointNumber() const { return static_cast(m_matchedReferencePoints.size()); } + + /*! + * Return the vector of reference image point. + * + * \warning Should not be modified. + * + * \return Vector of reference image point. + */ + const std::vector &getReferenceImagePointsList() const { return m_referenceImagePointsList; } + + /*! + * Return the vector of current image point. + * + * \warning Should not be modified. + * + * \return Vector of the current image point. + */ + const std::vector &getCurrentImagePointsList() const { return m_currentImagePointsList; } + + /*! + * Return the index of the matched associated to the current image point i. + * The ith element of the vector is the index of the reference image point + * matching with the current image point. + * + * \warning Should not be modified. + * + * \return The vector of matching index. + */ + const std::vector &getMatchedReferencePoints() const { return m_matchedReferencePoints; } + +protected: + /*! + * List of the points which define the reference. + */ + std::vector m_referenceImagePointsList; + + /*! + * List of the points which belong to the current image and have + * been matched with points belonging to the reference. + */ + std::vector m_currentImagePointsList; + + /*! + * Array containing the index in the array "m_referenceImagePointsList" of the + * reference points which have been matched. + * + * The first element of the "m_currentImagePointsList" array is matched with + * the nth element of the "m_referenceImagePointsList" array. The value of n is + * stored in the first element of the "m_matchedReferencePoints" array. + */ + std::vector m_matchedReferencePoints; + + /*! + * Flag to indicate if the reference has been built. + */ + bool m_reference_computed; + +private: + virtual void init() = 0; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpCalibration.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpCalibration.h new file mode 100644 index 00000000..f25e3cbe --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpCalibration.h @@ -0,0 +1,348 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Camera calibration. + */ + +/*! + * \file vpCalibration.h + * \brief Tools for camera calibration. + */ +#ifndef VP_CALIBRATION_H +#define VP_CALIBRATION_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpCalibration + * + * \ingroup group_vision_calib + * + * \brief Tools for perspective camera intrinsic parameters calibration. + * + * See \ref tutorial-calibration-intrinsic for more details. +*/ +class VISP_EXPORT vpCalibration +{ +public: + /*! + * Minimization algorithm use to estimate the camera parameters. + */ + typedef enum + { + CALIB_LAGRANGE, /*!< Lagrange approach without estimation of the + distortion. */ + CALIB_VIRTUAL_VS, /*!< Virtual visual servoing approach without estimation + of the distortion (results are similar to Lowe + approach). */ + CALIB_VIRTUAL_VS_DIST, /*!< Virtual visual servoing approach with + estimation of the distortion. */ + CALIB_LAGRANGE_VIRTUAL_VS, /*!< Lagrange approach first, than virtual + visual servoing approach, without + estimation of the distortion. */ + CALIB_LAGRANGE_VIRTUAL_VS_DIST, /*!< Lagrange approach first, than virtual + visual servoing approach, with + estimation of the distortion. */ + } vpCalibrationMethodType; + + /*! + * Pose computed using camera parameters without distortion (as a 3x4 matrix [R T]) + */ + vpHomogeneousMatrix cMo; + /*! + * Pose computed using camera parameters with distortion with distortion model + * (as a 3x4 matrix [R T]) + */ + vpHomogeneousMatrix cMo_dist; + /*! + * Camera intrinsic parameters for perspective projection model without distortion + */ + vpCameraParameters cam; + /*! + * Camera intrinsic parameters for perspective projection model with distortion + */ + vpCameraParameters cam_dist; + + /*! + * Position of the effector in relation to the reference coordinates (manipulator base coordinates) + */ + vpHomogeneousMatrix rMe; + /*! + * Position of the camera in end-effector frame using camera parameters without distortion + */ + vpHomogeneousMatrix eMc; + /*! + * Position of the camera in end-effector frame using camera parameters with distortion + */ + vpHomogeneousMatrix eMc_dist; + + /*! + * Fix aspect ratio (px/py) + */ + double m_aspect_ratio; + + /*! + * Default constructor. + */ + vpCalibration(); + + /*! + * Copy constructor. + */ + vpCalibration(const vpCalibration &c); + + /*! + * Destructor : delete the array of point (freed the memory) + */ + virtual ~vpCalibration(); + + /*! + * Add a new point in the array of points. + * \param X : 3D coordinate \f$ X \f$ of a point in the object frame. + * \param Y : 3D coordinate \f$ Y \f$ of a point in the object frame. + * \param Z : 3D coordinate \f$ Z \f$ of a point in the object frame. + * \param ip : 2D Coordinates of the point in the camera frame. + */ + int addPoint(double X, double Y, double Z, vpImagePoint &ip); + + /*! + * Copy operator. + * + * \param twinCalibration : Object to be copied. + */ + vpCalibration &operator=(const vpCalibration &twinCalibration); + + /*! + * Suppress all the point in the array of point. + */ + int clearPoint(); + + /*! + * Compute the calibration according to the desired method using one pose. + * + * \param method : Method that will be used to estimate the parameters. + * \param cMo_est : estimated homogeneous matrix that defines the pose. + * \param cam_est : estimated intrinsic camera parameters. + * \param verbose : set at true if information about the residual at each loop + * of the algorithm is hoped. + * + * \return EXIT_SUCCESS if the calibration succeed, EXIT_FAILURE otherwise. + */ + int computeCalibration(vpCalibrationMethodType method, vpHomogeneousMatrix &cMo_est, vpCameraParameters &cam_est, + bool verbose = false); + + /*! + * Compute the multi-images calibration according to the desired method using + * many poses. + * + * \param method : Method used to estimate the camera parameters. + * \param table_cal : Vector of vpCalibration. + * \param cam_est : Estimated intrinsic camera parameters. + * \param globalReprojectionError : Global reprojection error or global + * residual. + * \param verbose : Set at true if information about the residual at + * each loop of the algorithm is hoped. + * + * \return EXIT_SUCCESS if the calibration succeed, EXIT_FAILURE otherwise. + */ + static int computeCalibrationMulti(vpCalibrationMethodType method, std::vector &table_cal, + vpCameraParameters &cam_est, double &globalReprojectionError, bool verbose = false); + + /*! + * Compute and return the standard deviation expressed in pixel + * for pose matrix and camera intrinsic parameters. + * \param deviation : the standard deviation computed for the model without + * distortion. + * \param deviation_dist : the standard deviation computed for the + * model with distortion. + */ + void computeStdDeviation(double &deviation, double &deviation_dist); + + /*! + * Compute and return the standard deviation expressed in pixel + * for pose matrix and camera intrinsic parameters for model without + * distortion. + * + * \param cMo_est : the matrix that defines the pose to be tested. + * \param camera : camera intrinsic parameters to be tested. + * \return the standard deviation by point of the error in pixel . + */ + double computeStdDeviation(const vpHomogeneousMatrix &cMo_est, const vpCameraParameters &camera); + + /*! + * Compute and return the standard deviation expressed in pixel + * for pose matrix and camera intrinsic parameters with pixel to meter model. + * + * \param cMo_est : the matrix that defines the pose to be tested. + * \param camera : camera intrinsic parameters to be tested. + * \return the standard deviation by point of the error in pixel . + */ + double computeStdDeviation_dist(const vpHomogeneousMatrix &cMo_est, const vpCameraParameters &camera); + + /*! + * Display the data of the calibration (center of the tracked dots). + * + * \param I : Image where to display data. + * \param color : Color of the data. + * \param thickness : Thickness of the displayed data. + * \param subsampling_factor : Subsampling factor. Default value is 1. + * Admissible values are multiple of 2. Divide by this parameter the + * coordinates of the data points resulting from image processing. + */ + int displayData(vpImage &I, vpColor color = vpColor::red, unsigned int thickness = 1, + int subsampling_factor = 1); + + /*! + * Display estimated centers of dots using intrinsic camera parameters + * with model with distortion and the computed pose. + * \param I : Image where to display grid data. + * \param color : Color of the data. + * \param thickness : Thickness of the displayed data. + * \param subsampling_factor : Subsampling factor. Default value is 1. + * Admissible values are multiple of 2. Divide by this parameter the + * values of the camera parameters. + */ + int displayGrid(vpImage &I, vpColor color = vpColor::yellow, unsigned int thickness = 1, + int subsampling_factor = 1); + + //! Get the gain of the virtual visual servoing algorithm. + static double getLambda() { return m_gain; } + + /*! + * Get the residual in pixels. + */ + double getResidual(void) const { return m_residual; } + + /*! + * Get the residual for perspective projection with distortion (in pixels). + */ + double getResidual_dist(void) const { return m_residual_dist; } + + /*! + * Get the number of points. + */ + unsigned int get_npt() const { return m_npt; } + + /*! + * Basic initialisation (called by the constructors). + */ + int init(); + + /*! + * Read data from disk : + * data are organized as follow oX oY oZ u v + * + * \param filename : Name of the file. + */ + int readData(const std::string &filename); + + /*! + * Read calibration grid coordinates from disk. + * Data are organized as follow oX oY oZ + * + * \param filename : Name of the file. + * \param n : Number of points in the calibration grid. + * \param oX : List of oX coordinates. + * \param oY : List of oY coordinates. + * \param oZ : List of oZ coordinates. + * \param verbose : Additional printings if true (number of points on + * the calibration grid and their respective coordinates in the object + * frame). + * + * \return 0 if success, -1 if an error occurs. + */ + static int readGrid(const std::string &filename, unsigned int &n, std::list &oX, std::list &oY, + std::list &oZ, bool verbose = false); + + /*! + * Set the gain for the virtual visual servoing algorithm. + */ + static void setLambda(const double &lambda) { m_gain = lambda; } + + /*! + * Set pixel aspect ratio px/py. + * + * \param[in] aspect_ratio : px/py aspect ratio. Value need to be positive. + * To estimate a model where px=py set 1 as aspect ratio. + */ + void setAspectRatio(double aspect_ratio); + + /*! + * Write data into a file. + * + * Data are organized as follow oX oY oZ u v + * + * \param filename : Name of the file. + */ + int writeData(const std::string &filename); + +private: + void computePose(const vpCameraParameters &cam, vpHomogeneousMatrix &cMo); + void calibLagrange(vpCameraParameters &cam, vpHomogeneousMatrix &cMo); + + //! Compute the calibration using virtual visual servoing approach + void calibVVS(vpCameraParameters &cam, vpHomogeneousMatrix &cMo, bool verbose = false); + + static void calibVVSMulti(unsigned int nbPose, vpCalibration table_cal[], vpCameraParameters &cam, + bool verbose = false, double aspect_ratio = -1); + static void calibVVSMulti(std::vector &table_cal, vpCameraParameters &cam, + double &globalReprojectionError, bool verbose = false, double aspect_ratio = -1); + void calibVVSWithDistortion(vpCameraParameters &cam, vpHomogeneousMatrix &cMo, bool verbose = false); + static void calibVVSWithDistortionMulti(unsigned int nbPose, vpCalibration table_cal[], vpCameraParameters &cam, + bool verbose = false, double aspect_ratio = -1); + static void calibVVSWithDistortionMulti(std::vector &table_cal, vpCameraParameters &cam, + double &globalReprojectionError, bool verbose = false, + double aspect_ratio = -1); + + unsigned int m_npt; //!< number of points used in calibration computation + std::list m_LoX, m_LoY, m_LoZ; //!< list of points coordinates (3D in meters) + std::list m_Lip; //!< list of points coordinates (2D in pixels) + + double m_residual; //!< residual in pixel for camera model without distortion + double m_residual_dist; //!< residual in pixel for perspective projection with + //!< distortion model + + static double m_threshold; + static unsigned int m_nbIterMax; + static double m_gain; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpCalibrationException.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpCalibrationException.h new file mode 100644 index 00000000..8b9c5e60 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpCalibrationException.h @@ -0,0 +1,95 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Exceptions that can be emitted by the vpCalibration class and its derivatives. + */ + +#ifndef VP_CALIBRATION_EXCEPTION_H +#define VP_CALIBRATION_EXCEPTION_H + +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpCalibrationException + * \brief Error that can be emitted by the vpCalibration class. +*/ +class VISP_EXPORT vpCalibrationException : public vpException +{ +public: + /*! + * \brief Lists the possible error than can be emitted while calling + * vpCalibration member + */ + enum errorCodeEnum + { + //! Error returns by a constructor + constructionError, + //! Something is not initialized + notInitializedError, + //! Function not implemented + notImplementedError, + //! Index out of range + outOfRangeError, + //! Iterative algorithm doesn't converge + convergencyError, + //! Forbidden operator + forbiddenOperatorError, + }; + +public: + /*! + * Constructor. + */ + vpCalibrationException(int id, const char *format, ...) + { + this->code = id; + va_list args; + va_start(args, format); + setMessage(format, args); + va_end(args); + } + + /*! + * Constructor. + */ + vpCalibrationException(int id, const std::string &msg) : vpException(id, msg) { } + + /*! + * Constructor. + */ + VP_EXPLICIT vpCalibrationException(int id) : vpException(id) { } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpHandEyeCalibration.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpHandEyeCalibration.h new file mode 100644 index 00000000..f6724616 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpHandEyeCalibration.h @@ -0,0 +1,152 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Hand-eye calibration. + */ + +/*! + * \file vpHandEyeCalibration.h + * \brief Tools for hand-eye calibration. + */ +#ifndef VP_HAND_EYE_CALIBRATION_H +#define VP_HAND_EYE_CALIBRATION_H + +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE + +/*! + * \class vpHandEyeCalibration + * + * \ingroup group_vision_calib + * + * \brief Tool for hand-eye calibration. + * This class is able to consider eye-in-hand and eye-to-hand configurations. + * + * Let us consider: + * - \f$^r{\bf M}_e\f$ the homogeneous transformation between the robot reference frame and the + * robot end-effector, + * - \f$^c{\bf M}_o\f$ the homogeneous transformation between the camera frame and an object frame attached to the + * robot end-effector, + * - \f$^r{\bf M}_c\f$ the homogeneous transformation between the robot reference frame and the camera frame, + * - \f$^e{\bf M}_o\f$ the homogeneous transformation between the robot end-effector frame and the object frame + * attached to the end-effector. + * + * The hand-eye calibration process implemented in this class allows from the basket of + * \f$\{^r{\bf M}_e, ^c{\bf M}_o\}_i\f$ corresponding to couple of poses \f$ i \f$ to estimate: + * - in an eye-in-hand configuration \f$ {^r}{\bf M}_o \f$ and \f$ {^e}{\bf M}_c \f$ constant homogeneous extrinsic + * transformations (see \ref tutorial-calibration-extrinsic-eye-in-hand), + * - in an eye-to-hand configuration \f$ {^r}{\bf M}_c \f$ and \f$ {^e}{\bf M}_o \f$ constant homogeneous extrinsic + * transformations (see \ref tutorial-calibration-extrinsic-eye-to-hand). + */ + class VISP_EXPORT vpHandEyeCalibration +{ +public: + /*! + * Perform hand-eye calibration: + * - For the eye-in hand configuration, compute the constant transformations + * from the end effector to the camera frames (eMc), and from the robot + * reference to the object frames (rMo). + * - For the eye-to hand configuration, compute the constant transformations + * from the end effector to the object frames (eMo), and from the robot + * reference to the camera frames (rMo). + * + * \param[in] cMo : Vector of homogeneous matrices representing the transformation + * between the camera and the object for the eye-in-hand configuration and + * the inverse transformation for the eye-to-hand configuration (oMc). + * \param[in] rMe : Vector of homogeneous matrices representing the + * corresponding transformation between the end effector and robot reference + * frame. Must be the same size as cMo. + * + * \param[out] eMc : Homogeneous matrix representing the transformation + * between the effector and the camera in the eye-in-hand configuration and + * between the effector and the object in the eye-to-hand configuration. + * + * \param[out] rMo : Homogeneous matrix representing the transformation + * between the robot reference and the object in the eye-in-hand configuration + * and between the robot reference and the camera in the eye-to-hand configuration. + * + * \return 0 if calibration succeed, -1 if the system is not full rank, 1 if the algorithm doesn't converge. + */ + static int calibrate(const std::vector &cMo, const std::vector &rMe, + vpHomogeneousMatrix &eMc, vpHomogeneousMatrix &rMo); + /*! + * Perform hand-eye calibration: + * - For the eye-in hand configuration, compute the constant transformations + * from the end effector to the camera frames (eMc), and from the robot + * reference to the object frames (rMo). + * - For the eye-to hand configuration, compute the constant transformations + * from the end effector to the object frames (eMo), and from the robot + * reference to the camera frames (rMo). + * + * \param[in] cMo : Vector of homogeneous matrices representing the transformation + * between the camera and the object for the eye-in-hand configuration and + * the inverse transformation for the eye-to-hand configuration (oMc). + * \param[in] rMe : Vector of homogeneous matrices representing the + * corresponding transformation between the end effector and robot reference + * frame. Must be the same size as cMo. + * + * \param[out] eMc : Homogeneous matrix representing the transformation + * between the effector and the camera in the eye-in-hand configuration and + * between the effector and the object in the eye-to-hand configuration. + * + * \return 0 if calibration succeed, -1 if the system is not full rank, 1 if the algorithm doesn't converge. + */ + static int calibrate(const std::vector &cMo, const std::vector &rMe, + vpHomogeneousMatrix &eMc); + +private: + static void calibrationVerifrMo(const std::vector &cMo, const std::vector &rMe, + const vpHomogeneousMatrix &eMc, vpHomogeneousMatrix &rMo); + static void calibrationVerifrMo(const std::vector &cMo, + const std::vector &rMe, const vpHomogeneousMatrix &eMc); + static int calibrationRotationTsai(const std::vector &cMo, + const std::vector &rMe, vpRotationMatrix &eRc); + static int calibrationRotationTsaiOld(const std::vector &cMo, + const std::vector &rMe, vpRotationMatrix &eRc); + static int calibrationRotationProcrustes(const std::vector &cMo, + const std::vector &rMe, vpRotationMatrix &eRc); + static int calibrationTranslation(const std::vector &cMo, + const std::vector &rMe, vpRotationMatrix &eRc, + vpTranslationVector &eTc); + static int calibrationTranslationOld(const std::vector &cMo, + const std::vector &rMe, vpRotationMatrix &eRc, + vpTranslationVector &eTc); + static double calibrationErrVVS(const std::vector &cMo, const std::vector &rMe, + const vpHomogeneousMatrix &eMc, vpColVector &errVVS); + static int calibrationVVS(const std::vector &cMo, const std::vector &rMe, + vpHomogeneousMatrix &eMc); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpHomography.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpHomography.h new file mode 100644 index 00000000..8ade54a0 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpHomography.h @@ -0,0 +1,715 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Homography transformation. + */ + +/*! + * \file vpHomography.h + * + * This file defines an homography transformation. This class aims to provide + * some tools for homography computation. + */ + +#ifndef VP_HOMOGRAPHY_H +#define VP_HOMOGRAPHY_H + +#include +#include + +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE + +/*! + * \class vpHomography + * \ingroup group_vision_homography + * + * \brief Implementation of an homography and operations on homographies. + * + * This class aims to compute the homography wrt. two images \cite Marchand16a. + * + * The vpHomography class is derived from vpArray2D. + * + * These two images are both described by a set of points. The 2 sets (one per + * image) are sets of corresponding points : for a point in a image, there is + * the corresponding point (image of the same 3D point) in the other image + * points set. These 2 sets are the only data needed to compute the + * homography. One method used is the one introduced by Ezio Malis during his + * PhD \cite TheseMalis. A normalization is carried out on this points in order + * to improve the conditioning of the problem, what leads to improve the + * stability of the result. + * + * Store and compute the homography such that + * \f[ + * ^a{\bf p} = ^a{\bf H}_b\; ^b{\bf p} + * \f] + * + * with + * \f[ * ^a{\bf H}_b = ^a{\bf R}_b + \frac{^a{\bf t}_b}{^bd} + * { ^b{\bf n}^T} + * \f] + * + * The \ref tutorial-homography explains how to use this class. + * + * The example below shows also how to manipulate this class to first + * compute a ground truth homography from camera poses, project pixel + * coordinates points using an homography and lastly estimate an + * homography from a subset of 4 matched points in frame a and frame b + * respectively. + * + * \code + * #include + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * // Initialize in the object frame the coordinates in meters of 4 points that + * // belong to a planar object + * vpPoint Po[4]; + * Po[0].setWorldCoordinates(-0.1, -0.1, 0); + * Po[1].setWorldCoordinates( 0.2, -0.1, 0); + * Po[2].setWorldCoordinates( 0.1, 0.1, 0); + * Po[3].setWorldCoordinates(-0.1, 0.3, 0); + * + * // Initialize the pose between camera frame a and object frame o + * vpHomogeneousMatrix aMo(0, 0, 1, 0, 0, 0); // Camera is 1 meter far + * + * // Initialize the pose between camera frame a and camera frame + * // b. These two frames correspond for example to two successive + * // camera positions + * vpHomogeneousMatrix aMb(0.2, 0.1, 0, 0, 0, vpMath::rad(2)); + * + * // Compute the pose between camera frame b and object frame + * vpHomogeneousMatrix bMo = aMb.inverse() * aMo; + * + * // Initialize camera intrinsic parameters + * vpCameraParameters cam; + * + * // Compute the coordinates in pixels of the 4 object points in the + * // camera frame a + * vpPoint Pa[4]; + * std::vector xa(4), ya(4); // Coordinates in pixels of the points in frame a + * for(int i=0 ; i < 4 ; ++i) { + * Pa[i] = Po[i]; Pa[i].project(aMo); // Project the points from object frame to camera frame a + * vpMeterPixelConversion::convertPoint(cam, + * Pa[i].get_x(), Pa[i].get_y(), + * xa[i], ya[i]); + * } + * + * // Compute the coordinates in pixels of the 4 object points in the + * // camera frame b + * vpPoint Pb[4]; + * std::vector xb(4), yb(4); // Coordinates in pixels of the points in frame b + * for(int i=0 ; i < 4 ; ++i) { + * Pb[i] = Po[i]; Pb[i].project(bMo); // Project the points from object frame to camera frame a + * } + * + * // Compute equation of the 3D plane containing the points in camera frame b + * vpPlane bP(Pb[0], Pb[1], Pb[2]); + * + * // Compute the corresponding ground truth homography + * vpHomography aHb(aMb, bP); + * + * std::cout << "Ground truth homography aHb: \n" << aHb<< std::endl; + * + * // Compute the coordinates of the points in frame b using the ground + * // truth homography and the coordinates of the points in frame a + * vpHomography bHa = aHb.inverse(); + * for(int i = 0; i < 4 ; ++i){ + * double inv_z = 1. / (bHa[2][0] * xa[i] + bHa[2][1] * ya[i] + bHa[2][2]); + * + * xb[i] = (bHa[0][0] * xa[i] + bHa[0][1] * ya[i] + bHa[0][2]) * inv_z; + * yb[i] = (bHa[1][0] * xa[i] + bHa[1][1] * ya[i] + bHa[1][2]) * inv_z; + * } + * + * // Estimate the homography from 4 points coordinates expressed in pixels + * vpHomography::DLT(xb, yb, xa, ya, aHb, true); + * aHb /= aHb[2][2]; // Apply a scale factor to have aHb[2][2] = 1 + * + * std::cout << "Estimated homography aHb: \n" << aHb<< std::endl; + } + \endcode + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in using this class, you may have a look at:
+ * + * - \ref tutorial-homography +*/ +class VISP_EXPORT vpHomography : public vpArray2D +{ +public: + /*! + * Initialize an homography as identity. + */ + vpHomography(); + /*! + * Initialize an homography from another homography. + */ + vpHomography(const vpHomography &H); + //! Construction from translation and rotation and a plane. + vpHomography(const vpHomogeneousMatrix &aMb, const vpPlane &bP); + //! Construction from translation and rotation and a plane. + vpHomography(const vpRotationMatrix &aRb, const vpTranslationVector &atb, const vpPlane &bP); + //! Construction from translation and rotation and a plane. + vpHomography(const vpThetaUVector &tu, const vpTranslationVector &atb, const vpPlane &bP); + //! Construction from translation and rotation and a plane. + vpHomography(const vpPoseVector &arb, const vpPlane &bP); + + //! Construction from translation and rotation and a plane + vpHomography &buildFrom(const vpRotationMatrix &aRb, const vpTranslationVector &atb, const vpPlane &bP); + //! Construction from translation and rotation and a plane + vpHomography &buildFrom(const vpThetaUVector &tu, const vpTranslationVector &atb, const vpPlane &bP); + //! Construction from translation and rotation and a plane + vpHomography &buildFrom(const vpPoseVector &arb, const vpPlane &bP); + //! Construction from homogeneous matrix and a plane + vpHomography &buildFrom(const vpHomogeneousMatrix &aMb, const vpPlane &bP); + + /*! + * Transform an homography from pixel space to calibrated domain. + * + * Given homography \f$\bf G\f$ corresponding to the collineation matrix in the pixel space, + * compute the homography matrix \f$\bf H\f$ in the Euclidean space or calibrated domain using: + * \f[ {\bf H} = {\bf K}^{-1} {\bf G} {\bf K} \f] + * \param[in] cam : Camera parameters used to fill \f${\bf K}\f$ matrix such as + * \f[{\bf K} = + * \left[ \begin{array}{ccc} + * p_x & 0 & u_0 \\ + * 0 & p_y & v_0 \\ + * 0 & 0 & 1 + * \end{array}\right] + * \f] + * \return The corresponding homography matrix \f$\bf H\f$ in the Euclidean space or calibrated domain. + * + * \sa homography2collineation() + */ + vpHomography collineation2homography(const vpCameraParameters &cam) const; + + /*! + * Converts an homography to a matrix. + * \return The 3x3 matrix corresponding to the homography. + */ + vpMatrix convert() const; + + /*! + * Compute the camera displacement between two images from the homography \f$ + * {^a}{\bf H}_b \f$ which is here an implicit parameter (*this). + * + * \param aRb : Rotation matrix as an output \f$ {^a}{\bf R}_b \f$. + * \param atb : Translation vector as an output \f$ ^a{\bf t}_b \f$. + * \param n : Normal vector to the plane as an output. + */ + void computeDisplacement(vpRotationMatrix &aRb, vpTranslationVector &atb, vpColVector &n); + + /*! + * Compute the camera displacement between two images from the homography \f$ + * {^a}{\bf H}_b \f$ which is here an implicit parameter (*this). + * + * Camera displacement between \f$ {^a}{\bf p} \f$ and \f$ {^a}{\bf p} \f$ is + * represented as a rotation matrix \f$ {^a}{\bf R}_b \f$ and a translation + * vector \f$ ^a{\bf t}_b \f$ from which an homogeneous matrix can be build + * (vpHomogeneousMatrix). + * + * \param nd : Input normal vector to the plane used to compar with the normal + * vector \e n extracted from the homography. + * \param aRb : Rotation matrix as an output \f$ {^a}{\bf R}_b \f$. + * \param atb : Translation vector as an output \f$ ^a{\bf t}_b \f$. + * \param n : Normal vector to the plane as an output. + */ + void computeDisplacement(const vpColVector &nd, vpRotationMatrix &aRb, vpTranslationVector &atb, vpColVector &n); + + /*! + * Return homography determinant. + */ + double det() const; + + /*! + * Set the homography as identity transformation by setting the diagonal to 1 + * and all other values to 0. + */ + void eye(); + + /*! + * Transform an homography from calibrated domain to pixel space. + * + * Given homography \f$\bf H\f$ in the Euclidean space or in the calibrated domain, + * compute the homography \f$\bf G\f$ corresponding to the collineation matrix in the pixel space using: + * \f[ {\bf G} = {\bf K} {\bf H} {\bf K}^{-1} \f] + * \param[in] cam : Camera parameters used to fill \f${\bf K}\f$ matrix such as + * \f[{\bf K} = + * \left[ \begin{array}{ccc} + * p_x & 0 & u_0 \\ + * 0 & p_y & v_0 \\ + * 0 & 0 & 1 + * \end{array}\right] + * \f] + * \return The corresponding collineation matrix \f$\bf G\f$ in the pixel space. + * + * \sa collineation2homography() + */ + vpHomography homography2collineation(const vpCameraParameters &cam) const; + + /*! + * Return inverted homography. + * + * \param[in] sv_threshold : Threshold used to test the singular values. If + * a singular value is lower than this threshold we consider that the + * homography is not full rank. + * + * \param[out] rank : Rank of the homography that should be 3. + * + * \return Inverted homography \f$\bf H^{-1}\f$. + */ + vpHomography inverse(double sv_threshold = 1e-16, unsigned int *rank = nullptr) const; + + /*! + * Invert the homography. + * + * \param[in,out] bHa : \f$\bf H^{-1}\f$ with H = *this. + */ + void inverse(vpHomography &bHa) const; + + /*! + * Read an homography in a file, verify if it is really an homogeneous + * matrix. + * + * \param[in] f : the file. This file has to be written using save(). + * + * \sa save() + */ + void load(std::ifstream &f); + + /*! + * Multiplication by an homography. + * + * \param H : Homography to multiply with. + * + * \code + * vpHomography aHb, bHc; + * // Initialize aHb and bHc homographies + * vpHomography aHc = aHb * bHc; + * \endcode + */ + vpHomography operator*(const vpHomography &H) const; + + /*! + * Multiply an homography by a scalar. + * + * \param v : Value of the scalar. + * + * \code + * double v = 1.1; + * vpHomography aHb; + * // Initialize aHb + * vpHomography H = aHb * v; + * \endcode + */ + vpHomography operator*(const double &v) const; + + /*! + * Operation a = aHb * b. + * + * \param b : 3 dimension vector. + */ + vpColVector operator*(const vpColVector &b) const; + + /*! + * From the coordinates of the point in image plane b and the homography + * between image a and b computes the coordinates of the point in image plane + * a. + * + * \param b_P : 2D coordinates of the point in the image plane b. + * + * \return A point with 2D coordinates in the image plane a. + */ + vpPoint operator*(const vpPoint &b_P) const; + + /*! + * Divide an homography by a scalar. + * + * \param v : Value of the scalar. + * + * \code + * vpHomography aHb; + * // Initialize aHb + * vpHomography H = aHb / aHb[2][2]; + * \endcode + */ + vpHomography operator/(const double &v) const; + + /*! + * Divide all the element of the homography matrix by v : Hij = Hij / v + */ + vpHomography &operator/=(double v); + + /*! + * Copy operator. + * Allow operation such as aHb = H + * + * \param H : Homography matrix to be copied. + */ + vpHomography &operator=(const vpHomography &H); + + /*! + * Copy operator. + * Allow operation such as aHb = H + * + * \param H : Matrix to be copied. + */ + vpHomography &operator=(const vpMatrix &H); + + /*! + * Project the current image point (in frame b) into the frame a using the + * homography aHb. + * + * \param ipb : Homography defining the relation between frame a and frame b. + * \return The projected image point in the frame a. + */ + vpImagePoint projection(const vpImagePoint &ipb); + + /*! + * This function is not applicable to an homography that is always a + * 3-by-3 matrix. + * \exception vpException::fatalError When this function is called. + */ + VP_NORETURN void resize(unsigned int nrows, unsigned int ncols, bool flagNullify = true) + { + (void)nrows; + (void)ncols; + (void)flagNullify; + throw(vpException(vpException::fatalError, "Cannot resize an homography matrix")); + } + + /*! + * Save an homography in a file. + * The load() function allows then to read and set the homography from this + * file. + * + * \sa load() + */ + void save(std::ofstream &f) const; + + /*! + * From couples of matched points \f$^a{\bf p}=(x_a,y_a,1)\f$ in image a + * and \f$^b{\bf p}=(x_b,y_b,1)\f$ in image b with homogeneous coordinates, + * computes the homography matrix by resolving \f$^a{\bf p} = ^a{\bf H}_b\; + * ^b{\bf p}\f$ using the DLT (Direct Linear Transform) algorithm. + * + * At least 4 couples of points are needed. + * + * To do so, we use the DLT algorithm on the data, + * ie we resolve the linear system by SDV : \f$\bf{Ah} =0\f$ where + * \f$\bf{h}\f$ is the vector with the terms of \f$^a{\bf H}_b\f$ and + * \f$\mathbf{A}\f$ depends on the points coordinates. + * + * For each point, in homogeneous coordinates we have: + * \f[ + * ^a{\bf p} = ^a{\bf H}_b\; ^b{\bf p} + * \f] + * which is equivalent to: + * \f[ + * ^a{\bf p} \times {^a{\bf H}_b \; ^b{\bf p}} =0 + * \f] + * If we note \f$\mathbf{h}_j^T\f$ the \f$j^{\textrm{th}}\f$ line of + * \f$^a{\bf H}_b\f$, we can write: \f[ ^a{\bf H}_b \; ^b{\bf p} = \left( + * \begin{array}{c}\mathbf{h}_1^T \;^b{\bf p} \\\mathbf{h}_2^T \; ^b{\bf p} + * \\\mathbf{h}_3^T \;^b{\bf p} \end{array}\right) \f] + * + * Setting \f$^a{\bf p}=(x_{a},y_{a},w_{a})\f$, the cross product can be + * rewritten by: \f[ ^a{\bf p} \times ^a{\bf H}_b \; ^b{\bf p} =\left( + * \begin{array}{c}y_{a}\mathbf{h}_3^T \; ^b{\bf p}-w_{a}\mathbf{h}_2^T \; + * ^b{\bf p} \\w_{a}\mathbf{h}_1^T \; ^b{\bf p} -x_{a}\mathbf{h}_3^T \; ^b{\bf + * p} \\x_{a}\mathbf{h}_2^T \; ^b{\bf p}- y_{a}\mathbf{h}_1^T \; ^b{\bf + * p}\end{array}\right) \f] + * + * \f[ + * \underbrace{\left( \begin{array}{ccc}\mathbf{0}^T & -w_{a} \; ^b{\bf p}^T + * & y_{a} \; ^b{\bf p}^T \\ w_{a} + * \; ^b{\bf p}^T&\mathbf{0}^T & -x_{a} \; ^b{\bf p}^T \\ + * -y_{a} \; ^b{\bf p}^T & x_{a} \; ^b{\bf p}^T & + * \mathbf{0}^T\end{array}\right)}_{\mathbf{A}_i (3\times 9)} + * \underbrace{\left( \begin{array}{c}\mathbf{h}_{1}^{T} \\ + * \mathbf{h}_{2}^{T}\\\mathbf{h}_{3}^{T}\end{array}\right)}_{\mathbf{h} + * (9\times 1)}=0 \f] + * + * leading to an homogeneous system to be solved: + * \f$\mathbf{A}\mathbf{h}=0\f$ with \f$\mathbf{A}=\left(\mathbf{A}_1^T, ..., + * \mathbf{A}_i^T, ..., \mathbf{A}_n^T \right)^T\f$. + * + * It can be solved using an SVD decomposition: + * \f[\bf A = UDV^T \f] + * h is the column of V associated with the smallest singular + * value of A + * + * + * \param[in] xb : Vector that contains the coordinates along x-axis (horizontal) of matched points in image b. + * These coordinates are expressed in meters. + * \param[in] yb : Vector that contains the coordinates along y-axis (vertical) of matched points in image b. + * These coordinates are expressed in meters. + * \param[in] xa : Vector that contains the coordinates along x-axis (horizontal) of matched points in image a. + * These coordinates are expressed in meters. + * \param[in] ya : Vector that contains the coordinates along y-axis (vertical) of matched points in image a. + * These coordinates are expressed in meters. + * \param[out] aHb : Estimated homography that relies the transformation from image a to image b. + * \param[in] normalization : When set to true, the coordinates of the + * points are normalized. The normalization carried out is the one preconized by Hartley. + * + * \exception vpMatrixException::rankDeficient : When the rank of the matrix + * that should be 8 is deficient. + */ + static void DLT(const std::vector &xb, const std::vector &yb, const std::vector &xa, + const std::vector &ya, vpHomography &aHb, bool normalization = true); + + /*! + * From couples of matched points \f$^a{\bf p}=(x_a,y_a,1)\f$ in image a + * and \f$^b{\bf p}=(x_b,y_b,1)\f$ in image b with homogeneous coordinates, + * computes the homography matrix by resolving \f$^a{\bf p} = ^a{\bf H}_b\; + * ^b{\bf p}\f$ using Ezio Malis linear method (HLM) \cite Malis00b. + * + * This method can consider points that are planar or non planar. The algorithm + * for planar scene implemented in this file is described in Ezio Malis PhD + * thesis \cite TheseMalis. + * + * \param[in] xb : Vector that contains the coordinates along x-axis (horizontal) of matched points in image b. + * These coordinates are expressed in meters. + * \param[in] yb : Vector that contains the coordinates along y-axis (vertical) of matched points in image b. + * These coordinates are expressed in meters. + * \param[in] xa : Vector that contains the coordinates along x-axis (horizontal) of matched points in image a. + * These coordinates are expressed in meters. + * \param[in] ya : Vector that contains the coordinates along y-axis (vertical) of matched points in image a. + * These coordinates are expressed in meters. + * \param[in] isplanar : If true the points are assumed to be in a plane, otherwise there + * are assumed to be non planar. + * \param[out] aHb : Estimated homography that relies the transformation from image a to image b. + * + * If the boolean isplanar is true the points are assumed to be in a plane + * otherwise there are assumed to be non planar. + * + * \sa DLT() when the scene is planar. + */ + static void HLM(const std::vector &xb, const std::vector &yb, const std::vector &xa, + const std::vector &ya, bool isplanar, vpHomography &aHb); + + /*! + * From couples of matched points \f$^a{\bf p}=(x_a,y_a,1)\f$ in image a + * and \f$^b{\bf p}=(x_b,y_b,1)\f$ in image b with homogeneous coordinates, + * computes the homography matrix by resolving \f$^a{\bf p} = ^a{\bf H}_b\; + * ^b{\bf p}\f$ using Ransac algorithm. + * + * \param[in] xb : Vector that contains the coordinates along x-axis (horizontal) of matched points in image b. + * These coordinates are expressed in meters. + * \param[in] yb : Vector that contains the coordinates along y-axis (vertical) of matched points in image b. + * These coordinates are expressed in meters. + * \param[in] xa : Vector that contains the coordinates along x-axis (horizontal) of matched points in image a. + * These coordinates are expressed in meters. + * \param[in] ya : Vector that contains the coordinates along y-axis (vertical) of matched points in image a. + * These coordinates are expressed in meters. + * \param[out] aHb : Estimated homography that relies the transformation from image a to image b. + * \param[out] inliers : Vector that indicates if a matched point is an + * inlier (true) or an outlier (false). + * \param[out] residual : Global residual + * computed as \f$r = \sqrt{1/n \sum_{inliers} {\| {^a{\bf p} - {\hat{^a{\bf + * H}_b}} {^b{\bf p}}} \|}^{2}}\f$ with \f$n\f$ the number of inliers. + * \param[in] nbInliersConsensus : Minimal number of points requested to fit the + * estimated homography. + * \param[in] threshold : Threshold for outlier removing. A point is considered as + * an outlier if the reprojection error \f$\| {^a{\bf p} - {\hat{^a{\bf H}_b}} + * {^b{\bf p}}} \|\f$ is greater than this threshold. + * \param[in] normalization : When set to true, the coordinates of the points are + * normalized. The normalization carried out is the one preconized by Hartley. + * + * \return true if the homography could be computed, false otherwise. + */ + static bool ransac(const std::vector &xb, const std::vector &yb, const std::vector &xa, + const std::vector &ya, vpHomography &aHb, std::vector &inliers, double &residual, + unsigned int nbInliersConsensus, double threshold, bool normalization = true); + + /*! + * Given `iPa` a pixel with coordinates \f$(u_a,v_a)\f$ in + * image a, and the homography `bHa` in the Euclidean space or calibrated domain that links image a and b, computes the + * coordinates of the pixel \f$(u_b,v_b)\f$ in the image b using the camera + * parameters matrix \f$\bf K\f$. + * + * Compute \f$^b{\bf p} = {\bf K} \; {^b}{\bf H}_a \; {\bf K}^{-1} {^a}{\bf + * p}\f$ with \f$^a{\bf p}=(u_a,v_a,1)\f$ and \f$^b{\bf p}=(u_b,v_b,1)\f$ + * + * \return The coordinates in pixel of the point with coordinates + * \f$(u_b,v_b)\f$. + */ + static vpImagePoint project(const vpCameraParameters &cam, const vpHomography &bHa, const vpImagePoint &iPa); + + /*! + * Given `Pa` a point with normalized coordinates \f$(x_a,y_a,1)\f$ in the + * image plane a, and the homography `bHa` in the Euclidean space that links image a and b, computes + * the normalized coordinates of the point \f$(x_b,y_b,1)\f$ in the image plane + * b. + * + * Compute \f$^b{\bf p} = {^b}{\bf H}_a \; {^a}{\bf p}\f$ with \f$^a{\bf + * p}=(x_a,y_a,1)\f$ and \f$^b{\bf p}=(x_b,y_b,1)\f$ + * + * \return The coordinates in meter of the point with coordinates + * \f$(x_b,y_b)\f$. + */ + static vpPoint project(const vpHomography &bHa, const vpPoint &Pa); + + /*! + * From couples of matched points \f$^a{\bf p}=(x_a,y_a,1)\f$ in image a + * and \f$^b{\bf p}=(x_b,y_b,1)\f$ in image b with homogeneous coordinates, + * computes the homography matrix by resolving \f$^a{\bf p} = ^a{\bf H}_b\; + * ^b{\bf p}\f$ using a robust estimation scheme. + * + * This method is to compare to DLT() except that here a robust estimator is + * used to reject couples of points that are considered as outliers. + * + * At least 4 couples of points are needed. + * + * \param[in] xb : Vector that contains the coordinates along x-axis (horizontal) of matched points in image b. + * These coordinates are expressed in meters. + * \param[in] yb : Vector that contains the coordinates along y-axis (vertical) of matched points in image b. + * These coordinates are expressed in meters. + * \param[in] xa : Vector that contains the coordinates along x-axis (horizontal) of matched points in image a. + * These coordinates are expressed in meters. + * \param[in] ya : Vector that contains the coordinates along y-axis (vertical) of matched points in image a. + * These coordinates are expressed in meters. + * \param[out] aHb : Estimated homography that relies the transformation from image a to image b. + * \param[out] inliers : Vector that indicates if a matched point is an + * inlier (true) or an outlier (false). + * \param[out] residual : Global residual computed as + * \f$r = \sqrt{1/n \sum_{inliers} {\| {^a{\bf p} - {\hat{^a{\bf H}_b}} {^b{\bf p}}} \|}^{2}}\f$ + * with \f$n\f$ the number of inliers. + * \param[in] weights_threshold : Threshold applied on the weights updated during the + * robust estimation and used to consider if a point is an outlier or an + * inlier. Values should be in [0:1]. A couple of matched points that have a + * weight lower than this threshold is considered as an outlier. A value equal + * to zero indicates that all the points are inliers. + * \param[in] niter : Number of iterations of the estimation process. + * \param[in] normalization : When set to true, the coordinates of the points are normalized. + * The normalization carried out is the one preconized by Hartley. + * + * \sa DLT(), ransac() + */ + static void robust(const std::vector &xb, const std::vector &yb, const std::vector &xa, + const std::vector &ya, vpHomography &aHb, std::vector &inliers, double &residual, + double weights_threshold = 0.4, unsigned int niter = 4, bool normalization = true); + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + static void build(vpHomography &aHb, const vpHomogeneousMatrix &aMb, const vpPlane &bP); + + static void computeDisplacement(const vpHomography &aHb, const vpColVector &nd, vpRotationMatrix &aRb, + vpTranslationVector &atb, vpColVector &n); + + static void computeDisplacement(const vpHomography &aHb, vpRotationMatrix &aRb, vpTranslationVector &atb, + vpColVector &n); + + static void computeDisplacement(const vpHomography &H, double x, double y, std::list &vR, + std::list &vT, std::list &vN); + static double computeDisplacement(unsigned int nbpoint, vpPoint *c1P, vpPoint *c2P, vpPlane &oN, + vpHomogeneousMatrix &c2Mc1, vpHomogeneousMatrix &c1Mo, int userobust); + static double computeDisplacement(unsigned int nbpoint, vpPoint *c1P, vpPoint *c2P, vpPlane *oN, + vpHomogeneousMatrix &c2Mc1, vpHomogeneousMatrix &c1Mo, int userobust); + static double computeResidual(vpColVector &x, vpColVector &M, vpColVector &d); + // VVS + static double computeRotation(unsigned int nbpoint, vpPoint *c1P, vpPoint *c2P, vpHomogeneousMatrix &c2Mc1, + int userobust); + static void computeTransformation(vpColVector &x, unsigned int *ind, vpColVector &M); + static bool degenerateConfiguration(const vpColVector &x, unsigned int *ind); + static bool degenerateConfiguration(const vpColVector &x, unsigned int *ind, double threshold_area); + static bool degenerateConfiguration(const std::vector &xb, const std::vector &yb, + const std::vector &xa, const std::vector &ya); + static void hartleyNormalization(unsigned int n, const double *x, const double *y, double *xn, double *yn, double &xg, + double &yg, double &coef); + static void hartleyNormalization(const std::vector &x, const std::vector &y, std::vector &xn, + std::vector &yn, double &xg, double &yg, double &coef); + static void hartleyDenormalization(vpHomography &aHbn, vpHomography &aHb, double xg1, double yg1, double coef1, + double xg2, double yg2, double coef2); + +#endif // DOXYGEN_SHOULD_SKIP_THIS + +private: + static const double m_sing_threshold; /* equals 0.0001 */ + static const double m_threshold_rotation; + static const double m_threshold_displacement; + vpHomogeneousMatrix m_aMb; + + //! Reference plane coordinates expressed in frame b + vpPlane m_bP; + + //! Build the homography from aMb and Rb + void build(); + + /*! + * Insert the rotational part of an homogeneous transformation. + * To recompute the homography call build(). + */ + void insert(const vpHomogeneousMatrix &aRb); + + /*! + * Insert the rotational matrix. + * To recompute the homography call build(). + */ + void insert(const vpRotationMatrix &aRb); + + /*! + * Insert a theta u vector transformed internally into a rotation matrix. + * To recompute the homography call build(). + */ + void insert(const vpThetaUVector &tu); + + /*! + * Insert a translation vector. + * To recompute the homography call build(). + */ + void insert(const vpTranslationVector &atb); + + /*! + * Insert the reference plane. + * To recompute the homography call build(). + */ + void insert(const vpPlane &bP); + + static void initRansac(unsigned int n, double *xb, double *yb, double *xa, double *ya, vpColVector &x); +}; + +END_VISP_NAMESPACE + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpKeyPoint.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpKeyPoint.h new file mode 100644 index 00000000..d80ff007 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpKeyPoint.h @@ -0,0 +1,2351 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Key point functionalities. + */ +#ifndef VP_KEYPOINT_H +#define VP_KEYPOINT_H + +#include + +#if defined(VISP_HAVE_OPENCV) && \ + (((VISP_HAVE_OPENCV_VERSION < 0x050000) && defined(HAVE_OPENCV_CALIB3D) && defined(HAVE_OPENCV_FEATURES2D)) || \ + ((VISP_HAVE_OPENCV_VERSION >= 0x050000) && defined(HAVE_OPENCV_3D) && defined(HAVE_OPENCV_FEATURES))) + +#include // std::transform +#include // DBL_MAX +#include // std::ofstream +#include +#include // std::map +#include // std::accumulate +#include // srand, rand +#include // time +#include // std::vector + +#include +#include +#include +#include +#include +#include +#include +#ifdef VISP_HAVE_MODULE_IO +#include +#endif +#include +#include +#include +#include +#include + +#include + +#if defined(HAVE_OPENCV_FEATURES2D) +#include +#endif + +#if defined(HAVE_OPENCV_XFEATURES2D) +#include +#endif + +#if defined(HAVE_OPENCV_IMGPROC) +#include +#endif + +#if defined(HAVE_OPENCV_NONFREE) +#include +#endif + +BEGIN_VISP_NAMESPACE +/*! + * \class vpKeyPoint + * \ingroup group_vision_keypoints group_detection_keypoint group_detection_mbt_object + * + * \brief Class that allows keypoints 2D features detection (and descriptors extraction) + * and matching thanks to OpenCV library. Thus to enable this class OpenCV should + * be installed. Installation instructions are provided here + * https://visp.inria.fr/3rd_opencv. + * + * This class permits to use different types of detectors, extractors and + * matchers easily. So, the classical SIFT and SURF keypoints could be used, as + * well as ORB, FAST, (etc.) keypoints, depending of the version of OpenCV you + * use. + * + * \note Due to some patents, SIFT and SURF are packaged in an external module + * called nonfree module in OpenCV version before 3.0.0 and in xfeatures2d + * from 3.0.0. You have to check you have the corresponding module to use SIFT + * and SURF. + * + * Depending on OpenCV version, the table below shows which OpenCV module + * is required to be able to use a given 2D features detectors. + * + * 2D features detectors | OpenCV < 5.0 | OpenCV >= 5.0 + * :-------------------: | :----------: | :-----------: + * AGAST | features2d | xfeatures2d + * AKAZE | features2d | xfeatures2d + * BRISK | features2d | xfeatures2d + * GFTTDetector | features2d | features + * FAST | features2d | features + * KAZE | features2d | xfeatures2d + * MSDDetector | xfeatures2d | xfeatures2d + * MSER | features2d | features + * ORB | features2d | features + * SIFT | xfeatures2d | features + * SimpleBlobDetector | features2d | features + * STAR | xfeatures2d | xfeatures2d + * SURF | xfeatures2d | xfeatures2d + * + * Depending on OpenCV version, the table below shows which OpenCV module + * is required to be able to use a given 2D features descriptor. + * + * 2D features descriptors | OpenCV < 5.0 | OpenCV >= 5.0 + * :---------------------: | :----------: | :-----------: + * AKAZE | features2d | xfeatures2d + * BRIEF | xfeatures2d | xfeatures2d + * BRISK | features2d | xfeatures2d + * BoostDesc | xfeatures2d | xfeatures2d + * DAISY | xfeatures2d | xfeatures2d + * FREAK | xfeatures2d | xfeatures2d + * KAZE | features2d | xfeatures2d + * LATCH | xfeatures2d | xfeatures2d + * ORB | features2d | features + * SIFT | xfeatures2d | features + * SURF | xfeatures2d | xfeatures2d + * VGG | xfeatures2d | xfeatures2d + * + * The goal of this class is to provide a tool to match reference keypoints + * from a reference image (or train keypoints in OpenCV terminology) and detected + * keypoints from a current image (or query keypoints in OpenCV terminology). + * + * If you supply the corresponding 3D coordinates corresponding to the 2D + * coordinates of the reference keypoints, you can also estimate the pose of the + * object by matching a set of detected keypoints in the current image with the + * reference keypoints. + * + * If you use this class, the first thing you have to do is to build + * the reference keypoints by detecting keypoints in a reference image which + * contains the object to detect. Then you match keypoints detected in a current + * image with those detected in a reference image by calling matchPoint() + * methods. You can access to the lists of matched points thanks to the methods + * getMatchedPointsInReferenceImage() and getMatchedPointsInCurrentImage(). These + * two methods return a list of matched points. The nth element of the first list + * is matched with the nth element of the second list. To provide easy + * compatibility with OpenCV terminology, getTrainKeyPoints() give you access to + * the list of keypoints detected in train images (or reference images) and + * getQueryKeyPoints() give you access to the list of keypoints detected in a + * query image (or current image). The method getMatches() give you access to a + * list of cv::DMatch with the correspondence between the index of the train + * keypoints and the index of the query keypoints. + * + * The following small example shows how to use the class to do the matching + * between current and reference keypoints. + * + * \code + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * #if (VISP_HAVE_OPENCV_VERSION >= 0x020300) + * vpImage Irefrence; + * vpImage Icurrent; + * + * vpKeyPoint::vpFilterMatchingType filterType = vpKeyPoint::ratioDistanceThreshold; + * vpKeyPoint keypoint("ORB", "ORB", "BruteForce-Hamming", filterType); + * + * // First grab the reference image Irefrence + * // Add your code to load the reference image in Ireference + * + * // Build the reference ORB points. + * keypoint.buildReference(Irefrence); + * + * // Then grab another image which represents the current image Icurrent + * + * // Match points between the reference points and the ORB points computed in the current image. + * keypoint.matchPoint(Icurrent); + * + * // Display the matched points + * keypoint.display(Irefrence, Icurrent); + * #endif + * + * return (0); + * } + * \endcode + * + * It is also possible to build the reference keypoints in a region of interest + * (ROI) of an image and find keypoints to match in only a part of the current + * image. The small following example shows how to do this: + * + * \code + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * #if (VISP_HAVE_OPENCV_VERSION >= 0x020300) + * vpImage Ireference; + * vpImage Icurrent; + * + * vpKeyPoint::vpFilterMatchingType filterType = vpKeyPoint::ratioDistanceThreshold; + * vpKeyPoint keypoint("ORB", "ORB", "BruteForce-Hamming", filterType); + * + * //First grab the reference image Irefrence + * //Add your code to load the reference image in Ireference + * + * //Select a part of the image by clicking on two points which define a rectangle + * vpImagePoint corners[2]; + * for (int i=0 ; i < 2 ; i++) { + * vpDisplay::getClick(Ireference, corners[i]); + * } + * + * //Build the reference ORB points. + * int nbrRef; + * unsigned int height, width; + * height = static_cast(corners[1].get_i() - corners[0].get_i()); + * width = static_cast(corners[1].get_j() - corners[0].get_j()); + * nbrRef = keypoint.buildReference(Ireference, corners[0], height, width); + * + * //Then grab another image which represents the current image Icurrent + * + * //Select a part of the image by clicking on two points which define a rectangle + * for (int i=0 ; i < 2 ; i++) { + * vpDisplay::getClick(Icurrent, corners[i]); + * } + * + * //Match points between the reference points and the ORB points computed in the current image. + * int nbrMatched; + * height = static_cast(corners[1].get_i() - corners[0].get_i()); + * width = static_cast(corners[1].get_j() - corners[0].get_j()); + * nbrMatched = keypoint.matchPoint(Icurrent, corners[0], height, width); + * + * //Display the matched points + * keypoint.display(Ireference, Icurrent); + * #endif + * + * return(0); + * } + * \endcode + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in keypoints detection and matching, you may have a look at:
+ * + * - \ref tutorial-matching + * - \ref tutorial-homography-ransac +*/ +class VISP_EXPORT vpKeyPoint : public vpBasicKeyPoint +{ +public: + /*! Predefined filtering method identifier. */ + enum vpFilterMatchingType + { + constantFactorDistanceThreshold, /*!< Keep all the points below a constant + factor threshold. */ + stdDistanceThreshold, /*!< Keep all the points below a minimal distance + + the standard deviation. */ + ratioDistanceThreshold, /*!< Keep all the points enough discriminated (the + ratio distance between the two best matches is + below the threshold). */ + stdAndRatioDistanceThreshold, /*!< Keep all the points which fall with the + two conditions above. */ + noFilterMatching /*!< No filtering. */ + }; + + /*! Predefined detection method identifier. */ + enum vpDetectionMethodType + { + detectionThreshold, /*!< The object is present if the average of the + descriptor distances is below the threshold. */ + detectionScore /*!< Same condition than the previous but with a formula + taking into account the number of matches, the object is + present if the score is above the threshold. */ + }; + + /*! Predefined constant for training image format. */ + typedef enum + { + jpgImageFormat, /*!< Save training images in JPG format. */ + pngImageFormat, /*!< Save training images in PNG format. */ + ppmImageFormat, /*!< Save training images in PPM format. */ + pgmImageFormat /*!< Save training images in PGM format. */ + } vpImageFormatType; + + /*! Predefined constant for feature detection type. */ + enum vpFeatureDetectorType + { +#if (VISP_HAVE_OPENCV_VERSION >= 0x050000) +# if defined(HAVE_OPENCV_FEATURES) + DETECTOR_FAST, //!< FAST detector + DETECTOR_GFTT, //!< GFTT detector + DETECTOR_MSER, //!< MSER detector + DETECTOR_ORB, //!< ORB detector + DETECTOR_SIFT, //!< SIFT detector + DETECTOR_SimpleBlob, //!< SimpleBlob detector +# endif +# if defined(HAVE_OPENCV_XFEATURES2D) + DETECTOR_AGAST, //!< AGAST detector + DETECTOR_AKAZE, //!< AKAZE detector + DETECTOR_BRISK, //!< BRISK detector + DETECTOR_KAZE, //!< KAZE detector + DETECTOR_MSD, //!< MSD detector + DETECTOR_STAR, //!< STAR detector +# endif +# if defined(OPENCV_ENABLE_NONFREE) && defined(HAVE_OPENCV_XFEATURES2D) + DETECTOR_SURF, //!< SURF detector +# endif +#else // OpenCV < 5.0.0 +# if defined(HAVE_OPENCV_FEATURES2D) + DETECTOR_BRISK, //!< BRISK detector + DETECTOR_FAST, //!< FAST detector + DETECTOR_GFTT, //!< GFTT detector + DETECTOR_MSER, //!< MSER detector + DETECTOR_ORB, //!< ORB detector + DETECTOR_SimpleBlob, //!< SimpleBlob detector +# if (VISP_HAVE_OPENCV_VERSION >= 0x030000) + DETECTOR_AGAST, //!< AGAST detector + DETECTOR_AKAZE, //!< AKAZE detector + DETECTOR_KAZE, //!< KAZE detector +# endif +# endif +# if (VISP_HAVE_OPENCV_VERSION >= 0x030100) && defined(VISP_HAVE_OPENCV_XFEATURES2D) + DETECTOR_MSD, //!< MSD detector +# endif +# if ((VISP_HAVE_OPENCV_VERSION >= 0x030411 && CV_MAJOR_VERSION < 4) || (VISP_HAVE_OPENCV_VERSION >= 0x040400)) && defined(HAVE_OPENCV_FEATURES2D) + DETECTOR_SIFT, //!< SIFT detector +# endif +#if (VISP_HAVE_OPENCV_VERSION < 0x030000) || (defined(VISP_HAVE_OPENCV_XFEATURES2D)) + DETECTOR_STAR, //!< STAR detector +# endif +# if defined(OPENCV_ENABLE_NONFREE) && defined(HAVE_OPENCV_XFEATURES2D) + DETECTOR_SURF, //!< SURF detector +# endif +#endif + + DETECTOR_TYPE_SIZE //!< Number of detectors available + }; + + /*! Predefined constant for descriptor extraction type. */ + enum vpFeatureDescriptorType + { +#if (VISP_HAVE_OPENCV_VERSION >= 0x050000) +# if defined(HAVE_OPENCV_FEATURES) + DESCRIPTOR_ORB, //!< ORB descriptor + DESCRIPTOR_SIFT, //!< SIFT descriptor +# endif +# if defined(HAVE_OPENCV_XFEATURES2D) + DESCRIPTOR_AKAZE, //!< AKAZE descriptor + DESCRIPTOR_BRISK, //!< BRISK descriptor + DESCRIPTOR_BoostDesc, //!< BoostDesc descriptor + DESCRIPTOR_BRIEF, //!< BRIEF descriptor + DESCRIPTOR_DAISY, //!< DAISY descriptor + DESCRIPTOR_FREAK, //!< FREAK descriptor + DESCRIPTOR_KAZE, //!< KAZE descriptor + DESCRIPTOR_LATCH, //!< LATCH descriptor + DESCRIPTOR_VGG, //!< VGG descriptor +# endif +# if defined(OPENCV_ENABLE_NONFREE) && defined(HAVE_OPENCV_XFEATURES2D) + DESCRIPTOR_SURF, //!< SURF descriptor +# endif +#else // opencv < 5.0.0 +# if defined(HAVE_OPENCV_FEATURES2D) + DESCRIPTOR_BRISK, //!< BRISK descriptor + DESCRIPTOR_ORB, //!< ORB descriptor +# if (VISP_HAVE_OPENCV_VERSION >= 0x030000) + DESCRIPTOR_AKAZE, //!< AKAZE descriptor + DESCRIPTOR_KAZE, //!< KAZE descriptor +# endif +# endif +# if defined(HAVE_OPENCV_XFEATURES2D) + DESCRIPTOR_BRIEF, //!< BRIEF descriptor + DESCRIPTOR_DAISY, //!< DAISY descriptor + DESCRIPTOR_FREAK, //!< FREAK descriptor + DESCRIPTOR_LATCH, //!< LATCH descriptor +# endif +# if ((VISP_HAVE_OPENCV_VERSION >= 0x030411 && CV_MAJOR_VERSION < 4) || (VISP_HAVE_OPENCV_VERSION >= 0x040400)) && defined(HAVE_OPENCV_FEATURES2D) + DESCRIPTOR_SIFT, //!< SIFT descriptor +# endif +# if defined(OPENCV_ENABLE_NONFREE) && defined(HAVE_OPENCV_XFEATURES2D) + DESCRIPTOR_SURF, //!< SURF descriptor +# endif +#if (VISP_HAVE_OPENCV_VERSION >= 0x030200) && defined(VISP_HAVE_OPENCV_XFEATURES2D) + DESCRIPTOR_BoostDesc, //!< BoostDesc descriptor, only with OpenCV >= 3.2.0 + DESCRIPTOR_VGG, //!< VGG descriptor, only with OpenCV >= 3.2.0 +# endif +#endif + + DESCRIPTOR_TYPE_SIZE //!< Number of descriptors available + }; + + /*! + * Constructor to initialize the specified detector, descriptor, matcher and + * filtering method. + * + * \param detectorType : Type of feature detector. + * \param descriptorType : Type of the descriptor extractor. + * \param matcherName : Name of the matcher. + * \param filterType : Filtering matching method chosen. + */ + vpKeyPoint(const vpFeatureDetectorType &detectorType, const vpFeatureDescriptorType &descriptorType, + const std::string &matcherName, const vpFilterMatchingType &filterType = ratioDistanceThreshold); + + /*! + * Constructor to initialize the specified detector, descriptor, matcher and + * filtering method. + * + * \param detectorName : Name of the detector. + * \param extractorName : Name of the extractor. + * \param matcherName : Name of the matcher. + * \param filterType : Filtering matching method chosen. + */ + vpKeyPoint(const std::string &detectorName = "ORB", const std::string &extractorName = "ORB", + const std::string &matcherName = "BruteForce-Hamming", + const vpFilterMatchingType &filterType = ratioDistanceThreshold); + + /*! + * Constructor to initialize specified detector, extractor, matcher and + * filtering method. + * + * \param detectorNames : List of name detector for allowing multiple detectors. + * \param extractorNames : List of name extractor for allowing multiple extractors. + * \param matcherName : Name of the matcher. + * \param filterType : Filtering matching method chosen. + */ + vpKeyPoint(const std::vector &detectorNames, const std::vector &extractorNames, + const std::string &matcherName = "BruteForce", + const vpFilterMatchingType &filterType = ratioDistanceThreshold); + + /*! + * Build the reference keypoints list. + * + * \param I : Input reference image. + * \return The number of detected keypoints in the image \p I. + */ + unsigned int buildReference(const vpImage &I) VP_OVERRIDE; + + /*! + * Build the reference keypoints list in a region of interest in the image. + * + * \param I : Input reference image. + * \param iP : Position of the top-left corner of the region of interest. + * \param height : Height of the region of interest. + * \param width : Width of the region of interest. + * \return The number of detected keypoints in the current image I. + */ + unsigned int buildReference(const vpImage &I, const vpImagePoint &iP, unsigned int height, + unsigned int width) VP_OVERRIDE; + + /*! + * Build the reference keypoints list in a region of interest in the image. + * + * \param I : Input image. + * \param rectangle : Rectangle of the region of interest. + * \return The number of detected keypoints in the current image I. + */ + unsigned int buildReference(const vpImage &I, const vpRect &rectangle) VP_OVERRIDE; + + /*! + * Build the reference keypoints list and compute the 3D position + * corresponding of the keypoints locations. + * + * \param I : Input image. + * \param trainKeyPoints : List of the train keypoints. + * \param points3f : Output list of the 3D position corresponding of the keypoints locations. + * \param append : If true, append the supply train keypoints with those already present. + * \param class_id : The class id to be set to the input cv::KeyPoint if != -1. + * \return The number of detected keypoints in the current image I. + */ + unsigned int buildReference(const vpImage &I, std::vector &trainKeyPoints, + std::vector &points3f, bool append = false, int class_id = -1); + + /*! + * Build the reference keypoints list and compute the 3D position + * corresponding of the keypoints locations. + * + * \param I : Input image. + * \param trainKeyPoints : List of the train keypoints. + * \param points3f : List of the 3D position corresponding of the keypoints locations. + * \param trainDescriptors : List of the train descriptors. + * \param append : If true, append the supply train keypoints with those already present. + * \param class_id : The class id to be set to the input cv::KeyPoint if != -1. + * + * \return The number of keypoints in the current image I. + */ + unsigned int buildReference(const vpImage &I, const std::vector &trainKeyPoints, + const cv::Mat &trainDescriptors, const std::vector &points3f, + bool append = false, int class_id = -1); + + /*! + * Build the reference keypoints list. + * + * \param I_color : Input reference image. + * \return The number of detected keypoints in the image \p I. + */ + unsigned int buildReference(const vpImage &I_color); + + /*! + * Build the reference keypoints list in a region of interest in the image. + * + * \param I_color : Input reference image. + * \param iP : Position of the top-left corner of the region of interest. + * \param height : Height of the region of interest. + * \param width : Width of the region of interest. + * \return The number of detected keypoints in the current image I. + */ + unsigned int buildReference(const vpImage &I_color, const vpImagePoint &iP, unsigned int height, + unsigned int width); + + /*! + * Build the reference keypoints list in a region of interest in the image. + * + * \param I_color : Input image. + * \param rectangle : Rectangle of the region of interest. + * \return The number of detected keypoints in the current image I. + */ + unsigned int buildReference(const vpImage &I_color, const vpRect &rectangle); + + /*! + * Build the reference keypoints list and compute the 3D position + * corresponding of the keypoints locations. + * + * \param I_color : Input image. + * \param trainKeyPoints : List of the train keypoints. + * \param points3f : Output list of the 3D position corresponding of the keypoints locations. + * \param append : If true, append the supply train keypoints with those already present. + * \param class_id : The class id to be set to the input cv::KeyPoint if != -1. + * \return The number of detected keypoints in the current image I. + */ + unsigned int buildReference(const vpImage &I_color, std::vector &trainKeyPoints, + std::vector &points3f, bool append = false, int class_id = -1); + + /*! + * Build the reference keypoints list and compute the 3D position + * corresponding of the keypoints locations. + * + * \param I_color : Input image. + * \param trainKeyPoints : List of the train keypoints. + * \param points3f : List of the 3D position corresponding of the keypoints locations. + * \param trainDescriptors : List of the train descriptors. + * \param append : If true, append the supply train keypoints with those already present. + * \param class_id : The class id to be set to the input cv::KeyPoint if != -1. + * \return The number of detected keypoints in the current image I. + */ + unsigned int buildReference(const vpImage &I_color, const std::vector &trainKeyPoints, + const cv::Mat &trainDescriptors, const std::vector &points3f, + bool append = false, int class_id = -1); + + /*! + * Compute the 3D coordinate in the world/object frame given the 2D image + * coordinate and under the assumption that the point is located on a plane + * whose the plane equation is known in the camera frame. + * The Z-coordinate is retrieved according to the proportional relationship + * between the plane equation expressed in the normalized camera frame + * (derived from the image coordinate) and the same plane equation expressed + * in the camera frame. + * + * \param candidate : Keypoint we want to compute the 3D coordinate. + * \param roi : List of 3D points in the camera frame representing a planar face. + * \param cam : Camera parameters. + * \param cMo : Homogeneous matrix between the world and the camera frames. + * \param point : 3D coordinate in the world/object frame computed. + */ + static void compute3D(const cv::KeyPoint &candidate, const std::vector &roi, const vpCameraParameters &cam, + const vpHomogeneousMatrix &cMo, cv::Point3f &point); + + /*! + * Compute the 3D coordinate in the world/object frame given the 2D image + * coordinate and under the assumption that the point is located on a plane + * whose the plane equation is known in the camera frame. + * The Z-coordinate is retrieved according to the proportional relationship + * between the plane equation expressed in the normalized camera frame + * (derived from the image coordinate) and the same plane equation expressed + * in the camera frame. + * + * \param candidate : vpImagePoint we want to compute the 3D coordinate. + * \param roi : List of 3D points in the camera frame representing a planar face. + * \param cam : Camera parameters. + * \param cMo : Homogeneous matrix between the world and the camera frames. + * \param point : 3D coordinate in the world/object frame computed. + */ + static void compute3D(const vpImagePoint &candidate, const std::vector &roi, const vpCameraParameters &cam, + const vpHomogeneousMatrix &cMo, vpPoint &point); + + /*! + * Keep only keypoints located on faces and compute for those keypoints the 3D + * coordinate in the world/object frame given the 2D image coordinate and + * under the assumption that the point is located on a plane. + * + * \param cMo : Homogeneous matrix between the world and the camera frames. + * \param cam : Camera parameters. + * \param candidates : In input, list of keypoints detected in the whole + * image, in output, list of keypoints only located on planes. + * \param polygons : List of 2D polygons representing the projection of the faces in + * the image plane. + * \param roisPt : List of faces, with the 3D coordinates known in the camera frame. + * \param points : Output list of computed 3D coordinates (in + * the world/object frame) of keypoints located only on faces. + * \param descriptors : Optional parameter, pointer to the descriptors to filter. + */ + static void compute3DForPointsInPolygons(const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + std::vector &candidates, + const std::vector &polygons, + const std::vector > &roisPt, + std::vector &points, cv::Mat *descriptors = nullptr); + + /*! + * Keep only keypoints located on faces and compute for those keypoints the 3D + * coordinate in the world/object frame given the 2D image coordinate and + * under the assumption that the point is located on a plane. + * + * \param cMo : Homogeneous matrix between the world and the camera frames. + * \param cam : Camera parameters. + * \param candidates : In input, list of vpImagePoint located in the whole + * image, in output, list of vpImagePoint only located on planes. + * \param polygons : List of 2D polygons representing the projection of the faces in + * the image plane. + * \param roisPt : List of faces, with the 3D coordinates known in the camera frame. + * \param points : Output list of computed 3D coordinates (in the world/object frame) + * of vpImagePoint located only on faces. + * \param descriptors : Optional parameter, pointer to the descriptors to filter. + */ + static void compute3DForPointsInPolygons(const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + std::vector &candidates, + const std::vector &polygons, + const std::vector > &roisPt, + std::vector &points, cv::Mat *descriptors = nullptr); + + /*! + * Keep only keypoints located on cylinders and compute the 3D coordinates in + * the world/object frame given the 2D image coordinates. + * + * \param cMo : Homogeneous matrix between the world and the camera frames. + * \param cam : Camera parameters. + * \param candidates : In input, list of keypoints detected in the whole + * image, in output, list of keypoints only located on cylinders. + * \param cylinders : List of vpCylinder corresponding of the cylinder objects in the + * scene, projected in the camera frame. + * \param vectorOfCylinderRois : For each cylinder, the corresponding list of bounding box. + * \param points : Output list of computed 3D coordinates in the world/object frame for each + * keypoint located on a cylinder. + * \param descriptors : Optional parameter, pointer to the descriptors to filter. + */ + static void + compute3DForPointsOnCylinders(const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + std::vector &candidates, const std::vector &cylinders, + const std::vector > > &vectorOfCylinderRois, + std::vector &points, cv::Mat *descriptors = nullptr); + + /*! + * Keep only vpImagePoint located on cylinders and compute the 3D coordinates + * in the world/object frame given the 2D image coordinates. + * + * \param cMo : Homogeneous matrix between the world and the camera frames. + * \param cam : Camera parameters. + * \param candidates : In input, list of vpImagePoint located in the image, in + * output, list of vpImagePoint only located on cylinders. + * \param cylinders : List of vpCylinder corresponding of the cylinder objects in the scene, + * projected in the camera frame. + * \param vectorOfCylinderRois : For each cylinder, the corresponding list of bounding box. + * \param points : Output list of computed 3D coordinates in the world/object frame for each + * vpImagePoint located on a cylinder. + * \param descriptors : Optional parameter, pointer to the descriptors to filter. + */ + static void + compute3DForPointsOnCylinders(const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, + std::vector &candidates, const std::vector &cylinders, + const std::vector > > &vectorOfCylinderRois, + std::vector &points, cv::Mat *descriptors = nullptr); + + /*! + * Compute the pose using the correspondence between 2D points and 3D points + * using OpenCV function with RANSAC method. + * + * \param imagePoints : List of 2D points corresponding to the location of the detected keypoints. + * \param objectPoints : List of the 3D points in the object frame matched. + * \param cam : Camera parameters. + * \param cMo : Homogeneous matrix between the object frame and the camera frame. + * \param inlierIndex : List of indexes of inliers. + * \param elapsedTime : Elapsed time. + * \param func : Function pointer to filter the final pose returned by OpenCV pose estimation method. + * \return True if the pose has been computed, false otherwise (not enough points, or size list mismatch). + */ + bool computePose(const std::vector &imagePoints, const std::vector &objectPoints, + const vpCameraParameters &cam, vpHomogeneousMatrix &cMo, std::vector &inlierIndex, + double &elapsedTime, bool (*func)(const vpHomogeneousMatrix &) = nullptr); + + /*! + * Compute the pose using the correspondence between 2D points and 3D points + * using ViSP function with RANSAC method. + * + * \param objectVpPoints : List of vpPoint with coordinates expressed in the object and in the camera frame. + * \param cMo : Homogeneous matrix between the object frame and the camera frame. + * \param inliers : List of inliers. + * \param elapsedTime : Elapsed time. + * \param func : Function pointer to filter the pose in Ransac pose estimation, if we want + * to eliminate the poses which do not respect some criterion + * \return True if the pose has been computed, false otherwise (not enough points, or size list mismatch). + */ + bool computePose(const std::vector &objectVpPoints, vpHomogeneousMatrix &cMo, std::vector &inliers, + double &elapsedTime, bool (*func)(const vpHomogeneousMatrix &) = nullptr); + + /*! + * Compute the pose using the correspondence between 2D points and 3D points + * using ViSP function with RANSAC method. + * + * \param objectVpPoints : List of vpPoint with coordinates expressed in the object and in the camera frame. + * \param cMo : Homogeneous matrix between the object frame and the camera frame. + * \param inliers : List of inlier points. + * \param inlierIndex : List of inlier index. + * \param elapsedTime : Elapsed time. + * \return True if the pose has been computed, false otherwise (not enough points, or size list mismatch). + * \param func : Function pointer to filter the pose in Ransac pose estimation, if we want to eliminate the poses which + * do not respect some criterion + */ + bool computePose(const std::vector &objectVpPoints, vpHomogeneousMatrix &cMo, std::vector &inliers, + std::vector &inlierIndex, double &elapsedTime, + bool (*func)(const vpHomogeneousMatrix &) = nullptr); + + /*! + * Initialize the size of the matching image (case with a matching side by + * side between IRef and ICurrent). + * + * \param IRef : Reference image. + * \param ICurrent : Current image. + * \param IMatching : Image matching. + */ + void createImageMatching(vpImage &IRef, vpImage &ICurrent, + vpImage &IMatching); + + /*! + * Initialize the size of the matching image with appropriate size according + * to the number of training images. Used to display the matching of keypoints + * detected in the current image with those detected in multiple training + * images. + * + * \param ICurrent : Current image. + * \param IMatching : Image initialized with appropriate size. + */ + void createImageMatching(vpImage &ICurrent, vpImage &IMatching); + + /*! + * Initialize the size of the matching image (case with a matching side by + * side between IRef and ICurrent). + * + * \param IRef : Reference image. + * \param ICurrent : Current image. + * \param IMatching : Image matching. + */ + void createImageMatching(vpImage &IRef, vpImage &ICurrent, vpImage &IMatching); + + /*! + * Initialize the size of the matching image with appropriate size according + * to the number of training images. Used to display the matching of keypoints + * detected in the current image with those detected in multiple training + * images. + * + * \param ICurrent : Current image. + * \param IMatching : Image initialized with appropriate size. + */ + void createImageMatching(vpImage &ICurrent, vpImage &IMatching); + + /*! + * Detect keypoints in the image. + * + * \param I : Input image. + * \param keyPoints : Output list of the detected keypoints. + * \param rectangle : Optional rectangle of the region of interest. + */ + void detect(const vpImage &I, std::vector &keyPoints, + const vpRect &rectangle = vpRect()); + + /*! + * Detect keypoints in the image. + * + * \param I_color : Input image. + * \param keyPoints : Output list of the detected keypoints. + * \param rectangle : Optional rectangle of the region of interest. + */ + void detect(const vpImage &I_color, std::vector &keyPoints, const vpRect &rectangle = vpRect()); + + /*! + * Detect keypoints in the image. + * + * \param matImg : Input image. + * \param keyPoints : Output list of the detected keypoints. + * \param mask : Optional 8-bit integer mask to detect only where mask[i][j] != 0. + */ + void detect(const cv::Mat &matImg, std::vector &keyPoints, const cv::Mat &mask = cv::Mat()); + + /*! + * Detect keypoints in the image. + * + * \param I : Input image. + * \param keyPoints : Output list of the detected keypoints. + * \param elapsedTime : Elapsed time. + * \param rectangle : Optional rectangle of the region of interest. + */ + void detect(const vpImage &I, std::vector &keyPoints, double &elapsedTime, + const vpRect &rectangle = vpRect()); + + /*! + * Detect keypoints in the image. + * + * \param I_color : Input image. + * \param keyPoints : Output list of the detected keypoints. + * \param elapsedTime : Elapsed time. + * \param rectangle : Optional rectangle of the region of interest. + */ + void detect(const vpImage &I_color, std::vector &keyPoints, double &elapsedTime, + const vpRect &rectangle = vpRect()); + + /*! + * Detect keypoints in the image. + * + * \param matImg : Input image. + * \param keyPoints : Output list of the detected keypoints. + * \param elapsedTime : Elapsed time. + * \param mask : Optional 8-bit integer mask to detect only where mask[i][j] != 0. + */ + void detect(const cv::Mat &matImg, std::vector &keyPoints, double &elapsedTime, + const cv::Mat &mask = cv::Mat()); + + /*! + * Apply a set of affine transformations to the image, detect keypoints and + * reproject them into initial image coordinates. + * See http://www.ipol.im/pub/algo/my_affine_sift/ for the details. + * See https://github.com/Itseez/opencv/blob/master/samples/python2/asift.py + * for the Python implementation by Itseez and Matt Sheckells for the current + * implementation in C++. + * \param I : Input image. + * \param listOfKeypoints : List of detected keypoints in the multiple images after + * affine transformations. + * \param listOfDescriptors : Corresponding list of descriptors. + * \param listOfAffineI : Optional parameter, list of images after affine + * transformations. + */ + void detectExtractAffine(const vpImage &I, std::vector > &listOfKeypoints, + std::vector &listOfDescriptors, + std::vector > *listOfAffineI = nullptr); + + /*! + * Display the reference and the detected keypoints in the images. + * + * \param IRef : Input reference image. + * \param ICurrent : Input current image. + * \param size : Size of the displayed cross. + */ + void display(const vpImage &IRef, const vpImage &ICurrent, unsigned int size = 3) VP_OVERRIDE; + + /*! + * Display the reference keypoints. + * + * \param ICurrent : Input current image. + * \param size : Size of the displayed crosses. + * \param color : Color of the crosses. + */ + void display(const vpImage &ICurrent, unsigned int size = 3, const vpColor &color = vpColor::green) VP_OVERRIDE; + + /*! + * Display the reference and the detected keypoints in the images. + * + * \param IRef : Input reference image. + * \param ICurrent : Input current image. + * \param size : Size of the displayed cross. + */ + void display(const vpImage &IRef, const vpImage &ICurrent, unsigned int size = 3); + + /*! + * Display the reference keypoints. + * + * \param ICurrent : Input current image. + * \param size : Size of the displayed crosses. + * \param color : Color of the crosses. + */ + void display(const vpImage &ICurrent, unsigned int size = 3, const vpColor &color = vpColor::green); + + /*! + * Display the matching lines between the detected keypoints with those + * detected in one training image. + * + * \param IRef : Reference image, used to have the x-offset. + * \param IMatching : Resulting image matching. + * \param crossSize : Size of the displayed crosses. + * \param lineThickness : Thickness of the displayed lines. + * \param color : Color to use, if none, we pick randomly a color for each pair + * of matching. + */ + void displayMatching(const vpImage &IRef, vpImage &IMatching, unsigned int crossSize, + unsigned int lineThickness = 1, const vpColor &color = vpColor::green); + + /*! + * Display matching between keypoints detected in the current image and with + * those detected in the multiple training images. Display also RANSAC inliers + * if the list is supplied. + * + * \param ICurrent : Current image. + * \param IMatching : Resulting matching image. + * \param ransacInliers : List of Ransac inliers or empty list if not available. + * \param crossSize : Size of the displayed crosses. + * \param lineThickness : Thickness of the displayed line. + */ + void displayMatching(const vpImage &ICurrent, vpImage &IMatching, + const std::vector &ransacInliers = std::vector(), + unsigned int crossSize = 3, unsigned int lineThickness = 1); + + /*! + * Display the matching lines between the detected keypoints with those + * detected in one training image. + * + * \param IRef : Reference image, used to have the x-offset. + * \param IMatching : Resulting image matching. + * \param crossSize : Size of the displayed crosses. + * \param lineThickness : Thickness of the displayed lines. + * \param color : Color to use, if none, we pick randomly a color for each pair + * of matching. + */ + void displayMatching(const vpImage &IRef, vpImage &IMatching, unsigned int crossSize, + unsigned int lineThickness = 1, const vpColor &color = vpColor::green); + + /*! + * Display the matching lines between the detected keypoints with those + * detected in one training image. + * + * \param IRef : Reference image, used to have the x-offset. + * \param IMatching : Resulting image matching. + * \param crossSize : Size of the displayed crosses. + * \param lineThickness : Thickness of the displayed lines. + * \param color : Color to use, if none, we pick randomly a color for each pair + * of matching. + */ + void displayMatching(const vpImage &IRef, vpImage &IMatching, unsigned int crossSize, + unsigned int lineThickness = 1, const vpColor &color = vpColor::green); + + /*! + * Display matching between keypoints detected in the current image and with + * those detected in the multiple training images. Display also RANSAC inliers + * if the list is supplied. + * + * \param ICurrent : Current image. + * \param IMatching : Resulting matching image. + * \param ransacInliers : List of Ransac inliers or empty list if not available. + * \param crossSize : Size of the displayed crosses. + * \param lineThickness : Thickness of the displayed line. + */ + void displayMatching(const vpImage &ICurrent, vpImage &IMatching, + const std::vector &ransacInliers = std::vector(), + unsigned int crossSize = 3, unsigned int lineThickness = 1); + + /*! + * Extract the descriptors for each keypoints of the list. + * + * \param I : Input image. + * \param keyPoints : List of keypoints we want to extract their descriptors. + * \param descriptors : Descriptors matrix with at each row the descriptors + * values for each keypoint. + * \param trainPoints : Pointer to the list of 3D train points, when a keypoint + * cannot be extracted, we need to remove the corresponding 3D point. + */ + void extract(const vpImage &I, std::vector &keyPoints, cv::Mat &descriptors, + std::vector *trainPoints = nullptr); + + /*! + * Extract the descriptors for each keypoints of the list. + * + * \param I_color : Input image. + * \param keyPoints : List of keypoints we want to extract their descriptors. + * \param descriptors : Descriptors matrix with at each row the descriptors + * values for each keypoint. + * \param trainPoints : Pointer to the list of 3D train points, when a keypoint + * cannot be extracted, we need to remove the corresponding 3D point. + */ + void extract(const vpImage &I_color, std::vector &keyPoints, cv::Mat &descriptors, + std::vector *trainPoints = nullptr); + + /*! + * Extract the descriptors for each keypoints of the list. + * + * \param matImg : Input image. + * \param keyPoints : List of keypoints we want to extract their descriptors. + * \param descriptors : Descriptors matrix with at each row the descriptors + * values for each keypoint. + * \param trainPoints : Pointer to the list of 3D train points, when a keypoint cannot + * be extracted, we need to remove the corresponding 3D point. + */ + void extract(const cv::Mat &matImg, std::vector &keyPoints, cv::Mat &descriptors, + std::vector *trainPoints = nullptr); + + /*! + * Extract the descriptors for each keypoints of the list. + * + * \param I : Input image. + * \param keyPoints : List of keypoints we want to extract their descriptors. + * \param descriptors : Descriptors matrix with at each row the descriptors + * values for each keypoint. + * \param elapsedTime : Elapsed time. + * \param trainPoints : Pointer to the list of 3D train points, when a keypoint + * cannot be extracted, we need to remove the corresponding 3D point. + */ + void extract(const vpImage &I, std::vector &keyPoints, cv::Mat &descriptors, + double &elapsedTime, std::vector *trainPoints = nullptr); + + /*! + * Extract the descriptors for each keypoints of the list. + * + * \param I_color : Input image. + * \param keyPoints : List of keypoints we want to extract their descriptors. + * \param descriptors : Descriptors matrix with at each row the descriptors + * values for each keypoint. + * \param elapsedTime : Elapsed time. + * \param trainPoints : Pointer to the list of 3D train points, when a keypoint + * cannot be extracted, we need to remove the corresponding 3D point. + */ + void extract(const vpImage &I_color, std::vector &keyPoints, cv::Mat &descriptors, + double &elapsedTime, std::vector *trainPoints = nullptr); + + /*! + * Extract the descriptors for each keypoints of the list. + * + * \param matImg : Input image. + * \param keyPoints : List of keypoints we want to extract their descriptors. + * \param descriptors : Descriptors matrix with at each row the descriptors + * values for each keypoint. + * \param elapsedTime : Elapsed time. + * \param trainPoints : Pointer to the list of 3D train points, when a keypoint + * cannot be extracted, we need to remove the corresponding 3D point. + */ + void extract(const cv::Mat &matImg, std::vector &keyPoints, cv::Mat &descriptors, double &elapsedTime, + std::vector *trainPoints = nullptr); + + /*! + * Get the covariance matrix when estimating the pose using the Virtual + * Visual Servoing approach. + * + * \warning The compute covariance flag has to be true if you want to compute + * the covariance matrix. + * + * \sa setCovarianceComputation + */ + inline vpMatrix getCovarianceMatrix() const + { + if (!m_computeCovariance) { + std::cout << "Warning : The covariance matrix has not been computed. " + << "See setCovarianceComputation() to do it." + << std::endl; + return vpMatrix(); + } + + if (m_computeCovariance && !m_useRansacVVS) { + std::cout << "Warning : The covariance matrix can only be computed " + << "with a Virtual Visual Servoing approach." << std::endl + << "Use setUseRansacVVS(true) to choose to use a pose " + << "estimation method based on a Virtual Visual Servoing approach." << std::endl; + return vpMatrix(); + } + + return m_covarianceMatrix; + } + + /*! + * Get the elapsed time to compute the keypoint detection. + * + * \return The elapsed time. + */ + inline double getDetectionTime() const { return m_detectionTime; } + + /*! + * Get the detector pointer. + * \param type : Type of the detector. + * + * \return The detector or nullptr if the type passed in parameter does not + * exist. + */ + inline cv::Ptr getDetector(const vpFeatureDetectorType &type) const + { + std::map::const_iterator it_name = m_mapOfDetectorNames.find(type); + if (it_name == m_mapOfDetectorNames.end()) { + std::cerr << "Internal problem with the feature type and the corresponding name!" << std::endl; + } + + std::map >::const_iterator findDetector = + m_detectors.find(it_name->second); + if (findDetector != m_detectors.end()) { + return findDetector->second; + } + + std::cerr << "Cannot find: " << it_name->second << std::endl; + return cv::Ptr(); + } + + /*! + * Get the detector pointer. + * \param name : Name of the detector. + * + * \return The detector or nullptr if the name passed in parameter does not + * exist. + */ + inline cv::Ptr getDetector(const std::string &name) const + { + std::map >::const_iterator findDetector = m_detectors.find(name); + if (findDetector != m_detectors.end()) { + return findDetector->second; + } + + std::cerr << "Cannot find: " << name << std::endl; + return cv::Ptr(); + } + + /*! + * Get the feature detector name associated to the type. + */ + inline std::map getDetectorNames() const { return m_mapOfDetectorNames; } + + /*! + * Get the elapsed time to compute the keypoint extraction. + * + * \return The elapsed time. + */ + inline double getExtractionTime() const { return m_extractionTime; } + + /*! + * Get the extractor pointer. + * \param type : Type of the descriptor extractor. + * + * \return The descriptor extractor or nullptr if the name passed in parameter + * does not exist. + */ + inline cv::Ptr getExtractor(const vpFeatureDescriptorType &type) const + { + std::map::const_iterator it_name = m_mapOfDescriptorNames.find(type); + if (it_name == m_mapOfDescriptorNames.end()) { + std::cerr << "Internal problem with the feature type and the corresponding name!" << std::endl; + } + + std::map >::const_iterator findExtractor = + m_extractors.find(it_name->second); + if (findExtractor != m_extractors.end()) { + return findExtractor->second; + } + + std::cerr << "Cannot find: " << it_name->second << std::endl; + return cv::Ptr(); + } + + /*! + * Get the extractor pointer. + * \param name : Name of the descriptor extractor. + * + * \return The descriptor extractor or nullptr if the name passed in parameter + * does not exist. + */ + inline cv::Ptr getExtractor(const std::string &name) const + { + std::map >::const_iterator findExtractor = m_extractors.find(name); + if (findExtractor != m_extractors.end()) { + return findExtractor->second; + } + + std::cerr << "Cannot find: " << name << std::endl; + return cv::Ptr(); + } + + /*! + * Get the feature descriptor extractor name associated to the type. + */ + inline std::map getExtractorNames() const { return m_mapOfDescriptorNames; } + + /*! + * Get the image format to use when saving training images. + * + * \return The image format. + */ + inline vpImageFormatType getImageFormat() const { return m_imageFormat; } + + /*! + * Get the elapsed time to compute the matching. + * + * \return The elapsed time. + */ + inline double getMatchingTime() const { return m_matchingTime; } + + /*! + * Get the matcher pointer. + * + * \return The matcher pointer. + */ + inline cv::Ptr getMatcher() const { return m_matcher; } + + /*! + * Get the list of matches (correspondences between the indexes of the + * detected keypoints and the train keypoints). + * + * \return The list of matches. + */ + inline std::vector getMatches() const { return m_filteredMatches; } + + /*! + * Get the list of pairs with the correspondence between the matched query + * and train keypoints. + * + * \return The list of pairs with the correspondence between the matched + * query and train keypoints. + */ + inline std::vector > getMatchQueryToTrainKeyPoints() const + { + std::vector > matchQueryToTrainKeyPoints(m_filteredMatches.size()); + for (size_t i = 0; i < m_filteredMatches.size(); i++) { + matchQueryToTrainKeyPoints.push_back( + std::pair(m_queryFilteredKeyPoints[static_cast(m_filteredMatches[i].queryIdx)], + m_trainKeyPoints[static_cast(m_filteredMatches[i].trainIdx)])); + } + return matchQueryToTrainKeyPoints; + } + + /*! + * Get the number of train images. + * + * \return The number of train images. + */ + inline unsigned int getNbImages() const { return static_cast(m_mapOfImages.size()); } + + /*! + * Get the 3D coordinates of the object points matched (the corresponding 3D + * coordinates in the object frame of the keypoints detected in the current + * image after the matching). + * + * \param objectPoints : List of 3D coordinates in the object frame. + */ + void getObjectPoints(std::vector &objectPoints) const; + + /*! + * Get the 3D coordinates of the object points matched (the corresponding 3D + * coordinates in the object frame of the keypoints detected in the current + * image after the matching). + * + * \param objectPoints : List of 3D coordinates in the object frame. + */ + void getObjectPoints(std::vector &objectPoints) const; + + /*! + * Get the elapsed time to compute the pose. + * + * \return The elapsed time. + */ + inline double getPoseTime() const { return m_poseTime; } + + /*! + * Get the descriptors matrix for the query keypoints. + * + * \return Matrix with descriptors values at each row for each query + * keypoints. + */ + inline cv::Mat getQueryDescriptors() const { return m_queryDescriptors; } + + /*! + * Get the query keypoints list in OpenCV type. + * + * \param matches : If false return the list of all query keypoints extracted in the current image. + * If true, return only the query keypoints list that have matches. + * \param keyPoints : List of query keypoints (or keypoints detected in the + * current image). + */ + void getQueryKeyPoints(std::vector &keyPoints, bool matches = true) const; + + /*! + * Get the query keypoints list in ViSP type. + * + * \param keyPoints : List of query keypoints (or keypoints detected in the + * current image). + * \param matches : If false return the list of all query keypoints extracted in the current image. + * If true, return only the query keypoints list that have matches. + */ + void getQueryKeyPoints(std::vector &keyPoints, bool matches = true) const; + + /*! + * Get the list of Ransac inliers. + * + * \return The list of Ransac inliers. + */ + inline std::vector getRansacInliers() const { return m_ransacInliers; } + + /*! + * Get the list of Ransac outliers. + * + * \return The list of Ransac outliers. + */ + inline std::vector getRansacOutliers() const { return m_ransacOutliers; } + + /*! + * Get the train descriptors matrix. + * + * \return : Matrix with descriptors values at each row for each train + * keypoints (or reference keypoints). + */ + inline cv::Mat getTrainDescriptors() const { return m_trainDescriptors; } + + /*! + * Get the train keypoints list in OpenCV type. + * + * \param keyPoints : List of train keypoints (or reference keypoints). + */ + void getTrainKeyPoints(std::vector &keyPoints) const; + + /*! + * Get the train keypoints list in ViSP type. + * + * \param keyPoints : List of train keypoints (or reference keypoints). + */ + void getTrainKeyPoints(std::vector &keyPoints) const; + + /*! + * Get the train points (the 3D coordinates in the object frame) list in + * OpenCV type. + * + * \param points : List of train points (or reference points). + */ + void getTrainPoints(std::vector &points) const; + + /*! + * Get the train points (the 3D coordinates in the object frame) list in ViSP + * type. + * + * \param points : List of train points (or reference points). + */ + void getTrainPoints(std::vector &points) const; + + /*! + * Initialize a matcher based on its name. + * + * \param matcherName : Name of the matcher (e.g BruteForce, FlannBased). + */ + void initMatcher(const std::string &matcherName); + + /*! + * Insert a reference image and a current image side-by-side. + * + * \param IRef : Reference image. + * \param ICurrent : Current image. + * \param IMatching : Matching image for displaying all the matching between + * the query keypoints and those detected in the training images. + */ + void insertImageMatching(const vpImage &IRef, const vpImage &ICurrent, + vpImage &IMatching); + + /*! + * Insert the different training images in the matching image. + * + * \param ICurrent : Current image. + * \param IMatching : Matching image for displaying all the matching between + * the query keypoints and those detected in the training images + */ + void insertImageMatching(const vpImage &ICurrent, vpImage &IMatching); + + /*! + * Insert a reference image and a current image side-by-side. + * + * \param IRef : Reference image. + * \param ICurrent : Current image. + * \param IMatching : Matching image for displaying all the matching between + * the query keypoints and those detected in the training images. + */ + void insertImageMatching(const vpImage &IRef, const vpImage &ICurrent, vpImage &IMatching); + + /*! + * Insert the different training images in the matching image. + * + * \param ICurrent : Current image. + * \param IMatching : Matching image for displaying all the matching between + * the query keypoints and those detected in the training images + */ + void insertImageMatching(const vpImage &ICurrent, vpImage &IMatching); + + /*! + * Load configuration parameters from an XML config file. + * + * \param configFile : Path to the XML config file. + */ + void loadConfigFile(const std::string &configFile); + + /*! + * Load learning data saved on disk. + * + * \param filename : Path of the learning file. + * \param binaryMode : If true, the learning file is in a binary mode, + * otherwise it is in XML mode. + * \param append : If true, concatenate the learning data, otherwise reset the variables. + */ + void loadLearningData(const std::string &filename, bool binaryMode = false, bool append = false); + + /*! + * Match keypoints based on distance between their descriptors. + * + * \param trainDescriptors : Train descriptors (or reference descriptors). + * \param queryDescriptors : Query descriptors. + * \param matches : Output list of matches. + * \param elapsedTime : Elapsed time. + */ + void match(const cv::Mat &trainDescriptors, const cv::Mat &queryDescriptors, std::vector &matches, + double &elapsedTime); + + /*! + * Match keypoints detected in the image with those built in the reference + * list. + * + * \param I : Input current image. + * \return The number of matched keypoints. + */ + unsigned int matchPoint(const vpImage &I) VP_OVERRIDE; + + /*! + * Match keypoints detected in a region of interest of the image with those + * built in the reference list. + * + * \param I : Input image. + * \param iP : Coordinate of the top-left corner of the region of interest. + * \param height : Height of the region of interest. + * \param width : Width of the region of interest. + * \return The number of matched keypoints. + */ + unsigned int matchPoint(const vpImage &I, const vpImagePoint &iP, unsigned int height, + unsigned int width) VP_OVERRIDE; + + /*! + * Match keypoints detected in a region of interest of the image with those + * built in the reference list. + * + * \param I : Input image. + * \param rectangle : Rectangle of the region of interest. + * \return The number of matched keypoints. + */ + unsigned int matchPoint(const vpImage &I, const vpRect &rectangle) VP_OVERRIDE; + + /*! + * Match query keypoints with those built in the reference list using buildReference(). + * + * \param queryKeyPoints : List of the query keypoints. + * \param queryDescriptors : List of the query descriptors. + * + * \return The number of matched keypoints. + */ + unsigned int matchPoint(const std::vector &queryKeyPoints, const cv::Mat &queryDescriptors); + + /*! + * Match keypoints detected in the image with those built in the reference + * list and compute the pose. + * + * \param I : Input image. + * \param cam : Camera parameters. + * \param cMo : Homogeneous matrix between the object frame and the camera frame. + * \param func : Function pointer to filter the pose in Ransac pose + * estimation, if we want to eliminate the poses which do not respect some criterion. + * \param rectangle : Rectangle corresponding to the ROI (Region of Interest) to consider. + * \return True if the matching and the pose estimation are OK, false otherwise. + */ + bool matchPoint(const vpImage &I, const vpCameraParameters &cam, vpHomogeneousMatrix &cMo, + bool (*func)(const vpHomogeneousMatrix &) = nullptr, const vpRect &rectangle = vpRect()); + + /*! + * Match keypoints detected in the image with those built in the reference + * list and compute the pose. + * + * \param I : Input image. + * \param cam : Camera parameters. + * \param cMo : Homogeneous matrix between the object frame and the camera frame. + * \param error : Reprojection mean square error (in pixel) between the + * 2D points and the projection of the 3D points with the estimated pose. + * \param elapsedTime : Time to detect, extract, match and compute the pose. + * \param func : Function pointer to filter the pose in Ransac pose + * estimation, if we want to eliminate the poses which do not respect some criterion. + * \param rectangle : Rectangle corresponding to the ROI (Region of Interest) to consider. + * \return True if the matching and the pose estimation are OK, false otherwise. + */ + bool matchPoint(const vpImage &I, const vpCameraParameters &cam, vpHomogeneousMatrix &cMo, + double &error, double &elapsedTime, bool (*func)(const vpHomogeneousMatrix &) = nullptr, + const vpRect &rectangle = vpRect()); + + /*! + * Match keypoints detected in the image with those built in the reference + * list and return the bounding box and the center of gravity. + * + * \param I : Input image. + * \param boundingBox : Bounding box that contains the good matches. + * \param centerOfGravity : Center of gravity computed from the location of + * the good matches (could differ of the center of the bounding box). + * \param isPlanarObject : If the object is planar, the homography matrix is + * estimated to eliminate outliers, otherwise it is the fundamental matrix + * which is estimated. + * \param imPts1 : Pointer to the list of reference keypoints if not null. + * \param imPts2 : Pointer to the list of current keypoints if not null. + * \param meanDescriptorDistance : Pointer to the value + * of the average distance of the descriptors if not null. + * \param detectionScore : Pointer to the value of the detection score if not null. + * \param rectangle : Rectangle corresponding to the ROI (Region of Interest) + * to consider. + * \return True if the object is present, false otherwise. + */ + bool matchPointAndDetect(const vpImage &I, vpRect &boundingBox, vpImagePoint ¢erOfGravity, + const bool isPlanarObject = true, std::vector *imPts1 = nullptr, + std::vector *imPts2 = nullptr, double *meanDescriptorDistance = nullptr, + double *detectionScore = nullptr, const vpRect &rectangle = vpRect()); + + /*! + * Match keypoints detected in the image with those built in the reference + * list, compute the pose and return also the bounding box and the center of + * gravity. + * + * \param I : Input image. + * \param cam : Camera parameters. + * \param cMo : Homogeneous matrix between the object frame and the camera frame. + * \param error : Reprojection mean square error (in pixel) between the + * 2D points and the projection of the 3D points with the estimated pose. + * \param elapsedTime : Time to detect, extract, match and compute the pose. + * \param boundingBox : Bounding box that contains the good matches. + * \param centerOfGravity : Center of gravity computed from the location of + * the good matches (could differ of the center of the bounding box). + * \param func : Function pointer to filter the pose in Ransac pose estimation, if we + * want to eliminate the poses which do not respect some criterion. + * \param rectangle : Rectangle corresponding to the ROI (Region of Interest) to consider. + * \return True if the matching and the pose estimation are OK, false otherwise. + */ + bool matchPointAndDetect(const vpImage &I, const vpCameraParameters &cam, vpHomogeneousMatrix &cMo, + double &error, double &elapsedTime, vpRect &boundingBox, vpImagePoint ¢erOfGravity, + bool (*func)(const vpHomogeneousMatrix &) = nullptr, const vpRect &rectangle = vpRect()); + + /*! + * Match keypoints detected in the image with those built in the reference + * list. + * + * \param I_color : Input current image. + * \return The number of matched keypoints. + */ + unsigned int matchPoint(const vpImage &I_color); + + /*! + * Match keypoints detected in a region of interest of the image with those + * built in the reference list. + * + * \param I_color : Input image. + * \param iP : Coordinate of the top-left corner of the region of interest. + * \param height : Height of the region of interest. + * \param width : Width of the region of interest. + * \return The number of matched keypoints. + */ + unsigned int matchPoint(const vpImage &I_color, const vpImagePoint &iP, unsigned int height, + unsigned int width); + + /*! + * Match keypoints detected in a region of interest of the image with those + * built in the reference list. + * + * \param I_color : Input image. + * \param rectangle : Rectangle of the region of interest. + * \return The number of matched keypoints. + */ + unsigned int matchPoint(const vpImage &I_color, const vpRect &rectangle); + + /*! + * Match keypoints detected in the image with those built in the reference + * list and compute the pose. + * + * \param I_color : Input image. + * \param cam : Camera parameters. + * \param cMo : Homogeneous matrix between the object frame and the camera frame. + * \param func : Function pointer to filter the pose in Ransac pose + * estimation, if we want to eliminate the poses which do not respect some criterion. + * \param rectangle : Rectangle corresponding to the ROI (Region of Interest) to consider. + * \return True if the matching and the pose estimation are OK, false otherwise. + */ + bool matchPoint(const vpImage &I_color, const vpCameraParameters &cam, vpHomogeneousMatrix &cMo, + bool (*func)(const vpHomogeneousMatrix &) = nullptr, const vpRect &rectangle = vpRect()); + + /*! + * Match keypoints detected in the image with those built in the reference + * list and compute the pose. + * + * \param I_color : Input image. + * \param cam : Camera parameters. + * \param cMo : Homogeneous matrix between the object frame and the camera frame. + * \param error : Reprojection mean square error (in pixel) between the + * 2D points and the projection of the 3D points with the estimated pose. + * \param elapsedTime : Time to detect, extract, match and compute the pose. + * \param func : Function pointer to filter the pose in Ransac pose + * estimation, if we want to eliminate the poses which do not respect some criterion. + * \param rectangle : Rectangle corresponding to the ROI (Region of Interest) to consider. + * \return True if the matching and the pose estimation are OK, false otherwise. + */ + bool matchPoint(const vpImage &I_color, const vpCameraParameters &cam, vpHomogeneousMatrix &cMo, + double &error, double &elapsedTime, bool (*func)(const vpHomogeneousMatrix &) = nullptr, + const vpRect &rectangle = vpRect()); + + /*! + * Reset the instance as if we would declare another vpKeyPoint variable. + */ + void reset(); + + /*! + * Save the learning data in a file in XML or binary mode. + * + * \param filename : Path of the save file. + * \param binaryMode : If true, the data are saved in binary mode, otherwise + * in XML mode. + * \param saveTrainingImages : If true, save also the training images on disk. + */ + void saveLearningData(const std::string &filename, bool binaryMode = false, bool saveTrainingImages = true); + + /*! + * Set if the covariance matrix has to be computed in the Virtual Visual + * Servoing approach. + * + * \param flag : True if the covariance has to be computed, false otherwise. + */ + inline void setCovarianceComputation(const bool &flag) + { + m_computeCovariance = flag; + if (!m_useRansacVVS) { + std::cout << "Warning : The covariance matrix can only be computed " + << "with a Virtual Visual Servoing approach." << std::endl + << "Use setUseRansacVVS(true) to choose to use a pose " + << "estimation method based on a Virtual " + << "Visual Servoing approach." << std::endl; + } + } + + /*! + * Set the method to decide if the object is present or not. + * + * \param method : Detection method (detectionThreshold or detectionScore). + */ + inline void setDetectionMethod(const vpDetectionMethodType &method) { m_detectionMethod = method; } + + /*! + * Set and initialize a detector. + * + * \param detectorType : Type of the detector. + */ + inline void setDetector(const vpFeatureDetectorType &detectorType) + { + m_detectorNames.clear(); + m_detectorNames.push_back(m_mapOfDetectorNames[detectorType]); + m_detectors.clear(); + initDetector(m_mapOfDetectorNames[detectorType]); + } + + /*! + * Set and initialize a detector denominated by his name \p detectorName. + * + * \param detectorName : Name of the detector. + */ + inline void setDetector(const std::string &detectorName) + { + m_detectorNames.clear(); + m_detectorNames.push_back(detectorName); + m_detectors.clear(); + initDetector(detectorName); + } + +#if (VISP_HAVE_OPENCV_VERSION >= 0x020400 && VISP_HAVE_OPENCV_VERSION < 0x030000) + /*! + * Template function to set to a \p parameterName a value for a specific + * detector named by his \p detectorName. + * + * \param detectorName : Name of the detector + * \param parameterName : Name of the parameter + * \param value : Value to set + */ + template + inline void setDetectorParameter(const T1 detectorName, const T2 parameterName, const T3 value) + { + if (m_detectors.find(detectorName) != m_detectors.end()) { + m_detectors[detectorName]->set(parameterName, value); + } + } +#endif + + /*! + * Set and initialize a list of detectors denominated by their names \p + * detectorNames. + * + * \param detectorNames : List of detector names. + */ + inline void setDetectors(const std::vector &detectorNames) + { + m_detectorNames.clear(); + m_detectors.clear(); + m_detectorNames = detectorNames; + initDetectors(m_detectorNames); + } + + /*! + * Set and initialize a descriptor extractor. + * + * \param extractorType : Type of the descriptor extractor. + */ + inline void setExtractor(const vpFeatureDescriptorType &extractorType) + { + m_extractorNames.clear(); + m_extractorNames.push_back(m_mapOfDescriptorNames[extractorType]); + m_extractors.clear(); + initExtractor(m_mapOfDescriptorNames[extractorType]); + } + + /*! + * Set and initialize a descriptor extractor denominated by his name \p + * extractorName. + * + * \param extractorName : Name of the extractor. + */ + inline void setExtractor(const std::string &extractorName) + { + m_extractorNames.clear(); + m_extractorNames.push_back(extractorName); + m_extractors.clear(); + initExtractor(extractorName); + } + +#if (VISP_HAVE_OPENCV_VERSION >= 0x020400 && VISP_HAVE_OPENCV_VERSION < 0x030000) + /*! + * Template function to set to a \p parameterName a value for a specific + * extractor named by his \p extractorName. + * + * \param extractorName : Name of the extractor + * \param parameterName : Name of the parameter + * \param value : Value to set + */ + template + inline void setExtractorParameter(const T1 extractorName, const T2 parameterName, const T3 value) + { + if (m_extractors.find(extractorName) != m_extractors.end()) { + m_extractors[extractorName]->set(parameterName, value); + } + } +#endif + + /*! + * Set and initialize a list of extractors denominated by their names \p + * extractorNames. + * + * \param extractorNames : List of extractor names. + */ + inline void setExtractors(const std::vector &extractorNames) + { + m_extractorNames.clear(); + m_extractorNames = extractorNames; + m_extractors.clear(); + initExtractors(m_extractorNames); + } + + /*! + * Set the image format to use when saving training images. + * + * \param imageFormat : The image format. + */ + inline void setImageFormat(const vpImageFormatType &imageFormat) { m_imageFormat = imageFormat; } + + /*! + * Set and initialize a matcher denominated by his name \p matcherName. + * The different matchers are: + * - BruteForce (it uses L2 distance) + * - BruteForce-L1 + * - BruteForce-Hamming + * - BruteForce-Hamming(2) + * - FlannBased + * + * L1 and L2 norms are preferable choices for SIFT and SURF descriptors, + * NORM_HAMMING should be used with ORB, BRISK and BRIEF, NORM_HAMMING2 + * should be used with ORB when WTA_K==3 or 4. + * + * \param matcherName : Name of the matcher. + */ + inline void setMatcher(const std::string &matcherName) + { + m_matcherName = matcherName; + initMatcher(m_matcherName); + } + + /*! + * Set maximum number of keypoints to extract. + * \warning This functionality is only available for ORB and SIFT extractors. + * \param maxFeatures : Maximum number of keypoints to extract. Set -1 to use default values. + */ + void setMaxFeatures(int maxFeatures) { m_maxFeatures = maxFeatures; } + + /*! + * Set the filtering method to eliminate false matching. + * The different methods are: + * - vpKeyPoint::constantFactorDistanceThreshold : Keep matches whose descriptor + * distance is below dist_min * factor. + * - vpKeyPoint::stdDistanceThreshold : Keep matches whose the descriptor distance is + * below dist_min + standard_deviation. + * - vpKeyPoint::ratioDistanceThreshold : Keep matches enough discriminated when the ratio + * distance between the 2 best matches is below the threshold. + * - vpKeyPoint::stdAndRatioDistanceThreshold : Keep matches that agree with at least + * one of the two conditions. + * - vpKeyPoint::noFilterMatching : No filter is applied. + * + * \param filterType : Type of the filtering method + */ + inline void setFilterMatchingType(const vpFilterMatchingType &filterType) + { + m_filterType = filterType; + + // Use k-nearest neighbors (knn) to retrieve the two best matches for a + // keypoint So this is useful only for ratioDistanceThreshold method + if (filterType == ratioDistanceThreshold || filterType == stdAndRatioDistanceThreshold) { + m_useKnn = true; + +#if (VISP_HAVE_OPENCV_VERSION >= 0x020400 && VISP_HAVE_OPENCV_VERSION < 0x030000) + if (m_matcher != nullptr && m_matcherName == "BruteForce") { + // if a matcher is already initialized, disable the crossCheck + // because it will not work with knnMatch + m_matcher->set("crossCheck", false); + } +#endif + } + else { + m_useKnn = false; + +#if (VISP_HAVE_OPENCV_VERSION >= 0x020400 && VISP_HAVE_OPENCV_VERSION < 0x030000) + if (m_matcher != nullptr && m_matcherName == "BruteForce") { + // if a matcher is already initialized, set the crossCheck mode if + // necessary + m_matcher->set("crossCheck", m_useBruteForceCrossCheck); + } +#endif + } + } + + /*! + * Set the factor value for the filtering method: + * constantFactorDistanceThreshold. + * + * \param factor : Factor value + */ + inline void setMatchingFactorThreshold(const double factor) + { + if (factor > 0.0) { + m_matchingFactorThreshold = factor; + } + else { + throw vpException(vpException::badValue, "The factor must be positive."); + } + } + + /*! + * Set the ratio value for the filtering method: ratioDistanceThreshold. + * + * \param ratio : Ratio value (]0 ; 1]) + */ + inline void setMatchingRatioThreshold(double ratio) + { + if (ratio > 0.0 && (ratio < 1.0 || std::fabs(ratio - 1.0) < std::numeric_limits::epsilon())) { + m_matchingRatioThreshold = ratio; + } + else { + throw vpException(vpException::badValue, "The ratio must be in the interval ]0 ; 1]."); + } + } + + /*! + * Set the percentage value for defining the cardinality of the consensus + * group. + * + * \param percentage : Percentage value (]0 ; 100]) + */ + inline void setRansacConsensusPercentage(double percentage) + { + if (percentage > 0.0 && + (percentage < 100.0 || std::fabs(percentage - 100.0) < std::numeric_limits::epsilon())) { + m_ransacConsensusPercentage = percentage; + } + else { + throw vpException(vpException::badValue, "The percentage must be in the interval ]0 ; 100]."); + } + } + + /*! + * Set filter flag for RANSAC pose estimation. + */ + inline void setRansacFilterFlag(const vpPose::RANSAC_FILTER_FLAGS &flag) { m_ransacFilterFlag = flag; } + + /*! + * Set the maximum number of iterations for the Ransac pose estimation + * method. + * + * \param nbIter : Maximum number of iterations for the Ransac + */ + inline void setRansacIteration(int nbIter) + { + if (nbIter > 0) { + m_nbRansacIterations = nbIter; + } + else { + throw vpException(vpException::badValue, "The number of iterations must be greater than zero."); + } + } + + /*! + * Use or not the multithreaded version. + * + * \note Needs C++11 or higher. + */ + inline void setRansacParallel(bool parallel) { m_ransacParallel = parallel; } + + /*! + * Set the number of threads to use if multithreaded RANSAC pose. + * + * \param nthreads : Number of threads, if 0 the number of CPU threads will be determined + * \sa setRansacParallel + */ + inline void setRansacParallelNbThreads(unsigned int nthreads) { m_ransacParallelNbThreads = nthreads; } + + /*! + * Set the maximum reprojection error (in pixel) to determine if a point is + * an inlier or not. + * + * \param reprojectionError : Maximum reprojection error in pixel (used by + * OpenCV function) + */ + inline void setRansacReprojectionError(double reprojectionError) + { + if (reprojectionError > 0.0) { + m_ransacReprojectionError = reprojectionError; + } + else { + throw vpException(vpException::badValue, "The Ransac reprojection " + "threshold must be positive " + "as we deal with distance."); + } + } + + /*! + * Set the minimum number of inlier for the Ransac pose estimation method. + * + * \param minCount : Minimum number of inlier for the consensus + */ + inline void setRansacMinInlierCount(int minCount) + { + if (minCount > 0) { + m_nbRansacMinInlierCount = minCount; + } + else { + throw vpException(vpException::badValue, "The minimum number of inliers must be greater than zero."); + } + } + + /*! + * Set the maximum error (in meter) to determine if a point is an inlier or + * not. + * + * \param threshold : Maximum error in meter for ViSP function + */ + inline void setRansacThreshold(double threshold) + { + if (threshold > 0.0) { + m_ransacThreshold = threshold; + } + else { + throw vpException(vpException::badValue, "The Ransac threshold must be positive as we deal with distance."); + } + } + + /*! + * Set if multiple affine transformations must be used to detect and extract + * keypoints. + * + * \param useAffine : True to use multiple affine transformations, false + * otherwise + */ + inline void setUseAffineDetection(bool useAffine) { m_useAffineDetection = useAffine; } + +#if (VISP_HAVE_OPENCV_VERSION >= 0x020400 && VISP_HAVE_OPENCV_VERSION < 0x030000) + /*! + * Set if cross check method must be used to eliminate some false matches + * with a brute-force matching method. + * + * \param useCrossCheck : True to use cross check, false otherwise + */ + inline void setUseBruteForceCrossCheck(bool useCrossCheck) + { + // Only available with BruteForce and with k=1 (i.e not used with a + // ratioDistanceThreshold method) + if (m_matcher != nullptr && !m_useKnn && m_matcherName == "BruteForce") { + m_matcher->set("crossCheck", useCrossCheck); + } + else if (m_matcher != nullptr && m_useKnn && m_matcherName == "BruteForce") { + std::cout << "Warning, you try to set the crossCheck parameter with a " + << "BruteForce matcher but knn is enabled" + << " (the filtering method uses a ratio constraint)" << std::endl; + } + } +#endif + + /*! + * Set if we want to match the train keypoints to the query keypoints. + * + * \param useMatchTrainToQuery : True to match the train keypoints to the + * query keypoints + */ + inline void setUseMatchTrainToQuery(bool useMatchTrainToQuery) { m_useMatchTrainToQuery = useMatchTrainToQuery; } + + /*! + * Set the flag to choose between a percentage value of inliers for the + * cardinality of the consensus group or a minimum number. + * + * \param usePercentage : True to a percentage ratio of inliers, otherwise + * use a specified number of inliers + */ + inline void setUseRansacConsensusPercentage(bool usePercentage) { m_useConsensusPercentage = usePercentage; } + + /*! + * Set the flag to choose between the OpenCV or ViSP Ransac pose estimation + * function. + * + * \param ransacVVS : True to use ViSP function, otherwise use OpenCV + * function + */ + inline void setUseRansacVVS(bool ransacVVS) { m_useRansacVVS = ransacVVS; } + + /*! + * Set the flag to filter matches where multiple query keypoints are matched + * to the same train keypoints. + * + * \param singleMatchFilter : True to use the single match filter. + */ + inline void setUseSingleMatchFilter(bool singleMatchFilter) { m_useSingleMatchFilter = singleMatchFilter; } + +private: + //! If true, compute covariance matrix if the user select the pose + //! estimation method using ViSP + bool m_computeCovariance; + //! Covariance matrix + vpMatrix m_covarianceMatrix; + //! Current id associated to the training image used for the learning. + int m_currentImageId; + //! Method (based on descriptor distances) to decide if the object is + //! present or not. + vpDetectionMethodType m_detectionMethod; + //! Detection score to decide if the object is present or not. + double m_detectionScore; + //! Detection threshold based on average of descriptor distances to decide + //! if the object is present or not. + double m_detectionThreshold; + //! Elapsed time to detect keypoints. + double m_detectionTime; + //! List of detector names. + std::vector m_detectorNames; + //! Map of smart reference-counting pointers (similar to shared_ptr in + //! Boost) detectors, + // with a key based upon the detector name. + std::map > m_detectors; + //! Elapsed time to extract descriptors for the detected keypoints. + double m_extractionTime; + //! List of extractor name. + std::vector m_extractorNames; + //! Map of smart reference-counting pointers (similar to shared_ptr in + //! Boost) extractors, + // with a key based upon the extractor name. + std::map > m_extractors; + //! List of filtered matches between the detected and the trained keypoints. + std::vector m_filteredMatches; + //! Chosen method of filtering to eliminate false matching. + vpFilterMatchingType m_filterType; + //! Image format to use when saving the training images + vpImageFormatType m_imageFormat; + //! List of k-nearest neighbors for each detected keypoints (if the method + //! chosen is based upon on knn). + std::vector > m_knnMatches; + //! Map descriptor enum type to string. + std::map m_mapOfDescriptorNames; + //! Map detector enum type to string. + std::map m_mapOfDetectorNames; + //! Map of image id to know to which training image is related a training + //! keypoints. + std::map m_mapOfImageId; + //! Map of images to have access to the image buffer according to his image + //! id. + std::map > m_mapOfImages; + //! Smart reference-counting pointer (similar to shared_ptr in Boost) of + //! descriptor matcher (e.g. BruteForce or FlannBased). + cv::Ptr m_matcher; + //! Name of the matcher. + std::string m_matcherName; + //! List of matches between the detected and the trained keypoints. + std::vector m_matches; + //! Factor value for the filtering method: constantFactorDistanceThreshold. + double m_matchingFactorThreshold; + //! Ratio value for the filtering method: ratioDistanceThreshold. + double m_matchingRatioThreshold; + //! Elapsed time to do the matching. + double m_matchingTime; + //! List of pairs between the keypoint and the 3D point after the Ransac. + std::vector > m_matchRansacKeyPointsToPoints; + //! Maximum number of iterations for the Ransac method. + int m_nbRansacIterations; + //! Minimum number of inliers for the Ransac method. + int m_nbRansacMinInlierCount; + //! List of 3D points (in the object frame) filtered after the matching to + //! compute the pose. + std::vector m_objectFilteredPoints; + //! Elapsed time to compute the pose. + double m_poseTime; + /*! Matrix of descriptors (each row contains the descriptors values for each + keypoints detected in the current image). */ + cv::Mat m_queryDescriptors; + //! List of detected keypoints filtered after the matching. + std::vector m_queryFilteredKeyPoints; + //! List of keypoints detected in the current image. + std::vector m_queryKeyPoints; + //! Percentage value to determine the number of inliers for the Ransac + //! method. + double m_ransacConsensusPercentage; + //! Filtering flag for RANSAC and degenerate configuration check + vpPose::RANSAC_FILTER_FLAGS m_ransacFilterFlag; + //! List of inliers. + std::vector m_ransacInliers; + //! List of outliers. + std::vector m_ransacOutliers; + //! If true, use parallel RANSAC + bool m_ransacParallel; + //! Number of threads (if 0, try to determine the number of CPU threads) + unsigned int m_ransacParallelNbThreads; + //! Maximum reprojection error (in pixel for the OpenCV method) to decide if + //! a point is an inlier or not. + double m_ransacReprojectionError; + //! Maximum error (in meter for the ViSP method) to decide if a point is an + //! inlier or not. + double m_ransacThreshold; + //! Matrix of descriptors (each row contains the descriptors values for each + //! keypoints + // detected in the train images). + cv::Mat m_trainDescriptors; + //! List of keypoints detected in the train images. + std::vector m_trainKeyPoints; + //! List of 3D points (in the object frame) corresponding to the train + //! keypoints. + std::vector m_trainPoints; + //! List of 3D points in vpPoint format (in the object frame) corresponding + //! to the train keypoints. + std::vector m_trainVpPoints; + //! If true, use multiple affine transformations to cober the 6 affine + //! parameters + bool m_useAffineDetection; +#if (VISP_HAVE_OPENCV_VERSION >= 0x020400 && VISP_HAVE_OPENCV_VERSION < 0x030000) + //! If true, some false matches will be eliminate by keeping only pairs + //! (i,j) such that for i-th query descriptor the j-th descriptor in the + //! matcher’s collection is the nearest and vice versa. + bool m_useBruteForceCrossCheck; +#endif + //! Flag set if a percentage value is used to determine the number of + //! inliers for the Ransac method. + bool m_useConsensusPercentage; + //! Flag set if a knn matching method must be used. + bool m_useKnn; + //! Flag set if we want to match the train keypoints to the query keypoints, + //! useful when there is only one train image because it reduces the number + //! of possible false matches (by default it is the inverse because normally + //! there are multiple train images of different views of the object) + bool m_useMatchTrainToQuery; + //! Flag set if a Ransac VVS pose estimation must be used. + bool m_useRansacVVS; + //! If true, keep only pairs of keypoints where each train keypoint is + //! matched to a single query keypoint + bool m_useSingleMatchFilter; + //! Grayscale image buffer, used when passing color images + vpImage m_I; + //! Max number of features to extract, -1 to use default values + int m_maxFeatures; + + /*! + * Apply an affine and skew transformation to an image. + * \param tilt : Tilt value in the direction of x. + * \param phi : Rotation value. + * \param img : Modified image after the transformation. + * \param mask : Mask containing the location of the image pixels after the transformation. + * \param Ai : Inverse affine matrix + */ + void affineSkew(double tilt, double phi, cv::Mat &img, cv::Mat &mask, cv::Mat &Ai); + + /*! + * Compute the pose estimation error, the mean square error (in pixel) between + * the location of the detected keypoints and the location of the projection + * of the 3D model with the estimated pose. + * + * \param matchKeyPoints : List of pairs between the detected keypoints and + * the corresponding 3D points. + * \param cam : Camera parameters. + * \param cMo_est : Estimated pose of the object. + * + * \return The mean square error (in pixel) between the location of the + * detected keypoints and the location of the projection of the 3D model with + * the estimated pose. + */ + double computePoseEstimationError(const std::vector > &matchKeyPoints, + const vpCameraParameters &cam, const vpHomogeneousMatrix &cMo_est); + + /*! + * Filter the matches using the desired filtering method. + */ + void filterMatches(); + + /*! + * Initialize method for RANSAC parameters and for detectors, extractors and + * matcher, and for others parameters. + */ + void init() VP_OVERRIDE; + + /*! + * Initialize a keypoint detector based on its name. + * + * \param[in] detectorNames : Name of the detector (e.g FAST, SIFT, SURF, etc.). + */ + void initDetector(const std::string &detectorNames); + + /*! + * Initialize a list of keypoints detectors if we want to concatenate multiple + * detectors. + * + * \param[in] detectorNames : List of detector names. + */ + void initDetectors(const std::vector &detectorNames); + + /*! + * Initialize a descriptor extractor based on its name. + * + * \param[in] extractorName : Name of the extractor (e.g SIFT, SURF, ORB, etc.). + */ + void initExtractor(const std::string &extractorName); + + /*! + * Initialize a list of descriptor extractors if we want to concatenate + * multiple extractors. + * + * \param[in] extractorNames : List of extractor names. + */ + void initExtractors(const std::vector &extractorNames); + + /*! + * Initialize map of available detectors and descriptors. + */ + void initFeatureNames(); + + inline size_t myKeypointHash(const cv::KeyPoint &kp) + { + size_t _val = 2166136261U, scale = 16777619U; + Cv32suf u; + u.f = kp.pt.x; + _val = (scale * _val) ^ u.u; + u.f = kp.pt.y; + _val = (scale * _val) ^ u.u; + u.f = kp.size; + _val = (scale * _val) ^ u.u; + // As the keypoint angle can be computed for certain type of keypoint only + // when extracting the corresponding descriptor, the angle field is not + // taking into account for the hash + // u.f = kp.angle; _val = (scale * _val) ^ u.u; + u.f = kp.response; + _val = (scale * _val) ^ u.u; + _val = (scale * _val) ^ (static_cast(kp.octave)); + _val = (scale * _val) ^ (static_cast(kp.class_id)); + return _val; + } + +#if (VISP_HAVE_OPENCV_VERSION >= 0x030000) + /* + * Adapts a detector to detect points over multiple levels of a Gaussian + * pyramid. Useful for detectors that are not inherently scaled. + * From OpenCV 2.4.11 source code. + */ + class PyramidAdaptedFeatureDetector : public cv::FeatureDetector + { + public: + // maxLevel - The 0-based index of the last pyramid layer + PyramidAdaptedFeatureDetector(const cv::Ptr &detector, int maxLevel = 2); + + // TODO implement read/write + virtual bool empty() const VP_OVERRIDE; + + protected: + virtual void detect(cv::InputArray image, CV_OUT std::vector &keypoints, + cv::InputArray mask = cv::noArray()) VP_OVERRIDE; + virtual void detectImpl(const cv::Mat &image, std::vector &keypoints, + const cv::Mat &mask = cv::Mat()) const; + + cv::Ptr m_detector; + int m_maxLevel; + }; + + /* + * A class filters a vector of keypoints. + * Because now it is difficult to provide a convenient interface for all + * usage scenarios of the keypoints filter class, it has only several needed + * by now static methods. + */ + class KeyPointsFilter + { + public: + KeyPointsFilter() { } + + /* + * Remove keypoints within borderPixels of an image edge. + */ + static void runByImageBorder(std::vector &keypoints, cv::Size imageSize, int borderSize); + /* + * Remove keypoints of sizes out of range. + */ + static void runByKeypointSize(std::vector &keypoints, float minSize, float maxSize = FLT_MAX); + /* + * Remove keypoints from some image by mask for pixels of this image. + */ + static void runByPixelsMask(std::vector &keypoints, const cv::Mat &mask); + /* + * Remove duplicated keypoints. + */ + static void removeDuplicated(std::vector &keypoints); + + /* + * Retain the specified number of the best keypoints (according to the + * response) + */ + static void retainBest(std::vector &keypoints, int npoints); + }; + +#endif +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpPlaneEstimation.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpPlaneEstimation.h new file mode 100644 index 00000000..fd905171 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpPlaneEstimation.h @@ -0,0 +1,129 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Plane estimation. + */ + +/*! + * \file vpPlaneEstimation.h + * \brief Tools for plane estimation. + */ + +#pragma once + +#include + +// Check if std:c++17 or higher. +// Here we cannot use (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17) in the declaration of the class +#if ((__cplusplus >= 201703L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L))) + +// System +#include +#include + +// Core +#include +#include + +BEGIN_VISP_NAMESPACE + +/*! + * \class vpPlaneEstimation + * \ingroup group_vision_plane + * + * \note This class is only available with c++17 enabled. + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in learning how to use this class, you may have a look at:
+ * + * - \ref tutorial-planar-object-pose +*/ +class VISP_EXPORT vpPlaneEstimation +{ +public: + /*! + * Based on depth, estimate the plane equation of the roi. + * + * \param[in] I_depth_raw : Depth raw value. + * \param[in] depth_scale : Depth scale (used to convert depth value into meters). + * \param[in] depth_intrinsics : Depth camera parameters. + * \param[in] roi : Region of interest. + * \param[in] avg_nb_of_pts_to_estimate : Average number of points to use to estimate the plane (default: 500). + * \param[out] heat_map : Plane estimation heat map (optional). + * \return Plane equation. + */ + static std::optional estimatePlane(const vpImage &I_depth_raw, double depth_scale, + const vpCameraParameters &depth_intrinsics, const vpPolygon &roi, + const unsigned int avg_nb_of_pts_to_estimate = 500, + std::optional > > heat_map = {}); + + /*! + * Based on depth, estimate the plane equation of the roi. + * + * \param[in] I_depth_raw : Depth raw value. + * \param[in] depth_scale : Depth scale (used to convert depth value into meters). + * \param[in] depth_intrinsics : Depth camera parameters. + * \param[in] roi : Region of interest. + * \param[in] mask : OPtional boolean mask that indicates if a point must be considered + * \param[in] avg_nb_of_pts_to_estimate : Average number of points to use to estimate the plane (default: 500). + * \param[out] heat_map : Plane estimation heat map (optional). + * \return Plane equation. + */ + static std::optional estimatePlane(const vpImage &I_depth_raw, double depth_scale, + const vpCameraParameters &depth_intrinsics, const vpPolygon &roi, const std::optional> &mask, + const unsigned int avg_nb_of_pts_to_estimate = 500, + std::optional > > heat_map = {}); + + /*! + * Based on depth, estimate the plane equation of the roi. + * + * \param[in] I_depth : Depth map. + * \param[in] depth_intrinsics : Depth camera parameters. + * \param[in] roi : Region of interest. + * \param[in] mask : OPtional boolean mask that indicates if a point must be considered + * \param[in] avg_nb_of_pts_to_estimate : Average number of points to use to estimate the plane (default: 500). + * \param[out] heat_map : Plane estimation heat map (optional). + * \return Plane equation. + */ + static std::optional estimatePlane(const vpImage &I_depth, + const vpCameraParameters &depth_intrinsics, const vpPolygon &roi, const std::optional> &mask, + const unsigned int &avg_nb_of_pts_to_estimate = 500, + std::optional > > heat_map = {}); + +private: + //! Minimal number of points required to estimate a plane + static constexpr auto MinPointNbToEstimatePlane { 20u }; + //! Maximal subsampling factor applied to the point cloud to estimate a plane + static constexpr auto MaxSubSampFactorToEstimatePlane { 20u }; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpPose.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpPose.h new file mode 100644 index 00000000..fb7bf1f7 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpPose.h @@ -0,0 +1,905 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Pose computation. + */ + +/*! + * \file vpPose.h + * \brief Tools for pose computation (pose from point only). + */ + +#ifndef VP_POSE_H +#define VP_POSE_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +// Check if std:c++17 or higher. +// Here we cannot use (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17) in the declaration of the class +#if ((__cplusplus >= 201703L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L))) +#include +#include +#endif + +#include + +BEGIN_VISP_NAMESPACE + +/*! + * \class vpPose + * \ingroup group_vision_pose + * \brief Class used for pose computation from N points (pose from point only). + * Some of the algorithms implemented in this class are described in + * \cite Marchand16a. + * + * \note It is also possible to estimate a pose from other features using + * vpPoseFeatures class. + * + *

Tutorials & Examples

+ + Tutorials
+ To see how to use this class, you may have a look at:
+ + - \ref tutorial-pose-estimation +*/ +class VISP_EXPORT vpPose +{ +public: + //! Methods that could be used to estimate the pose from points. + typedef enum + { + LAGRANGE, /*!< Linear Lagrange approach (doesn't need an initialization) */ + DEMENTHON, /*!< Linear Dementhon aproach (doesn't need an initialization) */ + LOWE, /*!< Lowe aproach based on a Levenberg Marquartd non linear + minimization scheme that needs an initialization from Lagrange or + Dementhon aproach */ + RANSAC, /*!< Robust Ransac aproach (doesn't need an initialization) */ + LAGRANGE_LOWE, /*!< Non linear Lowe aproach initialized by Lagrange + approach */ + DEMENTHON_LOWE, /*!< Non linear Lowe aproach initialized by Dementhon + approach */ + VIRTUAL_VS, /*!< Non linear virtual visual servoing approach that needs an + initialization from Lagrange or Dementhon aproach */ + DEMENTHON_VIRTUAL_VS, /*!< Non linear virtual visual servoing approach + initialized by Dementhon approach */ + LAGRANGE_VIRTUAL_VS, /*!< Non linear virtual visual servoing approach + initialized by Lagrange approach */ + DEMENTHON_LAGRANGE_VIRTUAL_VS /*!< Non linear virtual visual servoing approach + initialized by either Dementhon or Lagrange approach, + depending on which method has the smallest residual. */ + } vpPoseMethodType; + + /*! + * Filter applied in Ransac + */ + enum RANSAC_FILTER_FLAGS + { + NO_FILTER, //!< No filter is applied + PREFILTER_DEGENERATE_POINTS, /*!< Remove degenerate points (same 3D or 2D coordinates) before the RANSAC. */ + CHECK_DEGENERATE_POINTS /*!< Check for degenerate points during the RANSAC. */ + }; + + unsigned int npt; //!< Number of point used in pose computation + std::list listP; //!< Array of point (use here class vpPoint) + + double residual; //!< Residual in meter + +public: + // Typedef a function that checks if a pose is valid. + typedef bool (*FuncCheckValidityPose)(const vpHomogeneousMatrix &); + + /*! + * Default constructor. + */ + vpPose(); + +#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98) + vpPose(const vpPose &) = default; + vpPose &operator=(const vpPose &) = default; +#endif + + /*! + * Constructor from a vector of points. + */ + VP_EXPLICIT vpPose(const std::vector &lP); + + /*! + * Destructor that deletes the array of point (freed the memory). + */ + virtual ~vpPose(); + + /*! + * Add a new point in the array of points. + * \param P : Point to add in the array of point. + * \warning Considering a point from the class vpPoint, oX, oY, and oZ will + * represent the 3D coordinates of the point in the object frame and x and y + * its 2D coordinates in the image plane. These 5 fields must be initialized + * to be used within this function. + */ + void addPoint(const vpPoint &P); + + /*! + * Add (append) a list of points in the array of points. + * \param lP : List of points to add (append). + * \warning Considering a point from the class vpPoint, oX, oY, and oZ will + * represent the 3D coordinates of the point in the object frame and x and y + * its 2D coordinates in the image plane. These 5 fields must be initialized + * to be used within this function. + */ + void addPoints(const std::vector &lP); + + /*! + * Delete the array of point + */ + void clearPoint(); + + /*! + * Compute the pose according to the desired method which are: + * - vpPose::LAGRANGE: Linear Lagrange approach (test is done to switch between + * planar and non planar algorithm) + * - vpPose::DEMENTHON: Linear Dementhon approach (test is done to switch + * between planar and non planar algorithm) + * - vpPose::LOWE: Lowe aproach based on a Levenberg Marquartd non linear + * minimization scheme that needs an initialization from Lagrange or Dementhon + * aproach + * - vpPose::LAGRANGE_LOWE: Non linear Lowe aproach initialized by Lagrange + * approach + * - vpPose::DEMENTHON_LOWE: Non linear Lowe aproach initialized by Dementhon + * approach + * - vpPose::VIRTUAL_VS: Non linear virtual visual servoing approach that needs + * an initialization from Lagrange or Dementhon aproach + * - vpPose::DEMENTHON_VIRTUAL_VS: Non linear virtual visual servoing approach + * initialized by Dementhon approach + * - vpPose::LAGRANGE_VIRTUAL_VS: Non linear virtual visual servoing approach + * initialized by Lagrange approach + * - vpPose::DEMENTHON_LAGRANGE_VIRTUAL_VS: Non linear virtual visual servoing approach + * initialized by either Dementhon or Lagrange approach, depending on which method + * has the smallest residual. + * - vpPose::RANSAC: Robust Ransac aproach (doesn't need an initialization) + */ + bool computePose(vpPoseMethodType method, vpHomogeneousMatrix &cMo, FuncCheckValidityPose func = nullptr); + + /*! + * @brief Method that first computes the pose \b cMo using the linear approaches of Dementhon and Lagrange + * and then uses the non-linear Virtual Visual Servoing approach to affine the pose which + * had the lowest residual. + * + * @param cMo the pose of the object with regard to the camera. + * @return true the pose computation was successful. + * @return false an error occurred during the pose computation. + */ + bool computePoseDementhonLagrangeVVS(vpHomogeneousMatrix &cMo); + + /*! + * \brief Compute and return the sum of squared residuals expressed in meter^2 for + * the pose matrix \e cMo. + * + * \param cMo : Input pose. The matrix that defines the pose to be tested. + * + * \return The value of the sum of squared residuals in meter^2. + * + * \note There is also the possibility to compute the residual expressed in pixel^2 + * using one of the following methods: + * - vpPose::computeResidual(const vpHomogeneousMatrix &, const vpCameraParameters &) + * - vpPose::computeResidual(const vpHomogeneousMatrix &, const vpCameraParameters &am, vpColVector &) + */ + double computeResidual(const vpHomogeneousMatrix &cMo) const; + + /*! + * \brief Compute and return the sum of squared residuals expressed in pixel^2 for + * the pose matrix \e cMo. + * + * \param cMo : Input pose. The matrix that defines the pose to be tested. + * \param cam : Camera parameters used to observe the points. + * + * \return The value of the sum of squared residuals in pixel^2. + * + * \note There is also the possibility to compute the residual expressed in meter^2 using + * vpPose::computeResidual(const vpHomogeneousMatrix &) + */ + double computeResidual(const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam) const; + + /*! + * \brief Compute and return the sum of squared residuals expressed in pixel^2 for + * the pose matrix \e cMo. + * + * \param cMo : Input pose. The matrix that defines the pose to be tested. + * \param cam : Camera parameters used to observe the points. + * \param squaredResidual : Input/output vector that will be resized and will contain the squared residuals + * expressed in pixel^2 of each point. + * + * \return The value of the sum of squared residuals in pixel^2. + * + * \note There is also the possibility to compute the residual expressed in meter^2 using + * vpPose::computeResidual(const vpHomogeneousMatrix &) + */ + double computeResidual(const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, vpColVector &squaredResidual) const; + + /*! + * Test the coplanarity of the set of points + * + * \param coplanar_plane_type : + * 1: if plane x=cst + * 2: if plane y=cst + * 3: if plane z=cst + * 4: if the points are collinear. + * 0: any other plane + * \param p_a : if different from null, it will be set to equal the a coefficient of the potential plan. + * \param p_b : if different from null, it will be set to equal the b coefficient of the potential plan. + * \param p_c : if different from null, it will be set to equal the c coefficient of the potential plan. + * \param p_d : if different from null, it will be set to equal the d coefficient of the potential plan. + * \return true if points are coplanar false otherwise. + */ + bool coplanar(int &coplanar_plane_type, double *p_a = nullptr, double *p_b = nullptr, double *p_c = nullptr, double *p_d = nullptr); + + /*! + * Display the coordinates of the points in the image plane that are used to + * compute the pose in image I. + */ + void displayModel(vpImage &I, vpCameraParameters &cam, vpColor col = vpColor::none); + + /*! + * Display the coordinates of the points in the image plane that are used to + * compute the pose in image I. + */ + void displayModel(vpImage &I, vpCameraParameters &cam, vpColor col = vpColor::none); + + /*! + * Compute the pose using Dementhon approach for planar objects + * this is a direct implementation of the algorithm proposed by + * Dementhon in his PhD. + */ + void poseDementhonPlan(vpHomogeneousMatrix &cMo); + + /*! + * Compute the pose using Dementhon approach for non planar objects. + * This is a direct implementation of the algorithm proposed by + * Dementhon and Davis in their 1995 paper \cite Dementhon95. + */ + void poseDementhonNonPlan(vpHomogeneousMatrix &cMo); + + /*! + * Compute the pose of a planar object using Lagrange approach. + * + * \param cMo : Estimated pose. No initialisation is requested to estimate cMo. + * \param p_isPlan : if different from nullptr, indicates if the object is planar or not. + * \param p_a : if different from nullptr, the a coefficient of the plan formed by the points. + * \param p_b : if different from nullptr, the b coefficient of the plan formed by the points. + * \param p_c : if different from nullptr, the c coefficient of the plan formed by the points. + * \param p_d : if different from nullptr, the d coefficient of the plan formed by the points. + */ + void poseLagrangePlan(vpHomogeneousMatrix &cMo, bool *p_isPlan = nullptr, double *p_a = nullptr, double *p_b = nullptr, + double *p_c = nullptr, double *p_d = nullptr); + + /*! + * Compute the pose of a non planar object using Lagrange approach. + * + * \param cMo : Estimated pose. No initialisation is requested to estimate cMo. + */ + void poseLagrangeNonPlan(vpHomogeneousMatrix &cMo); + + /*! + * \brief Compute the pose using the Lowe non linear approach + * it consider the minimization of a residual using + * the levenberg marquartd approach. + * + * The approach has been proposed by D.G Lowe in 1992 paper \cite Lowe92a. + */ + void poseLowe(vpHomogeneousMatrix &cMo); + + /*! + * Compute the pose using the Ransac approach. + * + * \param cMo : Computed pose + * \param func : Pointer to a function that takes in parameter a + * vpHomogeneousMatrix and returns true if the pose check is OK or false + * otherwise + * \return True if we found at least 4 points with a reprojection + * error below ransacThreshold. + * \note You can enable a multithreaded version if you have C++11 enabled using setUseParallelRansac(). + * The number of threads used can then be set with setNbParallelRansacThreads(). + * Filter flag can be used with setRansacFilterFlag(). + */ + bool poseRansac(vpHomogeneousMatrix &cMo, FuncCheckValidityPose func = nullptr); + + /*! + * Compute the pose using virtual visual servoing approach and + * a robust control law. + * + * This approach is described in \cite Comport06b. + */ + void poseVirtualVSrobust(vpHomogeneousMatrix &cMo); + + /*! + * Compute the pose using virtual visual servoing approach. + * + * This approach is described in \cite Marchand02c. + */ + void poseVirtualVS(vpHomogeneousMatrix &cMo); + + /*! + * Print to std::cout points used as input. + */ + void printPoint(); + + /*! + * Set singular value threshold in Dementhon pose estimation method. + */ + void setDementhonSvThreshold(const double &svThresh); + + /*! + * Set distance threshold to consider that when a point belongs to a plane. + */ + void setDistToPlaneForCoplanTest(double d); + + /*! + * Set virtual visual servoing gain. + */ + void setLambda(double lambda) { m_lambda = lambda; } + + /*! + * Set virtual visual servoing epsilon value used in the pseudo-inverse. + */ + void setVvsEpsilon(const double eps) + { + if (eps >= 0) { + vvsEpsilon = eps; + } + else { + throw vpException(vpException::badValue, "Epsilon value must be >= 0."); + } + } + + /*! + * Set virtual visual servoing pose estimator maximum number od iterations. + */ + void setVvsIterMax(int nb) { vvsIterMax = nb; } + + /*! + * Set Ransac requested number of inliers to reach consensus. + */ + void setRansacNbInliersToReachConsensus(const unsigned int &nbC) { ransacNbInlierConsensus = nbC; } + + /*! + * Set Ransac threshold. + */ + void setRansacThreshold(const double &t) + { + // Test whether or not t is > 0 + if (t > std::numeric_limits::epsilon()) { + ransacThreshold = t; + } + else { + throw vpException(vpException::badValue, "The Ransac threshold must be positive as we deal with distance."); + } + } + + /*! + * Set Ransac number of trials. + */ + void setRansacMaxTrials(const int &rM) { ransacMaxTrials = rM; } + + /*! + * Get the number of inliers. + */ + unsigned int getRansacNbInliers() const { return static_cast(ransacInliers.size()); } + + /** + * Get the vector of indexes corresponding to inliers. + */ + std::vector getRansacInlierIndex() const { return ransacInlierIndex; } + + /*! + * Get the vector of inliers. + */ + std::vector getRansacInliers() const { return ransacInliers; } + + /*! + * Set if the covariance matrix has to be computed in the Virtual Visual + * Servoing approach. + * + * \param flag : True if the covariance has to be computed, false otherwise. + */ + void setCovarianceComputation(const bool &flag) { computeCovariance = flag; } + + /*! + * Get the covariance matrix computed in the Virtual Visual Servoing + * approach. + * + * \warning The compute covariance flag has to be true if you want to compute + * the covariance matrix. + * + * \sa setCovarianceComputation + */ + vpMatrix getCovarianceMatrix() const + { + if (!computeCovariance) { + std::cout << "Warning: The covariance matrix has not been computed. See setCovarianceComputation() to do it." << std::endl; + } + return covarianceMatrix; + } + + /*! + * Set RANSAC filter flag. + * + * \param flag : RANSAC flag to use to prefilter or perform degenerate configuration check. + * \sa RANSAC_FILTER_FLAGS + * \warning Prefilter degenerate points consists to not add subsequent degenerate points. This means that + * it is possible to discard a valid point and keep an invalid point if the invalid point + * is added first. It is faster to prefilter for duplicate points instead of checking for degenerate + * configuration at each time. + * \note By default the flag is set to NO_FILTER. + */ + inline void setRansacFilterFlag(const RANSAC_FILTER_FLAGS &flag) { ransacFlag = flag; } + + /*! + * Get the number of threads for the parallel RANSAC implementation. + * + * \sa setNbParallelRansacThreads + */ + inline int getNbParallelRansacThreads() const { return nbParallelRansacThreads; } + + /*! + * Set the number of threads for the parallel RANSAC implementation. + * + * \note You have to enable the parallel version with setUseParallelRansac(). + * If the number of threads is 0, the number of threads to use is + * automatically determined with C++11. + * \sa setUseParallelRansac + */ + inline void setNbParallelRansacThreads(int nb) { nbParallelRansacThreads = nb; } + + /*! + * \return True if the parallel RANSAC version should be used (depends also to C++11 availability). + * + * \sa setUseParallelRansac + */ + inline bool getUseParallelRansac() const { return useParallelRansac; } + + /*! + * Set if parallel RANSAC version should be used or not (only if C++11). + * + * \note Need C++11 or higher. + */ + inline void setUseParallelRansac(bool use) { useParallelRansac = use; } + + /*! + * Get the vector of points. + * + * \return The vector of points. + */ + std::vector getPoints() const + { + std::vector vectorOfPoints(listP.begin(), listP.end()); + return vectorOfPoints; + } + + /*! + * Compute the pose of a planar object from corresponding 2D-3D point coordinates and depth map. + * Depth map is here used to estimate the 3D plane of the object. + * + * \param[in] depthMap : Depth map aligned to the color image from where \e corners are extracted. + * \param[in] corners : Vector of 2D pixel coordinates of the object in an image. + * \param[in] colorIntrinsics : Camera parameters used to convert \e corners from pixel to meters. + * \param[in] point3d : Vector of 3D points corresponding to the model of the planar object. + * \param[out] cMo : Computed pose. + * \param[out] confidence_index : Confidence index in range [0, 1]. When values are close to 1, it means + * that pose estimation confidence is high. Values close to 0 indicate that pose is not well estimated. + * This confidence index corresponds to the product between the normalized number of depth data covering the tag + * and the normalized M-estimator weights returned by the robust estimation of the tag 3D plane. + * + * The following code snippet implemented in tutorial-apriltag-detector-live-rgbd-realsense.cpp shows how + * to use this function to estimate the pose of an AprilTag using this method: + * \snippet tutorial-apriltag-detector-live-rgbd-realsense.cpp Pose from depth map + * + * \return true if pose estimation succeed, false otherwise. + */ + static bool computePlanarObjectPoseFromRGBD(const vpImage &depthMap, const std::vector &corners, + const vpCameraParameters &colorIntrinsics, + const std::vector &point3d, vpHomogeneousMatrix &cMo, + double *confidence_index = nullptr); + + /*! + * Compute the pose of multiple planar object from corresponding 2D-3D point coordinates and depth map. + * Depth map is here used to estimate the 3D plane of each planar object. + * + * This implementation is reserved for the case where multiple planar objects are considered and where an + * object like robot arm obstruct the view and interfere with plane equation estimation for each single object. + * Therefore this function considers only the 3D point inside the visible tags. + * + * \param[in] depthMap : Depth map aligned to the color image from where \e corners are extracted. + * + * \param[in] corners : Vector where each element is a vector containing 2D pixel coordinates of the 2D polygon that + * defines the object edges in an image. + * + * \param[in] colorIntrinsics : Camera parameters used to convert \e corners from pixel to + * meters. + * + * \param[in] point3d : Vector where each element is a vector containing 3D points coordinates of the 3D polygon that + * defines the model of the planar object. + * + * \param[out] cMo : Computed pose. + * + * \param[out] confidence_index : Confidence index in range [0, 1]. When values are close to 1, it means that pose + * estimation confidence is high. Values close to 0 indicate that pose is not well estimated. This confidence index + * corresponds to the product between the normalized number of depth data covering the tag and the normalized + * M-estimator weights returned by the robust estimation of the tag 3D plane. + * + * \param[in] coplanar_points : There are cases where all the planar objects are not in the same plane. In order to + * differentiate these cases, this parameter will be used to compute the common plane for all objects if its value is + * true and compute the plane individually for each object otherwise. + * + * \return true if pose estimation succeed, false otherwise. + */ + static bool computePlanarObjectPoseFromRGBD(const vpImage &depthMap, + const std::vector > &corners, + const vpCameraParameters &colorIntrinsics, + const std::vector > &point3d, + vpHomogeneousMatrix &cMo, double *confidence_index = nullptr, + bool coplanar_points = true); + + /*! + * Compute the number of RANSAC iterations to ensure with a probability \e p + * that at least one of the random samples of \e s points is free from + * outliers. + * \note See: Hartley and Zisserman, Multiple View Geometry in + * Computer Vision, p119 (2. How many samples?). + * + * \param probability : Probability that at least one of the random samples is + * free from outliers (typically p=0.99). + * \param epsilon : Probability that a + * selected point is an outlier (between 0 and 1). + * \param sampleSize : Minimum + * number of points to estimate the model (4 for a pose estimation). + * \param maxIterations : Upper bound on the number of iterations or -1 for INT_MAX. + * \return The number of RANSAC iterations to ensure with a probability \e p + * that at least one of the random samples of \e s points is free from outliers + * or \p maxIterations if it exceeds the desired upper bound or \e INT_MAX if + * maxIterations=-1. + */ + static int computeRansacIterations(double probability, double epsilon, const int sampleSize = 4, + int maxIterations = 2000); + + /*! + * Display in the image \e I the pose represented by its homogenous + * transformation \e cMo as a 3 axis frame. + * \param I : Image where the pose is displayed in overlay. + * \param cMo : Considered pose to display. + * \param cam : Camera parameters associated to image \e I. + * \param size : length in meter of the axis that will be displayed. + * \param col : Color used to display the 3 axis. If vpColor::none, red, green and blue will represent x-axis, y-axis + * and z-axis respectively. + */ + static void display(vpImage &I, vpHomogeneousMatrix &cMo, vpCameraParameters &cam, double size, + vpColor col = vpColor::none); + + /*! + * Display in the image \e I the pose represented by its homogenous + * transformation \e cMo as a 3 axis frame. + * \param I : Image where the pose is displayed in overlay. + * \param cMo : Considered pose to display. + * \param cam : Camera parameters associated to image \e I. + * \param size : length in meter of the axis that will be displayed. + * \param col : Color used to display the 3 axis. If vpColor::none, red, green and blue will represent x-axis, y-axis + * and z-axis respectively. + */ + static void display(vpImage &I, vpHomogeneousMatrix &cMo, vpCameraParameters &cam, double size, + vpColor col = vpColor::none); + + /*! + * Match a vector p2D of 2D point (x,y) and a vector p3D of 3D points + * (X,Y,Z) using the Ransac algorithm. + * + * At least numberOfInlierToReachAConsensus of true correspondence are required + * to validate the pose + * + * The inliers are given in a vector of vpPoint listInliers. + * + * The pose is returned in cMo. + * + * \param p2D : Vector of 2d points (x and y attributes are used). + * \param p3D : Vector of 3d points (oX, oY and oZ attributes are used). + * \param numberOfInlierToReachAConsensus : The minimum number of inlier to + * have to consider a trial as correct. + * \param threshold : The maximum error + * allowed between the 2d points and the reprojection of its associated 3d + * points by the current pose (in meter). + * \param ninliers : Number of inliers found for the best solution. + * \param listInliers : Vector of points (2d and + * 3d) that are inliers for the best solution. + * \param cMo : The computed pose (best solution). + * \param maxNbTrials : Maximum number of trials before + * considering a solution fitting the required \e + * numberOfInlierToReachAConsensus and \e threshold cannot be found. + * \param useParallelRansac : If true, use parallel RANSAC version (if C++11 is available). + * \param nthreads : Number of threads to use, if 0 the number of CPU threads will be determined. + * \param func : Pointer to a function that takes in parameter a vpHomogeneousMatrix and returns + * true if the pose check is OK or false otherwise + */ + static void findMatch(std::vector &p2D, std::vector &p3D, + const unsigned int &numberOfInlierToReachAConsensus, const double &threshold, + unsigned int &ninliers, std::vector &listInliers, vpHomogeneousMatrix &cMo, + const int &maxNbTrials = 10000, bool useParallelRansac = true, unsigned int nthreads = 0, + FuncCheckValidityPose func = nullptr); + +#ifdef VISP_HAVE_HOMOGRAPHY + /*! + * Carries out the camera pose of the image of a rectangle and + * the intrinsic parameters. The length on x axis is known but the + * proportion of the rectangle are unknown. + * + * This method is taken from "Markerless Tracking using Planar Structures + * in the Scene" by Gilles Simon. The idea is to compute the homography H + * giving the image point of the rectangle by associating them with the + * coordinates (0,0)(1,0)(1,1/s)(0,1/s) (the rectangle is on the Z=0 plane). + * If K is the intrinsic parameters matrix, we have s = ||Kh1||/ ||Kh2||. s + * gives us the proportion of the rectangle + * + * \param[out] p1 : The image of the first corner ot the rectangle with coordinates (0,0). + * \param[out] p2 : The image of the second corner ot the rectangle with coordinates (lx,0). + * \param[out] p3 : The image of the third corner ot the rectangle with coordinates (lx,lx/s). + * \param[out] p4 : The image of the fourth corner ot the rectangle with coordinates (0,lx/s). + * \param[in] cam : The camera used. + * \param[in] lx : The rectangle size on the x axis. + * \param[out] cMo : The camera pose. + * \return lx/s if no pb occurs + */ + static double poseFromRectangle(vpPoint &p1, vpPoint &p2, vpPoint &p3, vpPoint &p4, double lx, + const vpCameraParameters &cam, vpHomogeneousMatrix &cMo); +#endif + + // Check if std:c++17 or higher. + // Here we cannot use (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17) in the declaration of the class +#if ((__cplusplus >= 201703L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L))) + + /*! + * Compute the pose of a planar object from corresponding 2D-3D point coordinates and plane equation. + * Here at least 3 points are required. + * + * \param[in] plane_in_camera_frame : Plane in camera frame. + * \param[in] pts : Object points. + * \param[in] ips : Points in the image. + * \param[in] camera_intrinsics : Camera parameters. + * \param[in] cMo_init : Camera to object frame transformation used as initialization. When set to `std::nullopt`, + * this transformation is computed internally. + * \param[in] enable_vvs : When true, refine estimated pose using a virtual visual servoing scheme. + * \return Homogeneous matrix \f${^c}{\bf M}_o\f$ between camera frame and object frame when estimation succeed, + * nullopt otherwise. + * + * \sa To see an example, you can have a look at \ref tutorial-planar-object-pose + */ + template + static std::optional computePlanarObjectPoseWithAtLeast3Points( + const vpPlane &plane_in_camera_frame, const std::map &pts, + const std::map &ips, const vpCameraParameters &camera_intrinsics, + std::optional cMo_init = std::nullopt, bool enable_vvs = true) + { + if (cMo_init && (!enable_vvs)) { + throw(vpException( + vpException::fatalError, + "It doesn't make sense to use an initialized pose without enabling VVS to compute the pose from 4 points")); + } + + // Check if detection and model fit + // - The next line produces an internal compiler error with Visual Studio 2017: + // modules\vision\include\visp3/vision/vpPose.h(404): fatal error C1001: An internal error has occurred in the + // compiler. + // To work around this problem, try simplifying or changing the program near the locations listed above. + // Please choose the Technical Support command on the Visual C++ + // Help menu, or open the Technical Support help file for more information + // - Note that the next line builds with Visual Studio 2022. + // for ([[maybe_unused]] const auto &[ip_id, _] : ips) { + for (const auto &[ip_id, ip_unused] : ips) { + (void)ip_unused; + if (pts.find(ip_id) == end(pts)) { + throw(vpException(vpException::fatalError, + "Cannot compute pose with points and image points which do not have the same IDs")); + } + } + + std::vector P {}, Q {}; + // The next line in C++17 produces a build error with Visual Studio 2017, that's why we + // use rather C++11 to loop through std::map + // for (auto [pt_id, pt] : pts) { + for (const auto &pt_map : pts) { + if (ips.find(pt_map.first) != end(ips)) { + double x = 0, y = 0; + vpPoint pt = pt_map.second; + vpPixelMeterConversion::convertPoint(camera_intrinsics, ips.at(pt_map.first), x, y); + const auto Z = plane_in_camera_frame.computeZ(x, y); + + pt.set_x(x); + pt.set_y(y); + pt.set_Z(Z); + + Q.push_back(pt); + P.emplace_back(x * Z, y * Z, Z); + } + } + + if (Q.size() < 3) { + return std::nullopt; + } + + auto cMo = cMo_init.value_or(vpHomogeneousMatrix::compute3d3dTransformation(P, Q)); + if (!cMo.isValid()) { + return std::nullopt; + } + + return enable_vvs ? vpPose::poseVirtualVSWithDepth(Q, cMo).value_or(cMo) : cMo; + } + + /*! + * Compute the pose by virtual visual servoing using x,y and Z point coordinates as visual features. + * We recall that x,y are the coordinates of a point in the image plane which are obtained by perspective + * projection, while Z is the 3D coordinate of the point along the camera frame Z-axis. + * + * \param[in] cMo : Pose initial value used to initialize the non linear pose estimation algorithm. + * \param[in] points : A vector of points with [x,y,Z] values used as visual features. + * \return Estimated pose when the minimization converged, of std::nullopt when it failed. + */ + static std::optional poseVirtualVSWithDepth(const std::vector &points, + const vpHomogeneousMatrix &cMo); +#endif + +protected: + double m_lambda; //!< Parameters use for the virtual visual servoing approach + double m_dementhonSvThresh; //!< SVD threshold use for the pseudo-inverse computation in poseDementhonPlan + + /*! + * Compute and return the residual corresponding to the sum of squared residuals + * in meter^2 for the pose matrix \e cMo. + * + * \param cMo : the matrix that defines the pose to be tested. + * + * \return the value of the sum of squared residuals in meter^2. + */ + double computeResidualDementhon(const vpHomogeneousMatrix &cMo); + + /*! + * Method used in poseDementhonPlan() + * Return 0 if success, -1 if failure. + */ + int calculArbreDementhon(vpMatrix &b, vpColVector &U, vpHomogeneousMatrix &cMo); + +private: + void callLagrangePose(vpHomogeneousMatrix &cMo); + + //! Define the maximum number of iteration in VVS + int vvsIterMax; + //! Variable used in the Dementhon approach + std::vector c3d; + //! Flag used to specify if the covariance matrix has to be computed or not. + bool computeCovariance; + //! Covariance matrix + vpMatrix covarianceMatrix; + //! Found a solution when there are at least a minimum number of points in + //! the consensus set + unsigned int ransacNbInlierConsensus; + //! Maximum number of iterations for the RANSAC + int ransacMaxTrials; + //! List of inlier points + std::vector ransacInliers; + //! List of inlier point indexes (from the input list) + std::vector ransacInlierIndex; + //! RANSAC threshold to consider a sample inlier or not + double ransacThreshold; + //! Minimal distance point to plane to consider if the point belongs or not + //! to the plane + double distToPlaneForCoplanarityTest; + //! RANSAC flag to remove or not degenerate points + RANSAC_FILTER_FLAGS ransacFlag; + //! List of points used for the RANSAC (std::vector is contiguous whereas + //! std::list is a linked list) + std::vector listOfPoints; + //! If true, use a parallel RANSAC implementation + bool useParallelRansac; + //! Number of threads to spawn for the parallel RANSAC implementation + int nbParallelRansacThreads; + //! Stop the optimization loop when the residual change (|r-r_prec|) <= + //! epsilon + double vvsEpsilon; + + /*! + * Class dedicated to parallelize RANSAC. + */ + class vpRansacFunctor + { + public: + /*! + * Constructor. + */ + vpRansacFunctor(const vpHomogeneousMatrix &cMo_, unsigned int ransacNbInlierConsensus_, const int ransacMaxTrials_, + double ransacThreshold_, unsigned int initial_seed_, bool checkDegeneratePoints_, + const std::vector &listOfUniquePoints_, FuncCheckValidityPose func_) + : m_best_consensus(), m_checkDegeneratePoints(checkDegeneratePoints_), m_cMo(cMo_), m_foundSolution(false), + m_func(func_), m_listOfUniquePoints(listOfUniquePoints_), m_nbInliers(0), m_ransacMaxTrials(ransacMaxTrials_), + m_ransacNbInlierConsensus(ransacNbInlierConsensus_), m_ransacThreshold(ransacThreshold_), + m_uniRand(initial_seed_) + { } + + /*! + * Operator() that calls Ransac. + */ + void operator()() { m_foundSolution = poseRansacImpl(); } + + /*! + * Access the return value. + */ + bool getResult() const { return m_foundSolution; } + + /*! + * Get Ransac best consensus. + */ + std::vector getBestConsensus() const { return m_best_consensus; } + + /*! + * Get Ransac estimated pose. + */ + vpHomogeneousMatrix getEstimatedPose() const { return m_cMo; } + + /*! + * Get Ransac number of inliers. + */ + unsigned int getNbInliers() const { return m_nbInliers; } + + private: + std::vector m_best_consensus; //!< Best consensus + bool m_checkDegeneratePoints; //!< Flag to check for degenerate points + vpHomogeneousMatrix m_cMo; //!< Estimated pose + bool m_foundSolution; //!< Solution found + FuncCheckValidityPose m_func; //!< Pointer to ransac function + std::vector m_listOfUniquePoints; //!< List of unique points + unsigned int m_nbInliers; //!< Number of inliers + int m_ransacMaxTrials; //!< Ransac max trial number + unsigned int m_ransacNbInlierConsensus; //!< Number of inliers to check for a consensus + double m_ransacThreshold; //!< Residual threshold + vpUniRand m_uniRand; //!< Uniform random generator + + /*! + * Ransac implementation. + * \return true when a solution is found. + */ + bool poseRansacImpl(); + }; +}; + +END_VISP_NAMESPACE + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpPoseException.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpPoseException.h new file mode 100644 index 00000000..d6b08ca7 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpPoseException.h @@ -0,0 +1,96 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Error that can be emitted by the vpPose class and its derivatives + */ + +#ifndef VP_POSE_EXCEPTION_H +#define VP_POSE_EXCEPTION_H + +#include + +#include +#include + +BEGIN_VISP_NAMESPACE + +/*! + * \class vpPoseException + * \ingroup group_vision_pose + * \brief Error that can be emitted by the vpPose class and its derivatives. +*/ +class VISP_EXPORT vpPoseException : public vpException +{ +public: + /*! + * Lists the possible error than can be emitted while calling + * vpPose member + */ + enum errorCodeEnum + { + //! Generic pose error + poseError, + //! Something is not initialized + notInitializedError, + //! Function not implemented + notImplementedERR, + //! Index out of range + outOfRangeError, + //! Not enough points to compute the pose + notEnoughPointError + }; + +public: + /*! + * Constructor. + */ + vpPoseException(int id, const char *format, ...) + { + this->code = id; + va_list args; + va_start(args, format); + setMessage(format, args); + va_end(args); + } + + /*! + * Constructor. + */ + vpPoseException(int id, const std::string &msg) : vpException(id, msg) { } + + /*! + * Constructor. + */ + VP_EXPLICIT vpPoseException(int id) : vpException(id) { } +}; + +END_VISP_NAMESPACE + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpPoseFeatures.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpPoseFeatures.h new file mode 100644 index 00000000..587e633e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpPoseFeatures.h @@ -0,0 +1,828 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Pose computation from any features. + */ + +/*! + \file vpPose.h + \brief Tools for pose computation from any feature. +*/ + +#ifndef vpPoseFeatures_HH +#define vpPoseFeatures_HH + +#include + +#if defined(VISP_HAVE_MODULE_VISUAL_FEATURES) && (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +BEGIN_VISP_NAMESPACE + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +//################################################# +//## Call a function with a tuple as parameters +//################################################# +template struct vpDesiredFeatureBuilderWithTuple +{ + template + static void buildDesiredFeatureWithTuple(featureType &feature, RetType(*f)(ArgsF...), const std::tuple &t, + Args &&...args) + { + vpDesiredFeatureBuilderWithTuple::buildDesiredFeatureWithTuple(feature, f, t, std::get(t), args...); + } +}; + +template <> struct vpDesiredFeatureBuilderWithTuple<0> +{ + template + static void buildDesiredFeatureWithTuple(featureType & /* feature */, RetType(*f)(ArgsF...), + const std::tuple & /* t */, Args &&...args) + { + f(args...); + } +}; + +template <> struct vpDesiredFeatureBuilderWithTuple<1> +{ + template + static void buildDesiredFeatureWithTuple(featureType &feature, RetType(*f)(ArgsF...), const std::tuple &t, + Args &&...args) + { + vpDesiredFeatureBuilderWithTuple<0>::buildDesiredFeatureWithTuple(feature, f, t, feature, args...); + } +}; + +template +void buildDesiredFeatureWithTuple(featureType &feature, RetType(*f)(ArgsFunc...), std::tuple const &t) +{ + vpDesiredFeatureBuilderWithTuple::buildDesiredFeatureWithTuple(feature, f, t); +} + +//################################################# +//## Call a function with a tuple as parameters +//## Object Mode +//################################################# + +template struct vpDesiredFeatureBuilderObjectWithTuple +{ + template + static void buildDesiredFeatureObjectWithTuple(objType *obj, featureType &feature, RetType(objType:: *f)(ArgsF...), + const std::tuple &t, Args &&...args) + { + vpDesiredFeatureBuilderObjectWithTuple::buildDesiredFeatureObjectWithTuple(obj, feature, f, t, + std::get(t), args...); + } +}; + +template <> struct vpDesiredFeatureBuilderObjectWithTuple<0> +{ + template + static void buildDesiredFeatureObjectWithTuple(objType *obj, featureType & /*feature*/, + RetType(objType:: *f)(ArgsF...), const std::tuple & /* t */, + Args &&...args) + { + (obj->*f)(args...); + } +}; + +template <> struct vpDesiredFeatureBuilderObjectWithTuple<1> +{ + template + static void buildDesiredFeatureObjectWithTuple(objType *obj, featureType &feature, RetType(objType:: *f)(ArgsF...), + const std::tuple &t, Args &&...args) + { + vpDesiredFeatureBuilderObjectWithTuple<0>::buildDesiredFeatureObjectWithTuple(obj, feature, f, t, feature, args...); + } +}; + +template +void buildDesiredFeatureObjectWithTuple(objType *obj, featureType &feature, RetType(objType:: *f)(ArgsFunc...), + std::tuple const &t) +{ + vpDesiredFeatureBuilderObjectWithTuple::buildDesiredFeatureObjectWithTuple(obj, feature, f, t); +} + +//##################################################### +//## Call un function with a tuple as parameters +//## Track all the parameters with the cMo +//## Except the first one (must be de "BasicFeature" +//##################################################### + +template struct vpCurrentFeatureBuilderWithTuple +{ + template + static void buildCurrentFeatureWithTuple(featureType &feature, const vpHomogeneousMatrix &cMo, RetType(*f)(ArgsF...), + std::tuple &t, ArgsDecomposed &&...args) + { + auto proj = std::get(t); + proj.track(cMo); + vpCurrentFeatureBuilderWithTuple::buildCurrentFeatureWithTuple(feature, cMo, f, t, proj, args...); + } +}; + +template <> struct vpCurrentFeatureBuilderWithTuple<0> +{ + template + static void buildCurrentFeatureWithTuple(featureType & /*feature*/, const vpHomogeneousMatrix & /*cMo*/, + RetType(*f)(ArgsF...), std::tuple &, ArgsDecomposed &&...args) + { + f(args...); + } +}; + +template <> struct vpCurrentFeatureBuilderWithTuple<1> +{ + template + static void buildCurrentFeatureWithTuple(featureType &feature, const vpHomogeneousMatrix &cMo, RetType(*f)(ArgsF...), + std::tuple &t, ArgsDecomposed &&...args) + { + vpCurrentFeatureBuilderWithTuple<0>::buildCurrentFeatureWithTuple(feature, cMo, f, t, feature, args...); + } +}; + +template +void buildCurrentFeatureWithTuple(featureType &feature, const vpHomogeneousMatrix &cMo, RetType(*f)(ArgsFunc...), + std::tuple &t) +{ + vpCurrentFeatureBuilderWithTuple::buildCurrentFeatureWithTuple(feature, cMo, f, t); +} + +//##################################################### +//## Call un function with a tuple as parameters +//## Track all the parameters with the cMo +//## Except the first one (must be de "BasicFeature" +//## Object Mode +//##################################################### + +template struct vpCurrentFeatureBuilderObjectWithTuple +{ + template + static void buildCurrentFeatureObjectWithTuple(objType *obj, featureType &feature, const vpHomogeneousMatrix &cMo, + RetType(objType:: *f)(ArgsF...), std::tuple &t, + ArgsDecomposed &&...args) + { + auto proj = std::get(t); + proj.track(cMo); + vpCurrentFeatureBuilderObjectWithTuple::buildCurrentFeatureObjectWithTuple(obj, feature, cMo, f, t, proj, + args...); + } +}; + +template <> struct vpCurrentFeatureBuilderObjectWithTuple<0> +{ + template + static void buildCurrentFeatureObjectWithTuple(objType *obj, featureType & /*feature*/, + const vpHomogeneousMatrix & /*cMo*/, RetType(objType:: *f)(ArgsF...), + std::tuple &, ArgsDecomposed &&...args) + { + (obj->*f)(args...); + } +}; + +template <> struct vpCurrentFeatureBuilderObjectWithTuple<1> +{ + template + static void buildCurrentFeatureObjectWithTuple(objType *obj, featureType &feature, const vpHomogeneousMatrix &cMo, + RetType(objType:: *f)(ArgsF...), std::tuple &t, + ArgsDecomposed &&...args) + { + vpCurrentFeatureBuilderObjectWithTuple<0>::buildCurrentFeatureObjectWithTuple(obj, feature, cMo, f, t, feature, + args...); + } +}; + +template +void buildCurrentFeatureObjectWithTuple(objType *obj, featureType &feature, const vpHomogeneousMatrix &cMo, + RetType(objType:: *f)(ArgsFunc...), std::tuple &t) +{ + vpCurrentFeatureBuilderObjectWithTuple::buildCurrentFeatureObjectWithTuple(obj, feature, cMo, f, + t); +} + +//################################################# +//## Call that will be used in our vpPoseFeatures +//## to store the specific features. +//################################################# +/*! + * \class vpPoseSpecificFeature + * \ingroup group_vision_pose + * \brief Class used to define specific features that could be considered in + * pose estimation from visual features implemented in vpPoseFeatures. + */ +class VISP_EXPORT vpPoseSpecificFeature +{ +public: + vpPoseSpecificFeature() { } + virtual ~vpPoseSpecificFeature() { } + + virtual vpColVector error() = 0; + virtual vpMatrix currentInteraction() = 0; + virtual void createDesired() = 0; + virtual void createCurrent(const vpHomogeneousMatrix &cMo) = 0; +}; + +//################################################# +//## Template for all kind of specific features +//################################################# + +/*! + * \class vpPoseSpecificFeatureTemplate + * \ingroup group_vision_pose + * \brief Template class that allows to estimate a pose from all kind of + * specific features if the compiler support C++ 11. + */ +template +class vpPoseSpecificFeatureTemplate : public vpPoseSpecificFeature +{ +private: + featureType m_desiredFeature; + featureType m_currentFeature; + std::tuple *m_tuple; + RetType(*func_ptr)(Args...); + +public: + vpPoseSpecificFeatureTemplate(RetType(*f_ptr)(Args...), Args &&...args) + { + func_ptr = f_ptr; // std::move(f_ptr); + m_tuple = new std::tuple(args...); + } + virtual ~vpPoseSpecificFeatureTemplate() VP_OVERRIDE + { + delete m_tuple; + } + + virtual void createDesired() VP_OVERRIDE + { + buildDesiredFeatureWithTuple(m_desiredFeature, func_ptr, *m_tuple); + } + + virtual vpColVector error() VP_OVERRIDE + { + // std::cout << "Getting S... : " << std::get<0>(*tuple).get_s() << + // std::endl; + return m_currentFeature.error(m_desiredFeature); + } + + virtual vpMatrix currentInteraction() VP_OVERRIDE + { + return m_currentFeature.interaction(); + } + + virtual void createCurrent(const vpHomogeneousMatrix &cMo) VP_OVERRIDE + { + buildCurrentFeatureWithTuple(m_currentFeature, cMo, func_ptr, *m_tuple); + } +}; + +//################################################# +//## Template for all kind of specific features +//## Object Mode +//################################################# + +/*! + * \class vpPoseSpecificFeatureTemplateObject + * \ingroup group_vision_pose + * \brief Template class that allows to estimate a pose from all kind of + * specific features if the compiler support C++ 11. + */ +template +class vpPoseSpecificFeatureTemplateObject : public vpPoseSpecificFeature +{ +private: + featureType m_desiredFeature; + featureType m_currentFeature; + std::tuple *m_tuple; + RetType(ObjectType:: *func_ptr)(Args...); + ObjectType *m_obj; + +public: + vpPoseSpecificFeatureTemplateObject(ObjectType *o, RetType(ObjectType:: *f_ptr)(Args...), Args &&...args) + { + func_ptr = f_ptr; // std::move(f_ptr); + m_tuple = new std::tuple(args...); + m_obj = o; + } + + virtual ~vpPoseSpecificFeatureTemplateObject() VP_OVERRIDE + { + delete m_tuple; + } + + virtual void createDesired() VP_OVERRIDE + { + buildDesiredFeatureObjectWithTuple(m_obj, m_desiredFeature, func_ptr, *m_tuple); + } + + virtual vpColVector error() VP_OVERRIDE + { + return m_currentFeature.error(m_desiredFeature); + } + + virtual vpMatrix currentInteraction() VP_OVERRIDE + { + return m_currentFeature.interaction(); + } + + virtual void createCurrent(const vpHomogeneousMatrix &cMo) VP_OVERRIDE + { + buildCurrentFeatureObjectWithTuple(m_obj, m_currentFeature, cMo, func_ptr, *m_tuple); + } +}; +#endif // #ifndef DOXYGEN_SHOULD_SKIP_THIS + +/*! + * \class vpPoseFeatures + * \brief Tools for pose computation from any feature. + * \ingroup group_vision_pose + * + * This class allows to estimate a pose by virtual visual servoing from visual + * features. The features that are considered are points, segments, lines, + * ellipses. If the compiler is compatible with C++ 11, it is possible to + * introduce specific features that are not directly implemented in ViSP. + */ +class VISP_EXPORT vpPoseFeatures +{ +public: + /*! + * Method that will be used to estimate the pose from visual features. + */ + typedef enum + { + VIRTUAL_VS, /*!< Virtual visual servoing approach. */ + ROBUST_VIRTUAL_VS /*!< Robust virtual visual servoing approach. */ + } vpPoseFeaturesMethodType; + + /*! + * Default constructor. + */ + vpPoseFeatures(); + + /*! + * Destructor that deletes the array of features and projections. + */ + virtual ~vpPoseFeatures(); + +#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98) + vpPoseFeatures(const vpPoseFeatures &) = default; + vpPoseFeatures &operator=(const vpPoseFeatures &) = default; +#endif + + /*! + * Add a point feature to the list of features to be considered in the pose + * computation. + * + * \param[in] p : Point projection expressed as a vpPoint. + */ + void addFeaturePoint(const vpPoint &p); + + /*! + * Add a point 3D feature to the list of features to be considered in the pose + * computation. + * + * \param[in] p : Projection expressed as a vpPoint. + */ + void addFeaturePoint3D(const vpPoint &p); + + /*! + * Add a vanishing point feature to the list of features to be considered in + * the pose computation. + * + * \param[in] p : Projection expressed as a vpPoint. + */ + void addFeatureVanishingPoint(const vpPoint &p); + + /*! + * Add a vanishing point feature to the list of features to be considered in + * the pose computation. + * + * \param l1 : First line used to create the feature. + * \param l2 : Second line used to create the feature. + */ + void addFeatureVanishingPoint(const vpLine &l1, const vpLine &l2); + + /*! + * Add an ellipse feature to the list of features to be considered in the pose + * computation. + * + * \param[in] c : Ellipse projection expressed as a vpCircle. + */ + void addFeatureEllipse(const vpCircle &c); + + /*! + * Add an ellipse feature to the list of features to be considered in the pose + * computation. + * + * \param[in] s : Ellipse projection expressed as a vpSphere. + */ + void addFeatureEllipse(const vpSphere &s); + + /*! + * Add a line feature to the list of features to be considered in the pose + * computation. + * + * \param[in] l : Line projection expressed as a vpLine. + */ + void addFeatureLine(const vpLine &l); + + /*! + * Add a line feature to the list of features to be considered in the pose + * computation. + * + * \param[in] c : Line projection expressed as a vpCylinder. + * \param[in] line : Integer id that indicates which limb of the cylinder is to + * consider. It can be vpCylinder::line1 or vpCylinder::line2. + */ + void addFeatureLine(const vpCylinder &c, const int &line); + + /*! + * Add a segment feature to the list of features to be considered in the pose + * computation. + * + * \param P1 : First extremity projection. + * \param P2 : Second extremity projection. + */ + void addFeatureSegment(vpPoint &P1, vpPoint &P2); + + /*! + * Add a specific feature for the pose computation. + */ + template + void addSpecificFeature(RetType(*fct_ptr)(ArgsFunc...), Args &&...args); + + /*! + * Add a specific feature for the pose computation. + */ + template + void addSpecificFeature(ObjType *obj, RetType(ObjType:: *fct_ptr)(ArgsFunc...), Args &&...args); + + /*! + * Clear all the features + */ + void clear(); + + /*! + * Compute the pose according to the desired method (virtual visual servoing, + * or robust virtual visual servoing approach). + * + * \param cMo : Computed pose. + * + * \param type : Method to use for the pose computation. + * + * - The virtual visual servoing approach is described in \cite Marchand02c. + * + * - The robust virtual visual servoing approach is described in + * \cite Comport06b. + */ + void computePose(vpHomogeneousMatrix &cMo, const vpPoseFeaturesMethodType &type = VIRTUAL_VS); + + /*! + * Get the covariance matrix of the pose parameters computed by virtual + * visual servoing. + * + * \warning By default, the covariance matrix is not computed. To enable the + * computation, use setCovarianceComputation(). + */ + vpMatrix getCovarianceMatrix() const + { + if (!m_computeCovariance) + vpTRACE("Warning : The covariance matrix has not been computed. See " + "setCovarianceComputation() to do it."); + + return m_covarianceMatrix; + } + + /*! + * Get the gain that is used to compute the pose with the control law \f${\bf + * v} = -\lambda {\bf L}^+ ({\bf s} - {\bf s}^*)\f$. + * + * \return Value of \f$\lambda\f$, the gain of the control law. + */ + double getLambda() { return m_lambda; } + + /*! + * Get the maximum number of iterations of the virtual visual servoing (VVS) + * scheme implemented in computePose(). + * + * \return Maximum number of iterations used during VVS minimization. + */ + unsigned int getVVSIterMax() { return m_vvsIterMax; } + + /*! + * Enable or disable covariance computation of the pose parameters. + * + * \param flag : True if the covariance has to be computed, false otherwise. + */ + void setCovarianceComputation(const bool &flag) { m_computeCovariance = flag; } + + /*! + * Set the gain used in the virtual visual servoing scheme : \f${\bf v} = + * -\lambda {\bf L}^+ ({\bf s} - {\bf s}^*)\f$. + * + * \param val : Value of the gain \f$\lambda\f$. + */ + void setLambda(const double &val) { m_lambda = val; } + + /*! + * Set the maximum number of iterations used in computePose(). + * + * \param val : Maximum number of iteration used in the VVS scheme. + */ + void setVVSIterMax(const unsigned int &val) { m_vvsIterMax = val; } + + /*! + * Turn the verbose mode ON / OFF. + * + * \param mode : new verbose state. True to turn ON, false otherwise. + */ + void setVerbose(const bool &mode) { m_verbose = mode; } + +private: +#ifndef DOXYGEN_SHOULD_SKIP_THIS + template struct vpDuo + { + FeatureType *desiredFeature; + FirstParamType firstParam; + vpDuo() : desiredFeature(nullptr), firstParam() { } + }; + + template struct vpTrio + { + FeatureType *desiredFeature; + FirstParamType firstParam; + SecondParamType secondParam; + + vpTrio() : desiredFeature(nullptr), firstParam(), secondParam() { } + }; +#endif //#ifndef DOXYGEN_SHOULD_SKIP_THIS + + unsigned int m_maxSize; + unsigned int m_totalSize; + unsigned int m_vvsIterMax; + double m_lambda; + + bool m_verbose; + + bool m_computeCovariance; + vpMatrix m_covarianceMatrix; + + // vpFeaturePoint + std::vector > m_featurePoint_Point_list; + // vpFeaturePoint3D + std::vector > m_featurePoint3D_Point_list; + // vpFeatureVanishingPoint + std::vector > m_featureVanishingPoint_Point_list; + std::vector > m_featureVanishingPoint_DuoLine_list; + // vpFeatureEllipse + std::vector > m_featureEllipse_Sphere_list; + std::vector > m_featureEllipse_Circle_list; + // vpFeatureLine + std::vector > m_featureLine_Line_list; + std::vector > m_featureLine_DuoLineInt_List; + // vpFeatureSegment + std::vector > m_featureSegment_DuoPoints_list; + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + // Specific features + std::vector m_featureSpecific_list; +#endif + +/*! + * Get the error vector and L matrix from all the features. + * + * \param cMo : Current Pose. + * \param err : Resulting error vector. + * \param L : Resulting interaction matrix. + */ + void error_and_interaction(vpHomogeneousMatrix &cMo, vpColVector &err, vpMatrix &L); + + /*! + * Compute the pose thanks to the virtual visual servoing approach. + * + * This approach is described in \cite Marchand02c. + * + * \param cMo : Computed pose. + */ + void computePoseVVS(vpHomogeneousMatrix &cMo); + + /*! + * Compute the pose thanks to the robust virtual visual servoing approach + * described in \cite Comport06b. + * + * \param cMo : Computed pose. + */ + void computePoseRobustVVS(vpHomogeneousMatrix &cMo); +}; + +/*! + * Add a specific feature for the pose computation. + * + * \param fct_ptr : pointer on the function used to create the feature. + * \param args : List of function parameters; + * First argument supposed to be derived from vpBasicFeature + * (redefine interaction() and error() functions), others are supposed to be + * derived from vpForwardProjection (redefine track() function) + * + * \warning This function is only available with C++11. It has to be activated + * with USE_CPP11 option from CMake. + * + * \code + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * void vp_createPoint(vpFeaturePoint &fp,const vpPoint &p) { + * vpFeatureBuilder::create(fp,p); + * } + * + * void vp_createTwoPoint(vpFeaturePoint &fp,const vpPoint &p, const vpPoint&p2) { + * vpFeatureBuilder::create(fp,p); + * vpFeatureBuilder::create(fp,p2); + * } + * + * void vp_createLine(vpFeatureLine &fp,const vpLine &l) { + * vpFeatureBuilder::create(fp,l); + * } + * + * int main() + * { + * vpPoseFeatures pose; + * + * vpPoint pts[4]; + * vpLine line; + * + * //... Projection of the points and line + * + * vpFeaturePoint fp; + * vpFeatureLine fl; + * void (*ptr)(vpFeaturePoint&, const vpPoint&) = &vpFeatureBuilder::create; + * + * pose.addSpecificFeature(ptr, fp, pts[0]); + * pose.addSpecificFeature(&vp_createPoint, fp, pts[1]); + * pose.addSpecificFeature(&vp_createTwoPoint, fp, pts[2], pts[3]); + * pose.addSpecificFeature(&vp_createLine, fl, line); + * + * //... Pose Computation + * + * return 0; + * } + * \endcode + */ +template +void vpPoseFeatures::addSpecificFeature(RetType(*fct_ptr)(ArgsFunc...), Args &&...args) +{ + typedef typename std::tuple_element<0, std::tuple >::type featureTypeReference; + typedef typename std::remove_reference::type featureType; + m_featureSpecific_list.push_back( + new vpPoseSpecificFeatureTemplate(fct_ptr, std::forward(args)...)); + + m_featureSpecific_list.back()->createDesired(); + + m_totalSize++; + if (m_featureSpecific_list.size() > m_maxSize) + m_maxSize = static_cast(m_featureSpecific_list.size()); +} + +/*! + * Add a specific feature for the pose computation. + * + * \param obj : object used to call the function defined by fct_ptr. + * \param fct_ptr : pointer on the function used to create the feature. + * \param args : List of function parameters; + * First argument supposed to be derived from vpBasicFeature + * (redefine interaction() and error() functions), others are supposed to be + * derived from vpForwardProjection (redefine track() function) + * + * \warning This function is only available with C++11. It has to be activated + * with USE_CPP11 option from CMake. + * + * \code + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * class vp_createClass{ + * public: + * vp_createClass(){} + * + * int vp_createPoint(vpFeaturePoint &fp,const vpPoint &p) { + * vpFeatureBuilder::create(fp,p); + * return 2; + * } + * + * void vp_createTwoPoint(vpFeaturePoint &fp,const vpPoint &p, const vpPoint &p2) { + * vpFeatureBuilder::create(fp,p); vpFeatureBuilder::create(fp,p2); + * } + * + * void vp_createLine(vpFeatureLine &fp,const vpLine &l) { + * vpFeatureBuilder::create(fp,l); + * } + * }; + * + * int main() + * { + * vpPoseFeatures pose; + * + * vpPoint pts[3]; + * vpLine line; + * + * //... Projection of the points and line + * + * vpFeaturePoint fp; + * vpFeatureLine fl; + * + * vp_createClass cpClass; + * int (vp_createClass::*ptrClassPoint)(vpFeaturePoint&, const vpPoint&) + * = &vp_createClass::vp_createPoint; + * void (vp_createClass::*ptrClassTwoPoint)(vpFeaturePoint&, const vpPoint&, const vpPoint&) + * = &vp_createClass::vp_createTwoPoint; + * void (vp_createClass::*ptrClassLine)(vpFeatureLine &, const vpLine &) + * = &vp_createClass::vp_createLine; + * + * pose.addSpecificFeature(&cpClass, ptrClassPoint, fp, pts[0]); + * pose.addSpecificFeature(&cpClass, ptrClassTwoPoint, fp, pts[1], pts[2]); + * pose.addSpecificFeature(&cpClass, ptrClassLine, fl, line); + * + * //... Pose Computation + * + * return 0; + * } + * \endcode + */ +template +void vpPoseFeatures::addSpecificFeature(ObjType *obj, RetType(ObjType:: *fct_ptr)(ArgsFunc...), Args &&...args) +{ + typedef typename std::tuple_element<0, std::tuple >::type featureTypeReference; + typedef typename std::remove_reference::type featureType; + m_featureSpecific_list.push_back(new vpPoseSpecificFeatureTemplateObject( + obj, fct_ptr, std::forward(args)...)); + + m_featureSpecific_list.back()->createDesired(); + + m_totalSize++; + if (m_featureSpecific_list.size() > m_maxSize) + m_maxSize = static_cast(m_featureSpecific_list.size()); +} + +END_VISP_NAMESPACE +#endif + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpXmlConfigParserKeyPoint.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpXmlConfigParserKeyPoint.h new file mode 100644 index 00000000..c1e60c99 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/vision/vpXmlConfigParserKeyPoint.h @@ -0,0 +1,204 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * XML parser to load configuration for vpKeyPoint class. + */ + +/*! + \file vpXmlConfigParserKeyPoint.cpp + \brief Definition of the vpXmlConfigParserKeyPoint class member functions. + Class vpXmlConfigParserKeyPoint allows to load configuration defined in a + XML file for vpKeyPoint class. + +*/ + +#ifndef _vpXmlConfigParserKeyPoint_h_ +#define _vpXmlConfigParserKeyPoint_h_ + +#include + +#if defined(VISP_HAVE_PUGIXML) +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpXmlConfigParserKeyPoint + * \ingroup group_vision_keypoints + * + * Class vpXmlConfigParserKeyPoint allows to load configuration defined in a + * XML file for vpKeyPoint class. + * + * \warning This class is only available if pugixml is successfully built. +*/ +class VISP_EXPORT vpXmlConfigParserKeyPoint +{ +public: + /*! Enumerator for the different filtering matching method. */ + enum vpMatchingMethodEnum + { + constantFactorDistanceThreshold, /*!< Keep all the points below a constant + factor threshold. */ + stdDistanceThreshold, /*!< Keep all the points below a minimal distance + + the standard deviation. */ + ratioDistanceThreshold, /*!< Keep all the points enough discriminated. */ + stdAndRatioDistanceThreshold, /*!< Keep all the points which fall with the + two conditions. */ + noFilterMatching /*!< No filtering. */ + }; + + /*! + * Default constructor. + */ + vpXmlConfigParserKeyPoint(); + + /*! + * Default destructor. + */ + ~vpXmlConfigParserKeyPoint(); + + /*! + * Get the detector name. + * + * \return The detector name. + */ + std::string getDetectorName() const; + + /*! + * Get the extractor name. + * + * \return The extractor name. + */ + std::string getExtractorName() const; + + /*! + * Get the matcher name. + * + * \return The detector name. + */ + std::string getMatcherName() const; + + /*! + * Get the factor value. + * + * \return The factor value for the filtering method: constantFactorDistanceThreshold. + */ + double getMatchingFactorThreshold() const; + + /*! + * Get the filtering method. + * + * \return The filtering method. + */ + vpMatchingMethodEnum getMatchingMethod() const; + + /*! + * Get the ratio value. + * + * \return The factor value for the filtering method: ratioDistanceThreshold. + */ + double getMatchingRatioThreshold() const; + + /*! + * Get the maximum number of iterations for the Ransac method. + * + * \return The maximum number of iterations for the Ransac method. + */ + int getNbRansacIterations() const; + + /*! + * Get the minimum number of inliers for the Ransac method. + * + * \return The minimum number of inliers for the Ransac method. + */ + int getNbRansacMinInlierCount() const; + + /*! + * Get the percentage value of inliers for the Ransac method. + * + * \return The percentage value of inliers for the Ransac method. + */ + double getRansacConsensusPercentage() const; + + /*! + * Get the maximum reprojection error for a candidate inlier for the Ransac + * method. + * + * \return The maximum reprojection error for the Ransac method. + */ + double getRansacReprojectionError() const; + + /*! + * Get the maximum error for a candidate inlier for the Ransac method. + * + * \return The maximum error for the Ransac method. + */ + double getRansacThreshold() const; + + /*! + * Get the flag state to choose between a percentage of inliers or a fixed + * number. + * + * \return True to use a percentage value for inliers, false otherwise. + */ + bool getUseRansacConsensusPercentage() const; + + /*! + * Get the flag state to choose between OpenCV Ransac pose estimation or ViSP + * Ransac VVS pose estimation. + * + * \return True to use ViSP method, false otherwise. + */ + bool getUseRansacVVSPoseEstimation() const; + + /*! + * Parse an XML file to load configuration for vpKeyPoint class. + * \param filename : filename of the XML file to parse. + */ + void parse(const std::string &filename); + +private: + /*! + * Non copyable constructor. + */ + vpXmlConfigParserKeyPoint(const vpXmlConfigParserKeyPoint &); + + /*! + * Non copyable operator. + */ + vpXmlConfigParserKeyPoint &operator=(const vpXmlConfigParserKeyPoint &); + + //! PIMPL idiom + class Impl; + //! Pointer to implementation + Impl *m_impl; +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visp.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp.h new file mode 100644 index 00000000..f3eb272b --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp.h @@ -0,0 +1,96 @@ +/* + * ** File generated automatically, do not modify ** + * + * This file includes all the headers that are available in ViSP. + * + */ + +#ifndef _visp_h_ +#define _visp_h_ + +// File that defines which modules where included during ViSP build +// It contains the defines of the correct VISP_HAVE_MODULE_ values +#include + +// Then the list of defines is checked to include the correct headers +#ifdef VISP_HAVE_MODULE_AR +# include +#endif + +#ifdef VISP_HAVE_MODULE_BLOB +# include +#endif + +// Core library is always included; without no ViSP functionality available +#include + +#ifdef VISP_HAVE_MODULE_DETECTION +# include +#endif + +#ifdef VISP_HAVE_MODULE_DNN_TRACKER +# include +#endif + +#ifdef VISP_HAVE_MODULE_GUI +# include +#endif + +#ifdef VISP_HAVE_MODULE_IMGPROC +# include +#endif + +#ifdef VISP_HAVE_MODULE_IO +# include +#endif + +#ifdef VISP_HAVE_MODULE_JAVA +# include +#endif + +#ifdef VISP_HAVE_MODULE_JAVA_BINDINGS_GENERATOR +# include +#endif + +#ifdef VISP_HAVE_MODULE_KLT +# include +#endif + +#ifdef VISP_HAVE_MODULE_MBT +# include +#endif + +#ifdef VISP_HAVE_MODULE_ME +# include +#endif + +#ifdef VISP_HAVE_MODULE_ROBOT +# include +#endif + +#ifdef VISP_HAVE_MODULE_SENSOR +# include +#endif + +#ifdef VISP_HAVE_MODULE_TT +# include +#endif + +#ifdef VISP_HAVE_MODULE_TT_MI +# include +#endif + +#ifdef VISP_HAVE_MODULE_VISION +# include +#endif + +#ifdef VISP_HAVE_MODULE_VISUAL_FEATURES +# include +#endif + +#ifdef VISP_HAVE_MODULE_VS +# include +#endif + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_ar.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_ar.h new file mode 100644 index 00000000..2f3932a8 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_ar.h @@ -0,0 +1,26 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __visp_ar_h_ +#define __visp_ar_h_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_blob.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_blob.h new file mode 100644 index 00000000..4ad79eb9 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_blob.h @@ -0,0 +1,14 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __visp_blob_h_ +#define __visp_blob_h_ + +#include +#include +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_core.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_core.h new file mode 100644 index 00000000..79dfeb7e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_core.h @@ -0,0 +1,146 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __visp_core_h_ +#define __visp_core_h_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_detection.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_detection.h new file mode 100644 index 00000000..b3828aa1 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_detection.h @@ -0,0 +1,19 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __visp_detection_h_ +#define __visp_detection_h_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_dnn_tracker.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_dnn_tracker.h new file mode 100644 index 00000000..138cfb63 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_dnn_tracker.h @@ -0,0 +1,14 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __visp_dnn_tracker_h_ +#define __visp_dnn_tracker_h_ + +#include +#include +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_gui.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_gui.h new file mode 100644 index 00000000..2df540c0 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_gui.h @@ -0,0 +1,31 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __visp_gui_h_ +#define __visp_gui_h_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_imgproc.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_imgproc.h new file mode 100644 index 00000000..634845a4 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_imgproc.h @@ -0,0 +1,15 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __visp_imgproc_h_ +#define __visp_imgproc_h_ + +#include +#include +#include +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_io.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_io.h new file mode 100644 index 00000000..21a24c60 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_io.h @@ -0,0 +1,23 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __visp_io_h_ +#define __visp_io_h_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_klt.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_klt.h new file mode 100644 index 00000000..95bfb432 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_klt.h @@ -0,0 +1,13 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __visp_klt_h_ +#define __visp_klt_h_ + +#include +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_mbt.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_mbt.h new file mode 100644 index 00000000..9e5eda8a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_mbt.h @@ -0,0 +1,36 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __visp_mbt_h_ +#define __visp_mbt_h_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_me.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_me.h new file mode 100644 index 00000000..de21f039 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_me.h @@ -0,0 +1,19 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __visp_me_h_ +#define __visp_me_h_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_modules.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_modules.h new file mode 100644 index 00000000..2e9fd4bc --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_modules.h @@ -0,0 +1,31 @@ +/* + * ** File generated automatically, do not modify ** + * + * This file defines the list of modules available in current build configuration + * + */ + +#ifndef _visp_modules_h_ +#define _visp_modules_h_ + +#define VISP_HAVE_MODULE_AR +#define VISP_HAVE_MODULE_BLOB +#define VISP_HAVE_MODULE_CORE +#define VISP_HAVE_MODULE_DETECTION +#define VISP_HAVE_MODULE_DNN_TRACKER +#define VISP_HAVE_MODULE_GUI +#define VISP_HAVE_MODULE_IMGPROC +#define VISP_HAVE_MODULE_IO +#define VISP_HAVE_MODULE_KLT +#define VISP_HAVE_MODULE_MBT +#define VISP_HAVE_MODULE_ME +#define VISP_HAVE_MODULE_ROBOT +#define VISP_HAVE_MODULE_SENSOR +#define VISP_HAVE_MODULE_TT +#define VISP_HAVE_MODULE_TT_MI +#define VISP_HAVE_MODULE_VISION +#define VISP_HAVE_MODULE_VISUAL_FEATURES +#define VISP_HAVE_MODULE_VS + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_robot.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_robot.h new file mode 100644 index 00000000..c7f310b1 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_robot.h @@ -0,0 +1,54 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __visp_robot_h_ +#define __visp_robot_h_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_sensor.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_sensor.h new file mode 100644 index 00000000..634be389 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_sensor.h @@ -0,0 +1,37 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __visp_sensor_h_ +#define __visp_sensor_h_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_tt.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_tt.h new file mode 100644 index 00000000..c80b4ed6 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_tt.h @@ -0,0 +1,33 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __visp_tt_h_ +#define __visp_tt_h_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_tt_mi.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_tt_mi.h new file mode 100644 index 00000000..e3ee7a49 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_tt_mi.h @@ -0,0 +1,18 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __visp_tt_mi_h_ +#define __visp_tt_mi_h_ + +#include +#include +#include +#include +#include +#include +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_vision.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_vision.h new file mode 100644 index 00000000..0378c715 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_vision.h @@ -0,0 +1,23 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __visp_vision_h_ +#define __visp_vision_h_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_visual_features.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_visual_features.h new file mode 100644 index 00000000..3930c0d6 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_visual_features.h @@ -0,0 +1,39 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __visp_visual_features_h_ +#define __visp_visual_features_h_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_vs.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_vs.h new file mode 100644 index 00000000..f27b5ef1 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visp_vs.h @@ -0,0 +1,18 @@ +/* + * ** File generated automatically, do not modify ** + * +*/ + +#ifndef __visp_vs_h_ +#define __visp_vs_h_ + +#include +#include +#include +#include +#include +#include +#include + +#endif + diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpBasicFeature.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpBasicFeature.h new file mode 100644 index 00000000..08c4814f --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpBasicFeature.h @@ -0,0 +1,149 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Visual feature. + * + * Authors: + * Nicolas Mansard + */ + +/*! + \file vpBasicFeature.h + \brief class that defines what is a visual feature +*/ + +#ifndef vpBasicFeature_H +#define vpBasicFeature_H + +#include +#include +#include + +// Display Issue + +// Meter/pixel conversion +#include + +// Color / image / display +#include +#include +#include + +// #define FEATURE_ALL 0xff + +// #define FEATURE_LINE1 0x1 +// #define FEATURE_LINE2 0x2 +// #define FEATURE_LINE3 0x4 +// #define FEATURE_LINE4 0x8 +// #define FEATURE_LINE5 0x10 +// #define FEATURE_LINE6 0x20 +// #define FEATURE_LINE7 0x40 +// #define FEATURE_LINE8 0x80 + +BEGIN_VISP_NAMESPACE +/*! + * \class vpBasicFeature + * \ingroup group_visual_features + * \brief class that defines what is a visual feature +*/ +class VISP_EXPORT vpBasicFeature +{ +public: + static const unsigned int FEATURE_LINE[32]; + + enum vpBasicFeatureSelect { FEATURE_ALL = 0xffff }; + /*! + * \enum vpBasicFeatureDeallocatorType + * Indicates who should deallocate the feature. + */ + typedef enum { user, vpServo } vpBasicFeatureDeallocatorType; + +protected: + //! State of the visual feature. + vpColVector s; + //! Dimension of the visual feature. + unsigned int dim_s; + // int featureLine[8] ; + //! Ensure that all the parameters needed to compute the interaction matrix + //! are set. + bool *flags; + //! Number of parameters needed to compute the interaction matrix. + unsigned int nbParameters; + +public: + vpBasicFeature(); + vpBasicFeature(const vpBasicFeature &f); + virtual ~vpBasicFeature(); + + /** @name Inherited functionalities from vpBasicFeature */ + //@{ + /*! Return the dimension of the feature vector \f$\bf s\f$. */ + unsigned int dimension_s() { return dim_s; } + + virtual void display(const vpCameraParameters &cam, const vpImage &I, + const vpColor &color = vpColor::green, unsigned int thickness = 1) const = 0; + virtual void display(const vpCameraParameters &cam, const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1) const = 0; + + virtual void init() = 0; + + virtual vpColVector error(const vpBasicFeature &s_star, unsigned int select = FEATURE_ALL); + + // Get the feature vector. + vpColVector get_s(unsigned int select = FEATURE_ALL) const; + vpBasicFeatureDeallocatorType getDeallocate() { return deallocate; } + + // Get the feature vector dimension. + unsigned int getDimension(unsigned int select = FEATURE_ALL) const; + //! Compute the interaction matrix from a subset of the possible features. + virtual vpMatrix interaction(unsigned int select = FEATURE_ALL) = 0; + //! Return element \e i in the state vector (usage : x = s[i] ) + virtual inline double operator[](unsigned int i) const { return s[i]; } + vpBasicFeature &operator=(const vpBasicFeature &f); + //! Print the name of the feature. + virtual void print(unsigned int select = FEATURE_ALL) const = 0; + + virtual vpBasicFeature *duplicate() const = 0; + + void setDeallocate(vpBasicFeatureDeallocatorType d) { deallocate = d; } + void setFlags(); + //@} + + //! Select all the features. + static unsigned int selectAll() { return FEATURE_ALL; } + +protected: + void resetFlags(); + +protected: + vpBasicFeatureDeallocatorType deallocate; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureBuilder.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureBuilder.h new file mode 100644 index 00000000..385b71cd --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureBuilder.h @@ -0,0 +1,166 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Conversion between tracker and visual feature. + */ + +/*! + \file vpFeatureBuilder.h + \brief class that defines conversion between tracker and visual feature +*/ + +#ifndef vpFeatureBuilder_H +#define vpFeatureBuilder_H + + +#include + +// tracker +#ifdef VISP_HAVE_MODULE_BLOB +#include +#include +#endif + +#ifdef VISP_HAVE_MODULE_ME +#include +#include +#endif + +// forward projection tracker +#include +#include +#include +#include +#include + +// visual feature +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// others +#include + +// pixel / meter conversion +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + \class vpFeatureBuilder + + \ingroup group_visual_features_builder + \brief Class that defines conversion between trackers and visual features. + +

Tutorials & Examples

+ + Tutorials
+ If you are interested in how to use this class, you may have a look at:
+ + - \ref tutorial-ibvs + - \ref tutorial-simu-robot-pioneer + - \ref tutorial-franka-ibvs-eth +*/ +class VISP_EXPORT vpFeatureBuilder +{ +public: +// create vpFeaturePoint feature +#ifdef VISP_HAVE_MODULE_BLOB + static void create(vpFeaturePoint &s, const vpCameraParameters &cam, const vpDot &d); + static void create(vpFeaturePoint &s, const vpCameraParameters &cam, const vpDot2 &d); +#endif + static void create(vpFeaturePoint &s, const vpCameraParameters &cam, const vpImagePoint &t); + static void create(vpFeaturePoint &s, const vpPoint &p); + static void create(vpFeaturePoint &s, const vpCameraParameters &goodCam, const vpCameraParameters &wrongCam, + const vpPoint &p); + +#ifdef VISP_HAVE_MODULE_BLOB + static void create(vpFeatureSegment &s, const vpCameraParameters &cam, const vpDot &d1, const vpDot &d2); + static void create(vpFeatureSegment &s, const vpCameraParameters &cam, const vpDot2 &d1, const vpDot2 &d2); +#endif + static void create(vpFeatureSegment &s, const vpCameraParameters &cam, const vpImagePoint &ip1, + const vpImagePoint &ip2); + static void create(vpFeatureSegment &s, vpPoint &P1, vpPoint &P2); + +// create vpFeaturePointPolar feature +#ifdef VISP_HAVE_MODULE_BLOB + static void create(vpFeaturePointPolar &s, const vpCameraParameters &cam, const vpDot &dot); + static void create(vpFeaturePointPolar &s, const vpCameraParameters &cam, const vpDot2 &dot); +#endif + static void create(vpFeaturePointPolar &s, const vpCameraParameters &cam, const vpImagePoint &iP); + static void create(vpFeaturePointPolar &s, const vpPoint &p); + static void create(vpFeaturePointPolar &s, const vpCameraParameters &goodCam, const vpCameraParameters &wrongCam, + const vpPoint &p); + + // create vpFeaturePoint3D feature + static void create(vpFeaturePoint3D &s, const vpPoint &p); + + // create vpFeatureLine feature + static void create(vpFeatureLine &s, const vpLine &l); + static void create(vpFeatureLine &s, const vpCylinder &c, int line); + +#ifdef VISP_HAVE_MODULE_ME + static void create(vpFeatureLine &s, const vpCameraParameters &cam, const vpMeLine &mel); +#endif + + //! create vpFeatureEllipse feature + static void create(vpFeatureEllipse &s, const vpCircle &c); + static void create(vpFeatureEllipse &s, const vpSphere &sphere); +#ifdef VISP_HAVE_MODULE_BLOB + static void create(vpFeatureEllipse &s, const vpCameraParameters &cam, const vpDot &blob); + static void create(vpFeatureEllipse &s, const vpCameraParameters &cam, const vpDot2 &blob); +#endif + +#ifdef VISP_HAVE_MODULE_ME + static void create(vpFeatureEllipse &s, const vpCameraParameters &cam, const vpMeEllipse &ellipse); +#endif + + // To keep compat with previous releases, we set cartesian coordinates as default initialising select var to selectX() + // or selectY() + static void create(vpFeatureVanishingPoint &s, const vpPoint &p, + unsigned int select = (vpFeatureVanishingPoint::selectX() | vpFeatureVanishingPoint::selectY())); + static void create(vpFeatureVanishingPoint &s, const vpFeatureLine &l1, const vpFeatureLine &l2, + unsigned int select = (vpFeatureVanishingPoint::selectX() | vpFeatureVanishingPoint::selectY())); + static void create(vpFeatureVanishingPoint &s, const vpLine &l1, const vpLine &l2, + unsigned int select = (vpFeatureVanishingPoint::selectX() | vpFeatureVanishingPoint::selectY())); + // This function is new that's why select is not initialized + static void create(vpFeatureVanishingPoint &s, const vpCameraParameters &cam, const vpImagePoint &line1_ip1, + const vpImagePoint &line1_ip2, const vpImagePoint &line2_ip1, const vpImagePoint &line2_ip2, + unsigned int select); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureDepth.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureDepth.h new file mode 100644 index 00000000..06875b2a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureDepth.h @@ -0,0 +1,223 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * 2D point visual feature. + */ + +/*! + * \file vpFeatureDepth.h + * \brief Class that defines 3D point visual feature + */ + +#ifndef vpFeatureDepth_H +#define vpFeatureDepth_H + +#include +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpFeatureDepth + * \ingroup group_visual_features + * + * \brief Class that defines a 3D point visual feature \f$ s\f$ which + * is composed by one parameters that is \f$ log( \frac{Z}{Z^*}) \f$ + * that defines the current depth relative to the desired depth. Here + * \f$ Z \f$ represents the current depth and \f$ Z^* \f$ the desired + * depth. + * + * In this class \f$ x \f$ and \f$ y \f$ are the 2D coordinates in the + * camera frame and are given in meter. \f$ x \f$, \f$ y \f$ and \f$ Z + * \f$ are needed during the computation of the interaction matrix \f$ + * L \f$. + * + * The visual features can be set easily thanks to the build() method. + * + * As the visual feature \f$ s \f$ represents the current depth + * relative to the desired depth, the desired visual feature \f$ s^* + * \f$ is set to zero. Once the value of the visual feature is set, the + * interaction() method allows to compute the interaction matrix \f$ L + * \f$ associated to the visual feature, while the error() method + * computes the error vector \f$(s - s^*)\f$ between the current visual + * feature and the desired one which is here set to zero. + * + * The code below shows how to create a eye-in hand visual servoing + * task using a 3D depth feature \f$ log( \frac{Z}{Z^*}) \f$ that + * corresponds to the current depth relative to the desired depth. To + * control six degrees of freedom, at least five other features must be + * considered. First we create a current (\f$s\f$) 3D depth + * feature. Then we set the task to use the interaction matrix + * associated to the current feature \f$L_s\f$. And finally we compute + * the camera velocity \f$v=-\lambda \; L_s^+ \; (s-s^*)\f$. The + * current feature \f$s\f$ is updated in the while() loop. + * + * \code + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpServo task; // Visual servoing task + * + * vpFeatureDepth s; //The current point feature. + * //Set the current parameters x, y, Z and the desired depth Zs + * double x; // You have to compute the value of x. + * double y; // You have to compute the value of y. + * double Z; // You have to compute the value of Z. + * double Zs; // You have to define the desired depth Zs. + * //Set the point feature thanks to the current parameters. + * s.buildFrom(x, y, Z, log(Z/Zs)); + * + * // Set eye-in-hand control law. + * // The computed velocities will be expressed in the camera frame + * task.setServo(vpServo::EYEINHAND_CAMERA); + * // Interaction matrix is computed with the desired visual features sd + * task.setInteractionMatrixType(vpServo::CURRENT); + * + * // Add the 3D depth feature to the task + * task.addFeature(s); // s* is here considered as zero + * + * // Control loop + * for ( ; ; ) { + * // The new parameters x, y and Z must be computed here. + * + * // Update the current point visual feature + * s.buildFrom(x, y, Z, log(Z/Zs)); + * + * // compute the control law + * vpColVector v = task.computeControlLaw(); // camera velocity + * } + * return 0; + * } + * \endcode + * + * If you want to build your own control law, this other example shows how + * to create a current (\f$s\f$) and desired (\f$s^*\f$) 2D point visual + * feature, compute the corresponding error vector \f$(s-s^*)\f$ and finally + * build the interaction matrix \f$L_s\f$. + * + * \code + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpFeatureDepth s; //The current point feature. + * //Set the current parameters x, y, Z and the desired depth Zs + * double x; // You have to compute the value of x. + * double y; // You have to compute the value of y. + * double Z; // You have to compute the value of Z. + * double Zs; // You have to define the desired depth Zs. + * //Set the point feature thanks to the current parameters. + * s.buildFrom(x, y, Z, log(Z/Zs)); + * + * // Compute the interaction matrix L_s for the current point feature + * vpMatrix L = s.interaction(); + * + * // Compute the error vector (s-s*) for the point feature with s* considered as 0. + * vpColVector s_star(1); // The dimension is 1. + * s_star(1) = 0; // The value of s* is 0. + * s.error(s_star); + * } + * \endcode + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in how to use this class, you may have a look at:
+ * + * - \ref tutorial-simu-robot-pioneer +*/ +class VISP_EXPORT vpFeatureDepth : public vpBasicFeature +{ + +private: + //! The \f$ x \f$ 2D coordinate of the point in the camera frame (required + //! to compute the interaction matrix) + double x; + //! The \f$ y \f$ 2D coordinate of the point in the camera frame (required + //! to compute the interaction matrix) + double y; + //! The \f$ Z \f$ 3D coordinate of the point in the camera frame (required + //! to compute the interaction matrix) + double Z; + +public: + vpFeatureDepth(); + + /* + section Set coordinates + */ + vpFeatureDepth &buildFrom(const double &x, const double &y, const double &Z, const double &LogZoverZstar); + + void display(const vpCameraParameters &cam, const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1) const VP_OVERRIDE; + void display(const vpCameraParameters &cam, const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1) const VP_OVERRIDE; + vpFeatureDepth *duplicate() const VP_OVERRIDE; + vpColVector error(const vpBasicFeature &s_star, unsigned int select = FEATURE_ALL) VP_OVERRIDE; + + double get_x() const; + + double get_y() const; + + double get_Z() const; + + double get_LogZoverZstar() const; + + void init() VP_OVERRIDE; + vpMatrix interaction(unsigned int select = FEATURE_ALL) VP_OVERRIDE; + void print(unsigned int select = FEATURE_ALL) const VP_OVERRIDE; + + void set_x(double x); + + void set_y(double y); + + void set_Z(double Z); + + void set_LogZoverZstar(double LogZoverZstar); + + void set_xyZLogZoverZstar(double x, double y, double Z, double logZZs); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureEllipse.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureEllipse.h new file mode 100644 index 00000000..aadb60cd --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureEllipse.h @@ -0,0 +1,174 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * 2D ellipse visual feature. + */ + +/*! + * \file vpFeatureEllipse.h + * \brief Class that defines 2D ellipse visual feature + */ + +#ifndef vpFeatureEllipse_H +#define vpFeatureEllipse_H + +#include +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpFeatureEllipse + * \ingroup group_visual_features + * \brief Class that defines 2D ellipse visual feature. +*/ +class VISP_EXPORT vpFeatureEllipse : public vpBasicFeature +{ + /* + attributes and members directly related to the vpBasicFeature needs + other functionalities are useful but not mandatory + */ +private: + //! FeatureEllipse depth (required to compute the interaction matrix) + //! default Z = 1m + double A, B, C; + +public: + //! Default constructor. + vpFeatureEllipse(); + + /*! + \section Set coordinates + */ + //! basic constructor + vpFeatureEllipse(double x, double y, double n20, double n11, double n02); + + vpFeatureEllipse &buildFrom(const double &x, const double &y, const double &n20, const double &n11, const double &n02); + vpFeatureEllipse &buildFrom(const double &x, const double &y, const double &n20, const double &n11, const double &n02, const double &A, const double &B, const double &C); + + void display(const vpCameraParameters &cam, const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1) const VP_OVERRIDE; + void display(const vpCameraParameters &cam, const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1) const VP_OVERRIDE; + //! Feature duplication + vpFeatureEllipse *duplicate() const VP_OVERRIDE; + + //! compute the error between two visual features from a subset + //! a the possible features + vpColVector error(const vpBasicFeature &s_star, unsigned int select = FEATURE_ALL) VP_OVERRIDE; + + /*! + * Returns the visual feature corresponding to the ellipse centroid coordinate along camera x-axis. + */ + double get_x() const { return s[0]; } + /*! + * Returns the visual feature corresponding to the ellipse centroid coordinate along camera y-axis. + */ + double get_y() const { return s[1]; } + /*! + * Returns the visual feature corresponding to the second order centered moments + * of the ellipse normalized by its area \f$n_20 = mu_20/a\f$. + */ + double get_n20() const { return s[2]; } + /*! + * Returns the visual feature corresponding to the second order centered moments + * of the ellipse normalized by its area \f$n_11 = mu_11/a\f$. + */ + double get_n11() const { return s[3]; } + /*! + * Returns the visual feature corresponding to the second order centered moments + * of the ellipse normalized by its area \f$n_02 = mu_02/a\f$. + */ + double get_n02() const { return s[4]; } + + //! Default initialization. + void init() VP_OVERRIDE; + //! compute the interaction matrix from a subset a the possible features + vpMatrix interaction(unsigned int select = FEATURE_ALL) VP_OVERRIDE; + + //! Print the name of the feature + void print(unsigned int select = FEATURE_ALL) const VP_OVERRIDE; + + void set_x(double x); + void set_y(double y); + void set_xy(double x, double y); + void setABC(double A, double B, double C); + void setMoments(double n20, double n11, double n02); + + /*! + vpBasicFeature method instantiation + */ + + // feature selection + static unsigned int selectX(); + static unsigned int selectY(); + static unsigned int select_n20(); + static unsigned int select_n11(); + static unsigned int select_n02(); + +#if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) + /*! + @name Deprecated functions + */ + //@{ + VP_DEPRECATED void setMu(double mu20, double mu11, double mu02); + VP_DEPRECATED static unsigned int selectMu20(); + VP_DEPRECATED static unsigned int selectMu11(); + VP_DEPRECATED static unsigned int selectMu02(); + /*! + * \deprecated You should rather use get_n20(). + * This function is incorrectly named and is confusing since it + * returns the visual feature corresponding to the second order centered moments + * of the ellipse normalized by its area \f$n_20 = mu_20/a\f$. + */ + VP_DEPRECATED double getMu20() const { return s[2]; } + /*! + * \deprecated You should rather use get_n11(). + * This function is incorrectly named and is confusing since it + * returns the visual feature corresponding to the second order centered moments + * of the ellipse normalized by its area \f$n_11 = mu_11/a\f$. + */ + VP_DEPRECATED double getMu11() const { return s[3]; } + /*! + * \deprecated You should rather use get_n02(). + * This function is incorrectly named and is confusing since it + * returns the visual feature corresponding to the second order centered moments + * of the ellipse normalized by its area \f$n_02 = mu_02/a\f$. + */ + VP_DEPRECATED double getMu02() const { return s[4]; } + + //@} +#endif +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureException.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureException.h new file mode 100644 index 00000000..1c2149a8 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureException.h @@ -0,0 +1,97 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Exception that can be emitted by the vpFeature class and its derivatives. + */ + +/*! + * \file vpFeatureException.h + * \brief error that can be emitted by the vpFeature class and its derivatives + */ + +#ifndef _vpFeatureException_h_ +#define _vpFeatureException_h_ + +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpFeatureException + * \ingroup group_visual_features + * \brief Error that can be emitted by the vpBasicFeature class and its + * derivates. +*/ +class VISP_EXPORT vpFeatureException : public vpException +{ +public: + /*! + * \brief Lists the possible error than can be emitted while calling + * vpFeature member + */ + enum errorFeatureCodeEnum + { +//! Feature list or desired feature list is empty + badErrorVectorError, + //! Size mismatch error + sizeMismatchError, + //! Feature not initialized + notInitializedError, + //! Wrong feature initialization + badInitializationError + }; + +public: + /*! + * Constructor. + */ + vpFeatureException(int id, const char *format, ...) + { + this->code = id; + va_list args; + va_start(args, format); + setMessage(format, args); + va_end(args); + } + + /*! + * Constructor. + */ + vpFeatureException(int id, const std::string &msg) : vpException(id, msg) { } + + /*! + * Constructor. + */ + VP_EXPLICIT vpFeatureException(int id) : vpException(id) { } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureLine.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureLine.h new file mode 100644 index 00000000..b9c09638 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureLine.h @@ -0,0 +1,245 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * 2D line visual feature. + */ + +/*! + * \file vpFeatureLine.h + * \brief Class that defines 2D line visual feature + */ + +#ifndef _vpFeatureLine_h_ +#define _vpFeatureLine_h_ + +#include +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpFeatureLine + * \ingroup group_visual_features + * + * \brief Class that defines a 2D line visual feature \f$ s\f$ which is + * composed by two parameters that are \f$ \rho \f$ and \f$ \theta \f$, + * the polar coordinates of a line. + * + * In this class, the equation of the line in the image plane is given by : + * \f[ x \; cos(\theta) + y \; sin(\theta) -\rho = 0 \f] Here + * \f$ x \f$ and \f$ y \f$ are the coordinates of a point belonging to + * the line and they are given in meter. The following image shows the + * meanings of the distance \f$\rho\f$ and the angle \f$\theta\f$. + * + * \image html vpFeatureLine.gif + * \image latex vpFeatureLine.ps width=10cm + * + * You have to note that the \f$ \theta \f$ angle has its value between + * \f$ -\pi \f$ and \f$ \pi \f$ and that the \f$ \rho \f$ distance can + * be positive or negative. The conventions are illustrated by the + * image above. + * + * The visual features can be set easily from an instance of the + * classes vpLine, vpMeLine or vpCylinder. For more precision see the + * class vpFeatureBuilder. + * + * Once the values of the visual features are set, the interaction() + * method allows to compute the interaction matrix \f$ L \f$ associated + * to the visual feature, while the error() method computes the error + * vector \f$(s - s^*)\f$ between the current visual feature and the + * desired one. + * + * The code below shows how to create a eye-in hand visual servoing + * task using a 2D line feature \f$(\rho,\theta)\f$ that correspond to + * the 2D equation of a line in the image plan. To control six + * degrees of freedom, at least four other features must be considered + * like two other line features for example. First we create a current + * (\f$s\f$) 2D line feature. Then we set the task to use the + * interaction matrix associated to the current feature \f$L_s\f$. And + * finally we compute the camera velocity \f$v=-\lambda \; L_s^+ \; + * (s-s^*)\f$. The current feature \f$s\f$ is updated in the while() + * loop. + * + * \code + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpServo task; // Visual servoing task + * + * vpFeatureLine sd; //The desired line feature. + * // Sets the desired features rho and theta + * double rhod = 0; + * double thetad = 0; + * // Sets the parameters which describe the equation of a plane in the camera frame : AX+BY+CZ+D=0. + * // The line described by the features belongs to this plan. + * // Normally two plans are needed to describe a line. But to compute the interaction matrix only + * // one equation of the two plans is needed. + * // Notes that the Dd value must not be equal to zero ! + * double Ad = 0; + * double Bd = 0; + * double Cd = 1; + * double Dd = -1; + * // Set the line feature thanks to the desired parameters. + * sd.buildFrom(rhod, thetad, Ad,Bd, Cd, Dd); + * + * vpFeatureLine s; //The current line feature. + * // Sets the current features rho and theta + * double rho; // You have to compute the value of rho. + * double theta; // You have to compute the value of theta. + * // Set the line feature thanks to the current parameters. + * s.buildFrom(rho, theta); + * // In this case the parameters A, B, C, D are not needed because the interaction matrix is computed + * // with the desired visual feature. + * + * // Set eye-in-hand control law. + * // The computed velocities will be expressed in the camera frame + * task.setServo(vpServo::EYEINHAND_CAMERA); + * // Interaction matrix is computed with the desired visual features sd + * task.setInteractionMatrixType(vpServo::DESIRED); + * + * // Add the 2D line feature to the task + * task.addFeature(s, sd); + * + * // Control loop + * for ( ; ; ) { + * // The new parameters rho and theta must be computed here. + * + * // Update the current line visual feature + * s.buildFrom(rho, theta); + * + * // Compute the control law + * vpColVector v = task.computeControlLaw(); // camera velocity + * } + * return 0; + * } + * \endcode + * + * If you want to build your own control law, this other example shows how to + * create a current (\f$s\f$) and desired (\f$s^*\f$) 2D line visual feature, + * compute the corresponding error vector \f$(s-s^*)\f$ and finally build the + * interaction matrix \f$L_s\f$. + * + * \code + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpFeatureLine sd; //The desired line feature. + * // Sets the desired features rho and theta + * double rhod = 0; + * double thetad = 0; + * // Sets the parameters which describe the equation of a plane in the camera frame : AX+BY+CZ+D=0. + * double Ad = 0; double Bd = 0; double Cd = 1; double Dd = -1; + * // Set the line feature thanks to the desired parameters. + * sd.buildFrom(rhod, thetad, Ad,Bd, Cd, Dd); + * + * vpFeatureLine s; // The current line feature. + * // Sets the current features rho and theta + * double rho; // You have to compute the value of rho. + * double theta; // You have to compute the value of theta. + * // Sets the parameters which describe the equation of a plane in the camera frame : AX+BY+CZ+D=0. + * double A; // You have to compute the value of A. + * double B; // You have to compute the value of B. + * double C; // You have to compute the value of C. + * double D; // You have to compute the value of D. D must not be equal to zero ! + * // Set the line feature thanks to the current parameters. + * s.buildFrom(rho, theta, A, B, C, D); + * + * // Compute the interaction matrix L_s for the current line feature + * vpMatrix L = s.interaction(); + * // You can also compute the interaction matrix L_s for the desired line feature + * // The corresponding line of code is : vpMatrix L = sd.interaction(); + * + * // Compute the error vector (s-sd) for the line feature + * s.error(s_star); + * } + * \endcode +*/ +class VISP_EXPORT vpFeatureLine : public vpBasicFeature +{ + /*! + * Attributes and members directly related to the vpBasicFeature needs + * other functionalities ar useful but not mandatory + */ +private: + //! FeatureLine depth (required to compute the interaction matrix) + //! equation of a plane + double A, B, C, D; + +public: + vpFeatureLine(); + + vpFeatureLine &buildFrom(const double &rho, const double &theta); + vpFeatureLine &buildFrom(const double &rho, const double &theta, const double &A, const double &B, const double &C, const double &D); + + void display(const vpCameraParameters &cam, const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1) const VP_OVERRIDE; + void display(const vpCameraParameters &cam, const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1) const VP_OVERRIDE; + vpFeatureLine *duplicate() const VP_OVERRIDE; + vpColVector error(const vpBasicFeature &s_star, unsigned int select = FEATURE_ALL) VP_OVERRIDE; + + /*! + * Return the \f$ \rho \f$ subset value of the visual feature \f$ s \f$. + */ + double getRho() const { return s[0]; } + + /*! + * Return the \f$ \theta \f$ subset value of the visual feature \f$ s \f$. + */ + double getTheta() const { return s[1]; } + + void init() VP_OVERRIDE; + vpMatrix interaction(unsigned int select = FEATURE_ALL) VP_OVERRIDE; + void print(unsigned int select = FEATURE_ALL) const VP_OVERRIDE; + + void setRhoTheta(double rho, double theta); + void setABCD(double A, double B, double C, double D); + +public: + static unsigned int selectRho(); + static unsigned int selectTheta(); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureLuminance.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureLuminance.h new file mode 100644 index 00000000..4f23a5a3 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureLuminance.h @@ -0,0 +1,134 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Luminance based feature. + */ + +/*! + * \file vpFeatureLuminance.h + * \brief Class that defines the image luminance visual feature + * + * For more details see \cite Collewet08c. + */ + +#ifndef vpFeatureLuminance_h +#define vpFeatureLuminance_h + +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +#ifndef DOXYGEN_SHOULD_SKIP_THIS +/*! + * \class vpLuminance + * \brief Class that defines the luminance and gradient of a point. + * + * \sa vpFeatureLuminance +*/ +class VISP_EXPORT vpLuminance +{ +public: + double x, y; // point coordinates (in meter) + double I; // pixel intensity + double Ix, Iy; // pixel gradient + double Z; // pixel depth +}; +#endif + +/*! + * \class vpFeatureLuminance + * \ingroup group_visual_features + * \brief Class that defines the image luminance visual feature + * + * For more details see \cite Collewet08c. + */ +class VISP_EXPORT vpFeatureLuminance : public vpBasicFeature +{ +protected: + //! FeaturePoint depth (required to compute the interaction matrix) + //! default Z = 1m + double Z; + + //! Number of rows. + unsigned int nbr; + //! Number of column. + unsigned int nbc; + //! Border size. + unsigned int bord; + + //! Store the image (as a vector with intensity and gradient I, Ix, Iy) + vpLuminance *pixInfo; + int firstTimeIn; + +public: + vpFeatureLuminance(); + vpFeatureLuminance(const vpFeatureLuminance &f); + + //! Destructor. + virtual ~vpFeatureLuminance() VP_OVERRIDE; + + vpFeatureLuminance &buildFrom(vpImage &I); + + void display(const vpCameraParameters &cam, const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1) const VP_OVERRIDE; + void display(const vpCameraParameters &cam, const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1) const VP_OVERRIDE; + + vpFeatureLuminance *duplicate() const VP_OVERRIDE; + + vpColVector error(const vpBasicFeature &s_star, unsigned int select = FEATURE_ALL) VP_OVERRIDE; + void error(const vpBasicFeature &s_star, vpColVector &e); + + double get_Z() const; + unsigned int getBorder() const; + + + void init(unsigned int _nbr, unsigned int _nbc, double _Z); + + void init() VP_OVERRIDE; + vpMatrix interaction(unsigned int select = FEATURE_ALL) VP_OVERRIDE; + void interaction(vpMatrix &L); + void print(unsigned int select = FEATURE_ALL) const VP_OVERRIDE; + + vpFeatureLuminance &operator=(const vpFeatureLuminance &f); + + + void setCameraParameters(const vpCameraParameters &_cam); + void set_Z(double Z); + + static const int DEFAULT_BORDER; + +public: + vpCameraParameters cam; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureLuminanceMapping.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureLuminanceMapping.h new file mode 100644 index 00000000..95de2a2c --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureLuminanceMapping.h @@ -0,0 +1,430 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Luminance based feature. + */ + +#ifndef VP_FEATURE_LUMINANCE_MAPPING_H +#define VP_FEATURE_LUMINANCE_MAPPING_H +#include +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) +#include +#include + +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE + +/*! + * \brief Base class for functions that map an image and its interaction matrix to a different domain. + * + * \ingroup group_visual_features + * + * - The mapping\f$ \mathbf{I} \rightarrow \mathbf{z}\f$ is done via vpLuminanceMapping::map + * - The projection of the interaction matrix \f$ \mathbf{L_I} \rightarrow \mathbf{L_z}\f$ is performed in vpLuminanceMapping::interaction + * - If possible the inverse mapping (i.e., image reconstruction) is available throug vpLuminanceMapping::inverse +*/ +class VISP_EXPORT vpLuminanceMapping +{ +public: + /** + * \brief Construct a new vp Luminance Mapping object + * + * @param mappingSize The size of the space that this transformation maps to. + */ + vpLuminanceMapping(unsigned int mappingSize) : m_mappingSize(mappingSize) { } + + /** + * Destructor. + */ + virtual ~vpLuminanceMapping() { } + + /** + * \brief Map an image \p I to a representation \p s. + * This representation s has getProjectionSize() rows. + * + * Note that when combined with vpFeatureLuminanceMapping, + * The image \p I does not have the same size as the image input of vpFeatureLuminanceMapping::build. + * \p I is the center crop of this image. + * @param I The input image + * @param s The resulting representation that will serve as visual servoing features. + */ + virtual void map(const vpImage &I, vpColVector &s) = 0; + + /** + * \brief Compute the interaction matrix associated with the representation \p s + * + * @param I input image used to compute s + * @param LI Photometric interaction matrix associated to \p I (see vpFeatureLuminance) + * @param s the already computed representation + * @param L The output interaction matrix, of dimensions getProjectionSize() x 6 + */ + virtual void interaction(const vpImage &I, const vpMatrix &LI, const vpColVector &s, vpMatrix &L) = 0; + + /** + * \brief Reconstruct \p I from a representation \p s + * + * @param s the representation + * @param I Output lossy reconstruction + */ + virtual void inverse(const vpColVector &s, vpImage &I) = 0; + + /** + * \brief Returns the size of the space to which an image is mapped to. + * + * @return space size + */ + unsigned int getProjectionSize() const { return m_mappingSize; } + + /** + * \brief Returns the number of pixels that are removed by the photometric VS computation + * + * @return space size + */ + unsigned int getBorder() const { return m_border; } + + /** + * \brief Set the number of pixels that are removed by the photometric VS computation + * This function should be called by vpFeatureLuminanceMapping + * + * @param border + */ + void setBorder(unsigned border) { m_border = border; } + + static void imageAsVector(const vpImage &I, vpColVector &Ivec, unsigned border); + static void imageAsMatrix(const vpImage &I, vpMatrix &Imat, unsigned border); + +protected: + unsigned m_mappingSize; //! Final vector size + unsigned m_border; //! Borders that were removed during raw photometric VS computation +}; + +/** + * \brief Implementation of \cite Marchand19a. + * + * \ingroup group_visual_features + * + * Projects an image onto an orthogonal subspace, + * obtained via Principal Component Analysis (PCA). + * + * The orthogonal basis is obtained through Singular Value Decomposition of a dataset of images (see vpLuminancePCA::learn) + * where the \f$ k \f$ first basis vectors that explain the most variance are kept. + * + * an image \f$ I \f$ is projected to the representation \f$ \mathbf{s} \f$ with: + * \f[ \mathbf{s} = \mathbf{U}^\top (vec(\mathbf{I}) - vec(\mathbf{\bar I})) \f] + * + * with \f$ \mathbf{U} \f$ the subspace projection matrix (\f$ dim(\mathbf{I}) \times k \f$) and \f$ \mathbf{\bar I} \f$ is the average image computed from the dataset. + * + * + */ +class VISP_EXPORT vpLuminancePCA : public vpLuminanceMapping +{ +public: + vpLuminancePCA() : vpLuminanceMapping(0), m_basis(nullptr), m_mean(nullptr), m_Ivec(0), m_Ih(0), m_Iw(0) { } + + /** + * Destructor. + */ + virtual ~vpLuminancePCA() { } + + /** + * \brief Build a new PCA object + * + * @param basis \f$ \mathbf{U}^\top \f$ a k x dim(I) matrix + * @param mean \f$ vec(\mathbf{\bar I}) \f$ the mean image represented as a vector + * @param explainedVariance The explained variance for each of the k vectors. + */ + vpLuminancePCA(const std::shared_ptr &basis, const std::shared_ptr &mean, const vpColVector &explainedVariance); + + /** + * \brief Copy constructor: does not make a deep copy of the basis and mean + */ + vpLuminancePCA(const vpLuminancePCA &other); + + vpLuminancePCA &operator=(const vpLuminancePCA &other); + + /** + * \brief Initialize the PCA object with a basis, mean and explained variance vector + * + * \sa vpLuminancePCA() + * @param basis + * @param mean + * @param variance + */ + void init(const std::shared_ptr &basis, const std::shared_ptr &mean, const vpColVector &variance); + + /** + * \brief Get \f$ \mathbf{U}^\top \f$, the subspace projection matrix (\f$ k \times dim(\mathbf{I}) \f$) + * + * @return std::shared_ptr + */ + std::shared_ptr getBasis() const { return m_basis; } + /** + * \brief Get \f$ vec(\mathbf{\bar I}) \f$, the mean image computed from the dataset. + * @return std::shared_ptr + */ + std::shared_ptr getMean() const { return m_mean; } + + /** + * \brief Get the values of explained variance by each of the eigen vectors. + * + * When all eigenvectors of the dataset are considered, the explained variance total is 1. + * When they are not all considered (as should be the case), their sum should be below 1. + * @return vpColVector + */ + vpColVector getExplainedVariance() const { return m_explainedVariance; } + + void map(const vpImage &I, vpColVector &s) VP_OVERRIDE; + void inverse(const vpColVector &s, vpImage &I) VP_OVERRIDE; + void interaction(const vpImage &I, const vpMatrix &LI, const vpColVector &s, vpMatrix &L) VP_OVERRIDE; + + /** + * \brief Save the PCA basis to multiple text files, for later use via the \ref load function. + * + * @param basisFilename The file in which \f$ \mathbf{U}^\top \f$ is stored + * @param meanFileName The file in which \f$ \mathbf{\bar I} \f$ is stored + * @param explainedVarianceFile The file containing the explained variance. + * + * \throws if the basis is null or mean is null + */ + void save(const std::string &basisFilename, const std::string &meanFileName, const std::string &explainedVarianceFile) const; + + /** + * \brief Save the PCA basis to multiple text files, for later use via the \ref load function. + * + * @param basisFilename The file in which \f$ \mathbf{U}^\top \f$ is stored + * @param meanFileName The file in which \f$ \mathbf{\bar I} \f$ is stored + * @param explainedVarianceFile The file containing the explained variance. + * + * \throws if files cannot be read, or if basis and mean dimensions are incorrect. + */ + static vpLuminancePCA load(const std::string &basisFilename, const std::string &meanFileName, const std::string &explainedVarianceFile); + +#ifdef VISP_HAVE_MODULE_IO + /** + * \brief Compute a new Principal Component Analysis on set of images, stored on disk. + * + * @param imageFiles The list of image paths to load and use to compute the PCA + * @param projectionSize the number of eigenvectors that are kept for the final projection + * @param imageBorder The number of pixels to crop on each side of the image before adding it to the image set, effectively taking the center crop. + * Useful when the stored images do not have the correct dimensions + * @return the PCA computed on the imageFiles + * + * \throws if the images do not have the same dimensions + */ + static vpLuminancePCA learn(const std::vector &imageFiles, const unsigned int projectionSize, const unsigned int imageBorder = 0); +#endif + + /** + * \brief Compute a new Principal Component Analysis on set of images. + * + * @param images The list of images used to compute the PCA + * @param projectionSize the number of eigenvectors that are kept for the final projection + * @param imageBorder The number of pixels to crop on each side of the image before adding it to the image set, effectively taking the center crop. + * Useful when the images do not have the correct dimensions. Typically, the input image to PCA is smaller than the one used when computing luminance features, since the latter step requires crops the image. + * @return the PCA computed on the images + * + * \throws if the images do not have the same dimensions + */ + static vpLuminancePCA learn(const std::vector> &images, const unsigned int projectionSize, const unsigned int imageBorder = 0); + /** + * \brief Compute a new Principal Component Analysis on dataset + * + * @param images The data matrix, where each column represents a single data point (image) + * @param projectionSize the number of eigenvectors that are kept for the final projection + * @return the PCA computed on the images + */ + static vpLuminancePCA learn(const vpMatrix &images, const unsigned int projectionSize); + + +private: + std::shared_ptr m_basis; //! \f$ \mathbf{U}^\top \f$ a K by dim(I) orthogonal matrix + std::shared_ptr m_mean; //! \f$ \mathbf{\bar I} \f$ The mean image + vpColVector m_explainedVariance; //! The explained variance + vpColVector m_Ivec; //! Vector representation of the image + unsigned int m_Ih, m_Iw; //! Input image dimensions (without borders); +}; + +/** + * \brief Implementation of \cite Marchand20a. + * + * \ingroup group_visual_features + * + * Computes the Discrete Cosine Transform (DCT) representation of the image. + * Only the K first components are preserved and stored into a vector when calling map. These components correspond to the lowest frequencies of the input image. + */ +class VISP_EXPORT vpLuminanceDCT : public vpLuminanceMapping +{ +public: + /** + * \brief Helper class to iterate and get/set the values from a matrix, following a zigzag pattern. + * + */ + class VISP_EXPORT vpMatrixZigZagIndex + { + public: + vpMatrixZigZagIndex(); + /** + * \brief Initialize the ZigZag object. Computes and stores the zigzag indexing for a given matrix size + * + * @param rows the matrix's number of rows + * @param cols the matrix's number of cols + */ + void init(unsigned rows, unsigned cols); + /** + * \brief Fill the vector s with (end - start) values, according to the zigzag matrix indexing strategy + * + * @param m the matrix + * @param start The first value. Use 0 to start with the matrix's top left value + * @param end The last value to store in the vector. (exclusive) + * @param s The vector in which to store the values + */ + void getValues(const vpMatrix &m, unsigned int start, unsigned int end, vpColVector &s) const; + + /** + * \brief set the values in the matrix, according to the values stored in the vector s and the zigzag indexing strategy + * + * @param s The vector from which to set the values + * @param start the zigzag index at which to start filling values + * @param m The matrix in which the values will be replaced + */ + void setValues(const vpColVector &s, unsigned int start, vpMatrix &m) const; + + private: + std::vector m_rowIndex; // Contains the row index of the nth value of the zigzag indexing + std::vector m_colIndex; // Contains the row index of the nth value of the zigzag indexing + unsigned m_rows; + unsigned m_cols; + }; + + /** + * \brief Build a new DCT object + * + * @param k the number of components to keep from the DCT matrix and use as servoing features + */ + vpLuminanceDCT(const unsigned int k) : vpLuminanceMapping(k) + { + init(k); + } + + /** + * \brief Initialize the DCT object with the number of required components + */ + void init(const unsigned int k) + { + m_mappingSize = k; + m_border = vpFeatureLuminance::DEFAULT_BORDER; + m_Ih = m_Iw = 0; + } + + /** + * \brief Copy constructor + */ + vpLuminanceDCT(const vpLuminanceDCT &other); + + vpLuminanceDCT &operator=(const vpLuminanceDCT &) = default; + + void map(const vpImage &I, vpColVector &s) VP_OVERRIDE; + void inverse(const vpColVector &s, vpImage &I) VP_OVERRIDE; + void interaction(const vpImage &I, const vpMatrix &LI, const vpColVector &s, vpMatrix &L) VP_OVERRIDE; + +private: + void computeDCTMatrix(vpMatrix &D, unsigned int n) const; + void computeDCTMatrices(unsigned int rows, unsigned int cols); + +protected: + unsigned int m_Ih, m_Iw; //! image dimensions (without borders) + vpMatrix m_Imat; //! Image as a matrix + vpMatrix m_dct; //! DCT representation of the image + vpMatrix m_Dcols, m_Drows; //! the computed DCT matrices. The separable property of DCt is used so that a 1D DCT is computed on rows and another on columns of the result of the first dct; + std::array m_dIdrPlanes; //! Luminance interaction matrix, seen as six image planes + vpLuminanceDCT::vpMatrixZigZagIndex m_zigzag; //! zigzag indexing helper +}; + +/** + * \brief Class to combine luminance features (photometric servoing) + * + * \ingroup group_visual_features + * + * with a mapping \f$ f(\mathbf{I}) \f$ that projects an image to a low dimensional representation \f$ \mathbf{s} \f$ (see vpLuminanceMapping::map). + * The interaction matrix of \f$ \mathbf{s} \f$ is computed as a function of \f$ \mathbf{I}, \mathbf{L_I} \f$ (see vpLuminanceMapping::interaction) + * + * The mapping \f$ f \f$ is applied to the center crop of the image, + * where the interaction matrix of the pixels can be computed (see vpFeatureLuminance::getBorder). + * + * \see vpLuminanceDCT, vpLuminancePCA, vpFeatureLuminance + */ +class VISP_EXPORT vpFeatureLuminanceMapping : public vpBasicFeature +{ +public: + vpFeatureLuminanceMapping(const vpCameraParameters &cam, unsigned int h, unsigned int w, double Z, const std::shared_ptr mapping); + vpFeatureLuminanceMapping(const vpFeatureLuminance &luminance, std::shared_ptr mapping); + void init() VP_OVERRIDE; + void init(const vpCameraParameters &cam, unsigned int h, unsigned int w, double Z, std::shared_ptr mapping); + void init(const vpFeatureLuminance &luminance, std::shared_ptr mapping); + + vpFeatureLuminanceMapping(const vpFeatureLuminanceMapping &f); + vpFeatureLuminanceMapping &operator=(const vpFeatureLuminanceMapping &f); + vpFeatureLuminanceMapping *duplicate() const VP_OVERRIDE; + + virtual ~vpFeatureLuminanceMapping() = default; + + void buildFrom(vpImage &I); + + void display(const vpCameraParameters &, const vpImage &, const vpColor & = vpColor::green, + unsigned int = 1) const VP_OVERRIDE + { } + void display(const vpCameraParameters &, const vpImage &, const vpColor & = vpColor::green, + unsigned int = 1) const VP_OVERRIDE + { } + + vpColVector error(const vpBasicFeature &s_star, unsigned int select = FEATURE_ALL) VP_OVERRIDE; + void error(const vpBasicFeature &s_star, vpColVector &e); + + vpMatrix interaction(unsigned int select = FEATURE_ALL) VP_OVERRIDE; + void interaction(vpMatrix &L); + + void print(unsigned int select = FEATURE_ALL) const VP_OVERRIDE; + + vpFeatureLuminance &getLuminanceFeature() { return m_featI; } + std::shared_ptr &getMapping() { return m_mapping; } + +private: + std::shared_ptr m_mapping; + vpFeatureLuminance m_featI; + vpMatrix m_LI; //! Photometric interaction matrix + vpImage I; +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMoment.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMoment.h new file mode 100644 index 00000000..a1d4c053 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMoment.h @@ -0,0 +1,292 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Base for all moment features + */ + +/*! + * \file vpFeatureMoment.h + * \brief Base class for moment features. + * + * Handles common system operations like selection, duplication. Functionality is + * computed in derived classes. + */ +#ifndef _vpFeatureMoment_h_ +#define _vpFeatureMoment_h_ + +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +class vpMomentObject; +class vpMomentDatabase; +class vpFeatureMomentDatabase; +class vpMoment; + +/*! + * \class vpFeatureMoment + * + * \ingroup group_visual_features + * + * \brief This class defines shared system methods/attributes for 2D moment + * features but no functional code. It is used to compute interaction matrices + * for moment features. + * + * While vpMoment-type classes do only compute moment values and can by used for + * almost anything, vpFeatureMoment-type classes are specifically designed for + * visual servoing. More importantly, a vpFeatureMoment is used to compute the + * interaction matrix associated to it's moment primitive. + * + * This class is virtual and cannot be used directly. It defines the following + * characteristics common to all moment features: + * - Plane orientation parameters (A,B,C): + * Each camera frame corresponds to a physical planar object contained in a + * plane. This plane's equation has the following form: \f$ A \times x+B \times y + * + C = \frac{1}{Z} \f$. These parameters can be updated anytime. + * - Get corresponding moment primitive: for example a vpFeatureMomentCInvariant + * will provide access to a vpMomentCInvariant instance. + * - Provide access to a feature database (vpFeatureMomentDatabase). + * - All interaction matrices (different from vpBasicFeature::interaction which + * selects the required interaction matrix). + * + * Like vpMoment, vpFeatureMoment provides a vpFeatureMoment::update() method. + * But unlike vpMoment::update() which only acknowledges the new object, the + * vpFeatureMoment::update() acknowledges the new plane parameters AND computes + * the interaction matrices associated with the feature. + * + * A vpFeatureMoment will be often part of a vpFeatureMomentDatabase in the same + * way a vpMoment is part of a vpMomentDatabase. This database is specified + * inside the vpFeatureMoment::vpFeatureMoment() constructor. As a result, a + * vpFeatureMoment will be able to access other vpFeatureMoments through this + * database. + * + * A vpBasicFeature can be duplicated into a vpMomentGenericFeature. In that + * case, all data in the vpBasicFeature is copied but the feature's name is lost. + * For example if a vpFeatureMomentCInvariant is duplicated, the duplicate will + * be operational but could not be used in a vpFeatureMomentDatabase. + * + * Note that you can use vpFeatureMoment to do visual servoing but it is not it's + * only purpose. You may compute your interaction matrices with + * vpFeatureMoment::update() and use them for any purpose. + * + * \attention - A vpFeatureMoment is not responsible for updating the moment + * primitives it depends on. Make sure your vpMoments are all up to date before + * computing an interaction matrix using vpFeatureMoment. + * + * \attention - Be careful with orders. Often, computing a feature of order n + * requires vpMoment primitives of order n+1. Make sure to check the + * documentation of the specialized vpFeatureMoment classes when deciding to + * which order you want to initialize the object. An object of order 6 should be + * sufficient for all classic implementations of vpFeatureMoment. + * + * Here is an example of how to use a vpFeatureMoment (in this case + * vpFeatureMomentBasic). + * \code + * #include + * #include + * #include + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpPoint p; + * std::vector vec_p; // vector that contains the vertices + * + * p.set_x(1); p.set_y(1); // coordinates in meters in the image plane (vertex 1) + * vec_p.push_back(p); + * p.set_x(2); p.set_y(2); // coordinates in meters in the image plane (vertex 2) + * vec_p.push_back(p); + * + * //////////////////////////////REFERENCE VALUES//////////////////////////////// + * // Init object of order 3 because we need vpFeatureMomentBasic of order 2 which + * // implies third-order moment primitives + * vpMomentObject obj(3); + * obj.setType(vpMomentObject::DISCRETE); // Discrete mode for object + * obj.fromVector(vec_p); + * + * vpMomentDatabase mdb; //database for moment primitives. This will + * //only contain the basic moment. + * vpMomentBasic bm; //basic moment (this particular moment is nothing + * //more than a shortcut to the vpMomentObject) + * bm.linkTo(mdb); //add basic moment to moment database + * + * vpFeatureMomentBasic fmb(mdb,0,0,1,nullptr); + * + * //update and compute the vpMoment BEFORE doing any operations with vpFeatureMoment + * bm.update(obj); + * bm.compute(); + * + * fmb.update(0,0,1); //update the vpFeatureMoment with a plane + * //configuration + * std::cout << fmb.interaction(1,1) << std::endl; + * } + * \endcode +*/ +class VISP_EXPORT vpFeatureMoment : public vpBasicFeature +{ +protected: + const vpMoment *moment; + const vpMoment &getMoment() const { return *moment; } + vpMomentDatabase &moments; + vpFeatureMomentDatabase *featureMomentsDataBase; + std::vector interaction_matrices; + + double A; + double B; + double C; + std::string m_name; + + // private: + //#ifndef DOXYGEN_SHOULD_SKIP_THIS + // vpFeatureMoment(const vpFeatureMoment &fm) + // : vpBasicFeature(), moment(nullptr), moments(fm.moments), + // featureMomentsDataBase(nullptr), + // interaction_matrices(), A(0), B(0), C(0) + // { + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); + // } + // vpFeatureMoment &operator=(const vpFeatureMoment &){ + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); return *this; + // } + //#endif +#if !defined(VISP_MOMENTS_COMBINE_MATRICES) +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpFeatureMoment(const vpFeatureMoment &) = delete; // non construction-copyable + vpFeatureMoment &operator=(const vpFeatureMoment &) = delete; // non copyable +#endif +#endif + +public: + /*! + * Initializes the feature with information about the database of moment + * primitives, the object plane, feature database and matrix size. + * \param data_base : Moment database. The database of moment primitives (first + * parameter) is mandatory. It is used to access different moment values later + * used to compute the final matrix. + * \param A_ : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param B_ : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param C_ : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param featureMoments : Feature database + * \param nbmatrices : If you want to create a new vpFeatureMoment implementation, + * your feature will often have a matrix size of n lines. You can specify the + * number of lines by this parameter. + */ + vpFeatureMoment(vpMomentDatabase &data_base, double A_ = 0.0, double B_ = 0.0, double C_ = 0.0, + vpFeatureMomentDatabase *featureMoments = nullptr, unsigned int nbmatrices = 1) + : vpBasicFeature(), moment(nullptr), moments(data_base), featureMomentsDataBase(featureMoments), + interaction_matrices(nbmatrices), A(A_), B(B_), C(C_), m_name() + { } + + /** @name Inherited functionalities from vpFeatureMoment */ + //@{ + virtual void compute_interaction(void); + + vpBasicFeature *duplicate() const VP_OVERRIDE; + void display(const vpCameraParameters &cam, const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1) const VP_OVERRIDE; + void display(const vpCameraParameters &cam, const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1) const VP_OVERRIDE; + + int getDimension(unsigned int select = FEATURE_ALL) const; + void init(void) VP_OVERRIDE; + vpMatrix interaction(unsigned int select = FEATURE_ALL) VP_OVERRIDE; + + void linkTo(vpFeatureMomentDatabase &featureMoments); + + /*! + * Name of the moment corresponding to the feature. This allows to locate + * the moment associated with the feature in the provided database. + */ + virtual const std::string momentName() const = 0; + + /*! + * Name of the feature used to locate it in the database of features. + */ + virtual const std::string name() const = 0; + + void print(unsigned int select = FEATURE_ALL) const VP_OVERRIDE; + virtual void printDependencies(std::ostream &os) const; + + void update(double A, double B, double C); + + //@} + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpFeatureMoment &featM); +}; + +/*! + * \class vpMomentGenericFeature + * + * \ingroup group_visual_features + * + * \brief This class defines a generic feature used for moment feature + * duplication. + * + * A vpBasicFeature can be duplicated into a vpMomentGenericFeature. In that + * case, all data in the vpBasicFeature is copied but the feature's name is lost. + * For example if a vpFeatureMomentCInvariant is duplicated, the duplicate will + * be operational but could not be used in a vpFeatureMomentDatabase. The reason + * for this is that a vpMomentGenericFeature can refer to anything therefore it + * has no specific name. + * + * Duplication is mostly used internally in ViSP. + */ +class VISP_EXPORT vpMomentGenericFeature : public vpFeatureMoment +{ +public: + vpMomentGenericFeature(vpMomentDatabase &data_base, double A_, double B_, double C_, + vpFeatureMomentDatabase *featureMoments, const vpMoment *p_moment) + : vpFeatureMoment(data_base, A_, B_, C_, featureMoments) + { + this->moment = p_moment; + } + + /*! + * No specific moment name. + */ + const std::string momentName() const { return std::string(); } + + /*! + * No specific feature name. + */ + virtual const std::string name() const { return std::string(); } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentAlpha.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentAlpha.h new file mode 100644 index 00000000..6eb6061e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentAlpha.h @@ -0,0 +1,139 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Implementation for all supported moment features. + */ + +/*! + * \file vpFeatureMomentAlpha.h + * \brief Implementation of the interaction matrix computation for + * vpMomentAlpha. + */ + +#ifndef _vpFeatureMomentAlpha_h_ +#define _vpFeatureMomentAlpha_h_ + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +class vpMomentDatabase; +/*! + * \class vpFeatureMomentAlpha + * + * \ingroup group_visual_features + * + * \brief Functionality computation for in-plane rotation moment feature \f$ \alpha \f$: + * computes the interaction matrix associated with vpMomentAlpha. + * + * The interaction matrix for the feature can be deduced from \cite Tahri05z. + * + * This class computes the interaction matrix associated to \f$ \alpha = + * \frac{1}{2} arctan(\frac{2\mu_{11}}{\mu_{20}-\mu_{02}}) \f$ moment primitive. + * + * The interaction matrix for the feature has the following form: + * \f[{ + * \left[ \begin {array}{c} {\frac {\mu_{{1,1}}{\it DA}\,A}{d}}+{\frac { + * \left( {\it DA}\,\mu_{{0,2}}+1/2\,d-1/2\,{{\it DA}}^{2} \right) B}{d} + * }\\ \noalign{\medskip}{\frac { \left( {\it DA}\,\mu_{{0,2}}-1/2\,d-1/2 + * \,{{\it DA}}^{2} \right) A}{d}}-{\frac {B\mu_{{1,1}}{\it DA}}{d}} + * \\ \noalign{\medskip}Bw_{{x}}-Aw_{{y}}\\ \noalign{\medskip}{\frac { + * \beta\, \left( \mu_{{1,2}} \left( \mu_{{2,0}}-\mu_{{0,2}} \right) +\mu + * _{{1,1}} \left( \mu_{{0,3}}-\mu_{{2,1}} \right) \right) +\gamma\,x_{{ + * g}} \left( \mu_{{0,2}} \left( \mu_{{2,0}}-\mu_{{0,2}} \right) -2\,{\mu + * _{{1,1}}}^{2} \right) +\gamma\,y_{{g}}\mu_{{1,1}} \left( \mu_{{2,0}}+ + * \mu_{{0,2}} \right) }{d}}\\ \noalign{\medskip}{\frac {\beta\, \left( + * \mu_{{2,1}} \left( \mu_{{0,2}}-\mu_{{2,0}} \right) +\mu_{{1,1}} + * \left( \mu_{{3,0}}-\mu_{{1,2}} \right) \right) +\gamma\,x_{{g}}\mu_{ + * {1,1}} \left( \mu_{{2,0}}+\mu_{{0,2}} \right) +\gamma\,y_{{g}} \left( + * \mu_{{2,0}} \left( \mu_{{0,2}}-\mu_{{2,0}} \right) -2\,{\mu_{{1,1}}}^{ + * 2} \right) }{d}}\\ \noalign{\medskip}-1\end {array} \right] + * }^t + * \f] + * with \f${\it DA} = \mu_{{2,0}}-\mu_{{0,2}}\f$ and \f${\it d} = + * DA^2+4{\mu_{1,1}}^2\f$. + * + * - In the discrete case: + * \f$beta = 4\f$,\f$gamma = 2\f$. + * - In the dense case: + * \f$beta = 5\f$,\f$gamma = 1\f$. + * + * The interaction matrix computed is single-dimension (no selection possible) + * and can be obtained by calling vpFeatureMomentAlpha::interaction(). + * + * This feature is often used in moment-based visual servoing to control the + * planar rotation parameter. + * + * Minimum vpMomentObject order needed to compute this feature: 4. + * + * This feature depends on: + * - vpMomentCentered + * - vpMomentGravityCenter +*/ +class VISP_EXPORT vpFeatureMomentAlpha : public vpFeatureMoment +{ +public: + /*! + * Initializes the feature with information about the database of moment + * primitives, the object plane and feature database. + * \param data_base : Moment database. The database of moment primitives (first parameter) is mandatory. + * It is used to access different moment values later used to compute the final matrix. + * \param A_ : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param B_ : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param C_ : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param featureMoments : Feature database. + */ + vpFeatureMomentAlpha(vpMomentDatabase &data_base, double A_, double B_, double C_, + vpFeatureMomentDatabase *featureMoments = nullptr) + : vpFeatureMoment(data_base, A_, B_, C_, featureMoments, 1) + { } + + void compute_interaction() VP_OVERRIDE; + /*! + * Associated moment name. + */ + const std::string momentName() const VP_OVERRIDE + { + return "vpMomentAlpha"; + } + +/*! + * Feature name. + */ + const std::string name() const VP_OVERRIDE + { + return "vpFeatureMomentAlpha"; + } + + vpColVector error(const vpBasicFeature &s_star, unsigned int select = FEATURE_ALL) VP_OVERRIDE; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentArea.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentArea.h new file mode 100644 index 00000000..022996aa --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentArea.h @@ -0,0 +1,93 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Definition of vpFeatureMomentArea associated to vpMomentArea + */ + +/*! + * \file vpFeatureMomentArea.h + * \brief Implementation of the interaction matrix computation for + * vpMomentArea. + */ +#ifndef _vpFeatureMomentArea_h_ +#define _vpFeatureMomentArea_h_ + +#include +#include + +BEGIN_VISP_NAMESPACE +class vpMomentDatabase; + +/*! + * \class vpFeatureMomentArea + * + * \ingroup group_visual_features + * + * \brief Surface moment feature. Computes the interaction matrix associated + * with vpMomentArea. +*/ +class VISP_EXPORT vpFeatureMomentArea : public vpFeatureMoment +{ +public: + /*! + * Initializes the feature with information about the database of moment + * primitives, the object plane and feature database. + * \param data_base : Moment database. The database of moment primitives (first parameter) is mandatory. + * It is used to access different moment values later used to compute the final matrix. + * \param A_ : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param B_ : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param C_ : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param featureMoments : Feature database. + */ + vpFeatureMomentArea(vpMomentDatabase &data_base, double A_, double B_, double C_, + vpFeatureMomentDatabase *featureMoments = nullptr) + : vpFeatureMoment(data_base, A_, B_, C_, featureMoments, 1) + { } + + void compute_interaction() VP_OVERRIDE; + + /*! + * Associated moment name. + */ + const std::string momentName() const VP_OVERRIDE + { + return "vpMomentArea"; + } + + /*! + * Feature name. + */ + const std::string name() const VP_OVERRIDE + { + return "vpFeatureMomentArea"; + } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentAreaNormalized.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentAreaNormalized.h new file mode 100644 index 00000000..05ea7fda --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentAreaNormalized.h @@ -0,0 +1,222 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Implementation for all supported moment features. + */ + +/*! + * \file vpFeatureMomentAreaNormalized.h + * \brief Implementation of the interaction matrix computation for + * vpMomentAreaNormalized. + */ +#ifndef _vpFeatureMomentAreaNormalized_h_ +#define _vpFeatureMomentAreaNormalized_h_ + +#include +#include + +BEGIN_VISP_NAMESPACE +#ifdef VISP_MOMENTS_COMBINE_MATRICES +class vpMomentDatabase; + +/*! + * \class vpFeatureMomentAreaNormalized + * + * \ingroup group_visual_features + * + * \brief Functionality computation for normalized surface moment feature. + * Computes the interaction matrix associated with vpMomentAreaNormalized. + * + * The interaction matrix for the moment feature can be deduced from \cite + * Tahri05z. + * + * To do so, one must derive it and obtain a combination of interaction + * matrices by using (1). It allows to compute the interaction matrix for \f$ + * a_n \f$. + * + * The interaction matrix computed is single-dimension (no selection possible) + * and can be obtained by calling + * vpFeatureMomentGravityCenterNormalized::interaction. + * + * This feature is often used in moment-based visual servoing to control the + * depth parameter. + * + * Minimum vpMomentObject order needed to compute this feature: 1 in dense mode + * and 3 in discrete mode. + * + * This feature depends on: + * - vpMomentCentered + * - vpFeatureMomentCentered + * - vpMomentAreaNormalized + * - vpFeatureMomentBasic +*/ +class VISP_EXPORT vpFeatureMomentAreaNormalized : public vpFeatureMoment +{ +public: + /*! + * Initializes the feature with information about the database of moment + * primitives, the object plane and feature database. + * \param database : Moment database. The database of moment primitives (first parameter) is mandatory. + * It is used to access different moment values later used to compute the final matrix. + * \param A : Plane coefficient in a \f$ A \times x+B \times y + C = + * \frac{1}{Z} \f$ plane. + * \param B : Plane coefficient in a \f$ A \times x+B + * \times y + C = \frac{1}{Z} \f$ plane. + * \param C : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param featureMoments : Feature database. + */ + vpFeatureMomentAreaNormalized(vpMomentDatabase &database, double A_, double B_, double C_, + vpFeatureMomentDatabase *featureMoments = nullptr) + : vpFeatureMoment(database, A_, B_, C_, featureMoments, 1) + { } + + void compute_interaction() VP_OVERRIDE; + + /*! + * Associated moment name. + */ + const std::string momentName() const VP_OVERRIDE + { + return "vpMomentAreaNormalized"; + } + + /*! + * Feature name. + */ + const std::string name() const VP_OVERRIDE + { + return "vpFeatureMomentAreaNormalized"; + } +}; + +#else +class vpMomentDatabase; + +/*! + * \class vpFeatureMomentAreaNormalized + * + * \ingroup group_visual_features + * + * \brief Functionality computation for normalized surface moment feature. + * Computes the interaction matrix associated with vpMomentAreaNormalized. + * + * The interaction matrix for the moment has the following form: + * - In the discrete case: + * \f[ + * L_{a_n} = + * { + * \left[ + * \begin {array}{c} + * a_{{n}}Ae_{{2,0}}+a_{{n}}Be_{{1,1}} \\ + * \noalign{\medskip}a_{{n}}Ae_{{1,1}}+a_{{n}}Be_{{0,2}} \\ + * \noalign{\medskip}-a_{{n}}C+Bw_{{x}}-Aw_{{y}} \\ + * \noalign{\medskip}- \left( e_{{2,0}}+2\,e_{{0,2}} \right) y_{{g}}-e_{{2,1}}-x_{{g}}e_{{1,1}}+\eta_{{1,1}}e_{{1,0}}-e_{{0,3}}+\eta_{{0,2}}e_{{0,1}} \\ + * \noalign{\medskip} \left( 2\,e_{{2,0}}+e_{{0,2}} \right) x_{{g}}+e_{{3,0}}+y_{{g}}e_{{1,1}}-\eta_{{2,0}}e_{{1,0}}+e_{{1,2}}-\eta_{{1,1}}e_{{0,1}} \\ + * \noalign{\medskip}0 + * \end {array} + * \right] + * }^t + * \f] + * + * - In the dense case: + * \f[ + * L_{a_n} = + * { + * \left[ + * \begin {array}{c} + * 1/2\,a_{{n}}A \\ + * \noalign{\medskip}1/2\,a_{{n}}B \\ + * \noalign{\medskip}-a_{{n}}C-3/2\,Ax_{{n}}-3/2\,By_{{n}} \\ + * \noalign{\medskip}-3/2\,y_{{n}} \\ + * \noalign{\medskip}3/2\,x_{{n}} \\ + * \noalign{\medskip}0 + * \end {array} + * \right] + * }^t + * \f] + * with: + * - \f$e_{i,j}=\frac{\mu_{i,j}}{NA}\f$ + * - \f$NA=\mu_{2,0}+\mu_{0,2}\f$ + * - \f$\eta\f$ is the centered and normalized moment. + * To do so, one must derive it and obtain a combination of interaction + * matrices by using (1). It allows to compute the interaction matrix for \f$ a_n \f$. + * + * The interaction matrix computed is single-dimension (no selection possible) + * and can be obtained by calling vpFeatureMomentGravityCenterNormalized::interaction. + * + * This feature is often used in moment-based visual servoing to control the depth parameter. + * + * Minimum vpMomentObject order needed to compute this feature: 1 in dense mode + * and 3 in discrete mode. + * + * This feature depends on: + * - vpMomentCentered + * - vpMomentAreaNormalized + * - vpMomentGravityCenter + */ +class VISP_EXPORT vpFeatureMomentAreaNormalized : public vpFeatureMoment +{ +public: + /*! + * Initializes the feature with information about the database of moment + * primitives, the object plane and feature database. + * \param data_base : Moment database. The database of moment primitives (first parameter) is mandatory. + * It is used to access different moment values later used to compute the final matrix. + * \param A_ : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param B_ : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param C_ : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param featureMoments : Feature database. + */ + vpFeatureMomentAreaNormalized(vpMomentDatabase &data_base, double A_, double B_, double C_, + vpFeatureMomentDatabase *featureMoments = nullptr) + : vpFeatureMoment(data_base, A_, B_, C_, featureMoments, 1) + { } + + void compute_interaction() VP_OVERRIDE; + + /*! + * Associated moment name. + */ + const std::string momentName() const VP_OVERRIDE + { + return "vpMomentAreaNormalized"; + } + + /*! + * Feature name. + */ + const std::string name() const VP_OVERRIDE + { + return "vpFeatureMomentAreaNormalized"; + } +}; +#endif +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentBasic.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentBasic.h new file mode 100644 index 00000000..cb04744d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentBasic.h @@ -0,0 +1,115 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Implementation for all supported moment features. + */ + +/*! + * \file vpFeatureMomentBasic.h + * \brief Implementation of the interaction matrix computation for + * vpMomentBasic. + */ + +#ifndef _vpFeatureMomentBasic_h_ +#define _vpFeatureMomentBasic_h_ + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +class vpMomentDatabase; +/*! + * \class vpFeatureMomentBasic + * + * \ingroup group_visual_features + * + * \brief Functionality computation for basic moment feature. Computes the + * interaction matrix associated with vpMomentBasic. + * + * The interaction matrix for the basic moment feature is defined in + * \cite Tahri05z, equation (13). This vpFeatureMoment, as well as it's + * corresponding moment primitive is double-indexed. The interaction matrix \f$ + * L_{m_{ij}} \f$ is obtained by calling vpFeatureMomentBasic::interaction + * (i,j) and is associated to \f$ m_{ij} \f$ obtained by vpMomentBasic::get + * (i,j). vpFeatureMomentBasic computes interaction matrices all interaction + * matrices up to vpMomentObject::getOrder()-1. + * + * \attention The maximum order reached by vpFeatureMomentBasic is NOT the maximum order of the + * vpMomentObject, it is one unit smaller. For example if you define your + * vpMomentObject up to order n then vpFeatureMomentBasic will be able to + * compute interaction matrices up to order n-1 that is \f$ L_{m_{ij}} \f$ with + * \f$ i+j<=n-1 \f$. + * + * You can see an example of vpFeatureMomentBasic by looking at the + * documentation of the vpFeatureMoment class. + * + * This feature depends on: + * - vpMomentBasic +*/ +class VISP_EXPORT vpFeatureMomentBasic : public vpFeatureMoment +{ +protected: + unsigned int order; + +public: + vpFeatureMomentBasic(vpMomentDatabase &moments, double A, double B, double C, + vpFeatureMomentDatabase *featureMoments = nullptr); + + void compute_interaction() VP_OVERRIDE; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + /* Add function due to pure virtual definition in vpBasicFeature.h */ + vpMatrix interaction(unsigned int /* select = FEATURE_ALL */) VP_OVERRIDE + { + throw vpException(vpException::functionNotImplementedError, "Not implemented!"); + } +#endif + + vpMatrix interaction(unsigned int select_one, unsigned int select_two) const; + + /*! + * Associated moment name. + */ + const std::string momentName() const VP_OVERRIDE + { + return "vpMomentBasic"; + } + + /*! + * Feature name. + */ + const std::string name() const VP_OVERRIDE + { + return "vpFeatureMomentBasic"; + } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentCInvariant.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentCInvariant.h new file mode 100644 index 00000000..f7a977e8 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentCInvariant.h @@ -0,0 +1,338 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Implementation for all supported moment features. + */ + +/*! + * \file vpFeatureMomentCInvariant.h + * \brief Implementation of the interaction matrix computation for vpMomentCInvariant. + */ +#ifndef _vpFeatureMomentCInvariant_h_ +#define _vpFeatureMomentCInvariant_h_ + +#include +#include + +#ifdef VISP_MOMENTS_COMBINE_MATRICES +BEGIN_VISP_NAMESPACE + +/*! + * \class vpFeatureMomentCInvariant + * + * \ingroup group_visual_features + * + * \brief Functionality computation for 2D rotation/translation/scale + * non-symmetric invariant moment feature. Computes the interaction matrix + * associated with vpMomentCInvariant. + * + * The interaction matrix for the moment feature can be deduced from \cite + * Tahri05z, equations (9). To do so, one must derive them and obtain a + * combination of interaction matrices by using (1). It allows to compute the + * interaction matrix for \f$ c_i, i \in [1..10] \f$. + * + * These interaction matrices may be selected afterwards by calling + * vpFeatureMomentCInvariant::interaction(). The selection by the + * vpFeatureMomentCInvariant::selectCi method for \f$ L_{c_i} \f$. For example, + * to select \f$ L_{c_1} \f$ you should input + * vpFeatureMomentCInvariant::selectC1() into ViSP's selector. Special matrices + * for features \f$ S_x \f$ and \f$ S_y \f$ are selected by + * vpFeatureMomentCInvariant::selectSx() and + * vpFeatureMomentCInvariant::selectSy() respectively. Special matrices for + * features \f$ P_x \f$ and \f$ P_y \f$ are selected by + * vpFeatureMomentCInvariant::selectPx() and + * vpFeatureMomentCInvariant::selectPy() respectively. + * + * These features are often used in moment-based visual servoing to control the + * two out-of-plane rotations. + * + * Be careful about the nature of your object when selecting the right + * features. Use \f$ L_{S_{x}} \f$ and \f$ L_{S_{y}} \f$ when you're dealing + * with a symmetric object all other features otherwise. + * + * Minimum vpMomentObject order needed to compute this feature: 6. This is the + * highest order required by classic features. + * + * This feature depends on: + * - vpMomentCentered + * - vpFeatureMomentCentered + * - vpMomentCInvariant + * - vpFeatureMomentBasic + * + * An example of how to use vpFeatureMomentCInvariant in a complete visual + * servoing example is given in vpFeatureMomentCommon. +*/ +class VISP_EXPORT vpFeatureMomentCInvariant : public vpFeatureMoment +{ +public: + /*! + * Initializes the feature with information about the database of moment + * primitives, the object plane and feature database. + * \param moments : Moment database. The database of moment primitives (first parameter) is mandatory. + * It is used to access different moment values later used to compute the final matrix. + * \param A : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param B : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param C : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param featureMoments : Feature database. + */ + vpFeatureMomentCInvariant(vpMomentDatabase &moments, double A, double B, double C, + vpFeatureMomentDatabase *featureMoments = nullptr) + : vpFeatureMoment(moments, A, B, C, featureMoments, 16) + { } + + void compute_interaction() VP_OVERRIDE; + + /*! + * Associated moment name. + */ + const std::string momentName() const VP_OVERRIDE + { + return "vpMomentCInvariant"; + } + + /*! + * Feature name. + */ + const std::string name() const VP_OVERRIDE + { + return "vpFeatureMomentCInvariant"; + } + + /*! + * Shortcut selector for \f$C_1\f$. + */ + static unsigned int selectC1() { return 1 << 0; } + /*! + * Shortcut selector for \f$C_2\f$. + */ + static unsigned int selectC2() { return 1 << 1; } + /*! + * Shortcut selector for \f$C_3\f$. + */ + static unsigned int selectC3() { return 1 << 2; } + /*! + * Shortcut selector for \f$C_4\f$. + */ + static unsigned int selectC4() { return 1 << 3; } + /*! + * Shortcut selector for \f$C_5\f$. + */ + static unsigned int selectC5() { return 1 << 4; } + /*! + * Shortcut selector for \f$C_6\f$. + */ + static unsigned int selectC6() { return 1 << 5; } + /*! + * Shortcut selector for \f$C_7\f$. + */ + static unsigned int selectC7() { return 1 << 6; } + /*! + * Shortcut selector for \f$C_8\f$. + */ + static unsigned int selectC8() { return 1 << 7; } + /*! + * Shortcut selector for \f$C_9\f$. + */ + static unsigned int selectC9() { return 1 << 8; } + /*! + * Shortcut selector for \f$C_{10}\f$. + */ + static unsigned int selectC10() { return 1 << 9; } + /*! + * Shortcut selector for \f$S_x\f$. + */ + static unsigned int selectSx() { return 1 << 10; } + /*! + * Shortcut selector for \f$S_y\f$. + */ + static unsigned int selectSy() { return 1 << 11; } + /*! + * Shortcut selector for \f$P_x\f$. + */ + static unsigned int selectPx() { return 1 << 12; } + /*! + * Shortcut selector for \f$P_y\f$. + */ + static unsigned int selectPy() { return 1 << 13; } +}; +END_VISP_NAMESPACE +#else +BEGIN_VISP_NAMESPACE +class vpMomentDatabase; + +/*! + * \class vpFeatureMomentCInvariant + * + * \ingroup group_visual_features + * + * \brief Functionality computation for 2D rotation/translation/scale + * non-symmetric invariant moment feature. Computes the interaction matrix + * associated with vpMomentCInvariant. + * + * The interaction matrix for the moment feature can be deduced from + * \cite Tahri05z, equations (9). To do so, one must derive them and obtain a + * combination of interaction matrices by using (1). It allows to compute the + * interaction matrix for \f$ c_i, i \in [1..10] \f$. + * + * These interaction matrices may be selected afterwards by calling + * vpFeatureMomentCInvariant::interaction(). The selection by the + * vpFeatureMomentCInvariant::selectCi method for \f$ L_{c_i} \f$. For example, + * to select \f$ L_{c_1} \f$ you should input + * vpFeatureMomentCInvariant::selectC1() into ViSP's selector. Special matrices + * for features \f$ S_x \f$ and \f$ S_y \f$ are selected by + * vpFeatureMomentCInvariant::selectSx() and + * vpFeatureMomentCInvariant::selectSy() respectively. Special matrices for + * features \f$ P_x \f$ and \f$ P_y \f$ are selected by + * vpFeatureMomentCInvariant::selectPx() and + * vpFeatureMomentCInvariant::selectPy() respectively. + * + * These features are often used in moment-based visual servoing to control the + * two out-of-plane rotations. + * + * Be careful about the nature of your object when selecting the right + * features. Use \f$ L_{S_{x}} \f$ and \f$ L_{S_{y}} \f$ when you're dealing + * with a symmetric object all other features otherwise. + * + * Minimum vpMomentObject order needed to compute this feature: 6. This is the + * highest order required by classic features. + * + * This feature depends on: + * - vpMomentCentered + * - vpFeatureMomentCentered + * - vpMomentCInvariant + * - vpFeatureMomentBasic + * + * An example of how to use vpFeatureMomentCInvariant in a complete visual + * servoing example is given in vpFeatureMomentCommon. + */ +class VISP_EXPORT vpFeatureMomentCInvariant : public vpFeatureMoment +{ +private: + std::vector LI; + +public: + /*! + * Initializes the feature with information about the database of moment + * primitives, the object plane and feature database. + * \param data_base : Moment database. The database of moment primitives (first parameter) is mandatory. + * It is used to access different moment values later used to compute the final matrix. + * \param A_ : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param B_ : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param C_ : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param featureMoments : Feature database. + */ + vpFeatureMomentCInvariant(vpMomentDatabase &data_base, double A_, double B_, double C_, + vpFeatureMomentDatabase *featureMoments = nullptr) + : vpFeatureMoment(data_base, A_, B_, C_, featureMoments, 16), LI(16) + { } + + void compute_interaction() VP_OVERRIDE; + /*! + * Associated moment name. + */ + const std::string momentName() const VP_OVERRIDE + { + return "vpMomentCInvariant"; + } + /*! + * Feature name. + */ + const std::string name() const VP_OVERRIDE + { + return "vpFeatureMomentCInvariant"; + } + + /*! + * Shortcut selector for \f$C_1\f$. + */ + static unsigned int selectC1() { return 1 << 0; } + /*! + * Shortcut selector for \f$C_2\f$. + */ + static unsigned int selectC2() { return 1 << 1; } + /*! + * Shortcut selector for \f$C_3\f$. + */ + static unsigned int selectC3() { return 1 << 2; } + /*! + * Shortcut selector for \f$C_4\f$. + */ + static unsigned int selectC4() { return 1 << 3; } + /*! + * Shortcut selector for \f$C_5\f$. + */ + static unsigned int selectC5() { return 1 << 4; } + /*! + * Shortcut selector for \f$C_6\f$. + */ + static unsigned int selectC6() { return 1 << 5; } + /*! + * Shortcut selector for \f$C_7\f$. + */ + static unsigned int selectC7() { return 1 << 6; } + /*! + * Shortcut selector for \f$C_8\f$. + */ + static unsigned int selectC8() { return 1 << 7; } + /*! + * Shortcut selector for \f$C_9\f$. + */ + static unsigned int selectC9() { return 1 << 8; } + /*! + * Shortcut selector for \f$C_{10}\f$. + */ + static unsigned int selectC10() { return 1 << 9; } + /*! + * Shortcut selector for \f$S_x\f$. + */ + static unsigned int selectSx() { return 1 << 10; } + /*! + * Shortcut selector for \f$S_y\f$. + */ + static unsigned int selectSy() { return 1 << 11; } + /*! + * Shortcut selector for \f$P_x\f$. + */ + static unsigned int selectPx() { return 1 << 12; } + /*! + * Shortcut selector for \f$P_y\f$. + */ + static unsigned int selectPy() { return 1 << 13; } + + /*! + * Print all the interaction matrices of the moment invariants + */ + void printLsofInvariants(std::ostream &os) const; + + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpFeatureMomentCInvariant &featcinv); +}; +END_VISP_NAMESPACE +#endif +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentCentered.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentCentered.h new file mode 100644 index 00000000..c5785ca7 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentCentered.h @@ -0,0 +1,123 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Implementation for all supported moment features. + */ + +/*! + * \file vpFeatureMomentCentered.h + * \brief Implementation of the interaction matrix computation for vpMomentCentered. + */ +#ifndef _vpFeatureMomentCentered_h_ +#define _vpFeatureMomentCentered_h_ + +#include +#include +#include + +BEGIN_VISP_NAMESPACE +class vpMomentDatabase; + +/*! + * \class vpFeatureMomentCentered + * + * \ingroup group_visual_features + * + * \brief Functionality computation for centered moment feature. Computes the + * interaction matrix associated with vpMomentCentered. + * + * The interaction matrix for the feature is defined in \cite Tahri05z, + * equation (17). This vpFeatureMoment, as well as it's corresponding moment + * primitive is double-indexed. The interaction matrix \f$ L_{\mu_{ij}} \f$ is + * obtained by calling vpFeatureMomentBasic::interaction (i,j) and is + * associated to \f$ \mu_{ij} \f$ obtained by vpFeatureMomentCentered::get + * (i,j). + * + * vpFeatureMomentCentered computes interaction matrices all interaction + * matrices up to vpMomentObject::getOrder()-1. + * + * \attention The maximum order reached by vpFeatureMomentBasic is NOT the maximum order of the + * vpMomentObject, it is one unit smaller. For example if you define your + * vpMomentObject up to order n then vpFeatureMomentBasic will be able to + * compute interaction matrices up to order n-1 that is \f$ L_{m_{ij}} \f$ with + * \f$ i+j<=n-1 \f$. + * + * This feature depends on: + * - vpFeatureMomentBasic + * - vpFeatureMomentGravityCenter + * - vpMomentGravityCenter +*/ +class VISP_EXPORT vpFeatureMomentCentered : public vpFeatureMoment +{ +protected: + unsigned int order; + /*! + * Core computation of interaction matrix for moment m_pq. + */ + vpMatrix compute_Lmu_pq(const unsigned int &p, const unsigned int &q, const double &xg, const double &yg, + const vpMatrix &L_xg, const vpMatrix &L_yg, const vpMomentBasic &m, + const vpFeatureMomentBasic &feature_moment_m) const; + +public: + vpFeatureMomentCentered(vpMomentDatabase &moments, double A, double B, double C, + vpFeatureMomentDatabase *featureMoments = nullptr); + + void compute_interaction() VP_OVERRIDE; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + /* Add function due to pure virtual definition in vpBasicFeature.h */ + vpMatrix interaction(unsigned int /* select = FEATURE_ALL */) VP_OVERRIDE + { + throw vpException(vpException::functionNotImplementedError, "Not implemented!"); + } +#endif + + vpMatrix interaction(unsigned int select_one, unsigned int select_two) const; + + /*! + * Associated moment name + */ + const std::string momentName() const VP_OVERRIDE + { + return "vpMomentCentered"; + } + + /*! + * Feature name + */ + const std::string name() const VP_OVERRIDE + { + return "vpFeatureMomentCentered"; + } + + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpFeatureMomentCentered &v); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentCommon.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentCommon.h new file mode 100644 index 00000000..85ff34ab --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentCommon.h @@ -0,0 +1,276 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Pre-filled pseudo-database used to handle dependencies between common + *moment features. + */ + +/*! + * \file vpFeatureMomentCommon.h + * \brief Pre-filled pseudo-database used to handle dependencies between common + * moment features. + */ + +#ifndef _vpFeatureMomentCommon_h_ +#define _vpFeatureMomentCommon_h_ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +class vpMomentDatabase; +class vpServo; +/*! + * \class vpFeatureMomentCommon + * + * \ingroup group_visual_features + * + * \brief This class allows to access common vpFeatureMoments in a pre-filled + * database. + * + * It is a vpMomentDatabase filled with the following moments: + * - vpFeatureMomentGravityCenter + * - vpFeatureMomentGravityCenterNormalized + * - vpFeatureMomentAreaNormalized + * - vpFeatureMomentCInvariant + * - vpFeatureMomentAlpha + * - vpFeatureMomentCentered + * - vpFeatureMomentBasic + * + * There is no need to do the linkTo operations manually nor is it necessary to + * care about the order of feature computation. + * + * This class has an vpMomentCommon::updateAll method capable of updating the + * plane parameters AND computing interaction matrices inside the features. + * + * The moment features computed by this class are classical moments + * features used in moment-based visual servoing. For more + * information see \cite Tahri05z. + * + * To initialize this feature set, the user needs to supply a vpMomentDatabase + * containing at least the contents of vpMomentCommon. + * + * The features can be retrieved like from a normal vpFeatureMomentDatabase. + * However, some shortcuts to retrieve the features are provided. + * + * \attention Make sure your object is at least of order 6 when using this + * pre-filled database. + * + * The following code demonstrates the construction of a 6x6 interaction matrix + * as described in [1]. + * \code + * #include + * #include + * #include + * #include + * #include + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * // Define source polygon + * vpPoint p; + * std::vector vec_p; // vector that contains the vertices of the contour polygon + * + * p.set_x(-0.2); p.set_y(0.1); // coordinates in meters in the image plane (vertex 1) + * vec_p.push_back(p); + * p.set_x(+0.3); p.set_y(0.1); // coordinates in meters in the image plane (vertex 2) + * vec_p.push_back(p); + * p.set_x(+0.2); p.set_y(-0.1); // coordinates in meters in the image plane (vertex 3) + * vec_p.push_back(p); + * p.set_x(-0.2); p.set_y(-0.15); // coordinates in meters in the image plane (vertex 4) + * vec_p.push_back(p); + * p.set_x(-0.2); p.set_y(0.1); // close the contour (vertex 5 = vertex 1) + * vec_p.push_back(p); + * + * vpMomentObject src(6); // Create a source moment object with 6 as maximum order + * src.setType(vpMomentObject::DENSE_POLYGON); // The object is defined by a contour polygon + * src.fromVector(vec_p); // Init the dense object with the source polygon + * vec_p.clear(); + * + * //Define destination polygon. This is the source polygon translated + * //of 0.1 on x-axis + * p.set_x(-0.1); p.set_y(0.1); // coordinates in meters in the image plane (vertex 1) + * vec_p.push_back(p); + * p.set_x(+0.4); p.set_y(0.1); // coordinates in meters in the image plane (vertex 2) + * vec_p.push_back(p); + * p.set_x(+0.3); p.set_y(-0.1); // coordinates in meters in the image plane (vertex 3) + * vec_p.push_back(p); + * p.set_x(-0.1); p.set_y(-0.15); // coordinates in meters in the image plane (vertex 4) + * vec_p.push_back(p); + * p.set_x(-0.1); p.set_y(0.1); // close the contour (vertex 5 = vertex 1) + * vec_p.push_back(p); + * + * vpMomentObject dst(6); // Create a destination moment object with 6 as maximum order + * dst.setType(vpMomentObject::DENSE_POLYGON); // The object is defined by a contour polygon + * dst.fromVector(vec_p); // Init the dense object with the destination + * // polygon + * + * //init classic moment primitives (for source) + * vpMomentCommon mdb_src(vpMomentCommon::getSurface(dst),vpMomentCommon::getMu3(dst),vpMomentCommon::getAlpha(dst),1.); + * //Init classic features + * vpFeatureMomentCommon fmdb_src(mdb_src); + * + * ////init classic moment primitives (for destination) + * vpMomentCommon mdb_dst(vpMomentCommon::getSurface(dst),vpMomentCommon::getMu3(dst),vpMomentCommon::getAlpha(dst),1.); + * //Init classic features + * vpFeatureMomentCommon fmdb_dst(mdb_dst); + * + * //update+compute moment primitives from object (for source) + * mdb_src.updateAll(src); + * //update+compute features (+interaction matrices) from plane + * fmdb_src.updateAll(0.,0.,1.); + * + * //update+compute moment primitives from object (for destination) + * mdb_dst.updateAll(dst); + * //update+compute features (+interaction matrices) from plane + * fmdb_dst.updateAll(0.,0.,1.); + * + * //define visual servoing task + * vpServo task; + * task.setServo(vpServo::EYEINHAND_CAMERA); + * task.setInteractionMatrixType(vpServo::CURRENT); + * + * //Add all classic features to the task + * //In this example, source and destination features are translated by 0.1 + * //will produce a movement of 0.1 on x-axis. + * task.addFeature(fmdb_src.getFeatureGravityNormalized(),fmdb_dst.getFeatureGravityNormalized()); + * task.addFeature(fmdb_src.getFeatureAn(),fmdb_dst.getFeatureAn()); + * //the object is NOT symmetric + * //select C4 and C6 + * task.addFeature(fmdb_src.getFeatureCInvariant(),fmdb_dst.getFeatureCInvariant(),(1 << 3) | (1 << 5)); + * task.addFeature(fmdb_src.getFeatureAlpha(),fmdb_dst.getFeatureAlpha()); + * + * task.setLambda(1) ; + * vpColVector v = task.computeControlLaw() ; + * + * task.print(); + * + * return 0; + * } + * \endcode + * This code produces the following output: + * \code + * Visual servoing task: + * Type of control law + * Eye-in-hand configuration + * Control in the camera frame + * List of visual features : s + * 0.0166667,-0.00833333, + * 1, + * -0.312148,0.0249916, + * -1.43449, + * List of desired visual features : s* + * 0.116667,-0.00833333, + * 1, + * -0.312148,0.0249916, + * -1.43449, + * Interaction Matrix Ls + * -1 0 -6.938893904e-18 0.007291666667 -1.06875 -0.008333333333 + * 0 -1 3.469446952e-18 1.0171875 -0.007291666667 -0.01666666667 + * 0 0 -1 0.0125 0.025 0 + * 0 0 -4.585529113e-15 -0.2983860943 0.5832596643 -4.376751552e-16 + * 0 0 -3.58244462e-15 0.08633028234 -0.2484618767 3.63421192e-16 + * 4.353086256e-17 -1.339411156e-16 -0 -0.03019436997 -0.0168230563 -1 + * Error vector (s-s*) + * -0.1 0 0 1.831867991e-15 -1.072059108e-15 0 + * Gain : Zero= 1 Inf= 1 Deriv= 0 + * + * \endcode +*/ +class VISP_EXPORT vpFeatureMomentCommon : public vpFeatureMomentDatabase +{ +private: + vpFeatureMomentGravityCenter featureGravity; + vpFeatureMomentGravityCenterNormalized featureGravityNormalized; + vpFeatureMomentAreaNormalized featureAn; + vpFeatureMomentCInvariant featureCInvariant; + vpFeatureMomentAlpha featureAlpha; + vpFeatureMomentCentered featureCentered; + vpFeatureMomentBasic featureMomentBasic; + vpFeatureMomentArea feature_moment_area; + +public: + vpFeatureMomentCommon(vpMomentDatabase &moments, double A = 0.0, double B = 0.0, double C = 1.0); + void updateAll(double A, double B, double C); + /*! + * Returns alpha. + */ + vpFeatureMomentAlpha &getFeatureAlpha() { return featureAlpha; } + + /*! + * Returns normalized surface. + */ + vpFeatureMomentAreaNormalized &getFeatureAn() { return featureAn; } + /*! + * Returns basic moment. + */ + vpFeatureMomentBasic &getFeatureMomentBasic() { return featureMomentBasic; } + /*! + * Returns centered moments. + */ + vpFeatureMomentCentered &getFeatureCentered() { return featureCentered; } + + /*! + * Returns non-symmetric invariants. + */ + vpFeatureMomentCInvariant &getFeatureCInvariant() { return featureCInvariant; } + + /*! + * Returns normalized gravity center. + */ + vpFeatureMomentGravityCenterNormalized &getFeatureGravityNormalized() { return featureGravityNormalized; } + + /*! + * Returns the area. + */ + vpFeatureMomentArea &getFeatureArea() { return feature_moment_area; } + + /*! + * Returns gravity center. + */ + vpFeatureMomentGravityCenter &getFeatureGravityCenter() { return featureGravity; } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentDatabase.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentDatabase.h new file mode 100644 index 00000000..1fc69f7a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentDatabase.h @@ -0,0 +1,189 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Pseudo-database used to handle dependencies between moment features. + */ + +/*! + * \file vpFeatureMomentDatabase.h + * \brief Pseudo-database used to handle dependencies between moment features. + */ + +#ifndef _vpFeatureMomentDatabase_h_ +#define _vpFeatureMomentDatabase_h_ + +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +class vpFeatureMoment; +class vpMomentObject; +/*! + * \class vpFeatureMomentDatabase + * + * \ingroup group_visual_features + * + * \brief This class allows to register all feature moments (implemented in + * vpFeatureMoment... classes) so they can access each other according to their + * dependencies. + * + * Like moments (implemented in vpMoment... classes), a vpFeatureMoment needs + * to have access to other vpFeatureMoment's values to be computed. In most + * cases, a vpFeatureMoment needs both: vpMoments and vpFeatureMoments which + * explains the two databases (see vpFeatureMoment::vpFeatureMoment). For example + * vpFeatureMomentAlpha needs additional information about centered moments + * vpMomentCentered AND their interaction matrices obtained by + * vpFeatureMomentCentered in order to compute the moment's value from a + * vpMomentObject. Like the vpMomentCentered is stored in a vpMomentDatabase, the + * vpFeatureMomentCentered should be stored in a vpFeatureMomentDatabase. + * + * All moment features in a database can access each other freely at any time. + * They can also verify if a moment feature is present in the database or not. + * This code illustrates the use of both databases to handle dependencies + * between moment primitives and moment features: + * + * \code + * #include + * + * #include + * #include + * #include + * #include + * #include + * #include + * + * #include + * #include + * #include + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * try { + * + * vpPoint p; + * std::vector vec_p; // vector that contains the vertices + * + * p.set_x(1); p.set_y(1); // coordinates in meters in the image plane (vertex 1) + * vec_p.push_back(p); + * p.set_x(2); p.set_y(2); // coordinates in meters in the image plane (vertex 2) + * vec_p.push_back(p); + * + * //////////////////////////////REFERENCE VALUES//////////////////////////////// + * vpMomentObject obj(6); // Init object of order 6 because we are + * // computing C-invariants + * obj.setType(vpMomentObject::DISCRETE); // Discrete mode for object + * obj.fromVector(vec_p); + * + * vpMomentDatabase mdb; // database for moment primitives. This will + * // only contain the basic moment. + * vpMomentCentered mc; // Centered moment + * vpMomentBasic bm; // Basic moment + * vpMomentGravityCenter gc; // gravity center + * vpMomentCInvariant ci; // C-type invariant + * + * bm.linkTo(mdb); //add basic moment to moment database + * mc.linkTo(mdb); //add centered moment to moment database + * gc.linkTo(mdb); //add gravity center to moment database + * ci.linkTo(mdb); //add C-invariant to moment database + * + * vpFeatureMomentDatabase fmdb; // feature moment database to store + * // feature dependencies + * + * // Declare and link moments to database + * vpFeatureMomentBasic fmb(mdb,0.,0.,1.,&fmdb); fmb.linkTo(fmdb); + * vpFeatureMomentCentered fmc(mdb,0.,0.,1.,&fmdb); fmc.linkTo(fmdb); + * vpFeatureMomentCInvariant fci(mdb,0.,0.,1.,&fmdb); fci.linkTo(fmdb); + * + * // update the whole moment database + * mdb.updateAll(obj); + * + * // Compute moments in the correct order with the object + * bm.compute(); + * gc.compute(); + * mc.compute(); + * ci.compute(); + * + * // update the whole feature moment database with a plane + * fmb.update(0.,0.,1.); + * fmc.update(0.,0.,1.); + * fci.update(0.,0.,1.); + * + * std::cout << fci.interaction(vpFeatureMomentCInvariant::selectC1()) << std::endl; + * } + * catch(const vpException &e){ + * std::cout << e.getMessage() << std::endl; + * } + * + * return 0; + * } + * \endcode +*/ +class VISP_EXPORT vpFeatureMomentDatabase +{ +private: + struct vpCmpStr_t + { + bool operator()(const std::string &a, const std::string &b) const { return std::strcmp(a.c_str(), b.c_str()) < 0; } + std::string operator=(const std::string) { return std::string(); } // Only to avoid a warning under Visual with /Wall flag + }; + std::map featureMomentsDataBase; + void add(vpFeatureMoment &featureMoment, const std::string &name); + +public: + /*! + * Default constructor. + */ + vpFeatureMomentDatabase() : featureMomentsDataBase() { } + + /*! + * Virtual destructor that does nothing. + */ + virtual ~vpFeatureMomentDatabase() { } + + virtual void updateAll(double A = 0.0, double B = 0.0, double C = 1.0); + + vpFeatureMoment &get(const std::string &feature_name, bool &found); + + // friend VISP_EXPORT std::ostream & operator<<(std::ostream& os, const + // vpFeatureMomentDatabase& m); + friend class vpFeatureMoment; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentGravityCenter.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentGravityCenter.h new file mode 100644 index 00000000..2b8181cd --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentGravityCenter.h @@ -0,0 +1,266 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Implementation for all supported moment features. + */ + +/*! + * \file vpFeatureMomentGravityCenter.h + * \brief Implementation of the interaction matrix computation for + * vpMomentGravityCenter. + */ +#ifndef _vpFeatureMomentGravityCenter_h_ +#define _vpFeatureMomentGravityCenter_h_ + +#include +#include + +BEGIN_VISP_NAMESPACE +#ifdef VISP_MOMENTS_COMBINE_MATRICES +class vpMomentDatabase; +/*! + * \class vpFeatureMomentGravityCenter + * + * \ingroup group_visual_features + * + * \brief Functionality computation for gravity center moment feature. Computes + * the interaction matrix associated with vpMomentGravityCenter. + * + * The interaction matrix for the is defined in \cite Tahri05z, equation (16). + * It allows to compute the interaction matrices for \f$ (x_g,y_g) \f$. + * + * These interaction matrices may be selected afterwards by calling + * vpFeatureMomentGravityCenter::interaction(). The selection is done by the + * following methods: vpFeatureMomentGravityCenter::selectXg for \f$ L_{x_{g}} + * \f$ and vpFeatureMomentGravityCenter::selectYg for \f$ L_{y_{g}} \f$. The + * following code demonstrates a selection of \f$ L_{y_{g}} \f$: + * + * \code + * #include + * #include + * #include + * #include + * #include + * #include + * #include + * #include + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpPoint p; + * std::vector vec_p; // vector that contains the vertices + * + * p.set_x(1); p.set_y(1); // coordinates in meters in the image plane (vertex 1) + * vec_p.push_back(p); + * p.set_x(2); p.set_y(2); // coordinates in meters in the image plane (vertex 2) + * vec_p.push_back(p); + * + * //////////////////////////////REFERENCE VALUES//////////////////////////////// + * vpMomentObject obj(2); // Init object of order 2 because we need + * // vpFeatureMomentBasic of order 1 (for vpFeatureMomentGravityCenter) which + * // implies third-order moment primitives + * obj.setType(vpMomentObject::DISCRETE); // Discrete mode for object + * obj.fromVector(vec_p); + * + * vpMomentDatabase mdb; //database for moment primitives. This will + * //only contain the basic moment. + * vpMomentBasic bm; //basic moment (this particular moment is nothing + * //more than a shortcut to the vpMomentObject) + * vpMomentGravityCenter gc; //gravity center + * + * bm.linkTo(mdb); //add basic moment to moment database + * gc.linkTo(mdb); //add gravity center to moment database + * + * vpFeatureMomentDatabase fmdb; //feature moment database to store + * //feature dependencies + * + * //Declare and link moments to database + * vpFeatureMomentBasic fmb(mdb,0.,0.,1.,&fmdb); fmb.linkTo(fmdb); + * vpFeatureMomentGravityCenter fgc(mdb,0.,0.,1.,&fmdb); fgc.linkTo(fmdb); + * + * //update and compute the vpMomentBasic before computing vpMomentGravityCenter + * bm.update(obj); + * bm.compute(); + * //update and compute the vpMomentGravityCenter before computing vpFeatureMomentBasic + * gc.update(obj); + * gc.compute(); + * + * fmb.update(0.,0.,1.); //update the vpFeatureMoment with a plane + * //configuration and compute interaction matrix + * + * fgc.update(0.,0.,1.); //update the plane configuration for gravity + * //center feature and compute it's associated matrix. + * + * std::cout << fgc.interaction(1 << 1) << std::endl; + * + * return 0; + * } + * \endcode + * + * This code produces the following output: + * \code + * 0 -1 1.5 3.5 -2.5 -1.5 + * \endcode + * + * You can also use the shortcut selectors + * vpFeatureMomentGravityCenter::selectXg or + * vpFeatureMomentGravityCenter::selectYg as follows: + * + * \code + * task.addFeature(db_src.getFeatureGravityNormalized(), db_dst.getFeatureGravityNormalized(), + * vpFeatureMomentGravityCenter::selectXg() | vpFeatureMomentGravityCenter::selectYg()); + * \endcode + * This feature depends on: + * - vpFeatureMomentBasic + * + * Minimum vpMomentObject order needed to compute this feature: 2. +*/ +class VISP_EXPORT vpFeatureMomentGravityCenter : public vpFeatureMoment +{ +public: + /*! + * Initializes the feature with information about the database of moment + * primitives, the object plane and feature database. + * \param database : Moment database. The database of moment primitives (first parameter) is mandatory. + * It is used to access different moment values later used to compute the final matrix. + * \param A_ : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param B_ : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param C_ : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param featureMoments : Feature database. + */ + vpFeatureMomentGravityCenter(vpMomentDatabase &database, double A_, double B_, double C_, + vpFeatureMomentDatabase *featureMoments = nullptr) + : vpFeatureMoment(database, A_, B_, C_, featureMoments, 2) + { } + + void compute_interaction() VP_OVERRIDE; + + /*! + * Associated moment name. + */ + const std::string momentName() const VP_OVERRIDE { return "vpMomentGravityCenter"; } + + /*! + * Feature name. + */ + const std::string name() const VP_OVERRIDE { return "vpFeatureMomentGravityCenter"; } + + /*! + * Shortcut selector for \f$x_g\f$. + */ + static unsigned int selectXg() { return 1 << 0; } + + /*! + * Shortcut selector for \f$y_g\f$. + */ + static unsigned int selectYg() { return 1 << 1; } +}; + +#else +class vpMomentDatabase; +/*! + * \class vpFeatureMomentGravityCenter + * + * \ingroup group_visual_features + * + * \brief Functionality computation for gravity center moment feature. Computes + * the interaction matrix associated with vpMomentGravityCenter. + * + * The interaction matrix for the is defined in \cite Tahri05z, equation (16). + * It allows to compute the interaction matrices for \f$ (x_g,y_g) \f$. + * + * These interaction matrices may be selected afterwards by calling + * vpFeatureMomentGravityCenter::interaction(). The selection is done by the + * following methods: vpFeatureMomentGravityCenter::selectXg for \f$ L_{x_{g}} + * \f$ and vpFeatureMomentGravityCenter::selectYg for \f$ L_{y_{g}} \f$. + * + * You can use the selectors vpFeatureMomentGravityCenter::selectXg or + * vpFeatureMomentGravityCenter::selectYg as follows: + * + * \code + * task.addFeature(db_src.getFeatureGravityNormalized(), db_dst.getFeatureGravityNormalized(), + * vpFeatureMomentGravityCenter::selectXg() | vpFeatureMomentGravityCenter::selectYg()); + * \endcode + * This feature depends on: + * - vpMomentCentered + * - vpMomentGravityCenter + * + * Minimum vpMomentObject order needed to compute this feature: 2. + */ +class VISP_EXPORT vpFeatureMomentGravityCenter : public vpFeatureMoment +{ +public: + /*! + * Initializes the feature with information about the database of moment + * primitives, the object plane and feature database. + * \param data_base : Moment database. The database of moment primitives (first parameter) is mandatory. + * It is used to access different moment values later used to compute the final matrix. + * \param A_ : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param B_ : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param C_ : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param featureMoments : Feature database. + */ + vpFeatureMomentGravityCenter(vpMomentDatabase &data_base, double A_, double B_, double C_, + vpFeatureMomentDatabase *featureMoments = nullptr) + : vpFeatureMoment(data_base, A_, B_, C_, featureMoments, 2) + { } + + void compute_interaction() VP_OVERRIDE; + + /*! + * Associated moment name. + */ + const std::string momentName() const VP_OVERRIDE { return "vpMomentGravityCenter"; } + + /*! + * Feature name. + */ + const std::string name() const VP_OVERRIDE { return "vpFeatureMomentGravityCenter"; } + + /*! + * Shortcut selector for \f$x_g\f$. + */ + static unsigned int selectXg() { return 1 << 0; } + + /*! + * Shortcut selector for \f$y_g\f$. + */ + static unsigned int selectYg() { return 1 << 1; } +}; +#endif +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentGravityCenterNormalized.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentGravityCenterNormalized.h new file mode 100644 index 00000000..183dafbb --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureMomentGravityCenterNormalized.h @@ -0,0 +1,285 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Implementation for all supported moment features. + */ + +/*! + * \file vpFeatureMomentGravityCenterNormalized.h + * \brief Implementation of the interaction matrix computation for + * vpMomentGravityCenterNormalized. + */ +#ifndef _vpFeatureMomentGravityCenterNormalized_h_ +#define _vpFeatureMomentGravityCenterNormalized_h_ + +#include +#include + +BEGIN_VISP_NAMESPACE +#ifdef VISP_MOMENTS_COMBINE_MATRICES +class vpMomentDatabase; +/*! + * \class vpFeatureMomentGravityCenterNormalized + * + * \ingroup group_visual_features + * + * \brief Functionality computation for centered and normalized moment feature. + * Computes the interaction matrix associated with + * vpMomentGravityCenterNormalized. + * + * The interaction matrix for the moment feature can be deduced from \cite + * Tahri05z, equation (19). To do so, one must derive it and obtain a + * combination of interaction matrices by using (1). It allows to compute the + * interaction matrices for \f$ (x_n,y_n) \f$. + * + * These interaction matrices may be selected afterwards by calling + * vpFeatureMomentGravityCenterNormalized::interaction. The selection is done + * by the following methods: vpFeatureMomentGravityCenterNormalized::selectXn + * for \f$ L_{x_{n}} \f$ and vpFeatureMomentGravityCenterNormalized::selectYn + * for \f$ L_{y_{n}} \f$. You can use these shortcut selectors as follows: + * + * \code + * task.addFeature(db_src.getFeatureGravityNormalized(), db_dst.getFeatureGravityNormalized(), + * vpFeatureMomentGravityCenterNormalized::selectXn() | vpFeatureMomentGravityCenterNormalized::selectYn()); + * \endcode + * + * The behaviour of this feature is very similar to + * vpFeatureMomentGravityCenter which also contains a sample code demonstrating + * a selection. + * + * This feature is often used in moment-based visual servoing to control the + * planar translation parameters. + * + * Minimum vpMomentObject order needed to compute this feature: 2 in dense mode + * and 3 in discrete mode. + * + * This feature depends on: + * - vpFeatureMomentGravityCenter + * - vpMomentGravityCenter + * - vpMomentAreaNormalized + * - vpFeatureMomentAreaNormalized +*/ +class VISP_EXPORT vpFeatureMomentGravityCenterNormalized : public vpFeatureMoment +{ +public: + /*! + * Initializes the feature with information about the database of moment + * primitives, the object plane and feature database. + * \param database : Moment database. The database of moment primitives (first parameter) is mandatory. + * It is used to access different moment values later used to compute the final matrix. + * \param A_ : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param B_ : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param C_ : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param featureMoments : Feature database. + */ + vpFeatureMomentGravityCenterNormalized(vpMomentDatabase &database, double A_, double B_, double C_, + vpFeatureMomentDatabase *featureMoments = nullptr) + : vpFeatureMoment(database, A_, B_, C_, featureMoments, 2) + { } + + void compute_interaction() VP_OVERRIDE; + + /*! + * Associated moment name. + */ + const std::string momentName() const VP_OVERRIDE { return "vpMomentGravityCenterNormalized"; } + + /*! + * Feature name. + */ + const std::string name() const VP_OVERRIDE { return "vpFeatureMomentGravityCenterNormalized"; } + + /*! + * Shortcut selector for \f$x_n\f$. + */ + static unsigned int selectXn() { return 1 << 0; } + + /*! + * Shortcut selector for \f$y_n\f$. + */ + static unsigned int selectYn() { return 1 << 1; } +}; + +#else +class vpMomentDatabase; +/*! + * \class vpFeatureMomentGravityCenterNormalized + * + * \ingroup group_visual_features + * + * \brief Functionality computation for centered and normalized moment feature. + * Computes the interaction matrix associated with vpMomentGravityCenterNormalized. + * + * It computes the interaction matrices for \f$ (x_n,y_n) \f$. + * The interaction matrix for the moment feature has the following expression: + * - In the discrete case: + * \f[ + * L_{x_n} = + * { + * \left[ + * \begin{array}{c} + * -Ax_{{n}}\theta+ \left( x_{{n}}e_{{1,1}}-y_{{n}} \right) B-a_{{n}}C \\ + * \noalign{\medskip}Ax_{{n}}e_{{1,1}}+Bx_{{n}} \theta \\ + * \noalign{\medskip} + * \left( + * -a_{{n}}-w_{{y}} + * \right) + * A+Bw_{{x}} \\ + * \noalign{\medskip}a_{{n}}e_{{1,1}}{\it NA}+ \left( \eta_{{1,0}}e_{{1,1}}+\eta_{{0,1}}-e_{{2,1}}-x_{{g}}e_{{1,1}}+\eta_{{0,1}}\theta \right) x_{{n}}+ \left( \eta_{{1,0}}-x_{{g}}\theta \right) y_{{n}}-{\frac {x_{{n}}\eta_{{0,3}}}{{\it NA}}} \\ + * \noalign{\medskip} \left( -1+\theta \right) a_{{n}}{\it NA}+ \left( e_{{1,2}}+x_{{g}}-\eta_{{0,1}}e_{{1,1}}-2\,\eta_{{1,0}}+e_{{3,0}}+ \left( -x_{{g}}+\eta_{{1,0}} \right) \theta \right) x_{{n}}+e_{{1,1}}x_{{g}}y_{{n}}-a_{{n}} \\ + * \noalign{\medskip}y_{{n}} + * \end{array} + * \right] + * }^t + * \f] + * + * \f[ + * L_{y_n} = + * { + * \left[ + * \begin{array}{c} + * \left( 1-\theta \right) y_{{n}}A+y_{{n}}e_{{1,1}}B \\ + * \noalign{\medskip} \left( -x_{{n}}+y_{{n}}e_{{1,1}} + * \right) + * A+ \left( -1+\theta \right) y_{{n}}B-a_{{n}}C \\ + * \noalign{\medskip}-Aw_{{y}}+ \left( -a_{{n}}+w_{{x}} \right) B \\ + * \noalign{\medskip}\theta\,a_{{n}}{\it NA}+ + * \left( -e_{{2,1}}+\eta_{{1,0}}e_{{1,1}}+\eta_{{0,1}}-x_{{g}}e_{{1,1}}+ \left( \eta_{{0,1}}-y_{{g}} \right) \theta + * \right) y_{{n}}+a_{{n}}-{\frac {y_{{n}}\eta_{{0,3}}}{{\it NA}}} \\ + * \noalign{\medskip}-a_{{n}}e_{{1,1}}{\it NA}-x_{{n}}\eta_{{0,1}}+\left( e_{{1,2}}+y_{{g}}e_{{1,1}}-\eta_{{0,1}}e_{{1,1}}+x_{{g}}+e_{{3,0}}-2\,\eta_{{1,0}}+ \left( -x_{{g}}+\eta_{{1,0}} \right) \theta + * \right) y_{{n}} \\ + * \noalign{\medskip}-x_{{n}} + * \end{array} + * \right] + * }^t + * \f] + * + * - In the dense case: + * \f[ + * L_{x_n} = + * { + * \left[ + * \begin {array}{c} -a_{{n}}C-1/2\,Ax_{{n}}-By_{{n}} \\ + * \noalign{\medskip}1/2\,Bx_{{n}} \\ + * \noalign{\medskip} \left( -a_{{n}}-w_{{y}} \right) A+Bw_{{x}} \\ + * \noalign{\medskip} \left( 4\,\eta_{{1,0}}-1/2\,x_{{g}} \right) y_{{n}}+4\,a_{{n}}\eta_{{1,1}}+4\,x_{{n}}\eta_{{0,1}} \\ + * \noalign{\medskip} \left( -4\,\eta_{{1,0}}+1/2\,x_{{g}} \right) x_{{n}}+ \left( -1-4\,\eta_{{2,0}} \right) a_{{n}} \\ + * \noalign{\medskip}y_{{n}}\end {array} + * \right] + * }^t + * L_{y_n} = + * { + * \left[ + * \begin {array}{c} + * 1/2\,Ay_{{n}} \\ + * \noalign{\medskip}-1/2\,By_{{n}}-a_{{n}}C-Ax_{{n}} \\ + * \noalign{\medskip}-Aw_{{y}}+ \left( -a_{{n}}+w_{{x}} \right) B \\ + * \noalign{\medskip}4\,\theta\,a_{{n}}{\it NA}+ \left( 4\,\eta_{{0,1}}-1/2\,y_{{g}} \right) y_{{n}}+a_{{n}} \\ + * \noalign{\medskip} \left( -4\,\eta_{{1,0}}+1/2\,x_{{g}} \right) y_{{n}}-4\,a_{{n}}\eta_{{1,1}}-4\,x_{{n}}\eta_{{0,1}} \\ + * \noalign{\medskip}-x_{{n}} + * \end {array} + * \right] + * }^t + * \f] + * with: + * - \f$e_{i,j}=\frac{\mu_{i,j}}{NA}\f$ + * - \f$NA=\mu_{2,0}+\mu_{0,2}\f$ + * - \f$\theta=\frac{\eta_{0,2}}{NA}\f$ + * - \f$\eta\f$ is the centered and normalized moment. + * + * These interaction matrices may be selected afterwards by calling + * vpFeatureMomentGravityCenterNormalized::interaction. The selection is done by + * the following methods: vpFeatureMomentGravityCenterNormalized::selectXn for + * \f$ L_{x_{n}} \f$ and vpFeatureMomentGravityCenterNormalized::selectYn for \f$L_{y_{n}} \f$. + * You can use these shortcut selectors as follows: + * + * \code + * task.addFeature(db_src.getFeatureGravityNormalized(),db_dst.getFeatureGravityNormalized(), + * vpFeatureMomentGravityCenterNormalized::selectXn() | vpFeatureMomentGravityCenterNormalized::selectYn()); + * \endcode + * + * The behaviour of this feature is very similar to + * vpFeatureMomentGravityCenter which also contains a sample code demonstrating a + * selection. + * + * This feature is often used in moment-based visual servoing to control the + * planar translation parameters. + * + * Minimum vpMomentObject order needed to compute this feature: 2 in dense mode + * and 3 in discrete mode. + * + * This feature depends on: + * - vpFeatureMomentGravityCenter + * - vpMomentGravityCenter + * - vpMomentAreaNormalized + * - vpFeatureMomentAreaNormalized + */ +class VISP_EXPORT vpFeatureMomentGravityCenterNormalized : public vpFeatureMoment +{ +public: + /*! + * Initializes the feature with information about the database of moment + * primitives, the object plane and feature database. + * \param data_base : Moment database. The database of moment primitives (first parameter) is mandatory. + * It is used to access different moment values later used to compute the final matrix. + * \param A_ : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param B_ : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param C_ : Plane coefficient in a \f$ A \times x+B \times y + C = \frac{1}{Z} \f$ plane. + * \param featureMoments : Feature database. + */ + vpFeatureMomentGravityCenterNormalized(vpMomentDatabase &data_base, double A_, double B_, double C_, + vpFeatureMomentDatabase *featureMoments = nullptr) + : vpFeatureMoment(data_base, A_, B_, C_, featureMoments, 2) + { } + void compute_interaction() VP_OVERRIDE; + + /*! + * Associated moment name. + */ + const std::string momentName() const VP_OVERRIDE { return "vpMomentGravityCenterNormalized"; } + + /*! + * Feature name. + */ + const std::string name() const VP_OVERRIDE { return "vpFeatureMomentGravityCenterNormalized"; } + + /*! + * Shortcut selector for \f$x_n\f$. + */ + static unsigned int selectXn() { return 1 << 0; } + + /*! + * Shortcut selector for \f$y_n\f$. + */ + static unsigned int selectYn() { return 1 << 1; } +}; +#endif +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeaturePoint.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeaturePoint.h new file mode 100644 index 00000000..d8d90af2 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeaturePoint.h @@ -0,0 +1,242 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * 2D point visual feature. + */ + +/*! + * \file vpFeaturePoint.h + * \brief Class that defines 2D point visual feature + */ + +#ifndef vpFeaturePoint_H +#define vpFeaturePoint_H + +#include +#include +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpFeaturePoint + * \ingroup group_visual_features + * + * \brief Class that defines a 2D point visual feature \f$ s\f$ which + * is composed by two parameters that are the cartesian coordinates \f$ + * x \f$ and \f$ y \f$. + * + * In this class \f$ x \f$ and \f$ y \f$ are the 2D coordinates in the + * image plane and are given in meter. \f$ Z \f$ which is the 3D + * coordinate representing the depth is also a parameter of the + * point. It is needed during the computation of the interaction matrix + * \f$ L \f$. + * + * The visual features can be set easily from an instance of the + * classes vpPoint, vpDot or vpDot2. For more precision see the + * vpFeatureBuilder class. + * + * Once the values of the visual features are set, the interaction() + * method allows to compute the interaction matrix \f$ L \f$ associated + * to the visual feature, while the error() method computes the error + * vector \f$(s - s^*)\f$ between the current visual feature and the + * desired one. + * + * The code below shows how to create a eye-in hand visual servoing + * task using a 2D point feature \f$(x,y)\f$ that correspond to the 2D + * coordinates of a point in the image plane. To control six degrees + * of freedom, at least four other features must be considered like two + * other point features for example. First we create a current + * (\f$s\f$) 2D point feature. Then we set the task to use the + * interaction matrix associated to the current feature \f$L_s\f$. And + * finally we compute the camera velocity \f$v=-\lambda \; L_s^+ \; + * (s-s^*)\f$. The current feature \f$s\f$ is updated in the while() + * loop. + * + * \code + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpServo task; // Visual servoing task + * + * vpFeaturePoint sd; //The desired point feature. + * // Set the desired features x and y + * double xd = 0; + * double yd = 0; + * // Set the depth of the point in the camera frame. + * double Zd = 1; + * // Set the point feature thanks to the desired parameters. + * sd.buildFrom(xd, yd, Zd); + * + * vpFeaturePoint s; //The current point feature. + * // Set the current features x and y + * double x; // You have to compute the value of x. + * double y; // You have to compute the value of y. + * double Z; // You have to compute the value of Z. + * // Set the point feature thanks to the current parameters. + * s.buildFrom(x, y, Z); + * // In this case the parameter Z is not necessary because the interaction matrix is computed + * // with the desired visual feature. + * + * // Set eye-in-hand control law. + * // The computed velocities will be expressed in the camera frame + * task.setServo(vpServo::EYEINHAND_CAMERA); + * // Interaction matrix is computed with the desired visual features sd + * task.setInteractionMatrixType(vpServo::DESIRED); + * + * // Add the 2D point feature to the task + * task.addFeature(s, sd); + * + * // Control loop + * for ( ; ; ) { + * // The new parameters x and y must be computed here. + * + * // Update the current point visual feature + * s.buildFrom(x, y, Z); + * + * // Compute the control law + * vpColVector v = task.computeControlLaw(); // camera velocity + * } + * return 0; + * } + * \endcode + * + * If you want to build your own control law, this other example shows how + * to create a current (\f$s\f$) and desired (\f$s^*\f$) 2D point visual + * feature, compute the corresponding error vector \f$(s-s^*)\f$ and finally + * build the interaction matrix \f$L_s\f$. + * + * \code + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpFeaturePoint sd; //The desired point feature. + * // Set the desired features x and y + * double xd = 0; + * double yd = 0; + * // Set the depth of the point in the camera frame. + * double Zd = 1; + * // Set the point feature thanks to the desired parameters. + * sd.buildFrom(xd, yd, Zd); + * + * vpFeaturePoint s; //The current point feature. + * // Set the current features x and y + * double x; // You have to compute the value of x. + * double y; // You have to compute the value of y. + * double Z; // You have to compute the value of Z. + * // Set the point feature thanks to the current parameters. + * s.buildFrom(x, y, Z); + * + * // Compute the interaction matrix L_s for the current point feature + * vpMatrix L = s.interaction(); + * // You can also compute the interaction matrix L_s for the desired point feature + * // The corresponding line of code is : vpMatrix L = sd.interaction(); + * + * // Compute the error vector (s-sd) for the point feature + * s.error(s_star); + * } + * \endcode + * + *

Tutorials & Examples

+ * + * Tutorials
+ * An other fully explained example is given in:
+ * + * - \ref tutorial-ibvs. +*/ +class VISP_EXPORT vpFeaturePoint : public vpBasicFeature +{ +private: + //! FeaturePoint depth (required to compute the interaction matrix) + //! default Z = 1m + double Z; + +public: + vpFeaturePoint(); + + vpFeaturePoint &buildFrom(const double &x, const double &y, const double &Z); + + void display(const vpCameraParameters &cam, const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1) const VP_OVERRIDE; + void display(const vpCameraParameters &cam, const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1) const VP_OVERRIDE; + + vpFeaturePoint *duplicate() const VP_OVERRIDE; + + vpColVector error(const vpBasicFeature &s_star, unsigned int select = FEATURE_ALL) VP_OVERRIDE; + + double get_x() const; + + double get_y() const; + + double get_Z() const; + + void init() VP_OVERRIDE; + vpMatrix interaction(unsigned int select = FEATURE_ALL) VP_OVERRIDE; + void print(unsigned int select = FEATURE_ALL) const VP_OVERRIDE; + + void set_x(double x); + + void set_y(double y); + + void set_Z(double Z); + + void set_xyZ(double x, double y, double Z); + + // feature selection + static unsigned int selectX(); + static unsigned int selectY(); + + /*! + @name Deprecated functions + */ + typedef enum + { + X = 1, // x coordinates + Y = 2 // y coordinates + } vpFeaturePointType; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeaturePoint3D.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeaturePoint3D.h new file mode 100644 index 00000000..357d6f7c --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeaturePoint3D.h @@ -0,0 +1,268 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * 3D point visual feature. + */ + +/*! + * \file vpFeaturePoint3D.h + * \brief class that defines the 3D point visual feature. + */ + +#ifndef vpFeaturePoint3d_H +#define vpFeaturePoint3d_H + +#include +#include +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpFeaturePoint3D + * \ingroup group_visual_features + * \brief Class that defines the 3D point visual feature. + * + * A 3D point visual feature corresponds to a 3D point with \f$ + * {\bf X} = (X,Y,Z)\f$ coordinates in the camera frame. + * + * This class is intended to manipulate the 3D point visual feature + * \f$ s = (X,Y,Z) \f$. The interaction matrix related to \f$ s \f$ is given + * by: \f[ L = \left[ \begin{array}{rrrrrr} + * -1 & 0 & 0 & 0 & -Z & Y \\ + * 0 & -1 & 0 & Z & 0 & -X \\ + * 0 & 0 & -1 & -Y & X & 0 \\ + * \end{array} + * \right] + * \f] + * + * Two ways are allowed to initialize the feature. + * + * - The first way by setting the feature values \f$(X,Y,Z)\f$ using + * vpFeaturePoint3D member functions like set_X(), set_Y(), set_Z(), + * or also build(). + * + * - The second by using the feature builder functionalities to + * initialize the feature from a point structure like + * vpFeatureBuilder::create (vpFeaturePoint3D &, const vpPoint &). + * + * The interaction() method allows to compute the interaction matrix + * \f$ L\f$ associated to the 3D point visual feature, while the + * error() method computes the error vector \f$ (s - s^*)\f$ between the + * current visual feature and the desired one. + * + * The code below shows how to create a eye-in hand visual servoing + * task using a 3D point feature \f$(X,Y,Z)\f$ that correspond to the + * 3D point coordinates in the camera frame. To control six degrees of + * freedom, at least three other features must be considered like + * vpFeatureThetaU visual features. First we create a current (\f$s\f$) + * and desired (\f$s^*\f$) 3D point feature, set the task to use the + * interaction matrix associated to the desired feature \f$L_{s^*}\f$ + * and than compute the camera velocity \f$v=-\lambda \; {L_{s^*}}^+ \; + * (s-s^*)\f$. The current feature \f$s\f$ is updated in the while() + * loop while \f$s^*\f$ is set to \f$Z^*=1\f$. + * + * \code + * #include + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpServo task; // Visual servoing task + * + * // Set the 3D point coordinates in the object frame: oP + * vpPoint point(0.1, -0.1, 0); + * + * vpHomogeneousMatrix cMo; // Pose between the camera and the object frame + * cMo.buildFrom(0, 0, 1.2, 0, 0, 0); + * // ... cMo need here to be computed from a pose estimation + * + * point.changeFrame(cMo); // Compute the 3D point coordinates in the camera frame cP = cMo * oP + * + * // Creation of the current feature s + * vpFeaturePoint3D s; + * s.buildFrom(point); // Initialize the feature from the 3D point coordinates in the camera frame: s=(X,Y,Z) + * s.print(); + * + * // Creation of the desired feature s*. + * vpFeaturePoint3D s_star; + * s_star.buildFrom(0, 0, 1); // Z*=1 meter + * s_star.print(); + * + * // Set eye-in-hand control law. + * // The computed velocities will be expressed in the camera frame + * task.setServo(vpServo::EYEINHAND_CAMERA); + * // Interaction matrix is computed with the desired visual features s* + * task.setInteractionMatrixType(vpServo::DESIRED); + * // Set the constant gain + * double lambda = 0.8; + * task.setLambda(lambda); + * + * // Add the 3D point feature to the task + * task.addFeature(s, s_star); + * + * // Control loop + * for ( ; ; ) { + * // ... cMo need here to be estimated from for example a pose estimation. + * point.changeFrame(cMo); // Compute the 3D point coordinates in the camera frame cP = cMo * oP + * + * // Update the current 3D point visual feature + * s.buildFrom(point); + * + * // compute the control law + * vpColVector v = task.computeControlLaw(); // camera velocity + * } + * } + * \endcode + * + * If you want to deal only with the \f$(X,Y)\f$ subset feature from the 3D + * point feature, you have just to modify the addFeature() call in + * the previous example by the following line. In that case, the dimension + * of \f$s\f$ is two. + * + * \code + * // Add the (X,Y) subset feature from the 3D point visual feature to the task + * task.addFeature(s, s_star, vpFeaturePoint3D::selectX() | vpFeaturePoint3D::selectY()); + * \endcode + * + * If you want to build your own control law, this other example shows + * how to create a current (\f$s\f$) and desired (\f$s^*\f$) 3D + * point visual feature, compute the corresponding error + * vector \f$(s-s^*)\f$ and finally build the interaction matrix \f$L_s\f$. + * + * \code + * #include + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * // Set the 3D point coordinates in the object frame: oP + * vpPoint point(0.1, -0.1, 0); + * + * vpHomogeneousMatrix cMo; // Pose between the camera and the object frame + * cMo.buildFrom(0, 0, 1.2, 0, 0, 0); + * // ... cMo need here to be computed from a pose estimation + * + * point.changeFrame(cMo); // Compute the 3D point coordinates in the camera frame cP = cMo * oP + * + * // Creation of the current feature s + * vpFeaturePoint3D s; + * s.buildFrom(point); // Initialize the feature from the 3D point coordinates in the camera frame + * s.print(); + * + * // Creation of the desired feature s*. + * vpFeaturePoint3D s_star; + * s_star.buildFrom(0, 0, 1); // Z*=1 meter + * s_star.print(); + * + * // Compute the L_s interaction matrix associated to the current feature + * vpMatrix L = s.interaction(); + * std::cout << "L: " << L << std::endl; + * + * // Compute the error vector (s-s*) for the 3D point feature + * vpColVector e = s.error(s_star); // e = (s-s*) + * + * std::cout << "e: " << e << std::endl; + * } + * \endcode +*/ +class VISP_EXPORT vpFeaturePoint3D : public vpBasicFeature +{ +public: + // basic constructor + vpFeaturePoint3D(); + + /* + * Set coordinates + */ + + // build feature from a point (vpPoint) + vpFeaturePoint3D &buildFrom(const vpPoint &p); + // set the point XY and Z-coordinates + vpFeaturePoint3D &buildFrom(const double &X, const double &Y, const double &Z); + + void display(const vpCameraParameters &cam, const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1) const VP_OVERRIDE; + void display(const vpCameraParameters &cam, const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1) const VP_OVERRIDE; + + // feature duplication + vpFeaturePoint3D *duplicate() const VP_OVERRIDE; + + // compute the error between two visual features from a subset + // a the possible features + vpColVector error(const vpBasicFeature &s_star, unsigned int select = FEATURE_ALL) VP_OVERRIDE; + + // get the point X-coordinates + double get_X() const; + // get the point Y-coordinates + double get_Y() const; + // get the point depth (camera frame) + double get_Z() const; + + // basic construction + void init() VP_OVERRIDE; + // compute the interaction matrix from a subset a the possible features + vpMatrix interaction(unsigned int select = FEATURE_ALL) VP_OVERRIDE; + + // print the name of the feature + void print(unsigned int select = FEATURE_ALL) const VP_OVERRIDE; + +// set the point X-coordinates + void set_X(double X); + // set the point Y-coordinates + void set_Y(double Y); + // set the point depth (camera frame) + void set_Z(double Z); + // set the point XY and Z-coordinates + void set_XYZ(double X, double Y, double Z); + + static unsigned int selectX(); + static unsigned int selectY(); + static unsigned int selectZ(); +}; +END_VISP_NAMESPACE + +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeaturePointPolar.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeaturePointPolar.h new file mode 100644 index 00000000..66839403 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeaturePointPolar.h @@ -0,0 +1,319 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * 2D point with polar coordinates visual feature. + */ + +/*! + * \file vpFeaturePointPolar.h + * \brief Class that defines a 2D point visual feature with polar coordinates. + */ + +#ifndef vpFeaturePointPolar_H +#define vpFeaturePointPolar_H + +#include +#include +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpFeaturePointPolar + * \ingroup group_visual_features + * + * \brief Class that defines 2D image point visual feature with + * polar coordinates \f$(\rho,\theta)\f$ described in \cite Corke09a. + * + * Let us denote \f$(\rho,\theta)\f$ the polar coordinates of an image + * point, with \f$\rho\f$ the radius of the feature point with respect + * to the optical center and \f$\theta\f$ the angle. From cartesian + * coordinates \f$(x,y)\f$ of a image point, polar coordinates are + * obtained by: + * + * \f[\rho = \sqrt{x^2+y^2} \hbox{,}\; \; \theta = \arctan \frac{y}{x}\f] + * + * From polar coordinates, cartesian coordinates of the feature point + * can be obtained by: + * + * \f[x = \rho \cos\theta \hbox{,}\; \; y = \rho \sin\theta\f] + * + * This class is intended to manipulate the 2D image point visual + * feature in polar coordinates \f$ s = (\rho, \theta) \f$. The + * interaction matrix related to \f$ s \f$ is given by: + * + * \f[ + * L = \left[ + * \begin{array}{l} + * L_{\rho} \\ + * \; \\ + * L_{\theta}\\ + * \end{array} + * \right] + * = + * \left[ + * \begin{array}{cccccc} + * \frac{-\cos \theta}{Z} & \frac{-\sin \theta}{Z} & \frac{\rho}{Z} & + * (1+\rho^2)\sin\theta& -(1+\rho^2)\cos\theta & 0 \\ + * \;\\ \ + * \frac{\sin\theta}{\rho Z} & \frac{-\cos\theta}{\rho Z} & 0 & \cos\theta + * /\rho & \sin\theta/\rho & -1 \\ \end{array} \right] \f] + * + * where \f$Z\f$ is the 3D depth of the considered point in the camera frame. + * + * Two ways are allowed to initialize the feature. + * + * - The first way by setting the feature values \f$(\rho,\theta,Z)\f$ + * using vpFeaturePointPolar members like set_rho(), set_theta(), + * set_Z(), or set_rhoThetaZ(), or also build(). + * + * - The second way by using the feature builder functionalities to + * initialize the feature from a dot tracker, like + * vpFeatureBuilder::create (vpFeaturePointPolar &, const + * vpCameraParameters &, const vpDot &) or vpFeatureBuilder::create + * (vpFeaturePointPolar &, const vpCameraParameters &, const vpDot2 + * &). Be aware, that in that case only \f$(\rho,\theta)\f$ are + * initialized. You may also initialize the 3D depth \f$Z\f$. It is + * also possible to initialize the feature from a point structure, + * like vpFeatureBuilder::create(vpFeaturePointPolar &, const vpPoint + * &) or vpFeatureBuilder::create(vpFeaturePointPolar &, const + * vpCameraParameters &, const vpCameraParameters &, const vpPoint + * &). In that case all the feature parameters \f$(\rho,\theta,Z)\f$ + * would be initialized. + * + * The interaction() method allows to compute the interaction matrix + * \f$L\f$ associated to the visual feature, while the error() method + * computes the error vector \f$(s - s^*)\f$ between the current visual + * feature and the desired one. + * + * The code below shows how to create a eye-in hand visual servoing + * task using four 2D point features with polar coordinates. First we + * create four current features \f$s\f$ (p var name in the code) and + * four desired \f$s^*\f$ (pd var name in the code) point features with + * polar coordinates, set the task to use the interaction matrix + * associated to the current feature \f$L_{s}\f$ and than compute the + * camera velocity \f$v=-\lambda \; {L_{s}}^+ \; (s-s^*)\f$. The + * current feature \f$s\f$ is updated in the while() loop, while + * \f$s^*\f$ is initialized at the beginning. + * + * \code + * #include + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * // Create 4 points to specify the object of interest + * vpPoint point[4]; + * + * // Set the 3D point coordinates in the object frame: oP + * point[0].setWorldCoordinates(-0.1, -0.1, 0); + * point[1].setWorldCoordinates( 0.1, -0.1, 0); + * point[2].setWorldCoordinates( 0.1, 0.1, 0); + * point[3].setWorldCoordinates(-0.1, 0.1, 0); + * + * // Initialize the desired pose between the camera and the object frame + * vpHomogeneousMatrix cMod; + * cMod.buildFrom(0, 0, 1, 0, 0, 0); + * + * // Compute the desired position of the point + * for (int i = 0 ; i < 4 ; i++) { + * // Compute the 3D point coordinates in the camera frame cP = cMod * oP + * point[i].changeFrame(cMod); + * // Compute the perspective projection to set (x,y) + * point[i].projection(); + * } + * + * // Create 4 desired visual features as 2D points with polar coordinates + * vpFeaturePointPolar pd[4]; + * // Initialize the desired visual feature from the desired point positions + * for (int i = 0 ; i < 4 ; i++) + * vpFeatureBuilder::create(pd[i], point[i]); + * + * // Initialize the current pose between the camera and the object frame + * vpHomogeneousMatrix cMo; + * cMo.buildFrom(0, 0, 1.2, 0, 0, M_PI); + * // ... cMo need here to be computed from a pose estimation + * + * for (int i = 0 ; i < 4 ; i++) { + * // Compute the 3D point coordinates in the camera frame cP = cMo * oP + * point[i].changeFrame(cMo); + * // Compute the perspective projection to set (x,y) + * point[i].projection(); + * } + * // Create 4 current visual features as 2D points with polar coordinates + * vpFeaturePointPolar p[4]; + * // Initialize the current visual feature from the current point positions + * for (int i = 0 ; i < 4 ; i++) + * vpFeatureBuilder::create(p[i], point[i]); + * + * // Visual servo task initialization + * vpServo task; + * // - Camera is mounted on the robot end-effector and velocities are + * // computed in the camera frame + * task.setServo(vpServo::EYEINHAND_CAMERA); + * // - Interaction matrix is computed with the current visual features s + * task.setInteractionMatrixType(vpServo::CURRENT); + * // - Set the constant gain to 1 + * task.setLambda(1); + * // - Add current and desired features + * for (int i = 0 ; i < 4 ; i++) + * task.addFeature(p[i], pd[i]); + * + * // Control loop + * for ( ; ; ) { + * // ... cMo need here to be estimated from for example a pose estimation. + * // Computes the point coordinates in the camera frame and its 2D + * // coordinates in the image plane + * for (int i = 0 ; i < 4 ; i++) + * point[i].track(cMo) ; + + * // Update the current 2D point visual feature with polar coordinates + * for (int i = 0 ; i < 4 ; i++) + * vpFeatureBuilder::create(p[i], point[i]); + + * // compute the control law + * vpColVector v = task.computeControlLaw(); // camera velocity + * } + * } + * \endcode + * + * If you want to deal only with the \f$\rho\f$ subset feature from the 2D + * point feature set, you have just to modify the addFeature() call in the + * previous example by the following line. In that case, the dimension of + * \f$s\f$ is four. + * + * \code + * // Add the rho subset feature from the 2D point polar coordinates visual features + * task.addFeature(p[i], pd[i], vpFeaturePointPolar::selectRho()); + * \endcode + * + * If you want to build your own control law, this other example shows how + * to create a current (\f$s\f$) and desired (\f$s^*\f$) 2D point visual + * feature with polar coordinates, compute the corresponding error vector + * \f$(s-s^*)\f$ and finally build the interaction matrix \f$L_s\f$. + * + * \code + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * // Creation of the current feature s + * vpFeaturePointPolar s; + * // Initialize the current feature + * s.buildFrom(0.1, M_PI, 1); // rho=0.1m, theta=pi, Z=1m + * + * // Creation of the desired feature s + * vpFeaturePointPolar s_star; + * // Initialize the desired feature + * s.buildFrom(0.15, 0, 0.8); // rho=0.15m, theta=0, Z=0.8m + * + * // Compute the interaction matrix L_s for the current feature + * vpMatrix L = s.interaction(); + * + * // Compute the error vector (s-s*) for the point feature with polar coordinates + * s.error(s_star); + * + * return 0; + * } + * \endcode +*/ +class VISP_EXPORT vpFeaturePointPolar : public vpBasicFeature +{ +private: + //! FeaturePoint depth (required to compute the interaction matrix) + //! default Z = 1m + double Z; + +public: + // basic constructor + vpFeaturePointPolar(); + + vpFeaturePointPolar &buildFrom(const double &rho, const double &theta, const double &Z); + + void display(const vpCameraParameters &cam, const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1) const VP_OVERRIDE; + void display(const vpCameraParameters &cam, const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1) const VP_OVERRIDE; + + // feature duplication + vpFeaturePointPolar *duplicate() const VP_OVERRIDE; + + // compute the error between two visual features from a subset + // a the possible features + vpColVector error(const vpBasicFeature &s_star, unsigned int select = FEATURE_ALL) VP_OVERRIDE; + + // basic construction + void init() VP_OVERRIDE; + + // get the point rho-coordinates + double get_rho() const; + // get the point theta-coordinates + double get_theta() const; + // get the point depth (camera frame) + double get_Z() const; + + // compute the interaction matrix from a subset a the possible features + vpMatrix interaction(unsigned int select = FEATURE_ALL) VP_OVERRIDE; + + // print the name of the feature + void print(unsigned int select = FEATURE_ALL) const VP_OVERRIDE; + + // set the point rho-coordinates + void set_rho(double rho); + // set the point theta-coordinates + void set_theta(double theta); + // set the point depth (camera frame) + void set_Z(double Z); + // set the point rho, theta polar coordinates and Z coordinate + void set_rhoThetaZ(double rho, double theta, double Z); + + /* + vpBasicFeature method instantiation + */ + static unsigned int selectRho(); + static unsigned int selectTheta(); + +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureSegment.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureSegment.h new file mode 100644 index 00000000..9efa4a46 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureSegment.h @@ -0,0 +1,293 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2024 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Segment visual feature. + */ + +/*! + * \file vpFeatureSegment.h + * \brief class that defines the Segment visual feature + */ + +#ifndef VP_FEATURE_SEGMENT_H +#define VP_FEATURE_SEGMENT_H + +#include +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpFeatureSegment + * \ingroup group_visual_features + * + * \brief Class that defines a 2D segment visual features. + * This class allow to consider two sets of visual features: + * - the non normalized features \f${\bf s} = (x_c, y_c, l, \alpha)\f$ where + * \f$(x_c,y_c)\f$ are the coordinates of the segment center, \f$ l \f$ the + * segment length and \f$ \alpha \f$ the orientation of the segment with + * respect to the \f$ x \f$ axis. + * - or the normalized features \f${\bf s} = (x_n, y_n, l_n, \alpha)\f$ with + * \f$x_n = x_c/l\f$, \f$y_n = y_c/l\f$ and \f$l_n = 1/l\f$. + * + * The selection of the feature set is done either during construction using + * vpFeatureSegment(bool), or by setNormalized(bool). +*/ +class VISP_EXPORT vpFeatureSegment : public vpBasicFeature +{ +public: + // empty constructor + VP_EXPLICIT vpFeatureSegment(bool normalized = false); + +// change values of the segment + vpFeatureSegment &buildFrom(const double &x1, const double &y1, const double &Z1, const double &x2, const double &y2, const double &Z2); + + void display(const vpCameraParameters &cam, const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1) const VP_OVERRIDE; + void display(const vpCameraParameters &cam, const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1) const VP_OVERRIDE; + //! Feature duplication. + vpFeatureSegment *duplicate() const VP_OVERRIDE; + // compute the error between two visual features from a subset + // a the possible features + vpColVector error(const vpBasicFeature &s_star, unsigned int select = FEATURE_ALL) VP_OVERRIDE; + + /*! + * Get the x coordinate of the segment center in the image plane. + * + * \return If normalized features are used, return \f$ x_n = x_c / l \f$. + * Otherwise return \f$ x_c \f$. + */ + inline double getXc() const { return s[0]; } + + /*! + * Get the y coordinate of the segment center in the image plane. + * + * \return If normalized features are used, return \f$ y_n = y_c / l \f$. + * Otherwise return \f$ y_c \f$. + */ + inline double getYc() const { return s[1]; } + + /*! + * Get the length of the segment. + * + * \return If normalized features are used, return \f$ l_n = 1 / l \f$. + * Otherwise return \f$ l \f$. + */ + inline double getL() const { return s[2]; } + + /*! + * Get the value of \f$ \alpha \f$ which represents the orientation of + * the segment. + * + * \return The value of \f$ \alpha \f$. + */ + inline double getAlpha() const { return s[3]; } + + /*! + * Get the value of \f$ Z_1 \f$ which represents the Z coordinate in the + * camera frame of the 3D point that corresponds to the segment first point. + * + * \return The value of the depth \f$ Z_1 \f$. + */ + inline double getZ1() const { return Z1_; } + + /*! + * Get the value of \f$ Z_2 \f$ which represents the Z coordinate in the + * camera frame of the 3D point that corresponds to the segment second + * point. + * + * \return The value of the depth \f$ Z_2 \f$. + */ + inline double getZ2() const { return Z2_; } + + // Basic construction. + void init() VP_OVERRIDE; + + // compute the interaction matrix from a subset a the possible features + vpMatrix interaction(unsigned int select = FEATURE_ALL) VP_OVERRIDE; + + void print(unsigned int select = FEATURE_ALL) const VP_OVERRIDE; + + /*! + * Indicates if the normalized features are considered. + */ + bool isNormalized() { return normalized_; } + + static unsigned int selectXc(); + static unsigned int selectYc(); + static unsigned int selectL(); + static unsigned int selectAlpha(); + + /*! + * Set the king of feature to consider. + * \param normalized : If true, use normalized features \f${\bf s} = (x_n, + * y_n, l_n, \alpha)\f$. If false, use non normalized features \f${\bf s} = + * (x_c, y_c, l_c, \alpha)\f$. + */ + void setNormalized(bool normalized) { normalized_ = normalized; } + + /*! + * Set the value of the x coordinate of the segment center + * in the image plane. It is one parameter of the visual feature \f$ s \f$. + * + * \param val : Value to set, that is either equal to \f$ x_n = x_c/l \f$ + * when normalized features are considered, or equal to \f$ x_c \f$ + * otherwise. + */ + inline void setXc(double val) + { + s[0] = xc_ = val; + flags[0] = true; + } + + /*! + * Set the value of the y coordinate of the segment center + * in the image plane. It is one parameter of the visual feature \f$ s \f$. + * + * \param val : Value to set, that is either equal to \f$ y_n = y_c/l \f$ + * when normalized features are considered, or equal to \f$ y_c \f$ + * otherwise. + */ + inline void setYc(double val) + { + s[1] = yc_ = val; + flags[1] = true; + } + + /*! + * Set the value of the segment length in the image plane. It is one + * parameter of the visual feature \f$ s \f$. + * + * \param val : Value to set, that is either equal to \f$l_n= 1/l \f$ when + * normalized features are considered, or equal to \f$ l \f$ otherwise. + */ + inline void setL(double val) + { + s[2] = l_ = val; + flags[2] = true; + } + + /*! + * Set the value of \f$ \alpha \f$ which represents the orientation of the + * segment in the image plane. It is one parameter of the visual feature \f$ + * s \f$. + * + * \param val : \f$ \alpha \f$ value to set. + */ + inline void setAlpha(double val) + { + s[3] = alpha_ = val; + cos_a_ = cos(val); + sin_a_ = sin(val); + flags[3] = true; + } + + /*! + * Set the value of \f$ Z_1 \f$ which represents the Z coordinate in the + * camera frame of the 3D point that corresponds to the segment first point. + * + * This value is requested to compute the interaction matrix. + * + * \param val : \f$ Z_1 \f$ value to set. + * + * \exception vpFeatureException::badInitializationError : If Z1 is behind + * the camera or equal to zero. + */ + inline void setZ1(double val) + { + Z1_ = val; + + if (Z1_ < 0) { + vpERROR_TRACE("Point is behind the camera "); + std::cout << "Z1 = " << Z1_ << std::endl; + + throw(vpFeatureException(vpFeatureException::badInitializationError, "Point Z1 is behind the camera ")); + } + + if (fabs(Z1_) < 1e-6) { + vpERROR_TRACE("Point Z1 coordinates is null "); + std::cout << "Z1 = " << Z1_ << std::endl; + + throw(vpFeatureException(vpFeatureException::badInitializationError, "Point Z1 coordinates is null")); + } + + flags[4] = true; + } + + /*! + * Set the value of \f$ Z_2 \f$ which represents the Z coordinate in the + * camera frame of the 3D point that corresponds to the segment second point. + * + * This value is requested to compute the interaction matrix. + * + * \param val : \f$ Z_2 \f$ value to set. + * + * \exception vpFeatureException::badInitializationError : If Z2 is behind + * the camera or equal to zero. + */ + inline void setZ2(double val) + { + Z2_ = val; + + if (Z2_ < 0) { + vpERROR_TRACE("Point Z2 is behind the camera "); + std::cout << "Z2 = " << Z2_ << std::endl; + + throw(vpFeatureException(vpFeatureException::badInitializationError, "Point Z2 is behind the camera ")); + } + + if (fabs(Z2_) < 1e-6) { + vpERROR_TRACE("Point Z2 coordinates is null "); + std::cout << "Z2 = " << Z2_ << std::endl; + + throw(vpFeatureException(vpFeatureException::badInitializationError, "Point Z2 coordinates is null")); + } + + flags[5] = true; + } + +private: + double xc_; + double yc_; + double l_; + double alpha_; + double Z1_; + double Z2_; + double cos_a_; + double sin_a_; + bool normalized_; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureThetaU.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureThetaU.h new file mode 100644 index 00000000..11095e29 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureThetaU.h @@ -0,0 +1,310 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * ThetaU visual feature. + */ + +/*! + \file vpFeatureThetaU.h + \brief class that defines the ThetaU visual feature +*/ + +#ifndef vpFeatureThetaU_H +#define vpFeatureThetaU_H + +#include +#include +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpFeatureThetaU + * \ingroup group_visual_features + * + * \brief Class that defines a 3D visual feature \f$ s\f$ from a \f$ \theta + * u \f$ axis/angle parametrization that represent the rotation between + * to frames. + * + * Let us denote \f$ \theta u = (\theta u_x, \theta u_y, \theta u_z)\f$ . + * + * It is convenient to consider two coordinate frames: the current + * camera frame \f$ {\cal{F}}_c \f$ and the desired camera frame \f$ + * {\cal{F}}_{c^*} \f$. + * + * Let \f$^{c^*}R_c \f$ be the rotation matrix that gives the + * orientation of the current camera frame relative to the desired camera + * frame. Let \f$ \theta u_{^{c^*}R_c} \f$ to corresponding axis/angle + * representation of this rotation. + * + * Furthermore, let \f$^{c}R_{c^*} \f$ the rotation matrix that gives the + * orientation of the desired camera frame relative to the current + * camera frame. Let \f$ \theta u_{^{c}R_{c^*}} \f$ to corresponding + * axis/angle representation of this rotation. + * + * This class can be used to manipulate two kind of visual features: + * + * - \f$ s = \theta u_{^{c^*}R_c} \f$ if the orientation of current + * camera frame relative to the desired frame has to be + * considered. The desired visual feature \f$ s^* \f$ is equal to + * zero. The corresponding error is than equal to \f$ e=(s-s^*) = + * \theta u_{^{c^*}R_c} \f$. In this case, the interaction matrix + * related to \f$ s \f$ is given by \f[ L = \left[ \begin{array}{cc} + * 0_3 & L_{\theta u} \end{array} \right] \f] with \f[ + * L_{\theta u} = I_3 + \frac{\theta}{2} \; [u]_\times + + * \left(1 - \frac{sinc \theta}{sinc^2 \frac{\theta}{2}}\right) + * [u]^2_\times \f] where \f$ 0_3 \f$ is a \f$ 3 \times 3 \f$ nul + * matrix, \f$ I_3 \f$ is the \f$3 \times 3\f$ identity matrix, and + * for more readability \f$ \theta \f$ and \f$ u \f$ respectively the + * angle and the axis coordinates of the \f$ \theta u_{^{c^*}R_c} \f$ + * representation. + * + * - \f$ s = \theta u_{^{c}R_{c^*}} \f$ if it is more the orientation + * of the desired camera frame relative to the current frame that has + * to be considered. The desired visual feature \f$ s^* \f$ is equal + * to zero. The corresponding error is than equal to \f$e=(s-s^*) = + * \theta u_{^{c}R_{c^*}} \f$. In this case, the interaction matrix + * related to \f$ s \f$ is given by \f[ L = \left[ \begin{array}{cc} + * 0_3 & L_{\theta u} \end{array} \right] \f] with \f[ + * L_{\theta u} = -I_3 + \frac{\theta}{2} \; [u]_\times + * - \left(1 - \frac{sinc \theta}{sinc^2 \frac{\theta}{2}}\right) + * [u]^2_\times \f] where \f$ 0_3 \f$ is a \f$ 3 \times 3 \f$ nul + * matrix, \f$ I_3 \f$ is the \f$3 \times 3\f$ identity matrix, and + * for more readability \f$ \theta \f$ and \f$ u \f$ respectively the + * angle and the axis coordinates of the \f$ \theta u_{^{c}R_{c^*}} + * \f$ representation. + * + * The kind of visual feature is to set during the construction of the + * vpFeatureThetaU() object by using the selector + * vpFeatureThetaU::vpFeatureThetaURotationRepresentationType. + * + * To initialize the feature \f$(\theta u_x, \theta u_y, \theta u_z)\f$ + * you may use vpFeatureThetaU member functions like set_TUx(), + * set_TUy(), set_TUz(), or also build() functions. + * + * Depending on the choice of the visual feature representation, the + * interaction() method allows to compute the interaction matrix \f$ + * L \f$ associated to the visual feature, while the error() + * method computes the error vector \f$(s - s^*)\f$ between the current + * visual feature and the desired one. + * + * To know more on the \f$ \theta u \f$ axis/angle representation for a + * 3D rotation see the vpThetaUVector class. + * + * The code below shows how to create a eye-in hand visual servoing + * task using a 3D \f$\theta u\f$ feature \f$(\theta u_x,\theta u_y, + * \theta u_z)\f$ that correspond to the 3D rotation between the + * current camera frame and the desired camera frame. To control six + * degrees of freedom, at least three other features must be considered + * like vpFeatureTranslation visual features. First we create a current + * (\f$s\f$) 3D \f$\theta u\f$ feature, than set the + * task to use the interaction matrix associated to the current feature + * \f$L_s\f$ and than compute the camera velocity \f$v=-\lambda \; + * L_s^+ \; (s-s^*)\f$. The current feature \f$s\f$ is updated in the + * while() loop while \f$s^*\f$ is considered as zero. + * + * \code + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpServo task; // Visual servoing task + * + * vpHomogeneousMatrix cMcd; + * // ... cMcd need here to be initialized from for example a pose estimation. + * + * // Creation of the current feature s that correspond to the rotation + * // in angle/axis parametrization between the current camera frame + * // and the desired camera frame + * vpFeatureThetaU s(vpFeatureThetaU::cRcd); + * s.buildFrom(cMcd); // Initialization of the feature + * + * // Set eye-in-hand control law. + * // The computed velocities will be expressed in the camera frame + * task.setServo(vpServo::EYEINHAND_CAMERA); + * // Interaction matrix is computed with the current visual features s + * task.setInteractionMatrixType(vpServo::CURRENT); + * + * // Add the 3D ThetaU feature to the task + * task.addFeature(s); // s* is here considered as zero + * + * // Control loop + * for ( ; ; ) { + * // ... cMcd need here to be initialized from for example a pose estimation. + * + * // Update the current ThetaU visual feature + * s.buildFrom(cMcd); + * + * // compute the control law + * vpColVector v = task.computeControlLaw(); // camera velocity + * } + * } + * \endcode + * + * If you want to deal only with the \f$(\theta u_x,\theta u_y)\f$ subset + * feature from the 3D \f$\theta u\f$ , you have just to modify the + * addFeature() call in the previous example by the following line. In + * that case, the dimension of \f$s\f$ is two. + * + * \code + * // Add the (ThetaU_x, ThetaU_y) subset features from the 3D ThetaU + * // rotation to the task + * task.addFeature(s, vpFeatureThetaU::selectTUx() | vpFeatureThetaU::selectTUy()); + * \endcode + * + * If you want to build your own control law, this other example shows + * how to create a current (\f$s\f$) and desired (\f$s^*\f$) 3D + * \f$\theta u\f$ visual feature, compute the corresponding error + * vector \f$(s-s^*)\f$ and finally build the interaction matrix \f$L_s\f$. + * + * \code + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpHomogeneousMatrix cdMc; + * // ... cdMc need here to be initialized from for example a pose estimation. + * + * // Creation of the current feature s + * vpFeatureThetaU s(vpFeatureThetaU::cdRc); + * s.buildFrom(cdMc); // Initialization of the feature + * + * // Creation of the desired feature s*. By default this feature is + * // initialized to zero + * vpFeatureThetaU s_star(vpFeatureThetaU::cdRc); + * + * // Compute the interaction matrix L_s for the current ThetaU feature + * vpMatrix L = s.interaction(); + * + * // Compute the error vector (s-s*) for the ThetaU feature + * s.error(s_star); + * } + * \endcode +*/ +class VISP_EXPORT vpFeatureThetaU : public vpBasicFeature +{ +public: + typedef enum + { + TUx = 1, /*!< Select the subset \f$ \theta u_x \f$ visual feature + from the \f$ \theta u\f$ angle/axis representation. */ + TUy = 2, /*!< Select the subset \f$ \theta u_y \f$ visual feature + from the \f$ \theta u\f$ angle/axis representation. */ + TUz = 4 /*!< Select the subset \f$ \theta u_z \f$ visual feature + from the \f$ \theta u\f$ angle/axis representation. */ + } vpFeatureThetaUType; + typedef enum + { + cdRc, /*!< Selector used to manipulate the visual feature \f$ s + = \theta u_{^{c^*}R_c} \f$. This visual feature + represent the orientation of the current camera frame + relative to the desired camera frame. */ + cRcd /*!< Selector used to manipulate the visual feature \f$ s = \theta + u_{^{c}R_{c^*}} \f$. This visual feature + represent the orientation of the desired camera frame + relative to the current camera frame. */ + } vpFeatureThetaURotationRepresentationType; + /* + attributes and members directly related to the vpBasicFeature needs + other functionalities are useful but not mandatory + */ + +public: + // Basic constructor. + vpFeatureThetaU(); + VP_EXPLICIT vpFeatureThetaU(vpFeatureThetaURotationRepresentationType r); + vpFeatureThetaU(vpThetaUVector &tu, vpFeatureThetaURotationRepresentationType r); + vpFeatureThetaU(vpRotationMatrix &R, vpFeatureThetaURotationRepresentationType r); + vpFeatureThetaU(vpHomogeneousMatrix &M, vpFeatureThetaURotationRepresentationType r); + + vpFeatureThetaU &buildFrom(const vpThetaUVector &tu); + // build from a rotation matrix + vpFeatureThetaU &buildFrom(const vpRotationMatrix &R); + // build from an homogeneous matrix + vpFeatureThetaU &buildFrom(const vpHomogeneousMatrix &M); + + void display(const vpCameraParameters &cam, const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1) const VP_OVERRIDE; + void display(const vpCameraParameters &cam, const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1) const VP_OVERRIDE; + + //! Feature duplication. + vpFeatureThetaU *duplicate() const VP_OVERRIDE; + + // compute the error between two visual features from a subset + // a the possible features + vpColVector error(const vpBasicFeature &s_star, unsigned int select = FEATURE_ALL) VP_OVERRIDE; + + vpFeatureThetaURotationRepresentationType getFeatureThetaURotationType() const; + + double get_TUx() const; + double get_TUy() const; + double get_TUz() const; + + // Basic construction. + void init() VP_OVERRIDE; + // compute the interaction matrix from a subset a the possible features + vpMatrix interaction(unsigned int select = FEATURE_ALL) VP_OVERRIDE; + void print(unsigned int select = FEATURE_ALL) const VP_OVERRIDE; + + void set_TUx(double tu_x); + void set_TUy(double tu_y); + void set_TUz(double tu_z); + + void setFeatureThetaURotationType(const vpFeatureThetaURotationRepresentationType r); + +public: + /* + vpBasicFeature method instantiation + */ + static unsigned int selectTUx(); + static unsigned int selectTUy(); + static unsigned int selectTUz(); + +private: + vpFeatureThetaURotationRepresentationType rotation; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureTranslation.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureTranslation.h new file mode 100644 index 00000000..07ec08b2 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureTranslation.h @@ -0,0 +1,359 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * 3D translation visual feature. + */ + +/*! + \file vpFeatureTranslation.h + \brief class that defines the translation visual feature. +*/ + +#ifndef vpFeatureTranslation_H +#define vpFeatureTranslation_H + +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpFeatureTranslation + * \ingroup group_visual_features + * + * \brief Class that defines the translation visual feature + * \f$s=(t_x,t_y,t_z)\f$. + * + * It is convenient to consider two coordinate frames noted here \f$ + * {\cal{F}}_1 \f$ and \f$ {\cal{F}}_{2} \f$. + * + * Let \f$^{{\cal{F}}_2}M_{{\cal{F}}_1} \f$ be the homogeneous matrix that + * gives the orientation and the translation of the frame \f$ {\cal{F}}_1 \f$ + * with respect to the frame \f$ {\cal{F}}_2 \f$. + * + * \f[ + * ^{{\cal{F}}_2}M_{{\cal{F}}_1} = \left(\begin{array}{cc} + * ^{{\cal{F}}_2}R_{{\cal{F}}_1} & ^{{\cal{F}}_2}t_{{\cal{F}}_1} \\ + * {\bf 0}_{1\times 3} & 1 + * \end{array} + * \right) + * \f] + * + * with \f$^{{\cal{F}}_2}R_{{\cal{F}}_1} \f$ the rotation matrix that gives the + * orientation of the frame \f$ {\cal{F}}_1 \f$ relative to the frame \f$ + * {\cal{F}}_2 \f$ and \f$^{{\cal{F}}_2}t_{{\cal{F}}_1} \f$ the translation + * vector that gives the position of the frame \f$ {\cal{F}}_1 \f$ relative to + * the frame \f$ {\cal{F}}_2 \f$. To know more about homogeneous matrices see + * vpHomogeneousMatrix documentation. + * + * This class can be used to manipulate three kind of visual features: + * + * - This class can be used to manipulate the translation visual feature + * \f$s= ^{c^*}t_c\f$ which gives the position of + * the current camera frame relative to the desired camera frame. It is + * composed by the three components \f$(t_x,t_y,t_z)\f$. The desired visual + * feature \f$ s^* \f$ is equal to zero. The corresponding error is than equal to + * \f$ e=(s-s^*) = ^{c^*}t_c \f$. In this case, the interaction matrix related to + * \f$ s \f$ is given by \f[ L = [ ^{c^*}R_c \;\; 0_3] \f] + * + * - This class can also be used to manipulate the translation visual feature + * \f$s= ^{c}t_{c^*}\f$ which gives the position of + * the desired camera frame relative to the current camera frame. It is + * composed by the three components \f$(t_x,t_y,t_z)\f$. The desired visual + * feature \f$ s^* \f$ is equal to zero. The corresponding error is than equal to + * \f$ e=(s-s^*) = ^{c}t_{c^*} \f$. In this case, the interaction matrix related + * to \f$ s \f$ is given by \f[ L = [ -I_3 \;\; [^{c}t_{c^*}]_\times] \f] + * + * - Actually, this class can also be used to manipulate the + * translation visual feature \f$s= ^{c}t_o\f$ which gives the position + * of the object frame relative to the current camera frame. It is + * composed by the three components \f$(t_x,t_y,t_z)\f$ too. The + * desired visual feature \f$ s^* \f$ is the translation visual feature + * \f$s^*= ^{c^*}t_o\f$ which gives the position of the object frame + * relative to the desired camera frame. The corresponding error is + * than equal to \f$ e=(s-s^*) = ^{c}t_o - ^{c^*}t_o \f$. In this case, + * the interaction matrix related to \f$ s \f$ is given by \f[ L = [ + * -I_3 \;\; [^{c}t_o]_\times] \f] + * + * To initialize the feature \f$(t_x, t_y, t_z)\f$ you may use member + * functions like set_Tx(), set_Ty(), set_Tz(), or also build() + * functions. + * + * The interaction() method allows to compute the interaction matrix + * \f$ L\f$ associated to the translation visual feature, while the + * error() method computes the error vector \f$(s - s^*)\f$ between the + * current visual feature and the desired one. + * + * The code below shows how to create a eye-in hand visual servoing + * task using a 3D translation feature \f$(t_x,t_y,t_z)\f$ that + * correspond to the 3D translation between the desired camera frame + * and the current camera frame. To control six degrees of freedom, at + * least three other features must be considered like vpFeatureThetaU + * visual features. First we create a current (\f$s\f$) and desired + * (\f$s^*\f$) 3D translation feature, set the task to use the + * interaction matrix associated to the current feature \f$L_s\f$ and + * than compute the camera velocity \f$v=-\lambda \; L_s^+ \; + * (s-s^*)\f$. The current feature \f$s\f$ is updated in the while() loop + * while \f$s^*\f$ is set to zero. + * + * \code + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpServo task; // Visual servoing task + * + * vpHomogeneousMatrix cdMc; + * // ... cdMc need here to be initialized from for example a pose estimation. + * + * // Creation of the current visual feature s + * vpFeatureTranslation s(vpFeatureTranslation::cdMc); + * s.buildFrom(cdMc); // Initialization of the current feature s=(tx,ty,tz) + * + * // Set eye-in-hand control law. + * // The computed velocities will be expressed in the camera frame + * task.setServo(vpServo::EYEINHAND_CAMERA); + * // Interaction matrix is computed with the current visual features s + * task.setInteractionMatrixType(vpServo::CURRENT); + * // Set the constant gain + * double lambda = 0.8; + * task.setLambda(lambda); + * + * // Add the 3D translation feature to the task + * task.addFeature(s); // s* is here considered as zero + * + * // Control loop + * for ( ; ; ) { + * // ... cdMc need here to be initialized from for example a pose estimation. + * + * // Update the current 3D translation visual feature + * s.buildFrom(cdMc); + * + * // compute the control law + * vpColVector v = task.computeControlLaw(); // camera velocity + * } + * } + * \endcode + * + * If you want to deal only with the \f$(t_x,t_y)\f$ subset feature from the 3D + * translation, you have just to modify the addFeature() call in + * the previous example by the following line. In that case, the dimension + * of \f$s\f$ is two. + * + * \code + * // Add the (tx,ty) subset features from 3D translation to the task + * task.addFeature(s, vpFeatureTranslation::selectTx() | vpFeatureTranslation::selectTy()); + * \endcode + * + * If you want to build your own control law, this other example shows + * how to create a current (\f$s\f$) and desired (\f$s^*\f$) 3D + * translation visual feature, compute the corresponding error + * vector \f$(s-s^*)\f$ and finally build the interaction matrix \f$L_s\f$. + * + * \code + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpHomogeneousMatrix cdMc; + * // ... cdMc need here to be initialized from for example a pose estimation. + * + * // Creation of the current feature s + * vpFeatureTranslation s(vpFeatureTranslation::cdMc); + * s.buildFrom(cdMc); // Initialization of the feature + * + * // Creation of the desired feature s*. By default this feature is + * // initialized to zero + * vpFeatureTranslation s_star(vpFeatureTranslation::cdMc); + * + * // Compute the interaction matrix for the translation feature + * vpMatrix L = s.interaction(); + * + * // Compute the error vector (s-s*) for the translation feature + * vpColVector e = s.error(s_star); // e = (s-s*) + * } + * \endcode + * + * The code below shows how to create an eye-in hand visual servoing + * task using a 3D translation feature \f$(t_x,t_y,t_z)\f$ that + * correspond to the 3D translation between the current camera frame + * and the object frame. Like with the previous examples, to + * control six degrees of freedom, at least three other features must be + * considered like vpFeatureThetaU visual features. The way to initialize + * the visual features is quite the same as before. The difference is that + * the cMo method must be precised and the desired feature is note + * necessary equal to zero. + * + * \code + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpServo task; // Visual servoing task + * + * vpHomogeneousMatrix cdMo; + * // ... cdMo need here to be initialized from for example a pose estimation. + * + * // Creation of the desired visual feature s* + * vpFeatureTranslation s_star(vpFeatureTranslation::cMo); + * s_star.buildFrom(cdMo); // Initialization of the desired feature s*=(tx*,ty*,tz*) + * + * vpHomogeneousMatrix cMo; + * // ... cMo need here to be computed. + * + * // Creation of the current visual feature s + * vpFeatureTranslation s(vpFeatureTranslation::cMo); + * s.buildFrom(cMo); // Initialization of the current feature s=(tx,ty,tz) + * + * // Set eye-in-hand control law. + * // The computed velocities will be expressed in the camera frame + * task.setServo(vpServo::EYEINHAND_CAMERA); + * // Interaction matrix is computed with the current visual features s + * task.setInteractionMatrixType(vpServo::CURRENT); + * // Set the constant gain + * double lambda = 0.8; + * task.setLambda(lambda); + * + * // Add the 3D translation feature to the task + * task.addFeature(s, s_star); // s* is here considered as zero + * + * // Control loop + * for ( ; ; ) { + * // ... cMo need here to be computed from for example a pose estimation. + * + * // Update the current 3D translation visual feature + * s.buildFrom(cMo); + * + * // compute the control law + * vpColVector v = task.computeControlLaw(); // camera velocity + * } + * } + * \endcode +*/ +class VISP_EXPORT vpFeatureTranslation : public vpBasicFeature +{ +public: + /*! + * \enum vpFeatureTranslationRepresentationType + * Kind of implemented 3D translation feature. + */ + typedef enum + { +/*! Selector used to manipulate the visual feature \f$s= + ^{c^*}t_c\f$ which gives the position of the current camera frame + relative to the desired camera frame.*/ + cdMc, + /*! Selector used to manipulate the visual feature \f$s= + ^{c}t_{c^*}\f$ which gives the position of the desired camera frame + relative to the current camera frame.*/ + cMcd, + /*! Selector used to manipulate the visual feature \f$s= + ^{c}t_o\f$ which gives the position of the object frame relative to + the current camera frame. */ + cMo + } vpFeatureTranslationRepresentationType; + + // basic constructor + vpFeatureTranslation(); + // basic constructor specifying the type of translation feature + VP_EXPLICIT vpFeatureTranslation(vpFeatureTranslationRepresentationType r); + // constructor : build from an homogeneous matrix + // cdMc is the displacement that the camera has to realize + vpFeatureTranslation(vpHomogeneousMatrix &f2Mf1, vpFeatureTranslationRepresentationType r); + + // build from an homogeneous matrix + // cdMc is the displacement that the camera has to realize + vpFeatureTranslation &buildFrom(const vpHomogeneousMatrix &f2Mf1); + + void display(const vpCameraParameters &cam, const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1) const VP_OVERRIDE; + void display(const vpCameraParameters &cam, const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1) const VP_OVERRIDE; + + //! Feature duplication + vpFeatureTranslation *duplicate() const VP_OVERRIDE; + + // compute the error between two visual features from a subset + // a the possible features + vpColVector error(const vpBasicFeature &s_star, unsigned int select = FEATURE_ALL) VP_OVERRIDE; + + vpFeatureTranslationRepresentationType getFeatureTranslationType() const; + + double get_Tx() const; + double get_Ty() const; + double get_Tz() const; + + // basic construction + void init() VP_OVERRIDE; + // compute the interaction matrix from a subset a the possible features + vpMatrix interaction(unsigned int select = FEATURE_ALL) VP_OVERRIDE; + + // print the name of the feature + void print(unsigned int select = FEATURE_ALL) const VP_OVERRIDE; + + void set_Tx(double t_x); + void set_Ty(double t_y); + void set_Tz(double t_z); + + void setFeatureTranslationType(const vpFeatureTranslationRepresentationType r); + + // feature selection + static unsigned int selectTx(); + static unsigned int selectTy(); + static unsigned int selectTz(); + +private: + //! displacement that the camera has to realize + vpHomogeneousMatrix f2Mf1; + vpFeatureTranslationRepresentationType translation; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureVanishingPoint.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureVanishingPoint.h new file mode 100644 index 00000000..139da8a6 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpFeatureVanishingPoint.h @@ -0,0 +1,114 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * 2D vanishing point visual feature (Z coordinate in 3D space is infinity) + */ + +/*! + * \file vpFeatureVanishingPoint.h \brief Class that defines 2D vanishing + * point visual feature (Z coordinate in 3D space is infinity) + */ + +#ifndef vpFeatureVanishingPoint_H +#define vpFeatureVanishingPoint_H + +#include +#include +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpFeatureVanishingPoint + * \ingroup group_visual_features + * + * Class that defines 2D vanishing point visual features. Various features can be considered: + + * - Either the cartesian coordinates \f$ (x, y) \f$ of the vanishing point obtained from the intersection of two lines; + * in that case \f$ {\bf s} = (x, y) \f$ and the corresponding interaction matrices are: + * \f[ L_x = \left[ \begin{array}{cccccc} 0 & 0 & 0 & x y & -(1 + x^2) & y \end{array} \right] \f] + * \f[ L_y = \left[ \begin{array}{cccccc} 0 & 0 & 0 & 1 + y * y & -xy & -x \end{array} \right] \f] + * + * - Rather features function of the polar coordinates of the vanishing point obtained themselves from the polar + * coordinates of the two lines \f$(\rho_1, \theta_1)\f$ and \f$(\rho_2, \theta_2)\f$; in that case \f$ {\bf s} = + * (\arctan(1/\rho), 1/\rho, \alpha) \f$ with: \f[ 1/\rho = \frac{\sin(\theta_1 - \theta_2)}{\sqrt{\rho_1^2 + \rho_2^2 - + * 2 \rho_1 \rho_2 cos(\theta_1 - \theta_2)}} \f] \f[ \alpha = \frac{\rho_1 \cos \theta_2 - \rho_2 cos + * \theta_1}{\sqrt{\rho_1^2 + \rho_2^2 - 2 \rho_1 \rho_2 cos(\theta_1 - \theta_2)}} \f] The corresponding interaction + * matrices are: \f[ L_{\arctan(\frac{1}{\rho})} = \left[ \begin{array}{cccccc} 0 & 0 & 0 & - \sin \alpha & \cos \alpha & + * 0 \end{array} \right] \f] \f[ L_{\frac{1}{\rho}} = \left[ \begin{array}{cccccc} 0 & 0 & 0 & -(1 + \frac{1}{\rho^2}) + * \sin \alpha & (1 + \frac{1}{\rho^2}) \cos \alpha & 0 \end{array} \right] \f] \f[ L_{\alpha} = \left[ + * \begin{array}{cccccc} 0 & 0 & 0 & \frac{\cos \alpha}{\rho} & \frac{\sin \alpha}{\rho} & -1 \end{array} \right] \f] +*/ +class VISP_EXPORT vpFeatureVanishingPoint : public vpBasicFeature +{ +public: + static unsigned int selectAlpha(); + static unsigned int selectAtanOneOverRho(); + static unsigned int selectOneOverRho(); + static unsigned int selectX(); + static unsigned int selectY(); + +public: + vpFeatureVanishingPoint(); + + vpFeatureVanishingPoint &buildFrom(const double &x, const double &y); + + void display(const vpCameraParameters &cam, const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1) const VP_OVERRIDE; + void display(const vpCameraParameters &cam, const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1) const VP_OVERRIDE; + vpFeatureVanishingPoint *duplicate() const VP_OVERRIDE; + vpColVector error(const vpBasicFeature &s_star, unsigned int select = (vpFeatureVanishingPoint::selectX() | vpFeatureVanishingPoint::selectY())) VP_OVERRIDE; + + double get_x() const; + double get_y() const; + double getAtanOneOverRho() const; + double getOneOverRho() const; + double getAlpha() const; + + void init() VP_OVERRIDE; + vpMatrix interaction(unsigned int select = (vpFeatureVanishingPoint::selectX() | vpFeatureVanishingPoint::selectY())) VP_OVERRIDE; + void print(unsigned int select = (vpFeatureVanishingPoint::selectX() | vpFeatureVanishingPoint::selectY())) const VP_OVERRIDE; + + void set_x(double x); + void set_y(double y); + void set_xy(double x, double y); + void setAtanOneOverRho(double atan_one_over_rho); + void setOneOverRho(double one_over_rho); + void setAlpha(double alpha); + +protected: + unsigned int m_select; // Memory to know which features are used for display; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpGenericFeature.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpGenericFeature.h new file mode 100644 index 00000000..1283b4f7 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/visual_features/vpGenericFeature.h @@ -0,0 +1,226 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Generic feature (used to create new feature not implemented in ViSP). + */ + +/*! + * \file vpGenericFeature.h + * \brief class that defines what is a generic feature (used to create new + * feature not implemented in ViSP2 + */ + +#ifndef vpGenericFeature_hh +#define vpGenericFeature_hh + +#include +#include +#include +#include + +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpGenericFeature + * \ingroup group_visual_features + * + * \brief Class that enables to define a feature or a set of features which are + * not implemented in ViSP as a specific class. It is indeed possible to create + * its own features, to use the corresponding interaction matrix, and to compute + * an error between the current and the desired feature. Moreover the created + * features can be mixed with features already implemented. + * + * The following example shows how to use the vpGenericFeature class to create + * and use the feature \f$ log(Z) \f$ where Z corresponds to the depth of a point + * whose 2D coordinates in the camera frame are \f$ x \f$ and \f$ y \f$. The + * interaction matrix corresponding to this feature is \f[ L = + * \left[\begin{array}{cccccc} 0 & 0 & -1/Z & -y & x & 0 \end{array}\right]\f]. + * \code + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpServo task; // Visual servoing task + * + * //First we have to define the desired feature log(Z*) corresponding to the desired point. + * double xd = 0; //The x coordinate of the desired point. + * double yd = 0; //The y coordinate of the desired point. + * double Zd = 1; //The depth of the desired point. + * vpGenericFeature logZd(1); //The dimension of the feature is 1. + * logZd.set_s( log(Zd) ); + * + * //Then we have to define the current feature log(Z) corresponding to the current point. + * double x = 1; //The x coordinate of the current point. + * double y = 1; //The y coordinate of the current point. + * double Z = 2; //The depth of the current point. + * vpGenericFeature logZ(1); //The dimension of the feature is 1. + * logZ.set_s( log(Z) ); + * + * // Set eye-in-hand control law. + * // The computed velocities will be expressed in the camera frame + * task.setServo(vpServo::EYEINHAND_CAMERA); + * // Interaction matrix is computed with the current visual features sd + * task.setInteractionMatrixType(vpServo::CURRENT); + * + * // Add the point feature to the task + * task.addFeature(logZ, logZd); + * + * // Control loop + * for ( ; ; ) { + * // The new parameters x, y and Z must be computed here. + * + * // Update the current point visual feature + * logZ.set_s( log(Z) ) ; + * + * // We have to compute the interaction matrix corresponding to the feature. + * vpMatrix LlogZ(1,6) ; + * LlogZ[0][0] = LlogZ[0][1] = LlogZ[0][5] = 0 ; + * LlogZ[0][2] = -1/Z; + * LlogZ[0][3] = -y; + * LlogZ[0][4] = x; + * logZ.setInteractionMatrix(LlogZ) ; + * + * // compute the control law + * vpColVector v = task.computeControlLaw(); // camera velocity + * } + * return 0; + * } + * \endcode + * + * The second example shows how to create and use a feature whose specificity is + * to have a desired feature fixed to zero. It is the case for the feature \f$ + * log( \frac{Z}{Z^*}) \f$. + * + * \code + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpServo task; // Visual servoing task + * + * //First we have to define the desired feature log(Z*) corresponding to the desired point. + * double xd = 0; //The x coordinate of the desired point. + * double yd = 0; //The y coordinate of the desired point. + * double Zd = 1; //The depth of the desired point. + * + * //Then we have to define the current feature log(Z) corresponding to the current point. + * double x = 1; //The x coordinate of the current point. + * double y = 1; //The y coordinate of the current point. + * double Z = 2; //The depth of the current point. + * vpGenericFeature logZ(1); //The dimension of the feature is 1. + * logZ.set_s( log(Z/Zd) ); + * + * // Set eye-in-hand control law. + * // The computed velocities will be expressed in the camera frame + * task.setServo(vpServo::EYEINHAND_CAMERA); + * // Interaction matrix is computed with the current visual features sd + * task.setInteractionMatrixType(vpServo::CURRENT); + * + * // Add the point feature to the task + * task.addFeature(logZ); + * + * // Control loop + * for ( ; ; ) { + * // The new parameters x, y and Z must be computed here. + * + * // Update the current point visual feature + * logZ.set_s( log(Z/Zd) ) ; + * + * // We have to compute the interaction matrix corresponding to the feature. + * vpMatrix LlogZ(1,6) ; + * LlogZ[0][0] = LlogZ[0][1] = LlogZ[0][5] = 0 ; + * LlogZ[0][2] = -1/Z; + * LlogZ[0][3] = -y; + * LlogZ[0][4] = x; + * logZ.setInteractionMatrix(LlogZ) ; + * + * // compute the control law + * vpColVector v = task.computeControlLaw(); // camera velocity + * } + * return 0; + * } + * \endcode + * + * If the feature needs to be use with other features, the example + * servoSimuPoint2DhalfCamVelocity2.cpp shows how to do it. +*/ +class VISP_EXPORT vpGenericFeature : public vpBasicFeature +{ +private: + vpGenericFeature(); + +public: + VP_EXPLICIT vpGenericFeature(unsigned int dim); + + void display(const vpCameraParameters &cam, const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1) const VP_OVERRIDE; + void display(const vpCameraParameters &cam, const vpImage &I, const vpColor &color = vpColor::green, + unsigned int thickness = 1) const VP_OVERRIDE; + vpGenericFeature *duplicate() const VP_OVERRIDE; + vpColVector error(const vpBasicFeature &s_star, unsigned int select = FEATURE_ALL) VP_OVERRIDE; + + vpColVector error(unsigned int select = FEATURE_ALL); + + vpMatrix getInteractionMatrix() const { return L; } + void get_s(vpColVector &s) const; + void get_s(double &s0) const; + void get_s(double &s0, double &s1) const; + void get_s(double &s0, double &s1, double &s2) const; + + void init() VP_OVERRIDE; + vpMatrix interaction(unsigned int select = FEATURE_ALL) VP_OVERRIDE; + void print(unsigned int select = FEATURE_ALL) const VP_OVERRIDE; + void setInteractionMatrix(const vpMatrix &L); + void setError(const vpColVector &error_vector); + void set_s(const vpColVector &s); + void set_s(const double s0); + void set_s(const double s0, const double s1); + void set_s(const double s0, const double s1, const double s2); + +private: + typedef enum { errorNotInitialized, errorInitialized, errorHasToBeUpdated } vpGenericFeatureErrorType; + + vpMatrix L; + vpColVector err; + vpGenericFeatureErrorType errorStatus; +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/vs/vpAdaptiveGain.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/vs/vpAdaptiveGain.h new file mode 100644 index 00000000..07ebbc41 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/vs/vpAdaptiveGain.h @@ -0,0 +1,312 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Adaptive gain. + */ + +/*! + * \file vpAdaptiveGain.h + * \brief Adaptive gain + */ + +#ifndef _vpAdaptiveGain_h_ +#define _vpAdaptiveGain_h_ + +#include +#include + +BEGIN_VISP_NAMESPACE +class vpColVector; + +/*! + * \class vpAdaptiveGain + * + * \ingroup group_task + * + * \brief Adaptive gain computation. + * + * As described in \cite Kermorgant14a, a varying gain \f$ \lambda \f$ could be + * used in the visual servoing control law \f[{\bf v}_c = -\lambda {\bf + * L}^{+}_{e} {\bf e}\f] with + * + * \f[ \lambda (|| {\bf e}||) = (\lambda_0 - \lambda_\infty) e^{ -\frac{ + * \lambda'_0}{\lambda_0 - \lambda_\infty}||{\bf e}||} + \lambda_\infty \f] + * + * where: + * + * - \f$\lambda_0 = \lambda(0)\f$ is the gain in 0, that is for very small + * values of \f$||{\bf e}||\f$ + * - \f$\lambda_\infty = \lambda_{||{\bf e}|| \rightarrow \infty}\lambda(||{\bf + * e}||)\f$ is the gain to infinity, that is for very high values of \f$||{\bf + * e}||\f$ + * - \f$\lambda'_0\f$ is the slope of \f$\lambda\f$ at \f$||{\bf e}|| = 0\f$ + * + * As described in \ref tutorial-boost-vs, the interest of \ref adaptive_gain + * is to reduce the time to convergence in order to speed up the servo. + * + * The following example shows how to use this class in order to use an + * adaptive gain with the following parameters \f$\lambda_0 = 4\f$, + * \f$\lambda_\infty = 0.4 \f$ and \f$\lambda'_0 = 30\f$. + * + * \code + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpAdaptiveGain lambda(4, 0.4, 30); // lambda(0)=4, lambda(oo)=0.4 and lambda'(0)=30 + * + * vpServo servo; + * servo.setLambda(lambda); + * + * while(1) { + * vpColVector v = servo.computeControlLaw(); + * } + * } + * \endcode + * + * This other example shows how to use this class in order to set a constant + * gain \f$\lambda = 0.5\f$ that will ensure an exponential decrease of the task + * error. + * + * \code + * #include + * #include + * + * int main() + * { + * vpAdaptiveGain lambda(0.5); + * + * vpServo servo; + * servo.setLambda(lambda); + * + * while(1) { + * vpColVector v = servo.computeControlLaw(); + * } + * } + * \endcode +*/ + +class VISP_EXPORT vpAdaptiveGain +{ +public: + static const double DEFAULT_LAMBDA_ZERO; + static const double DEFAULT_LAMBDA_INFINITY; + static const double DEFAULT_LAMBDA_SLOPE; + +private: + // Coefficient such as lambda (x) = a * exp (-b*x) + c + double coeff_a; // \f$ a = \lambda(0) - \lambda(\infty) \f$ + double coeff_b; // \f$ b = {\dot \lambda}(0) / a \f$ + double coeff_c; // \f$ c = \lambda(\infty) \f$ + + // Last computed value + mutable double lambda; + +public: + /*! + * Basic constructor which initializes all the parameters with their default + * value: + * - \f$ \lambda_0 = 1.666 \f$ using vpAdaptiveGain::DEFAULT_LAMBDA_ZERO + * - \f$ \lambda_\infty = 0.1666 \f$ using + * vpAdaptiveGain::DEFAULT_LAMBDA_INFINITY + * - \f$ \lambda'_0 = 1.666 \f$ using vpAdaptiveGain::DEFAULT_LAMBDA_SLOPE + */ + vpAdaptiveGain(); + + /*! + * Constructor that initializes the gain as constant. In that case + * \f$\lambda(||{\bf e}||) = c\f$. + * + * \param c : Value of the constant gain. A typical value is 0.5. + */ + VP_EXPLICIT vpAdaptiveGain(double c); + + /*! + * Constructor that initializes the gain as adaptive. + * + * \param gain_at_zero : the expected gain when \f$||{\bf e}||=0\f$: + * \f$\lambda_0\f$. + * \param gain_at_infinity : the expected gain when \f$||{\bf + * e}||\rightarrow\infty\f$: \f$\lambda_\infty\f$. + * \param slope_at_zero : the + * expected slope of \f$\lambda(||{\bf e}||)\f$ when \f$||{\bf e}||=0\f$: + * \f$\lambda'_0\f$. + */ + vpAdaptiveGain(double gain_at_zero, double gain_at_infinity, double slope_at_zero); + + /*! + * Initializes the parameters to have a constant gain. In that case + * \f$\lambda(||{\bf e}||) = c\f$. + * + * \param c : Value of the constant gain. A typical value is 0.5. + */ + void initFromConstant(double c); + + /*! + * Initializes the parameters with the default value : + * - \f$ \lambda_0 = 1.666 \f$ using vpAdaptiveGain::DEFAULT_LAMBDA_ZERO + * - \f$ \lambda_\infty = 0.1666 \f$ using + * vpAdaptiveGain::DEFAULT_LAMBDA_INFINITY + * - \f$ \lambda'_0 = 1.666 \f$ using vpAdaptiveGain::DEFAULT_LAMBDA_SLOPE + */ + void initFromVoid(void); + + /*! + * Set the parameters \f$\lambda_0, \lambda_\infty, \lambda'_0\f$ used to + * compute \f$\lambda(||{\bf e}||)\f$. + * + * \param gain_at_zero : the expected gain when \f$||{\bf e}||=0\f$: + * \f$\lambda_0\f$. + * \param gain_at_infinity : the expected gain when \f$||{\bf + * e}||\rightarrow\infty\f$: \f$\lambda_\infty\f$. + * \param slope_at_zero : the + * expected slope of \f$\lambda(||{\bf e}||)\f$ when \f$||{\bf e}||=0\f$: + * \f$\lambda'_0\f$. + */ + void initStandard(double gain_at_zero, double gain_at_infinity, double slope_at_zero); + + /*! + * Sets the internal parameters in order to obtain a constant gain equal to + * the gain in 0 set through the parameter \f$\lambda_0\f$. + * + * \return It returns the value of the constant gain \f$\lambda_0\f$. + */ + double setConstant(void); + + /*! + * Computes the value of the adaptive gain \f$\lambda(x)\f$ using: + * + * \f[ \lambda (x) = (\lambda_0 - \lambda_\infty) e^{ -\frac{ + * \lambda'_0}{\lambda_0 - \lambda_\infty}x} + \lambda_\infty \f] + * + * \param x : Input value to consider. During a visual servo this value can be + * the Euclidean norm \f$||{\bf e}||\f$ or the infinity norm \f$||{\bf + * e}||_{\infty}\f$ of the task function. + * + * \return It returns the value of the computed gain. + */ + double value_const(double x) const; + + /*! + * Computes the value of the adaptive gain \f$\lambda(x)\f$ using: + * + * \f[ \lambda (x) = (\lambda_0 - \lambda_\infty) e^{ -\frac{ + * \lambda'_0}{\lambda_0 - \lambda_\infty}x} + \lambda_\infty \f] + * + * This value is stored as a parameter of the class. + * + * \param x : Input value to consider. During a visual servo this value can be + * the Euclidean norm \f$||{\bf e}||\f$ or the infinity norm \f$||{\bf + * e}||_{\infty}\f$ of the task function. + * + * \return It returns the value of the computed gain. + */ + double value(double x) const; + + /*! + * Gets the value of the gain at infinity (ie the value of \f$ \lambda_\infty = + * c \f$). This function is similar to limitValue() except that here the value + * is not stored as a parameter of the class. + * + * \return It returns the value of the gain at infinity. + */ + double limitValue_const(void) const; + + /*! + * Gets the value of the gain at infinity (ie the value of \f$\lambda_\infty = + * c \f$) and stores it as a parameter of the class. + * + * \return It returns the value of the gain at infinity. + */ + double limitValue(void) const; + + /*! + * Gets the last adaptive gain value which was stored in the class. + * + * \return It returns the last adaptive gain value which was stored in the + * class. + */ + inline double getLastValue(void) const { return this->lambda; } + + /*! + * Operator that computes \f$\lambda(x)\f$ where + * + * \f[ \lambda (x) = (\lambda_0 - \lambda_\infty) e^{ -\frac{ + * \lambda'_0}{\lambda_0 - \lambda_\infty}x} + \lambda_\infty \f] + * + * \param x : Input value to consider. During a visual servo this value can be + * the Euclidean norm \f$||{\bf e}||\f$ or the infinity norm \f$||{\bf + * e}||_{\infty}\f$ of the task function. + * + * \return It returns the value of the computed gain. + * + * \sa value() + */ + double operator()(double x) const; + + /*! + * Operator which computes \f$\lambda({||x||}_{\infty})\f$ where + * + * \f[ \lambda ({||x||}_{\infty}) = (\lambda_0 - \lambda_\infty) e^{ -\frac{ + * \lambda'_0}{\lambda_0 - \lambda_\infty}{||x||}_{\infty}} + \lambda_\infty + * \f] + * + * \param x : Input vector \f$ \bf x\f$ to consider. + * + * \return It returns the value of the computed gain. + */ + double operator()(const vpColVector &x) const; + + /*! + * Gets the value of the gain at infinity (ie the value of \f$\lambda_\infty = + * c \f$). + * + * \return It returns the value of the gain at infinity. + * + * \sa limitValue() + */ + double operator()(void) const; + + /*! + * Prints the adaptive gain parameters \f$\lambda_0, \lambda_\infty, + * \lambda'_0\f$. + * + * \param os : The stream where to print the adaptive gain parameters. + * \param lambda : The adaptive gain containing the parameters to print. + */ + friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpAdaptiveGain &lambda); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/vs/vpServo.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/vs/vpServo.h new file mode 100644 index 00000000..581b8979 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/vs/vpServo.h @@ -0,0 +1,1367 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Visual servoing control law. + */ + +/*! + * \file vpServo.h + * \brief Class required to compute the visual servoing control law. + */ + +#ifndef VP_SERVO_H +#define VP_SERVO_H + +#include + +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpServo + * + * \ingroup group_task + * Class required to compute the visual servoing control law described + * in \cite Chaumette06a and \cite Chaumette07a. + * + * \warning To avoid potential memory leaks, it is mandatory to call + * explicitly the kill() function to destroy the task. Otherwise, the + * destructor ~vpServo() launch an exception + * vpServoException::notKilledProperly. + * + * To learn how to use this class, we suggest first to follow the \ref + * tutorial-ibvs. The \ref tutorial-simu-robot-pioneer and \ref tutorial-boost-vs + * are also useful for advanced usage of this class. + * + * The example below shows how to build a position-based visual servo + * from 3D visual features \f$s=({^{c^*}}t_c,\theta u)\f$. In that + * case, we have \f$s^* = 0\f$. Let us denote \f$\theta u\f$ the angle/axis + * parametrization of the rotation \f${^{c^*}}R_c\f$. Moreover,\f$ + * {^{c^*}}t_c\f$ and \f${^{c^*}}R_c\f$ represent respectively the + * translation and the rotation between the desired camera frame and + * the current one obtained by pose estimation (see vpPose class). + * + * \code + * #include + * #include + * #include + * #include + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * // Creation of an homogeneous matrix that represent the displacement + * // the camera has to achieve to move from the desired camera frame + * // and the current one + * vpHomogeneousMatrix cdMc; + * + * // ... cdMc is here the result of a pose estimation + * + * // Creation of the current visual feature s = (c*_t_c, ThetaU) + * vpFeatureTranslation s_t(vpFeatureTranslation::cdMc); + * vpFeatureThetaU s_tu(vpFeatureThetaU::cdRc); + * // Set the initial values of the current visual feature s = (c*_t_c, ThetaU) + * s_t.buildFrom(cdMc); + * s_tu.buildFrom(cdMc); + * + * // Build the desired visual feature s* = (0,0) + * vpFeatureTranslation s_star_t(vpFeatureTranslation::cdMc); // Default initialization to zero + * vpFeatureThetaU s_star_tu(vpFeatureThetaU::cdRc); // Default initialization to zero + * + * vpColVector v; // Camera velocity + * double error; // Task error + * + * // Creation of the visual servo task. + * vpServo task; + * + * // Visual servo task initialization + * // - Camera is mounted on the robot end-effector and velocities are + * // computed in the camera frame + * task.setServo(vpServo::EYEINHAND_CAMERA); + * // - Interaction matrix is computed with the current visual features s + * task.setInteractionMatrixType(vpServo::CURRENT); + * // - Set the constant gain to 1 + * task.setLambda(1); + * // - Add current and desired translation feature + * task.addFeature(s_t, s_star_t); + * // - Add current and desired ThetaU feature for the rotation + * task.addFeature(s_tu, s_star_tu); + * + * // Visual servoing loop. The objective is here to update the visual + * // features s = (c*_t_c, ThetaU), compute the control law and apply + * // it to the robot + * do { + * // ... cdMc is here the result of a pose estimation + * + * // Update the current visual feature s + * s_t.buildFrom(cdMc); // Update translation visual feature + * s_tu.buildFrom(cdMc); // Update ThetaU visual feature + * + * v = task.computeControlLaw(); // Compute camera velocity skew + * error = ( task.getError() ).sumSquare(); // error = s^2 - s_star^2 + * } while (error > 0.0001); // Stop the task when current and desired visual features are close + * } + * \endcode + * + *

Tutorials & Examples

+ * + * Tutorials
+ * If you are interested in visual servoing, you may have a look at: + * + * - \ref tutorial-ibvs + * - \ref tutorial-simu-robot-pioneer + * - \ref tutorial-boost-vs + * - \ref tutorial-franka-ibvs + * - \ref tutorial-franka-pbvs + * - \ref tutorial-bebop2-vs + * - \ref tutorial-flir-ptu-vs + * - \ref tutorial-universal-robot-ibvs + * - \ref tutorial-universal-robot-pbvs +*/ +class VISP_EXPORT vpServo +{ + +public: + /*! + * Choice of the visual servoing control law. + */ + typedef enum + { + /*! + * No control law is specified. + */ + NONE, + /*! + * Eye-in-hand visual servoing with the following control law + * \f[{\bf v}_c = -\lambda {\widehat {\bf L}}^{+}_{e} {\bf e}\f] + * where camera velocities are computed. + */ + EYEINHAND_CAMERA, + /*! + * Eye-in-hand visual servoing with the following control law + * \f[{\dot {\bf q}} = -\lambda \left( {{\widehat {\bf L}}_{e} {^c}{\bf + * V}_e {^e}{\bf J}_e} \right)^{+} {\bf e}\f] where joint velocities are + * computed. + */ + EYEINHAND_L_cVe_eJe, + /*! + * Eye-to-hand visual servoing with the following control law + * \f[{\dot {\bf q}} = \lambda \left( {{\widehat {\bf L}}_{e} {^c}{\bf V}_e + * {^e}{\bf J}_e} \right)^{+} {\bf e}\f] where joint velocities are + * computed. + */ + EYETOHAND_L_cVe_eJe, + /*! + * Eye-to-hand visual servoing with the following control law + * \f[{\dot {\bf q}} = \lambda \left( {{\widehat {\bf L}}_{e} {^c}{\bf V}_f + * {^f}{\bf V}_e {^e}{\bf J}_e} \right)^{+} {\bf e}\f] where joint + * velocities are computed. + */ + EYETOHAND_L_cVf_fVe_eJe, + /*! + * Eye-to-hand visual servoing with the following control law + * \f[{\dot {\bf q}} = \lambda \left( {{\widehat {\bf L}}_{e} {^c}{\bf V}_f + * {^f}{\bf J}_e} \right)^{+} {\bf e}\f] where joint velocities are + * computed. + */ + EYETOHAND_L_cVf_fJe + } vpServoType; + + /*! + * Choice of the interaction matrix type used in the visual servoing control law. + */ + typedef enum + { + /*! + * In the control law (see vpServo::vpServoType), uses the interaction + * matrix \f${\widehat {\bf L}}_s \f$computed using the current features + * \f$\bf s\f$. + */ + CURRENT, + /*! + * In the control law (see vpServo::vpServoType), uses the interaction + * matrix \f${\widehat {\bf L}}_{s^*} \f$computed using the desired + * features \f${\bf s}^*\f$. + */ + DESIRED, + /*! + * In the control law (see vpServo::vpServoType), uses the interaction + * matrix \f${\widehat {\bf L}} = \left({\widehat {\bf L}}_s + {\widehat + * {\bf L}}_{s^*}\right)/2 \f$. + */ + MEAN, + /*! + * In the control law (see vpServo::vpServoType), uses an interaction + * matrix set by the user. + */ + USER_DEFINED + } vpServoIteractionMatrixType; + + /*! + * Choice of the interaction matrix inversion method. + */ + typedef enum + { + /*! + * In the control law (see vpServo::vpServoType), uses the + * transpose instead of the pseudo inverse. + */ + TRANSPOSE, + /*! + * In the control law (see vpServo::vpServoType), uses the pseudo inverse. + */ + PSEUDO_INVERSE + } vpServoInversionType; + + /*! + * Choice of the information to print. + */ + typedef enum + { + ALL, //!< Print all the task information. + CONTROLLER, //!< Print the type of controller law. + ERROR_VECTOR, //!< Print the error vector \f$\bf e = (s-s^*)\f$. + FEATURE_CURRENT, //!< Print the current features \f$\bf s\f$. + FEATURE_DESIRED, //!< Print the desired features \f${\bf s}^*\f$. + GAIN, //!< Print the gain \f$\lambda\f$. + INTERACTION_MATRIX, //!< Print the interaction matrix. + MINIMUM //!< Same as vpServo::vpServoPrintType::ERROR_VECTOR. + } vpServoPrintType; + + // private: + //#ifndef DOXYGEN_SHOULD_SKIP_THIS + // vpServo(const vpServo &) + // : L(), error(), J1(), J1p(), s(), sStar(), e1(), e(), q_dot(), v(), + // servoType(vpServo::NONE), + // rankJ1(0), featureList(), desiredFeatureList(), + // featureSelectionList(), lambda(), signInteractionMatrix(1), + // interactionMatrixType(DESIRED), inversionType(PSEUDO_INVERSE), + // cVe(), init_cVe(false), cVf(), init_cVf(false), fVe(), + // init_fVe(false), eJe(), init_eJe(false), fJe(), init_fJe(false), + // errorComputed(false), interactionMatrixComputed(false), dim_task(0), + // taskWasKilled(false), forceInteractionMatrixComputation(false), + // WpW(), I_WpW(), P(), sv(), mu(4.), e1_initial() + // { + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); + // } + // vpServo &operator=(const vpServo &){ + // throw vpException(vpException::functionNotImplementedError, "Not + // implemented!"); return *this; + // } + //#endif + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpServo(const vpServo &) = delete; // non construction-copyable + vpServo &operator=(const vpServo &) = delete; // non copyable +#endif + +public: + /*! + * Default constructor that initializes the following settings: + * - No control law is specified. The user has to call setServo() to specify + * the control law. + * - In the control law, the interaction matrix \f${\widehat {\bf L}}_e \f$ is + * computed with the desired features \f${\bf s}^*\f$. Using + * setInteractionMatrixType() you can also compute the interaction matrix with + * the current visual features, or from the mean \f$\left({\widehat {\bf L}}_s + * + {\widehat {\bf L}}_{s^*}\right)/2\f$. + * - In the control law the pseudo inverse will be used. The method + * setInteractionMatrixType() allows to use the transpose instead. + * + * \warning By default the threshold used to compute the pseudo-inverse is set to 1e-6. + * Advanced user can modify this value using setPseudoInverseThreshold(). + */ + vpServo(); + + /*! + * Constructor that allows to choose the visual servoing control law. + * + * \param servo_type : Visual servoing control law. + * + * The other settings are the following: + * - In the control law, the interaction matrix \f${\widehat {\bf L}}_e \f$ is + * computed with the desired features \f${\bf s}^*\f$. Using + * setInteractionMatrixType() you can also compute the interaction matrix with + * the current visual features, or from the mean \f$\left({\widehat {\bf L}}_s + * + {\widehat {\bf L}}_{s^*}\right)/2\f$. + * - In the control law the pseudo inverse will be used. The method + * setInteractionMatrixType() allows to use the transpose instead. + */ + VP_EXPLICIT vpServo(vpServoType servo_type); + + /*! + * Destructor. + * + * Since ViSP > 3.3.0 calls kill() to destroy the current and desired feature lists. + * + * \sa kill() + */ + virtual ~vpServo(); + + /*! + * Add a new set of 2 features \f$\bf s\f$ and \f${\bf s}^*\f$ in the task. + * + * \param s_cur : Current visual feature denoted \f$\bf s\f$. + * \param s_star : Desired visual feature denoted \f${\bf s}^*\f$. + * \param select : Feature selector. By default all the features in \e s and \e + * s_star are used, but is is possible to specify which one is used in case of + * multiple features. + * + * The following sample code explain how to use this method to add a visual + * feature point \f$(x,y)\f$: + * \code + * vpFeaturePoint s, s_star; + * ... + * vpServo task; + * task.addFeature(s, s_star); + * \endcode + * + * For example to use only the \f$x\f$ visual feature, the previous code + * becomes: + * \code + * vpFeaturePoint s, s_star; + * ... + * vpServo task; + * task.addFeature(s, s_star, vpFeaturePoint::selectX()); + * \endcode + */ + void addFeature(vpBasicFeature &s_cur, vpBasicFeature &s_star, unsigned int select = vpBasicFeature::FEATURE_ALL); + + /*! + * Add a new features \f$\bf s\f$ in the task. The desired visual feature + * denoted \f${\bf s}^*\f$ is equal to zero. + * + * \param s_cur : Current visual feature denoted \f$\bf s\f$. + * \param select : Feature selector. By default all the features in \e s are + * used, but is is possible to specify which one is used in case of multiple + * features. + * + * The following sample code explain how to use this method to add a \f$\theta + * {\bf u} =(\theta u_x, \theta u_y, \theta u_z)\f$ feature: + * \code + * vpFeatureThetaU s(vpFeatureThetaU::cRcd); + * ... + * vpServo task; + * task.addFeature(s); + * \endcode + * + * For example to use only the \f$\theta u_x\f$ feature, the previous code + * becomes: + * \code + * vpFeatureThetaU s(vpFeatureThetaU::cRcd); + * ... + * vpServo task; + * task.addFeature(s, vpFeatureThetaU::selectTUx); + * \endcode + */ + void addFeature(vpBasicFeature &s_cur, unsigned int select = vpBasicFeature::FEATURE_ALL); + + /*! + * Compute the control law specified using setServo(). See vpServo::vpServoType + * for more details concerning the control laws that are available. The \ref + * tutorial-ibvs and \ref tutorial-boost-vs are also useful to illustrate the + * usage of this function. + * + * The general form of the control law is the following: + * + * \f[ + * {\bf \dot q} = \pm \lambda {{\bf \widehat J}_e}^+ {\bf e} + * \f] + * + * where : + * - \f${\bf \dot q}\f$ is the resulting velocity command to apply to the + * robot. + * - the sign of the control law depends on the eye-in-hand or eye-to-hand + * configuration. + * - \f$\bf J\f$ is the Jacobian of the task. It is function of the interaction + * matrix and of the robot Jacobian. + * - \f$\bf e = (s-s^*)\f$ is the error to regulate. + * + * To ensure continuous sequencing the computeControlLaw(double) function can + * be used. It will ensure that the velocities that are computed are + * continuous. + */ + vpColVector computeControlLaw(); + + /*! + * Compute the control law specified using setServo(). See vpServo::vpServoType + * for more details concerning the control laws that are available. The \ref + * tutorial-boost-vs is also useful to illustrate the usage of this function. + * + * To the general form of the control law given in computeControlLaw(), we add + * here an additional term that comes from the task sequencing approach + * described in \cite Mansard07e equation (17). This additional term allows to + * compute continuous velocities by avoiding abrupt changes in the command. + * + * The form of the control law considered here is the following: + * + * \f[ + * {\bf \dot q} = \pm \lambda {{\bf \widehat J}_e}^+ {\bf e} \mp \lambda {{\bf + * \widehat J}_{e(0)}}^+ {{\bf e}(0)} \exp(-\mu t) \f] + * + * where : + * - \f${\bf \dot q}\f$ is the resulting continuous velocity command to apply + * to the robot. + * - the sign of the control law depends on the eye-in-hand or eye-to-hand + * configuration. + * - \f$\bf J\f$ is the Jacobian of the task. It is function of the interaction + * matrix and of the robot Jacobian. + * - \f$\bf e = (s-s^*)\f$ is the error to regulate. + * - \f$t\f$ is the time given as parameter of this method. + * - \f$\mu\f$ is a gain that is set by default to 4 and that could be modified + * using setMu(). + * - \f${\bf \widehat J}_{e(0)}^+ {\bf e}(0)\f$ is the value of \f${\bf + * \widehat J}_e^+ {\bf e}\f$ when \f$t=0\f$. This value is internally stored + * either at the first call of this method, or when \e t parameter is set to 0. + * + * \param t : Time in second. When set to zero, \f${{\bf \widehat J}_{e(0)}}^+ + * {{\bf e}(0)}\f$ is refreshed internally. + */ + vpColVector computeControlLaw(double t); + + /*! + * Compute the control law specified using setServo(). See vpServo::vpServoType + * for more details concerning the control laws that are available. + * + * To the general form of the control law given in computeControlLaw(), we add + * here an additional term that comes from the task sequencing approach + * described in \cite Mansard07e equation (17). This additional term allows to + * compute continuous velocities by avoiding abrupt changes in the command. + * + * The form of the control law considered here is the following: + * + * \f[ + * {\bf \dot q} = \pm \lambda {{\bf \widehat J}_e}^+ {\bf e} + \left({\bf \dot + * e}(0) \mp \lambda {{\bf \widehat J}_{e(0)}}^+ {{\bf e}(0)}\right) \exp(-\mu + * t) \f] + * + * where : + * - \f${\bf \dot q}\f$ is the resulting continuous velocity command to apply + * to the robot. + * - the sign of the control law depends on the eye-in-hand or eye-to-hand + * configuration. + * - \f$\bf J\f$ is the Jacobian of the task. It is function of the interaction + * matrix and of the robot Jacobian. + * - \f$\bf e = (s-s^*)\f$ is the error to regulate. + * - \f$t\f$ is the time given as parameter of this method. + * - \f$\mu\f$ is a gain that is set by default to 4 and that could be modified + * using setMu(). + * - \f${\bf \widehat J}_{e(0)}^+ {\bf e}(0)\f$ is the value of \f${\bf + * \widehat J}_e^+ {\bf e}\f$ when \f$t=0\f$. This value is internally stored + * either at the first call of this method, or when \e t parameter is set to 0. + * + * \param t : Time in second. When set to zero, \f${{\bf \widehat J}_{e(0)}}^+ + * {{\bf e}(0)}\f$ is refreshed internally. \param e_dot_init : Initial value + * of \f${\bf \dot e}(0)\f$. + */ + vpColVector computeControlLaw(double t, const vpColVector &e_dot_init); + + /*! + * Compute the error \f$\bf e =(s - s^*)\f$ between the current set of visual + * features \f$\bf s\f$ and the desired set of visual features \f$\bf s^*\f$. + * + * \return The error vector \f$\bf e\f$. + */ + vpColVector computeError(); + + /*! + * Compute and return the interaction matrix related to the set of visual + * features. + * + * \return The interaction matrix \f${\widehat {\bf L}}_e\f$ used in the + * control law specified using setServo(). + */ + vpMatrix computeInteractionMatrix(); + + /*! + * Return the task dimension. + */ + unsigned int getDimension() const; + + /*! + * Return the error \f$\bf e = (s - s^*)\f$ between the current set of visual + * features \f$\bf s\f$ and the desired set of visual features \f$\bf s^*\f$. The + * error vector is updated after a call of computeError() or computeControlLaw(). + * \code + * vpServo task; + * ... + * vpColVector v = task.computeControlLaw(); // Compute the velocity corresponding to the visual servoing + * vpColVector e = task.getError(); // Get the error vector + * \endcode + */ + inline vpColVector getError() const { return error; } + + /*! + * Return the interaction matrix \f$L\f$ used to compute the task jacobian + * \f$J_1\f$. The interaction matrix is updated after a call to + * computeInteractionMatrix() or computeControlLaw(). + * + * \code + * vpServo task; + * ... + * vpColVector v = task.computeControlLaw(); // Compute the velocity corresponding to the visual servoing vpMatrix + * L = task.getInteractionMatrix(); // Get the interaction matrix used to compute v + * \endcode + * \sa getTaskJacobian() + */ + inline vpMatrix getInteractionMatrix() const { return L; } + + /*! + * Return the projection operator \f${\bf I}-{\bf W}^+{\bf W}\f$. This + * operator is updated after a call of computeControlLaw(). + * + * \code + * vpServo task; + * ... + * vpColVector v = task.computeControlLaw(); // Compute the velocity corresponding to the visual servoing + * vpMatrix I_WpW = task.getI_WpW(); // Get the projection operator + * \endcode + * \sa getWpW() + */ + vpMatrix getI_WpW() const { return I_WpW; } + + /*! + * Return the visual servo type. + */ + inline vpServoType getServoType() const { return servoType; } + + /*! + * Return the large projection operator. This operator is updated + * after a call of computeControlLaw(). + * + * \code + * vpServo task; + * ... + * vpColVector v = task.computeControlLaw(); // Compute the velocity corresponding to the visual servoing + * vpMatrix P = task.getP(); // Get the large projection operator + * \endcode + * \sa getP() + */ + vpMatrix getLargeP() const { return P; } + + /*! + * Return the task jacobian \f$J\f$. The task jacobian is updated after a call + * of computeControlLaw(). + * + * In the general case, the task jacobian is given by \f${\bf J} = {\widehat + * {\bf L}} {^c}{\bf V}_a {^a}{\bf J}_e\f$. + * \code + * vpServo task; + * ... + * vpColVector v = task.computeControlLaw(); // Compute the velocity corresponding to the visual servoing vpMatrix + * J = task.getTaskJacobian(); // Get the task jacobian used to compute v + * \endcode + * \sa getTaskJacobianPseudoInverse(), getInteractionMatrix() + */ + vpMatrix getTaskJacobian() const { return J1; } + + /*! + * Return the pseudo inverse of the task jacobian \f$J\f$. + * + * In the general case, the task jacobian is given by \f${\bf J} = {\widehat + * {\bf L}} {^c}{\bf V}_a {^a}{\bf J}_e\f$. + * + * The task jacobian and its pseudo inverse are updated after a call of computeControlLaw(). + * + * \return Pseudo inverse \f${J}^{+}\f$ of the task jacobian. + * \code + * vpServo task; + * ... + * vpColVector v = task.computeControlLaw(); // Compute the velocity corresponding to the visual servoing + * vpMatrix Jp = task.getTaskJacobianPseudoInverse(); // Get the pseudo inverse of task jacobian used to compute v + * \endcode + * + * \sa getTaskJacobian() + */ + vpMatrix getTaskJacobianPseudoInverse() const { return J1p; } + + /*! + * Return the rank of the task jacobian. The rank is updated after a call of computeControlLaw(). + * + * \code + * vpServo task; + * ... + * vpColVector v = task.computeControlLaw(); // Compute the velocity corresponding to the visual servoing + * unsigned int rank = task.getTaskRank(); // Get the rank of the task jacobian + * \endcode + */ + unsigned int getTaskRank() const { return rankJ1; } + + /*! + * Get task singular values. + * + * \return Singular values that relies on the task jacobian pseudo inverse. + */ + inline vpColVector getTaskSingularValues() const { return sv; } + + /*! + * Return the projection operator \f${\bf W}^+{\bf W}\f$. This operator is + * updated after a call of computeControlLaw(). + * + * When the dimension of the task is equal to the number of degrees of freedom + * available \f${\bf W^+W = I}\f$. + * + * \code + * vpServo task; + * ... + * vpColVector v = task.computeControlLaw(); // Compute the velocity corresponding to the visual servoing + * vpMatrix WpW = task.getWpW(); // Get the projection operator + * \endcode + * \sa getI_WpW() + */ + vpMatrix getWpW() const { return WpW; } + + /*! + * Return the velocity twist matrix used to transform a velocity skew vector + * from end-effector frame into the camera frame. + */ + vpVelocityTwistMatrix get_cVe() const { return cVe; } + /*! + * Return the velocity twist matrix used to transform a velocity skew vector + * from robot fixed frame (also called world or base frame) into the camera + * frame. + */ + vpVelocityTwistMatrix get_cVf() const { return cVf; } + + /*! + * Return the velocity twist matrix used to transform a velocity skew vector + * from robot end-effector frame into the fixed frame (also called world or + * base frame). + */ + vpVelocityTwistMatrix get_fVe() const { return fVe; } + + /*! + * Return the robot jacobian expressed in the end-effector frame. + */ + vpMatrix get_eJe() const { return eJe; } + + /*! + * Return the robot jacobian expressed in the robot fixed frame (also called + * world or base frame). + */ + vpMatrix get_fJe() const { return fJe; } + + /*! + * Return pseudo-inverse threshold used to test the singular values. If + * a singular value is lower than this threshold we consider that the + * matrix is not full rank. + * + * \sa setPseudoInverseThreshold() + */ + double getPseudoInverseThreshold() const { return m_pseudo_inverse_threshold; } + + /*! + * Task destruction. Kill the current and desired visual feature lists. + * + * This function is called in the destructor. Since ViSP > 3.3.0 it is no more + * mandatory to call explicitly kill(). + * + * \code + * vpServo task ; + * vpFeatureThetaU s; + * ... + * task.addFeature(s); // Add current ThetaU feature + * + * task.kill(); // This call is no more mandatory since achieved in the destructor + * \endcode + */ + void kill(); + + /*! + * Prints on \e os stream information about the task: + * + * \param display_level : Indicates which are the task information to print. See + * vpServo::vpServoPrintType for more details. + * + * \param os : Output stream. + */ + void print(const vpServo::vpServoPrintType display_level = ALL, std::ostream &os = std::cout); + + /*! + * Compute and return the secondary task vector according to the classic + * projection operator \f${\bf I-W^+W}\f$ (see equation(7) in the paper + * \cite Marchand05b) or the new large projection operator (see equation(24) + * in the paper \cite Marey:2010). + * + * \param de2dt : Value of \f$\frac{\partial {\bf e_2}}{\partial t}\f$ the + * derivative of the secondary task \f${\bf e}_2\f$. + * \param useLargeProjectionOperator : if true will be use the large projection + * operator, if false the classic one (default). + * + * \return The secondary task vector. + * + * If the classic projection operator is used ( useLargeProjectionOperator = + * false (default value)) this function return: + * + * \f[ + * ({\bf I-W^+W})\frac{\partial {\bf e_2}}{\partial t} + * \f] + * + * Note that the secondary task vector need than to be added to the primary + * task which can be in the general case written as: \f[ + * -\lambda {\bf W^+W {\widehat {\bf J}}_e^+({\bf s-s^*})} + * \f] + * + * Otherwise if the new large projection operator is used ( + * useLargeProjectionOperator = true ) this function return: + * + * \f[ + * {\bf P}\frac{\partial {\bf e_2}}{\partial t} + * \f] + * + * where + * + * \f[ + * {\bf P} =\bar{\lambda }\left ( \left \| {\bf e} \right \| \right ){\bf P}_{ + * \left \| {\bf e } \right \| } \left ( 1 - \bar{\lambda }\left ( \left \| + * {\bf e } \right \| \right ) \right ) \left ( {\bf I-W^+W}\right ) \f] + * + * with + * + * \f[ + * {\bf P}_{\left \| {\bf e } \right \| } = I_{n} - \frac{1}{{\bf e }^\top {\bf + * J_{{\bf e }} } {\bf J_{{\bf e }}^\top }{\bf e }}{\bf J_{{\bf e }}^\top }{\bf + * e }{\bf e }^\top{\bf J_{{\bf e }} } \f] + * + * \warning computeControlLaw() must be call prior to this function since it + * updates the projection operators. + * + * The following sample code shows how to use this method to compute a + * secondary task using the classic projection operator: + * \code + * vpColVector v; + * // Velocity applied to the robot vpColVector de2dt; vpServo task; + * ... + * v = task.computeControlLaw(); // Compute the primary task + * v += task.secondaryTask(de2dt) // Compute and add the secondary task using the classical projection operator + * \endcode + * + * The following sample code shows how to use this method to compute a + * secondary task using the large projection operator: + * \code + * vpColVector v; + * // Velocity applied to the robot vpColVector de2dt; vpServo task; + * ... + * v = task.computeControlLaw(); // Compute the primary task + * v += task.secondaryTask(de2dt, true) // Compute and add the secondary task using the large projection operator + * \endcode + * + * \sa computeControlLaw() + */ + vpColVector secondaryTask(const vpColVector &de2dt, const bool &useLargeProjectionOperator = false); + + /*! + * Compute and return the secondary task vector according to the classic + * projection operator \f${\bf I-W^+W}\f$ (see equation(7) in the paper + * \cite Marchand05b) or the new large projection operator (see equation(24) + * in the paper \cite Marey:2010). + * + * \param e2 : Value of the secondary task \f${\bf e}_2\f$. + * \param de2dt : Value of \f$\frac{\partial {\bf e_2}}{\partial t}\f$ the + * derivative of the secondary task \f${\bf e}_2\f$. + * \param useLargeProjectionOperator : if true will be use the large projection + * operator, if false the classic one (default). + * + * \return The secondary task vector. + * + * If the classic projection operator is used ( useLargeProjectionOperator = + * false (default value)) this function return: + * + * \f[ + * -\lambda ({\bf I-W^+W}) {\bf e_2} + ({\bf I-W^+W})\frac{\partial {\bf + * e_2}}{\partial t} \f] + * + * Note that the secondary task vector need than to be added to the primary + * task which can be in the general case written as: \f[ + * -\lambda {\bf W^+W {\widehat {\bf J}}_e^+({\bf s-s^*})} + * \f] + * + * Otherwise if the new large projection operator is used ( + * useLargeProjectionOperator = true ) this function return: + * + * \f[ + * -\lambda {\bf P} {\bf e_2} + {\bf P}\frac{\partial {\bf e_2}}{\partial t} + * \f] + * + * where + * + * \f[ + * {\bf P} =\bar{\lambda }\left ( \left \| {\bf e} \right \| \right ){\bf P}_{ + * \left \| {\bf e } \right \| } \left ( 1 - \bar{\lambda }\left ( \left \| + * {\bf e } \right \| \right ) \right ) \left ( {\bf I-W^+W}\right ) \f] + * + * with + * + * \f[ + * {\bf P}_{\left \| {\bf e } \right \| } = I_{n} - \frac{1}{{\bf e }^\top {\bf + * J_{{\bf e }} } {\bf J_{{\bf e }}^\top }{\bf e }}{\bf J_{{\bf e }}^\top }{\bf + * e }{\bf e }^\top{\bf J_{{\bf e }} } \f] + * + * \warning computeControlLaw() must be call prior to this function since it + * updates the projection operators. + * + * The following sample code shows how to use this method to compute a + * secondary task using the classical projection operator: + * \code + * vpColVector v; + * // Velocity applied to the robot vpColVector e2; vpColVector de2dt; vpServo + * task; + * ... + * v = task.computeControlLaw(); // Compute the primary task + * v += task.secondaryTask(e2, de2dt) // Compute and add the secondary task using the classical projection operator + * \endcode + * + * The following sample code shows how to use this method to compute a + * secondary task using the large projection operator: + * \code + * vpColVector v; + * // Velocity applied to the robot vpColVector e2; vpColVector de2dt; vpServo + * task; + * ... + * v = task.computeControlLaw(); // Compute the primary task + * v += task.secondaryTask(e2, de2dt, true) // Compute and add the secondary task using the large projection operator + * \endcode + * + * \sa computeControlLaw() + */ + vpColVector secondaryTask(const vpColVector &e2, const vpColVector &de2dt, + const bool &useLargeProjectionOperator = false); + + /*! + * Compute and return the secondary task vector for joint limit avoidance + * \cite Marey:2010b using the new large projection operator (see equation(24) + * in the paper \cite Marey:2010). The robot avoids the joint limits very + * smoothly even when the main task constrains all the robot degrees of freedom. + * + * \param q : Actual joint positions vector + * + * \param dq : Actual joint velocities vector + * + * \param qmin : Vector containing the low limit value of each joint in the chain. + * \param qmax : Vector containing the high limit value of each joint in the chain. + * + * \param rho : tuning parameter \f${\left [ 0,\frac{1}{2} \right]}\f$ + * used to define the safe configuration for the joint. When the joint + * angle value cross the max or min boundaries (\f${ q_{l_{0}}^{max} }\f$ and + * \f${q_{l_{0}}^{min}}\f$) the secondary task is activated gradually. + * + * \param rho1 : tuning parameter \f${\left ] 0,1 \right ]}\f$ to compute the external + * boundaries (\f${q_{l_{1}}^{max}}\f$ and \f${q_{l_{1}}^{min}}\f$) for the joint + * limits. Here the secondary task it completely activated with the highest gain. + * + * \param lambda_tune : value \f${\left [ 0,1 \right ]}\f$ used to tune the + * difference in magnitude between the absolute value of the elements of the + * primary task and the elements of the secondary task. (See equation (17) + * \cite Marey:2010b ) + * + * \code + * vpServo task; + * vpColVector qmin; + * vpColVector qmax; + * vpColVector q; + * vpColVector dq; + * // Fill vector qmin and qmax with min and max limits of the joints (same joint order than vector q). + * // Update vector of joint position q and velocities dq; + * ... + * // Compute the velocity corresponding to the visual servoing + * vpColVector v = task.computeControlLaw(); + * // Compute and add the secondary task for the joint limit avoidance + * // using the large projection operator + * v += task.secondaryTaskJointLimitAvoidance(q, dq, qmin, qmax) + * \endcode + */ + vpColVector secondaryTaskJointLimitAvoidance(const vpColVector &q, const vpColVector &dq, const vpColVector &qmin, + const vpColVector &qmax, const double &rho = 0.1, + const double &rho1 = 0.3, const double &lambda_tune = 0.7); + + /*! + * Set a 6-dim column vector representing the degrees of freedom that are + * controlled in the camera frame. When set to 1, all the 6 dof are controlled. + * + * \param dof : Degrees of freedom to control in the camera frame. + * Below we give the correspondence between the index of the vector and the + * considered dof: + * - dof[0] = 1 if translation along X is controled, 0 otherwise; + * - dof[1] = 1 if translation along Y is controled, 0 otherwise; + * - dof[2] = 1 if translation along Z is controled, 0 otherwise; + * - dof[3] = 1 if rotation along X is controled, 0 otherwise; + * - dof[4] = 1 if rotation along Y is controled, 0 otherwise; + * - dof[5] = 1 if rotation along Z is controled, 0 otherwise; + * + * The following example shows how to use this function to control only wx, wy + * like a pan/tilt: + * \code + * #include + * #include + * + * #ifdef ENABLE_VISP_NAMESPACE + * using namespace VISP_NAMESPACE_NAME; + * #endif + * + * int main() + * { + * vpServo servo; + * servo.setServo(vpServo::EYEINHAND_CAMERA); + * vpFeaturePoint s, sd; + * servo.addFeature(s, sd); + * + * vpColVector dof(6, 1); + * dof[0] = 0; // turn off vx + * dof[1] = 0; // turn off vy + * dof[2] = 0; // turn off vz + * dof[5] = 0; // turn off wz + * servo.setCameraDoF(dof); + * + * while(1) { + * // vpFeatureBuilder::create(s, ...); // update current feature + * + * vpColVector v = servo.computeControlLaw(); // compute control law + * // only v[3] and v[4] corresponding to wx and wy are different from 0 + * } + * } + * \endcode + */ + void setCameraDoF(const vpColVector &dof); + + /*! + * Set a variable which enables to compute the interaction matrix at each + * iteration. + * + * When the interaction matrix is computed from the desired features \f${\bf + * s}^*\f$ which are in general constant, the interaction matrix \f${\widehat + * {\bf L}}_{s^*}\f$ is computed just at the first iteration of the servo + * loop. Sometimes, when the desired features are time dependent \f${{\bf + * s}(t)}^*\f$ or varying, the interaction matrix need to be computed at each + * iteration of the servo loop. This method allows to force the computation + * of \f${\widehat {\bf L}}\f$ in this particular case. + * + * \param force_computation : If true it forces the interaction matrix + * computation even if it is already done. + */ + void setForceInteractionMatrixComputation(bool force_computation) + { + this->forceInteractionMatrixComputation = force_computation; + } + + /*! + * Set the interaction matrix type (current, desired, mean or user defined) + * and how its inverse is computed. + * + * \param interactionMatrixType : The interaction matrix type. See vpServo::vpServoIteractionMatrixType for more + * details. + * + * \param interactionMatrixInversion : How is the inverse computed. See vpServo::vpServoInversionType for more details. + */ + void setInteractionMatrixType(const vpServoIteractionMatrixType &interactionMatrixType, + const vpServoInversionType &interactionMatrixInversion = PSEUDO_INVERSE); + + /*! + * Set the gain \f$\lambda\f$ used in the control law (see + * vpServo::vpServoType) as constant. + * + * The usage of an adaptive gain allows to reduce the convergence time, see + * setLambda(const vpAdaptiveGain&). + * + * \param c : Constant gain. Values are in general between 0.1 and 1. Higher + * is the gain, higher are the velocities that may be applied to the robot. + */ + void setLambda(double c) { lambda.initFromConstant(c); } + + /*! + * Set the gain \f$\lambda\f$ used in the control law (see + * vpServo::vpServoType) as adaptive. Value of \f$\lambda\f$ that is used in + * computeControlLaw() depend on the infinity norm of the task Jacobian. + * + * The usage of an adaptive gain rather than a constant gain allows to reduce + * the convergence time. + * + * \param gain_at_zero : the expected gain when \f$x=0\f$: \f$\lambda(0)\f$. + * \param gain_at_infinity : the expected gain when \f$x=\infty\f$: \f$\lambda(\infty)\f$. + * \param slope_at_zero : the expected slope of \f$\lambda(x)\f$ when \f$x=0\f$: \f${\dot \lambda}(0)\f$. + * + * For more details on these parameters see vpAdaptiveGain class. + */ + void setLambda(double gain_at_zero, double gain_at_infinity, double slope_at_zero) + { + lambda.initStandard(gain_at_zero, gain_at_infinity, slope_at_zero); + } + + /*! + * Set the gain \f$\lambda\f$ used in the control law (see + * vpServo::vpServoType) as adaptive. Value of \f$\lambda\f$ that is used in + * computeControlLaw() depend on the infinity norm of the task Jacobian. + * + * The usage of an adaptive gain rather than a constant gain allows to reduce + * the convergence time. + * + * \sa vpAdaptiveGain + */ + void setLambda(const vpAdaptiveGain &l) { lambda = l; } + + /*! + * Set the value of the parameter \f$\mu\f$ used to ensure the continuity of + * the velocities computed using computeControlLaw(double). + * + * A recommended value is 4. + */ + void setMu(double mu_) { this->mu = mu_; } + + /*! + * Set the visual servoing control law. + * \param servo_type : Control law that will be considered. + * See vpServo::vpServoType to see the possible values. + */ + void setServo(const vpServoType &servo_type); + + /*! + * Set the velocity twist matrix used to transform a velocity skew vector + * from end-effector frame into the camera frame. + */ + void set_cVe(const vpVelocityTwistMatrix &cVe_) + { + this->cVe = cVe_; + init_cVe = true; + } + + /*! + * Set the velocity twist matrix used to transform a velocity skew vector + * from end-effector frame into the camera frame. + */ + void set_cVe(const vpHomogeneousMatrix &cMe) + { + cVe.buildFrom(cMe); + init_cVe = true; + } + + /*! + * Set the velocity twist matrix used to transform a velocity skew vector + * from robot fixed frame (also called world or base frame) into the camera + * frame. + */ + void set_cVf(const vpVelocityTwistMatrix &cVf_) + { + this->cVf = cVf_; + init_cVf = true; + } + + /*! + * Set the velocity twist matrix used to transform a velocity skew vector + * from robot fixed frame (also called world or base frame) into the camera + * frame. + */ + void set_cVf(const vpHomogeneousMatrix &cMf) + { + cVf.buildFrom(cMf); + init_cVf = true; + } + + /*! + * Set the velocity twist matrix used to transform a velocity skew vector + * from robot end-effector frame into the fixed frame (also called world or + * base frame). + */ + void set_fVe(const vpVelocityTwistMatrix &fVe_) + { + this->fVe = fVe_; + init_fVe = true; + } + + /*! + * Set the velocity twist matrix used to transform a velocity skew vector + * from robot end-effector frame into the fixed frame (also called world or + * base frame). + */ + void set_fVe(const vpHomogeneousMatrix &fMe) + { + fVe.buildFrom(fMe); + init_fVe = true; + } + + /*! + * Set the robot jacobian expressed in the end-effector frame. + */ + void set_eJe(const vpMatrix &eJe_) + { + this->eJe = eJe_; + init_eJe = true; + } + + /*! + * Set the robot jacobian expressed in the robot fixed frame (also called + * world or base frame). + */ + void set_fJe(const vpMatrix &fJe_) + { + this->fJe = fJe_; + init_fJe = true; + } + + /*! + * Set the pseudo-inverse threshold used to test the singular values. If + * a singular value is lower than this threshold we consider that the + * matrix is not full rank. + * \param pseudo_inverse_threshold : Value to use. Default value is set to 1e-6. + * \sa getPseudoInverseThreshold() + */ + void setPseudoInverseThreshold(double pseudo_inverse_threshold) + { + m_pseudo_inverse_threshold = pseudo_inverse_threshold; + } + + /*! + * Test if all the initialization are correct. If true, the control law can + * be computed. + */ + bool testInitialization(); + + /*! + * Test if all the update are correct. If true control law can be computed. + */ + bool testUpdated(); + +protected: + /*! + * Initialize the servo with the following settings: + * + * - No control law is specified. The user has to call setServo() to specify + * the control law. + * - In the control law, the interaction matrix \f${\widehat {\bf L}}_e \f$ is + * computed with the desired features \f${\bf s}^*\f$. Using + * setInteractionMatrixType() you can also compute the interaction matrix with + * the current visual features, or from the mean \f$\left({\widehat {\bf L}}_s + * + {\widehat {\bf L}}_{s^*}\right)/2\f$. + * - In the control law the pseudo inverse will be used. The method + * setInteractionMatrixType() allows to use the transpose instead. + */ + void init(); + + /*! + * Compute the classic projection operator and the large projection operator. + */ + void computeProjectionOperators(const vpMatrix &J1_, const vpMatrix &I_, const vpMatrix &I_WpW_, + const vpColVector &error_, vpMatrix &P_) const; + +public: + //! Interaction matrix + vpMatrix L; + //! Error \f$(s - s^*)\f$ between the current set of visual features + //! \f$s\f$ and the desired set of visual features \f$s^*\f$. + //! This vector is updated after a call of computeError() or + //! computeControlLaw(). + vpColVector error; + //! Task Jacobian \f$J_1 = L {^c}V_a {^a}J_e\f$. + vpMatrix J1; + //! Pseudo inverse \f${J_1}^{+}\f$ of the task Jacobian. + vpMatrix J1p; + + //! Current state of visual features \f$s\f$. + //! This vector is updated after a call of computeError() or + //! computeControlLaw(). + vpColVector s; + //! Desired state of visual features \f$s^*\f$. + //! This vector is updated after a call of computeError() or + //! computeControlLaw(). + vpColVector sStar; + + //! Primary task \f$e_1 = {J_1}^{+}(s-s*)\f$ + vpColVector e1; + //! Task \f$e = e_1 + (I-{J_1}^{+} J_1) e_2\f$ + vpColVector e; + + //! Articular velocity + vpColVector q_dot; + //! Camera velocity + vpColVector v; + + //! Chosen visual servoing control law + vpServoType servoType; + + //! Rank of the task Jacobian + unsigned int rankJ1; + + //! List of current visual features \f$\bf s\f$. + std::list featureList; + //! List of desired visual features \f$\bf s^*\f$. + std::list desiredFeatureList; + //! List of selection among visual features + //! used for selection of a subset of each visual feature if required. + std::list featureSelectionList; + + //! Gain used in the control law. + vpAdaptiveGain lambda; + + //! Sign of the interaction +/- 1 (1 for eye-in-hand, -1 for + //! eye-to-hand configuration) + int signInteractionMatrix; + //! Type of the interaction matrix (current, mean, desired, user) + vpServoIteractionMatrixType interactionMatrixType; + //! Indicates if the transpose or the pseudo inverse of the + //! interaction matrix should be used to compute the task. + vpServoInversionType inversionType; + +protected: + /* + Twist transformation matrix + */ + + /*! + * Twist transformation matrix between camera frame (c) and robot end-effector + * frame (e). + */ + vpVelocityTwistMatrix cVe; + + /*! + * Boolean indicating if twist transformation matrix between camera frame (c) + * and robot end-effector frame (e) is set by the user and thus differs from eye + * matrix. + */ + bool init_cVe; + //! Twist transformation matrix between camera frame (c) and robot base frame (f). + vpVelocityTwistMatrix cVf; + /*! + * Boolean indicating if twist transformation matrix between camera frame (c) + * and robot base frame (f) is set by the user and thus differs from eye + * matrix. + */ + bool init_cVf; + /*! + * Twist transformation matrix between robot base frame (f) and robot + * end-effector frame (e). + */ + vpVelocityTwistMatrix fVe; + /*! + * Boolean indicating if twist transformation matrix between robot base frame (f) + * and robot end-effector frame(e) is set by the user and thus differs from eye + * matrix. + */ + bool init_fVe; + + /* + * Jacobians + */ + + //! Jacobian expressed in the end-effector frame (e). + vpMatrix eJe; + /*! + * Boolean indicating if Jacobian expressed in the end-effector frame (e) + * is set by the user and thus differs from eye matrix. + */ + bool init_eJe; + + //! Jacobian expressed in the robot base frame (f). + vpMatrix fJe; + /*! + * Boolean indicating if Jacobian expressed in the robot base frame (f) + * is set by the user and thus differs from eye matrix. + */ + bool init_fJe; + + /* + * Task building + */ + + //! true if the error has been computed. + bool errorComputed; + //! true if the interaction matrix has been computed. + bool interactionMatrixComputed; + //! Dimension of the task updated during computeControlLaw(). + unsigned int dim_task; + //! Flag to indicate if the task was killed + bool taskWasKilled; + //! Force the interaction matrix computation even if it is already done. + bool forceInteractionMatrixComputation; + + //! Identity matrix. + vpMatrix I; + //! Projection operators \f$\bf WpW\f$. + vpMatrix WpW; + //! Projection operators \f$\bf I-WpW\f$. + vpMatrix I_WpW; + /*! + * New Large projection operator (see equation(24) in the paper + * \cite Marey:2010). This projection operator allows performing secondary task + * even when the main task is full rank. \f[ + * {\bf P} =\bar{\lambda }\left ( \left \| {\bf e} \right \| \right ){\bf P}_{ + * \left \| {\bf e } \right \| } \left ( 1 - \bar{\lambda }\left ( \left \| + * {\bf e } \right \| \right ) \right ) \left ( {\bf I-W^+W}\right ) \f] + * + * with + * + * \f[ + * {\bf P}_{\left \| {\bf e } \right \| } = I_{n} - \frac{1}{{\bf e }^\top {\bf + * J_{{\bf e }} } {\bf J_{{\bf e }}^\top } + * {\bf e }}{\bf J_{{\bf e }}^\top }{\bf e }{\bf e }^\top{\bf J_{{\bf e }} } + * \f] + */ + vpMatrix P; + + //! Singular values from the pseudo inverse. + vpColVector sv; + + /*! + * Gain \f$ mu \f$ used to compute the control law with the task + * sequencing approach. + * \see computeControlLaw(double) and computeControlLaw(double, const vpColVector &) + */ + double mu; + + /*! + * First primary task value \f${\bf \widehat J}_e^+ {\bf e}\f$ used in the control + * law with the task sequencing approach when time is initial \f$t=0\f$. + * \see computeControlLaw(double) and computeControlLaw(double, const vpColVector &) + */ + vpColVector e1_initial; + + //! Boolean to know if cJc is identity (for fast computation) + bool iscJcIdentity; + + /*! + * A diag matrix used to determine which are the degrees of freedom that + * are controlled in the camera frame (c). + */ + vpMatrix cJc; + + bool m_first_iteration; //!< True until first call of computeControlLaw() is achieved + + double m_pseudo_inverse_threshold; //!< Threshold used in the pseudo inverse +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/vs/vpServoData.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/vs/vpServoData.h new file mode 100644 index 00000000..0910097d --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/vs/vpServoData.h @@ -0,0 +1,121 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Save data during the task execution. + */ + +/*! + \file vpServoData.h + \brief save data during the task execution +*/ + +#ifndef _vpServoData_h_ +#define _vpServoData_h_ + +#include +// Servo +#include + +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpServoData + * \ingroup group_task + * \brief Save data during the task execution when using vpServo. +*/ +class VISP_EXPORT vpServoData +{ +private: + std::ofstream velocityFile; + std::ofstream errorFile; + std::ofstream errorNormFile; + std::ofstream sFile; + std::ofstream sStarFile; + std::ofstream vNormFile; + + //! flag to known if velocity should be output in cm and degrees (true) + //! or in m/rad + bool cmDeg; + +public: +#ifndef DOXYGEN_SHOULD_SKIP_THIS + vpServoData(const vpServoData &sd) + : velocityFile(), errorFile(), errorNormFile(), sFile(), sStarFile(), vNormFile(), cmDeg(false) + { + *this = sd; + } + vpServoData &operator=(const vpServoData &) + { + throw vpException(vpException::functionNotImplementedError, "Not implemented!"); + } +#endif + + /*! + * Default constructor. + */ + vpServoData() : velocityFile(), errorFile(), errorNormFile(), sFile(), sStarFile(), vNormFile(), cmDeg(false) { } + + /*! + * Destructor that closes all data files if needed. + */ + virtual ~vpServoData() { close(); } + + //! Velocity output are set in cm and deg. + void setCmDeg(); + //! Velocity output are set in meter and deg (default). + void setMeterRad(); + //! Save visual-servoing control law data. + void save(const vpServo &task); + + /*! + * Set the directory in which data are saved. + * In this directory, creates the following files: + * - `vel.dat` that contains velocities computed + * - `error.dat` that contains visual-servo error \f$ {\bf e} = ({\bf s} - {\bf s}^*)\f$ + * - `errornorm.dat` that contains the sum square of the visual-servo error \f$ {\bf e} \f$ + * - `s.dat` that contains the current feature vector \f$ \bf s \f$ + * - `sStar.dat` that contains the desired feature vector \f$ {\bf s}^* \f$ + * + * @param directory : Path to the folder that contains data files to save. + * + * \sa close() + */ + void open(const std::string &directory); + + /*! + * Close all data files open with open() function. + * + * \sa open() + */ + void close(); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/vs/vpServoDisplay.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/vs/vpServoDisplay.h new file mode 100644 index 00000000..a92aa856 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/vs/vpServoDisplay.h @@ -0,0 +1,103 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Interface with the image for feature display. + */ + +/*! + * \file vpServoDisplay.h + * \brief interface with the image for feature display + */ + +#ifndef vpServoDisplay_H +#define vpServoDisplay_H + +#include +#include +#include +#include +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpServoDisplay + * \ingroup group_task + * \brief Interface with the image for feature display. +*/ +class VISP_EXPORT vpServoDisplay +{ +public: + /*! + * Display the current and the desired features in the image I. + * + * \warning To effectively display the dot graphics a call to + * vpDisplay::flush() is needed. + * + * \param s : Visual servoing control law. + * \param cam : Camera parameters. + * \param I : Image on which features have to be displayed. + * + * \param currentColor : Color for the current features. If vpColor::none, + * current features display is turned off. + * + * \param desiredColor : Color for the desired features. If vpColor::none, + * desired features display is turned off. + * + * \param thickness : Thickness of the feature representation. + */ + static void display(const vpServo &s, const vpCameraParameters &cam, const vpImage &I, + vpColor currentColor = vpColor::green, vpColor desiredColor = vpColor::red, + unsigned int thickness = 1); + + /*! + * Display the current and the desired features in the image I. + * + * \warning To effectively display the dot graphics a call to + * vpDisplay::flush() is needed. + * + * \param s : Visual servoing control law. + * \param cam : Camera parameters. + * \param I : Color image on which features have to be displayed. + * + * \param currentColor : Color for the current features. If vpColor::none, + * current features display is turned off. + * + * \param desiredColor : Color for the desired features. If vpColor::none, + * desired features display is turned off. + * + * \param thickness : Thickness of the feature representation. + */ + static void display(const vpServo &s, const vpCameraParameters &cam, const vpImage &I, + vpColor currentColor = vpColor::green, vpColor desiredColor = vpColor::red, + unsigned int thickness = 1); +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/include/visp3/vs/vpServoException.h b/dependency/x86/third_party/visp/3.7.0/include/visp3/vs/vpServoException.h new file mode 100644 index 00000000..b524c19c --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/include/visp3/vs/vpServoException.h @@ -0,0 +1,96 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2025 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * Description: + * Exception that can be emitted by the vpServo class and its derivatives. + */ + +/*! + * \file vpServoException.h + * \brief error that can be emitted by the vpServo class and its derivatives + */ + +#ifndef _vpServoException_h_ +#define _vpServoException_h_ + +#include +#include + +#include +#include + +BEGIN_VISP_NAMESPACE +/*! + * \class vpServoException + * \brief Error that can be emitted by the vpServo class and its derivatives. +*/ +class VISP_EXPORT vpServoException : public vpException +{ +public: + /*! + * \brief Lists the possible error than can be emitted while calling + * vpServo member + */ + enum errorServoCodeEnum + { + //! Current or desired feature list is empty + noFeatureError, + //! No degree of freedom is available to achieve the secondary task. + noDofFree, + //! Task was not killed properly + notKilledProperly, + //! Other exception + servoError + }; + +public: + /*! + * Constructor. + */ + vpServoException(int id, const char *format, ...) + { + this->code = id; + va_list args; + va_start(args, format); + setMessage(format, args); + va_end(args); + } + + /*! + * Constructor. + */ + vpServoException(int id, const std::string &msg) : vpException(id, msg) { } + + /*! + * Constructor. + */ + VP_EXPLICIT vpServoException(int id) : vpException(id) { } +}; +END_VISP_NAMESPACE +#endif diff --git a/dependency/x86/third_party/visp/3.7.0/lib/cmake/visp/VISPConfig-version.cmake b/dependency/x86/third_party/visp/3.7.0/lib/cmake/visp/VISPConfig-version.cmake new file mode 100644 index 00000000..dc9316d4 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/lib/cmake/visp/VISPConfig-version.cmake @@ -0,0 +1,31 @@ +# This is a basic version file for the Config-mode of find_package(). +# It is used by write_basic_package_version_file() as input file for configure_file() +# to create a version-file which can be installed along a config.cmake file. +# +# The created file sets PACKAGE_VERSION_EXACT if the current version string and +# the requested version string are exactly the same and it sets +# PACKAGE_VERSION_COMPATIBLE if the current version is >= requested version. +# The variable VISP_VERSION must be set before calling configure_file(). + +set(PACKAGE_VERSION 3.7.0) + +# There is a bug in CMake (at least in 2.7.0) whereby calling "find_package(FOO)" within +# "find_package(FOO)" results in the version being checked in the +# second version no matter if it was set. To get around this, check +# "PACKAGE_FIND_VERSION" and if empty set return variables to TRUE to +# make CMake happy. Not the best solution, but it does the trick. +if(NOT PACKAGE_FIND_VERSION) + set(PACKAGE_VERSION_COMPATIBLE TRUE) + set(PACKAGE_VERSION_EXACT TRUE) + return() +endif() + +if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}" ) + set(PACKAGE_VERSION_COMPATIBLE FALSE) +else() + set(PACKAGE_VERSION_COMPATIBLE TRUE) + if( "${PACKAGE_FIND_VERSION}" STREQUAL "${PACKAGE_VERSION}") + set(PACKAGE_VERSION_EXACT TRUE) + endif() +endif() + diff --git a/dependency/x86/third_party/visp/3.7.0/lib/cmake/visp/VISPConfig.cmake b/dependency/x86/third_party/visp/3.7.0/lib/cmake/visp/VISPConfig.cmake new file mode 100644 index 00000000..bead2966 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/lib/cmake/visp/VISPConfig.cmake @@ -0,0 +1,684 @@ +############################################################################# +# +# ViSP, open source Visual Servoing Platform software. +# Copyright (C) 2005 - 2025 by Inria. All rights reserved. +# +# This software is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# See the file LICENSE.txt at the root directory of this source +# distribution for additional information about the GNU GPL. +# +# For using ViSP with software that can not be combined with the GNU +# GPL, please contact Inria about acquiring a ViSP Professional +# Edition License. +# +# See https://visp.inria.fr for more information. +# +# This software was developed at: +# Inria Rennes - Bretagne Atlantique +# Campus Universitaire de Beaulieu +# 35042 Rennes Cedex +# France +# +# If you have questions regarding the use of this file, please contact +# Inria at visp@inria.fr +# +# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +# WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +# +# Description: +# CMake package config file for ViSP. +# +# ** File generated automatically, do not modify ** +# +# This file will define the following CMake variables: +# - VISP_LIBS : The list of all imported targets for VISP modules. +# - VISP_INCLUDE_DIRS : ViSP and third-party include directories +# - VISP_LIBRARIES : ViSP library to link against. Third-party libraries are +# linked automatically thanks to cmake export file VISPModules.cmake +# - VISP_VERSION_STRING : Full ViSP version that is build, "3.7.0" +# - VISP_VERSION_MAJOR : Major version part of VISP_VERSION: "3" +# - VISP_VERSION_MINOR : Minor version part of VISP_VERSION: "7" +# - VISP_VERSION_PATCH : Patch version part of VISP_VERSION: "0" +# +# Advanced variables: +# - VISP_SHARED : Use ViSP as shared library +# - VISP_CONFIG_PATH : Path to this VISPConfig.cmake +# - VISP_USE_FILE : File to include to use ViSP without specific cmake code +# - VISP_LIB_COMPONENTS: Present VISP modules list +# +# Typical usage in user project: +# +# find_package(VISP REQUIRED) +# include_directories(${VISP_INCLUDE_DIRS}) +# target_link_libraries(MY_TARGET_NAME ${VISP_LIBRARIES}) +# +# It is also possible to build your project using VISP_USE_FILE. +# +# find_package(VISP) +# if(VISP_FOUND) +# include(${VISP_USE_FILE}) +# endif() +# +# Or you can search for specific ViSP modules: +# +# find_package(VISP REQUIRED core io) +# +# If the module is found then VISP__FOUND is set to TRUE. +# +############################################################################# + +#if(VISP_FOUND) +# return() +#endif() + +#set(VISP_FOUND TRUE) + +# Set the version numbers +set(VISP_VERSION_STRING "3.7.0") +set(VISP_VERSION_MAJOR "3") +set(VISP_VERSION_MINOR "7") +set(VISP_VERSION_PATCH "0") + +set(VISP_LIB_COMPONENTS visp_vs;visp_visual_features;visp_vision;visp_tt_mi;visp_tt;visp_me;visp_mbt;visp_klt;visp_dnn_tracker;visp_blob;visp_sensor;visp_robot;visp_io;visp_imgproc;visp_gui;visp_detection;visp_core;visp_ar) + +# Form list of modules (components) to find +if(NOT VISP_FIND_COMPONENTS) + set(VISP_FIND_COMPONENTS ${VISP_LIB_COMPONENTS}) +endif() + +# expand short module names and see if requested components exist +set(VISP_FIND_COMPONENTS_ "") +foreach(__vpcomponent ${VISP_FIND_COMPONENTS}) + if(NOT __vpcomponent MATCHES "^visp_") + set(__vpcomponent visp_${__vpcomponent}) + endif() + list(FIND VISP_LIB_COMPONENTS ${__vpcomponent} __vpcomponentIdx) + if(__vpcomponentIdx LESS 0) + #requested component is not found... + if(NOT DEFINED VISP_FIND_REQUIRED_${__vpcomponent} OR VISP_FIND_REQUIRED_${__vpcomponent}) + message(FATAL_ERROR "${__vpcomponent} is required but was not found") + elseif(NOT VISP_FIND_QUIETLY) + message(WARNING "Optional ${__vpcomponent} was not found") + endif() + #indicate that module is NOT found + string(TOUPPER "${__vpcomponent}" __vpcomponentUP) + set(${__vpcomponentUP}_FOUND "${__vpcomponentUP}_FOUND-NOTFOUND") + else() + list(APPEND VISP_FIND_COMPONENTS_ ${__vpcomponent}) + # Not using list(APPEND) here, because VISP_LIBS may not exist yet. + # Also not clearing VISP_LIBS anywhere, so that multiple calls + # to find_package(VISP) with different component lists add up. + set(VISP_LIBS ${VISP_LIBS} "${__vpcomponent}") + #indicate that module is found + string(TOUPPER "${__vpcomponent}" __vpcomponentUP) + set(${__vpcomponentUP}_FOUND 1) + endif() +endforeach() +set(VISP_FIND_COMPONENTS ${VISP_FIND_COMPONENTS_}) +#message("VISP_FIND_COMPONENTS: ${VISP_FIND_COMPONENTS}") + +# Resolve dependencies +foreach(__opttype OPT DBG) + set(VISP_LIBS_${__opttype} "${VISP_LIBS}") +endforeach() + +# Some additional settings are required if ViSP is built as static libs +set(VISP_SHARED ON) + +# Extract the directory where *this* file has been installed (determined at cmake run-time) +set(VISP_CONFIG_PATH ${VISP_DIR} CACHE PATH "ViSPConfig.cmake path location" FORCE) +mark_as_advanced(VISP_CONFIG_PATH) + +if(NOT WIN32) + # Since this file is installed in ./${VISP_INSTALL_LIBDIR}/cmake/visp + # the first "../../" are here to go to ${VISP_INSTALL_LIBDIR}, and then + # VISP_INSTALL_LIBDIR_TO_PARENT goes to the parent dir "." + set(VISP_INSTALL_PATH "${VISP_CONFIG_PATH}/../../../") + # Get the absolute path with no ../.. relative marks, to eliminate implicit linker warnings + if(${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} VERSION_LESS 2.8) + get_filename_component(VISP_INSTALL_PATH "${VISP_INSTALL_PATH}" ABSOLUTE) + else() + get_filename_component(VISP_INSTALL_PATH "${VISP_INSTALL_PATH}" REALPATH) + endif() +endif() + +# Tells the user project where to find ViSP headers +set(VISP_INCLUDE_DIRS "${VISP_INSTALL_PATH}/include;/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/opencv/4.13.0/include/opencv4;/usr/include;/home/lgv/miniconda3/include;/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/realsense/v2.56.5/include") + +# Tells the user project ViSP library name +set(VISP_LIBRARIES ${VISP_LIBS}) + +# need to be improved +if(POLICY CMP0024) + # Fix to prevent multiple includes + if(NOT TARGET visp_core) + cmake_policy(PUSH) + cmake_policy(SET CMP0024 NEW) + # Our library dependencies (contains definitions for IMPORTED targets) + include("${CMAKE_CURRENT_LIST_DIR}/VISPModules.cmake") + cmake_policy(POP) + endif() +else() + # Fix for cmake 2.8.7 to prevent multiple includes + if(NOT TARGET visp_core) + # Our library dependencies (contains definitions for IMPORTED targets) + include("${CMAKE_CURRENT_LIST_DIR}/VISPModules.cmake") + endif() +endif() + +# where to find the USE file to be used by user project +set(VISP_USE_FILE "${CMAKE_CURRENT_LIST_DIR}/VISPUse.cmake") + +if(BUILD_TEST_COVERAGE) + # Add build options for test coverage. Currently coverage is only supported + # on gcc compiler + # Because using -fprofile-arcs with shared lib can cause problems like: + # hidden symbol `__bb_init_func', we add this option only for static + # library build + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -ftest-coverage -fprofile-arcs") +endif() + +#---------------------------------------------------------------------- +# Remember VISP install path for apps +#---------------------------------------------------------------------- +set(VISP_BIN_INSTALL_PATH "bin") + +#---------------------------------------------------------------------- +# Remember VISP third party libs configuration +#---------------------------------------------------------------------- +set(ENABLE_VISP_NAMESPACE "OFF") +set(ENABLE_EXPLICIT_KEYWORD "OFF") +set(ENABLE_IMAGE_TOOLS_WARP "ON") +set(VISP_HAVE_AFMA6 "") +set(VISP_HAVE_APRILTAG "TRUE") +set(VISP_HAVE_ARSDK "") +set(VISP_HAVE_ATIDAQ "") +set(VISP_HAVE_ATLAS "") +set(VISP_HAVE_BICLOPS "") +set(VISP_HAVE_CLIPPER "TRUE") +set(VISP_HAVE_CMU1394 "") +set(VISP_HAVE_COIN3D "") +set(VISP_HAVE_COIN3D_AND_GUI "") +set(VISP_HAVE_COMEDI "") +set(VISP_CXX_STANDARD_98 "199711L") +set(VISP_CXX_STANDARD_11 "201103L") +set(VISP_CXX_STANDARD_14 "201402L") +set(VISP_CXX_STANDARD_17 "201703L") +set(VISP_CXX_STANDARD "201703L") +set(VISP_HAVE_D3D9 "") +set(VISP_HAVE_DC1394 "TRUE") +set(VISP_HAVE_DIRECTSHOW "") +set(VISP_HAVE_DISPLAY "") +set(VISP_HAVE_DMTX "") +set(VISP_HAVE_EIGEN3 "") +set(VISP_HAVE_FFMPEG "") +set(VISP_HAVE_FLIR_PTU_SDK "") +set(VISP_HAVE_FRANKA "") +set(VISP_HAVE_FLYCAPTURE "") +set(VISP_HAVE_FT_IIT_SDK "") +set(VISP_HAVE_GDI "") +set(VISP_HAVE_GTK "") +set(VISP_HAVE_GSL "") +set(VISP_HAVE_HOMOGRAPHY "TRUE") +set(VISP_HAVE_JACOSDK "") +set(VISP_HAVE_JPEG "TRUE") +set(VISP_HAVE_LAPACK "TRUE") +set(VISP_HAVE_LAPACK_BUILT_IN "") +set(VISP_HAVE_LIBFREENECT "") +set(VISP_HAVE_LIBFREENECT_AND_DEPENDENCIES "") +set(VISP_HAVE_LIBFREENECT_OLD "") +set(VISP_HAVE_LIBUSB_1 "") +set(VISP_HAVE_MAVSDK "") +set(VISP_HAVE_MINIZ "TRUE") +set(VISP_HAVE_MKL "") +set(VISP_HAVE_NETLIB "TRUE") +set(VISP_HAVE_NLOHMANN_JSON "TRUE") +set(VISP_HAVE_NULLPTR "TRUE") +set(VISP_HAVE_OCCIPITAL_STRUCTURE "") +set(VISP_HAVE_OGRE "") +set(VISP_HAVE_OIS "") +set(VISP_HAVE_OPENBLAS "") +set(VISP_HAVE_OPENCV "TRUE") +set(VISP_HAVE_OPENCV_VERSION "(4<<16 | 13<<8 | 0)") +set(VISP_HAVE_OPENGL "1") +set(VISP_HAVE_OPENMP "TRUE") +set(VISP_HAVE_PANDA3D "") +set(VISP_HAVE_PARPORT "1") +set(VISP_HAVE_PCL "") +set(VISP_HAVE_PCL_VERSION "") +set(VISP_HAVE_PIONEER "") +set(VISP_HAVE_PNG "TRUE") +set(VISP_HAVE_POLOLU "TRUE") +set(VISP_HAVE_PTHREAD "TRUE") +set(VISP_HAVE_PTU46 "") +set(VISP_HAVE_PUGIXML "TRUE") +set(VISP_HAVE_PYLON "") +set(VISP_HAVE_QBDEVICE "TRUE") +set(VISP_HAVE_QT "") +set(VISP_HAVE_QUALISYS "") +set(VISP_HAVE_REALSENSE2 "TRUE") +set(VISP_HAVE_SIMDLIB "TRUE") +set(VISP_HAVE_SOQT "") +set(VISP_HAVE_SOWIN "") +set(VISP_HAVE_SOXT "") +set(VISP_HAVE_STBIMAGE "TRUE") +set(VISP_HAVE_TAKKTILE2 "TRUE") +set(VISP_HAVE_TENSORRT "") +set(VISP_HAVE_THREADS "TRUE") +set(VISP_HAVE_TINYEXR "TRUE") +set(VISP_HAVE_UEYE "") +set(VISP_HAVE_UR_RTDE "") +set(VISP_HAVE_V4L2 "TRUE") +set(VISP_HAVE_VICON "") +set(VISP_HAVE_VIPER650 "") +set(VISP_HAVE_VIPER850 "") +set(VISP_HAVE_VIRTUOSE "") +set(VISP_HAVE_X11 "TRUE") +set(VISP_HAVE_XML2 "TRUE") +set(VISP_HAVE_YARP "") +set(VISP_HAVE_ZBAR "TRUE") + + + +set(VISP_COMPILER_FAIL_REGEX + "command line option .* is valid for .* but not for C\\+\\+" # GNU + "command line option .* is valid for .* but not for C" # GNU + "unrecognized .*option" # GNU + "unknown .*option" # Clang + "ignoring unknown option" # MSVC + "warning D9002" # MSVC, any lang + "option .*not supported" # Intel + "[Uu]nknown option" # HP + "[Ww]arning: [Oo]ption" # SunPro + "command option .* is not recognized" # XL + "not supported in this configuration; ignored" # AIX + "File with unknown suffix passed to linker" # PGI + "WARNING: unknown flag:" # Open64 + ) + +#---------------------------------------------------------------------- +# Propagate c++ standard compiler option if enabled during ViSP build +#---------------------------------------------------------------------- +if(${VISP_CXX_STANDARD} EQUAL ${VISP_CXX_STANDARD_11}) + set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_STANDARD_REQUIRED FALSE) + set(CMAKE_CXX_EXTENSIONS OFF) # use -std=c++11 instead of -std=gnu++11 + if(NOT CMAKE_CXX11_COMPILE_FEATURES) + set(CXX11_CXX_FLAGS ) + if(CXX11_CXX_FLAGS) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX11_CXX_FLAGS}") + endif() + endif() +elseif(${VISP_CXX_STANDARD} EQUAL ${VISP_CXX_STANDARD_14}) + set(CMAKE_CXX_STANDARD 14) + set(CMAKE_CXX_STANDARD_REQUIRED FALSE) + set(CMAKE_CXX_EXTENSIONS OFF) # use -std=c++14 instead of -std=gnu++14 + if(NOT CMAKE_CXX14_COMPILE_FEATURES) + set(CXX14_CXX_FLAGS ) + if(CXX14_CXX_FLAGS) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX14_CXX_FLAGS}") + endif() + endif() +elseif(${VISP_CXX_STANDARD} EQUAL ${VISP_CXX_STANDARD_17}) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED FALSE) + set(CMAKE_CXX_EXTENSIONS OFF) # use -std=c++17 instead of -std=gnu++17 + if(NOT CMAKE_CXX17_COMPILE_FEATURES) + set(CXX17_CXX_FLAGS -std=c++17) + if(CXX17_CXX_FLAGS) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX17_CXX_FLAGS}") + endif() + endif() +endif() + +#---------------------------------------------------------------------- +# Propagate openmp compiler option if enabled during ViSP build +#---------------------------------------------------------------------- +if(VISP_HAVE_OPENMP) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp") +endif() + +#---------------------------------------------------------------------- +# Propagate pcl compiler option if enabled during ViSP build +#---------------------------------------------------------------------- +if(VISP_HAVE_PCL) + set(PCL_DEPS_COMPILE_OPTIONS "") + if(PCL_DEPS_COMPILE_OPTIONS) + foreach(pcl_compile_option ${PCL_DEPS_COMPILE_OPTIONS}) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${pcl_compile_option}") + endforeach() + endif() +endif() + +#---------------------------------------------------------------------- +# Propagate -fPIC option requested with Qt5 that requires position independent +# code if Qt was built with -reduce-relocations. +#---------------------------------------------------------------------- +if(UNIX) + if(CMAKE_COMPILER_IS_GNUCXX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") # Is needed for ANDROID too. + endif() +endif() + +# Create a dependency to the target extracted from the *.cpp file and put the target in the solution dependency folder. +macro(visp_add_dependency file_cpp dependency) + get_filename_component(target ${file_cpp} NAME_WE) + if(TARGET visp_${dependency}) + add_dependencies(visp_${dependency} ${target}) + if(ENABLE_SOLUTION_FOLDERS) + set_target_properties(${target} PROPERTIES FOLDER "${dependency}") + endif() + endif() +endmacro() + +#---------------------------------------------------------------------- +# Some useful macro to be able to build the tutorials/demos/examples/apps along side ViSP +#---------------------------------------------------------------------- +# Create a target from the *.cpp file, link against ViSP libraries and add a dependency to ViSP library +# to ensure that the library is build before this target. +macro(visp_add_target file_cpp) + if(POLICY CMP0020) + cmake_policy(PUSH) + cmake_policy(SET CMP0020 NEW) # For UsTK: Qt5 + endif() + get_filename_component(target ${file_cpp} NAME_WE) + include_directories(${VISP_INCLUDE_DIRS}) + add_executable(${target} ${file_cpp} ${ARGN}) + target_link_libraries(${target} ${VISP_LIBRARIES}) + if(POLICY CMP0020) + cmake_policy(POP) #cmake_policy(SET CMP0020 NEW) # For UsTK: Qt5 + endif() +endmacro() + +# Same as visp_add_target() except that it has also an install target. +# Create a target from the *.cpp file, link against ViSP libraries and add a dependency to ViSP library +# to ensure that the library is build before this target and finaly add an install target. +macro(visp_add_app file_cpp) + if(POLICY CMP0020) + cmake_policy(PUSH) + cmake_policy(SET CMP0020 NEW) # For UsTK: Qt5 + endif() + get_filename_component(target ${file_cpp} NAME_WE) + include_directories(${VISP_INCLUDE_DIRS}) + add_executable(${target} ${file_cpp} ${ARGN}) + target_link_libraries(${target} ${VISP_LIBRARIES}) + install(TARGETS ${target} DESTINATION ${VISP_BIN_INSTALL_PATH} CONFIGURATIONS Release COMPONENT dev) + if(COMMAND visp_add_dependency) + visp_add_dependency(${cpp} "apps") + endif() + if(POLICY CMP0020) + cmake_policy(POP) #cmake_policy(SET CMP0020 NEW) # For UsTK: Qt5 + endif() +endmacro() + +function(visp_find_dataset found) + set(file_to_test "mbt/cube.cao") + set(_found FALSE) + + if(DEFINED ENV{VISP_INPUT_IMAGE_PATH}) + if(EXISTS "$ENV{VISP_INPUT_IMAGE_PATH}/${file_to_test}") + set(_found TRUE) + elseif(EXISTS "$ENV{VISP_INPUT_IMAGE_PATH}/ViSP-images/${file_to_test}") + set(_found TRUE) + elseif(EXISTS "$ENV{VISP_INPUT_IMAGE_PATH}/visp-images/${file_to_test}") + set(_found TRUE) + endif() + endif() + + if(NOT _found) + if(EXISTS "/usr/share/visp-images-data/ViSP-images/${file_to_test}") + set(_found TRUE) + elseif(EXISTS "/usr/share/visp-images-data/visp-images/${file_to_test}") + set(_found TRUE) + endif() + endif() + + # Export return values + set(${found} "${_found}" CACHE INTERNAL "") +endfunction(visp_find_dataset) + +# Add test only if ViSP dataset found +macro(visp_add_test) + visp_find_dataset(DATASET_FOUND) + if(DATASET_FOUND) + add_test(${ARGN}) + endif() +endmacro() + + +# Copy the data files to the same location than the target associated to the cpp files +# Since CMake 3.0.0 policy CMP0026 was introduced to disallow location property on target. +# If CMake 3.0.0 is used, we use $ to get the target location +if (CMAKE_VERSION VERSION_GREATER 2.8.12) + macro(visp_copy_data file_cpp file_data) + get_filename_component(target ${file_cpp} NAME_WE) + get_filename_component(target_location "${target_location}" PATH) + add_custom_command( + TARGET ${target} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy "${file_data}" "$" + ) + endmacro() +else() + macro(visp_copy_data file_cpp file_data) + get_filename_component(target ${file_cpp} NAME_WE) + get_target_property(target_location ${target} LOCATION) + get_filename_component(target_location "${target_location}" PATH) + add_custom_command( + TARGET ${target} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy "${file_data}" "${target_location}" + ) + endmacro() +endif() + +# Copy the data files to the same location than the target associated to the cpp files +# Since CMake 3.0.0 policy CMP0026 was introduced to disallow location property on target. +# If CMake 3.0.0 is used, we use $ to get the target location +if (CMAKE_VERSION VERSION_GREATER 2.8.12) + macro(visp_copy_dir file_cpp root_dir rel_dir) + get_filename_component(target ${file_cpp} NAME_WE) + get_filename_component(target_location "${target_location}" PATH) + add_custom_command( + TARGET ${target} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory "${root_dir}/${rel_dir}" "$/${rel_dir}" + ) + endmacro() +else() + macro(visp_copy_data file_cpp root_dir rel_dir) + get_filename_component(target ${file_cpp} NAME_WE) + get_target_property(target_location ${target} LOCATION) + get_filename_component(target_location "${target_location}" PATH) + add_custom_command( + TARGET ${target} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory "${root_dir}/${rel_dir}" "${target_location}/${rel_dir}" + ) + endmacro() +endif() + +# Check dependencies for examples +macro(visp_check_dependencies) + set(VP_DEPENDENCIES_FOUND TRUE) + foreach(d ${ARGN}) + if(NOT TARGET ${d}) + #message(WARNING "ViSP: Can't resolve dependency: ${d}") + set(VP_DEPENDENCIES_FOUND FALSE) + break() + endif() + endforeach() +endmacro() + +# add subdirectory if dependencies are found +# visp_add_subdirectory( REQUIRED_DEPS ) +macro(visp_add_subdirectory subdir) + set(__deps "") + + foreach(arg ${ARGN}) + if(arg STREQUAL "REQUIRED_DEPS") + set(__varname "__deps") + else() + list(APPEND ${__varname} ${arg}) + endif() + endforeach() + unset(__varname) + + if(__deps STREQUAL "") + set(VP_DEPENDENCIES_FOUND TRUE) + else() + visp_check_dependencies(${__deps}) + endif() + + if(VP_DEPENDENCIES_FOUND) + add_subdirectory(${subdir}) + endif() +endmacro() + +# add specific compile flag useful to turn warning coming from 3rdparty +macro(visp_set_source_file_compile_flag file) +# if(DEFINED ACTIVATE_WARNING_3PARTY_MUTE) +# if(ACTIVATE_WARNING_3PARTY_MUTE) + set(__cxxflags "") + set(__lang "CXX") + if("${file}" MATCHES "\\.(c)$") + set(__lang "C") + elseif("${file}" MATCHES "\\.(cpp|cc|cxx)$") + set(__lang "CXX") + elseif("${file}" MATCHES "\\.(f|F)$") + set(__lang "Fortran") + endif() + # Since cxx11 option makes try_compile() result wrong, we remove all the CXX_FLAGS + # when we check if an option is available or not + #set(CXX_FLAGS_BACKUP ${CMAKE_CXX_FLAGS}) + #set(CMAKE_CXX_FLAGS "") + foreach(cxxflag ${ARGN}) + visp_check_flag_support(${__lang} ${cxxflag} __support_flag "") + if(${__support_flag}) + set(__cxxflags "${__cxxflags} ${cxxflag}") + endif() + endforeach() + #set(CMAKE_CXX_FLAGS ${CXX_FLAGS_BACKUP}) + #unset(CXX_FLAGS_BACKUP) + if(NOT ${__cxxflags} STREQUAL "") + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/${file}") + set_source_files_properties("${CMAKE_CURRENT_LIST_DIR}/${file}" PROPERTIES COMPILE_FLAGS "${__cxxflags}") + elseif(EXISTS ${file}) # for files that are in the build tree (like those produced by qt moc) + set_source_files_properties("${file}" PROPERTIES COMPILE_FLAGS "${__cxxflags}") + endif() + endif() +# endif() +# endif() +endmacro() + +# check if a compiler flag is supported +macro(visp_check_flag_support lang flag varname base_options) + if(CMAKE_BUILD_TYPE) + set(CMAKE_TRY_COMPILE_CONFIGURATION ${CMAKE_BUILD_TYPE}) + endif() + + if("_${lang}_" MATCHES "_CXX_") + set(_lang CXX) + elseif("_${lang}_" MATCHES "_C_") + set(_lang C) + elseif("_${lang}_" MATCHES "_Fortran_") + set(_lang Fortran) + else() + set(_lang ${lang}) + endif() + string(TOUPPER "${flag}" ${varname}) + string(REGEX REPLACE "^(/|-)" "HAVE_${_lang}_" ${varname} "${${varname}}") + string(REGEX REPLACE " -|-|=| |\\." "_" ${varname} "${${varname}}") + visp_check_compiler_flag("${_lang}" "${base_options} ${flag}" ${${varname}} ${ARGN}) +endmacro() + +# test if a compiler flag is supported +macro(visp_check_compiler_flag LANG FLAG RESULT) + set(_fname "${ARGN}") + if(NOT DEFINED ${RESULT}) + if(_fname) + # nothing + elseif("_${LANG}_" MATCHES "_CXX_") + set(_fname "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/src.cxx") + #if("${CMAKE_CXX_FLAGS} ${FLAG} " MATCHES "-Werror " OR "${CMAKE_CXX_FLAGS} ${FLAG} " MATCHES "-Werror=unknown-pragmas ") + file(WRITE "${_fname}" "int main() { return 0; }\n") + #else() + # file(WRITE "${_fname}" "#pragma\nint main() { return 0; }\n") + #endif() + elseif("_${LANG}_" MATCHES "_C_") + set(_fname "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/src.c") + #if("${CMAKE_C_FLAGS} ${FLAG} " MATCHES "-Werror " OR "${CMAKE_C_FLAGS} ${FLAG} " MATCHES "-Werror=unknown-pragmas ") + file(WRITE "${_fname}" "int main(void) { return 0; }\n") + #else() + # file(WRITE "${_fname}" "#pragma\nint main(void) { return 0; }\n") + #endif() + elseif("_${LANG}_" MATCHES "_OBJCXX_") + set(_fname "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/src.mm") + #if("${CMAKE_CXX_FLAGS} ${FLAG} " MATCHES "-Werror " OR "${CMAKE_CXX_FLAGS} ${FLAG} " MATCHES "-Werror=unknown-pragmas ") + file(WRITE "${_fname}" "int main() { return 0; }\n") + #else() + # file(WRITE "${_fname}" "#pragma\nint main() { return 0; }\n") + #endif() + elseif("_${LANG}_" MATCHES "_Fortran_") + set(_fname "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/src.f") + file(WRITE "${_fname}" " PROGRAM TEST\n RETURN\n END\n") + else() + unset(_fname) + endif() + if(_fname) + if(NOT "x${ARGN}" STREQUAL "x") + file(RELATIVE_PATH __msg "${CMAKE_SOURCE_DIR}" "${ARGN}") + set(__msg " (check file: ${__msg})") + else() + set(__msg "") + endif() + message(STATUS "Performing Test ${RESULT}${__msg}") + try_compile(${RESULT} + "${CMAKE_BINARY_DIR}" + "${_fname}" + CMAKE_FLAGS "-DCMAKE_EXE_LINKER_FLAGS=${CMAKE_EXE_LINKER_FLAGS}" # CMP0056 do this on new CMake + COMPILE_DEFINITIONS "${FLAG}" + OUTPUT_VARIABLE OUTPUT) + + if(${RESULT}) + string(REPLACE ";" "," OUTPUT_LINES "${OUTPUT}") + string(REPLACE "\n" ";" OUTPUT_LINES "${OUTPUT_LINES}") + foreach(_regex ${VP_COMPILER_FAIL_REGEX}) + if(NOT ${RESULT}) + break() + endif() + foreach(_line ${OUTPUT_LINES}) + if("${_line}" MATCHES "${_regex}") + file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log + "Build output check failed:\n" + " Regex: '${_regex}'\n" + " Output line: '${_line}'\n") + set(${RESULT} 0) + break() + endif() + endforeach() + endforeach() + endif() + + if(${RESULT}) + set(${RESULT} 1 CACHE INTERNAL "Test ${RESULT}") + message(STATUS "Performing Test ${RESULT} - Success") + else() + message(STATUS "Performing Test ${RESULT} - Failed") + set(${RESULT} "" CACHE INTERNAL "Test ${RESULT}") + file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log + "Compilation failed:\n" + " source file: '${_fname}'\n" + " check option: '${FLAG}'\n" + "===== BUILD LOG =====\n" + "${OUTPUT}\n" + "===== END =====\n\n") + endif() + else() + set(${RESULT} 0) + endif() + endif() +endmacro() diff --git a/dependency/x86/third_party/visp/3.7.0/lib/cmake/visp/VISPModules-release.cmake b/dependency/x86/third_party/visp/3.7.0/lib/cmake/visp/VISPModules-release.cmake new file mode 100644 index 00000000..76c5711a --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/lib/cmake/visp/VISPModules-release.cmake @@ -0,0 +1,189 @@ +#---------------------------------------------------------------- +# Generated CMake target import file for configuration "Release". +#---------------------------------------------------------------- + +# Commands may need to know the format version. +set(CMAKE_IMPORT_FILE_VERSION 1) + +# Import target "visp_core" for configuration "Release" +set_property(TARGET visp_core APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) +set_target_properties(visp_core PROPERTIES + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libvisp_core.so.3.7.0" + IMPORTED_SONAME_RELEASE "libvisp_core.so.3.7" + ) + +list(APPEND _IMPORT_CHECK_TARGETS visp_core ) +list(APPEND _IMPORT_CHECK_FILES_FOR_visp_core "${_IMPORT_PREFIX}/lib/libvisp_core.so.3.7.0" ) + +# Import target "visp_dnn_tracker" for configuration "Release" +set_property(TARGET visp_dnn_tracker APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) +set_target_properties(visp_dnn_tracker PROPERTIES + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libvisp_dnn_tracker.so.3.7.0" + IMPORTED_SONAME_RELEASE "libvisp_dnn_tracker.so.3.7" + ) + +list(APPEND _IMPORT_CHECK_TARGETS visp_dnn_tracker ) +list(APPEND _IMPORT_CHECK_FILES_FOR_visp_dnn_tracker "${_IMPORT_PREFIX}/lib/libvisp_dnn_tracker.so.3.7.0" ) + +# Import target "visp_gui" for configuration "Release" +set_property(TARGET visp_gui APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) +set_target_properties(visp_gui PROPERTIES + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libvisp_gui.so.3.7.0" + IMPORTED_SONAME_RELEASE "libvisp_gui.so.3.7" + ) + +list(APPEND _IMPORT_CHECK_TARGETS visp_gui ) +list(APPEND _IMPORT_CHECK_FILES_FOR_visp_gui "${_IMPORT_PREFIX}/lib/libvisp_gui.so.3.7.0" ) + +# Import target "visp_imgproc" for configuration "Release" +set_property(TARGET visp_imgproc APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) +set_target_properties(visp_imgproc PROPERTIES + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libvisp_imgproc.so.3.7.0" + IMPORTED_SONAME_RELEASE "libvisp_imgproc.so.3.7" + ) + +list(APPEND _IMPORT_CHECK_TARGETS visp_imgproc ) +list(APPEND _IMPORT_CHECK_FILES_FOR_visp_imgproc "${_IMPORT_PREFIX}/lib/libvisp_imgproc.so.3.7.0" ) + +# Import target "visp_io" for configuration "Release" +set_property(TARGET visp_io APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) +set_target_properties(visp_io PROPERTIES + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libvisp_io.so.3.7.0" + IMPORTED_SONAME_RELEASE "libvisp_io.so.3.7" + ) + +list(APPEND _IMPORT_CHECK_TARGETS visp_io ) +list(APPEND _IMPORT_CHECK_FILES_FOR_visp_io "${_IMPORT_PREFIX}/lib/libvisp_io.so.3.7.0" ) + +# Import target "visp_klt" for configuration "Release" +set_property(TARGET visp_klt APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) +set_target_properties(visp_klt PROPERTIES + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libvisp_klt.so.3.7.0" + IMPORTED_SONAME_RELEASE "libvisp_klt.so.3.7" + ) + +list(APPEND _IMPORT_CHECK_TARGETS visp_klt ) +list(APPEND _IMPORT_CHECK_FILES_FOR_visp_klt "${_IMPORT_PREFIX}/lib/libvisp_klt.so.3.7.0" ) + +# Import target "visp_me" for configuration "Release" +set_property(TARGET visp_me APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) +set_target_properties(visp_me PROPERTIES + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libvisp_me.so.3.7.0" + IMPORTED_SONAME_RELEASE "libvisp_me.so.3.7" + ) + +list(APPEND _IMPORT_CHECK_TARGETS visp_me ) +list(APPEND _IMPORT_CHECK_FILES_FOR_visp_me "${_IMPORT_PREFIX}/lib/libvisp_me.so.3.7.0" ) + +# Import target "visp_sensor" for configuration "Release" +set_property(TARGET visp_sensor APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) +set_target_properties(visp_sensor PROPERTIES + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libvisp_sensor.so.3.7.0" + IMPORTED_SONAME_RELEASE "libvisp_sensor.so.3.7" + ) + +list(APPEND _IMPORT_CHECK_TARGETS visp_sensor ) +list(APPEND _IMPORT_CHECK_FILES_FOR_visp_sensor "${_IMPORT_PREFIX}/lib/libvisp_sensor.so.3.7.0" ) + +# Import target "visp_ar" for configuration "Release" +set_property(TARGET visp_ar APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) +set_target_properties(visp_ar PROPERTIES + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libvisp_ar.so.3.7.0" + IMPORTED_SONAME_RELEASE "libvisp_ar.so.3.7" + ) + +list(APPEND _IMPORT_CHECK_TARGETS visp_ar ) +list(APPEND _IMPORT_CHECK_FILES_FOR_visp_ar "${_IMPORT_PREFIX}/lib/libvisp_ar.so.3.7.0" ) + +# Import target "visp_blob" for configuration "Release" +set_property(TARGET visp_blob APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) +set_target_properties(visp_blob PROPERTIES + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libvisp_blob.so.3.7.0" + IMPORTED_SONAME_RELEASE "libvisp_blob.so.3.7" + ) + +list(APPEND _IMPORT_CHECK_TARGETS visp_blob ) +list(APPEND _IMPORT_CHECK_FILES_FOR_visp_blob "${_IMPORT_PREFIX}/lib/libvisp_blob.so.3.7.0" ) + +# Import target "visp_robot" for configuration "Release" +set_property(TARGET visp_robot APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) +set_target_properties(visp_robot PROPERTIES + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libvisp_robot.so.3.7.0" + IMPORTED_SONAME_RELEASE "libvisp_robot.so.3.7" + ) + +list(APPEND _IMPORT_CHECK_TARGETS visp_robot ) +list(APPEND _IMPORT_CHECK_FILES_FOR_visp_robot "${_IMPORT_PREFIX}/lib/libvisp_robot.so.3.7.0" ) + +# Import target "visp_visual_features" for configuration "Release" +set_property(TARGET visp_visual_features APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) +set_target_properties(visp_visual_features PROPERTIES + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libvisp_visual_features.so.3.7.0" + IMPORTED_SONAME_RELEASE "libvisp_visual_features.so.3.7" + ) + +list(APPEND _IMPORT_CHECK_TARGETS visp_visual_features ) +list(APPEND _IMPORT_CHECK_FILES_FOR_visp_visual_features "${_IMPORT_PREFIX}/lib/libvisp_visual_features.so.3.7.0" ) + +# Import target "visp_vs" for configuration "Release" +set_property(TARGET visp_vs APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) +set_target_properties(visp_vs PROPERTIES + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libvisp_vs.so.3.7.0" + IMPORTED_SONAME_RELEASE "libvisp_vs.so.3.7" + ) + +list(APPEND _IMPORT_CHECK_TARGETS visp_vs ) +list(APPEND _IMPORT_CHECK_FILES_FOR_visp_vs "${_IMPORT_PREFIX}/lib/libvisp_vs.so.3.7.0" ) + +# Import target "visp_vision" for configuration "Release" +set_property(TARGET visp_vision APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) +set_target_properties(visp_vision PROPERTIES + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libvisp_vision.so.3.7.0" + IMPORTED_SONAME_RELEASE "libvisp_vision.so.3.7" + ) + +list(APPEND _IMPORT_CHECK_TARGETS visp_vision ) +list(APPEND _IMPORT_CHECK_FILES_FOR_visp_vision "${_IMPORT_PREFIX}/lib/libvisp_vision.so.3.7.0" ) + +# Import target "visp_detection" for configuration "Release" +set_property(TARGET visp_detection APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) +set_target_properties(visp_detection PROPERTIES + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libvisp_detection.so.3.7.0" + IMPORTED_SONAME_RELEASE "libvisp_detection.so.3.7" + ) + +list(APPEND _IMPORT_CHECK_TARGETS visp_detection ) +list(APPEND _IMPORT_CHECK_FILES_FOR_visp_detection "${_IMPORT_PREFIX}/lib/libvisp_detection.so.3.7.0" ) + +# Import target "visp_mbt" for configuration "Release" +set_property(TARGET visp_mbt APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) +set_target_properties(visp_mbt PROPERTIES + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libvisp_mbt.so.3.7.0" + IMPORTED_SONAME_RELEASE "libvisp_mbt.so.3.7" + ) + +list(APPEND _IMPORT_CHECK_TARGETS visp_mbt ) +list(APPEND _IMPORT_CHECK_FILES_FOR_visp_mbt "${_IMPORT_PREFIX}/lib/libvisp_mbt.so.3.7.0" ) + +# Import target "visp_tt" for configuration "Release" +set_property(TARGET visp_tt APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) +set_target_properties(visp_tt PROPERTIES + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libvisp_tt.so.3.7.0" + IMPORTED_SONAME_RELEASE "libvisp_tt.so.3.7" + ) + +list(APPEND _IMPORT_CHECK_TARGETS visp_tt ) +list(APPEND _IMPORT_CHECK_FILES_FOR_visp_tt "${_IMPORT_PREFIX}/lib/libvisp_tt.so.3.7.0" ) + +# Import target "visp_tt_mi" for configuration "Release" +set_property(TARGET visp_tt_mi APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) +set_target_properties(visp_tt_mi PROPERTIES + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libvisp_tt_mi.so.3.7.0" + IMPORTED_SONAME_RELEASE "libvisp_tt_mi.so.3.7" + ) + +list(APPEND _IMPORT_CHECK_TARGETS visp_tt_mi ) +list(APPEND _IMPORT_CHECK_FILES_FOR_visp_tt_mi "${_IMPORT_PREFIX}/lib/libvisp_tt_mi.so.3.7.0" ) + +# Commands beyond this point should not need to know the version. +set(CMAKE_IMPORT_FILE_VERSION) diff --git a/dependency/x86/third_party/visp/3.7.0/lib/cmake/visp/VISPModules.cmake b/dependency/x86/third_party/visp/3.7.0/lib/cmake/visp/VISPModules.cmake new file mode 100644 index 00000000..2f17c3b3 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/lib/cmake/visp/VISPModules.cmake @@ -0,0 +1,217 @@ +# Generated by CMake + +if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.6) + message(FATAL_ERROR "CMake >= 2.6.0 required") +endif() +cmake_policy(PUSH) +cmake_policy(VERSION 2.6...3.20) +#---------------------------------------------------------------- +# Generated CMake target import file. +#---------------------------------------------------------------- + +# Commands may need to know the format version. +set(CMAKE_IMPORT_FILE_VERSION 1) + +# Protect against multiple inclusion, which would fail when already imported targets are added once more. +set(_targetsDefined) +set(_targetsNotDefined) +set(_expectedTargets) +foreach(_expectedTarget visp_core visp_dnn_tracker visp_gui visp_imgproc visp_io visp_klt visp_me visp_sensor visp_ar visp_blob visp_robot visp_visual_features visp_vs visp_vision visp_detection visp_mbt visp_tt visp_tt_mi) + list(APPEND _expectedTargets ${_expectedTarget}) + if(NOT TARGET ${_expectedTarget}) + list(APPEND _targetsNotDefined ${_expectedTarget}) + endif() + if(TARGET ${_expectedTarget}) + list(APPEND _targetsDefined ${_expectedTarget}) + endif() +endforeach() +if("${_targetsDefined}" STREQUAL "${_expectedTargets}") + unset(_targetsDefined) + unset(_targetsNotDefined) + unset(_expectedTargets) + set(CMAKE_IMPORT_FILE_VERSION) + cmake_policy(POP) + return() +endif() +if(NOT "${_targetsDefined}" STREQUAL "") + message(FATAL_ERROR "Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_targetsDefined}\nTargets not yet defined: ${_targetsNotDefined}\n") +endif() +unset(_targetsDefined) +unset(_targetsNotDefined) +unset(_expectedTargets) + + +# Compute the installation prefix relative to this file. +get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +if(_IMPORT_PREFIX STREQUAL "/") + set(_IMPORT_PREFIX "") +endif() + +# Create imported target visp_core +add_library(visp_core SHARED IMPORTED) + +set_target_properties(visp_core PROPERTIES + INTERFACE_LINK_LIBRARIES "/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/opencv/4.13.0/lib/libopencv_core.so.4.13.0;/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/opencv/4.13.0/lib/libopencv_imgproc.so.4.13.0;/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/opencv/4.13.0/lib/libopencv_highgui.so.4.13.0;/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/opencv/4.13.0/lib/libopencv_calib3d.so.4.13.0;/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/opencv/4.13.0/lib/libopencv_features2d.so.4.13.0;/usr/lib/x86_64-linux-gnu/liblapack.so;/usr/lib/x86_64-linux-gnu/libblas.so;/usr/lib/x86_64-linux-gnu/libz.so;/usr/lib/gcc/x86_64-linux-gnu/11/libgomp.so" +) + +# Create imported target visp_dnn_tracker +add_library(visp_dnn_tracker SHARED IMPORTED) + +set_target_properties(visp_dnn_tracker PROPERTIES + INTERFACE_LINK_LIBRARIES "visp_core" +) + +# Create imported target visp_gui +add_library(visp_gui SHARED IMPORTED) + +set_target_properties(visp_gui PROPERTIES + INTERFACE_LINK_LIBRARIES "visp_core" +) + +# Create imported target visp_imgproc +add_library(visp_imgproc SHARED IMPORTED) + +set_target_properties(visp_imgproc PROPERTIES + INTERFACE_LINK_LIBRARIES "visp_core" +) + +# Create imported target visp_io +add_library(visp_io SHARED IMPORTED) + +set_target_properties(visp_io PROPERTIES + INTERFACE_LINK_LIBRARIES "visp_core;/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/opencv/4.13.0/lib/libopencv_videoio.so.4.13.0;/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/opencv/4.13.0/lib/libopencv_imgcodecs.so.4.13.0;/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/opencv/4.13.0/lib/libopencv_imgproc.so.4.13.0" +) + +# Create imported target visp_klt +add_library(visp_klt SHARED IMPORTED) + +set_target_properties(visp_klt PROPERTIES + INTERFACE_LINK_LIBRARIES "visp_core;/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/opencv/4.13.0/lib/libopencv_video.so.4.13.0" +) + +# Create imported target visp_me +add_library(visp_me SHARED IMPORTED) + +set_target_properties(visp_me PROPERTIES + INTERFACE_LINK_LIBRARIES "visp_core" +) + +# Create imported target visp_sensor +add_library(visp_sensor SHARED IMPORTED) + +set_target_properties(visp_sensor PROPERTIES + INTERFACE_LINK_LIBRARIES "visp_core;/usr/lib/x86_64-linux-gnu/libv4l2.so;/usr/lib/x86_64-linux-gnu/libv4lconvert.so;/usr/lib/x86_64-linux-gnu/libdc1394.so;/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/realsense/v2.56.5/lib/librealsense2.so" +) + +# Create imported target visp_ar +add_library(visp_ar SHARED IMPORTED) + +set_target_properties(visp_ar PROPERTIES + INTERFACE_LINK_LIBRARIES "visp_core" +) + +# Create imported target visp_blob +add_library(visp_blob SHARED IMPORTED) + +set_target_properties(visp_blob PROPERTIES + INTERFACE_LINK_LIBRARIES "visp_core" +) + +# Create imported target visp_robot +add_library(visp_robot SHARED IMPORTED) + +set_target_properties(visp_robot PROPERTIES + INTERFACE_LINK_LIBRARIES "visp_core;visp_gui;visp_io;visp_sensor" +) + +# Create imported target visp_visual_features +add_library(visp_visual_features SHARED IMPORTED) + +set_target_properties(visp_visual_features PROPERTIES + INTERFACE_LINK_LIBRARIES "visp_core;visp_io;visp_me;visp_blob" +) + +# Create imported target visp_vs +add_library(visp_vs SHARED IMPORTED) + +set_target_properties(visp_vs PROPERTIES + INTERFACE_LINK_LIBRARIES "visp_core;visp_io;visp_me;visp_blob;visp_visual_features" +) + +# Create imported target visp_vision +add_library(visp_vision SHARED IMPORTED) + +set_target_properties(visp_vision PROPERTIES + INTERFACE_LINK_LIBRARIES "visp_core;visp_io;visp_me;visp_blob;visp_visual_features;/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/opencv/4.13.0/lib/libopencv_calib3d.so.4.13.0;/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/opencv/4.13.0/lib/libopencv_flann.so.4.13.0" +) + +# Create imported target visp_detection +add_library(visp_detection SHARED IMPORTED) + +set_target_properties(visp_detection PROPERTIES + INTERFACE_LINK_LIBRARIES "visp_core;visp_io;visp_me;visp_blob;visp_visual_features;visp_vision;/usr/lib/x86_64-linux-gnu/libzbar.so;/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/opencv/4.13.0/lib/libopencv_objdetect.so.4.13.0;/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/opencv/4.13.0/lib/libopencv_dnn.so.4.13.0" +) + +# Create imported target visp_mbt +add_library(visp_mbt SHARED IMPORTED) + +set_target_properties(visp_mbt PROPERTIES + INTERFACE_LINK_LIBRARIES "visp_core;visp_gui;visp_io;visp_klt;visp_me;visp_ar;visp_blob;visp_visual_features;visp_vision" +) + +# Create imported target visp_tt +add_library(visp_tt SHARED IMPORTED) + +set_target_properties(visp_tt PROPERTIES + INTERFACE_LINK_LIBRARIES "visp_core;visp_io;visp_me;visp_blob;visp_visual_features;visp_vision" +) + +# Create imported target visp_tt_mi +add_library(visp_tt_mi SHARED IMPORTED) + +set_target_properties(visp_tt_mi PROPERTIES + INTERFACE_LINK_LIBRARIES "visp_core;visp_io;visp_me;visp_blob;visp_visual_features;visp_vision;visp_tt" +) + +if(CMAKE_VERSION VERSION_LESS 2.8.12) + message(FATAL_ERROR "This file relies on consumers using CMake 2.8.12 or greater.") +endif() + +# Load information for each installed configuration. +get_filename_component(_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) +file(GLOB CONFIG_FILES "${_DIR}/VISPModules-*.cmake") +foreach(f ${CONFIG_FILES}) + include(${f}) +endforeach() + +# Cleanup temporary variables. +set(_IMPORT_PREFIX) + +# Loop over all imported files and verify that they actually exist +foreach(target ${_IMPORT_CHECK_TARGETS} ) + foreach(file ${_IMPORT_CHECK_FILES_FOR_${target}} ) + if(NOT EXISTS "${file}" ) + message(FATAL_ERROR "The imported target \"${target}\" references the file + \"${file}\" +but this file does not exist. Possible reasons include: +* The file was deleted, renamed, or moved to another location. +* An install or uninstall procedure did not complete successfully. +* The installation package was faulty and contained + \"${CMAKE_CURRENT_LIST_FILE}\" +but not all the files it references. +") + endif() + endforeach() + unset(_IMPORT_CHECK_FILES_FOR_${target}) +endforeach() +unset(_IMPORT_CHECK_TARGETS) + +# This file does not depend on other imported targets which have +# been exported from the same project but in a separate export set. + +# Commands beyond this point should not need to know the version. +set(CMAKE_IMPORT_FILE_VERSION) +cmake_policy(POP) diff --git a/dependency/x86/third_party/visp/3.7.0/lib/cmake/visp/VISPUse.cmake b/dependency/x86/third_party/visp/3.7.0/lib/cmake/visp/VISPUse.cmake new file mode 100644 index 00000000..a74b0148 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/lib/cmake/visp/VISPUse.cmake @@ -0,0 +1,43 @@ +############################################################################# +# +# ViSP, open source Visual Servoing Platform software. +# Copyright (C) 2005 - 2025 by Inria. All rights reserved. +# +# This software is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# See the file LICENSE.txt at the root directory of this source +# distribution for additional information about the GNU GPL. +# +# For using ViSP with software that can not be combined with the GNU +# GPL, please contact Inria about acquiring a ViSP Professional +# Edition License. +# +# See https://visp.inria.fr for more information. +# +# This software was developed at: +# Inria Rennes - Bretagne Atlantique +# Campus Universitaire de Beaulieu +# 35042 Rennes Cedex +# France +# +# If you have questions regarding the use of this file, please contact +# Inria at visp@inria.fr +# +# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +# WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +# +# Description: +# cmake PackageConfig file. +# +############################################################################# + + +# Tell the compiler where to find ViSP's header files +# and the third party headers we depend on +include_directories(${VISP_INCLUDE_DIRS}) + +# Tell the compiler where to find ViSP's libraries +# and the third party libraries we depend on +link_libraries(${VISP_LIBRARIES}) diff --git a/dependency/x86/third_party/visp/3.7.0/lib/libvisp_ar.so b/dependency/x86/third_party/visp/3.7.0/lib/libvisp_ar.so new file mode 120000 index 00000000..a0e0271e --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/lib/libvisp_ar.so @@ -0,0 +1 @@ +libvisp_ar.so.3.7 \ No newline at end of file diff --git a/dependency/x86/third_party/visp/3.7.0/lib/libvisp_ar.so.3.7 b/dependency/x86/third_party/visp/3.7.0/lib/libvisp_ar.so.3.7 new file mode 120000 index 00000000..ab4c7cd7 --- /dev/null +++ b/dependency/x86/third_party/visp/3.7.0/lib/libvisp_ar.so.3.7 @@ -0,0 +1 @@ +libvisp_ar.so.3.7.0 \ No newline at end of file diff --git a/dependency/x86/third_party/visp/3.7.0/lib/libvisp_ar.so.3.7.0 b/dependency/x86/third_party/visp/3.7.0/lib/libvisp_ar.so.3.7.0 new file mode 100644 index 0000000000000000000000000000000000000000..01957058251471ec3bf7acc27598dbabe6e29137 GIT binary patch literal 18344 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

oAc@NItJU<2mb@6q2T`7}>*3 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 z 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

*D(p%ea%5cK>;J_q8uEr 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

>dKJ4HSbcDsdNUmhEFb?uI58XriX`I752HlF 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

40=Opt=NDxil`i7_?;M`UlJ_zfX^4{Oo5U$?p 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 cBDjA^J{ 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

OJicPA74 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�*801!^-H%TAH(Bb-HD$%?F$QXZxx9E+5 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�S*c>6(PlCu@?+ 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

o;~?#IJa3&q8P1hT6o%xL5enP5%UHMDw!xOe)H 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