pythonopencvimage-processing

Copy a part of an image in opencv and python


I'm trying to split an image into several sub-images with opencv by identifying templates of the original image and then copy the regions where I matched those templates. I'm a TOTAL newbie to opencv! I've identified the sub-images using:

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

After some cleanup I get a list of tuples called points in which I iterate to show the rectangles. tw and th is the template width and height respectively.

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

What I would like to accomplish is to save the octagons (https://dl.dropbox.com/u/239592/region01.png) into separated files.

How can I do this? I've read something about contours but I'm not sure how to use it. Ideally I would like to contour the octagon.

Thanks a lot for your help!


Solution

  • If template matching is working for you, stick to it. For instance, I considered the following template:

    enter image description here

    Then, we can pre-process the input in order to make it a binary one and discard small components. After this step, the template matching is performed. Then it is a matter of filtering the matches by means of discarding close ones (I've used a dummy method for that, so if there are too many matches you could see it taking some time). After we decide which points are far apart (and thus identify different hexagons), we can do minor adjusts to them in the following manner:

    Now you can sort this point list in an appropriate order such that the crops are done in raster order. The cropping part is easily achieved using slicing provided by 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)
    

    If you want only the contours of the hexagons, then crop on img_clean instead of img (but then it is pointless to sort the hexagons in raster order).

    Here is a representation of the different regions that would be cut for your two examples without modifying the code above:

    enter image description here enter image description here