图像处理方法java(一)
1.引言
??初学java,想做一个简易的美颜相机,对图像处理的一些方法进行了相应的学习。主要包括图像的原理、图像的数据处理、位运算、滤镜效果、图片读写等。
2.基本原理
2.1图像的基本原理
??每一张图像,其实都是由一个个的带有颜色的点组成的矩阵,也就是我们常说的像素点矩阵,同时横纵坐标像素点的个数乘积就是图像的分辨率。 ??每一个像素点的颜色,都是有相应的编号的,一般的存储格式是RGB和ARGB格式。RGB格式:RGB其实就是三原色,Red、Green、Blue,每一种原色的编号范围是0-255,也就是存储在一个字节(8位2进制)中。Red从0到255颜色会越来越“红”,红的程度会加深。 ??举个例子来说,对于RGB: 123, 200,13 来说,这个颜色是由Red123,Green200,Blue13这三种颜色合成的。 ??所以我们很容易知道,RGB是占三个字节,可以存储在整型变量(四个字节)中,并且存储在低24位,那么就会出现高8位没有使用的情况。ARGB格式就是把高8位也利用起来,用来存其他的信息例如照片拍摄日期等等。
2.2图像数据的处理
??初始化生成一个窗体,然后通过随机数随机生成一张RGB格式的图片。通过位移运算计算RGB格式里red、green、blue的值(位移运算在2.3会讲解)。对于滤镜等操作,就可以通过调节red、green、blue的值来实现,比如说red=red/2,就能实现图片红色降度,也就是照片的红色不会太鲜艳。 ??我们常常也会听说灰度图片,就是照片会变成灰色,这种照片的每一个像素点的RGB值均满足red=green=blue,我们可以调整三原色的占比来确定灰阶(详见代码)。 ??二维码:有了灰阶,也就是对每一个像素值都有唯一标准了。我们可以确定一个分界值a,当算出的灰度值大于a,就用白色来表示;若小于a,就用黑色来表示,并且使用fiilRect方法的时候,把像素点的宽和高调大一点,我们就可以得到这张图片的二维码了,也就是说,这个二维码储存了我这张图片的数据。
import javax.swing.*;
import java.awt.*;
import java.util.Random;
import javax.swing.JFrame;
public class ImageUI extends JFrame {
public void initUI(){
setTitle("图像编程");
setSize(1000,1000);
setVisible(true);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public void paint(Graphics g){
super.paint(g);
int[][] imgarr=new int[300][300];
Random random = new Random();
for (int i = 0; i < 300; i++) {
for (int j = 0; j < 300; j++) {
int colorValue = random.nextInt(256*256*256);
imgarr[i][j]=colorValue;
Color color = new Color(colorValue);
g.setColor(color);
g.fillRect(50+i,50+j,1,1);
}
}
for (int i = 0; i <imgarr.length; i+=10) {
for (int j = 0; j < imgarr[i].length; j+=10) {
int value = imgarr[i][j];
int red =(value>>16)&0xFF;
int green =(value>>8)&0xFF;
int blue =(value>>0)&0xFF;
int gray =(int)(red*0.36+green*0.29+blue*0.35);
if(gray>128){
g.setColor(Color.white);
}
else{
g.setColor(Color.black);
}
g.fillRect(400+i,50+j,10,10);
}
}
}
public static void main(String[] args) {
ImageUI img = new ImageUI();
img.initUI();
}
}
运行图片
2.3位运算
??这里主要讲如何通过存储RGB的整型int得到red、green、blue的值;以及red、green、blue如何合成为一个int。 ??我们了解了RGB的存储原理,很容易知道:17-24位存red,9-16位存green,1-8位存blue,用除法和减法可以提取出red、green、blue,但是这样运算速度会慢一些,可以用位移运算,运算速度会快一些。 ??假设RGB的值存储在int value中,通过位移运算有: ??????int red =(value>>16)&0xFF; ??????int green =(value>>8)&0xFF; ??????int blue =(value>>0)&0xFF; 为什么要位移之后与0xFF求与运算呢? 仔细分析后,我们知道,与0xFF做与运算后,可以将除低8位以外的所有位的数字变为0。 ??所以我们以此类推,我们发现把red、green、blue合成一个RGB时,可以通过位移加上或运算来实现: ??????int rgb= (red<<16)|(green<<8)|blue;
3.图片读写与滤镜效果
3.1图片读写
??图片的输入,我写了一个输入图片的方法,加上想要读取的图片路径,路径是相对路径和绝对路径都可以,我的图片和代码是放在一个文件夹里的,所以用的相对路径。 使用ImageIO.read()方法的时候,需要创建BufferedImage类的对象
int[][] imgarr = getImagePixArr("picture4.jpeg");
public int[][] getImagePixArr(String path){
File file = new File(path);
BufferedImage buffimg=null;
try {
buffimg = ImageIO.read(file);
} catch (IOException e) {
e.printStackTrace();
}
int w = buffimg.getWidth();
int h = buffimg.getHeight();
int[][] imgarr = new int[w][h];
for (int i = 0; i < w; i++) {
for (int j = 0; j < h; j++) {
imgarr[i][j]= buffimg.getRGB(i,j);
}
}
return imgarr;
}
??图片输出的话也是一样,也需要创建BufferedImage类的对象,代码如下:
public void outImagePixArr(int[][] imgarr ,String path){
File file = new File(path);
BufferedImage buffimg = new BufferedImage(imgarr.length ,imgarr[0].length ,BufferedImage.TYPE_INT_ARGB);
for (int i = 0; i < imgarr.length; i++){
for (int j = 0; j < imgarr[i].length; j++){
buffimg.setRGB(i, j, imgarr[i][j]);
}
}
try {
ImageIO.write(buffimg, "PNG", file);
} catch (IOException e) {
e.printStackTrace();
}
}
3.2滤镜效果
3.2.1原图输出
for (int i = 0; i < imgarr.length; i++) {
for (int j = 0; j < imgarr[i].length; j++) {
Color color = new Color(imgarr[i][j]);
g.setColor(color);
g.fillRect(i,j,1,1);
}
}
3.2.2马赛克
??马赛克就是模糊处理,把一个像素点的长宽变大即可。
for (int i = 0; i < imgarr.length; i+=10) {
for (int j = 0; j < imgarr[i].length; j+=10) {
Color color = new Color(imgarr[i][j]);
g.setColor(color);
g.fillRect(750+i,j,10,10);
}
}
3.2.3灰度
??在前面已经讲过,灰度图片的red=green=blue。
for (int i = 0; i < imgarr.length; i++) {
for (int j = 0; j < imgarr[i].length; j++) {
int value = imgarr[i][j];
int red=(value>>16)&0xFF;
int green=(value>>8)&0xFF;
int blue=(value>>0)&0xFF;
int gray = (red+green+blue)/3;
Color color = new Color(gray,gray,gray);
g.setColor(color);
g.fillRect(i,400+j,1,1);
}
}
3.2.4滤镜颜色
??这个滤镜是我自己调的,可能不是很好看,red变为原来的0.8,green变成原来的0.5,blue变成原来的0.6。
for (int i = 0; i < imgarr.length; i++){
for (int j = 0; j < imgarr[i].length; j++){
int value = imgarr[i][j];
int red=(value>>16)&0xFF;
int green=(value>>8)&0xFF;
int blue=(value>>0)&0xFF;
red = red*4/5;
green = green/2;
blue = blue*3/5;
Color color = new Color(red,green,blue);
g.setColor(color);
g.fillRect(i,400+j,1,1);
}
}
3.2.5轮廓
for (int i = 0; i < imgarr.length-2; i++) {
for (int j = 0; j < imgarr[i].length-2; j++) {
int value = imgarr[i][j];
int red=(value>>16)&0xFF;
int green=(value>>8)&0xFF;
int blue=(value>>0)&0xFF;
int gray = (red+green+blue)/3;
int nvalue = imgarr[i+2][j+2];
int nred=(nvalue>>16)&0xFF;
int ngreen=(nvalue>>8)&0xFF;
int nblue=(nvalue>>0)&0xFF;
int ngray = (nred+ngreen+nblue)/3;
if(Math.abs(gray-ngray)>10){
g.setColor(Color.white);
}else{
g.setColor(Color.black);
}
g.fillRect(750+i,400+j,1,1);
}
}
运行图片
4.交互式处理
4.1带按钮的窗体
??创建一个UI类,可以生成带按钮的窗体。那个布局方法是自动把按钮布局。
import javax.swing.*;
import java.awt.*;
import javax.swing.JFrame;
public class UI extends JFrame{
String[] strs={"原图","灰度","怀旧","马赛克","二值化","融合","轮廓"};
public void initUI() {
setTitle("图像窗体");
setSize(2200, 1000);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
FlowLayout fl =new FlowLayout();
setLayout(fl);
for (int i = 0; i < strs.length; i++) {
JButton btn = new JButton(strs[i]);
add(btn);
}
setVisible(true);
}
public static void main(String[] args) {
UI img = new UI();
img.initUI();
}
}
运行结果 但是我们在窗体上点击按钮,是没有反应的,于是我们需要一个Listener类,也就是监听器,来监听按钮是否被点击,如果被点击就会进行相应操作。
4.2动作监听器
??ActionListener——动作监听器,作用是监听按钮是否被点击了,如果被点击了就调用一个预设好的方法(方法的内容需要自己来写 )。实现接口interface时,用implements关键字,可以变相实现多继承。
import java.awt.event.ActionListener;
public class ImageListener implements ActionListener{
public void actionPerformed(ActionEvent e){
System.out.println("点击了按钮 ");
}
}
运行结果:点一下按钮就会输出“点击了按钮 ”
4.3综合起来
??前文讲了很多细节的处理以及方法,现在我们就可以创建一个窗体,一个有按钮的窗体,点击按钮能得到相应“滤镜”的图片,不再是4.1那样没有反应的按钮;也不再是4.2只输出一句话,我们重写方法后,可以把图片输出在窗体上。
4.3.1创建ImageUtils类
??创建这个工具类目的有两个:第一,读取某个路径下的图片,把他存在我们的像素点矩阵imgarr(二维数组)中,这个我在3.1中有介绍;第二,就是能把处理后的像素点矩阵“画出来”,也就是图片。
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class ImageUtils {
public int[][] getImagePixArr(String path){
File file = new File(path);
BufferedImage buffimg=null;
try {
buffimg = ImageIO.read(file);
} catch (IOException e) {
e.printStackTrace();
}
int w = buffimg.getWidth();
int h = buffimg.getHeight();
int[][] imgarr = new int[w][h];
for (int i = 0; i < w; i++) {
for (int j = 0; j < h; j++) {
imgarr[i][j]= buffimg.getRGB(i,j);
}
}
return imgarr;
}
public void drawImage0(int[][] imgarr,Graphics g){
for (int i = 0; i < imgarr.length; i++) {
for (int j = 0; j < imgarr[i].length; j++) {
Color color = new Color(imgarr[i][j]);
g.setColor(color);
g.fillRect(100+i,100+j,1,1);
}
}
}
}
4.3.2创建ImageListener类
??创建这个动作监听器类,作用是监听按钮是否被点击了,如果点击了就调用相应的方法。我们在4.2中重写了方法,点击就会输出“点击了按钮 ”;现在,我们再次重写方法,点击相应的按钮,就输出对应按钮的图片。(这里我只重写了输出原图的方法,输出其他“滤镜”图片的方法只需要把3.2中的代码粘贴修改即可)
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class ImageListener implements ActionListener{
Graphics gr;
ImageUtils imageUtils = new ImageUtils();
int[][] imgarr=null;
{
imgarr = imageUtils.getImagePixArr("picture4.jpeg");
}
public void actionPerformed(ActionEvent e){
String btnstr = e.getActionCommand();
if(btnstr.equals("原图")){
imageUtils.drawImage0(imgarr,gr);
}
}
}
4.3.3创建ImageUI类
??创建这个类,其实就是输出带有按钮的窗体,参考4.1,代码如下:
import javax.swing.*;
import java.awt.*;
public class ImageUI extends JFrame {
String[] strs={"原图","灰度","怀旧","马赛克","二值化","融合","轮廓"};
public ImageUI(String title) {
setTitle(title);
setSize(1000, 1000);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
FlowLayout fl =new FlowLayout();
setLayout(fl);
ImageListener imageL = new ImageListener();
for (int i = 0; i < strs.length; i++) {
JButton btn = new JButton(strs[i]);
btn.setBackground(Color.white);
btn.addActionListener(imageL);
add(btn);
}
setVisible(true);
Graphics g = this.getGraphics();
imageL.gr=g;
}
public static void main(String[] args) {
new ImageUI("图像处理编程");
}
}
4.3.4运行结果
??(1)没有点击按钮时:
??(2)点击按钮“原图”时:
5.心得体会
??本文到这里就结束了,后面博客会补充更“厉害”的图像处理方法,如PS等。一介java小白,欢迎大家批评指正。
|