使用 OpenCV 检测图像中已知形状/对象的方法

Rob*_*ert 11 c++ python opencv image-processing

我的任务是使用 OpenCV 检测给定图像中的对象(我不在乎它是 Python 还是 C++ 实现)。下面的三个示例中显示的对象是一个黑色矩形,其中包含五个白色矩形。所有维度都是已知的。

在此处输入图片说明

但是,图像的旋转、比例、距离、透视、照明条件、相机焦距/镜头和背景是未知的。黑色矩形的边缘不能保证完全可见,但是在五个白色矩形之前不会有任何东西 - 它们将始终完全可见。最终目标是能够检测图像中该对象的存在,并旋转、缩放和裁剪以显示移除透视的对象。鉴于它的四个角,我相当有信心我可以调整图像以仅裁剪对象。但是,我不太有信心能够可靠地找到这四个角。在模棱两可的情况下,与将图像的某些其他特征误识别为对象相比,没有找到对象更可取。

使用 OpenCV 我想出了以下方法,但是我觉得我可能会遗漏一些明显的东西。是否有更多方法可用,或者其中一种是最佳解决方案?

基于边缘的轮廓

第一个想法是寻找物体的外边缘。

使用 Canny 边缘检测(在缩放到已知大小、灰度和高斯模糊之后),找到与对象外部形状最匹配的轮廓。这可以解决透视、颜色、大小问题,但如果背景复杂,或者图像中其他地方有与对象形状相似的东西,则会失败。也许这可以通过一组更好的规则来改进以找到正确的轮廓 - 可能涉及五个白色矩形以及外边缘。

在此处输入图片说明 在此处输入图片说明

特征检测

下一个想法是使用特征检测匹配已知模板。

使用 ORB 特征检测、描述符匹配和单应性(来自本教程)失败,我相信是因为它检测的特征与对象内的其他特征非常相似(许多核心是四分之一白和四分之三黑) . 但是,我确实喜欢匹配已知模板的想法 - 这个想法对我来说很有意义。我想虽然因为对象在几何上非常基本,所以在特征匹配步骤中可能会发现很多误报。

在此处输入图片说明

平行线

使用 Houghlines 或 HoughLinesP,寻找间隔均匀的平行线。刚刚开始走这条路,所以需要研究阈值等的最佳方法。虽然对于具有复杂背景的图像来说看起来很混乱,但我认为它可能工作得很好,因为我可以依靠黑色对象中的白色矩形应该始终对比度高,可以很好地指示线条的位置。

在此处输入图片说明

'条码扫描'

我的最终想法是逐行扫描图像,寻找白色到黑色的图案。

我还没有开始使用这种方法,但我的想法是取一条图像(在某个角度),转换为 HSV 颜色空间,并在 Value 列中查找按顺序出现五次的常规黑白图案。这个想法对我来说听起来很有希望,因为我认为它应该忽略许多未知变量。

想法

我已经看过了许多OpenCV的教程,以及SO问题,如这一个,但是因为我的目标是相当简单的几何我有实施给出的思路问题。

我觉得这是一个可以实现的任务,但我的挣扎是不知道进一步追求哪种方法。我对前两个想法进行了相当多的试验,虽然我没有取得任何非常可靠的成果,但也许我遗漏了一些东西。是否有一种我没有想到的标准方法来完成这项任务,或者我建议的方法之一是最明智的?

编辑:一旦使用上述方法之一(或其他方法)找到角落,我正在考虑使用 Hu Moments 或 OpenCV 的 matchShapes() 函数来消除任何误报。

EDIT2:根据@Timo 的要求添加了更多输入图像示例


Tim*_*imo 5

我花了一些时间研究这个问题并制作了一个小 python 脚本。我正在检测您的形状内的白色矩形。将代码粘贴到 .py 文件中,并将所有输入图像复制到输入子文件夹中。图像的最终结果只是一个虚拟的自动取款机,脚本尚未完成。我会在接下来的几天里继续努力。该脚本将创建一个调试子文件夹,其中将保存一些显示当前检测状态的图像。

import numpy as np
import cv2
import os

INPUT_DIR = 'input'
DEBUG_DIR = 'debug'
OUTPUT_DIR = 'output'
IMG_TARGET_SIZE = 1000

# each algorithm must return a rotated rect and a confidence value [0..1]: (((x, y), (w, h), angle), confidence)

def main():
    # a list of all used algorithms
    algorithms = [rectangle_detection] 

    # load and prepare images
    files = list(os.listdir(INPUT_DIR))
    images = [cv2.imread(os.path.join(INPUT_DIR, f), cv2.IMREAD_GRAYSCALE) for f in files]
    images = [scale_image(img) for img in images]

    for img, filename in zip(images, files):
        results = [alg(img, filename) for alg in algorithms]
        roi, confidence = merge_results(results)

        display = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
        display = cv2.drawContours(display, [cv2.boxPoints(roi).astype('int32')], -1, (0, 230, 0))            
        cv2.imshow('img', display)
        cv2.waitKey()


def merge_results(results):
    '''Merges all results into a single result.'''
    return max(results, key=lambda x: x[1]) 

def scale_image(img):    
    '''Scales the image so that the biggest side is IMG_TARGET_SIZE.'''
    scale = IMG_TARGET_SIZE / np.max(img.shape)
    return cv2.resize(img, (0,0), fx=scale, fy=scale)     


def rectangle_detection(img, filename):    
    debug_img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
    _, binarized = cv2.threshold(img, 50, 255, cv2.THRESH_BINARY)    
    contours, _ = cv2.findContours(binarized, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)

    # detect all rectangles
    rois = []
    for contour in contours:
        if len(contour) < 4:
            continue
        cont_area = cv2.contourArea(contour)
        if not 1000 < cont_area < 15000: # roughly filter by the volume of the detected rectangles
            continue
        cont_perimeter = cv2.arcLength(contour, True)
        (x, y), (w, h), angle = rect = cv2.minAreaRect(contour)
        rect_area = w * h
        if cont_area / rect_area < 0.8: # check the 'rectangularity'
            continue        
        rois.append(rect)

    # save intermediate results in the debug folder
    rois_img = cv2.drawContours(debug_img, contours, -1, (0, 0, 230))
    rois_img = cv2.drawContours(rois_img, [cv2.boxPoints(rect).astype('int32') for rect in rois], -1, (0, 230, 0))
    save_dbg_img(rois_img, 'rectangle_detection', filename, 1)

    # todo: detect pattern

    return rois[0], 1.0 # dummy values


def save_dbg_img(img, folder, filename, index=0):
    '''Writes the given image to DEBUG_DIR/folder/filename_index.png.'''
    folder = os.path.join(DEBUG_DIR, folder)
    if not os.path.exists(folder):
        os.makedirs(folder)
    cv2.imwrite(os.path.join(folder, '{}_{:02}.png'.format(os.path.splitext(filename)[0], index)), img)


if __name__ == "__main__":
    main()
Run Code Online (Sandbox Code Playgroud)

这是当前 WIP 的示例图像

1

下一步是检测多个矩形之间的模式/关系。当我取得进展时,我会更新这个答案。