Skip to content

Latest commit

 

History

History
110 lines (75 loc) · 5.95 KB

2.5.md

File metadata and controls

110 lines (75 loc) · 5.95 KB

2.5 特殊的特征:直线特征

对于直线这个特征,和普通的特征检测一样,分为三个部分:检测、描述、匹配。

直线检测

直线检测,相当于输入一副图片,输出是一个包含各个线段的数组。

OpenCV 直线检测分为三种方法,这三种方法的代码在 test_line.ipynb 中:

霍尔变换找直线

这个写在了另一篇文章中;返回的是一个 ndarray,大小是 (n, 1, 4),即各个线段的起始和终止点。

LSD 直线检测

来自文章 LSD:a Line Segment Detector,这个方法不需要调参,非常舒服。OpenCV 中 line_descritpor 模块的 LSDDetector 类来完成此方法。创建这个类的对象,然后使用 detect 即可完成。

虽然说 LSD 不需要调参,但其实也涉及到金字塔选几层等这个简单的参数,在创建对象时,不传参数就是使用默认参数,否则传入是一个 LSDParam 对象,其本质也就是包含几个参数的数组。detect 同理,如果不传参数就使用对象的参数。

最后得到包含了一堆 KeyLines 对象的数组,每个 KeyLines 就是线段,遍历即可。其实也很直观,getStartPointgetEndPoint 分别返回起始点。KeyLines 中 octave 表示在哪一层金字塔可以找到这条线段,所以遍历的时候通常会判断当前的 octave 是否为 0,以此确保这条线段在原图上可以找到。可以在 test_line.ipynb 查看具体用法。

EDLine 直线检测

方法来自 2011 年的文章 EDLines: A real-time line segment detector with a false detection control,和 LSD 一样,不需要调参。这个方法本质上是从 LSD 方法发展而来的,速度更快。

OpenCV 中 line_descritpor 模块的 BinaryDescritpor 类来完成此方法。创建这个类的对象,然后使用 detect 即可完成。

最后结果和 LSD 类似,也是包了一堆 KeyLines 对象的数组。KeyLines 用法如上面 LSD 检测所述。

Fast line Detector

ximgproc 的方法,传入参数建立对象,然后调用方法就行。代码在 test_line.ipynb 第二个 block 中。

原始论文:

Jin Han Lee, Sehyung Lee, Guoxuan Zhang, Jongwoo Lim, Wan Kyun Chung, and Il Hong Suh. Outdoor place recognition in urban environments using straight lines. In 2014 IEEE International Conference on Robotics and Automation (ICRA), pages 5550–5557. IEEE, 2014.

EdgeDrawing

ximgproc 的方法,不仅可以检测直线,也可以检测边缘。同样也是传入参数建立对象,然后调用方法。代码在 test_line.ipynb 第二个 block 中。

原始论文:

Cihan Topal and Cuneyt Akinlar. Edge drawing: a combined real-time edge and segment detector. Journal of Visual Communication and Image Representation, 23(6):862–872, 2012.

直线描述

直线描述是用到叫做 LBD 方法,这个就是 line_descriptor 介绍页中大段介绍的内容,具体原理这里不谈。

OpenCV 中 line_descritpor 模块的 BinaryDescritpor 类来完成此方法。前面提到这个类可以用 detect 进行 EDLine 直线检测;而用 compute 则可以进行这里所说的直线描述。

直线匹配

直线匹配和特征匹配类似,不过特征匹配是特征点,直线匹配是线段。OpenCV 中 line_descritpor 模块的 BinaryDescriptorMatcher 类来完成此方法。创建这个类的对象,然后使用 match 即可完成。和特征点匹配的 BruteForceMatcher 和 FlannBasedMatcher 一样,有三种 match 方法:matchknnMatchradiusMatch,用法和特征点匹配一样。

代码实现

具体能跑的代码在 test_line.ipynb 中,这里进行选择性摘抄:

'''
完整的 检测直线 -> 描述直线 -> 匹配直线 流程
'''

# 只保留 octave 为 0 的直线
def get_octave_0(kls, dss):
    nkls, ndss = [], []
    for i, kl in enumerate(kls):
        if kl.octave == 0:
            nkls.append(kls[i])
            ndss.append(dss[i])
    return tuple(nkls), np.array(ndss)

detector = cv2.line_descriptor.BinaryDescriptor.createBinaryDescriptor()

keylines1 = detector.detect(src)
keylines1, descriptors1 = detector.compute(src, keylines1)
keylines1, descriptors1 = get_octave_0(keylines1, descriptors1)

# 不想找其他图片了,就重复对一张图片进行匹配吧...
src2 = np.copy(src)
keylines2 = detector.detect(src2)
keylines2, descriptors2 = detector.compute(src2, keylines2)
keylines2, descriptors2 = get_octave_0(keylines2, descriptors2)

# 匹配,有三种方法,但很遗憾 radiusMatch 没有 python 版本..
# BinaryDescriptorMacher,有些方法没有 python 版本,如 add 等,这个类本身继承自 DescriptorMatcher,和之前点的匹配基本差不多
MATCHES_DIST_THRESHOLD = 25
matcher = cv2.line_descriptor.BinaryDescriptorMatcher()
matches = matcher.match(descriptors1, descriptors2)
# matches = matcher.knnMatch(descriptors1, descriptors2, k=2)
# matches = matcher.radiusMatch(descriptors1, descriptors2, maxDistance=25)
matches = [m for m in matches if m.distance < MATCHES_DIST_THRESHOLD]

# 返回的时 DMatch,自行查阅相关方法
print(type(matches)) 

# 画图
matchimg1 = cv2.line_descriptor.drawLineMatches(
                img1=src, keylines1=keylines1,
                img2=src2, keylines2=keylines2,
                matches1to2=matches,
                # opencv python 版本的 bug,必须要添加 matchesMask(which matches must be drawn),否则报 Unkown C++ error
                matchesMask=np.ones(len(matches), dtype=np.uint8)
            )

show_images([('match', matchimg1)])