KANIKIG

KANIKIG

just for fun | 兴趣使然 Ph.D. in Engineering|❤️ #NFT $ETH| [Twitter](https://twitter.com/kanikig2)|[Github](https://github.com/KANIKIG)|[Telegram channel](https://t.me/kanikigtech)

OpenCVの基本ノート

image

概要:OpenCV の紹介と環境#

OpenCV の紹介#

  • opencv コンピュータビジョンオープンソースライブラリ、アルゴリズムは画像処理と機械学習に関わる。
  • Intel 社が貢献し、ロシアのエンジニアが大部分の C/C++ コードを提供。
  • BSD ライセンス、商用利用が無料。
  • SDK は Java、Python、IOS、Android をサポート。

OpenCV フレームワーク#

iShot2020-09-2402.26.26.png

サンプルコード#

#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);

画像の混合#

線形混合理論#

iShot2020-09-2402.22.30.png

関連 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、出力画像
*/

明るさとコントラストの調整#

理論#

iShot2020-09-2402.22.16.png

サンプル#

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#

原理#

iShot2020-09-2403.32.36.png

iShot2020-09-2403.32.43.png

iShot2020-09-2403.32.48.png

サンプル#

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#

中値フィルタ#

iShot2020-09-2404.20.14.png

双方向フィルタ#

iShot2020-09-2404.23.30.png

サンプル#

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より大きく、奇数でなければならない。
*/

膨張と腐食#

iShot2020-09-2803.40.02.png

腐食#

iShot2020-09-2803.40.14.png

膨張#

iShot2020-09-2803.40.09.png

サンプル#

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#

iShot2020-09-2804.12.31.png

閉じる close#

iShot2020-09-2804.12.46.png

形態学的勾配 Morphological Gradient#

iShot2020-09-2804.23.08.png

トップハット top hat#

iShot2020-09-2804.23.13.png

ブラックハット black hat#

iShot2020-09-2804.23.19.png

サンプル#

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
*/

形態学操作 水平垂直線の抽出#

原理#

iShot2020-09-2902.29.04.png

iShot2020-09-2902.29.10.png

iShot2020-09-2902.29.15.png

iShot2020-09-2902.29.22.png

実装の考え方#

iShot2020-09-2902.29.32.png

サンプル#

//二値化
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);

画像のアップサンプリングとダウンサンプリング#

画像ピラミッド#

iShot2020-09-2903.07.24.png

iShot2020-09-2903.08.01.png

iShot2020-09-2903.07.36.png

iShot2020-09-2903.07.49.png

サンプル#

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);

基本的な閾値操作#

閾値の概念#

iShot2020-09-2903.25.07.png

閾値の種類#

iShot2020-09-2903.25.56.png

iShot2020-09-2903.26.08.png

iShot2020-09-2903.26.14.png

iShot2020-09-2903.26.46.png

iShot2020-09-2903.26.52.png

iShot2020-09-2903.27.06.png

サンプル#

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
};

カスタム線形フィルタ#

畳み込みの概念#

iShot2020-09-2904.00.29.png

iShot2020-09-2904.00.43.png

iShot2020-09-2904.02.52.png

iShot2020-09-2904.02.15.png

一般的な演算子#

iShot2020-09-2904.03.02.png

カスタム畳み込みぼかし#

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);

エッジ処理#

畳み込みエッジ問題#

iShot2020-09-2904.13.20.png

エッジの処理#

iShot2020-09-2904.13.34.png

サンプル#

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 演算子#

畳み込み応用 - エッジ抽出#

iShot2020-10-0204.39.29.png

iShot2020-10-0204.39.51.png

iShot2020-10-0204.40.04.png

iShot2020-10-0204.40.15.png

iShot2020-10-0204.49.30.png

サンプル#

Sobel (
InputArray Src // 入力画像
OutputArray dst// 出力画像、サイズは入力画像と一致
int depth // 出力画像深度. 
Int dx.  // X方向、何階の導関数
int dy // Y方向、何階の導関数. 
int ksize, SOBEL演算子のカーネルサイズ、1357のいずれか
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 演算子#

理論#

iShot2020-10-0308.21.04.png

iShot2020-10-0308.21.20.png

サンプル#

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 エッジ検出#

アルゴリズムの紹介#

iShot2020-10-0308.33.22.png

iShot2020-10-0308.33.30.png

iShot2020-10-0308.33.36.png

iShot2020-10-0308.40.36.png

サンプル#

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);

ホフ変換 - 直線検出#

理論#

iShot2020-10-0308.48.47.png

iShot2020-10-0308.49.31.png

iShot2020-10-0308.49.25.png

iShot2020-10-0308.50.04.md.png

補足資料:https://blog.csdn.net/leonardohaig/article/details/87907462?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.edu_weight&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.edu_weight

サンプル#

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);
}

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。