表題について自分なりにまとめています。経験値が足りないせいで誤ったことを書いているかもしれません。
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のカラー画像を例にとって考えてみます。
Python版OpenCVでは、画像を表現するのに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]という配列がスライスにより得られる)