Multiple Same Color object tracking (Python3 Opencv) - python-3.x

I am seriously struggling here. I'm using open cv2 and python3. tracking multiple objects of the same color this question is the exact same one I'm asking. But the pages are out of date and the links don't work anymore. I can't find anything else online about it. I can track multiple colors (red object, green object, a blue object, etc) However I cannot for the life of me figure out how to track two red objects.
# import the necessary packages
from collections import deque
import numpy as np
import argparse
import imutils
import cv2
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-v", "--video",
help="path to the (optional) video file")
ap.add_argument("-b", "--buffer", type=int, default=64,
help="max buffer size")
args = vars(ap.parse_args())
# define the lower and upper boundaries of the "green"
# ball in the HSV color space, then initialize the
# list of tracked points
greenLower = (29, 86, 6)
greenUpper = (64, 255, 255)
pts = deque(maxlen=args["buffer"])
# if a video path was not supplied, grab the reference
# to the webcam
if not args.get("video", False):
camera = cv2.VideoCapture(0)
# otherwise, grab a reference to the video file
else:
camera = cv2.VideoCapture(args["video"])
# keep looping
while True:
# grab the current frame
(grabbed, frame) = camera.read()
# if we are viewing a video and we did not grab a frame,
# then we have reached the end of the video
if args.get("video") and not grabbed:
break
# resize the frame, blur it, and convert it to the HSV
# color space
frame = imutils.resize(frame, width=600)
# blurred = cv2.GaussianBlur(frame, (11, 11), 0)
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
# construct a mask for the color "green", then perform
# a series of dilations and erosions to remove any small
# blobs left in the mask
mask = cv2.inRange(hsv, greenLower, greenUpper)
mask = cv2.erode(mask, None, iterations=2)
mask = cv2.dilate(mask, None, iterations=2)
# find contours in the mask and initialize the current
# (x, y) center of the ball
cnts = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)[-2]
center = None
# only proceed if at least one contour was found
if len(cnts) > 0:
# find the largest contour in the mask, then use
# it to compute the minimum enclosing circle and
# centroid
c = max(cnts, key=cv2.contourArea)
I figured that in the line above this one that reads "c = max(cnts, key=cv2.contourArea)" I could simply find the second largest circle and use that one, but once again. I couldn't find anything online about how to do this.
((x, y), radius) = cv2.minEnclosingCircle(c)
M = cv2.moments(c)
center = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
# only proceed if the radius meets a minimum size
if radius > 10:
# draw the circle and centroid on the frame,
# then update the list of tracked points
cv2.circle(frame, (int(x), int(y)), int(radius),
(0, 255, 255), 2)
cv2.circle(frame, center, 5, (0, 0, 255), -1)
# update the points queue
pts.appendleft(center)

Related

Threshold in a superpixel opencv2

I'm trying to use cv2 module to receive pixel coordinates of relatively dark regions in an image.
First I divide it into super-pixels through the cv2.ximgproc.createSuperpixelSLIC() method.
Then I'd like to consider each super-pixel as a ROI, and threshold it based on its' the intensity, so that the darker regions (i.e., where the intensity is lower than some preconfigured threshold) will be 1, and 0 in regions where the intensity is relatively high (i.e., larger than this threshold).
I tried the following code, but the problem is that is highlights the background (as obviously it also dark).
import cv2
import numpy as np
# Parameters
IMG_FILE_PATH = 'PATH TO THE IMAGE'
REGION_SIZE = 200
RULER = 20
N = 10
SAMPLE_SIZE = 5
INTENSITY_TH = 100
# ---
# 1) Load the image
img = cv2.imread(IMG_FILE_PATH, cv2.IMREAD_GRAYSCALE)
# 2) Compute the superpixels
slic = cv2.ximgproc.createSuperpixelSLIC(img, region_size=REGION_SIZE, ruler=RULER)
# 3) Get the characteristics of the superpixels calculated
slic.iterate(N)
slic.enforceLabelConnectivity()
lbls = slic.getLabels()
num_slic = slic.getNumberOfSuperpixels()
# 4) Sample some of the superpixels
sample_idxs = np.random.choice(np.arange(num_slic), size=SAMPLE_SIZE, replace=False)
for idx in sample_idxs:
img_super_pixel = np.uint8(img * (lbls==idx).astype(np.int16))
ret, mask_fg = cv2.threshold(img_super_pixel, INTENSITY_TH, 255, cv2.THRESH_BINARY)
img_super_pixel_th = cv2.bitwise_and(img_super_pixel, img_super_pixel, mask=mask_fg)
cv2.imshow('Super-pixel', img_super_pixel)
cv2.imshow('Super-pixel - thresholded', img_super_pixel_th)
cv2.waitKey()
cv2.destroyAllWindows()
Here is a sample image:
Current Output Example:
So, as is seen - the background is represented with 1., obviously because it is less than the threshold, but what I need is that that only the black spots in the super-pixel would be white, and the background with the pixels which exceed the threshold in the super-pixel area, would be black.
Is there a way to apply threshold only on the ROI, viz. the super-pixel, and not on the background?
Thanks in advance.
I was able to solve this by manually checking the pixels in the region which are below the threshold, as shown in the following code:
import cv2
import numpy as np
import pandas as pd
from pathlib import Path
# Parameters
IMG_FILE_PATH = 'PATH_TO_IMAGE'
OUTDIR = Path('OUTPUT_FOLDER')
REGION_SIZE = 200
RULER = 20
N = 10
SAMPLE_SIZE = 5
INTENSITY_TH = 100
# ---
# 1) Load the image
img = cv2.imread(IMG_FILE_PATH, cv2.IMREAD_GRAYSCALE)
# 2) Compute the superpixels
slic = cv2.ximgproc.createSuperpixelSLIC(img, region_size=REGION_SIZE, ruler=RULER)
# 3) Get the characteristics of the superpixels calculated
slic.iterate(N)
slic.enforceLabelConnectivity()
mask_slic = slic.getLabelContourMask()
lbls = slic.getLabels()
num_slic = slic.getNumberOfSuperpixels()
# 4) Sample some of the superpixels
sample_idxs = np.random.choice(np.arange(num_slic), size=SAMPLE_SIZE, replace=False)
for idx in sample_idxs:
# 4.1) Create pandas.DataFrame to store the points and their validity based on the threshold
sp_pixels_df = pd.DataFrame(columns=['x', 'y', 'relevant'])
# 4.2) Get the current super-pixel
img_super_pixel = np.uint8(img * (lbls==idx).astype(np.int16))
# 4.3) Find the coordinates of the pixels inside the current super-pixel
img_super_pixel_idx = np.argwhere(lbls==idx)
# 4.4) Separate the x and y coordinates of the points which are located inside the superpixel
xs, ys = np.array([t[0] for t in img_super_pixel_idx]), np.array([t[1] for t in img_super_pixel_idx])
# 4.5) Find the pixels inside the superpixel, which intensity is below the threshold
low_intensity_pixels = img_super_pixel[tuple([xs, ys])] < INTENSITY_TH
# 4.6) Populate the pandas.DataFrame
sp_pixels_df['x'] = xs
sp_pixels_df['y'] = ys
sp_pixels_df['relevant'] = low_intensity_pixels
# 4.7) Get the valid pixel coordinates
relevant_points = sp_pixels_df.loc[sp_pixels_df.relevant, ['x', 'y']].values
# 4.8) Separate the x and y coordinates of the relevant points which are located inside the superpixel
relevant_xs, relevant_ys = np.array([t[0] for t in relevant_points]), np.array([t[1] for t in relevant_points])
# 4.9) Convert the gray-scale image to BGR to be able to mark the relevant pixels in red
img_super_pixel_highlighted = cv2.cvtColor(img_super_pixel, cv2.COLOR_GRAY2BGR)
# 4.10) Highlight the relevant pixels
img_super_pixel_highlighted[tuple([relevant_xs, relevant_ys])] = (0, 0, 255)
cv2.imshow('Original Superpixels', img_super_pixel)
cv2.imshow('Relevant pixels highlighted', img_super_pixel_highlighted)
cv2.waitKey()
cv2.destroyAllWindows()
Output:
Original:
Highlighted:
Cheers!

How to crop images based on mask threshold?

I have to crop a lot of images manually. Not the funniest thing to do. So I thought I'd try to do it using Python.
I can detect the subject, create a mask, but I have no idea how to get the points from the very bottom part and crop based on them.
Any help is appreciated
import cv2
img = cv2.imread('image5.jpg')
h, w = img.shape[:2]
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
thr = cv2.threshold(gray, 192, 255, cv2.THRESH_BINARY_INV)[1]
cv2.imwrite('result5.png', thr)
you can try to find all external contours using cv2.RETR_EXTERNAL and pick the bottom most point, like this:
import cv2
import numpy as np
import imutils
im = cv2.imread('images/tennis.jpg')
# Percent of original size
scale_percent = 20
width = int(im.shape[1] * scale_percent / 100)
height = int(im.shape[0] * scale_percent / 100)
dim = (width, height)
# Resize image
im = cv2.resize(im, dim, interpolation = cv2.INTER_AREA)
# Convert to grayscale
gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
# Canny
canny_output = cv2.Canny(im, 120, 240)
# Find external contours
contours, hierarchy = cv2.findContours(canny_output, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#cv2.drawContours(im, [contours[0]], 0, (0,255,0), 3) # Uncomment this line to see what contour opencv is finding
# Pick the bottom most point and add an offset (whatever value you want, this is just for aesthetics)
c = contours[0]
bottommost = tuple(c[c[:, :, 1].argmax()][0])[1] + 5
# Crop image
im = im[:bottommost, :]
# Show image
cv2.imshow('image', im)
cv2.waitKey()
Very good thinking I'd say! now the implementation:
xx,yy = thrs.nonzero()
max_crop_h = xx.max()
crop = img[:max_crop_h,:]
numpy has your back!

python opencv trying to find center of contours with moments method

This is my code.
import cv2
#detect all centers of objects
#mark all centers
#return modified image
def markCenter(canny,img):
#get contours using cv2 method - "findContours"
contours,hierarchy = cv2.findContours(canny,cv2.RETR_LIST,cv2.CHAIN_APPROX_NONE)
#sort contour by area
contours = sorted(contours, key=lambda x: cv2.contourArea(x))
contours = sorted(contours, key=cv2.contourArea,reverse=True)
#iterate over contour list
for cnt in contours:
#get center of contour using cv2 method - "moments"
M = cv2.moments(cnt)
#parse returned data from "moments"
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
# draw the center of the shape on the image
print(cX,cY) #print coordinates
print(img[cX,cY]) #print value of pixel in center of contour
cv2.circle(img, (cX, cY), 1, (255, 255, 255), -1) #mark the center of the contour
return img #return modified image
def main():
#read image
img = cv2.imread("resources/result_shapedet.jpg")
#show image
cv2.imshow("original",img)
#convert image to greyscale
imgGray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#blurr image
imgBlur = cv2.GaussianBlur(imgGray, (7, 7), 1)
#detect edges of objects with canny method
imgCanny = cv2.Canny(imgBlur, 40, 40)
#show image with marked centers
cv2.imshow("centers",markCenter(imgCanny,img))
#never close image windows
cv2.waitKey(0)
if __name__=="__main__":
main()
The function markCenter finds the contours and their center.
The function manages to find can in fact mark the centers, but when I try to find the value of the pixel in the center, I get the wrong answer/answer from another pixel.
Why is that, and how can I fix it?
Numpy uses rows/cols (AKA y/x) vs OpenCV which uses x/y. When you do the print, you are using numpy, so print(img[cX,cY]) needs to become print(img[cY,cX])
A good example of this common confusion can be found here:
https://stackoverflow.com/a/56849032/848419
Also remember that OpenCV uses BGR, so if you're using RGB as the reference values to identify the color, you need to reverse the elements.
img_RGB = img[::-1]

How to calculate the spray velocity ? Dense Optical Flow

I am trying to measure the velocity of a water spray.
The code can be seen here:
import cv2
import numpy as np
# Get a VideoCapture object from video and store it in vs
vc = cv2.VideoCapture('spray.avi')
# Read first frame
ret, first_frame = vc.read()
# Scale and resize image
resize_dim = 600
max_dim = max(first_frame.shape)
scale = resize_dim / max_dim
first_frame = cv2.resize(first_frame, None, fx=scale, fy=scale)
# Convert to gray scale
prev_gray = cv2.cvtColor(first_frame, cv2.COLOR_BGR2GRAY)
# Create mask
mask = np.zeros_like(first_frame)
# Sets image saturation to maximum
mask[..., 1] = 255
out = cv2.VideoWriter('video.mp4', -1, 1, (600, 600))
while (vc.isOpened()):
# Read a frame from video
ret, frame = vc.read()
# Convert new frame format`s to gray scale and resize gray frame obtained
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
gray = cv2.resize(gray, None, fx=scale, fy=scale)
# Calculate dense optical flow by Farneback method
# https://docs.opencv.org/3.0-beta/modules/video/doc/motion_analysis_and_object_tracking.html#calcopticalflowfarneback
flow = cv2.calcOpticalFlowFarneback(prev_gray, gray, None, pyr_scale=0.5, levels=5, winsize=11, iterations=5,
poly_n=5, poly_sigma=1.1, flags=0)
# Compute the magnitude and angle of the 2D vectors
magnitude, angle = cv2.cartToPolar(flow[..., 0], flow[..., 1])
# Set image hue according to the optical flow direction
mask[..., 0] = angle * 180 / np.pi / 2
# Set image value according to the optical flow magnitude (normalized)
mask[..., 2] = cv2.normalize(magnitude, None, 0, 255, cv2.NORM_MINMAX)
# Convert HSV to RGB (BGR) color representation
rgb = cv2.cvtColor(mask, cv2.COLOR_HSV2BGR)
# Resize frame size to match dimensions
frame = cv2.resize(frame, None, fx=scale, fy=scale)
# Open a new window and displays the output frame
dense_flow = cv2.addWeighted(frame, 1, rgb, 2, 0)
cv2.imshow("Dense optical flow", dense_flow)
#cv2.imshow("Initial video", vc )
out.write(dense_flow)
# Update previous frame
prev_gray = gray
# Frame are read by intervals of 1 millisecond. The programs breaks out of the while loop when the user presses the 'q' key
if cv2.waitKey(10) & 0xFF == ord('q'):
break
# The following frees up resources and closes all windows
vc.release()
cv2.destroyAllWindows()
I can have the video of the dense optical flow. Works fine.
(see image here
But I want to find the actual numbers of the velocity profile.
Is it somewhere within the code? And I haven't understood it yet?
Or are there any more additions needed to find them?
Thank you!!
(the code is from here: https://github.com/IRailean/Dense-Optical-Flow/blob/master/OpticalFlow.py

Detecting warm colors in the Python image

I have a problem and I need your help.
I have a series of thermographic images, of which I need to detect the hot spot (shown in the bar to the right of the image) in the area where the analysis is being done. In the case of these example images, the hot spot is in the focus of the crosshair, however, the goal is to imagine that I don't know where this point is and that the algorithm itself finds it, based on the bar on the right. I leave below some of these images as an example:
IR_1544.jpg
IR_1546.jpg
IR_1548.jpg
IR_1566.jpg
IR_1574.jpg
In this example, the sidebar indicates a temperature range between 33.2 and 97.7 ° C. I would like to identify in the image where the 97.7 ° C point is. Initially I created a code in which I read the BGR value at the highest point of the bar and look for this combination in the rest of the image, this didn't return anything. Not convinced, I created a code that identifies the RGB code in the entire bar and looks in the image, which also did not return anything, the code follows below:
# Find one of temperature bar colors in the image
import cv2
image_path = r"C:\Users\bruno\PycharmProjects\TCC\Imagens\IR_1544.jpg"
img = cv2.imread(image_path)
crop1 = img[69:171, 309:310]
for i in range(70, 172):
crop = img[i-1:i, 309:310]
num1, num2, num3 = cv2.split(crop)
for i in range(0, crop.shape[0]):
for j in range(0, crop.shape[1]):
if img[i][j][0] == num1:
if img[i][j][1] == num2:
if img[i][j][2] == num3:
print("I found")
cv2.imshow("img1", img)
cv2.imshow("img2", crop1)
cv2.waitKey(0)
cv2.destroyAllWindows()
I would like to know if there is another way that I can identify these colors in the image.
I thank everyone who can help !!
I had to follow a lot of tutorials to achieve my goal:
Estimate Brightness of an image Opencv
Convert HSV to grayscale in OpenCV
Finding the Brightest Spot in an Image using Python and OpenCV
OpenCV-Python Tutorials
OpenCV-Python Tutorials
Recognizing digits with OpenCV and Python
Recognise text and digit from the image with Python, OpenCV and Tesseract OCR
Recognize specific numbers from table image with Pytesseract OCR
Convert a number range to another range, maintaining ratio
import cv2
import numpy as np
import pytesseract # used to read the digits on images
from PIL import Image # transformation of image read with OpenCV to use it with pytesseract
src_path = 'C:/Users/user/Documents/StackOverflow/WarmColorDetection/'
pytesseract.pytesseract.tesseract_cmd = 'C:/Users/user/AppData/Local/Tesseract-OCR/tesseract.exe'
def find_temperature_range(img, y1=0, y2=0, x1=0, x2=0):
'''
Find the number that indicates the temperature range for that image.
:param img: The image where the temperature range is located.
:param y1: Start of the temperature scale label height.
:param y2: End of the temperature scale label height.
:param x1: Start of of the temperature scale label width.
:param x2: End of of the temperature scale label width.
:return: A temperature range value read.
'''
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
roi = gray[y1:y2, x1:x2] # ROI - Region of Interest
thresh = cv2.threshold(roi, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
kernel = np.ones((1, 1), np.uint8)
dilation = cv2.dilate(thresh, kernel, iterations=1)
# Recognize text with tesseract for python
binimagem = Image.fromarray(dilation)
temperature_range = pytesseract.image_to_string(binimagem,
config='--psm 10 -c tessedit_char_whitelist=01234567890.')
return float(temperature_range)
def find_warm_pixel(img, radius=3):
'''
Find warm pixel in the given image
:param img: Image where the warm pixel will be searched
:param radius: kernel
:return: A tuple with the values of (minVal, maxVal, minLoc, maxLoc)
'''
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Apply a Gaussian Blur to the image then find the brightest region
gray = cv2.GaussianBlur(gray, (radius, radius), 0)
return cv2.minMaxLoc(gray)
if __name__ == '__main__':
# Loop over all images and show the warm point of all of them
for i in range(1, 6):
img = cv2.imread(f'img/img{i}.jpg', 1)
y, x, _ = img.shape
img_copy = img.copy()
max_temp_range = find_temperature_range(img_copy, 45, 60, 280, 315)
min_temp_range = find_temperature_range(img_copy, 178, 194, 280, 315)
if i == 1:
max_temp_range = 97.7 # Could not read the correct number only for this case, as it's showing 77
(minVal, maxVal, minLoc, maxLoc) = find_warm_pixel(img_copy)
# Converting a pixel value based on minimum and maximum value range read from the image
# new_value = ( (old_value - old_min) / (old_max - old_min) ) * (new_max - new_min) + new_min
old_value = maxVal
old_min = 0
old_max = 255
temperature = ((old_value - old_min) / (old_max - old_min)) * (max_temp_range - min_temp_range) + min_temp_range
circle_radius = 3
cv2.circle(img, maxLoc, circle_radius, (255, 0, 0), 2) # draw a circle around the britest pixel
cv2.putText(img, f'Coordinate: {maxLoc}', (122, 210), cv2.FONT_HERSHEY_SIMPLEX, 0.35, (255, 255, 255), 1,
cv2.LINE_AA)
cv2.putText(img, f'Value: {temperature:.2f}', (122, 225), cv2.FONT_HERSHEY_SIMPLEX, 0.35,
(255, 255, 255), 1,
cv2.LINE_AA)
# Display the result
cv2.namedWindow(f'Image {i}', cv2.WINDOW_GUI_NORMAL)
cv2.resizeWindow(f'Image {i}', x, y)
cv2.imshow(f'Image {i}', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

Resources