Java类似相机中图像处理(上)
一、前情提要
二、项目流程
1. 创建窗体
2. 动作监听器
3. 获取图片文件像素值
4. 画原画
5. 画灰度图
6. 画黑白二值图
7. 画马赛克图
三、效果图
一、前情提要
上篇文章介绍了图像的原理,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. 动作监听器
在讲动作监听器之前,我先列举一下这次的动作监听器用到的新知识。
代码块:{}
:
- 在程序中用一对中括号包括一些代码语句,表示创建对象的时候执行,代表初始化
- 可以放一些程序中经常需要同样调用的函数,可以节省时间,也避免了不必要的开销。
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);
但是直接这么写你会发现报错
提示我们要try catch
环绕,那就点击照它说的做
这下就不报错了
现在这张图片的数据已经都存在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校正算法:
这里我用的是第四种平均值法:
用到了上节课的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);
}
}
}
三、效果图(使用流行的心海表情包)
原图
灰度图
黑白二值图
马赛克图