OpenCVのMatの基本的な使い方をメモします.
*OpenCV Mat とは
多次元配列を格納するためのクラスで,主に画像や2次元の行列を扱う時に利用します.
詳しい使い方は,下記のリンクが参考になります.
{{small:opencv cookbook : [link:http://opencv.jp/cookbook/opencv_mat.html] }}
** 画像を読み込むだけのサンプル
{#
#include <opencv2/opencv.hpp>
using namespace cv;
int main() {
Mat img = imread("Lenna.bmp", 1);
return 0;
}
#}
*サンプルコード (画像)
**画像の読み書き
{#
// 画像の読み込み (第二引数を0にするとグレースケールで読み込み)
Mat img = imread("Lenna.bmp", 1);
// 画像の表示 (第一引数はウィンドウ名)
imshow("img", img);
// waitKeyの呼び出した時点で,実際に画像が表示されます.
// 引数で指定する時間[ms]の間,キー入力を待ちます.引数0の時は無制限にキー入力を待ちます.
waitKey(0);
// 画像の保存
imwrite("img.bmp", img);
#}
**USBカメラから画像のキャプチャ
{#
// USBカメラのIDを設定して初期化
VideoCapture cap(0);
if (cap.isOpened() == false){
printf("could not find USB camera\n");
exit(0);
}
int key = 0;
// 27 = 'ESC' key
while (key != 27){
// capture
Mat img;
cap >> img;
imshow("img", img);
key = waitKey(1);
}
#}
**画素のアクセス
at関数を使って,画像の各画素の要素にアクセスできます.
{#
// VGA(640x480)の配列を確保
// CV_8UC3は配列の要素の種類 8U:1Byte(8bit),C3:3ch(つまりカラー画像)
Mat img(480, 640, CV_8UC3);
for (int v = 0; v < img.size().height; v++) {
for (int u = 0; u < img.size().width; u++) {
img.at<Vec3b>(v, u) = Vec3b(50, 100, 150); // B:50 G:100 R:150
}
}
#}
配列のポインタを取得してアクセスする方法もあります.
{#
Vec3b *ptr = (Vec3b*)img.ptr();
const int step = img.step / sizeof(Vec3b);
for (int v = 0; v < img.size().height; v++) {
for (int u = 0; u < img.size().width; u++) {
ptr[v * step + u] = Vec3b(50, 100, 150);
}
}
#}
ptr関数は確保した配列の先頭 つまり左上の画素のポインタを返します.(図1)
[img:cqsz]
{{図1}}
**要素の変換
カラー画像の要素は標準でBGRの順に並んでいます.
RGBの順序やグレースケールに変換する場合cvtColor関数を利用します.
{#
Mat bgr = imread("Lenna.bmp", 1);
Mat rgb;
cvtColor(bgr, rgb, CV_BGR2RGB);
Mat gry;
cvtColor(bgr, gry, CV_BGR2GRAY);
#}
**ROI (Region of Interest) の設定
特定の注目領域で処理を行いたいときにはROIを指定します.
{#
// 始点位置(100, 100) サイズ(200, 200)のROIを定義
Rect roi(100, 100, 200, 200);
// ROIの画像をMatとして抽出
Mat tmp = img(roi);
// ROIの画像処理 (例:ガウシアンフィルタ)
GaussianBlur(tmp, tmp, Size(11, 11), 3.0);
// tmpに対する画像処理は,元のimgにも反映されています.
#}
**画像のコピー
operator = ,clone , copyTo の3つの方法があります.
{#
// shallow copy 画像データの実態はメモリ上に1つ
// Aを編集すると,imgにも反映されます.
Mat A = img;
// deep copy 新しく画像データの実態をメモリ上に確保します
// BやCを編集しても,imgには反映されません.
Mat B = img.clone();
Mat C;
img.copyTo(C);
// ROIに対するコピー
Rect roi(100, 100, 200, 200);
Mat D = B(roi); // 出力画像
Mat E = Mat(200, 200, CV_8UC3, Scalar(255,255,255)); // 入力画像 白色サイズ (200, 200)
E.copyTo(D);
#}
*サンプルコード (行列)
**行列の初期化
{#
// 3x3の配列 それぞれ 0で初期化,1で初期化,単位行列を意味します.
// 行列演算を行う場合,配列の要素はfloat か double を設定しておきます.
Mat mat0 = Mat::zeros(3, 3, CV_32FC1);
Mat mat1 = Mat::ones(3, 3, CV_32FC1);
Mat mat2 = Mat::eye(3, 3, CV_32FC1);
// std::coutを利用して,配列の中身を表示できます.
std::cout << mat0 << std::endl;
std::cout << mat1 << std::endl;
std::cout << mat2 << std::endl;
#}
出力
{/
[0, 0, 0;
0, 0, 0;
0, 0, 0]
[1, 1, 1;
1, 1, 1;
1, 1, 1]
[1, 0, 0;
0, 1, 0;
0, 0, 1]
/}
**行列の演算
{#
// オペレータが定義されているため,様々な行列演算が可能です.
Mat mat3 = mat1 + mat2 * 2;
std::cout << mat3 << std::endl;
#}
出力
{/
[3, 1, 1;
1, 3, 1;
1, 1, 3]
/}
**派生クラス Mat_
Matの派生クラスに,Mat_があります.定義や代入が少し楽になります.
{#
Mat_<double> mat_ = cv::Mat_<double>(2, 2);
mat_ << 0.0, 1.0, 1.0, 1.0;
Mat mat = _mat;
std::cout << mat << std::endl; // mat_ としても出力は同じ
#}
出力
{/
[0, 1;
1, 1]
/}
**逆行列
{#
Mat mat = (cv::Mat_<double>(2, 2) << 0.0, 1.0, 1.0, 1.0);
std::cout << mat << std::endl;
std::cout << mat.inv() << std::endl;
#}
{/
[0, 1;
1, 1]
[-1, 1;
1, -0]
/}
**固有値分解
{#
Mat mat = (cv::Mat_<double>(2, 2) << 0.0, 1.0, 1.0, 1.0);
Mat eigVal, eigVec;
eigen(mat, eigVal, eigVec);
std::cout << mat << std::endl;
std::cout << eigVal << std::endl;
std::cout << eigVec << std::endl;
#}
{/
[0, 1;
1, 1]
[1.618033988749895;
-0.6180339887498948]
[0.5257311121191336, 0.8506508083520399;
0.8506508083520399, -0.5257311121191336]
/}
**特異値分解
{#
Mat mat = (cv::Mat_<double>(2, 2) << 0.0, 1.0, 1.0, 1.0);
Mat S, U, V;
SVD::compute(mat, S, U, V, cv::SVD::FULL_UV);
std::cout << mat << std::endl;
std::cout << S << std::endl;
std::cout << U << std::endl;
std::cout << V << std::endl;
#}
{/
[0, 1;
1, 1]
[1.618033988749895;
0.6180339887498948]
[0.5257311121191336, 0.85065080835204;
0.8506508083520399, -0.5257311121191336]
[0.5257311121191336, 0.8506508083520399;
-0.8506508083520399, 0.5257311121191336]
/}
**型変換
{#
Mat mat0 = Mat::zeros(2, 2, CV_8UC1);
mat0.at<unsigned char>(0, 0) = 100;
mat0.at<unsigned char>(1, 1) = 100;
Mat mat1;
// 第3引数と第4引数でスケールやオフセットを設定可能
mat0.convertTo(mat1, CV_32FC1, 1.0 / 255.0);
cout << mat0 << endl;
cout << mat1 << endl;
#}
{/
[100, 0;
0, 100]
[0.3921569, 0;
0, 0.3921569]
/}
**CSVの読み書き
CSVの読み込みは,該当する関数がOpenCVにはないようです.(2018年3月時点)
自前で実装するか,別途ライブラリを利用することになると思います.
{#
Mat mat0 = Mat::eye(3, 3, CV_32FC1);
Mat mat1;
// CSVファイルに書き込み
ofstream ofs("mat.csv");
ofs << format(mat0, Formatter::FMT_CSV) << endl;
// CSVファイルから読み込み
loadCSV(mat1, "mat.csv", CV_32FC1);
cout << mat1 << endl;
#}
{#
// loadCSV の実装例
bool loadCSV(Mat &mat, const char *path, int type) {
ifstream ifs(path);
if (ifs.fail()) return false;
vector<vector<double> > data;
string str;
while (getline(ifs, str)){
vector<double> tmp;
stringstream ss{ str };
string buf;
while (getline(ss, buf, ',')) {
double val = 0.0;
sscanf(buf.c_str(), "%lf", &val);
tmp.push_back(val);
}
if (tmp.size() > 0) {
data.push_back(tmp);
}
}
if (data.size() == 0) return false;
Mat mat64 = Mat::zeros(data.size(), data[0].size(), CV_64FC1);
for (int r = 0; r < data.size(); r++) {
for (int c = 0; c < data[r].size(); c++) {
mat64.at<double>(r, c) = data[r][c];
}
}
mat64.convertTo(mat, type);
return true;
}
#}
>> ご意見・ご質問など お気軽にご連絡ください.info