【OpenCV/C++】cv::canny()等で得たエッジをブロック化する 

# はじめに
cv::canny等で得たエッジは、物体の輪郭となる場合がありますが、
複数の物体がある場合に、エッジをブロック化して、
物体毎の大まかな範囲を得て、物体検出の一助になるようにします。
同一物体で複数のブロックをまたぐものは無いとすれば、
処理する範囲を限定することが出来ます。
今回は二値化マスク画像を結果とします。

# ブロック化手順

ブロック化手順はシンプルです。
エッジが見つかった場合は、行と列がブロックサイズの白の矩形で塗りつぶします。
同一ブロック内に、ほかのエッジが見つかっても既に塗りつぶされている事にします。

# 実装の手順
　今回の実装は以下の様になります。
　
1. 画像の取り込み
2. カラー画像をグレースケール画像に変換
3. Gaussian Blurをかける。
4. Cannyなどでエッジを検出する。
5. 輪郭を探して位置情報をためておく。
6. エッジのブロック化関数(makeBlockMaskFromEdge)を呼び出す。
7. 結果を表示する。

入力画像はOpenCVのサンプルの「basketball1.png」とします。

![basketball1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2883019/c045bce5-805d-497f-1738-0941d1c7dcc4.png)

# ソースコード

```C++:main.cpp

#include <opencv2/opencv.hpp>
#include <stdio.h>


//エッジからブロック化したマスクを作成する
bool makeBlockMaskFromEdge(const cv::Mat& edge_mat,cv::Mat& mask_mat,std::vector<cv::Rect>& edge_rects,int block_size);


int main(int argc, char* argv[])
{
	//グレイスケールとして読み込み
	cv::Mat sample_mat = cv::imread("basketball1.png",0);
	// 平滑化を行います．これがないと誤検出が起こりやすくなります．
	cv::GaussianBlur(sample_mat,sample_mat,cv::Size(9,9),2,2);
	//canny法でエッジ検出
	cv::Mat edges;
	cv::Canny(sample_mat,edges,150,200);
	//輪郭を得る
	std::vector<std::vector<cv::Point>> contours;
	std::vector<cv::Rect> edge_rects;
	cv::findContours(edges,contours,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_NONE);
	for(int idx = 0;idx < contours.size();idx++)
	{
		//面積チェック
		double area = cv::contourArea(contours[idx]);
		if(area < 1)
		{
			continue;
		}
		//点群に外接する傾いていない矩形を求めます
		cv::Rect R = cv::boundingRect(contours[idx]);
		//輪郭Rect追加
		edge_rects.push_back(R);
	}
	//エッジからブロック化したマスクを作成する
	cv::Mat mask_mat;
	makeBlockMaskFromEdge(edges,mask_mat,edge_rects,32);

	//結果表示
	cv::imshow("basketball1",sample_mat);
	cv::imshow("edges",edges);
	cv::imshow("mask_mat",mask_mat);
	cv::waitKey(0);

	return 0;
}

//---------------------------------------------------------------------------
//エッジからブロック化したマスクを作成する
//---------------------------------------------------------------------------
bool makeBlockMaskFromEdge(const cv::Mat& edge_mat,cv::Mat& mask_mat,std::vector<cv::Rect>& edge_rects,int block_size)
{
	//マスクの作成
	mask_mat = cv::Mat::zeros(edge_mat.size(),CV_8UC1);

	//全てのエッジを検索
	for(int idx = 0;idx < edge_rects.size();idx++)
	{
		//エッジ情報取得
		cv::Rect& edge_rect = edge_rects[idx];
		//エッジの領域
		cv::Rect  div_edge_rect;

		//ブロックを考慮した新しい左上座標
		div_edge_rect.x      = block_size * (edge_rect.x/block_size);
		div_edge_rect.y      = block_size * (edge_rect.y/block_size);
		//ブロックを考慮した新しい幅と高さ
		int new_w = edge_rect.width  + (edge_rect.x - div_edge_rect.x);
		int new_h = edge_rect.height + (edge_rect.y - div_edge_rect.y);

		div_edge_rect.width  = block_size * (1 + new_w /block_size);
		div_edge_rect.height = block_size * (1 + new_h/block_size);

		if(div_edge_rect.x + div_edge_rect.width > edge_mat.cols)
		{
			div_edge_rect.width = edge_mat.cols - div_edge_rect.x;
		}
		if(div_edge_rect.y + div_edge_rect.height > edge_mat.rows)
		{
			div_edge_rect.height = edge_mat.rows - div_edge_rect.y;
		}
		if(div_edge_rect.width < 1 || div_edge_rect.height < 1)
		{
			continue;
		}

		//エッジ画像の切り出し
		cv::Mat   div_edge_mat  = cv::Mat(edge_mat,div_edge_rect);
		//マスク画像の切り出し
		cv::Mat   div_mask_mat  = cv::Mat(mask_mat,div_edge_rect);
		//エッジを最後まで検索
		cv::Mat        temp_mat    = div_edge_mat.clone();
		int            all_sz      = temp_mat.cols * temp_mat.rows;
		unsigned char *p_msk_start = mask_mat.ptr<unsigned char>(0);
		unsigned char *p_edg_start = temp_mat.ptr<unsigned char>(0);
		unsigned char *p_edg_pos   = p_edg_start;

		while(true)
		{
			//エッジを探す
			int sz = all_sz - static_cast<int>(p_edg_pos - p_edg_start);
			unsigned char *p_next_edg_pos = static_cast<unsigned char *>(std::memchr(p_edg_pos,255,sz));
			//無い場合は終了
			if(p_next_edg_pos == nullptr)
			{
				break;
			}
			//見つかった座標から処理する範囲をセット
			int df = static_cast<int>(p_next_edg_pos - p_edg_start);
			int x  = df%temp_mat.step;
			int y  = df/temp_mat.step;
			int l  = block_size * (x/block_size);
			int t  = block_size * (y/block_size);
			int w  = block_size;
			int h  = block_size;

			cv::Rect range_rect(l,t,w,h);
			//マスクの更新
			cv::rectangle(div_mask_mat,range_rect,cv::Scalar(255),-1);
			//使用エッジの消去
			cv::rectangle(temp_mat,range_rect,cv::Scalar(0),-1);

			//次の検索位置
			p_edg_pos = p_next_edg_pos;
		}
	}

	return true;
}
```
# 動作結果

1. cv::Canny()によるエッジ化画像

しきい値の操作をすると検出されるエッジが変化します。

![edges.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2883019/0462189c-7981-11bb-e935-29e9f6ceac58.png)


2. ブロック化画像

一つの物体で一つのブロックになるのが理想です。
ブロックサイズを変えて見ると、結果が変化します。

![block.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2883019/4b0bb17a-5fcc-578b-e89a-14b050962415.png)


以上.