Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@Sentinels5599
Created January 24, 2019 22:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Sentinels5599/02918b4717486f75125adada58ca0368 to your computer and use it in GitHub Desktop.
Save Sentinels5599/02918b4717486f75125adada58ca0368 to your computer and use it in GitHub Desktop.
JeVois GRIP Vision Issues (PythonSandbox.py)
import libjevois as jevois
import cv2
import numpy as np
## Simple example of image processing using OpenCV in Python on JeVois
#
# This module is a basic FRC vision process.
#
# By default, it first gets an image, blurs it, extracts the green channel, thresholds that, and uses the threshold to place
# a mask over the initial image. It then runs an HSV filter on the masked image, erodes and dilates the result, and finally
# finds and filters the contours.
# You can find the constants for all of these using GRIP. Tune the program constants and generate Python code from GRIP. Then,
# paste those constants into the Constructor below. Custom code can also be inserted after all the GRIP process code.
#
# See http://jevois.org/tutorials for tutorials on getting started with programming JeVois in Python without having
# to install any development software on your host computer.
#
# @author Anand Rajamani
#
# @videomapping YUYV 320 240 59.9 YUYV 320 240 59.9 JeVois PythonSandbox
# @email anand.rajamani@scu.edu
# @mainurl http://jevois.org
# @supporturl http://jevois.org/doc
# @otherurl http://iLab.usc.edu
# @license GPL v3
# @distribution Unrestricted
# @restrictions None
# @ingroup modules
class PythonSandbox:
# ###################################################################################################
## Constructor
def __init__(self):
# Instantiate a JeVois Timer to measure our processing framerate:
self.timer = jevois.Timer("sandbox", 100, jevois.LOG_INFO)
# SPECIAL REPLACED BLUR CONSTANT
self.__blur_type = 0
# ###################################################################################################
# ALL CONSTANTS GO UNDER HERE (make sure to remove the self.__blur_type line)
self.__blur_radius = 5.0229474757776655
self.blur_output = None
self.__cv_extractchannel_src = self.blur_output
self.__cv_extractchannel_channel = 1.0
self.cv_extractchannel_output = None
self.__cv_threshold_src = self.cv_extractchannel_output
self.__cv_threshold_thresh = 30.0
self.__cv_threshold_maxval = 255.0
self.__cv_threshold_type = cv2.THRESH_BINARY
self.cv_threshold_output = None
self.__mask_input = self.blur_output
self.__mask_mask = self.cv_threshold_output
self.mask_output = None
self.__normalize_input = self.mask_output
self.__normalize_type = cv2.NORM_MINMAX
self.__normalize_alpha = 0.0
self.__normalize_beta = 255.0
self.normalize_output = None
self.__hsv_threshold_input = self.normalize_output
self.__hsv_threshold_hue = [66.36690647482014, 94.9130331623807]
self.__hsv_threshold_saturation = [0.0, 255.0]
self.__hsv_threshold_value = [41.384942893143105, 255.0]
self.hsv_threshold_output = None
self.__cv_erode_src = self.hsv_threshold_output
self.__cv_erode_kernel = None
self.__cv_erode_anchor = (-1, -1)
self.__cv_erode_iterations = 2.0
self.__cv_erode_bordertype = cv2.BORDER_CONSTANT
self.__cv_erode_bordervalue = (-1)
self.cv_erode_output = None
self.__cv_dilate_src = self.cv_erode_output
self.__cv_dilate_kernel = None
self.__cv_dilate_anchor = (-1, -1)
self.__cv_dilate_iterations = 1.0
self.__cv_dilate_bordertype = cv2.BORDER_CONSTANT
self.__cv_dilate_bordervalue = (-1)
self.cv_dilate_output = None
self.__find_contours_input = self.cv_dilate_output
self.__find_contours_external_only = True
self.find_contours_output = None
self.__filter_contours_contours = self.find_contours_output
self.__filter_contours_min_area = 25.0
self.__filter_contours_min_perimeter = 0.0
self.__filter_contours_min_width = 0.0
self.__filter_contours_max_width = 1000.0
self.__filter_contours_min_height = 0.0
self.__filter_contours_max_height = 1000.0
self.__filter_contours_solidity = [0.0, 100]
self.__filter_contours_max_vertices = 1000000.0
self.__filter_contours_min_vertices = 0.0
self.__filter_contours_min_ratio = 0.3
self.__filter_contours_max_ratio = 0.9
self.filter_contours_output = None
# END CONSTANTS
# ###################################################################################################
## Process function with USB output
def process(self, inframe, outframe):
# Get the next camera image (may block until it is captured) and here convert it to OpenCV BGR by default. If
# you need a grayscale image instead, just use getCvGRAY() instead of getCvBGR(). Also supported are getCvRGB()
# and getCvRGBA():
source0 = inimg = inframe.getCvBGR()
outimg = inimg = inframe.getCvBGR()
# Start measuring image processing time (NOTE: does not account for input conversion time):
self.timer.start()
#################################################################################################
# BEGIN GRIP CODE
#################################################################################################
"""
Runs the pipeline and sets all outputs to new values.
"""
# Step Blur0:
self.__blur_input = source0
(self.blur_output) = self.__blur(self.__blur_input, self.__blur_type, self.__blur_radius)
# Step CV_extractChannel0:
self.__cv_extractchannel_src = self.blur_output
(self.cv_extractchannel_output) = self.__cv_extractchannel(self.__cv_extractchannel_src, self.__cv_extractchannel_channel)
# Step CV_Threshold0:
self.__cv_threshold_src = self.cv_extractchannel_output
(self.cv_threshold_output) = self.__cv_threshold(self.__cv_threshold_src, self.__cv_threshold_thresh, self.__cv_threshold_maxval, self.__cv_threshold_type)
# Step Mask0:
self.__mask_input = self.blur_output
self.__mask_mask = self.cv_threshold_output
(self.mask_output) = self.__mask(self.__mask_input, self.__mask_mask)
# Step Normalize0:
self.__normalize_input = self.mask_output
(self.normalize_output) = self.__normalize(self.__normalize_input, self.__normalize_type, self.__normalize_alpha, self.__normalize_beta)
# Step HSV_Threshold0:
self.__hsv_threshold_input = self.normalize_output
(self.hsv_threshold_output) = self.__hsv_threshold(self.__hsv_threshold_input, self.__hsv_threshold_hue, self.__hsv_threshold_saturation, self.__hsv_threshold_value)
# Step CV_erode0:
self.__cv_erode_src = self.hsv_threshold_output
(self.cv_erode_output) = self.__cv_erode(self.__cv_erode_src, self.__cv_erode_kernel, self.__cv_erode_anchor, self.__cv_erode_iterations, self.__cv_erode_bordertype, self.__cv_erode_bordervalue)
# Step CV_dilate0:
self.__cv_dilate_src = self.cv_erode_output
(self.cv_dilate_output) = self.__cv_dilate(self.__cv_dilate_src, self.__cv_dilate_kernel, self.__cv_dilate_anchor, self.__cv_dilate_iterations, self.__cv_dilate_bordertype, self.__cv_dilate_bordervalue)
# Step Find_Contours0:
self.__find_contours_input = self.cv_dilate_output
(self.find_contours_output) = self.__find_contours(self.__find_contours_input, self.__find_contours_external_only)
# Step Filter_Contours0:
self.__filter_contours_contours = self.find_contours_output
(self.filter_contours_output) = self.__filter_contours(self.__filter_contours_contours, self.__filter_contours_min_area, self.__filter_contours_min_perimeter, self.__filter_contours_min_width, self.__filter_contours_max_width, self.__filter_contours_min_height, self.__filter_contours_max_height, self.__filter_contours_solidity, self.__filter_contours_max_vertices, self.__filter_contours_min_vertices, self.__filter_contours_min_ratio, self.__filter_contours_max_ratio)
#################################################################################################
# END GRIP CODE
##################################################################################################
# DEFAULT CUSTOM CODE
def getArea(con): # Gets the area of the contour
return cv2.contourArea(con)
def getYcoord(con): # Gets the Y coordinate of the contour
M = cv2.moments(con)
cy = int(M['m01']/M['m00'])
return cy
def getXcoord(con): # Gets the X coordinate of the contour
M = cv2.moments(con)
cy = int(M['m10']/M['m00'])
return cy
def sortByArea(conts) : # Returns an array sorted by area from smallest to largest
contourNum = len(conts) # Gets number of contours
sortedBy = sorted(conts, key=getArea) # sortedBy now has all the contours sorted by area
return sortedBy
##################################################################################################
# PUT YOUR CUSTOM CODE HERE
##################################################################################################
# Draws all contours on original image in red
cv2.drawContours(outimg, self.filter_contours_output, -1, (0, 0, 255), 1)
# Gets number of contours
contourNum = len(self.filter_contours_output)
# Sorts contours by the smallest area first
newContours = sortByArea(self.filter_contours_output)
# Send the contour data over Serial
for i in range (contourNum):
cnt = newContours[i]
x,y,w,h = cv2.boundingRect(cnt) # Get the stats of the contour including width and height
# which contour, 0 is first
toSend = ("CON" + str(i) +
"area" + str(getArea(cnt)) + # Area of contour
"x" + str(round((getXcoord(cnt)*1000/320)-500, 2)) + # x-coordinate of contour, -500 to 500 rounded to 2 decimal
"y" + str(round(375-getYcoord(cnt)*750/240, 2)) + # y-coordinate of contour, -375 to 375 rounded to 2 decimal
"h" + str(round(h*750/240, 2)) + # Height of contour, 0-750 rounded to 2 decimal
"w" + str(round(w*1000/320, 2))) # Width of contour, 0-1000 rounded to 2 decimal
jevois.sendSerial(toSend)
# Write a title:
cv2.putText(outimg, "Anand's JeVois Code", (3, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,255,255), 1, cv2.LINE_AA)
# Write frames/s info from our timer into the edge map (NOTE: does not account for output conversion time):
fps = self.timer.stop()
#height, width, channels = outimg.shape # if outimg is grayscale, change to: height, width = outimg.shape
height, width, channels = outimg.shape
cv2.putText(outimg, fps, (3, height - 6), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,255,255), 1, cv2.LINE_AA)
# Convert our BGR output image to video output format and send to host over USB. If your output image is not
# BGR, you can use sendCvGRAY(), sendCvRGB(), or sendCvRGBA() as appropriate:
outframe.sendCvBGR(outimg)
# outframe.sendCvGRAY(outimg)
##################################################################################################
# END CUSTOM CODE
###################################################################################################
# FUNCTIONS GO HERE (Anything that starts with "@staticmethod")
@staticmethod
def __blur(src, type, radius):
"""Softens an image using one of several filters.
Args:
src: The source mat (numpy.ndarray).
type: The blurType to perform represented as an int.
radius: The radius for the blur as a float.
Returns:
A numpy.ndarray that has been blurred.
"""
ksize = int(2 * round(radius) + 1)
return cv2.blur(src, (ksize, ksize))
#return cv2.medianBlur(src, (ksize, ksize)) # Perform a Median Blur
#return cv2.GaussianBlur(src,(ksize, ksize),0) # Perform a Gaussian Blur
@staticmethod
def __cv_extractchannel(src, channel):
"""Extracts given channel from an image.
Args:
src: A numpy.ndarray.
channel: Zero indexed channel number to extract.
Returns:
The result as a numpy.ndarray.
"""
return cv2.extractChannel(src, (int) (channel + 0.5))
@staticmethod
def __cv_threshold(src, thresh, max_val, type):
"""Apply a fixed-level threshold to each array element in an image
Args:
src: A numpy.ndarray.
thresh: Threshold value.
max_val: Maximum value for THRES_BINARY and THRES_BINARY_INV.
type: Opencv enum.
Returns:
A black and white numpy.ndarray.
"""
return cv2.threshold(src, thresh, max_val, type)[1]
@staticmethod
def __mask(input, mask):
"""Filter out an area of an image using a binary mask.
Args:
input: A three channel numpy.ndarray.
mask: A black and white numpy.ndarray.
Returns:
A three channel numpy.ndarray.
"""
return cv2.bitwise_and(input, input, mask=mask)
@staticmethod
def __normalize(input, type, a, b):
"""Normalizes or remaps the values of pixels in an image.
Args:
input: A numpy.ndarray.
type: Opencv enum.
a: The minimum value.
b: The maximum value.
Returns:
A numpy.ndarray of the same type as the input.
"""
return cv2.normalize(input, None, a, b, type)
@staticmethod
def __hsv_threshold(input, hue, sat, val):
"""Segment an image based on hue, saturation, and value ranges.
Args:
input: A BGR numpy.ndarray.
hue: A list of two numbers the are the min and max hue.
sat: A list of two numbers the are the min and max saturation.
lum: A list of two numbers the are the min and max value.
Returns:
A black and white numpy.ndarray.
"""
out = cv2.cvtColor(input, cv2.COLOR_BGR2HSV)
return cv2.inRange(out, (hue[0], sat[0], val[0]), (hue[1], sat[1], val[1]))
@staticmethod
def __cv_erode(src, kernel, anchor, iterations, border_type, border_value):
"""Expands area of lower value in an image.
Args:
src: A numpy.ndarray.
kernel: The kernel for erosion. A numpy.ndarray.
iterations: the number of times to erode.
border_type: Opencv enum that represents a border type.
border_value: value to be used for a constant border.
Returns:
A numpy.ndarray after erosion.
"""
return cv2.erode(src, kernel, anchor, iterations = (int) (iterations +0.5),
borderType = border_type, borderValue = border_value)
@staticmethod
def __cv_dilate(src, kernel, anchor, iterations, border_type, border_value):
"""Expands area of higher value in an image.
Args:
src: A numpy.ndarray.
kernel: The kernel for dilation. A numpy.ndarray.
iterations: the number of times to dilate.
border_type: Opencv enum that represents a border type.
border_value: value to be used for a constant border.
Returns:
A numpy.ndarray after dilation.
"""
return cv2.dilate(src, kernel, anchor, iterations = (int) (iterations +0.5),
borderType = border_type, borderValue = border_value)
@staticmethod
def __find_contours(input, external_only):
"""Sets the values of pixels in a binary image to their distance to the nearest black pixel.
Args:
input: A numpy.ndarray.
external_only: A boolean. If true only external contours are found.
Return:
A list of numpy.ndarray where each one represents a contour.
"""
if(external_only):
mode = cv2.RETR_EXTERNAL
else:
mode = cv2.RETR_LIST
method = cv2.CHAIN_APPROX_SIMPLE
im2, contours, hierarchy = cv2.findContours(input, mode=mode, method=method)
return contours
@staticmethod
def __filter_contours(input_contours, min_area, min_perimeter, min_width, max_width,
min_height, max_height, solidity, max_vertex_count, min_vertex_count,
min_ratio, max_ratio):
"""Filters out contours that do not meet certain criteria.
Args:
input_contours: Contours as a list of numpy.ndarray.
min_area: The minimum area of a contour that will be kept.
min_perimeter: The minimum perimeter of a contour that will be kept.
min_width: Minimum width of a contour.
max_width: MaxWidth maximum width.
min_height: Minimum height.
max_height: Maximimum height.
solidity: The minimum and maximum solidity of a contour.
min_vertex_count: Minimum vertex Count of the contours.
max_vertex_count: Maximum vertex Count.
min_ratio: Minimum ratio of width to height.
max_ratio: Maximum ratio of width to height.
Returns:
Contours as a list of numpy.ndarray.
"""
output = []
for contour in input_contours:
x,y,w,h = cv2.boundingRect(contour)
if (w < min_width or w > max_width):
continue
if (h < min_height or h > max_height):
continue
area = cv2.contourArea(contour)
if (area < min_area):
continue
if (cv2.arcLength(contour, True) < min_perimeter):
continue
hull = cv2.convexHull(contour)
solid = 100 * area / cv2.contourArea(hull)
if (solid < solidity[0] or solid > solidity[1]):
continue
if (len(contour) < min_vertex_count or len(contour) > max_vertex_count):
continue
ratio = (float)(w) / h
if (ratio < min_ratio or ratio > max_ratio):
continue
output.append(contour)
return output
#BlurType = Enum('BlurType', 'Box_Blur Gaussian_Blur Median_Filter Bilateral_Filter')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment