-
-
Notifications
You must be signed in to change notification settings - Fork 37.8k
WIP Adds facebox_face_detect #14279
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
WIP Adds facebox_face_detect #14279
Changes from 2 commits
e836674
0adb240
26c76e3
727ab95
663aeb1
bcd8a69
6520635
fadff18
6fa60c4
5350682
e9b997d
6c456ad
24ec8c5
e7aea5c
c076dbe
2cb9e2d
a566804
2b53729
8cb1e17
44be801
2bc87bf
cb839ef
fc1f6ee
7566bb5
2e3a27e
c497515
1b71ce3
833508f
403a546
9d0251c
c42c668
9fb2bf7
7da1d75
9d34e8c
a06f610
52a48b3
b5bae17
f2a17a5
03c3480
aba143a
7f1b591
daeccfe
c704cea
c23cc0e
d1460de
26f5eb1
fe92c1b
9859840
a7a3cff
54ccbbc
5f8f666
b49d984
fa0ad7b
c80b752
36cf212
f98525a
e37fd5b
7900ba3
bb76ba6
255a85a
3544704
75bf483
5ade84d
ec3ce4c
95d27bd
af8cd63
8410b63
1a93622
f6e29a6
4d08588
b9e8931
ed7d790
2baaeed
02bba10
2e8eaf4
64ba2c6
a4b6983
83e342d
2326312
f3411f8
e623e87
eaf710f
5cbf7c9
f5cf660
710f841
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,105 @@ | ||
| """ | ||
| Component that will perform facial detection via a local facebox instance. | ||
|
|
||
| For more details about this platform, please refer to the documentation at | ||
| https://home-assistant.io/components/image_processing.facebox_face_detect | ||
| """ | ||
| import base64 | ||
| import requests | ||
| import logging | ||
| import time | ||
| 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, CONF_SOURCE, CONF_ENTITY_ID, | ||
| CONF_NAME) | ||
| from homeassistant.const import (CONF_IP_ADDRESS, CONF_PORT) | ||
| from homeassistant.components.image_processing.microsoft_face_identify import ( | ||
| ImageProcessingFaceEntity) | ||
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
| PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ | ||
| vol.Required(CONF_IP_ADDRESS): cv.string, | ||
| vol.Required(CONF_PORT): cv.string, | ||
| }) | ||
|
|
||
|
|
||
| def setup_platform(hass, config, add_devices, discovery_info=None): | ||
| """Set up the classifier.""" | ||
| entities = [] | ||
| for camera in config[CONF_SOURCE]: | ||
| entities.append(FaceboxFaceDetectEntity( | ||
| config[CONF_IP_ADDRESS], | ||
| config[CONF_PORT], | ||
| camera[CONF_ENTITY_ID], | ||
| camera.get(CONF_NAME) | ||
| )) | ||
| add_devices(entities) | ||
|
|
||
|
|
||
| class FaceboxFaceDetectEntity(ImageProcessingFaceEntity): | ||
| """Perform a classification via a Facebox.""" | ||
|
|
||
| def __init__(self, ip, port, camera_entity, name=None): | ||
| """Init with the API key and model id""" | ||
| super().__init__() | ||
| self._url = "http://{}:{}/facebox/check".format(ip, port) | ||
| self._camera = camera_entity | ||
| if name: | ||
| self._name = name | ||
| else: | ||
| self._name = "Facebox {0}".format( | ||
| split_entity_id(camera_entity)[1]) | ||
| self._response_time = None | ||
| self.total_faces = 0 | ||
| self.faces = [] | ||
|
|
||
| def process_image(self, image): | ||
| """Process an image.""" | ||
| timer_start = time.perf_counter() | ||
| response = {} | ||
| try: | ||
| response = requests.post( | ||
| self._url, | ||
| json=self.encode_image(image), | ||
| timeout=30 | ||
| ).json() | ||
| except requests.exceptions.ConnectionError: | ||
| _LOGGER.error("ConnectionError: Is Facebox running?") | ||
| response['success'] = False | ||
|
|
||
| if response['success']: | ||
| elapsed_time = time.perf_counter() - timer_start | ||
| self._response_time = "{} seconds".format( | ||
|
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. Please update the time only if you find faces. Otherwise it flush the state database.
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. you mean if
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. yes. If there is no faces found, he should also not update time.
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. @pvizeli That would mean that the |
||
| str(round(elapsed_time, 1))) | ||
| self.total_faces = response['facesCount'] | ||
| self.faces = response['faces'] | ||
|
|
||
| else: | ||
| self.total_faces = "Request_failed" | ||
| self.faces = [] | ||
|
|
||
| def encode_image(self, image): | ||
| """base64 encode an image stream.""" | ||
| base64_img = base64.b64encode(image).decode('ascii') | ||
| return {"base64": base64_img} | ||
|
|
||
| @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 other state attributes.""" | ||
| return { | ||
| 'response_time': self._response_time, | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| """The tests for the facebox component.""" | ||
| import requests_mock | ||
|
|
||
| from homeassistant.const import (CONF_IP_ADDRESS, CONF_PORT) | ||
| from homeassistant.setup import setup_component | ||
| import homeassistant.components.image_processing as ip | ||
|
|
||
| from tests.common import ( | ||
| get_test_home_assistant, assert_setup_component) | ||
|
|
||
| MOCK_IP = '192.168.0.1' | ||
| MOCK_PORT = '8080' | ||
|
|
||
| MOCK_RESPONSE = """ | ||
| {"facesCount": 1, | ||
| "success": True, | ||
| "faces":['face_data']} | ||
| """ | ||
|
|
||
| VALID_ENTITY_ID = 'image_processing.facebox_demo_camera' | ||
| VALID_CONFIG = { | ||
| ip.DOMAIN: { | ||
| 'platform': 'facebox_face_detect', | ||
| CONF_IP_ADDRESS: MOCK_IP, | ||
| CONF_PORT: MOCK_PORT, | ||
| ip.CONF_SOURCE: { | ||
| ip.CONF_ENTITY_ID: 'camera.demo_camera'} | ||
| }, | ||
| 'camera': { | ||
| 'platform': 'demo' | ||
| } | ||
| } | ||
|
|
||
|
|
||
| class TestFaceboxSetup(object): | ||
| """Test class for image processing.""" | ||
|
|
||
| def setup_method(self): | ||
| """Setup things to be run when tests are started.""" | ||
| self.hass = get_test_home_assistant() | ||
|
|
||
| def test_setup_platform(self): | ||
| """Setup platform with one entity.""" | ||
|
|
||
| with assert_setup_component(1, ip.DOMAIN): | ||
| setup_component(self.hass, ip.DOMAIN, VALID_CONFIG) | ||
|
|
||
| assert self.hass.states.get(VALID_ENTITY_ID) | ||
|
|
||
| def test_process_image(self): | ||
| """Test processing of an image.""" | ||
|
|
||
| with assert_setup_component(1, ip.DOMAIN): | ||
| setup_component(self.hass, ip.DOMAIN, VALID_CONFIG) | ||
| assert self.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(self.hass, entity_id=VALID_ENTITY_ID) | ||
| self.hass.block_till_done() | ||
|
|
||
| state = self.hass.states.get(VALID_ENTITY_ID) | ||
| assert state.state == '1' | ||
|
|
||
| def teardown_method(self): | ||
| """Stop everything that was started.""" | ||
| self.hass.stop() |
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.
Why is the base face entity class located in the microsoft platform? This is weird. @pvizeli do you know? Normally we should not rely on another platform in a platform. The base entity class should probably move to
homeassistant.components.image_processing.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.
@MartinHjelmare I agree and propose a refactor
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.
@MartinHjelmare that is correct. On my inital PR it was inside component but I need remove this. In a later discusion about this designe, paulus agree that I can move back the code. But I found never time for this.
Uh oh!
There was an error while loading. Please reload this page.
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.
@robmarkcole if you have time, a refactor in a separate PR would be great.
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.
Sure
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.
Refactor in #14296