欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java实现读图像并作直方图均衡与灰度拉伸

程序员文章站 2022-03-18 19:41:53
...

Java实现读图像并作直方图均衡与灰度拉伸


北京航空航天大学计算机学院-2020春季图像处理与模式识别课程作业,使用Java实现读入图像,并对图像作直方图均衡与灰度拉伸


直方图均衡和灰度拉伸是数字图像处理中常见的灰度图像变换方法。本文通过Java实现读入图像后,对其转化为灰度图,压缩为统一高度后作直方图均衡与灰度拉伸,并随变换后的图像附上其直方图。

Java读入图像

Java有已经封装好的类 BufferedImage 可供我们方便地实现图像的读入和操作。在本文代码中用到两种方式创建 BufferedImage 对象,一是直接利用 ImageIO.read(file) ,读入的图像为 Image 类型,类型转换为BufferedImage 。二是直接通过 new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB) 构造方法新建 BufferedImage 对象,再对其进行操作。

首先是读入原始图像:

import javax.imageio.ImageIO;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

public class ImageProcess {
    // Original image
    private BufferedImage image;

    private ImageProcess(String fileStr) {
        File file = new File(fileStr);
        if (!file.exists()) {
            System.out.println("File does not exist.");
            return;
        }
        try {
            this.image = (BufferedImage) ImageIO.read(file);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        ImageProcess i1 = new ImageProcess("image01.jpg");
        System.out.println("done");
    }
}

原始图像读入后,我们希望对图像作预处理,将其放缩到我们希望的大小(等比例),并转化为灰度图。放缩和转化为灰度图的过程中每一步都新建 BufferedImage 来存储其处理后的图像。在转换为灰度图的过程中,已经根据直方图的定义存储了直方图的信息,由一维256数组存储,每个位置统计其对应的频数。

private void compress() {
    width = image.getWidth() * height / image.getHeight();
    compressedImg = new BufferedImage(width, height, 
                                      BufferedImage.TYPE_INT_RGB);
    compressedImg.getGraphics().
        drawImage(image.getScaledInstance(width, height, Image.SCALE_SMOOTH), 
                  0, 0, null);
}

private void toGrayScale() {
    grayScale = new int[width][height];
    grayScaleImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    for(int i = 0; i < width; i++) {
        for(int j = 0; j < height; j++) {
            //将24bit中存储的RGB值单个提取出来,可以理解为byte转int
            int r = (compressedImg.getRGB(i, j) & 0xff0000) >> 16;
            int g = (compressedImg.getRGB(i, j) & 0xff00) >> 8;
            int b = (compressedImg.getRGB(i, j) & 0xff);
            //这里是将RGB分别乘上一个权重,得出相应的灰度值gray
            int gray = (30*r + 59*g + 11*b) / 100;
            histogram[gray]++;
            grayScale[i][j] = gray;
            //将得出的灰度值转换成计算机中存储的模式
            int grayStd = ((gray & 0xff)<<16 ) | ((gray & 0xff)<<8) | 
                (gray & 0xff);
            grayScaleImg.setRGB(i, j, grayStd);
        }
    }
}

为了验证读入图像,并为后面输出处理效果做准备,在此使用 Graphics 来绘制 BufferedImage 中的图像,其用法如下:

import javax.swing.JFrame;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;

class ImagePlot extends JFrame{
    private BufferedImage image;
    private int[] histogram = null;

    public void paint(Graphics g) {
        super.paint(g);
        int width = image.getWidth();
        int height = image.getHeight();
        int ix = width / 10, iy = height / 3;
        g.drawImage(image, ix, iy, null);
    }

    ImagePlot(BufferedImage image) {
        super();
        this.image = image;
        this.setVisible(true);
    }
}


直方图均衡

直方图是反映一幅图像灰度级与该灰度级出现概率(或频数)之间关系的柱状图。其横坐标是灰度级(0-255),纵坐标是该灰度级出现的概率或频数。直方图均衡的方法将图像像素值概率分别拉升,扩大分别范围,提高了对比度。离散的直方图均衡其映射公式为:
sk=T(rk)=(L1)j=0kPr(rj)=L1MNj=0knj, k=1,2,3,,L1 s_k = T(r_k) = (L−1)\sum_{j=0}^kP_r(r_j)= \frac{L-1}{MN}\sum_{j=0}^kn_j,\space k=1, 2, 3, \dots, L-1
sks_k : 目标像素值,rkr_k : 原始像素值,LL : 灰度级(8位256),

Pr(rj)P_r(r_j) : rjr_j 在原始图中的概率,MNMN : 图像总像素数(宽×高),njn_j 原始图像中,像素值为 jj 的个数。

根据上面的公式,不难逐个像素计算出直方图均衡后每个点所映射的像素值:

private void histogramEqualization() {
    equalizationImg = 
        new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    for (int i = 0; i < width; i++) {
        for (int j = 0; j < height; j++) {
            int value = 0;
            for (int k = 0; k < grayScale[i][j]; k++)
                value += histogram[k];
            value = 255 * value / (width * height);
            equalization[value]++;
            int valueStd = ((value & 0xff) << 16 ) | ((value & 0xff) << 8) |
                (value & 0xff);
            equalizationImg.setRGB(i, j, valueStd);
        }
    }
}

原始的图像:
Java实现读图像并作直方图均衡与灰度拉伸
经过压缩与转化为灰度图后,灰度图与其直方图如下:
Java实现读图像并作直方图均衡与灰度拉伸
而经过直方图均衡后的灰度图及其直方图效果如下:
Java实现读图像并作直方图均衡与灰度拉伸
可以看出图像的对比度提升,细节得到了突出。


灰度拉伸

灰度变换是设计函数对灰度图中各个像素的灰度值作直接点变换。所谓拉伸是指将原本局限在某一灰度级范围内的图像,将其局限范围内的像素灰度拉伸到更广的范围(或将较广范围内的灰度值压缩到较小范围)。本文的代码采用了分段线性拉伸,首先设定一个阈值,直方图中该阈值之下频数对应的灰度级看作不活跃的灰度级,然后从0-255范围两端开始寻找图像较为活跃的灰度区间。得到该区间后,将该区间拉伸为原来的5倍,作为图像主要信息拉伸后的区间。而没有落在该区间内的点也分别线性压缩至两端。整个函数是分段线性函数,如下:
Java实现读图像并作直方图均衡与灰度拉伸
Java实现读图像并作直方图均衡与灰度拉伸
在本文的代码中,上图中的 c=(a+b)/2(ba)(stretchRatio/2)c = (a + b) / 2 - (b - a) * (stretchRatio / 2)d=(a+b)/2+(ba)(stretchRatio/2)d = (a + b) / 2 + (b - a) * (stretchRatio / 2) , stretchRatio=5stretchRatio = 5 。当上述c、d超出0-255范围时,取0或255

private void grayScaleStretching() {
    stretchingImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    int threshold = 100;
    int a = 0, b = 255, c, d;
    int stretchRatio = 5;
    for (int i = 0; i < 256; i++) {
        if (histogram[i] > threshold) {
            a = i;
            break;
        }
    }
    for (int i = 255; i >= 0; i--) {
        if (histogram[i] > threshold) {
            b = i;
            break;
        }
    }
    c = (a + b) / 2 - (b - a) * (stretchRatio / 2);
    if (c < 0) c = 0;
    d = (a + b) / 2 + (b - a) * (stretchRatio / 2);
    if (d > 255) d = 255;
    for (int i = 0; i < width; i++) {
        for (int j = 0; j < height; j++) {
            int value = grayScale[i][j];
            if (value < a) value = c * value / a;
            else if (value > b) value = (255 - d) * (value - b) / (255 - b) + d;
            else value = (d - c) * (value - a) / (b - a) + c;
            stretching[value]++;
            int valueStd = ((value & 0xff) << 16 ) | ((value & 0xff) << 8) |
            (value & 0xff);
            stretchingImg.setRGB(i, j, valueStd);
        }
    }
}

原始的图像:
Java实现读图像并作直方图均衡与灰度拉伸
经压缩与转换为灰度图像后,灰度图与其直方图如下:
Java实现读图像并作直方图均衡与灰度拉伸
而经过灰度线性拉伸后的灰度图及其直方图效果如下:
Java实现读图像并作直方图均衡与灰度拉伸
可以看出,灰度级的范围做了扩展,图像原本看上去无法分辨的部分可以看到细节。


完整的代码包含了绘制直方图的部分,如下:

// ImageProcess.java

import javax.imageio.ImageIO;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

public class ImageProcess {
    // Original image
    private BufferedImage image;
    // Compressed image, height: 500, maintain the original ratio.
    private int width;
    private static int height = 400;
    private BufferedImage compressedImg;
    // Gray scale Image
    private int[][] grayScale;
    private BufferedImage grayScaleImg;
    // Histogram of the original image
    private int[] histogram = new int[256];
    // Output image of histogram equalization
    private BufferedImage equalizationImg;
    // Histogram of the equalization output
    private int[] equalization = new int[256];
    // Output image of gray scale stretching
    private BufferedImage stretchingImg;
    // Histogram of the gray scale stretching output
    private int[] stretching = new int[256];


    private ImageProcess(String fileStr) {
        File file = new File(fileStr);
        if (!file.exists()) {
            System.out.println("File does not exist.");
            return;
        }
        try {
            this.image = (BufferedImage) ImageIO.read(file);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void compress() {
        width = image.getWidth() * height / image.getHeight();
        compressedImg = new BufferedImage(width, height, 
                                          BufferedImage.TYPE_INT_RGB);
        compressedImg.getGraphics().
            drawImage(image.getScaledInstance(width, height, Image.SCALE_SMOOTH),
                0, 0, null);
    }

    private void toGrayScale() {
        grayScale = new int[width][height];
        grayScaleImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        for(int i = 0; i < width; i++) {
            for(int j = 0; j < height; j++) {
                //将24bit中存储的RGB值单个提取出来,可以理解为byte转int
                int r = (compressedImg.getRGB(i, j) & 0xff0000) >> 16;
                int g = (compressedImg.getRGB(i, j) & 0xff00) >> 8;
                int b = (compressedImg.getRGB(i, j) & 0xff);
                //这里是将RGB分别乘上一个权重,得出相应的灰度值gray
                int gray = (30*r + 59*g + 11*b) / 100;
                histogram[gray]++;
                grayScale[i][j] = gray;
                //将得出的灰度值转换成计算机中存储的模式
                int grayStd = ((gray & 0xff)<<16 ) | ((gray & 0xff)<<8) |
                    (gray & 0xff);
                grayScaleImg.setRGB(i, j, grayStd);
            }
        }
    }

    private void histogramEqualization() {
        equalizationImg = 
            new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                int value = 0;
                for (int k = 0; k < grayScale[i][j]; k++)
                    value += histogram[k];
                value = 255 * value / (width * height);
                equalization[value]++;
                int valueStd = ((value & 0xff) << 16 ) | ((value & 0xff) << 8) | 
                    (value & 0xff);
                equalizationImg.setRGB(i, j, valueStd);
            }
        }
    }

    private void grayScaleStretching() {
        stretchingImg = 
            new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        int threshold = 100;
        int a = 0, b = 255, c, d;
        int stretchRatio = 5;
        for (int i = 0; i < 256; i++) {
            if (histogram[i] > threshold) {
                a = i;
                break;
            }
        }
        for (int i = 255; i >= 0; i--) {
            if (histogram[i] > threshold) {
                b = i;
                break;
            }
        }
        c = (a + b) / 2 - (b - a) * (stretchRatio / 2);
        if (c < 0) c = 0;
        d = (a + b) / 2 + (b - a) * (stretchRatio / 2);
        if (d > 255) d = 255;
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                int value = grayScale[i][j];
                if (value < a) value = c * value / a;
                else if (value > b) value = (255 - d) * (value - b) / (255 - b) + d;
                else value = (d - c) * (value - a) / (b - a) + c;
                stretching[value]++;
                int valueStd = ((value & 0xff) << 16 ) | ((value & 0xff) << 8) | 
                    (value & 0xff);
                stretchingImg.setRGB(i, j, valueStd);
            }
        }
    }

    private void show_image(int img) {
        new ImagePlot(grayScaleImg, histogram);
        if (img == 1) new ImagePlot(equalizationImg, equalization);
        if (img == 2) new ImagePlot(stretchingImg, stretching);
    }

    public static void main(String[] args) {
        ImageProcess i1 = new ImageProcess("image01.jpg");
        i1.compress();
        i1.toGrayScale();
        i1.histogramEqualization();
        i1.show_image(1);

        ImageProcess i2 = new ImageProcess("image02.jpg");
        i2.compress();
        i2.toGrayScale();
        i2.grayScaleStretching();
        i2.show_image(2);
        System.out.println("done");
    }
}

// ImagePlot.java

import javax.swing.JFrame;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;

class ImagePlot extends JFrame{
    private BufferedImage image;
    private int[] histogram = null;

    public void paint(Graphics g) {
        super.paint(g);
        int width = image.getWidth();
        int height = image.getHeight();
        int ix = width / 10, iy = height / 3;
        g.drawImage(image, ix, iy, null);
        if (histogram != null) {
            int recWidth = 3;
            g.drawLine(ix + width + ix, iy, ix + width + ix, iy + height);
            g.drawLine(ix + width + ix, iy + height, 
                       ix + width + ix + 256 * recWidth, iy + height);
            g.drawLine(ix + width + ix + 256 * recWidth, iy, 
                       ix + width + ix + 256 * recWidth, iy + height);
            g.setFont(new Font("Times New Roman", Font.PLAIN, 12));
            g.drawString("0", ix + width + ix - 10, iy + height + 10);
            int x = ix + width + ix, y = iy + height;
            int yMax = 0;
            for (int i = 0; i < 256; i++) {
                if (histogram[i] > yMax) yMax = histogram[i];
            }
            int lineSpace = 500;
            if (yMax > 10000) lineSpace = 2000;
            if (yMax > 50000) lineSpace = 5000;
            for (int i = 0; i < yMax / lineSpace; i++) {
                g.setColor(Color.lightGray);
                int h = iy + height - (i + 1) * lineSpace * height / yMax;
                g.drawLine(ix + width + ix, h, ix + width + ix + 256 * recWidth, h);
                g.setColor(Color.BLACK);
                g.setFont(new Font("Times New Roman", Font.PLAIN, 12));
                g.drawString(String.valueOf((i + 1) * lineSpace), 
                             ix + width + ix - 30, h + 5);
            }
            for (int i = 0; i < 256; i++) {
                g.setColor(Color.blue);
                int h = histogram[i] * height / yMax;
                g.drawRect(x, y - h, recWidth, h);
                if ((i + 1) % 16 == 0) {
                    g.setColor(Color.BLACK);
                    g.setFont(new Font("Times New Roman", Font.PLAIN, 12));
                    g.drawString(String.valueOf(i + 1), x - recWidth, y + 12);
                }
                x += recWidth;
            }
        }
    }

    ImagePlot(BufferedImage image) {
        super();
        this.image = image;
        this.setVisible(true);
    }

    ImagePlot(BufferedImage image, int[] histogram) {
        super();
        this.image = image;
        this.histogram = histogram;
        this.setVisible(true);
    }
}

相关标签: 杂记