我理解的图像主方向:图像中灰度分布的趋势方向和X轴的夹角。
思路:主方向的范围在[0,180)的区间内,可以让一条直线在这个范围内旋转一次,分别统计图像中感兴趣的点落在这条旋转轴上的像素个数分布直方图,该直方图的索引为原点到投影点的距离。由于该旋转轴通过原点,所以直线方程可以用(y = kx)表示(90°除外),图像上的点到旋转轴上的投影坐标可以很简单地计算出来。当旋转轴在角度α时,落在该旋转轴上的点在旋转过程中存在最大值,则角度α即为主方向。
如下图所示,当旋转轴在[0,90)角度区间内逆时针旋转时,图像上所有的点投影到旋转轴上的范围是点O到点P之间,由图像对角线顶点投影到旋转轴的点P不难看出旋转轴在逆时针旋转过程中图像上所有的点投影到旋转轴上距离原点肯定不会超过图像对角线的长度,即直方图的范围可以设置为[0, sqrt(width*width + height*height)]。
如下图所示当旋转轴在的旋转角度超过90°时,图像上的点会落在AB之间,这样投影点到原点的距离索引会产生重复部分(红线所示)
为了避免上图这种情况,当旋转轴的方向超过90时,将图像向左平移直到图像右下角与坐标轴原点重合如下图所示,当旋转轴在[91,180)范围内旋转时计算方向和图1方式相同。
测试图片:
代码如下:
// 通过将旋转轴旋转180度获取轮廓在其上面的投影统计计算图像主方向
int calcMainDirection(Mat& gray_img)
{
int theta = 0;
int img_w = gray_img.cols;
int img_h = gray_img.rows;
int diagonal_len = sqrt(img_w*img_w + img_h*img_h);
vector<Point> points;
for(int i=0; i<img_h; i++)
{
uchar* pLine = gray_img.data + i*img_w;
for(int j=0; j<img_w; j++)
{
// 保存图像中灰度级较低的坐标点
if(*(pLine+j) < 80){
// 读取的图像坐标系Y轴朝下,与笛卡尔坐标系相反,这里手动调整Y轴朝上
points.push_back(Point(j,img_h-i-1));
}
}
}
int maxVal= 0;
// 图像上任意坐标点到旋转轴上的投影距离不会超过图像对角线长度
int* hist = new int[diagonal_len];
// 设定主方向(在0~180旋转),当旋转到某一角度时,大部分坐标点会投影到旋转轴的某一个位置,
// 据此判断该方向是否就是需要的主方向
for(int angle=0; angle<180; angle++)
{
memset(hist, 0, diagonal_len*sizeof(int));
// 当角度为90时,直线方程不能用 y=kx+b 的形式表示
if(angle == 90)
{
for(int i=0; i<points.size(); i++)
hist[points[i].y]++;
}
else
{
// 角度转弧度,计算斜率
double rad = float(angle)/180*CV_PI;
double slope = tan(rad);
for(int i=0; i<points.size(); i++)
{
int pt_x = points[i].x;
int pt_y = points[i].y;
// 当旋转方向超过90度时,图像上的坐标点会投影到该旋转轴位于原点上方和原点下方两处,
// 而这两处计算得到的模长相等时会叠加造成干扰,为避免这种情况,将所有坐标点整体左移。
if(angle > 90){
pt_x -= img_w;
}
// 计算坐标点在角度为angle的直线上的投影坐标点
float x1 = (slope*pt_y+pt_x)/(slope*slope+1);
float y1 = slope*x1;
// 得到坐标原点到该投影坐标点的距离
int distance = sqrt(x1*x1 + y1*y1);
hist[distance]++;
}
}
// 计算图像上的坐标点在该方向上投影的最大值
sort(hist, hist+diagonal_len);
if(hist[diagonal_len-1] > maxVal){
maxVal = hist[diagonal_len-1];
theta = -angle;
}
}
delete[] hist;
return theta;
}
int main()
{
Mat grayImg = imread("../test.jpg", IMREAD_GRAYSCALE);
// 通过投影计算主方向
int angle = calcMainDirection(grayImg);
printf("angle:%d\n", angle);
return 0;
}
输出结果:-41,将左图顺时针旋转41°即为右图,这样即可以通过计算主方向的方法将图像方向进行矫正。