RANSAC-like fitting model methods used for different computer vision taks, such as 3D reconstruction, ego-motion estimation, image matching, etc., are based on sampling strategies that makes the output of the methods non-deterministic. Because of this, one should expect implementations of these methods in OpenCV to output different results when running multiple times on the same data and with the same paramters. But this is currently not happening, and running
F, status = cv2.findFundamentalMat(pts1, pts2, cv2.FM_RANSAC, ransacReprojThreshold, confidence, maxIters)
multiple times returns always the same fundamental matrix and number of inliers (FM_RANSAC can be replaced with any other USAC method). This is caused by a random seed initialised always to the same value in the OpenCV source code.
Therefore, STOP using this function!
Instead, start initialising the parameters of the struct UsacParams(), especially by randomly setting randomGeneratorState and the non-deterministic behaviour will be restored!
- Ubuntu 18.04 LTS
- OpenCV: 4.5.5
- Python: 3.10
Create a conda environment name USAC with py-opencv and tqdm packages:
conda create --name USAC
conda install -c conda-forge py-opencv
conda install -c conda-forge tqdm
From Linux terminal, run to activate the created conda environment and launch the testing Python script:
source run_USAC.sh
The script will run on two example images from the sequence Machine Hall 05 of the public EuRoC MAV Dataset located in data/EuR5.
Values of the arguments can be changed directly in the Python script or passed within the bash script.
The demo runs automatically the robust estimator MAGSAC++.
- n_runs: number of runs (default: 5)
- min_num_inliers: minimum number of inliers to accept the estimated fundamental matrix (default: 15)
- ransacReprojThreshold: maximum reprojection error allowed for RANSAC (default: 2.0)
- conf: confidence for RANSAC (default: 0.99)
- maxIters: maximum number of iterations for RANSAC (default: 1000)
- max_n_kps: maximum number of keypoints to detect in an image (default: 1000)
- dist_th: threshold on the Hamming distance for ORB features (default: 50)
- snn_th: threshold for the Lowe's ratio test or Second Nearest Neighbour (default: 0.6)
- feature: type of local image feature to use, for example SIFT or ORB (default: orb)
- SACestimator: algorithm to use for robustly estimating the fundamental matrix, for example RANSAC or MAGSAC++ (default: MAGSAC++)
The script returns the same fundamental matrix and number of inliers across the 100 runs without changing any value of the parameters. This behaviour is unexpected due to the sampling approach of the estimators (RANSAC, MAGSAC++). I posted a question on the OpenCV Forum to know more about this behaviour.
The behaviour is due to a random seed initialises always to zero when calling:
F, status = cv2.findFundamentalMat(pts1, pts2, cv2.USAC_MAGSAC, ransacReprojThreshold, confidence, maxIters)
or
F, status = cv2.findFundamentalMat(pts1, pts2, cv2.FM_RANSAC, ransacReprojThreshold, confidence, maxIters)
I would reccomend to avoid using the above functions. To fix the issue and folliwing the reply provided in OpenCV Forum, I would suggest using the following portions of code (here reported only for MAGSAC++):
def FindFundamentalMatMAGSACplusplus(pts1, pts2, ransacReprojThreshold, confidence, maxIters):
usac_params = cv2.UsacParams()
usac_params.randomGeneratorState = random.randint(0,1000000)
usac_params.confidence = confidence
usac_params.maxIterations = maxIters
usac_params.loMethod = cv2.LOCAL_OPTIM_SIGMA
usac_params.score = cv2.SCORE_METHOD_MAGSAC
usac_params.threshold = ransacReprojThreshold
# usac_params.isParallel = False # False is deafult
usac_params.loIterations = 10
usac_params.loSampleSize = 50
usac_params.neighborsSearch = cv2.NEIGH_GRID
usac_params.sampler = cv2.SAMPLING_UNIFORM
F, status = cv2.findFundamentalMat(pts1, pts2, usac_params)
return F, status
If you comment the line about setting the randomGenerateState, the code will generate the same output as the previous command:
F, status = cv2.findFundamentalMat(pts1, pts2, cv2.USAC_MAGSAC, ransacReprojThreshold, confidence, maxIters)
Setting the randomGenerateState to random integers will restore the standard non-deterministic behaviour of these estimators, providing different results for each run.
D. Barath, J. Noskova, M. Ivashechkin, J. Matas, MAGSAC++, a fast, reliable and accurate robust estimator, CVPR 2020
[paper] [code]
D. Mishkin, Evaluating OpenCV new RANSACs, Blog post [link]
The Python script is a personal adaption of codes taken from:
If you have any further enquiries, questions, comments, or you would like to file a bug report or a feature request, use the Github issue tracker.
This work is licensed under the MIT License. To view a copy of this license, see LICENSE.