core/homeassistant/components/openalpr_local/image_processing.py

222 lines
5.7 KiB
Python
Raw Normal View History

"""Component that will help set the OpenALPR local for ALPR processing."""
import asyncio
import io
2017-05-17 07:14:11 +00:00
import logging
import re
import voluptuous as vol
2017-05-17 07:14:11 +00:00
import homeassistant.helpers.config_validation as cv
from homeassistant.core import split_entity_id, callback
from homeassistant.const import CONF_REGION
from homeassistant.components.image_processing import (
2019-07-31 19:25:30 +00:00
PLATFORM_SCHEMA,
ImageProcessingEntity,
CONF_CONFIDENCE,
CONF_SOURCE,
CONF_ENTITY_ID,
CONF_NAME,
ATTR_ENTITY_ID,
ATTR_CONFIDENCE,
)
from homeassistant.util.async_ import run_callback_threadsafe
_LOGGER = logging.getLogger(__name__)
RE_ALPR_PLATE = re.compile(r"^plate\d*:")
RE_ALPR_RESULT = re.compile(r"- (\w*)\s*confidence: (\d*.\d*)")
2019-07-31 19:25:30 +00:00
EVENT_FOUND_PLATE = "image_processing.found_plate"
2019-07-31 19:25:30 +00:00
ATTR_PLATE = "plate"
ATTR_PLATES = "plates"
ATTR_VEHICLES = "vehicles"
OPENALPR_REGIONS = [
2019-07-31 19:25:30 +00:00
"au",
"auwide",
"br",
"eu",
"fr",
"gb",
"kr",
"kr2",
"mx",
"sg",
"us",
"vn2",
]
2019-07-31 19:25:30 +00:00
CONF_ALPR_BIN = "alp_bin"
2019-07-31 19:25:30 +00:00
DEFAULT_BINARY = "alpr"
2019-07-31 19:25:30 +00:00
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_REGION): vol.All(vol.Lower, vol.In(OPENALPR_REGIONS)),
vol.Optional(CONF_ALPR_BIN, default=DEFAULT_BINARY): cv.string,
}
)
2019-07-31 19:25:30 +00:00
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the OpenALPR local platform."""
2019-07-31 19:25:30 +00:00
command = [config[CONF_ALPR_BIN], "-c", config[CONF_REGION], "-"]
confidence = config[CONF_CONFIDENCE]
entities = []
for camera in config[CONF_SOURCE]:
2019-07-31 19:25:30 +00:00
entities.append(
OpenAlprLocalEntity(
camera[CONF_ENTITY_ID], command, confidence, camera.get(CONF_NAME)
)
)
async_add_entities(entities)
class ImageProcessingAlprEntity(ImageProcessingEntity):
"""Base entity class for ALPR image processing."""
def __init__(self):
2017-05-17 07:14:11 +00:00
"""Initialize base ALPR entity."""
self.plates = {}
self.vehicles = 0
@property
def state(self):
"""Return the state of the entity."""
confidence = 0
plate = None
# search high plate
for i_pl, i_co in self.plates.items():
if i_co > confidence:
confidence = i_co
plate = i_pl
return plate
@property
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
2019-07-31 19:25:30 +00:00
return "alpr"
@property
def state_attributes(self):
"""Return device specific state attributes."""
2019-07-31 19:25:30 +00:00
attr = {ATTR_PLATES: self.plates, ATTR_VEHICLES: self.vehicles}
return attr
def process_plates(self, plates, vehicles):
"""Send event with new plates and store data."""
run_callback_threadsafe(
self.hass.loop, self.async_process_plates, plates, vehicles
).result()
@callback
def async_process_plates(self, plates, vehicles):
"""Send event with new plates and store data.
plates are a dict in follow format:
{ 'plate': confidence }
This method must be run in the event loop.
"""
2019-07-31 19:25:30 +00:00
plates = {
plate: confidence
for plate, confidence in plates.items()
if confidence >= self.confidence
}
new_plates = set(plates) - set(self.plates)
2017-05-17 07:14:11 +00:00
# Send events
for i_plate in new_plates:
self.hass.async_add_job(
2019-07-31 19:25:30 +00:00
self.hass.bus.async_fire,
EVENT_FOUND_PLATE,
{
ATTR_PLATE: i_plate,
ATTR_ENTITY_ID: self.entity_id,
ATTR_CONFIDENCE: plates.get(i_plate),
2019-07-31 19:25:30 +00:00
},
)
2017-05-17 07:14:11 +00:00
# Update entity store
self.plates = plates
self.vehicles = vehicles
class OpenAlprLocalEntity(ImageProcessingAlprEntity):
"""OpenALPR local api entity."""
def __init__(self, camera_entity, command, confidence, name=None):
"""Initialize OpenALPR local API."""
super().__init__()
self._cmd = command
self._camera = camera_entity
self._confidence = confidence
if name:
self._name = name
else:
2019-07-31 19:25:30 +00:00
self._name = "OpenAlpr {0}".format(split_entity_id(camera_entity)[1])
@property
def confidence(self):
"""Return minimum confidence for send events."""
return self._confidence
@property
def camera_entity(self):
"""Return camera entity id from process pictures."""
return self._camera
@property
def name(self):
"""Return the name of the entity."""
return self._name
async def async_process_image(self, image):
"""Process image.
This method is a coroutine.
"""
result = {}
vehicles = 0
alpr = await asyncio.create_subprocess_exec(
*self._cmd,
loop=self.hass.loop,
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
2019-07-31 19:25:30 +00:00
stderr=asyncio.subprocess.DEVNULL,
)
2017-05-17 07:14:11 +00:00
# Send image
stdout, _ = await alpr.communicate(input=image)
2019-07-31 19:25:30 +00:00
stdout = io.StringIO(str(stdout, "utf-8"))
while True:
line = stdout.readline()
if not line:
break
new_plates = RE_ALPR_PLATE.search(line)
new_result = RE_ALPR_RESULT.search(line)
2017-05-17 07:14:11 +00:00
# Found new vehicle
if new_plates:
vehicles += 1
continue
2017-05-17 07:14:11 +00:00
# Found plate result
if new_result:
try:
2019-07-31 19:25:30 +00:00
result.update({new_result.group(1): float(new_result.group(2))})
except ValueError:
continue
self.async_process_plates(result, vehicles)