-
-
Notifications
You must be signed in to change notification settings - Fork 38k
Adds facebox #14307
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adds facebox #14307
Changes from 8 commits
fd685ba
4e6d676
e0608ef
8286f09
38b9eed
26128d2
afdab60
8e89ec3
faacce1
2568d34
a3ee098
d9ddc6e
9bfe89a
714cfac
0288897
07d6b27
3ce626d
8d39604
1c14d22
673a237
c782381
a012b81
8145a8e
753419d
5537585
107cf82
3afd19b
89d7896
2e002e1
64b5739
146fc59
c038ede
013d51e
50806d3
39b7223
3be6ae6
6960ea1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| """ | ||
| Component that will perform facial detection and identification via a local | ||
| facebox classifier. | ||
|
|
||
| For more details about this platform, please refer to the documentation at | ||
| https://home-assistant.io/components/image_processing.facebox | ||
| """ | ||
| import base64 | ||
| import logging | ||
|
|
||
| import requests | ||
| import voluptuous as vol | ||
|
|
||
| from homeassistant.core import split_entity_id | ||
| import homeassistant.helpers.config_validation as cv | ||
| from homeassistant.components.image_processing import ( | ||
| PLATFORM_SCHEMA, ImageProcessingFaceEntity, CONF_SOURCE, CONF_ENTITY_ID, | ||
| CONF_NAME) | ||
| from homeassistant.const import (CONF_IP_ADDRESS, CONF_PORT) | ||
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
| CLASSIFIER = 'facebox' | ||
|
|
||
| PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ | ||
| vol.Required(CONF_IP_ADDRESS): cv.string, | ||
| vol.Required(CONF_PORT): cv.port, | ||
| }) | ||
|
|
||
|
|
||
| def setup_platform(hass, config, add_devices, discovery_info=None): | ||
| """Set up the classifier.""" | ||
| entities = [] | ||
| for camera in config[CONF_SOURCE]: | ||
| entities.append(FaceClassifyEntity( | ||
| config[CONF_IP_ADDRESS], | ||
| config[CONF_PORT], | ||
| camera[CONF_ENTITY_ID], | ||
| camera.get(CONF_NAME) | ||
| )) | ||
| add_devices(entities) | ||
|
|
||
|
|
||
| class FaceClassifyEntity(ImageProcessingFaceEntity): | ||
| """Perform a face classification.""" | ||
|
|
||
| def __init__(self, ip, port, camera_entity, name=None): | ||
| """Init with the API key and model id""" | ||
| super().__init__() | ||
| self._url = "http://{}:{}/{}/check".format(ip, port, CLASSIFIER) | ||
| self._camera = camera_entity | ||
| if name: | ||
| self._name = name | ||
| else: | ||
| camera_name = split_entity_id(camera_entity)[1] | ||
| self._name = "{} {}".format( | ||
| CLASSIFIER, camera_name) | ||
| self._matched = {} | ||
|
|
||
| def process_image(self, image): | ||
| """Process an image.""" | ||
| response = {} | ||
| try: | ||
| response = requests.post( | ||
| self._url, | ||
| json=self.encode_image(image), | ||
| timeout=9 | ||
| ).json() | ||
| except requests.exceptions.ConnectionError: | ||
| _LOGGER.error("ConnectionError: Is {} running?".format(CLASSIFIER)) | ||
| response['success'] = False | ||
|
|
||
| if response['success']: | ||
| faces = response['faces'] | ||
| total = response['facesCount'] | ||
| self.process_faces(faces, total) | ||
| self._matched = self.get_matched_faces(faces) | ||
|
|
||
| else: | ||
| self.total_faces = None | ||
| self.faces = [] | ||
| self._matched = {} | ||
|
|
||
| def encode_image(self, image): | ||
| """base64 encode an image stream.""" | ||
| base64_img = base64.b64encode(image).decode('ascii') | ||
| return {"base64": base64_img} | ||
|
|
||
| def get_matched_faces(self, faces): | ||
| """Return the name and rounded confidence of matched faces.""" | ||
| return {face['name']: round(face['confidence'], 2) | ||
| for face in faces if face['matched']} | ||
|
|
||
| @property | ||
| def camera_entity(self): | ||
| """Return camera entity id from process pictures.""" | ||
| return self._camera | ||
|
|
||
| @property | ||
| def name(self): | ||
| """Return the name of the sensor.""" | ||
| return self._name | ||
|
|
||
| @property | ||
| def device_state_attributes(self): | ||
| """Return the classifier attributes.""" | ||
| return { | ||
| 'matched_faces': self._matched, | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| """The tests for the facebox component.""" | ||
| import requests_mock | ||
|
|
||
| from homeassistant.const import (CONF_IP_ADDRESS, CONF_PORT) | ||
| from homeassistant.setup import async_setup_component | ||
| import homeassistant.components.image_processing as ip | ||
|
|
||
| from tests.common import assert_setup_component | ||
|
|
||
| MOCK_IP = '192.168.0.1' | ||
| MOCK_PORT = '8080' | ||
|
|
||
| MOCK_RESPONSE = """ | ||
| {"facesCount": 1, | ||
| "success": True, | ||
| "faces":[{'confidence': 0.5812028911604818, | ||
| 'id': 'john.jpg', | ||
| 'matched': True, | ||
| 'name': 'John Lennon', | ||
| 'rect': {'height': 75, 'left': 63, 'top': 262, 'width': 74} | ||
| }] | ||
| """ | ||
|
|
||
| VALID_ENTITY_ID = 'image_processing.facebox_demo_camera' | ||
| VALID_CONFIG = { | ||
| ip.DOMAIN: { | ||
| 'platform': 'facebox', | ||
| CONF_IP_ADDRESS: MOCK_IP, | ||
| CONF_PORT: MOCK_PORT, | ||
| ip.CONF_SOURCE: { | ||
| ip.CONF_ENTITY_ID: 'camera.demo_camera'} | ||
| }, | ||
| 'camera': { | ||
| 'platform': 'demo' | ||
| } | ||
| } | ||
|
|
||
|
|
||
| async def test_setup_platform(hass): | ||
| """Setup platform with one entity.""" | ||
|
|
||
| await async_setup_component(hass, ip.DOMAIN, VALID_CONFIG) | ||
|
|
||
| assert hass.states.get(VALID_ENTITY_ID) | ||
|
|
||
|
|
||
| async def test_process_image(hass): | ||
| """Test processing of an image.""" | ||
|
|
||
| await async_setup_component(hass, ip.DOMAIN, VALID_CONFIG) | ||
| assert hass.states.get(VALID_ENTITY_ID) | ||
|
|
||
| with requests_mock.Mocker() as mock_req: | ||
| url = "http://{}:{}/facebox/check".format(MOCK_IP, MOCK_PORT) | ||
| mock_req.get(url, text=MOCK_RESPONSE) | ||
| ip.scan(hass, entity_id=VALID_ENTITY_ID) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Call the service directly with
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok |
||
| hass.block_till_done() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok |
||
|
|
||
| state = hass.states.get(VALID_ENTITY_ID) | ||
| assert state.state == '1' | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'tests.common.assert_setup_component' imported but unused