logo是包含了颜色、形状、特征等信息的图形实体。logo检测有很多挑战,比如视角变化、弯曲、形状和颜色的变化、遮挡、背景变化等。

下图是我跑的一个(百度随便找的,非项目图)识别一般的效果图,虽然可以识别出指定的logo(1中左图),也受到logo多余部分的影响,最终匹配获取的logo区域有所放大,仔细观察发现logo外围区域颜色都是自下而上渐变变淡,野点(离群点)阈值不够,导致识别区域多了一部分。

文章(Logo localization andrecognition in natural images using homographic class graphs,2015)提出了一种在自然图像中定位和分类logo的方法。为了解决视角变化,同一类logo实例的SIFT关键点之间进行单映射匹配。为了解决颜色变化,构建了一个logo互连的加权图,以提取潜在的某个类的多个类实例。通过将各个训练图像映射到中心图像上构建一个类模型。对于彩色反转logo,通过反转第一个类模型的特征方向获得第两个类模型,这将大大提高准确率。而且仅需要少量训练集图片即可完成匹配。

(另一种有意思的方法:使用(SIFT特征KMeans聚类关键点训练SVM)实现自然图像中的logo商标识别和定位)

原理

要辨识某物体的条件就是先掌握其特征!由于我们要辨识的是logo(某个物件)而非整张相片,因此需要提取所谓称为「Local features」的特征,作法是先在影像中选取重要的特征点(能明显表征为logo的区域),接着以其为base取得周围的特征(即local features)-提取图像特征点,这些来自不同相片的local features会透过Feature matching功能来比对是否有相同的物件。

透过特征点(Keypoint detection)→局部特征(Feature extraction)→Feature matching的方式来识别logo

我们只要从感兴趣的物件中设定具有这些特性的区域为关键点(特征点)keypoints,再针对各关键点计算并提取该区域的features,就能用来比对及辨识物体。

整个检测过程分为如上所述的(特征点→局部特征→Feature matching)3个步骤,所执行的动作依序是:Keypoint detection、Feature extraction以及Feature matching。

  1. Keypoint detection: 在图片中取得感兴趣的关键点(可能为edges、corners或blobs)。
  2. Feature extraction: 针对各关键点提取该区域的features(local features)。
  3. Feature matching:  关键点筛选并进行特征匹配。

我们可将上述的步骤归纳如下: 首先,在图片A与B分别找出Keypoints,再依据各个keypoints取出其local features(主要为梯度和角度所组成的Histogram),两张图片的local features进行feature matching,计算并match各个距离最小的keypoint 。

方法步骤

1)Keypoint detection

Keypoint detection的演算法有很多,openCV便提供了十一种方法:

  1. “FAST" – FastFeatureDetector
  2. “STAR" – StarFeatureDetector
  3. “SIFT" – SIFT (nonfree module) <-------------(目前最常用)
  4. “SURF" – SURF (nonfree module)
  5. “ORB" – ORB
  6. “BRISK" – BRISK
  7. “MSER" – MSER
  8. “GFTT" – GoodFeaturesToTrackDetector
  9. “HARRIS" – GoodFeaturesToTrackDetector with Harris detector enabled
  10. “Dense" – DenseFeatureDetector
  11. “SimpleBlob" – SimpleBlobDetector

还有以下二种方式,可与以上各方法合并来使用:

  1. “Grid" – GridAdaptedFeatureDetector
  2. “Pyramid" – PyramidAdaptedFeatureDetector

例如: “GridFAST", “PyramidSTAR" .

Keypoints关键点除了用来比对或辨识物件之外,也经常用于拼接(stitch)图像以制作全景图。

各算法实例参看博客:openCV获取图像特征点的方法

2)Feature extraction

SIFT特征是很好的描述图像特征的描述子。它对尺度、方向等具有不变性。在自然图像中,logo通常都十分小。若是直接提取SIFT特征,可能提取不到或者只能提取到几个特征点,这对检测是十分不利的。因此在训练图像中,首先剪切出只含有logo的部分作为“训练logo块”,然后再提取SIFT特征。

为了更好地描述logo做了两方面的修改。首先,(1)将SIFT中DoG的边缘阈值从10提高至100。这能够保证在不引入无用的特征点的同时,提取到更多的特征点来描述logo。其次,也是为了提高获得的特征点个数。数据集中测试图像中的logo过于微小,受[2]启发,在测试时,(2)将任何一维小于200像素的测试图像扩大一倍,这将提高准确率。其中200像素是一个经验值。

图像匹配的目的是通过寻找到两张图像的合适的映射关系,揭示图像对之间的空间对应关系。这里的映射关系指的是单应性,即评估将一张图像映射到另一张图像平面的单应性矩阵。(图像拼接)

3)Feature matching

单应性矩阵

单映性变换是相同场景的两个图像之间的一种连接,记为H。它可以将第一张图像中平面上的点(a,b)映射到第二张图上的(x,y)点:

给H乘以一个系数z,就变成把原来的(a,b,1)映射成(zx,zy,z)。该点实际上和(x,y,1)是同一个点,可以令z=1/h33,则h33=1。所以H中只有8个自由元素,至少需要4对图像对即可解出一个矩阵H。
单应性矩阵函数代码:

    # # 获取关键点的坐标# 1. # 将所有好的匹配的对应点的坐标存储下来,就是为了从序列中随机选取4组,以便下一步计算单应矩阵src_pts = np.float32([imageFromSet[0][m.queryIdx].pt for m in good]).reshape(-1, 1, 2)  # [[x,y],]dst_pts = np.float32([imgKeyPoints[n.trainIdx].pt for n in good]).reshape(-1, 1, 2)# 2.# 单应性估计#    由于我们的对象是平面且固定的,所以我们就可以找到两幅图片特征点的单应性变换。得到单应性变换的矩阵后就可以计算对应的目标角点:M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 0.0, None, 2000,0.995, )  # 单应矩阵估计,利用RANSAC方法计算单应矩阵,,置信度设为0.99 循环次数设置为2000'''# 有了H(M)单应性矩阵,我们可以查看源点被映射到query image中的位置计算单应性矩阵(homography),在这个函数参数中,输入的src_pts和dst_pts是两个对应的序列,这两组序列的每一对数据一一匹配,其中既有正确的匹配,也有错误的匹配,正确的可以称为内点,错误的称为外点,RANSAC方法就是从这些包含错误匹配的数据中,分离出正确的匹配,并且求得单应矩阵。返回值中M为变换矩阵。mask是掩模,online的点。mask:标记矩阵,标记内点和外点.他和m1,m2的长度一样,当一个m1和m2中的点为内点时,mask相应的标记为1,反之为0,说白了,通过mask我们最终可以知道序列中哪些是内点,哪些是外点。M(model):就是我们需要求解的单应矩阵.ransacReprojThreshold=0.0:为阈值,当某一个匹配与估计的假设小于阈值时,则被认为是一个内点,这个阈值,openCV默认给的是3,后期使用的时候自己也可以修改。confidence:为置信度,其实也就是人为的规定了一个数值,这个数值可以大致表示RANSAC结果的准确性,这个值初始时被设置为0.995maxIters:为初始迭代次数,RANSAC算法核心就是不断的迭代,这个值就是迭代的次数,默认设为了2000这个函数的前期,主要是设置了一些变量然后赋初值,然后转换相应的格式等等。//后面,由变换矩阵,求得变换后的物体边界四个点  plt.imshow(inliersNumber), plt.show()'''

RANSAC

迭代地随机选择4对特征对应关系,用直接线性变换(DLT)确定单应性矩阵H[3]。迭代次数越多,寻找到的匹配关系越准确。如果一次正确匹配的概率是pi,则nn次迭代之后得到正确匹配关系的概率是:

   
其中r是每次迭代时提取的图像对数。如上,r=4。

因为logo很小,所以从图中匹配到的概率也较小。为了提高得到正确匹配关系的概率,将RANSAC的迭代次数从500提升至200,000次。这将导致匹配的时间变长,而且测试阶段有很大可能找不到测试图像和类模型图像的单应性关系,这种情况下不需要进行全部的迭代过程。所以规定在测试阶段,如果匹配点数少于20,系统自动认为两张图像有很大的可能性找不到单应性关系,直接结束。这也是为什么输入数据增加,而算法运行时间不会增加?的原因:

  • (openCV的RANSAC算法首先把迭代的次数设置为2000,然后再迭代的过程中,动态的改变总迭代次数,无论输入数据有多少,总的迭代次数不会增加,并且通过4个匹配计算出估计的矩阵这个时间是不变的,通过估计矩阵来计算内点,这方面的增加的时间开销基本上可以忽略。所以导致的最终结果就是,无论输入点有多少,运算时间基本不会有太大变化。)
  • 如代码中niters本来是迭代的次数,也就是循环的次数。但是通过这行代码我们发现,每次循环后,都会对niters这个值进行更新,也就是每次循环后都会改变循环的总次数。
  • cvRANSACUpdateNumIters()函数利用confidence(置信度)count(总匹配个数)goodCount(当前内点个数)niters(当前的总迭代次数)这几个参数,来动态的改变总迭代次数的大小。该函数的中心思想就是当内点占的比例较多时,那么很有可能已经找到了正确的估计,所以就适当的减少迭代次数来节省时间。这个迭代次数的减少是以指数形式减少的,所以节省的时间开销也是非常的可观。因此最初设计的2000的迭代次数,可能最终的迭代次数只有几十。同样的,如果你自己一开始把迭代次数设置成10000或者更大,进过几次迭代后,niters又会变得非常小了。所以初始时的niters设置的再大,其实对最终的运行时间也没什么影响。我用我自己的程序简答试了一下,无论初值设为2000,10000,20000,最终的迭代次数都变成了58!!!

互连图

在训练图像完成所有可能的连接之后,以图的形式展示所创建的连接。

假设训练集共有n张图像,理论上一共会产生n(n-1)/2个有联系的图像对。然而实际上,由于遮挡、颜色反转等,并不是所有的图像对之间都有足够的信息(特征点)而产生一个正确的映射关系。但这并不是一个问题,因为任何两张图像之间都可以通过其他的图像构建联系,即所有图像都可以以直接或间接的方式存在连接。如图所示。

形成图之后,可以很方便地找到与其他图像连接数最多的“中心图像”/“核心图像”。在上图中,img7就是中心图像。与[4]中不同,在本文中为每一条边赋予了权重,权重大小与匹配的点对数数量成反比。

类模型

训练的目的是在同一个平面中,联合所有代表性的关键点和它们对应的描述子。联合特征指的是将所有的特征(关键点和描述子)映射到某一张图像上。只有关键点需要计算,描述子不需要再进行计算。使用之前得出的H,所有图像均可以映射到中心图像上。这里存在一个问题,对于直接连接的图像,直接应用单应性转换即可。对于那些间接连接的图像,比如图像1和图像n没有直接连接,需要通过以下公式完成映射:

由于映射会引入少量的误差,所以为了尽可能地减少误差,需要为间接相连的图像选择合适的映射路径。明显地,路径越长,引入的误差越多。因为边缘权重与匹配的点对数数量成反比,这使权重和最小的路径有最多的匹配点对数,能够更好地描述特征,即引入了更少的误差。

错误图谱

为了避免过多的不相关的信息影响最终的类模型,引入一个关键点的预过滤过程。对于每一对映射关系,构建一个错误图谱,它能将正确的和错误的匹配分隔开。图谱的值与区域的匹配正确性有直接关系,过程和[5]中相似。

这个图谱能够区分某个区域是正确的映射区域还是遮挡或者形变的区域。下图显示了一个错误图谱的例子。(c)是这个映射产生的错误图谱,其中深色区域显示了正确的匹配,浅色的部分显示了遮挡或logo的不同之处。

类的描述

在建立类模型时,提取出的特征点有很多相似的,映射到中心图像后,会有很多位置和描述都十分相近的关键点,这会减慢我们的匹配效率。所以进行描述子的量化,从而得到唯一的关键点和特征描述。[6]中,作者提到采用K-D树能够完成最大限度的量化,减少计算时间,我们这里采用的是kNN。

# 利用近似k近邻算法去寻找一致性,FLANN方法比BF(Brute-Force)方法快的多:
elif self.METHOD == Method.SIFT_BRUTE_FORCE or self.METHOD == Method.SIFT_FLANN:matches = self.keyPointsMatcher.knnMatch(imageFromSet[1], imgDescriptor, k=2)  # 函数返回一个训练集和询问集的一致性列表for match in matches:## 用比值判别法(ratio test)删除离群点if len(match) == 2:m, n = match# 这里使用的kNN匹配的k值为2(在训练集中找两个点),第一个匹配的是最近邻,第二个匹配的是次近邻。直觉上,一个正确的匹配会更接近第一个邻居。# 换句话说,一个[不]正确的匹配,[两个邻居]的距离是相似的。因此,我们可以通过查看二者距离的不同来评判距匹配程度的好坏。# 比值检测认为第一个匹配和第二个匹配的比值小于一个给定的值(一般是0.5),这里是0.7:if m.distance < self.DISTANCE * n.distance / 100:good.append(m)

光照反转logo模型

logo图像可能是在不同的光照条件下拍摄的,如下图所示。一般的SIFT描述符是去计算等效灰度图的特征点,能够很好地描述物体的形状信息,但它不能解决不同光照条件的问题。由于SIFT描述符仅计算等效灰度图像的事实,反转实际上指灰度/亮度水平的反转; 然而RGB颜色的反转在很小的程度上意味着灰度级别的反转。

对于具有不同光照条件的logo图像,在训练阶段,互连图形成了两个不同的集群。具有差不多相同亮度的训练集中的图像将能够通过单映射彼此匹配,而具有反相亮度级别的其他图像将聚集在另一个单独的集群中。

训练集中,有的类需要构建光照反转logo模型,而有的类一个模型就能达到较好的识别效果。为了自动检测需要反转模型的类,我们使用类紧凑性标准- 通过分析类的图:如果识别了两个单独的连接组件,那么该类必须有两个类模型。 由于中央图像被认为是具有最多连接的中心图像,所以这意味着该类的主要模型由图中最大的连接分量描述。如果图的其他集群中的图像一旦亮度反转,就能与类模型相匹配,那意味着实际上类将需要一个反向亮度模型。

如果某类需要构建反转模型,就从第二个集群中选择一个“次要中心图像”。如果按照与第一个模型完全相同的步骤来构建类的另一个模型将导致该类的弱描述,因为该集群包含的图像少于主体,因此信息较少。之前的第一个模型具有很高的描述力,反转之后是对类的中的反转部分进行适当表示。这一次只需要创建关键点的SIFT向量,位置保持不变,而SIFT的8个方向则必须进行相应反转。如下图所示。

参考代码(含代码注释):

SIFT

'''
在复杂的环境中,FLANN算法不容易将对象混淆,而像素级算法则容易混淆--(查看Matches输出图可知)单应性估计:由于我们的对象是平面且固定的,所以我们就可以找到两幅图片特征点的单应性变换。得到单应性变换的矩阵后就可以计算对应的目标角点
'''class Method(Enum):ORB = 1SIFT_BRUTE_FORCE = 2SIFT_FLANN = 3class FeatureExtraction(object):def get_filepaths(self, directory):file_paths = []  # 将存储所有的全文件路径的列表# Walk the tree.for root, directories, files in os.walk(directory):for filename in files:# 加入两个字符串以形成完整的文件路径filepath = os.path.join(root, filename)file_paths.append(filepath)  # Add it to the list.return file_paths  # 所有文件的路径def resize_function(self, x):step = 200x = x - 500.0if x < 0:scale = 1 / ((1.005) ** abs(x))else:scale = (x + step) / stepreturn scaledef resize_with_keypoints_and_descriptor(self, image, detector, scale=None):if scale is not None:image = cv2.resize(image, None,fx=scale,fy=scale,interpolation=cv2.INTER_CUBIC)imageGray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# cv2.imshow('GRAY NORMAL', imageGray)# clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))# imageGray = clahe.apply(imageGray)# imageGray = cv2.equalizeHist(imageGray, None)# cv2.imshow('GRAY - HISTOGRAM OPERATIONS', imageGray)keypoints, descriptor = detector.detectAndCompute(imageGray, None)imageWithKeypoints = np.zeros(image.shape, image.dtype)cv2.drawKeypoints(image, keypoints, imageWithKeypoints, self.red,cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS)return keypoints, descriptor, image, imageWithKeypointsdef resize_set(self):print('Resizing entire sets...')scale = self.PATTERNIMAGESCALEif self.METHOD == Method.ORB:self.scalesList = np.linspace(scale / 20, scale, 10)else:self.scalesList = np.linspace(scale, scale, 1)print('Scale linspace: ' + str(self.scalesList))self.logoImagesSet = []for image in self.logoImagesSetOriginal:for size in self.scalesList:self.logoImagesSet.append(self.resize_with_keypoints_and_descriptor(image, self.keyPointsLogoDetector, size))self.patternImagesSet = []for image in self.patternImagesSetOriginal:self.patternImagesSet.append(self.resize_with_keypoints_and_descriptor(image, self.keyPointsPatternDetector,scale))def getSetResults(self, images_set, imgKeyPoints, imgDescriptor):results = []for imageFromSet in images_set:# Match descriptors.imageDescriptorNumber = 0goodMatchesNumber = 0inliersNumber = 0statistics = (imageDescriptorNumber, goodMatchesNumber, inliersNumber)dstPoints = NoneM = Nonegood = NonematchesMask = Nonew = Noneh = Noneif imgDescriptor is not None and imageFromSet[1] is not None:imageDescriptorNumber = len(imgDescriptor)good = []if self.METHOD == Method.ORB or ((self.METHOD == Method.SIFT_BRUTE_FORCE or self.METHOD == Method.SIFT_FLANN) and len(imgDescriptor) > 1 and len(imageFromSet[1]) > 1):if self.METHOD == Method.ORB:matches = self.keyPointsMatcher.match(imageFromSet[1], imgDescriptor)# Sort them in the order of their distance.matches = sorted(matches, key=lambda x: x.distance)for m in matches:if m.distance < self.DISTANCE:good.append(m)# 利用近似k近邻算法去寻找一致性,FLANN方法比BF(Brute-Force)方法快的多:elif self.METHOD == Method.SIFT_BRUTE_FORCE or self.METHOD == Method.SIFT_FLANN:matches = self.keyPointsMatcher.knnMatch(imageFromSet[1], imgDescriptor, k=2)# 函数返回一个训练集和询问集的一致性列表for match in matches:## 用比值判别法(ratio test)删除离群点if len(match) == 2:m, n = match# 这里使用的kNN匹配的k值为2(在训练集中找两个点),第一个匹配的是最近邻,第二个匹配的是次近邻。直觉上,一个正确的匹配会更接近第一个邻居。# 换句话说,一个[不]正确的匹配,[两个邻居]的距离是相似的。因此,我们可以通过查看二者距离的不同来评判距匹配程度的好坏。# 比值检测认为第一个匹配和第二个匹配的比值小于一个给定的值(一般是0.5),这里是0.7:if m.distance < self.DISTANCE * n.distance / 100:good.append(m)goodMatchesNumber = len(good)matchesMask = []M = Noneif len(good) >= 1:# # 获取关键点的坐标#1. # 将所有好的匹配的对应点的坐标存储下来,就是为了从序列中随机选取4组,以便下一步计算单应矩阵src_pts = np.float32([imageFromSet[0][m.queryIdx].pt for m in good]).reshape(-1, 1, 2) #[[x,y],]dst_pts = np.float32([imgKeyPoints[n.trainIdx].pt for n in good]).reshape(-1, 1, 2)#2.# 单应性估计#    由于我们的对象是平面且固定的,所以我们就可以找到两幅图片特征点的单应性变换。得到单应性变换的矩阵后就可以计算对应的目标角点:M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 0.0, None, 2000, 0.995,) #单应矩阵估计,利用RANSAC方法计算单应矩阵,,置信度设为0.99 循环次数设置为2000'''# 有了H(M)单应性矩阵,我们可以查看源点被映射到query image中的位置计算单应性矩阵(homography),在这个函数参数中,输入的src_pts和dst_pts是两个对应的序列,这两组序列的每一对数据一一匹配,其中既有正确的匹配,也有错误的匹配,正确的可以称为内点,错误的称为外点,RANSAC方法就是从这些包含错误匹配的数据中,分离出正确的匹配,并且求得单应矩阵。返回值中M为变换矩阵。mask是掩模,online的点。mask:标记矩阵,标记内点和外点.他和m1,m2的长度一样,当一个m1和m2中的点为内点时,mask相应的标记为1,反之为0,说白了,通过mask我们最终可以知道序列中哪些是内点,哪些是外点。M(model):就是我们需要求解的单应矩阵.ransacReprojThreshold=0.0:为阈值,当某一个匹配与估计的假设小于阈值时,则被认为是一个内点,这个阈值,openCV默认给的是3,后期使用的时候自己也可以修改。confidence:为置信度,其实也就是人为的规定了一个数值,这个数值可以大致表示RANSAC结果的准确性,这个值初始时被设置为0.995maxIters:为初始迭代次数,RANSAC算法核心就是不断的迭代,这个值就是迭代的次数,默认设为了2000这个函数的前期,主要是设置了一些变量然后赋初值,然后转换相应的格式等等。//后面,由变换矩阵,求得变换后的物体边界四个点  plt.imshow(inliersNumber), plt.show()'''if self.maskEnable:matchesMask = mask.ravel().tolist()#// 把内点转换为drawMatches可以使用的格式#通过cv2.drawMatchesKnn画出匹配的特征点,再将好的匹配返回##3.# 有了H单应性矩阵,我们可以查看源点被映射到query image中的位置h, w = imageFromSet[2].shape[:2]# print(h, w)pts = np.float32([[0, 0], [0, h - 1], [w - 1, h - 1], [w - 1, 0]]).reshape(-1, 1, 2)#4.# perspectiveTransform返回点的列表if M is not None:dst = cv2.perspectiveTransform(pts, M)dstPoints = [np.int32(dst)] # //由变换矩阵,求得变换后的物体边界四个点.反复计算,求得4个点坐标#print(dstPoints)#5.# 计算非野点个数# // 状态为0表示野点(离群点)inliersNumber = len([x for x in mask if x != 0])else:print('Not enough matches!')statistics = (imageDescriptorNumber, goodMatchesNumber, inliersNumber)results.append((statistics, dstPoints, (M, (w, h)), good, matchesMask))return results # 这个结果传入下面函数:def getBestResultIndex(self, totalResults):counter = 0bestResult = 0index = 0for result in totalResults:if result[0][2] >= bestResult:bestResult = result[0][2]index = countercounter = counter + 1return indexdef generateOutputImages(self, name, patternImagesSet, index, additionalName, cameraImageWithKeypoints,imgKeyPoints):matchesImage = np.zeros(self.INITSHAPE, self.INITDTYPE)warpedImage = np.zeros(self.INITSHAPE, self.INITDTYPE)# 单应性矩阵图(就是上面都是圈圈的图)homograpyImage = cameraImageWithKeypoints.copy()if self.totalResults[index][1] is not None and self.totalResults[index][2][0] is not None:cv2.polylines(homograpyImage, self.totalResults[index][1], True, [100, 255, 0], 5, cv2.LINE_AA)''''''# 截出图上的logo图warpedImage = cv2.warpPerspective(self.cameraImage, self.totalResults[index][2][0],self.totalResults[index][2][1], None,cv2.WARP_INVERSE_MAP,cv2.BORDER_CONSTANT, (0, 0, 0))#plt.imshow(warpedImage), plt.show()if self.totalResults[index][3] is not None and self.totalResults[index][4] is not None:## 通过cv2.drawMatchesKnn画出匹配的特征点,再将好的匹配返回matchesImage = cv2.drawMatches(patternImagesSet[index][3], patternImagesSet[index][0], homograpyImage,imgKeyPoints,self.totalResults[index][3],None,self.blue, self.red,self.totalResults[index][4],cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS)print('Number [ ' + name + ' ]: ' + str(additionalName))print('Length of best image descriptor [ ' + name + ' ]: ' + str(self.totalResults[index][0][0]))print('Good matches [ ' + name + ' ]: ' + str(self.totalResults[index][0][1]))print('Inliers [ ' + name + ' ]: ' + str(self.totalResults[index][0][2]))## cv2.imshow('Original image', self.cameraImage)## cv2.imshow('Keypoints - image', cameraImageWithKeypoints)# cv2.imshow('Keypoints - pattern', patternImagesSet[index][3])## cv2.imshow('Homography', homograpyImage)## cv2.imshow('Matches', matchesImage)## cv2.imshow('Pattern image', patternImagesSet[index][2])# cv2.imshow('Warped image', warpedImage)outputImages = [matchesImage, homograpyImage, warpedImage, patternImagesSet[index][2],patternImagesSet[index][3], cameraImageWithKeypoints, self.cameraImage]return outputImagesdef saveResults(self, outputImages, name, additionalName):newCataloguePath = os.path.join(self.DESTINATIONPATH, str(additionalName), name)os.makedirs(newCataloguePath, exist_ok=True)cv2.imwrite(os.path.join(newCataloguePath, '7_Original image_' + name + '.png'), outputImages[6])cv2.imwrite(os.path.join(newCataloguePath, '6_Keypoints - image_' + name + '.png'), outputImages[5])cv2.imwrite(os.path.join(newCataloguePath, '5_Keypoints - pattern_' + name + '.png'), outputImages[4])cv2.imwrite(os.path.join(newCataloguePath, '2_Homography_' + name + '.png'), outputImages[1])cv2.imwrite(os.path.join(newCataloguePath, '1_Matches_' + name + '.png'), outputImages[0])cv2.imwrite(os.path.join(newCataloguePath, '4_Pattern image_' + name + '.png'), outputImages[3])cv2.imwrite(os.path.join(newCataloguePath, '3_Warped image_' + name + '.png'), outputImages[2])def compareWithLogo(self, name, maskEnable, saveFlag):self.maskEnable = maskEnableimgKeyPoints, imgDescriptor, self.cameraImage, cameraImageWithKeypoints = self.resize_with_keypoints_and_descriptor(sourceImage, self.keyPointsLogoDetector, #保留特征多self.SOURCEIMAGESCALE)self.totalResults = self.getSetResults(self.logoImagesSet, imgKeyPoints, imgDescriptor)index = self.getBestResultIndex(self.totalResults)outputImages = self.generateOutputImages('LOGO', self.logoImagesSet, index, name, cameraImageWithKeypoints,imgKeyPoints)if saveFlag:self.saveResults(outputImages, 'LOGO', name)outputImages.append(self.logoPaths[index // len(self.scalesList)])return outputImagesdef compareWithPattern(self, name, maskEnable, saveFlag):self.maskEnable = maskEnableimgKeyPoints, imgDescriptor, self.cameraImage, cameraImageWithKeypoints = self.resize_with_keypoints_and_descriptor(sourceImage, self.keyPointsPatternDetector, #保留特征少self.SOURCEIMAGESCALE)self.totalResults = self.getSetResults(self.patternImagesSet, imgKeyPoints, imgDescriptor)index = self.getBestResultIndex(self.totalResults)outputImages = self.generateOutputImages('PATTERN', self.patternImagesSet, index, name,cameraImageWithKeypoints,imgKeyPoints)if saveFlag:self.saveResults(outputImages, 'PATTERN', name)outputImages.append(self.patternPaths[index])return outputImagesdef changePatternScale(self, scale):self.PATTERNIMAGESCALE = scaleself.resize_set()def __init__(self, method, logoNfeatures, patternNfeatures, patternImageScale, sourceImagescale, distance):print('Preprocessing...')# IMPORTANT VARIABLES# 1 - ORB, 2 - SIFT BRUTE-FORCE, 3 - SIFT FLANNself.METHOD = methodself.PATTERNIMAGESCALE = patternImageScaleself.SOURCEIMAGESCALE = sourceImagescaleself.DISTANCE = distanceself.LOGOPATTERNSPATH = 'Logo_patterns'  #logo图案路径self.DRUGPATTERNSPATH = 'Drugs_patterns' #药物图案路径self.DESTINATIONPATH = 'RESULTS'  #目的地路径self.INITSHAPE = (480, 640, 3)self.INITDTYPE = np.uint8self.red = (20, 140, 255)self.blue = (220, 102, 20)# IMPORTANT VARIABLESprint('Loading pattern images...')self.patternPaths = self.get_filepaths(self.DRUGPATTERNSPATH)print(self.patternPaths)self.patternImagesSetOriginal = []self.patternImagesSet = []for f in self.patternPaths:self.patternImagesSetOriginal.append(cv2.imread(f))print('Loading logo images...')self.logoPaths = self.get_filepaths(self.LOGOPATTERNSPATH)print(self.logoPaths)self.logoImagesSetOriginal = []self.logoImagesSet = []for f in self.logoPaths:self.logoImagesSetOriginal.append(cv2.imread(f))self.keyPointsLogoDetector = Noneself.keyPointsPatternDetector = Noneself.keyPointsMatcher = Noneif self.METHOD == Method.ORB:self.keyPointsLogoDetector = cv2.ORB_create(nfeatures=logoNfeatures)self.keyPointsPatternDetector = cv2.ORB_create(nfeatures=patternNfeatures)self.keyPointsMatcher = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)elif self.METHOD == Method.SIFT_BRUTE_FORCE:self.keyPointsLogoDetector = cv2.xfeatures2d.SIFT_create(nfeatures=logoNfeatures)self.keyPointsPatternDetector = cv2.xfeatures2d.SIFT_create(nfeatures=patternNfeatures)self.keyPointsMatcher = cv2.BFMatcher()elif self.METHOD == Method.SIFT_FLANN:self.keyPointsLogoDetector = cv2.xfeatures2d.SIFT_create(nfeatures=logoNfeatures)self.keyPointsPatternDetector = cv2.xfeatures2d.SIFT_create(nfeatures=patternNfeatures)FLANN_INDEX_KDTREE = 1index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)search_params = dict(checks=50)self.keyPointsMatcher = cv2.FlannBasedMatcher(index_params, search_params)self.resize_set()def __del__(self):cv2.destroyAllWindows()print('Finished!')# #################################################################################################################################################### Parametry konstruktora:
# wybrana metoda z enuma (ORB, SIFT BRUTE-FORCE, SIFT-FLANN), liczba punktów kluczowych dla logo, liczba punktów kluczowych dla wzorów leków, skala dla obrazów zbioru (nie ustawiać zbyt małej skali), skala dla analizowanego obrazu (nie ustawiać zbyt małej skali), odległość punktów (integer od 0 do 100)featureExtraction = FeatureExtraction(Method.SIFT_FLANN, 10000, 1000, 0.5, 0.22, 80) # logoNfeatures=10000,patternNfeatures=1000,logo是人工选出来的,所以整个图都可是有用特征,featureExtraction.changePatternScale(0.8) # 也就是上面的0.5#SOURCESPATH = 'Source_images'
SOURCESPATH = 'test_img'
print('Loading source images...')
sourcePaths = featureExtraction.get_filepaths(SOURCESPATH)
print(sourcePaths)sourceImagesSet = []for f in sourcePaths:sourceImagesSet.append(cv2.imread(f))
print('Analyze...')mainCounter = 0waitingMs = 0for sourceImage in sourceImagesSet:logoOutput = featureExtraction.compareWithLogo(mainCounter, True, True)#print(logoOutput[7])patternOutput = featureExtraction.compareWithPattern(mainCounter, True, True)# cv2.imshow('7_Original image_', patternOutput[6])# cv2.imshow('6_Keypoints - image_', patternOutput[5])cv2.imshow('1_Matches_', patternOutput[0])# cv2.imshow('4_Pattern image_', patternOutput[3])# cv2.imshow('5_Keypoints - pattern_', patternOutput[4])cv2.imshow('2_Homography_', patternOutput[1])cv2.imshow('Warped image - Logo', logoOutput[2])cv2.imshow('3_Warped image_', patternOutput[2])print(patternOutput[7])mainCounter = mainCounter + 1key = cv2.waitKey(waitingMs) & 0xFF;if key == ord('q'):breakelif key == ord('a'):if waitingMs is 0:waitingMs = 1else:waitingMs = 0del featureExtraction

FAST关键点检测

from __future__ import print_function
import numpy as np
import cv2
import imutils'''
原理:必须有至少Ñ沿着连续像素圆形周边具有半径- R是所有或者亮或更暗比中心像素由阈值t疑问:是否可以修改参数,半径-R和N值?运用:特征匹配
'''image = cv2.imread("img/4.jpg")
orig = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)if imutils.is_cv2():detector = cv2.FeatureDetector_create("FAST")kps = detector.detect(gray)else:detector = cv2.FastFeatureDetector_create()kps = detector.detect(gray, None)print("# of keypoints: {}".format(len(kps)))for kp in kps:r = int(0.5 * kp.size)(x, y) = np.int0(kp.pt)cv2.circle(image, (x, y), r, (0, 255, 255), 2)cv2.imshow("Images", np.hstack([orig, image]))
cv2.waitKey(0)

Zernike矩阵

运用Zernike矩阵量化图像中的形状。在图片中寻找某个特定的形状.

from scipy.spatial import distance as dist
import numpy as np
import mahotas
import cv2
import imutils'''
运用Zernike矩阵量化图像中的形状。在图片中寻找某个特定的形状.
'''def describe_shapes(image):shapeFeatures = []gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)blurred = cv2.GaussianBlur(gray, (13, 13), 0)#cv2.imshow("2", blurred)thresh = cv2.threshold(blurred, 120, 255, cv2.THRESH_BINARY)[1]thresh = cv2.dilate(thresh, None, iterations=4)thres = cv2.erode(thresh, None, iterations=2)#cv2.imshow("1", thres)#cv2.waitKey(0)cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)cnts = cnts[0] if imutils.is_cv2() else cnts[1]for c in cnts:mask = np.zeros(image.shape[:2], dtype="uint8")cv2.drawContours(mask, [c], -1, 255, -1)(x, y, w, h) = cv2.boundingRect(c)roi = mask[y:y + h, x:x + w]#cv2.imshow("roi", roi)#cv2.waitKey(0)features = mahotas.features.zernike_moments(roi, cv2.minEnclosingCircle(c)[1], degree=8)shapeFeatures.append(features)return (cnts, shapeFeatures)refImage = cv2.imread("/home/raini/pro/LogoDetector/SIFT/PRI_Roche/test/img/44.jpg")
(_, gameFeatures) = describe_shapes(refImage)
shapesImage = cv2.imread("/home/raini/pro/LogoDetector/SIFT/PRI_Roche/test/img/2.jpg")
(cnts, shapeFeatures) = describe_shapes(shapesImage)
D = dist.cdist(gameFeatures, shapeFeatures)
print(D)
i = np.argmin(D)  # 获取最小距离的下标for (j, c) in enumerate(cnts):if i != j:box = cv2.minAreaRect(c)box = np.int0(cv2.cv.BoxPoints(box) if imutils.is_cv2() else cv2.boxPoints(box))cv2.drawContours(shapesImage, [box], - 1, (0, 0, 255), 2)print(i)
print(len(cnts))
##计算轮廓旋转边界
box = cv2.minAreaRect(cnts[i])
box = np.int0(cv2.cv.BoxPoints(box) if imutils.is_cv2() else cv2.boxPoints(box))
cv2.drawContours(shapesImage, [box], - 1, (0, 255, 0), 2)
(x, y, w, h) = cv2.boundingRect(cnts[i])
cv2.putText(shapesImage, "FOUND!", (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 3)
cv2.imshow("Input Image", refImage)
cv2.imshow("Detected Shapes", shapesImage)
cv2.waitKey(0)

参考

自然图像中的logo识别和定位:Logo localization andrecognition in natural images using homographic class graphs

[1]Boia R, Florea C, Florea L, et al. Logo localizationand recognition in natural images using homographic class graphs[J]. MachineVision and Applications, 2016, 27(2):287-301.

[2] Revaud J, Douze M, Schmid C. Correlation-basedburstiness for logo retrieval[C]// ACM International Conference on Multimedia.ACM, 2012:965-968.

[3]Hartley R, Zisserman A. Multiple View Geometry inComputer Vision[J]. Kybernetes, 2003, 30(9/10):1865 - 1872.

[4]Boia R, Florea C. Homographic Class Template for LogoLocalization and Recognition[M]// Pattern Recognition and Image Analysis.Springer International Publishing, 2015:487-495.

[5]Florea L, Florea C, Vranceanu R, et al. Can Your EyesTell Me How You Think? A Gaze Directed Estimation of the Mental Activity[C]//British Machine Vision Conference. 2013:60.1-60.11.

[6]Brown M, Lowe D G. Automatic Panoramic ImageStitching using Invariant Features[J]. International Journal of ComputerVision, 2007, 74(1):59-73.

[7]Romberg S, Pueyo L G, Lienhart R, et al. Scalablelogo recognition in real-world images[C]// ACM International Conference onMultimedia Retrieval. ACM, 2011:25.

[8] Joly A, Buisson O. Logo retrieval with a contrariovisual query expansion[C]// International Conference on Multimedia 2009,Vancouver, British Columbia, Canada, October. DBLP, 2009:581-584.

[9] Everingham M, Gool L V, Williams C K I, et al. ThePascal Visual Object Classes (VOC) Challenge[J]. International Journal ofComputer Vision, 2010, 88(2):303-338.

使用Py-OpenCV(SIFT关键点)实现自然图像中的logo商标识别和定位相关推荐

  1. 使用(SIFT特征KMeans聚类关键点训练SVM)实现自然图像中的logo商标识别和定位

    (本博客只记录方法,因为本人觉得这是机器学习特征工程中一种比较不错的做法) 上一篇博客中的方法:使用Py-OpenCV(SIFT关键点)实现自然图像中的logo商标识别和定位 当然也能提前欲知该方法的 ...

  2. 使用Python,OpenCV,K-Means聚类查找图像中最主要的颜色

    Python,OpenCV,K-Means聚类查找图像中最主要的颜色 1. K-Means是什么? 2. 步骤 3. 效果图 4. 源代码 参考 对于肉眼来说,从一幅图中识别出主要颜色很容易.那怎么用 ...

  3. 使用Python,OpenCV和Hough圆检测图像中的圆

    使用Python,OpenCV和Hough圆检测图像中的圆 1. 效果图 2. cv2.HoughCircles(image, method, dp, minDist) 3. 源码 参考 前几篇博客中 ...

  4. OpenCV演示代码以查找图像中的轮廓(附完整代码)

    OpenCV演示代码以查找图像中的轮廓 OpenCV演示代码以查找图像中的轮廓 OpenCV演示代码以查找图像中的轮廓 #include "opencv2/imgcodecs.hpp&quo ...

  5. OCR系列:CTPN 利用连接文本提案网络来检测自然图像中的文本

    译者按:  用于OCR领域的经典网络,CTPN是目前流传最广.影响最大的开源文本检测模型,可以检测水平或微斜的文本行.文本行可以被看成一个字符sequence,而不是一般物体检测中单个独立的目标.同一 ...

  6. 平行坐标系下采用CHT方法检测自然图像中的消失点(VanishingPoint)

    --------------------20210826更新-------------------- code和paper链接:https://pan.baidu.com/s/13RyRu0rg7Fh ...

  7. 学习何凯明在图像中的降噪技术和识别率问题

    之前玩微信由于图像噪音干扰太高一直导致没法在工业是过渡,我是从从事交通大类的神经计算开发,这个转年几年过去我对算法其实很多还是不同,太零碎了.并且实际开发过程涉及对硬件的流处理器和存储单位的思考和分配 ...

  8. Python实现条码识别:从图像中自动检测和识别条形码

    Python实现条码识别:从图像中自动检测和识别条形码 在现代零售业中,条形码是一项必不可少的技术.它们提供了一种快速.准确和可靠的方法来追踪和管理库存.随着计算机视觉技术的发展,我们可以利用Pyth ...

  9. opencv 模板匹配,在图像中寻找物体

    使用模板匹配在图像中寻找物体 模板匹配 模板匹配就是用来在大图中找小图,也就是说在一副图像中寻找另外一张模板图像的位置: opencv中用 cv.matchTemplate() 实现模板匹配. 模板匹 ...

  10. python opencv 利用 GrabCut 算法(opencv已经实现)从图像中分离出前景

    # 利用GrabCut算法从图像中分离出前景 import numpy as np import cv2 as cv from matplotlib import pyplot as pltimg = ...

最新文章

  1. [architecture]-Armv8 Cryptographic Extension介绍
  2. 改变照片分辨率的软件_AI黑科技竟如此强大,模糊照片无损放大600%变得更清晰!...
  3. gitlab 删除分支_如何删除gitlab上默认受保护的master主分支
  4. 第二章简答_微观 第二章 效用论
  5. git 拉取 未能顺利结束 (退出码 1)_小白的 asyncio :原理、源码 到实现(1)
  6. Linux crontab 详细介绍及执行php
  7. GCC的内嵌汇编语法 ATT汇编语言语法
  8. 分布式事务一致性方案
  9. 哪里的http和socks5代理ip稳定速度快
  10. 离线OCR、文字识别、ios证件扫描、ios系统OCR(ios、android)
  11. Vue + Spring Boot 项目实战:人事管理系统——完结撒花
  12. 基于互联网的环境影响评价数据服务平台
  13. 科研绘图软件GraphPad Prism教程(三)
  14. Python技术练习------自动化处理费用表
  15. android 布局滑动消失,SlideUp-Android
  16. 华为交换机添加用户及用户等级
  17. 使用树莓派3B、RTL-SDR、OpenWebRX搭建无线电监测站
  18. javacv+远程视频直播+音视频录制+视频抓取
  19. SQL Sever数据库存储过程
  20. 安盎顺汲称叫蒲惺勤狙陡邮可王大胖,从小就和他这个堂弟要好,听了此事,当然不肯罢休,找上

热门文章

  1. 局域网打印机怎么连接_苹果手机怎么连打印机?苹果系统怎么连接网络打印机?一看就会...
  2. Python中的时间序列数据可视化的完整指南
  3. Springboot入门手册
  4. QT designer安装及运用
  5. VIM 插件管理工具——vim-plug
  6. 步进电机驱动A4988,步进电机驱动程序编写
  7. (CVPR-2021)具有深度通用线性嵌入的跨视角步态识别
  8. vue中使用leaflet加载地图影像并拾取坐标点
  9. android开发学习计划
  10. 计算机网络实验二 VLAN间路由