簡體   English   中英

使用 opencv 找到包含另一個圖像的最相似的圖像

[英]Using opencv to find the most similar image that contains another image

如果標題不清楚,假設我有一個圖像列表 (10k+),並且我有一個正在搜索的目標圖像。

這是目標圖像的示例:

在此處輸入圖像描述

這是我想要搜索以找到“相似”的圖像示例(ex1、ex2 和 ex3):

在此處輸入圖像描述

在此處輸入圖像描述

在此處輸入圖像描述

這是我做的匹配(我使用 KAZE)

from matplotlib import pyplot as plt
import numpy as np
import cv2
from typing import List
import os
import imutils


def calculate_matches(des1: List[cv2.KeyPoint], des2: List[cv2.KeyPoint]):
    """
    does a matching algorithm to match if keypoints 1 and 2 are similar
    @param des1: a numpy array of floats that are the descriptors of the keypoints
    @param des2: a numpy array of floats that are the descriptors of the keypoints
    @return:
    """
    # bf matcher with default params
    bf = cv2.BFMatcher(cv2.NORM_L2)
    matches = bf.knnMatch(des1, des2, k=2)
    topResults = []
    for m, n in matches:
        if m.distance < 0.7 * n.distance:
            topResults.append([m])

    return topResults


def compare_images_kaze():
    cwd = os.getcwd()
    target = os.path.join(cwd, 'opencv_target', 'target.png')
    images_list = os.listdir('opencv_images')
    for image in images_list:
        # get my 2 images
        img2 = cv2.imread(target)
        img1 = cv2.imread(os.path.join(cwd, 'opencv_images', image))
        for i in range(0, 360, int(360 / 8)):
            # rotate my image by i
            img_target_rotation = imutils.rotate_bound(img2, i)

            # Initiate KAZE object with default values
            kaze = cv2.KAZE_create()
            kp1, des1 = kaze.detectAndCompute(img1, None)
            kp2, des2 = kaze.detectAndCompute(img2, None)
            matches = calculate_matches(des1, des2)

            try:
                score = 100 * (len(matches) / min(len(kp1), len(kp2)))
            except ZeroDivisionError:
                score = 0
            print(image, score)
            img3 = cv2.drawMatchesKnn(img1, kp1, img_target_rotation, kp2, matches,
                                      None, flags=2)
            img3 = cv2.cvtColor(img3, cv2.COLOR_BGR2RGB)
            plt.imshow(img3)
            plt.show()
            plt.clf()


if __name__ == '__main__':
    compare_images_kaze()

這是我的代碼的結果:

ex1.png 21.052631578947366
ex2.png 0.0
ex3.png 42.10526315789473

在此處輸入圖像描述 在此處輸入圖像描述 在此處輸入圖像描述

沒關系,它能夠判斷 ex1 相似,ex2 不相似。 但是它聲明 ex3 是相似的(甚至比 ex1 更相似),任何額外的預處理或后處理(可能是 ml?假設 ml 實際上有用)或者只是我可以對我的方法進行的更改,這些更改可以只保留ex1 相似,而不是 ex3?

(注意我創建的這個分數是我在網上找到的。不確定它是否是 go 關於它的准確方法)

在下面添加了更多示例

另一組例子:

這就是我正在尋找的

在此處輸入圖像描述

我希望上面的圖像與中間和底部的圖像相似(注意:我將目標圖像旋轉 45 度並將其與下面的圖像進行比較。)

特征匹配(如下面的答案所述)在發現與第二張圖像的相似性方面很有用,但對第三張圖像卻沒有(即使在正確旋轉之后)

在此處輸入圖像描述

在此處輸入圖像描述

在此處輸入圖像描述

檢測最相似的圖像

編碼

您可以使用模板匹配,您想要檢測它是否在其他圖像中的圖像就是模板。 我將那個小圖像保存在template.png中,其他三個圖像保存在img1.pngimg2.pngimg3.png中。

我定義了一個 function,它利用cv2.matchTemplate計算模板是否在圖像中的置信度。 在每張圖像上使用 function,得到最高置信度的是包含模板的圖像:

import cv2

template = cv2.imread("template.png", 0)
files = ["img1.png", "img2.png", "img3.png"]

for name in files:
    img = cv2.imread(name, 0)
    print(f"Confidence for {name}:")
    print(cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED).max())

Output:

Confidence for img1.png:
0.8906427
Confidence for img2.png:
0.4427919
Confidence for img3.png:
0.5933967

說明:

  1. 導入 opencv 模塊,通過將cv2.imread方法的第二個參數設置為0 ,將模板圖像讀取為灰度:
import cv2

template = cv2.imread("template.png", 0)
  1. 定義要確定哪個圖像包含模板的圖像列表:
files = ["img1.png", "img2.png", "img3.png"]
  1. 遍歷文件名並將每個文件名作為灰度圖像讀取:
for name in files:
    img = cv2.imread(name, 0)
  1. 最后,您可以使用cv2.matchTemplate來檢測每個圖像中的模板。 您可以使用許多檢測方法,但為此我決定使用cv2.TM_CCOEFF_NORMED方法:
    print(f"Confidence for {name}:")
    print(cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED).max())

function 的 output 的取值范圍在01之間,如您所見,它成功檢測到第一個圖像最有可能包含模板圖像(它具有最高的置信度)


可視化

編碼

如果檢測哪個圖像包含模板還不夠,並且您想要可視化,您可以嘗試以下代碼:

import cv2
import numpy as np

def confidence(img, template):
    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    template = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
    res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED)
    conf = res.max()
    return np.where(res == conf), conf

files = ["img1.png", "img2.png", "img3.png"]

template = cv2.imread("template.png")
h, w, _ = template.shape

for name in files:
    img = cv2.imread(name)
    ([y], [x]), conf = confidence(img, template)
    cv2.rectangle(img, (x, y), (x + w, y + h), (0, 0, 255), 2)
    text = f'Confidence: {round(float(conf), 2)}'
    cv2.putText(img, text, (x, y), 1, cv2.FONT_HERSHEY_PLAIN, (0, 0, 0), 2)
    cv2.imshow(name, img)
    
cv2.imshow('Template', template)
cv2.waitKey(0)

Output:

在此處輸入圖像描述

說明:

  1. 導入必要的庫:
import cv2
import numpy as np
  1. 定義一個 function 將接收完整圖像和模板圖像。 由於cv2.matchTemplate方法需要灰度圖像,因此將 2 個圖像轉換為灰度:
def confidence(img, template):
    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    template = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
  1. 使用cv2.matchTemplate方法檢測圖像中的模板,返回置信度最高的點的position,返回置信度最高的點:
    res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED)
    conf = res.max()
    return np.where(res == conf), conf
  1. 定義您要確定哪個包含模板的圖像列表,並讀入模板圖像:
files = ["img1.png", "img2.png", "img3.png"]
template = cv2.imread("template.png")
  1. 獲取模板圖像的大小以供以后在圖像上繪制矩形:
h, w, _ = template.shape
  1. 循環遍歷文件名並讀取每個圖像。 使用我們之前定義的confidence function,得到檢測到的模板左上角的xy position和檢測的置信度:
for name in files:
    img = cv2.imread(name)
    ([y], [x]), conf = confidence(img, template)
  1. 在圖像的角上畫一個矩形,然后將文本放在圖像上。 最后,顯示圖像:
    cv2.rectangle(img, (x, y), (x + w, y + h), (0, 0, 255), 2)
    text = f'Confidence: {round(float(conf), 2)}'
    cv2.putText(img, text, (x, y), 1, cv2.FONT_HERSHEY_PLAIN, (0, 0, 0), 2)
    cv2.imshow(name, img)
  1. 另外,顯示模板進行比較:
cv2.imshow('Template', template)
cv2.waitKey(0)

我不確定,如果給定的圖像類似於您的實際任務或數據,但是對於這種圖像,您可以嘗試簡單的模板匹配,參見。 這個 OpenCV 教程

基本上,我只是通過一些修改實現了本教程:

import cv2
import matplotlib.pyplot as plt

# Read images
examples = [cv2.imread(img) for img in ['ex1.png', 'ex2.png', 'ex3.png']]
target = cv2.imread('target.png')
h, w = target.shape[:2]

# Iterate examples
for i, img in enumerate(examples):

    # Template matching
    # cf. https://docs.opencv.org/4.5.2/d4/dc6/tutorial_py_template_matching.html
    res = cv2.matchTemplate(img, target, cv2.TM_CCOEFF_NORMED)

    # Get location of maximum
    _, max_val, _, top_left = cv2.minMaxLoc(res)

    # Set up threshold for decision target found or not
    thr = 0.7
    if max_val > thr:

        # Show found target in example
        bottom_right = (top_left[0] + w, top_left[1] + h)
        cv2.rectangle(img, top_left, bottom_right, (0, 255, 0), 2)

    # Visualization
    plt.figure(i, figsize=(10, 5))
    plt.subplot(1, 2, 1), plt.imshow(img[..., [2, 1, 0]]), plt.title('Example')
    plt.subplot(1, 2, 2), plt.imshow(res, vmin=0, vmax=1, cmap='gray')
    plt.title('Matching result'), plt.colorbar(), plt.tight_layout()

plt.show()

這些是結果:

示例 1

示例 2

示例 3

----------------------------------------
System information
----------------------------------------
Platform:      Windows-10-10.0.16299-SP0
Python:        3.9.1
PyCharm:       2021.1.1
Matplotlib:    3.4.1
OpenCV:        4.5.1
----------------------------------------

編輯:為了強調來自不同 colors 的信息,可以使用HSV 顏色空間中的色調通道進行模板匹配:

import cv2
import matplotlib.pyplot as plt

# Read images
examples = [
    [cv2.imread(img) for img in ['ex1.png', 'ex2.png', 'ex3.png']],
    [cv2.imread(img) for img in ['ex12.png', 'ex22.png', 'ex32.png']]
]
targets = [
    cv2.imread('target.png'),
    cv2.imread('target2.png')
]

# Iterate examples and targets
for i, (ex, target) in enumerate(zip(examples, targets)):
    for j, img in enumerate(ex):

        # Rotate last image from second data set
        if (i == 1) and (j == 2):
            img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)

        h, w = target.shape[:2]

        # Get hue channel from HSV color space
        target_h = cv2.cvtColor(target, cv2.COLOR_BGR2HSV)[..., 0]
        img_h = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)[..., 0]

        # Template matching
        # cf. https://docs.opencv.org/4.5.2/d4/dc6/tutorial_py_template_matching.html
        res = cv2.matchTemplate(img_h, target_h, cv2.TM_CCOEFF_NORMED)

        # Get location of maximum
        _, max_val, _, top_left = cv2.minMaxLoc(res)

        # Set up threshold for decision target found or not
        thr = 0.6
        if max_val > thr:

            # Show found target in example
            bottom_right = (top_left[0] + w, top_left[1] + h)
            cv2.rectangle(img, top_left, bottom_right, (0, 255, 0), 2)

        # Visualization
        plt.figure(i * 10 + j, figsize=(10, 5))
        plt.subplot(1, 2, 1), plt.imshow(img[..., [2, 1, 0]]), plt.title('Example')
        plt.subplot(1, 2, 2), plt.imshow(res, vmin=0, vmax=1, cmap='gray')
        plt.title('Matching result'), plt.colorbar(), plt.tight_layout()
        plt.savefig('{}.png'.format(i * 10 + j))

plt.show()

新結果:

在此處輸入圖像描述

在此處輸入圖像描述

在此處輸入圖像描述

在此處輸入圖像描述

在此處輸入圖像描述

在此處輸入圖像描述

這個概念

我們可以使用cv2.matchTemplate方法來檢測圖像在另一個圖像中的位置,但是對於您的第二組圖像,您需要旋轉。 此外,我們還需要考慮 colors。

cv2.matchTemplate將接受一個圖像、一個模板(另一個圖像)和一個模板檢測方法,並將返回一個灰度數組,其中灰度數組中最亮的點將是模板在該點上最有信心的點.

我們可以在 4 個不同的角度使用模板,並使用導致最高置信度的那個。 當我們檢測到與模板匹配的可能點時,我們使用 function (我們將自己定義)檢查模板中最常見的 colors 是否存在於我們檢測到的圖像的補丁中 如果不是,則忽略補丁,無論返回的置信度如何。

編碼

import cv2
import numpy as np

def frequent_colors(img, vals=3):
    colors, count = np.unique(np.vstack(img), return_counts=True, axis=0)
    sorted_by_freq = colors[np.argsort(count)]
    return sorted_by_freq[-vals:]

def get_templates(img):
    template = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    for i in range(3):
        yield cv2.rotate(template, i)
        
def detect(img, template, min_conf=0.45):
    colors = frequent_colors(template)
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    conf_max = min_conf
    shape = 0, 0, 0, 0
    for tmp in get_templates(template):
        h, w = tmp.shape
        res = cv2.matchTemplate(img_gray, tmp, cv2.TM_CCOEFF_NORMED)
        for y, x in zip(*np.where(res > conf_max)):
            conf = res[y, x]
            if conf > conf_max:
                seg = img[y:y + h, x:x + w]
                if all(np.any(np.all(seg == color, -1)) for color in colors):
                    conf_max = conf
                    shape = x, y, w, h
    return shape

files = ["img1_2.png", "img2_2.png", "img3_2.png"]
template = cv2.imread("template2.png")

for name in files:
    img = cv2.imread(name)
    x, y, w, h = detect(img, template)
    cv2.rectangle(img, (x, y), (x + w, y + h), (0, 0, 255), 2)
    cv2.imshow(name, img)

cv2.imshow('Template', template)
cv2.waitKey(0)

Output

在此處輸入圖像描述

說明

  1. 導入必要的庫:
import cv2
import numpy as np
  1. 定義一個 function, frequent_colors ,它將接收圖像並返回圖像中最頻繁的 colors。 可選參數val是返回多少 colors; 如果val3 ,則返回最頻繁的 3 個 colors:
def frequent_colors(img, vals=3):
    colors, count = np.unique(np.vstack(img), return_counts=True, axis=0)
    sorted_by_freq = colors[np.argsort(count)]
    return sorted_by_freq[-vals:]
  1. 定義一個 function, get_templates ,它將接收圖像,並以 4 個不同的角度生成圖像(灰度) - 原始、順時針 90、180 和逆時針 90:
def get_templates(img):
    template = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    for i in range(3):
        yield cv2.rotate(template, i)
  1. 定義一個 function, detect ,它將接收一個圖像和一個模板圖像,並返回圖像上檢測到的模板的邊界框的 x、y、w、h,對於這個 function,我們將使用frequent_colors和前面定義的get_templates函數。 min_conf參數將是將檢測分類為實際檢測所需的最小置信度:
def detect(img, template, min_conf=0.45):
  1. 檢測模板中出現頻率最高的三個 colors 並將它們存儲在變量colors中。 此外,定義主圖像的灰度版本:
    colors = frequent_colors(template)
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  1. 定義檢測到的最大置信度的初始值,以及檢測到的補丁的初始值:
    conf_max = min_conf
    shape = 0, 0, 0, 0
  1. 循環4個角度的灰度模板,得到灰度模板的形狀(隨着旋轉改變形狀) ,並使用cv2.matchTemplate方法得到圖像上檢測到的模板的灰度數組:
    for tmp in get_templates(template):
        h, w = tmp.shape
        res = cv2.matchTemplate(img_gray, tmp, cv2.TM_CCOEFF_NORMED)
  1. 循環遍歷置信度大於conf_min的檢測到的模板的 x、y 坐標,並將置信度存儲在變量conf中。 如果conf大於初始最大置信度變量conf_max ,則繼續檢測模板中所有三個最常見的 colors 是否存在於圖像的補丁中:
        for y, x in zip(*np.where(res > conf_max)):
            conf = res[y, x]
            if conf > conf_max:
                seg = img[y:y + h, x:x + w]
                if all(np.any(np.all(seg == color, -1)) for color in colors):
                    conf_max = conf
                    shape = x, y, w, h
  1. 最后我們可以返回形狀。 如果在圖像中未檢測到模板,則形狀將是為其定義的初始值0, 0, 0, 0
    return shape
  1. 最后,遍歷每個圖像並使用我們定義的detect function 來獲取邊界框的 x、y、w、h。 使用cv2.rectangle方法在圖像上繪制邊界框:
files = ["img1_2.png", "img2_2.png", "img3_2.png"]
template = cv2.imread("template2.png")

for name in files:
    img = cv2.imread(name)
    x, y, w, h = detect(img, template)
    cv2.rectangle(img, (x, y), (x + w, y + h), (0, 0, 255), 2)
    cv2.imshow(name, img)

cv2.imshow('Template', template)
cv2.waitKey(0)

首先,數據出現在圖表中,你不能從他們的數值數據中得到重疊的值嗎?

您是否嘗試過對從白藍到藍紅的顏色變化進行邊緣檢測,將一些圓圈擬合到這些邊緣,然后檢查它們是否重疊?

由於輸入數據非常受控(沒有有機照片或視頻),也許您不必 go ML 路線。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM