Drawing bbox for realtime object detection without ML

Question:

I am attempting to do real-time object detection without any ML. The approach is to identify the object per its color profile. I am trying to identify a rectangular object with a single colour and draw a bounding box. This is the code:

import cv2
import numpy as np

class ColourBounds:
    def __init__(self, rgb):
        hsv = cv2.cvtColor(np.uint8([[[rgb[2], rgb[1], rgb[0]]]]), cv2.COLOR_BGR2HSV).flatten()

        lower = [hsv[0] - 10]
        upper = [hsv[0] + 10]

        if lower[0] < 0:
            lower.append(179 + lower[0]) # + negative = - abs
            upper.append(179)
            lower[0] = 0
        elif upper[0] > 179:
            lower.append(0)
            upper.append(upper[0] - 179)
            upper[0] = 179

        self.lower = [np.array([h, 100, 100]) for h in lower]
        self.upper = [np.array([h, 255, 255]) for h in upper]

def contains_vertical(r1, r2):
    x1, y1, w1, h1 = r1
    x2, y2, w2, h2 = r2

    return x1 <= x2 < x1 + w1 and x1 <= x2 + w2 < x1 + w1

def drawLabel(w, h, x, y, text, frame):
    cv2.rectangle(frame,(x,y),(x+w,y+h),(120,0,0),2)
    cv2.putText(frame, text, (x,y), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,255,255), 2)

colourMap = {"Antigen Device": ColourBounds((237,237,237))}

while(True):

    frame = cv2.imread('antigen.png')

    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

    rects = {}

    for name, colour in colourMap.items():
        mask = cv2.inRange(hsv, colour.lower[0], colour.upper[0])

        if len(colour.lower) == 2:
            mask = mask | cv2.inRange(hsv, colour.lower[1], colour.upper[1])

        conts, heirarchy = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

        if (len(conts) == 0):
            continue

        biggest = sorted(conts, key=cv2.contourArea, reverse=True)[0]
        rect = cv2.boundingRect(biggest)
        x, y, w, h = rect

        if w < 50 or h < 50:
            continue

        if name == "Antigen Device":
            if any([contains_vertical(rects[n], rect) for n in rects]):
                continue

        rects[name] = rect
        drawLabel(w, h, x, y, name, frame)

    cv2.imshow('image',frame)
    k = cv2.waitKey(0) & 0xFF
    if k == 27:
        break

cv2.destroyAllWindows()
cv2.waitKey(1)

However, I do not see a bounding box or label. Below is my image. No changes are applied:
enter image description here

enter image description here

Asked By: oo92

||

Answers:

You are trying to do "color detection" and then draw a bounding box around it.

The easiest way:

Step 1: Implement color detection.

I implemented the following code to find the right values for the mask for your image. Play with the trackbars and when you are satisfied with the results, press q and the values for the mask ​​will be printed for you.

Note: it’s ok that you find more small contours besides your goal.

I found this values working great:

h_min, h_max, s_min, s_max, v_min, v_max: 0 179 0 15 223 255

import cv2
import numpy as np

def empty():
   pass


while True:

   img = cv2.imread(Path to your image)
   imgHSV = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
   h_min = cv2.getTrackbarPos("Hue Min", "TrackBars")
   h_max = cv2.getTrackbarPos("Hue Max", "TrackBars")
   s_min = cv2.getTrackbarPos("Sat Min", "TrackBars")
   s_max = cv2.getTrackbarPos("Sat Max", "TrackBars")
   v_min = cv2.getTrackbarPos("Val Min", "TrackBars")
   v_max = cv2.getTrackbarPos("Val Max", "TrackBars")
   lower = np.array([h_min, s_min, v_min])
   upper = np.array([h_max, s_max, v_max])
   mask = cv2.inRange(imgHSV, lower, upper)
   imgResult = cv2.bitwise_and(img, img, mask=mask)

   # for OpenCV 4
   contours, _ = cv2.findContours(mask, cv2.RETR_TREE, 
   cv2.CHAIN_APPROX_NONE) 

    # ---For OpenCV 3---
    # _, contours, _ = cv2.findContours(mask, cv2.RETR_TREE, 
    # cv2.CHAIN_APPROX_NONE) 
    # ---For OpenCV 3---

   for contour in contours:
         cv2.drawContours(img, contour, -1, (0, 255, 0), 3)

   cv2.imshow("Original", img)
   cv2.imshow("Result", imgResult)

   if cv2.waitKey(27) & 0xFF == ord('q'):
       print(h_min, h_max, s_min, s_max, v_min, v_max)
       break

Step 2: Draw the bounding box only around your goal. (2 drawing Options)

The code is similar to before but now we know the right values and we will draw the line around the contour that we want. Our goal is the biggest contour so we can use the contourArea() method to extract the size of the contour and then draw the "bounding box" only if the contour is big enough. It’s a little different implementation from your code but you can adjust it to your logic (sorting the sizes of the areas of the contours and draw only on the biggest one).

For example:

import cv2
import numpy as np

while True:
    frame = cv2.imread("stackoverflow2pic.jpeg")
    blurred_frame = cv2.GaussianBlur(frame, (5, 5), 0)
    hsv = cv2.cvtColor(blurred_frame, cv2.COLOR_BGR2HSV)

    lower = np.array([0, 0, 223])
    upper = np.array([179, 15, 255])
    mask = cv2.inRange(hsv, lower, upper)
    
    # for OpenCV 4
    contours, _ = cv2.findContours(mask, cv2.RETR_TREE, 
       cv2.CHAIN_APPROX_NONE) 

    # ---For OpenCV 3---
    # _, contours, _ = cv2.findContours(mask, cv2.RETR_TREE, 
    # cv2.CHAIN_APPROX_NONE) 
    # ---For OpenCV 3---

    for contour in contours:
        area = cv2.contourArea(contour)
        if area > 5000:
             # -- Draw Option 1 --
             cv2.drawContours(frame, contour, -1, (0, 255, 0), 3)

             # -- Draw Option 2--
             # rect = cv2.boundingRect(contour)
             # x, y, w, h = rect
             # cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)

    cv2.imshow("Mask", mask)
    cv2.imshow("Frame", frame)

    cv2.waitKey(1)

Result (Drawing Option 1):

Result (Drawing Option 2):

Answered By: Roy Amoyal

I found the top answer’s HSV finder code didn’t work because it was partially incomplete, I’ve fixed it, Although I should note if your image isn’t very big the track bars wont display properly, just increase the canvas size. Anyway here’s the full code:

import cv2
import numpy as np

def empty(x):
   pass

cv2.namedWindow('TrackBars')

cv2.createTrackbar('Hue Min', 'TrackBars', 0, 350, empty)
cv2.createTrackbar('Hue Max', 'TrackBars', 0, 350, empty)
cv2.createTrackbar('Sat Min', 'TrackBars', 0, 255, empty)
cv2.createTrackbar('Sat Max', 'TrackBars', 0, 255, empty)
cv2.createTrackbar('Val Min', 'TrackBars', 0, 255, empty)
cv2.createTrackbar('Val Max', 'TrackBars', 0, 255, empty)

while True:

   img = cv2.imread('YourImage.png')
   imgHSV = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
   h_min = cv2.getTrackbarPos("Hue Min", "TrackBars")
   h_max = cv2.getTrackbarPos("Hue Max", "TrackBars")
   s_min = cv2.getTrackbarPos("Sat Min", "TrackBars")
   s_max = cv2.getTrackbarPos("Sat Max", "TrackBars")
   v_min = cv2.getTrackbarPos("Val Min", "TrackBars")
   v_max = cv2.getTrackbarPos("Val Max", "TrackBars")
   lower = np.array([h_min, s_min, v_min])
   upper = np.array([h_max, s_max, v_max])
   mask = cv2.inRange(imgHSV, lower, upper)
   imgResult = cv2.bitwise_and(img, img, mask=mask)

   # for OpenCV 4
   contours, _ = cv2.findContours(mask, cv2.RETR_TREE,
   cv2.CHAIN_APPROX_NONE)

   for contour in contours:
         cv2.drawContours(img, contour, -1, (0, 255, 0), 3)

   cv2.imshow("TrackBars", img)
   cv2.imshow("Result", imgResult)

   if cv2.waitKey(27) & 0xFF == ord('q'):
       print("Lower Bounds {} {} {} | Upper Bounds {} {} {}".format(h_min, s_min, v_min, h_max, s_max, v_max))
       break

To start drag all max values to the right. I don’t understand why Saturation and Value maxes can and need to go above 100 as, as I understand, 100 should be the max for both. But it works so who am I to judge.

Answered By: Mr Sys