纸上得来终觉浅,绝知此事要躬行。学会算法的理论很重要,但是把理论高效的实现也是需要一点点练习的。
图像直方图的理论很简单,就是把一个图像的像素区间划分为几个子区间,然后统计图像中的像素包含在子区间内的个数。这里,我们可以很容易想到,图像每个像素之间对结果的影响是独立的,所以,代码实现时候可以考虑并行化设计。
opencv的实现流程,以uint8编码的图像为例
1,建立0-255到bins子区间的映射table。
即table[256], index是0-255, table[index]是在histogram[bins]中的index。
opencv建立table的接口函数
static void calcHistLookupTables_8u( const Mat& hist,
const SparseMat& shist,
int dims, const float** ranges,
const double* uniranges,
bool uniform, bool issparse,
vector& _tab )
关键逻辑代码:
double t = bins/(high - low);
double a = t
double b = -t*low
for (int j=low; j
2,统计图像上0-255各像素值对应的个数。
opencv每次统计四个相邻位置的像素,好像为利用了CPU流水线加速的原理,即同一指令,不同空间可以并行执行。
opencv接口函数:
static void calcHist_8u( vector& _ptrs,
const vector& _deltas,
Size imsize, Mat& hist, int dims,
const float** _ranges,
const double* _uniranges, bool uniform )
关键逻辑代码:
int matH[256] = { 0, };
for( x = 0; x <= imsize.width - 4; x += 4 )
{
int t0 = p0[x], t1 = p0[x+1];
matH[t0]++; matH[t1]++;
t0 = p0[x+2]; t1 = p0[x+3];
matH[t0]++; matH[t1]++;
}
p0 += x;
for( ; x < imsize.width; x++, p0 += step)
matH[*p0]++;
3,合并matH和table,得到最终hist的输出。
OUT_OF_RANGE =bins;
uchar* H = hist.data;
for(int i = 0; i < 256; i++ )
{
size_t hidx = tab[i];
if( hidx < OUT_OF_RANGE )
*(int*)(H + hidx) += matH[i];
}
整体流程关系:
1,为什么要拆分成三步完成呢?这样的时间复杂度=O(256+256+h*w)
在遍历一次图像时候,为什么不同时完成hist的统计呢?可能opencv的映射查表用时更短,拆分步骤中1,2步可以同时完成。
for i in range(Height):
for j in range(width):
scale=bins/(high-low)
index=image[i][i]*scale
hist[index]+=1
2,在实现遍历图像,每个点对结果的影响又是独立的,那么软件层面如何进行并行化加速?
是不是能够分成多个线程读写image,在读取了image的像素值后,要对hist[pixes]或者matH[pixes]同一位置写数时候,加一个锁保护,即同一位置一次只能一个线程写数,只能串行,不能并行。