#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ GEPARD - Gepard-Enabled PARticle Detection Copyright (C) 2018 Lars Bittrich and Josef Brandt, Leibniz-Institut für Polymerforschung Dresden e. V. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program, see COPYING. If not, see . """ import numpy as np import cv2 from copy import deepcopy def getContourStatsWithPixelScale(cnt, pixelscale): long, short, longellipse, shortellipse, area = getContourStats(cnt) return long*pixelscale, short*pixelscale, longellipse*pixelscale, shortellipse*pixelscale, area*pixelscale**2 def getContourStats(cnt): ##characterize particle longellipse, shortellipse = np.nan, np.nan if cnt.shape[0] >= 5: ##at least 5 points required for ellipse fitting... ellipse = cv2.fitEllipse(cnt) shortellipse, longellipse = ellipse[1] rect = cv2.minAreaRect(cnt) long, short = rect[1] if short>long: long, short = short, long area = cv2.contourArea(cnt) return long, short, longellipse, shortellipse, area class ColorRangeHSV(object): def __init__(self, name, hue, hue_tolerance, min_sat, max_sat): self.name = name self.minHue = hue-hue_tolerance/2 self.maxHue = hue+hue_tolerance/2 self.minSat = min_sat self.maxSat = max_sat def containsHSV(self, hsv): hue = hsv[0] sat = hsv[1] if self.minHue <= hue <= self.maxHue and self.minSat <= sat <= self.maxSat: return True else: if self.name != 'white': return False else: if sat < 128 and hsv[2] > 70: return True class ColorClassifier(object): def __init__(self): hue_tolerance = 30 self.colors = [ColorRangeHSV('yellow', 30, hue_tolerance, 30, 255), ColorRangeHSV('blue', 120, hue_tolerance, 80, 255), ColorRangeHSV('red', 180, hue_tolerance, 50, 255), ColorRangeHSV('red', 0, hue_tolerance, 50, 255), ColorRangeHSV('green', 70, hue_tolerance, 50, 255), ColorRangeHSV('white', 128, 256, 0, 50)] def classifyColor(self, meanHSV): result = 'non-determinable' for color in self.colors: if color.containsHSV(meanHSV): result = color.name break return result def getParticleColor(imgRGB, colorClassifier=None): img = cv2.cvtColor(imgRGB, cv2.COLOR_RGB2HSV_FULL) meanHSV = cv2.mean(img) if colorClassifier is None: colorClassifier = ColorClassifier() color = colorClassifier.classifyColor(meanHSV) return color def mergeContours(contours): img, xmin, ymin, padding = contoursToImg(contours) return imgToCnt(img, xmin, ymin, padding) def getParticleImageFromFullimage(contour, fullimage): contourCopy = deepcopy(contour) xmin, xmax, ymin, ymax = getContourExtrema(contourCopy) img = fullimage[ymin:ymax, xmin:xmax] mask = np.zeros(img.shape[:2]) for i in range(len(contourCopy)): contourCopy[i][0][0] -= xmin contourCopy[i][0][1] -= ymin cv2.drawContours(mask, [contourCopy], -1, (255, 255, 255), -1) cv2.drawContours(mask, [contourCopy], -1, (255, 255, 255), 1) img[mask == 0] = 0 img = np.array(img, dtype = np.uint8) return img def contoursToImg(contours, padding=2): contourCopy = deepcopy(contours) xmin, xmax, ymin, ymax = getContourExtrema(contourCopy) padding = padding #pixel in each direction rangex = int(np.round((xmax-xmin)+2*padding)) rangey = int(np.round((ymax-ymin)+2*padding)) img = np.zeros((rangey, rangex)) for curCnt in contourCopy: for i in range(len(curCnt)): curCnt[i][0][0] -= xmin-padding curCnt[i][0][1] -= ymin-padding cv2.drawContours(img, [curCnt], -1, 255, -1) cv2.drawContours(img, [curCnt], -1, 255, 1) img = np.uint8(cv2.morphologyEx(img, cv2.MORPH_CLOSE, np.ones((3, 3)))) return img, xmin, ymin, padding def imgToCnt(img, xmin, ymin, padding): def getSimpleContour(img): if cv2.__version__ > '3.5': contour, hierarchy = cv2.findContours(img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE) else: temp, contour, hierarchy = cv2.findContours(img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE) if len(contour)>1: raise NotConnectedContoursError return contour def getFullContour(img): if cv2.__version__ > '3.5': contour, hierarchy = cv2.findContours(img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE) else: temp, contour, hierarchy = cv2.findContours(img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE) if len(contour)>1: raise NotConnectedContoursError return contour contour = getSimpleContour(img) if len(contour[0]) < 5: contour = getFullContour(img) newContour = contour[0] for i in range(len(newContour )): newContour [i][0][0] += xmin-padding newContour [i][0][1] += ymin-padding return newContour def getContourExtrema(contours): try: cnt = np.vstack(tuple(contours)) xmin, xmax = cnt[:,0,:][:, 0].min(), cnt[:,0,:][:, 0].max() ymin, ymax = cnt[:,0,:][:, 1].min(), cnt[:,0,:][:, 1].max() except IndexError: #i.e., not a list of contours was passed, but an individual contour. Hence, the above indexing does not work xmin, xmax = cnt[:, 0].min(), cnt[:, 0].max() ymin, ymax = cnt[:, 1].min(), cnt[:, 1].max() return xmin, xmax, ymin, ymax class NotConnectedContoursError(Exception): pass if __name__ == '__main__': colors = {'white': (41, 25, 66), "red": (128, 121, 57), "red2": (23, 88, 49), "yellow": (25, 121, 91), "pink": (11, 79, 51), "brown": (32, 38, 64), "green": (54, 99, 53)} classifier= ColorClassifier() # print(classifier.hsv) for name, mean in colors.items(): print(name, classifier.classifyColor(mean))