このページは更新を停止しております。移行先のOpenCV 画像処理演習をご利用ください。
開発環境
- Windows 10
- Visual C++ Community 2017
- OpenCV 3.2.0
- OpenCV contrib 3.2.0
- インストールディレクトリ C:\opencv\
もくじ
- 基本操作
- 動画像処理
- フィルタリング処理
- 図形描画
- 行列操作
- 画素操作
- 幾何学変換
1. 基本操作
1.1 画像ファイルの読み込みと画像の表示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#include <opencv2/opencv.hpp> int main(void) { // 画像を格納するオブジェクトを宣言する cv::Mat image; // 画像ファイルから画像データを読み込む image = cv::imread("C:/opencv/sources/samples/data/lena.jpg"); // 表示するウィンドウを準備する(省略可) // ウィンドウにつける名前は自由 cv::namedWindow("画像"); // ウィンドウに画像を表示する cv::imshow("画像", image); // 何かキーが押されるまで待つ cv::waitKey(); return 0; } |
課題
画像ファイルを自分で用意し、その画像を読み込んで表示せよ。
1.2 画像の色変換とファイル保存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#include <opencv2/opencv.hpp> int main(void) { // 画像を格納するオブジェクトを宣言する cv::Mat src, dst; // 画像ファイルから画像データを読み込む src = cv::imread("C:/opencv/sources/samples/data/lena.jpg"); // グレイスケール画像に変換する // * カラーはBGR順である(RGBではない)ことに注意 cv::cvtColor(src, dst, cv::COLOR_BGR2GRAY); // ウィンドウに画像を表示する cv::imshow("原画像", src); cv::imshow("変換画像", dst); // 画像を保存する // * ファイルフォーマットは拡張子で自動判別される cv::imwrite("変換画像.jpg", dst); // 何かキーが押されるまで待つ cv::waitKey(); return 0; } |
課題
PNGフォーマットで画像をファイルに保存せよ。
1.3 画像の反転
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <opencv2/opencv.hpp> int main(void) { // 画像を格納するオブジェクトを宣言する cv::Mat src, dst; // 画像ファイルから画像データを読み込む src = cv::imread("C:/opencv/sources/samples/data/lena.jpg"); // 画像を反転する // * 0:上下反転, 1:左右反転 cv::flip(src, dst, 0); // ウィンドウに画像を表示する cv::imshow("原画像", src); cv::imshow("反転画像", dst); // 何かキーが押されるまで待つ cv::waitKey(); return 0; } |
課題
原画像を1枚読み込んで、原画像、上下反転画像、左右反転画像、180度回転画像の4つを表示せよ。
2. 動画像処理
2.1 動画ファイルの読み込みと動画の表示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
#include <opencv2/opencv.hpp> int main(void) { // 動画ファイルを取り込むためのオブジェクトを宣言する cv::VideoCapture cap; cap.open("C:/opencv/sources/samples/data/Megamind.avi"); // 動画ファイルが開けたか調べる if (cap.isOpened() == false) { printf("ファイルが開けません。\n"); return -1; } // 画像を格納するオブジェクトを宣言する cv::Mat frame; for (;;) { // 1フレームを取り込む cap >> frame; // cap から frame へ // 画像から空のとき、無限ループを抜ける if (frame.empty() == true) { break; } // ウィンドウに画像を表示する cv::imshow("再生中", frame); // 33ms待つ // * 引数にキー入力の待ち時間を指定できる。(ミリ秒単位) // * 引数が 0 または何も書かない場合、キー入力があるまで待ち続ける cv::waitKey(33); } return 0; } |
課題
動画の再生速度を変えよ。
2.2 簡単な動画再生ソフト
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
#include <opencv2/opencv.hpp> int main(void) { // 動画ファイルを取り込むためのオブジェクトを宣言する cv::VideoCapture cap; cap.open("C:/opencv/sources/samples/data/Megamind.avi"); // 動画ファイルが開けたか調べる if (cap.isOpened() == false) { printf("ファイルが開けません。\n"); return -1; } // 画像を格納するオブジェクトを宣言する cv::Mat frame; for (;;) { // 1フレームを取り込む cap >> frame; // cap から frame へ // 画像から空のとき、無限ループを抜ける if (frame.empty() == true) { break; } // ウィンドウに画像を表示する cv::imshow("再生中", frame); // 33ms待つ // キー入力されたらkeyへ文字コードを代入する int key = cv::waitKey(33); // 現在のフレーム番号(先頭から何フレーム目か)を表示する int n = (int)cap.get(cv::CAP_PROP_POS_FRAMES); // フレームの位置を取得 int m = (int)cap.get(cv::CAP_PROP_FRAME_COUNT); // 全フレーム数を取得 printf("フレーム %4d/%d\r", n, m); if (key == ' ') { // スペースキーが押されたら一時停止する printf("\n一時停止\n"); cv::waitKey(); } else if (key == 'r') { // Rキーが押されたら先頭から再生しなおす printf("\n巻き戻し\n"); cap.set(cv::CAP_PROP_POS_FRAMES, 0); // フレームの位置を0に設定 } else if (key == 's') { // Sキーが押されたら30フレームスキップする printf("\nスキップ\n"); cap.set(cv::CAP_PROP_POS_FRAMES, n + 30); // n+30に設定 } else if (key == 0x1b) { // ESCキーが押されたら終了する break; } } return 0; } |
課題
前方向にスキップや、再生完了後先頭フレームに戻して一時停止、逆再生などの機能を追加せよ。
2.3 動画像の変換とファイル保存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
#include <opencv2/opencv.hpp> int main(void) { // 動画ファイルを取り込むためのオブジェクトを宣言する cv::VideoCapture cap; cap.open("C:/opencv/sources/samples/data/Megamind.avi"); // 動画ファイルが開けたか調べる if (cap.isOpened() == false) { printf("ファイルが開けません。\n"); return -1; } // 作成する動画ファイルの諸設定 int fourcc, width, height; double fps; width = (int)cap.get(cv::CAP_PROP_FRAME_WIDTH); // フレーム横幅を取得 height = (int)cap.get(cv::CAP_PROP_FRAME_HEIGHT); // フレーム縦幅を取得 fps = cap.get(cv::CAP_PROP_FPS); // フレームレートを取得 fourcc = cv::VideoWriter::fourcc('X', 'V', 'I', 'D'); // AVI形式を指定 // * エンコード形式 "XVID" = AVI, "MP4V" = MPEG4, "WMV1" = WMV // 動画ファイルを書き出すためのオブジェクトを宣言する cv::VideoWriter writer; writer.open("ビデオ.avi", fourcc, fps, cv::Size(width, height)); // 画像を格納するオブジェクトを宣言する cv::Mat frame, dst; for (;;) { // 1フレームを取り込む cap >> frame; // cap から frame へ // 画像から空のとき、無限ループを抜ける if (frame.empty() == true) { break; } // ウィンドウに画像を表示する cv::imshow("変換中", frame); // 画像処理(画像を反転) cv::flip(frame, dst, 1); // 動画ファイルへ書き出す writer << dst; // dst から writer へ // 1ms待つ cv::waitKey(1); } return 0; } |
課題
他のフォーマットで動画をファイルに保存せよ。
2.4 パソコンに接続したカメラからの映像の取り込み
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
#include <opencv2/opencv.hpp> int main(void) { // 映像を取り込むためのオブジェクトを宣言する cv::VideoCapture cap; cap.open(0); // カメラ番号を指定する(通常は0) // カメラに接続できたか調べる if (cap.isOpened() == false) { printf("カメラに接続できません。\n"); return -1; } int width, height; double fps; width = (int)cap.get(cv::CAP_PROP_FRAME_WIDTH); // フレーム横幅を取得 height = (int)cap.get(cv::CAP_PROP_FRAME_HEIGHT); // フレーム縦幅を取得 fps = cap.get(cv::CAP_PROP_FPS); // フレームレートを取得(取得できないカメラもある) printf("画像サイズ %dx%d, %ffps\n", width, height, fps); // 画像を格納するオブジェクトを宣言する cv::Mat frame; for (;;) { // 1フレームを取り込む cap >> frame; // 画像から空のとき、無限ループを抜ける if (frame.empty() == true) { break; } // ウィンドウに画像を表示する cv::imshow("入力中", frame); // 33ms待つ int key = cv::waitKey(33); if (key == 0x1b) { // ESCキーが押されたら終了する break; } } return 0; } |
課題
カメラから取り込んだ映像を動画ファイルに保存せよ。
3. フィルタリング処理
3.1 平滑化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#include <opencv2/opencv.hpp> int main(void) { // 画像を格納するオブジェクトを宣言する cv::Mat src, dst1, dst2, dst3, dst4; // 画像ファイルから画像データを読み込む src = cv::imread("C:/opencv/sources/samples/data/board.jpg"); // 平均値フィルタ // * Size(x, y)でx方向、y方向のフィルタサイズを指定する cv::blur(src, dst1, cv::Size(7, 7)); // 中央値フィルタ cv::medianBlur(src, dst2, 7); // ガウシアンフィルタ // * Size(x, y)でx方向、y方向のフィルタサイズを指定する cv::GaussianBlur(src, dst3, cv::Size(7, 7), 0.0); // バイラテラルフィルタ cv::bilateralFilter(src, dst4, 20, 200, 20); // ウィンドウに画像を表示する cv::imshow("原画像", src); cv::imshow("平均値フィルタ", dst1); cv::imshow("中央値フィルタ", dst2); cv::imshow("ガウシアンフィルタ", dst3); cv::imshow("バイラテラルフィルタ", dst4); cv::waitKey(); return 0; } |
課題
フィルタサイズを変えて得られる画像の違いを調べよ。
3.2 輪郭抽出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#include <opencv2/opencv.hpp> int main(void) { // 画像を格納するオブジェクトを宣言する cv::Mat src, dst1, dst2, dst3, dst4, dst5, dst6; // 画像ファイルから画像データを読み込む // * 第2引数に IMREAD_GRAYSCALE を指定すると、グレイスケール画像に変換する src = cv::imread("C:/opencv/sources/samples/data/lena.jpg", cv::IMREAD_GRAYSCALE); // ラプラシアンフィルタ cv::Laplacian(src, dst1, -1); // Sobelフィルタ cv::Sobel(src, dst2, -1, 1, 0); // x方向 cv::Sobel(src, dst3, -1, 0, 1); // y方向 // Cannyフィルタ cv::Canny(src, dst4, 60.0, 150.0); // ウィンドウに画像を表示する cv::imshow("原画像", src); cv::imshow("ラプラシアンフィルタ", dst1); cv::imshow("Sobelフィルタ x方向", dst2); cv::imshow("Sobelフィルタ y方向", dst3); cv::imshow("Cannyフィルタ", dst4); cv::waitKey(); return 0; } |
課題
輪郭抽出をする前に画像を平滑化する場合としない場合とで、画像がどのように変わるか調べよ。
3.3 濃淡変換
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#include <opencv2/opencv.hpp> int main(void) { // 画像を格納するオブジェクトを宣言する cv::Mat src, dst1, dst2; // 画像ファイルから画像データを読み込む // * 第2引数に IMREAD_GRAYSCALE を指定すると、グレイスケール画像に変換する src = cv::imread("C:/opencv/sources/samples/data/lena.jpg", cv::IMREAD_GRAYSCALE); // ヒストグラム平坦化 cv::equalizeHist(src, dst1); // ルックアップテーブルを用いたガンマ補正 double gamma = 1.5; // ガンマ値 uchar lut[256]; // ルックアップテーブル用配列 for (int i = 0; i < 256; i++) { lut[i] = pow(i / 255.0, 1 / gamma) * 255.0; // ガンマ補正式 } cv::LUT(src, cv::Mat(1, 256, CV_8UC1, lut), dst2); // ルックアップテーブル変換 // ウィンドウに画像を表示する cv::imshow("原画像", src); cv::imshow("ヒストグラム平坦化", dst1); cv::imshow("ガンマ補正", dst2); cv::waitKey(); return 0; } |
課題
画像が暗くなるようにガンマ補正を掛けよ。
3.4 2値化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#include <opencv2/opencv.hpp> int main(void) { // 画像を格納するオブジェクトを宣言する cv::Mat src, dst1, dst2, dst3, dst4; // 画像ファイルから画像データを読み込む // * 第2引数に IMREAD_GRAYSCALE を指定すると、グレイスケール画像に変換する src = cv::imread("C:/opencv/sources/samples/data/lena.jpg", cv::IMREAD_GRAYSCALE); // 2値化処理 // * 2値化閾値 = 140, 変換後の最大値 = 255 cv::threshold(src, dst1, 140, 255, cv::THRESH_BINARY); // 2値化処理(判別分析2値化法(大津の方法)) // * 最適な閾値を自動的に算出する cv::threshold(src, dst2, 0, 255, cv::THRESH_OTSU); // 膨張処理(最大値フィルタ) cv::dilate(dst2, dst3, cv::Mat()); // 収縮処理(最小値フィルタ) cv::erode(dst2, dst4, cv::Mat()); cv::imshow("原画像", src); cv::imshow("2値化処理", dst1); cv::imshow("2値化処理(大津の方法)", dst2); cv::imshow("膨張処理", dst3); cv::imshow("収縮処理", dst4); cv::waitKey(); return 0; } |
課題
2値画像のオープニング処理とクロージング処理をそれぞれ行い、結果を比較せよ。
3.5 ROIの利用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
#include <opencv2/opencv.hpp> int main(void) { // 画像を格納するオブジェクトを宣言する cv::Mat src, dst; // 画像ファイルから画像データを読み込む src = cv::imread("C:/opencv/sources/samples/data/lena.jpg", cv::IMREAD_GRAYSCALE); // srcをdstへコピーする src.copyTo(dst); // ROIの範囲(長方形)を設定する // * (x, y, width, height)で指定 cv::Rect roi(50, 230, 400, 150); // 部分画像を生成 // * 部分画像とその元画像は共通の画像データを参照するため、 // 部分画像に変更を加えると、元画像も変更される。 cv::Mat srcROI = src(roi); // srcからroiの範囲で部分画像srcROIをつくる cv::Mat dstROI = dst(roi); // dstからroiの範囲で部分画像dstROIをつくる // 部分画像srcROIをヒストグラム平坦化したものをdstROIへ書き込む cv::equalizeHist(srcROI, dstROI); cv::imshow("原画像src", src); cv::imshow("変換後画像dst", dst); // dstROIの変更によってdstも変わっている cv::imshow("部分画像srcROI", srcROI); cv::imshow("部分画像dstROI", dstROI); cv::waitKey(); return 0; } |
課題
プログラムを次のように変更し、実行結果を見てROIの働きを理解せよ。
17行目追加 cv::Rect roi2(50, 0, 400, 150);
23行目変更 cv::Mat dstROI = dst(roi2);
4. 図形描画
4.1 基本図形の描画
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
#include <opencv2/opencv.hpp> int main(void) { // 画像を格納するオブジェクトを宣言する // * CV_8UC3 : 8bit 3channel = カラー画像 cv::Mat image(400, 400, CV_8UC3); // 画像を白色で塗りつぶす // * Scalar(Blue, Green, Red) RGB順ではないことに注意 image = cv::Scalar(255, 255, 255); // 線を引く // * line(画像, 始点座標, 終点座標, 色, 線幅, 連結) cv::line(image, cv::Point(10, 10), cv::Point(390, 30), cv::Scalar(255, 0, 0), 1, cv::LINE_4); cv::line(image, cv::Point(10, 20), cv::Point(390, 40), cv::Scalar(255, 0, 0), 1, cv::LINE_8); cv::line(image, cv::Point(10, 30), cv::Point(390, 50), cv::Scalar(255, 0, 0), 1, cv::LINE_AA); // 四角形を描く // * rectangle(画像, 四角形, 色, 線幅, 連結) // * 線幅 < 0 のときは四角形内を塗りつぶす cv::rectangle(image, cv::Rect(10, 60, 100, 100), cv::Scalar(0, 255, 0), 1, cv::LINE_AA); cv::rectangle(image, cv::Rect(120, 60, 100, 100), cv::Scalar(0, 255, 0), -1, cv::LINE_AA); // 円を描く // * circlee(画像, 中心座標, 半径, 色, 線幅, 連結) cv::circle(image, cv::Point(60, 230), 50, cv::Scalar(0, 0, 255), 1, cv::LINE_AA); cv::circle(image, cv::Point(170, 230), 50, cv::Scalar(0, 0, 255), -1, cv::LINE_AA); // 楕円を描く // * RotatedRect(中心座標, サイズ(x, y), 回転角度degree) // * ellipse(画像, RotatedRect, 色, 線幅, 連結) cv::RotatedRect rrect(cv::Point2f(60, 340), cv::Size(100, 50), 30); cv::ellipse(image, rrect, cv::Scalar(255, 0, 255), 1, cv::LINE_AA); // 円弧を描く // * RotatedRect(中心座標, サイズ(x, y), 回転角度degree) // * ellipse(画像, 中心座標, Size(x径, y径), 楕円の回転角度, 始点角度, 終点角度, 色, 線幅, 連結) cv::ellipse(image, cv::Point(170, 340), cv::Size(50, 50), 0, 30, 180, cv::Scalar(255, 0, 255), 1, cv::LINE_AA); cv::ellipse(image, cv::Point(280, 340), cv::Size(50, 50), 0, 30, 180, cv::Scalar(255, 0, 255), -1, cv::LINE_AA); cv::imshow("画像", image); cv::waitKey(); return 0; } |
課題
(1)オセロ盤(8×8マス)の図を描け。中央に黒と白のコマを2個ずつ配置せよ。
(2)整数n の値をキーボードから入力し、正n角形を描け。
4.2 テキストの描画
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#include <opencv2/opencv.hpp> #include <string> #include <vector> int main(void) { // 画像を格納するオブジェクトを宣言する // * CV_8UC3 : 8bit 3channel = カラー画像 cv::Mat image(400, 400, CV_8UC3); // 画像を白色で塗りつぶす // * Scalar(Blue, Green, Red) RGB順ではないことに注意 image = cv::Scalar(255, 255, 255); // 文字を描く // * putText(画像, "文字列", 座標, フォント, スケール, 色, 線幅, 連結) cv::putText(image, "Sample Text", cv::Point(10, 40), cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(255, 0, 0), 2, cv::LINE_AA); // ベースラインの位置を描く cv::line(image, cv::Point(10, 40), cv::Point(390, 40), cv::Scalar(0, 0, 255), 1, cv::LINE_4); // フォントの一覧を表示 std::vector<std::string> font{ "SIMPLEX", "PLAIN", "DUPLEX", "COMPLEX", "TRIPLEX", "COMPLEX_SMALL", "SCRIPT_SIMPLEX", "SCRIPT_COMPLEX" }; for(int i = 0; i < font.size(); i++) { cv::putText(image, "FONT_HERSHEY_" + font[i], cv::Point(10, i * 40 + 80), i, 0.6, cv::Scalar::all(0), 1, cv::LINE_AA); } cv::imshow("画像", image); cv::waitKey(); return 0; } |
5. 行列操作
5.1 行列の宣言と表示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
#include <opencv2/opencv.hpp> using namespace std; int main(void) { // 空の行列を宣言する cv::Mat mat1; // 大きさと型を指定して行列を宣言する(2行3列、float型) // * Debug: 初期化しない, Relese: 0で初期化 cv::Mat_<float> mat2(2, 3); // 方法1 cv::Mat mat3(2, 3, CV_32F); // 方法2 // 行列の宣言と初期値の代入をする cv::Mat mat4 = (cv::Mat_<float>(2, 3) << 1, 2, 3, 4, 5, 6); // 行列を表示する // * cout の使い方はC++の文法書を参照のこと cout << mat1 << endl; cout << mat3 << endl; cout << mat2 << endl; cout << mat4 << endl; // 行列の大きさを表示する printf("行数=%d, 列数=%d\n", mat2.rows, mat2.cols); return 0; } |
5.2 行列の計算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
#include <opencv2/opencv.hpp> using namespace std; int main(void) { // 行列を宣言する cv::Mat mat1 = (cv::Mat_<float>(2, 2) << 1, 2, 3, 4); cv::Mat mat2 = (cv::Mat_<float>(2, 2) << 1, 3, 4, 2); cv::Mat mat3, mat4, mat5, mat6, mat7; cout << "mat1=\n" << mat1 << endl; cout << "mat2=\n" << mat2 << endl; // 行列の和 mat3 = mat1 + mat2; cout << "mat1+mat2=\n" << mat3 << endl; // 行列の積 mat4 = mat1 * mat2; cout << "mat1*mat2=\n" << mat4 << endl; // 定数の乗算 mat5 = mat1 * 2; cout << "mat1*2=\n" << mat5 << endl; // 転置行列 mat6 = mat1.t(); cout << "mat1.t()=\n" << mat6 << endl; // 逆行列 mat7 = mat1.inv(); cout << "mat1.inv()=\n" << mat7 << endl; // 行列式 double det = cv::determinant(mat1); cout << "det(mat1)=" << det << endl; return 0; } |
課題
行列Aの転置行列を At、逆行列を A-1と表すこととする。また、行列Aと行列Bの積を A*B と表すこととする。
(1) 正方行列A, Bにおいて、(A*B)t = (Bt)*(At) が成り立つことをプログラムで計算をしてみて確認せよ。
(2) 正方行列Aにおいて、(A-1)t = (At)-1 が成り立つことをプログラムで計算をしてみて確認せよ。
5.3 行列の浅いコピー、深いコピー
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#include <opencv2/opencv.hpp> using namespace std; int main(void) { // 行列を宣言する cv::Mat mat1 = (cv::Mat_<float>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9); // 行と列を指定して要素の値を読み出す // * 行列の要素は0行0列目から始まる float x = mat1.at<float>(1, 2); cout << "mat1(1, 2)=" << x << endl; // 行と列を指定して値を代入する mat1.at<float>(0, 2) = 99; cout << "mat1=\n" << mat1 << endl; cv::Mat mat2, mat3; // 行列の浅いコピー // * mat2は、mat1と同じ記憶域のデータを指す mat2 = mat1; // 行列の深いコピー // * mat1の複製を作り、それをmat3へ代入する // * mat1.copyTo(mat3) でも可。 mat3 = mat1.clone(); // mat1のデータを変えてみる mat1 = mat1.t(); cout << "変更後のmat1=\n" << mat1 << endl; cout << "mat2=\n" << mat2 << endl; // mat2は変更後のmat1と同じ cout << "mat3=\n" << mat3 << endl; // mat3は変更前のmat1と同じ return 0; } |
5.4 連立方程式の解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
#include <opencv2/opencv.hpp> using namespace std; int main(void) { // 零行列を宣言する cv::Mat mat1 = cv::Mat::zeros(3, 3, CV_32F); // 単位行列を宣言する cv::Mat mat2 = cv::Mat::eye(3, 3, CV_32F); // 全要素が1の行列を宣言する // * Mat mat3(3, 3, CV_32F) で宣言後、mat3 = 1 としても全要素を1にできる cv::Mat mat3 = cv::Mat::ones(3, 3, CV_32F); cout << "mat1=\n" << mat1 << endl; cout << "mat2=\n" << mat2 << endl; cout << "mat3=\n" << mat3 << endl; // 連立1次方程式を解く // 2変数の連立1次方程式 // x + 2y = 5 // 3x - y = 8 を解く // 左辺と右辺の値 cv::Mat lhand = (cv::Mat_<float>(2, 2) << 1, 2, 3, -1); cv::Mat rhand = (cv::Mat_<float>(2, 1) << 5, 8); cv::Mat ans; // 逆行列を計算して解く ans = lhand.inv() * rhand; cout << "ans=\n" << ans << endl; // solve関数を用いて解く // * solveは、パラメータ数と方程式の数とが違う問題(優決定、劣決定)も解くことができる cv::solve(lhand, rhand, ans); cout << "ans=\n" << ans << endl; return 0; } |
課題
3変数の連立1次方程式を解くプログラムを作成せよ。
6. 画素操作
6.1 グレイスケール画像の画素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#include <opencv2/opencv.hpp> int main(void) { // 画像を格納するオブジェクトを宣言する // * uchar = unsigned char = 8bit/画素 cv::Mat_<uchar> src, dst1, dst2; // 画像ファイルから画像データを読み込む // * 第2引数に IMREAD_GRAYSCALE を指定すると、グレイスケール画像に変換する src = cv::imread("C:/opencv/sources/samples/data/lena.jpg", cv::IMREAD_GRAYSCALE); // srcと同じサイズで画像を作成する。 dst1 = cv::Mat_<uchar>(src.size()); dst2 = cv::Mat_<uchar>(src.size()); // 1画素ごとの変換 for (int y = 0; y < src.rows; y++) { for (int x = 0; x < src.cols; x++) { // srcの座標(x,y)の画素値を取り出す uchar value = src(y, x); // dst1の座標(x,y)に画素値を書き込む dst1(y, x) = 255 - value; // dst2の座標(x,src.rows-y-1)に画素値を書き込む dst2(src.rows - y - 1, x) = value; } } cv::imshow("原画像", src); cv::imshow("画像dst1", dst1); cv::imshow("画像dst2", dst2); cv::waitKey(); return 0; } |
6.2 フルカラー画像の画素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
#include <opencv2/opencv.hpp> int main(void) { // 画像を格納するオブジェクトを宣言する // * Vec3b = 1byte * 3 = 24bit/画素 cv::Mat_<cv::Vec3b> src, dst1, dst2; // 画像ファイルから画像データを読み込む // * 第2引数に IMREAD_COLOR を指定すると、カラー画像で読み込む src = cv::imread("C:/opencv/sources/samples/data/lena.jpg", cv::IMREAD_COLOR); // srcと同じサイズで画像を作成する。 dst1 = cv::Mat_<cv::Vec3b>(src.size()); dst2 = cv::Mat_<cv::Vec3b>(src.size()); // 1画素ごとの変換 for (int y = 0; y < src.rows; y++) { for (int x = 0; x < src.cols; x++) { // srcの座標(x,y)の画素値を取り出す uchar r, g, b; b = src(y, x)[0]; // 青成分 g = src(y, x)[1]; // 緑成分 r = src(y, x)[2]; // 赤成分 // dst1の座標(x,y)に画素値を書き込む(方法1) dst1(y, x)[0] = 0; // 青成分を0にする dst1(y, x)[1] = g; dst1(y, x)[2] = r; // dst2の座標(x,y)に画素値を書き込む(方法2) dst2(y, x) = cv::Vec3b(0, g, r); // 青成分を0にする } } cv::imshow("原画像", src); cv::imshow("画像dst1", dst1); cv::imshow("画像dst2", dst2); cv::waitKey(); return 0; } |
6.3 フィルタリングの計算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
#include <opencv2/opencv.hpp> int main(void) { // 画像を格納するオブジェクトを宣言する cv::Mat_<uchar> src, dst1, dst2; // 画像ファイルから画像データを読み込む src = cv::imread("C:/opencv/sources/samples/data/lena.jpg", cv::IMREAD_GRAYSCALE); // srcと同じサイズで画像を作成する。初期値を0にする。 dst1 = cv::Mat_<uchar>(src.size(), 0); dst2 = cv::Mat_<uchar>(src.size(), 0); // 平均値フィルタ // 3x3フィルタをかけるので、画像の走査範囲を1画素狭くしている for (int y = 1; y < src.rows - 1; y++) { for (int x = 1; x < src.cols - 1; x++) { int value; value = src(y - 1, x - 1) + src(y - 1, x) + src(y - 1, x + 1) + src(y, x - 1) + src(y, x) + src(y, x + 1) + src(y + 1, x - 1) + src(y + 1, x) + src(y + 1, x + 1); value /= 9; dst1(y, x) = value; } } // ラプラシアンフィルタ for (int y = 1; y < src.rows - 1; y++) { for (int x = 1; x < src.cols - 1; x++) { int value; value = + src(y - 1, x) + src(y, x - 1) - 4*src(y, x) + src(y, x + 1) + src(y + 1, x); value *= 2; // 画像が見やすくなるように値を大きくする value = abs(value); // 値の絶対値をとる dst2(y, x) = cv::saturate_cast<uchar>(value); // saturate_cast<型>は、型の値域になるように値をまるめる。 // saturate_cast<uchar>(value)は、value>255 のときvalue=255になり、 // value<0 のときvalue=0になる。 } } cv::imshow("原画像", src); cv::imshow("平均値フィルタ", dst1); cv::imshow("ラプラシアンフィルタ", dst2); cv::waitKey(); return 0; } |
7. 幾何学変換
7.1 逆変換を使わないアフィン変換(悪い例)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
#include <opencv2/opencv.hpp> int main(void) { // 画像を格納するオブジェクトを宣言する cv::Mat_<uchar> src, dst; // 画像ファイルから画像データを読み込む src = cv::imread("C:/opencv/sources/samples/data/lena.jpg", cv::IMREAD_GRAYSCALE); // srcと同じサイズで画像を作成する。初期値を0にする。 dst = cv::Mat_<uchar>(src.size(), 0); // アフィン変換行列 cv::Mat affine = (cv::Mat_<double>(3, 3) << 0.8, 0, 0, 0, 1.5, 0, 0, 0, 1); cv::Mat_<double> p0(3, 1), p1(3, 1); // p0:原画像の座標 p1:変換後画像の座標 for (int y = 0; y < src.rows; y++) { for (int x = 0; x < src.cols; x++) { int x1, y1; p0(0, 0) = x; // 原画像の座標 p0(1, 0) = y; // アフィン変換の計算 p1 = affine * p0; x1 = (int)(p1(0, 0) + 0.5); // 変換後画像の座標 y1 = (int)(p1(1, 0) + 0.5); if (x1 >= 0 && x1 < dst.cols && y1 >= 0 && y1 < dst.rows) { // 座標(x1,y1)が変換後画像の範囲内であれば画素値を設定 dst(y1, x1) = src(y, x); } } } cv::imshow("原画像", src); cv::imshow("アフィン変換後画像", dst); cv::waitKey(); return 0; } |
7.2 逆変換を使うアフィン変換
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
#include <opencv2/opencv.hpp> int main(void) { // 画像を格納するオブジェクトを宣言する cv::Mat_<uchar> src, dst; // 画像ファイルから画像データを読み込む src = cv::imread("C:/opencv/sources/samples/data/lena.jpg", cv::IMREAD_GRAYSCALE); // srcと同じサイズで画像を作成する。初期値を0にする。 dst = cv::Mat_<uchar>(src.size(), 0); // アフィン変換行列 cv::Mat affine = (cv::Mat_<double>(3, 3) << 0.8, 0, 0, 0, 1.5, 0, 0, 0, 1); cv::Mat_<double> p0(3, 1), p1(3, 1); // p0:原画像の座標 p1:変換後画像の座標 for (int y = 0; y < dst.rows; y++) { for (int x = 0; x < dst.cols; x++) { int x1, y1; p1(0, 0) = x; // 変換後画像の座標 p1(1, 0) = y; // アフィン変換の計算(逆行列を使用) p0 = affine.inv() * p1; x1 = (int)(p0(0, 0) + 0.5); // 原画像の座標 y1 = (int)(p0(1, 0) + 0.5); if (x1 >= 0 && x1 < src.cols && y1 >= 0 && y1 < src.rows) { // 座標(x1,y1)が原画像の範囲内であれば画素値を設定 dst(y, x) = src(y1, x1); } } } cv::imshow("原画像", src); cv::imshow("アフィン変換後画像", dst); cv::waitKey(); return 0; } |
7.3 アフィン変換とバイリニア補間
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
#include <opencv2/opencv.hpp> int main(void) { // 画像を格納するオブジェクトを宣言する cv::Mat_<uchar> src, dst1, dst2; // 画像ファイルから画像データを読み込む src = cv::imread("C:/opencv/sources/samples/data/lena.jpg", cv::IMREAD_GRAYSCALE); // srcと同じサイズで画像を作成する。初期値を0にする。 dst1 = cv::Mat_<uchar>(src.size(), 0); dst2 = cv::Mat_<uchar>(src.size(), 0); // アフィン変換行列 cv::Mat affine = (cv::Mat_<double>(3, 3) << 3, 0, 0, 0, 1, 0, 0, 0, 1); cv::Mat_<double> p0(3, 1), p1(3, 1); // p0:原画像の座標 p1:変換後画像の座標 for (int y = 0; y < dst1.rows; y++) { for (int x = 0; x < dst1.cols; x++) { int x1, y1, x2, y2; p1(0, 0) = x; // 変換後画像の座標 p1(1, 0) = y; // アフィン変換の計算(逆行列を使用) p0 = affine.inv() * p1; x1 = (int)(p0(0, 0) + 0.5); // 原画像の座標 y1 = (int)(p0(1, 0) + 0.5); // バイリニア補間 double xr, yr; x2 = (int)p0(0, 0); // 原画像の座標 y2 = (int)p0(1, 0); xr = p0(0, 0) - x2; yr = p0(1, 0) - y2; if (x1 >= 0 && x1 < src.cols - 1 && y1 >= 0 && y1 < src.rows - 1) { // 最近傍補間 dst1(y, x) = src(y1, x1); // バイリニア補間 dst2(y, x) = (1 - xr)*(1 - yr)*src(y2, x2) + xr*(1 - yr) *src(y2, x2 + 1) + (1 - xr)* yr *src(y2 + 1, x2) + xr* yr *src(y2 + 1, x2 + 1); } } } cv::imshow("原画像", src); cv::imshow("アフィン変換後画像(最近隣)", dst1); cv::imshow("アフィン変換後画像(バイリニア)", dst2); cv::waitKey(); return 0; } |
7.4 warpAffine関数によるアフィン変換
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include <opencv2/opencv.hpp> int main(void) { cv::Mat src, dst; src = cv::imread("C:/opencv/sources/samples/data/lena.jpg"); // アフィン変換行列(2行3列) cv::Mat affine = (cv::Mat_<double>(2, 3) << 0.8, 0, 0, 0, 1.5, 0); // アフィン変換 // * warpAffine(原画像, 変換後画像, 変換後画像サイズ, 補間方法) // * INTER_LINEAR 線形補間(バイリニア) // INTER_NEAREST 再近傍補間 // INTER_CUBIC バイキュービック補間 // INTER_LANCZOS4 Lanczos(ランチョス)補間 cv::warpAffine(src, dst, affine, src.size(), cv::INTER_LINEAR); cv::imshow("原画像", src); cv::imshow("変換後画像", dst); cv::waitKey(); return 0; } |
7.5 対応点からアフィン変換行列を算出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#include <opencv2/opencv.hpp> int main(void) { cv::Mat src, dst; src = cv::imread("C:/opencv/sources/samples/data/lena.jpg"); // アフィン変換行列 cv::Mat affine; // 対応点(3点)の設定 // * 0.8f の f は直前の数値がfloat型であることを示す。 // fをつけない実数定数はdouble型になる。 cv::Point2f srcPoint[3] = { { 0, 0 },{ 1, 0 },{ 0, 1 } }; cv::Point2f dstPoint[3] = { { 0, 0 },{ 0.8f, 0.2f },{ 0.2f, 0.8f } }; // 対応点からアフィン変換行列を求める // * srcPoint から dstPoint への変換を行うアフィン変換行列が得られる。 affine = cv::getAffineTransform(srcPoint, dstPoint); // アフィン変換 cv::warpAffine(src, dst, affine, src.size(), cv::INTER_LINEAR); cv::imshow("原画像", src); cv::imshow("変換後画像", dst); cv::waitKey(); return 0; } |
7.6 warpPerspective関数による射影変換
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#include <opencv2/opencv.hpp> int main(void) { cv::Mat src, dst; src = cv::imread("C:/opencv/sources/samples/data/lena.jpg"); // ホモグラフィ行列(3行3列) cv::Mat homography = (cv::Mat_<double>(3, 3) << 0.8, -0.2, 180, 0.4, 0.9, 40, 0.001, 0.0001, 1); // 射影変換 cv::warpPerspective(src, dst, homography, src.size(), cv::INTER_LINEAR); cv::imshow("原画像", src); cv::imshow("変換後画像", dst); cv::waitKey(); return 0; } |
7.7 立方体の面への画像の貼り付け
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
#include <opencv2/opencv.hpp> int main(void) { cv::Mat src, dst; src = cv::imread("C:/opencv/sources/samples/data/lena.jpg"); // 射影変換行列 cv::Mat homography1, homography2, homography3; // 対応点の設定 float w = src.cols - 1, h = src.rows - 1; cv::Point2f srcPoint[4] = { { 0, 0 },{ w, 0 },{ w, h },{ 0, h } }; cv::Point2f dstPoint1[4] = { { 25, 100 },{ 230, 45 },{ 362, 60 },{ 195, 150 } }; cv::Point2f dstPoint2[4] = { { 25, 100 },{ 195, 150 },{ 225, 370 },{ 85, 285 } }; cv::Point2f dstPoint3[4] = { { 195, 150 },{ 362, 60 },{ 350, 215 },{ 225, 370 } }; // 対応点から射影変換行列を求める(srcPoint → dstPoint1) homography1 = cv::getPerspectiveTransform(srcPoint, dstPoint1); homography2 = cv::getPerspectiveTransform(srcPoint, dstPoint2); homography3 = cv::getPerspectiveTransform(srcPoint, dstPoint3); // 射影変換 // * BORDER_TRANSPARENT 変換後画像を初期化しないで上書きする cv::warpPerspective(src, dst, homography1, cv::Size(400, 400), cv::INTER_LINEAR); cv::warpPerspective(src, dst, homography2, cv::Size(400, 400), cv::INTER_LINEAR, cv::BORDER_TRANSPARENT); cv::warpPerspective(src, dst, homography3, cv::Size(400, 400), cv::INTER_LINEAR, cv::BORDER_TRANSPARENT); // 立方体の辺を描く for (int i = 0; i < 4; i++) { cv::line(dst, dstPoint1[i], dstPoint1[(i + 1) & 3], cv::Scalar::all(255), 1, cv::LINE_AA); cv::line(dst, dstPoint2[i], dstPoint2[(i + 1) & 3], cv::Scalar::all(255), 1, cv::LINE_AA); cv::line(dst, dstPoint3[i], dstPoint3[(i + 1) & 3], cv::Scalar::all(255), 1, cv::LINE_AA); } cv::imshow("原画像", src); cv::imshow("立方体へのマッピング", dst); cv::waitKey(); return 0; } |