【问题标题】:Similar image search using an image使用图像进行相似图像搜索
【发布时间】:2014-09-29 16:21:58
【问题描述】:

我正在开展一个项目,其中将检查两个图像的相似性,例如“Google Image Search by image”。

我通过 Google 以及包括 stackoverflow 在内的各种网站进行了搜索,并了解了各种技术,如 直方图、筛选、傅立叶变换、像素抓取等。

这些事情对我来说太复杂了了解成为该领域的初学者。

我的问题是:

  1. 我可以从哪里开始??是否有任何书籍或网站提供有关如何实际使用这些技术的教程?

  2. 是否有可以为此目的实施的新技术?

我想开始通过颜色搜索图像,然后如果可能的话稍后再搜索其他属性。

首选语言是 Java。

关于这个主题有一个类似的帖子,但它是几年前写的。

【问题讨论】:

    标签: java image-processing histogram sift image-comparison


    【解决方案1】:

    我只是参考了@Alex 代码并添加了一些其他方法来从位置获取所有特定文件并一一处理图像。

    import java.awt.Graphics2D;
    import java.awt.Image;
    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import javax.imageio.ImageIO;
    
    public class ImageSearch {
    
        static HashMap<String, BufferedImage> imgMap = new HashMap<>();
    
        public static void main(String imgToFind, String...img2)throws IOException{
            System.out.println("---------------------Images is processing--------------------- ");
            search("path/to/images");
            System.out.println("----------------------Images processing is completed--------------------- ");
            System.out.println("----------------------Searching Images---------------------");
            System.out.println("Wiiner is :"+findImage(imgMap,getThumbnil(imgToFind)));
        }
    
    
        public static void search(String path) throws IOException{
    
            File file = new File(path);
            File[] fArray = file.listFiles();
    
            for (File f : fArray) {
    
                if (!f.isDirectory()) {
                    if(f.getAbsolutePath().toUpperCase().endsWith(".JPG")){
                        BufferedImage bufImage = getThumbnil(f.getAbsolutePath());
                        imgMap.put(f.getAbsolutePath(), bufImage);
                    }
    
                }
    
                if (f.isDirectory()) {
                    search(f.getAbsolutePath());
                }
    
            }
    
        }
    
    
        public static BufferedImage getThumbnil(String imgSrc) throws IOException{
            BufferedImage img = ImageIO.read(new File(imgSrc));
            Image thumbnail = img.getScaledInstance(8, 8, Image.SCALE_AREA_AVERAGING);
            return toBufferedImage(thumbnail);
        }
    
    
        public static BufferedImage toBufferedImage(Image img)
        {
            if (img instanceof BufferedImage)
            {
                return (BufferedImage) img;
            }
    
            // Create a buffered image with transparency
            BufferedImage bimage = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_ARGB);
    
            // Draw the image on to the buffered image
            Graphics2D bGr = bimage.createGraphics();
            bGr.drawImage(img, 0, 0, null);
            bGr.dispose();
    
            // Return the buffered image
            return bimage;
        }
    
        public static String findImage(HashMap<String, BufferedImage> imgMap, BufferedImage needle) {
    
            double lastDiff = Double.MAX_VALUE;
            BufferedImage winner = null;
            String winnerPath = "";
    
            for(Map.Entry<String, BufferedImage> candidate: imgMap.entrySet()) {
    
                String path = candidate.getKey();
                BufferedImage bufferedImage = candidate.getValue();
    
                double diff = compare(bufferedImage, needle);
                if(diff < lastDiff) {
                    lastDiff = diff;
                    winner = bufferedImage;
                    System.out.println("path :"+path);
                    winnerPath = path;
                }
            }
            return winnerPath;
        }
    
        public static double compare(BufferedImage img1, BufferedImage img2) {
            int width1 = img1.getWidth();
            int width2 = img2.getWidth();
            int height1 = img1.getHeight();
            int height2 = img2.getHeight();
            if ((width1 != width2) || (height1 != height2)) {
                throw new IllegalArgumentException("Error: Images dimensions mismatch");
            }
    
            int diff2 = 0;
    
            for (int i = 0; i < height1; i++) {
                for (int j = 0; j < width1; j++) {
                    int rgb1 = img1.getRGB(j, i);
                    int rgb2 = img2.getRGB(j, i);
                    int r1 = (rgb1 >> 16) & 0xff;
                    int g1 = (rgb1 >> 8) & 0xff;
                    int b1 = (rgb1) & 0xff;
                    int r2 = (rgb2 >> 16) & 0xff;
                    int g2 = (rgb2 >> 8) & 0xff;
                    int b2 = (rgb2) & 0xff;
    
                    diff2 += Math.pow(r1 - r2, 2) + Math.pow(g1 - g2, 2) + Math.pow(b1 - b2, 2); 
                }
            }
            return diff2 * 1.0 / (height1*width1);
        }
    
    }
    

    希望这会有所帮助。

    【讨论】:

      【解决方案2】:

      通过将图像大小调整为 8x8 缩略图,然后对每个图像中相应像素之间的 8 位 RGB 色差求均方误差,我得到了令人满意的结果。

      第 1 步。创建缩略图:

              BufferedImage img = ImageIO.read(src);
              Image thumbnail = img.getScaledInstance(8, 8, Image.SCALE_AREA_AVERAGING);
      

      查看https://community.oracle.com/docs/DOC-983611 了解为什么我选择较慢的SCALE_AREA_AVERAGING 而不是更新、更快的方法。

      步骤 2. 使用来自 Java converting Image to BufferedImagetoBufferedImage 方法将 Image 缩略图转换为 BufferedImage。将结果放入List&lt;BufferedImage&gt;

      第 3 步。计算均方误差

      此方法采用两个相同大小的缩略图大小的图像并返回差异。零表示图像非常相似:

      public static double compare(BufferedImage img1, BufferedImage img2) {
          int width1 = img1.getWidth();
          int width2 = img2.getWidth();
          int height1 = img1.getHeight();
          int height2 = img2.getHeight();
          if ((width1 != width2) || (height1 != height2)) {
              throw new IllegalArgumentException("Error: Images dimensions mismatch");
          }
      
          int diff2 = 0;
      
          for (int i = 0; i < height1; i++) {
              for (int j = 0; j < width1; j++) {
                  int rgb1 = img1.getRGB(j, i);
                  int rgb2 = img2.getRGB(j, i);
                  int r1 = (rgb1 >> 16) & 0xff;
                  int g1 = (rgb1 >> 8) & 0xff;
                  int b1 = (rgb1) & 0xff;
                  int r2 = (rgb2 >> 16) & 0xff;
                  int g2 = (rgb2 >> 8) & 0xff;
                  int b2 = (rgb2) & 0xff;
      
                  diff2 += Math.pow(r1 - r2, 2) + Math.pow(g1 - g2, 2) + Math.pow(b1 - b2, 2); 
              }
          }
          return diff2 * 1.0 / (height1*width1);
      }
      

      第 4 步。实施搜索

      只需找到差异最小的图像即可。根据您的用例,您可能还需要设置一个阈值,超过该阈值不会返回任何图像。在我的应用程序中,始终向用户显示最佳匹配,因此用户可以决定它是否是正确的图像,因此不需要硬编码阈值。

      public BufferedImage findImage(List<BufferedImage> haystack, BufferedImage needle) {
      
          double lastDiff = Double.MAX_VALUE;
          BufferedImage winner = null;
      
          for(BufferedImage candidate: haystack) {
              double diff = compare(candidate, needle);
              if(diff < lastDiff) {
                  lastDiff = diff;
                  winner = candidate;
              }
          }
          return winner;
      }
      

      【讨论】:

        【解决方案3】:

        这取决于您的用例。这些图像是通用的,还是在相似的照明条件和视角下拍摄的?


        可以根据模型的复杂程度对方法进行分类。从本质上讲,我们可以区分直接方法和基于特征的方法。

        直接(或基于强度)方法尝试(迭代地)通过基于重叠区域的强度差异最小化误差函数来估计相似度。每个图像必须以相同的比例、视角等表示完全相同的场景,以便重叠。相似度测量可以通过计算平方和差(SSD)或ZSSD、相关系数(CC)、互信息(MI)和相关比(RC)来实现。

        直接方法可能很有用,例如检查新的视频压缩算法的工作情况。看看下面的文章:

        http://docs.opencv.org/trunk/doc/tutorials/highgui/video-input-psnr-ssim/video-input-psnr-ssim.html


        另一方面,我们可以讨论基于特征的方法。他们试图建立点、线或其他几何实体之间的对应关系,以估计相机参数。当图像的结构单元(像素)不包含有关其内容的足够信息时,它们会很有用。因此,基于特征的方法尝试用数学特征(n 维向量)来表示图像内容,然后使用分类器比较这些特征,以获得关于它们相似性的度量。

        【讨论】:

        • 教程很好,很有帮助。对于初学者,我想按颜色比较两张图像并获得像谷歌图像搜索那样的结果(也许不是那么好)。 PSNR 和 SSIM 可以用于此目的吗?或者还有其他算法吗?我会检查更多关于 SSIM 的信息
        • PSNR 和 SSIM 只是衡量两个图像之间相似性的两个非常简单的指标。如果您想比较两张向日葵的图像,它们将不起作用,因此其中一张图像的左上角包含向日葵,而另一张图像的右下角包含向日葵。处理 search-image-base-by-image 的整个研究领域称为Content Based Image Retrieval (CBIR)
        • 您还应该看看以下内容:123
        【解决方案4】:

        为此,我创建了一个名为 图像相似性搜索器toolhttp://sourceforge.net/projects/imgndxr/http://sourceforge.net/projects/imgndxr/ 上的免费软件

        它使用两个库:

        LIRE(Lucene 图像检索)库提供了一种简单的方法来 根据颜色和纹理检索图像和照片 特征。 LIRE 创建图像特征的 Lucene 索引 基于内容的图像检索(CBIR)。几种不同的低电平 功能可用,例如 MPEG-7 ScalableColor、ColorLayout 和 EdgeHistogram、Auto Color Correlogram、PHOG、CEDD、JCD、FCTH 等 更多的。此外,用于搜索索引的简单和扩展方法 和结果浏览由 LIRE 提供。 LIRE 可扩展到 具有基于散列的近似索引的数百万张图像。里尔 库和 LIRE 演示应用程序以及所有源代码都是 在 Gnu GPL 许可下可用。

        Apache LuceneTM 是一种高性能、全功能的文本搜索 完全用 Java 编写的引擎库。是一种适合的技术 对于几乎所有需要全文搜索的应用程序,尤其是 跨平台。

        Apache Lucene 是一个开源项目,可免费下载。 请使用右侧的链接访问 Lucene。

        【讨论】:

        • 你的工作令人印象深刻,但我想自己研究算法,库提供了预定义的方法,在我的项目中使用可能不是一个好主意
        • 谢谢。 :-) 你认为这与“作为这个领域的初学者,事情太复杂了,我无法理解。”是一致的吗?
        • 可能是的,我只是这个领域的初学者。这就是提出这个问题的全部原因。
        • 我对你未来的工作很感兴趣。我想为 imgndxr 做贡献,不客气!首先要做的是一个更小、更简单的 LIRE 替代品。 LUCENE 是一种像数据库一样索引大量图像的工具。
        • imgndxr 状态不佳,缺少依赖项等,所以我必须创建自己的解决方案(见下文)
        猜你喜欢
        • 2021-02-07
        • 2015-06-18
        • 1970-01-01
        • 2011-02-24
        • 2017-04-01
        • 1970-01-01
        • 2016-06-26
        • 2020-02-16
        • 2012-12-02
        相关资源
        最近更新 更多