diff --git a/.gitignore b/.gitignore index b006761b8..1dc29ec9b 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ Python/app/__pycache__/ Python/app/handlers/__pycache__/ \.vscode/ + +backend/settings/ diff --git a/README.md b/README.md index b12fe724d..1491e47d1 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,84 @@ -Chameleon-Vision +# Chameleon-Vision +a free software for FRC teams to use for vision proccesing on their robots +## getting started +These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system. -https://docs.google.com/document/d/1qDuwHtpIPJfyXGIL8PJG89LZwRWbn2J9f-5g19lWL9U/edit?usp=sharing \ No newline at end of file +### Prerequisites + +so in order to run this project we will need to install python in order to run the backend and node.js with vue.js in order to run the fronted +#### backend +- python 3.7 and above +- opencv 3.4.5 +- tornado web framework +- robotpy-cscore +- pynetworktables +- pymq + +#### frontend +- vue.js +- vuex +- vue-router +- less and less-loader +- iView +- vue-native-websocket + + +### installing +#### for the backend +1. sudo apt-get update +2. apt-get dist-upgrade +3. sudo apt-get upgrade +4. sudo apt-get install python3-pip python3-dev cmake zip unzip build-essential git --fix-missing +5. sudo pip3 install numpy (if on raspberry pi do "sudo apt-get install python3-numpy") +6. OPENCV_VERSION=3.4.5 +7. wget -O opencv.zip https://github.com/opencv/opencv/archive/${OPENCV_VERSION}.zip +8. unzip opencv.zip +9. cd ~/opencv-${OPENCV_VERSION}/ +10. mkdir build +11. cd build +12. cmake -D BUILD_SHARED_LIBS=ON -D BUILD_opencv_python3=ON .. +13. make -j $(python3 -c 'import multiprocessing as mp; print(int(mp.cpu_count() * 1.5))') +14. make install +15. ldconfig +16. pip3 install robotpy-cscore pyzmq tornado + +to run the backend: +``` +sudo python3 Main.py +``` +if backed gets suck or no camera are recognized after a crash do: +``` +sudo pkill -9 python3 +``` + +#### for the frontend +1. sudo apt-get install nodejs npm +2. cd chameleon-client +3. sudo npm install +4. sudo npm install @vue/cli + +to run the front end you can open the cli ui by: +``` +vue ui +``` +of you can auto serve the ui by +``` +npm run serve +``` +## Hardware +this is important when choosing your sbc it is more important to have a good usb controller that a good cpu +on the odroid xu4 which is very fast i have got many bottlenecks from the usb controller and many times making the program crach +#### networking +it is very important to install Bonjour + + +## docs +main docs can be found at [google docs](https://docs.google.com/document/d/1qDuwHtpIPJfyXGIL8PJG89LZwRWbn2J9f-5g19lWL9U/edit?usp=sharing) + +## Authors +* **Sagi Frimer** - *initial work* - websocket, settings manager, UI +* **Ori Agranat** - *main coder* - vision loop , UI, websocket, networktables + +## Acknowledgments +* the [robotpy project](https://github.com/robotpy) and mainly the cscore libs +* basically all of stackoverflow diff --git a/backend/Main.py b/backend/Main.py index e1a47bc3e..02aa2c372 100644 --- a/backend/Main.py +++ b/backend/Main.py @@ -1,11 +1,14 @@ +from datetime import timedelta +from networktables import NetworkTables import tornado.ioloop import logging from app.ChameleonVisionApp import ChameleonApplication from app.classes.SettingsManager import SettingsManager from tornado.options import options -from app.handlers.VisionHandler import VisionHandler import threading import asyncio +from app.handlers.SocketHandler import send_all_async +from app.handlers.CameraHander import CameraHandler def run_server(): @@ -15,15 +18,24 @@ def run_server(): print(f"Serving on port {options.port}") app.listen(options.port) tornado.ioloop.IOLoop.current().start() + tornado.ioloop.IOLoop.instance().add_timeout(timedelta(seconds=1), + send_all_async) + + +def run(): + NetworkTables.startClientTeam(team=settings_manager.general_settings.get("team_number", 1577)) + # NetworkTables.initialize("localhost") + port = 5550 + for cam_name in settings_manager.usb_cameras: + CameraHandler(cam_name, port).run() + port += 1 + if __name__ == "__main__": logging.basicConfig(level=logging.DEBUG) - SettingsManager() - - VisionHandler().run() + settings_manager = SettingsManager() + run() server_thread = threading.Thread(target=run_server) server_thread.start() - while True: - pass diff --git a/backend/app/ChameleonVisionApp.py b/backend/app/ChameleonVisionApp.py index cd786c272..7336ae001 100644 --- a/backend/app/ChameleonVisionApp.py +++ b/backend/app/ChameleonVisionApp.py @@ -1,7 +1,6 @@ import tornado.web import tornado.websocket import os - from .handlers.MainHandler import MainHandler from .handlers.SocketHandler import ChameleonWebSocket from tornado.options import define diff --git a/backend/app/classes/Exceptions.py b/backend/app/classes/Exceptions.py index 59a8363e3..67493d2aa 100644 --- a/backend/app/classes/Exceptions.py +++ b/backend/app/classes/Exceptions.py @@ -1,4 +1,3 @@ - class PipelineAlreadyExistsException(Exception): def __init__(self, pipe_name): diff --git a/backend/app/classes/SettingsManager.py b/backend/app/classes/SettingsManager.py index 9e74507cc..75ae2182b 100644 --- a/backend/app/classes/SettingsManager.py +++ b/backend/app/classes/SettingsManager.py @@ -1,4 +1,3 @@ -import socket import os import json import cv2 diff --git a/backend/app/handlers/CameraHander.py b/backend/app/handlers/CameraHander.py new file mode 100644 index 000000000..8690693f4 --- /dev/null +++ b/backend/app/handlers/CameraHander.py @@ -0,0 +1,225 @@ +import math +import cv2 +import numpy +from cscore import CameraServer +from app.classes.SettingsManager import SettingsManager +from ..handlers.SocketHandler import send_all_async +from multiprocessing import Process +import threading +import zmq +import asyncio +import time +from networktables import NetworkTables +import networktables +from .VisionHandler import VisionHandler + + +class CameraHandler: + def __init__(self, cam_name, port): + #settings vars up for vision loop + self.cs = CameraServer.getInstance() + self.settings_manager = SettingsManager() + self.vision_handler = VisionHandler() + self.port = port + self.cam_name = cam_name + self.image = None + self.p_image = None + self.table = None + self.nt_data = {'valid': False} + self.time_stamp = 0 + + def run(self): + #starting main thread + threading.Thread(target=self.thread_proc).start() + + def thread_proc(self): + self.settings_manager.cams_curr_pipeline[self.cam_name] = "pipeline0" + pipeline = self.settings_manager.cams[self.cam_name]["pipelines"][self.settings_manager.cams_curr_pipeline[self.cam_name]] + FOV = self.settings_manager.cams[self.cam_name]["FOV"] + + def change_camera_values(pipline): + self.settings_manager.usb_cameras[self.cam_name].setBrightness(pipeline['brightness']) + self.settings_manager.usb_cameras[self.cam_name].setExposureManual(pipeline['exposure']) + self.settings_manager.usb_cameras[self.cam_name].setWhiteBalanceAuto() + + def pipeline_listener(table, key, value, is_new): + asyncio.set_event_loop(asyncio.new_event_loop()) + self.settings_manager.cams_curr_pipeline[self.cam_name] = value + change_camera_values(pipeline) + if self.cam_name == self.settings_manager.general_settings['curr_camera']: + self.settings_manager.general_settings['curr_pipeline'] = value + update_settings = self.settings_manager.get_curr_pipeline() + update_settings['curr_pipeline'] = self.settings_manager.general_settings["curr_pipeline"] + send_all_async(update_settings) + + def mode_listener(table, key, value, is_new): + change_camera_values({ + 'brightness': 25, + 'exposure': 15 + }) + #setting up network table + self.table = NetworkTables.getTable("/Chameleon-Vision/" + self.cam_name) + self.table.putString('Pipeline', self.settings_manager.cams_curr_pipeline[self.cam_name]) + self.table.addEntryListenerEx(pipeline_listener, key="Pipeline", + flags=networktables.NetworkTablesInstance.NotifyFlags.UPDATE) + self.table.addEntryListenerEx(mode_listener, key="Driver_Mode", + flags=networktables.NetworkTablesInstance.NotifyFlags.UPDATE) + + # getting video from current camera + cv_sink = self.cs.getVideo(camera=self.settings_manager.usb_cameras[self.cam_name]) + + width = self.settings_manager.cams[self.cam_name]["video_mode"]["width"] + height = self.settings_manager.cams[self.cam_name]["video_mode"]["height"] + + # setting up a video server for camera + cv_publish = self.cs.putVideo(name=self.cam_name, width=width, height=height) + # saving camera port in cam name dict for usage in client + self.settings_manager.cams_port[self.cam_name] = self.cs._sinks['serve_' + self.cam_name].getPort() + + # setting up a zmq connection to the opencv subprocess + context = zmq.Context() + socket = context.socket(zmq.PAIR) + socket.bind('tcp://*:%s' % str(self.port)) + + # starting the process with initial values + p = Process(target=self.camera_process, args=(self.cam_name, self.port, FOV)) + p.start() + + change_camera_values(pipeline) + + def _publish_thread(): + #getting image values and publishing process image and data + self.image = numpy.zeros(shape=(width, height, 3), dtype=numpy.uint8) + self.p_image = self.image + while True: + try: + self.time_stamp, self.image = cv_sink.grabFrame(self.image) + cv_publish.putFrame(self.p_image) + self.table.putBoolean('valid', self.nt_data['valid']) + # check if point is valid + if self.nt_data['valid']: + # send the point using network tables + self.table.putNumber('pitch', self.nt_data['pitch']) + self.table.putNumber('yaw', self.nt_data['yaw']) + self.table.putNumber('fps', self.nt_data['fps']) + self.table.putNumber('time_stamp', self.time_stamp) + # if the selected camera in ui is this cam send the point to the ui + except: + pass + + def _socket_thread(): + #publishing to websocket at slower interval + asyncio.set_event_loop(asyncio.new_event_loop()) + while True: + time.sleep(0.1) + if self.settings_manager.general_settings['curr_camera'] == self.cam_name: + try: + send_all_async({ + 'raw_point': self.nt_data['raw_point'], + 'point': { + 'pitch': self.nt_data['pitch'], + 'yaw': self.nt_data['yaw'], + 'fps': self.nt_data['fps'] + } + }) + except: + pass + + threading.Thread(target=_publish_thread).start() + threading.Thread(target=_socket_thread).start() + + while True: + #sending and reciving data from opencv sub process + pipeline = self.settings_manager.cams[self.cam_name]["pipelines"][ + self.settings_manager.cams_curr_pipeline[self.cam_name]] + socket.send_json(dict( + pipeline=pipeline + ), zmq.SNDMORE) + + socket.send_pyobj(self.image) + self.p_image = socket.recv_pyobj() + self.nt_data = socket.recv_json() + + def camera_process(self, cam_name, port, FOV): + from fractions import Fraction + #calc fov + diagonalView = math.radians(FOV) + + width = self.settings_manager.cams[cam_name]["video_mode"]["width"] + height = self.settings_manager.cams[cam_name]["video_mode"]["height"] + centerX = (width / 2) - .5 + centerY = (height / 2) - .5 + cam_area = width * height + + aspect_fraction = Fraction(width, height) + horizontal_ratio = aspect_fraction.numerator + vertical_ratio = aspect_fraction.denominator + + horizontalView = math.atan(math.tan(diagonalView / 2) * (horizontal_ratio / diagonalView)) * 2 + verticalView = math.atan(math.tan(diagonalView / 2) * (vertical_ratio / diagonalView)) * 2 + + H_FOCAL_LENGTH = width / (2 * math.tan((horizontalView / 2))) + V_FOCAL_LENGTH = height / (2 * math.tan((verticalView / 2))) + #setting up zmq socket + context = zmq.Context() + socket = context.socket(zmq.PAIR) + socket.connect('tcp://localhost:%s' % str(port)) + #setting up filter countours class + filter_contours = self.vision_handler.Filter_Contours(center_x=centerX, center_y=centerY) + + x = 1 + counter = 0 + start_time = time.time() + fps = 0 + + while True: + obj = socket.recv_json() + image = socket.recv_pyobj() + curr_pipeline = obj["pipeline"] + if curr_pipeline['orientation'] == "Inverted": + M = cv2.getRotationMatrix2D((width / 2, height / 2), 180, 1) + image = cv2.warpAffine(image, M, (width, height)) + hsv_image = self.vision_handler._hsv_threshold(curr_pipeline["hue"], + curr_pipeline["saturation"], curr_pipeline["value"], + image, curr_pipeline["erode"], curr_pipeline["dilate"]) + # if table.getBoolean("Driver_Mode", False): + contours = self.vision_handler.find_contours(hsv_image) + filtered_contours = filter_contours.filter_contours(input_contours=contours, area=curr_pipeline['area'], + ratio=curr_pipeline['ratio'], + extent=curr_pipeline['extent'], + sort_mode=curr_pipeline['sort_mode'], cam_area=cam_area, + target_grouping=curr_pipeline['target_group'], + target_intersection= + curr_pipeline['target_intersection']) + final_contour = self.vision_handler.output_contour(filtered_contours) + try: + center = final_contour[0] + center_x = (center[1] - curr_pipeline['B']) / curr_pipeline["M"] + center_y = (center[0] * curr_pipeline["M"]) + curr_pipeline["B"] + pitch = self.vision_handler.calculate_pitch(pixel_y=center[1], center_y=center_y, v_focal_length=V_FOCAL_LENGTH) + yaw = self.vision_handler.calculate_yaw(pixel_x=center[0], center_x=center_x, h_focal_length=H_FOCAL_LENGTH) + valid = True + except IndexError: + center = None + pitch = None + yaw = None + valid = False + + if curr_pipeline['is_binary']: + draw_image = hsv_image + else: + draw_image = image + res = self.vision_handler.draw_image(input_image=draw_image, contour=final_contour) + socket.send_pyobj(res) + socket.send_json(dict( + pitch=pitch, + yaw=yaw, + valid=valid, + raw_point=center, + fps=fps + )) + counter += 1 + if (time.time() - start_time) > x: + fps = (counter / (time.time() - start_time)) + counter = 0 + start_time = time.time() diff --git a/backend/app/handlers/SocketHandler.py b/backend/app/handlers/SocketHandler.py index 35c5750ee..b6f54d136 100644 --- a/backend/app/handlers/SocketHandler.py +++ b/backend/app/handlers/SocketHandler.py @@ -1,12 +1,10 @@ -import asyncio - import tornado.websocket import json from ..classes.Exceptions import NoCameraConnectedException from ..classes.SettingsManager import SettingsManager -web_socket_clients = [] +web_socket_clients = set() def send_all_async(message): @@ -18,6 +16,7 @@ def send_all_async(message): class ChameleonWebSocket(tornado.websocket.WebSocketHandler): + actions = {} set_this_camera_settings = ["exposure", "brightness"] @@ -38,7 +37,7 @@ class ChameleonWebSocket(tornado.websocket.WebSocketHandler): def open(self): self.send_full_settings() if self not in web_socket_clients: - web_socket_clients.append(self) + web_socket_clients.add(self) print("WebSocket opened") diff --git a/backend/app/handlers/VisionHandler.py b/backend/app/handlers/VisionHandler.py index 2d5a1f6af..b669c0177 100644 --- a/backend/app/handlers/VisionHandler.py +++ b/backend/app/handlers/VisionHandler.py @@ -1,22 +1,10 @@ -import asyncio -import time - -from networktables import NetworkTables -import networktables import cv2 import numpy -from cscore import CameraServer -from app.classes.SettingsManager import SettingsManager -from ..classes.Singleton import Singleton -from multiprocessing import Process -import threading -import zmq import math from enum import Enum, unique -from ..handlers.SocketHandler import send_all_async -class VisionHandler(metaclass=Singleton): +class VisionHandler(): def __init__(self): self.kernel = numpy.ones((5, 5), numpy.uint8) @@ -243,193 +231,3 @@ class VisionHandler(metaclass=Singleton): def calculate_yaw(self, pixel_x, center_x, h_focal_length): yaw = math.degrees(math.atan((pixel_x - center_x) / h_focal_length)) return yaw - - def run(self): - NetworkTables.startClientTeam(team=SettingsManager.general_settings.get("team_number", 1577)) - # NetworkTables.initialize("localhost") - - cs = CameraServer.getInstance() - port = 5550 - - for cam_name in SettingsManager().usb_cameras: - threading.Thread(target=self.thread_proc, args=(cs, cam_name, port)).start() - port += 1 - - def thread_proc(self, cs, cam_name, port=5557): - asyncio.set_event_loop(asyncio.new_event_loop()) - SettingsManager.cams_curr_pipeline[cam_name] = "pipeline0" - pipeline = SettingsManager().cams[cam_name]["pipelines"][SettingsManager.cams_curr_pipeline[cam_name]] - FOV = SettingsManager().cams[cam_name]["FOV"] - - def change_camera_values(pipline): - SettingsManager.usb_cameras[cam_name].setBrightness(pipeline['brightness']) - SettingsManager.usb_cameras[cam_name].setExposureManual(pipeline['exposure']) - SettingsManager.usb_cameras[cam_name].setWhiteBalanceAuto() - - def pipeline_listener(table, key, value, is_new): - asyncio.set_event_loop(asyncio.new_event_loop()) - SettingsManager.cams_curr_pipeline[cam_name] = value - change_camera_values(pipeline) - if cam_name == SettingsManager().general_settings['curr_camera']: - SettingsManager().general_settings['curr_pipeline'] = value - update_settings = SettingsManager().get_curr_pipeline() - update_settings['curr_pipeline'] = SettingsManager().general_settings["curr_pipeline"] - send_all_async(update_settings) - - def mode_listener(table, key, value, is_new): - change_camera_values({ - 'brightness': 25, - 'exposure': 15 - }) - - table = NetworkTables.getTable("/Chameleon-Vision/" + cam_name) - table.putString('Pipeline', SettingsManager.cams_curr_pipeline[cam_name]) - table.addEntryListenerEx(pipeline_listener, key="Pipeline", - flags=networktables.NetworkTablesInstance.NotifyFlags.UPDATE) - table.addEntryListenerEx(mode_listener, key="Driver_Mode", - flags=networktables.NetworkTablesInstance.NotifyFlags.UPDATE) - #gettings video from curent camera - cv_sink = cs.getVideo(camera=SettingsManager.usb_cameras[cam_name]) - - width = SettingsManager().cams[cam_name]["video_mode"]["width"] - height = SettingsManager().cams[cam_name]["video_mode"]["height"] - - image = numpy.zeros(shape=(width, height, 3), dtype=numpy.uint8) - - #setting up a video server for camera - cv_publish = cs.putVideo(name=cam_name, width=width, height=height) - # saving camera port in cam name dict for usage in client - SettingsManager().cams_port[cam_name] = cs._sinks['serve_'+cam_name].getPort() - - #setting up a zmq connection to the opencv subprocess - context = zmq.Context() - socket = context.socket(zmq.PAIR) - socket.bind('tcp://*:%s' % str(port)) - - #starting the process with inital values - p = Process(target=self.camera_process, args=(cam_name, port, FOV)) - p.start() - - change_camera_values(pipeline) - - while True: - pipeline = SettingsManager().cams[cam_name]["pipelines"][SettingsManager.cams_curr_pipeline[cam_name]] - _, image = cv_sink.grabFrame(image) - socket.send_json(dict( - pipeline=pipeline - ), zmq.SNDMORE) - - socket.send_pyobj(image) - p_image = socket.recv_pyobj() - nt_data = socket.recv_json() - table.putBoolean('valid', nt_data['valid']) - # check if point is valid - - # print(nt_data['fps']) - - if nt_data['valid']: - #send the point using network tables - table.putNumber('pitch', nt_data['pitch']) - table.putNumber('yaw', nt_data['yaw']) - #if the selected camera in ui is this cam send the point to the ui - - if SettingsManager().general_settings['curr_camera'] == cam_name: - try: - if nt_data['raw_point'] is not None: - send_all_async({ - 'raw_point': nt_data['raw_point'], - 'point': { - 'pitch': nt_data['pitch'], - 'yaw': nt_data['yaw'], - 'fps': nt_data['fps'] - } - }) - except Exception as e: - print(e) - #send the image to the camera server - - cv_publish.putFrame(p_image) - - def camera_process(self, cam_name, port, FOV): - from fractions import Fraction - - diagonalView = math.radians(FOV) #needs to be implemented in client - - width = SettingsManager().cams[cam_name]["video_mode"]["width"] - height = SettingsManager().cams[cam_name]["video_mode"]["height"] - centerX = (width / 2) - .5 - centerY = (height / 2) - .5 - cam_area = width * height - - aspect_fraction = Fraction(width,height) - horizontal_ratio = aspect_fraction.numerator - vertical_ratio = aspect_fraction.denominator - - horizontalView = math.atan(math.tan(diagonalView/2) * (horizontal_ratio / diagonalView)) * 2 - verticalView = math.atan(math.tan(diagonalView/2) * (vertical_ratio / diagonalView)) * 2 - - H_FOCAL_LENGTH = width / (2*math.tan((horizontalView/2))) - V_FOCAL_LENGTH = height / (2*math.tan((verticalView/2))) - - context = zmq.Context() - socket = context.socket(zmq.PAIR) - socket.connect('tcp://localhost:%s' % str(port)) - filter_contours = self.Filter_Contours(center_x=centerX, center_y=centerY) - x = 1 - counter = 0 - start_time = time.time() - fps = 0 - while True: - obj = socket.recv_json() - image = socket.recv_pyobj() - curr_pipeline = obj["pipeline"] - if curr_pipeline['orientation'] == "Inverted": - M = cv2.getRotationMatrix2D((width / 2, height / 2), 180, 1) - image = cv2.warpAffine(image, M, (width, height)) - hsv_image = self._hsv_threshold(curr_pipeline["hue"], - curr_pipeline["saturation"], curr_pipeline["value"], - image, curr_pipeline["erode"], curr_pipeline["dilate"]) - # if table.getBoolean("Driver_Mode", False): - contours = self.find_contours(hsv_image) - filtered_contours = filter_contours.filter_contours(input_contours=contours, area=curr_pipeline['area'], - ratio=curr_pipeline['ratio'], - extent=curr_pipeline['extent'], - sort_mode=curr_pipeline['sort_mode'], cam_area=cam_area, - target_grouping=curr_pipeline['target_group'], - target_intersection= - curr_pipeline['target_intersection']) - final_contour = self.output_contour(filtered_contours) - try: - center = final_contour[0] - center_x = (center[1] - curr_pipeline['B']) / curr_pipeline["M"] - center_y = (center[0] * curr_pipeline["M"]) + curr_pipeline["B"] - pitch = self.calculate_pitch(pixel_y=center[1], center_y=center_y, v_focal_length=V_FOCAL_LENGTH) - yaw = self.calculate_yaw(pixel_x=center[0], center_x=center_x, h_focal_length=H_FOCAL_LENGTH) - valid = True - except IndexError: - center = None - pitch = None - yaw = None - valid = False - - if curr_pipeline['is_binary']: - draw_image = hsv_image - else: - draw_image = image - res = self.draw_image(input_image=draw_image, contour=final_contour) - socket.send_pyobj(res) - socket.send_json(dict( - pitch=pitch, - yaw=yaw, - valid=valid, - raw_point=center, - fps=fps - )) - counter += 1 - if (time.time() - start_time) > x: - fps = (counter / (time.time() - start_time)) - counter = 0 - start_time = time.time() - - - diff --git a/backend/requirement.txt b/backend/requirement.txt new file mode 100644 index 000000000..47a0882ef --- /dev/null +++ b/backend/requirement.txt @@ -0,0 +1,3 @@ +tornado +pyzmq +robotpy-cscore \ No newline at end of file diff --git a/backend/settings/cams/USB Camera-B4.09.24.1.json b/backend/settings/cams/USB Camera-B4.09.24.1.json index 2dd7aa24f..91187d8b0 100644 --- a/backend/settings/cams/USB Camera-B4.09.24.1.json +++ b/backend/settings/cams/USB Camera-B4.09.24.1.json @@ -1 +1 @@ -{"pipelines": {"pipeline0": {"exposure": 50, "brightness": 15, "orientation": "Normal", "hue": [0, 100], "saturation": [0, 100], "value": [0, 100], "erode": false, "dilate": false, "area": [0, 100], "ratio": [0, 20], "extent": [0, 100], "is_binary": 0, "sort_mode": "Largest", "target_group": "Single", "target_intersection": "Up", "M": 1, "B": 0}, "pipeline1": {"exposure": 50, "brightness": 50, "orientation": "Normal", "hue": [0, 100], "saturation": [0, 100], "value": [0, 100], "erode": false, "dilate": false, "area": [0, 100], "ratio": [0, 20], "extent": [0, 100], "is_binary": 0, "sort_mode": "Largest", "target_group": "Single", "target_intersection": "Up", "M": 1, "B": 0}, "pipeline2": {"exposure": 50, "brightness": 50, "orientation": "Normal", "hue": [0, 100], "saturation": [0, 100], "value": [0, 100], "erode": false, "dilate": false, "area": [0, 100], "ratio": [0, 20], "extent": [0, 100], "is_binary": "Normal", "sort_mode": "Largest", "target_group": "Single", "target_intersection": "Up", "M": 1, "B": 0}, "pipeline3": {"exposure": 50, "brightness": 50, "orientation": "Normal", "hue": [0, 100], "saturation": [0, 100], "value": [0, 100], "erode": false, "dilate": false, "area": [0, 100], "ratio": [0, 20], "extent": [0, 100], "is_binary": "Normal", "sort_mode": "Largest", "target_group": "Single", "target_intersection": "Up", "M": 1, "B": 0}, "pipeline4": {"exposure": 50, "brightness": 50, "orientation": "Normal", "hue": [0, 100], "saturation": [0, 100], "value": [0, 100], "erode": false, "dilate": false, "area": [0, 100], "ratio": [0, 20], "extent": [0, 100], "is_binary": "Normal", "sort_mode": "Largest", "target_group": "Single", "target_intersection": "Up", "M": 1, "B": 0}, "pipeline5": {"exposure": 50, "brightness": 50, "orientation": "Normal", "hue": [0, 100], "saturation": [0, 100], "value": [0, 100], "erode": false, "dilate": false, "area": [0, 100], "ratio": [0, 20], "extent": [0, 100], "is_binary": "Normal", "sort_mode": "Largest", "target_group": "Single", "target_intersection": "Up", "M": 1, "B": 0}, "pipeline6": {"exposure": 50, "brightness": 50, "orientation": "Normal", "hue": [0, 100], "saturation": [0, 100], "value": [0, 100], "erode": false, "dilate": false, "area": [0, 100], "ratio": [0, 20], "extent": [0, 100], "is_binary": "Normal", "sort_mode": "Largest", "target_group": "Single", "target_intersection": "Up", "M": 1, "B": 0}, "pipeline7": {"exposure": 50, "brightness": 50, "orientation": "Normal", "hue": [0, 100], "saturation": [0, 100], "value": [0, 100], "erode": false, "dilate": false, "area": [0, 100], "ratio": [0, 20], "extent": [0, 100], "is_binary": "Normal", "sort_mode": "Largest", "target_group": "Single", "target_intersection": "Up", "M": 1, "B": 0}, "pipeline8": {"exposure": 50, "brightness": 50, "orientation": "Normal", "hue": [0, 100], "saturation": [0, 100], "value": [0, 100], "erode": false, "dilate": false, "area": [0, 100], "ratio": [0, 20], "extent": [0, 100], "is_binary": "Normal", "sort_mode": "Largest", "target_group": "Single", "target_intersection": "Up", "M": 1, "B": 0}, "pipeline9": {"exposure": 50, "brightness": 50, "orientation": "Normal", "hue": [0, 100], "saturation": [0, 100], "value": [0, 100], "erode": false, "dilate": false, "area": [0, 100], "ratio": [0, 20], "extent": [0, 100], "is_binary": 0, "sort_mode": "Largest", "target_group": "Single", "target_intersection": "Up", "M": 1, "B": 0}}, "path": "/dev/v4l/by-path/pci-0000:02:03.0-usb-0:1:1.0-video-index0", "video_mode": {"fps": 187, "width": 320, "height": 240, "pixel_format": "kYUYV"}, "resolution": 0, "FOV": 60.8} \ No newline at end of file +{"pipelines": {"pipeline0": {"exposure": 50, "brightness": 15, "orientation": "Normal", "hue": [0, 100], "saturation": [0, 100], "value": [0, 100], "erode": false, "dilate": true, "area": [0, 100], "ratio": [0, 20], "extent": [0, 100], "is_binary": 0, "sort_mode": "Largest", "target_group": "Single", "target_intersection": "Up", "M": 1, "B": 0}, "pipeline1": {"exposure": 50, "brightness": 50, "orientation": "Normal", "hue": [0, 100], "saturation": [0, 100], "value": [0, 100], "erode": false, "dilate": false, "area": [0, 100], "ratio": [0, 20], "extent": [0, 100], "is_binary": 0, "sort_mode": "Largest", "target_group": "Single", "target_intersection": "Up", "M": 1, "B": 0}, "pipeline2": {"exposure": 50, "brightness": 50, "orientation": "Normal", "hue": [0, 100], "saturation": [0, 100], "value": [0, 100], "erode": false, "dilate": false, "area": [0, 100], "ratio": [0, 20], "extent": [0, 100], "is_binary": "Normal", "sort_mode": "Largest", "target_group": "Single", "target_intersection": "Up", "M": 1, "B": 0}, "pipeline3": {"exposure": 50, "brightness": 50, "orientation": "Normal", "hue": [0, 100], "saturation": [0, 100], "value": [0, 100], "erode": false, "dilate": false, "area": [0, 100], "ratio": [0, 20], "extent": [0, 100], "is_binary": "Normal", "sort_mode": "Largest", "target_group": "Single", "target_intersection": "Up", "M": 1, "B": 0}, "pipeline4": {"exposure": 50, "brightness": 50, "orientation": "Normal", "hue": [0, 100], "saturation": [0, 100], "value": [0, 100], "erode": false, "dilate": false, "area": [0, 100], "ratio": [0, 20], "extent": [0, 100], "is_binary": "Normal", "sort_mode": "Largest", "target_group": "Single", "target_intersection": "Up", "M": 1, "B": 0}, "pipeline5": {"exposure": 50, "brightness": 50, "orientation": "Normal", "hue": [0, 100], "saturation": [0, 100], "value": [0, 100], "erode": false, "dilate": false, "area": [0, 100], "ratio": [0, 20], "extent": [0, 100], "is_binary": "Normal", "sort_mode": "Largest", "target_group": "Single", "target_intersection": "Up", "M": 1, "B": 0}, "pipeline6": {"exposure": 50, "brightness": 50, "orientation": "Normal", "hue": [0, 100], "saturation": [0, 100], "value": [0, 100], "erode": false, "dilate": false, "area": [0, 100], "ratio": [0, 20], "extent": [0, 100], "is_binary": "Normal", "sort_mode": "Largest", "target_group": "Single", "target_intersection": "Up", "M": 1, "B": 0}, "pipeline7": {"exposure": 50, "brightness": 50, "orientation": "Normal", "hue": [0, 100], "saturation": [0, 100], "value": [0, 100], "erode": false, "dilate": false, "area": [0, 100], "ratio": [0, 20], "extent": [0, 100], "is_binary": "Normal", "sort_mode": "Largest", "target_group": "Single", "target_intersection": "Up", "M": 1, "B": 0}, "pipeline8": {"exposure": 50, "brightness": 50, "orientation": "Normal", "hue": [0, 100], "saturation": [0, 100], "value": [0, 100], "erode": false, "dilate": false, "area": [0, 100], "ratio": [0, 20], "extent": [0, 100], "is_binary": "Normal", "sort_mode": "Largest", "target_group": "Single", "target_intersection": "Up", "M": 1, "B": 0}, "pipeline9": {"exposure": 50, "brightness": 50, "orientation": "Normal", "hue": [0, 100], "saturation": [0, 100], "value": [0, 100], "erode": false, "dilate": false, "area": [0, 100], "ratio": [0, 20], "extent": [0, 100], "is_binary": 0, "sort_mode": "Largest", "target_group": "Single", "target_intersection": "Up", "M": 1, "B": 0}}, "path": "/dev/v4l/by-path/pci-0000:02:03.0-usb-0:1:1.0-video-index0", "video_mode": {"fps": 187, "width": 320, "height": 240, "pixel_format": "kYUYV"}, "resolution": 0, "FOV": 60.8} \ No newline at end of file diff --git a/chameleon-client/README.md b/chameleon-client/README.md index d3a694552..b93695ec8 100644 --- a/chameleon-client/README.md +++ b/chameleon-client/README.md @@ -1,5 +1,5 @@ # chameleon-client - +the front end project of chameleon vision ## Project setup ``` npm install @@ -24,6 +24,3 @@ npm run test ``` npm run lint ``` - -### Customize configuration -See [Configuration Reference](https://cli.vuejs.org/config/).