nipunasudha
nipunasudha

Reputation: 2607

OpenCV- Detecting objects excluding shadows

I'm developing a way to detect cars from an aerial view image. I'm using scikit package to calculate the difference of an empty parking lot image and a parking lot with cars to detect the foreign objects. Then I draw the minimum area rectangle around cars.

This works well when there are no shadows.

Empty parking lot image (Please ignore the maroon color car)

Empty parking lot

Without shadows

Without shadows original

Without shadows threshold

Without shadows result

With shadows (Problem)

When there are car shadows, they are also in the minimum area rectangle. How can I exclude shadows from the rectangle?

With shadows original

With shadows threshold

With shadows result

Here is my source

import numpy as np
from skimage.measure import compare_ssim
import imutils
from cv2 import cv2

# construct the argument parse and parse the arguments
# load the two input images
imageA = cv2.imread('empty-lot.png')
imageB = cv2.imread('two-car-lot-shadow.png')

# convert the images to grayscale
grayA = cv2.cvtColor(imageA, cv2.COLOR_BGR2GRAY)
grayB = cv2.cvtColor(imageB, cv2.COLOR_BGR2GRAY)

# compute the Structural Similarity Index (SSIM) between the two
# images, ensuring that the difference image is returned
(score, diff) = compare_ssim(grayA, grayB, full=True, gaussian_weights=True, sigma=4)
diff = (diff * 255).astype("uint8")
print("SSIM: {}".format(score))

# threshold the difference image, followed by finding contours to
# obtain the regions of the two input images that differ
thresh = cv2.threshold(diff, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if imutils.is_cv2() else cnts[1]

# loop over the contours
for c in cnts:
    # compute the min area rect of the contour and area
    rect = cv2.minAreaRect(c)
    box = cv2.boxPoints(rect)
    area = cv2.contourArea(c)
    # remove small contour areas
    if area < 10000: continue
    # convert all coordinates floating point values to int
    box = np.int0(box)
    # draw a green rectangle
    cv2.drawContours(imageB, [box], 0, (0, 255, 0), 2)

# show the output images
cv2.imshow("Modified", imageB)
cv2.imshow("Thresh", thresh)
k = cv2.waitKey() & 0xFF
if k == 27:
    exit()

Upvotes: 3

Views: 5809

Answers (1)

Carson
Carson

Reputation: 8008

You can convert the image format to HSV, it's easy to remove the shadow.

import ctypes
import numpy as np
import cv2
from pathlib import Path
from typing import List, Union, Callable


def main():
    img_bgr: np.ndarray = cv2.imread(str(Path('parking.jpg')))
    img_hsv: np.ndarray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV)
    mask = cv2.inRange(src=img_hsv, lowerb=np.array([0, 64, 153]), upperb=np.array([179, 255, 255]))
    img_hsv_modify: np.ndarray = cv2.bitwise_and(img_bgr, img_bgr, mask=mask)
    # show_img(img_hsv_modify)

    img_mask_gray = cv2.cvtColor(img_hsv_modify, cv2.COLOR_BGR2GRAY)
    threshold_val, img_bit = cv2.threshold(img_mask_gray, 0, 255, cv2.THRESH_BINARY)  # type: float, np.ndarray
    contours, hierarchy = cv2.findContours(img_bit, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    img_bgr_copy = np.copy(img_bgr)
    for cnt in filter(lambda c: cv2.contourArea(c) > 10000, contours):
        box = cv2.boxPoints(cv2.minAreaRect(cnt))
        cv2.drawContours(img_bgr_copy, [np.int0(box)], -1, color=(0, 255, 0), thickness=2)

    show_img([img_bgr, img_hsv_modify, cv2.cvtColor(img_bit, cv2.COLOR_GRAY2BGR), img_bgr_copy],
             note=['original', 'hsv modify', 'bit', 'result'])
    show_img(img_bgr_copy)


if __name__ == '__main__':
    main()

And you may ask how do I know the lower bound and upper bound. (Please see the extension code: control_bar_hsv.py)

mask = cv2.inRange(src=img_hsv, lowerb=np.array([0, 64, 153]), upperb=np.array([179, 255, 255]))

result:

enter image description here

all image:

enter image description here

Extension code

show_img:

def show_img(img_list: Union[np.ndarray, List[np.ndarray]], combine_fun: Callable = np.vstack,
             window_name='demo', window_size=(ctypes.windll.user32.GetSystemMetrics(0) // 2, ctypes.windll.user32.GetSystemMetrics(1) // 2),
             delay_time=0, note: Union[str, List[str]] = None, **options):
    if isinstance(img_list, np.ndarray):
        img_list = [img_list]

    if isinstance(note, str):
        print(note)

    cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)
    if window_size:
        w, h = window_size
        cv2.resizeWindow(window_name, w, h)

    result_list = []
    for idx, img in enumerate(img_list):
        img = np.copy(img)
        if note and isinstance(note, list) and idx < len(note):
            cv2.putText(img, note[idx], org=options.get('org', (50, 50)),
                        fontFace=options.get('fontFace', cv2.FONT_HERSHEY_SIMPLEX),
                        fontScale=options.get('fontScale', 2), color=(0, 255, 255), thickness=4)
        result_list.append(img)
    cv2.imshow(window_name, combine_fun(result_list))
    cv2.waitKey(delay_time)

control_bar_hsv.py

It's easy to use, just copy and paste and then tell it where is your image path, done.

and if you want to create other control_bar, maybe ControlBarBase will help you write to write less code.

"""
control_bar_hsv.py
"""

import cv2
from pathlib import Path
import numpy as np
import ctypes
import functools
from typing import Tuple, Callable


class ControlBarBase:
    __slots__ = ('img_bgr',)
    WAIT_TIME = 500  # milliseconds
    CONTROL_PANEL_NAME = 'control_panel'
    IMAGE_PANEL_NAME = 'image'
    SCREEN_SIZE: Tuple[int, int] = None

    def __init__(self, img_path: Path):
        self.img_bgr: np.ndarray = cv2.imread(str(img_path))
        self.init_window()
        self.init_track_bar()

    def init_window(self):
        for name in (self.CONTROL_PANEL_NAME, self.IMAGE_PANEL_NAME):
            cv2.namedWindow(name, cv2.WINDOW_NORMAL)
            if self.SCREEN_SIZE:
                screen_width, screen_height = self.SCREEN_SIZE
                cv2.resizeWindow(name, int(screen_width), int(screen_height))

    def init_track_bar(self):
        """
        self.build_track_bar(label_name, range_lim=(0, 255), default_value=0, callback)
        """
        raise NotImplementedError('subclasses of _ControlBarBase must provide a init_track_bar() method')

    def render(self):
        raise NotImplementedError('subclasses of _ControlBarBase must provide a render() method.')

    def build_track_bar(self, label_name: str,
                        range_lim: Tuple[int, int], default_value: int, callback: Callable = lambda x: ...):
        min_val, max_val = range_lim
        cv2.createTrackbar(label_name, self.CONTROL_PANEL_NAME, min_val, max_val, callback)
        cv2.setTrackbarPos(label_name, self.CONTROL_PANEL_NAME, default_value)

    def get_trackbar_pos(self, widget_name: str):
        return cv2.getTrackbarPos(widget_name, self.CONTROL_PANEL_NAME)

    def run(self):
        while 1:
            img, callback_func = self.render()
            cv2.imshow(self.IMAGE_PANEL_NAME, img)
            if (cv2.waitKey(self.WAIT_TIME) & 0xFF == ord('q') or
                    cv2.getWindowProperty(self.IMAGE_PANEL_NAME, 0) == -1 or
                    cv2.getWindowProperty(self.CONTROL_PANEL_NAME, 0) == -1):
                callback_func()
                break
        cv2.destroyAllWindows()


class ControlBarHSV(ControlBarBase):
    __slots__ = ()
    WAIT_TIME = 500
    SCREEN_SIZE = ctypes.windll.user32.GetSystemMetrics(0) / 2, ctypes.windll.user32.GetSystemMetrics(1) / 2

    def init_track_bar(self):
        self.build_track_bar('HMin', range_lim=(0, 179), default_value=0)
        self.build_track_bar('SMin', (0, 255), 0)
        self.build_track_bar('VMin', (0, 255), 0)

        self.build_track_bar('HMax', (0, 179), 179)
        self.build_track_bar('SMax', (0, 255), 255)
        self.build_track_bar('VMax', (0, 255), 255)

    def render(self):
        # get current positions of all trackbars
        h_min = self.get_trackbar_pos('HMin')
        s_min = self.get_trackbar_pos('SMin')
        v_min = self.get_trackbar_pos('VMin')

        h_max = self.get_trackbar_pos('HMax')
        s_max = self.get_trackbar_pos('SMax')
        v_max = self.get_trackbar_pos('VMax')

        # Set minimum and max HSV values to display
        lower = np.array([h_min, s_min, v_min])
        upper = np.array([h_max, s_max, v_max])

        # Create HSV Image and threshold into a range.
        hsv = cv2.cvtColor(self.img_bgr, cv2.COLOR_BGR2HSV)
        mask = cv2.inRange(hsv, lower, upper)
        img_output: np.ndarray = cv2.bitwise_and(self.img_bgr, self.img_bgr, mask=mask)

        @functools.wraps(self.render)
        def cb_func():
            return print(f'cv2.inRange(src=hsv, lowerb=np.array([{h_min}, {s_min}, {v_min}]), upperb=np.array([{h_max}, {s_max}, {v_max}]))')

        return img_output, cb_func


if __name__ == '__main__':
    from argparse import ArgumentParser

    arg_parser = ArgumentParser()
    arg_parser.add_argument("src_path", type=Path, help="source image path")
    args = arg_parser.parse_args()
    obj = ControlBarHSV(args.src_path)  # ControlBarHSV(Path('parking.jpg'))
    obj.run()

enter image description here

Upvotes: 1

Related Questions