BMP 图像文件解析及直方图均衡化算法(Java)

Posted on 2022-11-28 17:04  Capterlliar  阅读(140)  评论(0编辑  收藏  举报

BMP图像解析,基本照抄关于Java读取和编写BMP文件的总结

直方图均衡化没抄着,自己写了一个。

代码结构:

GUI:

Java Swing实现

import Utils.BMPImage;
import Utils.GraphUtil;

import javax.swing.*;
import java.awt.*;

public class GUI extends JFrame {
    public String path="./src/main/resources/Panda4.bmp";

    public void init(){
        this.setTitle("BMP解析");//设置标题
        this.setSize(600,350);//设置窗体大小
        this.setDefaultCloseOperation(3);//点击关闭,程序自动退出。
        this.setResizable(false);//设置窗体大小不可以调节
        this.setLocationRelativeTo(null);//设置窗体出现在屏幕中间
        this.setLayout(new BorderLayout());

        JPanel panel1=new JPanel();
        panel1.setBorder(BorderFactory.createEmptyBorder(10,10,0,10));
        panel1.setLayout(new GridLayout(1,2,0,5));
        BMPImage bmpImage = new BMPImage(this.path);
        panel1.add(bmpImage);

        JPanel panel2=new JPanel();
        JButton button1=new JButton("直方图均衡化");
        button1.setFocusable(false);
        panel2.add(button1);
        button1.addActionListener(e -> {
            BMPImage bmpImage2=GraphUtil.HistogramEqualization(bmpImage);
            panel1.add(bmpImage2);
            SwingUtilities.updateComponentTreeUI(panel1);
        });

        this.add(panel1,BorderLayout.CENTER);
        this.add(panel2,BorderLayout.SOUTH);

        this.setVisible(true);
    }
    public static void main(String[] args) {
        GUI gui = new GUI();
        gui.init();
    }
}
View Code

BMPImage:

重载paint函数来显示图片。注意这段代码只能打开24-bit的bmp。

package Utils;

import javax.swing.*;
import java.awt.*;
import java.io.IOException;

public class BMPImage extends JPanel {
    public int width;

    public int height;

    public int[][] red, green, blue;

    public BMPImage(String fileName) {
        try {
            java.io.FileInputStream fin = new java.io.FileInputStream(fileName);

            java.io.BufferedInputStream bis = new java.io.BufferedInputStream(
                    fin);

            // 建立两个字节数组来得到文件头和信息头的数据
            byte[] array1 = new byte[14];
            bis.read(array1, 0, 14);

            byte[] array2 = new byte[40];
            bis.read(array2, 0, 40);

            // 翻译bmp文件的数据,即将字节数据转化为int数据
            // 通过翻译得到位图数据的宽和高
            width = ChangeInt(array2, 7);
            height = ChangeInt(array2, 11);

            // 调用可以将整个位图数据读取成byte数组的方法
            getInf(bis);

            fin.close();
            bis.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public BMPImage(int width,int height){
        this.width=width;
        this.height=height;
        red = new int[height][width];
        green = new int[height][width];
        blue = new int[height][width];
    }

    public int ChangeInt(byte[] array2, int start) {
        // 因为char,byte,short这些数据类型经过运算符后会自动转为成int数据类,
        // 所以array2[start]&0xff的实际意思就是通过&0xff将字符数据转化为正int数据,然后在进行位运算。
        // 这里需要注意的是<<的优先级别比&高,所以必须加上括号。

        int i = (int) ((array2[start] & 0xff) << 24)
                | ((array2[start - 1] & 0xff) << 16)
                | ((array2[start - 2] & 0xff) << 8)
                | (array2[start - 3] & 0xff);
        return i;
    }

    public void getInf(java.io.BufferedInputStream bis) {
        // 给数组开辟空间
        red = new int[height][width];
        green = new int[height][width];
        blue = new int[height][width];

        // 通过计算得到每行计算机需要填充的字符数。
        // 为什么要填充?这是因为windows系统在扫描数据的时候,每行都是按照4个字节的倍数来读取的。
        // 因为图片是由每个像素点组成。而每个像素点都是由3个颜色分量来构成的,而每个分量占据1个字节。
        // 因此在内存存储中实际图片数据每行的长度是width*3。
        int skip_width = 0;
        int m = width * 3 % 4;
        if (m != 0) {
            skip_width = 4 - m;
        }

        // 通过遍历给数组填值
        // 这里需要注意,因为根据bmp的保存格式。
        // 位图数据中height的值如果是正数的话:
        // 那么数据就是按从下到上,从左到右的顺序来保存。这个称之为倒向位图。
        // 反之就是按从上到下,从左到右的顺序来保存。这个则称之为正向位图。
        for (int i = height - 1; i >= 0; i--) {
            for (int j = 0; j < width; j++) {
                try {
                    // 这里遍历的时候,一定要注意本来像素是有RGB来表示,
                    // 但是在存储的时候由于windows是小段存储,所以在内存中是BGR顺序。
                    blue[i][j] = bis.read();
                    green[i][j] = bis.read();
                    red[i][j] = bis.read();

                    // 这里一定要知道,其实系统在给位图数据中添加填充0的时候,都是加在每行的最后。
                    // 但是我们在使用dis.skipBytes()这个方法的时候,却不一定要在最后一列。
                    // 系统在填充数据的时候,在数据上加了标记。
                    // 所以dis.skipBytes()这个方法只要调用了,那么系统就会自动不读取填充数据。
                    if (j == 0) {
                        bis.skip(skip_width);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                try {
                    g.setColor(new Color(red[i][j], green[i][j], blue[i][j]));
                    g.fillRect(j, i, 1, 1);// 这里可以使用画点的任何方法,除了上面那种特例。
                }catch (Exception e){
                    continue;
                }
            }
        }
    }
}
View Code

GraphUtil放处理图片的方法。

HistogramEqualization: 直方图均衡化

灰度计算公式:

red * 0.3 + green * 0.59 + blue * 0.11

直方图均衡化过程:计算每一灰度值所拥有像素在全部像素中比例,依次进行累加,将其前缀和作为该灰度在新图像中的映射值。原理大概是累计分布函数大致是均匀的。

package Utils;

import java.util.HashMap;
import java.util.Map;

public class GraphUtil {
    public static BMPImage HistogramEqualization(BMPImage img){
        int[][] gray=new int[img.height][img.width];
        Map<Integer,Integer> map=new HashMap<>();
        for(int i=0;i<img.height;i++){
            for(int j=0;j<img.width;j++){
                try {
                    gray[i][j] = (int) (img.red[i][j] * 0.3 + img.green[i][j] * 0.59 + img.blue[i][j] * 0.11);
                    if (map.containsKey(gray[i][j])) {
                        map.put(gray[i][j], map.get(gray[i][j]) + 1);
                    } else {
                        map.put(gray[i][j], 1);
                    }
                }catch (Exception e){
                    System.out.println(i+" "+j);
                }
            }
        }
        Map<Integer,Integer> map2=new HashMap<>();
        int ans=0;
        int cnt=img.width*img.height;
        for(Map.Entry<Integer,Integer> entry:map.entrySet()) {
            ans+=entry.getValue();
            int newval=(int)(((double)ans/cnt)*256);
            map2.put(entry.getKey(),newval);
        }
        for(int i=0;i<img.height;i++){
            for(int j=0;j<img.width;j++){
                gray[i][j]=map2.get(gray[i][j]);
            }
        }
        BMPImage img2=new BMPImage(img.width,img.height);
        for(int i=0;i<img.height;i++){
            for(int j=0;j<img.width;j++){
                img2.red[i][j]=gray[i][j];
                img2.green[i][j]=gray[i][j];
                img2.blue[i][j]=gray[i][j];
            }
        }
        return img2;
    }
}
View Code

放两张运行结果图:

 

 熊猫的眼睛和猫猫条纹都出来了,不错。