概要:OpenCV の紹介と環境#
OpenCV の紹介#
- opencv コンピュータビジョンオープンソースライブラリ、アルゴリズムは画像処理と機械学習に関わる。
- Intel 社が貢献し、ロシアのエンジニアが大部分の C/C++ コードを提供。
- BSD ライセンス、商用利用が無料。
- SDK は Java、Python、IOS、Android をサポート。
OpenCV フレームワーク#
サンプルコード#
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main() {
Mat srcImage = imread("/Users/kanikig/Documents/CProjects/test/test.jpg");
if (!srcImage.data) {
std::cout << "画像が読み込まれませんでした";
return -1;
}
imshow("[img]", srcImage);
waitKey(0);
return 0;
}
画像の読み込み、変更、保存#
色空間:
- ビットマップ
- グレースケール
- RGB 真彩色
- CMYK
- HSV
- YUV
画像の読み込み#
Mat srcImage = imread(filename, int flags)
flags =
IMREAD_UNCHANGED (<0) は元の画像を変更せずに読み込むことを示す
IMREAD_GRAYSCALE (0) は元の画像をグレースケール画像として読み込むことを示す
IMREAD_COLOR (>0) は元の画像をRGB画像として読み込むことを示す(デフォルト)
画像の表示#
namedWindow("ウィンドウ1", CV_WINDOW_NORMAL);
/*
パラメータ1:ウィンドウの名前
パラメータ2:ウィンドウタイプ、CV_WINDOW_AUTOSIZE の場合、ウィンドウサイズは画像サイズと等しくなり、サイズを変更できない。
CV_WINDOW_NORMAL の場合、ウィンドウは自由にサイズを変更できる。
*/
imshow("ウィンドウ1", srcImage); //「ウィンドウ1」で画像を出力。
画像の変更#
Mat gray_image;
cvtColor(image, gray_image, COLOR_RGB2GRAY);
画像の保存#
imwrite(filename, Input_img, param);
/*
指定されたディレクトリパスに画像ファイルを保存
8ビット、16ビットのPNG、JPG、Tiffファイル形式で、単一チャネルまたは三チャネルのBGB画像のみがこの方法で保存可能
PNG形式で保存する際は透明チャネルの画像を保存可能
圧縮パラメータを指定できる
*/
行列のマスク操作#
ピクセルポインタの取得#
//画像の深さを確認し、falseの場合はエラー
CV_Assert(myImage.depth() == CV_8U);
//現在の行ポインタを取得(行は0から始まる)
const uchar* current= Image.ptr<uchar>(row);
//現在のピクセル点のピクセル値を取得
p(row,col) =current[col];
//ピクセル範囲処理、RGBが0-255の範囲内であることを保証
saturate_cast<ucahr>();
マスク操作#
//マスクを定義
Mat kernel = (Mat_<char>(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
//マスク操作
filter2D(src, dst, src.depth(), kernel);
//プログラムの計時
t = (double)getTickCount();
t = ((double)getTickCount() - t)/getTickFrequency();
Mat オブジェクト#
Mat オブジェクトの使用#
//コンストラクタ
Mat dst;
dst = Mat(int rows, int cols, int type);
dst = Mat(Size size, int type);
//等価
dst.create(Size size, int type);
//最初の2つのパラメータはそれぞれ行(row)と列(column)を示し、3番目のCV_8UC3の8は各チャネルが8ビットであることを示し、Uは符号なし、CはChar型、3はチャネル数が3であることを示し、4番目のパラメータは各ピクセル値の初期化を示すベクトルで、ベクトルの長さはチャネル数と一致する
dst = Mat M(2,2,CV_8UC3, Scalar(0,0,255))
dst = Scalar(127, 0, 255); //画像を単一のグレースケールと色に設定
//一般的なメソッド
Image.cols;
Image.rows;
Image.copyTo(mat);
Image.clone();
Image.convertTo(Mat dst, int type);
Image.channels(); //RGB=3, グレースケール=1
Image.depth(); //通常は-1
Image.empty();
uchar* ptr(row);
Mat dst = src.clone();
src.copyTo(dst);
Mat 定義配列#
Mat C = (Mat_<double>(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
eye(int rows, int cols, int type); //単位行列を生成
画像操作#
画像の読み書き#
imread();
imwrite();
ピクセルの読み書き#
int px = Image.at<uchar>(row, col);
ピクセルの変更#
int height = image.rows;
int width = image.cols;
int channels = image.channels();
printf("height=%d width=%d channels=%d", height, width, channels);
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
if (channels == 3) {
image.at<Vec3b>(row, col)[0] = 0; // 青
image.at<Vec3b>(row, col)[1] = 0; // 緑
}
}
}
//Vec3bは三チャネルの順序が青、緑、赤のuchar型データ。Vec3fは三チャネルのfloat型データ
//CV_8UC1からCV32F1への変換は以下のように実現する:src.convertTo(dst, CV_32F);
//反転
bitwise_not(src, dst);
画像の混合#
線形混合理論#
関連 API#
AddWeighted( const CvArr* src1, double alpha,const CvArr* src2, double beta,double gamma, CvArr* dst );
/*
パラメータ1:src1、第1の元の配列
パラメータ2:alpha、第1の配列要素の重み
パラメータ3:src2第2の元の配列
パラメータ4:beta、第2の配列要素の重み
パラメータ5:gamma、画像1と画像2の和に追加される数値(デフォルト0)。大きすぎないように、そうでないと画像が真っ白になる。合計が255以上になると純白になる。
パラメータ6:dst、出力画像
*/
明るさとコントラストの調整#
理論#
サンプル#
Mat new_image = Mat::zeros( image.size(), image.type() ); //元の画像と同じサイズとタイプの空白画像を作成、ピクセル値は0で初期化
saturate_cast<uchar>(value) //値の範囲を0~255の間に確保
Mat.at<Vec3b>(y,x)[index]=value //各ピクセル点の各チャネルに値を設定
int height = image.rows;
int width = image.cols;
double alpha = 1.2;
double beta = 50;
output = Mat::zeros(image.size(), image.type());
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
output.at<Vec3b>(row, col)[0] = saturate_cast<uchar>(alpha * image.at<Vec3b>(row, col)[0] + beta); //青
output.at<Vec3b>(row, col)[1] = saturate_cast<uchar>(alpha * image.at<Vec3b>(row, col)[1] + beta); //緑
output.at<Vec3b>(row, col)[2] = saturate_cast<uchar>(alpha * image.at<Vec3b>(row, col)[2] + beta); //赤
}
}
}
形状と文字の描画#
Point と Scalar#
Point p;
p.x = 10;
p.y = 8;
//等価
p = Point(10,8);
Scalar(B, G, R);
形状の描画#
(LINE_4, LINE_8, LINE_AA) //AAはアンチエイリアス
line(frame, beginPoint, endPoint, Scalar(0, 0, 255), 2); //開始点はbeginPoint, 終点はendPoint, 色は赤、線幅は2、shiftはデフォルト値
Rect rect = Rect(200, 100, 300, 300); // 開始xy、幅高さ
rectangle(Image, rect, color, 2, LINE_8);
eclipse(Image, Point(Image.cols/2, Image.rows/2), Size, 90, 0, 360, color, 2);
circle(central point, radius, color, 2);
//多角形
Point pts[1][5];
pts[0][0] = Point(100,100);
fillpoly(Image, in, out, 1, color, 8);
文字#
putText(
cv::Mat& img, // 描画する画像
const string& text, // 描画する文字
cv::Point origin, // テキストボックスの左下隅
int fontFace, // フォント (例:cv::FONT_HERSHEY_PLAIN)
double fontScale, // サイズ因子、値が大きいほど文字が大きくなる
cv::Scalar color, // 線の色(RGB)
int thickness = 1, // 線の幅
int lineType = 8, // 線型(4近傍または8近傍、デフォルトは8近傍)
bool bottomLeftOrigin = false // true='原点は左下'
);
ランダム描画#
RNG rng(12345);
Point pt1;
Point pt2;
for (int i =0; i < 1000; i++){
pt1.x = rng.uniform(0, Image.cols);
pt1.y = rng.uniform(0, Image.rows);
pt2.x = rng.uniform(0, Image.cols);
pt2.y = rng.uniform(0, Image.rows);
Scalar color = Scalar(rng.uniform(0,255), rng.uniform(0,255), rng.uniform(0,255));
if (wairKey(50) > 0){
break; //50秒ごとにループ、キーが押されるまで
}
line(Image, pt1, pt2, color, 1, 8);
}
画像のぼかし 1#
原理#
サンプル#
blur(Mat src, Mat dst, Size(xradius, yradius), Point(-1,-1));
GaussianBlur(Mat src, Mat dst, Size(11, 11), sigmax, sigmay); //Size(x, y), x, y は正数で奇数でなければならない
画像のぼかし 2#
中値フィルタ#
双方向フィルタ#
サンプル#
medianBlur(Mat src, Mat dest, ksize);
bilateralFilter(src, dest, 15, 100, 3);
/* 15 –計算の半径d、半径内のピクセルが計算に含まれる、-1を指定するとsigma spaceパラメータの値に基づく
100 – sigma color は、どの程度の差異内のピクセルが計算されるかを決定
3 – sigma space dの値が0より大きい場合は無効、そうでない場合はdの値を計算する
中値ぼかしのksizeサイズは1より大きく、奇数でなければならない。
*/
膨張と腐食#
腐食#
膨張#
サンプル#
kernel = getStructuringElement(int shape, Size ksize, Point anchor);
/*
- 形状 (MORPH_RECT \MORPH_CROSS \MORPH_ELLIPSE)
- サイズ 奇数 Size(1,1);
- アンカー点 デフォルトはPoint(-1, -1)で中心ピクセルを意味する
*/
dilate(src, dst, kernel);
erode(src, dst, kernel);
//構造要素のサイズを動的に調整
createTrackbar(const String & trackbarname, const String winName, int* value, int count, Trackbarcallback func, void* userdata=0);
/*
形式パラメータ一、trackbarname:スライドスペースの名前;
形式パラメータ二、winname:スライドスペースが依存する画像ウィンドウの名前;
形式パラメータ三、value:初期値;
形式パラメータ四、count:スライドコントロールのスケール範囲;
形式パラメータ五、TrackbarCallbackはコールバック関数で、以下のように定義される:
*/
void (CV_CDECL *TrackbarCallback)(int pos, void* userdata);
デモ#
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
Mat src, dst;
char OUTPUT_WIN[] = "出力画像";
int element_size = 3;
int max_size = 21;
void CallBack_Demo(int, void*);
int main(int argc, char** argv) {
src = imread("D:/vcprojects/images/test1.png");
if (!src.data) {
printf("画像を読み込めませんでした...\n");
return -1;
}
namedWindow("入力画像", CV_WINDOW_AUTOSIZE);
imshow("入力画像", src);
namedWindow(OUTPUT_WIN, CV_WINDOW_AUTOSIZE);
//前の二つのパラメータは一致する必要がある
createTrackbar("要素サイズ :", OUTPUT_WIN, &element_size, max_size, CallBack_Demo);
CallBack_Demo(0, 0);
waitKey(0);
return 0;
}
void CallBack_Demo(int, void*) {
int s = element_size * 2 + 1;
Mat structureElement = getStructuringElement(MORPH_RECT, Size(s, s), Point(-1, -1));
// dilate(src, dst, structureElement, Point(-1, -1), 1);
erode(src, dst, structureElement);
imshow(OUTPUT_WIN, dst);
return;
}
形態学操作#
開く open#
閉じる close#
形態学的勾配 Morphological Gradient#
トップハット top hat#
ブラックハット black hat#
サンプル#
Mat kernel = getStructuringElement(MORPH_RECT, Size(11,11), Point(-1, -1));
morphologyEx(src, dst, CV_MOP_BLACKHAT, kernel);
/*
- Mat src – 入力画像
- Mat dest – 出力結果
- int OPT – CV_MOP_OPEN/ CV_MOP_CLOSE/ CV_MOP_GRADIENT / CV_MOP_TOPHAT/ CV_MOP_BLACKHAT 形態学操作の種類
Mat kernel 構造要素
int Iteration 繰り返し回数、デフォルトは1
*/
形態学操作 水平垂直線の抽出#
原理#
実装の考え方#
サンプル#
//二値化
adaptiveThreshold(
Mat src, // 入力のグレースケール画像
Mat dest, // 二値画像
double maxValue, // 二値画像の最大値
int adaptiveMethod // 自適応法、いずれか一つのみ –
// ADAPTIVE_THRESH_MEAN_C , ADAPTIVE_THRESH_GAUSSIAN_C
int thresholdType,// 閾値の種類
int blockSize, // ブロックサイズ
double C // 定数C 正数、0、負数のいずれか
);
adaptiveThreshold(~gray_src, binImg, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 15, -2);
// ~ 反転 bitwise_not(src, dst);
// 水平構造要素
Mat hline = getStructuringElement(MORPH_RECT, Size(src.cols / 16, 1), Point(-1, -1));
// 垂直構造要素
Mat vline = getStructuringElement(MORPH_RECT, Size(1, src.rows / 16), Point(-1, -1));
// 矩形構造、OCRに使用可能
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
morphologyEx(src, dst, CV_MOP_BLACKHAT, hline);
画像のアップサンプリングとダウンサンプリング#
画像ピラミッド#
サンプル#
pyrUp(Mat src, Mat dst, Size(src.cols*2, src.rows*2))
//生成された画像は元の画像の幅と高さがそれぞれ2倍に拡大される
pyrDown(Mat src, Mat dst, Size(src.cols/2, src.rows/2))
//生成された画像は元の画像の幅と高さがそれぞれ1/2に縮小される
// DOG
Mat gray_src, g1, g2, dogImg;
cvtColor(src, gray_src, CV_BGR2GRAY);
GaussianBlur(gray_src, g1, Size(5, 5), 0, 0);
GaussianBlur(g1, g2, Size(5, 5), 0, 0);
subtract(g1, g2, dogImg, Mat());
// 正規化表示
normalize(dogImg, dogImg, 255, 0, NORM_MINMAX);
imshow("DOG画像", dogImg);
基本的な閾値操作#
閾値の概念#
閾値の種類#
サンプル#
cvtColor(src, gray_src, CV_BGR2GRAY);
threshold(src, dst, 0, 255, THRESH_TRIANGLE | type_value);
//OISUとTRIANGLEは自動的に閾値を計算するため、dstの後の閾値は0と書かれても無視される
enum ThresholdTypes {
THRESH_BINARY = 0,
THRESH_BINARY_INV = 1,
THRESH_TRUNC = 2,
THRESH_TOZERO = 3,
THRESH_TOZERO_INV = 4,
THRESH_MASK = 7,
THRESH_OTSU = 8,
THRESH_TRIANGLE = 16
};
カスタム線形フィルタ#
畳み込みの概念#
一般的な演算子#
カスタム畳み込みぼかし#
filter2D(
Mat src, //入力画像
Mat dst, // ぼかし画像
int depth, // 画像深度32/8
Mat kernel, // 畳み込み核/テンプレート
Point anchor, // アンカーポジション
double delta // 計算されたピクセル+delta
)
サンプル#
// Sobel X方向
Mat kernel_x = (Mat_<int>(3, 3) << -1, 0, 1, -2,0,2,-1,0,1);
//Sobel Y方向
Mat kernel_y = (Mat_<int>(3, 3) << -1, -2, -1, 0,0,0, 1,2,1);
// ラプラス演算子
Mat kernel = (Mat_<int>(3, 3) << 0, -1, 0, -1, 4, -1, 0, -1, 0);
filter2D(src, dst, -1, kernel_x, Point(-1, -1), 0.0);
エッジ処理#
畳み込みエッジ問題#
エッジの処理#
サンプル#
copyMakeBorder(
Mat src, // 入力画像
Mat dst, // エッジを追加した画像
int top, // エッジの長さ、一般的に上下左右で同じ値を取る、
int bottom,
int left,
int right,
int borderType // エッジの種類
Scalar value
)
while(true){
c = waitKey(500);
//esc
if((char)c == 27)
{break;}
}
Sobel 演算子#
畳み込み応用 - エッジ抽出#
サンプル#
Sobel (
InputArray Src // 入力画像
OutputArray dst// 出力画像、サイズは入力画像と一致
int depth // 出力画像深度.
Int dx. // X方向、何階の導関数
int dy // Y方向、何階の導関数.
int ksize, SOBEL演算子のカーネルサイズ、1、3、5、7のいずれか
double scale = 1
double delta = 0
int borderType = BORDER_DEFAULT
)
//Sobel の改良版
Scharr (
InputArray Src // 入力画像
OutputArray dst// 出力画像、サイズは入力画像と一致
int depth // 出力画像深度.
Int dx. // X方向、何階の導関数
int dy // Y方向、何階の導関数.
double scale = 1
double delta = 0
int borderType = BORDER_DEFAULT
)
//手順
GaussianBlur( src, dst, Size(3,3), 0, 0, BORDER_DEFAULT );
cvtColor( src, gray, COLOR_RGB2GRAY );
Scharr(gray_src, xgrad, CV_16S, 1, 0);
Scharr(gray_src, ygrad, CV_16S, 0, 1);
// Sobel(gray_src, xgrad, CV_16S, 1, 0, 3);
// Sobel(gray_src, ygrad, CV_16S, 0, 1, 3);
convertScaleAbs(xgrad, xgrad);// 画像Aのピクセルの絶対値を計算し、画像Bに出力
convertScaleAbs(ygrad, ygrad);
addWeighted( xgrad, 0.5,ygrad, 0.5, 0, xygrad);
Laplacian 演算子#
理論#
サンプル#
Laplacian(
InputArray src,
OutputArray dst,
int depth, //深度CV_16S
int kisze, // 3
double scale = 1,
double delta =0.0,
int borderType = 4
)
Mat gray_src, edge_image;
GaussianBlur(src, dst, Size(3, 3), 0, 0);
cvtColor(dst, gray_src, CV_BGR2GRAY);
Laplacian(gray_src, edge_image, CV_16S, 3);
convertScaleAbs(edge_image, edge_image);
threshold(edge_image, edge_image, 0, 255, THRESH_OTSU | THRESH_BINARY);
Canny エッジ検出#
アルゴリズムの紹介#
サンプル#
Canny(
InputArray src, // 8ビットの入力画像
OutputArray edges,// 出力エッジ画像、一般的に二値画像、背景は黒
double threshold1,// 低閾値、通常は高閾値の1/2または1/3
double threshold2,// 高閾値
int aptertureSize,// Soble演算子のサイズ、通常3x3、値は3
bool L2gradient // trueを選択するとL2で正規化、そうでない場合はL1で正規化
)
cvtColor(src, gray_src, CV_BGR2GRAY);
blur(gray_src, gray_src, Size(3, 3), Point(-1, -1), BORDER_DEFAULT);
Canny(gray_src, edge_output, t1_value, t1_value * 2, 3, false);
ホフ変換 - 直線検出#
理論#
サンプル#
HoughLines(
InputArray src, // 入力画像、必ず8ビットのグレースケール画像
OutputArray lines, // 出力の極座標で表される直線
double rho, // 極座標生成時のピクセルスキャンのステップサイズ
double theta, // 極座標生成時の角度ステップ、一般的にCV_PI/180を取る
int threshold, // 閾値、十分な交点を持つ極座標点のみが直線と見なされる
double srn=0;// 多尺度ホフ変換を適用するかどうか、0を設定すると古典的ホフ変換
double stn=0;// 多尺度ホフ変換を適用するかどうか、0を設定すると古典的ホフ変換
double min_theta=0; // 角度スキャン範囲 0 ~180の間、デフォルトで良い
double max_theta=CV_PI
) // 一般的には経験豊富な開発者が使用し、平面空間に戻す必要がある
HoughLinesP(
InputArray src, // 入力画像、必ず8ビットのグレースケール画像
OutputArray lines, // 出力の極座標で表される直線
double rho, // 極座標生成時のピクセルスキャンのステップサイズ、一般的に1
double theta, //極座標生成時の角度ステップ、一般的にCV_PI/180を取る
int threshold, // 閾値、十分な交点を持つ極座標点のみが直線と見なされる
double minLineLength=0;// 最小直線長
double maxLineGap=0;// 最大間隔
)
// エッジを抽出
Canny(src, src_gray, 150, 200);
cvtColor(src_gray, dst, CV_GRAY2BGR);
imshow("エッジ画像", src_gray);
vector<Vec4f> plines;
HoughLinesP(src_gray, plines, 1, CV_PI / 180.0, 10, 0, 10);
Scalar color = Scalar(0, 0, 255);
for (size_t i = 0; i < plines.size(); i++) {
Vec4f hline = plines[i];
line(dst, Point(hline[0], hline[1]), Point(hline[2], hline[3]), color, 3, LINE_AA);
}