Hough变换(小白直接入门圆形检测,附带Java源码)

上了一门叫做Image Process 2的专业课,发现了一个天坑。首先从最基本的讲起:

步骤一

首先明白几个基础术语概念:

1、像素点

像素点是最小的图像单元,一张图片由好多的像素点组成。

2、像素
把鼠标放在一个图片上,这个时候会显示尺寸和大小,这里的尺寸就是像素。

3、RGB
​因为一个像素点的颜色是由RGB三个值来表现的,所以像素点矩阵对应三个颜色向量矩阵,分别是R矩阵(500 *338大小),G矩阵(500 *338大小),B矩阵(500 *338大小)。如果每个矩阵的第一行第一列的值分别为:R:240,G:223,B:204,所以这个像素点的颜色就是(240,223,204)

4、灰度
灰度是表明图像明暗的数值,即黑白图像中点的颜色深度,范围一般从0到255,白色为255 ,黑色为0,故黑白图片也称灰度图像。灰度值指的是单个像素点的亮度。灰度值越大表示越亮。

5、图像的灰度化
 灰度就是没有色彩,RGB色彩分量全部相等。图像的灰度化就是让像素点矩阵中的每一个像素点都满足关系:R=G=B,此时的这个值叫做灰度值。如RGB(100,100,100)就代表灰度值为100,RGB(50,50,50)代表灰度值为50。

灰度化处理

一般灰度化处理的方法:在灰度化的图像中灰度值的范围为0~255

1.浮点算法:Gray=R*0.3+G*0.59+B*0.11                R=G=B

2.整数方法:Gray=(R*30+G*59+B*11)/100              R=G=B

3.移位方法:Gray =(R*28+G*151+B*77)>>8            R=G=B

4.平均值法:Gray=(R+G+B)/3                            R=G=B

5.仅取绿色:Gray=G                                              R=G=B

二值化处理的方法(这个过程可以用代码实现,也可以用PS实现):

二值化就是让图像的像素点矩阵中的每个像素点的灰度值为0(黑色)或者255(白色),也就是让整个图像呈现只有黑和白的效果。在二值化后的图像中的灰度值范围是0或者255。那么一个像素点在灰度化之后的灰度值怎么转化为0或者255呢?比如灰度值为100,那么在二值化后到底是0还是255?这就涉及到取一个阀值的问题。

1、取阀值为127(相当于0~255的中数,(0+255)/2=127),让灰度值小于等于127的变 为0(黑色),灰度值大于127的变为255(白色),这样做的好处是计算量小速度快,但是 缺点也是很明显的,因为这个阀值在不同的图片中均为127,但是不同的图片,他们的颜色分布差别很大,所以用127做阀值,白菜萝卜一刀切,效果肯定是不好的。

2、计算像素点矩阵中的所有像素点的灰度值的平均值avg

(像素点1灰度值+...+像素点n灰度值)/ n = 像素点平均值avg,然后让每一个像素点与avg一 一比较,小于等于avg的像素点就为0(黑色),大于avg的 像 素点为255(白色),这样做比方法1好一些。

3、使用直方图方法(也叫双峰法)来寻找二值化阀值,直方图是图像的重要特质。直方图方法 认为图像由前景和背景组成,在灰度直方图上,前景和背景都形成高峰,在双峰之间的最低谷处就是阀值所在。取到阀值之后再一 一比较就可以了。

具体实现方法请参考:https://blog.csdn.net/bravebean/article/details/51374066

6、灰度值与像素值的关系
如果对于一张本身就是灰度图像(8位灰度图像)来说,他的像素值就是它的灰度值,如果是一张彩色图像,则它的灰度值需要经过函数映射来得到。灰度图像是由纯黑和纯白来过渡得到的,在黑色中加入白色就得到灰色,纯黑和纯白按不同的比例来混合就得到不同的灰度值。R=G=B=255为白色,R=G=B=0为黑色,R=G=B=小于255的某个整数时,此时就为某个灰度值。

7、灰度级
灰度级表明图像中不同灰度的最大数量。灰度级越大,图像的亮度范围越大。

8、图像分辨率
图像分辨率是指每英寸图像内的像素点数。图像分辨率是有单位的,叫ppi(像素每英寸)。分辨率越高,像素的点密度越高,图像越逼真(这就是为什么做大幅的喷绘时,要求图片分辨率要高,就是为了保证每英寸的画面上拥有更多的像素点)。

9、前景和背景

简单理解:前景是你感兴趣的对象。背景却不是。
举个例子:传送带上有个螺丝钉,你想检测它又没有缺陷。
那这个螺丝钉就是图像中的前景,传送带就是图像中的背景。

步骤二

关于Java数字图像处理基础知识,如Java如何读写一个图像,调用什么方法对图像进行像素处理,提取像素等等,请参考:https://blog.csdn.net/jia20003/article/details/7279667

步骤三

一、简单介绍

Hough变换是图像处理中从图像中识别几何形状的基本方法之一。Hough变换的基本原理在于利用点与线的对偶性,将原始图像空间的给定的曲线通过曲线表达形式变为参数空间的一个点。这样就把原始图像中给定曲线的检测问题转化为寻找参数空间中的峰值问题。也即把检测整体特性转化为检测局部特性。比如直线、椭圆、圆、弧线等。

二、Hough变换的基本思想

设已知一黑白图像上画了一条直线,要求出这条直线所在的位置。我们知道,直线的方程可以用y=k*x+b 来表示,其中k和b是参数,分别是斜率和截距。过某一点(x0,y0)的所有直线的参数都会满足方程y0=kx0+b。即点(x0,y0)确定了一族直线。方程y0=kx0+b在参数k--b平面上是一条直线,(你也可以是方程b=-x0*k+y0对应的直线)。这样,图像x--y平面上的一个前景像素点就对应到参数平面上的一条直线。我们举个例子说明解决前面那个问题的原理。设图像上的直线是y=x, 我们先取上面的三个点:A(0,0), B(1,1), C(22)。可以求出,过A点的直线的参数要满足方程b=0, 过B点的直线的参数要满足方程1=k+b, 过C点的直线的参数要满足方程2=2k+b, 这三个方程就对应着参数平面上的三条直线,而这三条直线会相交于一点(k=1,b=0)。 同理,原图像上直线y=x上的其它点(如(3,3),(4,4)等) 对应参数平面上的直线也会通过点(k=1,b=0)。这个性质就为我们解决问题提供了方法,就是把图像平面上的点对应到参数平面上的线,最后通过统计特性来解决问题。假如图像平面上有两条直线,那么最终在参数平面上就会看到两个峰值点,依此类推。

简而言之,Hough变换思想为:在原始图像坐标系下的一个点对应了参数坐标系中的一条直线,同样参数坐标系的一条直线对应了原始坐标系下的一个点,然后,原始坐标系下呈现直线的所有点,它们的斜率和截距是相同的,所以它们在参数坐标系下对应于同一个点。这样在将原始坐标系下的各个点投影到参数坐标系下之后,看参数坐标系下有没有聚集点,这样的聚集点就对应了原始坐标系下的直线。

在实际应用中,y=k*x+b形式的直线方程没有办法表示x=c形式的直线(这时候,直线的斜率为无穷大)。所以实际应用中,是采用参数方程p=x*cos(theta)+y*sin(theta)。这样,图像平面上的一个点就对应到参数p---theta平面上的一条曲线上,其它的还是一样。

三、Hough变换推广

1、已知半径的圆

         其实Hough变换可以检测任意的已知表达形式的曲线,关键是看其参数空间的选择,参数空间的选择可以根据它的表达形式而定。比如圆的表达形式为 ,所以当检测某一半径的圆的时候,可以选择与原图像空间同样的空间作为参数空间。那么圆图像空间中的一个圆对应了参数空间中的一个点,参数空间中的一个点对应了图像空间中的一个圆,圆图像空间中在同一个圆上的点,它们的参数相同即a,b相同,那么它们在参数空间中的对应的圆就会过同一个点(a,b),所以,将原图像空间中的所有点变换到参数空间后,根据参数空间中点的聚集程度就可以判断出图像空间中有没有近似于圆的图形。如果有的话,这个参数就是圆的参数。

2、未知半径的圆

         对于圆的半径未知的情况下,可以看作是有三个参数的圆的检测,中心和半径。这个时候原理仍然相同,只是参数空间的维数升高,计算量增大。图像空间中的任意一个点都对应了参数空间中的一簇圆曲线。 ,其实是一个圆锥型。参数空间中的任意一个点对应了图像空间中的一个圆。

步骤三还可参考其他博客:https://blog.csdn.net/tina_ttl/article/details/53063341

                                           https://blog.csdn.net/jia20003/article/details/7724530
 

步骤四

附上Java图片处理源码(此时图片已经过灰值化处理):

有三个for循环,第一个for循环利用Hough转换,将每种可能性放入三维数组,第二个for循环,找出数组中的最大值(可以得到圆心坐标,半径R),第三个for循环根据圆心坐标,半径在图片上画出检测到的圆。

public void process() {

        if (img != null) {
            // For each circle with parameters x0,y0,r we measure
            // how similar the structures in the image img  :

            int[][][] h = new int[img.getWidth()][img.getHeight()][img.getWidth()];

            for (int x0 = 0; x0 < img.getWidth(); x0++) {
                for (int y0 = 0; y0 < img.getHeight(); y0++) {
                    for (int R = 0; R < img.getWidth(); R++) {
                        //h[x0][y0][R] measures how many pixel in img ly on that the circle

                                int argb_in = img.getRGB(x0, y0); //根据坐标得到像素(返回一个值可以得到ARGB的值)
                                int r = getRed(argb_in); //用ps已经把图片灰度处理了

                        if (r > 236) {
                           for (int x = 0; x < img.getWidth(); x++) {
                             for (int y = 0; y < img.getHeight(); y++) {
                                 if (Math.abs((x - x0) * (x - x0) + (y - y0) * (y - y0) - R * R) < 10) h[x][y][R]++;
                               }
                             }
                        }

                    }
                }
            }
            // Find the parameters x0,y0,r such that h has the maximum (the peak value)
            int xmax = 0;
            int ymax = 0;
            int Rmax = 0;
            int Max = 0;

            for (int x0 = 0; x0 < img.getWidth(); x0++) {
                for (int y0 = 0; y0 < img.getHeight(); y0++) {
                    for (int R = 0; R < img.getWidth(); R++) {
                        if (h[x0][y0][R] > Max) {
                            Max = h[x0][y0][R];
                            xmax = x0;
                            ymax = y0;
                            Rmax = R;
                        }

                    }
                }
            }
            //professor's meaning is to find the center of circle, but i don't understand, because the
            //h[][][]'s founction is to search the whitest point(but how can you think the center point is
            // the whitest).
            System.out.println(xmax);
            System.out.println(ymax);
            System.out.println(Rmax);

            for (int x = 0; x < img.getWidth(); x++) {
                for (int y = 0; y < img.getHeight(); y++) {

                    int argb_in = img.getRGB(x, y);
                    int r = getRed(argb_in);
                    int argb_out = setARGB(255, r / 2, r / 2, r / 2);
                    img_out.setRGB(x, y, argb_out);
                    //let's paint this first circle
                    //(x-70)^2+(y-70)^2 - 1 < 10 表示的就是一个圆
                    if (Math.abs((x - xmax) * (x - xmax) + (y - ymax) * (y - ymax) - Rmax * Rmax) < 10) {
                        argb_out = setARGB(255, 255, 255, 255);//First param is Alpha
                        img_out.setRGB(x, y, argb_out);

                    }
                }//y
            }//x

这段代码还有些小问题,就是检测结果不准,我现在还没有研究清楚,等我再研究研究。/笑哭

附上效果图:

参考文献:https://blog.csdn.net/zhaogang1993/article/details/41380247

                  https://blog.csdn.net/Strive_0902/article/details/78023080

                  https://blog.csdn.net/lichunchi123/article/details/78937022

猜你喜欢

转载自blog.csdn.net/weixin_41126303/article/details/83395679