在机器视觉处理中,我们经常要对检测到的物体的方位特征进行评估。比如说,我们要 OCR 识别一个字符串。那么这个字符串与x轴的夹角就很重要,我们需要这个信息把这个字符串转正,然后才方便识别。
条形码识别也类似,尤其是当我们条形码不是很清晰时,首先将条形码转正,然后用各向异性的滤波器处理一下,可以让条形码变得更清晰易于读取。
这里给出一种基于统计参数的特征提取方法。这个方法已经有几十年历史了,算是个老方法,但是效果很不错,所以值得写篇文章来介绍介绍。
一片区域 R 的矩定义为:
当p 和q 都取 0 时,得到的就是这片区域的面积。也就是:
矩还可以归一化,也就是用上面的定义再除以面积 a。
表示的是这片区域的重心。可以用它来描述区域的位置。
归一化的矩回随区域在图像中的位置不同而变化,要去除这个影响,可以用中心矩,中心矩只反映区域本身的特征。
具体的方法是将这个区域当作一个椭圆区域,那么用上面5个参量就可以计算出椭圆的长短轴和旋转角度。具体公式如下:
椭圆的这几个参数的图形解释如下图:
利用这几个参数就可以确定区域的方位和尺寸了。
比如我们有下面的一幅测试图像。
用上面方法计算出的椭圆如下:
可以看出结果非常的好。尤其是旋转角度,计算的非常准确。
下面是我的测试代码,供参考。用到了些 Qt 的功能。
- #include
- #include
- #include
- #include
- #include "picturebox.h"
- #include
- QImage threshold(const QImage &image, quint8 th)
- {
- int height = image.height();
- int width = image.width();
- QImage ret(width, height, QImage::Format_Indexed8);
- ret.setColorCount(256);
- for(int i = 0; i < 256; i++)
- {
- ret.setColor(i, qRgb(i, i, i));
- }
- for(int i = 0; i < height; i ++)
- {
- const uchar *pSrc = (uchar *)image.constScanLine(i);
- uchar *pDest = (uchar *)ret.scanLine(i);
- for( int j = 0; j < width; j ++)
- {
- pDest[j] = (pSrc[j] > th)? 255: 0;
- }
- }
- return ret;
- }
- QImage toGray( const QImage &image )
- {
- int height = image.height();
- int width = image.width();
- QImage ret(width, height, QImage::Format_Indexed8);
- ret.setColorCount(256);
- for(int i = 0; i < 256; i++)
- {
- ret.setColor(i, qRgb(i, i, i));
- }
- qDebug () << image.format();
- switch(image.format())
- {
- case QImage::Format_Indexed8:
- case QImage::Format_Grayscale8:
- for(int i = 0; i < height; i ++)
- {
- const uchar *pSrc = (uchar *)image.constScanLine(i);
- uchar *pDest = (uchar *)ret.scanLine(i);
- memcpy(pDest, pSrc, width);
- }
- break;
- case QImage::Format_RGB32:
- case QImage::Format_ARGB32:
- case QImage::Format_ARGB32_Premultiplied:
- for(int i = 0; i < height; i ++)
- {
- const QRgb *pSrc = (QRgb *)image.constScanLine(i);
- uchar *pDest = (uchar *)ret.scanLine(i);
- for( int j = 0; j < width; j ++)
- {
- pDest[j] = qGray(pSrc[j]);
- }
- }
- break;
- }
- return ret;
- }
- QPointF center(const QImage &image, int value)
- {
- if(image.isNull() || image.format() != QImage::Format_Indexed8)
- {
- return QPointF(-1, -1);
- }
- int width = image.width();
- int height = image.height();
- int x_mean = 0;
- int y_mean = 0;
- int count = 0;
- for(int j = 0; j < height; j ++)
- {
- const uchar * p = image.constScanLine(j);
- for(int i = 0; i < width; i++)
- {
- if( p[i] == value )
- {
- x_mean += i;
- y_mean += j;
- count++;
- }
- }
- }
- return QPointF((double)x_mean / count, (double)y_mean / count);
- }
- struct ELLIPSE_PARA
- {
- double x_mean; //椭圆的中心坐标 x
- double y_mean; //椭圆的中心坐标 y
- double r1; //椭圆的长轴半径
- double r2; //椭圆的短轴半径
- double theta; //椭圆的长轴与 x 轴的夹角(逆时针)
- };
- /**
- * [url=home.php?mod=space&uid=2666770]@Brief[/url] ellipseFit 将一片区域当作椭圆来估计五个几何参数
- * [url=home.php?mod=space&uid=3142012]@param[/url] image
- * @param value
- * @param para
- */
- bool ellipseFit(const QImage &image, int value, ELLIPSE_PARA * para)
- {
- if(image.isNull() || image.format() != QImage::Format_Indexed8)
- {
- return false;
- }
- QPointF c = center(image, value);
- int width = image.width();
- int height = image.height();
- double n01 = c.x();
- double n10 = c.y();
- double mu20 = 0.0;
- double mu02 = 0.0;
- double mu11 = 0.0;
- int count = 0;
- for(int row = 0; row < height; row ++)
- {
- const uchar * p = image.constScanLine(row);
- for(int col = 0; col < width; col++)
- {
- if( p[col] == value )
- {
- mu02 += (col - n01) * (col - n01);
- mu20 += (row - n10) * (row - n10);
- mu11 += (col - n01) * (row - n10);
- count ++;
- }
- }
- }
- if(count == 0)
- {
- return false;
- }
- mu20 /= count;
- mu02 /= count;
- mu11 /= count;
- double t1 = mu20 + mu02;
- double t2 = mu20 - mu02;
- double t3 = sqrt(t2 * t2 + 4 * mu11 * mu11);
- double r1 = sqrt(2 * ( t1 + t3) );
- double r2 = sqrt(2 * ( t1 - t3) );
- double theta = - atan2(2 * mu11, mu02 - mu20) / 2.0;
- para->r1 = r1;
- para->r2 = r2;
- para->theta = theta;
- para->x_mean = n01;
- para->y_mean = n10;
- return true;
- }
- int main(int argc, char *argv[])
- {
- QApplicaTIon a(argc, argv);
- QImage image("D:/test55.png");
- QImage imageGray = toGray(image);
- //imageGray = threshold(imageGray, 128);
- ELLIPSE_PARA para;
- ellipseFit(imageGray, 0, ¶);
- qDebug() << para.r1;
- qDebug() << para.r2;
- qDebug() << para.theta * 180 / 3.14159;
- QPointF c(para.x_mean, para.y_mean);
- qDebug() << c;
- QPainter painter(&image);
- painter.setPen(Qt::red);
- painter.translate(c);
- painter.rotate(-para.theta * 180 / 3.14159);
- painter.drawEllipse(QPointF(0, 0), para.r1, para.r2 );
- PictureBox box;
- box.seTImage(image);
- box.show();
- return a.exec();
- }
复制代码
|