Skip to content

Commit

Permalink
Use CS Core on supported platforms for USB cameras (#956)
Browse files Browse the repository at this point in the history
* Update jpackage to release 64

Existing version was deleted

* Use CS Core on supported platforms for USB cameras

Can be more reliable then OpenCV, and handles disconnects properly

* Fix unused imports
  • Loading branch information
ThadHouse authored Jul 14, 2020
1 parent 04de723 commit 3e2210a
Show file tree
Hide file tree
Showing 3 changed files with 212 additions and 108 deletions.
Original file line number Diff line number Diff line change
@@ -1,97 +1,101 @@
package edu.wpi.grip.core.sources;

import edu.wpi.cscore.CameraServerJNI;
import edu.wpi.cscore.HttpCamera;
import org.bytedeco.javacpp.Loader;
import org.bytedeco.javacpp.opencv_core.Mat;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameConverter;
import org.bytedeco.javacv.FrameGrabber;
import org.bytedeco.javacv.OpenCVFrameConverter;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

import static com.google.common.base.Preconditions.checkNotNull;

// This is here because FrameGrabber has an exception called Exception which triggers PMD
@SuppressWarnings({"PMD.AvoidThrowingRawExceptionTypes", "all"})
public class CSCameraFrameGrabber extends FrameGrabber {

private static Exception loadingException = null;

private final String url;
private final double readTimeout;

private Mat decoded = null;
private FrameConverter<Mat> converter = new OpenCVFrameConverter.ToMat();

private JavaCvSink javaCvSink;
private HttpCamera httpCamera;

public CSCameraFrameGrabber(String urlstr, int readTimeout, TimeUnit unit) {
super();
this.url = checkNotNull(urlstr, "urlstr");
this.readTimeout = TimeUnit.MILLISECONDS.convert(readTimeout, unit) / 1000.0;
}

public static void tryLoad() throws Exception {
if (loadingException != null) {
throw loadingException;
} else {
try {
Loader.load(org.bytedeco.javacpp.opencv_highgui.class);
CameraServerJNI.Helper.setExtractOnStaticLoad(false);
CameraServerJNI.forceLoad();
} catch (Throwable t) {
throw loadingException = new Exception("Failed to load " + CSCameraFrameGrabber.class, t);
}
}
}

@Override
public void start() throws Exception {
javaCvSink = new JavaCvSink("InputCamera");
httpCamera = new HttpCamera("InputHttp", url);
javaCvSink.setSource(httpCamera);
}

public void stop() throws Exception {
if (javaCvSink != null) {
javaCvSink.close();
}

if (httpCamera != null) {
httpCamera.close();
}

if (decoded != null) {
decoded.close();
}
}

@Override
public void trigger() throws Exception {
}

@Override
public Frame grab() throws Exception {
try {
if (decoded == null) {
decoded = new Mat();
}
long frameTime = javaCvSink.grabFrame(decoded, readTimeout);
if (frameTime > 0) {
return converter.convert(decoded);
} else {
throw new IOException("Frame not read: " + frameTime);
}
} catch (IOException e) {
throw new Exception(e.getMessage(), e);
}
}

@Override
public void release() throws Exception {
}
}
package edu.wpi.grip.core.sources;

import edu.wpi.cscore.CameraServerJNI;
import edu.wpi.cscore.HttpCamera;
import org.bytedeco.javacpp.Loader;
import org.bytedeco.javacpp.opencv_core.Mat;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameConverter;
import org.bytedeco.javacv.FrameGrabber;
import org.bytedeco.javacv.OpenCVFrameConverter;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

import static com.google.common.base.Preconditions.checkNotNull;

// This is here because FrameGrabber has an exception called Exception which triggers PMD
@SuppressWarnings({"PMD.AvoidThrowingRawExceptionTypes", "all"})
public class CSHttpCameraFrameGrabber extends FrameGrabber {

private static Exception loadingException = null;

private final String url;
private final double readTimeout;

private Mat decoded = null;
private FrameConverter<Mat> converter = new OpenCVFrameConverter.ToMat();

private JavaCvSink javaCvSink;
private HttpCamera httpCamera;

public CSHttpCameraFrameGrabber(String urlstr, int readTimeout, TimeUnit unit) {
super();
this.url = checkNotNull(urlstr, "urlstr");
this.readTimeout = TimeUnit.MILLISECONDS.convert(readTimeout, unit) / 1000.0;
}

public static void tryLoad() throws Exception {
if (loadingException != null) {
throw loadingException;
} else {
try {
Loader.load(org.bytedeco.javacpp.opencv_highgui.class);
CameraServerJNI.Helper.setExtractOnStaticLoad(false);
CameraServerJNI.forceLoad();
} catch (Throwable t) {
throw loadingException = new Exception("Failed to load " + CSHttpCameraFrameGrabber.class, t);
}
}
}

public HttpCamera getCamera() {
return this.httpCamera;
}

@Override
public void start() throws Exception {
javaCvSink = new JavaCvSink("InputCamera");
httpCamera = new HttpCamera("InputHttp", url);
javaCvSink.setSource(httpCamera);
}

public void stop() throws Exception {
if (javaCvSink != null) {
javaCvSink.close();
}

if (httpCamera != null) {
httpCamera.close();
}

if (decoded != null) {
decoded.close();
}
}

@Override
public void trigger() throws Exception {
}

@Override
public Frame grab() throws Exception {
try {
if (decoded == null) {
decoded = new Mat();
}
long frameTime = javaCvSink.grabFrame(decoded, readTimeout);
if (frameTime > 0) {
return converter.convert(decoded);
} else {
throw new IOException("Frame not read: " + frameTime);
}
} catch (IOException e) {
throw new Exception(e.getMessage(), e);
}
}

@Override
public void release() throws Exception {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package edu.wpi.grip.core.sources;

import edu.wpi.cscore.CameraServerJNI;
import edu.wpi.cscore.UsbCamera;
import org.bytedeco.javacpp.Loader;
import org.bytedeco.javacpp.opencv_core.Mat;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameConverter;
import org.bytedeco.javacv.FrameGrabber;
import org.bytedeco.javacv.OpenCVFrameConverter;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

// This is here because FrameGrabber has an exception called Exception which triggers PMD
@SuppressWarnings({"PMD.AvoidThrowingRawExceptionTypes", "all"})
public class CSUsbCameraFrameGrabber extends FrameGrabber {

private static Exception loadingException = null;

private final int deviceId;
private final double readTimeout;

private Mat decoded = null;
private FrameConverter<Mat> converter = new OpenCVFrameConverter.ToMat();

private JavaCvSink javaCvSink;
private UsbCamera usbCamera;

public CSUsbCameraFrameGrabber(int deviceId, int readTimeout, TimeUnit unit) {
super();
this.deviceId = deviceId;
this.readTimeout = TimeUnit.MILLISECONDS.convert(readTimeout, unit) / 1000.0;
}

public static void tryLoad() throws Exception {
if (loadingException != null) {
throw loadingException;
} else {
try {
Loader.load(org.bytedeco.javacpp.opencv_highgui.class);
CameraServerJNI.Helper.setExtractOnStaticLoad(false);
CameraServerJNI.forceLoad();
} catch (Throwable t) {
throw loadingException = new Exception("Failed to load " + CSUsbCameraFrameGrabber.class, t);
}
}
}

public UsbCamera getCamera() {
return this.usbCamera;
}

@Override
public void start() throws Exception {
javaCvSink = new JavaCvSink("InputCamera");
usbCamera = new UsbCamera("InputUsb", deviceId);
javaCvSink.setSource(usbCamera);
}

public void stop() throws Exception {
if (javaCvSink != null) {
javaCvSink.close();
}

if (usbCamera != null) {
usbCamera.close();
}

if (decoded != null) {
decoded.close();
}
}

@Override
public void trigger() throws Exception {
}

@Override
public Frame grab() throws Exception {
try {
if (decoded == null) {
decoded = new Mat();
}
long frameTime = javaCvSink.grabFrame(decoded, readTimeout);
if (frameTime > 0) {
return converter.convert(decoded);
} else {
throw new IOException("Frame not read: " + frameTime);
}
} catch (IOException e) {
throw new Exception(e.getMessage(), e);
}
}

@Override
public void release() throws Exception {
}
}
23 changes: 12 additions & 11 deletions core/src/main/java/edu/wpi/grip/core/sources/CameraSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import org.bytedeco.javacpp.opencv_core.Mat;
import org.bytedeco.javacv.FrameGrabber;
import org.bytedeco.javacv.OpenCVFrameGrabber;
import org.bytedeco.javacv.VideoInputFrameGrabber;

import java.io.IOException;
import java.net.MalformedURLException;
Expand Down Expand Up @@ -60,8 +59,8 @@ public class CameraSource extends Source implements RestartableService {
* Reading from an existing connection shouldn't take that long. If it does we should really give
* up and try to reconnect.
*/
private static final int IP_CAMERA_READ_TIMEOUT = 5;
private static final TimeUnit IP_CAMERA_TIMEOUT_UNIT = TimeUnit.SECONDS;
private static final int CAMERA_READ_TIMEOUT = 5;
private static final TimeUnit CAMERA_TIMEOUT_UNIT = TimeUnit.SECONDS;

private static final String DEVICE_NUMBER_PROPERTY = "deviceNumber";
private static final String ADDRESS_PROPERTY = "address";
Expand Down Expand Up @@ -391,11 +390,13 @@ public static class FrameGrabberFactoryImpl implements FrameGrabberFactory {

@Override
public FrameGrabber create(int deviceNumber) {
// On Windows, videoInput is much more reliable for webcam capture. On other platforms,
// OpenCV's frame
// grabber class works fine.
if (StandardSystemProperty.OS_NAME.value().contains("Windows")) {
return new VideoInputFrameGrabber(deviceNumber);
// On Windows and Linux, use CS Core USB Cameras.
// Mac support does not exist, so must use OpenCV's Grabber
if (StandardSystemProperty.OS_NAME.value().contains("Windows")
|| StandardSystemProperty.OS_NAME.value().contains("Linux")) {
return new CSUsbCameraFrameGrabber(deviceNumber,
CAMERA_READ_TIMEOUT,
CAMERA_TIMEOUT_UNIT);
} else {
return new OpenCVFrameGrabber(deviceNumber);
}
Expand All @@ -409,10 +410,10 @@ public FrameGrabber create(String addressProperty) throws MalformedURLException
if (new URL(addressProperty).getPath().length() <= 1) {
addressProperty += DEFAULT_IP_CAMERA_PATH;
}
return new CSCameraFrameGrabber(
return new CSHttpCameraFrameGrabber(
addressProperty,
IP_CAMERA_READ_TIMEOUT,
IP_CAMERA_TIMEOUT_UNIT);
CAMERA_READ_TIMEOUT,
CAMERA_TIMEOUT_UNIT);
}
}
}

0 comments on commit 3e2210a

Please sign in to comment.