Merge branch 'Java' into 'dev'

java translation complete

See merge request chameleon-vision/Chameleon-Vision!12
This commit is contained in:
ori agranat
2019-09-24 16:52:35 +00:00
77 changed files with 2886 additions and 5228 deletions

99
.gitignore vendored
View File

@@ -12,3 +12,102 @@ Python/app/handlers/__pycache__/
backend/settings/
/.vscode/
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
Main/Settings/
Main/.gradle
Main/target
New client/chameleon-client/node_modules/
Main/dependency-reduced-pom.xml
Main/src/main/java/META-INF

64
Main/chameleon-vision.iml Normal file
View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_12">
<output url="file://$MODULE_DIR$/target/classes" />
<output-test url="file://$MODULE_DIR$/target/test-classes" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Maven: io.javalin:javalin:3.4.1" level="project" />
<orderEntry type="library" name="Maven: org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.31" level="project" />
<orderEntry type="library" name="Maven: org.jetbrains.kotlin:kotlin-stdlib:1.3.31" level="project" />
<orderEntry type="library" name="Maven: org.jetbrains.kotlin:kotlin-stdlib-common:1.3.31" level="project" />
<orderEntry type="library" name="Maven: org.jetbrains:annotations:13.0" level="project" />
<orderEntry type="library" name="Maven: org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.31" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.26" level="project" />
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-server:9.4.19.v20190610" level="project" />
<orderEntry type="library" name="Maven: javax.servlet:javax.servlet-api:3.1.0" level="project" />
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-http:9.4.19.v20190610" level="project" />
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-util:9.4.19.v20190610" level="project" />
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-io:9.4.19.v20190610" level="project" />
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-webapp:9.4.19.v20190610" level="project" />
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-xml:9.4.19.v20190610" level="project" />
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-servlet:9.4.19.v20190610" level="project" />
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-security:9.4.19.v20190610" level="project" />
<orderEntry type="library" name="Maven: org.eclipse.jetty.websocket:websocket-server:9.4.19.v20190610" level="project" />
<orderEntry type="library" name="Maven: org.eclipse.jetty.websocket:websocket-common:9.4.19.v20190610" level="project" />
<orderEntry type="library" name="Maven: org.eclipse.jetty.websocket:websocket-api:9.4.19.v20190610" level="project" />
<orderEntry type="library" name="Maven: org.eclipse.jetty.websocket:websocket-client:9.4.19.v20190610" level="project" />
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-client:9.4.19.v20190610" level="project" />
<orderEntry type="library" name="Maven: org.eclipse.jetty.websocket:websocket-servlet:9.4.19.v20190610" level="project" />
<orderEntry type="library" name="Maven: org.json:json:20190722" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:slf4j-simple:1.7.26" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-math3:3.6.1" level="project" />
<orderEntry type="library" name="Maven: com.google.code.gson:gson:2.8.5" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-beans:5.1.9.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-core:5.1.9.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-jcl:5.1.9.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.9" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.cscore:cscore-java:2019.4.1" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.cscore:cscore-jni:linuxathena:2019.4.1" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.cscore:cscore-jni:linuxraspbian:2019.4.1" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.cscore:cscore-jni:linuxx86-64:2019.4.1" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.cscore:cscore-jni:windowsx86-64:2019.4.1" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.cameraserver:cameraserver-java:2019.4.1" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.ntcore:ntcore-java:2019.4.1" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.ntcore:ntcore-jni:linuxathena:2019.4.1" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.ntcore:ntcore-jni:linuxraspbian:2019.4.1" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.ntcore:ntcore-jni:linuxx86-64:2019.4.1" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.ntcore:ntcore-jni:windowsx86-64:2019.4.1" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.wpiutil:wpiutil-java:2019.4.1" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.thirdparty.frc2019.opencv:opencv-java:3.4.4-5" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.thirdparty.frc2019.opencv:opencv-jni:windowsx86-64:3.4.4-5" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.thirdparty.frc2019.opencv:opencv-jni:linuxx86-64:3.4.4-5" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.thirdparty.frc2019.opencv:opencv-jni:linuxathena:3.4.4-5" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.thirdparty.frc2019.opencv:opencv-jni:linuxraspbian:3.4.4-5" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.10.0.pr1" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.10.0.pr1" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.10.0.pr1" level="project" />
</component>
</module>

220
Main/pom.xml Normal file
View File

@@ -0,0 +1,220 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.chameleon-vision.main</groupId>
<artifactId>chameleon-vision</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<!--setup for java jdk 12-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<release>12</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.chameleonvision.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<repositories>
<!--WPI official maven repo for frc libs-->
<repository>
<id>WPI</id>
<name>WPI Maven repo</name>
<url>https://first.wpi.edu/FRC/roborio/maven/release</url>
</repository>
</repositories>
<dependencies>
<!--javalin micro webservices framework-->
<dependency>
<groupId>io.javalin</groupId>
<artifactId>javalin</artifactId>
<version>3.4.1</version>
</dependency>
<!--org.json from saving and loading data-->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20190722</version>
</dependency>
<!--slf4j for javalin -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.26</version>
</dependency>
<!--apache common classes libs-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-math3</artifactId>
<version>3.6.1</version>
</dependency>
<!--google json save and load library-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
<!--what is this for again?-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<!-- supported platforms for wpilib JNI classifiers
linuxathena
linuxraspbian
linuxx86-64
windowsx86-64
-->
<!--frc cscore java libs-->
<dependency>
<groupId>edu.wpi.first.cscore</groupId>
<artifactId>cscore-java</artifactId>
<version>2019.4.1</version>
</dependency>
<!--frc cscore interface libs-->
<dependency>
<groupId>edu.wpi.first.cscore</groupId>
<artifactId>cscore-jni</artifactId>
<version>2019.4.1</version>
<classifier>linuxathena</classifier>
</dependency>
<dependency>
<groupId>edu.wpi.first.cscore</groupId>
<artifactId>cscore-jni</artifactId>
<version>2019.4.1</version>
<classifier>linuxraspbian</classifier>
</dependency>
<dependency>
<groupId>edu.wpi.first.cscore</groupId>
<artifactId>cscore-jni</artifactId>
<version>2019.4.1</version>
<classifier>linuxx86-64</classifier>
</dependency>
<dependency>
<groupId>edu.wpi.first.cscore</groupId>
<artifactId>cscore-jni</artifactId>
<version>2019.4.1</version>
<classifier>windowsx86-64</classifier>
</dependency>
<!--frc camera server libs-->
<dependency>
<groupId>edu.wpi.first.cameraserver</groupId>
<artifactId>cameraserver-java</artifactId>
<version>2019.4.1</version>
</dependency>
<!--frc network table java libs-->
<dependency>
<groupId>edu.wpi.first.ntcore</groupId>
<artifactId>ntcore-java</artifactId>
<version>2019.4.1</version>
</dependency>
<!--frc network tables interface libs-->
<dependency>
<groupId>edu.wpi.first.ntcore</groupId>
<artifactId>ntcore-jni</artifactId>
<version>2019.4.1</version>
<classifier>linuxathena</classifier>
</dependency>
<dependency>
<groupId>edu.wpi.first.ntcore</groupId>
<artifactId>ntcore-jni</artifactId>
<version>2019.4.1</version>
<classifier>linuxraspbian</classifier>
</dependency>
<dependency>
<groupId>edu.wpi.first.ntcore</groupId>
<artifactId>ntcore-jni</artifactId>
<version>2019.4.1</version>
<classifier>linuxx86-64</classifier>
</dependency>
<dependency>
<groupId>edu.wpi.first.ntcore</groupId>
<artifactId>ntcore-jni</artifactId>
<version>2019.4.1</version>
<classifier>windowsx86-64</classifier>
</dependency>
<!--frc java libs-->
<dependency>
<groupId>edu.wpi.first.wpiutil</groupId>
<artifactId>wpiutil-java</artifactId>
<version>2019.4.1</version>
</dependency>
<!-- WPI OpenCV for all supported platforms -->
<dependency>
<groupId>edu.wpi.first.thirdparty.frc2019.opencv</groupId>
<artifactId>opencv-java</artifactId>
<version>3.4.4-5</version>
</dependency>
<dependency>
<groupId>edu.wpi.first.thirdparty.frc2019.opencv</groupId>
<artifactId>opencv-jni</artifactId>
<version>3.4.4-5</version>
<classifier>windowsx86-64</classifier>
</dependency>
<dependency>
<groupId>edu.wpi.first.thirdparty.frc2019.opencv</groupId>
<artifactId>opencv-jni</artifactId>
<version>3.4.4-5</version>
<classifier>linuxx86-64</classifier>
</dependency>
<dependency>
<groupId>edu.wpi.first.thirdparty.frc2019.opencv</groupId>
<artifactId>opencv-jni</artifactId>
<version>3.4.4-5</version>
<classifier>linuxathena</classifier>
</dependency>
<dependency>
<groupId>edu.wpi.first.thirdparty.frc2019.opencv</groupId>
<artifactId>opencv-jni</artifactId>
<version>3.4.4-5</version>
<classifier>linuxraspbian</classifier>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.0.pr1</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,25 @@
package com.chameleonvision;
public class CameraException extends Exception {
public enum CameraExceptionType {
NO_CAMERA,
BAD_CAMERA,
BAD_PIPELINE,
BAD_SETTING;
@Override
public String toString() {
switch (this) {
case NO_CAMERA: return "No camera connected!";
case BAD_CAMERA: return "Invalid camera!";
case BAD_PIPELINE: return "Invalid pipeline!";
case BAD_SETTING: return "Invalid camera/pipeline setting!";
default: return "Unknown camera exception!";
}
}
}
public CameraException(CameraExceptionType camExceptionType) {
super(camExceptionType.toString());
}
}

View File

@@ -0,0 +1,25 @@
package com.chameleonvision;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class FileHelper {
private FileHelper() {} // no construction, utility class
public static void CheckPath(String path) {
if (path.equals("")) return;
Path realPath = Path.of(path);
CheckPath(realPath);
}
public static void CheckPath(Path path) {
if (!Files.exists(path)) {
try {
Files.createDirectories(path);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

View File

@@ -0,0 +1,21 @@
package com.chameleonvision;
import com.chameleonvision.settings.SettingsManager;
import com.chameleonvision.vision.camera.CameraManager;
import com.chameleonvision.vision.process.VisionProcess;
import com.chameleonvision.web.Server;
public class Main {
public static void main(String[] args) {
if (CameraManager.initializeCameras()) {
SettingsManager.initialize();
for (var camSet : CameraManager.getAllCamerasByName().entrySet()) {
new Thread(new VisionProcess(camSet.getValue())).start();
}
// NetworkTableInstance.getDefault().startClientTeam(SettingsManager.GeneralSettings.team_number);
Server.main(8888);
} else {
System.err.println("No cameras connected!");
}
}
}

View File

@@ -0,0 +1,46 @@
package com.chameleonvision;
public class MemoryManager {
private static final long MEGABYTE_FACTOR = 1024L * 1024L;
private int collectionThreshold;
private int lastUsedMb = 0;
public MemoryManager(int collectionThreshold) {
this.collectionThreshold = collectionThreshold;
}
public void setCollectionThreshold(int collectionThreshold) {
this.collectionThreshold = collectionThreshold;
}
public static long getUsedMemory() {
return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
}
public static int getUsedMemoryMB() {
return (int) (getUsedMemory() / MEGABYTE_FACTOR);
}
private static void collect() {
System.gc();
System.runFinalization();
}
public void run() { run(false); }
public void run(boolean print) {
var usedMem = getUsedMemoryMB();
if (usedMem != lastUsedMb) {
lastUsedMb = usedMem;
if (print) System.out.printf("Memory usage: %dMB\n", usedMem);
}
if (usedMem >= collectionThreshold) {
collect();
if (print) System.out.printf("Garbage collected at %dMB\n", usedMem);
}
}
}

View File

@@ -0,0 +1,71 @@
package com.chameleonvision.settings;
import java.net.*;
import java.util.Collections;
import java.util.Enumeration;
import org.apache.commons.lang3.SystemUtils;
public class NetworkSettings {
public String connectionType, ip, netmask, gateway, hostname;
public void run() {
// String adapter = getAdapter();
if (SystemUtils.IS_OS_LINUX) {//TODO check linux commands
String adapter = getAdapter();
if (!adapter.equals("")) {
executeCommand("ifconfig " + adapter + " down");
if (connectionType.equals("DHCP"))
executeCommand("dhclient -r " + adapter);
else if (connectionType.equals("Static")) {
executeCommand("ifconfig " + adapter + " " + this.ip + " netmask " + this.netmask);
executeCommand("route add default gw " + this.gateway + " " + adapter);
}
executeCommand("ifconfig " + adapter + " up");
}
executeCommand("hostnamectl set-hostname " + this.hostname);
}
// //TODO check windows commands
// else if (SystemUtils.IS_OS_WINDOWS) {
// if (!adapter.equals("")) {
// if (connectionType.equals("DHCP")){
// executeCommand("cmd /c interface ip set address \"" + adapter + "\" dhcp");
// }
// else if (connectionType.equals("Static")) {
// executeCommand("cmd /c netsh interface ip set address \"" + adapter + "\" static " + this.ip + " " + this.netmask + " " + this.gateway + "1");
// }
// }
// //TODO find a way to change hostname in windows
// }
}
private void executeCommand(String command) {
try {
Process p = Runtime.getRuntime().exec(command);
System.out.println("Executing "+ command);
p.waitFor();
p.destroy();
} catch (Exception e) {
System.err.println("Error while executing command!");
e.printStackTrace();
}
}
public static String getAdapter() {
try {//TODO fix windows get adapter
Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
for (NetworkInterface netint : Collections.list(nets)) {
Enumeration<InetAddress> ee = netint.getInetAddresses();
for (InetAddress addr : Collections.list(ee))
if (addr instanceof Inet4Address)
if ((addr.getAddress()[0] & 0xFF) == 192 && (addr.getAddress()[1] & 0xFF) == 168) {
System.out.println("found robot network interface at " + netint.getName() + " ip: " + addr.getHostAddress());
return netint.getName();
}
}
} catch (SocketException e) {
System.err.println("Socket exception while trying to find current ip");
}
return "";
}
}

View File

@@ -0,0 +1,113 @@
package com.chameleonvision.settings;
import com.chameleonvision.FileHelper;
import com.chameleonvision.vision.GeneralSettings;
import com.chameleonvision.vision.camera.CameraManager;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
public class SettingsManager {
public static final Path SettingsPath = Paths.get(System.getProperty("user.dir"), "Settings");
public static com.chameleonvision.vision.GeneralSettings GeneralSettings;
private SettingsManager() {}
public static void initialize() {
initGeneralSettings();
NetworkSettings netSettings = new NetworkSettings();
netSettings.hostname = GeneralSettings.hostname;
netSettings.gateway = GeneralSettings.gateway;
netSettings.netmask = GeneralSettings.netmask;
netSettings.connectionType = GeneralSettings.connection_type;
netSettings.ip = GeneralSettings.ip;
netSettings.run();
var allCameras = CameraManager.getAllCamerasByName();
if (!allCameras.containsKey(GeneralSettings.curr_camera) && allCameras.size() > 0) {
var cam = allCameras.entrySet().stream().findFirst().get().getValue();
GeneralSettings.curr_camera = cam.name;
GeneralSettings.curr_pipeline = cam.getCurrentPipelineIndex();
}
}
public enum Platform {
WINDOWS_64("Windows x64"),
LINUX_64("Linux x64"),
LINUX_RASPBIAN("Linux Raspbian"),
LINUX_AARCH64("Linux ARM 64bit"),
MACOS_64("Mac OS x64"),
UNSUPPORTED("Unsupported Platform");
public final String value;
Platform(String value) {
this.value = value;
}
}
public static Platform getCurrentPlatform() {
var osName = System.getProperty("os.name");
var osArch = System.getProperty("os.arch");
if (osName.contains("Windows")) {
if (osArch.equals("amd64")) return Platform.WINDOWS_64;
return Platform.UNSUPPORTED;
}
if (osName.contains("Linux")) {
if (osArch.equals("amd64")) return Platform.LINUX_64;
if (osArch.contains("rasp")) return Platform.LINUX_RASPBIAN;
if (osArch.contains("aarch")) return Platform.LINUX_64;
return Platform.UNSUPPORTED;
}
if (osName.contains("Mac")) {
if (osArch.equals("amd64")) return Platform.MACOS_64;
return Platform.UNSUPPORTED;
}
return Platform.UNSUPPORTED;
}
private static void initGeneralSettings() {
FileHelper.CheckPath(SettingsPath);
try {
GeneralSettings = new Gson().fromJson(new FileReader(Paths.get(SettingsPath.toString(), "Settings.json").toString()), com.chameleonvision.vision.GeneralSettings.class);
} catch (FileNotFoundException e) {
GeneralSettings = new GeneralSettings();
}
}
public static void updateCameraSetting(String cameraName, int pipelineNumber) {
GeneralSettings.curr_camera = cameraName;
GeneralSettings.curr_pipeline = pipelineNumber;
}
public static void updatePipelineSetting(int pipelineNumber) {
GeneralSettings.curr_pipeline = pipelineNumber;
}
public static void saveSettings() {
CameraManager.saveCameras();
saveGeneralSettings();
}
private static void saveGeneralSettings() {
try {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
FileWriter writer = new FileWriter(Paths.get(SettingsPath.toString(), "settings.json").toString());
gson.toJson(GeneralSettings, writer);
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,12 @@
package com.chameleonvision.vision;
public class GeneralSettings {
public int team_number = 1577;
public String connection_type = "DHCP";
public String ip = "";
public String gateway = "";
public String netmask = "";
public String hostname = "Chameleon-vision";
public String curr_camera = "";
public Integer curr_pipeline = null;
}

View File

@@ -0,0 +1,25 @@
package com.chameleonvision.vision;
import java.util.Arrays;
import java.util.List;
public class Pipeline {
public int exposure = 50;
public int brightness = 50;
public String orientation = "Normal";
public List<Integer> hue = Arrays.asList(50, 180);
public List<Integer> saturation = Arrays.asList(50, 255);
public List<Integer> value = Arrays.asList(50, 255);
public boolean erode = false;
public boolean dilate = false;
public List<Integer> area = Arrays.asList(0, 100);
public List<Double> ratio = Arrays.asList(0D, 20D);
public List<Integer> extent = Arrays.asList(0, 100);
public int is_binary = 0;
public String sort_mode = "Largest";
public String target_group = "Single";
public String target_intersection = "Up";
public double M = 1;
public double B = 0;
public boolean is_calibrated = false;
}

View File

@@ -0,0 +1,53 @@
package com.chameleonvision.vision.camera;
import edu.wpi.cscore.VideoMode;
@SuppressWarnings("WeakerAccess")
public class CamVideoMode {
public final int fps;
public final int width;
public final int height;
public final String pixel_format;
public CamVideoMode(VideoMode videoMode) {
fps = videoMode.fps;
width = videoMode.width;
height = videoMode.height;
pixel_format = videoMode.pixelFormat.name();
}
public VideoMode.PixelFormat getActualPixelFormat() {
return VideoMode.PixelFormat.valueOf(pixel_format);
}
public boolean isEqualToVideoMode(VideoMode videoMode) {
return videoMode.fps == fps && videoMode.width == width && videoMode.height == height && videoMode.pixelFormat == getActualPixelFormat();
}
public boolean equals(VideoMode vm) {
return vm.fps == fps &&
vm.width == width &&
vm.height == height &&
vm.pixelFormat == getActualPixelFormat();
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof CamVideoMode) {
var cvm = (CamVideoMode) obj;
return cvm.fps == fps &&
cvm.width == width &&
cvm.height == height &&
cvm.pixel_format.equals(pixel_format);
} else if (obj instanceof VideoMode) {
var vm = (VideoMode) obj;
return equals(vm);
} else {
return false;
}
}
}

View File

@@ -0,0 +1,199 @@
package com.chameleonvision.vision.camera;
import com.chameleonvision.settings.SettingsManager;
import com.chameleonvision.vision.Pipeline;
import com.chameleonvision.web.ServerHandler;
import edu.wpi.cscore.*;
import edu.wpi.first.cameraserver.CameraServer;
import org.opencv.core.Mat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.stream.IntStream;
public class Camera {
private static final double DEFAULT_FOV = 60.8;
private static final int MINIMUM_FPS = 30;
private static final int MINIMUM_WIDTH = 320;
private static final int MINIMUM_HEIGHT = 240;
private static final int MAX_INIT_MS = 1500;
public final String name;
public final String path;
private final UsbCamera UsbCam;
private final VideoMode[] availableVideoModes;
private final CameraServer cs = CameraServer.getInstance();
private final CvSink cvSink;
private final Object cvSourceLock = new Object();
private CvSource cvSource;
private double FOV;
private CameraValues camVals;
private CamVideoMode camVideoMode;
private int currentPipelineIndex;
private HashMap<Integer, Pipeline> pipelines;
private long initTimeout;
public Camera(String cameraName) {
this(cameraName, DEFAULT_FOV);
}
public Camera(String cameraName, double fov) {
this(cameraName,CameraManager.AllUsbCameraInfosByName.get(cameraName), fov);
}
public Camera(String cameraName, UsbCameraInfo usbCamInfo, double fov) {
this(cameraName ,usbCamInfo, fov, new HashMap<>(), 0);
}
public Camera(String cameraName, double fov, int videoModeIndex) {
this(cameraName, fov, new HashMap<>(), videoModeIndex);
}
public Camera(String cameraName, double fov, HashMap<Integer, Pipeline> pipelines, int videoModeIndex) {
this(cameraName, CameraManager.AllUsbCameraInfosByName.get(cameraName), fov, pipelines, videoModeIndex);
}
public Camera(String cameraName, UsbCameraInfo usbCamInfo, double fov, HashMap<Integer, Pipeline> pipelines, int videoModeIndex) {
FOV = fov;
name = cameraName;
path = usbCamInfo.path;
UsbCam = new UsbCamera(name, path);
this.pipelines = pipelines;
// set up video modes according to minimums
if (SettingsManager.getCurrentPlatform() == SettingsManager.Platform.WINDOWS_64 && !UsbCam.isConnected()) {
System.out.print("Waiting on camera... ");
initTimeout = System.nanoTime();
while(!UsbCam.isConnected())
{
//TODO add a time sleep, can wait only so long before giving up
if (((System.nanoTime() - initTimeout) / 1e6 ) >= MAX_INIT_MS) {
break;
}
}
var initTimeMs = (System.nanoTime() - initTimeout) / 1e6;
System.out.printf("Camera initialized in %.2fms\n", initTimeMs);
}
availableVideoModes = Arrays.stream(UsbCam.enumerateVideoModes()).filter(v -> v.fps >= MINIMUM_FPS && v.width >= MINIMUM_WIDTH && v.height >= MINIMUM_HEIGHT).toArray(VideoMode[]::new);
if (videoModeIndex <= availableVideoModes.length - 1) {
setCamVideoMode(videoModeIndex, false);
} else {
setCamVideoMode(0, false);
}
cvSink = cs.getVideo(UsbCam);
cvSource = cs.putVideo(name, camVals.ImageWidth, camVals.ImageHeight);
var s = (MjpegServer) cs.getServer("serve_" + name);
CameraManager.CameraPorts.put(name, s.getPort());
}
VideoMode[] getAvailableVideoModes() {
return availableVideoModes;
}
public int getStreamPort() {
var s = (MjpegServer) cs.getServer("serve_" + name);
return s.getPort();
}
public void setCamVideoMode(int videoMode, boolean updateCvSource) {
setCamVideoMode(new CamVideoMode(availableVideoModes[videoMode]), updateCvSource);
}
private void setCamVideoMode(CamVideoMode newVideoMode, boolean updateCvSource) {
var prevVideoMode = this.camVideoMode;
this.camVideoMode = newVideoMode;
UsbCam.setPixelFormat(newVideoMode.getActualPixelFormat());
UsbCam.setFPS(newVideoMode.fps);
UsbCam.setResolution(newVideoMode.width, newVideoMode.height);
// update camera values
camVals = new CameraValues(this);
if (prevVideoMode != null && !prevVideoMode.equals(newVideoMode) && updateCvSource) { // if resolution changed
synchronized (cvSourceLock) {
cvSource = cs.putVideo(name, newVideoMode.width, newVideoMode.height);
}
ServerHandler.sendFullSettings();
}
}
void addPipeline() {
addPipeline(pipelines.size());
}
private void addPipeline(int pipelineNumber) {
if (pipelines.containsKey(pipelineNumber)) return;
pipelines.put(pipelineNumber, new Pipeline());
}
public Pipeline getCurrentPipeline() {
return pipelines.get(currentPipelineIndex);
}
public int getCurrentPipelineIndex() {
return currentPipelineIndex;
}
void setCurrentPipelineIndex(int pipelineNumber) {
if (pipelineNumber - 1 > pipelines.size()) return;
currentPipelineIndex = pipelineNumber;
}
public HashMap<Integer, Pipeline> getPipelines() {
return pipelines;
}
public CamVideoMode getVideoMode() {
return camVideoMode;
}
public int getVideoModeIndex() {
return IntStream.range(0, availableVideoModes.length)
.filter(i -> camVideoMode.equals(availableVideoModes[i]))
.findFirst()
.orElse(-1);
}
public double getFOV() {
return FOV;
}
public void setFOV(double fov) {
FOV = fov;
camVals = new CameraValues(this);
}
public int getBrightness() {
return getCurrentPipeline().brightness;
}
public void setBrightness(int brightness) {
getCurrentPipeline().brightness = brightness;
UsbCam.setBrightness(brightness);
}
public void setExposure(int exposure) {
getCurrentPipeline().exposure = exposure;
UsbCam.setExposureManual(exposure);
}
public long grabFrame(Mat image) {
return cvSink.grabFrame(image);
}
public CameraValues getCamVals() {
return camVals;
}
public void putFrame(Mat image) {
synchronized (cvSourceLock) {
cvSource.putFrame(image);
}
}
}

View File

@@ -0,0 +1,35 @@
package com.chameleonvision.vision.camera;
import com.chameleonvision.vision.Pipeline;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.google.gson.*;
import java.lang.reflect.Type;
import java.util.HashMap;
public class CameraDeserializer implements JsonDeserializer<Camera> {
@Override
public Camera deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context) throws JsonParseException {
var jsonObj = jsonElement.getAsJsonObject();
var camFOV = jsonObj.get("FOV").getAsDouble();
var camName = jsonObj.get("name").getAsString();
var videoModeIndex = jsonObj.get("resolution").getAsInt();
var pipelines = jsonObj.get("pipelines");
HashMap<Integer, Pipeline> actualPipelines = new HashMap<>();
ObjectMapper mapper = new ObjectMapper();
TypeFactory typeFactory = mapper.getTypeFactory();
MapType mapType = typeFactory.constructMapType(HashMap.class, Integer.class, Pipeline.class);
try {
actualPipelines = mapper.readValue(pipelines.toString(), mapType);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return actualPipelines != null ? new Camera(camName, camFOV, actualPipelines, videoModeIndex) : new Camera(camName, camFOV, videoModeIndex);
}
}

View File

@@ -0,0 +1,131 @@
package com.chameleonvision.vision.camera;
import com.chameleonvision.CameraException;
import com.chameleonvision.FileHelper;
import com.chameleonvision.settings.SettingsManager;
import com.chameleonvision.vision.Pipeline;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import edu.wpi.cscore.UsbCamera;
import edu.wpi.cscore.UsbCameraInfo;
import org.opencv.videoio.VideoCapture;
import java.io.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class CameraManager {
private static final Path CamConfigPath = Paths.get(SettingsManager.SettingsPath.toString(), "Cams");
public static HashMap<String, Integer> CameraPorts = new HashMap<>();
private static HashMap<String, Camera> AllCamerasByName = new HashMap<>();
static HashMap<String, UsbCameraInfo> AllUsbCameraInfosByName = new HashMap<>() {{
var suffix = 0;
for (var info : UsbCamera.enumerateUsbCameras()) {
var cap = new VideoCapture(info.dev);
if (cap.isOpened()) {
cap.release();
var name = info.name;
while (this.containsKey(name)) {
suffix++;
name = String.format("%s(%s)", info.name, suffix);
}
put(name, info);
}
}
}};
public static HashMap<String, Camera> getAllCamerasByName() {
return AllCamerasByName;
}
public static boolean initializeCameras() {
if (AllUsbCameraInfosByName.size() == 0) return false;
FileHelper.CheckPath(CamConfigPath);
for (var entry : AllUsbCameraInfosByName.entrySet()) {
var camPath = Paths.get(CamConfigPath.toString(), String.format("%s.json", entry.getKey()));
File camJsonFile = new File(camPath.toString());
if (camJsonFile.exists() && camJsonFile.length() != 0) {
try {
Gson gson = new GsonBuilder().registerTypeAdapter(Camera.class, new CameraDeserializer()).create();
var camJsonFileReader = new FileReader(camPath.toString());
var gsonRead = gson.fromJson(camJsonFileReader, Camera.class);
AllCamerasByName.put(entry.getKey(), gsonRead);
} catch (FileNotFoundException ex) {
ex.printStackTrace();
}
} else {
if (!addCamera(new Camera(entry.getKey()), entry.getKey())) {
System.err.println("Failed to add camera! Already exists!");
}
}
}
return true;
}
private static boolean addCamera(Camera camera, String cameraName) {
if (AllCamerasByName.containsKey(cameraName)) return false;
camera.addPipeline();
AllCamerasByName.put(cameraName, camera);
return true;
}
private static Camera getCamera(String cameraName) {
return AllCamerasByName.get(cameraName);
}
public static Camera getCurrentCamera() throws CameraException {
if (AllCamerasByName.size() == 0) throw new CameraException(CameraException.CameraExceptionType.NO_CAMERA);
var curCam = AllCamerasByName.get(SettingsManager.GeneralSettings.curr_camera);
if (curCam == null) throw new CameraException(CameraException.CameraExceptionType.BAD_CAMERA);
return curCam;
}
public static void setCurrentCamera(String cameraName) throws CameraException {
if (!AllCamerasByName.containsKey(cameraName))
throw new CameraException(CameraException.CameraExceptionType.BAD_CAMERA);
SettingsManager.GeneralSettings.curr_camera = cameraName;
SettingsManager.updateCameraSetting(cameraName, getCurrentCamera().getCurrentPipelineIndex());
}
public static Pipeline getCurrentPipeline() throws CameraException {
return getCurrentCamera().getCurrentPipeline();
}
public static void setCurrentPipeline(int pipelineNumber) throws CameraException {
if (!getCurrentCamera().getPipelines().containsKey(pipelineNumber))
throw new CameraException(CameraException.CameraExceptionType.BAD_PIPELINE);
getCurrentCamera().setCurrentPipelineIndex(pipelineNumber);
SettingsManager.updatePipelineSetting(pipelineNumber);
}
public static List<String> getResolutionList() throws CameraException {
if (!SettingsManager.GeneralSettings.curr_camera.equals("")) {
List<String> list = new ArrayList<>();
for (var res : CameraManager.getCamera(SettingsManager.GeneralSettings.curr_camera).getAvailableVideoModes()) {
list.add(String.format("%s X %s at %s fps", res.width, res.height, res.fps));
}
return list;
}
throw new CameraException(CameraException.CameraExceptionType.NO_CAMERA);
}
public static void saveCameras() {
for (var entry : AllCamerasByName.entrySet()) {
try {
Gson gson = new GsonBuilder().setPrettyPrinting().registerTypeAdapter(Camera.class, new CameraSerializer()).create();
FileWriter writer = new FileWriter(Paths.get(CamConfigPath.toString(), String.format("%s.json", entry.getKey())).toString());
gson.toJson(entry.getValue(), writer);
writer.flush();
writer.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}

View File

@@ -0,0 +1,22 @@
package com.chameleonvision.vision.camera;
import com.google.gson.*;
import java.lang.reflect.Type;
public class CameraSerializer implements JsonSerializer<Camera> {
@Override
public JsonElement serialize(Camera camera, Type type, JsonSerializationContext context) {
JsonObject obj = new JsonObject();
obj.addProperty("FOV", camera.getFOV());
obj.addProperty("path", camera.path);
obj.addProperty("name", camera.name);
var pipelines = context.serialize(camera.getPipelines());
obj.add("pipelines", pipelines);
obj.addProperty("resolution", camera.getVideoModeIndex());
obj.add("camVideoMode", context.serialize(camera.getVideoMode()));
return obj;
}
}

View File

@@ -0,0 +1,50 @@
package com.chameleonvision.vision.camera;
import org.apache.commons.math3.fraction.Fraction;
import org.apache.commons.math3.util.FastMath;
@SuppressWarnings("WeakerAccess")
public class CameraValues {
public final int ImageWidth;
public final int ImageHeight;
public final double FOV;
public final double ImageArea;
public final double CenterX;
public final double CenterY;
public final double DiagonalView;
public final Fraction AspectFraction;
public final int HorizontalRatio;
public final int VerticalRatio;
public final double HorizontalView;
public final double VerticalView;
public final double HorizontalFocalLength;
public final double VerticalFocalLength;
public CameraValues(Camera camera) {
this(camera.getVideoMode().width, camera.getVideoMode().height, camera.getFOV());
}
public CameraValues(int imageWidth, int imageHeight, double fov) {
ImageWidth = imageWidth;
ImageHeight = imageHeight;
FOV = fov;
ImageArea = ImageWidth * ImageHeight;
CenterX = ((double) ImageWidth / 2) - 0.5;
CenterY = ((double) ImageHeight / 2) - 0.5;
DiagonalView = FastMath.toRadians(FOV);
AspectFraction = new Fraction(ImageWidth, ImageHeight);
HorizontalRatio = AspectFraction.getNumerator();
VerticalRatio = AspectFraction.getDenominator();
HorizontalView = FastMath.atan(FastMath.tan(DiagonalView / 2) * (HorizontalRatio / DiagonalView)) * 2;
VerticalView = FastMath.atan(FastMath.tan(DiagonalView/2) * (VerticalRatio / DiagonalView)) * 2;
HorizontalFocalLength = ImageWidth / (2 * FastMath.tan(HorizontalView /2));
VerticalFocalLength = ImageHeight / (2 * FastMath.tan(VerticalView /2));
}
public double CalculatePitch(double PixelY, double centerY){
double pitch = FastMath.toDegrees(FastMath.atan((PixelY - centerY) / VerticalFocalLength));
return (pitch * -1);
}
public double CalculateYaw(double PixelX, double centerX){
return FastMath.toDegrees(FastMath.atan((PixelX - centerX) / HorizontalFocalLength));
}
}

View File

@@ -0,0 +1,207 @@
package com.chameleonvision.vision.process;
import com.chameleonvision.vision.camera.CameraValues;
import org.apache.commons.math3.util.FastMath;
import org.jetbrains.annotations.NotNull;
import org.opencv.core.*;
import org.opencv.imgproc.Imgproc;
import java.util.*;
@SuppressWarnings("WeakerAccess")
public class CVProcess {
private final CameraValues CamVals;
private HashMap<String, Integer> TargetGrouping = new HashMap<>() {{
put("Single", 1);
put("Dual", 2);
put("Triple", 3);
put("Quadruple", 4);
put("Quintuple", 5);
}};
private Mat Kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(5, 5));
private Mat hsvImage = new Mat();
private List<MatOfPoint> FoundContours = new ArrayList<>();
private Mat binaryMat = new Mat();
private List<MatOfPoint> FilteredContours = new ArrayList<>();
private Comparator<RotatedRect> SortByCentermostComparator = Comparator.comparingDouble(this::calcDistance);
private List<RotatedRect> FinalCountours = new ArrayList<>();
private Mat intersectMatA = new Mat();
private Mat intersectMatB = new Mat();
CVProcess(CameraValues camVals) {
CamVals = camVals;
}
void HSVThreshold(Mat srcImage, Mat dst, @NotNull Scalar hsvLower, @NotNull Scalar hsvUpper, boolean shouldErode, boolean shouldDilate) {
Imgproc.cvtColor(srcImage, hsvImage, Imgproc.COLOR_RGB2HSV, 3);
Core.inRange(hsvImage, hsvLower, hsvUpper, dst);
if (shouldErode) {
Imgproc.erode(dst, dst, Kernel);
}
if (shouldDilate) {
Imgproc.dilate(dst, dst, Kernel);
}
hsvImage.release();
}
List<MatOfPoint> FindContours(Mat src) {
src.copyTo(binaryMat);
FoundContours.clear();
Imgproc.findContours(binaryMat, FoundContours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_TC89_L1);
binaryMat.release();
return FoundContours;
}
List<MatOfPoint> FilterContours(List<MatOfPoint> InputContours, List<Integer> area, List<Double> ratio, List<Integer> extent) {
for (MatOfPoint Contour : InputContours) {
try {
double contourArea = Imgproc.contourArea(Contour); //TODO change scaling
double targetArea = (contourArea / CamVals.ImageArea) * 100;
double minArea = Math.pow(area.get(0), 4);
double maxArea = Math.pow(area.get(1), 4);
if (targetArea < minArea || targetArea > maxArea) {
continue;
}
var rect = Imgproc.minAreaRect(new MatOfPoint2f(Contour.toArray()));
var targetFullness = (contourArea / rect.size.area()) * 100;
if (targetFullness < extent.get(0) || targetArea > extent.get(1)) {
continue;
}
double aspectRatio = rect.size.width / rect.size.height;//TODO i think aspectRatio is inverted
if (aspectRatio < ratio.get(0) || aspectRatio > ratio.get(1)) {
continue;
}
FilteredContours.add(Contour);
} catch (Exception e) {
System.err.println("Error while filtering contours");
e.printStackTrace();
}
}
return FilteredContours;
}
private double calcDistance(RotatedRect rect) {
return FastMath.sqrt(FastMath.pow(CamVals.CenterX - rect.center.x, 2) + FastMath.pow(CamVals.CenterY - rect.center.y, 2));
}
RotatedRect SortTargetsToOne(List<RotatedRect> inputRects, String sortMode) {
switch (sortMode) {
case "Largest":
return Collections.max(inputRects, Comparator.comparing(rect -> rect.size.area()));
case "Smallest":
return Collections.min(inputRects, Comparator.comparing(rect -> rect.size.area()));
case "Highest":
return Collections.min(inputRects, Comparator.comparing(rect -> rect.center.y));
case "Lowest":
return Collections.max(inputRects, Comparator.comparing(rect -> rect.center.y));
case "Leftmost":
return Collections.min(inputRects, Comparator.comparing(rect -> rect.center.x));
case "Rightmost":
return Collections.max(inputRects, Comparator.comparing(rect -> rect.center.x));
case "Centermost":
return Collections.min(inputRects, SortByCentermostComparator);
default:
return inputRects.get(0); // default to whatever the first contour is, but this should never happen
}
}
List<RotatedRect> GroupTargets(List<MatOfPoint> InputContours, String IntersectionPoint, String TargetGroup) {
FinalCountours.clear();
if (!TargetGroup.equals("Single")) {
for (var i = 0; i < InputContours.size(); i++) {
List<Point> FinalContourList = new ArrayList<>(InputContours.get(i).toList());
for (var c = 0; c < (TargetGrouping.get(TargetGroup) - 1); c++) {
try {
MatOfPoint firstContour = InputContours.get(i + c);
MatOfPoint secondContour = InputContours.get(i + c + 1);
if (IsIntersecting(firstContour, secondContour, IntersectionPoint)) {
FinalContourList.addAll(secondContour.toList());
}
else{
FinalContourList.clear();
break;
}
firstContour.release();
secondContour.release();
MatOfPoint2f contour = new MatOfPoint2f();
contour.fromList(FinalContourList);
if (contour.cols() != 0 && contour.rows() != 0) {
RotatedRect rect = Imgproc.minAreaRect(contour);
FinalCountours.add(rect);
}
} catch (IndexOutOfBoundsException e) {
FinalContourList.clear();
break;
}
}
}
} else {
for (MatOfPoint inputContour : InputContours) {
MatOfPoint2f contour = new MatOfPoint2f();
contour.fromArray(inputContour.toArray());
if (contour.cols() != 0 && contour.rows() != 0) {
RotatedRect rect = Imgproc.minAreaRect(contour);
FinalCountours.add(rect);
}
}
}
return FinalCountours;
}
private boolean IsIntersecting(MatOfPoint ContourOne, MatOfPoint ContourTwo, String IntersectionPoint) {
if (IntersectionPoint.equals("None")) {
return true;
}
try {
Imgproc.fitLine(ContourOne, intersectMatA, Imgproc.CV_DIST_L2, 0, 0.01, 0.01);
Imgproc.fitLine(ContourTwo, intersectMatB, Imgproc.CV_DIST_L2, 0, 0.01, 0.01);
double vxA = intersectMatA.get(0, 0)[0];
double vyA = intersectMatA.get(1, 0)[0];
double x0A = intersectMatA.get(2, 0)[0];
double y0A = intersectMatA.get(3, 0)[0];
double mA = vyA / vxA;
double vxB = intersectMatB.get(0, 0)[0];
double vyB = intersectMatB.get(1, 0)[0];
double x0B = intersectMatB.get(2, 0)[0];
double y0B = intersectMatB.get(3, 0)[0];
double mB = vyB / vxB;
double bA = y0A - (mA*x0A);
double bB = y0B - (mB*x0B);
double intersectionX = ((mA * x0A) - y0A - (mB * x0B) + y0B )/ (mA - mB);
double intersectionY = (mA * (intersectionX - x0A)) + y0A;
double massX = intersectionX + 1;
double massY = intersectionY + ((mA + bA + mB +bB) / 2);
switch (IntersectionPoint) {
case "Up": {
if (intersectionY < massY) {
return true;
}
break;
}
case "Down": {
if (intersectionY > massY) {
return true;
}
break;
}
case "Left": {
if (intersectionX < massX) {
return true;
}
break;
}
case "Right": {
if (intersectionX > massX) {
return true;
}
break;
}
}
return false;
} catch (Exception e) {
return false;
}
}
}

View File

@@ -0,0 +1,65 @@
package com.chameleonvision.vision.process;
import com.chameleonvision.vision.camera.Camera;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
public class CameraProcess implements Runnable {
private final Camera camera;
private final int maxFPS;
private final Object inputFrameLock = new Object();
private final Object outputFrameLock = new Object();
private Mat inputFrame;
private Mat outputFrame;
private long timestamp;
CameraProcess(Camera camera) {
this.camera = camera;
maxFPS = camera.getVideoMode().fps;
var camVals = camera.getCamVals();
inputFrame = new Mat(camVals.ImageWidth, camVals.ImageHeight, CvType.CV_8UC3);
outputFrame = new Mat(camVals.ImageWidth, camVals.ImageHeight, CvType.CV_8UC3);
}
private void updateFrameSize() {
var camVals = camera.getCamVals();
synchronized (inputFrameLock) {
inputFrame = new Mat(camVals.ImageWidth, camVals.ImageHeight, CvType.CV_8UC3);
}
synchronized (outputFrameLock) {
outputFrame = new Mat(camVals.ImageWidth, camVals.ImageHeight, CvType.CV_8UC3);
}
}
void updateFrame(Mat inputFrame) {
synchronized (inputFrameLock) {
inputFrame.copyTo(this.inputFrame);
}
}
long getLatestFrame(Mat outputFrame) {
synchronized (outputFrameLock) {
this.outputFrame.copyTo(outputFrame);
return timestamp;
}
}
@Override
public void run() {
while (!Thread.interrupted()) {
synchronized (outputFrameLock) {
timestamp = camera.grabFrame(outputFrame);
}
synchronized (inputFrameLock) {
camera.putFrame(inputFrame);
}
var msToWait = (long) 1000 / maxFPS;
try {
Thread.sleep(msToWait);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

View File

@@ -0,0 +1,12 @@
package com.chameleonvision.vision.process;
import org.opencv.core.RotatedRect;
public class PipelineResult {
public boolean IsValid = false;
public double CalibratedX = 0.0;
public double CalibratedY = 0.0;
public double Pitch = 0.0;
public double Yaw = 0.0;
RotatedRect RawPoint;
}

View File

@@ -0,0 +1,236 @@
package com.chameleonvision.vision.process;
import com.chameleonvision.settings.SettingsManager;
import com.chameleonvision.vision.Pipeline;
import com.chameleonvision.vision.camera.Camera;
import com.chameleonvision.web.ServerHandler;
import edu.wpi.first.networktables.*;
import org.opencv.core.*;
import org.opencv.imgproc.Imgproc;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class VisionProcess implements Runnable {
private final Camera camera;
private final String cameraName;
private final CameraProcess cameraProcess;
// NetworkTables
private NetworkTableEntry ntPipelineEntry;
private NetworkTableEntry ntDriverModeEntry;
private NetworkTableEntry ntYawEntry;
private NetworkTableEntry ntPitchEntry;
private NetworkTableEntry ntDistanceEntry;
private NetworkTableEntry ntTimeStampEntry;
private NetworkTableEntry ntValidEntry;
// chameleon specific
private Pipeline currentPipeline;
private CVProcess cvProcess;
// pipeline process items
private List<MatOfPoint> FoundContours = new ArrayList<>();
private List<MatOfPoint> FilteredContours = new ArrayList<>();
private List<RotatedRect> GroupedContours = new ArrayList<>();
private Mat cameraInputMat = new Mat();
private Mat hsvThreshMat = new Mat();
private Mat streamOutputMat = new Mat();
private Scalar contourRectColor = new Scalar(255, 0, 0);
private long TimeStamp = 0;
public VisionProcess(Camera processCam) {
camera = processCam;
this.cameraName = camera.name;
// NetworkTables
NetworkTable ntTable = NetworkTableInstance.getDefault().getTable("/chameleon-vision/" + cameraName);
ntPipelineEntry = ntTable.getEntry("Pipeline");
ntDriverModeEntry = ntTable.getEntry("Driver_Mode");
ntPitchEntry = ntTable.getEntry("Pitch");
ntYawEntry = ntTable.getEntry("Yaw");
ntDistanceEntry = ntTable.getEntry("Distance");
ntTimeStampEntry = ntTable.getEntry("TimeStamp");
ntValidEntry = ntTable.getEntry("Valid");
ntDriverModeEntry.addListener(this::DriverModeListener, EntryListenerFlags.kUpdate);
ntPipelineEntry.addListener(this::PipelineListener, EntryListenerFlags.kUpdate);
ntDriverModeEntry.setBoolean(false);
ntPipelineEntry.setString("pipeline" + camera.getCurrentPipelineIndex());
// camera settings
cvProcess = new CVProcess(camera.getCamVals());
cameraProcess = new CameraProcess(camera);
}
private void DriverModeListener(EntryNotification entryNotification) {
if (entryNotification.value.getBoolean()) {
camera.setExposure(25);
camera.setBrightness(15);
} else {
Pipeline pipeline = camera.getCurrentPipeline();
camera.setExposure(pipeline.exposure);
camera.setBrightness(pipeline.brightness);
}
}
private void PipelineListener(EntryNotification entryNotification) {
var ntPipelineIndex = Integer.parseInt(entryNotification.value.getString().replace("pipeline", ""));
if (camera.getPipelines().containsKey(ntPipelineIndex)) {
// camera.setEntryNotification.value.getString());
var pipeline = camera.getCurrentPipeline();
camera.setExposure(pipeline.exposure);
camera.setBrightness(pipeline.brightness);
HashMap<String, Object> pipeChange = new HashMap<>();
pipeChange.put("curr_pipeline", ntPipelineIndex);
ServerHandler.broadcastMessage(pipeChange);
} else {
ntPipelineEntry.setString("pipeline" + camera.getCurrentPipelineIndex());
}
}
private void drawContour(Mat inputMat, RotatedRect contourRect) {
if (contourRect == null) return;
List<MatOfPoint> drawnContour = new ArrayList<>();
Point[] vertices = new Point[4];
contourRect.points(vertices);
drawnContour.add(new MatOfPoint(vertices));
Imgproc.drawContours(inputMat, drawnContour, 0, contourRectColor, 3);
Imgproc.circle(inputMat, contourRect.center, 3, contourRectColor);
}
private void updateNetworkTables(PipelineResult pipelineResult) {
ntValidEntry.setBoolean(pipelineResult.IsValid);
if (pipelineResult.IsValid) {
ntYawEntry.setNumber(pipelineResult.Yaw);
ntPitchEntry.setNumber(pipelineResult.Pitch);
}
ntTimeStampEntry.setNumber(TimeStamp);
}
private PipelineResult runVisionProcess(Mat inputImage, Mat outputImage) {
var pipelineResult = new PipelineResult();
if (currentPipeline == null) {
return pipelineResult;
}
if (!currentPipeline.orientation.equals("Normal")) {
Core.flip(inputImage, inputImage, -1);
}
if (ntDriverModeEntry.getBoolean(false)) {
inputImage.copyTo(outputImage);
return pipelineResult;
}
Scalar hsvLower = new Scalar(currentPipeline.hue.get(0), currentPipeline.saturation.get(0), currentPipeline.value.get(0));
Scalar hsvUpper = new Scalar(currentPipeline.hue.get(1), currentPipeline.saturation.get(1), currentPipeline.value.get(1));
cvProcess.HSVThreshold(inputImage, hsvThreshMat, hsvLower, hsvUpper, currentPipeline.erode, currentPipeline.dilate);
if (currentPipeline.is_binary == 1) {
Imgproc.cvtColor(hsvThreshMat, outputImage, Imgproc.COLOR_GRAY2BGR, 3);
} else {
inputImage.copyTo(outputImage);
}
FoundContours = cvProcess.FindContours(hsvThreshMat);
if (FoundContours.size() > 0) {
FilteredContours = cvProcess.FilterContours(FoundContours, currentPipeline.area, currentPipeline.ratio, currentPipeline.extent);
if (FilteredContours.size() > 0) {
GroupedContours = cvProcess.GroupTargets(FilteredContours, currentPipeline.target_intersection, currentPipeline.target_group);
if (GroupedContours.size() > 0) {
var finalRect = cvProcess.SortTargetsToOne(GroupedContours, currentPipeline.sort_mode);
pipelineResult.RawPoint = finalRect;
pipelineResult.IsValid = true;
if (!currentPipeline.is_calibrated) {
pipelineResult.CalibratedX = camera.getCamVals().CenterX;
pipelineResult.CalibratedY = camera.getCamVals().CenterY;
} else {
pipelineResult.CalibratedX = (finalRect.center.y - currentPipeline.B) / currentPipeline.M;
pipelineResult.CalibratedY = finalRect.center.x * currentPipeline.M + currentPipeline.B;
}
pipelineResult.Pitch = camera.getCamVals().CalculatePitch(finalRect.center.y, pipelineResult.CalibratedY);
pipelineResult.Yaw = camera.getCamVals().CalculateYaw(finalRect.center.x, pipelineResult.CalibratedX);
drawContour(outputImage, finalRect);
}
}
}
return pipelineResult;
}
@Override
public void run() {
// processing time tracking
long startTime;
long fpsLastTime = 0;
double processTimeMs;
double fps = 0;
double uiFps = 0;
int maxFps = camera.getVideoMode().fps;
new Thread(cameraProcess).start();
long lastFrameEndNanosec = 0;
while (!Thread.interrupted()) {
startTime = System.nanoTime();
if ((startTime - lastFrameEndNanosec) * 1e-6 >= 1000.0 / maxFps + 3) { // 3 additional fps to allow for overhead
FoundContours.clear();
FilteredContours.clear();
GroupedContours.clear();
// update FPS for ui only every 0.5 seconds
if ((startTime - fpsLastTime) * 1e-6 >= 500) {
if (fps >= maxFps) {
uiFps = maxFps;
} else {
uiFps = fps;
}
fpsLastTime = System.nanoTime();
}
currentPipeline = camera.getCurrentPipeline();
// start fps counter right before grabbing input frame
TimeStamp = cameraProcess.getLatestFrame(cameraInputMat);
if (cameraInputMat.cols() == 0 && cameraInputMat.rows() == 0) {
continue;
}
// get vision data
var pipelineResult = runVisionProcess(cameraInputMat, streamOutputMat);
updateNetworkTables(pipelineResult);
if (cameraName.equals(SettingsManager.GeneralSettings.curr_camera)) {
HashMap<String, Object> WebSend = new HashMap<>();
HashMap<String, Object> point = new HashMap<>();
List<Double> center = new ArrayList<>();
if (pipelineResult.IsValid) {
center.add(pipelineResult.RawPoint.center.x);
center.add(pipelineResult.RawPoint.center.y);
point.put("pitch", pipelineResult.Pitch);
point.put("yaw", pipelineResult.Yaw);
} else {
center.add(0.0);
center.add(0.0);
point.put("pitch", 0);
point.put("yaw", 0);
}
point.put("fps", uiFps);
WebSend.put("point", point);
WebSend.put("raw_point", center);
ServerHandler.broadcastMessage(WebSend);
}
cameraProcess.updateFrame(streamOutputMat);
cameraInputMat.release();
hsvThreshMat.release();
// calculate FPS
lastFrameEndNanosec = System.nanoTime();
processTimeMs = (lastFrameEndNanosec - startTime) * 1e-6;
fps = 1000 / processTimeMs;
//please dont enable if you are not debugging
// System.out.printf("%s - Process time: %-5.2fms, FPS: %-5.2f, FoundContours: %d, FilteredContours: %d, GroupedContours: %d\n", cameraName, processTimeMs, fps, FoundContours.size(), FilteredContours.size(), GroupedContours.size());
}
}
}
}

View File

@@ -0,0 +1,31 @@
package com.chameleonvision.web;
import com.chameleonvision.settings.SettingsManager;
import io.javalin.Javalin;
public class Server {
public static ServerHandler handler;
public static void main(int port) {
handler = new ServerHandler();
Javalin app = Javalin.create();
app.config.addStaticFiles("web");
app.ws("/websocket", ws -> {
ws.onConnect(ctx -> {
handler.onConnect(ctx);
System.out.println("Socket Connected");
});
ws.onClose(ctx -> {
handler.onClose(ctx);
System.out.println("Socket Disconnected");
SettingsManager.saveSettings();
});
ws.onMessage(ctx -> {
handler.onMessage(ctx);
});
});
app.start(port);
}
}

View File

@@ -0,0 +1,204 @@
package com.chameleonvision.web;
import com.chameleonvision.CameraException;
import com.chameleonvision.settings.SettingsManager;
import com.chameleonvision.vision.Pipeline;
import com.chameleonvision.vision.camera.Camera;
import com.chameleonvision.vision.camera.CameraManager;
import com.google.gson.JsonArray;
import edu.wpi.cscore.VideoException;
import io.javalin.websocket.WsCloseContext;
import io.javalin.websocket.WsConnectContext;
import io.javalin.websocket.WsContext;
import io.javalin.websocket.WsMessageContext;
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.beans.BeanUtils;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ServerHandler {
private static List<WsContext> users;
ServerHandler() {
users = new ArrayList<>();
}
void onConnect(WsConnectContext context) {
users.add(context);
sendFullSettings();
}
public void onClose(WsCloseContext context) {
users.remove(context);
}
void onMessage(WsMessageContext data) throws CameraException {
broadcastMessage(data.message(), data);
JSONObject jsonObject = new JSONObject(data.message());
String key = null;
var jsonKeySetArray = jsonObject.keySet().toArray();
try {
key = jsonKeySetArray[0].toString();
} catch (Exception ex) {
System.err.println("WebSocket JSON data was empty!");
}
if (key == null) return;
Object value = jsonObject.get(key);
System.out.printf("Got websocket json data: [%s, %s]\n", key, value);
if (hasField(CameraManager.getCurrentPipeline(), key)) {
//Special cases for exposure and brightness and aspect ratio
switch (key) {
case "exposure":
int newExposure = (int) value;
System.out.printf("Changing exposure to %d\n", newExposure);
try {
CameraManager.getCurrentCamera().setExposure(newExposure);
} catch (VideoException e) {
System.out.println("Exposure changes is not supported on your webcam/webcam's driver");
}
break;
case "brightness":
int newBrightness = (int) value;
System.out.printf("Changing brightness to %d\n", newBrightness);
CameraManager.getCurrentCamera().setBrightness(newBrightness);
break;
case "ratio":
//If there is any better to convert Integer to Double you're welcome to change it
List<Double> doubleRatio = CameraManager.getCurrentPipeline().ratio;
List<Object> newRatio = ((JSONArray) value).toList();
for (int i = 0; i < newRatio.size(); i++) {
doubleRatio.set(i, Double.parseDouble(newRatio.get(i).toString()));
}
break;
default:
//Any other field in CameraManager that doesn't need anything special
setField(CameraManager.getCurrentPipeline(), key, value);
break;
}
} else {
switch (key) {
case "change_general_settings_values":
JSONObject newSettings = (JSONObject) value;
Map<String, Object> map = newSettings.toMap();
map.forEach((s, o) -> setField(SettingsManager.GeneralSettings, s, o));
SettingsManager.saveSettings();
break;
case "curr_camera":
String newCamera = (String) value;
System.out.printf("Changing camera to %s\n", newCamera);
CameraManager.setCurrentCamera(newCamera);
HashMap<String, Integer> portMap = new HashMap<>();
portMap.put("port", CameraManager.getCurrentCamera().getStreamPort());
sendFullSettings();
break;
case "curr_pipeline":
String newPipeline = (String) value;
var pipelineNumber = Integer.parseInt(newPipeline.replace("pipeline", ""));
System.out.printf("Changing pipeline to %s\n", newPipeline);
CameraManager.setCurrentPipeline(pipelineNumber);
// broadcastMessage(allFieldsToMap(CameraManager.getCurrentPipeline()));
broadcastMessage(allFieldsToMap(CameraManager.getCurrentPipeline()));
break;
case "resolution":
int newVideoMode = (int) value;
System.out.printf("Changing video mode to %d\n", newVideoMode);
CameraManager.getCurrentCamera().setCamVideoMode(newVideoMode, true);
break;
case "FOV":
double newFov = Double.parseDouble(value.toString());
System.out.printf("Changing FOV to %f\n", newFov);
CameraManager.getCurrentCamera().setFOV(newFov);
break;
default:
System.out.printf("Unexpected value from websocket: [%s, %s]\n", key, value);
break;
}
}
}
private void setField(Object obj, String fieldName, Object value) {
try {
Field[] fields = obj.getClass().getFields();
for (Field f : fields) {
if (f.getName().equals(fieldName)) {
if (BeanUtils.isSimpleValueType(value.getClass())) {
f.set(obj, value);
} else if (value.getClass() == JSONArray.class) {
f.set(obj, ((JSONArray) value).toList());
}
}
}
} catch (IllegalAccessException e) {
System.out.println("IllegalAccessException ");
e.printStackTrace();
}
}
private static void broadcastMessage(Object obj, WsContext userToSkip) {//TODO check if session id is a good way to differentiate users
for (var user : users) {
if (userToSkip != null && user.getSessionId().equals(userToSkip.getSessionId())) {
continue;
}
if (obj.getClass() == String.class)
user.send((String) obj);
else if (obj.getClass() == HashMap.class)
user.send(new JSONObject((HashMap<String, Object>) obj).toString());
else
user.send(new JSONObject(obj).toString());
}
}
public static void broadcastMessage(Object obj) {
broadcastMessage(obj, null);//Broadcasts the message to ever user
}
private boolean hasField(Object obj, String fieldName) {
Field[] fields = obj.getClass().getFields();
for (Field field : fields) {
if (fieldName.equals(field.getName()))
return true;
}
return false;
}
private static Map<String, Object> allFieldsToMap(Object obj) {
Map map = new HashMap<String, Object>();
try {
Field[] fields = obj.getClass().getFields();
for (Field field : fields) {
map.put(field.getName(), field.get(obj));
}
} catch (IllegalAccessException e) {
System.err.println("Illegal Access error:" + e.getStackTrace());
}
return map;
}
public static void sendFullSettings() {
//General settings
Map<String, Object> fullSettings = new HashMap<>(allFieldsToMap(SettingsManager.GeneralSettings));
fullSettings.put("cameraList", CameraManager.getAllCamerasByName().keySet());
try {
var currentCamera = CameraManager.getCurrentCamera();
fullSettings.putAll(allFieldsToMap(currentCamera.getCurrentPipeline()));
fullSettings.put("pipelineList", currentCamera.getPipelines().keySet());
fullSettings.put("resolutionList", CameraManager.getResolutionList());
fullSettings.put("resolution", currentCamera.getVideoModeIndex());
fullSettings.put("FOV", currentCamera.getFOV());
fullSettings.put("port", currentCamera.getStreamPort());
} catch (CameraException e) {
System.err.println("No camera found!");
//TODO: add message to ui to inform that there are no cameras
}
broadcastMessage(fullSettings);
}
}

View File

@@ -0,0 +1,2 @@
Manifest-Version: 1.0
Main-Class: com.chameleonvision.Main

View File

Before

Width:  |  Height:  |  Size: 542 KiB

After

Width:  |  Height:  |  Size: 542 KiB

View File

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

@@ -1 +1 @@
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=/favicon.png><title>Chameleon Vision</title><link href=/css/app.8be123c7.css rel=preload as=style><link href=/js/app.fd9292a1.js rel=preload as=script><link href=/js/chunk-vendors.a3ecb371.js rel=preload as=script><link href=/css/app.8be123c7.css rel=stylesheet></head><body><noscript><strong>We're sorry but Chameleon Vision doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.a3ecb371.js></script><script src=/js/app.fd9292a1.js></script></body></html>
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=/favicon.png><title>Chameleon Vision</title><link href=/css/app.ae0631ad.css rel=preload as=style><link href=/js/app.a39e3e86.js rel=preload as=script><link href=/js/chunk-vendors.60cc7e7e.js rel=preload as=script><link href=/css/app.ae0631ad.css rel=stylesheet></head><body><noscript><strong>We're sorry but Chameleon Vision doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.60cc7e7e.js></script><script src=/js/app.a39e3e86.js></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 542 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

@@ -0,0 +1 @@
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=/favicon.png><title>Chameleon Vision</title><link href=/css/app.ae0631ad.css rel=preload as=style><link href=/js/app.a39e3e86.js rel=preload as=script><link href=/js/chunk-vendors.60cc7e7e.js rel=preload as=script><link href=/css/app.ae0631ad.css rel=stylesheet></head><body><noscript><strong>We're sorry but Chameleon Vision doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.60cc7e7e.js></script><script src=/js/app.a39e3e86.js></script></body></html>

View File

@@ -151,4 +151,13 @@ main docs can be found at [google docs](https://docs.google.com/document/d/1qDuw
* the [robotpy project](https://github.com/robotpy) and mainly the cscore libs
* basically all of stackoverflow
* basically all of stackoverflow
##License
Copyright (C) 2019 Ori Agranat oriagranat9@gmail.com
* This file is part of Chameleon Vision.
Chameleon Vision can not be copied without the express permission of Ori Agranat
Chameleon Vision binaries may be distributed under [Creative Commons Attribution 4.0](https://creativecommons.org/licenses/by/4.0/)

View File

@@ -1,36 +0,0 @@
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
import threading
import asyncio
from app.handlers.CameraHander import CameraHandler
def run_server():
asyncio.set_event_loop(asyncio.new_event_loop())
tornado.options.parse_command_line()
app = ChameleonApplication()
print(f"Serving on port {options.port}")
app.listen(options.port)
tornado.ioloop.IOLoop.current().start()
def run():
NetworkTables.startClientTeam(team=settings_manager.general_settings.get("team_number", 1577))
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)
settings_manager = SettingsManager()
run()
server_thread = threading.Thread(target=run_server)
server_thread.start()

View File

@@ -1,27 +0,0 @@
import tornado.web
import tornado.websocket
import os
from .handlers.MainHandler import MainHandler
from .handlers.SocketHandler import ChameleonWebSocket
from tornado.options import define
define("port", default=8888, help="run on the given port", type=int)
class ChameleonApplication(tornado.web.Application):
def __init__(self):
handlers = [
(r"/", MainHandler),
(r"/websocket", ChameleonWebSocket),
(r"/(.*)", tornado.web.StaticFileHandler,
{"path": r"{0}".format(os.path.join(os.path.dirname(__file__), "site"))}),
]
settings = dict({
"template_path": os.path.join(os.path.dirname(__file__), "site"),
"static_path": os.path.join(os.path.dirname(__file__), "site"),
"debug": True
}
)
super(ChameleonApplication, self).__init__(handlers, **settings)

View File

@@ -1,10 +0,0 @@
class PipelineAlreadyExistsException(Exception):
def __init__(self, pipe_name):
super(f"Pipeline {pipe_name} already exists")
class NoCameraConnectedException(Exception):
def __init__(self):
super("No camera as been detected")

View File

@@ -1,263 +0,0 @@
import os
import json
import cv2
import cscore
import subprocess
from cscore._cscore import VideoMode
from .Singleton import Singleton
from .Exceptions import PipelineAlreadyExistsException, NoCameraConnectedException
from ..handlers.IPHandler import ChangeIP
class SettingsManager(metaclass=Singleton):
cams = {}
usb_cameras = {}
usb_cameras_info = {}
general_settings = {}
cams_port = {}
cams_curr_pipeline = {}
default_pipeline = {
"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
}
default_general_settings = {
"team_number": 1577,
"connection_type": "DHCP",
"ip": "",
"gateway": "",
"netmask": "",
"hostname": "Chameleon-Vision",
"curr_camera": "",
"curr_pipeline": ""
}
def __init__(self):
self.settings_path = os.path.join(os.getcwd(), "settings")
self.cams_path = os.path.join(self.settings_path, "cams")
self._init_general_settings()
ChangeIP(connection_type=self.general_settings['connection_type'], hostname=self.general_settings['hostname'],
ip=self.general_settings['ip'],
netmask=self.general_settings['netmask'], gateway=self.general_settings['gateway'])
self._init_cameras_info()
self._init_usb_cameras()
self._init_cameras()
self._init_usb_cameras_settings()
if self.general_settings["curr_camera"] not in self.cams:
if len(self.cams) > 0:
cam_name = list(self.cams.keys())[0]
self.general_settings["curr_camera"] = cam_name
self.general_settings["curr_pipeline"] = list(self.cams[cam_name]["pipelines"].keys())[0]
else:
self.general_settings["curr_camera"] = ""
self.general_settings["curr_pipeline"] = ""
def _init_general_settings(self):
try:
with open(os.path.join(self.settings_path, 'settings.json')) as setting_file:
self.general_settings = json.load(setting_file)
except FileNotFoundError:
self.general_settings = self.default_general_settings.copy()
# Initiate our camera's settings
def _init_cameras(self):
for cam_name in self.usb_cameras_info:
if os.path.exists(os.path.join(self.cams_path, cam_name + '.json')):
with open(os.path.join(self.cams_path, cam_name + '.json'), 'r') as camera:
self.cams[cam_name] = json.load(camera)
if len(self.cams[cam_name]["pipelines"]) == 0:
self.create_new_pipeline(cam_name=cam_name)
else:
self.create_new_cam(cam_name)
# Initiate true usb cameras(filters microphones and double cameras)
def _init_cameras_info(self):
true_cameras = []
usb_devices = cscore.UsbCamera.enumerateUsbCameras()
for index, device in enumerate(usb_devices):
cap = cv2.VideoCapture(device.dev)
if cap.isOpened():
true_cameras.append(index)
cap.release()
for i in true_cameras:
device_name = usb_devices[i].name
suffix = 0
while device_name in self.usb_cameras_info:
suffix += 1
device_name = f"{device.name}({str(suffix)})"
self.usb_cameras_info[device_name] = usb_devices[i]
# Initiate cscore usb devices
def _init_usb_cameras(self):
for device_name in self.usb_cameras_info:
device = self.usb_cameras_info[device_name]
camera = cscore.UsbCamera(name=device_name, dev=device.dev)
self.usb_cameras[device_name] = camera
def _init_usb_cameras_settings(self):
for cam_name in self.usb_cameras:
self.usb_cameras[cam_name].setPixelFormat(pixelFormat=getattr(VideoMode.PixelFormat, self.cams[cam_name]["video_mode"]["pixel_format"]))
self.usb_cameras[cam_name].setFPS(self.cams[cam_name]["video_mode"]["fps"])
self.usb_cameras[cam_name].setResolution(width=self.cams[cam_name]["video_mode"]["width"], height=self.cams[cam_name]["video_mode"]["height"])
# Change usb camera settings
def set_camera_settings(self, camera_name, dic):
if "brightness" in dic:
self.usb_cameras[camera_name].setBrightness(dic["brightness"])
if "exposure" in dic:
self.usb_cameras[camera_name].setExposureManual(dic["exposure"])
if "resolution" in dic:
video_mode: VideoMode = self.usb_cameras[camera_name].enumerateVideoModes()[int(dic["resolution"])]
self.cams[camera_name]["video_mode"] = {
"fps": video_mode.fps,
"width": video_mode.width,
"height": video_mode.height,
"pixel_format": str(video_mode.pixelFormat).split('.')[1]
}
self.usb_cameras[camera_name].setVideoMode(self.usb_cameras[camera_name].enumerateVideoModes()[int(dic["resolution"])])
if "FOV" in dic:
self.cams[camera_name]["FOV"] = float(dic["FOV"])
# Access methods
def get_curr_pipeline(self):
if self.general_settings["curr_pipeline"]:
return self.cams[self.general_settings["curr_camera"]]["pipelines"][self.general_settings["curr_pipeline"]]
raise NoCameraConnectedException()
def get_resolution_list(self):
if self.general_settings["curr_camera"]:
str_list = []
for val in self.usb_cameras[self.general_settings["curr_camera"]].enumerateVideoModes():
str_list.append("{width} X {height} at {fps} fps".format(width=str(val.width),
height=str(val.height), fps=str(val.fps)))
return str_list
raise NoCameraConnectedException()
def get_curr_cam(self):
if self.general_settings["curr_camera"]:
return self.cams[self.general_settings["curr_camera"]]
raise NoCameraConnectedException()
def set_curr_camera(self, cam_name):
if cam_name in self.cams:
self.general_settings["curr_camera"] = cam_name
self.general_settings["curr_pipeline"] = list(self.get_curr_cam()["pipelines"].keys())[0]
def set_curr_pipeline(self, pipe_name):
if pipe_name in self.get_curr_cam()["pipelines"]:
self.general_settings["curr_pipeline"] = pipe_name
def change_pipeline_values(self, dic, cam_name=None, pipe_name=None):
if not cam_name:
cam_name = self.general_settings["curr_camera"]
if not pipe_name:
pipe_name = self.general_settings["curr_pipeline"]
for key in dic:
if key in self.default_pipeline:
self.cams[cam_name]["pipelines"][pipe_name][key] = dic[key]
def change_general_settings_values(self, dic):
for key in dic['change_general_settings_values']:
if key in self.default_general_settings.keys():
self.general_settings[key] = dic['change_general_settings_values'][key]
self.save_settings()
subprocess.call(['reboot'])
# after all values has been set change settings
# Creators
def create_new_pipeline(self, pipe_name=None, cam_name=None):
if not cam_name:
cam_name = self.general_settings["curr_camera"]
if not pipe_name:
suffix = 0
pipe_name = "pipeline" + str(suffix)
while pipe_name in self.cams[cam_name]["pipelines"]:
suffix += 1
pipe_name = "pipeline" + str(suffix)
elif self.cams[cam_name]["pipelines"][pipe_name]:
raise PipelineAlreadyExistsException(pipe_name)
self.cams[cam_name]["pipelines"][pipe_name] = self.default_pipeline.copy()
def create_new_cam(self, cam_name):
self.cams[cam_name] = {}
self.cams[cam_name]["pipelines"] = {}
for i in range(10):
self.create_new_pipeline(cam_name=cam_name)
self.cams[cam_name]["path"] = self.usb_cameras_info[cam_name].otherPaths[0] if len(
self.usb_cameras_info[cam_name].otherPaths) == 1 else self.usb_cameras_info[cam_name].otherPaths[1]
video_mode: VideoMode = self.usb_cameras[cam_name].enumerateVideoModes()[0]
self.cams[cam_name]["video_mode"] = {
"fps": video_mode.fps,
"width": video_mode.width,
"height": video_mode.height,
"pixel_format": str(video_mode.pixelFormat).split('.')[1],
}
self.cams[cam_name]['resolution'] = 0
self.cams[cam_name]["FOV"] = 60.8
# Savers
def save_settings(self):
self._save_general_settings()
self._save_cameras()
def _save_cameras(self):
if not os.path.exists(self.cams_path):
os.mkdir(self.cams_path)
for cam in self.cams:
with open(os.path.join(self.cams_path, cam + '.json'), 'w+') as camera:
json.dump(self.cams[cam], camera)
def _save_general_settings(self):
if not os.path.exists(self.settings_path):
os.mkdir(self.settings_path)
with open(os.path.join(self.settings_path, 'settings.json'), 'w+') as setting_file:
json.dump(self.general_settings, setting_file)

View File

@@ -1,8 +0,0 @@
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]

View File

@@ -1,252 +0,0 @@
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(pipeline):
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())
if value in self.settings_manager.cams[self.cam_name]['pipelines'].keys():
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)
else:
self.table.putString('Pipeline', self.settings_manager.cams_curr_pipeline[self.cam_name])
def mode_listener(table, key, value, is_new):
if value:
change_camera_values({
'brightness': 25,
'exposure': 15
})
else:
change_camera_values(pipeline)
#setting up network table
self.table = NetworkTables.getTable("/Chameleon-Vision/" + self.cam_name)
#init values for pipeline and driver mode
self.table.putString('Pipeline', self.settings_manager.cams_curr_pipeline[self.cam_name])
self.table.putBoolean('Driver_Mode', False)
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('time_stamp', self.nt_data['time_stamp'])
self.table.putNumber('fps', self.nt_data['fps'])
# 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,
driver_mode=self.table.getBoolean('Driver_Mode', False)
), zmq.SNDMORE)
socket.send_pyobj((self.time_stamp,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()
curr_pipeline = obj['pipeline']
driver_mode = obj['driver_mode']
time_stamp, image = socket.recv_pyobj()
if curr_pipeline['orientation'] == "Inverted":
M = cv2.getRotationMatrix2D((width / 2, height / 2), 180, 1)
image = cv2.warpAffine(image, M, (width, height))
if not driver_mode:
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]
if curr_pipeline["M"] == 1 and curr_pipeline["B"] == 0:
center_x = centerX
center_y = centerY
else:
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)
else:
res = image
center = None
pitch = None
yaw = None
valid = False
socket.send_pyobj(res)
socket.send_json(dict(
pitch=pitch,
yaw=yaw,
valid=valid,
raw_point=center,
fps=fps,
time_stamp=time_stamp
))
counter += 1
if (time.time() - start_time) > x:
fps = (counter / (time.time() - start_time))
counter = 0
start_time = time.time()

View File

@@ -1,49 +0,0 @@
import subprocess
import netifaces
class ChangeIP:
def __init__(self, connection_type, ip, netmask, gateway, hostname):
adapter = self.find_adapter()
if adapter is not None:
self.shutdown_adapter(adapter)
if connection_type == "DHCP":
self.change_to_dhcp(adapter=adapter)
elif connection_type == "Static":
self.change_to_static(adapter=adapter, ip=ip, netmask=netmask, gateway=gateway)
self.start_adapter(adapter)
else:
print("not connected to robot radio cannot set ip")
self.change_hostname(hostname=hostname)
@staticmethod
def change_to_dhcp(adapter):
subprocess.call(['dhclient',"-r", adapter])
@staticmethod
def change_to_static(adapter, ip, netmask, gateway):
subprocess.call(['ifconfig', adapter, ip, 'netmask', netmask])
subprocess.call(['route', 'add', 'default', 'gw', gateway, adapter])
@staticmethod
def shutdown_adapter(adapter):
subprocess.call(['ifconfig', adapter, 'down'])
@staticmethod
def start_adapter(adapter):
subprocess.call(['ifconfig', adapter, 'up'])
@staticmethod
def find_adapter():
for i_name in netifaces.interfaces():
interface = netifaces.ifaddresses(i_name)[netifaces.AF_INET][0]
address = interface['addr'].split('.')[0]
if address == "10":
return str(i_name)
@staticmethod
def change_hostname(hostname):
subprocess.call(['hostnamectl', 'set-hostname', hostname])

View File

@@ -1,6 +0,0 @@
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html")

View File

@@ -1,123 +0,0 @@
import tornado.websocket
import json
from ..classes.Exceptions import NoCameraConnectedException
from ..classes.SettingsManager import SettingsManager
web_socket_clients = set()
def send_all_async(message):
for ws in web_socket_clients:
try:
ws.write_message(json.dumps(message))
except AssertionError as a:
pass
class ChameleonWebSocket(tornado.websocket.WebSocketHandler):
actions = {}
set_this_camera_settings = ["exposure", "brightness"]
def __init__(self, application, request, **kwargs):
super().__init__(application, request, **kwargs)
self.settings_manager = SettingsManager()
self.init_actions()
def init_actions(self):
self.actions["change_pipeline_values"] = self.change_pipeline_values
self.actions["change_general_settings_values"] = self.settings_manager.change_general_settings_values
self.actions["curr_camera"] = self.change_curr_camera
self.actions["curr_pipeline"] = self.change_curr_pipeline
self.actions['resolution'] = self.set_resolution
self.actions['FOV'] = self.set_fov
def open(self):
self.send_full_settings()
if self not in web_socket_clients:
web_socket_clients.add(self)
print("WebSocket opened")
def on_message(self, message):
try:
message_dic = json.loads(message)
for key in message_dic:
self.actions.get(key, self.actions["change_pipeline_values"])(message_dic)
print(message)
except Exception as e:
print("crash " + e)
def on_close(self):
self.settings_manager.save_settings()
if self in web_socket_clients:
web_socket_clients.remove(self)
print("WebSocket closed")
def check_origin(self, origin):
return True
def set_resolution(self, message):
self.settings_manager.get_curr_cam()['resolution'] = message['resolution']
SettingsManager().set_camera_settings(camera_name=SettingsManager().general_settings['curr_camera'],
dic=message)
self.settings_manager.save_settings()
def set_fov(self, message):
self.settings_manager.get_curr_cam()['FOV'] = message['FOV']
self.settings_manager.save_settings()
def send_curr_pipeline(self):
try:
self.write_message(self.settings_manager.get_curr_pipeline())
except NoCameraConnectedException:
# TODO: return something if no camera connected
self.write_message("No camera connected")
def send_curr_cam(self):
try:
self.write_message(self.settings_manager.get_curr_cam())
except NoCameraConnectedException:
# TODO: return something if no camera connected
self.write_message("No camera connected")
def send_curr_port(self):
self.write_message({
'port': self.settings_manager.cams_port[self.settings_manager.general_settings["curr_camera"]]
})
def send_full_settings(self):
full_settings = self.settings_manager.general_settings.copy()
full_settings["cameraList"] = list(self.settings_manager.cams.copy().keys())
try:
full_settings.update(self.settings_manager.get_curr_pipeline())
full_settings["pipelineList"] = list(self.settings_manager.cams[self.settings_manager.general_settings["curr_camera"]]["pipelines"].keys())
full_settings["resolutionList"] = self.settings_manager.get_resolution_list()
full_settings['resolution'] = self.settings_manager.get_curr_cam()['resolution']
full_settings['FOV'] = self.settings_manager.get_curr_cam()['FOV']
full_settings['port'] = self.settings_manager.cams_port[self.settings_manager.general_settings["curr_camera"]]
except NoCameraConnectedException:
# TODO: return something if no camera connected
full_settings["data"] = None
self.write_message(full_settings)
def change_curr_camera(self, dic):
self.settings_manager.set_curr_camera(cam_name=dic["curr_camera"])
self.send_curr_port()
self.send_curr_cam()
def change_curr_pipeline(self, dic):
self.settings_manager.set_curr_pipeline(pipe_name=dic["curr_pipeline"])
self.settings_manager.cams_curr_pipeline[self.settings_manager.general_settings['curr_camera']] = dic["curr_pipeline"]
self.send_curr_pipeline()
def change_pipeline_values(self, dic):
self.settings_manager.change_pipeline_values(dic)
for key in self.set_this_camera_settings:
if key in dic:
self.settings_manager.set_camera_settings(self.settings_manager.general_settings["curr_camera"],
dic)

View File

@@ -1,221 +0,0 @@
import cv2
import numpy
import math
from enum import Enum, unique
class VisionHandler():
def __init__(self):
self.kernel = numpy.ones((5, 5), numpy.uint8)
def _hsv_threshold(self, hue: list, saturation: list, value: list, img: numpy.ndarray, is_erode: bool,
is_dilate: bool):
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
thresh = cv2.inRange(hsv, (hue[0], saturation[0], value[0]), (hue[1], saturation[1], value[1]))
erode_img = cv2.erode(thresh, kernel=self.kernel, iterations=is_erode)
dilate_img = cv2.dilate(erode_img, kernel=self.kernel, iterations=is_dilate)
return dilate_img
def find_contours(self, binary_img: numpy.ndarray):
_,contours, _ = cv2.findContours(binary_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_TC89_L1)
return contours
class Filter_Contours:
def __init__(self,center_x, center_y):
self.sort_mode = self.SortMode(center_x=center_x, center_y=center_y)
self.center_y = center_y
self.center_x = center_x
class SortMode:
def __init__(self, center_x, center_y):
self.center_x = center_x
self.center_y = center_y
@classmethod
def moment_x(cls,contour):
M = cv2.moments(contour)
try:
x = float(M['m10'] / M['m00'])
except ZeroDivisionError:
x = 0
return x
@classmethod
def moment_y(cls, contour):
M = cv2.moments(contour)
try:
y = float(M['m01'] / M['m00'])
except ZeroDivisionError:
y = 0
return y
@classmethod
def calc_distance(cls,contour, center_x, center_y):
M = cv2.moments(contour)
try:
x = int(M['m10'] / M['m00'])
except ZeroDivisionError:
x = 0
try:
y = int(M['m01'] / M['m00'])
except ZeroDivisionError:
y = 0
# this function was suggested by my girlfriend maya jugend that i really love
return math.sqrt((center_x-x)**2 + (center_y-y)**2)
def Largest(self, input_contours):
return sorted(input_contours, key=lambda x: cv2.contourArea(x), reverse=True)
def Smallest(self, input_contours):
return sorted(input_contours, key=lambda x: cv2.contourArea(x))
def Highest(self, input_contours):
return sorted(input_contours, key=lambda x: self.moment_y(x))
def Lowest(self, input_contours):
return sorted(input_contours, key=lambda x: self.moment_y(x),reverse=True)
def Rightmost(self, input_contours):
return sorted(input_contours, key=lambda x: self.moment_x(x), reverse=True)
def Leftmost(self, input_contours):
return sorted(input_contours, key=lambda x: self.moment_x(x))
def Closest(self, input_contours):
return sorted(input_contours, key=lambda x: self.calc_distance(x, center_x=self.center_x,
center_y=self.center_y), reverse=True)
def filter_contours(self, input_contours, cam_area, area, ratio, extent, sort_mode, target_grouping,
target_intersection):
class TargetGroup(Enum):
Single = 1
Dual = 2
Triple = 3
Quadruple = 4
Quintuple = 6
def group_target(i_contours, target_group, intersection_point):
def is_intersecting(contour_a, contour_b, intersection_direction):
[vx_a, vy_a, x0_a, y0_a] = cv2.fitLine(contour_a, cv2.DIST_L2, 0, 0.01, 0.01)
[vx_b, vy_b, x0_b, y0_b] = cv2.fitLine(contour_b, cv2.DIST_L2, 0, 0.01, 0.01)
# getting line data of both contours
m_a = vy_a / vx_a
m_b = vy_b / vx_b
# calculating slope of both lines
try:
intersection_x = ((m_a * x0_a) - y0_a - (m_b * x0_b) + y0_b) / (m_a - m_b)
except ZeroDivisionError:
if intersection_direction == 'Parallel':
return True
else:
return False
intersection_y = (m_a * (intersection_x - x0_a)) + y0_a
# finding intersection point
if intersection_direction == 'Up':
if intersection_y < self.center_y:
return True
elif intersection_direction == 'Down':
if intersection_y > self.center_y:
return True
elif intersection_direction == 'Left':
if intersection_x < self.center_x:
return True
elif intersection_direction == 'Right':
if intersection_x > self.center_x:
return True
else:
return False
if target_group != TargetGroup.Single:
f_contour_list = []
for index, g_contour in enumerate(i_contours):
final_contour = g_contour
for c in range(target_group.value - 1):
try:
first_contour = i_contours[index + c]
second_contour = i_contours[index + c + 1]
except IndexError:
final_contour = []
break
if is_intersecting(first_contour, second_contour, intersection_point):
final_contour = numpy.concatenate((final_contour, second_contour))
else:
final_contour = []
break
if final_contour != []:
f_contour_list.append(final_contour)
return f_contour_list
else:
return i_contours
'''start of the first filtration of contours'''
filtered_contours = []
for contour in input_contours:
try:
contour_area = cv2.contourArea(contour)
target_area = float(contour_area / cam_area)*100
if target_area >= area[1] or target_area <= area[0]:
continue
rect = cv2.minAreaRect(contour)
bounding_rect_area = rect[1][0] * rect[1][1]
try:
target_fullness = float(contour_area / bounding_rect_area)*100
except ZeroDivisionError:
target_fullness = 0
if target_fullness <= extent[0] or target_fullness >= extent[1]:
continue
try:
aspect_ratio = float(rect[1][0]/rect[1][1])
except ZeroDivisionError:
aspect_ratio = 0
if aspect_ratio <= ratio[0] or aspect_ratio >= ratio[1]:
continue
filtered_contours.append(contour)
except Exception as e:
print(e)
continue
#checking for contour grouping before sorting
grouped_contours = group_target(filtered_contours, TargetGroup[target_grouping], target_intersection)
try:
sorted_contours = getattr(self.sort_mode, sort_mode)(grouped_contours)
except TypeError:
sorted_contours = []
return sorted_contours
def output_contour(self, sorted_contours):
if len(sorted_contours) > 0:
selected_contour = sorted_contours[0]
rect = cv2.minAreaRect(selected_contour)
else:
return []
return rect
def draw_image(self, input_image, contour):
if len(input_image.shape)<3:
input_image = cv2.cvtColor(input_image, cv2.COLOR_GRAY2RGB)
if contour != []:
box = cv2.boxPoints(contour)
box = numpy.int0(box)
cv2.drawContours(input_image, [box], 0, (0, 0, 255), 3)
# center_point = (int(rectangle[0][0]), int(rectangle[0][1]))
# cv2.circle(input_image, center_point, 0, (0, 255, 0), thickness=3, lineType=8, shift=0)
return input_image
def calculate_pitch(self, pixel_y, center_y, v_focal_length):
pitch = math.degrees(math.atan((pixel_y - center_y) / v_focal_length))
pitch *= -1
return pitch
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

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +0,0 @@
tornado
pyzmq
robotpy-cscore
netifaces

View File

@@ -67,6 +67,20 @@
this.$refs.menu.updateOpened();
this.$refs.menu.updateActiveName();
})
},
isEquale(message,prop){
if(typeof (this.$store.state[prop]) == "object"){
for(var i = this.$store.state[prop].length; i--;) {
if(this.$store.state[prop][i] !== message[prop][i]){
return false;
}
}
} else{
if(this.$store.state[prop] != message[prop]){
return false
}
}
return true;
}
},
computed: {
@@ -86,9 +100,11 @@
let message = JSON.parse(data.data);
for (var prop in message){
if(message.hasOwnProperty(prop)){
this.$store.state[prop] = message[prop];
this.$store.state[prop] = message[prop];
// console.log(message);
}
console.log(data.data);
}
}
catch{

View File

@@ -6,7 +6,7 @@
<chselect title="camera" :list="cameraList" Xkey="curr_camera"></chselect>
</Col>
<Col span="12">
<chselect title="pipline" :list="pipelineList" Xkey="curr_pipeline"></chselect>
<chselect title="pipeline" :list="pipelineList" Xkey="curr_pipeline"></chselect>
</Col>
</Row>
</Header>

View File

@@ -5,7 +5,7 @@
<chrange class="spacing" title="Extent" Xkey="extent"></chrange>
<chselect class="spacing" title="Target Group" Xkey="target_group"
:list="['Single','Dual','Triple','Quadruple','Quintuple']"></chselect>
<chselect class="spacing" title="Target Intersaction" Xkey="target_intersection"
<chselect class="spacing" title="Target Intersection" Xkey="target_intersection"
:list="['Up','Down','Left','Right','Parallel']" :isDisabled="isSingle"></chselect>
</div>
</template>

View File

@@ -44,7 +44,7 @@ import chrange from './ch-range.vue'
let m = (this.pointB[1] - this.pointA[1]) / (this.pointB[0] - this.pointA[0]);
let b = this.pointA[1] - (m * this.pointA[0]);
if(isNaN(m) === false && isNaN(b) === false){
this.sendSlope(m,b);
this.sendSlope(m,b,true);
} else{
this.$Message.error("Point A and B are to close apart");
}
@@ -53,13 +53,14 @@ import chrange from './ch-range.vue'
}
},
clearPoints:function(){
this.sendSlope(1,0);
this.sendSlope(1,0,false);
this.pointA = undefined;
this.pointB = undefined;
},
sendSlope(m,b){
sendSlope(m,b,valid){
this.$socket.sendObj({'M':m});
this.$socket.sendObj({'B':b});
this.$socket.sendObj({'is_calibrated':valid});
}
},
computed: {

View File

@@ -1,10 +0,0 @@
#!/bin/bash
apt-get update
apt-get dist-upgrade
apt-get upgrade
apt-get install python3-pip python3-dev cmake zip unzip build-essential git libnss-mdns --fix-missing
apt-get install python3-numpy
apt-get install python3-opencv
pip3 install robotpy-cscore
pip3 install pyzmq
pip3 install tornado

4088
logo.ai

File diff suppressed because one or more lines are too long

96
package-lock.json generated
View File

@@ -1,96 +0,0 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"async-validator": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/async-validator/-/async-validator-1.10.1.tgz",
"integrity": "sha512-VLiLKZuJc8VIeAMC3YobVsZov8XPNhbwyIkKjhPW5cFnhZXH+HHJpkE270YMD/6zJIOJXUN/Cq0t3fR7XPwaDQ==",
"requires": {
"babel-runtime": "6.x"
}
},
"babel-runtime": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
"integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
"requires": {
"core-js": "^2.4.0",
"regenerator-runtime": "^0.11.0"
}
},
"batch-processor": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/batch-processor/-/batch-processor-1.0.0.tgz",
"integrity": "sha1-dclcMrdI4IUNEMKxaPa9vpiRrOg="
},
"core-js": {
"version": "2.6.5",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz",
"integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A=="
},
"deepmerge": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz",
"integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA=="
},
"element-resize-detector": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/element-resize-detector/-/element-resize-detector-1.2.0.tgz",
"integrity": "sha512-UmhNB8sIJVZeg56gEjgmMd6p37sCg8j8trVW0LZM7Wzv+kxQ5CnRHcgRKBTB/kFUSn3e7UP59kl2V2U8Du1hmg==",
"requires": {
"batch-processor": "1.0.0"
}
},
"iview": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/iview/-/iview-3.3.0.tgz",
"integrity": "sha512-PyqhfxEO9/4rcDNZ1FhMMGjcKh/y/F1p/00/UXrNxXBnih4rIb8GxgxYk0Y14bzuCL/AuMAisOBbQm51o5iOlQ==",
"requires": {
"async-validator": "^1.10.0",
"deepmerge": "^2.2.1",
"element-resize-detector": "^1.2.0",
"js-calendar": "^1.2.3",
"lodash.throttle": "^4.1.1",
"popper.js": "^1.14.6",
"tinycolor2": "^1.4.1",
"v-click-outside-x": "^3.5.6"
}
},
"js-calendar": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/js-calendar/-/js-calendar-1.2.3.tgz",
"integrity": "sha512-dAA1/Zbp4+c5E+ARCVTIuKepXsNLzSYfzvOimiYD4S5eeP9QuplSHLcdhfqFSwyM1o1u6ku6RRRCyaZ0YAjiBw=="
},
"lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
},
"popper.js": {
"version": "1.14.7",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.7.tgz",
"integrity": "sha512-4q1hNvoUre/8srWsH7hnoSJ5xVmIL4qgz+s4qf2TnJIMyZFUFMGH+9vE7mXynAlHSZ/NdTmmow86muD0myUkVQ=="
},
"regenerator-runtime": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
},
"tinycolor2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz",
"integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g="
},
"undefined": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/undefined/-/undefined-0.1.0.tgz",
"integrity": "sha1-m3BqSzKtMMIMpP5l3cu72sMr3tA="
},
"v-click-outside-x": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/v-click-outside-x/-/v-click-outside-x-3.7.1.tgz",
"integrity": "sha512-WmUgmcIXr9clVpm1AYS/FgHtcDicfnfoxgQCNg4O6vfk9GVnxA0vSqO321ogUo0b7czYTidj7fQENvWFMWOkUg=="
}
}
}