关于C++:图像处理:”可口可乐罐头”识别算法的改进

Image Processing: Algorithm Improvement for 'Coca-Cola Can' Recognition

我在过去几年中从事过的最有趣的项目之一是一个关于图像处理的项目。我们的目标是开发一个能够识别可口可乐"罐头"的系统(请注意,我在强调"罐头"这个词,你马上就会明白为什么)。您可以看到下面的一个示例,其中可以在绿色矩形中通过缩放和旋转进行识别。

Template matching

对项目的一些限制:

  • 背景可能很嘈杂。
  • 可以有任何规模或旋转,甚至方向(在合理范围内)。
  • 图像可能有一定程度的模糊性(轮廓可能不完全是直线)。
  • 图像中可能有可口可乐瓶,算法应该只检测到罐头!
  • 图像的亮度可能会有很大的变化(所以你不能太依赖颜色检测)。
  • 罐可以部分隐藏在侧面或中间,也可以部分隐藏在瓶子后面。
  • 图像中可能根本没有罐头,在这种情况下,你必须找不到任何东西,然后写一条这样的信息。

所以你可能会遇到这样的棘手问题(在本例中,我的算法完全失败了):

Total fail

不久前我做了这个项目,做这个项目很有趣,我有一个不错的实现。以下是关于我的实现的一些详细信息:

语言:使用OpenCV库在C++中完成。

预处理:对于图像预处理,即将图像转换成更原始的形式,给算法提供两种方法:

  • 将颜色域从RGB更改为HSV,并基于"红色"色调进行过滤,饱和度高于某个阈值以避免橙色,低值过滤以避免暗色调。最终的结果是一个黑白二元图像,其中所有的白色像素都表示与该阈值匹配的像素。显然,图像中仍然有很多垃圾,但这减少了必须处理的维度的数量。Binarized image
  • 噪声过滤采用中值滤波(取所有相邻像素的中值,用该值替换像素)来降低噪声。
  • 采用Canny边缘检测滤波器,在两个先例步骤后得到所有项目的轮廓。Contour detection
  • 算法:我为这项任务选择的算法本身取自这本关于特征提取的好书,叫做广义Hough变换(与常规的Hough变换有很大的不同)。它基本上说了一些事情:

    • 你可以在不知道解析方程的情况下描述空间中的物体(这里就是这种情况)。
    • 它可以抵抗像缩放和旋转这样的图像变形,因为它基本上可以测试图像的每个比例因子和旋转因子的组合。
    • 它使用算法将"学习"的基本模型(模板)。
    • 轮廓图像中的每一个像素都会投票给另一个像素,根据它从模型中获得的信息,这个像素应该是你的物体的中心(在重力方面)。

    最后,你会得到一个投票的热图,例如这里,所有的罐子轮廓的像素都会投票给它的重力中心,所以你会在与中心对应的相同像素上有很多投票,并且会在热图中看到一个峰值,如下所示:

    GHT

    一旦你有了这个,一个简单的基于阈值的启发式可以给你中心像素的位置,从中你可以得到比例和旋转,然后围绕它绘制你的小矩形(最终的比例和旋转因子显然是相对于你的原始模板)。至少在理论上…

    结果:虽然这种方法在基本病例中有效,但在某些领域却严重缺乏:

    • 非常慢!我压力不够大。处理这30个测试图像需要将近一整天的时间,显然是因为我有一个非常高的旋转和平移比例因子,因为一些罐子非常小。
    • 当瓶子出现在图像中时,它完全丢失了,出于某种原因,几乎总是找到瓶子而不是罐子(可能是因为瓶子更大,因此像素更多,因此投票更多)
    • 模糊图像也不好,因为投票结果是在围绕中心的随机位置以像素结束,从而以一个非常嘈杂的热图结束。
    • 实现了平移和旋转的差异,但没有实现定向,这意味着无法识别没有直接面对摄像机目标的罐子。

    你能帮我改进我的特定算法吗?我只使用opencv特性来解决上面提到的四个具体问题。

    我希望有些人也能从中学习到一些东西,毕竟我认为不仅仅是提问的人


    另一种方法是使用比例不变特征变换(SIFT)或加速鲁棒特征(SURF)提取特征(关键点)。

    它在opencv 2.3.1中实现。

    您可以使用Features2d+同形法中的功能找到一个很好的代码示例来查找已知对象

    这两种算法对缩放和旋转都是不变的。因为它们使用功能,所以您也可以处理遮挡(只要有足够的关键点可见)。

    Enter image description here

    图像源:教程示例

    筛选需要几百毫秒的处理时间,浏览速度稍快,但不适合实时应用。球体使用的速度快,相对于旋转不变性较弱。

    原始文件

    • 冲浪:加速强劲的功能
    • 独特的图像特征从比例不变的关键点
    • ORB:筛选或冲浪的有效选择


    为了加快速度,我会利用这样一个事实,即你不需要找到一个任意的图像/对象,特别是带有可口可乐标志的图像/对象。这是很重要的,因为这个标志是非常独特的,它应该有一个特征,规模不变的签名在频域,特别是在红色通道的RGB。也就是说,水平扫描线(在水平对齐的徽标上训练)遇到的从红到白到红的交替图案在穿过徽标的中心轴时将具有独特的"节奏"。这种节奏会在不同的尺度和方向上"加速"或"减速",但在比例上保持相等。您可以通过徽标水平和垂直地识别/定义几十条这样的扫描线,还可以以星型模式更对角地识别/定义若干条这样的扫描线。称之为"签名扫描行"。

    Signature scan line

    在目标图像中搜索这个签名是一个简单的扫描水平条图像的问题。寻找红色通道中的高频(表示从红色区域移动到白色区域),一旦发现,查看是否跟随培训课程中确定的频率节奏之一。一旦找到匹配项,您将立即知道扫描线在徽标中的方向和位置(如果在培训期间跟踪这些内容),因此从那里识别徽标的边界是微不足道的。

    如果这不是一个线性有效的算法,或者几乎是这样,我会感到惊讶。很明显,它没有解决你的罐瓶歧视,但至少你会有你的标志。

    (更新:为了识别瓶子,我会在商标旁边寻找可乐(棕色液体),也就是说,在瓶子里。或者,在一个空瓶子的情况下,我会寻找一个帽子,将始终具有相同的基本形状,大小和距离的标志,通常都是白色或红色。搜索一个纯色的椭圆形状的帽子应该在那里,相对于标志。当然不是万无一失,但你在这里的目标应该是快速找到容易的。)

    (我的图像处理时代已经过去几年了,所以我把这个建议保持在高水平和概念上。我想这可能有点接近人眼的工作方式——或者至少是我的大脑的工作方式!)


    有趣的问题:当我瞥一眼你的瓶子图片时,我觉得它也是一个罐子。但是,作为一个人类,我所做的是告诉他们不同之处,当时我注意到它也是一个瓶子…

    那么,要区分罐子和瓶子,先扫描瓶子怎么样?如果你找到了一个,在找罐头之前先把标签遮住。

    如果你已经在做罐头的话,实施起来并不难。真正的缺点是它使处理时间加倍。(但考虑到现实世界中的应用,你最终还是会想做瓶子;-)


    即使人类在第二幅图像中也很难区分瓶子和罐子(前提是瓶子的透明区域是隐藏的)?

    除了一个非常小的区域外,它们几乎是相同的(即,罐顶的宽度有点小,而瓶子的包装纸整个宽度相同,但有一个微小的变化对吗?)

    我想到的第一件事是检查瓶子的红色顶部。但如果瓶子没有顶部,或者部分隐藏(如上所述),这仍然是一个问题。

    我想的第二件事是瓶子的透明度。OpenCv在寻找图像中的透明对象方面做了一些工作。检查以下链接。

    • OpenCV会议记录2012-03-19

    • OpenCV会议记录2012-02-28

    特别是看看这个,看看它们能多精确地检测到玻璃:

    • OpenCV会议记录2012-04-24

    见执行结果:

    Enter image description here

    他们说这是K.McHenry和J.Ponce于2006年在CVPR上发表的论文"一个测地活动轮廓框架"的实现。

    这可能对您的情况有点帮助,但问题再次出现,如果瓶子被装满。

    所以我认为,在这里,你可以先找到瓶子的透明主体,或者找到一个红色区域,连接到两个侧面的透明物体上,这显然就是瓶子。(理想工作时,图像如下。)

    Enter image description here

    现在你可以去掉黄色区域,也就是瓶子的标签,然后运行你的算法来找到罐子。

    无论如何,这个解决方案也有不同的问题,就像其他解决方案一样。

  • 只有当你的瓶子是空的时候它才起作用。在这种情况下,你必须搜索两种黑色之间的红色区域(如果可口可乐的液体是黑色的)。
  • 另一个问题,如果透明部分被覆盖。
  • 但不管怎样,如果图片中没有上述问题,这似乎是一个更好的方法。


    我真的很喜欢达伦·库克和斯塔克对这个问题的回答。我当时正把我的想法投入到对这些问题的评论中,但我相信我的方法太过简单,不能离开这里。

    简言之,您已经确定了一个算法来确定可口可乐标志是否存在于空间中的特定位置。你现在正试图确定,对于任意方向和任意比例因子,一个启发式的适合区分可口可乐罐和其他物体,包括:瓶子,广告牌,广告和可口可乐用具,所有这些都与这个标志相关。在您的问题陈述中,您没有提到许多这些额外的案例,但我觉得它们对您的算法的成功至关重要。

    这里的秘密是确定一个罐子包含什么视觉特征,或者通过负空间,确定其他不存在于罐子中的可乐产品存在什么特征。为此,当前的热门答案勾画出了一个基本的选择"可以"的方法,如果并且仅当"瓶子"没有被识别,无论是通过瓶盖,液体,或其他类似的视觉启发。

    问题是这个坏了。例如,瓶子可能是空的,没有盖子,导致假阳性。或者,它可能是一个部分瓶子,附加的功能损坏,再次导致错误的检测。不用说,这不优雅,也不适合我们的目的。

    为此,罐的最正确选择标准如下:

    • 物体轮廓的形状,如你在问题中所描绘的,是正确的吗?如果是这样,+1。
    • 如果我们假设存在自然光或人造光,我们是否检测到瓶子的铬轮廓,表明这是由铝制成的?如果是这样,+1。
    • 相对于我们的光源(关于光源检测的说明视频链接),我们是否确定物体的镜面特性是正确的?如果是这样,+1。
    • 我们是否可以确定标识为can的对象的任何其他属性,包括但不限于徽标的拓扑图像倾斜、对象的方向、对象的并置(例如,在一个平面上,如一张桌子或在其他can的上下文中),以及是否存在拉片?如果是的话,对于每个人,+1。

    您的分类可能如下所示:

    • 对于每一个候选人匹配,如果检测到可口可乐标志,画一个灰色的边界。
    • 对于+2以上的每个匹配,绘制一个红色边框。

    这从视觉上向用户突出了检测到的内容,强调了可能被正确检测为损坏的罐子的弱阳性。

    对每个属性的检测具有非常不同的时间和空间复杂性,对于每种方法,通过http://dsp.stackexchange.com快速传递对于为您的目的确定最正确和最有效的算法是非常合理的。我在这里的意图是,纯粹而简单地强调,通过使一小部分候选检测空间失效来检测某个东西是否是一个罐头,并不是解决这个问题的最有力或最有效的解决方案,理想情况下,您应该相应地采取适当的行动。

    嘿,恭喜黑客新闻发布!总的来说,这是一个非常了不起的问题,值得我们去宣传。:)


    看形状

    在罐子/瓶子的红色部分取一个甘德。请注意,瓶子标签是直的,但罐的顶部会逐渐变细。您可以通过比较红色部分在其长度上的宽度来区分这两者。

    查看亮点

    区分瓶子和罐子的一种方法是材料。瓶子是塑料做的,而罐子是铝金属做的。在光线充足的情况下,观察瓶子的特殊性是区分瓶子标签和罐子标签的一种方法。

    据我所知,这就是一个人如何区分这两种标签的区别。如果光照条件较差,在区分这两种光照方式时必然会有一些不确定性。在这种情况下,您必须能够检测到透明/半透明瓶本身的存在。


    请看一下Zdenek Kalal的捕食者追踪器。它需要一些训练,但它可以主动地学习被跟踪对象如何看待不同的方向和尺度,并实时地完成它!

    源代码在他的站点上可用。它在Matlab中,但也许有一个Java实现已经由社区成员完成了。我已经成功地在C中重新实现了TLD的跟踪部分。如果我没记错的话,TLD使用蕨类作为关键点探测器。我使用surf或sift(已经由@stacker建议)来重新获取被追踪器丢失的对象。跟踪器的反馈使得随着时间的推移很容易建立一个动态的筛选/浏览模板列表,随着时间的推移,可以以非常高的精度重新获取对象。

    如果您对我的追踪器实现感兴趣,请随时询问。


    如果你不局限于一个不受限制的摄像头,也许你可以使用Xbox Kinect之类的距离传感器。通过这种方法,您可以对图像进行深度和基于颜色的匹配分割。这样可以更快地分离图像中的对象。然后,您可以使用ICP匹配或类似技术,甚至匹配罐的形状,而不是仅仅是其轮廓或颜色,并且考虑到它是圆柱形的,如果您之前对目标进行了三维扫描,这对于任何方向都是一个有效的选择。这些技术通常很快,特别是当用于这样一个特定的目的,应该解决你的速度问题。

    我也可以建议,不一定是为了准确性或速度,但为了好玩,你可以在你的色调分割图像上使用一个训练过的神经网络来识别罐子的形状。它们速度非常快,通常可以达到80/90%的准确度。培训将是一个有点长的过程,尽管你将不得不手动识别每个图像中的罐头。


    我会检测红色矩形:rgb->hsv,过滤红色->binary image,close(膨胀然后腐蚀,在matlab中称为imclose)。

    然后从最大到最小的矩形中查看。在已知位置/比例下具有较小矩形的矩形都可以删除(假设瓶子比例恒定,则较小的矩形将是瓶盖)。

    这会给你留下红色矩形,然后你需要以某种方式检测标识来判断它们是红色矩形还是可乐罐。像OCR,但有一个已知的标志?


    这可能是一个非常幼稚的想法(或者根本不起作用),但所有可乐罐的尺寸都是固定的。因此,如果同一张图片同时包含一个罐子和一个瓶子,那么你可以根据尺寸因素将它们区分开来(瓶子会更大)。现在,由于缺少深度(即3D映射到2D映射),瓶子可能看起来缩小,没有尺寸差异。您可以使用立体声成像恢复一些深度信息,然后恢复原始大小。


    嗯,我真的觉得我在想些什么(这是有史以来最有趣的问题——所以即使找到了一个可以接受的答案,但如果不继续努力找到"完美"的答案,那就太可惜了)。好的。

    一旦你找到了商标,你的麻烦就完成了一半。然后你只需要找出标志周围的区别就行了。此外,我们希望尽可能少做额外的工作。我认为这其实是一个简单的部分…好的。

    商标周围是什么?对于一个罐子,我们可以看到金属,尽管有灯光的影响,但它的基本颜色没有任何变化。只要我们知道标签的角度,我们就可以知道标签正上方是什么,所以我们要看看它们之间的区别:好的。

    好的。

    在这里,标志的上方和下方是完全黑暗的,颜色一致。在这方面相对容易。好的。

    好的。

    在这里,上面和下面的是光,但在颜色上仍然是一致的。它都是银的,而且所有的银金属实际上看起来很罕见,而且一般都是银的颜色。另外,它是在一个薄的狭缝中,并且足够接近已经被识别的红色,所以你可以在它的整个长度上追踪它的形状,以计算出可以被认为是罐头的金属环的百分比。事实上,你只需要一小部分罐沿可以看出它是它的一部分,但你仍然需要找到一个平衡,以确保它不只是一个空瓶子,后面有金属。好的。

    好的。

    最后,还有一个棘手的问题。但不是那么棘手,一旦我们只走在红色包装的正上方(和正下方)。它是透明的,这意味着它将显示它背后的一切。这很好,因为它背后的东西在颜色上不太可能像罐头的银色圆形金属那样一致。它背后可能有许多不同的东西,它们会告诉我们它是一个空的(或充满了清澈的液体)瓶子,或者是一个一致的颜色,这可能意味着它充满了液体,或者瓶子只是在一个纯色的前面。我们正在研究最接近顶部和底部的颜色,正确颜色出现在正确位置的可能性相对较小。我们知道这是一个瓶子,因为它没有罐子的关键视觉元素,相对于瓶子后面的东西,这是相对简单的。好的。

    (最后一个是我在一个空的大可口可乐瓶里找到的最好的——有趣的是,瓶盖和戒指是黄色的,这表明瓶盖的红色可能不应该被依赖。)好的。

    在极少数的情况下,瓶子后面有相似的银色阴影,即使在提取塑料之后,或者瓶子内充满了相同的银色液体阴影,我们可以回到我们可以粗略估计的银的形状上——正如我所提到的,银是圆形的,并遵循罐的形状。但即使我在图像处理方面缺乏一定的知识,这听起来还是很慢。更好的是,为什么不通过检查标志的侧面来推断这一点,以确保那里没有相同的银色?啊,但是如果一个罐子后面有同样的银色阴影呢?然后,我们确实需要更多地关注形状,再看看罐子的顶部和底部。好的。

    根据这一切需要多么完美,可能会非常缓慢,但我想我的基本概念是先检查最简单和最接近的东西。在计算出其他元素的形状之前,根据已经匹配的形状周围的颜色差异(这似乎是最微不足道的部分)。要列出它,需要:好的。

    • 找到主要的吸引力(红色的标志背景,可能是标志本身的方向,虽然在情况下可以被关闭,你需要集中在红色单独)
    • 通过非常明显的红色再次验证形状和方向。
    • 检查形状周围的颜色(因为它快速而无痛)
    • 最后,如果需要的话,请在主要吸引力周围验证这些颜色的形状,以获得正确的圆度。

    如果你不能做到这一点,这可能意味着罐子的顶部和底部都被盖住了,而人类能够用来可靠区分罐子和瓶子的唯一可能的东西就是罐子的遮挡和反射,这将是一场更难处理的战斗。但是,为了更进一步,您可以使用其他答案中提到的半透明扫描技术,按照罐/瓶的角度来检查更多类似瓶的特性。好的。

    有趣的另一个噩梦可能包括一个方便地坐在瓶子后面的罐子,距离如此之远,以至于它的金属刚好显示在标签的上方和下方,只要你沿着红色标签的整个长度扫描,它仍然会失败-这实际上更是个问题,因为你没有检测到一个罐子。在这里你可以,而不是考虑到你实际上是在检测一个瓶子,包括意外的罐头。杯子是半空的,那样的话!好的。

    作为免责声明,我对这个问题之外的图像处理没有经验,也没有考虑过,但它是如此有趣,它让我思考了相当深入,在阅读了所有其他答案后,我认为这可能是最简单和最有效的方法来完成它。就我个人而言,我很高兴我不必考虑编写这个程序!好的。

    编辑好的。

    bad drawing of a can in MS paint另外,看看我在微软画图…这是绝对可怕的,相当不完整,但基于形状和颜色,你可以猜测它可能会是什么。从本质上讲,这些是唯一需要人们费心扫描的东西。当你看到这个非常独特的形状和颜色组合如此接近时,还能是什么呢?我没有画的那一点,白色的背景,应该被认为是"任何不一致的东西"。如果它有一个透明的背景,它几乎可以覆盖任何其他图像,你仍然可以看到它。好的。好啊。


    我不知道opencv,但是从逻辑上看这个问题,我认为你可以通过改变你要找的图片来区分瓶子和罐头,比如可口可乐。你应该加入到罐的顶部,就像在罐的顶部有银色衬里的可口可乐和在瓶子的情况下不会有这样的银色衬里。

    但很明显,这种算法在罐头顶部被隐藏的情况下会失败,但在这种情况下,即使人类也无法区分两者(如果瓶子/罐头中只有可口可乐部分可见的话)


    我喜欢这个挑战,想给出一个解决问题的答案。

  • 提取标识的特征(关键点、描述符,如筛选、浏览)
  • 将这些点与徽标的模型图像匹配(使用matcher,如brute force)
  • 估计刚体的坐标(PNP问题-SolvePNP)
  • 根据刚性体估计盖的位置
  • 做反向投影,计算瓶子瓶盖的图像像素位置(ROI)(我假设你有相机的固有参数)
  • 用方法检查盖子是否在那里。如果有,这就是瓶子
  • 另一个问题是检测到了盖子。它可以是复杂的,也可以是简单的。如果我是你,我只需检查ROI中的颜色柱状图就可以做出简单的决定。

    如果我错了,请给出反馈。谢谢。


    有很多颜色描述符用于识别对象,下面的文章对它们进行了比较。当与筛网或冲浪结合时,它们特别强大。单独浏览或筛选对于可口可乐的形象不是很有用,因为他们不认识很多兴趣点,你需要颜色信息来帮助。我使用bic(边框/内部像素Classi?阳离子)在一个项目中使用冲浪,识别物体效果很好。

    网络图像检索中的颜色描述符:比较研究


    您需要一个从经验中有机地学习和提高分类准确性的程序。

    我建议深入学习,随着深入学习,这将成为一个微不足道的问题。

    您可以在TensorFlow上重新培训初始v3模型:

    如何为新类别重新定义初始阶段的最后一层。

    在这种情况下,你将训练一个卷积神经网络,将一个物体分类为可口可乐罐或不可口可乐罐。


    回答这个问题晚了几年。在过去的5年里,随着CNN将最新技术推向极限,我现在不会使用OpenCV来完成这项任务!(我知道你特别想要OpenCV的特性)我觉得像更快的RCNN、Yolo、SSD等目标检测算法比OpenCV特性更能解决这个问题。如果我现在要解决这个问题(6年后!!)我肯定会使用更快的RCNN。


    我喜欢你的问题,不管它是否偏离主题:p

    一个有趣的旁白;我刚刚完成了我学位上的一门课程,我们学习了机器人学和计算机视觉。我们这学期的计划和你描述的非常相似。

    我们必须开发一个机器人,在各种照明和环境条件下,使用Xbox Kinect检测任何方向的可乐瓶和可乐罐。我们的解决方案包括在色调通道上结合使用带通滤波器和霍夫圆变换。我们可以稍微限制环境(我们可以选择机器人和Kinect传感器的位置和方式),否则我们将使用sift或surf转换。

    你可以在我的博客上阅读我们的方法,主题是:)


    深度学习

    收集至少几百张包含可乐罐的图像,将它们周围的边界框标注为正类,包括可乐瓶和其他可乐产品,将它们标记为负类以及随机对象。

    除非您收集了一个非常大的数据集,否则请对小数据集执行使用深度学习功能的技巧。理想情况下,将支持向量机(SVM)与深度神经网络相结合。

    一旦你将图像输入到一个以前训练过的深度学习模型(例如googlenet),而不是使用神经网络的决策层(final)来进行分类,那么就使用前一层的数据作为特征来训练你的分类器。

    opencv和google net:http://docs.opencv.org/trunk/d5/de7/tutorial_dnn_googlenet.html(http://docs.opencv.org/trunk/d5/de7/tutorial_dnn_googlenet.html)

    OpenCV和SVM:http://docs.opencv.org/2.4/doc/tutorials/ml/introduction_to_svm/introduction_to_svm.html


    作为所有这些好的解决方案的替代方案,您可以训练自己的分类器,使您的应用程序能够抵御错误。例如,你可以使用HAAR训练,为你的目标提供大量正面和负面的图像。

    它可以只提取罐头,并且可以与透明物体的检测相结合。


    有一个来自mvtec的名为halcon的计算机视觉软件包,它的演示可以给你很好的算法思想。有许多类似于您的问题的例子,您可以在演示模式下运行,然后查看代码中的操作符,并了解如何从现有的opencv操作符中实现它们。

    我使用这个包快速地为类似这样的问题建立复杂算法的原型,然后找到如何使用现有的OpenCV特性来实现它们。特别是对于您的案例,您可以尝试在OpenCV中实现嵌入在操作员find-scalled-shape-u模型中的功能。一些操作人员引用了有关算法实现的科学论文,这有助于了解如何在OpenCV中执行类似的操作。希望这有帮助…


    这是我做的一个老项目。地图图像很容易与JavaScript一起使用。我给你这个东西,你读它,知道如何使用它。我们不需要jquery和其他系统来使用地图图像。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
        //Copyright Cherif yahiaoui, by ELEBAN.FR

    //variables de flottement.
    var myInstOne = null;
    var globalize = null;

    var eleban_preload_images = function (name, imgs, url){
    try{
        var oThis = this;
        this.images = new Array();
        this.imageshover = new Array();
        this.imagesNames = new Array(imgs.split(";"));


            for(var i=0; i < this.imagesNames[0].length; i++){
                this.images[i] = new Image();
                this.imageshover[i] = new Image();
            }

        this.url = url;

        this.GetAbsoluteurl = function () {

        var img = new Image(); img.src = url;
        url = img.src; img = null;
            this.url = url;

        };

        this.Preload = function () {

            for(var i=0; i < this.imagesNames[0].length; i++){
                this.images[i].src = this.url+("btn-"+this.imagesNames[0][i]+".png");
                this.imageshover[i].src = this.url+("btn-"+this.imagesNames[0][i]+"-hover.png");
            }

        };
        this.GetAbsoluteurl();
        this.Preload();
    }
    finally {return;}
    }

    var g_preloaderhover = new eleban_preload_images("loaderhover","menu;malette;reservation;cabine;facebook;map;amis","./images/");


    //variable arret flottement
    var g_stopflo = false;

    var myObjfloater = function(name, idname, itop, differ ) {
    var oThis = this; // création d'une référence vers l'objet courant
    this.name = name;
    this.id =idname;
    this.xstep= 0.3;
    this.itime = 30;
    this.obj = null;
    this.y = itop;
    this.yadd = 0;
    this.up = true;
    this.pause = false;
    this.differ = differ;
    this.coordsimage = null;
    this.objimg = null;
    this.initimages = false;
    this.compteur = 0;
    this.over = false;
    this.timeoutstop = null;
    try{
    this.initimage = function(){
    var img = this.obj.getElementsByTagName('img')[0];
    this.coordsimage = new Array(img.width, img.height);
    this.objimg = img;
    this.initimages = true;
    };


    this.myMethod = function() {
    if(!g_stopflo){
        if(this.differ != 0){
    this.differ=this.differ-0.1;
    }else{

    if(this.obj){
    if(this.over == false){
        this.yadd=this.yadd+0.1; this.itime = this.itime + 10;
    this.obj.style.visibility ="hidden";
    this.y = ((this.up)? this.y - this.yadd : this.y + this.yadd);
    this.obj.style.marginTop = this.y +"%" ;
    this.obj.style.visibility ="visible";

    if (this.yadd > this.xstep){
        this.up = (this.up)? false : true;
        this.yadd = -0.1; this.itime=180;
    }
    }
    }else{
        if (document){
            if(document.getElementById) {
             this.obj = document.getElementById(this.id);
            //this.y = this.obj.offsetTop;
            }else{
            if(document.getElementByTagName) { this.obj = document.getElementByTagName(this.id); this.y = this.obj.offsetTop;}
            }

        }
    }
    }
    this.timeoutstop=setTimeout(function() { oThis.myMethod(); }, this.itime);
    }    
    };

    this.callDelayed = function() {
        // utilisation de la référence vers l'objet
    if(!g_stopflo){
        this.timeoutstop=setTimeout(function() { oThis.myMethod(); }, this.itime);
    }
    };
    }
    finally {return;}
    };

    // special creation des zones AREA
    function eleban_createallarea(){
    try{
    var measur = new Array("w","h");
    measur["w"] = new Array(330,570,185,300,115,390,225);
    measur["h"] = new Array(460,570,295,450,100,190,115);
    var ititle = new Array("Voir les menus  et nos suggestions","Repas &agrave; emporter","R&eacute;servation d&rsquo;une table","Nous contacter","Nous rejoindre sur FaceBook","Calculer votre trajet","liste des amis");
    var ihref = new Array("menus.html","emporter.html","reservation.html","contact.html","likebox.html","google.html","amis.html");
    var b_map = new Array(0,1,2,3,4,5,6);
    b_map[0] ="71,32,240,32,249,43,289,352,280,366,102,385,90,371,51,38";
    b_map[1] ="66,52,95,14,129,56,115,91,100,93,112,273,128,284,122,366,176,343,193,296,191,194,147,189,145,166,201,111,199,84,545,105,532,354,509,388,412,478,32,401,77,383,87,375,82,286,95,269,94,221,24,195,11,165,9,120,89,123,89,94,78,92,77,92,77,93,75,93,77,93,76,93,79,92";
    b_map[2] ="19,25,169,38,173,112,161,113,105,103,90,125,91,262,121,269,124,281,96,293,62,289,49,281,56,268,83,264,84,121,71,98,16,90";
    b_map[3] ="60,0,216,1,226,20,225,403,168,421,42,410,45,10";
    b_map[4] ="31,7,72,10,82,18,88,45,88,71,76,81,29,80,17,68,16,18";
    b_map[5] ="91,40,141,38,178,27,184,4,211,5,223,24,240,23,386,135,229,121,103,180,6,156,49,94";
    b_map[6] ="6,32,69,18,79,6,118,7,141,2,149,10,211,17,202,28,209,30,189,62,195,70,178,74,180,90,164,90,154,107,68,101,34,104,34,98,18,97,28,84,15,84,30,65";

    if (document.getElementById){
    for (var i=0; i<b_map.length;i++){
    var obj = document.getElementById("pc_menu"+i);
        if(obj){
        var ct = '<img class="pc_menu" src="'+g_preloaderhover.images[i].src+'" alt="" width="'+measur["w"][i]+'" height="'+measur["h"][i]+'" usemap="#MAP_INDEX'+i+'" \/>';
        ct+='<map name="MAP_INDEX'+i+'">';
        ct+='';
        ct+='<\/map>';
        obj.innerHTML = ct;
        }
    }
    }
    }
    finally {return;}
    }

    //preload, creation et gestion de tous les evenements


    var image_resizer = function(g_layer){


        b_org_elm = new Array("w", "h");
        b_org_elm["w"] = new Array(330,570,185,300,115,390,225);
        b_org_elm["h"] = new Array(460,570,295,450,100,190,115);

        b_map = new Array(0,1,2,3,4,5,6);
        b_map[0] = new Array(71,32,240,32,249,43,289,352,280,366,102,385,90,371,51,38);
        b_map[1] = new Array(66,52,95,14,129,56,115,91,100,93,112,273,128,284,122,366,176,343,193,296,191,194,147,189,145,166,201,111,199,84,545,105,532,354,509,388,412,478,32,401,77,383,87,375,82,286,95,269,94,221,24,195,11,165,9,120,89,123,89,94,78,92,77,92,77,93,75,93,77,93,76,93,79,92);
        b_map[2] = new Array(19,25,169,38,173,112,161,113,105,103,90,125,91,262,121,269,124,281,96,293,62,289,49,281,56,268,83,264,84,121,71,98,16,90);
        b_map[3] = new Array(60,0,216,1,226,20,225,403,168,421,42,410,45,10);
        b_map[4] = new Array(31,6,70,10,78,18,84,23,88,44,88,70,78,80,75,81,33,82,23,76,18,69,16,22,21,13);
        b_map[5] = new Array(91,40,141,38,178,27,184,4,211,5,223,24,240,23,386,135,229,121,103,180,6,156,49,94);
        b_map[6] = new Array(6,32,69,18,79,6,118,7,141,2,149,10,211,17,202,28,209,30,189,62,195,70,178,74,180,90,164,90,154,107,68,101,34,104,34,98,18,97,28,84,15,84,30,65);


        b_layer = g_layer;

    //gere mouseover
        this.mouseover = function(e){
            if (!e) var e = window.event;
            var tg = (window.event) ? e.srcElement : e.target
                if (tg.nodeName){
                    if(tg.nodeName =="AREA"){
                    var divpar = (tg.parentNode)? tg.parentNode.parentNode : tg.parentElement.parentElement;
                        if (divpar){
                            if(divpar.nodeName =="DIV"){
                                var iiobjimg = divpar.getElementsByTagName('img');
                                    if (iiobjimg){
                                        ii = parseInt(divpar.id.substring(divpar.id.length-1,divpar.id.length));
                                        iiobjimg[0].src = g_preloaderhover.imageshover[ii].src;
                                    }
                            }
                        }
                    }
                }
        };

    //gere mouseout
        this.mouseout = function(e){
            if (!e) var e = window.event;
            tg = (window.event) ? e.srcElement : e.target
                if (tg.nodeName){
                    if(tg.nodeName =="AREA"){
                    divpar = (tg.parentNode)? tg.parentNode.parentNode : tg.parentElement.parentElement;
                        if (divpar){
                            if(divpar.nodeName =="DIV"){
                                var iiobjimg = divpar.getElementsByTagName('img');
                                    if (iiobjimg){
                                        ii = parseInt(divpar.id.substring(divpar.id.length-1,divpar.id.length));
                                        iiobjimg[0].src = g_preloaderhover.images[ii].src;
                                    }
                            }
                        }
                    }
                }
        };

    //ajout evenements entree sortie à la page web lors du chargement de la page
        this.init = function () {

            for(var i=0; i<b_org_elm["w"].length;i++){
                w = document.getElementById("pc_menu"+i).offsetWidth;
                h = document.getElementById("pc_menu"+i).offsetHeight;

                xa = w/parseFloat(b_org_elm["w"][i]);
                ya = h/parseFloat(b_org_elm["h"][i]);

                area = document.getElementById("pc_menu"+i).getElementsByTagName('area')[0];

                b_map2 = area.coords.split(",");
                yswitch = true;
                    for(m=0; m<b_map2.length;m++){
                    b_map2[m] = Math.round(parseFloat(b_map[i][m]) * ((yswitch)? xa: ya));
                    yswitch = (yswitch)? false :  true;
                    }
                area.coords = b_map2.join(',');
            }
        };


        this.resize = function () {
        clearTimeout(myInstOne.timeoutstop);
        g_stopflo=true;

        globalize.init();
        g_stopflo=false;
        myInstOne.obj = null;
        myInstOne.callDelayed();
        };


        nar = document.getElementsByTagName('area').length;

            for(var i=0; i<nar;i++){
                var elem = document.getElementsByTagName('area')[i];
                if (elem.addEventListener){
                        elem.addEventListener("onmouseover",this.mouseover,true);
                    elem.addEventListener("onmouseout",this.mouseout,true);
                }else if (elem.attachEvent) {
                        elem.attachEvent("onmouseover", this.mouseover);
                        elem.attachEvent("onmouseout", this.mouseout);
                }else{
                        elem["onmouseover"] = this.mouseover;
                        elem["onmouseout"] = this.mouseout;
                }
            }

                window.onresize = this.resize;
            window.onmouseover = this.mouseover;
            window.onmouseout = this.mouseout;
    }


    //permet de temporiser et éviter les erreurs de chargement des objets
    function temporise_Init(Lastdiv){
    if(document.getElementById){
        if(document.getElementById(Lastdiv)){

        eleban_createallarea();

        myInstOne = new myObjfloater('b_menumap11', 'pc_menu1', 1, 0);

        globalize = new image_resizer(document.getElementById('pc_redim'));
        globalize.init();
            globalize.resize();



        }else{
        setTimeout(temporise_Init(Lastdiv), 30);
        }
    }
    }


    window.onload = function () {
    temporise_Init("pc_bandeau");
    }

    如果您对它的实时性感兴趣,那么您需要添加一个预处理过滤器来确定什么是用重负荷的东西扫描的。一个好的、快速的、实时的、预处理的过滤器,它可以让你扫描那些更像可口可乐罐的东西,而不是在移动到更模糊的东西之前,它是这样的:搜索图像中最大的色块,这些色块与你的可口可乐罐的sqrt(pow(red,2) + pow(blue,2) + pow(green,2))有一定的公差。从一个非常严格的颜色公差开始,并逐步降低到更宽松的颜色公差。然后,当你的机器人在指定的时间内处理当前帧时,它会将当前找到的瓶子用于你的目的。请注意,您必须调整sqrt(pow(red,2) + pow(blue,2) + pow(green,2))中的RGB颜色,以使其正确。

    另外,这看起来很愚蠢,但在编译C代码时,是否确保启用了-oFast编译器优化?


    我首先要找的是颜色-像红色,在图像中做红眼检测时-有一个特定的颜色范围要检测,它的一些特征考虑到周围区域,例如,如果在图像中确实可见,它与另一只眼睛之间的距离。

    1:第一个特点是颜色,红色占主导地位。在检测到可口可乐的红色后,有几个项目值得关注1a:这个红色区域有多大(是否有足够的数量来确定一个真正的罐头或不足够的——10个像素可能不够)。1b:标签上有"可口可乐"或"波浪"的颜色吗?1B1:是否有足够的可能性考虑它是一个标签。

    如果图像中不存在第1项,则该项是一种快捷的预处理-继续。

    所以如果是这样的话,我就可以利用我的图像片段,开始从有问题的区域中寻找更多的缩放-基本上看周围的区域/边缘…

    2:给出上述图像区域ID的1-验证相关项目的周围点[边缘]。A:有没有看起来像是罐顶或罐底的银器?B:瓶子看起来可能是透明的,但玻璃桌子也可能是透明的,玻璃桌子/架子或透明区域也可能是透明的,如果是这样的话,可能会有多个瓶子出来。一个瓶子可能有一个红色的盖子,但它可能没有,但它应该有瓶顶/螺纹螺钉的形状,或者一个盖子。C:即使A和B失败了,它仍然可以是部分的。当它是部分的时候,这就更复杂了,因为部分瓶子/部分看起来可能是一样的,所以对红色区域边缘到边缘的测量进行更多的处理。小瓶的大小可能相似。

    3:在上面的分析之后,也就是我看文字和波浪标志的时候——因为我可以定位我对单词中的一些字母的搜索,因为你可能没有所有的文本,因为没有所有的可以,波浪会在某些点上与文本对齐(距离方向),这样我就可以搜索这种可能性,并知道是什么。ch字母应该存在于距离x处的波的那个点上。


    也许晚了很多年,但还是有一个理论值得尝试。

    红色标志区域的边框与瓶/罐的外形尺寸之比不同。如果是罐装,应该是1:1,而瓶子(有盖或无盖)的比例不同。这应该可以很容易地区分两者。

    更新:由于罐和瓶的尺寸不同,标识区域的水平曲率也会有所不同。如果你的机器人需要拿起罐子/瓶子,并且你相应地决定握力,这可能特别有用。