在阅读了两篇关于设定阈值来识别LED图像的论文后,对如何设定合适的阈值来对不同的LED-ID进行解码有了初步的认识,下面是我对这几种方法的理解及思考。
(1)三次多项式拟合
算法基于的思路非常简单,通过构造一个多项式曲线对列矩阵上点的灰度值分别作差,然后平方来消除正负,着重刻画差的大小,再全体求和得到一个以a0、a1、a2、a3为变量来描绘差的整体大小的函数E,分别对a0、a1、a2、a3求偏导并令其为0,得到令E为最小值的a0、a1、a2、a3,再将其代入原式中即得到所要求的多项式f(xi),相当于对灰度值进行了一个非线性回归,多项式在每一个xi点处的值即为该点灰度值的阈值。然而偏导为零仅为E取得最小值的必要条件,无法保证得到的多项式曲线是最优的,因此该法数学上有天然的缺陷,并且得到的曲线一般比较平缓,缺乏对快速密集信号的响应能力,在照度较低时误码率高。
(2)迭代阈值
对目标图片的像素先进行合适大小的分组(像素数应当可以被整除),每组分割过大,阈值曲线无法正确地落在灰度值的中间;分割过小,阈值曲线将接近图像的最大灰度值,均无法得到合理的阈值。分组完成以后算出每组的平均灰度值T,并将此平均灰度值T作为阈值将原来小组的灰度值再分成两组(大于和小于该均值T),然后分别计算这两个子小组的平均灰度值U1、U2,将其相加取平均得到新的灰度值Tk,用Tk的值取代上面的T并重复上述过程直到Tk=T(实际操作中难以达到且性价比不高,故设定合理的值α>0,当α>|Tk-T|时认为满足条件)。
(3)快速自适应阈值
其基本思想是计算像素灰度值的平均波动值。该算法可通过对函数的递归调用来完成,因此其在硬件上易于实现。
更快的计算方法是计算到xi处灰度值的前s个灰度值,并对其根据与xi的距离分别进行加权(1-1/s)n,离得越远影响越小,离得近的影响大。S的取值通常为所处理像素列大小的1/8。r为比例系数,可通过实验来合理选取其大小(有0.85左右的经验值)。
(4)平均极值法
该算法的核心在于“线性插值”和“阈值计算”两个部分。首先为了提高采样点,对实验中得到的像素向量进行线性插值。
然后找出其中的灰度最大值Gmax,对该灰度值两边的像素分别交替求局部极小值和局部极大值,每个相邻的极大值和极小值的平均值即为它们之间的像素点的灰度阈值Gthreshold。其中,由于要剔除可能存在的噪声对信号的影响,因此要设定一个参考值Gref来进行区分,并且目标极值Pcursor与上一个极值之间的距离应当大于一个采样率。
最后
在实际选取ROI来进行处理时,由于选取的ROI往往比LED圆形区域要大,因此很难确保ROI的宽度值/2以后的列像素落在LED区域的中央。为此,应对ROI进行适当的裁剪并对列数进行判断,若为奇数则选取中间相邻的3列,然后取分别进行处理的结果进行比对,以少数服从多数的原则进行取舍;若为偶数,则选取中间的2列,然后进行处理并遵循保护信号1的原则(当然这可能会使被噪声干扰的可能性增加),最后再将最终的处理结果输出。
下面是示例部分代码:
for (int i = 0; i < srcROI.size(); i++)
{
resizeROI(srcROI[i], srcROI[i]); //裁剪ROI,在测试里发现,由于本来框的拟合就较好这个resize意义并不是很大
}
double T = 0;
int output = 0;
vector<int>outputarray; //result array
int s = ((srcROI[0].rows / 8)+0.5); //得到s的合适值,四舍五入
select(srcROI[0], srcROI[0]);
int colnumber = 0;
if (srcROI[0].cols%2==0)
{
colnumber = 2;
}
else
{
colnumber = 3;
}
vector<vector<int>>resultarray;
int rownumber = srcROI[0].rows;
switch (colnumber)
{
case 2:
for(int c = 0; c < 2; c++)
{
//先处理前s-1个像素,这里的思路是用第s个像素的阈值来代替其阈值
singlepixelthreshold(srcROI[0], T, rownumber, s - 1, s);
for (int i = 0; i < s - 1; i++)
{
uchar* data = srcROI[0].ptr<uchar>(i);
if (data[c] > T)
{
output = 1; //默认白色为1
}
else
{
output = 0;
}
outputarray.push_back(output);
}
//处理后面的像素
for (int i = s - 1; i < rownumber; i++)
{
singlepixelthreshold(srcROI[0], T, rownumber, i, s);
uchar* data = srcROI[0].ptr<uchar>(i);
if (data[c] > T)
{
output = 1; //默认白色为1
}
else
{
output = 0;
}
outputarray.push_back(output);
}
resultarray.push_back(outputarray);
outputarray.clear();
}
for (int i = 0; i < rownumber; i++)
{//对照验证,有一个1就为1,尽可能保护1
resultarray[0][i] || resultarray[1][i] ? outputarray.push_back(1) : outputarray.push_back(0);
}
break;
case 3:
for (int c = 0; c < 3; c++)
{
//先处理前s-1个像素,这里的思路是用第s个像素的阈值来代替其阈值
singlepixelthreshold(srcROI[0], T, rownumber, s - 1, s);
for (int i = 0; i < s - 1; i++)
{
uchar* data = srcROI[0].ptr<uchar>(i);
if (data[c] > T)
{
output = 1; //默认白色为1
}
else
{
output = 0;
}
outputarray.push_back(output);
}
//处理后面的像素
for (int i = s - 1; i < rownumber; i++)
{
singlepixelthreshold(srcROI[0], T, rownumber, i, s);
uchar* data = srcROI[0].ptr<uchar>(i);
if (data[c] > T)
{
output = 1; //默认白色为1
}
else
{
output = 0;
}
outputarray.push_back(output);
}
resultarray.push_back(outputarray);
outputarray.clear();
}
int sum = 0;
for (int i = 0; i < rownumber; i++)
{//对照验证,少数服从多数
sum = resultarray[0][i] + resultarray[1][i] + resultarray[2][i];
sum >= 2 ? outputarray.push_back(1) : outputarray.push_back(0);
}
break;
}
return 0;
}
void resizeROI(Mat input, Mat &output) { //筛选出不全为0的行,作为新ROI的边界
int rownumber = input.rows;
int colnumber = input.cols;
int top, bottom; //记录行数
//TOP
for (int i = 0; i < rownumber; i++) //会损失掉一开始部分的0信号
{
int exit = 0;
uchar* data = input.ptr<uchar>(i);
for (int j = 0; j < colnumber; j++)
{
if (data[j] <= 20) { //背景并不是全黑的,所以不能取0
continue;
}
else {
exit = 1;
break;
}
}
if (exit == 1) {
top = i;
break;
}
}
//BOTTOM
for (int i = rownumber-1; i > 0; i--) //会损失掉末尾部分的0信号
{
int exit = 0;
uchar* data = input.ptr<uchar>(i);
for (int j = 0; j < colnumber; j++)
{
if (data[j] <= 20) {
continue;
}
else {
exit = 1;
break;
}
}
if (exit == 1) {
bottom = i;
break;
}
}
output = input(Rect(0, top, colnumber, bottom - top + 1)); //截图
}
void select(Mat input, Mat &output) {
int rownumber = input.rows;
int colnumber = input.cols;
if (colnumber%2==0) //整除
{
output = input(Rect((colnumber / 2) - 1, 0, 2, rownumber));
}
else {
output = input(Rect((colnumber / 2) - 1, 0, 3, rownumber));
}
}
void singlepixelthreshold(Mat input, double &output, int rownumber, int i, int s) {
double T = 0;
double f = 0;
int n = 0;
for (int j = i; j > i-s; j--)
{
uchar* data = input.ptr<uchar>(j);
T += pow((1 - 1.f / s), n)*data[0];
f += pow((1 - 1.f / s), n);
n++;
}
output = 0.88*T / f; //这里的r取0.88,得到最终的阈值
}