在OpenCV和Python中复制图像的一部分

0 投票
2 回答
6201 浏览
提问于 2025-04-17 16:04

我正在尝试使用opencv把一张图片分割成几个小图片,方法是先找到原始图片中的模板,然后复制那些匹配到的区域。老实说,我对opencv完全是个新手!我已经用以下代码找到了小图片:

result = cv2.matchTemplate(img, template, cv2.TM_CCORR_NORMED)

经过一些处理后,我得到了一个叫做points的元组列表,我会遍历这个列表来显示矩形框。tw和th分别是模板的宽度和高度。

for pt in points:
    re = cv2.rectangle(img, pt, (pt[0] + tw, pt[1] + th), 0, 2)
    print('%s, %s' % (str(pt[0]), str(pt[1])))
    count+=1

我想要实现的目标是把这些八边形(https://dl.dropbox.com/u/239592/region01.png)保存到单独的文件里。

我该怎么做呢?我读到了一些关于轮廓的内容,但不太确定怎么使用。理想情况下,我想给八边形画出轮廓。

非常感谢你的帮助!

2 个回答

1

抱歉,我不太明白你提问的内容,关于你是如何将模板匹配和轮廓联系起来的。

不过,下面有一个使用轮廓的小技巧。这个方法是基于你提供的那张图片假设其他图片也差不多。我不确定这个方法是否适用于你的其他图片,但我觉得这可以帮助你入门。你可以自己试试,并根据需要进行调整和修改。

我做了以下几步:

1 - 我需要找到八边形的边缘。所以我使用了Otsu方法对图像进行阈值处理,然后进行了膨胀和腐蚀(或者你可以用任何适合你所有图片的方法,注意图像左边缘的边缘)。

2 - 然后找到了轮廓(关于轮廓的更多信息:http://goo.gl/r0ID0

3 - 对于每个轮廓,找到它的凸包,计算它的面积(A)和周长(P)。

4 - 对于一个完美的八边形P*P/A 大约等于 13.25。我在这里用了这个公式,然后裁剪并保存了它。

5 - 你会发现裁剪时也会去掉一些八边形的边缘。如果你想保留这些边缘,可以调整裁剪的尺寸。

代码:

import cv2
import numpy as np

img = cv2.imread('region01.png')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
thresh = cv2.dilate(thresh,None,iterations = 2)
thresh = cv2.erode(thresh,None)

contours,hierarchy = cv2.findContours(thresh,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
number = 0

for cnt in contours:
    hull = cv2.convexHull(cnt)
    area = cv2.contourArea(hull)
    P = cv2.arcLength(hull,True)

    if ((area != 0) and (13<= P**2/area <= 14)):
        #cv2.drawContours(img,[hull],0,255,3)
        x,y,w,h = cv2.boundingRect(hull)
        number = number + 1
        roi = img[y:y+h,x:x+w]
        cv2.imshow(str(number),roi)
        cv2.imwrite("1"+str(number)+".jpg",roi)

cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()

这六个八边形将被保存为单独的文件。

希望这对你有帮助!!!

4

如果模板匹配对你有效,那就继续使用它。例如,我考虑了下面这个模板:

在这里输入图片描述

接下来,我们可以对输入进行预处理,把它变成二进制形式,并去掉一些小的部分。完成这个步骤后,就可以进行模板匹配了。接着,我们需要通过去掉相近的匹配项来过滤结果(我用了一种简单的方法来处理这个,所以如果匹配项太多,你可能会看到处理需要一些时间)。在我们确定哪些点之间距离较远(从而识别出不同的六边形)后,可以对它们进行一些小调整,具体方法如下:

  • 按y坐标排序;
  • 如果两个相邻的项目的y坐标太接近,就把它们的y坐标设为相同。

现在,你可以按照合适的顺序对这些点进行排序,这样裁剪就可以按照栅格顺序进行。裁剪部分可以很容易地通过numpy提供的切片功能来实现。

import sys
import cv2
import numpy

outbasename = 'hexagon_%02d.png'

img = cv2.imread(sys.argv[1])
template = cv2.cvtColor(cv2.imread(sys.argv[2]), cv2.COLOR_BGR2GRAY)
theight, twidth = template.shape[:2]

# Binarize the input based on the saturation and value.
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
saturation = hsv[:,:,1]
value = hsv[:,:,2]
value[saturation > 35] = 255
value = cv2.threshold(value, 0, 255, cv2.THRESH_OTSU)[1]
# Pad the image.
value = cv2.copyMakeBorder(255 - value, 3, 3, 3, 3, cv2.BORDER_CONSTANT, value=0)

# Discard small components.
img_clean = numpy.zeros(value.shape, dtype=numpy.uint8)
contours, _ = cv2.findContours(value, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
for i, c in enumerate(contours):
    area = cv2.contourArea(c)
    if area > 500:
        cv2.drawContours(img_clean, contours, i, 255, 2)


def closest_pt(a, pt):
    if not len(a):
        return (float('inf'), float('inf'))
    d = a - pt
    return a[numpy.argmin((d * d).sum(1))]

match = cv2.matchTemplate(img_clean, template, cv2.TM_CCORR_NORMED)

# Filter matches.
threshold = 0.8
dist_threshold = twidth / 1.5
loc = numpy.where(match > threshold)
ptlist = numpy.zeros((len(loc[0]), 2), dtype=int)
count = 0
print "%d matches" % len(loc[0])
for pt in zip(*loc[::-1]):
    cpt = closest_pt(ptlist[:count], pt)
    dist = ((cpt[0] - pt[0]) ** 2 + (cpt[1] - pt[1]) ** 2) ** 0.5
    if dist > dist_threshold:
        ptlist[count] = pt
        count += 1

# Adjust points (could do for the x coords too).
ptlist = ptlist[:count]
view = ptlist.ravel().view([('x', int), ('y', int)])
view.sort(order=['y', 'x'])
for i in xrange(1, ptlist.shape[0]):
    prev, curr = ptlist[i - 1], ptlist[i]
    if abs(curr[1] - prev[1]) < 5:
        y = min(curr[1], prev[1])
        curr[1], prev[1] = y, y

# Crop in raster order.
view.sort(order=['y', 'x'])
for i, pt in enumerate(ptlist, start=1):
    cv2.imwrite(outbasename % i,
            img[pt[1]-2:pt[1]+theight-2, pt[0]-2:pt[0]+twidth-2])
    print 'Wrote %s' % (outbasename % i)

如果你只想要六边形的轮廓,那就对img_clean进行裁剪,而不是img(但这样就没有必要按栅格顺序对六边形进行排序了)。

下面是你两个例子中不同区域的表示,这些区域会被裁剪,而不需要修改上面的代码:

在这里输入图片描述 在这里输入图片描述

撰写回答