人工智能应用实例:图片降噪
场景设置
对白色背景、黑色前景的黑白图片进行降噪处理,可以假定背景部分多于前景。
降噪模型
我们可以对图片建立这样一个两层的二维模型,底层表示原图,顶层表示任意的噪声图,xi为原图第i个像素,yi为噪声图第i个像素,xi、yi的取值只有1和-1,1表示白色,-1表示黑色。
显然,我们对噪声图任取的一个像素yi可以有以下3个推测:
1、这个像素更加可能是白色,因为白色背景多于黑色前景。
2、这个像素应该和它周围的几个像素颜色一致。
3、如果不能确定这个像素在原图到底是白色还是黑色,那么更有可能原图就是现在噪声图的这个颜色,因为毕竟噪声像素是少量的,大部分像素维持了原图的颜色。
基于此,可以对噪声图定义一个能量公式:
注意:这里的xi、yi颠倒过来了,xi为噪声图,yi为原图。
公式中三个参数都是正实数,第一项表示对噪声图每个像素的值求和,第二项表示对噪声图每个像素与之周围像素差异的求和,第三项表示噪声图像素与原图像素的差异求和。显然基于我们以上的3点推测,越接近原图,噪声图的能量值应该越低。
降噪过程
有了这样一个图片模型和噪声图能量公式,我们就可以对图片降噪了。图片降噪基于一个很简单的想法,我们对整个噪声图的每个像素扫描一次(也可以扫描多遍),看看每次改变该像素点的颜色能否使整个噪声图的能量值降低,如果能量值降低了,我们就接受这一次改动,否则,该像素颜色保持不变。
不过,这里还存在一个问题,我们不可能取得能量公式中原图每个像素yi的值!其实这也无妨,可以简单地用每次降噪处理(一次只处理一个像素)得到的新的降噪图去逼近原图,而一开始采用的就是噪声图。因此,每次降噪处理都要计算两个能量值,一个是当前已经得到的降噪图(被用来逼近原图,即xi、yi都是自身)的能量值,另一个是改变一个像素颜色得到的图片(xi为自身,yi为前面得到的降噪图)的能量值。
Java代码实现
由于某些限制,代码实现中图片读写使用了十分复杂的手段,实际上可以通过Java的imageio包进行简化。
另外,图片能量公式的3个参数这里采用了手动指定的方式:fa=0.0,fb=1.0,fc=2.1,还可以通过更加智能的算法自动学习得到,比如爬山算法、模拟退火算法、遗传算法。
// ImageIOer.java /** * > ImageIOer * * ImageIOer is a class to read and save images, * and it implements IImageIO. * * @author RuanShiHai */ import java.awt.*; import java.awt.image.*; import java.io.*; import javax.imageio.*; public class ImageIOer { /** * Read a bmp image * @param filePath - the image file to read * @return an Image object of the image */ public Image myRead(String filePath) { try { // the length of BMP file header int bfLen = 14; // the length of BMP information header int biLen = 40; FileInputStream fs = new FileInputStream(filePath); byte[] bf = new byte[bfLen]; byte[] bi = new byte[biLen]; // read BMP file header fs.read(bf, 0, bfLen); // read BMP information header fs.read(bi, 0, biLen); // pixels value of width int biWidth = ((((int) bi[7] & 0xff) << 24) | (((int) bi[6] & 0xff) << 16) | (((int) bi[5] & 0xff) << 8) | ((int) bi[4] & 0xff)); // pixels value of height int biHeight = (((int) bi[11] & 0xff) << 24) | (((int) bi[10] & 0xff) << 16) | (((int) bi[9] & 0xff) << 8) | ((int) bi[8] & 0xff); // the number of bits of each pixel int biBitCount = ((((int) bi[15] & 0xff) << 8) | ((int) bi[14] & 0xff)); // the size of bytes of the image int biSizeImage = ((((int) bi[23] & 0xff) << 24) | (((int) bi[22] & 0xff) << 16) | (((int) bi[21] & 0xff) << 8) | ((int) bi[20] & 0xff)); // only processes 24bits bmp image if (biBitCount != 24) { fs.close(); return null; } // the number of bytes to be padded after each row int nPad = (biSizeImage / biHeight) - (biWidth * 3); // read pixels data from image into a byte array byte[] bRGB = new byte[biSizeImage]; fs.read(bRGB, 0, biSizeImage); fs.close(); int index = 0; int[] data = new int[biWidth * biHeight]; // store pixels in an array of int type for (int i = biHeight - 1; i >= 0; i--) { for (int j = 0; j < biWidth; j++) { data[(i * biWidth) + j] = (((int) 255 << 24) | (((int) bRGB[index + 2] & 0xff) << 16) | (((int) bRGB[index + 1] & 0xff) << 8) | ((int) bRGB[index] & 0xff)); index += 3; } index += nPad; } // create an Image object Toolkit kit = Toolkit.getDefaultToolkit(); Image image = kit.createImage(new MemoryImageSource(biWidth, biHeight, data, 0, biWidth)); return image; } catch (Exception e) { e.printStackTrace(); System.out.println("Caught exception in loadbitmap!"); } return null; } /** * Store an Image as a bmp image file * @param image - Image object to be saved as a bmp image file * @param filePath - the name of the bmp image file * @return the Image object */ public Image myWrite(Image image, String filePath) { try { File imgFile = new File(filePath + ".bmp"); BufferedImage bi = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB); Graphics2D biContext = bi.createGraphics(); // get a BufferedImage object from an Image object biContext.drawImage(image, 0, 0, null); biContext.dispose(); ImageIO.write(bi, "bmp", imgFile); return image; } catch (Exception e) { e.printStackTrace(System.out); } return null; } }
// PictureNoise.java import java.awt.*; import java.awt.image.*; import java.io.*; import java.util.Random; import javax.imageio.*; public class PictureNoise { private static final double rate = 0.1; private static final double fa = 0; private static final double fb = 1.0; private static final double fc = 2.1; public void generateAPicture() { try { int width = 640; int height = 480; // 创建BufferedImage对象 Font font = new Font("微软雅黑", Font.BOLD, 160); BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); // 获取Graphics2D Graphics2D g2d = image.createGraphics(); // 画图 g2d.setBackground(new Color(255, 255, 255)); g2d.setPaint(new Color(0, 0, 0)); g2d.clearRect(0, 0, width, height); g2d.setFont(font); g2d.drawString("Java", 140, 200); g2d.drawString("Python", 40, 400); // 释放对象 g2d.dispose(); // 保存文件 ImageIO.write(image, "bmp", new File("/home/ln/picture.bmp")); } catch (Exception ex) { ex.printStackTrace(); } } // generate a picture with noise public Image getNoise(Image sourceImage) { BufferedImage bi = new BufferedImage(sourceImage.getWidth(null), sourceImage.getHeight(null), BufferedImage.TYPE_INT_RGB); Graphics2D biContext = bi.createGraphics(); // get a BufferedImage object from an Image object biContext.drawImage(sourceImage, 0, 0, null); biContext.dispose(); // create an array of int type to store rgb values of each pixel int[] rgbs = new int[bi.getWidth() * bi.getHeight()]; bi.getRGB(0, 0, bi.getWidth(), bi.getHeight(), rgbs, 0, bi.getWidth()); Random rd = new Random(); int index = 0; for (int i = 0; i < bi.getHeight(); i++) { for (int j = 0; j < bi.getWidth(); j++, index++) { if (rd.nextDouble() < rate) rgbs[index] = (rgbs[index] & 0xff000000) | (~rgbs[index] & 0x00ffffff); } } // create a new Image object Toolkit kit = Toolkit.getDefaultToolkit(); Image image = kit.createImage(new MemoryImageSource(bi.getWidth(), bi .getHeight(), rgbs, 0, bi.getWidth())); return image; } public Image reduceNoise(Image sourceImage, double fa, double fb, double fc) { BufferedImage bi = new BufferedImage(sourceImage.getWidth(null), sourceImage.getHeight(null), BufferedImage.TYPE_INT_RGB); Graphics2D biContext = bi.createGraphics(); // get a BufferedImage object from an Image object biContext.drawImage(sourceImage, 0, 0, null); biContext.dispose(); // create an array of int type to store rgb values of each pixel int[] rgbs = new int[bi.getWidth() * bi.getHeight()]; bi.getRGB(0, 0, bi.getWidth(), bi.getHeight(), rgbs, 0, bi.getWidth()); int height = bi.getHeight(), width = bi.getWidth(); for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { double dd; int d1, d2, d3; d1 = 2; if ((rgbs[i * width + j] & 0x00ffffff) == 0) d1 = -2; d2 = 0; int rc[][] = { { -1, 0 }, { 0, 1 }, { 1, 0 }, { 0, -1 } }; for (int k = 0; k < 4; k++) { if (isValid(i + rc[k][0], j + rc[k][1], height, width)) { if ((rgbs[(i + rc[k][0]) * width + j + rc[k][1]] & 0x00ffffff) == (rgbs[i * width + j] & 0x00ffffff)) d2 += -2; else d2 += 2; } } d2 *= 2; d3 = -2; dd = fa * d1 - fb * d2 - fc * d3; if (dd < 0) rgbs[i * width + j] = (rgbs[i * width + j] & 0xff000000) | (~rgbs[i * width + j] & 0x00ffffff); } } // create a new Image object Toolkit kit = Toolkit.getDefaultToolkit(); Image image = kit.createImage(new MemoryImageSource(bi.getWidth(), bi .getHeight(), rgbs, 0, bi.getWidth())); return image; } private boolean isValid(int row, int col, int height, int width) { return (row >= 0 && row < height && col >= 0 && col < width); } public static void main(String[] args) { PictureNoise pictureNoise = new PictureNoise(); ImageIOer imageIOer = new ImageIOer(); pictureNoise.generateAPicture(); Image sourceImage = imageIOer.myRead("/picture.bmp"); Image noiseImage = pictureNoise.getNoise(sourceImage); Image noNoiseImage = pictureNoise.reduceNoise(noiseImage, fa, fb, fc); imageIOer.myWrite(noiseImage, "/picture_with_noise"); imageIOer.myWrite(noNoiseImage, "/picture_noise_processed"); } }