Java类似相机中图像处理(上)

一、前情提要

二、项目流程

java 图像描边 java图像处理_图像处理1. 创建窗体

java 图像描边 java图像处理_图像处理2. 动作监听器

java 图像描边 java图像处理_图像处理3. 获取图片文件像素值

java 图像描边 java图像处理_图像处理4. 画原画

java 图像描边 java图像处理_图像处理5. 画灰度图

java 图像描边 java图像处理_图像处理6. 画黑白二值图

java 图像描边 java图像处理_图像处理7. 画马赛克图

三、效果图

一、前情提要

java 图像描边 java图像处理_图像处理上篇文章介绍了图像的原理,RGB三色值用int值存储以及再拆分为三色值,Java中的位移运算符以及十六进制数,这篇文章我将把这些付诸实际,制作几个简单的图像处理功能。

二、项目流程

1. 创建窗体

  首先,我们要有窗体来显示图像,就用到了之前学习的JFrame,并为每个按钮添加动作监听器ImageListener来分别实现功能。

public class ImagePad {
    String[] strs = {"打开","保存","原图","灰度","二值化","马赛克","圆点马赛克",
            "怀旧","轮廓","素描","锐化","缩小","放大",
    };// 本篇没有全部实现,不过后面会做出完整的项目
    // 创建监听器对象
    ImageListener img1 = new ImageListener();

    public void showUI(){
        JFrame jf = new JFrame("图像处理V1.0");
        jf.setSize(800,600);//大小
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//直接关闭
        jf.setLocationRelativeTo(null);//中心弹窗
        jf.setLayout(new FlowLayout());//流式布局

        // 创建按钮对象 | 设置属性(文本 尺寸 背景颜色 字体大小 监听器)
        Font font = new Font("黑体",Font.BOLD,16);
        for (int i = 0; i < strs.length; i++) {
            JButton btn = new JButton(strs[i]);
            btn.setBackground(Color.WHITE);
            btn.setFont(font);
            btn.addActionListener(img1);//添加动作监听器
            jf.add(btn);
        }

        jf.setVisible(true);
        // 可视化之后 获取一个Graphics
        img1.g = jf.getGraphics();//监听器获取窗体中的画笔
    }

    public static void main(String[] args) {
        new ImagePad().showUI();
    }
}

2. 动作监听器

  在讲动作监听器之前,我先列举一下这次的动作监听器用到的新知识。

  1. 代码块:{}
  • 在程序中用一对中括号包括一些代码语句,表示创建对象的时候执行,代表初始化
  • 可以放一些程序中经常需要同样调用的函数,可以节省时间,也避免了不必要的开销。
  1. switch(a) case语句
  • switch-case语句可以很方便的进行情况列举,比通常的if else if…简便很多,根据a和case的值匹配,运行相应的代码。
  • 不要忘记break;跳出循环,也可以写上default语句,便于都不满足时执行。

  下面是动作监听器ImageListener的代码

public class ImageListener implements ActionListener {
    ImageTools imgtools = new ImageTools();
    Graphics g;
    int[][] imageArr;

    {// 代码块 创建对象的时候就执行 初始化的方法调用
        String path = "D:\\OneDrive\\桌面\\xinhai.jpg";
        // 传入 图片路径 得到图片的像素矩二维数组
        imageArr = imgtools.getImagePiexArray (path);//下面会讲到,不用着急
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        // 获取按钮上的字符串
        String btnStr = e.getActionCommand();
        System.out.println("点击了:"+ btnStr);

        // 根据点击的字符串 操作绘制图片
        switch (btnStr){
            case "原图":
                // 绘制图片
                System.out.println("原图");
                imgtools.drawImage(imageArr,g);
                break;
            case "灰度":
                imgtools.drawGrayImage(imageArr,g);
                break;
            case "二值化":
                imgtools.drawBinaryImage(imageArr,g);
                break;
            case "马赛克":
                imgtools.drawMosaicImage(imageArr,g);
                break;
        }
    }
}

3. 获取图片文件像素值

  这样基本的框架就打好了,我们现在来学习绘图功能的实现。

先普及个小知识,在idea中已经写好的方法前面输入/**在回车,会出现一种新的注释,叫做文档注释例如

/** 文档注释 * 根据一个路径来获取图片的二维像素数组 * @param path 参数: 图片路径 * @return 图片的二位数组 */

  我们先考虑如何画出原图

  首先要创建一个获取图像中像素的方法,由于图像就是一个二维矩阵,所以我们的返回值也要是一个二维数组。

  我们要先获得图像文件中的像素,自然就需要用到文件操作,

/** 文档注释
 * 根据一个路径来获取图片的二维像素数组
 * @param path 参数: 图片路径
 * @return 图片的二位数组
 */  
public int[][] getImagePiexArray(String path){
    File file = new File(path);//path是文件的路径
}

  然后我们使用BufferedImage类创建一个图像缓冲区,用来将图片加载到内存中,通过图像缓冲区能够很方便的操作图像。

BufferedImage buffimg = null;

  通过ImageIO中的read方法读取上面创建的图片文件对象file,装载在buffimg中

buffimg = ImageIO.read(file);

  但是直接这么写你会发现报错

java 图像描边 java图像处理_java_09

  提示我们要try catch环绕,那就点击照它说的做

java 图像描边 java图像处理_图像处理_10

  这下就不报错了

  现在这张图片的数据已经都存在buffimg中了,我们可以进行进一步操作。

  图片的本质也就是像素值的二维矩阵,对于buffimg也同理,我们分别获取这个二维矩阵的长和宽

//buffimg 宽 高 像素值
int width = buffimg.getWidth();
int height = buffimg.getHeight();

  现在我们创建一个同样长同样宽的二维数组imgArr,为了下面存储图片每个位置的像素值。

int[][] imgArr = new int[width][height];

  接下来我们遍历这个二维数组,将bufferimg中的像素RGB值存入imgArr。使用的是BufferedImage中的getRGB(int x,int y)方法获取某一位置的像素

for (int i = 0; i < imgArr.length; i++) {
    for (int j = 0; j < imgArr[i].length; j++) {
        imgArr[i][j] = buffimg.getRGB(i,j);
    }
}
return imgArr;

  这样获取图像像素值的方法就写完了,我们可以在别的方法中调用以快速获取图片像素。

  完整代码如下

public int[][] getImagePiexArray(String path){
        File file = new File(path);
        BufferedImage buffimg = null;

        try {
            buffimg = ImageIO.read(file);
        } catch (IOException e) {
            e.printStackTrace();
        }

        //buffimg 宽 高 像素值
        int width = buffimg.getWidth();
        int height = buffimg.getHeight();
        // 根据宽 高 创建一个二维数组
        int[][] imgArr = new int[width][height];

        // 遍历循环 将所有的像素值 存入数组中了
        // 遍历 从头到尾 全部取出来
        for (int i = 0; i < imgArr.length; i++) {
            for (int j = 0; j < imgArr[i].length; j++) {
                imgArr[i][j] = buffimg.getRGB(i,j);
            }
        }
        return imgArr;
}

4. 画原图

把获得的imgArr数组原封不动的画出来,因为我们的imgArr本来就是存储的图片文件的像素,那么直接遍历数组画出来就是原图。

  画图我们肯定需要画笔对象Graphics,每个像素值我们可以用自己喜欢的方式去绘制,比如按照矩形绘制,按照圆形绘制等等。

/**
     * 原图
     * @param imgArr
     * @param g
     */
    public void drawImage(int[][] imgArr, Graphics g){
        for (int i = 0; i < imgArr.length; i++) {
            for (int j = 0; j < imgArr[i].length; j++) {
                int pixnum = imgArr[i][j];//获取当前像素颜色,用int值表示
                Color color = new Color(pixnum);
                g.setColor(color);
                X Y 为预先定义好的偏置值,设定图像的位置
                g.fillRect(X + i, Y + j, 1, 1);//采用矩形绘制,原图像素点也是1*1的矩形,因此不会有任何缝隙
            }
        }
    }

5. 画灰度图

拆分出三原色并按一定比例混合。

画灰度图的6种方法:

1.浮点法:Gray=R0.3+G0.59+B*0.11

2.整数法:Gray=(R30+G59+B*11)/100

3.移位法:Gray =(R77+G151+B*28)>>8;

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

5.仅取绿色:Gray=G;

6.Gamma校正算法:

java 图像描边 java图像处理_前端_11

  这里我用的是第四种平均值法:

  用到了上节课的int值拆分为RGB

/**
     * 灰度图
     * @param imgArr
     * @param g
     */
    public void drawGrayImage(int[][] imgArr, Graphics g){
        for (int i = 0; i < imgArr.length; i++) {
            for (int j = 0; j < imgArr[i].length; j++) {
                int pixnum = imgArr[i][j];
                // 拆分像素值为 R G B
                int red = (pixnum >> 16) & 255;
                int green = (pixnum >> 8) & 255;
                int blue = (pixnum >> 0) & 255;
                // 灰度的原理 R=G=B
                // 均值法
                int gray = (red + green + blue)/3;
                Color color = new Color(gray,gray,gray);
                g.setColor(color);
                g.fillRect(X + i, Y + j,1 ,1);
            }
        }
    }

6. 画黑白二值图

对灰度值再进行划分,大于等于127的设为白色,小于127的设为黑色,就完成了简单的二值化。

/**
     * 黑白
     * @param imgArr
     * @param g
     */
    public void drawBinaryImage(int[][] imgArr, Graphics g){
        for (int i = 0; i < imgArr.length; i++) {
            for (int j = 0; j < imgArr[i].length; j++) {
                int pixnum = imgArr[i][j];
                // 拆分像素值为 R G B
                int red = (pixnum >> 16) & 255;
                int green = (pixnum >> 8) & 255;
                int blue = (pixnum >> 0) & 255;
                // 灰度的原理 R=G=B
                // 均值法
                int gray = (red + green + blue)/3;
                // 利用灰度值 做二分
                if(gray < 127){
                    g.setColor(Color.BLACK);
                }else {
                    g.setColor(Color.WHITE);
                }
                g.fillRect(X + i, Y + j,1 ,1);
            }
        }
    }

7. 画马赛克图

改成10 * 10,并且把数组的便利步长也改为加10,那么就达成了简单的马赛克效果,因为像素块变大了。

/**
     * 马赛克
     * @param imgArr
     * @param g
     */
    public void drawMosaicImage(int[][] imgArr, Graphics g){
        for (int i = 0; i < imgArr.length; i+=10) {
            for (int j = 0; j < imgArr[i].length; j+=10) {
                int pixnum = imgArr[i][j];
                // 间距采样
                Color color = new Color(pixnum);
                g.setColor(color);
                g.fillRect(X+i,Y+j,10,10);
            }
        }
    }

三、效果图(使用流行的心海表情包)

原图

java 图像描边 java图像处理_图像处理_12

灰度图

java 图像描边 java图像处理_图像处理_13

黑白二值图

java 图像描边 java图像处理_前端_14

马赛克图

java 图像描边 java图像处理_java 图像描边_15