読者です 読者をやめる 読者になる 読者になる

cv::Mat使用時の画素へのアクセス方法


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

OpenCVのcv::Matクラスに格納された画像データの各画素にアクセスする方法を、以下の2種類ご紹介する。

  • atメソッドを用いる方法
  • dataメソッドを用いる方法

画素にアクセスする回数が少ないのならatメソッド、全画素に順番に読み出すなど頻繁に画素にアクセスするのならdataメソッドを用いる方法が良いと思う。

atメソッドを用いる場合

画素をピンポイントで読んだり書いたりするには、atメソッドを使う。ただし、atメソッドを使うには、Matクラスが1画素あたり何バイトからなるのかを事前に知っていないといけない。

  • 例:白黒画像の場合

1画素の濃淡は1バイト(0〜255)で表されるので、以下のようになる。

cv::Mat img = cv::imread("cat.jpg");
int intensity = img.at<unsigned char>(y, x); //X座標がx, Y座標がyに位置するピクセルの値を取得
  • 例:カラー画像の場合

1画素はB, G, R各1バイトずつ、合計3バイトで表されるので、以下のようになる。3bのbははっきり由来が分からない。byteのbであろうか。

cv::Mat img = cv::imread("cat.jpg");
int B = img.at<Vec3b>(y, x)[0]
int G = img.at<Vec3b>(y, x)[1]
int R = img.at<Vec3b>(y, x)[2]

dataメソッドを用いる場合

atメソッドのオーバーヘッドが気になる場合は、dataメソッドを用いて生の画素が並んでいるメモリ領域に直接アクセスする。
例えば、画像中の全画素について、すべてのチャネルを舐める処理は、読み込む画像がカラーか白黒かにかかわらず共通して以下のように書ける…はず。間違っていたらごめんなさい…

cv::Mat src = cv::imread("cat.jpg");
for(int y = 0; y < src.rows; ++y){
	for(int x = 0; x < src.cols; ++x){
		// 画像のチャネル数分だけループ。白黒の場合は1回、カラーの場合は3回     
		for(int c = 0; c < src.channels(); ++c){
			cout << src.data[ y * src.step + x * src.elemSize() + c ] << endl;
		}
	}
}

例:ポスタライズ

dataメソッドでのアクセスが正しく動いていることを確かめるため、入力画像をポスタライズするサンプルコードを作成した。分かりやすさを優先するためにピクセルごとにアドレスを計算している。

#include "stdafx.h"
#include <cstdio>
#include <windows.h>
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

using namespace std;

int posterize_intensity(int intensity, int poster_level){
	int bin_size = 256 / poster_level;
	return (intensity / bin_size) * (255 / (poster_level - 1));
}

void posterize_color_and_mono(){

	// 画像を白黒で読み込むか、カラーで読み込むかのループ。color_flagが0の場合は白黒、1の場合はカラー
	for(int color_flag = 0; color_flag <= 1; ++color_flag){

		// 読み込み画像
		cv::Mat src = cv::imread("./input/cat.jpg", color_flag);
		// ポスタライズ結果画像
		cv::Mat dst(src.size(), src.type());

		// ポスタライズに使う色数でループ。順に2色、4色、8色、…、256色
		for(int poster_level = 2; poster_level <= 256; poster_level *= 2){
			for(int y = 0; y < src.rows; ++y){
				for(int x = 0; x < src.cols; ++x){
					// チャネル数でループ。白黒画像の場合は1回、カラー画像の場合は3回ループする
					for(int c = 0; c < src.channels(); ++c){
						// 画素に直接アクセス
						dst.data[ y * src.step + x * src.elemSize() + c ] = posterize_intensity( src.data[ y * src.step + x * dst.elemSize() + c ], poster_level );
					}
				}
			}

			// ポスタライズ画像を保存するファイルネームを作成
			ostringstream sout;
			if (color_flag == 0){
				sout << "./output/cat_mono_" << poster_level << ".jpg"; 
			}
			else{
				sout << "./output/cat_color_" << poster_level << ".jpg"; 
			}
			imwrite(sout.str(), dst);
		}
	}
}

int _tmain(int argc, _TCHAR *argv[])
{
	posterize_color_and_mono();
	return 0;
}

結果の一部はこんな感じ。
白黒画像を2色でポスタライズ(画素値が0〜127の画素の画素値を0に、画素値が128〜255の画素を1に変更)

白黒画像を4色でポスタライズ

白黒画像を8色でポスタライズ

カラー画像を8色でポスタライズ(R, G, Bそれぞれを2値化)

カラー画像を64色でポスタライズ(R, G, Bそれぞれを4値化)

カラー画像を512色でポスタライズ(R, G, Bそれぞれを8値化)

参考