バックグラウンド
メディアンフィルタリング、最大値フィルタリング、最小値フィルタリングは並べ替えフィルタリングに属し、画像のノイズ除去処理でよく使用されます。
最大値/最小値フィルタリングの処理は比較的理解しやすく、ウィンドウ内の各数値を 1 つずつ比較し、それぞれの比較で、属するタスクに応じて最大値または最小値が保持されます。スライディングウィンドウを3*3とすると、ウィンドウ内の9個の数値を8回比較し、最大値/最小値フィルタリングの結果を得ることができます。
メディアン フィルタリングは、その名前が示すように、ウィンドウ内の数値の中央値をフィルタリングの結果として取得することを指します。最適化の実装アイデアを考慮しない場合、ウィンドウ内のすべての要素をソートし、その中間値を取得することになります。ソートアルゴリズムの時間計算量は nlog(n)、つまり n* です。 log(n) の比較が必要です。n=9 の場合、すべてのデータを並べ替えるには少なくとも 27 回の比較が必要です。
実際、メディアン フィルターはウィンドウ内の中央値さえわかればよく、すべてのデータを並べ替える必要はなく、このように考慮すると比較の回数を減らすことができます。
opencv ソースコードの解釈
opencv のメディアン フィルターのソース コード パスは、 modules/imgproc/src/smooth.cpp です。ソースコードは最後に添付します。
Opencv のメディアン フィルター実装は、3*3、5*5 フィルター サイズをサポートします。メディアン フィルタリングは、並べ替えネットワークを定義することによって実装されます。
ソート ネットワークの設計思想は、2 種類のコンパレータを定義することです。1 つは OP で、OP オペレータは 2 つの数値 a と b が渡されることを認識し、OP コンパレータを通過した後、a と b の大きい方の数が返されます。 b は b に割り当てられ、小さい方が b に割り当てられます。番号は a に割り当てられます。もう 1 つのコンパレータは VOP です。VOP は受信 va を指します。vb は同じ要素を持つベクトルです。OP と同じです。ベクトルは 1 つずつ比較され、大きい数は vb に格納され、小数は va に格納されます。この種の VOP は、コンピュータがハードウェア アクセラレーションをサポートしている場合、useSIMD のスイッチがオンになり、CPU 命令セットがタスクの進行を加速するために使用されることを意味します。
以下の図に示すように、ソース コードの核心は次のようにコンパレータを定義することであり、ウィンドウ サイズが 3*3 の場合、中央値を取得するために 19 回の比較のみが実行されます。なぜこのようなことが可能なのでしょうか? ここでアルゴリズムの考え方が使われます。
単純化されたアルゴリズムの問題は、9 つの数値が与えられた場合に、最小限の比較回数で 9 つの数値の中央値を取得する方法です。
比較の回数に関する三分法を理解する
https://www.cnblogs.com/olivermahout/p/9346353.html では、三分割法を使用する原則が紹介されており、各行を比較し、一連の比較を通じて最終結果を取得します。9 つの数値の中央値の場合、21 個のペアワイズ コンパレータのみが使用されます。
以下に示すように
1. 比較の最初のラウンド、9 回。1 は 1 行目の 3 つの数値を並べ替えて比較することを意味し、A2 は 2 行目の 3 つの数値を並べ替えて比較することを、A3 は 3 行目の 3 つの数値を並べ替えて比較することを意味します。
2. 2 回目の比較、9 回。B1 は A1、A2、A3 の最大値を集め、B2 は A1、A2、A3 の中間値を集め、B3 は A1、A2、A3 の最小値を集めます。B1、B2、B3 の 3 つの数値を比較します。
3. 3 回目の比較、3 回目。C は、B1、B2、B3 の最小値、中央値、最大値をそれぞれ集計します。Cで3つの値の大小を比較し、9つの数値の中央値である中央値を出力します。
ここで、なぜ B1、B2、B3 は A1、A2、A3 の比較結果をこのように解釈するのか、また C はなぜ B1、B2、B3 の比較結果をこのように解釈するのか、という疑問が生じるでしょう。なぜこの設計でこれら 9 つの数値の中央値が得られるのでしょうか? 以下、opencv のコード設計と合わせて説明します。
比較の数についての OpenCV 設計の理解
opencv コンパレーターでは、上記の 3 点法よりも比較が 2 つ少なく、9 つの数値のペアごとの比較を 19 回実行することで中央値を取得できます。しかし、実際の考え方は同じです。最適化のポイントは、B1 と B3 の比較が 1 つ少ないことです。
opencv の全体的な考え方は、4 ラウンドの比較を実行することです (追記: 無関係な数値の比較は並行して実行されるように設計できます)。
最初のラウンドでは、3 行の数字と各行の 3 つの数字を並べ替えます。
2 番目のラウンドでは、9 つの数値のうち 2 つが中央値である可能性が除外されます。
3 番目のラウンドでは、残りの 7 つの数値のうち 4 つが中央値である可能性が除外されます。
4 番目のラウンドでは、残りの 3 つの数値を比較し、2 つを除外し、中央値を見つけます。
その理由は次のとおりです。
前提を理解してください:
n は奇数であり、n 個の数値の中央値が取られ、ある数値が (n+1)/2 数より大きいか、または (n+1)/2 数より小さいと判断される場合、その数値は成立しません。中央値になります。
op(a,b) の定義: a と b のアドレスを渡し、a と b の 2 つのアドレスの数値を比較し、大きい方の数値をアドレス b に置き、小さい方の数値をアドレス a に置きます。
第1轮比较。这一轮比较,完成了3行数据,每行内的大小排列,即此时p0≤p1≤p2,p3≤p4≤p5,p6≤p7≤p8
op(p1, p2); op(p4, p5); op(p7, p8);
op(p0, p1);op(p3, p4); op(p6, p7);
op(p1, p2); op(p4, p5);op(p7, p8);
第2轮比较。这一轮比较,可以把p0, p8排除是中值的可能。
op(p0, p3); op(p5, p8);
P0: p0至少小于5个数,所以可排除可能。
P8: p0至少大于5个数,所以可排除可能。
第3轮比较。这一轮比较:把P1, P3, P5, P7排除是中值的可能。
op(p4, p7);op(p3, p6);op(p2, p5);
op(p1, p4);
op(p4, p7);
P1: 在前2轮比较中,P2, P5至少大于P1,P4,P7这三个数的两个,这一轮中P1又是P1,P4,P7三个数中的最小数,所以P1小于至少4个数(P2, P5,P4,P7)。排除P1是中值的可能。
P3: 在前2轮比较中,P3小于P4,P5或者P1, P2,这一轮中,P3又是P3, P6,P7中的最小值,所以P3至少小于4个数(P6, P7,(P4,P5)或(P1,P2))。排除P3是中值的可能。
P5: 在前2轮比较中,P5大于P4,P3或者P6, P7,而P2大于P1,至此,P2,P5至少大于3个数了。 这一轮中,P5又是P2, P5中的最大值,所以P5至少大于4个数(P1, P2,(P4,P3)或(P6,P7))。排除P5是中值的可能。
P7: 在前2轮比较中,至少P1,P4,P7这三个数的两个大于P3, P6,这一轮中P7又是P1,P4,P7三个数中的最大数,所以P7大于至少4个数(P1, P3,P4,P6)。排除P7是中值的可能。
第4轮比较。这一轮比较中,3个数找到中间值,需要进行2次比较。得到P4就是中值。
op(p4, p2); op(p6, p4); op(p4, p2);
最后
对于窗口是5*5的中值滤波,比较次数更多,但是实现思想和3*3是一样的。
怎么样,可能这就是算法优化的极致了吧,能少比较1次也是好的。*^ ^*
Opencv源码
放上opencv的源码,不用再辛苦去找了。
template<class Op, class VecOp>
static void
medianBlur_SortNet( const Mat& _src, Mat& _dst, int m )
{
typedef typename Op::value_type T;
typedef typename Op::arg_type WT;
typedef typename VecOp::arg_type VT;
const T* src = (const T*)_src.data;
T* dst = (T*)_dst.data;
int sstep = (int)(_src.step/sizeof(T));
int dstep = (int)(_dst.step/sizeof(T));
Size size = _dst.size();
int i, j, k, cn = _src.channels();
Op op;
VecOp vop;
volatile bool useSIMD = checkHardwareSupport(CV_CPU_SSE2);
if( m == 3 )
{
if( size.width == 1 || size.height == 1 )
{
int len = size.width + size.height - 1;
int sdelta = size.height == 1 ? cn : sstep;
int sdelta0 = size.height == 1 ? 0 : sstep - cn;
int ddelta = size.height == 1 ? cn : dstep;
for( i = 0; i < len; i++, src += sdelta0, dst += ddelta )
for( j = 0; j < cn; j++, src++ )
{
WT p0 = src[i > 0 ? -sdelta : 0];
WT p1 = src[0];
WT p2 = src[i < len - 1 ? sdelta : 0];
op(p0, p1); op(p1, p2); op(p0, p1);
dst[j] = (T)p1;
}
return;
}
size.width *= cn;
for( i = 0; i < size.height; i++, dst += dstep )
{
const T* row0 = src + std::max(i - 1, 0)*sstep;
const T* row1 = src + i*sstep;
const T* row2 = src + std::min(i + 1, size.height-1)*sstep;
int limit = useSIMD ? cn : size.width;
for(j = 0;; )
{
for( ; j < limit; j++ )
{
int j0 = j >= cn ? j - cn : j;
int j2 = j < size.width - cn ? j + cn : j;
WT p0 = row0[j0], p1 = row0[j], p2 = row0[j2];
WT p3 = row1[j0], p4 = row1[j], p5 = row1[j2];
WT p6 = row2[j0], p7 = row2[j], p8 = row2[j2];
op(p1, p2); op(p4, p5); op(p7, p8); op(p0, p1);
op(p3, p4); op(p6, p7); op(p1, p2); op(p4, p5);
op(p7, p8); op(p0, p3); op(p5, p8); op(p4, p7);
op(p3, p6); op(p1, p4); op(p2, p5); op(p4, p7);
op(p4, p2); op(p6, p4); op(p4, p2);
dst[j] = (T)p4;
}
if( limit == size.width )
break;
for( ; j <= size.width - VecOp::SIZE - cn; j += VecOp::SIZE )
{
VT p0 = vop.load(row0+j-cn), p1 = vop.load(row0+j), p2 = vop.load(row0+j+cn);
VT p3 = vop.load(row1+j-cn), p4 = vop.load(row1+j), p5 = vop.load(row1+j+cn);
VT p6 = vop.load(row2+j-cn), p7 = vop.load(row2+j), p8 = vop.load(row2+j+cn);
vop(p1, p2); vop(p4, p5); vop(p7, p8); vop(p0, p1);
vop(p3, p4); vop(p6, p7); vop(p1, p2); vop(p4, p5);
vop(p7, p8); vop(p0, p3); vop(p5, p8); vop(p4, p7);
vop(p3, p6); vop(p1, p4); vop(p2, p5); vop(p4, p7);
vop(p4, p2); vop(p6, p4); vop(p4, p2);
vop.store(dst+j, p4);
}
limit = size.width;
}
}
}
else if( m == 5 )
{
if( size.width == 1 || size.height == 1 )
{
int len = size.width + size.height - 1;
int sdelta = size.height == 1 ? cn : sstep;
int sdelta0 = size.height == 1 ? 0 : sstep - cn;
int ddelta = size.height == 1 ? cn : dstep;
for( i = 0; i < len; i++, src += sdelta0, dst += ddelta )
for( j = 0; j < cn; j++, src++ )
{
int i1 = i > 0 ? -sdelta : 0;
int i0 = i > 1 ? -sdelta*2 : i1;
int i3 = i < len-1 ? sdelta : 0;
int i4 = i < len-2 ? sdelta*2 : i3;
WT p0 = src[i0], p1 = src[i1], p2 = src[0], p3 = src[i3], p4 = src[i4];
op(p0, p1); op(p3, p4); op(p2, p3); op(p3, p4); op(p0, p2);
op(p2, p4); op(p1, p3); op(p1, p2);
dst[j] = (T)p2;
}
return;
}
size.width *= cn;
for( i = 0; i < size.height; i++, dst += dstep )
{
const T* row[5];
row[0] = src + std::max(i - 2, 0)*sstep;
row[1] = src + std::max(i - 1, 0)*sstep;
row[2] = src + i*sstep;
row[3] = src + std::min(i + 1, size.height-1)*sstep;
row[4] = src + std::min(i + 2, size.height-1)*sstep;
int limit = useSIMD ? cn*2 : size.width;
for(j = 0;; )
{
for( ; j < limit; j++ )
{
WT p[25];
int j1 = j >= cn ? j - cn : j;
int j0 = j >= cn*2 ? j - cn*2 : j1;
int j3 = j < size.width - cn ? j + cn : j;
int j4 = j < size.width - cn*2 ? j + cn*2 : j3;
for( k = 0; k < 5; k++ )
{
const T* rowk = row[k];
p[k*5] = rowk[j0]; p[k*5+1] = rowk[j1];
p[k*5+2] = rowk[j]; p[k*5+3] = rowk[j3];
p[k*5+4] = rowk[j4];
}
op(p[1], p[2]); op(p[0], p[1]); op(p[1], p[2]); op(p[4], p[5]); op(p[3], p[4]);
op(p[4], p[5]); op(p[0], p[3]); op(p[2], p[5]); op(p[2], p[3]); op(p[1], p[4]);
op(p[1], p[2]); op(p[3], p[4]); op(p[7], p[8]); op(p[6], p[7]); op(p[7], p[8]);
op(p[10], p[11]); op(p[9], p[10]); op(p[10], p[11]); op(p[6], p[9]); op(p[8], p[11]);
op(p[8], p[9]); op(p[7], p[10]); op(p[7], p[8]); op(p[9], p[10]); op(p[0], p[6]);
op(p[4], p[10]); op(p[4], p[6]); op(p[2], p[8]); op(p[2], p[4]); op(p[6], p[8]);
op(p[1], p[7]); op(p[5], p[11]); op(p[5], p[7]); op(p[3], p[9]); op(p[3], p[5]);
op(p[7], p[9]); op(p[1], p[2]); op(p[3], p[4]); op(p[5], p[6]); op(p[7], p[8]);
op(p[9], p[10]); op(p[13], p[14]); op(p[12], p[13]); op(p[13], p[14]); op(p[16], p[17]);
op(p[15], p[16]); op(p[16], p[17]); op(p[12], p[15]); op(p[14], p[17]); op(p[14], p[15]);
op(p[13], p[16]); op(p[13], p[14]); op(p[15], p[16]); op(p[19], p[20]); op(p[18], p[19]);
op(p[19], p[20]); op(p[21], p[22]); op(p[23], p[24]); op(p[21], p[23]); op(p[22], p[24]);
op(p[22], p[23]); op(p[18], p[21]); op(p[20], p[23]); op(p[20], p[21]); op(p[19], p[22]);
op(p[22], p[24]); op(p[19], p[20]); op(p[21], p[22]); op(p[23], p[24]); op(p[12], p[18]);
op(p[16], p[22]); op(p[16], p[18]); op(p[14], p[20]); op(p[20], p[24]); op(p[14], p[16]);
op(p[18], p[20]); op(p[22], p[24]); op(p[13], p[19]); op(p[17], p[23]); op(p[17], p[19]);
op(p[15], p[21]); op(p[15], p[17]); op(p[19], p[21]); op(p[13], p[14]); op(p[15], p[16]);
op(p[17], p[18]); op(p[19], p[20]); op(p[21], p[22]); op(p[23], p[24]); op(p[0], p[12]);
op(p[8], p[20]); op(p[8], p[12]); op(p[4], p[16]); op(p[16], p[24]); op(p[12], p[16]);
op(p[2], p[14]); op(p[10], p[22]); op(p[10], p[14]); op(p[6], p[18]); op(p[6], p[10]);
op(p[10], p[12]); op(p[1], p[13]); op(p[9], p[21]); op(p[9], p[13]); op(p[5], p[17]);
op(p[13], p[17]); op(p[3], p[15]); op(p[11], p[23]); op(p[11], p[15]); op(p[7], p[19]);
op(p[7], p[11]); op(p[11], p[13]); op(p[11], p[12]);
dst[j] = (T)p[12];
}
if( limit == size.width )
break;
for( ; j <= size.width - VecOp::SIZE - cn*2; j += VecOp::SIZE )
{
VT p[25];
for( k = 0; k < 5; k++ )
{
const T* rowk = row[k];
p[k*5] = vop.load(rowk+j-cn*2); p[k*5+1] = vop.load(rowk+j-cn);
p[k*5+2] = vop.load(rowk+j); p[k*5+3] = vop.load(rowk+j+cn);
p[k*5+4] = vop.load(rowk+j+cn*2);
}
vop(p[1], p[2]); vop(p[0], p[1]); vop(p[1], p[2]); vop(p[4], p[5]); vop(p[3], p[4]);
vop(p[4], p[5]); vop(p[0], p[3]); vop(p[2], p[5]); vop(p[2], p[3]); vop(p[1], p[4]);
vop(p[1], p[2]); vop(p[3], p[4]); vop(p[7], p[8]); vop(p[6], p[7]); vop(p[7], p[8]);
vop(p[10], p[11]); vop(p[9], p[10]); vop(p[10], p[11]); vop(p[6], p[9]); vop(p[8], p[11]);
vop(p[8], p[9]); vop(p[7], p[10]); vop(p[7], p[8]); vop(p[9], p[10]); vop(p[0], p[6]);
vop(p[4], p[10]); vop(p[4], p[6]); vop(p[2], p[8]); vop(p[2], p[4]); vop(p[6], p[8]);
vop(p[1], p[7]); vop(p[5], p[11]); vop(p[5], p[7]); vop(p[3], p[9]); vop(p[3], p[5]);
vop(p[7], p[9]); vop(p[1], p[2]); vop(p[3], p[4]); vop(p[5], p[6]); vop(p[7], p[8]);
vop(p[9], p[10]); vop(p[13], p[14]); vop(p[12], p[13]); vop(p[13], p[14]); vop(p[16], p[17]);
vop(p[15], p[16]); vop(p[16], p[17]); vop(p[12], p[15]); vop(p[14], p[17]); vop(p[14], p[15]);
vop(p[13], p[16]); vop(p[13], p[14]); vop(p[15], p[16]); vop(p[19], p[20]); vop(p[18], p[19]);
vop(p[19], p[20]); vop(p[21], p[22]); vop(p[23], p[24]); vop(p[21], p[23]); vop(p[22], p[24]);
vop(p[22], p[23]); vop(p[18], p[21]); vop(p[20], p[23]); vop(p[20], p[21]); vop(p[19], p[22]);
vop(p[22], p[24]); vop(p[19], p[20]); vop(p[21], p[22]); vop(p[23], p[24]); vop(p[12], p[18]);
vop(p[16], p[22]); vop(p[16], p[18]); vop(p[14], p[20]); vop(p[20], p[24]); vop(p[14], p[16]);
vop(p[18], p[20]); vop(p[22], p[24]); vop(p[13], p[19]); vop(p[17], p[23]); vop(p[17], p[19]);
vop(p[15], p[21]); vop(p[15], p[17]); vop(p[19], p[21]); vop(p[13], p[14]); vop(p[15], p[16]);
vop(p[17], p[18]); vop(p[19], p[20]); vop(p[21], p[22]); vop(p[23], p[24]); vop(p[0], p[12]);
vop(p[8], p[20]); vop(p[8], p[12]); vop(p[4], p[16]); vop(p[16], p[24]); vop(p[12], p[16]);
vop(p[2], p[14]); vop(p[10], p[22]); vop(p[10], p[14]); vop(p[6], p[18]); vop(p[6], p[10]);
vop(p[10], p[12]); vop(p[1], p[13]); vop(p[9], p[21]); vop(p[9], p[13]); vop(p[5], p[17]);
vop(p[13], p[17]); vop(p[3], p[15]); vop(p[11], p[23]); vop(p[11], p[15]); vop(p[7], p[19]);
vop(p[7], p[11]); vop(p[11], p[13]); vop(p[11], p[12]);
vop.store(dst+j, p[12]);
}
limit = size.width;
}
}
}
}
}