はじめに
cv::canny等で得たエッジは、物体の輪郭となる場合がありますが、
複数の物体がある場合に、エッジをブロック化して、
物体毎の大まかな範囲を得て、物体検出の一助になるようにします。
同一物体で複数のブロックをまたぐものは無いとすれば、
処理する範囲を限定することが出来ます。
今回は二値化マスク画像を結果とします。
ブロック化手順
ブロック化手順はシンプルです。
エッジが見つかった場合は、行と列がブロックサイズの白の矩形で塗りつぶします。
同一ブロック内に、ほかのエッジが見つかっても既に塗りつぶされている事にします。
実装の手順
今回の実装は以下の様になります。
- 画像の取り込み
- カラー画像をグレースケール画像に変換
- Gaussian Blurをかける。
- Cannyなどでエッジを検出する。
- 輪郭を探して位置情報をためておく。
- エッジのブロック化関数(makeBlockMaskFromEdge)を呼び出す。
- 結果を表示する。
入力画像はOpenCVのサンプルの「basketball1.png」とします。
ソースコード
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;
}
動作結果
- cv::Canny()によるエッジ化画像
しきい値の操作をすると検出されるエッジが変化します。
- ブロック化画像
一つの物体で一つのブロックになるのが理想です。
ブロックサイズを変えて見ると、結果が変化します。
以上.