[WIP] Fix opencv (#7864)
* Updates to opencv image processor * Remove opencv hub * Requirements * Remove extra line * Fix linting errors * Indentation * Requirements * Linting * Check for import on platform setup * Remove opencv requirement * Linting * fix style * fix lint
This commit is contained in:
parent
482db94372
commit
97f62cfb78
3 changed files with 124 additions and 220 deletions
|
@ -7,22 +7,56 @@ https://home-assistant.io/components/image_processing.opencv/
|
|||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
import requests
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import split_entity_id
|
||||
from homeassistant.components.image_processing import (
|
||||
ImageProcessingEntity, PLATFORM_SCHEMA)
|
||||
from homeassistant.components.opencv import (
|
||||
ATTR_MATCHES, CLASSIFIER_GROUP_CONFIG, CONF_CLASSIFIER, CONF_ENTITY_ID,
|
||||
CONF_NAME, process_image)
|
||||
CONF_SOURCE, CONF_ENTITY_ID, CONF_NAME, PLATFORM_SCHEMA,
|
||||
ImageProcessingEntity)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['numpy==1.12.0']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['opencv']
|
||||
ATTR_MATCHES = 'matches'
|
||||
ATTR_TOTAL_MATCHES = 'total_matches'
|
||||
|
||||
CASCADE_URL = \
|
||||
'https://raw.githubusercontent.com/opencv/opencv/master/data/' + \
|
||||
'lbpcascades/lbpcascade_frontalface.xml'
|
||||
|
||||
CONF_CLASSIFIER = 'classifer'
|
||||
CONF_FILE = 'file'
|
||||
CONF_MIN_SIZE = 'min_size'
|
||||
CONF_NEIGHBORS = 'neighbors'
|
||||
CONF_SCALE = 'scale'
|
||||
|
||||
DEFAULT_CLASSIFIER_PATH = 'lbp_frontalface.xml'
|
||||
DEFAULT_MIN_SIZE = (30, 30)
|
||||
DEFAULT_NEIGHBORS = 4
|
||||
DEFAULT_SCALE = 1.1
|
||||
DEFAULT_TIMEOUT = 10
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=2)
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(CLASSIFIER_GROUP_CONFIG)
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_CLASSIFIER, default=None): {
|
||||
cv.string: vol.Any(
|
||||
cv.isfile,
|
||||
vol.Schema({
|
||||
vol.Required(CONF_FILE): cv.isfile,
|
||||
vol.Optional(CONF_SCALE, DEFAULT_SCALE): float,
|
||||
vol.Optional(CONF_NEIGHBORS, DEFAULT_NEIGHBORS):
|
||||
cv.positive_int,
|
||||
vol.Optional(CONF_MIN_SIZE, DEFAULT_MIN_SIZE):
|
||||
vol.Schema((int, int))
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
def _create_processor_from_config(hass, camera_entity, config):
|
||||
|
@ -37,41 +71,63 @@ def _create_processor_from_config(hass, camera_entity, config):
|
|||
return processor
|
||||
|
||||
|
||||
def _get_default_classifier(dest_path):
|
||||
"""Download the default OpenCV classifier."""
|
||||
_LOGGER.info('Downloading default classifier')
|
||||
req = requests.get(CASCADE_URL, stream=True)
|
||||
with open(dest_path, 'wb') as fil:
|
||||
for chunk in req.iter_content(chunk_size=1024):
|
||||
if chunk: # filter out keep-alive new chunks
|
||||
fil.write(chunk)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the OpenCV image processing platform."""
|
||||
if discovery_info is None:
|
||||
try:
|
||||
# Verify opencv python package is preinstalled
|
||||
# pylint: disable=unused-import,unused-variable
|
||||
import cv2 # noqa
|
||||
except ImportError:
|
||||
_LOGGER.error("No opencv library found! " +
|
||||
"Install or compile for your system " +
|
||||
"following instructions here: " +
|
||||
"http://opencv.org/releases.html")
|
||||
return
|
||||
|
||||
devices = []
|
||||
for camera_entity in discovery_info[CONF_ENTITY_ID]:
|
||||
devices.append(
|
||||
_create_processor_from_config(hass, camera_entity, discovery_info))
|
||||
entities = []
|
||||
if config[CONF_CLASSIFIER] is None:
|
||||
dest_path = hass.config.path(DEFAULT_CLASSIFIER_PATH)
|
||||
_get_default_classifier(dest_path)
|
||||
config[CONF_CLASSIFIER] = {
|
||||
'Face': dest_path
|
||||
}
|
||||
|
||||
add_devices(devices)
|
||||
for camera in config[CONF_SOURCE]:
|
||||
entities.append(OpenCVImageProcessor(
|
||||
hass, camera[CONF_ENTITY_ID], camera.get(CONF_NAME),
|
||||
config[CONF_CLASSIFIER]
|
||||
))
|
||||
|
||||
add_devices(entities)
|
||||
|
||||
|
||||
class OpenCVImageProcessor(ImageProcessingEntity):
|
||||
"""Representation of an OpenCV image processor."""
|
||||
|
||||
def __init__(self, hass, camera_entity, name, classifier_configs):
|
||||
def __init__(self, hass, camera_entity, name, classifiers):
|
||||
"""Initialize the OpenCV entity."""
|
||||
self.hass = hass
|
||||
self._camera_entity = camera_entity
|
||||
self._name = name
|
||||
self._classifier_configs = classifier_configs
|
||||
if name:
|
||||
self._name = name
|
||||
else:
|
||||
self._name = "OpenCV {0}".format(
|
||||
split_entity_id(camera_entity)[1])
|
||||
self._classifiers = classifiers
|
||||
self._matches = {}
|
||||
self._total_matches = 0
|
||||
self._last_image = None
|
||||
|
||||
@property
|
||||
def last_image(self):
|
||||
"""Return the last image."""
|
||||
return self._last_image
|
||||
|
||||
@property
|
||||
def matches(self):
|
||||
"""Return the matches it found."""
|
||||
return self._matches
|
||||
|
||||
@property
|
||||
def camera_entity(self):
|
||||
"""Return camera entity id from process pictures."""
|
||||
|
@ -85,20 +141,54 @@ class OpenCVImageProcessor(ImageProcessingEntity):
|
|||
@property
|
||||
def state(self):
|
||||
"""Return the state of the entity."""
|
||||
total_matches = 0
|
||||
for group in self._matches.values():
|
||||
total_matches += len(group)
|
||||
return total_matches
|
||||
return self._total_matches
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
"""Return device specific state attributes."""
|
||||
return {
|
||||
ATTR_MATCHES: self._matches
|
||||
ATTR_MATCHES: self._matches,
|
||||
ATTR_TOTAL_MATCHES: self._total_matches
|
||||
}
|
||||
|
||||
def process_image(self, image):
|
||||
"""Process the image."""
|
||||
self._last_image = image
|
||||
self._matches = process_image(
|
||||
image, self._classifier_configs, False)
|
||||
import cv2 # pylint: disable=import-error
|
||||
import numpy
|
||||
|
||||
# pylint: disable=no-member
|
||||
cv_image = cv2.imdecode(numpy.asarray(bytearray(image)),
|
||||
cv2.IMREAD_UNCHANGED)
|
||||
|
||||
for name, classifier in self._classifiers.items():
|
||||
scale = DEFAULT_SCALE
|
||||
neighbors = DEFAULT_NEIGHBORS
|
||||
min_size = DEFAULT_MIN_SIZE
|
||||
if isinstance(classifier, dict):
|
||||
path = classifier[CONF_FILE]
|
||||
scale = classifier.get(CONF_SCALE, scale)
|
||||
neighbors = classifier.get(CONF_NEIGHBORS, neighbors)
|
||||
min_size = classifier.get(CONF_MIN_SIZE, min_size)
|
||||
else:
|
||||
path = classifier
|
||||
|
||||
# pylint: disable=no-member
|
||||
cascade = cv2.CascadeClassifier(path)
|
||||
|
||||
detections = cascade.detectMultiScale(
|
||||
cv_image,
|
||||
scaleFactor=scale,
|
||||
minNeighbors=neighbors,
|
||||
minSize=min_size)
|
||||
matches = {}
|
||||
total_matches = 0
|
||||
regions = []
|
||||
# pylint: disable=invalid-name
|
||||
for (x, y, w, h) in detections:
|
||||
regions.append((int(x), int(y), int(w), int(h)))
|
||||
total_matches += 1
|
||||
|
||||
matches[name] = regions
|
||||
|
||||
self._matches = matches
|
||||
self._total_matches = total_matches
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue