OpenCV C++とPythonでの画像の取り扱いの比較


このエントリーをはてなブックマークに追加

表題について自分なりにまとめています。経験値が足りないせいで誤ったことを書いているかもしれません。

C++のcv::Mat

例えば縦幅480, 横幅640のカラー画像を例にとって考えてみます。

cv::Mat img = cv::imread("board.jpg", CV_LOAD_IMAGE_COLOR);

cv::Matではdimension(次元)数とchannel(チャンネル)数を区別して考えます。カラー画像の場合、次元数は縦・横の2、チャンネル数はB, G, Rの3になります。

// 次元数(画像なので縦・横の2次元)
std::cout << "dims: " << img.dims << std::endl; // 2
// チャンネル数
std::cout << "channels: " << img.channels() << std::endl; // 3

B, G, Rがそれぞれ何ビットの情報を持っているか(=ビット深度はいくつか)は、以下のようにして調べます。

// (複数チャンネルから成る)1要素のサイズ [バイト単位]
std::cout << "elemSize: " << img.elemSize() << "[byte]" << std::endl; // 3
// 1要素内の1チャンネル分のサイズ [バイト単位]
std::cout << "elemSize1 (elemSize/channels): " << img.elemSize1() << "[byte]" << std::endl; // 1

画素のアクセス方法については前に cv::Mat使用時の画素へのアクセス方法 - minus9d's diary に書いたのでご参照ください。

Pythonのndarray

同じく縦幅480, 横幅640のカラー画像を例にとって考えてみます。

PythonOpenCVでは、画像を表現するのにnumpyのndarrayを借用します。なのでcv::Matと概念が1対1で対応しません。これが混乱のもとになります。

ndarrayでは次元とチャンネルを区別しません。その結果どういうことになるかというと、カラー画像は「縦×横×BGR」の3次元配列、白黒画像は「縦×横」の2次元配列で格納されます。

color_img = cv2.imread('board.jpg', cv2.IMREAD_COLOR) # imgはカラー画像
print(color_img.shape) # (480L, 640L, 3L) という要素3のタプルが返る

gray_img = cv2.imread('board.jpg', cv2.IMREAD_GRAYSCALE) # imgは白黒画像
print(gray_img.shape) # (480L, 640L) という要素2のタプルが返る

このようにカラー画像と白黒画像とで次元数が異なるので、カラー画像と白黒画像の区別を以下のように行おうとするコードを書くと実行時に死んでしまいます。

if img.shape[2] == 3:
    print("img is color")
# エラー! もしimgが白黒なら、3次元目の要素にはアクセスできず "IndexError: tuple index out of range" が発生
elif img.shape[2] == 1:
    print("img is gray")

どうするのが正解なのでしょう? len(img.shape)が2なら白黒、3ならカラーと区別できますがエレガントではない気がします。

ビット深度のようなものはimg.dtypeで調べればよいと思われます。

print("depth(?) = ", img.dtype) # dtype('uint8')

画素にアクセスするときは、[]の中に1次元,2次元,3次元それぞれのindexを並べます。

print img[20, 100, 0] # y座標20 × x座標100 × B
print img[20, 100, 1] # y座標20 × x座標100 × G
print img[20, 100, 2] # y座標20 × x座標100 × R
print img[20, 100] # y座標20 × x座標100 ([B G R]という配列がスライスにより得られる)