Java Image Processing Cookbook

网上看到一本e-book,名字叫“Java Image Processing Cookbook”(链接:http://www.lac.inpe.br/~rafael.santos/JIPCookbook 作者:Rafael Santos)。尚未全部写完。
最近正好有些照片需要处理,对这个话题比较感兴趣,给出一些中文翻译和source代码,供以后参考。

如何比较两幅图片以便知道是否相同?

介绍:
    人们经常会询问如何比较两幅图片以便知道它们是否相同,希望能给出个简单的函数或者方法来返回一个true/false,或是0-1之间的值。其中,0代表不同,1代表相同,而0-1之间的值则被看作是不同的相似程度。现实中有许多这方面的应用,比如,想要了解是否违反了肖像权,出于安全目的而进行的人群中的脸部识别,语义学上的图片检索,以及宇宙飞船的可视化导航等等。
    不幸的是图片内容的比较可不是一件简单的任务,相反,在大多数情况下是件非常棘手的事。
    例如,考虑下面的几幅图片。它们中的大多数都反映了同一种动物,因此在某些情况下它们被认为是相同的(比如想要在网上查找栅栏后的哺乳动物),而在另外一些场合却被认为是完全不相同的(比如想要查找松鼠抬脸或是栅栏后的大型哺乳动物)。
    大多数人在思考图片内容时并没有意识到这中间经过了人的视觉系统和大脑的非常复杂的处理。做个试验,把第一张图片拿给不同的人看,并问他们“这图片里有什么?”--可能大多数人会描述图片中的动物,可是描述本身在完整性(图片中观察到的相关物体的数目)和细节(每个物体自身的信息)方面可能存在不同。如果连人自己都无法得出一个简单,准确的描述图片中有什么的话,怎么能期待计算机用简单的算法可以完成这件事呢?
    问题是计算机无法去“看”一副图片,并且去和别的图片进行“比较”来决定它们的内容是否相同。考虑下这里会有多少变量我们需要处理:图片中的每个物体都有形状,大小,方向,颜色,纹理等等;同样的物体在不同的图片里会有不同的位置,大小,方向或亮度,或是它们的混合。我们应该如何处理这些不同呢?无视还是重视?我们要给每个物体赋予不同的“权重”以便在比较中使得某些方面的测量要比另外些更重要吗?小的物体可能会在某些图片中丢失,我们要忽略它们吗?如果一个物体是背景的话我们需要考虑进行比较吗?那什么又是所谓的背景呢?
    

比较图片内容
    如果你想从语义上比较图片的话,你面对的将是一个艰巨的任务,用简单的算法是无法完成的。你可能会不得不处理图片,抽取出一些特征来映射到那些语义上的物体,即使这些特征在某些方面(诸如位置,大小)是不同的,然后再比较这些物体的特征而不是像素或其它图片特征。
    你看,图片内容比较确实不是一件简单的事情,但是如果我们决定把问题简单化,那么事情就会变得简单起来。与其问“这些图片里的物体是否一致?”,我们可以这样问“这张图片里的一些地方是否在某种程度上和另一张里的相似?”。现在我们可以用一些图片处理技术来解决这个问题了。
    可以解决这个问题的一些步骤是:
    1. 如果需要,预处理图片(诸如增强对比度,滤除干扰等)。
    2. 图片分段。即把图片分成若干区域,把相近的像素放在同一个区域。这可以用region-growing,mathematical morphology, clustering or classification等算法。有很多这样的算法,只要google下“image segmentation”和别的关键字就可以得到很多信息。
    3. 为每个区域创建描述符。描述符由区域计算得到,可以包含形状,面积,周长,洞的数目,区域的一般颜色,纹理,方向,位置等等。
    4. 如果需要,再做一次图片分段。如果某些区域被认为是同一个物体则可以将它们合并。注意这步可能需要具备对物体和任务的深入了解,且经常和要解决的任务有关。
    5. 如果需要,根据手头的任务过滤区域,去除掉那些跟任务无关的小区域或者不重要的区域(这里又需要具备对任务的理解)。
    6. 保存图片的区域描述符以便未来处理,重复上述步骤处理其它图片。
    7. 用描述符来比较图片的内容。可以使用很多算法:pattern matching, classification, clustering, artificial intelligence and data mining in general。
    注意上述步骤仅仅是一般方法,并不保证能够解决特殊的任务。还要注意有很多可能的算法能够用于这些步骤,而且每种算法又会有很多变种和参数,因此使用不恰当的算法将会导致失败-要知道自己正在做什么,这很重要,这样你才会得到预期的,有用的结果。
    还要记住一点的是,对我们人类来说,指出哪些图片是“松鼠仰脸”或者“小的啮齿动物挂在栅栏上”是很容易的事情,但是仅仅用一些图片处理算法是轻易做不到这点的。



简单图片比较
    有时候,对图片只做简单的相似性比较也是很有用的。例如,考虑下面这个任务:请找出跟你手上有的那张内容相似的所有图片来,但不必保证他们代表或包含有相同的物体-仅仅是差不多相似就行了。很显然,这样结果将会找出磁盘上所有的图片来,只要相似就可以。
    有多种解决方案,不过,它们都要求从图片里抽出一些特征,用来和其他图片里的特征作比较,并且,还要计算相似程度。当然,根据你的喜好这些可以做得很简单也可以做得很复杂。
     作为例子,让我们选择一个简单的特征抽取方法和相似度计算来说明。参见下图:

用于这个测试的特征将是个25RGB的三棱锥,对照左图上的25个方格子上的平均RGB值。这个图片将被标准化成300*300的像素。只保存颜色均值,其他特征都被舍弃。每个格子是30*30像素。

因此图片将由25*3个特征向量代表。为了计算A,B两张图片的相似度,我们将取出每张图片的25个格子,计算它们间的Euclidean距离并累计。
根据定义A到A的距离将为0,计算出来的上限是25*(Math.sqrt( (255-0)*(255-0)+ (255-0)*(255-0)+ (255-0)*(255-0) )),或者是11041多点。


之所以选择这个方法是因为它简单易懂,容易实现,并且能很容易被读者修改。它混合了颜色和空间信息,比起像素与像素的比较或者是整幅图片的均值比较,这种方法被认为更健壮。但是,它确实非常简单,不能期望在任何情况下都表现出色,而仅仅只能当作个例子。

下面将用24张测试图片作例子来验证上述方法(图片略),源代码是NaiveSimilarityFinder.java。它有一个GUI,允许用户选择一个参照图片A,然后读取同一个目录下所有的图片,将他们归一化成相同的大小(300*300),抽取出特征值,计算与A的特征值的距离,并按照从最近距离到最远距离的顺序显示出来。
 NaiveSimilarityFinder.java
   1 /*
   2  * Part of the Java Image Processing Cookbook, please see
   3  * http://www.lac.inpe.br/~rafael.santos/JIPCookbook/index.jsp
   4  * for information on usage and distribution.
   5  * Rafael Santos (rafael.santos@lac.inpe.br)
   6  */
   7 package howto.compare;
   8  
   9 import java.awt.BorderLayout;
  10 import java.awt.Color;
  11 import java.awt.Container;
  12 import java.awt.Font;
  13 import java.awt.GridLayout;
  14 import java.awt.image.RenderedImage;
  15 import java.awt.image.renderable.ParameterBlock;
  16 import java.io.File;
  17 import java.io.IOException;
  18  
  19 import javax.imageio.ImageIO;
  20 import javax.media.jai.InterpolationNearest;
  21 import javax.media.jai.JAI;
  22 import javax.media.jai.iterator.RandomIter;
  23 import javax.media.jai.iterator.RandomIterFactory;
  24 import javax.swing.JFileChooser;
  25 import javax.swing.JFrame;
  26 import javax.swing.JLabel;
  27 import javax.swing.JOptionPane;
  28 import javax.swing.JPanel;
  29 import javax.swing.JScrollPane;
  30  
  31 import com.sun.media.jai.widget.DisplayJAI;
  32 /**
  33  * This class uses a very simple, naive similarity algorithm to compare an image
  34  * with all others in the same directory.
  35  */
  36 public class NaiveSimilarityFinder extends JFrame
  37   {
  38   // The reference image "signature" (25 representative pixels, each in R,G,B).
  39   // We use instances of Color to make things simpler.
  40   private Color[][] signature;
  41   // The base size of the images.
  42   private static final int baseSize = 300;
  43   // Where are all the files?
  44   private static final String basePath = 
  45     "/home/rafael/Pesquisa/ImageSimilarity";
  46   
  47  /*
  48   * The constructor, which creates the GUI and start the image processing task.
  49   */
  50   public NaiveSimilarityFinder(File reference) throws IOException
  51     {
  52     // Create the GUI
  53     super("Naive Similarity Finder");
  54     Container cp = getContentPane();
  55     cp.setLayout(new BorderLayout());
  56     // Put the reference, scaled, in the left part of the UI.
  57     RenderedImage ref = rescale(ImageIO.read(reference));
  58     cp.add(new DisplayJAI(ref), BorderLayout.WEST);
  59     // Calculate the signature vector for the reference.
  60     signature = calcSignature(ref);
  61     // Now we need a component to store X images in a stack, where X is the
  62     // number of images in the same directory as the original one.
  63     File[] others = getOtherImageFiles(reference);
  64     JPanel otherPanel = new JPanel(new GridLayout(others.length, 2));
  65     cp.add(new JScrollPane(otherPanel), BorderLayout.CENTER);
  66     // For each image, calculate its signature and its distance from the
  67     // reference signature.
  68     RenderedImage[] rothers = new RenderedImage[others.length];
  69     double[] distances = new double[others.length];
  70     for (int o = 0; o < others.length; o++)
  71       {
  72       rothers[o] = rescale(ImageIO.read(others[o]));
  73       distances[o] = calcDistance(rothers[o]);
  74       }
  75     // Sort those vectors *together*.
  76     for (int p1 = 0; p1 < others.length - 1; p1++)
  77       for (int p2 = p1 + 1; p2 < others.length; p2++)
  78         {
  79         if (distances[p1] > distances[p2])
  80           {
  81           double tempDist = distances[p1];
  82           distances[p1] = distances[p2];
  83           distances[p2] = tempDist;
  84           RenderedImage tempR = rothers[p1];
  85           rothers[p1] = rothers[p2];
  86           rothers[p2] = tempR;
  87           File tempF = others[p1];
  88           others[p1] = others[p2];
  89           others[p2] = tempF;
  90           }
  91         }
  92     // Add them to the UI.
  93     for (int o = 0; o < others.length; o++)
  94       {
  95       otherPanel.add(new DisplayJAI(rothers[o]));
  96       JLabel ldist = new JLabel("<html>" + others[o].getName() + "<br>"
  97           + String.format("% 13.3f", distances[o]) + "</html>");
  98       ldist.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 36));
  99       System.out.printf("<td class=\"simpletable legend\">  <img src=\"MiscResources/ImageSimilarity/icons/miniicon_%s\" alt=\"Similarity result\"><br>% 13.3f</td>\n", others[o].getName(),distances[o]);
 100       otherPanel.add(ldist);
 101       }
 102     // More GUI details.
 103     pack();
 104     setVisible(true);
 105     setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 106     }
 107  
 108  /*
 109   * This method rescales an image to 300,300 pixels using the JAI scale
 110   * operator.
 111   */
 112   private RenderedImage rescale(RenderedImage i)
 113     {
 114     float scaleW = ((float) baseSize) / i.getWidth();
 115     float scaleH = ((float) baseSize) / i.getHeight();
 116     // Scales the original image
 117     ParameterBlock pb = new ParameterBlock();
 118     pb.addSource(i);
 119     pb.add(scaleW);
 120     pb.add(scaleH);
 121     pb.add(0.0F);
 122     pb.add(0.0F);
 123     pb.add(new InterpolationNearest());
 124     // Creates a new, scaled image and uses it on the DisplayJAI component
 125     return JAI.create("scale", pb);
 126     }
 127   
 128  /*
 129   * This method calculates and returns signature vectors for the input image.
 130   */
 131   private Color[][] calcSignature(RenderedImage i)
 132     {
 133     // Get memory for the signature.
 134     Color[][] sig = new Color[5][5];
 135     // For each of the 25 signature values average the pixels around it.
 136     // Note that the coordinate of the central pixel is in proportions.
 137     float[] prop = new float[]
 138       {1f / 10f, 3f / 10f, 5f / 10f, 7f / 10f, 9f / 10f};
 139     for (int x = 0; x < 5; x++)
 140       for (int y = 0; y < 5; y++)
 141         sig[x][y] = averageAround(i, prop[x], prop[y]);
 142     return sig;
 143     }
 144  
 145  /*
 146   * This method averages the pixel values around a central point and return the
 147   * average as an instance of Color. The point coordinates are proportional to
 148   * the image.
 149   */
 150   private Color averageAround(RenderedImage i, double px, double py)
 151     {
 152     // Get an iterator for the image.
 153     RandomIter iterator = RandomIterFactory.create(i, null);
 154     // Get memory for a pixel and for the accumulator.
 155     double[] pixel = new double[3];
 156     double[] accum = new double[3];
 157     // The size of the sampling area.
 158     int sampleSize = 15;
 159     // Sample the pixels.
 160     for (double x = px * baseSize - sampleSize; x < px * baseSize + sampleSize; x++)
 161       {
 162       for (double y = py * baseSize - sampleSize; y < py * baseSize
 163           + sampleSize; y++)
 164         {
 165         iterator.getPixel((int) x, (int) y, pixel);
 166         accum[0] += pixel[0];
 167         accum[1] += pixel[1];
 168         accum[2] += pixel[2];
 169         }
 170       }
 171     // Average the accumulated values.
 172     accum[0] /= sampleSize * sampleSize * 4;
 173     accum[1] /= sampleSize * sampleSize * 4;
 174     accum[2] /= sampleSize * sampleSize * 4;
 175     return new Color((int) accum[0], (int) accum[1], (int) accum[2]);
 176     }
 177  
 178  /*
 179   * This method calculates the distance between the signatures of an image and
 180   * the reference one. The signatures for the image passed as the parameter are
 181   * calculated inside the method.
 182   */
 183   private double calcDistance(RenderedImage other)
 184     {
 185     // Calculate the signature for that image.
 186     Color[][] sigOther = calcSignature(other);
 187     // There are several ways to calculate distances between two vectors,
 188     // we will calculate the sum of the distances between the RGB values of
 189     // pixels in the same positions.
 190     double dist = 0;
 191     for (int x = 0; x < 5; x++)
 192       for (int y = 0; y < 5; y++)
 193         {
 194         int r1 = signature[x][y].getRed();
 195         int g1 = signature[x][y].getGreen();
 196         int b1 = signature[x][y].getBlue();
 197         int r2 = sigOther[x][y].getRed();
 198         int g2 = sigOther[x][y].getGreen();
 199         int b2 = sigOther[x][y].getBlue();
 200         double tempDist = Math.sqrt((r1 - r2) * (r1 - r2) + (g1 - g2)
 201             * (g1 - g2) + (b1 - b2) * (b1 - b2));
 202         dist += tempDist;
 203         }
 204     return dist;
 205     }
 206  
 207  /*
 208   * This method get all image files in the same directory as the reference.
 209   * Just for kicks include also the reference image.
 210   */
 211   private File[] getOtherImageFiles(File reference)
 212     {
 213     File dir = new File(reference.getParent());
 214     // List all the image files in that directory.
 215     File[] others = dir.listFiles(new JPEGImageFileFilter());
 216     return others;
 217     }
 218  
 219  /*
 220   * The entry point for the application, which opens a file with an image that
 221   * will be used as reference and starts the application.
 222   */
 223   public static void main(String[] args) throws IOException
 224     {
 225     JFileChooser fc = new JFileChooser(basePath);
 226     fc.setFileFilter(new JPEGImageFileFilter());
 227     int res = fc.showOpenDialog(null);
 228     // We have an image!
 229     if (res == JFileChooser.APPROVE_OPTION)
 230       {
 231       File file = fc.getSelectedFile();
 232       new NaiveSimilarityFinder(file);
 233       }
 234     // Oops!
 235     else
 236       {
 237       JOptionPane.showMessageDialog(null,
 238           "You must select one image to be the reference.", "Aborting...",
 239           JOptionPane.WARNING_MESSAGE);
 240       }
 241     }
 242   

辅助类:
JPEGImageFileFilter.java
   1 /*
   2  * Part of the Java Image Processing Cookbook, please see
   3  * http://www.lac.inpe.br/~rafael.santos/JIPCookbook/index.jsp
   4  * for information on usage and distribution.
   5  * Rafael Santos (rafael.santos@lac.inpe.br)
   6  */
   7 package howto.compare;
   8  
   9 import java.io.File;
  10  
  11 import javax.swing.filechooser.FileFilter;
  12  
  13 /*
  14  * This class implements a generic file name filter that allows the listing/selection
  15  * of JPEG files.
  16  */
  17 public class JPEGImageFileFilter extends FileFilter implements java.io.FileFilter
  18   {
  19   public boolean accept(File f)
  20     {
  21     if (f.getName().toLowerCase().endsWith(".jpeg")) return true;
  22     if (f.getName().toLowerCase().endsWith(".jpg")) return true;
  23     return false;
  24     }
  25   public String getDescription()
  26     {
  27     return "JPEG files";
  28     }
  29  
  30   }

上述程序在帮助整理大量的重复拍摄的数码照片(只有细微的一些差别)方面很有用。当然,对该程序稍作修改即可增强其功能。

posted on 2009-03-03 23:14 koradji 阅读(1671) 评论(5)  编辑  收藏 所属分类: Java Image Processing

评论

# re: Java Image Processing Cookbook[未登录] 2009-07-01 21:12 yun

神经病啊 代码还排数字上去干什么是
还要写个软件来去掉你的数字
真麻烦啊你  回复  更多评论   

# re: Java Image Processing Cookbook[未登录] 2009-07-01 23:00 yun


好像那代码的作者本身的网站就列了这些数字
那作者真是麻烦  回复  更多评论   

# re: Java Image Processing Cookbook 2009-07-21 18:20 malaka

至于嘛!骂作者是“神经病”,人家写博客让你看,你还这么说人家,素质,注意你的素质  回复  更多评论   

# re: Java Image Processing Cookbook 2009-10-14 08:15 库夫

虽然不是很理解,不过很不错,板凳素质,差了点  回复  更多评论   

# re: Java Image Processing Cookbook 2012-08-15 21:19 yadong

完成一个项目正需要完成这样的工作, 一楼你的素质啊。不会用vim的童鞋真的是伤不起啊!!  回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航:
 
<2009年3月>
22232425262728
1234567
891011121314
15161718192021
22232425262728
2930311234

导航

统计

常用链接

留言簿(2)

随笔分类

随笔档案

文章分类

文章档案

收藏夹

db2

dos

Groovy

Hibernate

java

WAS

web application

搜索

最新评论

阅读排行榜

评论排行榜