Repository: opencv
Issue: ORB rejecting keypoints detected by SURF when they are close to the edge of the image.
Labels: bug,category: features2d

### System Information

OpenCV python version: 4.8.0-dev (built from source with xfeatures2d non-free modules and python bindings)
Operating System / Platform: Ubuntu 20.04.6 LTS
Python version: 3.8.10

### Detailed description

I'm testing various combinations of keypoint detectors and descriptors and ran into an issue when trying to use certain detectors with ORB as a descriptor. In my image, SURF detects a keypoint (index 47 in its tuple of keypoints) at:

kp.angle=27.298503875732422
kp.class_id=-1
kp.octave=0
kp.pt=(15.569972038269043, 422.731689453125)
kp.response=14287.0673828125
kp.size=22.0

When I send this keypoint to ORB.compute(), it returns None for both the returned keypoint and its descriptor. My assumption is that the way ORB interprets size tells it that the keypoint would incorporate areas outside of the image, but if so, why can SURF detect it?

This happens with other keypoints as well, however, this was the first example point that I found in my analysis of the problem. This also happens when using BRISK and KAZE as detectors.

To my understanding, the size attribute is meant to be the diameter of a keypoint, therefore, this keypoint's radius would be 11.0 pixels, meaning it fits within the image. ORB seems to think it does not fit in the image and I therefore wonder if it considers "size" to mean radius instead.

### Steps to reproduce

Note: I do not own the image. It was taken by Fillipe Ramos and downloaded at https://www.flickr.com/photos/filipe-ramos/51335974980/in/photolist-2mdohQU-2n4JhLc-2MScnu-L1vo-2ogvuDM-pFQaXY-2kJW1W6-2kvQCn9-2iAnrSo-2oDamFV-UrirZM-nkDi9Q-2j9F2xn-2nKSFf7-2oa72tG-QeRP2P-2nfq1H6-2oeTR5Q-2boXCBf-2ow1Cij-2onTXnz-2boXCFd-TRT6n1-2iajwzZ-G6nvEY-2otHaLk-2mr1MVp-UKpKea-2oHLY5J-2iorarR-2gyXtri-2oxrSGK-2n3ihtQ-2i26Gbh-2oEVZuu-26KBv1t-2k1xPJT-MxjpEE-2iR554q-S4f4k6-2no1pQh-2n1qWeH-2m4RmqZ-2kPoTCq-2oqkG7J-3feDXC-2meSxqf-29XiRSq-26vd1P7-2iR55aT (CC BY 2.0 DEED).

![medium_geometries](https://github.com/opencv/opencv/assets/93782975/530321d2-c6a3-4c73-9d72-2d0091a237c3)

    from more_itertools import pairwise

    import cv2 as cv
    import numpy as np

    def main():
        test_image = cv.imread('medium_geometries.jpg', cv.IMREAD_GRAYSCALE)
        
        possible_descriptors = [cv.ORB_create()]
        possible_detectors = [[
            cv.xfeatures2d.SURF_create(),
            cv.xfeatures2d.StarDetector_create(),
            cv.ORB_create(nfeatures=10000),
            cv.BRISK_create(),
            cv.KAZE_create(),
            cv.AKAZE_create()
        ]]
        used_detectors = possible_detectors

        for detector in unique_used_detectors:
            keypoints = detector.detect(test_image)

            num_angled_kp = 0
            for kp in keypoints:
                if kp.angle != -1:
                    num_angled_kp += 1

            num_same_angle = 0
            kp_pairs = pairwise(keypoints)
            for kp_1, kp_2 in kp_pairs:
                if kp_1.angle == kp_2.angle:
                    num_same_angle += 1
            if num_same_angle == (len(keypoints) - 1):
                num_angled_kp = 0

            print(f'{detector.__class__.__name__}: {num_angled_kp}/{len(keypoints)} keypoints were assigned orientations')   
        print("\n")

        for detector_set, descriptor in zip(possible_detectors, possible_descriptors):
            for detector in detector_set:
                keypoints = detector.detect(test_image)

                # normal way to compute descriptors:
                # new_keypoints, desc = descriptor.compute(test_image, keypoints)
                # this code block was to test if sending keypoints individually would prevent it from reassigning angles
                # as I thought it was due to ORB reordering the tuple of keypoints,
                # I now know it is because ORB rejects some of the keypoints
                new_keypoints = ()
                descriptors = []
                for i, kp in enumerate(keypoints):
                    returned_kp, desc = descriptor.compute(test_image, (kp,))
                    new_keypoints += returned_kp
                    descriptors.append(desc[0])
                desc = np.array(descriptors)

                num_reangled_kp = 0
                for kp, new_kp in zip(keypoints, new_keypoints):
                    if new_kp.angle != kp.angle:
                        num_reangled_kp += 1
                print(f'{detector.__class__.__name__}_{descriptor.__class__.__name__}: {num_reangled_kp}/{len(new_keypoints)} REASSIGNED orientations')
            print("\n")

            for detector in detector_set:
                keypoints = detector.detect(test_image)

                # revert all keypoint angles back to default (no angle)
                for i in range(len(keypoints)):
                    keypoints[i].angle = -1

                # see if it overwrites EMPTY angles
                new_keypoints, desc = descriptor.compute(test_image, keypoints)
                num_reangled_kp = 0
                for kp, new_kp in zip(keypoints, new_keypoints):
                    if new_kp.angle != kp.angle:
                        num_reangled_kp += 1

                print(f'{detector.__class__.__name__}_{descriptor.__class__.__name__}: {num_reangled_kp}/{len(new_keypoints)} assigned over -1')

            print("\n")

    if __name__ == '__main__':
        main()

### Issue submission checklist

- [X] I report the issue, it's not a question
- [x] 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)