Java & OpenCV (二) —— 简单的灰度图操作

这次我们来尝试通过OpenCV来对图片进行一些简单处理,包括伽马校正、直方图均衡、平滑与锐化。(没错选自《数字图像处理》大学课本第三章,刚自学完)。涉及到的理论知识都只是大概上做介绍,有不清楚的部分请自行查阅资料。

首先为了与日后写的不同代码区分开来,新建了一个Grayscale类,继承自己写的一个Image类。Image类中有成员image数据类型为Mat,有一个默认的缺省构造方法以及一个读图像的read方法与写回文件的write方法,这些也没啥好说的就不上代码了。不过我很X疼地对各个处理方法不是改变原图像而是返回处理后图像,从逻辑上来讲应该是修改原图像的,但是由于很多变换是不可逆的这样变换编程中反而会造成麻烦,所以采用了返回值的方法。

二值化 threshhold

类中首先添加了在之前示例中使用的二值化的方法。这一次仔细研究了一下其中的那个表示方法的参数,各参数对应的含义都写在了注释里这里就不说了,另一个参数thresh表示的就是临界阈值。代码如下

public Mat threshold(double thresh, int type) {
        Mat processed_image = image.clone();
        /*
         * about type
         * THRESH_BINARY -- dst(x,y) = (src(x,y)>thresh)?maxval:0
         * THRESH_BINARY_INV -- dst(x,y) = (src(x,y)>thresh)?0:maxval
         * THRESH_TRUNC -- dst(x,y) = (src(x,y)>thresh)?thresh:src(x,y)
         * THRESH_TOZERO -- dst(x,y) = (src(x,y)>thresh)?src(x,y):0
         * THRESH_TOZERO_INV -- dst(x,y) = (src(x,y)>thresh)?0:src(x,y)
         * */
        Imgproc.threshold(processed_image, processed_image, thresh, 255, type);
        return processed_image;
    }

伽马校正 gamma_correct

OpenCV貌似没有自带的伽马校正函数(至少我没找到),所以这部分就是自己实现的了。所谓伽马校正说的粗一点就是调节图片亮度,在各像素点相对灰度大致不变的前提下重新调整灰度值,使用的是指数函数作为映射函数,伽马值就是指数函数的指数。映射前要先将灰度值调整为0~1,映射后再乘以最大灰度值-1还原。想象一下指数函数的函数图像,当gamma小于1大于0时,灰度值较低的值会被映射为较高的值,而较高的值变化幅度较小;而gamma大于1时灰度值较低的值变化幅度较小,而较高的值变化幅度较大。换句话说gamma<1时可以调亮图片,而>1时会调暗图片。

代码的逻辑就是简单的遍历每个像素调整其灰度值,注意这里由于是灰度图所以get方法获取的数组只有一个元素,而对于彩色图的伽马校正其实只要再加一层循环对数组中每个元素都使用该计算公式就可以调整各通道的值了。代码如下

public Mat gamma_correct(double gamma) {
        Mat processed_image = image.clone();
        int rows = processed_image.rows(), cols = processed_image.cols();
        for (int row = 0; row < rows; row++) {
            for (int col = 0; col < cols; col++) {
                processed_image.put(row, col, Math.pow(processed_image.get(row, col)[0]/255, gamma)*255);
            }
        }
        return processed_image;
    }

直方图均衡 histogram_equalizate

这里的直方图是指灰度图各灰度的像素点数统计出的直方图,研究表明一个均匀分布的直方图对应的图像可以使人眼有更好的识别能力。使用离散化近似的累计概率分布函数作为映射函数可以达到这一目的。(生物学与数学原理有兴趣可以自行查阅)。

这个方法OpenCV是自带的,就直接调用了。代码如下

public Mat histogram_equalizate() {
        Mat processed_image = image.clone();
        Imgproc.equalizeHist(processed_image, processed_image);
        return processed_image;
    }

平滑 smooth

所谓平滑就是为了使图像的亮度平缓渐变,减小图像的梯度,同时可以消除噪声。一般采用的方法就是根据处理的像素与该像素周围像素的值取平均、加权平均或中位数等方法。(具体的可自行查阅)

C或者C++的OpenCV库是自带一个cvSmooth的函数的,然而Java的包中貌似是没有,所以这个又需要自行编写,万幸cvSmooth其实也是调用的OpenCV中的其他方法,所以我不需要重头开始对像素点进行处理。明明有C++中参数CV_BLUR等final变量的定义,却没有这个方法,也是奇异,可能是我没找到吧。

cvSmooth提供的平滑方法貌似包括无尺度变换的模糊、模糊、高斯滤波、中值滤波以及双向滤波。关于尺度变换、高斯滤波和双向滤波这几个词的含义有兴趣可以自行查询,后续开始研究细节实现时也可能会涉及。关于3个额外参数,param1和param2一般用于计算上面提到的那个“周围部分”的面积,对于中值滤波需要方形区域所以一个参数就够,param3用于作为高斯滤波的标准差。boxFilter的depth参数是表示图像每个像素的字节数,一般用原图的深度就好。bilateralFilter的第三个参数越大,滤波的去噪声效果就越好但运行会越慢,推荐的是使用5作为参数。代码如下

public Mat smooth(int method, int param1, int param2, double param3) {
        Mat processed_image = image.clone();
        if (param2 == 0) param2 = 1;

        switch (method) {
        case Imgproc.CV_BLUR_NO_SCALE:
            Imgproc.boxFilter(processed_image, processed_image, processed_image.depth(),
                    new Size(param1, param2), new Point(-1, -1), true);
            break;
        case Imgproc.CV_BLUR:
            Imgproc.blur(processed_image, processed_image, new Size(param1, param2));
            break;
        case Imgproc.CV_GAUSSIAN:
            Imgproc.GaussianBlur(processed_image, processed_image, new Size(param1, param2), param3, param3);
            break;
        case Imgproc.CV_MEDIAN:
            Imgproc.medianBlur(processed_image, processed_image, param1);
            break;
        case Imgproc.CV_BILATERAL:
            Imgproc.bilateralFilter(processed_image, processed_image, 5, param1, param2);
            break;
        default:
        }

        return processed_image;
    }

锐化 sharp

锐化从某种意义上刚好与平滑相反,其目的是为了补强图像中物体的轮廓,使各部分内容更易被人眼识别。使用有限差分法计算各像素与邻近像素的变化导数,将变化较大的部分认定为轮廓部分,提取出来并“加”到原图像上。

到具体数据计算,首先是“邻近部分”到底是哪一区域,有多种计算方法,这里我们使用拉普拉斯滤波,即一个3x3的矩阵中中间元素z5为我们处理的元素,那么计算值为z2+z4+z6+z8-4*z5(这是1阶的,2阶公式为z1+z2+z3+z4+z6+z7+z8+z9-8*z5)。直接使用这个公式处理图像可以得到一个只含边界的图像,由于原像素灰度值处于被减数位置,所以边界为白色。而锐化我们需要“加深”边界部分,所以计算值取反,4*z5-z2-z4-z6-z8,再将其映射回原图像,得到最终的映射函数为5*z5-z2-z4-z6-z8。

OpenCV提供了拉普拉斯滤波的方法,可以尝试使用来观察拉普拉斯滤波获得的结果。锐化并没有直接的相应方法,但是我们可以使用filter2D这一方法。该方法对每一像素根据输入参数矩阵作相应计算。这里我们使用一阶的拉普拉斯滤波,故将(0,1)、(1,0)、(1,2)、(2,1)位置的值设为-1,将(1,1)位置的值设为5。第三个参数表示Mat的类型,这些final变量的命名规则为CV_[每一像素字节数]+[像素值数据类型(unsigned,signed,float)]+C+信道数,由于使用的图每个像素值为浮点数故使用CV_32FC1。Scalar用于初始化矩阵中的值,第N个参数对应第N个信道,由于我们只有一个信道所以仅需一个参数0。代码如下

public Mat sharp() {
        Mat processed_image = image.clone();
        //Imgproc.Laplacian(processed_image, processed_image, processed_image.depth());
        Mat kernal = new Mat(3,3,CvType.CV_32FC1, new Scalar(0));
        kernal.put(0, 1, -1);
        kernal.put(1, 0, -1);
        kernal.put(1, 1, 5);
        kernal.put(1, 2, -1);
        kernal.put(2, 1, -1);
        Imgproc.filter2D(processed_image, processed_image, processed_image.depth(), kernal);
        return processed_image;
    }

总结

以上就是《数字图像处理》第三章提到的一些图像处理方法的的实现,本部分也就到这里为止了。其实OpenCV不止是一个图像处理的库,然而可能这一系列的博客最后仅涉及数字图像的处理(也许会有计算机视觉),嘛就是这样。

你可能感兴趣的:(图像处理)