はじめに
OpenCV (v4.x) imread の引数に指定する mode の解説です。
IMREAD_GRAYSCALE や IMREAD_COLOR、もしくは 0 や 1 でお馴染みだと思います。
rose.png (サンプル画像) |
---|
ImageMagick の magick rose: rose.pngで作成 |
% python
>>> import cv2
>>> img = cv2.imread("rose.png", cv2.IMREAD_COLOR)
>>> print(img.shape, img.dtype)
(46, 70, 3) uint8
この記事で RGB と記述している色構成は NumPy 配列的には逆順の BGR ですが、読みにくいのであえて RGB と表記します。あと RGBA は BGRA の順です。(ABGR じゃ無いです)
% magick -size 16x8 'xc:rgb(12,34,56)' PNG24:RGB.png
% magick -size 16x8 'xc:rgb(12,34,56)' PNG32:RGBA.png
RGB.png | RGBA.png |
---|---|
% python
>>> import cv2
>>> img = cv2.imread("RGB.png", cv2.IMREAD_UNCHANGED)
>>> print(img.shape, img.dtype, img[1,1,])
(8, 16, 3) uint8 [56 34 12]
>>> img = cv2.imread("RGBA.png", cv2.IMREAD_UNCHANGED)
>>> print(img.shape, img.dtype, img[1,1,])
(8, 16, 4) uint8 [ 56 34 12 255]
mode 一覧
以下のページに mode 一覧があります。
enum cv::ImreadModes {
cv::IMREAD_UNCHANGED = -1,
cv::IMREAD_GRAYSCALE = 0,
cv::IMREAD_COLOR = 1,
cv::IMREAD_ANYDEPTH = 2,
cv::IMREAD_ANYCOLOR = 4,
cv::IMREAD_LOAD_GDAL = 8,
cv::IMREAD_REDUCED_GRAYSCALE_2 = 16,
cv::IMREAD_REDUCED_COLOR_2 = 17,
cv::IMREAD_REDUCED_GRAYSCALE_4 = 32,
cv::IMREAD_REDUCED_COLOR_4 = 33,
cv::IMREAD_REDUCED_GRAYSCALE_8 = 64,
cv::IMREAD_REDUCED_COLOR_8 = 65,
cv::IMREAD_IGNORE_ORIENTATION = 128
}
公式仕様よりも実装でどう動くのかを重視して、各 mode を説明してみます。
imread mode | 非公式独自解説 |
---|---|
IMREAD_UNCHANGED | 画像ファイルの色や精度をなるべく維持して取り込みます。色構成 Grayscale,RGB,RGBA、ビット深度 uint8,uint16,float32 を使い分けます。現状、RGBA(透明度つき画像)は、この mode でしか扱えません。なお、他の mode と組み合わせて使えない孤高の mode です |
IMREAD_GRAYSCALE | グレースケール(1チャネル)で取り込みます。 0 指定と同じです。デフォルトで uint8 になります。デコーダでのグレースケール化を期待できるのが良い感じです。 |
IMREAD_COLOR | RGB (3チャネル、配列順は逆の BGR) で取り込みます。 1 指定と同じです。デフォルト uint8 。IMREAD_GRAYSCALE もそうですが入力時に形式が統一され、大量の画像を処理する時に大変便利です |
IMREAD_ANYDEPTH | IMREAD_{GRAYSCALE,COLOR} と bit or して使います。画像ファイルのビット深度に応じて、uint8,uint16,float32 を使い分け、元の精度をなるべく維持して取り込みます |
IMREAD_ANYCOLOR | 画像ファイルがグレースケールの場合は IMREAD_GRAYSCALE として、それ以外は IMREAD_COLOR と同様の処理で取り込みます。RGBA は何故か対応していません。 |
IMREAD_LOAD_GDAL | GDAL ファイルを読み込む為のフラグです。このブログエントリでは特に言及しません |
IMREAD_REDUCED_GRAYSCALE_{2,4,8} IMREAD_REDUCED_COLOR_{2,4,8} |
1/2, 1/4, 1/8 でリサイズ縮小して取り込むフラグです。特に JPEG だと scaling decode で超高速になります。JPEG 以外だと Nearest 縮小するだけであえて使うメリットはなさそうです |
IMREAD_IGNORE_ORIENTATION | IMREAD_GRAYSCALE や IMREAD_COLOR を指定するとデフォルトで Exif Orientation に応じた画像の回転補正処理が適用されるのを、このフラグで抑止できます。ちなみに、IMREAD_UNCHANGED は元々無効(回転補正しない)です |
将来は分かりませんが、今のところ Alpha プレーン(透明度)に対応しているのは、IMREAD_UNCHANGED だけです。
mode の全体
任意の形式をそのまま取り込む IMREAD_UNCHANGED と、固定の形式で受け取るのがデフォルトで固定具合をフラグで制御する IMREAD_GRAYSCALE / IMREAD_COLOR (RGB 形式) で、大きく2つに分かれます。
図の右側は、画像ファイルの色構成を維持しない代わりに、Grayscale 1ch か、RGB 3ch かで色構成を固定し、かつ色深度も 8bit に固定する事で、画像を処理しやすくするメリットがあります。
IMREAD_ANYDEPTH をつける事で uint8 に加えて uint16 や float32 で受け取る事も可能です。(uint16 や float32 の固定で受け取るフラグがあった方が良さそうな気もします)
IMREAD_ANYCOLOR を使うと、Grayscale 1ch と RGB 3ch を使い分けて適切な方で受け取ります。
指定の仕方としては、(a) は単体のみで他と組み合わせ不可。(b)でモードを指定した時に (c) のフラグをオプション的に複数追加できる。といった組み合わせ方になります。
- (a) IMREAD_UNCHANGED は単体で使うモード。
- (b) IMREAD_GRAYSCALE, IMREAD_COLOR が純粋なモード。IMREAD_ANYCOLOR、 IMREAD_REDUCED_〜{2,4,8} も一応、モード。
- (c) IMREAD_ANYDEPTH, IMREAD_LOAD_GDAL, IMREAD_IGNORE_ORIENTATION がフラグ。
個人的には IMREAD_REDUCED_{GRAYSCALE,COLOR}{2,4,8} は IMREAD{GRAYSCALE,COLOR} をベースに、IMREAD_REDUCED_{2,4,8} をフラグとして用意して欲しいところですが、もしかしたら、2,4,8 が排他の関係にあるので、こうしたのかもしれません。
- なるべく画像ファイルの元の形式のまま読み込む。
import cv2
img = cv2.imread("test.png", cv2.IMREAD_UNCHANGED)
- uint8 Grayscale (1ch) で受け取る。
import cv2
img = cv2.imread("test.png", cv2.IMREAD_GRAYSCALE)
- uint8 RGB (3ch, 24bit color) で受け取る。
import cv2
img = cv2.imread("test.png", cv2.IMREAD_COLOR)
- uint8/uint16/float32 のいずれかの RGB で受け取る。
import cv2
img = cv2.imread("test.png", cv2.IMREAD_COLOR | cv2.READ_ANYDEPTH)
- uint8 Grayscale 又は RGB (3ch, 24bit color) で受け取る。
import cv2
img = cv2.imread("test.png", cv2.IMREAD_ANYCOLOR)
- 1/8 サイズの画像を uint8 RGB (24bit color) で受け取る。
import cv2
img = cv2.imread("test.png", cv2.IMREAD_REDUCED_COLOR_8)
色構成 (color component)
UNCHANGED
画像ファイルの色構成に応じて、グレースケール、RGB, RGBA のいずれかで取り込みます。
Gray+Alpha 形式がちょっと特殊で、PNG だと RGBA に、TIFF だと alpha が消えて Gray になります。(もしかして TIFF の alpha プレーンに対応してない?)
(2024/11/9 追記) v4.5.4/v4.5.5 での BMP 32bit の違い
IMREAD_UNCHANGED 指定で 32bit BMP 画像ファイルを imread した時、マイナーバージョンの違いでも動きが異なるそうです。
- 【OpenCV-Python】BGRA(32bit,8bitx4ch)のBitmapファイル読込
OpenCVで32bit(BGRA, 8bit4ch)のBitmapファイル(*.bmp)をimread()関数でファイルを開くと、OpenCVのバージョンに依存して、32bit(4ch) もしくは 24bit(3ch) で読込まれるようです。
結果からすると、Ver.4.5.4以前では 32bit(8bit4ch)、Ver.4.5.5以降では24bit(8bit3ch)で読込まれます。
GRAYSCALE
画像ファイルの色コンポーネントに関わらず、グレースケールで取り込みます。RGB 等の色がついた画像はグレースケール化します。
COLOR
画像ファイルの色コンポーネントに関わらず、RGB で取り込みます。画像ファイルがグレースケールの場合、その値を R=G=B で展開します。
ANYCOLOR
IMREAD_ANYCOLOR は Grayscale / RGB の適切な方で取り込みますが、RGBA には対応していません。
// grab the decoded type
int type = decoder->type();
if( (flags & IMREAD_LOAD_GDAL) != IMREAD_LOAD_GDAL && flags != IMREAD_UNCHANGED )
{
if( (flags & IMREAD_ANYDEPTH) == 0 )
type = CV_MAKETYPE(CV_8U, CV_MAT_CN(type));
if( (flags & IMREAD_COLOR) != 0 ||
((flags & IMREAD_ANYCOLOR) != 0 && CV_MAT_CN(type) > 1) )
type = CV_MAKETYPE(CV_MAT_DEPTH(type), 3);
else
type = CV_MAKETYPE(CV_MAT_DEPTH(type), 1);
}
恐らく、本来やりたかったはずの実装はこうでしょう。
if( (flags & IMREAD_ANYCOLOR) == 0 && CV_MAT_CN(type) > 1)
{
if( (flags & IMREAD_COLOR) != 0)
type = CV_MAKETYPE(CV_MAT_DEPTH(type), 3);
else
type = CV_MAKETYPE(CV_MAT_DEPTH(type), 1);
}
公式でも認識はしていて、でも今更動作を変えられないので、この件は将来に持ち越すようです。
(このツイートの時点では勘違いしていましたが、IMREAD_ANYCOLOR はグレースケールをグレースケールのまま受け入れるので、一応意味はあります)IMREAD_ANYCOLORの存在意義は正直よくわからないですがalalekさんは「既存アプリに影響出るので振る舞いを変更する予定はない」とコメントしてますね。5.0のマイルストーンが付いているので5.0以降がどうなのかはもう少し様子を見ないとわからないです。https://github.com/opencv/opencv/issues/21695#issuecomment-1064986959
— dandelion (@dandelion1124) 2022年8月11日
imcount 等の関数でデフォルト引数に ANYCOUNT を使うなど中途半端に実装が入っている事からして、将来的には RGBA 対応するかもしれません。
CV_EXPORTS_W size_t imcount(const String& filename, int flags = IMREAD_ANYCOLOR);
DEPTH (カラー深度)
UNCHANGED
IMREAD_UNCHANGED は特別で、他の mode と一緒には使えません。
8bit 以下のは 8 bit で、16bit は 16bit で取り込みます。
なお、浮動小数点も対応しています。PFM や Radience HDR 等がそうです。
ちなみに、もっと色んな depth で実験した結果は、だいぶカオスでした。
TIFF の処理が弱い印象です。BMP の 4bit は ARGB4444 を読ませようとして、図の通り駄目でした。
GRAYSCALE/ COLOR/ ANYCOLOR
IMREAD_GRAYSCALE や IMREAD_COLOR, IMREAD_ANYCOLOR を単体で指定すると、色深度は 8bit 固定になります。
OpenCV は画像データを Numpy ndarray の形式で持ちますが、GRAYSCALE だと {height, width} (uint8) の2次元、COLOR 指定では {height, width, 3} (uint8) の 3次元になります。
ANYCOLOR を指定すると画像ファイルがグレースケールだと {height, width} (uint8) 、色付きの場合は {height, width, 3} (uint8) になります。
GRAYSCALE/COLOR/ANYCOLOR | ANYDETH
IMREAD_ANYDETH をつけた場合、depth に関して IMREAD_UNCHANGED と同じ動きになります。
つまり、uint8, uint16, float32 どの ndarray が生成されるかは、元の画像ファイル次第です。
REDUCED (高速縮小)
IMREAD_COLOR の代わりに IMREAD_REDUCED_COLOR_8 を指定すると、1/8 サイズの画像を取得できます。
JPEG は 8x8 サイズの周波数成分を持つので、そのうち 1x1 だけ参照すれば、いきなり 1/8 サイズの画像が取得できる理屈です。
リサイズ特有の画質劣化がなく、かつ超高速です(条件によっては10倍くらい速い)。
理屈はこちらで解説しています。参考までに。ImageMagick の size hinting と同じです。
JPEG の size hinting について |
---|
https://blog.awm.jp/2016/01/08/jpeghint/ |
JPEG 以外の画像ファイル形式に対しては、恐らく(1/8で取得する)動作を合わせる為だけに、INTER_LINEAR_EXACT(固定小数でリニア補完) で縮小リサイズ処理をします。
具体例
IMREAD_UNCHANGED
IMREAD_UNCHANGED は特別で、他の mode と一緒には使えません。
img = cv2.imread(infile, cv2.IMREAD_UNCHANGED)
無理に使うとすると、以下のような罠にはまります。
img = cv2.imread(infile, cv2.IMREAD_UNCHANGED + cv2.IMREAD_COLOR)
これは、IMREAD_UNCHANGED(-1) + IMREAD_COLOR(1) = IMREAD_GRAYSCALE(0) なので、グレースケール画像として読み込みます。 あと、+ は使わない方が良いです。ビットフラグなので | の方が自然です。
img = cv2.imread(infile, cv2.IMREAD_UNCHANGED | cv2.IMREAD_COLOR)
-1 は 2の補数表現で all 1 なので、1 と or をとっても all1 で -1 のままなので、以下と同じになるでしょう。(ただし、負値が2の補数以外のプラットフォームだと話が異なる)
img = cv2.imread("test.png" cv2.IMREAD_UNCHANGED)
何にせよ UNCHANGED を他のフラグと組み合わせるのはやめましょう。
IMREAD_GRAYSCALE
単体で使うと uint8 グレースケール(最大256色)で画像を取り込みます。
カラー画像の場合でも、グレースケールに変換して取り込みます。
img = cv2.imread("test.png", cv2.IMREAD_GRAYSCALE)
uint16 / float32 のまま画像が欲しい場合は、ANYDEPTH をつけます。
img = cv2.imread("test.png", cv2.IMREAD_GRAYSCALE | cv2.IMREAD_ANYCOLOR)
IMREAD_COLOR
uint8 RGB (256^3=16,777,216色)で取り込みます。
img = cv2.imread("test.png", IMREAD_COLOR)
uint16 / float32 のまま画像が欲しい場合は、ANYDEPTH をつけます。
img = cv2.imread("test.png", cv2.IMREAD_COLOR | cv2.IMREAD_ANYCOLOR)
IMREAD_ANYCOLOR
現状は、グレースケールと RGB のどちらかで取り込みます。
% magick rose: -colorspace gray rose-gray.png
% magick rose: -colorspace sRGB PNG24:rose-RGB.png
>>> import cv2
>>> img = cv2.imread("rose-gray.png", cv2.IMREAD_ANYCOLOR)
>>> print(img.shape, img.dtype)
(46, 70) uint8
>>> img = cv2.imread("rose-RGB.png", cv2.IMREAD_ANYCOLOR)
>>> print(img.shape, img.dtype)
(46, 70, 3) uint8
=>
IMREAD_REDUCED_〜
例えば、巨大なファイルで縮小リサイズに時間がかかる時、画像ファイルが JPEG であれば、以下の指定で処理時間を増やさず(むしろ減らして)1/8サイズの画像が取得できます。
- グレースケール
img = cv2.imread("test.png", cv2.IMREAD_REDUCED_GRAYSCALE_8)
- カラー RGB
img = cv2.imread("test.png", cv2.IMREAD_REDUCED_COLOR_8)
検証環境
import cv2
print(cv2.getBuildInformation())
上記命令結果の、一部。
Version control (extra): 4.5.1
Platform:
Timestamp: 2021-01-02T13:02:25Z
Host: Darwin 17.7.0 x86_64
CMake: 3.18.4
CMake generator: Unix Makefiles
CMake build tool: /usr/bin/make
Configuration: Release
(略)
Media I/O:
ZLib: build (ver 1.2.11)
JPEG: build-libjpeg-turbo (ver 2.0.6-62)
WEBP: build (ver encoder: 0x020f)
PNG: build (ver 1.6.37)
TIFF: build (ver 42 - 4.0.10)
JPEG 2000: build (ver 2.3.1)
OpenEXR: build (ver 2.3.0)
HDR: YES
SUNRASTER: YES
PXM: YES
PFM: YES
Video I/O:
DC1394: NO
FFMPEG: YES
avcodec: YES (58.54.100)
avformat: YES (58.29.100)
avutil: YES (56.31.100)
swscale: YES (5.5.100)
avresample: YES (4.0.0)
GStreamer: NO
AVFoundation: YES
検証画像の作り方
ImageMagick で画像ファイルを作って検証しました、尚、TIFF の中身確認は ExifTool が便利です。
色構成
% # PNG
% magick rose: -colorspace Gray rose-gray.png
% magick rose: -colorspace sRGB png24:rose-rgb.png
% magick rose: -colorspace sRGB -alpha on -fuzz 25% -transparent black rose-rgba.png
% # TIFF
% magick rose: -colorspace Gray rose-gray.tiff
% magick rose: -colorspace Gray -alpha on -fuzz 25% -transparent black rose-grayalpha.tiff
% magick rose: -colorspace sRGB rose-rgb.tiff
% magick rose: -colorspace sRGB -alpha on -fuzz 25% -transparent black rose-rgba.tiff
% magick rose: -colorspace CMYK rose-cmyk.tiff
% magick rose: -colorspace CMYK -alpha on -fuzz 25% -transparent black rose-cmyka.tiff
ビット深度
% magick rose: -colorspace gray -depth 1 rose-1.png
% magick rose: -colorspace gray -depth 2 rose-2.png
% magick rose: -colorspace gray -depth 4 rose-4.png
% magick rose: -depth 8 rose-8.png
% magick rose: -define png:bit-depth=16 rose-16.png
% magick rose: -define -depth 16 rose-uint16.tiff
% magick rose: -define -depth 32 rose-uint32.tiff
% magick rose: -define -depth 64 rose-uint64.tiff
% # 符号付き
% magick rose: -define quantum:format=signed -depth 16 rose-sint16.tiff
% magick rose: -define quantum:format=signed -depth 32 rose-sint32.tiff
% magick rose: -define quantum:format=signed -depth 64 rose-sint64.tiff
% # 浮動小数点
% magick rose: -define quantum:format=floating-point -depth 16 rose-float16.tiff
% magick rose: -define quantum:format=floating-point -depth 32 rose-float32.tiff
% magick rose: -define quantum:format=floating-point -depth 64 rose-float64.tiff
% magick rose: rose.pfm # Netpbm
% magick rose: rose.hdr # Radiance HDR
% magick rose: rose.exr # OpenEXR
所感
- IMREAD_ANYCOLOR は恐らく RGBA 対応のために追加したと思われますが、今のところグレーと RGB のみ対応です。名前的には GRAYSCALE や COLOR に対するオプションっぽいですが、これらに並列した第3のカラーモードの可能性も捨てきれません。謎です。
- IMREAD_ANYDEPTH は GRAYSCALE や COLOR で折角実現してる固定した形式で受け取るメリットを手放しています。IMREAD_DEPTH_UINT16, IMREAD_DEPTH_FLOAT32 も作った方が良いのでは?
- IMREAD_REDUCED_COLOR_8 は定義が微妙だと思います。mode のベースは GRAYSCALE と COLOR の2つに限定して、IMREAD_REDUCED_8 をフラグで追加できるようにして、今までのは alias として残して欲しい。
- TIFF の透明度が処理できてないかも。要調査。
- uint8 / uint16 / float32 限定なのか、float16 は uint16 に、uint32 は float32 に変換されるの、対応する型を増やしても良いのでは。
- imcount は引数デフォルトを先走って ANYCOLOR にしているので、引数を省略した時に意図しない挙動になりそうですが、実は この指定した mode を見ずに decoder に問い合わせをしているので、UNCHANGED 以外を指定した時だけ意図しない挙動になる。といった訳のわからない実装になっています。(C++ で試したら何しても 1 が返ってきて更に謎。。)
実装
- enum ImreadModes
- imread function