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

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.995
    maxIters:为初始迭代次数,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 = 1
    SIFT_BRUTE_FORCE = 2
    SIFT_FLANN = 3


class 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 = 200
        x = x - 500.0
        if x < 0:
            scale = 1 / ((1.005) ** abs(x))
        else:
            scale = (x + step) / step
        return scale

    def 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, imageWithKeypoints

    def resize_set(self):
        print('Resizing entire sets...')

        scale = self.PATTERNIMAGESCALE
        if 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 = 0
            goodMatchesNumber = 0
            inliersNumber = 0
            statistics = (imageDescriptorNumber, goodMatchesNumber, inliersNumber)
            dstPoints = None
            M = None
            good = None
            matchesMask = None
            w = None
            h = None

            if 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 = None
                    if 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.995
                        maxIters:为初始迭代次数,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 = 0
        bestResult = 0
        index = 0
        for result in totalResults:
            if result[0][2] >= bestResult:
                bestResult = result[0][2]
                index = counter
            counter = counter + 1
        return index

    def 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 outputImages

    def 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 = maskEnable

        imgKeyPoints, 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 outputImages

    def compareWithPattern(self, name, maskEnable, saveFlag):
        self.maskEnable = maskEnable

        imgKeyPoints, 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 outputImages

    def changePatternScale(self, scale):
        self.PATTERNIMAGESCALE = scale
        self.resize_set()

    def __init__(self, method, logoNfeatures, patternNfeatures, patternImageScale, sourceImagescale, distance):
        print('Preprocessing...')

        # IMPORTANT VARIABLES

        # 1 - ORB, 2 - SIFT BRUTE-FORCE, 3 - SIFT FLANN
        self.METHOD = method

        self.PATTERNIMAGESCALE = patternImageScale
        self.SOURCEIMAGESCALE = sourceImagescale
        self.DISTANCE = distance

        self.LOGOPATTERNSPATH = 'Logo_patterns'  #logo图案路径
        self.DRUGPATTERNSPATH = 'Drugs_patterns' #药物图案路径
        self.DESTINATIONPATH = 'RESULTS'  #目的地路径

        self.INITSHAPE = (480, 640, 3)
        self.INITDTYPE = np.uint8

        self.red = (20, 140, 255)
        self.blue = (220, 102, 20)

        # IMPORTANT VARIABLES

        print('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 = None
        self.keyPointsPatternDetector = None
        self.keyPointsMatcher = None

        if 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 = 1
            index_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 = 0

waitingMs = 0

for 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 + 1

    key = cv2.waitKey(waitingMs) & 0xFF;
    if key == ord('q'):
        break
    elif key == ord('a'):
        if waitingMs is 0:
            waitingMs = 1
        else:
            waitingMs = 0

del 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.
 

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页