Commit bb5c2af0 authored by WillBrennan's avatar WillBrennan Committed by GitHub

Merge pull request #4 from WillBrennan/develop

Develop
parents 139e5f1b 52db149e
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/
*.py[cod] *.py[cod]
*.pyc *$py.class
# C extensions # C extensions
*.so *.so
...@@ -14,19 +14,17 @@ develop-eggs/ ...@@ -14,19 +14,17 @@ develop-eggs/
dist/ dist/
downloads/ downloads/
eggs/ eggs/
.eggs/
lib/ lib/
lib64/ lib64/
parts/ parts/
sdist/ sdist/
var/ var/
wheels/
*.egg-info/ *.egg-info/
.installed.cfg .installed.cfg
*.egg *.egg
# Linux Temporary Files
*~
# PyInstaller # PyInstaller
# Usually these files are written by a python script from a template # Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it. # before PyInstaller builds the exe, so as to inject date/other infos into it.
...@@ -41,9 +39,12 @@ pip-delete-this-directory.txt ...@@ -41,9 +39,12 @@ pip-delete-this-directory.txt
htmlcov/ htmlcov/
.tox/ .tox/
.coverage .coverage
.coverage.*
.cache .cache
nosetests.xml nosetests.xml
coverage.xml coverage.xml
*,cover
.hypothesis/
# Translations # Translations
*.mo *.mo
...@@ -51,6 +52,14 @@ coverage.xml ...@@ -51,6 +52,14 @@ coverage.xml
# Django stuff: # Django stuff:
*.log *.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation # Sphinx documentation
docs/_build/ docs/_build/
...@@ -58,5 +67,29 @@ docs/_build/ ...@@ -58,5 +67,29 @@ docs/_build/
# PyBuilder # PyBuilder
target/ target/
# PyCharm # Jupyter Notebook
.idea/ .ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# dotenv
.env
# virtualenv
.venv/
venv/
ENV/
# Spyder project settings
.spyderproject
# Rope project settings
.ropeproject
# Intelija ID
.idea/
\ No newline at end of file
#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'Will Brennan'
# Built-in Modules
import time
import argparse
import logging
# Standard Modules
import cv2
import numpy
# Custom Modules
logger = logging.getLogger('main')
class SkinDetector(object):
def __init__(self, args):
assert isinstance(args, argparse.Namespace), 'args must be of type argparse.Namespace'
self.args = args
self.mask = None
logger.debug('SkinDetector initialised')
@staticmethod
def assert_image(img, grey=False):
logger.debug('Applying assertions...')
depth = 3
if grey:
depth = 2
assert isinstance(img, numpy.ndarray), 'image must be a numpy array'
assert len(img.shape) == depth, 'skin detection can only work on color images'
assert img.size > 100, 'seriously... you thought this would work?'
def get_mask_hsv(self, img):
logger.debug('Applying hsv threshold')
self.assert_image(img)
lower_thresh = numpy.array([0, 50, 0], dtype=numpy.uint8)
upper_thresh = numpy.array([120, 150, 255], dtype=numpy.uint8)
img_hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
msk_hsv = cv2.inRange(img_hsv, lower_thresh, upper_thresh)
if self.args.debug:
scripts.display('input', img)
scripts.display('mask_hsv', msk_hsv)
self.add_mask(msk_hsv)
def get_mask_rgb(self, img):
logger.debug('Applying rgb thresholds')
lower_thresh = numpy.array([45, 52, 108], dtype=numpy.uint8)
upper_thresh = numpy.array([255, 255, 255], dtype=numpy.uint8)
mask_a = cv2.inRange(img, lower_thresh, upper_thresh)
mask_b = 255*((img[:, :, 2]-img[:, :, 1])/20)
logger.debug('mask_b unique: {0}'.format(numpy.unique(mask_b)))
mask_c = 255*((numpy.max(img, axis=2)-numpy.min(img, axis=2))/20)
logger.debug('mask_d unique: {0}'.format(numpy.unique(mask_c)))
msk_rgb = cv2.bitwise_and(mask_a, mask_b)
msk_rgb = cv2.bitwise_and(mask_c, msk_rgb)
if self.args.debug:
scripts.display('input', img)
scripts.display('mask_rgb', msk_rgb)
self.add_mask(msk_rgb)
def get_mask_ycrcb(self, img):
self.assert_image(img)
lower_thresh = numpy.array([90, 100, 130], dtype=numpy.uint8)
upper_thresh = numpy.array([230, 120, 180], dtype=numpy.uint8)
img_ycrcb = cv2.cvtColor(img, cv2.COLOR_RGB2YCR_CB)
msk_ycrcb = cv2.inRange(img_ycrcb, lower_thresh, upper_thresh)
if self.args.debug:
scripts.display('input', img)
scripts.display('mask_ycrcb', msk_ycrcb)
self.add_mask(msk_ycrcb)
def grab_cut_mask(self, img_col, mask):
kernel = numpy.ones((50, 50), numpy.float32)/(50*50)
dst = cv2.filter2D(mask, -1, kernel)
dst[dst != 0] = 255
free = numpy.array(cv2.bitwise_not(dst), dtype=numpy.uint8)
if self.args.debug:
scripts.display('not skin', free)
scripts.display('grabcut input', mask)
grab_mask = numpy.zeros(mask.shape, dtype=numpy.uint8)
grab_mask[:, :] = 2
grab_mask[mask == 255] = 1
grab_mask[free == 255] = 0
print numpy.unique(grab_mask)
if numpy.unique(grab_mask).tolist() == [0, 1]:
logger.debug('conducting grabcut')
bgdModel = numpy.zeros((1, 65), numpy.float64)
fgdModel = numpy.zeros((1, 65), numpy.float64)
if img_col.size != 0:
mask, bgdModel, fgdModel = cv2.grabCut(img_col, grab_mask, None, bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_MASK)
mask = numpy.where((mask == 2) | (mask == 0), 0, 1).astype('uint8')
else:
logger.warning('img_col is empty')
return mask
@staticmethod
def closing(msk):
assert isinstance(msk, numpy.ndarray), 'msk must be a numpy array'
assert msk.ndim == 2, 'msk must be a greyscale image'
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
msk = cv2.morphologyEx(msk, cv2.MORPH_CLOSE, kernel)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
msk = cv2.morphologyEx(msk, cv2.MORPH_OPEN, kernel, iterations=2)
return msk
def process(self, img):
logger.debug('Initialising process')
dt = time.time()
self.assert_image(img)
logger.debug('Generating mean-color image')
#img = mean_color.img_mean(img)
logger.debug('Conducting thresholding')
self.n_mask = 0
self.mask = numpy.zeros(img.shape[:2], dtype=numpy.uint8)
self.get_mask_hsv(img)
self.get_mask_rgb(img)
self.get_mask_ycrcb(img)
logger.debug('Thresholding sum of masks')
self.threshold(self.args.thresh)
if self.args.debug:
scripts.display('skin_mask', self.mask)
scripts.display('input_img', img)
dt = round(time.time()-dt, 2)
hz = round(1/dt, 2)
logger.debug('Conducted processing in {0}s ({1}Hz)'.format(dt, hz))
self.mask = self.closing(self.mask)
self.mask = self.grab_cut_mask(img, self.mask)
return self.mask
def add_mask(self, img):
logger.debug('normalising mask')
self.assert_image(img, grey=True)
img[img < 128] = 0
img[img >= 128] = 1
logger.debug('normalisation complete')
logger.debug('adding mask to total mask')
self.mask += img
self.n_mask += 1
logger.debug('add mask complete')
def threshold(self, threshold):
assert isinstance(threshold, float), 'threshold must be a float (current type - {0})'.format(type(threshold))
assert 0 <= threshold <= 1, 'threshold must be between 0 & 1 (current value - {0})'.format(threshold)
assert self.n_mask > 0, 'Number of masks must be greater than 0 [n_mask ({0}) = {1}]'.format(type(self.n_mask), self.n_mask)
logger.debug('Threshold Value - {0}%'.format(int(100*threshold)))
logger.debug('Number of Masks - {0}'.format(self.n_mask))
self.mask /= self.n_mask
self.mask[self.mask < threshold] = 0
self.mask[self.mask >= threshold] = 255
logger.debug('{0}% of the image is skin'.format(int((100.0/255.0)*numpy.sum(self.mask)/(self.mask.size))))
return self.mask
\ No newline at end of file
#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'Will Brennan'
# Built-in Modules
import logging
# Standard Modules
import cv2
import numpy
# Custom Modules
import mean_color
logger = logging.getLogger('main')
class SuperContour(object):
def __init__(self, width=32):
self.width = width
def grid_contours(self, frame, contours, heir):
result_cont, result_heir, result_rois = [], [], []
logger.debug('segmenting contours with grid')
for contour in contours:
msk = numpy.zeros(frame.shape, dtype=frame.dtype)
cv2.drawContours(msk, [contour], -1, 255, -1)
bbox = cv2.boundingRect(contour)
w0, h0 = bbox[0]//self.width, bbox[1]//self.width
n_w = max(1, ((bbox[0]+bbox[2])//self.width) - w0)
n_h = max(1, ((bbox[1]+bbox[3])//self.width) - h0)
for i in range(n_w):
for j in range(n_h):
grid_msk = numpy.zeros(frame.shape, dtype=frame.dtype)
grid_box = numpy.array([[(w0+i)*self.width, (h0+j)*self.width],
[(w0+i+1)*self.width, (h0+j)*self.width],
[(w0+i)*self.width, (h0+j+1)*self.width],
[(w0+i+1)*self.width, (h0+j+1)*self.width]],
dtype=numpy.uint8)
cv2.drawContours(grid_msk, [grid_box], -1, 255, -1)
grid_msk = cv2.bitwise_and(grid_msk, msk)
result_cont.append(grid_msk)
# todo: work out stats of new contour!
# todo: mix grid with contours to form super pixels!
contours = result_cont
logger.debug('checking and removing overlap...')
msk_all = numpy.zeros(frame.shape[:2], dtype=frame.dtype)
for msk in contours:
msk = cv2.bitwise_and(msk, cv2.bitwise_not(msk_all))
if msk.sum() != 0:
result_cont.append(msk)
msk_all = numpy.min(255, cv2.add(msk_all, msk))
logger.debug('grid contours complete')
contours = result_cont
return contours, heir, rois
def process(self, frame):
frame_gry = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
contours, heir = cv2.findContours(frame_gry, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contours, heir, rois = self.grid_contours(frame, contours, heir)
for i in range(len(contours)):
roi, contour = rois[i], contours[i]
mask = numpy.zeros(frame.shape[:2], dtype=frame.dtype)
cv2.drawContours(mask, [contour], -1, 255, -1)
yield roi, contour
\ No newline at end of file
...@@ -4,22 +4,41 @@ __author__ = 'Will Brennan' ...@@ -4,22 +4,41 @@ __author__ = 'Will Brennan'
# Built-in Modules # Built-in Modules
import argparse
import logging import logging
# Standard Modules # Standard Modules
import cv2 import cv2
import numpy
# Custom Modules # Custom Modules
import main
import scripts import scripts
from SkinDetector import SkinDetector
if __name__ == '__main__': if __name__ == '__main__':
args = scripts.get_args(from_file=False) parser = argparse.ArgumentParser(description=__doc__)
logger = scripts.get_logger(quite=args.quite, debug=args.debug) parser.add_argument('image_paths', type=str, nargs='+', help="paths to one or more images or image directories")
parser.add_argument('-b', '--debug', dest='debug', action='store_true', help='enable debug logging')
parser.add_argument('-q', '--quite', dest='quite', action='store_true', help='disable all logging')
parser.add_argument('-t', '--thresh', dest='thresh', default=0.5, type=float, help='threshold for skin mask')
args = parser.parse_args()
logger = logging.getLogger('main')
if not args.quite:
if args.debug:
level = logging.DEBUG
else:
level = logging.INFO
ch = logging.StreamHandler()
ch.setLevel(level=level)
formatter = logging.Formatter('%(asctime)s - %(funcName)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
logger.addHandler(ch)
detector = SkinDetector(args)
cam = cv2.VideoCapture(0) cam = cv2.VideoCapture(0)
while True: while True:
ret, img_col = cam.read() ret, img_col = cam.read()
img_msk = main.process(img_col, args=args) img_msk = detector.process(img_col)
if not args.display: if not args.display:
scripts.display('img_col', img_col) scripts.display('img_col', img_col)
scripts.display('img_msk', img_msk) scripts.display('img_msk', img_msk)
......
#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'Will Brennan'
# Built-in Modules
# Standard Modules
# Custom Modules
from main import *
import scripts
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'Will Brennan'
# Built-in modules
import time
import logging
# Standard modules
import numpy
import sklearn.utils
import sklearn.cluster as cluster
# Custom modules
logger = logging.getLogger('main')
def img_mean(frame, n_clusters=64):
result = frame.copy()
flat_frame = frame.reshape(-1, 3)
frame = sklearn.utils.shuffle(flat_frame)[:min(1000, frame.shape[0]*frame.shape[1])]
logger.debug('frame shape: {0}'.format(frame.shape))
logger.debug('starting training...')
t0 = time.time()
kmeans = cluster.KMeans(n_clusters=n_clusters, random_state=0).fit(frame)
logger.debug('training took {0}s'.format(round(time.time()-t0, 2)))
lookup = kmeans.predict(flat_frame)
label_idx = 0
for i in range(result.shape[0]):
for j in range(result.shape[1]):
result[i][j] = kmeans.cluster_centers_[lookup[label_idx]]
label_idx += 1
logger.debug('label shape: {0}'.format(result.shape))
result.astype(dtype=numpy.uint8)
return result
\ No newline at end of file
...@@ -4,95 +4,11 @@ __author__ = 'Will Brennan' ...@@ -4,95 +4,11 @@ __author__ = 'Will Brennan'
# Built-in Modules # Built-in Modules
import os
import sys
import argparse
import logging
# Standard Modules # Standard Modules
import cv2 import cv2
import numpy import numpy
# Custom Modules # Custom Modules
def get_logger(level=logging.INFO, quite=False, debug=False, to_file=''):
"""
This function initialises a logger to stdout.
:return: logger
"""
assert level in [logging.DEBUG, logging.INFO, logging.WARNING, logging.CRITICAL]
logger = logging.getLogger('main')
formatter = logging.Formatter('%(asctime)s - %(funcName)s - %(levelname)s - %(message)s')
if debug:
level = logging.DEBUG
logger.setLevel(level=level)
if not quite:
if to_file:
fh = logging.FileHandler(to_file)
fh.setLevel(level=level)
fh.setFormatter(formatter)
logger.addHandler(fh)
else:
ch = logging.StreamHandler()
ch.setLevel(level=level)
ch.setFormatter(formatter)
logger.addHandler(ch)
return logger
def get_args(default=None, args_string='', from_file=True):
"""
This function gets the command line arguments and passes any unknown arguments to ALE.
:param default: dictionary of default arguments with keys as `dest`
:return: command line arguments
"""
if not default:
default = {}
parser = argparse.ArgumentParser(description=__doc__)
if from_file:
parser.add_argument('image_paths', type=str, nargs='+', help="Filepath for input images or folder containing images")
parser.add_argument('-n', '--name', dest='name', default='DEFAULT_NAME', type=str, help='Basename of all export files')
parser.add_argument('-b', '--debug', dest='debug', action='store_true', help='Lower logging level to debug')
parser.add_argument('-q', '--quite', dest='quite', action='store_true', help='Disable all logging entirely')
parser.add_argument('-d', '--display', dest='display', action='store_true', help="Display Game while learning and testing")
parser.add_argument('-s', '--save', dest='save', action='store_true', help="If parsed saves the input image and mask with random file name, records name to logger")
parser.add_argument('-t', '--thresh', dest='thresh', default=0.5, type=float, help='thresholding for skin mask')
if args_string:
args_string = args_string.split(' ')
args = parser.parse_args(args_string)
else:
args = parser.parse_args()
return args
def gen_args():
return get_args(args_string='USED_GEN_ARGS')
def find_images(path, recursive=True):
if os.path.isdir(path):
return list(xfind_images(path, recursive=recursive))
elif os.path.exists(path):
return [path]
else:
raise ValueError('path is not a valid path or directory')
def xfind_images(directory, recursive=False, ignore=True):
assert os.path.isdir(directory), 'FileIO - get_images: Directory does not exist'
assert isinstance(recursive, bool), 'FileIO - get_images: recursive must be a boolean variable'
ext, result = ['png', 'jpg', 'jpeg'], []
for path_a in os.listdir(directory):
path_a = directory+'/'+path_a
if os.path.isdir(path_a) and recursive:
for path_b in xfind_images(path_a):
yield path_b
check_a = path_a.split('.')[-1] in ext
check_b = ignore or ('-' not in path_a.split('/')[-1])
if check_a and check_b:
yield path_a
def display(title, img, max_size=200000): def display(title, img, max_size=200000):
assert isinstance(img, numpy.ndarray), 'img must be a numpy array' assert isinstance(img, numpy.ndarray), 'img must be a numpy array'
assert isinstance(title, str), 'title must be a string' assert isinstance(title, str), 'title must be a string'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment