OpenCV 2.x 演習

今週の内容
z 静止画
グレースケールへの変換
入力と出力の両方を表示
グレースケール画像のエッジ抽出
ぼかし
1.
2.
3.
4.
OpenCV 2.x 演習
z 動画 (ファイル)
知的計算システム研究室
1. グレースケールへの変換
(contributor: 加島 隆博)
z 動画 (カメラ)
1. グレースケールへの変換
2
カラーからグレースケールへの変換
カラーからグレースケールへの変換
z 概要
z グレースケール画像の特徴
¾ 1 チャンネルのみ
¾ 白から黒のグレースケール画像に変換
カラー
‹ 一般的には各画素を 0 ~ 255 の 256 段階で表わす
‹ 値が大きいほど明るい
グレースケール
変換
0
3
255
4
カラーからグレースケールへの変換
カラーからグレースケールへの変換
z 色空間変換関数
z 色空間変換関数
¾ 変換コード
void
cvtColor(const Mat &src,
Mat &dst,
int code,
int dstCn = 0)
‹ CV_BGR2GRAY
z BGR → グレースケール
‹ CV_BGR2HSV
z BGR → HSV
‹ CV_HSV2BGR
¾ 引数
z HSV → BGR
‹ src: 入力画像
‹ dst: 出力画像
‹ code: 変換コード
‹ dstCn: 出力画像のチャンネル数 (省略可能)
‹ CV_BGR2YCbCr
z BGR → YCbCr
‹ CV_YCbCr2BGR
z YCbCr → BGR
6
5
カラーからグレースケールへの変換
入力と出力の両方を表示
z 関数の使い方
z 入力と出力の両方を表示
// カラー画像
cv::Mat colorImage = ...;
¾ 簡単に比較できるように
// ⼊⼒画像
cv::imshow("Input", colorImage);
// グレースケール画像
cv::Mat grayImage;
// 変換
cv::cvtColor(colorImage, grayImage, CV_BGR2GRAY);
z 演習
¾ カラー画像をグレースケール画像に変換して表示するプ
ログラムを作成せよ
7
// 出⼒画像
cv::imshow("Output", grayImage);
ウィンドウ タイトル
画像
8
グレースケール画像のエッジ抽出
グレースケール画像のエッジ抽出
z 概要
z Laplacian
¾ グレースケール画像のエッジを抽出
元画像
void Laplacian(
// ⼊⼒画像
const Mat& src,
// 出⼒画像
Mat& dst,
// ビット深度
int ddepth,
// アパーチャサイズ。正の奇数 (省略可能)
int ksize=1,
// スケール ファクタ (省略可能)
double scale=1,
// 結果に⾜される値 (省略可能)
double delta=0,
// ピクセル外挿⼿法 (省略可能)
int borderType=BORDER_DEFAULT)
エッジ
抽出
9
10
グレースケール画像のエッジ抽出
グレースケール画像のエッジ抽出
z Sobel
z 使用例
void Sobel(
// ⼊⼒画像
const Mat& src,
// 出⼒画像
Mat& dst,
// 出⼒画像のビット深度
int ddepth,
// xに関する微分の次数
int xorder,
// yに関する微分の次数
int yorder,
// 拡張Sobelカーネルのサイズ (省略可能)
int ksize=3,
// スケール ファクタ (省略可能)
double scale=1,
// 結果に⾜される値 (省略可能)
double delta=0,
// ピクセル外挿⼿法 (省略可能)
int borderType=BORDER_DEFAULT)
¾ Laplacian
cv::Laplacian(grayImage, edgeImage, CV_8U, 3);
¾ Sobel
cv::Sobel(grayImage, edgeImage, CV_8U, 1, 0);
z 演習
¾ グレースケール画像のエッジを抽出して表示するプ
ログラムを作成せよ
‹ Laplacian と Sobel 両方を試すこと
11
12
ぼかし
ぼかし
z 概要
z blur
¾ ぼかす
元画像
void blur(
// ⼊⼒画像
const Mat& src,
// 出⼒画像
Mat& dst,
// 平滑化カーネルサイズ
Size ksize,
// アンカー点 (省略可能)
Point anchor=Point(‐1, ‐1),
// 画像外ピクセルの外挿モード (省略可能)
int borderType=BORDER_DEFAULT)
ぼかされた画像
処理
13
14
ぼかし
動画ファイルのグレースケールへの変換
z 使用例
z 演習
¾ 動画ファイル (.avi) をグレースケールに変換しながら
表示するプログラムを作成せよ
cv::blur(originalImage,
blurredImage,
cv::Size(7, 7));
z 演習
¾ 画像をぼかして表示するプログラムを作成せよ
‹ カラー画像とグレースケール画像両方で試すこと
15
16
カメラの動画のグレースケールへの変換
メモリ上に画像を作成
z 演習
z メモリ上に画像の領域を作成する
¾ カメラからの動画をグレースケールに変換しながら
表示するプログラムを作成せよ
¾ メモリにアクセスして画像を作成・加工・検査
¾ 新しい画像を作成するには、まずメモリ領域を確保
する必要
¾ 作成時には主に以下のパラメータを指定
‹ 大きさ
‹ ビット深度
‹ チャンネル数
17
18
cv::Mat クラス
作成時の引数
z 画像 (や行列) を表すクラス
z 作成時の引数は
z 画像領域を確保するには
cv::Mat image;
…
image.create(cv::Size(640, 480), CV_8UC3);
¾ 変数作成と同時にコンストラクタで作成する
cv::Mat image(cv::Size(640, 480), CV_8UC3);
¾ 変数作成後に作成・再作成する
画像の大きさ (ピクセル数)
(縦幅, 横幅)
cv::Mat image;
…
image.create(cv::Size(640, 480), CV_8UC3);
19
ビット数
型
チャンネル数
20
ビット数と型とチャンネル数
ビット数・型・チャンネル数の指定
z ビット数 (深度)
z 組み合わせて指定
¾ 色の値を表すために何ビット使うか
‹ ビット数が大きいほど正確・多くのメモリ領域が必要
¾ 例: 8 ビット、16 ビット、32 ビット
prefix
ビット数
型
チャンネル数
CV_
8
16
32
64
U
S
F
C1
C2
C3
C4
z型
¾ 整数型
‹ 符号なし (負数なし) → U
‹ 符号あり (負数あり) → S
¾ 浮動小数点数型 → F
z チャンネル数
¾ グレースケールなら 1 つ、カラー画像なら 3 つなど
ビット数×チャンネル数 = bits per pixel (bpp)
21
よく使われるもの
22
CV_8UC1 のメモリ上の画像表現
z CV_8UC3
ゴミ バイト
4 ‐ (w mod 4) 個
¾ 各 8 ビットの RGB カラー画像など (24 bpp)
1 ピクセル
z CV_8UC1
0 1 2 3 4 5
¾ 二値画像やグレースケール画像 (8 bpp)
w‐6 w‐4 w‐2
w‐5 w‐3 w‐1
0
1
2
...
これらの情報は
•メモリ領域取得時に何バイト取得するか
•ライブラリの画像処理関数側が、指定された画像がどのような
形式で表現されているか/どのような形式で出力するか
などを決定するために使われる
h ‐ 1
23
24
メモリ上の画像表現の詳細
CV_8UC3 のメモリ上の画像表現
ゴミ バイト
4 ‐ ((w * 3) mod 4) 個
1 ピクセル
0
1
...
w ‐ 2
z ピクセルの並び順
¾ トップ ダウン (top-down)
‹ 左上から 1 行ずつ右下に向かって並ぶ
‹ 一般的な画像処理はこっち
w ‐ 1
¾ ボトム アップ (bottom-up)
0
‹ 右下から 1 行ずつ右上に向かって並ぶ
‹ BMP ファイルや Windows 上ではこっちが多い
1
z RGB 色空間の場合
¾ 逆順に BGR と並んでいる
2
...
z 1 行ずつ終端にゴミのバイトが付く場合がある
¾ 1 行のバイト数が 4 で割り切れないとき、4 で割り切れ
るようにするために付けられる
h ‐ 1
25
画像の大きさの取得方法
CV_8UC4 のメモリ上の画像表現
z幅
32 bpp などの画像の場合、4 で絶対に割り切れるのでゴミのバイトがない
(ので楽 & 高速)
1 ピクセル
0
1
...
w ‐ 2
26
¾ cols メンバ変数
w ‐ 1
z 高さ
0
¾ rows メンバ変数
1
cv::Mat image;
…
int pixels = image.cols * image.rows;
2
...
h ‐ 1
27
28
ピクセルへのアクセス
ピクセルへのアクセス
z ptr メンバ関数 (メソッド)
ptr()
¾ ピクセル データへのポインタを取得
または
ptr(0)
‹ uchar 型 (8 ビット符号なし整数型) のポインタ
‹ このポインタを使ってピクセルを読み書き
¾ ptr 関数に引数を…
‹ 入れなければ、画像の先頭画素へのポインタを取得
‹ 整数の引数を入れると、その行の先頭画素へのポインタ
を取得
ptr(1)
ptr(2)
29
30
ポインタについて
実際のポインタの使い方
z メモリのアドレスを指し示すもの
z ポインタの前に * を付けることで、そのアドレス
の値を読み書き可能
z ポインタに対して整数の加減算を行うと、その分
だけ指し示すメモリのアドレスが移動
z 全ての画素値を 0 にする
31
¾ (CV_8UC1 の場合)
for (int y = 0; y < image.rows; ++y) {
// y ⾏⽬の先頭画素へのポインタを取得
uchar *p = image.ptr(y);
for (int x = 0; x < image.cols; ++x) {
// ポインタが指し⽰すアドレスに 0 を書き込み
*p = 0;
// 1 を⾜して次の画素を指し⽰すようにする
p = p + 1;
}
}
32
実際のポインタの使い方
別の書き方
z 全ての画素値を 0 にする
z 以下のように書けるので
*(p + n)
¾ (CV_8UC3 の RGB 画像の場合)
for (int y = 0; y < image.rows; ++y) {
// y ⾏⽬の先頭画素へのポインタを取得
uchar *p = image.ptr(y);
for (int x = 0; x < image.cols; ++x) {
// それぞれの RGB 値を 0 にする
*p = 0; p = p + 1; // B
*p = 0; p = p + 1; // G
*p = 0; p = p + 1; // R
}
}
p[n]
for (int y = 0; y < image.rows; ++y) {
// y ⾏⽬の先頭画素へのポインタを取得
uchar *p = image.ptr(y);
for (int x = 0; x < image.cols; ++x) {
// それぞれの RGB 値を 0 にする
p[2] = 0; // R
p[1] = 0; // G
p[0] = 0; // B
p += 3;
}
}
33
34
別の例
演習: グレースケール化
z 画像を加工して別領域に書き込む
z 画像をグレースケール化して表示するプログラ
ムを作成せよ
for (int y = 0; y < srcImage.rows; ++y) {
const uchar *srcp = srcImage.ptr(y);
uchar *dstp = dstImage.ptr(y);
for (int x = 0; x < srcImage.cols;
++x, srcp += 3, dstp += 3) {
dstp[2] = 255 ‐ srcp[2];
dstp[1] = 255 ‐ srcp[1];
dstp[0] = 255 ‐ srcp[0];
}
}
¾ 変換処理は前述のポインタを使って作成すること
z 「グレースケール化」…と言っても
¾ いくつかアルゴリズムが存在する
35
36
RGB の合計を 3 で割る方法
中間値を使う方法
z (R + G + B) / 3
z (max(R, G, B) + min(R, G, B)) / 2
カラー画像
グレースケール画像
カラー画像
グレースケール画像
37
38
係数 (NTSC) を使う方法
ポスタライズ (posterization)
z R * 0.298912 + G * 0.586611 +
B * 0.114478
z 減色処理
カラー画像
¾ 色を減らす (16,777,216 色 → 16 色など)
グレースケール画像
元画像
39
減色された画像
40
演習
ポインタを使った画素処理の詳細
z 画像をポスタライズして表示するプログラムを作
成せよ
z cv::Mat から得られる
情報
¾ ポインタを使った画素処理を用いて作成すること
先頭画素への
ポインタ: ptr()
1 行分のバイト数: step [byte]
1 行分の画素数 (幅): cols [px]
z 動画 (AVI) ファイルをグレースケール化して表示
するプログラムを作成せよ
¾ ポインタを使った画素処理を用いて作成すること
z カメラからの画像をグレースケール化して表示
するプログラムを作成せよ
行数 (高さ): rows
¾ ポインタを使った画素処理を用いて作成すること
41
チャンネル数: channels()
ポインタを使った画素処理の詳細
ポインタを使った画素処理の詳細
z 「任意位置の画素へのアクセス処理」の一般化
z 先程の式を利用した、全画素へのアクセス
¾ cv::Mat を img、画素の位置を x, y とすると
uchar *p = img.ptr() +
img.step * y +
img.channels() * x;
¾ ptr 関数に行数 (y) を入れて、行頭の画素へのポイ
ンタを得ることもできるので…
uchar *p = img.ptr(y) +
img.channels() * x;
43
42
for (int y = 0; y < img.rows; ++y) {
for (int x = 0; x < img.cols; ++x) {
uchar *p = img.ptr() +
img.step * y +
img.channels() * x;
*(p + 2) = 0;
*(p + 1) = 0;
*(p + 0) = 0;
}
}
44
ポインタを使った画素処理の詳細
ポインタを使った画素処理の詳細
z 少し簡略化 & 高速化を考えてみると
z x 位置の算出で掛け算 (×3) をしたくないなら
¾ 代わりに画素ごとに + 3 していく
for (int y = 0; y < img.rows; ++y) {
uchar *p = img.ptr(y);
for (int x = 0; x < img.cols; ++x) {
*(p + 3 * x + 2) = 0;
*(p + 3 * x + 1) = 0;
*(p + 3 * x + 0) = 0;
}
}
for (int y = 0; y < img.rows; ++y) {
uchar *p = img.ptr(y);
for (int x = 0; x < img.cols; ++x, p += 3) {
*(p + 2) = 0;
*(p + 1) = 0;
*(p + 0) = 0;
}
}
45
46
ポインタを使った処理の更なる例
キーボードからの入力
z 画素ごとに処理しない
z int cv::waitKey(int delay = 0)
¾ 全画素の RGB を 0 にするだけなら、データを 0 で埋
めればよい
uchar *p
for (int
i <
p[i]
}
¾ キーボードからの入力を待つ
¾ 引数: delay
‹ 入力を待つ時間 (ミリ秒)
‹ デフォルトは 0
‹ 0 の場合 (デフォルト)、入力があるまで永遠に待つ
= img.ptr();
i = 0;
img.rows * img.cols; ++i) {
= 0;
¾ 戻り値は入力されたキーボードの文字
‹ 何も入力されなかったら ‐1
¾ 注意点
‹ 何か画像が表示 (cv::imshow) されていないと機能しない
‹ 画像のウィンドウにフォーカスを当てて入力
47
48
キーボードからの入力
画像ファイルの保存
z 特定のキーの入力に反応する例
z bool cv::imwrite(
const std::string &filename,
const cv::Mat &img)
¾ 画像を指定されたファイル名として保存する
¾ 引数: filename
if (cv::waitKey() == 'a') {
// ...
}
‹ ファイル名
¾ 引数: img
‹ 画像
z例
cv::imwrite("hoge.bmp", img);
49
50
演習
キー入力文字に応じた多方向分岐
z 演習(カメラ入力のファイル保存)
z 文字ごとに if文を用いるとプログラムが複雑に
z switch(‐case)文が便利
switch文からの脱出
¾ カメラからの入力画像を表示し、キーボードから特定のキー
(例えば文字`s’)が押されたら、その時の画像をファイル(例
えば“hoge.bmp”)に保存するプログラムを作成せよ
¾ なお,キーボードから文字`e’または`q’が押されたらプログラ
ム終了せよ
flagc = cv::waitKey(33);
switch (flagc) {
これが無いと、下に
そのまま進む
case ‘a’: 文字aに対する処理内容; break;
case ‘b’: 文字bに対する処理内容; break;
case ‘d’:
case ‘D’: ...; break;
default : ...; end(0);
}
51
何も記述しないと下に進む
即ち、’d’ または ‘D’ の処理
の記述を1回で済ませられる
プログラムの(正常)終了
cv::waitKey() はキー入力が無い時は ‐1 を返す事に注意
52
演習
演習(カメラ入力のファイル保存)の擬似コード
// 入力画像を保存するプログラム
// Input: カメラ入力,キーボード
// Outpu:カメラ入力画像,保存画像
// キー入力=
// s ... 入力画像をファイルに保存
// e または q ... プログラムの終了
main{
InImg宣言; // 入力画像
SaveImg宣言; // 保存画像
cap(0)宣言; // カメラ
cap >> InImg;
// カメラから1フレーム取得
SaveImg生成; // InImgの画像サイズ情報を利用
for(;;){ // プログラム終了まで繰り返し
cap >> InImg; // カメラから1フレーム取得
char = cv::waitKey(33);
// キー入力を33msec待つ
switch (char){ // 入力文字により多方向分岐
case 's': InImg を SaveImg にコピー;
SaveImg を
ファイル"hoge.bmp" に保存;
case 'e' または 'q': プログラム終了;
} // end switch
InImg をウィンドウに表示;
最後に保存された SaveImg を
ウィンドウに表示;
}
z 演習(カメラ入力フレーム間の差分表示)
¾ カメラからの入力画像を表示し、キーボードから特定のキー
(例えば文字`s’)が押されたら、前回押されたときの画像と入
力画像との差分を表示するプログラムを作成せよ
¾ 差分は前回画像を a,入力画像を b,差分画像を c とする時
,c(青)=|b(青)-a(青) |, c(緑)=|b(緑)-a(緑) |, c(赤
)=|b(赤)-a(赤) |と定義する
¾ 表示は「入力画像」,「前回画像」,「差分画像」の3つ
¾ なお,キーボードから文字`s’以外が押されたらプログラム終
了せよ
}
53
演習(入力フレーム間の差分表示)の擬似コード
// 入力画像を保存するプログラム
// Input: カメラ入力,キーボード
// Output:カメラ入力画像,
前回画像,差分画像
// キー入力=
// s ... 入力画像をファイルに保存
// s以外 ... プログラムの終了
main{
InImg宣言; // 入力画像
SaveImg宣言; // 前回画像
DiffImg宣言; // 差分画像
cap(0)宣言; // カメラ
cap >> InImg;
// カメラから1フレーム取得
SaveImg生成; DiffImg生成; // InImgの画像サイズを利用
for(;;){ cap >> InImg; char = cv::waitKey(33);
switch (char){ case 's': InImg を SaveImg にコピー;
case ‘s’ 以外: プログラム終了;
} // end switch
for(各行){
for(各列){
DiffImg(青)=|InImg(青)‐SaveImg(青)|;
DiffImg(緑)=|InImg(緑)‐SaveImg(緑)|;
DiffImg(赤)=|InImg(赤)‐SaveImg(赤)|;
}
} // end for of 各行
InImg をウィンドウに表示;
SaveImg をウィンドウに表示;
DiffImg をウィンドウに表示;
} // end for(;;)
}
55
54