SIFT匹配计算机视觉

4
我需要确定超市中酸奶的位置。源照片如下:enter image description here 使用以下模板: enter image description here 我使用SIFT提取模板的关键点。
img1 = cv.imread('train.jpg')
sift = cv.SIFT_create()# queryImage
kp1, des1 = sift.detectAndCompute(img1, None)
path = glob.glob("template.jpg")
cv_img = []
l=0

for img in path:
    img2 = cv.imread(img) # trainImage
    # Initiate SIFT detector

    # find the keypoints and descriptors with SIFT

    kp2, des2 = sift.detectAndCompute(img2,None)
    # FLANN parameters
    FLANN_INDEX_KDTREE = 1
    index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
    search_params = dict(checks=50)   # or pass empty dictionary
    flann = cv.FlannBasedMatcher(index_params,search_params)
    matches = flann.knnMatch(des1,des2,k=2)
    # Need to draw only good matches, so create a mask

    # ratio test as per Lowe's paper

    if (l < len(matches)):
        l = len(matches)
        image = img2
        match = matches

        
        
    h_query, w_query, _= img2.shape

    matchesMask = [[0,0] for i in range(len(match))]
    good_matches = []
    good_matches_indices = {}
    for i,(m,n) in enumerate(match):
        if m.distance < 0.7*n.distance:
            matchesMask[i]=[1,0]
            good_matches.append(m)
            good_matches_indices[len(good_matches) - 1] = i

    
    bboxes = []
    
    
    
    src_pts = np.float32([ kp1[m.queryIdx].pt for m in good ]).reshape(-1,2)
    dst_pts = np.float32([ kp2[m.trainIdx].pt for m in good ]).reshape(-1,2)

    model, inliers = initialize_ransac(src_pts, dst_pts)
    
    n_inliers = np.sum(inliers)
    matched_indices = [good_matches_indices[idx] for idx in inliers.nonzero()[0]]
    
    print(len(matched_indices))
    model, inliers = ransac(
        (src_pts, dst_pts),
        AffineTransform, min_samples=4,
        residual_threshold=4, max_trials=20000
    )

    n_inliers = np.sum(inliers)
    print(n_inliers)
    matched_indices = [good_matches_indices[idx] for idx in inliers.nonzero()[0]]
    print(matched_indices)
    

    q_coordinates = np.array([(0, 0), (h_query, w_query)])
    coords = model.inverse(q_coordinates)
    print(coords)
    
    h_query, w_query,_ = img2.shape
    q_coordinates = np.array([(0, 0), (h_query, w_query)])
    coords = model.inverse(q_coordinates)
    print(coords)
#     bboxes_list.append((i, coords))
                

    M, mask = cv.findHomography(src_pts, dst_pts, cv.RANSAC, 2)

    draw_params = dict(matchColor = (0,255,0),
                        singlePointColor = (255,0,0),
                        matchesMask = matchesMask,
                        flags = cv.DrawMatchesFlags_DEFAULT)

    img3 = cv.drawMatchesKnn(img1,kp1,image,kp2,match,None,**draw_params)

    plt.imshow(img3),plt.show()

SIFT的结果看起来像

图片描述输入在这里

问题是什么是将点聚类以获得代表每个酸奶的矩形的最佳方法?我尝试了RANSAC,但这种方法在这种情况下不起作用。


2
将“干草堆”中的每个特征与“针”中的特征匹配。这似乎是您所做的,所以很好。--尝试使用findHomography函数,删除与单应性匹配的内点,然后重复此过程。--也许可以将图像细分以帮助findHomography函数的匹配。 - Christoph Rackwitz
2
模板匹配也可能是一种选择。在Stackoverflow上有很多这样的问题... 同时也不清楚你是想搜索立方形酸奶、绿色酸奶、Activia酸奶还是带有奇异果口味的绿色酸奶。 我认为,考虑到你想要实现什么目标,你需要采用不同的策略。 - Grillteller
2
OpenCV的“模板匹配”除了平移之外,对缩放、剪切、旋转等都不具有不变性。特别是光照变化(取决于匹配模式),都会导致问题。其他软件包具有基于轮廓/边缘/部分模型的“模板匹配”,因此这在某种程度上更为先进。可能更容易的方法是使用通用对象检测DNN并将其训练到模板上。 - Christoph Rackwitz
@ChristophRackwitz 您在OpenCV的模板匹配方面是正确的,我同意您的方法。我只是想提一下还存在其他策略。可能,我也会使用R-CNN(并增强我的酸奶;)之类的东西,但入门门槛总是相当高的。很多事情也取决于图像的输入质量以及您能否在超市拍照的设置。 - Grillteller
@ChristophRackwitz 我尝试了你的解决方案,但它也不起作用。 - Ivan Anisimov
4个回答

2
我提出了一种基于 这篇 论文中讨论的方法。我稍微修改了这个方法,因为使用案例并不完全相同,但他们确实使用SIFT特征匹配来定位视频帧中的多个对象。他们使用PCA进行时间缩减,但对于静态图像可能不需要。

把模板图像沿水平方向分成左、中、右三个区域,沿垂直方向分成上、下两个区域。

现在,在模板图像和源图像之间匹配特征时,您将从这些区域的某些关键点匹配到源图像上的多个位置的特征。您可以使用这些关键点来确定源图像中哪个位置有哪个模板区域。如果存在重叠区域,即来自不同区域的关键点与源图像中的接近关键点匹配,则表示错误匹配。

将每组匹配关键点标记为源图像上邻域内的左、中、右、上、下,具体取决于它们是否具有来自模板图像的特定区域的大部分匹配项。

从源图像上的每个左侧区域开始向右移动,如果我们发现中心区域后面跟着一个右侧区域,那么标记为左侧和右侧之间的源图像区域可以被标记为一个模板对象的位置。

可能会有重叠的对象,这可能导致从左侧区域向右移动时出现另一个左侧区域。两个左侧区域之间的区域可以标记为一个模板对象。

对于进一步细化的位置,可以裁剪并重新匹配标记为一个模板对象的每个源图像区域和模板图像。


0

以下是您可以做的:

基础图像 = 货架的整体图片

模板图像 = 单个产品图像

  1. 从两个图像(基础图像和模板图像)获取SIFT匹配。
  2. 进行特征匹配。
  3. 获取所有与模板图像匹配的基础图像中的点。(参见图例)
  4. 根据模板图像的大小创建聚类。(这里阈值为50px)
  5. 获取聚类的边界框。
  6. 裁剪每个边界框聚类并检查与模板图像的匹配情况。
  7. 接受至少有最小百分比匹配的所有聚类。(这里取关键点的最小10%)
    def plot_pts(img, pts):
        img_plot = img.copy() 
        for i in range(len(pts)):
            img_plot = cv2.circle(img_plot, (int(pts[i][0]), int(pts[i][1])), radius=7, color=(255, 0, 0), thickness=-1)
    
        plt.figure(figsize=(20, 10))
        plt.imshow(img_plot)
        
    def plot_bbox(img, bbox_list):
        img_plot = img.copy() 
    
        
        for i in range(len(bbox_list)):
            start_pt = bbox_list[i][0]
            end_pt = bbox_list[i][2]
            img_plot = cv2.rectangle(img_plot, pt1=start_pt, pt2=end_pt, color=(255, 0, 0), thickness=2)
    
        plt.figure(figsize=(20, 10))
        plt.imshow(img_plot)
        
    def get_distance(pt1, pt2):
        x1, y1 = pt1
        x2, y2 = pt2    
        return np.sqrt(np.square(x1 - x2) + np.square(y1 - y2))
    
    def check_centroid(pt, centroid):
        x, y = pt
        cx, cy = centroid
        
        distance = get_distance(pt1=(x, y), pt2=(cx, cy))
        if distance < max_distance:
            return True
        else:
            return False
        
    def update_centroid(pt, centroids_list):
        new_centroids_list = centroids_list.copy()
        
        flag_new_centroid = True
        
        for j, c in enumerate(centroids_list):
            temp_centroid = np.mean(c, axis=0)
            if_close = check_centroid(pt, temp_centroid)
            if if_close:
                new_centroids_list[j].append(pt)
                flag_new_centroid = False
                break
            
        if flag_new_centroid:
            new_centroids_list.append([pt])
    
        new_centroids_list = recheck_centroid(new_centroids_list)
        return new_centroids_list
    
    
    def recheck_centroid(centroids_list):
        new_centroids_list = [list(set(c)) for c in centroids_list]
        return new_centroids_list
    
    
    def get_bbox(pts):
        minn_x, minn_y = np.min(pts, axis=0)
        maxx_x, maxx_y = np.max(pts, axis=0)
        
        return [[minn_x, minn_y], [maxx_x, minn_y], [maxx_x, maxx_y], [minn_x, maxx_y]]


    class RotateAndTransform:
        def __init__(self, path_img_ref):
            self.path_img_ref = path_img_ref        
            self.ref_img = self._read_ref_image()
            
            #sift
            self.sift = cv2.SIFT_create()
            
            #feature matching
            self.bf = cv2.BFMatcher()
            
            # FLANN parameters
            FLANN_INDEX_KDTREE = 1
            index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
            search_params = dict(checks=50)   # or pass empty dictionary
            self.flann = cv2.FlannBasedMatcher(index_params,search_params)
    
        def _read_ref_image(self):
            ref_img = cv2.imread(self.path_img_ref, cv2.IMREAD_COLOR)  
            ref_img = cv2.cvtColor(ref_img, cv2.COLOR_BGR2RGB)
            return ref_img
    
        def read_src_image(self, path_img_src):
            self.path_img_src = path_img_src
            
            # read images
            # ref_img = cv2.imread(self.path_img_ref, cv2.IMREAD_COLOR)  
            src_img = cv2.imread(path_img_src, cv2.IMREAD_COLOR)
            
            
            src_img = cv2.cvtColor(src_img, cv2.COLOR_BGR2RGB)
    
            return src_img
        
        def convert_bw(self, img):
            img_bw = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
            return img_bw
        
        def get_keypoints_descriptors(self, img_bw):
            keypoints, descriptors = self.sift.detectAndCompute(img_bw,None)
            return keypoints, descriptors
        
        def get_matches(self, src_descriptors, ref_descriptors, threshold=0.6):
            matches = self.bf.knnMatch(ref_descriptors, src_descriptors, k=2)
            flann_matches = self.flann.knnMatch(ref_descriptors, src_descriptors,k=2)
    
            good_matches = []
            good_flann_matches = []
    
            # Apply ratio test for Brute Force
            for m,n in matches:
                if m.distance <threshold*n.distance:
                    good_matches.append([m])
    
            print(f'Numner of BF Match: {len(matches)}, Number of good  BF Match: {len(good_matches)}')
    
            # Apply ratio test for FLANN
            for m,n in flann_matches:
                if m.distance < threshold*n.distance:
                    good_flann_matches.append([m])
    
            # matches = sorted(matches, key = lambda x:x.distance)
            print(f'Numner of FLANN Match: {len(flann_matches)}, Number of good Flann Match: {len(good_flann_matches)}')
            
            return good_matches, good_flann_matches
        
        
        def get_src_dst_pts(self, good_flann_matches, ref_keypoints, src_keypoints):
            pts_src = []
            pts_ref = []
            n = len(good_flann_matches)
    
            for i in range(n):
                ref_index = good_flann_matches[i][0].queryIdx
                src_index = good_flann_matches[i][0].trainIdx
    
                pts_src.append(src_keypoints[src_index].pt)
                pts_ref.append(ref_keypoints[ref_index].pt)
    
            return np.array(pts_src), np.array(pts_ref)
        
    def extend_bbox(bbox, increment=0.1):
        bbox_new = bbox.copy()
        bbox_new[0] = [bbox_new[0][0] - int(bbox_new[0][0] * increment), bbox_new[0][1] - int(bbox_new[0][1] * increment)]
        bbox_new[1] = [bbox_new[1][0] + int(bbox_new[1][0] * increment), bbox_new[1][1] - int(bbox_new[1][1] * increment)]
        bbox_new[2] = [bbox_new[2][0] + int(bbox_new[2][0] * increment), bbox_new[2][1] + int(bbox_new[2][1] * increment)]
        bbox_new[3] = [bbox_new[3][0] - int(bbox_new[3][0] * increment), bbox_new[3][1] + int(bbox_new[3][1] * increment)]
        return bbox_new
    
    def crop_bbox(img, bbox):
        y, x = bbox[0]
        h, w = bbox[1][0] - bbox[0][0], bbox[2][1] - bbox[0][1]
        return img[x: x + w, y: y + h, :]

    base_img = cv2.imread(path_img_base)
    ref_img = cv2.imread(path_img_ref)
    
    rnt = RotateAndTransform(path_img_ref)
    ref_img_bw = rnt.convert_bw(img=rnt.ref_img)
    ref_keypoints, ref_descriptors = rnt.get_keypoints_descriptors(ref_img_bw)
    
    base_img = rnt.read_src_image(path_img_src = path_img_base)
    base_img_bw = rnt.convert_bw(img=base_img)
    
    base_keypoints, base_descriptors = rnt.get_keypoints_descriptors(base_img_bw)
    good_matches, good_flann_matches = rnt.get_matches(src_descriptors=base_descriptors, ref_descriptors=ref_descriptors, threshold=0.6)
    
    
    ref_points = []
    
    for gm in good_flann_matches:
        x, y = ref_keypoints[gm[0].queryIdx].pt
        x, y = int(x), int(y)
        ref_points.append((x, y))

    max_distance = 50
    
    centroids = [[ref_points[0]]]
    
    for i in tqdm(range(len(ref_points))):
        pt = ref_points[i]
        centroids = update_centroid(pt, centroids)
        
    bbox = [get_bbox(c) for c in centroi[![enter image description here][1]][1]ds]
    centroids = [np.mean(c, axis=0) for c in centroids]
    print(f'Number of Points: {len(good_flann_matches)}, centroids: {len(centroids)}')

    data = []
    for i in range(len(bbox)):
        temp_crop_img = crop_bbox(ref_img, extend_bbox(bbox[i], 0.01))
        temp_crop_img_bw = rnt.convert_bw(img=temp_crop_img)
    
        temp_crop_keypoints, temp_crop_descriptors = rnt.get_keypoints_descriptors(temp_crop_img_bw)
    
        good_matches, good_flann_matches = rnt.get_matches(src_descriptors=base_descriptors, ref_descriptors=temp_crop_descriptors, threshold=0.6)
        
        temp_data = {'image': temp_crop_img,
                     'num_matched': len(good_flann_matches),
                     'total_keypoints' : len(base_keypoints),
                    }
        
        data.append(temp_data)


    filter_data = [{'num_matched' : i['num_matched'], 'image': i['image']} for i in data if i['num_matched'] > 25]
    
    for i in range(len(filter_data)):
        temp_num_match = filter_data[i]['num_matched']
        plt.figure()
        plt.title(f'num matched: {temp_num_match}')
        plt.imshow(filter_data[i]['image'])



Results of the method


0

尝试在空间上工作:对于img2中的每个关键点,获取一些边界框,并仅考虑其中的点以进行RANSAC单应性检查以寻找最佳匹配。

您还可以使用重叠窗口进行操作,然后丢弃类似的结果单应性。


-3

首先,您可以使用像this这样的网络检测货架上的任何物品,它在这个确切的上下文中经过预训练,并且表现很好。在将图像馈送到网络之前,还应该对图像进行矫正。您将获得每个产品的边界框(可能会有一些误报/漏报,但那是另一个问题)。然后,您可以使用SIFT将每个框与模板匹配并计算得分(由您定义哪个得分有效),但我建议使用另一种方法,如孪生网络,如果您有一致的数据集。


无法从https://github.com/eg4000/SKU110K_CVPR19加载模型。 - Ivan Anisimov
有什么问题吗?你能详细说明一下吗? - rok
我从https://drive.google.com/u/0/uc?id=1f9tRzJSqjuUQzXz8WjJC0V_WD-8y_6wy&export=download加载模型。 并使用以下代码: from keras.models import load_model model = load_model('model.h5') 在本地机器上运行它并出现错误: TypeError: ('Keyword argument not understood:', 'freeze') - Ivan Anisimov
请勿在评论中发布代码,请打开一个新问题,因为这是一个不同的问题。这里可能是解决方案 https://github.com/fizyr/keras-retinanet/issues/214 - rok

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接