写真からバクテリアコロニーなどの円形オブジェクトをカウントするアプリケーションを開発しています。
簡単にできるのは、オブジェクトが一般的に背景からはっきりと区別できるという事実です。
ただし、分析を難しくする問題はほとんどありません。
- バックグラウンドは、緩やかな強度変化と急速な強度変化を示します。
- コンテナの端では、オブジェクトは円形ではなく楕円形になります。
- オブジェクトのエッジがかなりぼやけていることがあります。
- オブジェクトがクラスター化します。
- オブジェクトは非常に小さい場合があります (直径 6 ピクセル)
- 最終的に、アルゴリズムは (GUI を介して) 画像解析を深く理解していない人々によって使用されるため、パラメータは直感的で非常に少ないものでなければなりません。
この問題は科学文献で何度も取り上げられており、たとえば循環ハフ変換や分水界アプローチを使用して「解決」されていますが、結果に満足したことは一度もありません。
説明された 1 つの簡単なアプローチは、適応しきい値処理によって前景を取得し、(この記事で説明したように) 距離変換を使用してクラスター化されたオブジェクトを分割することです。
この方法をうまく実装できましたが、強度の突然の変化に常に対応できるとは限りませんでした。また、私は同僚から、より「斬新な」アプローチを提案するよう求められました。
したがって、前景を抽出する新しい方法を探していました。
したがって、他のしきい値処理/ブロブ検出方法を調査しました。MSER を試してみましたが、私の場合はあまり堅牢ではなく、非常に遅いことがわかりました。
私は最終的に、これまでのところ優れた結果をもたらすアルゴリズムを思いつきました。
- 画像の 3 つのチャネルを分割し、それらのノイズ (ブラー/メディアン ブラー) を減らします。各チャネルについて:
- 元のチャネルと畳み込み (大きなカーネル ブラーによる) チャネルとの絶対差を計算することにより、適応しきい値処理の最初のステップの手動実装を適用します。次に、すべての関連するしきい値の値について:
- 2)の結果にしきい値を適用します
- 輪郭を見つける
- 輪郭の形状 (サイズ、面積、凸面など) を許可して、輪郭を有効または無効にします。
- 次に、有効な連続領域 (輪郭で区切られた領域) のみがアキュムレータ (チャネルごとに 1 つのアキュムレータ) に再描画されます。
- しきい値の値を超える連続領域を蓄積した後、「領域のスコア」のマップになります。強度が最も高い領域は、形態フィルタ基準を最も頻繁に満たした領域です。
- 次に、3 つのマップ (チャネルごとに 1 つ) がグレースケールに変換され、しきい値が設定されます (しきい値はユーザーが制御します)。
作業する必要がある画像の種類を示すために:
この写真は、上部に 3 つのサンプル画像の一部を表し、下部にそれぞれの部分の私のアルゴリズム (青 = 前景) の結果を表しています。
これが私の C++ 実装です: 3-7
/*
* cv::Mat dst[3] is the result of the absolute difference between original and convolved channel.
* MCF(std::vector<cv::Point>, int, int) is a filter function that returns an positive int only if the input contour is valid.
*/
/* Allocate 3 matrices (1 per channel)*/
cv::Mat accu[3];
/* We define the maximal threshold to be tried as half of the absolute maximal value in each channel*/
int maxBGR[3];
for(unsigned int i=0; i<3;i++){
double min, max;
cv::minMaxLoc(dst[i],&min,&max);
maxBGR[i] = max/2;
/* In addition, we fill accumulators by zeros*/
accu[i]=cv::Mat(compos[0].rows,compos[0].cols,CV_8U,cv::Scalar(0));
}
/* This loops are intended to be multithreaded using
#pragma omp parallel for collapse(2) schedule(dynamic)
For each channel */
for(unsigned int i=0; i<3;i++){
/* For each value of threshold (m_step can be > 1 in order to save time)*/
for(int j=0;j<maxBGR[i] ;j += m_step ){
/* Temporary matrix*/
cv::Mat tmp;
std::vector<std::vector<cv::Point> > contours;
/* Thresholds dst by j*/
cv::threshold(dst[i],tmp, j, 255, cv::THRESH_BINARY);
/* Finds continous regions*/
cv::findContours(tmp, contours, CV_RETR_LIST, CV_CHAIN_APPROX_TC89_L1);
if(contours.size() > 0){
/* Tests each contours*/
for(unsigned int k=0;k<contours.size();k++){
int valid = MCF(contours[k],m_minRad,m_maxRad);
if(valid>0){
/* I found that redrawing was very much faster if the given contour was copied in a smaller container.
* I do not really understand why though. For instance,
cv::drawContours(miniTmp,contours,k,cv::Scalar(1),-1,8,cv::noArray(), INT_MAX, cv::Point(-rect.x,-rect.y));
is slower especially if contours is very long.
*/
std::vector<std::vector<cv::Point> > tpv(1);
std::copy(contours.begin()+k, contours.begin()+k+1, tpv.begin());
/* We make a Roi here*/
cv::Rect rect = cv::boundingRect(tpv[0]);
cv::Mat miniTmp(rect.height,rect.width,CV_8U,cv::Scalar(0));
cv::drawContours(miniTmp,tpv,0,cv::Scalar(1),-1,8,cv::noArray(), INT_MAX, cv::Point(-rect.x,-rect.y));
accu[i](rect) = miniTmp + accu[i](rect);
}
}
}
}
}
/* Make the global scoreMap*/
cv::merge(accu,3,scoreMap);
/* Conditional noise removal*/
if(m_minRad>2)
cv::medianBlur(scoreMap,scoreMap,3);
cvtColor(scoreMap,scoreMap,CV_BGR2GRAY);
2 つの質問があります。
そのような前景抽出アプローチの名前は何ですか?この場合にそれを使用するのが不適切な理由はありますか?
等高線を再帰的に見つけて描画することは非常に負荷がかかるため、アルゴリズムを高速化したいと考えています。この目標を達成する方法を教えてください。
助けてくれてどうもありがとう、