简易美颜相机
简易美颜相机,是我最初接触JAVA的小项目,通过这个项目,我初步学会了许多JAVA的基础知识,下面就让我们开始吧!
首先让我们理清整个项目的大概逻辑:
1.一张图片和摄像头的显示以及各种不同的处理效果,是在窗体上显示的,所以我们首先需要创建需要一个窗体;
2.处理效果是鼠标点击窗体上的按钮来实现的,所以我们肯定需要添加许多按钮,为了美观也可以添加面板和不同的布局管理器;
3.图像是在窗体上“画”出来的,所以我们肯定需要在窗体上获取画笔,而不同的处理效果则是画笔的不同“画法”来实现的;
4.摄像头方面首先需要导入java额外的webcam包(这里不再赘述,大家可以自行百度解决),然后需要添加线程来控制开启和关闭,其余的跟处理图片的步骤类似。
总体就是这四步骤啦,下面一个个来实现。
/*1.1显示美颜相机界面(功能) *1.2监听器:设置界面程序的交互功能 *1.3在界面上显示图像效果(原图,处理的效果图):获取画笔 */ public class drawPixel0715 { public static void main(String[] args){ drawPixel0715 ui=new drawPixel0715(); ui.showUI(); } //1.1显示操作界面的方法 //swing都有一个paint方法,绘制组件 public void showUI(){ //窗体:默认边框布局 MyFrame jf=new MyFrame(); //像素点 jf.setSize(1300,800);//窗体的特有设置大小方法 jf.setTitle("PC版美颜相机");//窗体的标题 //设置居中显示 jf.setLocationRelativeTo(null); //设置退出进程 jf.setDefaultCloseOperation(3); //设置流式布局管理器 BorderLayout border=new BorderLayout(); jf.setLayout(border); //2.面板:默认是流式布局 JPanel eastPanel=new JPanel(); //设置背景色 eastPanel.setBackground(Color.GRAY); Dimension dm=new Dimension(140,0); //处理JFrame,其他组件设置大小都是该方法 eastPanel.setPreferredSize(dm); jf.add(eastPanel,BorderLayout.EAST); //2.按钮 PixelMouse mouse=new PixelMouse(); String[] name={"原图","马赛克","灰度","底片","浮雕","油画","怀旧","黑白","打开图片","保存图片","打开摄像头","关闭摄像头","清屏"}; for(int i=0;i<name.length;i++){ JButton jbu=new JButton(name[i]); jbu.setPreferredSize(new Dimension(200,30));//该dimension不能重复利用 eastPanel.add(jbu); jbu.addActionListener(mouse);
} jf.setVisible(true); //1(3).获取画笔:图像显示在哪个组件上,画笔就从该组件上获取 //从窗体上获取画笔对象,一定要在窗体显示可见之后 Graphics g=jf.getGraphics(); //1(2).监听器 //给窗体添加鼠标监听器,给监听器绑定处理类 jf.addMouseListener(mouse); //给按钮添加动作监听器 //a.事件源:当前动作所发生的组件 //b.确定监听器方法:动作监听器 //c.绑定事件处理类 mouse.gr=g; mouse.getMyFrame(jf); //把PixelMouse类的数组传递给MyFrame jf.arrB=mouse.arrB; } } |
这里需要特别注意,我们需要创建一个类来继承JFrame类,重写paint方法来解决图像的重绘问题(即拖动窗体和暂时隐藏图像效果会消失),代码如下:
//1.4自定义窗体类 public class MyFrame extends JFrame{ public BufferedImage[] arrB; //定义set方法,初始化属性 //super表示当前类的子类对象 //this表示当前类对象 //重写paint方法 public void paint(Graphics g){ //1.绘制组件 super.paint(g); System.out.println("绘制组件!"); //2.图像效果 for(int i=0;i<arrB.length;i++){ BufferedImage bufferImage=arrB[i]; g.drawImage(bufferImage, 40, 90, 1080,540,null); } } |
3.图像处理画法
在处理图像效果时,我们要从微观的角度来考虑:图像是由一个个排列紧密的像素点构成的,这些像素点可以看作一个二维数组,每一个像素点具有坐标、颜色和大小,坐标是x、y来确定,颜色以int型(4个字节32位)数字来表示,又是由透明度(这部分暂时不作处理)和红绿蓝三原色组成的,这四个成员占据依次占据int类型的8位,这里的处理效果是对三原色进行不同处理,例如马赛克就是将一部分像素点放大并把其余像素点覆盖,灰度就是创建一个新的color对象,新三原色数值都是原三原色的均值等等,我们百度参考他人设计好的算法。
然后我们注意到,如果图片分辨率较高,这时就会“画”得比较慢,我们可以添加缓冲区来解决,然后获取缓冲区画笔。图片本身是bufferedImage类的对象,上述提到的二维数组也是通过bufferedImage的getWidth和getHeight来获取。
代码如下:
public class PixelMouse implements MouseListener,ActionListener{ //全局变量:属性 public Graphics gr; //保存传递过来的画笔对象 public String name; public File file; public File f2; public File file2; public BufferedImage bufferImage; //定义BufferedImage数组,保存绘制的缓冲图像数据 public BufferedImage[] arrB=new BufferedImage[100]; //操作数组的下标 public int index=0; public MyFrame jf; public void getMyFrame(MyFrame jf){this.jf=jf;} public void actionPerformed(ActionEvent e){ //识别点击的按钮对象 //获取按钮上的内容,局部变量 name = e.getActionCommand(); if("打开图片".equals(name)){ //实现打开电脑中不同图片的功能,需要添加文件选择器 JFileChooser jfc = new JFileChooser(); //实现文件的过滤功能,第一个参数是选项,第二个和第三个是筛选的文件后缀,可任意添加筛选类型 FileNameExtensionFilter filter = new FileNameExtensionFilter( "JPG & PNG Images", "jpg", "png"); jfc.setFileFilter(filter); //打开菜单 jfc.showOpenDialog(null); //选择的文件路径 file =jfc.getSelectedFile(); System.out.println("file:"+file); } if("保存图片".equals(name)){ //代码形式与打开图片的类似 JFileChooser jfc2 = new JFileChooser(); jfc2.showSaveDialog(null); file2 =jfc2.getSelectedFile(); System.out.println("已保存"); save(); } System.out.println("点击按钮 "+name); } //图像效果处理方法 public void mouseClicked(MouseEvent e){ System.out.println("点击"); //获取当前的坐标值 int x = e.getX(); int y = e.getY(); drawPixel(); } //加载图像数据 // ImageIcon icon = new ImageIcon("E:\\23.jpg"); //render是buff父类,作参数时直接使用子类buff参数,相当于自动转型 public void drawPixel(){ File path = file; int[][] arrPixel = getImagePixel(path);// 20 30 //1.创建缓冲区 bufferImage=new BufferedImage(arrPixel.length,arrPixel[0].length ,BufferedImage.TYPE_INT_RGB); //2.获取缓冲区画笔 Graphics g2=bufferImage.getGraphics(); if("打开图片".equals(name)){ for(int i=0;i<arrPixel.length;i++){ for(int j=0;j<arrPixel[i].length;j++){ int pixel = arrPixel[i][j]; //取出像素值 Color color3 = new Color(pixel); //转成color对象,设置给画笔 g2.setColor(color3); //绘制像素点 g2.fillRect(i, j, 1, 1); } } } if("马赛克".equals(name)) { for(int i=0;i<arrPixel.length;i++){ for(int j=0;j<arrPixel[i].length;j+=8){ int pixel = arrPixel[i][j]; //取出像素值 //右移提取 三原色:pixel>>0&0xFF Color color = new Color(pixel); //转成color对象,设置给画笔 // color.getRed() // color.getGreen(); g2.setColor(color); //绘制像素点 g2.fillRect(i, j, 8, 8);
} } } if("原图".equals(name)){ for(int i=0;i<arrPixel.length;i++){ for(int j=0;j<arrPixel[i].length;j++){ int pixel = arrPixel[i][j]; //取出像素值 Color color3 = new Color(pixel); //转成color对象,设置给画笔 g2.setColor(color3); //绘制像素点 g2.fillRect(i, j, 1, 1); } } } if("灰度".equals(name)){ for(int i=0;i<arrPixel.length;i++){ for(int j=0;j<arrPixel[i].length;j++){ int pixel2 = arrPixel[i][j]; Color color2=new Color(pixel2); int r=color2.getRed(); int g=color2.getGreen(); int b=color2.getBlue(); int gray=(r+g+b)/3; Color newcolor=new Color(gray,gray,gray); g2.setColor(newcolor); //绘制像素点 g2.fillRect(i, j, 1, 1); } } } if("底片".equals(name)){ for(int i=0;i<arrPixel.length;i++){ for(int j=0;j<arrPixel[i].length;j++){ int pixel3 = arrPixel[i][j]; Color color3=new Color(pixel3); int r=255-color3.getRed(); int g=255-color3.getGreen(); int b=255-color3.getBlue(); Color newcolor2=new Color(r,g,b); g2.setColor(newcolor2); //绘制像素点 g2.fillRect(i, j, 1, 1);
} } } if("浮雕".equals(name)){ for(int i=0;i<arrPixel.length-1;i++){ for(int j=0;j<arrPixel[i].length-1;j++){ int pixel4 = arrPixel[i][j]; int pixel5 = arrPixel[i+1][j+1]; Color dd=new Color(pixel5); Color cc=new Color(pixel4); int r=128+cc.getRed()-dd.getRed(); int g=128+cc.getGreen()-dd.getGreen(); int b=128+cc.getBlue()-dd.getBlue(); if (r > 255) r = 255; if (r < 0) r = 0; if (g > 255) g = 255; if (g < 0) g = 0; if (b > 255) b = 255; if (b < 0) b = 0; Color newcolor3=new Color(r,g,b); g2.setColor(newcolor3); //绘制像素点 g2.fillRect(i, j, 1, 1); } } } if("油画".equals(name)){ for(int i=0;i<arrPixel.length;i=i+4){ for(int j=0;j<arrPixel[i].length;j=j+4){ int pixel6 = arrPixel[i][j]; //取出像素值 Color color6 = new Color(pixel6); //转成color对象,设置给画笔 g2.setColor(color6); //绘制像素点 g2.fillRect(i, j, 5, 5); } } } if("怀旧".equals(name)){ for(int i=0;i<arrPixel.length;i++){ for(int j=0;j<arrPixel[i].length;j++){ int pixel7 = arrPixel[i][j]; //取出像素值 Color color7 = new Color(pixel7); //转成color对象,设置给画笔 int[] rgb=new int[3]; rgb[0] = (int) (0.393*color7.getRed()+0.769*color7.getGreen()+0.189*color7.getBlue()); rgb[1] = (int) (0.349*color7.getRed()+0.686*color7.getGreen()+0.168*color7.getBlue()); rgb[2] = (int) (0.272*color7.getRed()+0.534*color7.getGreen()+0.131*color7.getBlue()); for(int m=0;m<3;m++) { if(rgb[m]<0) rgb[m]=0; else if(rgb[m]>255) rgb[m]=255; } Color newcolor7 = new Color(rgb[0],rgb[1],rgb[2]); g2.setColor(newcolor7); //绘制像素点 g2.fillRect(i, j, 1, 1); } } } if("黑白".equals(name)){ for(int i=0;i<arrPixel.length;i++){ for(int j=0;j<arrPixel[i].length;j++){ int pixel2 = arrPixel[i][j]; Color color2=new Color(pixel2); int r=color2.getRed(); int g=color2.getGreen(); int b=color2.getBlue(); int gray=(r+g+b)/3; if(gray>=128){ r=g=b=255; } else { r=g=b=0;} Color newcolor=new Color(r,g,b); g2.setColor(newcolor); //绘制像素点 g2.fillRect(i, j, 1, 1); } } } if("清屏".equals(name)){ jf.repaint(); for(int i=0;i<arrB.length;i++){ arrB[i]=null; } }
gr.drawImage(bufferImage, 40, 90,1080,540, null); System.out.println("name:"+name); //把图像效果保存在数组中 arrB[index++] = bufferImage; } public void save(){ try{ if(!file2.exists()){ file2.createNewFile(); } ImageIO.write(bufferImage, "png", file2); System.out.println("xxxx"); } catch(IOException e) { e.printStackTrace(); }
} //获取图片的像素值 public int[][] getImagePixel(File path){ //创建文件对象(图片)
//读取图片文件数据,返回缓冲图片对象 BufferedImage bufferImage = null; //这里添加处理异常语句,避免程序在出错时直接中断 try { bufferImage = ImageIO.read(file); } catch (IOException e) { e.printStackTrace(); } //根据缓冲图片的大小确定二维数组 int w = bufferImage.getWidth(); int h = bufferImage.getHeight(); int[][] arrPixel = new int[w][h];
for(int i=0;i<w;i++){ for(int j=0;j<h;j++){ //获取每个位置的像素值 int pixel = bufferImage.getRGB(i, j); arrPixel[i][j] = pixel; } } return arrPixel; }
public void mousePressed(MouseEvent e){
} public void mouseReleased(MouseEvent e){
} public void mouseEntered(MouseEvent e){
} public void mouseExited(MouseEvent e){
} } |
4.摄像头
摄像头处理实际上就是一直从摄像头获取实时图片来处理,就是说这里需要创建一个类来继承Thread线程类。但是,这里我不知道如何将图片处理的算法和摄像头的算法共享使用,所以我就创建了一个Pixel类复制图片算法,代码如下:
public class Pixel { public void drawPixel1(BufferedImage buffImage,Graphics gr) { int[][] arrPixel = getImagePixel(buffImage);
for(int i=0;i<arrPixel.length;i++){ for(int j=0;j<arrPixel[i].length;j+=8){ int pixel = arrPixel[i][j]; //取出像素值 //右移提取 三原色:pixel>>0&0xFF Color color = new Color(pixel); //转成color对象,设置给画笔 // color.getRed() // color.getGreen(); gr.setColor(color); //绘制像素点 gr.fillRect(i+300, j+200, 8, 8);
} } //gr.drawImage(buffImage, 300, 150, 540, 540, null); //jf.repaint(); } public void drawPixel2(BufferedImage buffImage,Graphics gr) { int[][] arrPixel = getImagePixel(buffImage);
for(int i=0;i<arrPixel.length;i++){ for(int j=0;j<arrPixel[i].length;j++){ int pixel2 = arrPixel[i][j]; Color color2=new Color(pixel2); int r=color2.getRed(); int g=color2.getGreen(); int b=color2.getBlue(); int gray=(r+g+b)/3; Color newcolor=new Color(gray,gray,gray); gr.setColor(newcolor); //绘制像素点 gr.fillRect(i+300, j+200, 1, 1); } } //gr.drawImage(buffImage, 300, 150, 540, 540, null); //jf.repaint(); } public void drawPixel3(BufferedImage buffImage,Graphics gr) { int[][] arrPixel = getImagePixel(buffImage);
for(int i=0;i<arrPixel.length;i++){ for(int j=0;j<arrPixel[i].length;j++){ int pixel3 = arrPixel[i][j]; Color color3=new Color(pixel3); int r=255-color3.getRed(); int g=255-color3.getGreen(); int b=255-color3.getBlue(); Color newcolor2=new Color(r,g,b); gr.setColor(newcolor2); //绘制像素点 gr.fillRect(i+300, j+200, 1, 1);
} } //gr.drawImage(buffImage, 300, 150, 540, 540, null); System.out.println("已调用底片处理方法"); //jf.repaint(); } public void drawPixel4(BufferedImage buffImage,Graphics gr) { int[][] arrPixel = getImagePixel(buffImage);
for(int i=0;i<arrPixel.length-1;i++){ for(int j=0;j<arrPixel[i].length-1;j++){ int pixel4 = arrPixel[i][j]; int pixel5 = arrPixel[i+1][j+1]; Color dd=new Color(pixel5); Color cc=new Color(pixel4); int r=128+cc.getRed()-dd.getRed(); int g=128+cc.getGreen()-dd.getGreen(); int b=128+cc.getBlue()-dd.getBlue(); if (r > 255) r = 255; if (r < 0) r = 0; if (g > 255) g = 255; if (g < 0) g = 0; if (b > 255) b = 255; if (b < 0) b = 0; Color newcolor3=new Color(r,g,b); gr.setColor(newcolor3); //绘制像素点 gr.fillRect(i+300, j+200, 1, 1); } } //gr.drawImage(buffImage, 300, 150, 540, 540, null); //jf.repaint(); } public void drawPixel5(BufferedImage buffImage,Graphics gr) { int[][] arrPixel = getImagePixel(buffImage);
for(int i=0;i<arrPixel.length;i=i+4){ for(int j=0;j<arrPixel[i].length;j=j+4){ int pixel6 = arrPixel[i][j]; //取出像素值 Color color6 = new Color(pixel6); //转成color对象,设置给画笔 gr.setColor(color6); //绘制像素点 gr.fillRect(i+300, j+200, 5, 5); } } // gr.drawImage(buffImage, 300, 150, 540, 540, null); //jf.repaint(); } public void drawPixel6(BufferedImage buffImage,Graphics gr) { int[][] arrPixel = getImagePixel(buffImage);
for(int i=0;i<arrPixel.length;i++){ for(int j=0;j<arrPixel[i].length;j++){ int pixel7 = arrPixel[i][j]; //取出像素值 Color color7 = new Color(pixel7); //转成color对象,设置给画笔 int[] rgb=new int[3]; rgb[0] = (int) (0.393*color7.getRed()+0.769*color7.getGreen()+0.189*color7.getBlue()); rgb[1] = (int) (0.349*color7.getRed()+0.686*color7.getGreen()+0.168*color7.getBlue()); rgb[2] = (int) (0.272*color7.getRed()+0.534*color7.getGreen()+0.131*color7.getBlue()); for(int m=0;m<3;m++) { if(rgb[m]<0) rgb[m]=0; else if(rgb[m]>255) rgb[m]=255; } Color newcolor7 = new Color(rgb[0],rgb[1],rgb[2]); gr.setColor(newcolor7); //绘制像素点 gr.fillRect(i+300, j+200, 1, 1);
} } //gr.drawImage(buffImage, 300, 150, 540, 540, null); //jf.repaint(); } public void drawPixel7(BufferedImage buffImage,Graphics gr) { int[][] arrPixel = getImagePixel(buffImage); for(int i=0;i<arrPixel.length;i++){ for(int j=0;j<arrPixel[i].length;j++){ int pixel2 = arrPixel[i][j]; Color color2=new Color(pixel2); int r=color2.getRed(); int g=color2.getGreen(); int b=color2.getBlue(); int gray=(r+g+b)/3; if(gray>=128){ r=g=b=255; } else { r=g=b=0;} Color newcolor=new Color(r,g,b); gr.setColor(newcolor); //绘制像素点 gr.fillRect(i+300, j+200, 1, 1);
} } //gr.drawImage(buffImage, 300, 150, 540, 540, null); //jf.repaint(); }
public int[][] getImagePixel(BufferedImage buffImage) { //创建图片文件
//根据图片大小设置数组 int w = buffImage.getWidth(); int h = buffImage.getHeight(); int[][] arrPixel = new int[w][h];
for(int i=0;i<w;i++) { for(int j=0;j<h;j++) { //获取每个像素点的值 int pixel = buffImage.getRGB(i, j); arrPixel[i][j] = pixel; } } return arrPixel; } } |
public class Threadop extends Thread{ public Graphics gr; public int op; public BufferedImage bufferImage; public Pixel pixel = new Pixel(); public String name; //区分不同处理效果 public void setop(int op){ this.op=op; } public void setPixelName(String n) { name = n; } public void run(){ Webcam webcam = Webcam.getDefault(); webcam.open(); while(true){ bufferImage=webcam.getImage(); if("马赛克".equals(name)) { pixel.drawPixel1(bufferImage, gr); } else if("黑白".equals(name)) { pixel.drawPixel7(bufferImage, gr); } else if("底片".equals(name)) { pixel.drawPixel3(bufferImage, gr); //System.out.println("方法调用了"); } else if("灰度".equals(name)) { pixel.drawPixel2(bufferImage, gr); } else if("浮雕".equals(name)) { pixel.drawPixel4(bufferImage, gr); } else if("怀旧".equals(name)) { pixel.drawPixel6(bufferImage, gr); } else if("油画".equals(name)) { pixel.drawPixel5(bufferImage, gr); } else{ gr.drawImage(bufferImage, 300, 200, 640, 480, null); } if(op==1){break;} } } } |
然后,我们也要在窗体里添加相应的摄像头按钮,在监听器类的全局属性中创建Threadop对象,在actionPerformed方法里添加相应方法,代码如下:
public Webcam webcam; public Threadop tr=new Threadop(); public void getwebcam(Webcam webcam){ this.webcam=webcam; } public void actionPerformed(ActionEvent e){ //识别点击的按钮对象 //获取按钮上的内容,局部变量 tr.gr=gr;//将监听器中已保存的画笔传给线程中的画笔 name = e.getActionCommand(); if("打开图片".equals(name)){ //实现打开电脑中不同图片的功能,需要添加文件选择器 JFileChooser jfc = new JFileChooser(); //实现文件的过滤功能,第一个参数是选项,第二个和第三个是筛选的文件后缀,可任意添加筛选类型 FileNameExtensionFilter filter = new FileNameExtensionFilter( "JPG & PNG Images", "jpg", "png"); jfc.setFileFilter(filter); //打开菜单 jfc.showOpenDialog(null); //选择的文件路径 file =jfc.getSelectedFile(); System.out.println("file:"+file); } if("保存图片".equals(name)){ //代码形式与打开图片的类似 JFileChooser jfc2 = new JFileChooser(); jfc2.showSaveDialog(null); file2 =jfc2.getSelectedFile(); System.out.println("已保存"); save(); } if("打开摄像头".equals(name)){ tr.start(); System.out.println(tr+" op "+tr.op); } if("关闭摄像头".equals(name)){ tr.setop(1); System.out.println(tr+" op "+tr.op); System.out.println("关闭"); jf.repaint(); } if("马赛克".equals(name)) { tr.setPixelName(name); } if("黑白".equals(name)) { tr.setPixelName(name); } if("灰度".equals(name)) { tr.setPixelName(name); } if("怀旧".equals(name)) { tr.setPixelName(name); } if("油画".equals(name)) { tr.setPixelName(name); } if("底片".equals(name)) { tr.setPixelName(name); } if("浮雕".equals(name)) { tr.setPixelName(name); }
System.out.println("点击按钮 "+name); } |
这样,我们的项目就基本完成了。
实现效果如下:
基本界面:
图像处理效果:
(打开摄像头)
(灰度效果)
总结:
这个小项目在老师的指导下,历经千辛万苦才完成,从只能显示图片、不同效果处理一张图片、处理多张图片、使用摄像头、不同效果处理摄像头,一步步走来,在写这篇教程的过程中,我真实地感觉自己的动手写代码能力提高了不少,许多基础知识都灵活地运用其中,作品完成时我内心有说不尽的非常开心与激动。学无止境,我会一直走下去的。最后,感谢您的阅读!