【Visual Studio】OpenCVで画像の部分処理（ROI）

#はじめに
######※これは、VC++ Visual Studioを利用する初心者向けの学習用記事です。
今回はVisual Studioを利用し、画像処理ライブラリOpenCVを用いて、画像の一部分を処理するROI関数を説明する。
OpenCV 4.0.0を利用しています。

#OpenCVでの画像の部分処理
OpenCVで画像の部分処理方法は、以下の関数を利用する。

```cpp:
cv::Mat::Mat(const Mat &m, const Rect &roi);
```
ROIは、Region of Interestの略ですね。対象領域と考えるとわかりやすいですね。

```cpp:画像の部分処理・切り出し
cv::Mat roi_src(src, cv::Rect(30, 30, 50, 50));
//新しく、「roi_src」というMat型を宣言
//srcは、部分処理・切り出しを行う元画像
//cv::Rectによって領域を元画像左上を(0, 0)として、座標(30, 30)を左上として、横に50(右方向),縦に50(下方向)切り出すように指定

cv::Mat roi_src;									//先に変数を宣言
roi_src = cv::Mat(src, cv::Rect(30, 30, 50, 50));	//上部と似たような形での宣言
roi_src = src(cv::Rect(30, 30, 50, 50));			//省略した記述方法
```
どれも結果は同様になります。

**注意点として、**
**ROIは、「元画像のSRCの画像を参照しているだけ」ということを忘れないでください。**
**つまり、元画像が変更されればROI画像も変更されます。また、ROI画像が変更されれば元画像も変更されます。**

この対策として、用意されているのが、clone関数です。

```cpp:
Mat cv::Mat::clone();
```
この関数は配列データをコピーし新しい配列を作成してくれます。
これにより、元画像の参照をなくし、新しい変数配列として宣言が可能です。

```cpp:配列コピーについて
cv::Mat roi_clone = src(cv::Rect(30, 30, 50, 50)).clone();
//新しく、「roi_clone」というMat型を宣言
//srcは、部分処理・切り出しを行う元画像
//cv::Rectによって領域を元画像左上を(0, 0)として、座標(30, 30)を左上として、横に50(右方向),縦に50(下方向)切り出すように指定
//clone()で配列をコピーし、roi_cloneに格納する。

cv::Mat roi_clone;											//先に変数を宣言
roi_clone = cv::Mat(src, cv::Rect(30, 30, 50, 50)).clone();	//上部と似たような形での宣言
roi_clone = src(cv::Rect(30, 30, 50, 50)).clone();			//省略した記述方法
```

##画像の部分処理・切り出しのプログラム
```cpp:Roi_Test1.cpp
#include <opencv2/opencv.hpp>

#ifdef _DEBUG
#pragma comment(lib, "opencv_world400d.lib")
#else
#pragma comment(lib, "opencv_world400.lib")
#endif

using namespace std;
using namespace cv;

int main()
{
    Mat src;

    src = Mat(Size(640, 480), CV_8UC3, Scalar(0, 0, 0));                //640x480の３チャンネル黒画像を用意する。
    //src = imread("src.png");                                          //画像を読み込むのであればimreadを利用する。

    Mat roi_src(src, Rect(50, 50, 400, 300));                           //配列コピーなし（メモリ参照）で切り取り            
    Mat roi_clone;                                                      //先に変数を宣言
    roi_clone = Mat(src, cv::Rect(50, 50, 400, 300)).clone();           //配列コピーありで切り取り

    //わかりやすくするために処理を入れます。
    rectangle(src, Rect(100, 100, 50, 200), Scalar(0, 0, 255), -1);     //元画像の一部を赤くする
    rectangle(roi_src, Rect(0, 0, 50, 200), Scalar(255, 0, 0), -1);     //メモリ参照しているROI画像の一部を青くする
    rectangle(roi_clone, Rect(0, 0, 50, 200), Scalar(0, 255, 0), -1);   //配列コピーしているROI画像の一部を緑色にする
    
    imshow("src", src);                                                 //元画像表示
    imshow("roi_src", roi_src);                                         //配列コピーなし（メモリ参照）画像表示
    imshow("roi_clone", roi_clone);                                     //配列コピ画像表示

    waitKey();

    return 0;
}
```
簡単に、元画像、メモリ参照している画像としていない画像の３枚と用意し、それぞれにrectangle関数で長方形を描画するようなプログラムを組んでみました。
実行してみて理解を深めていただければ幸いです。


#ROIに関係する関数について
メモリ参照しているROI画像の方では、どの部分を参照しているのかROIの座標を取得することができ、参照している画像の全体サイズも取得できます。また、その範囲を変更することも可能です。ROIしている領域のサイズが欲しいと思った方は、ROI画像の入ったMat型のrowsとcolsを取得してください。

あまり使う人もいなそうなので関数とサンプルプログラムだけ載せておきますね。


```cpp:ROI指定領域の座標や大きさの確認方法（メモリ参照方式のみ）
void cv::Mat::locateROI(Size &wholeSize, Point &ofs);	
```

```cpp:ROI指定領域の変更方法（メモリ参照方式のみ）
Mat& cv::Mat::adjustROI	(int dtop,int dbottom,int dleft,int dright);		
```

```cpp:指定領域の座標や大きさの確認と変更方法
roi_src.locateROI(wholesize, ofs);
//roi_src メモリ参照して領域を切り出しているMat型
//whilesize このSize型に、参照している画像全体の横幅と縦幅が入ります。
//ofs オフセット。Point型で、領域の左上座標が入ります。

roi_src.adjustROI(50, 100, 50, 100);
//roi_src メモリ参照して領域を切り出しているMat型
//数値については、左から「上」「下」「左」「右」への拡大数を指定します。
//今回は、上左へは50ずつ、右下へ100ずつ拡大しています。
//上と左を拡大するとオフセットの座標も変更されます。
```

##指定領域の座標や大きさの確認と変更方法のプログラム
```cpp:Roi_Test2.cpp
#include <opencv2/opencv.hpp>

#ifdef _DEBUG
#pragma comment(lib, "opencv_world400d.lib")
#else
#pragma comment(lib, "opencv_world400.lib")
#endif

using namespace std;
using namespace cv;

int main()
{
    Mat src;

    src = Mat(Size(640, 480), CV_8UC3, Scalar(0, 0, 0));                //640x480の３チャンネル黒画像を用意する。
    //src = imread("src.png");                                          //画像を読み込むのであればimreadを利用する。

    Mat roi_src(src, Rect(100, 100, 50, 50));                           //配列コピーなし（メモリ参照）で切り取り            
   
    Size wholesize;                                                     //サイズ保管用変数
    Point ofs;                                                          //オフセット（座標）保管用変数

    roi_src.locateROI(wholesize, ofs);                                  //座標取得
    cout << "切り出しのまま" << endl;
    cout << "roi_src Offset: " << ofs.x << "," << ofs.y << endl;
    cout << "roi_src Whole Size: " << wholesize.width << "x" << wholesize.height << endl;
    imshow("roi_src(変更前)", roi_src);                                 //配列コピーなし（メモリ参照）画像表示

    roi_src.adjustROI(50, 100, 50, 100);                                //領域変更 上に50,下に100,右に50,左に100拡大

    roi_src.locateROI(wholesize, ofs);                                  //座標取得
    cout << endl << "変更後" << endl;
    cout << "roi_src Offset: " << ofs.x << "," << ofs.y << endl;
    cout << "roi_src Whole Size: " << wholesize.width << "x" << wholesize.height << endl;
    imshow("src", src);                                                 //元画像表示
    imshow("roi_src(変更後)", roi_src);                                 //配列コピーなし（メモリ参照）画像表示

    waitKey();

    return 0;
}
```

```bash:出力結果
切り出しのまま
roi_src Offset: 100,100
roi_src Whole Size: 640x480

変更後
roi_src Offset: 50,50
roi_src Whole Size: 640x480
```

#注意点
ROIでは、画像サイズに対して、範囲を超える領域を指定するとAssertion failedが起きます。
元画像の大きさを把握し、その範囲に収まる領域を指定するようにしてください。
Rectに設定する値はすべて１以上でなければいけません。

#さいごに
今回はVisual Studioを利用し、画像処理ライブラリOpenCVを用いて画像の一部分を処理するROI関数の説明を行った。
特徴点検出などでオブジェクト検出した際に、自動的にその部分をROIで切り出し、処理するなど、
様々な応用が考えられますので、使ってみてください。