Repository: opencv
Issue: Not able to stitch some of the  input image in stitching_detailed.py
Labels: question (invalid tracker)

### System Information


OpenCV python version: 4.10.0
Operating System / Platform:Windows
Python version: 3.7.8

### Detailed description

PS C:\Users\hp\Downloads\stitch> python stitching.py
stitching.py:80: DeprecationWarning: The default dtype for empty Series will be 'object' instead of 'float64' in a future version. Specify a dtype explicitly to silence this warning.
  opt = pd.Series()
['Stitching_Dataset/sti_1.jpeg', 'Stitching_Dataset/sti_2.jpeg', 'Stitching_Dataset/sti_3.jpeg']
name: Stitching_Dataset/sti_1.jpeg
[ WARN:0@0.515] global ocl.cpp:1030 cv::ocl::OpenCLExecutionContext::Impl::getInitializedExecutionContext OpenCL: Can't initialize OpenCL context/device/queue: unknown C++ exception
name: Stitching_Dataset/sti_2.jpeg
name: Stitching_Dataset/sti_3.jpeg
len(images): 3
len of indices: 1
3
1
num_images: 1
Need more images
PS C:\Users\hp\Downloads\stitch> 
![sti_1](https://github.com/opencv/opencv/assets/98905329/4ffaa6c9-610b-4764-a707-291a5073058b)
![sti_2](https://github.com/opencv/opencv/assets/98905329/a9d4e86e-760d-4ab9-897d-5d2541c61bd4)
![sti_3](https://github.com/opencv/opencv/assets/98905329/d66cef45-9937-4b37-8c2f-129c4a472ee5)
![Concatenate_image](https://github.com/opencv/opencv/assets/98905329/43a07ea7-28e9-488e-88a2-a0be1c8db916)

"We are currently implementing image stitching using the stitching_detailed.py code. While we are able to successfully stitch some input images, we encounter difficulties with others. Could you suggest improvements or changes to enhance the accuracy of stitching for more complex images?"
 and i have given three input image with their concatenated image and it is produced output for need more images 
 
 how can i more image stitching for all complex image


### Steps to reproduce


from __future__ import print_function
import argparse
from collections import OrderedDict
import pandas as pd
import cv2 as cv
import numpy as np

EXPOS_COMP_CHOICES = OrderedDict()
EXPOS_COMP_CHOICES['gain_blocks'] = cv.detail.ExposureCompensator_GAIN_BLOCKS
EXPOS_COMP_CHOICES['gain'] = cv.detail.ExposureCompensator_GAIN
EXPOS_COMP_CHOICES['channel'] = cv.detail.ExposureCompensator_CHANNELS
EXPOS_COMP_CHOICES['channel_blocks'] = cv.detail.ExposureCompensator_CHANNELS_BLOCKS
EXPOS_COMP_CHOICES['no'] = cv.detail.ExposureCompensator_NO

BA_COST_CHOICES = OrderedDict()
BA_COST_CHOICES['ray'] = cv.detail_BundleAdjusterRay
BA_COST_CHOICES['reproj'] = cv.detail_BundleAdjusterReproj
BA_COST_CHOICES['affine'] = cv.detail_BundleAdjusterAffinePartial
BA_COST_CHOICES['no'] = cv.detail_NoBundleAdjuster

FEATURES_FIND_CHOICES = OrderedDict()
try:
    cv.SIFT_create() # check if the function can be called
    FEATURES_FIND_CHOICES['surf'] = cv.SIFT_create().create
except (AttributeError, cv.error) as e:
    print("SURF not available")
# if SURF not available, ORB is default
FEATURES_FIND_CHOICES['orb'] = cv.ORB.create
try:
    FEATURES_FIND_CHOICES['sift'] = cv.SIFT_create
except AttributeError:
    print("SIFT not available")
try:
    FEATURES_FIND_CHOICES['brisk'] = cv.BRISK_create
except AttributeError:
    print("BRISK not available")
try:
    FEATURES_FIND_CHOICES['akaze'] = cv.AKAZE_create
except AttributeError:
    print("AKAZE not available")

SEAM_FIND_CHOICES = OrderedDict()
SEAM_FIND_CHOICES['gc_color'] = cv.detail_GraphCutSeamFinder('COST_COLOR')
SEAM_FIND_CHOICES['gc_colorgrad'] = cv.detail_GraphCutSeamFinder('COST_COLOR_GRAD')
SEAM_FIND_CHOICES['dp_color'] = cv.detail_DpSeamFinder('COLOR')
SEAM_FIND_CHOICES['dp_colorgrad'] = cv.detail_DpSeamFinder('COLOR_GRAD')
SEAM_FIND_CHOICES['voronoi'] = cv.detail.SeamFinder_createDefault(cv.detail.SeamFinder_VORONOI_SEAM)
SEAM_FIND_CHOICES['no'] = cv.detail.SeamFinder_createDefault(cv.detail.SeamFinder_NO)

ESTIMATOR_CHOICES = OrderedDict()
ESTIMATOR_CHOICES['homography'] = cv.detail_HomographyBasedEstimator
ESTIMATOR_CHOICES['affine'] = cv.detail_AffineBasedEstimator

WARP_CHOICES = (
    'spherical',
    'plane',
    'affine',
    'cylindrical',
    'fisheye',
    'stereographic',
    'compressedPlaneA2B1',
    'compressedPlaneA1.5B1',
    'compressedPlanePortraitA2B1',
    'compressedPlanePortraitA1.5B1',
    'paniniA2B1',
    'paniniA1.5B1',
    'paniniPortraitA2B1',
    'paniniPortraitA1.5B1',
    'mercator',
    'transverseMercator',
)

WAVE_CORRECT_CHOICES = OrderedDict()
WAVE_CORRECT_CHOICES['horiz'] = cv.detail.WAVE_CORRECT_HORIZ
WAVE_CORRECT_CHOICES['no'] = None
WAVE_CORRECT_CHOICES['vert'] = cv.detail.WAVE_CORRECT_VERT

BLEND_CHOICES = ('multiband', 'feather', 'no',)
opt = pd.Series()

# opt.img_names = ['/content/Stitching_Dataset/s_1.jpeg','/content/Stitching_Dataset/s_2.jpeg','/content/Stitching_Dataset/s_3.jpeg']
opt.img_names = ['Stitching_Dataset/sti_1.jpeg','Stitching_Dataset/sti_2.jpeg','Stitching_Dataset/sti_3.jpeg']
# opt.img_names = ['/content/Stitching_Dataset/st_1.jpg','/content/Stitching_Dataset/st_2.jpg','/content/Stitching_Dataset/st_3.jpg']
# opt.img_names = ['/content/Stitching_Dataset/stitching_1.jpeg','/content/Stitching_Dataset/stitching_2.jpeg','/content/Stitching_Dataset/stitching_3.jpeg']
# opt.img_names = ['/content/gdrive/MyDrive/camera_stitch/out_1.png','/content/gdrive/MyDrive/camera_stitch/out_2.png','/content/gdrive/MyDrive/camera_stitch/out_2.png']
# opt.img_names = ['/content/gdrive/MyDrive/camera_stitch/sr_1.png','/content/gdrive/MyDrive/camera_stitch/sr_2.png']
# opt.img_names = ['/content/img/P1011370.JPG','/content/img/P1011371.JPG']
# opt.img_names = ['/content/img/a.jpg','/content/img/b.jpg','/content/img/c.jpg']
# opt.img_names = ['/content/img/P1010371.JPG','/content/img/P1010372.JPG','/content/img/P1010373.JPG']
# opt.img_names = ['/content/img/P1010378.JPG','/content/img/P1010379.JPG']
# opt.img_names = ['/content/img/P1011069.JPG','/content/img/P1011070.JPG','/content/img/P1011071.JPG']

# Read the images
images = [cv.imread(path) for path in opt.img_names]

# Check if images were loaded successfully
for i, img in enumerate(images):
    if img is None:
        print(f"Error loading image {opt.img_names[i]}")
        exit()

# Optionally resize images to the same height for horizontal concatenation
# Here we resize all images to the height of the first image
height = images[0].shape[0]
resized_images = [cv.resize(img, (int(img.shape[1] * height / img.shape[0]), height)) for img in images]

# Concatenate images horizontally
horizontal_concat = np.concatenate(resized_images, axis=1)

# cv.imshow("Concatenated Image:",horizontal_concat)
cv.imwrite("Concatenate_image.png",horizontal_concat)
# cv.waitKey(0)
# # closing all open windows 
# cv.destroyAllWindows() 




# parser.add_argument(
#     'img_names', nargs='+',
#     help="Files to stitch", type=str
# )
opt.try_cuda = True
# parser.add_argument(
#     '--try_cuda',
#     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_cuda'
# )
opt.work_megapix = 0.6
# parser.add_argument(
#     '--work_megapix', action='store', default=0.6,
#     help="Resolution for image registration step. The default is 0.6 Mpx",
#     type=float, dest='work_megapix')
opt.features =list(FEATURES_FIND_CHOICES.keys())[0]
# parser.add_argument(
#     '--features', action='store', default=list(FEATURES_FIND_CHOICES.keys())[0],
#     help="Type of features used for images matching. The default is '%s'." % list(FEATURES_FIND_CHOICES.keys())[0],
#     choices=FEATURES_FIND_CHOICES.keys(),
#     type=str, dest='features'
# )
opt.matcher = 'homography'
# parser.add_argument(
#     '--matcher', action='store', default='homography',
#     help="Matcher used for pairwise image matching. The default is 'homography'.",
#     choices=('homography', 'affine'),
#     type=str, dest='matcher'
# )
opt.estimator = list(ESTIMATOR_CHOICES.keys())[0]
# parser.add_argument(
#     '--estimator', action='store', default=list(ESTIMATOR_CHOICES.keys())[0],
#     help="Type of estimator used for transformation estimation. The default is '%s'." % list(ESTIMATOR_CHOICES.keys())[0],
#     choices=ESTIMATOR_CHOICES.keys(),
#     type=str, dest='estimator'
# )
opt.match_conf = 0.5
# 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'
# )
opt.conf_thresh = 1.0
# parser.add_argument(
#     '--conf_thresh', action='store', default=1.0,
#     help="Threshold for two images are from the same panorama confidence.The default is 1.0.",
#     type=float, dest='conf_thresh'
# )
opt.ba = list(BA_COST_CHOICES.keys())[0]
# parser.add_argument(
#     '--ba', action='store', default=list(BA_COST_CHOICES.keys())[0],
#     help="Bundle adjustment cost function. The default is '%s'." % list(BA_COST_CHOICES.keys())[0],
#     choices=BA_COST_CHOICES.keys(),
#     type=str, dest='ba'
# )
opt.ba_refine_mask = 'xxxxx'
# parser.add_argument(
#     '--ba_refine_mask', action='store', default='xxxxx',
#     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 'xxxxx'. "
#          "If bundle adjustment doesn't support estimation of selected parameter then "
#          "the respective flag is ignored.",
#     type=str, dest='ba_refine_mask'
# )
opt.wave_correct = list(WAVE_CORRECT_CHOICES.keys())[0]
# parser.add_argument(
#     '--wave_correct', action='store', default=list(WAVE_CORRECT_CHOICES.keys())[0],
#     help="Perform wave effect correction. The default is '%s'" % list(WAVE_CORRECT_CHOICES.keys())[0],
#     choices=WAVE_CORRECT_CHOICES.keys(),
#     type=str, dest='wave_correct'
# )

# keep it this always zero becuase of this one is best for warping
opt.warp = WARP_CHOICES[0]
# parser.add_argument(
#     '--warp', action='store', default=WARP_CHOICES[0],
#     help="Warp surface type. The default is '%s'." % WARP_CHOICES[0],
#     choices=WARP_CHOICES,
#     type=str, dest='warp'
# )
opt.seam_megapix = 0.1
# parser.add_argument(
#     '--seam_megapix', action='store', default=0.1,
#     help="Resolution for seam estimation step. The default is 0.1 Mpx.",
#     type=float, dest='seam_megapix'
# )
opt.seam = list(SEAM_FIND_CHOICES.keys())[0]
# parser.add_argument(
#     '--seam', action='store', default=list(SEAM_FIND_CHOICES.keys())[0],
#     help="Seam estimation method. The default is '%s'." % list(SEAM_FIND_CHOICES.keys())[0],
#     choices=SEAM_FIND_CHOICES.keys(),
#     type=str, dest='seam'
# )
opt.compose_megapix = -1
# parser.add_argument(
#     '--compose_megapix', action='store', default=-1,
#     help="Resolution for compositing step. Use -1 for original resolution. The default is -1",
#     type=float, dest='compose_megapix'
# )
opt.expos_comp = list(EXPOS_COMP_CHOICES.keys())[0]
# parser.add_argument(
#     '--expos_comp', action='store', default=list(EXPOS_COMP_CHOICES.keys())[0],
#     help="Exposure compensation method. The default is '%s'." % list(EXPOS_COMP_CHOICES.keys())[0],
#     choices=EXPOS_COMP_CHOICES.keys(),
#     type=str, dest='expos_comp'
# )
opt.expos_comp_nr_feeds = 1
# parser.add_argument(
#     '--expos_comp_nr_feeds', action='store', default=1,
#     help="Number of exposure compensation feed.",
#     type=np.int32, dest='expos_comp_nr_feeds'
# )
opt.expos_comp_nr_filtering = 2
# parser.add_argument(
#     '--expos_comp_nr_filtering', action='store', default=2,
#     help="Number of filtering iterations of the exposure compensation gains.",
#     type=float, dest='expos_comp_nr_filtering'
# )
opt.expos_comp_block_size =32
# parser.add_argument(
#     '--expos_comp_block_size', action='store', default=32,
#     help="BLock size in pixels used by the exposure compensator. The default is 32.",
#     type=np.int32, dest='expos_comp_block_size'
# )
opt.blend = BLEND_CHOICES[0]
# parser.add_argument(
#     '--blend', action='store', default=BLEND_CHOICES[0],
#     help="Blending method. The default is '%s'." % BLEND_CHOICES[0],
#     choices=BLEND_CHOICES,
#     type=str, dest='blend'
# )
opt.blend_strength = 10
# parser.add_argument(
#     '--blend_strength', action='store', default=5,
#     help="Blending strength from [0,100] range. The default is 5",
#     type=np.int32, dest='blend_strength'
# )
opt.output = "result.jpg"
# parser.add_argument(
#     '--output', action='store', default='result.jpg',
#     help="The default is 'result.jpg'",
#     type=str, dest='output'
# )
opt.timelapse = None
# parser.add_argument(
#     '--timelapse', action='store', default=None,
#     help="Output warped images separately as frames of a time lapse movie, "
#          "with 'fixed_' prepended to input file names.",
#     type=str, dest='timelapse'
# )
# parser.add_argument(
#     '--rangewidth', action='store', default=-1,
#     help="uses range_width to limit number of images to match with.",
#     type=int, dest='rangewidth'
# )
opt.rangewidth = -1


def get_matcher(args):
    try_cuda = args.try_cuda
    matcher_type = args.matcher
    if args.match_conf is None:
        if args.features == 'orb':
            match_conf = 0.3
        else:
            match_conf = 0.65
    else:
        match_conf = args.match_conf
    range_width = args.rangewidth
    if matcher_type == "affine":
        matcher = cv.detail_AffineBestOf2NearestMatcher(False, try_cuda, match_conf)
    elif range_width == -1:
        matcher = cv.detail_BestOf2NearestMatcher(try_cuda, match_conf)
    else:
        matcher = cv.detail_BestOf2NearestRangeMatcher(range_width, try_cuda, match_conf)
    return matcher


def get_compensator(args):
    expos_comp_type = EXPOS_COMP_CHOICES[args.expos_comp]
    expos_comp_nr_feeds = args.expos_comp_nr_feeds
    expos_comp_block_size = args.expos_comp_block_size
    # expos_comp_nr_filtering = args.expos_comp_nr_filtering
    if expos_comp_type == cv.detail.ExposureCompensator_CHANNELS:
        compensator = cv.detail_ChannelsCompensator(expos_comp_nr_feeds)
        # compensator.setNrGainsFilteringIterations(expos_comp_nr_filtering)
    elif expos_comp_type == cv.detail.ExposureCompensator_CHANNELS_BLOCKS:
        compensator = cv.detail_BlocksChannelsCompensator(
            expos_comp_block_size, expos_comp_block_size,
            expos_comp_nr_feeds
        )
        # compensator.setNrGainsFilteringIterations(expos_comp_nr_filtering)
    else:
        compensator = cv.detail.ExposureCompensator_createDefault(expos_comp_type)
    return compensator


args = opt
img_names = args.img_names
print(img_names)
work_megapix = args.work_megapix
seam_megapix = args.seam_megapix
compose_megapix = args.compose_megapix
conf_thresh = args.conf_thresh
ba_refine_mask = args.ba_refine_mask
wave_correct = WAVE_CORRECT_CHOICES[args.wave_correct]
warp_type = args.warp
blend_type = args.blend
blend_strength = args.blend_strength
result_name = args.output
if args.timelapse is not None:
    timelapse = True
    if args.timelapse == "as_is":
        timelapse_type = cv.detail.Timelapser_AS_IS
    elif args.timelapse == "crop":
        timelapse_type = cv.detail.Timelapser_CROP
    else:
        print("Bad timelapse method")
        exit()
else:
    timelapse = False
finder = FEATURES_FIND_CHOICES[args.features]()
seam_work_aspect = 1
full_img_sizes = []
features = []
images = []
is_work_scale_set = False
is_seam_scale_set = False
is_compose_scale_set = False
for name in img_names:
    print("name:",name)
    full_img = cv.imread(name)
    if full_img is None:
        print("Cannot read image ", name)
        exit()
    full_img_sizes.append((full_img.shape[1], full_img.shape[0]))
    if work_megapix < 0:
        img = full_img
        work_scale = 1
        is_work_scale_set = True
    else:
        if is_work_scale_set is False:
            work_scale = min(1.0, np.sqrt(work_megapix * 1e6 / (full_img.shape[0] * full_img.shape[1])))
            is_work_scale_set = True
        img = cv.resize(src=full_img, dsize=None, fx=work_scale, fy=work_scale, interpolation=cv.INTER_LINEAR_EXACT)
    if is_seam_scale_set is False:
        if seam_megapix > 0:
            seam_scale = min(1.0, np.sqrt(seam_megapix * 1e6 / (full_img.shape[0] * full_img.shape[1])))
        else:
            seam_scale = 1.0
        seam_work_aspect = seam_scale / work_scale
        is_seam_scale_set = True
    img_feat = cv.detail.computeImageFeatures2(finder, img)
    features.append(img_feat)
    img = cv.resize(src=full_img, dsize=None, fx=seam_scale, fy=seam_scale, interpolation=cv.INTER_LINEAR_EXACT)
    images.append(img)


print("len(images):",len(images))
matcher = get_matcher(args)
p = matcher.apply2(features)
matcher.collectGarbage()
indices = cv.detail.leaveBiggestComponent(features, p, conf_thresh)
print("len of indices:",len(indices))
img_subset = []
img_names_subset = []
full_img_sizes_subset = []
for i in range(len(indices)):
    print(len(img_names))
    img_names_subset.append(img_names[indices[i]])
    img_subset.append(images[indices[i]])
    full_img_sizes_subset.append(full_img_sizes[indices[i]])
print(len(img_subset))
images = img_subset
img_names = img_names_subset
full_img_sizes = full_img_sizes_subset
num_images = len(img_names)
print("num_images:",num_images)
if num_images < 2:
    print("Need more images")
    exit()

estimator = ESTIMATOR_CHOICES[args.estimator]()
b, cameras = estimator.apply(features, p, None)
print("Homography estimation Success")
if not b:
    print("Homography estimation failed.")
    exit()
for cam in cameras:
    cam.R = cam.R.astype(np.float32)

adjuster = BA_COST_CHOICES[args.ba]()
adjuster.setConfThresh(conf_thresh)
refine_mask = np.zeros((3, 3), np.uint8)
if ba_refine_mask[0] == 'x':
    refine_mask[0, 0] = 1
if ba_refine_mask[1] == 'x':
    refine_mask[0, 1] = 1
if ba_refine_mask[2] == 'x':
    refine_mask[0, 2] = 1
if ba_refine_mask[3] == 'x':
    refine_mask[1, 1] = 1
if ba_refine_mask[4] == 'x':
    refine_mask[1, 2] = 1
adjuster.setRefinementMask(refine_mask)
b, cameras = adjuster.apply(features, p, cameras)
if not b:
    print("Camera parameters adjusting failed.")
    exit()
focals = []
for cam in cameras:
    focals.append(cam.focal)
focals.sort()
if len(focals) % 2 == 1:
    warped_image_scale = focals[len(focals) // 2]
else:
    warped_image_scale = (focals[len(focals) // 2] + focals[len(focals) // 2 - 1]) / 2
if wave_correct is not None:
    rmats = []
    for cam in cameras:
        rmats.append(np.copy(cam.R))
    rmats = cv.detail.waveCorrect(rmats, wave_correct)
    for idx, cam in enumerate(cameras):
        cam.R = rmats[idx]
corners = []
masks_warped = []
images_warped = []
sizes = []
masks = []
for i in range(0, num_images):
    um = cv.UMat(255 * np.ones((images[i].shape[0], images[i].shape[1]), np.uint8))
    masks.append(um)

warper = cv.PyRotationWarper(warp_type, warped_image_scale * seam_work_aspect)  # warper could be nullptr?
for idx in range(0, num_images):
    K = cameras[idx].K().astype(np.float32)
    swa = seam_work_aspect
    K[0, 0] *= swa
    K[0, 2] *= swa
    K[1, 1] *= swa
    K[1, 2] *= swa
    corner, image_wp = warper.warp(images[idx], K, cameras[idx].R, cv.INTER_LINEAR, cv.BORDER_REFLECT)
    corners.append(corner)
    sizes.append((image_wp.shape[1], image_wp.shape[0]))
    images_warped.append(image_wp)
    p, mask_wp = warper.warp(masks[idx], K, cameras[idx].R, cv.INTER_NEAREST, cv.BORDER_CONSTANT)
    masks_warped.append(mask_wp.get())

images_warped_f = []
for img in images_warped:
    imgf = img.astype(np.float32)
    images_warped_f.append(imgf)

compensator = get_compensator(args)
compensator.feed(corners=corners, images=images_warped, masks=masks_warped)

seam_finder = SEAM_FIND_CHOICES[args.seam]
masks_warped = seam_finder.find(images_warped_f, corners, masks_warped)
compose_scale = 1
corners = []
sizes = []
blender = None
timelapser = None
# https://github.com/opencv/opencv/blob/4.x/samples/cpp/stitching_detailed.cpp#L725 ?
for idx, name in enumerate(img_names):
    full_img = cv.imread(name)
    if not is_compose_scale_set:
        if compose_megapix > 0:
            compose_scale = min(1.0, np.sqrt(compose_megapix * 1e6 / (full_img.shape[0] * full_img.shape[1])))
        is_compose_scale_set = True
        compose_work_aspect = compose_scale / work_scale
        warped_image_scale *= compose_work_aspect
        warper = cv.PyRotationWarper(warp_type, warped_image_scale)
        for i in range(0, len(img_names)):
            cameras[i].focal *= compose_work_aspect
            cameras[i].ppx *= compose_work_aspect
            cameras[i].ppy *= compose_work_aspect
            sz = (int(round(full_img_sizes[i][0] * compose_scale)),
                    int(round(full_img_sizes[i][1] * compose_scale)))
            K = cameras[i].K().astype(np.float32)
            roi = warper.warpRoi(sz, K, cameras[i].R)
            corners.append(roi[0:2])
            sizes.append(roi[2:4])
    if abs(compose_scale - 1) > 1e-1:
        img = cv.resize(src=full_img, dsize=None, fx=compose_scale, fy=compose_scale,
                        interpolation=cv.INTER_LINEAR_EXACT)
    else:
        img = full_img
    _img_size = (img.shape[1], img.shape[0])
    K = cameras[idx].K().astype(np.float32)
    corner, image_warped = warper.warp(img, K, cameras[idx].R, cv.INTER_LINEAR, cv.BORDER_REFLECT)
    mask = 255 * np.ones((img.shape[0], img.shape[1]), np.uint8)
    p, mask_warped = warper.warp(mask, K, cameras[idx].R, cv.INTER_NEAREST, cv.BORDER_CONSTANT)
    compensator.apply(idx, corners[idx], image_warped, mask_warped)
    image_warped_s = image_warped.astype(np.int16)
    dilated_mask = cv.dilate(masks_warped[idx], None)
    seam_mask = cv.resize(dilated_mask, (mask_warped.shape[1], mask_warped.shape[0]), 0, 0, cv.INTER_LINEAR_EXACT)
    mask_warped = cv.bitwise_and(seam_mask, mask_warped)
    if blender is None and not timelapse:
        blender = cv.detail.Blender_createDefault(cv.detail.Blender_NO)
        dst_sz = cv.detail.resultRoi(corners=corners, sizes=sizes)
        blend_width = np.sqrt(dst_sz[2] * dst_sz[3]) * blend_strength / 100
        if blend_width < 1:
            blender = cv.detail.Blender_createDefault(cv.detail.Blender_NO)
        elif blend_type == "multiband":
            blender = cv.detail_MultiBandBlender()
            blender.setNumBands((np.log(blend_width) / np.log(2.) - 1.).astype(np.int32))
        elif blend_type == "feather":
            blender = cv.detail_FeatherBlender()
            blender.setSharpness(1. / blend_width)
        blender.prepare(dst_sz)
    elif timelapser is None and timelapse:
        timelapser = cv.detail.Timelapser_createDefault(timelapse_type)
        timelapser.initialize(corners, sizes)
    if timelapse:
        ma_tones = np.ones((image_warped_s.shape[0], image_warped_s.shape[1]), np.uint8)
        timelapser.process(image_warped_s, ma_tones, corners[idx])
        pos_s = img_names[idx].rfind("/")
        if pos_s == -1:
            fixed_file_name = "fixed_" + img_names[idx]
        else:
            fixed_file_name = img_names[idx][:pos_s + 1] + "fixed_" + img_names[idx][pos_s + 1:]
        cv.imwrite(fixed_file_name, timelapser.getDst())
    else:
        blender.feed(cv.UMat(image_warped_s), mask_warped, corners[idx])
if not timelapse:
    result = None
    result_mask = None
    result, result_mask = blender.blend(result, result_mask)
    cv.imwrite(result_name, result)
    zoom_x = 600.0 / result.shape[1]
    dst = cv.normalize(src=result, dst=None, alpha=255., norm_type=cv.NORM_MINMAX, dtype=cv.CV_8U)
    dst = cv.resize(dst, dsize=None, fx=zoom_x, fy=zoom_x)
    cv.imshow("Stitched Image:",dst)
    cv.waitKey(0) 
    # closing all open windows 
    cv.destroyAllWindows() 

print("Done")



### Issue submission checklist

- [X] I report the issue, it's not a question
- [ ] I checked the problem with documentation, FAQ, open issues, forum.opencv.org, Stack Overflow, etc and have not found any solution
- [ ] I updated to the latest OpenCV version and the issue is still there
- [X] There is reproducer code and related data files (videos, images, onnx, etc)