from typing import Optional
from enum import IntEnum
try:
import numpy
except ModuleNotFoundError:
pass
[docs]
class ImageColorMode(IntEnum):
GRAY = 0
BGR = 1
BGRA = 2
RGB = 3
RGBA = 4
[docs]
class ImageCompressMode(IntEnum):
RAW = 0
JPEG = 1
WEBP = 2
PNG = 3
[docs]
class ImageFrame:
"""画像データ (ver2.4〜)
* 8bitのグレースケール, BGR, BGRAフォーマットのみを扱う
* 画像受信時にはjpegやpngなどにエンコードされたデータが入ることもある
"""
_width: int
_height: int
_data: bytes
_color_mode: int
_cmp_mode: int
def __init__(
self, width: int, height: int, data: bytes, color_mode: int, compress_mode: int
) -> None:
self._width = width
self._height = height
self._data = data
self._color_mode = color_mode
self._cmp_mode = compress_mode
assert width * height * self.channels == len(data)
[docs]
def empty(self) -> bool:
"""画像が空かどうかを返す"""
return len(self._data) == 0
@property
def width(self) -> int:
"""画像の幅"""
return self._width
@property
def height(self) -> int:
"""画像の高さ"""
return self._height
@property
def channels(self) -> int:
"""1ピクセルあたりのデータサイズ(byte数)"""
if self._color_mode == ImageColorMode.GRAY:
return 1
if self._color_mode == ImageColorMode.BGR:
return 3
if self._color_mode == ImageColorMode.RGB:
return 3
if self._color_mode == ImageColorMode.BGRA:
return 3
if self._color_mode == ImageColorMode.RGBA:
return 3
raise ValueError("Unknown color format")
@property
def color_mode(self) -> int:
"""色の並び順
compress_modeがRAWでない場合意味をなさない。
ImageColorMode のenumを参照
"""
return self._color_mode
@property
def compress_mode(self) -> int:
"""画像の圧縮モード
ImageCompressMode のenumを参照
"""
return self._cmp_mode
@property
def data(self) -> bytes:
"""画像データ
compress_modeがRAWの場合、height * width * channels
要素の画像データ。 それ以外の場合、圧縮された画像のデータ
"""
return self._data
[docs]
@staticmethod
def from_numpy(img: "numpy.ndarray", color_mode: int) -> "ImageFrame":
"""numpy配列からImageFrameを作成する
color_mode がGRAYの場合 (height, width) または (height, width, 1),
color_mode がBGR,RGBの場合 (height, width, 3),
color_mode がBGRA,RGBAの場合 (height, width, 4)
のuint8配列のみが使用可能
"""
import numpy
assert isinstance(img, numpy.ndarray), "img must be numpy.ndarray"
assert img.dtype == numpy.uint8, "only dtype uint8 is supported"
if color_mode == ImageColorMode.GRAY:
assert len(img.shape) == 2 or (
len(img.shape) == 3 and img.shape[2] == 1
), "shape must be (height, width) or (height, width, 1)"
elif color_mode == ImageColorMode.BGR or color_mode == ImageColorMode.RGB:
assert (
len(img.shape) == 3 and img.shape[2] == 3
), "shape must be (height, width, 3)"
elif color_mode == ImageColorMode.BGRA or color_mode == ImageColorMode.RGBA:
assert (
len(img.shape) == 3 and img.shape[2] == 4
), "shape must be (height, width, 4)"
else:
raise ValueError("Unknown color format")
return ImageFrame(
img.shape[1],
img.shape[0],
img.tobytes(),
color_mode,
ImageCompressMode.RAW,
)
[docs]
def numpy(self) -> "numpy.ndarray":
"""numpy配列に変換する
color_mode がGRAYの場合 (height, width, 1),
color_mode がBGR,RGBの場合 (height, width, 3),
color_mode がBGRA,RGBAの場合 (height, width, 4)
のuint8配列を返す
compress_mode がRAWでない場合はエラー
"""
import numpy
assert (
self._cmp_mode == ImageCompressMode.RAW
), "compressed image is not supported"
return numpy.frombuffer(self._data, dtype=numpy.uint8).reshape(
self._height, self._width, self.channels
)
[docs]
class ImageReq:
width: Optional[int]
height: Optional[int]
color_mode: Optional[int]
compress_mode: Optional[int]
quality: Optional[int]
frame_rate: Optional[float]
def __init__(self, width, height, color_mode, compress_mode, quality, frame_rate):
self.width = width
self.height = height
self.color_mode = color_mode
self.compress_mode = compress_mode
self.quality = quality
self.frame_rate = frame_rate
def __eq__(self, obj) -> bool:
return (
isinstance(obj, ImageReq)
and self.width == obj.width
and self.height == obj.height
and self.color_mode == obj.color_mode
and self.compress_mode == obj.compress_mode
and self.quality == obj.quality
and self.frame_rate == obj.frame_rate
)
def __ne__(self, obj) -> bool:
return not self == obj