[cscore] Use Raw for CvSink and CvSource (#6364)

Eventually we want to get to a point where we can remove OpenCV from the internals of cscore. The start to doing that is converting the existing CvSource and CvSink methods to RawFrame.

For CvSource, this is 100% a free operation. We can do everything the existing code could have done (with one small exception we can fairly easily fix).

For CvSink, by defaut this change would incur one extra copy, but no extra allocations. A set of direct methods were added to CvSink to add a method to avoid this extra copy.
This commit is contained in:
Thad House
2024-02-12 22:33:03 -08:00
committed by GitHub
parent 4f9d73783b
commit fb947fe998
28 changed files with 666 additions and 1203 deletions

View File

@@ -366,6 +366,8 @@ public final class CameraServer {
}
case kCv:
return "cv:";
case kRaw:
return "raw:";
default:
return "unknown:";
}

View File

@@ -113,6 +113,8 @@ static std::string_view MakeSourceValue(CS_Source source,
}
case CS_SOURCE_CV:
return "cv:";
case CS_SOURCE_RAW:
return "raw:";
default:
return "unknown:";
}

View File

@@ -212,6 +212,7 @@ public class CameraServerJNI {
* Creates a raw source.
*
* @param name Source name.
* @param isCv true for a Cv source.
* @param pixelFormat Pixel format.
* @param width Image width.
* @param height Image height.
@@ -219,7 +220,7 @@ public class CameraServerJNI {
* @return Raw source handle.
*/
public static native int createRawSource(
String name, int pixelFormat, int width, int height, int fps);
String name, boolean isCv, int pixelFormat, int width, int height, int fps);
//
// Source Functions
@@ -630,9 +631,10 @@ public class CameraServerJNI {
* Creates a raw sink.
*
* @param name Sink name.
* @param isCv true for a Cv source.
* @return Raw sink handle.
*/
public static native int createRawSink(String name);
public static native int createRawSink(String name, boolean isCv);
//
// Sink Functions

View File

@@ -5,6 +5,9 @@
package edu.wpi.first.cscore;
import edu.wpi.first.util.PixelFormat;
import edu.wpi.first.util.RawFrame;
import java.nio.ByteBuffer;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
/**
@@ -12,15 +15,52 @@ import org.opencv.core.Mat;
* OpenCV builds. For an alternate OpenCV, see the documentation how to build your own with RawSink.
*/
public class CvSink extends ImageSink {
private final RawFrame m_frame = new RawFrame();
private Mat m_tmpMat;
private ByteBuffer m_origByteBuffer;
private int m_width;
private int m_height;
private PixelFormat m_pixelFormat;
@Override
public void close() {
if (m_tmpMat != null) {
m_tmpMat.release();
}
m_frame.close();
super.close();
}
private int getCVFormat(PixelFormat pixelFormat) {
int type = 0;
switch (pixelFormat) {
case kYUYV:
case kRGB565:
type = CvType.CV_8UC2;
break;
case kBGR:
type = CvType.CV_8UC3;
break;
case kGray:
case kMJPEG:
default:
type = CvType.CV_8UC1;
break;
}
return type;
}
/**
* Create a sink for accepting OpenCV images. WaitForFrame() must be called on the created sink to
* Create a sink for accepting OpenCV images. grabFrame() must be called on the created sink to
* get each new image.
*
* @param name Source name (arbitrary unique identifier)
* @param pixelFormat Source pixel format
*/
public CvSink(String name, PixelFormat pixelFormat) {
super(CameraServerCvJNI.createCvSink(name, pixelFormat.getValue()));
super(CameraServerJNI.createRawSink(name, true));
m_pixelFormat = pixelFormat;
OpenCvLoader.forceStaticLoad();
}
/**
@@ -33,22 +73,9 @@ public class CvSink extends ImageSink {
this(name, PixelFormat.kBGR);
}
/// Create a sink for accepting OpenCV images in a separate thread.
/// A thread will be created that calls WaitForFrame() and calls the
/// processFrame() callback each time a new frame arrives.
/// @param name Source name (arbitrary unique identifier)
/// @param processFrame Frame processing function; will be called with a
/// time=0 if an error occurred. processFrame should call GetImage()
/// or GetError() as needed, but should not call (except in very
/// unusual circumstances) WaitForImage().
// public CvSink(wpi::StringRef name,
// std::function<void(uint64_t time)> processFrame) {
// super(CameraServerJNI.createCvSinkCallback(name, processFrame));
// }
/**
* Wait for the next frame and get the image. Times out (returning 0) after 0.225 seconds. The
* provided image will have three 3-bit channels stored in BGR order.
* provided image will have the pixelFormat this class was constructed with.
*
* @param image Where to store the image.
* @return Frame time, or 0 on error (call GetError() to obtain the error message)
@@ -59,7 +86,7 @@ public class CvSink extends ImageSink {
/**
* Wait for the next frame and get the image. Times out (returning 0) after timeout seconds. The
* provided image will have three 3-bit channels stored in BGR order.
* provided image will have the pixelFormat this class was constructed with.
*
* @param image Where to store the image.
* @param timeout Retrieval timeout in seconds.
@@ -67,18 +94,140 @@ public class CvSink extends ImageSink {
* is in 1 us increments.
*/
public long grabFrame(Mat image, double timeout) {
return CameraServerCvJNI.grabSinkFrameTimeout(m_handle, image.nativeObj, timeout);
long rv = grabFrameDirect(timeout);
if (rv <= 0) {
return rv;
}
m_tmpMat.copyTo(image);
return rv;
}
/**
* Wait for the next frame and get the image. May block forever. The provided image will have
* three 3-bit channels stored in BGR order.
* Wait for the next frame and get the image. May block forever. The provided image will have the
* pixelFormat this class was constructed with.
*
* @param image Where to store the image.
* @return Frame time, or 0 on error (call GetError() to obtain the error message); the frame time
* is in 1 us increments.
*/
public long grabFrameNoTimeout(Mat image) {
return CameraServerCvJNI.grabSinkFrame(m_handle, image.nativeObj);
long rv = grabFrameNoTimeoutDirect();
if (rv <= 0) {
return rv;
}
m_tmpMat.copyTo(image);
return rv;
}
/**
* Get the direct backing mat for this sink.
*
* <p>This mat can be invalidated any time any of the grab* methods are called, or when the CvSink
* is closed.
*
* @return The backing mat.
*/
public Mat getDirectMat() {
return m_tmpMat;
}
/**
* Wait for the next frame and store the image. Times out (returning 0) after 0.225 seconds. The
* provided image will have the pixelFormat this class was constructed with. Use getDirectMat() to
* grab the image.
*
* @return Frame time, or 0 on error (call GetError() to obtain the error message)
*/
public long grabFrameDirect() {
return grabFrameDirect(0.225);
}
/**
* Wait for the next frame and store the image. Times out (returning 0) after timeout seconds. The
* provided image will have the pixelFormat this class was constructed with. Use getDirectMat() to
* grab the image.
*
* @param timeout Retrieval timeout in seconds.
* @return Frame time, or 0 on error (call GetError() to obtain the error message); the frame time
* is in 1 us increments.
*/
@SuppressWarnings("PMD.CompareObjectsWithEquals")
public long grabFrameDirect(double timeout) {
m_frame.setInfo(0, 0, 0, m_pixelFormat);
long rv =
CameraServerJNI.grabRawSinkFrameTimeout(m_handle, m_frame, m_frame.getNativeObj(), timeout);
if (rv <= 0) {
return rv;
}
if (m_frame.getData() != m_origByteBuffer
|| m_width != m_frame.getWidth()
|| m_height != m_frame.getHeight()
|| m_pixelFormat != m_frame.getPixelFormat()) {
m_origByteBuffer = m_frame.getData();
m_height = m_frame.getHeight();
m_width = m_frame.getWidth();
m_pixelFormat = m_frame.getPixelFormat();
if (m_frame.getStride() == 0) {
m_tmpMat =
new Mat(
m_frame.getHeight(),
m_frame.getWidth(),
getCVFormat(m_pixelFormat),
m_origByteBuffer);
} else {
m_tmpMat =
new Mat(
m_frame.getHeight(),
m_frame.getWidth(),
getCVFormat(m_pixelFormat),
m_origByteBuffer,
m_frame.getStride());
}
}
return rv;
}
/**
* Wait for the next frame and store the image. May block forever. The provided image will have
* the pixelFormat this class was constructed with. Use getDirectMat() to grab the image.
*
* @return Frame time, or 0 on error (call GetError() to obtain the error message); the frame time
* is in 1 us increments.
*/
@SuppressWarnings("PMD.CompareObjectsWithEquals")
public long grabFrameNoTimeoutDirect() {
m_frame.setInfo(0, 0, 0, m_pixelFormat);
long rv = CameraServerJNI.grabRawSinkFrame(m_handle, m_frame, m_frame.getNativeObj());
if (rv <= 0) {
return rv;
}
if (m_frame.getData() != m_origByteBuffer
|| m_width != m_frame.getWidth()
|| m_height != m_frame.getHeight()
|| m_pixelFormat != m_frame.getPixelFormat()) {
m_origByteBuffer = m_frame.getData();
m_height = m_frame.getHeight();
m_width = m_frame.getWidth();
m_pixelFormat = m_frame.getPixelFormat();
if (m_frame.getStride() == 0) {
m_tmpMat =
new Mat(
m_frame.getHeight(),
m_frame.getWidth(),
getCVFormat(m_pixelFormat),
m_origByteBuffer);
} else {
m_tmpMat =
new Mat(
m_frame.getHeight(),
m_frame.getWidth(),
getCVFormat(m_pixelFormat),
m_origByteBuffer,
m_frame.getStride());
}
}
return rv;
}
}

View File

@@ -20,8 +20,9 @@ public class CvSource extends ImageSource {
*/
public CvSource(String name, VideoMode mode) {
super(
CameraServerCvJNI.createCvSource(
name, mode.pixelFormat.getValue(), mode.width, mode.height, mode.fps));
CameraServerJNI.createRawSource(
name, true, mode.pixelFormat.getValue(), mode.width, mode.height, mode.fps));
OpenCvLoader.forceStaticLoad();
}
/**
@@ -34,7 +35,8 @@ public class CvSource extends ImageSource {
* @param fps fps
*/
public CvSource(String name, PixelFormat pixelFormat, int width, int height, int fps) {
super(CameraServerCvJNI.createCvSource(name, pixelFormat.getValue(), width, height, fps));
super(CameraServerJNI.createRawSource(name, true, pixelFormat.getValue(), width, height, fps));
OpenCvLoader.forceStaticLoad();
}
/**
@@ -47,6 +49,43 @@ public class CvSource extends ImageSource {
* @param image OpenCV image
*/
public void putFrame(Mat image) {
CameraServerCvJNI.putSourceFrame(m_handle, image.nativeObj);
// We only support 8-bit images, convert if necessary
boolean cleanupRequired = false;
Mat finalImage;
if (image.depth() == 0) {
finalImage = image;
} else {
finalImage = new Mat();
image.convertTo(finalImage, 0);
cleanupRequired = true;
}
try {
int channels = finalImage.channels();
PixelFormat format;
if (channels == 1) {
format = PixelFormat.kGray;
} else if (channels == 3) {
format = PixelFormat.kBGR;
} else {
throw new VideoException("Unsupported image type");
}
// TODO old code supported BGRA, but the only way I can support that is slow.
// Update cscore to support BGRA for raw frames
CameraServerJNI.putRawSourceFrameData(
m_handle,
finalImage.dataAddr(),
(int) finalImage.total() * channels,
finalImage.width(),
finalImage.height(),
image.width(),
format.getValue());
} finally {
if (cleanupRequired) {
finalImage.release();
}
}
}
}

View File

@@ -9,11 +9,13 @@ import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.opencv.core.Core;
/** CameraServer CV JNI. */
public class CameraServerCvJNI {
static boolean libraryLoaded = false;
/** OpenCV Native Loader. */
public final class OpenCvLoader {
@SuppressWarnings("PMD.MutableStaticState")
static boolean libraryLoaded;
static RuntimeLoader<Core> loader = null;
@SuppressWarnings("PMD.MutableStaticState")
static RuntimeLoader<Core> loader;
/** Sets whether JNI should be loaded in the static block. */
public static class Helper {
@@ -45,7 +47,6 @@ public class CameraServerCvJNI {
String opencvName = Core.NATIVE_LIBRARY_NAME;
if (Helper.getExtractOnStaticLoad()) {
try {
CameraServerJNI.forceLoad();
loader =
new RuntimeLoader<>(opencvName, RuntimeLoader.getDefaultExtractionRoot(), Core.class);
loader.loadLibraryHashed();
@@ -57,6 +58,15 @@ public class CameraServerCvJNI {
}
}
/**
* Forces a static load.
*
* @return a garbage value
*/
public static int forceStaticLoad() {
return libraryLoaded ? 1 : 0;
}
/**
* Force load the library.
*
@@ -66,7 +76,6 @@ public class CameraServerCvJNI {
if (libraryLoaded) {
return;
}
CameraServerJNI.forceLoad();
loader =
new RuntimeLoader<>(
Core.NATIVE_LIBRARY_NAME, RuntimeLoader.getDefaultExtractionRoot(), Core.class);
@@ -74,64 +83,6 @@ public class CameraServerCvJNI {
libraryLoaded = true;
}
/**
* Creates a CV source.
*
* @param name Name.
* @param pixelFormat OpenCV pixel format.
* @param width Image width.
* @param height Image height.
* @param fps Frames per second.
* @return CV source.
*/
public static native int createCvSource(
String name, int pixelFormat, int width, int height, int fps);
/**
* Put source frame.
*
* @param source Source handle.
* @param imageNativeObj Image native object handle.
*/
public static native void putSourceFrame(int source, long imageNativeObj);
/**
* Creates a CV sink.
*
* @param name Name.
* @param pixelFormat OpenCV pixel format.
* @return CV sink handle.
*/
public static native int createCvSink(String name, int pixelFormat);
// /**
// * Creates a CV sink callback.
// *
// * @param name Name.
// * @param processFrame Process frame callback.
// */
// public static native int createCvSinkCallback(String name,
// void (*processFrame)(long time));
/**
* Returns sink frame handle.
*
* @param sink Sink handle.
* @param imageNativeObj Image native object handle.
* @return Sink frame handle.
*/
public static native long grabSinkFrame(int sink, long imageNativeObj);
/**
* Returns sink frame timeout in microseconds.
*
* @param sink Sink handle.
* @param imageNativeObj Image native object handle.
* @param timeout Timeout in seconds.
* @return Sink frame timeout in microseconds.
*/
public static native long grabSinkFrameTimeout(int sink, long imageNativeObj, double timeout);
/** Utility class. */
private CameraServerCvJNI() {}
private OpenCvLoader() {}
}

View File

@@ -48,6 +48,8 @@ public class VideoSink implements AutoCloseable {
return Kind.kMjpeg;
case 4:
return Kind.kCv;
case 8:
return Kind.kRaw;
default:
return Kind.kUnknown;
}

View File

@@ -86,6 +86,8 @@ public class VideoSource implements AutoCloseable {
return Kind.kHttp;
case 4:
return Kind.kCv;
case 8:
return Kind.kRaw;
default:
return Kind.kUnknown;
}

View File

@@ -22,7 +22,7 @@ public class RawSink extends ImageSink {
* @param name Source name (arbitrary unique identifier)
*/
public RawSink(String name) {
super(CameraServerJNI.createRawSink(name));
super(CameraServerJNI.createRawSink(name, false));
}
/**

View File

@@ -26,7 +26,7 @@ public class RawSource extends ImageSource {
public RawSource(String name, VideoMode mode) {
super(
CameraServerJNI.createRawSource(
name, mode.pixelFormat.getValue(), mode.width, mode.height, mode.fps));
name, false, mode.pixelFormat.getValue(), mode.width, mode.height, mode.fps));
}
/**
@@ -39,7 +39,7 @@ public class RawSource extends ImageSource {
* @param fps fps
*/
public RawSource(String name, PixelFormat pixelFormat, int width, int height, int fps) {
super(CameraServerJNI.createRawSource(name, pixelFormat.getValue(), width, height, fps));
super(CameraServerJNI.createRawSource(name, false, pixelFormat.getValue(), width, height, fps));
}
/**

View File

@@ -104,3 +104,140 @@ void ConfigurableSourceImpl::SetEnumPropertyChoices(
prop->name, property, CS_PROP_ENUM,
prop->value, {});
}
namespace cs {
static constexpr unsigned SourceMask = CS_SOURCE_CV | CS_SOURCE_RAW;
void NotifySourceError(CS_Source source, std::string_view msg,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || (data->kind & SourceMask) == 0) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<ConfigurableSourceImpl&>(*data->source).NotifyError(msg);
}
void SetSourceConnected(CS_Source source, bool connected, CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || (data->kind & SourceMask) == 0) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<ConfigurableSourceImpl&>(*data->source).SetConnected(connected);
}
void SetSourceDescription(CS_Source source, std::string_view description,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || (data->kind & SourceMask) == 0) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<ConfigurableSourceImpl&>(*data->source)
.SetDescription(description);
}
CS_Property CreateSourceProperty(CS_Source source, std::string_view name,
CS_PropertyKind kind, int minimum, int maximum,
int step, int defaultValue, int value,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || (data->kind & SourceMask) == 0) {
*status = CS_INVALID_HANDLE;
return -1;
}
int property = static_cast<ConfigurableSourceImpl&>(*data->source)
.CreateProperty(name, kind, minimum, maximum, step,
defaultValue, value);
return Handle{source, property, Handle::kProperty};
}
CS_Property CreateSourcePropertyCallback(
CS_Source source, std::string_view name, CS_PropertyKind kind, int minimum,
int maximum, int step, int defaultValue, int value,
std::function<void(CS_Property property)> onChange, CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || (data->kind & SourceMask) == 0) {
*status = CS_INVALID_HANDLE;
return -1;
}
int property = static_cast<ConfigurableSourceImpl&>(*data->source)
.CreateProperty(name, kind, minimum, maximum, step,
defaultValue, value, onChange);
return Handle{source, property, Handle::kProperty};
}
void SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
std::span<const std::string> choices,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || (data->kind & SourceMask) == 0) {
*status = CS_INVALID_HANDLE;
return;
}
// Get property index; also validate the source owns this property
Handle handle{property};
int i = handle.GetParentIndex();
if (i < 0) {
*status = CS_INVALID_HANDLE;
return;
}
auto data2 = Instance::GetInstance().GetSource(Handle{i, Handle::kSource});
if (!data2 || data->source.get() != data2->source.get()) {
*status = CS_INVALID_HANDLE;
return;
}
int propertyIndex = handle.GetIndex();
static_cast<ConfigurableSourceImpl&>(*data->source)
.SetEnumPropertyChoices(propertyIndex, choices, status);
}
} // namespace cs
extern "C" {
void CS_NotifySourceError(CS_Source source, const char* msg,
CS_Status* status) {
return cs::NotifySourceError(source, msg, status);
}
void CS_SetSourceConnected(CS_Source source, CS_Bool connected,
CS_Status* status) {
return cs::SetSourceConnected(source, connected, status);
}
void CS_SetSourceDescription(CS_Source source, const char* description,
CS_Status* status) {
return cs::SetSourceDescription(source, description, status);
}
CS_Property CS_CreateSourceProperty(CS_Source source, const char* name,
enum CS_PropertyKind kind, int minimum,
int maximum, int step, int defaultValue,
int value, CS_Status* status) {
return cs::CreateSourceProperty(source, name, kind, minimum, maximum, step,
defaultValue, value, status);
}
CS_Property CS_CreateSourcePropertyCallback(
CS_Source source, const char* name, enum CS_PropertyKind kind, int minimum,
int maximum, int step, int defaultValue, int value, void* data,
void (*onChange)(void* data, CS_Property property), CS_Status* status) {
return cs::CreateSourcePropertyCallback(
source, name, kind, minimum, maximum, step, defaultValue, value,
[=](CS_Property property) { onChange(data, property); }, status);
}
void CS_SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
const char** choices, int count,
CS_Status* status) {
wpi::SmallVector<std::string, 8> vec;
vec.reserve(count);
for (int i = 0; i < count; ++i) {
vec.push_back(choices[i]);
}
return cs::SetSourceEnumPropertyChoices(source, property, vec, status);
}
} // extern "C"

View File

@@ -33,7 +33,7 @@ class ConfigurableSourceImpl : public SourceImpl {
void NumSinksChanged() override;
void NumSinksEnabledChanged() override;
// OpenCV-specific functions
// Frame based specific functions
void NotifyError(std::string_view msg);
int CreateProperty(std::string_view name, CS_PropertyKind kind, int minimum,
int maximum, int step, int defaultValue, int value);

View File

@@ -1,270 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "CvSinkImpl.h"
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <wpi/SmallString.h>
#include "Handle.h"
#include "Instance.h"
#include "Log.h"
#include "Notifier.h"
#include "c_util.h"
#include "cscore_cpp.h"
using namespace cs;
CvSinkImpl::CvSinkImpl(std::string_view name, wpi::Logger& logger,
Notifier& notifier, Telemetry& telemetry,
VideoMode::PixelFormat pixelFormat)
: SinkImpl{name, logger, notifier, telemetry}, m_pixelFormat{pixelFormat} {
m_active = true;
// m_thread = std::thread(&CvSinkImpl::ThreadMain, this);
}
CvSinkImpl::CvSinkImpl(std::string_view name, wpi::Logger& logger,
Notifier& notifier, Telemetry& telemetry,
VideoMode::PixelFormat pixelFormat,
std::function<void(uint64_t time)> processFrame)
: SinkImpl{name, logger, notifier, telemetry}, m_pixelFormat{pixelFormat} {}
CvSinkImpl::~CvSinkImpl() {
Stop();
}
void CvSinkImpl::Stop() {
m_active = false;
// wake up any waiters by forcing an empty frame to be sent
if (auto source = GetSource()) {
source->Wakeup();
}
// join thread
if (m_thread.joinable()) {
m_thread.join();
}
}
uint64_t CvSinkImpl::GrabFrame(cv::Mat& image) {
SetEnabled(true);
auto source = GetSource();
if (!source) {
// Source disconnected; sleep for one second
std::this_thread::sleep_for(std::chrono::seconds(1));
return 0;
}
auto frame = source->GetNextFrame(); // blocks
if (!frame) {
// Bad frame; sleep for 20 ms so we don't consume all processor time.
std::this_thread::sleep_for(std::chrono::milliseconds(20));
return 0; // signal error
}
if (!frame.GetCv(image, m_pixelFormat)) {
// Shouldn't happen, but just in case...
std::this_thread::sleep_for(std::chrono::milliseconds(20));
return 0;
}
return frame.GetTime();
}
uint64_t CvSinkImpl::GrabFrame(cv::Mat& image, double timeout) {
SetEnabled(true);
auto source = GetSource();
if (!source) {
// Source disconnected; sleep for one second
std::this_thread::sleep_for(std::chrono::seconds(1));
return 0;
}
auto frame = source->GetNextFrame(timeout); // blocks
if (!frame) {
// Bad frame; sleep for 20 ms so we don't consume all processor time.
std::this_thread::sleep_for(std::chrono::milliseconds(20));
return 0; // signal error
}
if (!frame.GetCv(image, m_pixelFormat)) {
// Shouldn't happen, but just in case...
std::this_thread::sleep_for(std::chrono::milliseconds(20));
return 0;
}
return frame.GetTime();
}
// Send HTTP response and a stream of JPG-frames
void CvSinkImpl::ThreadMain() {
Enable();
while (m_active) {
auto source = GetSource();
if (!source) {
// Source disconnected; sleep for one second
std::this_thread::sleep_for(std::chrono::seconds(1));
continue;
}
SDEBUG4("waiting for frame");
Frame frame = source->GetNextFrame(); // blocks
if (!m_active) {
break;
}
if (!frame) {
// Bad frame; sleep for 10 ms so we don't consume all processor time.
std::this_thread::sleep_for(std::chrono::milliseconds(10));
continue;
}
// TODO m_processFrame();
}
Disable();
}
namespace cs {
CS_Sink CreateCvSink(std::string_view name, VideoMode::PixelFormat pixelFormat,
CS_Status* status) {
auto& inst = Instance::GetInstance();
return inst.CreateSink(
CS_SINK_CV, std::make_shared<CvSinkImpl>(name, inst.logger, inst.notifier,
inst.telemetry, pixelFormat));
}
CS_Sink CreateCvSinkCallback(std::string_view name,
VideoMode::PixelFormat pixelFormat,
std::function<void(uint64_t time)> processFrame,
CS_Status* status) {
auto& inst = Instance::GetInstance();
return inst.CreateSink(
CS_SINK_CV,
std::make_shared<CvSinkImpl>(name, inst.logger, inst.notifier,
inst.telemetry, pixelFormat, processFrame));
}
static constexpr unsigned SinkMask = CS_SINK_CV | CS_SINK_RAW;
void SetSinkDescription(CS_Sink sink, std::string_view description,
CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data || (data->kind & SinkMask) == 0) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<CvSinkImpl&>(*data->sink).SetDescription(description);
}
uint64_t GrabSinkFrame(CS_Sink sink, cv::Mat& image, CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data || data->kind != CS_SINK_CV) {
*status = CS_INVALID_HANDLE;
return 0;
}
return static_cast<CvSinkImpl&>(*data->sink).GrabFrame(image);
}
uint64_t GrabSinkFrameTimeout(CS_Sink sink, cv::Mat& image, double timeout,
CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data || data->kind != CS_SINK_CV) {
*status = CS_INVALID_HANDLE;
return 0;
}
return static_cast<CvSinkImpl&>(*data->sink).GrabFrame(image, timeout);
}
std::string GetSinkError(CS_Sink sink, CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data || (data->kind & SinkMask) == 0) {
*status = CS_INVALID_HANDLE;
return std::string{};
}
return static_cast<CvSinkImpl&>(*data->sink).GetError();
}
std::string_view GetSinkError(CS_Sink sink, wpi::SmallVectorImpl<char>& buf,
CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data || (data->kind & SinkMask) == 0) {
*status = CS_INVALID_HANDLE;
return {};
}
return static_cast<CvSinkImpl&>(*data->sink).GetError(buf);
}
void SetSinkEnabled(CS_Sink sink, bool enabled, CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data || (data->kind & SinkMask) == 0) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<CvSinkImpl&>(*data->sink).SetEnabled(enabled);
}
} // namespace cs
extern "C" {
CS_Sink CS_CreateCvSink(const char* name, enum WPI_PixelFormat pixelFormat,
CS_Status* status) {
return cs::CreateCvSink(
name, static_cast<VideoMode::PixelFormat>(pixelFormat), status);
}
CS_Sink CS_CreateCvSinkCallback(const char* name,
enum WPI_PixelFormat pixelFormat, void* data,
void (*processFrame)(void* data, uint64_t time),
CS_Status* status) {
return cs::CreateCvSinkCallback(
name, static_cast<VideoMode::PixelFormat>(pixelFormat),
[=](uint64_t time) { processFrame(data, time); }, status);
}
void CS_SetSinkDescription(CS_Sink sink, const char* description,
CS_Status* status) {
return cs::SetSinkDescription(sink, description, status);
}
#if CV_VERSION_MAJOR < 4
uint64_t CS_GrabSinkFrame(CS_Sink sink, struct CvMat* image,
CS_Status* status) {
auto mat = cv::cvarrToMat(image);
return cs::GrabSinkFrame(sink, mat, status);
}
uint64_t CS_GrabSinkFrameTimeout(CS_Sink sink, struct CvMat* image,
double timeout, CS_Status* status) {
auto mat = cv::cvarrToMat(image);
return cs::GrabSinkFrameTimeout(sink, mat, timeout, status);
}
#endif // CV_VERSION_MAJOR < 4
uint64_t CS_GrabSinkFrameCpp(CS_Sink sink, cv::Mat* image, CS_Status* status) {
return cs::GrabSinkFrame(sink, *image, status);
}
uint64_t CS_GrabSinkFrameTimeoutCpp(CS_Sink sink, cv::Mat* image,
double timeout, CS_Status* status) {
return cs::GrabSinkFrameTimeout(sink, *image, timeout, status);
}
char* CS_GetSinkError(CS_Sink sink, CS_Status* status) {
wpi::SmallString<128> buf;
auto str = cs::GetSinkError(sink, buf, status);
if (*status != 0) {
return nullptr;
}
return cs::ConvertToC(str);
}
void CS_SetSinkEnabled(CS_Sink sink, CS_Bool enabled, CS_Status* status) {
return cs::SetSinkEnabled(sink, enabled, status);
}
} // extern "C"

View File

@@ -1,50 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef CSCORE_CVSINKIMPL_H_
#define CSCORE_CVSINKIMPL_H_
#include <stdint.h>
#include <atomic>
#include <functional>
#include <string_view>
#include <thread>
#include <opencv2/core/core.hpp>
#include <wpi/condition_variable.h>
#include "Frame.h"
#include "SinkImpl.h"
namespace cs {
class SourceImpl;
class CvSinkImpl : public SinkImpl {
public:
CvSinkImpl(std::string_view name, wpi::Logger& logger, Notifier& notifier,
Telemetry& telemetry, VideoMode::PixelFormat pixelFormat);
CvSinkImpl(std::string_view name, wpi::Logger& logger, Notifier& notifier,
Telemetry& telemetry, VideoMode::PixelFormat pixelFormat,
std::function<void(uint64_t time)> processFrame);
~CvSinkImpl() override;
void Stop();
uint64_t GrabFrame(cv::Mat& image);
uint64_t GrabFrame(cv::Mat& image, double timeout);
private:
void ThreadMain();
std::atomic_bool m_active; // set to false to terminate threads
std::thread m_thread;
std::function<void(uint64_t time)> m_processFrame;
VideoMode::PixelFormat m_pixelFormat;
};
} // namespace cs
#endif // CSCORE_CVSINKIMPL_H_

View File

@@ -1,232 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "CvSourceImpl.h"
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <wpi/timestamp.h>
#include "Handle.h"
#include "Instance.h"
#include "Log.h"
#include "Notifier.h"
#include "c_util.h"
#include "cscore_cpp.h"
using namespace cs;
CvSourceImpl::CvSourceImpl(std::string_view name, wpi::Logger& logger,
Notifier& notifier, Telemetry& telemetry,
const VideoMode& mode)
: ConfigurableSourceImpl{name, logger, notifier, telemetry, mode} {}
CvSourceImpl::~CvSourceImpl() = default;
void CvSourceImpl::PutFrame(cv::Mat& image) {
// We only support 8-bit images; convert if necessary.
cv::Mat finalImage;
if (image.depth() == CV_8U) {
finalImage = image;
} else {
image.convertTo(finalImage, CV_8U);
}
std::unique_ptr<Image> dest;
switch (image.channels()) {
case 1:
dest =
AllocImage(VideoMode::kGray, image.cols, image.rows, image.total());
finalImage.copyTo(dest->AsMat());
break;
case 3:
dest = AllocImage(VideoMode::kBGR, image.cols, image.rows,
image.total() * 3);
finalImage.copyTo(dest->AsMat());
break;
case 4:
dest = AllocImage(VideoMode::kBGR, image.cols, image.rows,
image.total() * 3);
cv::cvtColor(finalImage, dest->AsMat(), cv::COLOR_BGRA2BGR);
break;
default:
SERROR("PutFrame: {}-channel images not supported", image.channels());
return;
}
SourceImpl::PutFrame(std::move(dest), wpi::Now());
}
namespace cs {
CS_Source CreateCvSource(std::string_view name, const VideoMode& mode,
CS_Status* status) {
auto& inst = Instance::GetInstance();
return inst.CreateSource(CS_SOURCE_CV, std::make_shared<CvSourceImpl>(
name, inst.logger, inst.notifier,
inst.telemetry, mode));
}
void PutSourceFrame(CS_Source source, cv::Mat& image, CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || data->kind != CS_SOURCE_CV) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<CvSourceImpl&>(*data->source).PutFrame(image);
}
static constexpr unsigned SourceMask = CS_SINK_CV | CS_SINK_RAW;
void NotifySourceError(CS_Source source, std::string_view msg,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || (data->kind & SourceMask) == 0) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<CvSourceImpl&>(*data->source).NotifyError(msg);
}
void SetSourceConnected(CS_Source source, bool connected, CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || (data->kind & SourceMask) == 0) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<CvSourceImpl&>(*data->source).SetConnected(connected);
}
void SetSourceDescription(CS_Source source, std::string_view description,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || (data->kind & SourceMask) == 0) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<CvSourceImpl&>(*data->source).SetDescription(description);
}
CS_Property CreateSourceProperty(CS_Source source, std::string_view name,
CS_PropertyKind kind, int minimum, int maximum,
int step, int defaultValue, int value,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || (data->kind & SourceMask) == 0) {
*status = CS_INVALID_HANDLE;
return -1;
}
int property = static_cast<CvSourceImpl&>(*data->source)
.CreateProperty(name, kind, minimum, maximum, step,
defaultValue, value);
return Handle{source, property, Handle::kProperty};
}
CS_Property CreateSourcePropertyCallback(
CS_Source source, std::string_view name, CS_PropertyKind kind, int minimum,
int maximum, int step, int defaultValue, int value,
std::function<void(CS_Property property)> onChange, CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || (data->kind & SourceMask) == 0) {
*status = CS_INVALID_HANDLE;
return -1;
}
int property = static_cast<CvSourceImpl&>(*data->source)
.CreateProperty(name, kind, minimum, maximum, step,
defaultValue, value, onChange);
return Handle{source, property, Handle::kProperty};
}
void SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
std::span<const std::string> choices,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || (data->kind & SourceMask) == 0) {
*status = CS_INVALID_HANDLE;
return;
}
// Get property index; also validate the source owns this property
Handle handle{property};
int i = handle.GetParentIndex();
if (i < 0) {
*status = CS_INVALID_HANDLE;
return;
}
auto data2 = Instance::GetInstance().GetSource(Handle{i, Handle::kSource});
if (!data2 || data->source.get() != data2->source.get()) {
*status = CS_INVALID_HANDLE;
return;
}
int propertyIndex = handle.GetIndex();
static_cast<CvSourceImpl&>(*data->source)
.SetEnumPropertyChoices(propertyIndex, choices, status);
}
} // namespace cs
extern "C" {
CS_Source CS_CreateCvSource(const char* name, const CS_VideoMode* mode,
CS_Status* status) {
return cs::CreateCvSource(name, static_cast<const cs::VideoMode&>(*mode),
status);
}
#if CV_VERSION_MAJOR < 4
void CS_PutSourceFrame(CS_Source source, struct CvMat* image,
CS_Status* status) {
auto mat = cv::cvarrToMat(image);
return cs::PutSourceFrame(source, mat, status);
}
#endif // CV_VERSION_MAJOR < 4
void CS_PutSourceFrameCpp(CS_Source source, cv::Mat* image, CS_Status* status) {
return cs::PutSourceFrame(source, *image, status);
}
void CS_NotifySourceError(CS_Source source, const char* msg,
CS_Status* status) {
return cs::NotifySourceError(source, msg, status);
}
void CS_SetSourceConnected(CS_Source source, CS_Bool connected,
CS_Status* status) {
return cs::SetSourceConnected(source, connected, status);
}
void CS_SetSourceDescription(CS_Source source, const char* description,
CS_Status* status) {
return cs::SetSourceDescription(source, description, status);
}
CS_Property CS_CreateSourceProperty(CS_Source source, const char* name,
enum CS_PropertyKind kind, int minimum,
int maximum, int step, int defaultValue,
int value, CS_Status* status) {
return cs::CreateSourceProperty(source, name, kind, minimum, maximum, step,
defaultValue, value, status);
}
CS_Property CS_CreateSourcePropertyCallback(
CS_Source source, const char* name, enum CS_PropertyKind kind, int minimum,
int maximum, int step, int defaultValue, int value, void* data,
void (*onChange)(void* data, CS_Property property), CS_Status* status) {
return cs::CreateSourcePropertyCallback(
source, name, kind, minimum, maximum, step, defaultValue, value,
[=](CS_Property property) { onChange(data, property); }, status);
}
void CS_SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
const char** choices, int count,
CS_Status* status) {
wpi::SmallVector<std::string, 8> vec;
vec.reserve(count);
for (int i = 0; i < count; ++i) {
vec.push_back(choices[i]);
}
return cs::SetSourceEnumPropertyChoices(source, property, vec, status);
}
} // extern "C"

View File

@@ -1,37 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef CSCORE_CVSOURCEIMPL_H_
#define CSCORE_CVSOURCEIMPL_H_
#include <atomic>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include <opencv2/core/core.hpp>
#include "ConfigurableSourceImpl.h"
#include "SourceImpl.h"
namespace cs {
class CvSourceImpl : public ConfigurableSourceImpl {
public:
CvSourceImpl(std::string_view name, wpi::Logger& logger, Notifier& notifier,
Telemetry& telemetry, const VideoMode& mode);
~CvSourceImpl() override;
// OpenCV-specific functions
void PutFrame(cv::Mat& image);
private:
std::atomic_bool m_connected{true};
};
} // namespace cs
#endif // CSCORE_CVSOURCEIMPL_H_

View File

@@ -143,25 +143,28 @@ void RawSinkImpl::ThreadMain() {
}
namespace cs {
CS_Sink CreateRawSink(std::string_view name, CS_Status* status) {
static constexpr unsigned SinkMask = CS_SINK_CV | CS_SINK_RAW;
CS_Sink CreateRawSink(std::string_view name, bool isCv, CS_Status* status) {
auto& inst = Instance::GetInstance();
return inst.CreateSink(CS_SINK_RAW,
return inst.CreateSink(isCv ? CS_SINK_CV : CS_SINK_RAW,
std::make_shared<RawSinkImpl>(
name, inst.logger, inst.notifier, inst.telemetry));
}
CS_Sink CreateRawSinkCallback(std::string_view name,
CS_Sink CreateRawSinkCallback(std::string_view name, bool isCv,
std::function<void(uint64_t time)> processFrame,
CS_Status* status) {
auto& inst = Instance::GetInstance();
return inst.CreateSink(CS_SINK_RAW, std::make_shared<RawSinkImpl>(
name, inst.logger, inst.notifier,
inst.telemetry, processFrame));
return inst.CreateSink(
isCv ? CS_SINK_CV : CS_SINK_RAW,
std::make_shared<RawSinkImpl>(name, inst.logger, inst.notifier,
inst.telemetry, processFrame));
}
uint64_t GrabSinkFrame(CS_Sink sink, WPI_RawFrame& image, CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data || data->kind != CS_SINK_RAW) {
if (!data || (data->kind & SinkMask) == 0) {
*status = CS_INVALID_HANDLE;
return 0;
}
@@ -171,25 +174,26 @@ uint64_t GrabSinkFrame(CS_Sink sink, WPI_RawFrame& image, CS_Status* status) {
uint64_t GrabSinkFrameTimeout(CS_Sink sink, WPI_RawFrame& image, double timeout,
CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data || data->kind != CS_SINK_RAW) {
if (!data || (data->kind & SinkMask) == 0) {
*status = CS_INVALID_HANDLE;
return 0;
}
return static_cast<RawSinkImpl&>(*data->sink).GrabFrame(image, timeout);
}
} // namespace cs
extern "C" {
CS_Sink CS_CreateRawSink(const char* name, CS_Status* status) {
return cs::CreateRawSink(name, status);
CS_Sink CS_CreateRawSink(const char* name, CS_Bool isCv, CS_Status* status) {
return cs::CreateRawSink(name, isCv, status);
}
CS_Sink CS_CreateRawSinkCallback(const char* name, void* data,
CS_Sink CS_CreateRawSinkCallback(const char* name, CS_Bool isCv, void* data,
void (*processFrame)(void* data,
uint64_t time),
CS_Status* status) {
return cs::CreateRawSinkCallback(
name, [=](uint64_t time) { processFrame(data, time); }, status);
name, isCv, [=](uint64_t time) { processFrame(data, time); }, status);
}
uint64_t CS_GrabRawSinkFrame(CS_Sink sink, struct WPI_RawFrame* image,
@@ -201,4 +205,5 @@ uint64_t CS_GrabRawSinkFrameTimeout(CS_Sink sink, struct WPI_RawFrame* image,
double timeout, CS_Status* status) {
return cs::GrabSinkFrameTimeout(sink, *image, timeout, status);
}
} // extern "C"

View File

@@ -50,34 +50,39 @@ void RawSourceImpl::PutFrame(const WPI_RawFrame& image) {
}
namespace cs {
CS_Source CreateRawSource(std::string_view name, const VideoMode& mode,
CS_Status* status) {
static constexpr unsigned SourceMask = CS_SOURCE_CV | CS_SOURCE_RAW;
CS_Source CreateRawSource(std::string_view name, bool isCv,
const VideoMode& mode, CS_Status* status) {
auto& inst = Instance::GetInstance();
return inst.CreateSource(CS_SOURCE_RAW, std::make_shared<RawSourceImpl>(
name, inst.logger, inst.notifier,
inst.telemetry, mode));
return inst.CreateSource(
isCv ? CS_SOURCE_CV : CS_SOURCE_RAW,
std::make_shared<RawSourceImpl>(name, inst.logger, inst.notifier,
inst.telemetry, mode));
}
void PutSourceFrame(CS_Source source, const WPI_RawFrame& image,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || data->kind != CS_SOURCE_RAW) {
if (!data || (data->kind & SourceMask) == 0) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<RawSourceImpl&>(*data->source).PutFrame(image);
}
} // namespace cs
extern "C" {
CS_Source CS_CreateRawSource(const char* name, const CS_VideoMode* mode,
CS_Status* status) {
return cs::CreateRawSource(name, static_cast<const cs::VideoMode&>(*mode),
status);
CS_Source CS_CreateRawSource(const char* name, CS_Bool isCv,
const CS_VideoMode* mode, CS_Status* status) {
return cs::CreateRawSource(name, isCv,
static_cast<const cs::VideoMode&>(*mode), status);
}
void CS_PutRawSourceFrame(CS_Source source, const struct WPI_RawFrame* image,
CS_Status* status) {
return cs::PutSourceFrame(source, *image, status);
}
} // extern "C"

View File

@@ -4,11 +4,13 @@
#include "SinkImpl.h"
#include <wpi/SmallString.h>
#include <wpi/json.h>
#include "Instance.h"
#include "Notifier.h"
#include "SourceImpl.h"
#include "c_util.h"
using namespace cs;
@@ -195,3 +197,67 @@ void SinkImpl::UpdatePropertyValue(int property, bool setString, int value,
}
void SinkImpl::SetSourceImpl(std::shared_ptr<SourceImpl> source) {}
namespace cs {
static constexpr unsigned SinkMask = CS_SINK_CV | CS_SINK_RAW;
void SetSinkDescription(CS_Sink sink, std::string_view description,
CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data || (data->kind & SinkMask) == 0) {
*status = CS_INVALID_HANDLE;
return;
}
data->sink->SetDescription(description);
}
std::string GetSinkError(CS_Sink sink, CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data || (data->kind & SinkMask) == 0) {
*status = CS_INVALID_HANDLE;
return std::string{};
}
return data->sink->GetError();
}
std::string_view GetSinkError(CS_Sink sink, wpi::SmallVectorImpl<char>& buf,
CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data || (data->kind & SinkMask) == 0) {
*status = CS_INVALID_HANDLE;
return {};
}
return data->sink->GetError(buf);
}
void SetSinkEnabled(CS_Sink sink, bool enabled, CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data || (data->kind & SinkMask) == 0) {
*status = CS_INVALID_HANDLE;
return;
}
data->sink->SetEnabled(enabled);
}
} // namespace cs
extern "C" {
void CS_SetSinkDescription(CS_Sink sink, const char* description,
CS_Status* status) {
return cs::SetSinkDescription(sink, description, status);
}
char* CS_GetSinkError(CS_Sink sink, CS_Status* status) {
wpi::SmallString<128> buf;
auto str = cs::GetSinkError(sink, buf, status);
if (*status != 0) {
return nullptr;
}
return cs::ConvertToC(str);
}
void CS_SetSinkEnabled(CS_Sink sink, CS_Bool enabled, CS_Status* status) {
return cs::SetSinkEnabled(sink, enabled, status);
}
} // extern "C"

View File

@@ -7,7 +7,6 @@
#include <cstddef>
#include <cstdlib>
#include <opencv2/core/core.hpp>
#include <wpi/MemAlloc.h>
#include <wpi/SmallString.h>

View File

@@ -6,7 +6,6 @@
#include <span>
#include <fmt/format.h>
#include <opencv2/core/core.hpp>
#define WPI_RAWFRAME_JNI
#include <wpi/RawFrame.h>
@@ -82,32 +81,6 @@ static void ListenerOnExit() {
jvm->DetachCurrentThread();
}
/// throw java exception
static void ThrowJavaException(JNIEnv* env, const std::exception* e) {
wpi::SmallString<128> what;
jclass je = nullptr;
if (e) {
const char* exception_type = "std::exception";
if (dynamic_cast<const cv::Exception*>(e)) {
exception_type = "cv::Exception";
je = env->FindClass("org/opencv/core/CvException");
}
what = exception_type;
what += ": ";
what += e->what();
} else {
what = "unknown exception";
}
if (!je) {
je = exceptionEx;
}
env->ThrowNew(je, what.c_str());
}
extern "C" {
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
@@ -593,40 +566,15 @@ Java_edu_wpi_first_cscore_CameraServerJNI_createHttpCameraMulti
return val;
}
/*
* Class: edu_wpi_first_cscore_CameraServerCvJNI
* Method: createCvSource
* Signature: (Ljava/lang/String;IIII)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_cscore_CameraServerCvJNI_createCvSource
(JNIEnv* env, jclass, jstring name, jint pixelFormat, jint width, jint height,
jint fps)
{
if (!name) {
nullPointerEx.Throw(env, "name cannot be null");
return 0;
}
CS_Status status = 0;
auto val = cs::CreateCvSource(
JStringRef{env, name}.str(),
cs::VideoMode{static_cast<cs::VideoMode::PixelFormat>(pixelFormat),
static_cast<int>(width), static_cast<int>(height),
static_cast<int>(fps)},
&status);
CheckStatus(env, status);
return val;
}
/*
* Class: edu_wpi_first_cscore_CameraServerJNI
* Method: createRawSource
* Signature: (Ljava/lang/String;IIII)I
* Signature: (Ljava/lang/String;ZIIII)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_cscore_CameraServerJNI_createRawSource
(JNIEnv* env, jclass, jstring name, jint pixelFormat, jint width, jint height,
jint fps)
(JNIEnv* env, jclass, jstring name, jboolean isCv, jint pixelFormat,
jint width, jint height, jint fps)
{
if (!name) {
nullPointerEx.Throw(env, "name cannot be null");
@@ -634,7 +582,7 @@ Java_edu_wpi_first_cscore_CameraServerJNI_createRawSource
}
CS_Status status = 0;
auto val = cs::CreateRawSource(
JStringRef{env, name}.str(),
JStringRef{env, name}.str(), isCv,
cs::VideoMode{static_cast<cs::VideoMode::PixelFormat>(pixelFormat),
static_cast<int>(width), static_cast<int>(height),
static_cast<int>(fps)},
@@ -1202,27 +1150,6 @@ Java_edu_wpi_first_cscore_CameraServerJNI_getHttpCameraUrls
return MakeJStringArray(env, arr);
}
/*
* Class: edu_wpi_first_cscore_CameraServerCvJNI
* Method: putSourceFrame
* Signature: (IJ)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_cscore_CameraServerCvJNI_putSourceFrame
(JNIEnv* env, jclass, jint source, jlong imageNativeObj)
{
try {
cv::Mat& image = *((cv::Mat*)imageNativeObj);
CS_Status status = 0;
cs::PutSourceFrame(source, image, &status);
CheckStatus(env, status);
} catch (const std::exception& e) {
ThrowJavaException(env, &e);
} catch (...) {
ThrowJavaException(env, nullptr);
}
}
/*
* Class: edu_wpi_first_cscore_CameraServerJNI
* Method: putRawSourceFrame
@@ -1421,42 +1348,21 @@ Java_edu_wpi_first_cscore_CameraServerJNI_createMjpegServer
return val;
}
/*
* Class: edu_wpi_first_cscore_CameraServerCvJNI
* Method: createCvSink
* Signature: (Ljava/lang/String;I)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_cscore_CameraServerCvJNI_createCvSink
(JNIEnv* env, jclass, jstring name, jint pixelFormat)
{
if (!name) {
nullPointerEx.Throw(env, "name cannot be null");
return 0;
}
CS_Status status = 0;
auto val = cs::CreateCvSink(
JStringRef{env, name}.str(),
static_cast<cs::VideoMode::PixelFormat>(pixelFormat), &status);
CheckStatus(env, status);
return val;
}
/*
* Class: edu_wpi_first_cscore_CameraServerJNI
* Method: createRawSink
* Signature: (Ljava/lang/String;)I
* Signature: (Ljava/lang/String;Z)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_cscore_CameraServerJNI_createRawSink
(JNIEnv* env, jclass, jstring name)
(JNIEnv* env, jclass, jstring name, jboolean isCv)
{
if (!name) {
nullPointerEx.Throw(env, "name cannot be null");
return 0;
}
CS_Status status = 0;
auto val = cs::CreateRawSink(JStringRef{env, name}.str(), &status);
auto val = cs::CreateRawSink(JStringRef{env, name}.str(), isCv, &status);
CheckStatus(env, status);
return val;
}
@@ -1707,54 +1613,6 @@ Java_edu_wpi_first_cscore_CameraServerJNI_setSinkDescription
CheckStatus(env, status);
}
/*
* Class: edu_wpi_first_cscore_CameraServerCvJNI
* Method: grabSinkFrame
* Signature: (IJ)J
*/
JNIEXPORT jlong JNICALL
Java_edu_wpi_first_cscore_CameraServerCvJNI_grabSinkFrame
(JNIEnv* env, jclass, jint sink, jlong imageNativeObj)
{
try {
cv::Mat& image = *((cv::Mat*)imageNativeObj);
CS_Status status = 0;
auto rv = cs::GrabSinkFrame(sink, image, &status);
CheckStatus(env, status);
return rv;
} catch (const std::exception& e) {
ThrowJavaException(env, &e);
return 0;
} catch (...) {
ThrowJavaException(env, nullptr);
return 0;
}
}
/*
* Class: edu_wpi_first_cscore_CameraServerCvJNI
* Method: grabSinkFrameTimeout
* Signature: (IJD)J
*/
JNIEXPORT jlong JNICALL
Java_edu_wpi_first_cscore_CameraServerCvJNI_grabSinkFrameTimeout
(JNIEnv* env, jclass, jint sink, jlong imageNativeObj, jdouble timeout)
{
try {
cv::Mat& image = *((cv::Mat*)imageNativeObj);
CS_Status status = 0;
auto rv = cs::GrabSinkFrameTimeout(sink, image, timeout, &status);
CheckStatus(env, status);
return rv;
} catch (const std::exception& e) {
ThrowJavaException(env, &e);
return 0;
} catch (...) {
ThrowJavaException(env, nullptr);
return 0;
}
}
/*
* Class: edu_wpi_first_cscore_CameraServerJNI
* Method: grabRawSinkFrame

View File

@@ -347,7 +347,7 @@ char** CS_GetHttpCameraUrls(CS_Source source, int* count, CS_Status* status);
/** @} */
/**
* @defgroup cscore_opencv_source_cfunc OpenCV Source Functions
* @defgroup cscore_frame_source_cfunc Frame Source Functions
* @{
*/
void CS_NotifySourceError(CS_Source source, const char* msg, CS_Status* status);
@@ -409,7 +409,7 @@ int CS_GetMjpegServerPort(CS_Sink sink, CS_Status* status);
/** @} */
/**
* @defgroup cscore_opencv_sink_cfunc OpenCV Sink Functions
* @defgroup cscore_frame_sink_cfunc Frame Sink Functions
* @{
*/
void CS_SetSinkDescription(CS_Sink sink, const char* description,

View File

@@ -294,7 +294,7 @@ std::vector<std::string> GetHttpCameraUrls(CS_Source source, CS_Status* status);
/** @} */
/**
* @defgroup cscore_opencv_source_func OpenCV Source Functions
* @defgroup cscore_frame_source_func Frame Source Functions
* @{
*/
void NotifySourceError(CS_Source source, std::string_view msg,
@@ -365,7 +365,7 @@ int GetMjpegServerPort(CS_Sink sink, CS_Status* status);
/** @} */
/**
* @defgroup cscore_opencv_sink_func OpenCV Sink Functions
* @defgroup cscore_frame_sink_func Frame Sink Functions
* @{
*/
void SetSinkDescription(CS_Sink sink, std::string_view description,

View File

@@ -7,72 +7,17 @@
#include <functional>
#include "cscore_c.h"
#ifdef CSCORE_CSCORE_RAW_CV_H_
#error "Cannot include both cscore_cv.h and cscore_raw_cv.h in the same file"
#endif
#ifdef __cplusplus
#include "cscore_oo.h" // NOLINT(build/include_order)
#endif
#if CV_VERSION_MAJOR < 4
#ifdef __cplusplus
extern "C" { // NOLINT(build/include_order)
#endif
struct CvMat;
void CS_PutSourceFrame(CS_Source source, struct CvMat* image,
CS_Status* status);
uint64_t CS_GrabSinkFrame(CS_Sink sink, struct CvMat* image, CS_Status* status);
uint64_t CS_GrabSinkFrameTimeout(CS_Sink sink, struct CvMat* image,
double timeout, CS_Status* status);
#ifdef __cplusplus
} // extern "C"
#endif
#endif // CV_VERSION_MAJOR < 4
#ifdef __cplusplus
#include <opencv2/core/mat.hpp>
#include "cscore_oo.h"
namespace cv {
class Mat;
} // namespace cv
#include "cscore_raw.h"
namespace cs {
/**
* @defgroup cscore_cpp_opencv_special cscore C functions taking a cv::Mat*
*
* These are needed for specific interop implementations.
* @{
*/
extern "C" {
uint64_t CS_GrabSinkFrameCpp(CS_Sink sink, cv::Mat* image, CS_Status* status);
uint64_t CS_GrabSinkFrameTimeoutCpp(CS_Sink sink, cv::Mat* image,
double timeout, CS_Status* status);
void CS_PutSourceFrameCpp(CS_Source source, cv::Mat* image, CS_Status* status);
} // extern "C"
/** @} */
void PutSourceFrame(CS_Source source, cv::Mat& image, CS_Status* status);
uint64_t GrabSinkFrame(CS_Sink sink, cv::Mat& image, CS_Status* status);
uint64_t GrabSinkFrameTimeout(CS_Sink sink, cv::Mat& image, double timeout,
CS_Status* status);
/**
* A source for user code to provide OpenCV images as video frames.
* These sources require the WPILib OpenCV builds.
* For an alternate OpenCV, include "cscore_raw_cv.h" instead, and
* include your Mat header before that header.
*
* This is not dependent on any opencv binary ABI, and can be used
* with versions of most versions of OpenCV.
*/
class CvSource : public ImageSource {
public:
@@ -87,7 +32,7 @@ class CvSource : public ImageSource {
CvSource(std::string_view name, const VideoMode& mode);
/**
* Create an OpenCV source.
* Create an OpenCV source.
*
* @param name Source name (arbitrary unique identifier)
* @param pixelFormat Pixel format
@@ -111,14 +56,15 @@ class CvSource : public ImageSource {
};
/**
* A sink for user code to accept video frames as OpenCV images.
* These sinks require the WPILib OpenCV builds.
* For an alternate OpenCV, include "cscore_raw_cv.h" instead, and
* include your Mat header before that header.
* A source for user code to accept video frames as OpenCV images.
*
* This is not dependent on any opencv binary ABI, and can be used
* with versions of most versions of OpenCV.
*/
class CvSink : public ImageSink {
public:
CvSink() = default;
CvSink(const CvSink& sink);
/**
* Create a sink for accepting OpenCV images.
@@ -127,89 +73,191 @@ class CvSink : public ImageSink {
* image.
*
* @param name Source name (arbitrary unique identifier)
* @param pixelFormat Source pixel format
* @param pixelFormat The pixel format to read
*/
explicit CvSink(std::string_view name, VideoMode::PixelFormat pixelFormat =
VideoMode::PixelFormat::kBGR);
/**
* Create a sink for accepting OpenCV images in a separate thread.
* Wait for the next frame and get the image.
* Times out (returning 0) after timeout seconds.
* The provided image will have the pixelFormat this class was constructed
* with.
*
* <p>A thread will be created that calls WaitForFrame() and calls the
* processFrame() callback each time a new frame arrives.
*
* @param name Source name (arbitrary unique identifier)
* @param processFrame Frame processing function; will be called with a
* time=0 if an error occurred. processFrame should call GetImage()
* or GetError() as needed, but should not call (except in very
* unusual circumstances) WaitForImage().
* @param pixelFormat Source pixel format
* @return Frame time, or 0 on error (call GetError() to obtain the error
* message); the frame time is in the same time base as wpi::Now(),
* and is in 1 us increments.
*/
CvSink(std::string_view name, std::function<void(uint64_t time)> processFrame,
VideoMode::PixelFormat pixelFormat = VideoMode::PixelFormat::kBGR);
[[nodiscard]]
uint64_t GrabFrame(cv::Mat& image, double timeout = 0.225);
/**
* Wait for the next frame and get the image. May block forever.
* The provided image will have the pixelFormat this class was constructed
* with.
*
* @return Frame time, or 0 on error (call GetError() to obtain the error
* message); the frame time is in the same time base as wpi::Now(),
* and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrameNoTimeout(cv::Mat& image);
/**
* Wait for the next frame and get the image.
* Times out (returning 0) after timeout seconds.
* The provided image will have three 8-bit channels stored in BGR order.
* The provided image will have the pixelFormat this class was constructed
* with. The data is backed by data in the CvSink. It will be invalidated by
* any grabFrame*() call on the sink.
*
* @return Frame time, or 0 on error (call GetError() to obtain the error
* message); the frame time is in the same time base as wpi::Now(),
* and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrame(cv::Mat& image, double timeout = 0.225) const;
uint64_t GrabFrameDirect(cv::Mat& image, double timeout = 0.225);
/**
* Wait for the next frame and get the image. May block forever.
* The provided image will have three 8-bit channels stored in BGR order.
* The provided image will have the pixelFormat this class was constructed
* with. The data is backed by data in the CvSink. It will be invalidated by
* any grabFrame*() call on the sink.
*
* @return Frame time, or 0 on error (call GetError() to obtain the error
* message); the frame time is in the same time base as wpi::Now(),
* and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrameNoTimeout(cv::Mat& image) const;
uint64_t GrabFrameNoTimeoutDirect(cv::Mat& image);
private:
wpi::RawFrame rawFrame;
VideoMode::PixelFormat pixelFormat;
};
inline CvSource::CvSource(std::string_view name, const VideoMode& mode) {
m_handle = CreateCvSource(name, mode, &m_status);
m_handle = CreateRawSource(name, true, mode, &m_status);
}
inline CvSource::CvSource(std::string_view name, VideoMode::PixelFormat format,
int width, int height, int fps) {
m_handle =
CreateCvSource(name, VideoMode{format, width, height, fps}, &m_status);
m_handle = CreateRawSource(name, true, VideoMode{format, width, height, fps},
&m_status);
}
inline void CvSource::PutFrame(cv::Mat& image) {
// We only support 8-bit images; convert if necessary.
cv::Mat finalImage;
if (image.depth() == CV_8U) {
finalImage = image;
} else {
image.convertTo(finalImage, CV_8U);
}
int channels = finalImage.channels();
WPI_PixelFormat format;
if (channels == 1) {
format = WPI_PIXFMT_GRAY;
} else if (channels == 3) {
format = WPI_PIXFMT_BGR;
} else {
// TODO Error
return;
}
// TODO old code supported BGRA, but the only way I can support that is slow.
// Update cscore to support BGRA for raw frames
WPI_RawFrame frame; // use WPI_Frame because we don't want the destructor
frame.data = finalImage.data;
frame.freeFunc = nullptr;
frame.freeCbData = nullptr;
frame.size = finalImage.total() * channels;
frame.width = finalImage.cols;
frame.height = finalImage.rows;
frame.stride = finalImage.cols;
frame.pixelFormat = format;
m_status = 0;
PutSourceFrame(m_handle, image, &m_status);
PutSourceFrame(m_handle, frame, &m_status);
}
inline CvSink::CvSink(std::string_view name,
VideoMode::PixelFormat pixelFormat) {
m_handle = CreateCvSink(name, pixelFormat, &m_status);
m_handle = CreateRawSink(name, true, &m_status);
this->pixelFormat = pixelFormat;
}
inline CvSink::CvSink(std::string_view name,
std::function<void(uint64_t time)> processFrame,
VideoMode::PixelFormat pixelFormat) {
m_handle = CreateCvSinkCallback(name, pixelFormat, processFrame, &m_status);
inline CvSink::CvSink(const CvSink& sink)
: ImageSink{sink}, pixelFormat{sink.pixelFormat} {}
inline uint64_t CvSink::GrabFrame(cv::Mat& image, double timeout) {
cv::Mat tmpnam;
auto retVal = GrabFrameDirect(tmpnam);
if (retVal <= 0) {
return retVal;
}
tmpnam.copyTo(image);
return retVal;
}
inline uint64_t CvSink::GrabFrame(cv::Mat& image, double timeout) const {
m_status = 0;
return GrabSinkFrameTimeout(m_handle, image, timeout, &m_status);
inline uint64_t CvSink::GrabFrameNoTimeout(cv::Mat& image) {
cv::Mat tmpnam;
auto retVal = GrabFrameNoTimeoutDirect(tmpnam);
if (retVal <= 0) {
return retVal;
}
tmpnam.copyTo(image);
return retVal;
}
inline uint64_t CvSink::GrabFrameNoTimeout(cv::Mat& image) const {
m_status = 0;
return GrabSinkFrame(m_handle, image, &m_status);
inline constexpr int GetCvFormat(WPI_PixelFormat pixelFormat) {
int type = 0;
switch (pixelFormat) {
case WPI_PIXFMT_YUYV:
case WPI_PIXFMT_RGB565:
type = CV_8UC2;
break;
case WPI_PIXFMT_BGR:
type = CV_8UC3;
break;
case WPI_PIXFMT_GRAY:
case WPI_PIXFMT_MJPEG:
default:
type = CV_8UC1;
break;
}
return type;
}
inline uint64_t CvSink::GrabFrameDirect(cv::Mat& image, double timeout) {
rawFrame.height = 0;
rawFrame.width = 0;
rawFrame.pixelFormat = pixelFormat;
m_status = GrabSinkFrameTimeout(m_handle, rawFrame, timeout, &m_status);
if (m_status <= 0) {
return m_status;
}
image =
cv::Mat{rawFrame.height, rawFrame.width,
GetCvFormat(static_cast<WPI_PixelFormat>(rawFrame.pixelFormat)),
rawFrame.data};
return m_status;
}
inline uint64_t CvSink::GrabFrameNoTimeoutDirect(cv::Mat& image) {
rawFrame.height = 0;
rawFrame.width = 0;
rawFrame.pixelFormat = pixelFormat;
m_status = GrabSinkFrame(m_handle, rawFrame, &m_status);
if (m_status <= 0) {
return m_status;
}
image =
cv::Mat{rawFrame.height, rawFrame.width,
GetCvFormat(static_cast<WPI_PixelFormat>(rawFrame.pixelFormat)),
rawFrame.data};
return m_status;
}
} // namespace cs
#endif
#endif // CSCORE_CSCORE_CV_H_

View File

@@ -221,7 +221,9 @@ class VideoSource {
/// HTTP video source.
kHttp = CS_SOURCE_HTTP,
/// CV video source.
kCv = CS_SOURCE_CV
kCv = CS_SOURCE_CV,
/// Raw video source.
kRaw = CS_SOURCE_RAW,
};
/** Connection strategy. Used for SetConnectionStrategy(). */
@@ -856,7 +858,9 @@ class VideoSink {
/// MJPEG video sink.
kMjpeg = CS_SINK_MJPEG,
/// CV video sink.
kCv = CS_SINK_CV
kCv = CS_SINK_CV,
/// Raw video sink.
kRaw = CS_SINK_RAW,
};
VideoSink() noexcept = default;

View File

@@ -28,9 +28,9 @@ uint64_t CS_GrabRawSinkFrame(CS_Sink sink, struct WPI_RawFrame* rawImage,
uint64_t CS_GrabRawSinkFrameTimeout(CS_Sink sink, struct WPI_RawFrame* rawImage,
double timeout, CS_Status* status);
CS_Sink CS_CreateRawSink(const char* name, CS_Status* status);
CS_Sink CS_CreateRawSink(const char* name, CS_Bool isCv, CS_Status* status);
CS_Sink CS_CreateRawSinkCallback(const char* name, void* data,
CS_Sink CS_CreateRawSinkCallback(const char* name, CS_Bool isCv, void* data,
void (*processFrame)(void* data,
uint64_t time),
CS_Status* status);
@@ -38,8 +38,8 @@ CS_Sink CS_CreateRawSinkCallback(const char* name, void* data,
void CS_PutRawSourceFrame(CS_Source source, const struct WPI_RawFrame* image,
CS_Status* status);
CS_Source CS_CreateRawSource(const char* name, const CS_VideoMode* mode,
CS_Status* status);
CS_Source CS_CreateRawSource(const char* name, CS_Bool isCv,
const CS_VideoMode* mode, CS_Status* status);
/** @} */
#ifdef __cplusplus
@@ -54,11 +54,11 @@ namespace cs {
* @{
*/
CS_Source CreateRawSource(std::string_view name, const VideoMode& mode,
CS_Status* status);
CS_Source CreateRawSource(std::string_view name, bool isCv,
const VideoMode& mode, CS_Status* status);
CS_Sink CreateRawSink(std::string_view name, CS_Status* status);
CS_Sink CreateRawSinkCallback(std::string_view name,
CS_Sink CreateRawSink(std::string_view name, bool isCv, CS_Status* status);
CS_Sink CreateRawSinkCallback(std::string_view name, bool isCv,
std::function<void(uint64_t time)> processFrame,
CS_Status* status);
@@ -166,14 +166,14 @@ class RawSink : public ImageSink {
};
inline RawSource::RawSource(std::string_view name, const VideoMode& mode) {
m_handle = CreateRawSource(name, mode, &m_status);
m_handle = CreateRawSource(name, false, mode, &m_status);
}
inline RawSource::RawSource(std::string_view name,
VideoMode::PixelFormat format, int width,
int height, int fps) {
m_handle =
CreateRawSource(name, VideoMode{format, width, height, fps}, &m_status);
m_handle = CreateRawSource(name, false, VideoMode{format, width, height, fps},
&m_status);
}
inline void RawSource::PutFrame(wpi::RawFrame& image) {
@@ -182,12 +182,12 @@ inline void RawSource::PutFrame(wpi::RawFrame& image) {
}
inline RawSink::RawSink(std::string_view name) {
m_handle = CreateRawSink(name, &m_status);
m_handle = CreateRawSink(name, false, &m_status);
}
inline RawSink::RawSink(std::string_view name,
std::function<void(uint64_t time)> processFrame) {
m_handle = CreateRawSinkCallback(name, processFrame, &m_status);
m_handle = CreateRawSinkCallback(name, false, processFrame, &m_status);
}
inline uint64_t RawSink::GrabFrame(wpi::RawFrame& image, double timeout) const {

View File

@@ -1,228 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef CSCORE_CSCORE_RAW_CV_H_
#define CSCORE_CSCORE_RAW_CV_H_
#ifdef CSCORE_CSCORE_CV_H_
#error "Cannot include both cscore_cv.h and cscore_raw_cv.h in the same file"
#endif
#include <functional>
#include <opencv2/core/mat.hpp>
#include "cscore_raw.h"
namespace cs {
/**
* A source for using the raw frame API to provide opencv images.
*
* If you are using the WPILib OpenCV builds, do not use this, and
* instead include "cscore_cv.h" to get a more performant version.
*
* This is not dependent on any opencv binary ABI, and can be used
* with versions of OpenCV that are not 3. If using OpenCV 3, use
* CvSource.
*/
class RawCvSource : public RawSource {
public:
RawCvSource() = default;
/**
* Create a Raw OpenCV source.
*
* @param name Source name (arbitrary unique identifier)
* @param mode Video mode being generated
*/
RawCvSource(std::string_view name, const VideoMode& mode);
/**
* Create a Raw OpenCV source.
*
* @param name Source name (arbitrary unique identifier)
* @param pixelFormat Pixel format
* @param width width
* @param height height
* @param fps fps
*/
RawCvSource(std::string_view name, VideoMode::PixelFormat pixelFormat,
int width, int height, int fps);
/**
* Put an OpenCV image and notify sinks.
*
* <p>Only 8-bit single-channel or 3-channel (with BGR channel order) images
* are supported. If the format, depth or channel order is different, use
* cv::Mat::convertTo() and/or cv::cvtColor() to convert it first.
*
* @param image OpenCV image
*/
void PutFrame(cv::Mat& image);
private:
wpi::RawFrame rawFrame;
};
/**
* A sink for user code to accept raw video frames as OpenCV images.
*
* If you are using the WPILib OpenCV builds, do not use this, and
* instead include "cscore_cv.h" to get a more performant version.
*
* This is not dependent on any opencv binary ABI, and can be used
* with versions of OpenCV that are not 3. If using OpenCV 3, use
* CvSink.
*/
class RawCvSink : public RawSink {
public:
RawCvSink() = default;
/**
* Create a sink for accepting OpenCV images.
*
* <p>WaitForFrame() must be called on the created sink to get each new
* image.
*
* @param name Source name (arbitrary unique identifier)
*/
explicit RawCvSink(std::string_view name);
/**
* Create a sink for accepting OpenCV images in a separate thread.
*
* <p>A thread will be created that calls WaitForFrame() and calls the
* processFrame() callback each time a new frame arrives.
*
* @param name Source name (arbitrary unique identifier)
* @param processFrame Frame processing function; will be called with a
* time=0 if an error occurred. processFrame should call GetImage()
* or GetError() as needed, but should not call (except in very
* unusual circumstances) WaitForImage().
*/
RawCvSink(std::string_view name,
std::function<void(uint64_t time)> processFrame);
/**
* Wait for the next frame and get the image.
* Times out (returning 0) after timeout seconds.
* The provided image will have three 8-bit channels stored in BGR order.
*
* @return Frame time, or 0 on error (call GetError() to obtain the error
* message); the frame time is in the same time base as wpi::Now(),
* and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrame(cv::Mat& image, double timeout = 0.225);
/**
* Wait for the next frame and get the image. May block forever.
* The provided image will have three 8-bit channels stored in BGR order.
*
* @return Frame time, or 0 on error (call GetError() to obtain the error
* message); the frame time is in the same time base as wpi::Now(),
* and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrameNoTimeout(cv::Mat& image);
/**
* Wait for the next frame and get the image.
* Times out (returning 0) after timeout seconds.
* The provided image will have three 8-bit channels stored in BGR order.
*
* @return Frame time, or 0 on error (call GetError() to obtain the error
* message); the frame time is in the same time base as wpi::Now(),
* and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrameDirect(cv::Mat& image, double timeout = 0.225);
/**
* Wait for the next frame and get the image. May block forever.
* The provided image will have three 8-bit channels stored in BGR order.
*
* @return Frame time, or 0 on error (call GetError() to obtain the error
* message); the frame time is in the same time base as wpi::Now(),
* and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrameNoTimeoutDirect(cv::Mat& image);
private:
wpi::RawFrame rawFrame;
};
inline RawCvSource::RawCvSource(std::string_view name, const VideoMode& mode)
: RawSource{name, mode} {}
inline RawCvSource::RawCvSource(std::string_view name,
VideoMode::PixelFormat format, int width,
int height, int fps)
: RawSource{name, format, width, height, fps} {}
inline void RawCvSource::PutFrame(cv::Mat& image) {
m_status = 0;
rawFrame.data = reinterpret_cast<char*>(image.data);
rawFrame.width = image.cols;
rawFrame.height = image.rows;
rawFrame.totalData = image.total() * image.channels();
rawFrame.pixelFormat =
image.channels() == 3 ? WPI_PIXFMT_BGR : WPI_PIXFMT_GRAY;
PutSourceFrame(m_handle, rawFrame, &m_status);
}
inline RawCvSink::RawCvSink(std::string_view name) : RawSink{name} {}
inline RawCvSink::RawCvSink(std::string_view name,
std::function<void(uint64_t time)> processFrame)
: RawSink{name, processFrame} {}
inline uint64_t RawCvSink::GrabFrame(cv::Mat& image, double timeout) {
cv::Mat tmpnam;
auto retVal = GrabFrameDirect(tmpnam);
if (retVal <= 0) {
return retVal;
}
tmpnam.copyTo(image);
return retVal;
}
inline uint64_t RawCvSink::GrabFrameNoTimeout(cv::Mat& image) {
cv::Mat tmpnam;
auto retVal = GrabFrameNoTimeoutDirect(tmpnam);
if (retVal <= 0) {
return retVal;
}
tmpnam.copyTo(image);
return retVal;
}
inline uint64_t RawCvSink::GrabFrameDirect(cv::Mat& image, double timeout) {
rawFrame.height = 0;
rawFrame.width = 0;
rawFrame.pixelFormat = WPI_PixelFormat::WPI_PIXFMT_BGR;
m_status = RawSink::GrabFrame(rawFrame, timeout);
if (m_status <= 0) {
return m_status;
}
image = cv::Mat{rawFrame.height, rawFrame.width, CV_8UC3, rawFrame.data};
return m_status;
}
inline uint64_t RawCvSink::GrabFrameNoTimeoutDirect(cv::Mat& image) {
rawFrame.height = 0;
rawFrame.width = 0;
rawFrame.pixelFormat = WPI_PixelFormat::WPI_PIXFMT_BGR;
m_status = RawSink::GrabFrameNoTimeout(rawFrame);
if (m_status <= 0) {
return m_status;
}
image = cv::Mat{rawFrame.height, rawFrame.width, CV_8UC3, rawFrame.data};
return m_status;
}
} // namespace cs
#endif // CSCORE_CSCORE_RAW_CV_H_

View File

@@ -155,4 +155,13 @@
<Bug pattern="RV_RETURN_VALUE_OF_PUTIFABSENT_IGNORED" />
<Class name="edu.wpi.first.networktables.NetworkTableInstance" />
</Match>
<!--
Strict reading of Object.equals() contract means that whenever equals() behaviour is defined, all implementations
need to adhere to it. The only reason to override the method (assuming correct API design, of course) is to provide
a more efficient implementation. This rule would be forcing a @SuppressFBWarnings on perfectly compliant classes.
More information at https://github.com/spotbugs/spotbugs/issues/511
-->
<Match>
<Bug pattern="EQ_DOESNT_OVERRIDE_EQUALS" />
</Match>
</FindBugsFilter>