前阵子做图像搜索功能, 对比过一些搜索算法, 发现 sift 比较能满足我的需要, 它在图像旋转, 比例缩放, 图像扭曲的情况下也能有很好的识别效果, 在网上找了一些资料, 有些介绍了算法的细节, 有些做了对比评测
www.cscjournals.org/csc/manuscript/Journals/IJIP/volume3/Issue4/IJIP-51.pdf
2013计算机视觉代码合集 - Note of Transposition - 博客频道 - CSDN.NET
Computer Vision Algorithm Implementations
ImageAnalyzer | NeoTOS - Torben Schinke
OpenSIFT: An Open-Source SIFT Library
VLFeat(1)——SIFT图像特征提取(VC++实现)_席思丝萌_新浪博客
特征提取步骤 SIFT,PCA-SIFT,GLOH,SURF - 研发管理
原理就不扯了, 网上找来一个老外的代码, 按需要改造了一下, 封装了一个组件, 方便使用.
组件地址: https://github.com/myshzzx/mlib/tree/master/imagesearch
实际使用是这样的, 首先对图像进行预处理(比如缩小图像, 以减少无关的细节干扰), 然后对图像生成 SIFT 特征值. 图像搜索即是在大规模样本特征值里寻找与目标特征值最相近的特征值.
一般大小的图片可以计算出 10^2 ~ 10^3 数量级的特征值, 每个特征值是一个 float[128], 即一个 128 维浮点向量, 而图像搜索就是计算向量的欧氏距离, 找距离最小的样本向量, 这无疑是海量的计算. 一个比较好的做法是利用向量的空间信息构建k-d树, 即k维向量树, 这里 k=128, 查找的时候利用空间信息优先查找可能性大的分支, 搜索时间 T(n) = θ(log(n)).
我尝试用GPU加速来加快搜索, 用到 aparapi, 无奈它只支持基本类型的运算, 无法处理 Java 对象, 就是说k-d树没法用了, 只能用数组组织特征值, GPU处理欧氏距离的计算, 由于计算粒度小, 数据交互量大, 用这种方式反而可能比用CPU还慢.
这个组件比较适合样本图像差距较大的情况, 由于每个特征值的匹配查找只找出最接近的那个, 如果样本库里有多个与目标图像接近的图像, 那么最接近的那个会被找到, 而不那么接近的其他图像将会被它 "掩盖". 如果有这样的情况, 那么需要修改搜索代码.
下面贴一段示例代码演示一下它的用法.
public class SiftHelperTest { private static final Logger log = LoggerFactory.getLogger(SiftHelperTest.class); private static String[] dirs = new String[]{ "E:\\project\\texture\\picsRep\\sample" }; private static File resultDir = new File("l:/result"); public static void main(String[] args) throws IOException, InterruptedException { SiftHelper.FeaMgrType feaMgrType = SiftHelper.FeaMgrType.KDTree; final SiftHelper<String> analyzer = new SiftHelper<>(String.class, feaMgrType); // 准备预处理器 ImgPreProc samplePreProc = genImgPreProc(180); ImgPreProc testPreProc = genImgPreProc(150); analyzer.setImgPreProc(samplePreProc); analyzer.setMinMatchCount(2); // 准备样本库 final ExecutorService exec = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); for (String dir : dirs) FileUtil.recurDir(new File(dir), null, new FileUtil.FileTask() { @Override public void handle(final File f) { exec.submit(() -> { try { analyzer.bindImage(f.getName(), ImageIO.read(f)); } catch (Exception e) { e.printStackTrace(); } }); } }); exec.shutdown(); exec.awaitTermination(1, TimeUnit.DAYS); // 设置搜索超时, 即给定一个特征值的搜索超时, 设置一个合适的值可以获得很好的 搜索效果/搜索耗时 比. analyzer.getFeatureManager().setEachFeatureTimeout(3000); System.out.println("ready to search."); BufferedReader s = new BufferedReader(new InputStreamReader(System.in)); String file; while ((file = s.readLine()) != null) { if ((file = file.trim()).length() < 1) continue; // 执行搜索 searchImg(analyzer, file, testPreProc, dirs[0], resultDir); } } static void searchImg(SiftHelper<String> analyzer, String file, ImgPreProc preProc, String sampleDir, File resultDir) { try { List<ImageFeature> features = preProc.process(ImageIO.read(new File(file))); List<ImageAnalyzerResult<String>> res = analyzer.findImage(features); System.out.println(file + " Results: " + res.size()); int i = 1; if (resultDir.exists()) for (File child : resultDir.listFiles()) if (child.isFile()) child.delete(); for (ImageAnalyzerResult<String> result : res) { System.out.println(result); if (resultDir.exists()) { String dir = sampleDir + "/" + result.getUserObj(); Files.copy(Paths.get(dir), Paths.get(resultDir.getPath() + "/" + (i++) + " " + result.getUserObj())); } } System.out.println(); } catch (Exception e) { log.error("searchImg failed: " + file, e); } System.gc(); } static ImgPreProc genImgPreProc(int scaleLimit) { GrayScaleLimit preProc = new GrayScaleLimit(); ImageFeatureExtractor sift = new JavaSIFT(); preProc.setFe(sift); preProc.setScaleLimit(scaleLimit); return preProc; } }