├── __init__.py ├── proto ├── __init__.py ├── location_service.proto ├── location_service_pb2_grpc.py └── location_service_pb2.py ├── pip └── requirements.txt ├── docs └── target_img.png ├── bin ├── color-picker.sh ├── hat-controller.sh ├── firmata-controller.sh ├── dual-object-tracker.sh └── single-object-tracker.sh ├── Makefile ├── hat_servo.py ├── simple_location_reader.py ├── locations.py ├── http_reporter.py ├── location_mqtt_subscriber.py ├── .gitignore ├── hat_controller.py ├── location_mqtt_publisher.py ├── plot_locations.py ├── blinkt_subscriber.py ├── location_server.py ├── firmata_controller.py ├── vertical_object_tracker.py ├── firmata_servo.py ├── draw_locations.py ├── generic_servo.py ├── location_client.py ├── generic_filter.py ├── multi_object_tracker.py ├── calibrate_servo.py ├── single_object_filter.py ├── color_picker.py ├── dual_object_filter.py ├── object_tracker.py └── README.md /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /proto/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pip/requirements.txt: -------------------------------------------------------------------------------- 1 | imutils 2 | grpcio 3 | blinkt 4 | 5 | -------------------------------------------------------------------------------- /docs/target_img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/athenian-programming/object-tracking/HEAD/docs/target_img.png -------------------------------------------------------------------------------- /bin/color-picker.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | export PYTHONPATH=${PYTHONPATH}:../common-robotics 4 | ./color_picker.py -w 800 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | py-stubs: 3 | python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. ./proto/location_service.proto 4 | -------------------------------------------------------------------------------- /bin/hat-controller.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | export PYTHONPATH=${PYTHONPATH}:../common-robotics 4 | ./hat_controller.py --grpc localhost 5 | -------------------------------------------------------------------------------- /bin/firmata-controller.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | export PYTHONPATH=${PYTHONPATH}:../common-robotics 4 | ./firmata_controller.py --grpc pleiku -s ttyACM0 5 | -------------------------------------------------------------------------------- /bin/dual-object-tracker.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | export PYTHONPATH=${PYTHONPATH}:../common-robotics 4 | ./dual_object_tracker.py --bgr "174, 56, 5" --display --leds --usb --horizontal --vertical 5 | -------------------------------------------------------------------------------- /bin/single-object-tracker.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | export PYTHONPATH=${PYTHONPATH}:../common-robotics 4 | ./single_object_tracker.py --bgr "174, 56, 5" --display --flipy --horizontal --vertical 5 | -------------------------------------------------------------------------------- /proto/location_service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package opencv_object_tracking; 3 | 4 | message ClientInfo { 5 | string info = 1; 6 | } 7 | 8 | message ServerInfo { 9 | string info = 1; 10 | } 11 | 12 | message Location { 13 | int32 id = 1; 14 | int32 x = 2; 15 | int32 y = 3; 16 | int32 width = 4; 17 | int32 height = 5; 18 | int32 middle_inc = 6; 19 | } 20 | 21 | service LocationService { 22 | 23 | rpc registerClient (ClientInfo) returns (ServerInfo) { 24 | } 25 | 26 | rpc getLocations (ClientInfo) returns (stream Location) { 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /hat_servo.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from generic_servo import Servo 4 | 5 | 6 | class HatServo(Servo): 7 | def __init__(self, name, hat_func, alternate, secs_per_180, pix_per_degree): 8 | super(HatServo, self).__init__(name, alternate, secs_per_180, pix_per_degree) 9 | self.__hat_func = hat_func 10 | self.__currpos = None 11 | self.jiggle() 12 | 13 | def jiggle(self): 14 | # Provoke an update from the color tracker 15 | self.set_angle(80, pause=.1) 16 | self.set_angle(90, pause=.1) 17 | 18 | def get_currpos(self): 19 | return self.__currpos 20 | 21 | def set_angle(self, val, pause=None): 22 | # Pan Tilt Hat servo takes value -90 to 90. pyFirmata servo takes 0 - 180. So adjust here 23 | self.__hat_func(val - 90) 24 | if pause is not None: 25 | time.sleep(pause) 26 | self.__currpos = val 27 | -------------------------------------------------------------------------------- /simple_location_reader.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import logging 5 | 6 | import arc852.cli_args as cli 7 | from arc852.cli_args import LOG_LEVEL, GRPC_HOST 8 | from arc852.cli_args import setup_cli_args 9 | from arc852.utils import setup_logging 10 | 11 | from location_client import LocationClient 12 | 13 | logger = logging.getLogger(__name__) 14 | 15 | 16 | def main(): 17 | # Parse CLI args 18 | args = setup_cli_args(cli.grpc_host, cli.log_level) 19 | 20 | # Setup logging 21 | setup_logging(level=args[LOG_LEVEL]) 22 | 23 | with LocationClient(args[GRPC_HOST]) as client: 24 | try: 25 | while True: 26 | print("Got location: {0}".format(client.get_xy())) 27 | except KeyboardInterrupt: 28 | pass 29 | 30 | logger.info("Exiting...") 31 | 32 | 33 | if __name__ == "__main__": 34 | main() 35 | -------------------------------------------------------------------------------- /locations.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import socket 3 | 4 | import grpc 5 | from grpc_support import CannotConnectException 6 | from grpc_support import grpc_url 7 | from utils import setup_logging 8 | 9 | from proto.location_service_pb2 import ClientInfo 10 | from proto.location_service_pb2 import LocationServiceStub 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | class Locations(object): 16 | def __init__(self, hostname): 17 | url = grpc_url(hostname) 18 | try: 19 | channel = grpc.insecure_channel(url) 20 | self._stub = LocationServiceStub(channel) 21 | self._client_info = ClientInfo(info="{0} client".format(socket.gethostname())) 22 | self._server_info = self._stub.registerClient(self._client_info) 23 | except grpc._channel._Rendezvous: 24 | raise CannotConnectException(url) 25 | 26 | def values(self): 27 | return self._stub.getLocations(self._client_info) 28 | 29 | 30 | def main(): 31 | setup_logging() 32 | for val in Locations("localhost").values(): 33 | logger.info("Read value:\n{0}".format(val)) 34 | logger.info("Exiting...") 35 | 36 | 37 | if __name__ == "__main__": 38 | main() 39 | -------------------------------------------------------------------------------- /http_reporter.py: -------------------------------------------------------------------------------- 1 | from threading import Thread 2 | 3 | import arc852.cli_args as cli 4 | from arc852.cli_args import LOG_LEVEL, GRPC_HOST 5 | from arc852.cli_args import setup_cli_args 6 | from arc852.utils import setup_logging 7 | from arc852.utils import sleep 8 | from flask import Flask 9 | 10 | from location_client import LocationClient 11 | 12 | 13 | def main(): 14 | # Parse CLI args 15 | args = setup_cli_args(cli.grpc_host, cli.log_level) 16 | 17 | # Setup logging 18 | setup_logging(level=args[LOG_LEVEL]) 19 | 20 | http = Flask(__name__) 21 | 22 | 23 | @http.route("/count") 24 | def val_count(): 25 | global count 26 | return "Read {0} values".format(count) 27 | 28 | 29 | def read_values(): 30 | global count 31 | while True: 32 | print("Got location: {0}".format(client.get_xy())) 33 | count += 1 34 | 35 | 36 | # Start client 37 | with LocationClient(args[GRPC_HOST]) as client: 38 | 39 | # Run read_values in a thread 40 | count = 0 41 | Thread(target=read_values).start() 42 | 43 | # Run HTTP server in a thread 44 | Thread(target=http.run, kwargs={"port": 8080}).start() 45 | 46 | sleep() 47 | 48 | 49 | if __name__ == "__main__": 50 | main() 51 | -------------------------------------------------------------------------------- /location_mqtt_subscriber.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import arc852.cli_args as cli 4 | from arc852.cli_args import CAMERA_NAME, MQTT_HOST, LOG_LEVEL 5 | from arc852.cli_args import setup_cli_args 6 | from arc852.mqtt_connection import MqttConnection 7 | from arc852.utils import setup_logging, waitForKeyboardInterrupt 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | def main(): 13 | # Parse CLI args 14 | args = setup_cli_args(cli.grpc_host, cli.mqtt_host, cli.camera_name, cli.log_level) 15 | 16 | # Setup logging 17 | setup_logging(level=args[LOG_LEVEL]) 18 | 19 | 20 | # Define MQTT callbacks 21 | def on_connect(mqtt_client, userdata, flags, rc): 22 | logger.info("Connected with result code: {0}".format(rc)) 23 | mqtt_client.subscribe("{0}/#".format(userdata[CAMERA_NAME])) 24 | 25 | 26 | def on_message(mqtt_client, userdata, msg): 27 | logger.info("{0} {1}".format(msg.topic, msg.payload)) 28 | 29 | 30 | # Setup MQTT client 31 | with MqttConnection(args[MQTT_HOST], 32 | userdata={CAMERA_NAME: args[CAMERA_NAME]}, 33 | on_connect=on_connect, 34 | on_message=on_message): 35 | waitForKeyboardInterrupt() 36 | 37 | logger.info("Exiting...") 38 | 39 | 40 | if __name__ == "__main__": 41 | main() 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | .hypothesis/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # IPython Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # dotenv 81 | .env 82 | 83 | # virtualenv 84 | venv/ 85 | ENV/ 86 | 87 | # Spyder project settings 88 | .spyderproject 89 | 90 | # Rope project settings 91 | .ropeproject 92 | -------------------------------------------------------------------------------- /hat_controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import logging 5 | from threading import Thread 6 | 7 | import arc852.cli_args as cli 8 | import pantilthat as pth 9 | from arc852.cli_args import LOG_LEVEL, GRPC_HOST 10 | from arc852.cli_args import setup_cli_args 11 | from arc852.utils import setup_logging 12 | 13 | import calibrate_servo 14 | from hat_servo import HatServo 15 | from location_client import LocationClient 16 | 17 | logger = logging.getLogger(__name__) 18 | 19 | if __name__ == "__main__": 20 | # Setuo CLI args 21 | args = setup_cli_args(cli.grpc_host, cli.alternate, cli.calib, cli.log_level) 22 | 23 | alternate = args["alternate"] 24 | calib = args["calib"] 25 | 26 | setup_logging(level=args[LOG_LEVEL]) 27 | 28 | with LocationClient(args[GRPC_HOST]) as client: 29 | 30 | # Create servos 31 | servo_x = HatServo("Pan", pth.pan, alternate, 1.0, 8) 32 | servo_y = HatServo("Tilt", pth.tilt, alternate, 1.0, 8) 33 | 34 | if calib: 35 | calib_t = Thread(target=calibrate_servo.calibrate, args=(client, servo_x, servo_y)) 36 | calib_t.start() 37 | calib_t.join() 38 | else: 39 | if alternate: 40 | # Set servo X to go first if alternating 41 | servo_x.ready_event.set() 42 | 43 | try: 44 | servo_x.start(False, lambda: client.get_x(), servo_y.ready_event if not calib else None) 45 | servo_y.start(False, lambda: client.get_y(), servo_x.ready_event if not calib else None) 46 | servo_x.join() 47 | servo_y.join() 48 | except KeyboardInterrupt: 49 | pass 50 | finally: 51 | servo_x.stop() 52 | servo_y.stop() 53 | 54 | logger.info("Exiting...") 55 | -------------------------------------------------------------------------------- /location_mqtt_publisher.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import logging 5 | from threading import Thread 6 | 7 | import arc852.cli_args as cli 8 | from arc852.cli_args import CAMERA_NAME, LOG_LEVEL, MQTT_HOST, GRPC_HOST 9 | from arc852.cli_args import setup_cli_args 10 | from arc852.mqtt_connection import MqttConnection 11 | from arc852.utils import setup_logging, waitForKeyboardInterrupt 12 | 13 | from location_client import LocationClient 14 | 15 | logger = logging.getLogger(__name__) 16 | 17 | 18 | def main(): 19 | # Parse CLI args 20 | args = setup_cli_args(cli.grpc_host, cli.mqtt_host, cli.camera_name, cli.log_level) 21 | 22 | # Setup logging 23 | setup_logging(level=args[LOG_LEVEL]) 24 | 25 | # Start location reader 26 | with LocationClient(args[GRPC_HOST]) as loc_client: 27 | 28 | # Define MQTT callbacks 29 | def on_connect(mqtt_client, userdata, flags, rc): 30 | logger.info("Connected with result code: {0}".format(rc)) 31 | Thread(target=publish_locations, args=(mqtt_client, userdata)).start() 32 | 33 | 34 | def publish_locations(mqtt_client, userdata): 35 | while True: 36 | x_loc, y_loc = loc_client.get_xy() 37 | if x_loc is not None and y_loc is not None: 38 | result, mid = mqtt_client.publish("{0}/x".format(userdata[CAMERA_NAME]), payload=x_loc[0]) 39 | result, mid = mqtt_client.publish("{0}/y".format(userdata[CAMERA_NAME]), payload=y_loc[0]) 40 | 41 | 42 | # Setup MQTT client 43 | with MqttConnection(args[MQTT_HOST], 44 | userdata={CAMERA_NAME: args[CAMERA_NAME]}, 45 | on_connect=on_connect): 46 | waitForKeyboardInterrupt() 47 | 48 | logger.info("Exiting...") 49 | 50 | 51 | if __name__ == "__main__": 52 | main() 53 | -------------------------------------------------------------------------------- /plot_locations.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import time 3 | 4 | import arc852.cli_args as cli 5 | import plotly.graph_objs as go 6 | import plotly.plotly as py 7 | import plotly.tools as tls 8 | from arc852.cli_args import LOG_LEVEL, GRPC_HOST 9 | from arc852.cli_args import setup_cli_args 10 | from arc852.utils import setup_logging 11 | 12 | from location_client import LocationClient 13 | 14 | logger = logging.getLogger(__name__) 15 | 16 | 17 | def main(): 18 | # Parse CLI args 19 | args = setup_cli_args(cli.grpc_host, cli.log_level) 20 | 21 | # Setup logging 22 | setup_logging(level=args[LOG_LEVEL]) 23 | 24 | # Start location client 25 | with LocationClient(args[GRPC_HOST]) as client: 26 | 27 | stream_ids = tls.get_credentials_file()['stream_ids'] 28 | stream_id = stream_ids[0] 29 | 30 | # Declare graph 31 | graph = go.Scatter(x=[], y=[], mode='lines+markers', stream=dict(token=stream_id, maxpoints=80)) 32 | data = go.Data([graph]) 33 | layout = go.Layout(title='Target Locations', xaxis=go.XAxis(range=[0, 800]), yaxis=go.YAxis(range=[0, 450])) 34 | fig = go.Figure(data=data, layout=layout) 35 | py.plot(fig, filename='plot-locations') 36 | 37 | # Write data 38 | stream = py.Stream(stream_id) 39 | stream.open() 40 | 41 | logger.info("Opening plot.ly tab") 42 | time.sleep(5) 43 | 44 | try: 45 | while True: 46 | x_val, y_val = client.get_xy() 47 | 48 | if x_val[0] == -1 or y_val[0] == -1: 49 | continue 50 | 51 | x = x_val[1] - abs(x_val[1] - x_val[0]) 52 | y = abs(y_val[1] - y_val[0]) 53 | 54 | stream.write(dict(x=x, y=y)) 55 | time.sleep(.10) 56 | except KeyboardInterrupt: 57 | pass 58 | finally: 59 | stream.close() 60 | 61 | logger.info("Exiting...") 62 | 63 | 64 | if __name__ == "__main__": 65 | main() 66 | -------------------------------------------------------------------------------- /blinkt_subscriber.py: -------------------------------------------------------------------------------- 1 | import arc852.cli_args as cli 2 | from arc852.cli_args import setup_cli_args 3 | from arc852.constants import LOG_LEVEL, MQTT_HOST, TOPIC, LED_NAME, LED_BRIGHTNESS_DEFAULT, LED_BRIGHTNESS 4 | from arc852.mqtt_connection import MqttConnection 5 | from arc852.utils import is_raspi, setup_logging, waitForKeyboardInterrupt 6 | 7 | if is_raspi(): 8 | from blinkt import set_pixel, show 9 | 10 | import logging 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | class BlinktSubscriber(object): 16 | def __init__(self, brightness=LED_BRIGHTNESS_DEFAULT): 17 | self._brightness = brightness 18 | 19 | def set_leds(self, left_color, right_color): 20 | if is_raspi(): 21 | for i in range(0, 4): 22 | set_pixel(i, left_color[2], left_color[1], left_color[0], brightness=self._brightness) 23 | for i in range(4, 8): 24 | set_pixel(i, right_color[2], right_color[1], right_color[0], brightness=self._brightness) 25 | show() 26 | 27 | def clear_leds(self): 28 | for i in range(8): 29 | set_pixel(i, 0, 0, 0, brightness=self._brightness) 30 | 31 | 32 | def main(): 33 | # Parse CLI args 34 | args = setup_cli_args(cli.grpc_host, cli.mqtt_host, cli.led_name, cli.led_brightness, cli.log_level) 35 | 36 | # Setup logging 37 | setup_logging(level=args[LOG_LEVEL]) 38 | 39 | blinkt = BlinktSubscriber(args[LED_BRIGHTNESS]) 40 | 41 | 42 | # Define MQTT callbacks 43 | def on_connect(mqtt_client, userdata, flags, rc): 44 | logger.info("Connected with result code: {0}".format(rc)) 45 | mqtt_client.subscribe("leds/{0}".format(userdata[TOPIC])) 46 | 47 | 48 | def on_message(mqtt_client, userdata, msg): 49 | logger.info("{0} {1}".format(msg.topic, msg.payload)) 50 | 51 | 52 | # Setup MQTT client 53 | with MqttConnection(args[MQTT_HOST], 54 | userdata={TOPIC: args[LED_NAME]}, 55 | on_connect=on_connect, 56 | on_message=on_message): 57 | waitForKeyboardInterrupt() 58 | 59 | logger.info("Exiting...") 60 | 61 | 62 | if __name__ == "__main__": 63 | main() 64 | -------------------------------------------------------------------------------- /proto/location_service_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | import proto.location_service_pb2 as proto_dot_location__service__pb2 5 | 6 | 7 | class LocationServiceStub(object): 8 | def __init__(self, channel): 9 | """Constructor. 10 | 11 | Args: 12 | channel: A grpc.Channel. 13 | """ 14 | self.registerClient = channel.unary_unary( 15 | '/opencv_object_tracking.LocationService/registerClient', 16 | request_serializer=proto_dot_location__service__pb2.ClientInfo.SerializeToString, 17 | response_deserializer=proto_dot_location__service__pb2.ServerInfo.FromString, 18 | ) 19 | self.getLocations = channel.unary_stream( 20 | '/opencv_object_tracking.LocationService/getLocations', 21 | request_serializer=proto_dot_location__service__pb2.ClientInfo.SerializeToString, 22 | response_deserializer=proto_dot_location__service__pb2.Location.FromString, 23 | ) 24 | 25 | 26 | class LocationServiceServicer(object): 27 | def registerClient(self, request, context): 28 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 29 | context.set_details('Method not implemented!') 30 | raise NotImplementedError('Method not implemented!') 31 | 32 | def getLocations(self, request, context): 33 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 34 | context.set_details('Method not implemented!') 35 | raise NotImplementedError('Method not implemented!') 36 | 37 | 38 | def add_LocationServiceServicer_to_server(servicer, server): 39 | rpc_method_handlers = { 40 | 'registerClient': grpc.unary_unary_rpc_method_handler( 41 | servicer.registerClient, 42 | request_deserializer=proto_dot_location__service__pb2.ClientInfo.FromString, 43 | response_serializer=proto_dot_location__service__pb2.ServerInfo.SerializeToString, 44 | ), 45 | 'getLocations': grpc.unary_stream_rpc_method_handler( 46 | servicer.getLocations, 47 | request_deserializer=proto_dot_location__service__pb2.ClientInfo.FromString, 48 | response_serializer=proto_dot_location__service__pb2.Location.SerializeToString, 49 | ), 50 | } 51 | generic_handler = grpc.method_handlers_generic_handler( 52 | 'opencv_object_tracking.LocationService', rpc_method_handlers) 53 | server.add_generic_rpc_handlers((generic_handler,)) 54 | -------------------------------------------------------------------------------- /location_server.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import time 3 | from threading import Thread 4 | 5 | import grpc 6 | from concurrent import futures 7 | from grpc_support import GenericServer 8 | from utils import setup_logging 9 | from utils import sleep 10 | 11 | from proto.location_service_pb2 import Location 12 | from proto.location_service_pb2 import LocationServiceServicer 13 | from proto.location_service_pb2 import ServerInfo 14 | from proto.location_service_pb2 import add_LocationServiceServicer_to_server 15 | 16 | logger = logging.getLogger(__name__) 17 | 18 | 19 | class LocationServer(LocationServiceServicer, GenericServer): 20 | def __init__(self, port=None): 21 | super(LocationServer, self).__init__(port=port, desc="location server") 22 | self.grpc_server = None 23 | 24 | def registerClient(self, request, context): 25 | logger.info("Connected to {0} client {1} [{2}]".format(self.desc, context.peer(), request.info)) 26 | return ServerInfo(info="Server invoke count {0}".format(self.increment_cnt())) 27 | 28 | def getLocations(self, request, context): 29 | client_info = request.info 30 | return self.currval_generator(context.peer()) 31 | 32 | def _init_values_on_start(self): 33 | self.write_location(-1, -1, 0, 0, 0) 34 | 35 | def _start_server(self): 36 | logger.info("Starting gRPC {0} listening on {1}".format(self.desc, self.hostname)) 37 | self.grpc_server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) 38 | add_LocationServiceServicer_to_server(self, self.grpc_server) 39 | self.grpc_server.add_insecure_port(self.hostname) 40 | self.grpc_server.start() 41 | try: 42 | while not self.stopped: 43 | time.sleep(1) 44 | except KeyboardInterrupt: 45 | pass 46 | finally: 47 | self.stop() 48 | 49 | def write_location(self, x, y, width, height, middle_inc): 50 | if not self.stopped: 51 | self.set_currval(Location(id=self.id, 52 | x=x, 53 | y=y, 54 | width=width, 55 | height=height, 56 | middle_inc=middle_inc)) 57 | self.id += 1 58 | 59 | 60 | def main(): 61 | def _run_server(port): 62 | server = LocationServer(port).start() 63 | 64 | for i in range(100): 65 | server.write_location(x=i, y=i + 1, width=i + 2, height=i + 3, middle_inc=i + 4) 66 | time.sleep(1) 67 | 68 | 69 | setup_logging() 70 | Thread(target=_run_server, args=(50052,)).start() 71 | sleep() 72 | 73 | 74 | if __name__ == "__main__": 75 | main() 76 | -------------------------------------------------------------------------------- /firmata_controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import argparse 5 | import logging 6 | import sys 7 | from threading import Thread 8 | 9 | import arc852.cli_args as cli 10 | from arc852.cli_args import LOG_LEVEL, GRPC_HOST 11 | from arc852.utils import is_windows 12 | from arc852.utils import setup_logging 13 | from pyfirmata import Arduino 14 | 15 | import calibrate_servo 16 | from firmata_servo import FirmataServo 17 | from location_client import LocationClient 18 | 19 | logger = logging.getLogger(__name__) 20 | 21 | 22 | def main(): 23 | parser = argparse.ArgumentParser() 24 | parser.add_argument("-s", "--serial", default="ttyACM0", type=str, 25 | help="Arduino serial port [ttyACM0] (OSX is cu.usbmodemXXXX)") 26 | cli.grpc_host(parser) 27 | parser.add_argument("-x", "--xservo", default=5, type=int, help="X servo PWM pin [5]") 28 | parser.add_argument("-y", "--yservo", default=6, type=int, help="Y servo PWM pin [6]") 29 | cli.alternate(parser) 30 | cli.calib(parser) 31 | cli.log_level(parser) 32 | args = vars(parser.parse_args()) 33 | 34 | alternate = args["alternate"] 35 | calib = args["calib"] 36 | xservo = args["xservo"] 37 | yservo = args["yservo"] 38 | 39 | setup_logging(level=args[LOG_LEVEL]) 40 | 41 | # Setup firmata client 42 | port = ("" if is_windows() else "/dev/") + args["serial"] 43 | try: 44 | board = Arduino(port) 45 | logger.info("Connected to Arduino at: {0}".format(port)) 46 | except OSError as e: 47 | logger.error("Failed to connect to Arduino at {0} - [{1}]".format(port, e)) 48 | sys.exit(0) 49 | 50 | with LocationClient(args[GRPC_HOST]) as client: 51 | # Create servos 52 | servo_x = FirmataServo("Pan", alternate, board, "d:{0}:s".format(xservo), 1.0, 8) 53 | servo_y = FirmataServo("Tilt", alternate, board, "d:{0}:s".format(yservo), 1.0, 8) 54 | 55 | try: 56 | if calib: 57 | try: 58 | calib_t = Thread(target=calibrate_servo.calibrate, args=(client, servo_x, servo_y)) 59 | calib_t.start() 60 | calib_t.join() 61 | except KeyboardInterrupt: 62 | pass 63 | else: 64 | if alternate: 65 | # Set servo X to go first 66 | servo_x.ready_event.set() 67 | try: 68 | servo_x.start(True, lambda: client.get_x(), servo_y.ready_event if not calib else None) 69 | servo_y.start(False, lambda: client.get_y(), servo_x.ready_event if not calib else None) 70 | servo_x.join() 71 | servo_y.join() 72 | except KeyboardInterrupt: 73 | pass 74 | finally: 75 | servo_x.stop() 76 | servo_y.stop() 77 | finally: 78 | board.exit() 79 | 80 | logger.info("Exiting...") 81 | 82 | 83 | if __name__ == "__main__": 84 | main() 85 | -------------------------------------------------------------------------------- /vertical_object_tracker.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import logging 5 | 6 | from cli_args import LOG_LEVEL 7 | from constants import DISPLAY, BGR_COLOR, WIDTH, MIDDLE_PERCENT, MASK_X, MASK_Y, USB_PORT 8 | from constants import FLIP_X, DRAW_CONTOUR, DRAW_BOX, VERTICAL_LINES, HORIZONTAL_LINES 9 | from constants import FLIP_Y, HTTP_DELAY_SECS, HTTP_FILE, HTTP_VERBOSE 10 | from constants import MINIMUM_PIXELS, GRPC_PORT, LEDS, HSV_RANGE, CAMERA_NAME, USB_CAMERA, HTTP_HOST 11 | from opencv_utils import contour_slope_degrees 12 | from utils import setup_logging 13 | 14 | from object_tracker import ObjectTracker 15 | from single_object_filter import SingleObjectFilter 16 | 17 | logger = logging.getLogger(__name__) 18 | 19 | 20 | def test_for_rope(_filter): 21 | # Bail if no contour is available 22 | if _filter.contour is None: 23 | _filter.reset_data() 24 | return 25 | 26 | slope, degrees = contour_slope_degrees(_filter.contour) 27 | 28 | # logger.info("Slope: {0}".format(slope)) 29 | # logger.info("Degrees: {0}".format(degrees)) 30 | 31 | if abs(degrees) < 80 or (slope is not None and abs(slope) < 20): 32 | _filter.reset_data() 33 | 34 | 35 | def main(): 36 | # Parse CLI args 37 | args = ObjectTracker.cli_args() 38 | 39 | # Setup logging 40 | setup_logging(level=args[LOG_LEVEL]) 41 | 42 | tracker = ObjectTracker(width=args[WIDTH], 43 | middle_percent=args[MIDDLE_PERCENT], 44 | display=args[DISPLAY], 45 | flip_x=args[FLIP_X], 46 | flip_y=args[FLIP_Y], 47 | mask_x=args[MASK_X], 48 | mask_y=args[MASK_Y], 49 | usb_camera=args[USB_CAMERA], 50 | usb_port=args[USB_PORT], 51 | camera_name=args[CAMERA_NAME], 52 | http_host=args[HTTP_HOST], 53 | http_file=args[HTTP_FILE], 54 | http_delay_secs=args[HTTP_DELAY_SECS], 55 | http_verbose=args[HTTP_VERBOSE]) 56 | 57 | obj_filter = SingleObjectFilter(tracker, 58 | bgr_color=args[BGR_COLOR], 59 | hsv_range=args[HSV_RANGE], 60 | minimum_pixels=args[MINIMUM_PIXELS], 61 | grpc_port=args[GRPC_PORT], 62 | leds=args[LEDS], 63 | display_text=True, 64 | draw_contour=args[DRAW_CONTOUR], 65 | draw_box=args[DRAW_BOX], 66 | vertical_lines=args[VERTICAL_LINES], 67 | horizontal_lines=args[HORIZONTAL_LINES], 68 | predicate=test_for_rope) 69 | try: 70 | tracker.start(obj_filter) 71 | except KeyboardInterrupt: 72 | pass 73 | finally: 74 | tracker.stop() 75 | 76 | logger.info("Exiting...") 77 | 78 | 79 | if __name__ == "__main__": 80 | main() 81 | -------------------------------------------------------------------------------- /firmata_servo.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import time 3 | 4 | from generic_servo import Servo 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | class FirmataServo(Servo): 9 | def __init__(self, name, alternate, board, pin_args, secs_per_180, pix_per_degree): 10 | super(FirmataServo, self).__init__(name, alternate, secs_per_180, pix_per_degree) 11 | self.__pin = board.get_pin(pin_args) 12 | self.jiggle() 13 | 14 | def jiggle(self): 15 | # Provoke an update from the color tracker 16 | self.write_pin(80) 17 | self.write_pin(90) 18 | 19 | def set_angle(self, val, pause=None): 20 | self.write_pin(val, pause) 21 | 22 | def get_currpos(self): 23 | return self.read_pin() 24 | 25 | def read_pin(self): 26 | return self.__pin.read() 27 | 28 | def write_pin(self, val, pause=-None): 29 | if pause is not None: 30 | self.__pin.write(val) 31 | time.sleep(pause) 32 | else: 33 | wait = (self.__secs_per_180 / 180) * abs((val - self.get_currpos())) 34 | self.__pin.write(val) 35 | time.sleep(wait) 36 | 37 | def run_servo2(self, forward, loc_source, other_ready_event): 38 | while not self.__stopped: 39 | try: 40 | if self.__alternate: 41 | self.ready_event.wait() 42 | self.ready_event.clear() 43 | 44 | # Get latest location 45 | img_pos, img_total, middle_inc, id_val = loc_source() 46 | 47 | # Skip if object is not seen 48 | if img_pos == -1 or img_total == -1: 49 | logger.info("No target seen: {0}".format(self.name)) 50 | continue 51 | 52 | midpoint = img_total / 2 53 | 54 | curr_pos = self.get_currpos() 55 | 56 | if img_pos < midpoint - middle_inc: 57 | err = abs(midpoint - img_pos) 58 | adj = max(int(err / self.__ppd), 1) 59 | new_pos = curr_pos + adj if forward else curr_pos - adj 60 | print("{0} off by {1} pixels going from {2} to {3} adj {4}" 61 | .format(self.name, err, new_pos, curr_pos, adj)) 62 | elif img_pos > midpoint + middle_inc: 63 | err = img_pos - midpoint 64 | adj = max(int(err / self.__ppd), 1) 65 | new_pos = curr_pos - adj if forward else curr_pos + adj 66 | print("{0} off by {1} pixels going from {2} to {3} adj {4}" 67 | .format(self.name, err, new_pos, curr_pos, adj)) 68 | else: 69 | continue 70 | 71 | delta = abs(new_pos - curr_pos) 72 | 73 | # If you do not pause long enough, the servo will go bonkers 74 | # Pause for a time relative to distance servo has to travel 75 | wait_time = (self.__secs_per_180 / 180) * delta 76 | 77 | # Write servo value 78 | self.write_pin(new_pos, wait_time) 79 | 80 | finally: 81 | if self.__alternate and other_ready_event is not None: 82 | other_ready_event.set() 83 | 84 | time.sleep(.10) 85 | -------------------------------------------------------------------------------- /draw_locations.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from threading import Thread 5 | 6 | import arc852.cli_args as cli 7 | from arc852.cli_args import LOG_LEVEL, GRPC_HOST 8 | from arc852.cli_args import setup_cli_args 9 | from arc852.utils import is_python3 10 | from arc852.utils import setup_logging 11 | 12 | from location_client import LocationClient 13 | 14 | if is_python3(): 15 | import tkinter as tk 16 | else: 17 | import Tkinter as tk 18 | 19 | 20 | class LocationSketch(object): 21 | def __init__(self, canvas): 22 | self.__canvas = canvas 23 | self.__drawLines = True 24 | self.__drawPoints = True 25 | self.__stopped = False 26 | 27 | def toggle_lines(self): 28 | self.__drawLines = not self.__drawLines 29 | 30 | def toggle_points(self): 31 | self.__drawPoints = not self.__drawPoints 32 | 33 | def clear_canvas(self): 34 | self.__canvas.delete("all") 35 | 36 | def plot_vals(self, locations, w, h): 37 | prev_x, prev_y = None, None 38 | curr_w = w 39 | while not self.__stopped: 40 | x_val, y_val = locations.get_xy() 41 | 42 | if x_val[0] == -1 or y_val[0] == -1: 43 | prev_x, prev_y = None, None 44 | continue 45 | 46 | # Check if width of image has changed 47 | if x_val[1] != curr_w: 48 | self.__canvas.delete("all") 49 | self.__canvas.config(width=x_val[1], height=y_val[1]) 50 | curr_w = x_val[1] 51 | prev_x, prev_y = None, None 52 | continue 53 | 54 | x = x_val[1] - abs(x_val[1] - x_val[0]) 55 | y = y_val[0] 56 | 57 | if self.__drawPoints: 58 | self.__canvas.create_oval(x - 1, y - 1, x + 1, y + 1) 59 | 60 | if self.__drawLines and prev_x is not None: 61 | self.__canvas.create_line(prev_x, prev_y, x, y, fill="red") 62 | 63 | prev_x, prev_y = x, y 64 | 65 | def stop(self): 66 | self.__stopped = True 67 | 68 | 69 | def main(): 70 | # Parse CLI args 71 | args = setup_cli_args(cli.grpc_host, cli.log_level) 72 | 73 | setup_logging(level=args[LOG_LEVEL]) 74 | 75 | with LocationClient(args[GRPC_HOST]) as client: 76 | init_w, init_h = 800, 450 77 | 78 | root = tk.Tk() 79 | 80 | canvas = tk.Canvas(root, bg="white", width=init_w, height=init_h) 81 | canvas.pack() 82 | 83 | sketch = LocationSketch(canvas) 84 | 85 | b = tk.Button(root, text="Clear", command=sketch.clear_canvas) 86 | b.pack(side=tk.LEFT) 87 | 88 | lb_var = tk.IntVar() 89 | lb_var.set(1) 90 | lb = tk.Checkbutton(root, text="Lines", variable=lb_var, command=sketch.toggle_lines) 91 | lb.pack(side=tk.LEFT) 92 | 93 | pb_var = tk.IntVar() 94 | pb_var.set(1) 95 | pb = tk.Checkbutton(root, text="Points", variable=pb_var, command=sketch.toggle_points) 96 | pb.pack(side=tk.LEFT) 97 | 98 | Thread(target=sketch.plot_vals, args=(client, init_w, init_h)).start() 99 | 100 | root.mainloop() 101 | 102 | 103 | if __name__ == "__main__": 104 | main() 105 | -------------------------------------------------------------------------------- /generic_servo.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import time 3 | from threading import Event 4 | from threading import Thread 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | class Servo(object): 9 | def __init__(self, name, alternate=False, secs_per_180=0.50, pix_per_degree=6.5): 10 | self.__alternate = alternate 11 | self.__secs_per_180 = secs_per_180 12 | self.__ppd = pix_per_degree 13 | self.ready_event = Event() 14 | self.__thread = None 15 | self.__stopped = False 16 | logger.info("Created servo: {0} alternate={1} secs_per_180={2} pix_per_degree={3}" 17 | .format(self.name, self.__alternate, self.__secs_per_180, self.__ppd)) 18 | self.name = name 19 | 20 | def get_currpos(self): 21 | return -1 22 | 23 | def set_angle(self, val, pause=None): 24 | pass 25 | 26 | def run_servo(self, forward, loc_source, other_ready_event): 27 | logger.info("Started servo: {0}".format(self.name)) 28 | while not self.__stopped: 29 | try: 30 | if self.__alternate: 31 | self.ready_event.wait() 32 | self.ready_event.clear() 33 | 34 | # Get latest location 35 | img_pos, img_total, middle_inc, id_val = loc_source() 36 | 37 | # Skip if object is not seen 38 | if img_pos == -1 or img_total == -1: 39 | logger.info("No target seen: {0}".format(self.name)) 40 | continue 41 | 42 | midpoint = img_total / 2 43 | 44 | curr_pos = self.get_currpos() 45 | 46 | if img_pos < midpoint - middle_inc: 47 | err = abs(midpoint - img_pos) 48 | adj = max(int(err / self.__ppd), 1) 49 | new_pos = curr_pos + adj if forward else curr_pos - adj 50 | # print("{0} off by {1} pixels going from {2} to {3} adj {4}" 51 | # .format(self.__name, err, curr_pos, new_pos, adj)) 52 | elif img_pos > midpoint + middle_inc: 53 | err = img_pos - midpoint 54 | adj = max(int(err / self.__ppd), 1) 55 | new_pos = curr_pos - adj if forward else curr_pos + adj 56 | # print("{0} off by {1} pixels going from {2} to {3} adj {4}" 57 | # .format(self.__name, err, curr_pos, new_pos, adj)) 58 | else: 59 | continue 60 | 61 | delta = abs(new_pos - curr_pos) 62 | 63 | # If you do not pause long enough, the servo will go bonkers 64 | # Pause for a time relative to distance servo has to travel 65 | wait_time = (self.__secs_per_180 / 180) * delta 66 | 67 | # Write servo value 68 | self.set_angle(new_pos, pause=wait_time) 69 | 70 | finally: 71 | if self.__alternate and other_ready_event is not None: 72 | other_ready_event.set() 73 | 74 | time.sleep(.10) 75 | 76 | def start(self, forward, loc_source, other_ready_event): 77 | self.__thread = Thread(target=self.run_servo, args=(forward, loc_source, other_ready_event)) 78 | self.__thread.start() 79 | 80 | def join(self): 81 | self.__thread.join() 82 | 83 | def stop(self): 84 | logger.info("Stopping servo {0}".format(self.name)) 85 | self.__stopped = True 86 | -------------------------------------------------------------------------------- /location_client.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import logging 3 | import socket 4 | import time 5 | from threading import Event 6 | 7 | import grpc 8 | from arc852.grpc_support import GenericClient 9 | from arc852.grpc_support import TimeoutException 10 | from arc852.utils import setup_logging 11 | 12 | from proto.location_service_pb2 import ClientInfo 13 | from proto.location_service_pb2 import LocationServiceStub 14 | 15 | logger = logging.getLogger(__name__) 16 | 17 | 18 | class LocationClient(GenericClient): 19 | def __init__(self, hostname): 20 | super(LocationClient, self).__init__(hostname, desc="location client") 21 | self.__x_ready = Event() 22 | self.__y_ready = Event() 23 | self.__currval = None 24 | 25 | def _mark_ready(self): 26 | self.__x_ready.set() 27 | self.__y_ready.set() 28 | 29 | def _get_values(self, pause_secs=2.0): 30 | channel = grpc.insecure_channel(self.hostname) 31 | stub = LocationServiceStub(channel) 32 | while not self.stopped: 33 | logger.info("Connecting to gRPC server at {0}...".format(self.hostname)) 34 | try: 35 | client_info = ClientInfo(info="{0} client".format(socket.gethostname())) 36 | server_info = stub.registerClient(client_info) 37 | except BaseException as e: 38 | logger.error("Failed to connect to gRPC server at {0} [{1}]".format(self.hostname, e)) 39 | time.sleep(pause_secs) 40 | continue 41 | 42 | logger.info("Connected to gRPC server at {0} [{1}]".format(self.hostname, server_info.info)) 43 | 44 | try: 45 | for val in stub.getLocations(client_info): 46 | with self.value_lock: 47 | self.__currval = copy.deepcopy(val) 48 | self._mark_ready() 49 | except BaseException as e: 50 | logger.info("Disconnected from gRPC server at {0} [{1}]".format(self.hostname, e)) 51 | time.sleep(pause_secs) 52 | 53 | # Non-blocking 54 | def get_loc(self, name): 55 | return self.__currval.x if name == "x" else self.__currval.y 56 | 57 | # Non-blocking 58 | def get_size(self, name): 59 | return self.__currval.width if name == "x" else self.__currval.height 60 | 61 | # Blocking 62 | def get_x(self, timeout=None): 63 | while not self.stopped: 64 | if not self.__x_ready.wait(timeout): 65 | raise TimeoutException 66 | with self.value_lock: 67 | if self.__x_ready.is_set() and not self.stopped: 68 | self.__x_ready.clear() 69 | return self.__currval.x, self.__currval.width, self.__currval.middle_inc, self.__currval.id 70 | 71 | # Blocking 72 | def get_y(self, timeout=None): 73 | while not self.stopped: 74 | if not self.__y_ready.wait(timeout): 75 | raise TimeoutException 76 | with self.value_lock: 77 | if self.__y_ready.is_set() and not self.stopped: 78 | self.__y_ready.clear() 79 | return self.__currval.y, self.__currval.height, self.__currval.middle_inc, self.__currval.id 80 | 81 | # Blocking 82 | def get_xy(self): 83 | return self.get_x(), self.get_y() 84 | 85 | 86 | def main(): 87 | setup_logging() 88 | with LocationClient("localhost") as client: 89 | for i in range(1000): 90 | logger.info("Read value: {0}".format(client.get_xy())) 91 | logger.info("Exiting...") 92 | 93 | 94 | if __name__ == "__main__": 95 | main() 96 | -------------------------------------------------------------------------------- /generic_filter.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | 4 | from cli_args import GRPC_PORT_DEFAULT 5 | from constants import MINIMUM_PIXELS_DEFAULT, HSV_RANGE_DEFAULT 6 | from contour_finder import ContourFinder 7 | from location_server import LocationServer 8 | from utils import is_raspi 9 | 10 | # I tried to include this in the constructor and make it depedent on self.__leds, but it does not work 11 | if is_raspi(): 12 | from blinkt import set_pixel, show 13 | 14 | logger = logging.getLogger(__name__) 15 | 16 | 17 | class GenericFilter(object): 18 | def __init__(self, 19 | tracker, 20 | bgr_color, 21 | hsv_range=HSV_RANGE_DEFAULT, 22 | minimum_pixels=MINIMUM_PIXELS_DEFAULT, 23 | grpc_port=GRPC_PORT_DEFAULT, 24 | leds=False, 25 | display_text=False, 26 | draw_contour=False, 27 | draw_box=False, 28 | vertical_lines=False, 29 | horizontal_lines=False, 30 | predicate=None): 31 | self.tracker = tracker 32 | self.leds = leds 33 | self.display_text = display_text 34 | self.draw_contour = draw_contour 35 | self.draw_box = draw_box 36 | self.vertical_lines = vertical_lines 37 | self.horizontal_lines = horizontal_lines 38 | self.predicate = predicate 39 | self._prev_x, self._prev_y = -1, -1 40 | self.height, self.width = -1, -1 41 | self.contours = None 42 | self.contour_finder = ContourFinder(bgr_color, hsv_range, minimum_pixels) 43 | self.location_server = LocationServer(grpc_port) 44 | 45 | @property 46 | def prev_x(self): 47 | return self._prev_x 48 | 49 | @prev_x.setter 50 | def prev_x(self, val): 51 | self._prev_x = val 52 | 53 | @property 54 | def prev_y(self): 55 | return self._prev_y 56 | 57 | @prev_y.setter 58 | def prev_y(self, val): 59 | self._prev_y = val 60 | 61 | @property 62 | def middle_inc(self): 63 | # The middle margin calculation is based on % of width for horizontal and vertical boundary 64 | mid_x = self.width / 2 65 | middle_pct = (float(self.tracker.middle_percent) / 100.0) / 2 66 | return int(mid_x * middle_pct) 67 | 68 | def start(self): 69 | try: 70 | self.location_server.start() 71 | except BaseException as e: 72 | logger.error("Unable to start location server [{0}]".format(e), exc_info=True) 73 | sys.exit(1) 74 | if self.leds: 75 | self.clear_leds() 76 | 77 | def stop(self): 78 | if self.leds: 79 | self.clear_leds() 80 | self.location_server.stop() 81 | 82 | def reset(self): 83 | self.prev_x, self.prev_y = -1, -1 84 | 85 | def reset_data(self): 86 | raise Exception("Should be implemented by sub-class") 87 | 88 | def process_image(self, image): 89 | raise Exception("Should be implemented by sub-class") 90 | 91 | def publish_data(self): 92 | raise Exception("Should be implemented by sub-class") 93 | 94 | def markup_image(self, image): 95 | raise Exception("Should be implemented by sub-class") 96 | 97 | def set_leds(self, left_color, right_color): 98 | if is_raspi(): 99 | for i in range(0, 4): 100 | set_pixel(i, left_color[2], left_color[1], left_color[0], brightness=0.05) 101 | for i in range(4, 8): 102 | set_pixel(i, right_color[2], right_color[1], right_color[0], brightness=0.05) 103 | show() 104 | 105 | def clear_leds(self): 106 | self.set_leds([0, 0, 0], [0, 0, 0]) 107 | -------------------------------------------------------------------------------- /multi_object_tracker.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import logging 3 | 4 | import arc852.cli_args as cli 5 | from arc852.cli_args import GRPC_PORT_DEFAULT 6 | from arc852.cli_args import LOG_LEVEL 7 | from arc852.constants import DRAW_CONTOUR, DRAW_BOX, VERTICAL_LINES, HORIZONTAL_LINES, MASK_X, MASK_Y, USB_PORT 8 | from arc852.constants import HSV_RANGE, MIDDLE_PERCENT, FLIP_X, FLIP_Y 9 | from arc852.constants import HTTP_DELAY_SECS, HTTP_FILE, HTTP_VERBOSE 10 | from arc852.constants import MINIMUM_PIXELS, CAMERA_NAME, HTTP_HOST, USB_CAMERA, DISPLAY, WIDTH 11 | from arc852.utils import setup_logging 12 | 13 | from dual_object_filter import DualObjectFilter 14 | from object_tracker import ObjectTracker 15 | from single_object_filter import SingleObjectFilter 16 | 17 | DUAL_BGR = "dual_bgr" 18 | SINGLE_BGR = "single_bgr" 19 | DUAL_PORT = "dual_port" 20 | SINGLE_PORT = "single_port" 21 | 22 | logger = logging.getLogger(__name__) 23 | 24 | 25 | def main(): 26 | # Parse CLI args 27 | p = argparse.ArgumentParser() 28 | cli.usb_camera(p), 29 | cli.width(p), 30 | cli.middle_percent(p), 31 | cli.minimum_pixels(p), 32 | cli.hsv_range(p), 33 | cli.leds(p), 34 | cli.flip_x(p), 35 | cli.flip_y(p), 36 | cli.mask_x(p), 37 | cli.mask_y(p), 38 | cli.vertical_lines(p), 39 | cli.horizontal_lines(p), 40 | cli.camera_name_optional(p), 41 | cli.display(p), 42 | cli.draw_contour(p), 43 | cli.draw_box(p), 44 | cli.http_host(p), 45 | cli.http_delay_secs(p), 46 | cli.http_file(p), 47 | cli.http_verbose(p), 48 | p.add_argument("--dualbgr", dest=DUAL_BGR, required=True, help="Dual color BGR value") 49 | p.add_argument("--singlebgr", dest=SINGLE_BGR, required=True, help="Single color BGR value") 50 | p.add_argument("--dualport", dest=DUAL_PORT, default=GRPC_PORT_DEFAULT, type=int, 51 | help="Dual gRPC port [{0}]".format(GRPC_PORT_DEFAULT)) 52 | p.add_argument("--singleport", dest=SINGLE_PORT, default=GRPC_PORT_DEFAULT + 1, type=int, 53 | help="Dual gRPC port [{0}]".format(GRPC_PORT_DEFAULT + 1)) 54 | cli.log_level(p) 55 | args = vars(p.parse_args()) 56 | 57 | # Setup logging 58 | setup_logging(level=args[LOG_LEVEL]) 59 | 60 | tracker = ObjectTracker(width=args[WIDTH], 61 | middle_percent=args[MIDDLE_PERCENT], 62 | display=args[DISPLAY], 63 | flip_x=args[FLIP_X], 64 | flip_y=args[FLIP_Y], 65 | mask_x=args[MASK_X], 66 | mask_y=args[MASK_Y], 67 | usb_camera=args[USB_CAMERA], 68 | usb_port=args[USB_PORT], 69 | camera_name=args[CAMERA_NAME], 70 | http_host=args[HTTP_HOST], 71 | http_file=args[HTTP_FILE], 72 | http_delay_secs=args[HTTP_DELAY_SECS], 73 | http_verbose=args[HTTP_VERBOSE]) 74 | 75 | dual_filter = DualObjectFilter(tracker, 76 | bgr_color=args[DUAL_BGR], 77 | hsv_range=args[HSV_RANGE], 78 | minimum_pixels=args[MINIMUM_PIXELS], 79 | grpc_port=args[DUAL_PORT], 80 | leds=False, 81 | display_text=False, 82 | draw_contour=args[DRAW_CONTOUR], 83 | draw_box=args[DRAW_BOX], 84 | vertical_lines=args[VERTICAL_LINES], 85 | horizontal_lines=args[HORIZONTAL_LINES]) 86 | 87 | single_filter = SingleObjectFilter(tracker, 88 | bgr_color=args[SINGLE_BGR], 89 | hsv_range=args[HSV_RANGE], 90 | minimum_pixels=args[MINIMUM_PIXELS], 91 | grpc_port=args[SINGLE_PORT], 92 | leds=False, 93 | display_text=True, 94 | draw_contour=args[DRAW_CONTOUR], 95 | draw_box=args[DRAW_BOX], 96 | vertical_lines=False, 97 | horizontal_lines=False) 98 | 99 | try: 100 | tracker.start(single_filter, dual_filter) 101 | except KeyboardInterrupt: 102 | pass 103 | finally: 104 | tracker.stop() 105 | 106 | logger.info("Exiting...") 107 | 108 | 109 | if __name__ == "__main__": 110 | main() 111 | -------------------------------------------------------------------------------- /calibrate_servo.py: -------------------------------------------------------------------------------- 1 | def calibrate(locations, servo_x, servo_y): 2 | def center_servos(): 3 | servo_x.set_angle(90) 4 | servo_y.set_angle(90) 5 | 6 | name = "x" 7 | servo = servo_x 8 | 9 | # This is a hack to get around python3 not having raw_input 10 | try: 11 | input = raw_input 12 | except NameError: 13 | pass 14 | 15 | while True: 16 | try: 17 | key = input("{0} {1} ({2}, {3})> " 18 | .format(name.upper(), servo.get_currpos(), locations.get_loc("x"), locations.get_loc("y"))) 19 | except KeyboardInterrupt: 20 | break 21 | 22 | pause = 0.25 23 | 24 | if key == "?" or key == "h": 25 | print("Valid commands:") 26 | print(" x : set current server to pan servo") 27 | print(" y : set current server to tilt servo") 28 | print(" g : advance current servo one location response") 29 | print(" s : run scan on current servo") 30 | print(" c : center current servo") 31 | print(" C : center both servos") 32 | print(" + : increase current servo position 1 degree") 33 | print(" - : decrease current servo position 1 degree") 34 | print(" number : set current servo position number degree") 35 | print(" ? : print summary of commands") 36 | print(" q : quit") 37 | elif key == "c": 38 | servo.set_angle(90) 39 | elif key == "C": 40 | center_servos() 41 | elif key == "x": 42 | name = "x" 43 | servo = servo_x 44 | elif key == "y": 45 | name = "y" 46 | servo = servo_y 47 | elif key == "g": 48 | servo.ready_event.set() 49 | elif key == "l": 50 | servo.set_angle(90) 51 | servo_pos = 90 52 | img_start = locations.get_loc(name) 53 | img_last = -1 54 | for i in range(90, 0, -1): 55 | servo.set_angle(i, pause) 56 | img_pos = locations.get_loc(name) 57 | if img_pos == -1: 58 | break 59 | img_last = img_pos 60 | servo_pos = i 61 | pixels = img_start - img_last 62 | degrees = 90 - servo_pos 63 | ppd = abs(float(pixels / degrees)) 64 | print("{0} pixels {1} degrees from center to left edge at pos {2} {3} pix/deg" 65 | .format(pixels, degrees, servo_pos, ppd)) 66 | elif key == "r": 67 | servo.set_angle(90) 68 | servo_pos = 90 69 | img_start = locations.get_loc(name) 70 | img_last = -1 71 | for i in range(90, 180): 72 | servo.set_angle(i, pause) 73 | img_pos = locations.get_loc(name) 74 | if img_pos == -1: 75 | break 76 | img_last = img_pos 77 | servo_pos = i 78 | pixels = img_last - img_start 79 | degrees = servo_pos - 90 80 | ppd = abs(float(pixels / degrees)) 81 | print("{0} pixels {1} degrees from center to left edge at pos {2} {3} pix/deg" 82 | .format(pixels, degrees, servo_pos, ppd)) 83 | elif key == "s": 84 | center_servos() 85 | servo.set_angle(0) 86 | 87 | start_pos = -1 88 | end_pos = -1 89 | for i in range(0, 180, 1): 90 | servo.set_angle(i, pause) 91 | if locations.get_loc(name) != -1: 92 | start_pos = i 93 | print("Target starts at position {0}".format(start_pos)) 94 | break 95 | 96 | if start_pos == -1: 97 | print("No target found") 98 | continue 99 | 100 | for i in range(start_pos, 180, 1): 101 | servo.set_angle(i, pause) 102 | if locations.get_loc(name) == -1: 103 | break 104 | end_pos = i 105 | 106 | print("Target ends at position {0}".format(end_pos)) 107 | 108 | total_pixels = locations.get_size(name) 109 | total_pos = end_pos - start_pos 110 | if total_pos > 0: 111 | pix_per_deg = round(total_pixels / float(total_pos), 2) 112 | servo.set_angle(90) 113 | print("{0} degrees to cover {1} pixels [{2} pixels/degree]" 114 | .format(total_pos, total_pixels, pix_per_deg)) 115 | else: 116 | print("No target found") 117 | 118 | elif len(key) == 0: 119 | pass 120 | elif key == "-" or key == "_": 121 | servo.set_angle(servo.get_currpos() - 1) 122 | elif key == "+" or key == "=": 123 | servo.set_angle(servo.get_currpos() + 1) 124 | elif key.isdigit(): 125 | servo.set_angle(int(key)) 126 | elif key == "q": 127 | break 128 | else: 129 | print("Invalid input: {0}".format(key)) 130 | -------------------------------------------------------------------------------- /single_object_filter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import logging 5 | 6 | import cv2 7 | import opencv_defaults as defs 8 | from cli_args import LOG_LEVEL 9 | from constants import DISPLAY, BGR_COLOR, WIDTH, MIDDLE_PERCENT, FLIP_X 10 | from constants import DRAW_CONTOUR, DRAW_BOX, VERTICAL_LINES, HORIZONTAL_LINES 11 | from constants import FLIP_Y, HTTP_DELAY_SECS, HTTP_FILE, HTTP_VERBOSE 12 | from constants import MASK_X, MASK_Y, USB_PORT 13 | from constants import MINIMUM_PIXELS, GRPC_PORT, LEDS, HSV_RANGE, CAMERA_NAME, USB_CAMERA, HTTP_HOST 14 | from opencv_utils import BLUE, GREEN, RED 15 | from opencv_utils import get_moment 16 | from utils import setup_logging 17 | 18 | from generic_filter import GenericFilter 19 | from object_tracker import ObjectTracker 20 | 21 | logger = logging.getLogger(__name__) 22 | 23 | 24 | class SingleObjectFilter(GenericFilter): 25 | def __init__(self, tracker, *args, **kwargs): 26 | super(SingleObjectFilter, self).__init__(tracker, *args, **kwargs) 27 | self.contour = None 28 | self.area = None 29 | self.img_x, self.img_y = -1, -1 30 | self.height, self.width = None, None 31 | 32 | def reset_data(self): 33 | self.img_x, self.img_y = -1, -1 34 | 35 | def process_image(self, image): 36 | self.contour = None 37 | self.reset_data() 38 | self.height, self.width = image.shape[:2] 39 | 40 | # Find the largest contour 41 | self.contours = self.contour_finder.get_max_contours(image, count=1) 42 | 43 | if self.contours is not None and len(self.contours) == 1: 44 | self.contour, self.area, self.img_x, self.img_y = get_moment(self.contours[0]) 45 | 46 | def publish_data(self): 47 | # Write location if it is different from previous value written 48 | if self.img_x != self.prev_x or self.img_y != self.prev_y: 49 | self.location_server.write_location(self.img_x, self.img_y, self.width, self.height, self.middle_inc) 50 | self.prev_x, self.prev_y = self.img_x, self.img_y 51 | 52 | def markup_image(self, image): 53 | mid_x, mid_y = self.width / 2, self.height / 2 54 | middle_inc = int(self.middle_inc) 55 | 56 | x_in_middle = mid_x - middle_inc <= self.img_x <= mid_x + middle_inc 57 | y_in_middle = mid_y - middle_inc <= self.img_y <= mid_y + middle_inc 58 | x_color = GREEN if x_in_middle else RED if self.img_x == -1 else BLUE 59 | y_color = GREEN if y_in_middle else RED if self.img_y == -1 else BLUE 60 | 61 | # Set Blinkt leds 62 | if self.leds: 63 | self.set_leds(x_color, y_color) 64 | 65 | if not self.tracker.markup_image: 66 | return 67 | 68 | text = "#{0} ({1}, {2})".format(self.tracker.cnt, self.width, self.height) 69 | text += " {0}%".format(self.tracker.middle_percent) 70 | 71 | if self.contours is not None and len(self.contours) == 1: 72 | x, y, w, h = cv2.boundingRect(self.contour) 73 | if self.draw_box: 74 | cv2.rectangle(image, (x, y), (x + w, y + h), BLUE, 2) 75 | if self.draw_contour: 76 | cv2.drawContours(image, [self.contour], -1, GREEN, 2) 77 | cv2.circle(image, (self.img_x, self.img_y), 4, RED, -1) 78 | text += " ({0}, {1})".format(self.img_x, self.img_y) 79 | text += " {0}".format(self.area) 80 | 81 | # Draw the alignment lines 82 | if self.vertical_lines: 83 | cv2.line(image, (mid_x - middle_inc, 0), (mid_x - middle_inc, self.height), x_color, 1) 84 | cv2.line(image, (mid_x + middle_inc, 0), (mid_x + middle_inc, self.height), x_color, 1) 85 | if self.horizontal_lines: 86 | cv2.line(image, (0, mid_y - middle_inc), (self.width, mid_y - middle_inc), y_color, 1) 87 | cv2.line(image, (0, mid_y + middle_inc), (self.width, mid_y + middle_inc), y_color, 1) 88 | if self.display_text: 89 | cv2.putText(image, text, defs.TEXT_LOC, defs.TEXT_FONT, defs.TEXT_SIZE, RED, 1) 90 | 91 | 92 | def main(): 93 | # Parse CLI args 94 | args = ObjectTracker.cli_args() 95 | 96 | # Setup logging 97 | setup_logging(level=args[LOG_LEVEL]) 98 | 99 | tracker = ObjectTracker(width=args[WIDTH], 100 | middle_percent=args[MIDDLE_PERCENT], 101 | display=args[DISPLAY], 102 | flip_x=args[FLIP_X], 103 | flip_y=args[FLIP_Y], 104 | mask_x=args[MASK_X], 105 | mask_y=args[MASK_Y], 106 | usb_camera=args[USB_CAMERA], 107 | usb_port=args[USB_PORT], 108 | camera_name=args[CAMERA_NAME], 109 | http_host=args[HTTP_HOST], 110 | http_file=args[HTTP_FILE], 111 | http_delay_secs=args[HTTP_DELAY_SECS], 112 | http_verbose=args[HTTP_VERBOSE]) 113 | 114 | obj_filter = SingleObjectFilter(tracker, 115 | bgr_color=args[BGR_COLOR], 116 | hsv_range=args[HSV_RANGE], 117 | minimum_pixels=args[MINIMUM_PIXELS], 118 | grpc_port=args[GRPC_PORT], 119 | leds=args[LEDS], 120 | display_text=True, 121 | draw_contour=args[DRAW_CONTOUR], 122 | draw_box=args[DRAW_BOX], 123 | vertical_lines=args[VERTICAL_LINES], 124 | horizontal_lines=args[HORIZONTAL_LINES]) 125 | try: 126 | tracker.start(obj_filter) 127 | except KeyboardInterrupt: 128 | pass 129 | finally: 130 | tracker.stop() 131 | 132 | logger.info("Exiting...") 133 | 134 | 135 | if __name__ == "__main__": 136 | main() 137 | -------------------------------------------------------------------------------- /color_picker.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import logging 5 | import time 6 | 7 | import arc852.camera as camera 8 | import arc852.cli_args as cli 9 | import arc852.opencv_defaults as defs 10 | import cv2 11 | import imutils 12 | import numpy as np 13 | from arc852.cli_args import LOG_LEVEL 14 | from arc852.cli_args import setup_cli_args 15 | from arc852.image_server import ImageServer 16 | from arc852.opencv_utils import GREEN 17 | from arc852.opencv_utils import RED 18 | from arc852.utils import setup_logging 19 | from arc852.utils import strip_loglevel 20 | 21 | logger = logging.getLogger(__name__) 22 | 23 | 24 | class ColorPicker(object): 25 | roi_size = 24 26 | orig_roi_size = roi_size 27 | roi_inc = 6 28 | move_inc = 4 29 | x_adj = 0 30 | y_adj = 0 31 | 32 | def __init__(self, 33 | width, 34 | usb_camera, 35 | flip_x, 36 | flip_y, 37 | display, 38 | http_host, 39 | http_file, 40 | http_delay_secs, 41 | http_verbose): 42 | self.__width = width 43 | self.__usb_camera = usb_camera 44 | self.__flip_x = flip_x 45 | self.__flip_y = flip_y 46 | self.__display = display 47 | self.__orig_width = self.__width 48 | self.__cam = camera.Camera(usb_camera=usb_camera) 49 | self.__image_server = ImageServer(http_file, 50 | camera_name="Color Picker", 51 | http_host=http_host, 52 | http_delay_secs=http_delay_secs, 53 | http_verbose=http_verbose) 54 | 55 | # Do not run this in a background thread. cv2.waitKey has to run in main thread 56 | def start(self): 57 | self.__image_server.start() 58 | cnt = 0 59 | while self.__cam.is_open(): 60 | image = self.__cam.read() 61 | if image is None: 62 | logger.error("Null image read from camera") 63 | time.sleep(.5) 64 | continue 65 | 66 | image = imutils.resize(image, width=self.__width) 67 | 68 | if self.__flip_x: 69 | image = cv2.flip(image, 0) 70 | 71 | if self.__flip_y: 72 | image = cv2.flip(image, 1) 73 | 74 | height, width = image.shape[:2] 75 | 76 | roi_x = int((width / 2) - (self.roi_size / 2) + self.x_adj) 77 | roi_y = int((height / 2) - (self.roi_size / 2) + self.y_adj) 78 | roi = image[roi_y:roi_y + self.roi_size, roi_x:roi_x + self.roi_size] 79 | 80 | roi_h, roi_w = roi.shape[:2] 81 | roi_canvas = np.zeros((roi_h, roi_w, 3), dtype="uint8") 82 | roi_canvas[0:roi_h, 0:roi_w] = roi 83 | 84 | # Calculate averge color in ROI 85 | avg_color_per_row = np.average(roi_canvas, axis=0) 86 | avg_color = np.average(avg_color_per_row, axis=0) 87 | avg_color = np.uint8(avg_color) 88 | 89 | # Draw a rectangle around the sample area 90 | cv2.rectangle(image, (roi_x, roi_y), (roi_x + self.roi_size, roi_y + self.roi_size), GREEN, 1) 91 | 92 | # Add text info 93 | bgr_text = "BGR value: [{0}, {1}, {2}]".format(avg_color[0], avg_color[1], avg_color[2]) 94 | roi_text = " ROI: {0}x{1} ".format(str(self.roi_size), str(self.roi_size)) 95 | cv2.putText(image, bgr_text + roi_text, defs.TEXT_LOC, defs.TEXT_FONT, defs.TEXT_SIZE, RED, 1) 96 | 97 | # Overlay color swatch on image 98 | size = int(width * 0.20) 99 | image[height - size:height, width - size:width] = avg_color 100 | 101 | cnt += 1 102 | 103 | # if self.__image_server.enabled and cnt % 30 == 0: 104 | if cnt % 30 == 0: 105 | logger.info(bgr_text) 106 | 107 | self.__image_server.image = image 108 | 109 | if self.__display: 110 | # Display image 111 | cv2.imshow("Image", image) 112 | 113 | key = cv2.waitKey(30) & 0xFF 114 | 115 | if key == 255: 116 | pass 117 | elif key == ord("c") or key == ord(" "): 118 | print(bgr_text) 119 | elif roi_y >= self.move_inc and (key == 0 or key == ord("k")): # Up 120 | self.y_adj -= self.move_inc 121 | elif roi_y <= height - self.roi_size - self.move_inc and (key == 1 or key == ord("j")): # Down 122 | self.y_adj += self.move_inc 123 | elif roi_x >= self.move_inc and (key == 2 or key == ord("h")): # Left 124 | self.x_adj -= self.move_inc 125 | elif roi_x <= width - self.roi_size - self.move_inc - self.move_inc \ 126 | and (key == 3 or key == ord("l")): # Right 127 | self.x_adj += self.move_inc 128 | elif self.roi_size >= self.roi_inc * 2 and (key == ord("-") or key == ord("_")): 129 | self.roi_size -= self.roi_inc 130 | self.x_adj, self.y_adj = 0, 0 131 | elif self.roi_size <= self.roi_inc * 49 and (key == ord("+") or key == ord("=")): 132 | self.roi_size += self.roi_inc 133 | self.x_adj, self.y_adj = 0, 0 134 | elif key == ord("r"): 135 | self.__width = self.__orig_width 136 | self.roi_size = self.orig_roi_size 137 | elif key == ord("<"): 138 | self.__width -= 10 139 | elif key == ord(">"): 140 | self.__width += 10 141 | elif key == ord("q"): 142 | self.__cam.close() 143 | break 144 | 145 | def stop(self): 146 | self.__image_server.stop() 147 | 148 | 149 | def main(): 150 | # Parse CLI args 151 | args = setup_cli_args(cli.width, 152 | cli.usb_camera, 153 | # cli.usb_port, 154 | cli.display, 155 | cli.flip_x, 156 | cli.flip_y, 157 | cli.http_host, 158 | cli.http_file, 159 | cli.http_delay_secs, 160 | cli.http_verbose, 161 | cli.log_level) 162 | 163 | # Setup logging 164 | setup_logging(level=args[LOG_LEVEL]) 165 | 166 | color_picker = ColorPicker(**strip_loglevel(args)) 167 | try: 168 | color_picker.start() 169 | except KeyboardInterrupt: 170 | pass 171 | finally: 172 | color_picker.stop() 173 | 174 | logger.info("Exiting...") 175 | 176 | 177 | if __name__ == "__main__": 178 | main() 179 | -------------------------------------------------------------------------------- /dual_object_filter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import logging 5 | 6 | import cv2 7 | import opencv_defaults as defs 8 | from cli_args import LOG_LEVEL 9 | from constants import DRAW_CONTOUR, DRAW_BOX, HTTP_DELAY_SECS, HTTP_VERBOSE, HTTP_FILE, VERTICAL_LINES 10 | from constants import HORIZONTAL_LINES, MASK_X, MASK_Y, USB_PORT 11 | from constants import MINIMUM_PIXELS, GRPC_PORT, HSV_RANGE, LEDS, CAMERA_NAME, HTTP_HOST, USB_CAMERA 12 | from constants import WIDTH, DISPLAY, BGR_COLOR, MIDDLE_PERCENT, FLIP_X, FLIP_Y 13 | from opencv_utils import BLUE, GREEN, RED, YELLOW 14 | from opencv_utils import get_moment 15 | from utils import setup_logging 16 | 17 | from generic_filter import GenericFilter 18 | from object_tracker import ObjectTracker 19 | 20 | logger = logging.getLogger(__name__) 21 | 22 | 23 | class DualObjectFilter(GenericFilter): 24 | def __init__(self, tracker, *args, **kwargs): 25 | super(DualObjectFilter, self).__init__(tracker, *args, **kwargs) 26 | self.contour1, self.contour2 = None, None 27 | self.img_x1, self.img_y1 = -1, -1 28 | self.img_x2, self.img_y2 = -1, -1 29 | self.avg_x, self.avg_y = -1, -1 30 | self.height, self.width = None, None 31 | 32 | def reset_data(self): 33 | self.avg_x, self.avg_y = -1, -1 34 | 35 | def process_image(self, image): 36 | self.contour1, self.contour2 = None, None 37 | self.reset_data() 38 | self.height, self.width = image.shape[:2] 39 | 40 | # Find the 2 largest vertical contours 41 | self.contours = self.contour_finder.get_max_contours(image, count=2) 42 | # self.contours = self.contour_finder.get_max_vertical_contours(image, count=2) 43 | 44 | # Check for > 2 in case one of the targets is divided. 45 | # The calculation will be off, but something will be better than nothing 46 | if self.contours is not None and len(self.contours) >= 2: 47 | self.contour1, area1, self.img_x1, self.img_y1 = get_moment(self.contours[0]) 48 | self.contour2, area2, self.img_x2, self.img_y2 = get_moment(self.contours[1]) 49 | 50 | # Calculate the average location between the two midpoints 51 | self.avg_x = (abs(self.img_x1 - self.img_x2) / 2) + min(self.img_x1, self.img_x2) 52 | self.avg_y = (abs(self.img_y1 - self.img_y2) / 2) + min(self.img_y1, self.img_y2) 53 | 54 | def publish_data(self): 55 | # Write location if it is different from previous value written 56 | if self.avg_x != self.prev_x or self.avg_y != self.prev_y: 57 | self.location_server.write_location(self.avg_x, self.avg_y, self.width, self.height, self.middle_inc) 58 | self.prev_x, self.prev_y = self.avg_x, self.avg_y 59 | 60 | def markup_image(self, image): 61 | mid_x, mid_y = self.width / 2, self.height / 2 62 | middle_inc = int(self.middle_inc) 63 | 64 | x_in_middle = mid_x - middle_inc <= self.avg_x <= mid_x + middle_inc 65 | y_in_middle = mid_y - middle_inc <= self.avg_y <= mid_y + middle_inc 66 | x_color = GREEN if x_in_middle else RED if self.avg_x == -1 else BLUE 67 | y_color = GREEN if y_in_middle else RED if self.avg_y == -1 else BLUE 68 | 69 | # Set Blinkt leds 70 | if self.leds: 71 | self.set_leds(x_color, y_color) 72 | 73 | if not self.tracker.markup_image: 74 | return 75 | 76 | text = "#{0} ({1}, {2})".format(self.tracker.cnt, self.width, self.height) 77 | text += " {0}%".format(self.tracker.middle_percent) 78 | 79 | if self.contours is not None and len(self.contours) >= 2: 80 | x1, y1, w1, h1 = cv2.boundingRect(self.contour1) 81 | x2, y2, w2, h2 = cv2.boundingRect(self.contour2) 82 | 83 | if self.draw_box: 84 | cv2.rectangle(image, (x1, y1), (x1 + w1, y1 + h1), BLUE, 2) 85 | cv2.rectangle(image, (x2, y2), (x2 + w2, y2 + h2), BLUE, 2) 86 | 87 | if self.draw_contour: 88 | cv2.drawContours(image, [self.contour1], -1, GREEN, 2) 89 | cv2.drawContours(image, [self.contour2], -1, GREEN, 2) 90 | 91 | cv2.circle(image, (self.img_x1, self.img_y1), 4, RED, -1) 92 | cv2.circle(image, (self.img_x2, self.img_y2), 4, RED, -1) 93 | 94 | # Draw midpoint 95 | cv2.circle(image, (self.avg_x, self.avg_y), 4, YELLOW, -1) 96 | text += " Avg: ({0}, {1})".format(self.avg_x, self.avg_y) 97 | 98 | # Draw the alignment lines 99 | if self.vertical_lines: 100 | cv2.line(image, (mid_x - middle_inc, 0), (mid_x - middle_inc, self.height), x_color, 1) 101 | cv2.line(image, (mid_x + middle_inc, 0), (mid_x + middle_inc, self.height), x_color, 1) 102 | if self.horizontal_lines: 103 | cv2.line(image, (0, mid_y - middle_inc), (self.width, mid_y - middle_inc), y_color, 1) 104 | cv2.line(image, (0, mid_y + middle_inc), (self.width, mid_y + middle_inc), y_color, 1) 105 | if self.display_text: 106 | cv2.putText(image, text, defs.TEXT_LOC, defs.TEXT_FONT, defs.TEXT_SIZE, RED, 1) 107 | 108 | 109 | def main(): 110 | # Parse CLI args 111 | args = ObjectTracker.cli_args() 112 | 113 | # Setup logging 114 | setup_logging(level=args[LOG_LEVEL]) 115 | 116 | tracker = ObjectTracker(width=args[WIDTH], 117 | middle_percent=args[MIDDLE_PERCENT], 118 | display=args[DISPLAY], 119 | flip_x=args[FLIP_X], 120 | flip_y=args[FLIP_Y], 121 | mask_x=args[MASK_X], 122 | mask_y=args[MASK_Y], 123 | usb_camera=args[USB_CAMERA], 124 | usb_port=args[USB_PORT], 125 | camera_name=args[CAMERA_NAME], 126 | http_host=args[HTTP_HOST], 127 | http_file=args[HTTP_FILE], 128 | http_delay_secs=args[HTTP_DELAY_SECS], 129 | http_verbose=args[HTTP_VERBOSE]) 130 | 131 | obj_filter = DualObjectFilter(tracker, 132 | bgr_color=args[BGR_COLOR], 133 | hsv_range=args[HSV_RANGE], 134 | minimum_pixels=args[MINIMUM_PIXELS], 135 | grpc_port=args[GRPC_PORT], 136 | leds=args[LEDS], 137 | display_text=True, 138 | draw_contour=args[DRAW_CONTOUR], 139 | draw_box=args[DRAW_BOX], 140 | vertical_lines=args[VERTICAL_LINES], 141 | horizontal_lines=args[HORIZONTAL_LINES]) 142 | try: 143 | tracker.start(obj_filter) 144 | except KeyboardInterrupt: 145 | pass 146 | finally: 147 | tracker.stop() 148 | 149 | logger.info("Exiting...") 150 | 151 | 152 | if __name__ == "__main__": 153 | main() 154 | -------------------------------------------------------------------------------- /object_tracker.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import time 3 | 4 | import arc852.cli_args as cli 5 | import arc852.opencv_utils as utils 6 | import cv2 7 | import imutils 8 | import numpy as np 9 | from arc852.camera import Camera 10 | from arc852.cli_args import setup_cli_args 11 | from arc852.image_server import ImageServer 12 | 13 | logger = logging.getLogger(__name__) 14 | 15 | BLACK = np.uint8((0, 0, 0)) 16 | 17 | 18 | class ObjectTracker(object): 19 | def __init__(self, 20 | width, 21 | middle_percent, 22 | display, 23 | flip_x, 24 | flip_y, 25 | mask_x, 26 | mask_y, 27 | usb_camera, 28 | usb_port, 29 | camera_name, 30 | http_host, 31 | http_file, 32 | http_delay_secs, 33 | http_verbose): 34 | self.__width = width 35 | self.__middle_percent = middle_percent 36 | self.__orig_width = width 37 | self.__orig_middle_percent = middle_percent 38 | self.__display = display 39 | self.__flip_x = flip_x 40 | self.__flip_y = flip_y 41 | self.__mask_x = mask_x 42 | self.__mask_y = mask_y 43 | self.__filters = None 44 | 45 | self.__stopped = False 46 | self.__cnt = 0 47 | self.__cam = Camera(usb_camera=usb_camera, usb_port=usb_port) 48 | self.__image_server = ImageServer(http_file, 49 | camera_name=camera_name, 50 | http_host=http_host, 51 | http_delay_secs=http_delay_secs, 52 | http_verbose=http_verbose) 53 | 54 | @property 55 | def width(self): 56 | return self.__width 57 | 58 | @width.setter 59 | def width(self, width): 60 | if 200 <= width <= 4000: 61 | self.__width = width 62 | if self.__filters: 63 | for f in self.__filters: 64 | f.reset() 65 | 66 | @property 67 | def middle_percent(self): 68 | return self.__middle_percent 69 | 70 | @middle_percent.setter 71 | def middle_percent(self, val): 72 | if 2 <= val <= 98: 73 | self.__middle_percent = val 74 | if self.__filters: 75 | for filter in self.__filters: 76 | filter.reset() 77 | 78 | @property 79 | def markup_image(self): 80 | return self.__display or self.__image_server.enabled 81 | 82 | # Do not run this in a background thread. cv2.waitKey has to run in main thread 83 | def start(self, *filters): 84 | self.__filters = filters 85 | 86 | if self.__filters: 87 | for f in self.__filters: 88 | f.start() 89 | 90 | self.__image_server.start() 91 | 92 | if not self.__cam.is_open(): 93 | logger.error("Camera is closed") 94 | 95 | while self.__cam.is_open() and not self.__stopped: 96 | try: 97 | image = self.__cam.read() 98 | if image is None: 99 | logger.error("Null image read from camera") 100 | time.sleep(.5) 101 | continue 102 | 103 | image = imutils.resize(image, width=self.width) 104 | image = self.flip(image) 105 | 106 | # Apply masks 107 | if self.__mask_y != 0: 108 | height, width = image.shape[:2] 109 | mask_height = abs(int((self.__mask_y / 100.0) * height)) 110 | if self.__mask_y < 0: 111 | image[0: mask_height, 0: width] = BLACK 112 | else: 113 | image[height - mask_height: height, 0: width] = BLACK 114 | 115 | if self.__mask_x != 0: 116 | height, width = image.shape[:2] 117 | mask_width = abs(int((self.__mask_x / 100.0) * width)) 118 | if self.__mask_x < 0: 119 | image[0: height, 0: mask_width] = BLACK 120 | else: 121 | image[0: height, width - mask_width: width] = BLACK 122 | 123 | if self.__filters: 124 | for f in self.__filters: 125 | f.process_image(image) 126 | for f in self.__filters: 127 | if f.predicate: 128 | f.predicate(f) 129 | f.publish_data() 130 | f.markup_image(image) 131 | 132 | self.__image_server.image = image 133 | 134 | if self.__display: 135 | self.display_image(image) 136 | 137 | self.__cnt += 1 138 | 139 | except KeyboardInterrupt as e: 140 | raise e 141 | except BaseException as e: 142 | logger.error("Unexpected error in main loop [{0}]".format(e), exc_info=True) 143 | time.sleep(1) 144 | 145 | self.__cam.close() 146 | 147 | def stop(self): 148 | self.__stopped = True 149 | 150 | if self.__filters: 151 | for f in self.__filters: 152 | f.stop() 153 | 154 | self.__image_server.stop() 155 | 156 | def display_image(self, image): 157 | cv2.imshow("Image", image) 158 | 159 | key = cv2.waitKey(1) & 0xFF 160 | 161 | if key == 255: 162 | pass 163 | elif key == ord("w"): 164 | self.width -= 10 165 | elif key == ord("W"): 166 | self.width += 10 167 | elif key == ord("-") or key == ord("_") or key == 0: 168 | self.middle_percent -= 1 169 | elif key == ord("+") or key == ord("=") or key == 1: 170 | self.middle_percent += 1 171 | elif key == ord("r"): 172 | self.width = self.__orig_width 173 | self.middle_percent = self.__orig_middle_percent 174 | elif key == ord("s"): 175 | utils.write_image(image, log_info=True) 176 | elif key == ord("q"): 177 | self.stop() 178 | 179 | def flip(self, image): 180 | img = image 181 | if self.__flip_x: 182 | img = cv2.flip(img, 0) 183 | if self.__flip_y: 184 | img = cv2.flip(img, 1) 185 | return img 186 | 187 | @staticmethod 188 | def cli_args(): 189 | return setup_cli_args(cli.bgr, 190 | cli.usb_camera, 191 | cli.usb_port, 192 | cli.width, 193 | cli.middle_percent, 194 | cli.minimum_pixels, 195 | cli.hsv_range, 196 | cli.grpc_port, 197 | cli.leds, 198 | cli.flip_x, 199 | cli.flip_y, 200 | cli.mask_x, 201 | cli.mask_y, 202 | cli.vertical_lines, 203 | cli.horizontal_lines, 204 | cli.camera_name_optional, 205 | cli.display, 206 | cli.draw_contour, 207 | cli.draw_box, 208 | cli.http_host, 209 | cli.http_file, 210 | cli.http_delay_secs, 211 | cli.http_verbose, 212 | cli.log_level) 213 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Code Health](https://landscape.io/github/athenian-robotics/object-tracking/master/landscape.svg?style=flat)](https://landscape.io/github/athenian-robotics/object-tracking/master) 2 | [![Code Climate](https://codeclimate.com/github/athenian-robotics/object-tracking/badges/gpa.svg)](https://codeclimate.com/github/athenian-robotics/object-tracking) 3 | 4 | # OpenCV Object Tracking and Pan/Tilt Servo Control 5 | 6 | ## Package Dependencies 7 | 8 | Using the *pysearchimages* Raspbian distro (which has OpenCV 3.2 bundled), 9 | install the required Python packages with: 10 | 11 | ```bash 12 | source start_py2cv3.sh 13 | pip install --upgrade pip 14 | pip install -r pip/requirements.txt 15 | sudo -H pip install arc852-robotics --extra-index-url https://pypi.fury.io/pambrose/ 16 | ``` 17 | 18 | Info on arc852-robotics is [here](https://github.com/athenian-robotics/arc852-robotics). 19 | 20 | ## Color Picker 21 | 22 | color_picker.py is used to choose a target BGR value. 23 | 24 | ### Usage 25 | 26 | ```bash 27 | $ ./color_picker.py 28 | ``` 29 | 30 | ### CLI Options 31 | 32 | | Option | Description | Default | 33 | |:---------------|----------------------------------------------------|----------------| 34 | | -u, --usb | Use USB Raspi camera | false | 35 | | -w, --width | Image width | 400 | 36 | | --display | Display image | false | 37 | | -x, --flipx | Flip image on X axis | false | 38 | | -y, --flipy | Flip image on Y axis | false | 39 | | --http | HTTP hostname:port | localhost:8080 | 40 | | --delay | HTTP delay secs | 0.25 | 41 | | -i, --file | HTTP template file | | 42 | | --verbose-http | Enable verbose HTTP log | false | 43 | | -v, --verbose | Enable debugging output | false | 44 | | -h, --help | Summary of options | | 45 | 46 | ### Display Keystrokes 47 | 48 | | Keystroke | Action | 49 | |:----------:|----------------------------------------------------| 50 | | c | Print current BGR value to console | 51 | | k | Move ROI up | 52 | | j | Move ROI down | 53 | | h | Move ROI left | 54 | | k | Move ROI right | 55 | | - | Decrease ROI size | 56 | | + | Increase ROI size | 57 | | < | Decrease image size | 58 | | > | Increase image size | 59 | | r | Reset ROI size and image size | 60 | | q | Quit | 61 | 62 | 63 | ## Single Object Tracker 64 | 65 | The object_tracker.py script runs the LocationServer, which generates 66 | the location of the object having the target BGR value. It supplies data to 67 | clients like servo_controller.py via gRPC. The smaller the image width, the smaller 68 | the matching target area. Thus, decreasing the image width may require also 69 | decreasing the minimum target pixel area. 70 | 71 | ### Usage 72 | 73 | ```bash 74 | $ python single_object_tracker.py --bgr "174, 56, 5" --display 75 | ``` 76 | 77 | ### CLI Options 78 | 79 | | Option | Description | Default | 80 | |:---------------|----------------------------------------------------|----------------| 81 | | --bgr | BGR target value | | 82 | | -u, --usb | Use USB Raspi camera | false | 83 | | -w, --width | Image width | 400 | 84 | | -e, --percent | Middle percent | 15 | 85 | | --min | Minimum target pixel area | 100 | 86 | | --range | HSV Range | 20 | 87 | | --leds | Enable Blinkt led feedback | false | 88 | | --display | Display image | false | 89 | | -x, --flipx | Flip image on X axis | false | 90 | | -y, --flipy | Flip image on Y axis | false | 91 | | -t, --http | HTTP hostname:port | localhost:8080 | 92 | | --delay | HTTP delay secs | 0.25 | 93 | | -i, --file | HTTP template file | | 94 | | -p, --port | gRPC server port | 50051 | 95 | | --verbose-http | Enable verbose HTTP log | false | 96 | | -v, --verbose | Enable debugging output | false | 97 | | -h, --help | Summary of options | | 98 | 99 | 100 | ### Sample Image 101 | 102 | ![alt text](https://github.com/pambrose/opencv_object_tracking/raw/master/docs/target_img.png "Object Tracking") 103 | 104 | 105 | ### Display Keystrokes 106 | 107 | | Keystroke | Action | 108 | |:----------:|----------------------------------------------------| 109 | | - | Decrease center area | 110 | | + | Increase center area | 111 | | w | Decrease image size | 112 | | W | Increase image size | 113 | | r | Reset center area and image size | 114 | | s | Save current image to disk | 115 | | q | Quit | 116 | 117 | 118 | ## FirmataServo Controller 119 | 120 | The firmata_controller.py script reads the location values provided by single_object_tracker.py 121 | and adjusts the pan/tilt servos accordingly. 122 | 123 | ### Usage 124 | 125 | ```bash 126 | $ firmata_controller.py --port ttyACM0 --grpc localhost 127 | ``` 128 | 129 | ### CLI Options 130 | 131 | | Option | Description | Default | 132 | |:---------------|----------------------------------------------------|---------| 133 | | -s, --serial | Arduino serial port | ttyACM0 | 134 | | -g, --grpc | Object Tracker gRPC server hostname | | 135 | | -x, --xservo | X servo PWM pin | 5 | 136 | | -y, --xyservo | Y servo PWM pin | 6 | 137 | | --calib | Calibration mode | false | 138 | | -v, --verbose | Enable debugging output | false | 139 | | -h, --help | Summary of options | | 140 | 141 | 142 | 143 | ## Relevant Links 144 | 145 | ### Hardware 146 | * [Raspberry Pi Camera](https://www.adafruit.com/products/3099) 147 | * [Pan/Tilt Kit](https://www.adafruit.com/product/1967) 148 | * [Blinkt](http://www.athenian-robotics.org/blinkt/) 149 | 150 | ### Software 151 | * [pyfirmata](http://www.athenian-robotics.org/pyfirmata/) 152 | * [gRPC](http://www.athenian-robotics.org/grpc/) 153 | * [OpenCV](http://www.athenian-robotics.org/opencv/) 154 | * [Plot.ly](http://www.athenian-robotics.org/plotly/) 155 | 156 | 157 | Instructions on how to display Raspi OpenCV camera images on a Mac are 158 | [here](http://www.athenian-robotics.org/opencv/) -------------------------------------------------------------------------------- /proto/location_service_pb2.py: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: proto/location_service.proto 3 | 4 | import sys 5 | 6 | _b = sys.version_info[0] < 3 and (lambda x: x) or (lambda x: x.encode('latin1')) 7 | from google.protobuf import descriptor as _descriptor 8 | from google.protobuf import message as _message 9 | from google.protobuf import reflection as _reflection 10 | from google.protobuf import symbol_database as _symbol_database 11 | 12 | # @@protoc_insertion_point(imports) 13 | 14 | _sym_db = _symbol_database.Default() 15 | 16 | DESCRIPTOR = _descriptor.FileDescriptor( 17 | name='proto/location_service.proto', 18 | package='opencv_object_tracking', 19 | syntax='proto3', 20 | serialized_pb=_b( 21 | '\n\x1cproto/location_service.proto\x12\x16opencv_object_tracking\"\x1a\n\nClientInfo\x12\x0c\n\x04info\x18\x01 \x01(\t\"\x1a\n\nServerInfo\x12\x0c\n\x04info\x18\x01 \x01(\t\"_\n\x08Location\x12\n\n\x02id\x18\x01 \x01(\x05\x12\t\n\x01x\x18\x02 \x01(\x05\x12\t\n\x01y\x18\x03 \x01(\x05\x12\r\n\x05width\x18\x04 \x01(\x05\x12\x0e\n\x06height\x18\x05 \x01(\x05\x12\x12\n\nmiddle_inc\x18\x06 \x01(\x05\x32\xc7\x01\n\x0fLocationService\x12Z\n\x0eregisterClient\x12\".opencv_object_tracking.ClientInfo\x1a\".opencv_object_tracking.ServerInfo\"\x00\x12X\n\x0cgetLocations\x12\".opencv_object_tracking.ClientInfo\x1a .opencv_object_tracking.Location\"\x00\x30\x01\x62\x06proto3') 22 | ) 23 | _sym_db.RegisterFileDescriptor(DESCRIPTOR) 24 | 25 | _CLIENTINFO = _descriptor.Descriptor( 26 | name='ClientInfo', 27 | full_name='opencv_object_tracking.ClientInfo', 28 | filename=None, 29 | file=DESCRIPTOR, 30 | containing_type=None, 31 | fields=[ 32 | _descriptor.FieldDescriptor( 33 | name='info', full_name='opencv_object_tracking.ClientInfo.info', index=0, 34 | number=1, type=9, cpp_type=9, label=1, 35 | has_default_value=False, default_value=_b("").decode('utf-8'), 36 | message_type=None, enum_type=None, containing_type=None, 37 | is_extension=False, extension_scope=None, 38 | options=None), 39 | ], 40 | extensions=[ 41 | ], 42 | nested_types=[], 43 | enum_types=[ 44 | ], 45 | options=None, 46 | is_extendable=False, 47 | syntax='proto3', 48 | extension_ranges=[], 49 | oneofs=[ 50 | ], 51 | serialized_start=56, 52 | serialized_end=82, 53 | ) 54 | 55 | _SERVERINFO = _descriptor.Descriptor( 56 | name='ServerInfo', 57 | full_name='opencv_object_tracking.ServerInfo', 58 | filename=None, 59 | file=DESCRIPTOR, 60 | containing_type=None, 61 | fields=[ 62 | _descriptor.FieldDescriptor( 63 | name='info', full_name='opencv_object_tracking.ServerInfo.info', index=0, 64 | number=1, type=9, cpp_type=9, label=1, 65 | has_default_value=False, default_value=_b("").decode('utf-8'), 66 | message_type=None, enum_type=None, containing_type=None, 67 | is_extension=False, extension_scope=None, 68 | options=None), 69 | ], 70 | extensions=[ 71 | ], 72 | nested_types=[], 73 | enum_types=[ 74 | ], 75 | options=None, 76 | is_extendable=False, 77 | syntax='proto3', 78 | extension_ranges=[], 79 | oneofs=[ 80 | ], 81 | serialized_start=84, 82 | serialized_end=110, 83 | ) 84 | 85 | _LOCATION = _descriptor.Descriptor( 86 | name='Location', 87 | full_name='opencv_object_tracking.Location', 88 | filename=None, 89 | file=DESCRIPTOR, 90 | containing_type=None, 91 | fields=[ 92 | _descriptor.FieldDescriptor( 93 | name='id', full_name='opencv_object_tracking.Location.id', index=0, 94 | number=1, type=5, cpp_type=1, label=1, 95 | has_default_value=False, default_value=0, 96 | message_type=None, enum_type=None, containing_type=None, 97 | is_extension=False, extension_scope=None, 98 | options=None), 99 | _descriptor.FieldDescriptor( 100 | name='x', full_name='opencv_object_tracking.Location.x', index=1, 101 | number=2, type=5, cpp_type=1, label=1, 102 | has_default_value=False, default_value=0, 103 | message_type=None, enum_type=None, containing_type=None, 104 | is_extension=False, extension_scope=None, 105 | options=None), 106 | _descriptor.FieldDescriptor( 107 | name='y', full_name='opencv_object_tracking.Location.y', index=2, 108 | number=3, type=5, cpp_type=1, label=1, 109 | has_default_value=False, default_value=0, 110 | message_type=None, enum_type=None, containing_type=None, 111 | is_extension=False, extension_scope=None, 112 | options=None), 113 | _descriptor.FieldDescriptor( 114 | name='width', full_name='opencv_object_tracking.Location.width', index=3, 115 | number=4, type=5, cpp_type=1, label=1, 116 | has_default_value=False, default_value=0, 117 | message_type=None, enum_type=None, containing_type=None, 118 | is_extension=False, extension_scope=None, 119 | options=None), 120 | _descriptor.FieldDescriptor( 121 | name='height', full_name='opencv_object_tracking.Location.height', index=4, 122 | number=5, type=5, cpp_type=1, label=1, 123 | has_default_value=False, default_value=0, 124 | message_type=None, enum_type=None, containing_type=None, 125 | is_extension=False, extension_scope=None, 126 | options=None), 127 | _descriptor.FieldDescriptor( 128 | name='middle_inc', full_name='opencv_object_tracking.Location.middle_inc', index=5, 129 | number=6, type=5, cpp_type=1, label=1, 130 | has_default_value=False, default_value=0, 131 | message_type=None, enum_type=None, containing_type=None, 132 | is_extension=False, extension_scope=None, 133 | options=None), 134 | ], 135 | extensions=[ 136 | ], 137 | nested_types=[], 138 | enum_types=[ 139 | ], 140 | options=None, 141 | is_extendable=False, 142 | syntax='proto3', 143 | extension_ranges=[], 144 | oneofs=[ 145 | ], 146 | serialized_start=112, 147 | serialized_end=207, 148 | ) 149 | 150 | DESCRIPTOR.message_types_by_name['ClientInfo'] = _CLIENTINFO 151 | DESCRIPTOR.message_types_by_name['ServerInfo'] = _SERVERINFO 152 | DESCRIPTOR.message_types_by_name['Location'] = _LOCATION 153 | 154 | ClientInfo = _reflection.GeneratedProtocolMessageType('ClientInfo', (_message.Message,), dict( 155 | DESCRIPTOR=_CLIENTINFO, 156 | __module__='proto.location_service_pb2' 157 | # @@protoc_insertion_point(class_scope:opencv_object_tracking.ClientInfo) 158 | )) 159 | _sym_db.RegisterMessage(ClientInfo) 160 | 161 | ServerInfo = _reflection.GeneratedProtocolMessageType('ServerInfo', (_message.Message,), dict( 162 | DESCRIPTOR=_SERVERINFO, 163 | __module__='proto.location_service_pb2' 164 | # @@protoc_insertion_point(class_scope:opencv_object_tracking.ServerInfo) 165 | )) 166 | _sym_db.RegisterMessage(ServerInfo) 167 | 168 | Location = _reflection.GeneratedProtocolMessageType('Location', (_message.Message,), dict( 169 | DESCRIPTOR=_LOCATION, 170 | __module__='proto.location_service_pb2' 171 | # @@protoc_insertion_point(class_scope:opencv_object_tracking.Location) 172 | )) 173 | _sym_db.RegisterMessage(Location) 174 | 175 | try: 176 | # THESE ELEMENTS WILL BE DEPRECATED. 177 | # Please use the generated *_pb2_grpc.py files instead. 178 | import grpc 179 | from grpc.framework.common import cardinality 180 | from grpc.framework.interfaces.face import utilities as face_utilities 181 | from grpc.beta import implementations as beta_implementations 182 | from grpc.beta import interfaces as beta_interfaces 183 | 184 | 185 | class LocationServiceStub(object): 186 | 187 | def __init__(self, channel): 188 | """Constructor. 189 | 190 | Args: 191 | channel: A grpc.Channel. 192 | """ 193 | self.registerClient = channel.unary_unary( 194 | '/opencv_object_tracking.LocationService/registerClient', 195 | request_serializer=ClientInfo.SerializeToString, 196 | response_deserializer=ServerInfo.FromString, 197 | ) 198 | self.getLocations = channel.unary_stream( 199 | '/opencv_object_tracking.LocationService/getLocations', 200 | request_serializer=ClientInfo.SerializeToString, 201 | response_deserializer=Location.FromString, 202 | ) 203 | 204 | 205 | class LocationServiceServicer(object): 206 | 207 | def registerClient(self, request, context): 208 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 209 | context.set_details('Method not implemented!') 210 | raise NotImplementedError('Method not implemented!') 211 | 212 | def getLocations(self, request, context): 213 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 214 | context.set_details('Method not implemented!') 215 | raise NotImplementedError('Method not implemented!') 216 | 217 | 218 | def add_LocationServiceServicer_to_server(servicer, server): 219 | rpc_method_handlers = { 220 | 'registerClient': grpc.unary_unary_rpc_method_handler( 221 | servicer.registerClient, 222 | request_deserializer=ClientInfo.FromString, 223 | response_serializer=ServerInfo.SerializeToString, 224 | ), 225 | 'getLocations': grpc.unary_stream_rpc_method_handler( 226 | servicer.getLocations, 227 | request_deserializer=ClientInfo.FromString, 228 | response_serializer=Location.SerializeToString, 229 | ), 230 | } 231 | generic_handler = grpc.method_handlers_generic_handler( 232 | 'opencv_object_tracking.LocationService', rpc_method_handlers) 233 | server.add_generic_rpc_handlers((generic_handler,)) 234 | 235 | 236 | class BetaLocationServiceServicer(object): 237 | """The Beta API is deprecated for 0.15.0 and later. 238 | 239 | It is recommended to use the GA API (classes and functions in this 240 | file not marked beta) for all further purposes. This class was generated 241 | only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0.""" 242 | 243 | def registerClient(self, request, context): 244 | context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) 245 | 246 | def getLocations(self, request, context): 247 | context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) 248 | 249 | 250 | class BetaLocationServiceStub(object): 251 | """The Beta API is deprecated for 0.15.0 and later. 252 | 253 | It is recommended to use the GA API (classes and functions in this 254 | file not marked beta) for all further purposes. This class was generated 255 | only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0.""" 256 | 257 | def registerClient(self, request, timeout, metadata=None, with_call=False, protocol_options=None): 258 | raise NotImplementedError() 259 | 260 | registerClient.future = None 261 | 262 | def getLocations(self, request, timeout, metadata=None, with_call=False, protocol_options=None): 263 | raise NotImplementedError() 264 | 265 | 266 | def beta_create_LocationService_server(servicer, pool=None, pool_size=None, default_timeout=None, 267 | maximum_timeout=None): 268 | """The Beta API is deprecated for 0.15.0 and later. 269 | 270 | It is recommended to use the GA API (classes and functions in this 271 | file not marked beta) for all further purposes. This function was 272 | generated only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0""" 273 | request_deserializers = { 274 | ('opencv_object_tracking.LocationService', 'getLocations'): ClientInfo.FromString, 275 | ('opencv_object_tracking.LocationService', 'registerClient'): ClientInfo.FromString, 276 | } 277 | response_serializers = { 278 | ('opencv_object_tracking.LocationService', 'getLocations'): Location.SerializeToString, 279 | ('opencv_object_tracking.LocationService', 'registerClient'): ServerInfo.SerializeToString, 280 | } 281 | method_implementations = { 282 | ('opencv_object_tracking.LocationService', 'getLocations'): face_utilities.unary_stream_inline( 283 | servicer.getLocations), 284 | ('opencv_object_tracking.LocationService', 'registerClient'): face_utilities.unary_unary_inline( 285 | servicer.registerClient), 286 | } 287 | server_options = beta_implementations.server_options(request_deserializers=request_deserializers, 288 | response_serializers=response_serializers, 289 | thread_pool=pool, thread_pool_size=pool_size, 290 | default_timeout=default_timeout, 291 | maximum_timeout=maximum_timeout) 292 | return beta_implementations.server(method_implementations, options=server_options) 293 | 294 | 295 | def beta_create_LocationService_stub(channel, host=None, metadata_transformer=None, pool=None, pool_size=None): 296 | """The Beta API is deprecated for 0.15.0 and later. 297 | 298 | It is recommended to use the GA API (classes and functions in this 299 | file not marked beta) for all further purposes. This function was 300 | generated only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0""" 301 | request_serializers = { 302 | ('opencv_object_tracking.LocationService', 'getLocations'): ClientInfo.SerializeToString, 303 | ('opencv_object_tracking.LocationService', 'registerClient'): ClientInfo.SerializeToString, 304 | } 305 | response_deserializers = { 306 | ('opencv_object_tracking.LocationService', 'getLocations'): Location.FromString, 307 | ('opencv_object_tracking.LocationService', 'registerClient'): ServerInfo.FromString, 308 | } 309 | cardinalities = { 310 | 'getLocations': cardinality.Cardinality.UNARY_STREAM, 311 | 'registerClient': cardinality.Cardinality.UNARY_UNARY, 312 | } 313 | stub_options = beta_implementations.stub_options(host=host, metadata_transformer=metadata_transformer, 314 | request_serializers=request_serializers, 315 | response_deserializers=response_deserializers, 316 | thread_pool=pool, thread_pool_size=pool_size) 317 | return beta_implementations.dynamic_stub(channel, 'opencv_object_tracking.LocationService', cardinalities, 318 | options=stub_options) 319 | except ImportError: 320 | pass 321 | # @@protoc_insertion_point(module_scope) 322 | --------------------------------------------------------------------------------