2

I was reading the docs about template matching with opencv and python and in the last part about template matching with multiple objects, the code detect the 19 coins on the mario image but, is it possible to count the number of objects detected with some function on python like len() or any opencv method?

Here is the code showed on the tutorial: http://docs.opencv.org/3.1.0/d4/dc6/tutorial_py_template_matching.html

Template Matching Code:

import cv2
import numpy as np
from matplotlib import pyplot as plt

img_rgb = cv2.imread('mario.png')
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
template = cv2.imread('mario_coin.png',0)
w, h = template.shape[::-1]

res = cv2.matchTemplate(img_gray,template,cv2.TM_CCOEFF_NORMED)
threshold = 0.8
loc = np.where( res >= threshold)
for pt in zip(*loc[::-1]):
    cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,0,255), 2)

cv2.imwrite('res.png',img_rgb)

And the result is: Mario Bros & Coins

So, is there any way to count the coins detected on the image and print the number on the terminal? Something like:

The Template Matching code showed before...

print "Function that detect number of coins with template matching"
>>> 19
  • 1
    Show us what you have tried so far. – DimKoim Nov 27 '16 at 00:54
  • See this similar [question](http://stackoverflow.com/q/33990259/1628638). The answer there does not provide a proper solution to the "65 vs. 19" issue in the question, however. A reasonably reliable solution would be to not count matches that are very close to a previous match. (If you provide your input images, it will be easier for people to code this for you.) – Ulrich Stern Nov 30 '16 at 07:00

6 Answers6

4

I found a suitable solution (for my application) in counting unique matches as Ulrich suggested. It's not ideal, but playing with the "sensitivity" normally yields results within +/- 2% for my application.

import cv2
import numpy as np
from matplotlib import pyplot as plt

img_rgb = cv2.imread('mario.png')
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
template = cv2.imread('mario_coin.png',0)
w, h = template.shape[::-1]

res = cv2.matchTemplate(img_gray,template,cv2.TM_CCOEFF_NORMED)
threshold = 0.8
loc = np.where( res >= threshold)

f = set()

for pt in zip(*loc[::-1]):
    cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,0,255), 2)

    sensitivity = 100
    f.add((round(pt[0]/sensitivity), round(pt[1]/sensitivity)))

cv2.imwrite('res.png',img_rgb)

found_count = len(f)
Nemontemi
  • 51
  • 7
2

i made a list of all the matches and for every new match i check if there's an intersection with the bounding box any of the matches in the list:

res = cv.matchTemplate(image,template,cv.TM_CCOEFF_NORMED)
threshold = 0.5
loc = np.where(res >= threshold)
matches = []
for pt in zip(*loc[::-1]):
    intersection = 0
    for match in matches:
        if intersected(match, (match[0] + w, match[1] + h), pt, (pt[0] + w, pt[1] + h)):
            intersection = 1
            break
    if intersection == 0:
        matches.append(pt)
        rect = cv.rectangle(image, pt, (pt[0] + w, pt[1] + h), (0, 0, 255), 2)

this is the code to check for an intersection:

def intersected(bottom_left1, top_right1, bottom_left2, top_right2):
    if top_right1[0] < bottom_left2[0] or bottom_left1[0] > top_right2[0]:
        return 0

    if top_right1[1] < bottom_left2[1] or bottom_left1[1] > top_right2[1]:
        return 0

    return 1
Eyad
  • 21
  • 1
0

I used a list to store the very first (x,y) of many same object detections. Then for every (x,y) in the found detections(there must be many detections on the one same object), I calculate the distance between the new (x,y) and every points in the list. If the distance is large enough, it must be the first found of a new detection. Then I put the new (x,y)to the list. It is stupid but really works.

The purpose is to remove the points nearby the (x,y) of the first detection of an object and keep only one point of that 'group', then iterate all the points in loc to locate more 'groups' and find one and the only one point in every group.

import cv2
import numpy as np
import matplotlib.pyplot as plt
import math

def notInList(newObject):
    for detectedObject in detectedObjects:
        if math.hypot(newObject[0]-detectedObject[0],newObject[1]-detectedObject[1]) < thresholdDist:
        return False
    return True

img_rgb = cv2.imread("7.jpg")
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
template = cv2.imread("face.jpg",0)
w, h = template.shape[::-1]
res = cv2.matchTemplate(img_gray,template,cv2.TM_CCOEFF_NORMED)
threshold = 0.85
loc = np.where( res >= threshold)

detectedObjects=[]
thresholdDist=30

for pt in zip(*loc[::-1]):
    if len(detectedObjects) == 0 or notInList(pt):
        detectedObjects.append(pt)
        cellImage=img_rgb[pt[1]:pt[1]+h, pt[0]:pt[0]+w]
        cv2.imwrite("results/"+str(pt[1])+"_"+str(pt[0])+".jpg",cellImage, 
        [int(cv2.IMWRITE_JPEG_QUALITY), 50])    
joshhu
  • 23
  • 8
0

To anyone still wondering: It's way easier to sort the list "zip(*loc[::-1})" first.

For i.e my script returned found results like this:

(580, 822)
(871, 822)
(1017, 822)
(434, 823)
(726, 823)
(871, 823)
(1017, 823)
7

You'll notice that there's multiple duplication but not just in order. Just simply sorting this with "sorted(zip(*loc[::-1]))" will now make getting distance a piece of cake, by simply calculating adjacent 2 point and check distance on every loop.

By adding condition in loop and check if current point has smaller distance than desired one will get job done nicely. I never learned python properly so I'm not sure this is efficient way or not.. At least this worked for my use case. You can check it out below.

Source code (Github) / Test Result (Imgur)


Example Code:

    img = np.array( *YOUR_SCREENSHOT_HERE* )
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    template = cv2.imread( *TARGET_IMAGE_HERE* , 0)
    w, h = template.shape[::-1]

    res = cv2.matchTemplate(img_gray, template, cv2.TM_CCOEFF_NORMED)
    loc = np.where(res >= precision)

    count = 0
    last_pt = [0, 0]   # Set this negative if target image is at top-left corner.

    for pt in sorted(zip(*loc[::-1])):
        if sqrt(abs(last_pt[0]-pt[0])**2 + abs(last_pt[0]-pt[0])**2) < threshold*min([h, w]):
            continue
        else:
            last_pt = pt
            print(pt)
            count = count + 1
            cv2.rectangle(img, pt, (pt[0] + w, pt[1] + h), (0, 0, 255), 2)

    cv2.imwrite('res.png', img)
    return count
jupiterbjy
  • 2,882
  • 1
  • 10
  • 28
-1

That is how I did it:

loc = np.where( res >= threshhold)
print(len(loc[0])) #match occurences count

loc is 2D array.

Jon
  • 4,593
  • 3
  • 13
  • 33
Greg
  • 1
-2

import time import cv2 import numpy as np from PIL import ImageGrab

while True:

count = 0
stop = 0

img = ImageGrab.grab()
img_np = np.array(img)

gray = cv2.cvtColor(img_np, cv2.COLOR_BGR2GRAY)

frame = cv2.cvtColor(img_np, cv2.COLOR_BGR2RGB)

Template = cv2.imread('image.png' ,0)
w, h = Template.shape[::-1]

res = cv2.matchTemplate(gray, Template, cv2.TM_CCOEFF_NORMED)
threshold = 0.90
loc = np.where(res >= threshold)

font = cv2.FONT_HERSHEY_SIMPLEX

for pt in zip(*loc[::-1]):

    cv2.rectangle(frame, pt, (pt[0] + w, pt[1] + h), (0,0,255) ,2)

    count = count + 1

    print(count)

    stop = 1

cv2.imshow('frame',frame)

if (stop == 1):

    break