将源图像与一组已知图像进行比较

时间:2015-05-07 16:40:40

标签: ios image opencv image-processing image-compression

我正在为我的一位朋友的父母建立一个应用程序,这个应用程序可悲地中风,不能再说话,阅读或拼写。然而,他可以绘制相当详细的图纸。

我目前已经构建了一个可以处理绘图图像并检测基本形状的应用程序。 (线条,正方形和三角形)应用程序可以计算每个形状的绘制数量,因此它知道两个正方形的图像与仅有一个正方形的图像之间的差异。

这会给用户带来大量的认知负担,以记住形状的所有组合及其含义。我目前正在通过

检测图像中的轮廓

findContours(maskMat, contours, hierarchy, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);

我想要实现的是用户绘制形状,将其添加到一组已知图形,然后每次绘制图像时,应用程序处理每个已知图像,将其与源图像进行比较并保存相似度值。然后取最高相似度值,假设它高于阈值,则可以将其绘制为最佳已知图像。

我已经研究过OpenCV模式匹配以及模板,但结果不可靠。

我正在寻求提供最佳方法的建议,以提供我希望的结果。

我为我的大学讲座制作了一个宣传视频,以便最好地说明该应用的功能。如果您有兴趣,可以在这里查看。 https://youtu.be/ngMUUIsLHoc

提前致谢。

5 个答案:

答案 0 :(得分:5)

首先,这看起来像一个很棒的应用程序。并为了一个梦幻般的目的。干得好!

就您的问题而言,在观看视频时,似乎有一种方法如下:

1.将每个绘图区域划分为(例如)一个3x3网格,并允许每个区域包含一个图元,例如垂直线,水平线,正方形,圆形,三角形或根本不包含任何图形。 (这在某种程度上取决于朋友父母的运动控制)

  1. 图像完成后,检测这些图元并编码(比方说)9字符键,可用于检索适当的图像。例如,如果三角形是,T,方形是S而空是下划线,那么根据视频“我回家”的代码将是“_T__S ____”。

  2. 启动新图像时,您可以在绘制每个图元时检测它们并使用它构建一个搜索键,其中键具有“?”对于未知字符。然后,您可以快速从数据库中检索所有可能的匹配项。

  3. 例如,如果用户在顶部中间区域绘制一个三角形,则会将其编码为“?T ???????”这将匹配'_T__S____'以及'_TT ______'

    如果限制用户绘制到屏幕的较小区域是不可行的,那么您仍然可以存储表示每个图元的相对位置的编码关键字。

    为此,您可以计算每个图元的质心,从左到右,从上到下对它们进行排序,然后存储它们相对位置的一些表示,例如:正方形上方的三角形可能是TVS,其中V表示S低于T,正方形左边的三角形可能是T

    希望这会有所帮助。

    祝你好运!

答案 1 :(得分:5)

  1. Sketch-based image retrieval。有很多关于使用草图作为查询来查找真实图像的文献。不是你想要的,但是一些相同的技术可能适用于使用草图作为查询来查找草图。他们甚至可以不加修改地工作。

  2. 自动识别手写汉字(或类似的书写系统)。关于这方面也有很多文献;问题是类似的,书写系统已经从图像草图演变而来,但是简化和风格化。尝试应用一些相同的技术。

  3. 个人线条的数量,顺序,位置可能比作为图像的完成草图更具信息性。有没有办法可以捕获这个?如果您的用户使用手写笔绘图,您可以记录每条线的手写笔轨迹。这将比图像本身具有更多的信息内容。想想有人在闭着眼睛画一辆汽车(例如)。沿着轨迹走,你可以很容易地发现它是一辆汽车。从图片上看可能要困难得多。

  4. 如果您可以按照描述捕捉线条,那么匹配问题可以是某种近似,可以将图像A中的某些线条与图像B中最相似的线条进行匹配(可能会变形,偏移等)。它们也应该与其他线具有相似的关系:如果(例如)两条线在图像A中交叉,它们应该在图像B中交叉,并且在相似的角度和沿着每条线的长度的类似位置交叉。为了更加健壮,理想情况下,这应该处理一个图像中的两行对应于另一行中的单个(合并)行的事情。

答案 2 :(得分:5)

基于描述的方法

根据您的视频,我假设您最感兴趣的是比较线条图而不是详细的草图。对于线条图,我可以想到以下基于描述的方法。所提出的描述是基于比率的,并且不依赖于形状的绝对尺寸/尺寸,并且还应该很好地处理变化。

计算形状描述

您需要计算规范化形状的描述,该描述可以适应不同实例之间的微小变化。如前面的答案所述,在形状匹配和基于草图的检索方面有大量文献,所以我不再重复。假设您正在处理的形状是线条或多边形,以下相对简单的方法应该有效。

  1. 使用Hough Transform检测图像中的所有线条。给出了一个非常详细的例子here。手绘线可能不完全笔直,霍夫可能无法将它们检测为单线,而是具有不同斜率和截距的多个不同线段。您可以将草绘限制为仅以线条的形式,或使用适合单条直线的线条拟合方法,以获得略微不规则的手绘线条。

  2. 首先根据X坐标对Y线进行排序,然后是Y坐标。

  3. 从左到右,从下到上(或任何固定顺序)遍历线,并为每一行计算以下属性。

  4. 通过连接上述值来创建特征向量,例如,如果草图有三条线,则特征向量将是形式 { theta1, theta2, theta3, lengthRatio1, lengthRatio2, lengthRatio3, segmentRatio1, segmentRatio2, segmentRatio3 }

  5. 匹配查询和数据库形状

    如上所述,您可以为数据库中的所有形状以及任何新查询创建特征向量表示。您现在可以编写一个计算两个向量之间距离的简单函数。下面给出了伪代码。

    int numLines; // computed using hough transform
    vector<float> featureVector1(vec_size);
    vector<float> featureVector1(vec_size);
    ...
    // feature vectors computed as explained//
    
    // Compute error between two vectors //
    
    float angleError = 0.0f, lengthRatioError = 0.0, segmentRatioError = 0.0;
    
    float diff = 0.0;    
    // (0,numLines-1) elements of the vector are angles
    for(int i=0; i < numLines; i++) {
        diff = abs(featureVector1[i] - featureVector2[i]);
        angleError += diff;
    }
    
    diff = 0.0;
    // (numLines,2numLines-1) elements of the vector are length ratios
    for(int i=numLines; i < 2*numLines-1; i++) {
        diff = abs(featureVector1[i] - featureVector2[i]);
        lengthRatioError += diff;
    }
    
    diff = 0.0;
    // (2*numLines,3numLines-1) elements of the vector are segment ratios
    // These values should be zero for no intersection
    for(int i=numLines; i < 2*numLines-1; i++) {
        diff = abs(featureVector1[i] - featureVector2[i]);
        segmentRatioError += diff;
    }
    
    // Weights for errors - you should play around with these. 
    float w1 = 1.0, w2 = 1.0, w3 = 1.0;
    totalError = w1*angleError + w2*lengthRatioError + w3*segmentRatioError;
    

    将查询功能与所有数据库功能匹配,并找到距离最小的形状(totalError)。如果距离低于阈值,则声明匹配,否则将查询声明为新形状并添加到数据库。

答案 3 :(得分:4)

可能有用的一种方法,由于这个库已存在多长时间以及它的维护状态,因此使用ImageMagick。让我来证明一下。

1使用

安装Pod
pod 'ImageMagick', '6.8.8-9'
在viewController中的

2或您希望在图像之间进行比较的任何其他视图中,导入以下内容:

#import <wand/MagickWand.h>

4创建简易模式异常&#34;宏&#34;检查错误,而不必在每次需要检查异常时编写异常方法:

#define ThrowWandException(wand) { \
char * description; \
ExceptionType severity; \
\
description = MagickGetException(wand,&severity); \
(void) fprintf(stderr, "%s %s %lu %s\n", GetMagickModule(), description); \
description = (char *) MagickRelinquishMemory(description); \
exit(-1); \
}

3创建比较方法以比较两个图像:

-(void)compareTwoImages:(UIImage*)firstImage secondImage:(UIImage*)secondImage comparitorSize:(size_t)comparitorSize {

    double diff1, diff2, diff3, diff4, diff5, diff6, diff7, diff8, diff9, diff10, diff11, diff12;

    MagickWandGenesis();
    MagickWand *magick_wand_1 = NewMagickWand();
    NSData * dataObject1 = UIImagePNGRepresentation(firstImage);
    MagickBooleanType status1;
    status1 = MagickReadImageBlob(magick_wand_1, [dataObject1 bytes], [dataObject1 length]);

    if (status1 == MagickFalse) {
        ThrowWandException(magick_wand_1);
    }

    MagickWandGenesis();
    MagickWand *magick_wand_2 = NewMagickWand();
    NSData * dataObject11 = UIImagePNGRepresentation(secondImage);
    MagickBooleanType status11;
    status11 = MagickReadImageBlob(magick_wand_2, [dataObject11 bytes], [dataObject11 length]);

    if (status11 == MagickFalse) {
        ThrowWandException(magick_wand_2);
    }

    MagickScaleImage(magick_wand_2, comparitorSize, comparitorSize);
    MagickScaleImage(magick_wand_1, comparitorSize, comparitorSize);

    MagickWandGenesis();
    MagickWand *magick_wand_3 = NewMagickWand();

    MagickCompareImages(magick_wand_1, magick_wand_2, UndefinedMetric, &diff1);
    MagickCompareImages(magick_wand_1, magick_wand_2, AbsoluteErrorMetric, &diff2);
    MagickCompareImages(magick_wand_1, magick_wand_2, MeanAbsoluteErrorMetric, &diff3);
    MagickCompareImages(magick_wand_1, magick_wand_2, MeanErrorPerPixelMetric, &diff4);
    MagickCompareImages(magick_wand_1, magick_wand_2, MeanSquaredErrorMetric, &diff5);
    MagickCompareImages(magick_wand_1, magick_wand_2, PeakAbsoluteErrorMetric, &diff6);
    MagickCompareImages(magick_wand_1, magick_wand_2, PeakSignalToNoiseRatioMetric, &diff7);
    MagickCompareImages(magick_wand_1, magick_wand_2, RootMeanSquaredErrorMetric, &diff8);
    MagickCompareImages(magick_wand_1, magick_wand_2, NormalizedCrossCorrelationErrorMetric, &diff8);
    MagickCompareImages(magick_wand_1, magick_wand_2, FuzzErrorMetric, &diff10);
    MagickCompareImages(magick_wand_1, magick_wand_2, UndefinedErrorMetric, &diff11);
    MagickCompareImages(magick_wand_1, magick_wand_2, PerceptualHashErrorMetric, &diff12);

    NSLog(@"UndefinedMetric: %.21f", diff1);
    NSLog(@"AbsoluteErrorMetric: %.21f", diff2);
    NSLog(@"MeanAbsoluteErrorMetric: %.21f", diff3);
    NSLog(@"MeanErrorPerPixelMetric: %.21f", diff4);
    NSLog(@"MeanSquaredErrorMetric: %.21f", diff5);
    NSLog(@"PeakAbsoluteErrorMetric: %.21f", diff6);
    NSLog(@"PeakSignalToNoiseRatioMetric: %.21f", diff7);
    NSLog(@"RootMeanSquaredErrorMetric: %.21f", diff8);
    NSLog(@"NormalizedCrossCorrelationErrorMetric: %.21f", diff9);
    NSLog(@"FuzzErrorMetric: %.21f", diff10);
    NSLog(@"UndefinedErrorMetric: %.21f", diff11);
    NSLog(@"PerceptualHashErrorMetric: %.21f", diff12);

    DestroyMagickWand(magick_wand_1);
    DestroyMagickWand(magick_wand_2);
    DestroyMagickWand(magick_wand_3);

    MagickWandTerminus();
}

5观察输出到调试器(显然,你需要另一种使用某种&#34; threshhold&#34;监视器的方法,以确定什么级别显示&#34;确切或者接近匹配&#34; vs你自己认为匹配的东西)。此外,非常重要的说明,我有一个&#34; size_t&#34;上述方法输入中的大小变量是因为您无法比较不同大小的图像,因此您必须首先调整您所感知的图像的大小,使其感觉大小合理。#34;然后使用ImageMagick调整两个图像的大小,然后比较图像:

示例1:

[self compareTwoImages:[UIImage imageNamed:@"book.png"]
           secondImage:[UIImage imageNamed:@"book.png"]
        comparitorSize:32];
     

[76233:1364823] UndefinedMetric:0.866871957624008593335

     

[76233:1364823] AbsoluteErrorMetric:0.000000000000000000000

     

[76233:1364823] MeanAbsoluteErrorMetric:0.000000000000000000000

     

[76233:1364823] MeanErrorPerPixelMetric: 0.000000000000000000000 **

     

[76233:1364823] MeanSquaredErrorMetric:0.000000000000000000000

     

[76233:1364823] PeakAbsoluteErrorMetric:0.000000000000000000000

     

[76233:1364823] PeakSignalToNoiseRatioMetric:inf

     

[76233:1364823] RootMeanSquaredErrorMetric:0.866871957624008593335

     

[76233:1364823] NormalizedCrossCorrelationErrorMetric:   0.000000000000000000000

     

[76233:1364823] FuzzErrorMetric:0.000000000000000000000

     

[76233:1364823] UndefinedErrorMetric:0.866871957624008593335

     

[76233:1364823] PerceptualHashErrorMetric:0.000000000000000000000

示例2:

[self compareTwoImages:[UIImage imageNamed:@"book.png"]
             secondImage:[UIImage imageNamed:@"arrow.png"]
          comparitorSize:32];
     

[76338:1368754] UndefinedMetric:0.074585376822533272501

     

[76338:1368754] AbsoluteErrorMetric:795.000000000000000000000

     

[76338:1368754] MeanAbsoluteErrorMetric:0.314410045058480136504

     

[76338:1368754] MeanErrorPerPixelMetric:328395.000000000000000000000

     

[76338:1368754] MeanSquaredErrorMetric:0.245338692857198115149

     

[76338:1368754] PeakAbsoluteErrorMetric:1.000000000000000000000

     

[76338:1368754] PeakSignalToNoiseRatioMetric:6.102339529383479899138

     

[76338:1368754] RootMeanSquaredErrorMetric:0.074585376822533272501

     

[76338:1368754] NormalizedCrossCorrelationErrorMetric:0.000000000000000000000

     

[76338:1368754] FuzzErrorMetric:0.571942529580490965913

     

[76338:1368754] UndefinedErrorMetric:0.074585376822533272501

     

[76338:1368754] PerceptualHashErrorMetric:1827.005561849247442296473

这里有很多数学上的进展。我没有时间解释所有这些变量,但足以说明两个图像之间的这种比较使用了一些众所周知的方法来比较两个图像。您必须从此处获取并测试统计信息以根据您的喜好自定义此选项,并选择适合您目的的错误阈值。

快速解释:

ImageMagick是一款经过战斗测试的图像处理库,虽然上面列出的这些方法都是“黑盒子”#34;这种黑色的盒子可以让你节省时间,转而使用OpenCV。 ImageMagick已经建立了一些非常好的图像处理算法,它的历史以及开箱即用的#34;这种方法起作用了#34;考虑到您需要开发自己的图像识别/处理库或方法,这是ImageMagick的最大好处之一。 (顺便说一下,我与ImageMagick没有关联,我对这个产品非常满意,这就是全部)

上述Objective-C方法中使用的方法是源自IOS的ImageMagick Library的方法。您将不得不阅读这些方法,但您知道,它们是用C语言编写的,而不是Objective-C,这意味着其中一些对于磨机图像I / O或其他的运行来说是陌生的。处理图书馆。然而,我认为难以理解的唯一部分(假设一个人是C代码的新手)是如何理解&#34;&amp;&#34;符号是在一些变量面前做的。除此之外,还有声明和使用结构的问题,但这可以通过使用Stack Overflow轻松解决,这里有很多信息。

我不喜欢你必须为这样的应用编写自己的库或图像处理算法。我认为你为有需要的人做了一件非常好的事情,如果你能锁定一个MVP,我认为它将帮助很多人,我认为使用ImageMagick会帮助你早日实现目标。需要重写ImageMagick为您自己的目的已经做的一些事情。

最后要注意的一点是,ImageMagick建立在一些非常低级别的C库上。我需要运行某种处理比较来确定ImageMagick在IOS设备上的表现与Image I / O之类的效果。但是,我觉得这两个图像处理库共享一些相同的图像处理功能和速度。纠正我,如果有人确定图像I / O在测试中肯定更快,但我只是告诉你这个,所以你知道这个ISN&#39;你的平均POD安装,这是一个强大的机械部分,通过一些IOS开发人员的优雅,他们使用CoccoaPods基本上降低了IOS版本。除此之外,ImageMagick用于所有计算平台,主要是命令行工具

统计数据,数字意味着什么:

以下是您需要了解我在调试器输出中显示的统计数据背后的数据所需的资源:

第一个是旧的,但仍然相关:

http://www.ict.griffith.edu.au/anthony/info/graphics/image_comparing

这个似乎是最新的:

http://www.imagemagick.org/Usage/compare/

无论您选择使用此库还是其他,祝您好运,我喜欢您使用此应用程序的地方,您的演示看起来很棒!

编辑:我差点忘了告诉你这里最重要的部分..如果统计数据全部归还&#34;零&#34;除了&#34; UndefinedMetric&#34;,&#34; PeakSignalToNoiseRatioMetric&#34;,&#34; RootMeanSquaredErrorMetric&#34;和&#34; UndefinedErrorMetric&#34;之外的所有内容,你很可能有一个MATCH!

答案 4 :(得分:2)

我强烈建议使用bag of words模型。

如果你想使用这样的模型来识别说自行车,它会将图像细分为它的部分(鞍座,刨花,框架,转向)。如果它看到包含两个weels,saddle和一个框架的另一个图像,它可能使图片适合自行车(因为相当多的单词是相似的)。这种方法的问题在于没有考虑单词的空间关系。

在这个特定的情况下,我相信这会很合理,因为草图往往是许多较小(可识别)对象的组合。您不必一直检测自行车,而是可以将较小的检测部件(比如车轮)重新用于需要检测的其他部件(摩托车,汽车等)。

编辑:OpenCV甚至还有一个单词包模型的实现。可以找到here