Esempio n. 1
0
def create_paintings_db(db_path, data_path):
    """Creates a list of all paintings in the DB and their info.

    Parameters
    ----------
    db_path: str
        path of the directory containing all painting files.
    data_path: str
        path of the '.csv' file containing all paintings info.

    Returns
    -------
    list
        list of `Painting` objects, describing all the paintings that populate
        the DB.
    """
    paintings_db = []

    try:
        df_painting_data = pd.read_csv(ntpath.realpath(data_path))
    except UnicodeDecodeError:
        sys.exit(f"Error in the data file format: {data_path}")

    db_path = ntpath.realpath(db_path)
    orb = cv2.ORB_create()
    for subdir, dirs, files in os.walk(db_path):
        for painting_file in files:
            image = cv2.imread(os.path.join(db_path, painting_file))

            painting_info = df_painting_data.loc[df_painting_data['Image'] == painting_file].iloc[0]
            title = painting_info['Title']
            author = painting_info['Author']
            room = painting_info['Room']

            # create ORB keypoints and descriptors for each painting in the DB
            src_kp, src_des = orb.detectAndCompute(image, None)
            painting = Painting(
                image,
                title,
                author,
                room,
                painting_file,
                keypoints=src_kp,
                descriptors=src_des
            )
            paintings_db.append(painting)
    return paintings_db
Esempio n. 2
0
class Louvre(pyglet.window.Window):
    def __init__(self):
        super(Louvre, self).__init__(width=800,height=600)

        self.set_exclusive_mouse(True)
        self.grid    = grid(size=500)
        self.camera  = camera(pos=(0., -100., -750.),rot=(0.,0.,0.),window=self)
        self.renderMode = GL_FILL

        # rotational values
        self.xrot = self.yrot = self.zrot = 0.0

        # lighting
        self.light = False

        # walls and floors
        self.floor = Floor(500, 500, 'lightwood.jpg')
        self.right_wall = Floor(500, 500, 'lightwood.jpg')
        self.left_wall = Floor(500, 500, 'lightwood.jpg')
        self.behind_wall = Floor(500, 500, 'lightwood.jpg')
        self.ceiling = Floor(500, 500, 'lightwood.jpg')


        # Exhibits
        self.exhibits = []

        self.teapot = Teapot()   
        self.exhibits.append(self.teapot)

        self.basketball = Basketball()
        self.exhibits.append(self.basketball)

        self.snowman = Snowman()
        self.exhibits.append(self.snowman)

        starry_night_label = "Starry Night Painting\n----------\n- Texture On/Off\n- Rotate by Z\n- Scaling On/Off" 
        self.starry_night = Painting(100, 100, 'starry_night.jpg', starry_night_label)
        self.exhibits.append(self.starry_night)
        

        scream_label = "Scream Painting\n----------\n- Texture On/Off\n- Rotate by Z\n- Scaling On/Off" 
        self.scream = Painting(100, 100, 'scream.jpg', scream_label)
        self.exhibits.append(self.scream)
        

        van_gogh_label = "Van Gogh Painting\n----------\n- Texture On/Off\n- Rotate by Z\n- Scaling On/Off"     
        self.scream = Painting(100, 100, 'scream.jpg', scream_label)
        self.van_gogh = Painting(50, 70, 'vangogh.jpg', van_gogh_label)
        self.exhibits.append(self.van_gogh)
        
        son_of_man_label =  " Son of Man Painting\n----------\n- Texture On/Off\n- Rotate by Z\n- Scaling On/Off"
        self.son_of_man = Painting(70, 100, 'son_of_man.jpg', son_of_man_label)
        self.exhibits.append(self.son_of_man)
         
        # Current Focus
        self.current_index = 0
        self.current_focus = self.exhibits[self.current_index]
        
        # Signboard
        sign_text = "Welcome to the Louvre\n--------------------\n* X - rotate an object via X\n* Y - rotate an object via Y\n* Z - rotate an object via Z\n* T - Enable/Disable Texture\n* UP-Arrow - Scale up\n* Down-Arrow - Scale down\n* Tab - Switch object\n---------------\nCurrent Object Index " + str(self.current_index)
        self.signboard = Text(sign_text, x = 0, y = 0)

        # Lighting
        LightAmbient  = (GLfloat*4)(0.5, 0.5, 0.5, 1.0)
        LightDiffuse  = (GLfloat*4)(1.0, 1.0, 1.0, 1.0)
        LightPosition = (GLfloat*4)(0.0, 0.0, 2.0, 1.0)

        # initializations
        glEnable(GL_TEXTURE_2D)                                 # Textures
        glLightfv( GL_LIGHT1, GL_AMBIENT, LightAmbient )        # Lighting
        glLightfv( GL_LIGHT1, GL_DIFFUSE, LightDiffuse )
        glLightfv( GL_LIGHT1, GL_POSITION, LightPosition )

    def on_mouse_motion(self,x, y, dx, dy):
        self.camera.rot_y += dx / 4. 
        self.camera.rot_x -= dy / 4.

    def on_key_press(self,sym, mod):
        if sym == key.ESCAPE:   
            self.has_exit = True
        if sym == key.F:        # toggle between windows and fullscreen
            self.set_fullscreen(not self.fullscreen)
        if sym == key.R:
            if self.renderMode == GL_FILL:
                self.renderMode = GL_LINE
            else:
                self.renderMode = GL_FILL
            glPolygonMode(GL_FRONT_AND_BACK, self.renderMode)
        if sym == key.L:
            self.light = not self.light
            if not self.light:
                glDisable(GL_LIGHTING)
            else:
                glEnable(GL_LIGHTING)
        if sym == key.J:
            self.current_focus.shelf.height += 0.9
            print "Shelf Height of Obj %d is %f" % (self.current_index, self.current_focus.shelf.height)
        if sym == key.K:
            self.current_focus.shelf.height -= 0.9
            print "Shelf Height of Obj %d is %f" % (self.current_index, self.current_focus.shelf.height)
        

        # Control of rotational axes
        if sym == key.X:
            self.current_focus.xrot = not self.current_focus.xrot
            print "Rotation by X axis : %r " % self.current_focus.xrot
        if sym == key.Y:
            self.current_focus.yrot = not self.current_focus.yrot
            print "Rotation by Y axis : %r " % self.current_focus.yrot
        if sym == key.Z:
            self.current_focus.zrot = not self.current_focus.zrot
            print "Rotation by Z axis : %r" % self.current_focus.zrot

        if sym == key.UP:
            self.current_focus.scale += 0.5 
        if sym == key.DOWN:
            if self.current_focus.scale <= 0.5:
                self.current_focus.scale = 0.5
            else:
                self.current_focus.scale -= 0.5
        if sym == key.T:
            self.current_focus.texture_status = not self.current_focus.texture_status
        if sym == key.TAB:
            if self.current_index == len(self.exhibits) - 1:
                self.current_index = 0
            else:
                self.current_index += 1
            self.current_focus = self.exhibits[self.current_index]

    # Function that sets the camera to 3D mode            
    def on_resize(self,width, height): 
        glViewport(0, 0, self.width, self.height) 
        glMatrixMode(GL_PROJECTION) 
        glLoadIdentity() 
        gluPerspective(30., self.width / float(self.height), .1, 8000.)
        glMatrixMode(GL_MODELVIEW) 
        return pyglet.event.EVENT_HANDLED

    # your update function
    def update(self):
        self.dispatch_events()
        dt=pyglet.clock.tick()

        #update everything
        self.camera.update(dt)
        self.grid.update(dt)

        for exhibit in self.exhibits:
            exhibit.update()

        sign_text = "Welcome to the Louvre\n--------------------\n* X - rotate an object via X\n* Y - rotate an object via Y\n* Z - rotate an object via Z\n* T - Enable/Disable Texture\n* UP-Arrow - Scale up\n* Down-Arrow - Scale down\n* Tab - Switch object\n---------------\nCurrent Object Index " + str(self.current_index)
        self.signboard.update(sign_text)
    # your draw function
    def draw(self):
        glEnable(GL_TEXTURE_2D)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glLoadIdentity()
        glEnable(GL_DEPTH_TEST)

        self.camera.draw()
        # self.grid.draw()

        # floor
        glPushMatrix()
        glRotatef(90, 1, 0, 0)
        self.floor.draw()
        glPopMatrix()

        # right-side wall
        glPushMatrix()
        glTranslatef(500, 500, 0 )
        glRotatef(90, 0, 1, 0)
        self.right_wall.draw()
        glPopMatrix()

        # left-side wall
        glPushMatrix()
        glTranslatef(-500, 500, 0)
        glRotatef(90, 0, 1, 0)
        self.left_wall.draw()
        glPopMatrix()

        # behind wall
        glPushMatrix()
        glTranslatef(0, 500, -250)
        self.behind_wall.draw()
        glPopMatrix()

        # ceiling
        glPushMatrix()
        glTranslatef(0, self.left_wall.height * 2, 0)
        glRotatef(90, 1, 0, 0)
        self.ceiling.draw()
        glPopMatrix()

        # teapot
        glPushMatrix()
        glTranslatef( -200, self.teapot.shelf.height, 0)
        self.teapot.draw()
        glPopMatrix()

        # basketball
        glPushMatrix()
        glTranslatef( 200, self.basketball.shelf.height, 0)
        self.basketball.draw()
        glPopMatrix()

        # snowman
        glPushMatrix()
        glTranslatef(-200, self.snowman.shelf.height, 400)
        glRotatef(90, 0, 1, 0)
        self.snowman.draw()
        glPopMatrix()

        # paintings ( starry_night )
        glPushMatrix()
        glTranslatef(200, 300, -249)
        self.starry_night.draw()
        glPopMatrix()

        glPushMatrix()
        glTranslatef(-499, 300, 0)
        glRotatef(90, 0, 1, 0)
        self.scream.draw()
        glPopMatrix()

        glPushMatrix()
        glTranslatef(-200, 300, -249)
        self.van_gogh.draw()
        glPopMatrix()

        glPushMatrix()
        glTranslatef(499, 300, 0)
        glRotatef(-90, 0, 1, 0)
        self.son_of_man.draw()
        glPopMatrix()

        # signboard
        glPushMatrix()
        glTranslatef(300, 100, 350)
        glRotatef(-45, 0, 1, 0)
        self.signboard.draw()
        glPopMatrix()
Esempio n. 3
0
    def __init__(self):
        super(Louvre, self).__init__(width=800,height=600)

        self.set_exclusive_mouse(True)
        self.grid    = grid(size=500)
        self.camera  = camera(pos=(0., -100., -750.),rot=(0.,0.,0.),window=self)
        self.renderMode = GL_FILL

        # rotational values
        self.xrot = self.yrot = self.zrot = 0.0

        # lighting
        self.light = False

        # walls and floors
        self.floor = Floor(500, 500, 'lightwood.jpg')
        self.right_wall = Floor(500, 500, 'lightwood.jpg')
        self.left_wall = Floor(500, 500, 'lightwood.jpg')
        self.behind_wall = Floor(500, 500, 'lightwood.jpg')
        self.ceiling = Floor(500, 500, 'lightwood.jpg')


        # Exhibits
        self.exhibits = []

        self.teapot = Teapot()   
        self.exhibits.append(self.teapot)

        self.basketball = Basketball()
        self.exhibits.append(self.basketball)

        self.snowman = Snowman()
        self.exhibits.append(self.snowman)

        starry_night_label = "Starry Night Painting\n----------\n- Texture On/Off\n- Rotate by Z\n- Scaling On/Off" 
        self.starry_night = Painting(100, 100, 'starry_night.jpg', starry_night_label)
        self.exhibits.append(self.starry_night)
        

        scream_label = "Scream Painting\n----------\n- Texture On/Off\n- Rotate by Z\n- Scaling On/Off" 
        self.scream = Painting(100, 100, 'scream.jpg', scream_label)
        self.exhibits.append(self.scream)
        

        van_gogh_label = "Van Gogh Painting\n----------\n- Texture On/Off\n- Rotate by Z\n- Scaling On/Off"     
        self.scream = Painting(100, 100, 'scream.jpg', scream_label)
        self.van_gogh = Painting(50, 70, 'vangogh.jpg', van_gogh_label)
        self.exhibits.append(self.van_gogh)
        
        son_of_man_label =  " Son of Man Painting\n----------\n- Texture On/Off\n- Rotate by Z\n- Scaling On/Off"
        self.son_of_man = Painting(70, 100, 'son_of_man.jpg', son_of_man_label)
        self.exhibits.append(self.son_of_man)
         
        # Current Focus
        self.current_index = 0
        self.current_focus = self.exhibits[self.current_index]
        
        # Signboard
        sign_text = "Welcome to the Louvre\n--------------------\n* X - rotate an object via X\n* Y - rotate an object via Y\n* Z - rotate an object via Z\n* T - Enable/Disable Texture\n* UP-Arrow - Scale up\n* Down-Arrow - Scale down\n* Tab - Switch object\n---------------\nCurrent Object Index " + str(self.current_index)
        self.signboard = Text(sign_text, x = 0, y = 0)

        # Lighting
        LightAmbient  = (GLfloat*4)(0.5, 0.5, 0.5, 1.0)
        LightDiffuse  = (GLfloat*4)(1.0, 1.0, 1.0, 1.0)
        LightPosition = (GLfloat*4)(0.0, 0.0, 2.0, 1.0)

        # initializations
        glEnable(GL_TEXTURE_2D)                                 # Textures
        glLightfv( GL_LIGHT1, GL_AMBIENT, LightAmbient )        # Lighting
        glLightfv( GL_LIGHT1, GL_DIFFUSE, LightDiffuse )
        glLightfv( GL_LIGHT1, GL_POSITION, LightPosition )
Esempio n. 4
0
def detect_paintings(img, generator, show_image, print_next_step, print_time, scale_factor=1.):
    """Detect Paintings in the image using OpenCV functions.

    Parameters
    ----------
    img: ndarray
        the input image
    generator: generator
        generator function used to take track of the current step number
        and print useful information during processing.
    show_image: function
        function used to show image of the intermediate results
    print_next_step:function
        function used to print info about current processing step
    print_time: function
        function used to print info about execution time
    scale_factor: float
        scale factor for which the original image was scaled

    Returns
    -------
    list
        a list containing one `Painting` object for each
        painting detected in the input image.

    """
    h_img, w_img, c_img = img.shape

    # Step 1: Perform mean shift segmentation on the image
    # ----------------------------
    print_next_step(generator, "Mean Shift Segmentation")
    start_time = time.time()
    spatial_radius = 7  # 8 # 8 # 5 #8 or 7
    color_radius = 14  # 15 # 40 #40 #35 or 15
    maximum_pyramid_level = 1  # 1
    img_mss = mean_shift_segmentation(img, spatial_radius, color_radius, maximum_pyramid_level)
    print_time(start_time)
    show_image('mean_shift_segmentation', img_mss, height=405, width=720)

    # Step 2: Get a mask of just the wall in the gallery
    # ----------------------------
    print_next_step(generator, "Mask the Wall")
    start_time = time.time()
    color_difference = 2  # 2 # 1
    x_samples = 8  # 8 or 16
    wall_mask = find_largest_segment(img_mss, color_difference, x_samples)
    print_time(start_time)
    show_image(f'mask_largest_segment', wall_mask, height=405, width=720)

    # Step 4: Invert the wall mask
    # ----------------------------
    print_next_step(generator, "Invert Wall Mask")
    start_time = time.time()
    wall_mask_inverted = invert_image(wall_mask)
    print_time(start_time)
    show_image('mask_inverted', wall_mask_inverted, height=405, width=720)

    # Step 3: Erode and Dilate the wall mask to remove noise
    # ----------------------------
    print_next_step(generator, "Erode and Dilate")
    kernel_size = 20  # 18 or 20

    start_time = time.time()
    eroded_wall_mask = image_erosion(wall_mask_inverted, kernel_size)
    print_time(start_time)
    show_image('mask_eroded', eroded_wall_mask, height=405, width=720)

    start_time = time.time()
    dilated_wall_mask = image_dilation(eroded_wall_mask, kernel_size)
    print_time(start_time)
    show_image('mask_dilated', dilated_wall_mask, height=405, width=720)

    wall_mask_inverted = dilated_wall_mask

    # ----------------------------
    # Connected Components Analysis:
    #   Perform connected components on the inverted wall mask to find the
    #   non-wall components
    # ----------------------------

    # Step 5: Find all contours
    # ----------------------------
    print_next_step(generator, "Find Contours")
    start_time = time.time()
    contours_mode = cv2.RETR_TREE
    contours_method = cv2.CHAIN_APPROX_NONE  # cv2.CHAIN_APPROX_SIMPLE
    contours_1, hierarchy_1 = find_image_contours(wall_mask_inverted, contours_mode, contours_method)
    # Draw the contours on the image (https://docs.opencv.org/trunk/d4/d73/tutorial_py_contours_begin.html)
    # img_contours = img.copy()
    # cv2.drawContours(img_contours, contours_1, -1, (0, 255, 0), 3)
    # show_image('image_contours_1', img_contours, height=405, width=720)

    # Add a white border to manage cases when `find_largest_segment`
    # works the opposite way (wall black and painting white)
    thickness = 1
    wall_mask_inverted_2 = cv2.rectangle(wall_mask_inverted, (0, 0), (w_img - 1, h_img - 1), 255, thickness)
    # show_image("wall_mask_inverted_2", wall_mask_inverted_2, height=405, width=720)

    # Step 5: Find all contours
    # ----------------------------
    contours_mode = cv2.RETR_TREE
    contours_method = cv2.CHAIN_APPROX_NONE  # cv2.CHAIN_APPROX_SIMPLE
    contours_2, hierarchy_2 = find_image_contours(wall_mask_inverted_2, contours_mode, contours_method)
    print_time(start_time)
    # Draw the contours on the image (https://docs.opencv.org/trunk/d4/d73/tutorial_py_contours_begin.html)
    # img_contours = img.copy()
    # cv2.drawContours(img_contours, contours_2, -1, (0, 255, 0), 3)
    # show_image('image_contours_2', img_contours, height=405, width=720)

    remove_overlapping = False
    error_in_wall_mask = False
    if len(contours_2) >= len(contours_1):
        contours = contours_2
        hierarchy = hierarchy_2
        remove_overlapping = True
        # Fix the wall mask considering the one before the inversion
        wall_mask_inverted = invert_image(dilated_wall_mask)
        error_in_wall_mask = True
        show_image('wall_mask_corrected', wall_mask_inverted, height=405, width=720)
    else:
        contours = contours_1
        hierarchy = hierarchy_1

    # # Print every contour step-by-step
    # img_contours = img.copy()
    # for contour in contours:
    #     cv2.drawContours(img_contours, [contour], 0, (0, 255, 0), 3)
    #     show_image('image_contours', img_contours, height=405, width=720)

    # Step 6: Refine list of components found
    # ----------------------------
    print_next_step(generator, "Refine Components found")
    start_time = time.time()
    find_min_area_rect = True  # True
    width_min = 150
    height_min = 150
    area_percentage_min = 0.6
    candidate_painting_contours = extract_candidate_painting_contours(
        img=img,
        contours=contours,
        hierarchy=hierarchy,
        show_image=show_image,
        find_min_area_rect=find_min_area_rect,
        width_min=width_min,
        height_min=height_min,
        area_percentage_min=area_percentage_min,
        remove_overlapping=remove_overlapping
    )
    print_time(start_time)
    img_refined_contours = img.copy()
    cv2.drawContours(img_refined_contours, candidate_painting_contours, -1, (0, 255, 0), 3)
    show_image('paintings_contours_refined', img_refined_contours, height=405, width=720)

    # Step SEGMENTATION: create a segmented image where only the candidate contours are white, in order to
    #                    remove unwanted object and make the following operation (erosion/dilation) faster
    # -----------------------
    print_next_step(generator, "Create Segmented Mask")
    start_time = time.time()
    segmented_img = create_segmented_image(wall_mask_inverted, candidate_painting_contours)
    # show_image('segmented_img', segmented_img, height=405, width=720)
    segmented_img = image_morphology_tranformation(segmented_img, cv2.MORPH_OPEN, 20)
    show_image('segmented_img', segmented_img, height=405, width=720)
    print_time(start_time)

    # I further refine the contours
    candidate_painting_contours, _ = find_image_contours(segmented_img, contours_mode, contours_method)

    # -----------------------
    # PADDING: add black padding to avoid unwanted "adherent" effects at the border when do erosion
    # -----------------------
    thickness = 1
    segmented_img = cv2.copyMakeBorder(segmented_img, thickness, thickness, thickness, thickness, cv2.BORDER_CONSTANT,
                                       None, 0)

    # Step 7: Erode components to remove unwanted objects connected to the frame
    #         If there was an error in the wall mask (is inverted) then apply Dilation
    # ----------------------------
    print_next_step(generator, "Erode Components")
    start_time = time.time()
    kernel_size = 20  # 23 or 40
    if not error_in_wall_mask:
        cleaned_wall_mask = image_erosion(segmented_img, kernel_size)
    else:
        kernel_size = 30
        cleaned_wall_mask = image_dilation(segmented_img, kernel_size)
        cleaned_wall_mask = image_erosion(cleaned_wall_mask, kernel_size)
    print_time(start_time)
    # show_image('image_mask_cleaned', cleaned_wall_mask, height=405, width=720)

    # Remove padding
    cleaned_wall_mask = cleaned_wall_mask[thickness:-thickness, thickness:-thickness]

    # Step 8: Blur using Median Filter to smooth the lines of the frame
    # ----------------------------
    print_next_step(generator, "Blur with Median Filter")
    start_time = time.time()
    blur_size = 31  # 15
    blurred_mask = image_blurring(cleaned_wall_mask, blur_size)
    print_time(start_time)
    show_image('mask_eroded_and_blurred', blurred_mask, height=405, width=720)

    # ----------------------------
    # RECOGNIZE PAINTING:
    # for each frame contour, recognise a painting from it
    # ----------------------------

    paintings_detected = []
    for contour in candidate_painting_contours:
        x, y, w_rect, h_rect = cv2.boundingRect(contour)

        sub_img = img[y:y + h_rect, x:x + w_rect]
        sub_mask = blurred_mask[y:y + h_rect, x:x + w_rect]

        show_image('sub_image', sub_img)
        show_image('sub_mask', sub_mask)

        # -----------------------
        # BORDER:
        # Add a black pixel of border in order to avoid problem
        # when you will try find edge of painting touching the border.
        # You can also use the `borderType=` parameter of `cv2.erode`
        # -----------------------
        thickness = 1
        pad_sub_mask = sub_mask.copy()
        cv2.rectangle(pad_sub_mask, (0, 0), (w_rect - 1, h_rect - 1), 0, thickness)

        # Step 9: Canny Edge detection to get the outline of the frame
        # ----------------------------
        print_next_step(generator, "Canny Edge detection")
        start_time = time.time()
        threshold1 = 70  # 50
        threshold2 = 140  # 100
        edges = canny_edge_detection(pad_sub_mask, threshold1, threshold2)
        print_time(start_time)
        show_image('mask_canny', edges)

        # Step 10: Hough Lines to find vertical and horizontal edges of the paintings
        # ----------------------------
        print_next_step(generator, "Hough Lines")
        start_time = time.time()
        probabilistic_mode = False
        rho = 1
        theta = np.pi / 180
        threshold = 50  # 50 or 30 or 40 or 0
        ratio_percentage = 0.10
        lines = find_hough_lines(
            img=edges,
            probabilistic_mode=probabilistic_mode,
            rho=rho,
            theta=theta,
            threshold=threshold,
            ratio_percentage=ratio_percentage
        )
        print_time(start_time)
        img_lines = draw_lines(edges, lines, probabilistic_mode)
        # show_image("Detected Lines (in red)", img_lines)

        if lines is None:
            # I can't find lines in special situation, e.g the painting is not squared (rounded, octagonal, ...)
            # In this case the corners are the tl, tr, br, bl point of `sub_img` and the contour is the original one
            corners = np.float32([
                [0, 0],
                [w_rect - 1, 0],
                [w_rect - 1, h_rect - 1],
                [0, h_rect - 1]
            ])
            painting_contour = contour
        else:
            # Step 11: Create mask from painting edges
            # ----------------------------
            print_next_step(generator, "Create mask from painting edges")
            start_time = time.time()
            color_value = 255
            extended_lines_mask = extend_image_lines(sub_mask, lines, probabilistic_mode, color_value)
            print_time(start_time)
            show_image('hough_lines_mask', extended_lines_mask)

            # Step 12: Isolate Painting from mask
            # ----------------------------
            print_next_step(generator, "Isolate Painting from mask")
            start_time = time.time()
            painting_contour = isolate_painting(extended_lines_mask)
            print_time(start_time)
            # Draw the contours on the image (https://docs.opencv.org/trunk/d4/d73/tutorial_py_contours_begin.html)
            img_painting_contour = np.zeros((sub_img.shape[0], sub_img.shape[1]), dtype=np.uint8)
            cv2.drawContours(img_painting_contour, [painting_contour], 0, 255, cv2.FILLED)
            # If `cv2.drawContours` doesn't work, use `cv2.fillPoly`
            # cv2.fillPoly(img_painting_contour, pts=[painting_contour], color=255)
            show_image('painting_contours', img_painting_contour)

            # -----------------------
            # BORDER
            # -----------------------
            thickness = 1
            img_painting_contour = cv2.rectangle(img_painting_contour, (0, 0), (w_rect - 1, h_rect - 1), 0, thickness)

            # Step 13: Corner Detection of the painting
            # ----------------------------
            print_next_step(generator, "Corner Detection")
            start_time = time.time()
            max_number_corners = 4
            corner_quality = 0.001
            min_distance = 10  # 20
            corners = find_corners(
                img_painting_contour,
                max_number_corners=max_number_corners,
                corner_quality=corner_quality,
                min_distance=min_distance
            )
            # painting_corners = np.zeros((sub_img.shape[0], sub_img.shape[1]), dtype=np.uint8)
            # draw_corners(painting_corners, corners)
            # show_image('corners_TEST', painting_corners)

            # Checking corners to avoid problem (read function descr. for info)
            min_percentage = 0.70  # 0.7 or 0.75 or 0.6
            corners = check_corners_area(sub_img, contour, corners, min_percentage)
            print_time(start_time)

        # Draw painting corners
        painting_corners = np.zeros((sub_img.shape[0], sub_img.shape[1]), dtype=np.uint8)
        draw_corners(painting_corners, corners)
        show_image('painting_corners', painting_corners)

        # Create a new `Painting` object with all the information
        # about the painting detected
        detected_painting = Painting()
        detected_painting.bounding_box = np.int32(np.array([x, y, w_rect, h_rect]) * scale_factor)
        x, y, w_rect, h_rect = detected_painting.bounding_box
        detected_painting.frame_contour = np.int32(contour * scale_factor)
        detected_painting.points = translate_points(np.int32(painting_contour * scale_factor), [x, y])
        detected_painting.corners = translate_points(np.int32(corners * scale_factor), [x, y])
        paintings_detected.append(detected_painting)

        # cv2.waitKey(0)

    return paintings_detected