mirror of
https://github.com/opencv/opencv.git
synced 2024-12-12 15:19:11 +08:00
233 lines
8.4 KiB
Python
233 lines
8.4 KiB
Python
|
"""
|
||
|
Stitching sample (advanced)
|
||
|
===========================
|
||
|
|
||
|
Show how to use Stitcher API from python.
|
||
|
"""
|
||
|
|
||
|
# Python 2/3 compatibility
|
||
|
from __future__ import print_function
|
||
|
|
||
|
import argparse
|
||
|
|
||
|
import cv2 as cv
|
||
|
import numpy as np
|
||
|
|
||
|
from opencv_stitching.stitcher import Stitcher
|
||
|
|
||
|
from opencv_stitching.image_handler import ImageHandler
|
||
|
from opencv_stitching.feature_detector import FeatureDetector
|
||
|
from opencv_stitching.feature_matcher import FeatureMatcher
|
||
|
from opencv_stitching.subsetter import Subsetter
|
||
|
from opencv_stitching.camera_estimator import CameraEstimator
|
||
|
from opencv_stitching.camera_adjuster import CameraAdjuster
|
||
|
from opencv_stitching.camera_wave_corrector import WaveCorrector
|
||
|
from opencv_stitching.warper import Warper
|
||
|
from opencv_stitching.exposure_error_compensator import ExposureErrorCompensator # noqa
|
||
|
from opencv_stitching.seam_finder import SeamFinder
|
||
|
from opencv_stitching.blender import Blender
|
||
|
from opencv_stitching.timelapser import Timelapser
|
||
|
|
||
|
parser = argparse.ArgumentParser(
|
||
|
prog="opencv_stitching_tool.py",
|
||
|
description="Rotation model images stitcher"
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'img_names', nargs='+',
|
||
|
help="Files to stitch", type=str
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'--medium_megapix', action='store',
|
||
|
default=ImageHandler.DEFAULT_MEDIUM_MEGAPIX,
|
||
|
help="Resolution for image registration step. "
|
||
|
"The default is %s Mpx" % ImageHandler.DEFAULT_MEDIUM_MEGAPIX,
|
||
|
type=float, dest='medium_megapix'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'--detector', action='store',
|
||
|
default=FeatureDetector.DEFAULT_DETECTOR,
|
||
|
help="Type of features used for images matching. "
|
||
|
"The default is '%s'." % FeatureDetector.DEFAULT_DETECTOR,
|
||
|
choices=FeatureDetector.DETECTOR_CHOICES.keys(),
|
||
|
type=str, dest='detector'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'--nfeatures', action='store',
|
||
|
default=500,
|
||
|
help="Type of features used for images matching. "
|
||
|
"The default is 500.",
|
||
|
type=int, dest='nfeatures'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'--matcher_type', action='store', default=FeatureMatcher.DEFAULT_MATCHER,
|
||
|
help="Matcher used for pairwise image matching. "
|
||
|
"The default is '%s'." % FeatureMatcher.DEFAULT_MATCHER,
|
||
|
choices=FeatureMatcher.MATCHER_CHOICES,
|
||
|
type=str, dest='matcher_type'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'--range_width', action='store',
|
||
|
default=FeatureMatcher.DEFAULT_RANGE_WIDTH,
|
||
|
help="uses range_width to limit number of images to match with.",
|
||
|
type=int, dest='range_width'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'--try_use_gpu',
|
||
|
action='store',
|
||
|
default=False,
|
||
|
help="Try to use CUDA. The default value is no. "
|
||
|
"All default values are for CPU mode.",
|
||
|
type=bool, dest='try_use_gpu'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'--match_conf', action='store',
|
||
|
help="Confidence for feature matching step. "
|
||
|
"The default is 0.3 for ORB and 0.65 for other feature types.",
|
||
|
type=float, dest='match_conf'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'--confidence_threshold', action='store',
|
||
|
default=Subsetter.DEFAULT_CONFIDENCE_THRESHOLD,
|
||
|
help="Threshold for two images are from the same panorama confidence. "
|
||
|
"The default is '%s'." % Subsetter.DEFAULT_CONFIDENCE_THRESHOLD,
|
||
|
type=float, dest='confidence_threshold'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'--matches_graph_dot_file', action='store',
|
||
|
default=Subsetter.DEFAULT_MATCHES_GRAPH_DOT_FILE,
|
||
|
help="Save matches graph represented in DOT language to <file_name> file.",
|
||
|
type=str, dest='matches_graph_dot_file'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'--estimator', action='store',
|
||
|
default=CameraEstimator.DEFAULT_CAMERA_ESTIMATOR,
|
||
|
help="Type of estimator used for transformation estimation. "
|
||
|
"The default is '%s'." % CameraEstimator.DEFAULT_CAMERA_ESTIMATOR,
|
||
|
choices=CameraEstimator.CAMERA_ESTIMATOR_CHOICES.keys(),
|
||
|
type=str, dest='estimator'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'--adjuster', action='store',
|
||
|
default=CameraAdjuster.DEFAULT_CAMERA_ADJUSTER,
|
||
|
help="Bundle adjustment cost function. "
|
||
|
"The default is '%s'." % CameraAdjuster.DEFAULT_CAMERA_ADJUSTER,
|
||
|
choices=CameraAdjuster.CAMERA_ADJUSTER_CHOICES.keys(),
|
||
|
type=str, dest='adjuster'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'--refinement_mask', action='store',
|
||
|
default=CameraAdjuster.DEFAULT_REFINEMENT_MASK,
|
||
|
help="Set refinement mask for bundle adjustment. It looks like 'x_xxx', "
|
||
|
"where 'x' means refine respective parameter and '_' means don't "
|
||
|
"refine, and has the following format:<fx><skew><ppx><aspect><ppy>. "
|
||
|
"The default mask is '%s'. "
|
||
|
"If bundle adjustment doesn't support estimation of selected "
|
||
|
"parameter then the respective flag is ignored."
|
||
|
"" % CameraAdjuster.DEFAULT_REFINEMENT_MASK,
|
||
|
type=str, dest='refinement_mask'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'--wave_correct_kind', action='store',
|
||
|
default=WaveCorrector.DEFAULT_WAVE_CORRECTION,
|
||
|
help="Perform wave effect correction. "
|
||
|
"The default is '%s'" % WaveCorrector.DEFAULT_WAVE_CORRECTION,
|
||
|
choices=WaveCorrector.WAVE_CORRECT_CHOICES.keys(),
|
||
|
type=str, dest='wave_correct_kind'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'--warper_type', action='store', default=Warper.DEFAULT_WARP_TYPE,
|
||
|
help="Warp surface type. The default is '%s'." % Warper.DEFAULT_WARP_TYPE,
|
||
|
choices=Warper.WARP_TYPE_CHOICES,
|
||
|
type=str, dest='warper_type'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'--low_megapix', action='store', default=ImageHandler.DEFAULT_LOW_MEGAPIX,
|
||
|
help="Resolution for seam estimation and exposure estimation step. "
|
||
|
"The default is %s Mpx." % ImageHandler.DEFAULT_LOW_MEGAPIX,
|
||
|
type=float, dest='low_megapix'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'--compensator', action='store',
|
||
|
default=ExposureErrorCompensator.DEFAULT_COMPENSATOR,
|
||
|
help="Exposure compensation method. "
|
||
|
"The default is '%s'." % ExposureErrorCompensator.DEFAULT_COMPENSATOR,
|
||
|
choices=ExposureErrorCompensator.COMPENSATOR_CHOICES.keys(),
|
||
|
type=str, dest='compensator'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'--nr_feeds', action='store',
|
||
|
default=ExposureErrorCompensator.DEFAULT_NR_FEEDS,
|
||
|
help="Number of exposure compensation feed.",
|
||
|
type=np.int32, dest='nr_feeds'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'--block_size', action='store',
|
||
|
default=ExposureErrorCompensator.DEFAULT_BLOCK_SIZE,
|
||
|
help="BLock size in pixels used by the exposure compensator. "
|
||
|
"The default is '%s'." % ExposureErrorCompensator.DEFAULT_BLOCK_SIZE,
|
||
|
type=np.int32, dest='block_size'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'--finder', action='store', default=SeamFinder.DEFAULT_SEAM_FINDER,
|
||
|
help="Seam estimation method. "
|
||
|
"The default is '%s'." % SeamFinder.DEFAULT_SEAM_FINDER,
|
||
|
choices=SeamFinder.SEAM_FINDER_CHOICES.keys(),
|
||
|
type=str, dest='finder'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'--final_megapix', action='store',
|
||
|
default=ImageHandler.DEFAULT_FINAL_MEGAPIX,
|
||
|
help="Resolution for compositing step. Use -1 for original resolution. "
|
||
|
"The default is %s" % ImageHandler.DEFAULT_FINAL_MEGAPIX,
|
||
|
type=float, dest='final_megapix'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'--blender_type', action='store', default=Blender.DEFAULT_BLENDER,
|
||
|
help="Blending method. The default is '%s'." % Blender.DEFAULT_BLENDER,
|
||
|
choices=Blender.BLENDER_CHOICES,
|
||
|
type=str, dest='blender_type'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'--blend_strength', action='store', default=Blender.DEFAULT_BLEND_STRENGTH,
|
||
|
help="Blending strength from [0,100] range. "
|
||
|
"The default is '%s'." % Blender.DEFAULT_BLEND_STRENGTH,
|
||
|
type=np.int32, dest='blend_strength'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'--timelapse', action='store', default=Timelapser.DEFAULT_TIMELAPSE,
|
||
|
help="Output warped images separately as frames of a time lapse movie, "
|
||
|
"with 'fixed_' prepended to input file names. "
|
||
|
"The default is '%s'." % Timelapser.DEFAULT_TIMELAPSE,
|
||
|
choices=Timelapser.TIMELAPSE_CHOICES,
|
||
|
type=str, dest='timelapse'
|
||
|
)
|
||
|
parser.add_argument(
|
||
|
'--output', action='store', default='result.jpg',
|
||
|
help="The default is 'result.jpg'",
|
||
|
type=str, dest='output'
|
||
|
)
|
||
|
|
||
|
__doc__ += '\n' + parser.format_help()
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
print(__doc__)
|
||
|
args = parser.parse_args()
|
||
|
args_dict = vars(args)
|
||
|
|
||
|
# Extract In- and Output
|
||
|
img_names = args_dict.pop("img_names")
|
||
|
img_names = [cv.samples.findFile(img_name) for img_name in img_names]
|
||
|
output = args_dict.pop("output")
|
||
|
|
||
|
stitcher = Stitcher(**args_dict)
|
||
|
panorama = stitcher.stitch(img_names)
|
||
|
|
||
|
cv.imwrite(output, panorama)
|
||
|
|
||
|
zoom_x = 600.0 / panorama.shape[1]
|
||
|
preview = cv.resize(panorama, dsize=None, fx=zoom_x, fy=zoom_x)
|
||
|
|
||
|
cv.imshow(output, preview)
|
||
|
cv.waitKey()
|
||
|
cv.destroyAllWindows()
|