core/homeassistant/components/openalpr_local/image_processing.py

216 lines
5.6 KiB
Python
Raw Normal View History

"""Component that will help set the OpenALPR local for ALPR processing."""
import asyncio
import io
import re
import voluptuous as vol
from homeassistant.components.image_processing import (
ATTR_CONFIDENCE,
ATTR_ENTITY_ID,
2019-07-31 19:25:30 +00:00
CONF_CONFIDENCE,
CONF_ENTITY_ID,
CONF_NAME,
CONF_SOURCE,
PLATFORM_SCHEMA,
ImageProcessingEntity,
2019-07-31 19:25:30 +00:00
)
from homeassistant.const import CONF_REGION
from homeassistant.core import callback, split_entity_id
import homeassistant.helpers.config_validation as cv
from homeassistant.util.async_ import run_callback_threadsafe
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",
]
CONF_ALPR_BIN = "alpr_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 extra_state_attributes(self):
"""Return device specific state attributes."""
return {ATTR_PLATES: self.plates, ATTR_VEHICLES: self.vehicles}
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:
2020-04-07 21:14:28 +00:00
self._name = f"OpenAlpr {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,
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)