Files
Convention-Python/Convention/Runtime/Visual/OpenCV.py

1433 lines
50 KiB
Python
Raw Normal View History

2025-07-21 14:47:31 +08:00
from ..Config import *
try:
import cv2 as base
import cv2.data as BaseData
except ImportError:
InternalImportingThrow("OpenCV", ["opencv-python"])
raise
try:
from PIL import ImageFile, Image
except ImportError:
InternalImportingThrow("OpenCV", ["Pillow"])
raise
from ..File import tool_file
# OpenCV Image format is BGR
# PIL Image format is RBG
VideoWriter = base.VideoWriter
def mp4_with_MPEG4_fourcc() -> int:
return VideoWriter.fourcc(*"mp4v")
def avi_with_Xvid_fourcc() -> int:
return VideoWriter.fourcc(*"XVID")
def avi_with_DivX_fourcc() -> int:
return VideoWriter.fourcc(*"DIVX")
def avi_with_MJPG_fourcc() -> int:
return VideoWriter.fourcc(*"MJPG")
def mp4_or_avi_with_H264_fourcc() -> int:
return VideoWriter.fourcc(*"X264")
def avi_with_H265_fourcc() -> int:
return VideoWriter.fourcc(*"H264")
def wmv_with_WMV1_fourcc() -> int:
return VideoWriter.fourcc(*"WMV1")
def wmv_with_WMV2_fourcc() -> int:
return VideoWriter.fourcc(*"WMV2")
def oggTheora_with_THEO_fourcc() -> int:
return VideoWriter.fourcc(*"THEO")
def flv_with_FLV1_fourcc() -> int:
return VideoWriter.fourcc(*"FLV1")
class VideoWriterInstance(VideoWriter):
def __init__(
self,
file_name: Union[tool_file, str],
fourcc: int,
fps: float,
frame_size: tuple[int, int],
is_color: bool = True
):
super().__init__(str(file_name), fourcc, fps, frame_size, is_color)
def __del__(self):
self.release()
AffineFeature_feature2D = base.AffineFeature.create
SIFT_Feature2D = base.SIFT.create
ORB_Feature2D = base.ORB.create
BRISK_Feature2D = base.BRISK.create
AKAZE_Feature2D = base.AKAZE.create
KAZE_Feature2D = base.KAZE.create
MSER_Feature2D = base.MSER.create
FastFeatureDetector_Feature2D = base.FastFeatureDetector.create
AgastFeatureDetector_Feature2D = base.AgastFeatureDetector.create
GFTTDetector_Feature2D = base.GFTTDetector.create
SimpleBlobDetector_Feature2D = base.SimpleBlobDetector.create
class Feature2DInstance[featrue:base.Feature2D]:
def __init__(
self,
feature2D:Union[featrue, Callable[[],featrue]]
):
if isinstance(feature2D, base.Feature2D):
self.mainFeature2D = feature2D
else:
self.mainFeature2D = feature2D()
def detect[Mat_or_Mats:Union[
MatLike,
base.UMat,
Sequence[MatLike],
Sequence[base.UMat]
]](
self,
image: Mat_or_Mats,
mask: Optional[Mat_or_Mats] = None
) -> Sequence[base.KeyPoint]:
return self.mainFeature2D.detect(image, mask)
def compute[Mat_or_Mats:Union[
MatLike,
base.UMat,
Sequence[MatLike],
Sequence[base.UMat]
]](
self,
image: Mat_or_Mats,
keypoints: Optional[Sequence[base.KeyPoint]] = None,
descriptors: Optional[Mat_or_Mats] = None
) -> Tuple[Sequence[base.KeyPoint], MatLike]:
return self.mainFeature2D.compute(image, keypoints, descriptors)
def detectAndCompute[_Mat:Union[
MatLike,
base.UMat,
]](
self,
image: _Mat,
mask: Optional[_Mat] = None,
descriptors: Optional[_Mat] = None,
useProvidedKeypoints:bool = False
) -> Tuple[Sequence[base.KeyPoint], MatLike]:
return self.mainFeature2D.detectAndCompute(image, mask, descriptors, useProvidedKeypoints)
def wait_key(delay:int):
return base.waitKey(delay)
def until_esc():
return wait_key(0)
def is_current_key(key:str, *, wait_delay:int = 1):
return wait_key(wait_delay) & 0xFF == ord(key[0])
class light_cv_view:
def __init__(self, filename_or_index:Union[str, tool_file, int]):
self.__capture: base.VideoCapture = None
self.stats: bool = True
self.retarget(filename_or_index)
def __del__(self):
self.release()
@override
def ToString(self):
return f"View<{self.width}x{self.height}>"
def __bool__(self):
return self.stats
def is_open(self):
return self.__capture.isOpened()
def release(self):
if self.__capture is not None:
self.__capture.release()
def retarget(self, filename_or_index:Union[str, tool_file, int]):
self.release()
if isinstance(filename_or_index, int):
self.__capture = base.VideoCapture(filename_or_index)
else:
self.__capture = base.VideoCapture(str(filename_or_index))
return self
def next_frame(self) -> MatLike:
self.stats, frame =self.__capture.read()
if self.stats:
return frame
else:
return None
def get_captrue_info(self, id:int):
return self.__capture.get(id)
def get_prop_pos_msec(self):
return self.get_captrue_info(0)
def get_prop_pos_frames(self):
return self.get_captrue_info(1)
def get_prop_avi_ratio(self):
return self.get_captrue_info(2)
def get_prop_frame_width(self):
return self.get_captrue_info(3)
def get_prop_frame_height(self):
return self.get_captrue_info(4)
def get_prop_fps(self):
return self.get_captrue_info(5)
def get_prop_fourcc(self):
return self.get_captrue_info(6)
def get_prop_frame_count(self):
return self.get_captrue_info(7)
def get_prop_format(self):
return self.get_captrue_info(8)
def get_prop_mode(self):
return self.get_captrue_info(9)
def get_prop_brightness(self):
return self.get_captrue_info(10)
def get_prop_contrast(self):
return self.get_captrue_info(11)
def get_prop_saturation(self):
return self.get_captrue_info(12)
def get_prop_hue(self):
return self.get_captrue_info(13)
def get_prop_gain(self):
return self.get_captrue_info(14)
def get_prop_exposure(self):
return self.get_captrue_info(15)
def get_prop_convert_rgb(self):
return self.get_captrue_info(16)
def setup_capture(self, id:int, value):
self.__capture.set(id, value)
return self
def set_prop_pos_msec(self, value:int):
return self.setup_capture(0, value)
def set_prop_pos_frames(self, value:int):
return self.setup_capture(1, value)
def set_prop_avi_ratio(self, value:float):
return self.setup_capture(2, value)
def set_prop_frame_width(self, value:int):
return self.setup_capture(3, value)
def set_prop_frame_height(self, value:int):
return self.setup_capture(4, value)
def set_prop_fps(self, value:int):
return self.setup_capture(5, value)
def set_prop_fourcc(self, value):
return self.setup_capture(6, value)
def set_prop_frame_count(self, value):
return self.setup_capture(7, value)
def set_prop_format(self, value):
return self.setup_capture(8, value)
def set_prop_mode(self, value):
return self.setup_capture(9, value)
def set_prop_brightness(self, value):
return self.setup_capture(10, value)
def set_prop_contrast(self, value):
return self.setup_capture(11, value)
def set_prop_saturation(self, value):
return self.setup_capture(12, value)
def set_prop_hue(self, value):
return self.setup_capture(13, value)
def set_prop_gain(self, value):
return self.setup_capture(14, value)
def set_prop_exposure(self, value):
return self.setup_capture(15, value)
def set_prop_convert_rgb(self, value:int):
return self.setup_capture(16, value)
def set_prop_rectification(self, value:int):
return self.setup_capture(17, value)
@property
def width(self) -> float:
return self.get_prop_frame_width()
@width.setter
def width(self, value:float) -> float:
self.set_prop_frame_width(value)
return value
@property
def height(self):
return self.get_prop_frame_height()
@height.setter
def height(self, value:float) -> float:
self.set_prop_frame_height(value)
return value
@property
def frame_size(self) -> Tuple[float, float]:
return self.get_prop_frame_width(), self.get_prop_frame_height()
@property
def shape(self):
return self.frame_size
@frame_size.setter
def frame_size(self, value:Tuple[float, float]) -> Tuple[float, float]:
self.set_prop_frame_width(value[0])
self.set_prop_frame_height(value[1])
return value
class light_cv_camera(light_cv_view, any_class):
def __init__(self, index:int = 0):
self.writer: VideoWriter = None
super().__init__(int(index))
@override
def release(self):
super().release()
if self.writer is not None:
self.writer.release()
def current_frame(self):
return self.next_frame()
def recording(
self,
stop_pr: Callable[[], bool],
writer: Union[VideoWriter, Callable[[MatLike], Any]],
):
writer_stats = False
if isinstance(writer, VideoWriter):
self.writer = writer
writer_stats = True
while self.is_open():
if stop_pr():
break
frame = self.current_frame()
base.imshow("__recording__", frame)
if writer_stats:
writer.write(frame)
else:
writer(frame)
base.destroyWindow("__recording__")
return self
@override
def ToString(self):
return f"Camera<{self.width}x{self.height}>"
def get_zero_mask(shape, *args, **kwargs) -> MatLike:
return np.zeros(shape, *args, **kwargs)
def get_one_mask(shape, value, *args, **kwargs) -> MatLike:
return np.ones(shape, value, *args, **kwargs)
class ImageObject(left_np_ndarray_reference):
@property
def __image(self) -> MatLike:
return self.ref_value
@__image.setter
def __image(self, value:MatLike) -> MatLike:
self.ref_value = value
return value
@overload
def __init__(self, imagePath:str, flags:Optional[int] = None):...
@overload
def __init__(self, image:tool_file, flags:Optional[int] = None):...
@overload
def __init__(self, camera:light_cv_camera):...
@overload
def __init__(self, image:MatLike, flags:Optional[int] = None):...
@overload
def __init__(self, image:Self):...
@overload
def __init__(self, image:Image.Image):...
@overload
def __init__(self, image:ImageFile.ImageFile):...
@overload
def __init__(self, image:np.ndarray):...
def __init__(
self,
image: Optional[Union[
str,
Self,
light_cv_camera,
tool_file,
MatLike,
np.ndarray,
ImageFile.ImageFile,
Image.Image
]],
flags: Optional[Any] = None
) -> None:
super().__init__()
self.__camera: light_cv_camera = None
self.current: MatLike = None
self.__gray: MatLike = None
if isinstance(image, light_cv_camera):
self.lock_from_camera(image)
else:
self.load_image(image, flags)
def internal_check_when_image_is_none_throw_error(self):
if self.image is None:
raise ValueError("Image is None")
return self
@override
def SymbolName(self):
return "Image"
@override
def ToString(self):
current = self.image
if current is None:
return "null"
return f"Image<{current.shape[1]}x{current.shape[0]}:\n"+str(
self.image)+"\n>"
@property
def camera(self) -> light_cv_camera:
if self.__camera is None or self.__camera.is_open() is False:
return None
else:
return self.__camera
@property
def image(self) -> MatLike:
if self.current is not None:
return self.current
elif self.camera is None:
return self.__image
else:
return self.__camera.current_frame()
@image.setter
def image(self, image: Optional[Union[
str,
Self,
tool_file,
MatLike,
np.ndarray,
ImageFile.ImageFile,
Image.Image
]]):
self.load_image(image)
def load_from_nparray(
self,
array_: np.ndarray,
code: Optional[int] = None,
*args, **kwargs
):
self.__gray = None
if code is None:
self.__image = array_
else:
self.__image = base.cvtColor(array_, code, *args, **kwargs).astype(np.uint8)
return self
def load_from_PIL_image(
self,
image: Image.Image,
code: Optional[int] = None,
*args, **kwargs
):
if code is None:
self.load_from_nparray(np.array(image), *args, **kwargs)
else:
self.load_from_nparray(base.cvtColor(np.array(image), code, *args, **kwargs).astype(np.uint8))
return self
def load_from_PIL_ImageFile(
self,
image: ImageFile.ImageFile,
rect: Optional[Tuple[float, float, float, float]] = None
):
return self.load_from_PIL_image(image.crop(rect))
def load_from_cv2_image(self, image: MatLike):
self.__gray = None
self.__image = image
return self
def lock_from_camera(self, camera: light_cv_camera):
self.__camera = camera
return self
@property
def height(self) -> int:
return self.shape[0]
@property
def width(self) -> int:
return self.shape[1]
@property
def channel_depth(self) -> int:
return self.shape[2]
@property
def pixel_count(self) -> int:
return self.image.size
@property
def dtype(self):
return self.image.dtype
def is_enable(self):
return self.image is not None
def is_invalid(self):
return self.is_enable() is False
def __bool__(self):
return self.is_enable()
def __MatLike__(self):
return self.image
@overload
def load_image(self, image:str, flags:int = -1):...
@overload
def load_image(self, image:tool_file, flags:int = -1):...
@overload
def load_image(self, image:light_cv_camera):...
@overload
def load_image(self, image:MatLike):...
@overload
def load_image(self, image:Self):...
@overload
def load_image(self, image:Image.Image):...
@overload
def load_image(self, image:ImageFile.ImageFile):...
def load_image(
self,
image: Optional[Union[
str,
tool_file,
Self,
MatLike,
np.ndarray,
ImageFile.ImageFile,
Image.Image
]],
flags: int = -1
):
"""加载图片"""
if image is None:
self.__image = None
return self
elif isinstance(image, ImageObject):
self.__image = image.image
elif isinstance(image, MatLike):
self.__image = image
elif isinstance(image, np.ndarray):
self.load_from_nparray(image, flags)
elif isinstance(image, ImageFile.ImageFile):
self.load_from_PIL_ImageFile(image, flags)
elif isinstance(image, Image.Image):
self.load_from_PIL_image(image, flags)
elif isinstance(image, loss_file):
self.__image = None
else:
self.__image = base.imread(str(image), flags)
return self
def save_image(
self,
save_path: Union[str, tool_file],
is_path_must_exist: bool = False
) -> Self:
"""保存图片"""
if isinstance(save_path, loss_file):
return self
if is_path_must_exist:
Wrapper2File(save_path).try_create_parent_path()
if self.is_enable():
base.imwrite(str(save_path), self.image)
return self
def show_image(
self,
window_name: str = "Image",
delay: Union[int,str] = 0,
image_show_func: Callable[[Self], None] = None,
*args, **kwargs
):
"""显示图片"""
if self.is_invalid():
return self
if self.camera is not None:
while (wait_key(1) & 0xFF != ord(str(delay)[0])) and self.camera is not None:
# dont delete this line, self.image is camera flame now, see<self.current = None>
self.current = self.image
if image_show_func is not None:
image_show_func(self)
if self.current is not None:
base.imshow(window_name, self.current)
# dont delete this line, see property<image>
self.current = None
else:
base.imshow(window_name, self.image)
base.waitKey(delay = int(delay), *args, **kwargs)
if base.getWindowProperty(window_name, base.WND_PROP_VISIBLE) > 0:
base.destroyWindow(window_name)
return self
# 绝对值转换
def convert_scale_abs(self):
"""绝对值转换"""
return ImageObject(base.convertScaleAbs(self.image))
# 图像边缘检测
def edge_detect_with_sobel(
self,
*,
ksize: Optional[int] = None,
scale: Optional[float] = None,
delta: Optional[float] = None,
borderType: Optional[int] = None,
**kwargs
):
if ksize is not None:
kwargs["ksize"] = ksize
if scale is not None:
kwargs["scale"] = scale
if delta is not None:
kwargs["delta"] = delta
if borderType is not None:
kwargs["borderType"] = borderType
gray = self.get_grayscale()
dx = base.Sobel(gray, base.CV_16S, 1, 0, **kwargs)
dy = base.Sobel(gray, base.CV_16S, 0, 1, **kwargs)
return ImageObject(dx).convert_scale_abs().merge_with_blending(
ImageObject(dy).convert_scale_abs(), (0.5, 0.5))
def edge_detect_with_roberts(self):
kernelx = np.array([[-1,0],[0,1]], dtype=int)
kernely = np.array([[0,-1],[1,0]], dtype=int)
gray = self.get_grayscale()
dx = base.filter2D(gray, base.CV_16S, kernelx)
dy = base.filter2D(gray, base.CV_16S, kernely)
return ImageObject(dx).convert_scale_abs().merge_with_blending(
ImageObject(dy).convert_scale_abs(), (0.5, 0.5))
def edge_detect_with_laplacian(
self,
kernalSize: int = 3
):
gray = self.get_grayscale()
return ImageObject(base.convertScaleAbs(
base.Laplacian(gray, base.CV_16S, ksize=kernalSize)
))
def edge_detect_with_canny(
self,
threshold1: float,
threshold2: float,
**kwargs
):
return ImageObject(base.Canny(
self.get_grayscale(), threshold1, threshold2, **kwargs
))
# 分离通道
def split(self):
"""分离通道"""
return base.split(self.image)
def split_to_image_object(self):
"""分离通道"""
return [ImageObject(channel) for channel in self.split()]
@property
def channels(self):
return self.split()
@property
def blue_channel(self):
return self.channels[0]
@property
def green_channel(self):
return self.channels[1]
@property
def red_channel(self):
return self.channels[2]
@property
def alpha_channel(self):
return self.channels[3]
def get_blue_image(self):
return ImageObject(self.blue_channel)
def get_green_image(self):
return ImageObject(self.green_channel)
def get_red_image(self):
return ImageObject(self.red_channel)
def get_alpha_image(self):
return ImageObject(self.alpha_channel)
# 混合通道
def merge_channels_from_list(self, channels:List[MatLike]):
"""合并通道"""
self.image = base.merge(channels)
return self
def merge_channels(self, blue:MatLike, green:MatLike, red:MatLike):
"""合并通道"""
return self.merge_channels_from_list([blue, green, red])
def merge_channel_list(self, bgr:List[MatLike]):
"""合并通道"""
return self.merge_channels_from_list(bgr)
# Transform
def get_resize_image(self, width:int, height:int):
if self.is_enable():
return ImageObject(base.resize(self.image, (width, height)))
return None
def get_rotate_image(self, angle:float):
if self.is_invalid():
return None
(h, w) = self.image.shape[:2]
center = (w // 2, h // 2)
M = base.getRotationMatrix2D(center, angle, 1.0)
return ImageObject(base.warpAffine(self.image, M, (w, h)))
def resize_image(self, width:int, height:int):
"""调整图片大小"""
new_image = self.get_resize_image(width, height)
if new_image is not None:
self.image = new_image.image
return self
def rotate_image(self, angle:float):
"""旋转图片"""
new_image = self.get_rotate_image(angle)
if new_image is not None:
self.image = new_image.image
return self
# 图片翻折
def flip(self, flip_code:int):
"""翻转图片"""
if self.is_enable():
self.image = base.flip(self.image, flip_code)
return self
def horizon_flip(self):
"""水平翻转图片"""
return self.flip(1)
def vertical_flip(self):
"""垂直翻转图片"""
return self.flip(0)
def both_flip(self):
"""双向翻转图片"""
return self.flip(-1)
# 合并序列
def horizontal_stack(self, *images:Self):
"""水平合并图片"""
return ImageObject(base.hconcat(self.image, *[image.image for image in images]))
def vertical_stack(self, *images:Self):
"""垂直合并图片"""
return ImageObject(base.vconcat(self.image, *[image.image for image in images]))
# 色彩空间猜测
def guess_color_space(self) -> str:
"""猜测色彩空间"""
if self.is_invalid():
return None
image = self.image
# 计算每个通道的像素值分布
hist_b = base.calcHist([image], [0], None, [256], [0, 256])
hist_g = base.calcHist([image], [1], None, [256], [0, 256])
hist_r = base.calcHist([image], [2], None, [256], [0, 256])
# 计算每个通道的像素值总和
sum_b = np.sum(hist_b)
sum_g = np.sum(hist_g)
sum_r = np.sum(hist_r)
# 根据像素值总和判断色彩空间
if sum_b > sum_g and sum_b > sum_r:
#print("The image might be in BGR color space.")
return "BGR"
elif sum_g > sum_b and sum_g > sum_r:
#print("The image might be in GRAY color space.")
return "GRAY"
else:
#print("The image might be in RGB color space.")
return "RGB"
# 颜色转化
def get_convert(self, color_convert:int):
"""颜色转化"""
if self.is_invalid():
return None
return ImageObject(base.cvtColor(self.image, color_convert))
def convert_to(self, color_convert:int):
"""颜色转化"""
if self.is_invalid():
return None
self.image = self.get_convert(color_convert)
return self
def is_grayscale(self):
return self.dimension == 2
def get_grayscale(self, curColor=base.COLOR_BGR2GRAY) -> MatLike:
if self.is_invalid():
return None
if self.__gray is None and self.camera is None:
self.__gray = base.cvtColor(self.image, curColor)
return self.__gray
def convert_to_grayscale(self):
"""将图片转换为灰度图"""
self.__image = self.get_grayscale()
return self
def get_convert_flag(
self,
targetColorTypeName:Literal[
"BGR", "RGB", "GRAY", "YCrCb"
]
) -> Optional[int]:
"""获取颜色转化标志"""
flag = self.guess_color_space()
if flag is None:
return None
if targetColorTypeName == "BGR":
if flag == "RGB":
return base.COLOR_RGB2BGR
elif flag == "GRAY":
return base.COLOR_GRAY2BGR
elif flag == "YCrCb":
return base.COLOR_YCrCb2BGR
elif targetColorTypeName == "RGB":
if flag == "BGR":
return base.COLOR_BGR2RGB
elif flag == "GRAY":
return base.COLOR_GRAY2RGB
elif flag == "YCrCb":
return base.COLOR_YCrCb2RGB
elif targetColorTypeName == "GRAY":
if flag == "RGB":
return base.COLOR_RGB2GRAY
elif flag == "RGB":
return base.COLOR_BGR2GRAY
return None
# 原址裁切
def sub_image(self, x:int, y:int ,width:int ,height:int):
"""裁剪图片"""
if self.is_invalid():
return self
self.image = self.image[y:y+height, x:x+width]
return self
# 直方图
def equalizeHist(self, is_cover = False) -> MatLike:
"""直方图均衡化"""
if self.is_invalid():
return self
result:MatLike = base.equalizeHist(self.image)
if is_cover:
self.image = result
return result
def calcHist(
self,
channel: Union[List[int], int],
mask: Optional[MatLike] = None,
hist_size: Sequence[int] = [256],
ranges: Sequence[float] = [0, 256]
) -> MatLike:
"""计算直方图"""
if self.is_invalid():
return None
return base.calcHist(
[self.image],
channel if isinstance(channel, list) else [channel],
mask,
hist_size,
ranges)
# 子集操作
def sub_image_with_rect(self, rect:Tuple[float, float, float, float]):
"""裁剪图片"""
if self.is_invalid():
return self
self.image = self.image[rect[1]:rect[1]+rect[3], rect[0]:rect[0]+rect[2]]
return self
def sub_image_with_box(self, box:Tuple[float, float, float, float]):
"""裁剪图片"""
if self.is_invalid():
return self
self.image = self.image[box[1]:box[3], box[0]:box[2]]
return self
def sub_cover_with_rect(self, image:Union[Self, MatLike], rect:Tuple[float, float, float, float]):
"""覆盖图片"""
if self.is_invalid():
raise ValueError("Real Image is none")
if isinstance(image, MatLike):
image = ImageObject(image)
self.image[rect[1]:rect[1]+rect[3], rect[0]:rect[0]+rect[2]] = image.image
return self
def sub_cover_with_box(self, image:Union[Self, MatLike], box:Tuple[float, float, float, float]):
"""覆盖图片"""
if self.is_invalid():
raise ValueError("Real Image is none")
if isinstance(image, MatLike):
image = ImageObject(image)
self.image[box[1]:box[3], box[0]:box[2]] = image.image
return self
def operator_cv(self, func:Callable[[MatLike], Any], *args, **kwargs):
func(self.image, *args, **kwargs)
return self
# 序列合并
@override
def _inject_stack_uniform_item(self):
return np.uint8(self.image)
# 从另一图像合并
def merge_with_blending(self, other:Self, weights:Tuple[float, float]):
return ImageObject(base.addWeighted(self.image, weights[0], other.image, weights[1], 0))
# 从另一图像合并(遮罩)
def merge_with_mask(self, other:Self, mask:Self):
return ImageObject(base.bitwise_and(self.image, other.image, mask.image))
# 滤波
def filter(self, ddepth:int, kernel:MatLike, *args, **kwargs):
return base.filter2D(self.image, ddepth, kernel, *args, **kwargs)
def filter_blur(self, kernalSize:Tuple[float, float]):
return base.blur(self.image, kernalSize)
def filter_gaussian(self, kernalSize:Tuple[float, float], sigmaX:float, sigmaY:float):
return base.GaussianBlur(self.image, kernalSize, sigmaX, sigmaY)
def filter_median(self, kernalSize:int):
return base.medianBlur(self.image, kernalSize)
def filter_bilateral(self, d:float, sigmaColor:float, sigmaSpace:float):
return base.bilateralFilter(self.image, d, sigmaColor, sigmaSpace)
def filter_sobel(self, dx:int, dy:int, kernalSize:int):
return base.Sobel(self.image, -1, dx, dy, ksize=kernalSize)
def filter_canny(self, threshold1:float, threshold2:float):
return base.Canny(self.image, threshold1, threshold2)
def filter_laplacian(self, kernalSize:int):
return base.Laplacian(self.image, -1, ksize=kernalSize)
def filter_scharr(self, dx:int, dy:int):
return base.Scharr(self.image, -1, dx, dy)
def filter_box_blur(self, kernalSize:Tuple[float, float]):
return base.boxFilter(self.image, -1, ksize=kernalSize, normalize=0)
# 阈值
def threshold(
self,
threshold:float,
type:int
):
return base.threshold(self.image, threshold, 255, type)
def adaptiveThreshold(
self,
adaptiveMethod: int = base.ADAPTIVE_THRESH_MEAN_C,
thresholdType: int = base.THRESH_BINARY,
blockSize: int = 11,
C: float = 2,
):
return base.adaptiveThreshold(self.image, 255, adaptiveMethod, thresholdType, blockSize, C)
# 获取二值化
def Separate2EnableScene(self,*, is_front=True, is_back=False):
'''
return mask -> front, back
'''
if is_back == is_front:
is_back = not is_front
gray = self.get_grayscale()
if is_front:
return base.threshold(gray, 255.0/2.0, 255, base.THRESH_BINARY)
else:
return base.threshold(gray, 255.0/2.0, 255, base.THRESH_BINARY_INV)
def Separate2EnableScenes_with_Otsu(self,*, is_front=True, is_back=False):
'''
return mask -> front, back
'''
if is_back == is_front:
is_back = not is_front
gray = self.get_grayscale()
if is_front:
return base.threshold(gray, 0, 255, base.THRESH_BINARY | base.THRESH_OTSU)
else:
return base.threshold(gray, 0, 255, base.THRESH_BINARY_INV | base.THRESH_OTSU)
# 获取二值化遮罩
def SeparateFrontBackScenes(self):
'''
return mask -> front, back
'''
gray = self.get_grayscale()
_, front = base.threshold(gray, 255.0/2.0, 255, base.THRESH_BINARY)
_, back = base.threshold(gray, 255.0/2.0, 255, base.THRESH_BINARY_INV)
return np.where(gray>=front), np.where(gray>=back)
def SeparateFrontBackScenes_with_Otsu(self):
'''
return mask -> front, back
'''
gray = self.get_grayscale()
_, front = base.threshold(gray, 0, 255, base.THRESH_BINARY | base.THRESH_OTSU)
_, back = base.threshold(gray, 0, 255, base.THRESH_BINARY_INV | base.THRESH_OTSU)
return np.where(gray>=front), np.where(gray>=back)
# 获取核
def get_kernel(self, shape:int, kernalSize:Tuple[float, float]):
return base.getStructuringElement(shape, kernalSize)
def get_rect_kernal(self, kernalSize:Tuple[float, float]):
return self.get_kernel(base.MORPH_RECT, kernalSize)
def get_cross_kernal(self, kernalSize:Tuple[float, float]):
return self.get_kernel(base.MORPH_CROSS, kernalSize)
def get_ellipse_kernal(self, kernalSize:Tuple[float, float]):
return self.get_kernel(base.MORPH_ELLIPSE, kernalSize)
# 膨胀
def dilate(self, kernel:Optional[MatLike]=None, *args, **kwargs):
if kernel is None:
kernel = self.get_rect_kernal((3, 3))
return base.dilate(self.image, kernel, *args, **kwargs)
# 腐蚀
def erode(self, kernel:Optional[MatLike]=None, *args, **kwargs):
if kernel is None:
kernel = self.get_rect_kernal((3, 3))
return base.erode(self.image, kernel, *args, **kwargs)
# 开运算
def open_operator(self, kernel:Optional[MatLike]=None, *args, **kwargs):
if kernel is None:
kernel = self.get_rect_kernal((3, 3))
return base.morphologyEx(self.image, base.MORPH_OPEN, kernel, *args, **kwargs)
# 闭运算
def close_operator(self, kernel:Optional[MatLike]=None, *args, **kwargs):
if kernel is None:
kernel = self.get_rect_kernal((3, 3))
return base.morphologyEx(self.image, base.MORPH_CLOSE, kernel, *args, **kwargs)
# 梯度运算
def gradient_operator(self, kernel:Optional[MatLike]=None, *args, **kwargs):
if kernel is None:
kernel = self.get_rect_kernal((3, 3))
return base.morphologyEx(self.image, base.MORPH_GRADIENT, kernel, *args, **kwargs)
# 顶帽运算
def tophat_operator(self, kernel:Optional[MatLike]=None, *args, **kwargs):
if kernel is None:
kernel = self.get_rect_kernal((3, 3))
return base.morphologyEx(self.image, base.MORPH_TOPHAT, kernel, *args, **kwargs)
# 黑帽运算
def blackhat_operator(self, kernel:Optional[MatLike]=None, *args, **kwargs):
if kernel is None:
kernel = self.get_rect_kernal((3, 3))
return base.morphologyEx(self.image, base.MORPH_BLACKHAT, kernel, *args, **kwargs)
# 绘制轮廓
def drawContours(
self,
contours: Sequence[MatLike],
contourIdx: int = -1,
color: Union[MatLike, Tuple[int]] = (0, 0, 0),
thickness: int = 1,
lineType: int = base.LINE_8,
hierarchy: Optional[MatLike] = None,
maxLevel: int = base.FILLED,
offset: Optional[Point] = None,
is_draw_on_self:bool = False
) -> MatLike:
image = self.image if is_draw_on_self else self.image.copy()
return base.drawContours(image, contours, contourIdx, color, thickness, lineType, hierarchy, maxLevel, offset)
# 修改自身的绘制
def draw_rect(
self,
rect: Rect,
color: Union[MatLike, Tuple[int]] = (0, 0, 0),
thickness: int = 1,
lineType: int = base.LINE_8,
) -> MatLike:
base.rectangle(self.image, rect, color, thickness, lineType)
return self
# 获取轮廓
def get_contours(
self,
*,
mode: int = base.RETR_LIST,
method: int = base.CHAIN_APPROX_SIMPLE,
is_front: bool = True,
contours: Optional[Sequence[MatLike]] = None,
hierarchy: Optional[MatLike] = None,
offset: Optional[Point] = None
) -> Tuple[Sequence[MatLike], MatLike]:
_, bin = self.Separate2EnableScene(is_front=is_front)
if offset is not None:
return base.findContours(bin, mode, method, contours, hierarchy, offset)
else:
return base.findContours(bin, mode, method, contours, hierarchy)
def get_contours_mask(
self,
width: int,
*,
mode: int = base.RETR_LIST,
method: int = base.CHAIN_APPROX_SIMPLE,
is_front: bool = True,
contours: Optional[Sequence[MatLike]] = None,
hierarchy: Optional[MatLike] = None,
offset: Optional[Point] = None,
) -> Tuple[Sequence[MatLike], MatLike]:
find_contours, _ = self.get_contours(
mode=mode,
method=method,
is_front=is_front,
contours=contours,
hierarchy=hierarchy,
offset=offset
)
return base.drawContours(get_zero_mask(self.shape, dtype=np.uint8), find_contours, -1, (255, 255, 255), width)
def get_contours_fill_inside_mask(
self,
*,
mode: int = base.RETR_LIST,
method: int = base.CHAIN_APPROX_SIMPLE,
is_front: bool = True,
contours: Optional[Sequence[MatLike]] = None,
hierarchy: Optional[MatLike] = None,
offset: Optional[Point] = None
) -> Tuple[Sequence[MatLike], MatLike]:
return self.get_contours_mask(
mode=mode,
method=method,
is_front=is_front,
contours=contours,
hierarchy=hierarchy,
offset=offset,
width=-1
)
# 获取轮廓方框
def get_xy_rect_from_contours(
self,
*,
mode: int = base.RETR_LIST,
method: int = base.CHAIN_APPROX_SIMPLE,
is_front: bool = True,
contours: Optional[Sequence[MatLike]] = None,
hierarchy: Optional[MatLike] = None,
offset: Optional[Point] = None
) -> Sequence[Rect]:
return [base.boundingRect(contour) for contour in self.get_contours(
mode=mode,
method=method,
is_front=is_front,
contours=contours,
hierarchy=hierarchy,
offset=offset
)]
def get_minarea_rect_from_contours(
self,
*,
mode: int = base.RETR_LIST,
method: int = base.CHAIN_APPROX_SIMPLE,
is_front: bool = True,
contours: Optional[Sequence[MatLike]] = None,
hierarchy: Optional[MatLike] = None,
offset: Optional[Point] = None
) -> Sequence[RotatedRect]:
return [base.minAreaRect(contour) for contour in self.get_contours(
mode=mode,
method=method,
is_front=is_front,
contours=contours,
hierarchy=hierarchy,
offset=offset)]
# 图像匹配
def match_on_scene(
self,
scene_image: Self,
# Feature2D config
featrue_type: Optional[Union[
base.Feature2D,
ClosuresCallable[base.Feature2D],
Feature2DInstance
]] = SIFT_Feature2D,
optout_feature_kp_and_des_ref:
Optional[left_value_reference[
Tuple[Sequence[base.KeyPoint], MatLike]
]] = None,
# Match Config
match_min_points: int = 4,
# Draw rect Config
rect_color: Tuple[int, int, int] = (0, 255, 0),
rect_thickness: int = 2,
# Draw match Config
out_drawMatches_ref:Optional[left_value_reference[MatLike]
] = None,
drawMatches_range: Optional[Tuple[int, int]] = None
) -> MatLike:
'''
本图像作为目标特征
Args
---
Target Image
scene_image:
识别的场景, 此图像将作为目标匹配的场景
Feature2D Config
type:
特征检测器类型/生成器/实例, 默认为SIFT
ref:
左值引用容器, ref_value为空时将用于存储特征点与描述符,
不为空则提取ref_value作为本次的特征点与描述符
Match Config
min_points:
匹配的最小点数, 小于此值则无法找到目标物体
Draw rect Config
color:
矩形框颜色, 默认为绿色
thickness:
矩形框厚度, 默认为2
Draw match Config
ref:
左值引用容器, 将用于存储合成的对比图像
range:
匹配点的绘制范围(靠前的匹配度高), 默认为全部绘制
Return
---
MatLike: 绘制了方框的矩形
'''
# 读取目标图和场景图
target_img = self.get_grayscale()
scene_img = scene_image.get_grayscale()
# 初始化SIFT检测器
feature2D:Feature2DInstance = featrue_type if (
isinstance(featrue_type, Feature2DInstance)
) else Feature2DInstance(featrue_type)
# 检测关键点和描述符
kp1:Sequence[base.KeyPoint] = None
des1:MatLike = None
if optout_feature_kp_and_des_ref is None:
kp1, des1 = feature2D.detectAndCompute(target_img, None)
else:
if optout_feature_kp_and_des_ref.ref_value is None:
kp1, des1 = feature2D.detectAndCompute(target_img, None)
optout_feature_kp_and_des_ref.ref_value = (kp1, des1)
else:
kp1, des1 = optout_feature_kp_and_des_ref.ref_value
kp2, des2 = feature2D.detectAndCompute(scene_img, None)
# 初始化BFMatcher
bf = base.BFMatcher(base.NORM_L2, crossCheck=True)
# 匹配描述符
matches = bf.match(des1, des2)
matches = sorted(matches, key=lambda x: x.distance)
# 如果匹配点数少于min_match_points个无法找到目标物体
if len(matches) < match_min_points:
return None
# 提取匹配点的位置
src_pts = np.float32([kp1[m.queryIdx].pt for m in matches]).reshape(-1, 1, 2)
dst_pts = np.float32([kp2[m.trainIdx].pt for m in matches]).reshape(-1, 1, 2)
# 使用RANSAC算法找到单应性矩阵
M, _ = base.findHomography(src_pts, dst_pts, base.RANSAC, 5.0)
# 获取目标物体的边界框
h, w = target_img.shape
pts = np.float32([[0, 0], [0, h - 1], [w - 1, h - 1], [w - 1, 0]]).reshape(-1, 1, 2)
dst = base.perspectiveTransform(pts, M)
# 在scene中绘制边界框
result = scene_image.image.copy()
base.polylines(
result,
[np.int32(dst)],
True,
rect_color,
rect_thickness,
base.LINE_AA
)
# 合成的绘制结果
if out_drawMatches_ref is not None:
if drawMatches_range is None:
out_drawMatches_ref.ref_value = base.drawMatches(
self.image,
kp1,
scene_image.image,
kp2,
matches,
None, flags=base.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS
)
else:
out_drawMatches_ref.ref_value = base.drawMatches(
self.image,
kp1,
scene_image.image,
kp2,
matches[drawMatches_range[0]:drawMatches_range[1]],
None, flags=base.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS
)
# 返回绘制了方框的结果
return result
# 图像卷积
def conv(self, kernel:MatLike, *args, **kwargs):
return base.filter2D(self.image, -1, kernel, *args, **kwargs)
def conv_with_shape(self, shape:Tuple[int, int], *args, **kwargs):
return base.filter2D(self.image, -1, shape, *args, **kwargs)
# np加速
def get_new_noise(
raw_image: Optional[MatLike],
height: int,
weight: int,
*,
mean: float = 0,
sigma: float = 25,
dtype = np.uint8
) -> MatLike:
noise = raw_image
if noise is None:
noise = np.zeros((height, weight), dtype=dtype)
base.randn(noise, mean, sigma)
return base.cvtColor(noise, base.COLOR_GRAY2BGR)
class NoiseImageObject(ImageObject):
def __init__(
self,
height: int,
weight: int,
*,
mean: float = 0,
sigma: float = 25,
dtype = np.uint8
):
super().__init__(get_new_noise(
None, height, weight, mean=mean, sigma=sigma, dtype=dtype
))
@override
def SymbolName(self):
return "Noise"
def Unwrapper(image:Optional[Union[
str,
ImageObject,
tool_file,
MatLike,
np.ndarray,
ImageFile.ImageFile,
Image.Image
]]) -> MatLike:
return image.image if isinstance(image, ImageObject) else ImageObject(image).image
def Wrapper(image:Optional[Union[
str,
ImageObject,
tool_file,
MatLike,
np.ndarray,
ImageFile.ImageFile,
Image.Image
]]) -> ImageObject:
return ImageObject(image)
class light_cv_window:
def __init__(self, name:str):
self.__my_window_name = name
base.namedWindow(self.__my_window_name)
def __del__(self):
self.destroy()
def show_image(self, image:Union[ImageObject, MatLike]):
if self.__my_window_name is None:
self.__my_window_name = "window"
if isinstance(image, ImageObject):
image = image.image
base.imshow(self.__my_window_name, image)
return self
def destroy(self):
if self.__my_window_name is not None and base.getWindowProperty(self.__my_window_name, base.WND_PROP_VISIBLE) > 0:
base.destroyWindow(self.__my_window_name)
return self
@property
def window_rect(self):
return base.getWindowImageRect(self.__my_window_name)
@window_rect.setter
def window_rect(self, rect:Tuple[float, float, float, float]):
self.set_window_rect(rect[0], rect[1], rect[2], rect[3])
def set_window_size(self, weight:int, height:int):
base.resizeWindow(self.__my_window_name, weight, height)
return self
def get_window_size(self) -> Tuple[float, float]:
rect = self.window_rect
return rect[2], rect[3]
def get_window_property(self, prop_id:int):
return base.getWindowProperty(self.__my_window_name, prop_id)
def set_window_property(self, prop_id:int, prop_value:int):
base.setWindowProperty(self.__my_window_name, prop_id, prop_value)
return self
def get_prop_frame_width(self):
return self.window_rect[2]
def get_prop_frame_height(self):
return self.window_rect[3]
def is_full_window(self):
return base.getWindowProperty(self.__my_window_name, base.WINDOW_FULLSCREEN) > 0
def set_full_window(self):
base.setWindowProperty(self.__my_window_name, base.WINDOW_FULLSCREEN, 1)
return self
def set_normal_window(self):
base.setWindowProperty(self.__my_window_name, base.WINDOW_FULLSCREEN, 0)
return self
def is_using_openGL(self):
return base.getWindowProperty(self.__my_window_name, base.WINDOW_OPENGL) > 0
def set_using_openGL(self):
base.setWindowProperty(self.__my_window_name, base.WINDOW_OPENGL, 1)
return self
def set_not_using_openGL(self):
base.setWindowProperty(self.__my_window_name, base.WINDOW_OPENGL, 0)
return self
def is_autosize(self):
return base.getWindowProperty(self.__my_window_name, base.WINDOW_AUTOSIZE) > 0
def set_autosize(self):
base.setWindowProperty(self.__my_window_name, base.WINDOW_AUTOSIZE, 1)
return self
def set_not_autosize(self):
base.setWindowProperty(self.__my_window_name, base.WINDOW_AUTOSIZE, 0)
return self
def set_window_rect(self, x:int, y:int, weight:int, height:int):
base.moveWindow(self.__my_window_name, x, y)
return self.set_window_size(weight, height)
def set_window_pos(self, x:int, y:int):
base.moveWindow(self.__my_window_name, x, y)
return self
def wait_key(self, wait_time:int=0):
return base.waitKey(wait_time)
def get_haarcascade_frontalface(name_or_default:Optional[str]=None):
if name_or_default is None:
name_or_default = "haarcascade_frontalface_default"
return base.CascadeClassifier(BaseData.haarcascades+'haarcascade_frontalface_default.xml')
def detect_human_face(
image: ImageObject,
detecter: base.CascadeClassifier,
scaleFactor: float = 1.1,
minNeighbors: int = 4,
*args, **kwargs):
'''return is Rect[]'''
return detecter.detectMultiScale(image.image, scaleFactor, minNeighbors, *args, **kwargs)
class internal_detect_faces_oop(Callable[[ImageObject], None]):
def __init__(self):
self.face_cascade = get_haarcascade_frontalface()
def __call__(self, image:ImageObject):
gray = image.convert_to_grayscale()
faces = self.face_cascade.detectMultiScale(gray, 1.3, 5)
for (x,y,w,h) in faces:
image.operator_cv(base.rectangle,(x,y),(x+w,y+h),(255,0,0),2)
def easy_detect_faces(camera:light_cv_camera):
ImageObject(camera).show_image("window", 'q', internal_detect_faces_oop())
# 示例使用
if __name__ == "__main__":
img_obj = ImageObject("path/to/your/image.jpg")
img_obj.show_image()
img_obj.resize_image(800, 600)
img_obj.rotate_image(45)
img_obj.convert_to_grayscale()
img_obj.save_image("path/to/save/image.jpg")
# Override tool_file to tool_file_ex
class tool_file_cvex(tool_file):
def __init__(self, file_path:str, *args, **kwargs):
super().__init__(file_path, *args, **kwargs)
@override
def load_as_image(self) -> ImageObject:
self.data = ImageObject(self)
return self.data
@override
def save_as_image(self, path = None):
image:ImageObject = self.data
image.save_image(path if path is not None else self.get_path())
return self
def WrapperFile2CVEX(file:Union[tool_file_or_str, tool_file_cvex]):
if isinstance(file, tool_file_cvex):
return file
elif isinstance(file, str):
return tool_file_cvex(file)
elif isinstance(file, tool_file):
result = tool_file_cvex(str(file))
result.data = file.data
return result
else:
raise TypeError("file must be tool_file or str")