简单理解霍夫变换

来源:互联网 发布:手机淘宝怎样更新版本 编辑:程序博客网 时间:2024/05/23 12:01

这些天,敲代码的时候遇到了一个需要自动纠正图片角度的问题,想了很久不知道怎么办。正好老师在上课的时候提到了霍夫变换这个名词,我犹如发现了金矿,回来就猛地一通百度。无奈自己智商实在不太够,网上大神说的我看了很久依然不太理解,于是浪费了很多时间,宛如回到了高中(或者初中)画直线方程图像的年代……

于是,在我终于弄明白之后,就有了这篇文章。写给和我一样的新人看看,也欢迎大神批评指正。


首先,霍夫变换的作用是找出一张图片中的直线(也可以是圆等更复杂的图形,这里只讨论直线),那么怎么找呢?这里需要回顾一下中学数学的极坐标相关知识(至于为什么用极坐标而不是直角坐标,后面再说):

函数图像1

假设图片中有一条直线BC,那么对于BC上任意一点A(x,y),连接AO,将 AOB 记为θ,将OA记为ρ则:

OE=xcosθ
AE=ysinθ
ρ=xcosθ+ysinθ(1)

因此,我们可以使用公式1表示每一个图片上任何一个点到原点的长度。那么,在这条直线上的其他点呢?霍夫变换的思想就是,在同一条直线上的所有点,当θ=90ABO,即OABC时,使用公式1都会取得相同的值。下面我尝试简单说明一下,这里混用了直角坐标和极坐标,怪我懒吧……

BC线y=kx+b
ρ=xcosθ+(kx+b)sinθ
线M(x1,y1)N(x2,y2)
ρ1=ρ2(x1x2)cosθ=k(x2x1)sinθ
k=cosθsinθ
OAtanθ,sinθcosθOABC

由上面的式子可知,当θ的取值使得OABC垂直时,BC上每个点经过公式1计算得到的值都相同。于是我们可以开始写代码了。

首先我们需要一张二值化的图片(如果不了解灰度化、二值化等概念,建议先百度或者参考我的其他文章),设有一张二值化的图片,前景为白色,背景为黑色,我们的程序遍历每个像素,如果是前景色,那么就针对该像素的坐标,使用每一个可能的θ值来计算相应的ρ值。计算完毕后,对每一个(θ,ρ)出现的次数进行保存。根据前面所说的,凡是一条直线上的点,经过计算都会取得相同的值,因此当出现的次数超过一定阀值时,这个(θ,ρ)可以看做是一条直线。
如果需要将直线转换为直角坐标的话,那么:

k=cosθsinθ
b=ρsinθ

好了,讲了这么多,下面放代码吧。代码用OpenCV读取图片文件,其他自己写:

Line HoughLines(Mat &mat){    if (mat.channels() > 1)    {        throw (std::exception("ERROR"));        //这里偷个懒,假设传入的图片已经过二值化处理    }    uchar foreColor = 255;    int xCenter = mat.cols / 2;    int yCenter = mat.rows / 2;    int *angelMap[60];    //theta角的取值范围是-90度到90度,每3度为一个区间,如需提高精确度,可以细分区间    int maxP = static_cast<int>(sqrt(xCenter * xCenter + yCenter * yCenter));    //计算rho的取值范围,每2个单位为一个区间,包括正负    for (size_t idx = 0; idx < 60; idx++)    {        angelMap[idx] = new int[maxP + 1]{ 0 };        //分配储存空间,记录每个(rho, theta)值出现的次数    }    for (size_t idx = 0; idx < mat.rows; idx++)    {        uchar* ptr = mat.ptr<uchar>(idx);        for (size_t subIdx = 0; subIdx < mat.cols; subIdx++)        {            if (ptr[subIdx] == foreColor)            {                int x = subIdx - xCenter;                int y = yCenter - idx;                //将坐标原点转换为图片中间,方便计算                for (int theta = -90; theta < 90; theta+=3)                {                    int p = (static_cast<int>(x * cosf(PIRate * theta)                              + y * sinf(PIRate * theta)) + maxP) / 2;                    angelMap[theta / 3 + 30][p]++;                    //分别将rho和theta的值映射到数组的索引下标,然后修改计数                }            }        }    }    int maxRho = 0, maxTheta = 0, maxVal = 0;    for (size_t idx = 0; idx < 60; idx++)    {        //这里偷个懒,只计算最大的那条直线        for (size_t subIdx = 0; subIdx <= maxP; subIdx++)        {            if (angelMap[idx][subIdx] > maxVal)            {                maxVal = angelMap[idx][subIdx];                maxRho = subIdx;                maxTheta = idx;            }        }    }    int angle = (maxTheta - 30) * 3;    Line line(-cosf(angle * PIRate) / sinf(angle * PIRate),             (maxRho * 2 - maxP) / sinf(angle * PIRate));    for (size_t idx = 0; idx < 60; idx++)    {        delete[] angelMap[idx];    }    return line;}

另有Line的数据结构,简单的写一下:

struct Line{    float k;    float b;    Line(float k, float b)    {        this->k = k;        this->b = b;    }    int getY(int x)    {        return static_cast<int>(k * x + b + 0.5f);    }    int getX(int y)    {        return static_cast<int>((y - b) / k + 0.5f);    }};

然后简单测试一下,测试代码就懒得放了,直接看效果图吧(寝室随手拍的,请忽略那一堆衣服)~
这里写图片描述

效果还不错~完结撒花!

0 0
原创粉丝点击