I think I found a solution. This is a little longer since I did not have enough time, but maybe it helps. I coded, if only for this problem, but for many images this should be easy to generalize.
Some naming conventions first:
- I define βfirst level areasβ as compact areas that are surrounded by a background. Such first level areas may consist of different subregions.
- A first-level region consisting of more than one subdomain is called a critical region.
My main idea is to compare the contour lengths of two subregions that are part of the same critical region. However, I do not compare their full length of the contour, but only a segment that is close to the background . One with a shorter contour close to the background is considered a hole.
First, I'll start with images of the results.
Some idea of ββwhat we are talking about vizualizing naming conventions above:

Two subdomains of the critical region. Two border segments of each of the areas that are close to the background are marked with different colors (very thin, blue and dark red, but visible). These segments are obviously not ideal (thin areas cause errors), but enough to compare their lengths:

Final result. In case you want the hole to be closed, let me know, you just need to assign the original black outlines to the regions instead of the background ([EDIT] I included three marked lines of code that assign the borders to the regions as you wanted):

The code is attached here. I used the OpenCV loop function, which is quite complicated, and some masking methods. The code is legthy because of its visualization, sorry for its limited readability, but there seems to be no two-line solution to this problem.
Some concluding remarks: I first tried matching paths using sets of dots, which would avoid loops and allow the use of set.intersection to define two path segments close to the background, but since your black lines are rather thick, the paths are slipperyly incompatible. I tried skeletonizing the contours, but this opened up another can of worms, so I worked with the dump approach, looping and calculating the distance between the contour points. There may be a more convenient way to do this part, but it works.
I also looked at using Shapely , there may be ways to take advantage of this, but I did not find them, so I dropped it again.
import numpy as np import scipy.ndimage as ndimage from matplotlib import pyplot as plt import cv2 img= ndimage.imread('image.png')