Opencv (#7261)
* OpenCV * Fix * Type-o * Remove unused opencv camera component discovery.
This commit is contained in:
parent
70ad06d71c
commit
b321e0ef80
4 changed files with 314 additions and 0 deletions
|
@ -77,6 +77,9 @@ omit =
|
|||
homeassistant/components/octoprint.py
|
||||
homeassistant/components/*/octoprint.py
|
||||
|
||||
homeassistant/components/opencv.py
|
||||
homeassistant/components/*/opencv.py
|
||||
|
||||
homeassistant/components/qwikswitch.py
|
||||
homeassistant/components/*/qwikswitch.py
|
||||
|
||||
|
|
120
homeassistant/components/image_processing/opencv.py
Normal file
120
homeassistant/components/image_processing/opencv.py
Normal file
|
@ -0,0 +1,120 @@
|
|||
"""
|
||||
Component that performs OpenCV classification on images.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/image_processing.opencv/
|
||||
"""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ['opencv']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_TIMEOUT = 10
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=2)
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(CLASSIFIER_GROUP_CONFIG)
|
||||
|
||||
|
||||
def _create_processor_from_config(hass, camera_entity, config):
|
||||
"""Create an OpenCV processor from configurtaion."""
|
||||
classifier_config = config[CONF_CLASSIFIER]
|
||||
name = '{} {}'.format(
|
||||
config[CONF_NAME],
|
||||
split_entity_id(camera_entity)[1].replace('_', ' '))
|
||||
|
||||
processor = OpenCVImageProcessor(
|
||||
hass,
|
||||
camera_entity,
|
||||
name,
|
||||
classifier_config,
|
||||
)
|
||||
|
||||
return processor
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the OpenCV image processing platform."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
devices = []
|
||||
for camera_entity in discovery_info[CONF_ENTITY_ID]:
|
||||
devices.append(
|
||||
_create_processor_from_config(
|
||||
hass,
|
||||
camera_entity,
|
||||
discovery_info))
|
||||
|
||||
add_devices(devices)
|
||||
|
||||
|
||||
class OpenCVImageProcessor(ImageProcessingEntity):
|
||||
"""Representation of an OpenCV image processor."""
|
||||
|
||||
def __init__(self, hass, camera_entity, name, classifier_configs):
|
||||
"""Initialize the OpenCV entity."""
|
||||
self.hass = hass
|
||||
self._camera_entity = camera_entity
|
||||
self._name = name
|
||||
self._classifier_configs = classifier_configs
|
||||
self._matches = {}
|
||||
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."""
|
||||
return self._camera_entity
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the image processor."""
|
||||
return self._name
|
||||
|
||||
@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
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
"""Return device specific state attributes."""
|
||||
return {
|
||||
ATTR_MATCHES: self._matches
|
||||
}
|
||||
|
||||
def process_image(self, image):
|
||||
"""Process the image."""
|
||||
self._last_image = image
|
||||
self._matches = process_image(image,
|
||||
self._classifier_configs,
|
||||
False)
|
182
homeassistant/components/opencv.py
Normal file
182
homeassistant/components/opencv.py
Normal file
|
@ -0,0 +1,182 @@
|
|||
"""
|
||||
Support for OpenCV image/video processing.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/opencv/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_NAME,
|
||||
CONF_ENTITY_ID,
|
||||
CONF_FILE_PATH
|
||||
)
|
||||
from homeassistant.helpers import (
|
||||
discovery,
|
||||
config_validation as cv,
|
||||
)
|
||||
|
||||
REQUIREMENTS = ['opencv-python==3.2.0.6', 'numpy==1.12.0', 'urllib3==1.21']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_MATCHES = 'matches'
|
||||
|
||||
BASE_PATH = os.path.realpath(__file__)
|
||||
|
||||
CASCADE_URL = \
|
||||
'https://raw.githubusercontent.com/opencv/opencv/master/data/' +\
|
||||
'lbpcascades/lbpcascade_frontalface.xml'
|
||||
|
||||
CONF_CLASSIFIER = 'classifier'
|
||||
CONF_COLOR = 'color'
|
||||
CONF_GROUPS = 'classifier_group'
|
||||
CONF_MIN_SIZE = 'min_size'
|
||||
CONF_NEIGHBORS = 'neighbors'
|
||||
CONF_SCALE = 'scale'
|
||||
|
||||
DATA_CLASSIFIER_GROUPS = 'classifier_groups'
|
||||
|
||||
DEFAULT_COLOR = (255, 255, 0)
|
||||
DEFAULT_CLASSIFIER_PATH = os.path.join(
|
||||
os.path.dirname(BASE_PATH),
|
||||
'lbp_frontalface.xml')
|
||||
DEFAULT_NAME = 'OpenCV'
|
||||
DEFAULT_MIN_SIZE = (30, 30)
|
||||
DEFAULT_NEIGHBORS = 4
|
||||
DEFAULT_SCALE = 1.1
|
||||
|
||||
DOMAIN = 'opencv'
|
||||
|
||||
CLASSIFIER_GROUP_CONFIG = {
|
||||
vol.Required(CONF_CLASSIFIER): vol.All(
|
||||
cv.ensure_list,
|
||||
[vol.Schema({
|
||||
vol.Optional(CONF_COLOR, default=DEFAULT_COLOR):
|
||||
vol.Schema((int, int, int)),
|
||||
vol.Optional(CONF_FILE_PATH, default=DEFAULT_CLASSIFIER_PATH):
|
||||
cv.isfile,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME):
|
||||
cv.string,
|
||||
vol.Optional(CONF_MIN_SIZE, default=DEFAULT_MIN_SIZE):
|
||||
vol.Schema((int, int)),
|
||||
vol.Optional(CONF_NEIGHBORS, default=DEFAULT_NEIGHBORS):
|
||||
cv.positive_int,
|
||||
vol.Optional(CONF_SCALE, default=DEFAULT_SCALE):
|
||||
float
|
||||
})]),
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_ids,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
}
|
||||
CLASSIFIER_GROUP_SCHEMA = vol.Schema(CLASSIFIER_GROUP_CONFIG)
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
vol.Required(CONF_GROUPS): vol.All(
|
||||
cv.ensure_list,
|
||||
[CLASSIFIER_GROUP_SCHEMA]
|
||||
),
|
||||
})
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
# NOTE:
|
||||
# pylint cannot find any of the members of cv2, using disable=no-member
|
||||
# to pass linting
|
||||
|
||||
|
||||
def cv_image_to_bytes(cv_image):
|
||||
"""Convert OpenCV image to bytes."""
|
||||
import cv2
|
||||
|
||||
# pylint: disable=no-member
|
||||
encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), 90]
|
||||
# pylint: disable=no-member
|
||||
success, data = cv2.imencode('.jpg', cv_image, encode_param)
|
||||
|
||||
if success:
|
||||
return data.tobytes()
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def cv_image_from_bytes(image):
|
||||
"""Convert image bytes to OpenCV image."""
|
||||
import cv2
|
||||
import numpy
|
||||
|
||||
# pylint: disable=no-member
|
||||
return cv2.imdecode(numpy.asarray(bytearray(image)), cv2.IMREAD_UNCHANGED)
|
||||
|
||||
|
||||
def process_image(image, classifier_group, is_camera):
|
||||
"""Process the image given a classifier group."""
|
||||
import cv2
|
||||
import numpy
|
||||
|
||||
# pylint: disable=no-member
|
||||
cv_image = cv2.imdecode(numpy.asarray(bytearray(image)),
|
||||
cv2.IMREAD_UNCHANGED)
|
||||
group_matches = {}
|
||||
for classifier_config in classifier_group:
|
||||
classifier_path = classifier_config[CONF_FILE_PATH]
|
||||
classifier_name = classifier_config[CONF_NAME]
|
||||
color = classifier_config[CONF_COLOR]
|
||||
scale = classifier_config[CONF_SCALE]
|
||||
neighbors = classifier_config[CONF_NEIGHBORS]
|
||||
min_size = classifier_config[CONF_MIN_SIZE]
|
||||
|
||||
# pylint: disable=no-member
|
||||
classifier = cv2.CascadeClassifier(classifier_path)
|
||||
|
||||
detections = classifier.detectMultiScale(cv_image,
|
||||
scaleFactor=scale,
|
||||
minNeighbors=neighbors,
|
||||
minSize=min_size)
|
||||
regions = []
|
||||
# pylint: disable=invalid-name
|
||||
for (x, y, w, h) in detections:
|
||||
if is_camera:
|
||||
# pylint: disable=no-member
|
||||
cv2.rectangle(cv_image,
|
||||
(x, y),
|
||||
(x + w, y + h),
|
||||
color,
|
||||
2)
|
||||
else:
|
||||
regions.append((int(x), int(y), int(w), int(h)))
|
||||
group_matches[classifier_name] = regions
|
||||
|
||||
if is_camera:
|
||||
return cv_image_to_bytes(cv_image)
|
||||
else:
|
||||
return group_matches
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass, config):
|
||||
"""Set up the OpenCV platform entities."""
|
||||
_LOGGER.info('Async setup for opencv')
|
||||
if not os.path.isfile(DEFAULT_CLASSIFIER_PATH):
|
||||
_LOGGER.info('Downloading default classifier')
|
||||
import urllib3
|
||||
|
||||
http = urllib3.PoolManager()
|
||||
request = http.request('GET', CASCADE_URL, preload_content=False)
|
||||
|
||||
with open(DEFAULT_CLASSIFIER_PATH, 'wb') as out:
|
||||
while True:
|
||||
data = request.read(1028)
|
||||
if not data:
|
||||
break
|
||||
out.write(data)
|
||||
|
||||
request.release_conn()
|
||||
|
||||
for group in config[DOMAIN][CONF_GROUPS]:
|
||||
discovery.load_platform(hass, 'image_processing', DOMAIN, group)
|
||||
|
||||
return True
|
|
@ -408,12 +408,18 @@ netdisco==1.0.0rc3
|
|||
# homeassistant.components.sensor.neurio_energy
|
||||
neurio==0.3.1
|
||||
|
||||
# homeassistant.components.opencv
|
||||
numpy==1.12.0
|
||||
|
||||
# homeassistant.components.google
|
||||
oauth2client==4.0.0
|
||||
|
||||
# homeassistant.components.climate.oem
|
||||
oemthermostat==1.1
|
||||
|
||||
# homeassistant.components.opencv
|
||||
opencv-python==3.2.0.6
|
||||
|
||||
# homeassistant.components.sensor.openevse
|
||||
openevsewifi==0.4
|
||||
|
||||
|
@ -802,6 +808,9 @@ uber_rides==0.4.1
|
|||
# homeassistant.components.sensor.ups
|
||||
upsmychoice==1.0.2
|
||||
|
||||
# homeassistant.components.opencv
|
||||
urllib3==1.21
|
||||
|
||||
# homeassistant.components.camera.uvc
|
||||
uvcclient==0.10.0
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue