import numpy as np
import copy
import cv2
from typing import *
from ..threshold import Threshold
BaseForColor = NewType("BaseForColor", Union[int, Tuple[Tuple[int, int, int], Tuple[int, int, int]]])
SERIALIZED_COLOR_KEYS = {"high", "low"}
[docs]def validate(value, ceiling):
"""
Checks if val is positive and less than the ceiling value.
:param value: The value to be checked (usually an int)
:param ceiling: The highest value "val" can be. (usually an int)
:return: True if val is less than ceiling and not negative
"""
value = int(value)
return 0 <= value < ceiling
[docs]def assert_hsv(hsv_point):
"""
Makes sure the hsv point(or vector) given in the parameter has valid values.
:param hsv_point: a 3 length list with 3 ints describing a point the hsv color space
that describe a color limit in a range for a findContour function.
:return: True if valid False if not.
:rtype: bool
"""
return validate(hsv_point[0], 180) and validate(hsv_point[1], 256) and validate(hsv_point[2], 256)
[docs]class Color(Threshold):
"""
Color is a Threshold object (an object that turns an image to a binary image
- an image with pixels with the value 1 and 0)
Color object thresholds using 2 HSV color ranges.
Read more about HSV here:
https://www.lifewire.com/what-is-hsv-in-design-1078068
HSV Ranges in OVL (And in the underlying open-cv (cv2)) are:
Hue - 0 to 179 (360/2 so divide any regular HSV value you use by 2)
So if Yellow is 40 - 80 (degrees) in regular HSV palette its 20 to 40 in ovl
Saturation - 0 to 255 (255 is equal to 1 or 100%)
Value - 0 to 255 (255 is equal to 1 or 100%)
2 ranges are passed to the Constructor:
1) Low HSV Range - the lowest acceptable H S V values ([low_H, low_S, low_V])
2) High HSV Range - the highest acceptable H S V values ([high_H, high_S, high_V])
.. code-block:: python
low_range = [15, 100, 100]
high_range = [45, 255, 255]
color = ovl.Color(low_range, high_range)
The color object can the be passed to a Vision object to threshold binary images
Threshold object can be used by themselves using the color.convert() method.
NOTE: Threshold objects automatically convert images to HSV (From the default BGR)
There are multiple built in "battery included" pre-made color object
for instant use in testing and tuning
List of colors:
Red (MultiColorObject) : Red (low) + Red (high)
Red (Low): [0, 100, 100], [8, 255, 255]
Red (High): [172, 100, 100], [179, 255, 255]
Note: in order to find red, use both ranges (low and high) and use the some of both results.
Blue: [105, 100, 100], [135, 255, 255]
Green: [45, 100, 100], [75, 255, 255]
Yellow: [20, 100, 100], [55, 255, 255]
Orange: [10, 100, 100], [18, 255, 255]
Grey: [0, 0, 0], [179, 50, 195]
Black: [0, 0, 0], [179, 255, 30]
White: [0, 0, 200], [179, 20, 255]
Teal: [110, 100, 100], [130, 255, 255]
Purple: [135, 100, 100], [165, 255, 255]
"""
[docs] def validate(self, *args, **kwargs):
return assert_hsv(self.low_bound) and assert_hsv(self.high_bound)
def __init__(self, low: BaseForColor, high: BaseForColor):
"""
Constructor for the Color used to turn images to binary images based on
HSV color space (pixels that are in the specified HSV Range)
H - 0 - 179
S - 0 - 255
V - 0 - 255
:param high: high hsv limit of the color
:param low: low hsv limit of the color
"""
if type(low) is tuple:
low = list(low)
if type(low) is int:
low = [low, 100, 100]
if type(high) is tuple:
high = list(high)
if type(high) is int:
high = [high, 255, 255]
self.__low_bound = np.array(low)
self.__high_bound = np.array(high)
[docs] def threshold(self, image: np.ndarray) -> np.ndarray:
return cv2.inRange(image, self.low, self.high)
[docs] def convert(self, image: np.ndarray) -> np.ndarray:
"""
Converts a given image to hsv and then thresholds and returns the binary mask
:param image: a BGR image
:return: binary mask
:rtype: numpy array
"""
hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
return self.threshold(hsv_image)
def __repr__(self):
return 'Color({}, {})'.format(repr(self.low_bound), repr(self.high_bound))
[docs] def copy(self):
"""
Duplicates the Color object so that changes do not affect the original color
Useful for modifying BuiltInColors without changing the default
:return: a copy of the color object
"""
return copy.deepcopy(self)
@property
def low_bound(self):
if isinstance(self.__low_bound, np.ndarray):
return self.__low_bound.tolist()
return self.__low_bound
@property
def high_bound(self):
if isinstance(self.__high_bound, np.ndarray):
return self.__high_bound.tolist()
return self.__high_bound
@property
def low(self):
"""
Returns the low hsv limit of the color.
:return: An uint8 numpy array with the low limit of the color.
:rtype: uint8 numpy array
"""
return self.__low_bound
@property
def high(self):
"""
Returns the high hsv limit of the color.
:return: An uint8 numpy array with the high limit of the color.
:rtype: uint8 numpy array
"""
return self.__high_bound
def __str__(self):
return self.__repr__()