[cscore] Split cscore classes into separate headers

Fixes #3713.
This commit is contained in:
Peter Johnson
2026-01-04 16:59:02 -08:00
parent f08258f784
commit e9d226491c
77 changed files with 2977 additions and 2721 deletions

View File

@@ -13,6 +13,8 @@
#include <fmt/format.h>
#include "wpi/cameraserver/CameraServerShared.hpp"
#include "wpi/cs/VideoEvent.hpp"
#include "wpi/cs/VideoListener.hpp"
#include "wpi/nt/BooleanTopic.hpp"
#include "wpi/nt/IntegerTopic.hpp"
#include "wpi/nt/NetworkTable.hpp"
@@ -247,17 +249,17 @@ void Instance::UpdateStreamValues() {
}
}
static std::string PixelFormatToString(int pixelFormat) {
static std::string PixelFormatToString(wpi::util::PixelFormat pixelFormat) {
switch (pixelFormat) {
case cs::VideoMode::PixelFormat::kMJPEG:
case wpi::util::PixelFormat::kMJPEG:
return "MJPEG";
case cs::VideoMode::PixelFormat::kYUYV:
case wpi::util::PixelFormat::kYUYV:
return "YUYV";
case cs::VideoMode::PixelFormat::kRGB565:
case wpi::util::PixelFormat::kRGB565:
return "RGB565";
case cs::VideoMode::PixelFormat::kBGR:
case wpi::util::PixelFormat::kBGR:
return "BGR";
case cs::VideoMode::PixelFormat::kGray:
case wpi::util::PixelFormat::kGray:
return "Gray";
default:
return "Unknown";
@@ -506,7 +508,7 @@ cs::UsbCamera CameraServer::StartAutomaticCapture(std::string_view name,
cs::MjpegServer CameraServer::AddSwitchedCamera(std::string_view name) {
auto& inst = ::GetInstance();
// create a dummy CvSource
cs::CvSource source{name, cs::VideoMode::PixelFormat::kMJPEG, 160, 120, 30};
cs::CvSource source{name, wpi::util::PixelFormat::kMJPEG, 160, 120, 30};
cs::MjpegServer server = StartAutomaticCapture(source);
inst.m_fixedSources[server.GetHandle()] = source.GetHandle();
@@ -568,7 +570,7 @@ cs::CvSink CameraServer::GetVideo(const cs::VideoSource& camera) {
}
cs::CvSink CameraServer::GetVideo(const cs::VideoSource& camera,
cs::VideoMode::PixelFormat pixelFormat) {
wpi::util::PixelFormat pixelFormat) {
auto& inst = ::GetInstance();
wpi::util::SmallString<64> name{"opencv_"};
name += camera.GetName();
@@ -611,7 +613,7 @@ cs::CvSink CameraServer::GetVideo(std::string_view name) {
}
cs::CvSink CameraServer::GetVideo(std::string_view name,
cs::VideoMode::PixelFormat pixelFormat) {
wpi::util::PixelFormat pixelFormat) {
auto& inst = ::GetInstance();
cs::VideoSource source;
{
@@ -630,7 +632,7 @@ cs::CvSink CameraServer::GetVideo(std::string_view name,
cs::CvSource CameraServer::PutVideo(std::string_view name, int width,
int height) {
::GetInstance();
cs::CvSource source{name, cs::VideoMode::kMJPEG, width, height, 30};
cs::CvSource source{name, wpi::util::PixelFormat::kMJPEG, width, height, 30};
StartAutomaticCapture(source);
return source;
}

View File

@@ -6,12 +6,13 @@
#include <stdint.h>
#include <span>
#include <string>
#include <string_view>
#include <vector>
#include "wpi/cs/cscore_cv.hpp"
#include "wpi/cs/CvSink.hpp"
#include "wpi/cs/CvSource.hpp"
#include "wpi/cs/MjpegServer.hpp"
#include "wpi/cs/UsbCamera.hpp"
#include "wpi/util/PixelFormat.hpp"
namespace wpi {
@@ -107,7 +108,7 @@ class CameraServer {
* camera
*/
static cs::CvSink GetVideo(const cs::VideoSource& camera,
cs::VideoMode::PixelFormat pixelFormat);
wpi::util::PixelFormat pixelFormat);
/**
* Get OpenCV access to the specified camera. This allows you to get
@@ -126,7 +127,7 @@ class CameraServer {
* camera
*/
static cs::CvSink GetVideo(std::string_view name,
cs::VideoMode::PixelFormat pixelFormat);
wpi::util::PixelFormat pixelFormat);
/**
* Create a MJPEG stream with OpenCV input. This can be called to pass custom

View File

@@ -8,7 +8,8 @@
#include <functional>
#include <memory>
#include "wpi/cs/cscore_cv.hpp"
#include "wpi/cs/CvSink.hpp"
#include "wpi/cs/VideoSource.hpp"
#include "wpi/vision/VisionPipeline.hpp"
namespace wpi::vision {

View File

@@ -4,7 +4,7 @@
#include <cstdio>
#include "wpi/cs/cscore.h"
#include "wpi/cs/UsbCamera.hpp"
#include "wpi/util/print.hpp"
int main() {
@@ -57,13 +57,13 @@ int main() {
for (const auto& mode : camera.EnumerateVideoModes()) {
const char* pixelFormat;
switch (mode.pixelFormat) {
case wpi::cs::VideoMode::kMJPEG:
case wpi::util::PixelFormat::kMJPEG:
pixelFormat = "MJPEG";
break;
case wpi::cs::VideoMode::kYUYV:
case wpi::util::PixelFormat::kYUYV:
pixelFormat = "YUYV";
break;
case wpi::cs::VideoMode::kRGB565:
case wpi::util::PixelFormat::kRGB565:
pixelFormat = "RGB565";
break;
default:

View File

@@ -6,17 +6,19 @@
#include <opencv2/core/core.hpp>
#include "wpi/cs/cscore.h"
#include "wpi/cs/cscore_cv.hpp"
#include "wpi/cs/CvSink.hpp"
#include "wpi/cs/CvSource.hpp"
#include "wpi/cs/HttpCamera.hpp"
#include "wpi/cs/MjpegServer.hpp"
#include "wpi/util/print.hpp"
int main() {
wpi::cs::HttpCamera camera{"httpcam", "http://localhost:8081/?action=stream"};
camera.SetVideoMode(wpi::cs::VideoMode::kMJPEG, 320, 240, 30);
camera.SetVideoMode(wpi::util::PixelFormat::kMJPEG, 320, 240, 30);
wpi::cs::CvSink cvsink{"cvsink"};
cvsink.SetSource(camera);
wpi::cs::CvSource cvsource{"cvsource", wpi::cs::VideoMode::kMJPEG, 320, 240,
30};
wpi::cs::CvSource cvsource{"cvsource", wpi::util::PixelFormat::kMJPEG, 320,
240, 30};
wpi::cs::MjpegServer cvMjpegServer{"cvhttpserver", 8083};
cvMjpegServer.SetSource(cvsource);

View File

@@ -6,7 +6,7 @@
#include <cstdio>
#include <thread>
#include "wpi/cs/cscore.h"
#include "wpi/cs/UsbCamera.hpp"
#include "wpi/util/StringExtras.hpp"
#include "wpi/util/print.hpp"

View File

@@ -4,18 +4,21 @@
#include <opencv2/core/core.hpp>
#include "wpi/cs/cscore_cv.hpp"
#include "wpi/cs/CvSink.hpp"
#include "wpi/cs/CvSource.hpp"
#include "wpi/cs/MjpegServer.hpp"
#include "wpi/cs/UsbCamera.hpp"
#include "wpi/util/print.hpp"
int main() {
wpi::cs::UsbCamera camera{"usbcam", 0};
camera.SetVideoMode(wpi::cs::VideoMode::kMJPEG, 320, 240, 30);
camera.SetVideoMode(wpi::util::PixelFormat::kMJPEG, 320, 240, 30);
wpi::cs::MjpegServer mjpegServer{"httpserver", 8081};
mjpegServer.SetSource(camera);
wpi::cs::CvSink cvsink{"cvsink"};
cvsink.SetSource(camera);
wpi::cs::CvSource cvsource{"cvsource", wpi::cs::VideoMode::kMJPEG, 320, 240,
30};
wpi::cs::CvSource cvsource{"cvsource", wpi::util::PixelFormat::kMJPEG, 320,
240, 30};
wpi::cs::MjpegServer cvMjpegServer{"cvhttpserver", 8082};
cvMjpegServer.SetSource(cvsource);

View File

@@ -4,7 +4,10 @@
#include <cstdio>
#include "wpi/cs/cscore.h"
#include "wpi/cs/MjpegServer.hpp"
#include "wpi/cs/RawEvent.hpp"
#include "wpi/cs/UsbCamera.hpp"
#include "wpi/cs/cscore_cpp.hpp"
#include "wpi/util/print.hpp"
int main() {
@@ -14,7 +17,7 @@ int main() {
wpi::util::print(" {}\n", addr);
}
wpi::cs::UsbCamera camera{"usbcam", 0};
camera.SetVideoMode(wpi::cs::VideoMode::kMJPEG, 320, 240, 30);
camera.SetVideoMode(wpi::util::PixelFormat::kMJPEG, 320, 240, 30);
wpi::cs::MjpegServer mjpegServer{"httpserver", 8081};
mjpegServer.SetSource(camera);

View File

@@ -14,8 +14,8 @@
#include <opencv2/core/mat.hpp>
#include <opencv2/imgproc.hpp>
#include "wpi/cs/cscore.h"
#include "wpi/cs/cscore_cv.hpp"
#include "wpi/cs/CvSink.hpp"
#include "wpi/cs/UsbCamera.hpp"
#include "wpi/gui/wpigui.hpp"
#include "wpi/util/mutex.hpp"
#include "wpi/util/print.hpp"
@@ -31,7 +31,7 @@ int main() {
std::atomic<bool> stopCamera{false};
wpi::cs::UsbCamera camera{"usbcam", 0};
camera.SetVideoMode(wpi::cs::VideoMode::kMJPEG, 640, 480, 30);
camera.SetVideoMode(wpi::util::PixelFormat::kMJPEG, 640, 480, 30);
wpi::cs::CvSink cvsink{"cvsink"};
cvsink.SetSource(camera);

View File

@@ -2,7 +2,7 @@
// 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 "wpi/cs/cscore.h"
#include "wpi/cs/cscore_cpp.hpp"
#include "wpi/util/print.hpp"
int main() {

View File

@@ -6,11 +6,9 @@
#include <atomic>
#include <functional>
#include <memory>
#include <span>
#include <string>
#include <string_view>
#include <vector>
#include "SourceImpl.hpp"

View File

@@ -13,6 +13,7 @@
#include "Instance.hpp"
#include "SourceImpl.hpp"
#include "wpi/util/PixelFormat.hpp"
using namespace wpi::cs;
@@ -67,7 +68,7 @@ Image* Frame::GetNearestImage(int width, int height) const {
}
Image* Frame::GetNearestImage(int width, int height,
VideoMode::PixelFormat pixelFormat,
wpi::util::PixelFormat pixelFormat,
int jpegQuality) const {
if (!m_impl) {
return nullptr;
@@ -93,22 +94,23 @@ Image* Frame::GetNearestImage(int width, int height,
// 2) Same width, height, different (but non-JPEG) pixelFormat (color conv)
// 2a) If we want JPEG output, prefer BGR over other pixel formats
if (pixelFormat == VideoMode::kMJPEG) {
if (pixelFormat == wpi::util::PixelFormat::kMJPEG) {
for (auto i : m_impl->images) {
if (i->Is(width, height, VideoMode::kBGR)) {
if (i->Is(width, height, wpi::util::PixelFormat::kBGR)) {
return i;
}
}
}
for (auto i : m_impl->images) {
if (i->Is(width, height) && i->pixelFormat != VideoMode::kMJPEG) {
if (i->Is(width, height) &&
i->pixelFormat != wpi::util::PixelFormat::kMJPEG) {
return i;
}
}
// 3) Different width, height, same pixelFormat (only if non-JPEG) (resample)
if (pixelFormat != VideoMode::kMJPEG) {
if (pixelFormat != wpi::util::PixelFormat::kMJPEG) {
// 3a) Smallest image at least width/height in size
for (auto i : m_impl->images) {
if (i->IsLarger(width, height) && i->pixelFormat == pixelFormat &&
@@ -135,7 +137,8 @@ Image* Frame::GetNearestImage(int width, int height,
// (color conversion + resample)
// 4a) Smallest image at least width/height in size
for (auto i : m_impl->images) {
if (i->IsLarger(width, height) && i->pixelFormat != VideoMode::kMJPEG &&
if (i->IsLarger(width, height) &&
i->pixelFormat != wpi::util::PixelFormat::kMJPEG &&
(!found || (i->IsSmaller(*found)))) {
found = i;
}
@@ -146,7 +149,7 @@ Image* Frame::GetNearestImage(int width, int height,
// 4b) Largest image (less than width/height)
for (auto i : m_impl->images) {
if (i->pixelFormat != VideoMode::kMJPEG &&
if (i->pixelFormat != wpi::util::PixelFormat::kMJPEG &&
(!found || (i->IsLarger(*found)))) {
found = i;
}
@@ -158,7 +161,7 @@ Image* Frame::GetNearestImage(int width, int height,
// 5) Same width, height, JPEG pixelFormat (decompression). As there may be
// multiple JPEG images, find the highest quality one.
for (auto i : m_impl->images) {
if (i->Is(width, height, VideoMode::kMJPEG) &&
if (i->Is(width, height, wpi::util::PixelFormat::kMJPEG) &&
(!found || i->jpegQuality > found->jpegQuality)) {
found = i;
// consider one without a quality setting to be the highest quality
@@ -175,7 +178,8 @@ Image* Frame::GetNearestImage(int width, int height,
// 6) Different width, height, JPEG pixelFormat (decompression)
// 6a) Smallest image at least width/height in size
for (auto i : m_impl->images) {
if (i->IsLarger(width, height) && i->pixelFormat == VideoMode::kMJPEG &&
if (i->IsLarger(width, height) &&
i->pixelFormat == wpi::util::PixelFormat::kMJPEG &&
(!found || (i->IsSmaller(*found)))) {
found = i;
}
@@ -186,7 +190,7 @@ Image* Frame::GetNearestImage(int width, int height,
// 6b) Largest image (less than width/height)
for (auto i : m_impl->images) {
if (i->pixelFormat != VideoMode::kMJPEG &&
if (i->pixelFormat != wpi::util::PixelFormat::kMJPEG &&
(!found || (i->IsLarger(*found)))) {
found = i;
}
@@ -199,7 +203,7 @@ Image* Frame::GetNearestImage(int width, int height,
return m_impl->images.empty() ? nullptr : m_impl->images[0];
}
Image* Frame::ConvertImpl(Image* image, VideoMode::PixelFormat pixelFormat,
Image* Frame::ConvertImpl(Image* image, wpi::util::PixelFormat pixelFormat,
int requiredJpegQuality, int defaultJpegQuality) {
if (!image || image->Is(image->width, image->height, pixelFormat,
requiredJpegQuality)) {
@@ -211,169 +215,171 @@ Image* Frame::ConvertImpl(Image* image, VideoMode::PixelFormat pixelFormat,
// anything else with it. Note that if the destination format is JPEG, we
// still need to do this (unless it was already a JPEG, in which case we
// would have returned above).
if (cur->pixelFormat == VideoMode::kMJPEG) {
if (cur->pixelFormat == wpi::util::PixelFormat::kMJPEG) {
cur = ConvertMJPEGToBGR(cur);
if (pixelFormat == VideoMode::kBGR) {
if (pixelFormat == wpi::util::PixelFormat::kBGR) {
return cur;
}
}
// Color convert
switch (pixelFormat) {
case VideoMode::kRGB565:
case wpi::util::PixelFormat::kRGB565:
// If source is YUYV, UYVY, Gray, or Y16, need to convert to BGR first
if (cur->pixelFormat == VideoMode::kYUYV) {
if (cur->pixelFormat == wpi::util::PixelFormat::kYUYV) {
// Check to see if BGR version already exists...
if (Image* newImage =
GetExistingImage(cur->width, cur->height, VideoMode::kBGR)) {
if (Image* newImage = GetExistingImage(cur->width, cur->height,
wpi::util::PixelFormat::kBGR)) {
cur = newImage;
} else {
cur = ConvertYUYVToBGR(cur);
}
} else if (cur->pixelFormat == VideoMode::kUYVY) {
} else if (cur->pixelFormat == wpi::util::PixelFormat::kUYVY) {
// Check to see if BGR version already exists...
if (Image* newImage =
GetExistingImage(cur->width, cur->height, VideoMode::kBGR)) {
if (Image* newImage = GetExistingImage(cur->width, cur->height,
wpi::util::PixelFormat::kBGR)) {
cur = newImage;
} else {
cur = ConvertUYVYToBGR(cur);
}
} else if (cur->pixelFormat == VideoMode::kGray) {
} else if (cur->pixelFormat == wpi::util::PixelFormat::kGray) {
// Check to see if BGR version already exists...
if (Image* newImage =
GetExistingImage(cur->width, cur->height, VideoMode::kBGR)) {
if (Image* newImage = GetExistingImage(cur->width, cur->height,
wpi::util::PixelFormat::kBGR)) {
cur = newImage;
} else {
cur = ConvertGrayToBGR(cur);
}
} else if (cur->pixelFormat == VideoMode::kY16) {
} else if (cur->pixelFormat == wpi::util::PixelFormat::kY16) {
// Check to see if BGR version already exists...
if (Image* newImage =
GetExistingImage(cur->width, cur->height, VideoMode::kBGR)) {
if (Image* newImage = GetExistingImage(cur->width, cur->height,
wpi::util::PixelFormat::kBGR)) {
cur = newImage;
} else if (Image* newImage = GetExistingImage(cur->width, cur->height,
VideoMode::kGray)) {
} else if (Image* newImage =
GetExistingImage(cur->width, cur->height,
wpi::util::PixelFormat::kGray)) {
cur = ConvertGrayToBGR(newImage);
} else {
cur = ConvertGrayToBGR(ConvertY16ToGray(cur));
}
}
return ConvertBGRToRGB565(cur);
case VideoMode::kGray:
case VideoMode::kY16:
case wpi::util::PixelFormat::kGray:
case wpi::util::PixelFormat::kY16:
// If source is also grayscale, convert directly
if (pixelFormat == VideoMode::kGray &&
cur->pixelFormat == VideoMode::kY16) {
if (pixelFormat == wpi::util::PixelFormat::kGray &&
cur->pixelFormat == wpi::util::PixelFormat::kY16) {
return ConvertY16ToGray(cur);
} else if (pixelFormat == VideoMode::kY16 &&
cur->pixelFormat == VideoMode::kGray) {
} else if (pixelFormat == wpi::util::PixelFormat::kY16 &&
cur->pixelFormat == wpi::util::PixelFormat::kGray) {
return ConvertGrayToY16(cur);
}
// If source is YUYV, UYVY, convert directly to Gray
// If RGB565, need to convert to BGR first
if (cur->pixelFormat == VideoMode::kYUYV) {
if (cur->pixelFormat == wpi::util::PixelFormat::kYUYV) {
cur = ConvertYUYVToGray(cur);
} else if (cur->pixelFormat == VideoMode::kUYVY) {
} else if (cur->pixelFormat == wpi::util::PixelFormat::kUYVY) {
cur = ConvertUYVYToGray(cur);
} else if (cur->pixelFormat == VideoMode::kRGB565) {
} else if (cur->pixelFormat == wpi::util::PixelFormat::kRGB565) {
// Check to see if BGR version already exists...
if (Image* newImage =
GetExistingImage(cur->width, cur->height, VideoMode::kBGR)) {
if (Image* newImage = GetExistingImage(cur->width, cur->height,
wpi::util::PixelFormat::kBGR)) {
cur = newImage;
} else {
cur = ConvertRGB565ToBGR(cur);
}
cur = ConvertBGRToGray(cur);
}
if (pixelFormat == VideoMode::kY16) {
if (pixelFormat == wpi::util::PixelFormat::kY16) {
cur = ConvertGrayToY16(cur);
}
return cur;
case VideoMode::kBGR:
case VideoMode::kMJPEG:
if (cur->pixelFormat == VideoMode::kYUYV) {
case wpi::util::PixelFormat::kBGR:
case wpi::util::PixelFormat::kMJPEG:
if (cur->pixelFormat == wpi::util::PixelFormat::kYUYV) {
cur = ConvertYUYVToBGR(cur);
} else if (cur->pixelFormat == VideoMode::kUYVY) {
} else if (cur->pixelFormat == wpi::util::PixelFormat::kUYVY) {
cur = ConvertUYVYToBGR(cur);
} else if (cur->pixelFormat == VideoMode::kRGB565) {
} else if (cur->pixelFormat == wpi::util::PixelFormat::kRGB565) {
cur = ConvertRGB565ToBGR(cur);
} else if (cur->pixelFormat == VideoMode::kGray) {
if (pixelFormat == VideoMode::kBGR) {
} else if (cur->pixelFormat == wpi::util::PixelFormat::kGray) {
if (pixelFormat == wpi::util::PixelFormat::kBGR) {
return ConvertGrayToBGR(cur);
} else {
return ConvertGrayToMJPEG(cur, defaultJpegQuality);
}
} else if (cur->pixelFormat == VideoMode::kY16) {
} else if (cur->pixelFormat == wpi::util::PixelFormat::kY16) {
// Check to see if Gray version already exists...
if (Image* newImage =
GetExistingImage(cur->width, cur->height, VideoMode::kGray)) {
if (Image* newImage = GetExistingImage(cur->width, cur->height,
wpi::util::PixelFormat::kGray)) {
cur = newImage;
} else {
cur = ConvertY16ToGray(cur);
}
if (pixelFormat == VideoMode::kBGR) {
if (pixelFormat == wpi::util::PixelFormat::kBGR) {
return ConvertGrayToBGR(cur);
} else {
return ConvertGrayToMJPEG(cur, defaultJpegQuality);
}
}
break;
case VideoMode::kBGRA:
case wpi::util::PixelFormat::kBGRA:
// If source is RGB565, YUYV, UYVY, Gray or Y16, need to convert to BGR
// first
if (cur->pixelFormat == VideoMode::kRGB565) {
if (cur->pixelFormat == wpi::util::PixelFormat::kRGB565) {
// Check to see if BGR version already exists...
if (Image* newImage =
GetExistingImage(cur->width, cur->height, VideoMode::kBGR)) {
if (Image* newImage = GetExistingImage(cur->width, cur->height,
wpi::util::PixelFormat::kBGR)) {
cur = newImage;
} else {
cur = ConvertRGB565ToBGR(cur);
}
} else if (cur->pixelFormat == VideoMode::kYUYV) {
} else if (cur->pixelFormat == wpi::util::PixelFormat::kYUYV) {
// Check to see if BGR version already exists...
if (Image* newImage =
GetExistingImage(cur->width, cur->height, VideoMode::kBGR)) {
if (Image* newImage = GetExistingImage(cur->width, cur->height,
wpi::util::PixelFormat::kBGR)) {
cur = newImage;
} else {
cur = ConvertYUYVToBGR(cur);
}
} else if (cur->pixelFormat == VideoMode::kUYVY) {
} else if (cur->pixelFormat == wpi::util::PixelFormat::kUYVY) {
// Check to see if BGR version already exists...
if (Image* newImage =
GetExistingImage(cur->width, cur->height, VideoMode::kBGR)) {
if (Image* newImage = GetExistingImage(cur->width, cur->height,
wpi::util::PixelFormat::kBGR)) {
cur = newImage;
} else {
cur = ConvertUYVYToBGR(cur);
}
} else if (cur->pixelFormat == VideoMode::kGray) {
} else if (cur->pixelFormat == wpi::util::PixelFormat::kGray) {
// Check to see if BGR version already exists...
if (Image* newImage =
GetExistingImage(cur->width, cur->height, VideoMode::kBGR)) {
if (Image* newImage = GetExistingImage(cur->width, cur->height,
wpi::util::PixelFormat::kBGR)) {
cur = newImage;
} else {
cur = ConvertGrayToBGR(cur);
}
} else if (cur->pixelFormat == VideoMode::kY16) {
} else if (cur->pixelFormat == wpi::util::PixelFormat::kY16) {
// Check to see if BGR version already exists...
if (Image* newImage =
GetExistingImage(cur->width, cur->height, VideoMode::kBGR)) {
if (Image* newImage = GetExistingImage(cur->width, cur->height,
wpi::util::PixelFormat::kBGR)) {
cur = newImage;
} else if (Image* newImage = GetExistingImage(cur->width, cur->height,
VideoMode::kGray)) {
} else if (Image* newImage =
GetExistingImage(cur->width, cur->height,
wpi::util::PixelFormat::kGray)) {
cur = ConvertGrayToBGR(newImage);
} else {
cur = ConvertGrayToBGR(ConvertY16ToGray(cur));
}
}
return ConvertBGRToBGRA(cur);
case VideoMode::kYUYV:
case VideoMode::kUYVY:
case wpi::util::PixelFormat::kYUYV:
case wpi::util::PixelFormat::kUYVY:
default:
return nullptr; // Unsupported
}
// Compress if destination is JPEG
if (pixelFormat == VideoMode::kMJPEG) {
if (pixelFormat == wpi::util::PixelFormat::kMJPEG) {
cur = ConvertBGRToMJPEG(cur, defaultJpegQuality);
}
@@ -381,14 +387,14 @@ Image* Frame::ConvertImpl(Image* image, VideoMode::PixelFormat pixelFormat,
}
Image* Frame::ConvertMJPEGToBGR(Image* image) {
if (!image || image->pixelFormat != VideoMode::kMJPEG) {
if (!image || image->pixelFormat != wpi::util::PixelFormat::kMJPEG) {
return nullptr;
}
// Allocate an BGR image
auto newImage =
m_impl->source.AllocImage(VideoMode::kBGR, image->width, image->height,
image->width * image->height * 3);
auto newImage = m_impl->source.AllocImage(wpi::util::PixelFormat::kBGR,
image->width, image->height,
image->width * image->height * 3);
// Decode
cv::Mat newMat = newImage->AsMat();
@@ -404,14 +410,14 @@ Image* Frame::ConvertMJPEGToBGR(Image* image) {
}
Image* Frame::ConvertMJPEGToGray(Image* image) {
if (!image || image->pixelFormat != VideoMode::kMJPEG) {
if (!image || image->pixelFormat != wpi::util::PixelFormat::kMJPEG) {
return nullptr;
}
// Allocate an grayscale image
auto newImage =
m_impl->source.AllocImage(VideoMode::kGray, image->width, image->height,
image->width * image->height);
m_impl->source.AllocImage(wpi::util::PixelFormat::kGray, image->width,
image->height, image->width * image->height);
// Decode
cv::Mat newMat = newImage->AsMat();
@@ -427,14 +433,14 @@ Image* Frame::ConvertMJPEGToGray(Image* image) {
}
Image* Frame::ConvertYUYVToBGR(Image* image) {
if (!image || image->pixelFormat != VideoMode::kYUYV) {
if (!image || image->pixelFormat != wpi::util::PixelFormat::kYUYV) {
return nullptr;
}
// Allocate a BGR image
auto newImage =
m_impl->source.AllocImage(VideoMode::kBGR, image->width, image->height,
image->width * image->height * 3);
auto newImage = m_impl->source.AllocImage(wpi::util::PixelFormat::kBGR,
image->width, image->height,
image->width * image->height * 3);
// Convert
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_YUV2BGR_YUYV);
@@ -449,14 +455,14 @@ Image* Frame::ConvertYUYVToBGR(Image* image) {
}
Image* Frame::ConvertYUYVToGray(Image* image) {
if (!image || image->pixelFormat != VideoMode::kYUYV) {
if (!image || image->pixelFormat != wpi::util::PixelFormat::kYUYV) {
return nullptr;
}
// Allocate a grayscale image
auto newImage =
m_impl->source.AllocImage(VideoMode::kGray, image->width, image->height,
image->width * image->height);
m_impl->source.AllocImage(wpi::util::PixelFormat::kGray, image->width,
image->height, image->width * image->height);
// Convert
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_YUV2GRAY_YUYV);
@@ -471,14 +477,14 @@ Image* Frame::ConvertYUYVToGray(Image* image) {
}
Image* Frame::ConvertUYVYToBGR(Image* image) {
if (!image || image->pixelFormat != VideoMode::kUYVY) {
if (!image || image->pixelFormat != wpi::util::PixelFormat::kUYVY) {
return nullptr;
}
// Allocate a BGR image
auto newImage =
m_impl->source.AllocImage(VideoMode::kBGR, image->width, image->height,
image->width * image->height * 3);
auto newImage = m_impl->source.AllocImage(wpi::util::PixelFormat::kBGR,
image->width, image->height,
image->width * image->height * 3);
// Convert
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_YUV2BGR_UYVY);
@@ -493,14 +499,14 @@ Image* Frame::ConvertUYVYToBGR(Image* image) {
}
Image* Frame::ConvertUYVYToGray(Image* image) {
if (!image || image->pixelFormat != VideoMode::kUYVY) {
if (!image || image->pixelFormat != wpi::util::PixelFormat::kUYVY) {
return nullptr;
}
// Allocate a grayscale image
auto newImage =
m_impl->source.AllocImage(VideoMode::kGray, image->width, image->height,
image->width * image->height);
m_impl->source.AllocImage(wpi::util::PixelFormat::kGray, image->width,
image->height, image->width * image->height);
// Convert
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_YUV2GRAY_UYVY);
@@ -515,14 +521,14 @@ Image* Frame::ConvertUYVYToGray(Image* image) {
}
Image* Frame::ConvertBGRToRGB565(Image* image) {
if (!image || image->pixelFormat != VideoMode::kBGR) {
if (!image || image->pixelFormat != wpi::util::PixelFormat::kBGR) {
return nullptr;
}
// Allocate a RGB565 image
auto newImage =
m_impl->source.AllocImage(VideoMode::kRGB565, image->width, image->height,
image->width * image->height * 2);
auto newImage = m_impl->source.AllocImage(wpi::util::PixelFormat::kRGB565,
image->width, image->height,
image->width * image->height * 2);
// Convert
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_RGB2BGR565);
@@ -537,14 +543,14 @@ Image* Frame::ConvertBGRToRGB565(Image* image) {
}
Image* Frame::ConvertRGB565ToBGR(Image* image) {
if (!image || image->pixelFormat != VideoMode::kRGB565) {
if (!image || image->pixelFormat != wpi::util::PixelFormat::kRGB565) {
return nullptr;
}
// Allocate a BGR image
auto newImage =
m_impl->source.AllocImage(VideoMode::kBGR, image->width, image->height,
image->width * image->height * 3);
auto newImage = m_impl->source.AllocImage(wpi::util::PixelFormat::kBGR,
image->width, image->height,
image->width * image->height * 3);
// Convert
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_BGR5652RGB);
@@ -559,14 +565,14 @@ Image* Frame::ConvertRGB565ToBGR(Image* image) {
}
Image* Frame::ConvertBGRToGray(Image* image) {
if (!image || image->pixelFormat != VideoMode::kBGR) {
if (!image || image->pixelFormat != wpi::util::PixelFormat::kBGR) {
return nullptr;
}
// Allocate a Grayscale image
auto newImage =
m_impl->source.AllocImage(VideoMode::kGray, image->width, image->height,
image->width * image->height);
m_impl->source.AllocImage(wpi::util::PixelFormat::kGray, image->width,
image->height, image->width * image->height);
// Convert
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_BGR2GRAY);
@@ -581,14 +587,14 @@ Image* Frame::ConvertBGRToGray(Image* image) {
}
Image* Frame::ConvertGrayToBGR(Image* image) {
if (!image || image->pixelFormat != VideoMode::kGray) {
if (!image || image->pixelFormat != wpi::util::PixelFormat::kGray) {
return nullptr;
}
// Allocate a BGR image
auto newImage =
m_impl->source.AllocImage(VideoMode::kBGR, image->width, image->height,
image->width * image->height * 3);
auto newImage = m_impl->source.AllocImage(wpi::util::PixelFormat::kBGR,
image->width, image->height,
image->width * image->height * 3);
// Convert
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_GRAY2BGR);
@@ -603,7 +609,7 @@ Image* Frame::ConvertGrayToBGR(Image* image) {
}
Image* Frame::ConvertBGRToMJPEG(Image* image, int quality) {
if (!image || image->pixelFormat != VideoMode::kBGR) {
if (!image || image->pixelFormat != wpi::util::PixelFormat::kBGR) {
return nullptr;
}
if (!m_impl) {
@@ -617,9 +623,9 @@ Image* Frame::ConvertBGRToMJPEG(Image* image, int quality) {
// Per Wikipedia, Q=100 on a sample image results in 8.25 bits per pixel,
// this is a little bit more conservative in assuming 50% space savings over
// the equivalent BGR image.
auto newImage =
m_impl->source.AllocImage(VideoMode::kMJPEG, image->width, image->height,
image->width * image->height * 1.5);
auto newImage = m_impl->source.AllocImage(wpi::util::PixelFormat::kMJPEG,
image->width, image->height,
image->width * image->height * 1.5);
// Compress
if (m_impl->compressionParams.empty()) {
@@ -638,7 +644,7 @@ Image* Frame::ConvertBGRToMJPEG(Image* image, int quality) {
}
Image* Frame::ConvertGrayToMJPEG(Image* image, int quality) {
if (!image || image->pixelFormat != VideoMode::kGray) {
if (!image || image->pixelFormat != wpi::util::PixelFormat::kGray) {
return nullptr;
}
if (!m_impl) {
@@ -652,9 +658,9 @@ Image* Frame::ConvertGrayToMJPEG(Image* image, int quality) {
// Per Wikipedia, Q=100 on a sample image results in 8.25 bits per pixel,
// this is a little bit more conservative in assuming 25% space savings over
// the equivalent grayscale image.
auto newImage =
m_impl->source.AllocImage(VideoMode::kMJPEG, image->width, image->height,
image->width * image->height * 0.75);
auto newImage = m_impl->source.AllocImage(
wpi::util::PixelFormat::kMJPEG, image->width, image->height,
image->width * image->height * 0.75);
// Compress
if (m_impl->compressionParams.empty()) {
@@ -673,14 +679,14 @@ Image* Frame::ConvertGrayToMJPEG(Image* image, int quality) {
}
Image* Frame::ConvertGrayToY16(Image* image) {
if (!image || image->pixelFormat != VideoMode::kGray) {
if (!image || image->pixelFormat != wpi::util::PixelFormat::kGray) {
return nullptr;
}
// Allocate a Y16 image
auto newImage =
m_impl->source.AllocImage(VideoMode::kY16, image->width, image->height,
image->width * image->height * 2);
auto newImage = m_impl->source.AllocImage(wpi::util::PixelFormat::kY16,
image->width, image->height,
image->width * image->height * 2);
// Convert with linear scaling
image->AsMat().convertTo(newImage->AsMat(), CV_16U, 256);
@@ -695,14 +701,14 @@ Image* Frame::ConvertGrayToY16(Image* image) {
}
Image* Frame::ConvertY16ToGray(Image* image) {
if (!image || image->pixelFormat != VideoMode::kY16) {
if (!image || image->pixelFormat != wpi::util::PixelFormat::kY16) {
return nullptr;
}
// Allocate a Grayscale image
auto newImage =
m_impl->source.AllocImage(VideoMode::kGray, image->width, image->height,
image->width * image->height);
m_impl->source.AllocImage(wpi::util::PixelFormat::kGray, image->width,
image->height, image->width * image->height);
// Scale min to 0 and max to 255
cv::normalize(image->AsMat(), newImage->AsMat(), 255, 0, cv::NORM_MINMAX);
@@ -717,14 +723,14 @@ Image* Frame::ConvertY16ToGray(Image* image) {
}
Image* Frame::ConvertBGRToBGRA(Image* image) {
if (!image || image->pixelFormat != VideoMode::kBGR) {
if (!image || image->pixelFormat != wpi::util::PixelFormat::kBGR) {
return nullptr;
}
// Allocate a RGB565 image
auto newImage =
m_impl->source.AllocImage(VideoMode::kBGRA, image->width, image->height,
image->width * image->height * 4);
auto newImage = m_impl->source.AllocImage(wpi::util::PixelFormat::kBGRA,
image->width, image->height,
image->width * image->height * 4);
// Convert
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_BGR2BGRA);
@@ -739,7 +745,7 @@ Image* Frame::ConvertBGRToBGRA(Image* image) {
}
Image* Frame::GetImageImpl(int width, int height,
VideoMode::PixelFormat pixelFormat,
wpi::util::PixelFormat pixelFormat,
int requiredJpegQuality, int defaultJpegQuality) {
if (!m_impl) {
return nullptr;
@@ -759,7 +765,7 @@ Image* Frame::GetImageImpl(int width, int height,
// anything else with it. Note that if the destination format is JPEG, we
// still need to do this (unless the width/height/compression were the same,
// in which case we already returned the existing JPEG above).
if (cur->pixelFormat == VideoMode::kMJPEG) {
if (cur->pixelFormat == wpi::util::PixelFormat::kMJPEG) {
cur = ConvertMJPEGToBGR(cur);
}
@@ -784,7 +790,7 @@ Image* Frame::GetImageImpl(int width, int height,
}
bool Frame::GetCv(cv::Mat& image, int width, int height,
VideoMode::PixelFormat pixelFormat) {
wpi::util::PixelFormat pixelFormat) {
Image* rawImage = GetImage(width, height, pixelFormat);
if (!rawImage) {
return false;
@@ -809,7 +815,7 @@ std::unique_ptr<Image> CreateImageFromBGRA(wpi::cs::SourceImpl* source,
cv::Mat finalImage{static_cast<int>(height), static_cast<int>(width), CV_8UC4,
const_cast<uint8_t*>(data), stride};
std::unique_ptr<Image> dest = source->AllocImage(
VideoMode::PixelFormat::kBGR, width, height, width * height * 3);
wpi::util::PixelFormat::kBGR, width, height, width * height * 3);
cv::cvtColor(finalImage, dest->AsMat(), cv::COLOR_BGRA2BGR);
return dest;
}

View File

@@ -12,7 +12,8 @@
#include <vector>
#include "Image.hpp"
#include "wpi/cs/cscore_cpp.hpp"
#include "wpi/util/PixelFormat.hpp"
#include "wpi/util/RawFrame.h"
#include "wpi/util/SmallVector.hpp"
#include "wpi/util/mutex.hpp"
@@ -109,13 +110,13 @@ class Frame {
return m_impl->images[0]->height;
}
int GetOriginalPixelFormat() const {
wpi::util::PixelFormat GetOriginalPixelFormat() const {
if (!m_impl) {
return 0;
return wpi::util::PixelFormat::kUnknown;
}
std::scoped_lock lock(m_impl->mutex);
if (m_impl->images.empty()) {
return 0;
return wpi::util::PixelFormat::kUnknown;
}
return m_impl->images[0]->pixelFormat;
}
@@ -156,7 +157,7 @@ class Frame {
}
Image* GetExistingImage(int width, int height,
VideoMode::PixelFormat pixelFormat) const {
wpi::util::PixelFormat pixelFormat) const {
if (!m_impl) {
return nullptr;
}
@@ -170,7 +171,7 @@ class Frame {
}
Image* GetExistingImage(int width, int height,
VideoMode::PixelFormat pixelFormat,
wpi::util::PixelFormat pixelFormat,
int jpegQuality) const {
if (!m_impl) {
return nullptr;
@@ -186,18 +187,18 @@ class Frame {
Image* GetNearestImage(int width, int height) const;
Image* GetNearestImage(int width, int height,
VideoMode::PixelFormat pixelFormat,
wpi::util::PixelFormat pixelFormat,
int jpegQuality = -1) const;
Image* Convert(Image* image, VideoMode::PixelFormat pixelFormat) {
if (pixelFormat == VideoMode::kMJPEG) {
Image* Convert(Image* image, wpi::util::PixelFormat pixelFormat) {
if (pixelFormat == wpi::util::PixelFormat::kMJPEG) {
return nullptr;
}
return ConvertImpl(image, pixelFormat, -1, 80);
}
Image* ConvertToMJPEG(Image* image, int requiredQuality,
int defaultQuality = 80) {
return ConvertImpl(image, VideoMode::kMJPEG, requiredQuality,
return ConvertImpl(image, wpi::util::PixelFormat::kMJPEG, requiredQuality,
defaultQuality);
}
Image* ConvertMJPEGToBGR(Image* image);
@@ -216,28 +217,28 @@ class Frame {
Image* ConvertY16ToGray(Image* image);
Image* ConvertBGRToBGRA(Image* image);
Image* GetImage(int width, int height, VideoMode::PixelFormat pixelFormat) {
if (pixelFormat == VideoMode::kMJPEG) {
Image* GetImage(int width, int height, wpi::util::PixelFormat pixelFormat) {
if (pixelFormat == wpi::util::PixelFormat::kMJPEG) {
return nullptr;
}
return GetImageImpl(width, height, pixelFormat, -1, 80);
}
Image* GetImageMJPEG(int width, int height, int requiredQuality,
int defaultQuality = 80) {
return GetImageImpl(width, height, VideoMode::kMJPEG, requiredQuality,
defaultQuality);
return GetImageImpl(width, height, wpi::util::PixelFormat::kMJPEG,
requiredQuality, defaultQuality);
}
bool GetCv(cv::Mat& image, VideoMode::PixelFormat pixelFormat) {
bool GetCv(cv::Mat& image, wpi::util::PixelFormat pixelFormat) {
return GetCv(image, GetOriginalWidth(), GetOriginalHeight(), pixelFormat);
}
bool GetCv(cv::Mat& image, int width, int height,
VideoMode::PixelFormat pixelFormat);
wpi::util::PixelFormat pixelFormat);
private:
Image* ConvertImpl(Image* image, VideoMode::PixelFormat pixelFormat,
Image* ConvertImpl(Image* image, wpi::util::PixelFormat pixelFormat,
int requiredJpegQuality, int defaultJpegQuality);
Image* GetImageImpl(int width, int height, VideoMode::PixelFormat pixelFormat,
Image* GetImageImpl(int width, int height, wpi::util::PixelFormat pixelFormat,
int requiredJpegQuality, int defaultJpegQuality);
void DecRef() {
if (m_impl && --(m_impl->refcount) == 0) {

View File

@@ -305,7 +305,7 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::util::raw_istream& is,
// the data directly into it.
unsigned int contentLength = v.value();
auto image =
AllocImage(VideoMode::PixelFormat::kMJPEG, 0, 0, contentLength);
AllocImage(wpi::util::PixelFormat::kMJPEG, 0, 0, contentLength);
is.read(image->data(), contentLength);
if (!m_active || is.has_error()) {
return false;
@@ -325,7 +325,7 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::util::raw_istream& is,
PutError("did not receive a JPEG image", wpi::util::Now());
return false;
}
PutFrame(VideoMode::PixelFormat::kMJPEG, width, height, imageBuf,
PutFrame(wpi::util::PixelFormat::kMJPEG, width, height, imageBuf,
wpi::util::Now());
}
@@ -333,9 +333,9 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::util::raw_istream& is,
// update video mode if not set
std::scoped_lock lock(m_mutex);
if (m_mode.pixelFormat != VideoMode::PixelFormat::kMJPEG ||
if (m_mode.pixelFormat != wpi::util::PixelFormat::kMJPEG ||
m_mode.width == 0 || m_mode.height == 0) {
m_mode.pixelFormat = VideoMode::PixelFormat::kMJPEG;
m_mode.pixelFormat = wpi::util::PixelFormat::kMJPEG;
m_mode.width = width;
m_mode.height = height;
}
@@ -474,9 +474,9 @@ bool HttpCameraImpl::CacheProperties(CS_Status* status) const {
// Pretty typical set of video modes
m_videoModes.clear();
m_videoModes.emplace_back(VideoMode::kMJPEG, 640, 480, 30);
m_videoModes.emplace_back(VideoMode::kMJPEG, 320, 240, 30);
m_videoModes.emplace_back(VideoMode::kMJPEG, 160, 120, 30);
m_videoModes.emplace_back(wpi::util::PixelFormat::kMJPEG, 640, 480, 30);
m_videoModes.emplace_back(wpi::util::PixelFormat::kMJPEG, 320, 240, 30);
m_videoModes.emplace_back(wpi::util::PixelFormat::kMJPEG, 160, 120, 30);
m_properties_cached = true;
return true;
@@ -525,7 +525,7 @@ void HttpCameraImpl::SetExposureManual(int value, CS_Status* status) {
}
bool HttpCameraImpl::SetVideoMode(const VideoMode& mode, CS_Status* status) {
if (mode.pixelFormat != VideoMode::kMJPEG) {
if (mode.pixelFormat != wpi::util::PixelFormat::kMJPEG) {
return false;
}
std::scoped_lock lock(m_mutex);

View File

@@ -5,7 +5,6 @@
#pragma once
#include <atomic>
#include <functional>
#include <initializer_list>
#include <memory>
#include <span>
@@ -15,7 +14,8 @@
#include <vector>
#include "SourceImpl.hpp"
#include "wpi/cs/cscore_cpp.hpp"
#include "wpi/cs/VideoMode.hpp"
#include "wpi/cs/cscore_c.h"
#include "wpi/net/HttpUtil.hpp"
#include "wpi/util/StringMap.hpp"
#include "wpi/util/condition_variable.hpp"

View File

@@ -10,7 +10,7 @@
#include <opencv2/core/core.hpp>
#include "default_init_allocator.hpp"
#include "wpi/cs/cscore_cpp.hpp"
#include "wpi/util/PixelFormat.hpp"
namespace wpi::cs {
@@ -53,20 +53,20 @@ class Image {
cv::Mat AsMat() {
int type;
switch (pixelFormat) {
case VideoMode::kYUYV:
case VideoMode::kRGB565:
case VideoMode::kY16:
case VideoMode::kUYVY:
case wpi::util::PixelFormat::kYUYV:
case wpi::util::PixelFormat::kRGB565:
case wpi::util::PixelFormat::kY16:
case wpi::util::PixelFormat::kUYVY:
type = CV_8UC2;
break;
case VideoMode::kBGR:
case wpi::util::PixelFormat::kBGR:
type = CV_8UC3;
break;
case VideoMode::kBGRA:
case wpi::util::PixelFormat::kBGRA:
type = CV_8UC4;
break;
case VideoMode::kGray:
case VideoMode::kMJPEG:
case wpi::util::PixelFormat::kGray:
case wpi::util::PixelFormat::kMJPEG:
default:
type = CV_8UC1;
break;
@@ -76,18 +76,18 @@ class Image {
int GetStride() const {
switch (pixelFormat) {
case VideoMode::kYUYV:
case VideoMode::kRGB565:
case VideoMode::kY16:
case VideoMode::kUYVY:
case wpi::util::PixelFormat::kYUYV:
case wpi::util::PixelFormat::kRGB565:
case wpi::util::PixelFormat::kY16:
case wpi::util::PixelFormat::kUYVY:
return 2 * width;
case VideoMode::kBGR:
case wpi::util::PixelFormat::kBGR:
return 3 * width;
case VideoMode::kBGRA:
case wpi::util::PixelFormat::kBGRA:
return 4 * width;
case VideoMode::kGray:
case wpi::util::PixelFormat::kGray:
return width;
case VideoMode::kMJPEG:
case wpi::util::PixelFormat::kMJPEG:
default:
return 0;
}
@@ -98,15 +98,16 @@ class Image {
bool Is(int width_, int height_) {
return width == width_ && height == height_;
}
bool Is(int width_, int height_, VideoMode::PixelFormat pixelFormat_) {
bool Is(int width_, int height_, wpi::util::PixelFormat pixelFormat_) {
return width == width_ && height == height_ && pixelFormat == pixelFormat_;
}
bool Is(int width_, int height_, VideoMode::PixelFormat pixelFormat_,
bool Is(int width_, int height_, wpi::util::PixelFormat pixelFormat_,
int jpegQuality_) {
// Consider +/-5 on JPEG quality to be "close enough"
return width == width_ && height == height_ &&
pixelFormat == pixelFormat_ &&
(pixelFormat != VideoMode::kMJPEG || jpegQuality_ == -1 ||
(pixelFormat != wpi::util::PixelFormat::kMJPEG ||
jpegQuality_ == -1 ||
(jpegQuality != -1 && std::abs(jpegQuality - jpegQuality_) <= 5));
}
bool IsLarger(int width_, int height_) {
@@ -122,7 +123,7 @@ class Image {
std::vector<uchar> m_data;
public:
VideoMode::PixelFormat pixelFormat{VideoMode::kUnknown};
wpi::util::PixelFormat pixelFormat{wpi::util::PixelFormat::kUnknown};
int width{0};
int height{0};
int jpegQuality{-1};

View File

@@ -445,28 +445,28 @@ void MjpegServerImpl::ConnThread::SendHTML(wpi::util::raw_ostream& os,
for (auto mode : source.EnumerateVideoModes(&status)) {
os << "<tr><td>";
switch (mode.pixelFormat) {
case VideoMode::kMJPEG:
case wpi::util::PixelFormat::kMJPEG:
os << "MJPEG";
break;
case VideoMode::kYUYV:
case wpi::util::PixelFormat::kYUYV:
os << "YUYV";
break;
case VideoMode::kRGB565:
case wpi::util::PixelFormat::kRGB565:
os << "RGB565";
break;
case VideoMode::kBGR:
case wpi::util::PixelFormat::kBGR:
os << "BGR";
break;
case VideoMode::kBGRA:
case wpi::util::PixelFormat::kBGRA:
os << "BGRA";
break;
case VideoMode::kGray:
case wpi::util::PixelFormat::kGray:
os << "gray";
break;
case VideoMode::kY16:
case wpi::util::PixelFormat::kY16:
os << "Y16";
break;
case VideoMode::kUYVY:
case wpi::util::PixelFormat::kUYVY:
os << "UYVY";
break;
default:
@@ -565,25 +565,25 @@ void MjpegServerImpl::ConnThread::SendJSON(wpi::util::raw_ostream& os,
os << '{';
os << "\n\"pixelFormat\": \"";
switch (mode.pixelFormat) {
case VideoMode::kMJPEG:
case wpi::util::PixelFormat::kMJPEG:
os << "MJPEG";
break;
case VideoMode::kYUYV:
case wpi::util::PixelFormat::kYUYV:
os << "YUYV";
break;
case VideoMode::kRGB565:
case wpi::util::PixelFormat::kRGB565:
os << "RGB565";
break;
case VideoMode::kBGR:
case wpi::util::PixelFormat::kBGR:
os << "BGR";
break;
case VideoMode::kGray:
case wpi::util::PixelFormat::kGray:
os << "gray";
break;
case VideoMode::kY16:
case wpi::util::PixelFormat::kY16:
os << "Y16";
break;
case VideoMode::kUYVY:
case wpi::util::PixelFormat::kUYVY:
os << "UYVY";
break;
default:
@@ -752,15 +752,15 @@ void MjpegServerImpl::ConnThread::SendStream(wpi::net::raw_socket_ostream& os) {
bool addDHT = false;
size_t locSOF = size;
switch (image->pixelFormat) {
case VideoMode::kMJPEG:
case wpi::util::PixelFormat::kMJPEG:
// Determine if we need to add DHT to it, and allocate enough space
// for adding it if required.
addDHT = JpegNeedsDHT(data, &size, &locSOF);
break;
case VideoMode::kUYVY:
case VideoMode::kRGB565:
case VideoMode::kYUYV:
case VideoMode::kY16:
case wpi::util::PixelFormat::kUYVY:
case wpi::util::PixelFormat::kRGB565:
case wpi::util::PixelFormat::kYUYV:
case wpi::util::PixelFormat::kY16:
default:
// Bad frame; sleep for 10 ms so we don't consume all processor time.
std::this_thread::sleep_for(std::chrono::milliseconds(10));

View File

@@ -13,12 +13,7 @@
#include "SinkImpl.hpp"
#include "wpi/net/NetworkAcceptor.hpp"
#include "wpi/net/NetworkStream.hpp"
#include "wpi/net/raw_socket_ostream.hpp"
#include "wpi/util/SafeThread.hpp"
#include "wpi/util/SmallVector.hpp"
#include "wpi/util/raw_istream.hpp"
#include "wpi/util/raw_ostream.hpp"
namespace wpi::cs {

View File

@@ -8,7 +8,8 @@
#include <utility>
#include "Handle.hpp"
#include "wpi/cs/cscore_cpp.hpp"
#include "wpi/cs/RawEvent.hpp"
#include "wpi/cs/VideoMode.hpp"
#include "wpi/util/CallbackManager.hpp"
namespace wpi::cs {

View File

@@ -13,7 +13,7 @@
#include <vector>
#include "PropertyImpl.hpp"
#include "wpi/cs/cscore_cpp.hpp"
#include "wpi/cs/cscore_c.h"
#include "wpi/util/StringMap.hpp"
#include "wpi/util/json_fwd.hpp"
#include "wpi/util/mutex.hpp"

View File

@@ -100,7 +100,7 @@ uint64_t RawSinkImpl::GrabFrameImpl(WPI_RawFrame& rawFrame,
auto width = rawFrame.width;
auto height = rawFrame.height;
auto pixelFormat =
static_cast<VideoMode::PixelFormat>(rawFrame.pixelFormat);
static_cast<wpi::util::PixelFormat>(rawFrame.pixelFormat);
if (width <= 0 || height <= 0) {
width = incomingFrame.GetOriginalWidth();
height = incomingFrame.GetOriginalHeight();
@@ -118,7 +118,7 @@ uint64_t RawSinkImpl::GrabFrameImpl(WPI_RawFrame& rawFrame,
rawFrame.height = newImage->height;
rawFrame.width = newImage->width;
rawFrame.stride = newImage->GetStride();
rawFrame.pixelFormat = newImage->pixelFormat;
rawFrame.pixelFormat = static_cast<int>(newImage->pixelFormat);
rawFrame.size = newImage->size();
std::copy(newImage->data(), newImage->data() + rawFrame.size, rawFrame.data);
rawFrame.timestamp = incomingFrame.GetTime();

View File

@@ -13,8 +13,7 @@
#include "Frame.hpp"
#include "SinkImpl.hpp"
#include "wpi/cs/cscore_raw.h"
#include "wpi/util/condition_variable.hpp"
#include "wpi/util/RawFrame.h"
namespace wpi::cs {

View File

@@ -24,7 +24,7 @@ RawSourceImpl::~RawSourceImpl() = default;
void RawSourceImpl::PutFrame(const WPI_RawFrame& image) {
auto currentTime = wpi::util::Now();
std::string_view data_view{reinterpret_cast<char*>(image.data), image.size};
SourceImpl::PutFrame(static_cast<VideoMode::PixelFormat>(image.pixelFormat),
SourceImpl::PutFrame(static_cast<wpi::util::PixelFormat>(image.pixelFormat),
image.width, image.height, data_view, currentTime);
}

View File

@@ -5,15 +5,10 @@
#pragma once
#include <atomic>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include "ConfigurableSourceImpl.hpp"
#include "SourceImpl.hpp"
#include "wpi/cs/cscore_raw.h"
namespace wpi::cs {

View File

@@ -11,7 +11,6 @@
#include "SourceImpl.hpp"
#include "wpi/util/Logger.hpp"
#include "wpi/util/json_fwd.hpp"
#include "wpi/util/mutex.hpp"
namespace wpi::cs {

View File

@@ -14,6 +14,7 @@
#include "Log.hpp"
#include "Notifier.hpp"
#include "Telemetry.hpp"
#include "wpi/util/PixelFormat.hpp"
#include "wpi/util/StringExtras.hpp"
#include "wpi/util/json.hpp"
#include "wpi/util/timestamp.hpp"
@@ -149,7 +150,7 @@ VideoMode SourceImpl::GetVideoMode(CS_Status* status) const {
return m_mode;
}
bool SourceImpl::SetPixelFormat(VideoMode::PixelFormat pixelFormat,
bool SourceImpl::SetPixelFormat(wpi::util::PixelFormat pixelFormat,
CS_Status* status) {
auto mode = GetVideoMode(status);
if (!mode) {
@@ -199,21 +200,21 @@ bool SourceImpl::SetConfigJson(const wpi::util::json& config,
try {
auto str = config.at("pixel format").get<std::string>();
if (wpi::util::equals_lower(str, "mjpeg")) {
mode.pixelFormat = wpi::cs::VideoMode::kMJPEG;
mode.pixelFormat = wpi::util::PixelFormat::kMJPEG;
} else if (wpi::util::equals_lower(str, "yuyv")) {
mode.pixelFormat = wpi::cs::VideoMode::kYUYV;
mode.pixelFormat = wpi::util::PixelFormat::kYUYV;
} else if (wpi::util::equals_lower(str, "rgb565")) {
mode.pixelFormat = wpi::cs::VideoMode::kRGB565;
mode.pixelFormat = wpi::util::PixelFormat::kRGB565;
} else if (wpi::util::equals_lower(str, "bgr")) {
mode.pixelFormat = wpi::cs::VideoMode::kBGR;
mode.pixelFormat = wpi::util::PixelFormat::kBGR;
} else if (wpi::util::equals_lower(str, "bgra")) {
mode.pixelFormat = wpi::cs::VideoMode::kBGRA;
mode.pixelFormat = wpi::util::PixelFormat::kBGRA;
} else if (wpi::util::equals_lower(str, "gray")) {
mode.pixelFormat = wpi::cs::VideoMode::kGray;
mode.pixelFormat = wpi::util::PixelFormat::kGray;
} else if (wpi::util::equals_lower(str, "y16")) {
mode.pixelFormat = wpi::cs::VideoMode::kY16;
mode.pixelFormat = wpi::util::PixelFormat::kY16;
} else if (wpi::util::equals_lower(str, "uyvy")) {
mode.pixelFormat = wpi::cs::VideoMode::kUYVY;
mode.pixelFormat = wpi::util::PixelFormat::kUYVY;
} else {
SWARNING("SetConfigJson: could not understand pixel format value '{}'",
str);
@@ -251,19 +252,18 @@ bool SourceImpl::SetConfigJson(const wpi::util::json& config,
}
// if all of video mode is set, use SetVideoMode, otherwise piecemeal it
if (mode.pixelFormat != VideoMode::kUnknown && mode.width != 0 &&
if (mode.pixelFormat != wpi::util::PixelFormat::kUnknown && mode.width != 0 &&
mode.height != 0 && mode.fps != 0) {
SINFO(
"SetConfigJson: setting video mode to pixelFormat {}, width {}, height "
"{}, fps {}",
mode.pixelFormat, mode.width, mode.height, mode.fps);
static_cast<int>(mode.pixelFormat), mode.width, mode.height, mode.fps);
SetVideoMode(mode, status);
} else {
if (mode.pixelFormat != wpi::cs::VideoMode::kUnknown) {
SINFO("SetConfigJson: setting pixelFormat {}", mode.pixelFormat);
SetPixelFormat(
static_cast<wpi::cs::VideoMode::PixelFormat>(mode.pixelFormat),
status);
if (mode.pixelFormat != wpi::util::PixelFormat::kUnknown) {
SINFO("SetConfigJson: setting pixelFormat {}",
static_cast<int>(mode.pixelFormat));
SetPixelFormat(mode.pixelFormat, status);
}
if (mode.width != 0 && mode.height != 0) {
SINFO("SetConfigJson: setting width {}, height {}", mode.width,
@@ -362,28 +362,28 @@ wpi::util::json SourceImpl::GetConfigJsonObject(CS_Status* status) {
// pixel format
std::string_view pixelFormat;
switch (m_mode.pixelFormat) {
case VideoMode::kMJPEG:
case wpi::util::PixelFormat::kMJPEG:
pixelFormat = "mjpeg";
break;
case VideoMode::kYUYV:
case wpi::util::PixelFormat::kYUYV:
pixelFormat = "yuyv";
break;
case VideoMode::kRGB565:
case wpi::util::PixelFormat::kRGB565:
pixelFormat = "rgb565";
break;
case VideoMode::kBGR:
case wpi::util::PixelFormat::kBGR:
pixelFormat = "bgr";
break;
case VideoMode::kBGRA:
case wpi::util::PixelFormat::kBGRA:
pixelFormat = "bgra";
break;
case VideoMode::kGray:
case wpi::util::PixelFormat::kGray:
pixelFormat = "gray";
break;
case VideoMode::kY16:
case wpi::util::PixelFormat::kY16:
pixelFormat = "y16";
break;
case VideoMode::kUYVY:
case wpi::util::PixelFormat::kUYVY:
pixelFormat = "uyvy";
break;
default:
@@ -429,7 +429,7 @@ std::vector<VideoMode> SourceImpl::EnumerateVideoModes(
}
std::unique_ptr<Image> SourceImpl::AllocImage(
VideoMode::PixelFormat pixelFormat, int width, int height, size_t size) {
wpi::util::PixelFormat pixelFormat, int width, int height, size_t size) {
std::unique_ptr<Image> image;
{
std::scoped_lock lock{m_poolMutex};
@@ -464,10 +464,10 @@ std::unique_ptr<Image> SourceImpl::AllocImage(
return image;
}
void SourceImpl::PutFrame(VideoMode::PixelFormat pixelFormat, int width,
void SourceImpl::PutFrame(wpi::util::PixelFormat pixelFormat, int width,
int height, std::string_view data, Frame::Time time,
WPI_TimestampSource timeSrc) {
if (pixelFormat == VideoMode::PixelFormat::kBGRA) {
if (pixelFormat == wpi::util::PixelFormat::kBGRA) {
// Write BGRA as BGR to save a copy
auto image =
CreateImageFromBGRA(this, width, height, width * 4,

View File

@@ -12,11 +12,12 @@
#include <vector>
#include "Frame.hpp"
#include "Handle.hpp"
#include "Image.hpp"
#include "PropertyContainer.hpp"
#include "wpi/cs/cscore_cpp.hpp"
#include "wpi/cs/VideoMode.hpp"
#include "wpi/cs/cscore_c.h"
#include "wpi/util/Logger.hpp"
#include "wpi/util/PixelFormat.hpp"
#include "wpi/util/RawFrame.h"
#include "wpi/util/condition_variable.hpp"
#include "wpi/util/json_fwd.hpp"
@@ -119,7 +120,7 @@ class SourceImpl : public PropertyContainer {
// These have default implementations but can be overridden for custom
// or optimized behavior.
virtual bool SetPixelFormat(VideoMode::PixelFormat pixelFormat,
virtual bool SetPixelFormat(wpi::util::PixelFormat pixelFormat,
CS_Status* status);
virtual bool SetResolution(int width, int height, CS_Status* status);
virtual bool SetFPS(int fps, CS_Status* status);
@@ -131,7 +132,7 @@ class SourceImpl : public PropertyContainer {
std::vector<VideoMode> EnumerateVideoModes(CS_Status* status) const;
std::unique_ptr<Image> AllocImage(VideoMode::PixelFormat pixelFormat,
std::unique_ptr<Image> AllocImage(wpi::util::PixelFormat pixelFormat,
int width, int height, size_t size);
protected:
@@ -139,7 +140,7 @@ class SourceImpl : public PropertyContainer {
void UpdatePropertyValue(int property, bool setString, int value,
std::string_view valueStr) override;
void PutFrame(VideoMode::PixelFormat pixelFormat, int width, int height,
void PutFrame(wpi::util::PixelFormat pixelFormat, int width, int height,
std::string_view data, Frame::Time time,
WPI_TimestampSource timeSrc = WPI_TIMESRC_FRAME_DEQUEUE);
void PutFrame(std::unique_ptr<Image> image, Frame::Time time,

View File

@@ -12,7 +12,6 @@
#include "Notifier.hpp"
#include "SourceImpl.hpp"
#include "wpi/util/DenseMap.hpp"
#include "wpi/util/timestamp.h"
using namespace wpi::cs;

View File

@@ -4,7 +4,7 @@
#pragma once
#include "wpi/cs/cscore_cpp.hpp"
#include "wpi/cs/cscore_c.h"
#include "wpi/util/SafeThread.hpp"
namespace wpi::cs {

View File

@@ -0,0 +1,44 @@
// 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 "wpi/cs/VideoSink.hpp"
#include <vector>
#include <fmt/format.h>
#include "wpi/util/json.hpp"
using namespace wpi::cs;
wpi::util::json VideoSink::GetConfigJsonObject() const {
m_status = 0;
return GetSinkConfigJsonObject(m_handle, &m_status);
}
std::vector<VideoProperty> VideoSink::EnumerateProperties() const {
wpi::util::SmallVector<CS_Property, 32> handles_buf;
CS_Status status = 0;
auto handles = EnumerateSinkProperties(m_handle, handles_buf, &status);
std::vector<VideoProperty> properties;
properties.reserve(handles.size());
for (CS_Property handle : handles) {
properties.emplace_back(VideoProperty{handle});
}
return properties;
}
std::vector<VideoSink> VideoSink::EnumerateSinks() {
wpi::util::SmallVector<CS_Sink, 16> handles_buf;
CS_Status status = 0;
auto handles = ::wpi::cs::EnumerateSinkHandles(handles_buf, &status);
std::vector<VideoSink> sinks;
sinks.reserve(handles.size());
for (int handle : handles) {
sinks.emplace_back(VideoSink{handle});
}
return sinks;
}

View File

@@ -2,13 +2,13 @@
// 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 "wpi/cs/cscore_oo.hpp"
#include "wpi/cs/VideoSource.hpp"
#include <string>
#include <vector>
#include <fmt/format.h>
#include "wpi/cs/VideoSink.hpp"
#include "wpi/util/json.hpp"
using namespace wpi::cs;
@@ -18,11 +18,6 @@ wpi::util::json VideoSource::GetConfigJsonObject() const {
return GetSourceConfigJsonObject(m_handle, &m_status);
}
wpi::util::json VideoSink::GetConfigJsonObject() const {
m_status = 0;
return GetSinkConfigJsonObject(m_handle, &m_status);
}
std::vector<VideoProperty> VideoSource::EnumerateProperties() const {
wpi::util::SmallVector<CS_Property, 32> handles_buf;
CS_Status status = 0;
@@ -61,29 +56,3 @@ std::vector<VideoSource> VideoSource::EnumerateSources() {
}
return sources;
}
std::vector<VideoProperty> VideoSink::EnumerateProperties() const {
wpi::util::SmallVector<CS_Property, 32> handles_buf;
CS_Status status = 0;
auto handles = EnumerateSinkProperties(m_handle, handles_buf, &status);
std::vector<VideoProperty> properties;
properties.reserve(handles.size());
for (CS_Property handle : handles) {
properties.emplace_back(VideoProperty{handle});
}
return properties;
}
std::vector<VideoSink> VideoSink::EnumerateSinks() {
wpi::util::SmallVector<CS_Sink, 16> handles_buf;
CS_Status status = 0;
auto handles = ::wpi::cs::EnumerateSinkHandles(handles_buf, &status);
std::vector<VideoSink> sinks;
sinks.reserve(handles.size());
for (int handle : handles) {
sinks.emplace_back(VideoSink{handle});
}
return sinks;
}

View File

@@ -13,6 +13,7 @@
#include "c_util.hpp"
#include "wpi/cs/cscore_cpp.hpp"
#include "wpi/util/MemAlloc.hpp"
#include "wpi/util/PixelFormat.hpp"
#include "wpi/util/SmallString.hpp"
#include "wpi/util/string.hpp"
@@ -178,9 +179,9 @@ CS_Bool CS_SetSourceVideoModeDiscrete(CS_Source source,
CS_Status* status) {
return wpi::cs::SetSourceVideoMode(
source,
wpi::cs::VideoMode{static_cast<wpi::cs::VideoMode::PixelFormat>(
static_cast<int>(pixelFormat)),
width, height, fps},
wpi::cs::VideoMode{
static_cast<wpi::util::PixelFormat>(static_cast<int>(pixelFormat)),
width, height, fps},
status);
}
@@ -189,8 +190,7 @@ CS_Bool CS_SetSourcePixelFormat(CS_Source source,
CS_Status* status) {
return wpi::cs::SetSourcePixelFormat(
source,
static_cast<wpi::cs::VideoMode::PixelFormat>(
static_cast<int>(pixelFormat)),
static_cast<wpi::util::PixelFormat>(static_cast<int>(pixelFormat)),
status);
}

View File

@@ -17,6 +17,7 @@
#include "SourceImpl.hpp"
#include "Telemetry.hpp"
#include "wpi/net/hostname.hpp"
#include "wpi/util/PixelFormat.hpp"
#include "wpi/util/SmallString.hpp"
#include "wpi/util/json.hpp"
@@ -323,7 +324,7 @@ bool SetSourceVideoMode(CS_Source source, const VideoMode& mode,
return data->source->SetVideoMode(mode, status);
}
bool SetSourcePixelFormat(CS_Source source, VideoMode::PixelFormat pixelFormat,
bool SetSourcePixelFormat(CS_Source source, wpi::util::PixelFormat pixelFormat,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data) {

View File

@@ -10,9 +10,10 @@
#define WPI_RAWFRAME_JNI
#include "org_wpilib_vision_camera_CameraServerJNI.h"
#include "wpi/cs/cscore_raw.h"
#include "wpi/cs/cscore_cpp.hpp"
#include "wpi/cs/cscore_raw.hpp"
#include "wpi/cs/cscore_runloop.hpp"
#include "wpi/util/RawFrame.h"
#include "wpi/util/RawFrame.hpp"
#include "wpi/util/SmallString.hpp"
#include "wpi/util/jni_util.hpp"
@@ -583,10 +584,9 @@ Java_org_wpilib_vision_camera_CameraServerJNI_createRawSource
CS_Status status = 0;
auto val = wpi::cs::CreateRawSource(
JStringRef{env, name}.str(), isCv,
wpi::cs::VideoMode{
static_cast<wpi::cs::VideoMode::PixelFormat>(pixelFormat),
static_cast<int>(width), static_cast<int>(height),
static_cast<int>(fps)},
wpi::cs::VideoMode{static_cast<wpi::util::PixelFormat>(pixelFormat),
static_cast<int>(width), static_cast<int>(height),
static_cast<int>(fps)},
&status);
CheckStatus(env, status);
return val;
@@ -771,9 +771,8 @@ Java_org_wpilib_vision_camera_CameraServerJNI_setSourceVideoMode
CS_Status status = 0;
auto val = wpi::cs::SetSourceVideoMode(
source,
wpi::cs::VideoMode(
static_cast<wpi::cs::VideoMode::PixelFormat>(pixelFormat), width,
height, fps),
wpi::cs::VideoMode(static_cast<wpi::util::PixelFormat>(pixelFormat),
width, height, fps),
&status);
CheckStatus(env, status);
return val;
@@ -790,8 +789,7 @@ Java_org_wpilib_vision_camera_CameraServerJNI_setSourcePixelFormat
{
CS_Status status = 0;
auto val = wpi::cs::SetSourcePixelFormat(
source, static_cast<wpi::cs::VideoMode::PixelFormat>(pixelFormat),
&status);
source, static_cast<wpi::util::PixelFormat>(pixelFormat), &status);
CheckStatus(env, status);
return val;
}

View File

@@ -0,0 +1,229 @@
// 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.
#pragma once
#include <stdint.h>
#include <string_view>
#include <opencv2/core/mat.hpp>
#include "wpi/cs/ImageSink.hpp"
#include "wpi/cs/cscore_raw.hpp"
#include "wpi/util/PixelFormat.hpp"
#include "wpi/util/RawFrame.hpp"
namespace wpi::cs {
/**
* A sink 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) : ImageSink{sink}, pixelFormat{sink.pixelFormat} {}
/**
* 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)
* @param pixelFormat The pixel format to read
*/
explicit CvSink(std::string_view name, wpi::util::PixelFormat pixelFormat =
wpi::util::PixelFormat::kBGR) {
m_handle = CreateRawSink(name, true, &m_status);
this->pixelFormat = pixelFormat;
}
/**
* 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.
*
* @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::util::Now(), and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrame(cv::Mat& image, double timeout = 0.225) {
cv::Mat tmpnam;
auto retVal = GrabFrameDirect(tmpnam);
if (retVal <= 0) {
return retVal;
}
tmpnam.copyTo(image);
return retVal;
}
/**
* 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::util::Now(), and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrameNoTimeout(cv::Mat& image) {
cv::Mat tmpnam;
auto retVal = GrabFrameNoTimeoutDirect(tmpnam);
if (retVal <= 0) {
return retVal;
}
tmpnam.copyTo(image);
return retVal;
}
/**
* 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. 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::util::Now(), and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrameDirect(cv::Mat& image, double timeout = 0.225) {
rawFrame.height = 0;
rawFrame.width = 0;
rawFrame.stride = 0;
rawFrame.pixelFormat = static_cast<int>(pixelFormat);
auto timestamp =
GrabSinkFrameTimeout(m_handle, rawFrame, timeout, &m_status);
if (m_status != CS_OK) {
return 0;
}
image =
cv::Mat{rawFrame.height, rawFrame.width,
GetCvFormat(static_cast<WPI_PixelFormat>(rawFrame.pixelFormat)),
rawFrame.data, static_cast<size_t>(rawFrame.stride)};
return timestamp;
}
/**
* Wait for the next frame and get the image. May block forever.
* 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::util::Now(), and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrameNoTimeoutDirect(cv::Mat& image) {
rawFrame.height = 0;
rawFrame.width = 0;
rawFrame.stride = 0;
rawFrame.pixelFormat = static_cast<int>(pixelFormat);
auto timestamp = GrabSinkFrame(m_handle, rawFrame, &m_status);
if (m_status != CS_OK) {
return 0;
}
image =
cv::Mat{rawFrame.height, rawFrame.width,
GetCvFormat(static_cast<WPI_PixelFormat>(rawFrame.pixelFormat)),
rawFrame.data, static_cast<size_t>(rawFrame.stride)};
return timestamp;
}
/**
* 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. The data is backed by data in the CvSink. It will be invalidated by
* any grabFrame*() call on the sink.
*
* <p>If lastFrameTime is provided and non-zero, the sink will fill image with
* the first frame from the source that is not equal to lastFrameTime. If
* lastFrameTime is zero, the time of the current frame owned by the CvSource
* is used, and this function will block until the connected CvSource provides
* a new frame.
*
* @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::util::Now(), and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrameDirectLastTime(cv::Mat& image, uint64_t lastFrameTime,
double timeout = 0.225) {
rawFrame.height = 0;
rawFrame.width = 0;
rawFrame.stride = 0;
rawFrame.pixelFormat = static_cast<int>(pixelFormat);
auto timestamp = GrabSinkFrameTimeoutLastTime(m_handle, rawFrame, timeout,
lastFrameTime, &m_status);
if (m_status != CS_OK) {
return 0;
}
image =
cv::Mat{rawFrame.height, rawFrame.width,
GetCvFormat(static_cast<WPI_PixelFormat>(rawFrame.pixelFormat)),
rawFrame.data, static_cast<size_t>(rawFrame.stride)};
return timestamp;
}
/**
* Get the last time a frame was grabbed. This uses the same time base as
* wpi::util::Now().
*
* @return Time in 1 us increments.
*/
[[nodiscard]]
uint64_t LastFrameTime() {
return rawFrame.timestamp;
}
/**
* Get the time source for the timestamp the last frame was grabbed at.
*
* @return Time source
*/
[[nodiscard]]
WPI_TimestampSource LastFrameTimeSource() {
return static_cast<WPI_TimestampSource>(rawFrame.timestampSrc);
}
private:
constexpr int GetCvFormat(WPI_PixelFormat pixelFormat) {
int type = 0;
switch (pixelFormat) {
case WPI_PIXFMT_YUYV:
case WPI_PIXFMT_RGB565:
case WPI_PIXFMT_Y16:
case WPI_PIXFMT_UYVY:
type = CV_8UC2;
break;
case WPI_PIXFMT_BGR:
type = CV_8UC3;
break;
case WPI_PIXFMT_BGRA:
type = CV_8UC4;
break;
case WPI_PIXFMT_GRAY:
case WPI_PIXFMT_MJPEG:
default:
type = CV_8UC1;
break;
}
return type;
}
wpi::util::RawFrame rawFrame;
wpi::util::PixelFormat pixelFormat;
};
} // namespace wpi::cs

View File

@@ -0,0 +1,188 @@
// 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.
#pragma once
#include <string_view>
#include <opencv2/core/mat.hpp>
#include "wpi/cs/ImageSource.hpp"
#include "wpi/cs/VideoMode.hpp"
#include "wpi/cs/cscore_raw.hpp"
namespace wpi::cs {
/**
* A source for user code to provide OpenCV images as video frames.
*
* 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:
CvSource() = default;
/**
* Create an OpenCV source.
*
* @param name Source name (arbitrary unique identifier)
* @param mode Video mode being generated
*/
CvSource(std::string_view name, const VideoMode& mode) {
m_handle = CreateRawSource(name, true, mode, &m_status);
}
/**
* Create an OpenCV source.
*
* @param name Source name (arbitrary unique identifier)
* @param pixelFormat Pixel format
* @param width width
* @param height height
* @param fps fps
*/
CvSource(std::string_view name, wpi::util::PixelFormat pixelFormat, int width,
int height, int fps) {
m_handle = CreateRawSource(
name, true, VideoMode{pixelFormat, width, height, fps}, &m_status);
}
/**
* Put an OpenCV image and notify sinks
*
* <p>
* The image format is guessed from the number of channels. The channel
* mapping is as follows. 1: kGray 2: kYUYV 3: BGR 4: BGRA Any other channel
* numbers will throw an error. If your image is an in alternate format, use
* the overload that takes a PixelFormat.
*
* @param image OpenCV Image
*/
void 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::util::PixelFormat format;
if (channels == 1) {
// 1 channel is assumed Grayscale
format = wpi::util::PixelFormat::kGray;
} else if (channels == 2) {
// 2 channels is assumed YUYV
format = wpi::util::PixelFormat::kYUYV;
} else if (channels == 3) {
// 3 channels is assumed BGR
format = wpi::util::PixelFormat::kBGR;
} else if (channels == 4) {
// 4 channels is assumed BGRA
format = wpi::util::PixelFormat::kBGRA;
} else {
// TODO Error
return;
}
PutFrame(finalImage, format, true);
}
/**
* Put an OpenCV image and notify sinks.
*
* <p>
* The format of the Mat must match the PixelFormat. You will corrupt memory
* if they dont. With skipVerification false, we will verify the number of
* channels matches the pixel format. If skipVerification is true, this step
* is skipped and is passed straight through.
*
* @param image OpenCV image
* @param pixelFormat The pixel format of the image
* @param skipVerification skip verifying pixel format
*/
void PutFrame(cv::Mat& image, wpi::util::PixelFormat pixelFormat,
bool skipVerification) {
// 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);
}
if (!skipVerification) {
if (!VerifyFormat(finalImage, pixelFormat)) {
// TODO Error
return;
}
}
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() * finalImage.channels();
frame.width = finalImage.cols;
frame.height = finalImage.rows;
frame.stride = finalImage.step;
frame.pixelFormat = static_cast<int>(pixelFormat);
m_status = 0;
PutSourceFrame(m_handle, frame, &m_status);
}
private:
static bool VerifyFormat(cv::Mat& image, wpi::util::PixelFormat pixelFormat) {
int channels = image.channels();
switch (pixelFormat) {
case wpi::util::PixelFormat::kBGR:
if (channels == 3) {
return true;
}
break;
case wpi::util::PixelFormat::kBGRA:
if (channels == 4) {
return true;
}
break;
case wpi::util::PixelFormat::kGray:
if (channels == 1) {
return true;
}
break;
case wpi::util::PixelFormat::kRGB565:
if (channels == 2) {
return true;
}
break;
case wpi::util::PixelFormat::kUYVY:
if (channels == 2) {
return true;
}
break;
case wpi::util::PixelFormat::kY16:
if (channels == 2) {
return true;
}
break;
case wpi::util::PixelFormat::kYUYV:
if (channels == 2) {
return true;
}
break;
case wpi::util::PixelFormat::kMJPEG:
if (channels == 1) {
return true;
}
break;
default:
break;
}
return false;
}
};
} // namespace wpi::cs

View File

@@ -0,0 +1,151 @@
// 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.
#pragma once
#include <initializer_list>
#include <span>
#include <string>
#include <string_view>
#include <vector>
#include "wpi/cs/VideoCamera.hpp"
#include "wpi/cs/cscore_cpp.hpp"
namespace wpi::cs {
/**
* A source that represents a MJPEG-over-HTTP (IP) camera.
*/
class HttpCamera : public VideoCamera {
public:
/**
* HTTP camera kind.
*/
enum HttpCameraKind {
/// Unknown camera kind.
kUnknown = CS_HTTP_UNKNOWN,
/// MJPG Streamer camera.
kMJPGStreamer = CS_HTTP_MJPGSTREAMER,
/// CS Core camera.
kCSCore = CS_HTTP_CSCORE,
};
/**
* Create a source for a MJPEG-over-HTTP (IP) camera.
*
* @param name Source name (arbitrary unique identifier)
* @param url Camera URL (e.g. "http://10.x.y.11/video/stream.mjpg")
* @param kind Camera kind (e.g. kAxis)
*/
HttpCamera(std::string_view name, std::string_view url,
HttpCameraKind kind = kUnknown) {
m_handle = CreateHttpCamera(
name, url, static_cast<CS_HttpCameraKind>(static_cast<int>(kind)),
&m_status);
}
/**
* Create a source for a MJPEG-over-HTTP (IP) camera.
*
* @param name Source name (arbitrary unique identifier)
* @param url Camera URL (e.g. "http://10.x.y.11/video/stream.mjpg")
* @param kind Camera kind (e.g. kAxis)
*/
HttpCamera(std::string_view name, const char* url,
HttpCameraKind kind = kUnknown) {
m_handle = CreateHttpCamera(
name, url, static_cast<CS_HttpCameraKind>(static_cast<int>(kind)),
&m_status);
}
/**
* Create a source for a MJPEG-over-HTTP (IP) camera.
*
* @param name Source name (arbitrary unique identifier)
* @param url Camera URL (e.g. "http://10.x.y.11/video/stream.mjpg")
* @param kind Camera kind (e.g. kAxis)
*/
HttpCamera(std::string_view name, const std::string& url,
HttpCameraKind kind = kUnknown)
: HttpCamera(name, std::string_view{url}, kind) {}
/**
* Create a source for a MJPEG-over-HTTP (IP) camera.
*
* @param name Source name (arbitrary unique identifier)
* @param urls Array of Camera URLs
* @param kind Camera kind (e.g. kAxis)
*/
HttpCamera(std::string_view name, std::span<const std::string> urls,
HttpCameraKind kind = kUnknown) {
m_handle = CreateHttpCamera(
name, urls, static_cast<CS_HttpCameraKind>(static_cast<int>(kind)),
&m_status);
}
/**
* Create a source for a MJPEG-over-HTTP (IP) camera.
*
* @param name Source name (arbitrary unique identifier)
* @param urls Array of Camera URLs
* @param kind Camera kind (e.g. kAxis)
*/
template <typename T>
HttpCamera(std::string_view name, std::initializer_list<T> urls,
HttpCameraKind kind = kUnknown) {
std::vector<std::string> vec;
vec.reserve(urls.size());
for (const auto& url : urls) {
vec.emplace_back(url);
}
m_handle = CreateHttpCamera(
name, vec, static_cast<CS_HttpCameraKind>(static_cast<int>(kind)),
&m_status);
}
/**
* Get the kind of HTTP camera.
*
* <p>Autodetection can result in returning a different value than the camera
* was created with.
*/
HttpCameraKind GetHttpCameraKind() const {
m_status = 0;
return static_cast<HttpCameraKind>(
static_cast<int>(::wpi::cs::GetHttpCameraKind(m_handle, &m_status)));
}
/**
* Change the URLs used to connect to the camera.
*/
void SetUrls(std::span<const std::string> urls) {
m_status = 0;
::wpi::cs::SetHttpCameraUrls(m_handle, urls, &m_status);
}
/**
* Change the URLs used to connect to the camera.
*/
template <typename T>
void SetUrls(std::initializer_list<T> urls) {
std::vector<std::string> vec;
vec.reserve(urls.size());
for (const auto& url : urls) {
vec.emplace_back(url);
}
m_status = 0;
::wpi::cs::SetHttpCameraUrls(m_handle, vec, &m_status);
}
/**
* Get the URLs used to connect to the camera.
*/
std::vector<std::string> GetUrls() const {
m_status = 0;
return ::wpi::cs::GetHttpCameraUrls(m_handle, &m_status);
}
};
} // namespace wpi::cs

View File

@@ -0,0 +1,55 @@
// 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.
#pragma once
#include <string>
#include <string_view>
#include "wpi/cs/VideoSink.hpp"
#include "wpi/cs/cscore_cpp.hpp"
namespace wpi::cs {
/**
* A base class for single image reading sinks.
*/
class ImageSink : public VideoSink {
protected:
ImageSink() = default;
public:
/**
* Set sink description.
*
* @param description Description
*/
void SetDescription(std::string_view description) {
m_status = 0;
SetSinkDescription(m_handle, description, &m_status);
}
/**
* Get error string. Call this if WaitForFrame() returns 0 to determine
* what the error is.
*/
std::string GetError() const {
m_status = 0;
return GetSinkError(m_handle, &m_status);
}
/**
* Enable or disable getting new frames.
*
* <p>Disabling will cause processFrame (for callback-based CvSinks) to not
* be called and WaitForFrame() to not return. This can be used to save
* processor resources when frames are not needed.
*/
void SetEnabled(bool enabled) {
m_status = 0;
SetSinkEnabled(m_handle, enabled, &m_status);
}
};
} // namespace wpi::cs

View File

@@ -0,0 +1,170 @@
// 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.
#pragma once
#include <initializer_list>
#include <span>
#include <string>
#include <string_view>
#include <vector>
#include "wpi/cs/VideoProperty.hpp"
#include "wpi/cs/VideoSource.hpp"
#include "wpi/cs/cscore_cpp.hpp"
namespace wpi::cs {
/**
* A base class for single image providing sources.
*/
class ImageSource : public VideoSource {
protected:
ImageSource() = default;
public:
/**
* Signal sinks that an error has occurred. This should be called instead
* of NotifyFrame when an error occurs.
*
* @param msg Notification message.
*/
void NotifyError(std::string_view msg) {
m_status = 0;
NotifySourceError(m_handle, msg, &m_status);
}
/**
* Set source connection status. Defaults to true.
*
* @param connected True for connected, false for disconnected
*/
void SetConnected(bool connected) {
m_status = 0;
SetSourceConnected(m_handle, connected, &m_status);
}
/**
* Set source description.
*
* @param description Description
*/
void SetDescription(std::string_view description) {
m_status = 0;
SetSourceDescription(m_handle, description, &m_status);
}
/**
* Create a property.
*
* @param name Property name
* @param kind Property kind
* @param minimum Minimum value
* @param maximum Maximum value
* @param step Step value
* @param defaultValue Default value
* @param value Current value
* @return Property
*/
VideoProperty CreateProperty(std::string_view name, VideoProperty::Kind kind,
int minimum, int maximum, int step,
int defaultValue, int value) {
m_status = 0;
return VideoProperty{CreateSourceProperty(
m_handle, name, static_cast<CS_PropertyKind>(static_cast<int>(kind)),
minimum, maximum, step, defaultValue, value, &m_status)};
}
/**
* Create an integer property.
*
* @param name Property name
* @param minimum Minimum value
* @param maximum Maximum value
* @param step Step value
* @param defaultValue Default value
* @param value Current value
* @return Property
*/
VideoProperty CreateIntegerProperty(std::string_view name, int minimum,
int maximum, int step, int defaultValue,
int value) {
m_status = 0;
return VideoProperty{CreateSourceProperty(
m_handle, name,
static_cast<CS_PropertyKind>(
static_cast<int>(VideoProperty::Kind::kInteger)),
minimum, maximum, step, defaultValue, value, &m_status)};
}
/**
* Create a boolean property.
*
* @param name Property name
* @param defaultValue Default value
* @param value Current value
* @return Property
*/
VideoProperty CreateBooleanProperty(std::string_view name, bool defaultValue,
bool value) {
m_status = 0;
return VideoProperty{CreateSourceProperty(
m_handle, name,
static_cast<CS_PropertyKind>(
static_cast<int>(VideoProperty::Kind::kBoolean)),
0, 1, 1, defaultValue ? 1 : 0, value ? 1 : 0, &m_status)};
}
/**
* Create a string property.
*
* @param name Property name
* @param value Current value
* @return Property
*/
VideoProperty CreateStringProperty(std::string_view name,
std::string_view value) {
m_status = 0;
auto prop = VideoProperty{CreateSourceProperty(
m_handle, name,
static_cast<CS_PropertyKind>(
static_cast<int>(VideoProperty::Kind::kString)),
0, 0, 0, 0, 0, &m_status)};
prop.SetString(value);
return prop;
}
/**
* Configure enum property choices.
*
* @param property Property
* @param choices Choices
*/
void SetEnumPropertyChoices(const VideoProperty& property,
std::span<const std::string> choices) {
m_status = 0;
SetSourceEnumPropertyChoices(m_handle, property.m_handle, choices,
&m_status);
}
/**
* Configure enum property choices.
*
* @param property Property
* @param choices Choices
*/
template <typename T>
void SetEnumPropertyChoices(const VideoProperty& property,
std::initializer_list<T> choices) {
std::vector<std::string> vec;
vec.reserve(choices.size());
for (const auto& choice : choices) {
vec.emplace_back(choice);
}
m_status = 0;
SetSourceEnumPropertyChoices(m_handle, property.m_handle, vec, &m_status);
}
};
} // namespace wpi::cs

View File

@@ -0,0 +1,120 @@
// 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.
#pragma once
#include <string>
#include <string_view>
#include "wpi/cs/VideoSink.hpp"
#include "wpi/cs/cscore_cpp.hpp"
namespace wpi::cs {
/**
* A sink that acts as a MJPEG-over-HTTP network server.
*/
class MjpegServer : public VideoSink {
public:
MjpegServer() = default;
/**
* Create a MJPEG-over-HTTP server sink.
*
* @param name Sink name (arbitrary unique identifier)
* @param listenAddress TCP listen address (empty string for all addresses)
* @param port TCP port number
*/
MjpegServer(std::string_view name, std::string_view listenAddress, int port) {
m_handle = CreateMjpegServer(name, listenAddress, port, &m_status);
}
/**
* Create a MJPEG-over-HTTP server sink.
*
* @param name Sink name (arbitrary unique identifier)
* @param port TCP port number
*/
MjpegServer(std::string_view name, int port) : MjpegServer(name, "", port) {}
/**
* Get the listen address of the server.
*/
std::string GetListenAddress() const {
m_status = 0;
return wpi::cs::GetMjpegServerListenAddress(m_handle, &m_status);
}
/**
* Get the port number of the server.
*/
int GetPort() const {
m_status = 0;
return wpi::cs::GetMjpegServerPort(m_handle, &m_status);
}
/**
* Set the stream resolution for clients that don't specify it.
*
* <p>It is not necessary to set this if it is the same as the source
* resolution.
*
* <p>Setting this different than the source resolution will result in
* increased CPU usage, particularly for MJPEG source cameras, as it will
* decompress, resize, and recompress the image, instead of using the
* camera's MJPEG image directly.
*
* @param width width, 0 for unspecified
* @param height height, 0 for unspecified
*/
void SetResolution(int width, int height) {
m_status = 0;
SetProperty(GetSinkProperty(m_handle, "width", &m_status), width,
&m_status);
SetProperty(GetSinkProperty(m_handle, "height", &m_status), height,
&m_status);
}
/**
* Set the stream frames per second (FPS) for clients that don't specify it.
*
* <p>It is not necessary to set this if it is the same as the source FPS.
*
* @param fps FPS, 0 for unspecified
*/
void SetFPS(int fps) {
m_status = 0;
SetProperty(GetSinkProperty(m_handle, "fps", &m_status), fps, &m_status);
}
/**
* Set the compression for clients that don't specify it.
*
* <p>Setting this will result in increased CPU usage for MJPEG source cameras
* as it will decompress and recompress the image instead of using the
* camera's MJPEG image directly.
*
* @param quality JPEG compression quality (0-100), -1 for unspecified
*/
void SetCompression(int quality) {
m_status = 0;
SetProperty(GetSinkProperty(m_handle, "compression", &m_status), quality,
&m_status);
}
/**
* Set the default compression used for non-MJPEG sources. If not set,
* 80 is used. This function has no effect on MJPEG source cameras; use
* SetCompression() instead to force recompression of MJPEG source images.
*
* @param quality JPEG compression quality (0-100)
*/
void SetDefaultCompression(int quality) {
m_status = 0;
SetProperty(GetSinkProperty(m_handle, "default_compression", &m_status),
quality, &m_status);
}
};
} // namespace wpi::cs

View File

@@ -0,0 +1,91 @@
// 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.
#pragma once
#include <string>
#include <string_view>
#include "wpi/cs/VideoMode.hpp"
#include "wpi/cs/cscore_c.h"
namespace wpi::cs {
/**
* Listener event
*/
struct RawEvent {
enum Kind {
kSourceCreated = CS_SOURCE_CREATED,
kSourceDestroyed = CS_SOURCE_DESTROYED,
kSourceConnected = CS_SOURCE_CONNECTED,
kSourceDisconnected = CS_SOURCE_DISCONNECTED,
kSourceVideoModesUpdated = CS_SOURCE_VIDEOMODES_UPDATED,
kSourceVideoModeChanged = CS_SOURCE_VIDEOMODE_CHANGED,
kSourcePropertyCreated = CS_SOURCE_PROPERTY_CREATED,
kSourcePropertyValueUpdated = CS_SOURCE_PROPERTY_VALUE_UPDATED,
kSourcePropertyChoicesUpdated = CS_SOURCE_PROPERTY_CHOICES_UPDATED,
kSinkSourceChanged = CS_SINK_SOURCE_CHANGED,
kSinkCreated = CS_SINK_CREATED,
kSinkDestroyed = CS_SINK_DESTROYED,
kSinkEnabled = CS_SINK_ENABLED,
kSinkDisabled = CS_SINK_DISABLED,
kNetworkInterfacesChanged = CS_NETWORK_INTERFACES_CHANGED,
kTelemetryUpdated = CS_TELEMETRY_UPDATED,
kSinkPropertyCreated = CS_SINK_PROPERTY_CREATED,
kSinkPropertyValueUpdated = CS_SINK_PROPERTY_VALUE_UPDATED,
kSinkPropertyChoicesUpdated = CS_SINK_PROPERTY_CHOICES_UPDATED,
kUsbCamerasChanged = CS_USB_CAMERAS_CHANGED
};
RawEvent() = default;
explicit RawEvent(RawEvent::Kind kind_) : kind{kind_} {}
RawEvent(std::string_view name_, CS_Handle handle_, RawEvent::Kind kind_)
: kind{kind_}, name{name_} {
if (kind_ == kSinkCreated || kind_ == kSinkDestroyed ||
kind_ == kSinkEnabled || kind_ == kSinkDisabled) {
sinkHandle = handle_;
} else {
sourceHandle = handle_;
}
}
RawEvent(std::string_view name_, CS_Source source_, const VideoMode& mode_)
: kind{kSourceVideoModeChanged},
sourceHandle{source_},
name{name_},
mode{mode_} {}
RawEvent(std::string_view name_, CS_Source source_, RawEvent::Kind kind_,
CS_Property property_, CS_PropertyKind propertyKind_, int value_,
std::string_view valueStr_)
: kind{kind_},
sourceHandle{source_},
name{name_},
propertyHandle{property_},
propertyKind{propertyKind_},
value{value_},
valueStr{valueStr_} {}
Kind kind;
// Valid for kSource* and kSink* respectively
CS_Source sourceHandle = CS_INVALID_HANDLE;
CS_Sink sinkHandle = CS_INVALID_HANDLE;
// Source/sink/property name
std::string name;
// Fields for kSourceVideoModeChanged event
VideoMode mode;
// Fields for kSourceProperty* events
CS_Property propertyHandle;
CS_PropertyKind propertyKind;
int value;
std::string valueStr;
// Listener that was triggered
CS_Listener listener{0};
};
} // namespace wpi::cs

View File

@@ -0,0 +1,107 @@
// 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.
#pragma once
#include <functional>
#include <string_view>
#include "wpi/cs/ImageSink.hpp"
#include "wpi/cs/cscore_raw.hpp"
#include "wpi/util/RawFrame.hpp"
namespace wpi::cs {
/**
* A sink for user code to accept video frames as raw bytes.
*
* This is a complex API, most cases should use CvSource.
*/
class RawSink : public ImageSink {
public:
RawSink() = default;
/**
* Create a sink for accepting raw images.
*
* <p>GrabFrame() must be called on the created sink to get each new
* image.
*
* @param name Source name (arbitrary unique identifier)
*/
explicit RawSink(std::string_view name) {
m_handle = CreateRawSink(name, false, &m_status);
}
/**
* Create a sink for accepting raws 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().
*/
RawSink(std::string_view name,
std::function<void(uint64_t time)> processFrame) {
m_handle = CreateRawSinkCallback(name, false, processFrame, &m_status);
}
protected:
/**
* 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::util::Now(), and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrame(wpi::util::RawFrame& image, double timeout = 0.225) const {
m_status = 0;
return GrabSinkFrameTimeout(m_handle, image, timeout, &m_status);
}
/**
* 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::util::Now(), and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrameNoTimeout(wpi::util::RawFrame& image) const {
m_status = 0;
return GrabSinkFrame(m_handle, image, &m_status);
}
/**
* 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.
*
* <p>If lastFrameTime is provided and non-zero, the sink will fill image with
* the first frame from the source that is not equal to lastFrameTime. If
* lastFrameTime is zero, the time of the current frame owned by the CvSource
* is used, and this function will block until the connected CvSource provides
* a new frame.
*
* @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::util::Now(), and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrameLastTime(wpi::util::RawFrame& image, uint64_t lastFrameTime,
double timeout = 0.225) const {
m_status = 0;
return GrabSinkFrameTimeoutLastTime(m_handle, image, timeout, lastFrameTime,
&m_status);
}
};
} // namespace wpi::cs

View File

@@ -0,0 +1,62 @@
// 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.
#pragma once
#include <string_view>
#include "wpi/cs/ImageSource.hpp"
#include "wpi/cs/VideoMode.hpp"
#include "wpi/cs/cscore_raw.hpp"
#include "wpi/util/RawFrame.hpp"
namespace wpi::cs {
/**
* A source for user code to provide video frames as raw bytes.
*
* This is a complex API, most cases should use CvSource.
*/
class RawSource : public ImageSource {
public:
RawSource() = default;
/**
* Create a raw frame source.
*
* @param name Source name (arbitrary unique identifier)
* @param mode Video mode being generated
*/
RawSource(std::string_view name, const VideoMode& mode) {
m_handle = CreateRawSource(name, false, mode, &m_status);
}
/**
* Create a raw frame source.
*
* @param name Source name (arbitrary unique identifier)
* @param pixelFormat Pixel format
* @param width width
* @param height height
* @param fps fps
*/
RawSource(std::string_view name, VideoMode::PixelFormat pixelFormat,
int width, int height, int fps) {
m_handle = CreateRawSource(
name, false, VideoMode{pixelFormat, width, height, fps}, &m_status);
}
protected:
/**
* Put a raw image and notify sinks.
*
* @param image raw frame image
*/
void PutFrame(wpi::util::RawFrame& image) {
m_status = 0;
PutSourceFrame(m_handle, image, &m_status);
}
};
} // namespace wpi::cs

View File

@@ -0,0 +1,89 @@
// 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.
#pragma once
#include <string>
#include <string_view>
#include <vector>
#include "wpi/cs/VideoCamera.hpp"
#include "wpi/cs/cscore_cpp.hpp"
namespace wpi::cs {
/**
* A source that represents a USB camera.
*/
class UsbCamera : public VideoCamera {
public:
UsbCamera() = default;
/**
* Create a source for a USB camera based on device number.
*
* @param name Source name (arbitrary unique identifier)
* @param dev Device number (e.g. 0 for /dev/video0)
*/
UsbCamera(std::string_view name, int dev) {
m_handle = CreateUsbCameraDev(name, dev, &m_status);
}
/**
* Create a source for a USB camera based on device path.
*
* @param name Source name (arbitrary unique identifier)
* @param path Path to device (e.g. "/dev/video0" on Linux)
*/
UsbCamera(std::string_view name, std::string_view path) {
m_handle = CreateUsbCameraPath(name, path, &m_status);
}
/**
* Enumerate USB cameras on the local system.
*
* @return Vector of USB camera information (one for each camera)
*/
static std::vector<UsbCameraInfo> EnumerateUsbCameras() {
CS_Status status = 0;
return ::wpi::cs::EnumerateUsbCameras(&status);
}
/**
* Change the path to the device.
*/
void SetPath(std::string_view path) {
m_status = 0;
return ::wpi::cs::SetUsbCameraPath(m_handle, path, &m_status);
}
/**
* Get the path to the device.
*/
std::string GetPath() const {
m_status = 0;
return ::wpi::cs::GetUsbCameraPath(m_handle, &m_status);
}
/**
* Get the full camera information for the device.
*/
UsbCameraInfo GetInfo() const {
m_status = 0;
return ::wpi::cs::GetUsbCameraInfo(m_handle, &m_status);
}
/**
* Set how verbose the camera connection messages are.
*
* @param level 0=don't display Connecting message, 1=do display message
*/
void SetConnectVerbose(int level) {
m_status = 0;
SetProperty(GetSourceProperty(m_handle, "connect_verbose", &m_status),
level, &m_status);
}
};
} // namespace wpi::cs

View File

@@ -0,0 +1,30 @@
// 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.
#pragma once
#include <string>
#include <vector>
namespace wpi::cs {
/**
* USB camera information
*/
struct UsbCameraInfo {
/** Device number (e.g. N in '/dev/videoN' on Linux) */
int dev = -1;
/** Path to device if available (e.g. '/dev/video0' on Linux) */
std::string path;
/** Vendor/model name of the camera as provided by the USB driver */
std::string name;
/** Other path aliases to device (e.g. '/dev/v4l/by-id/...' etc on Linux) */
std::vector<std::string> otherPaths;
/** USB Vendor Id */
int vendorId = -1;
/** USB Product Id */
int productId = -1;
};
} // namespace wpi::cs

View File

@@ -0,0 +1,103 @@
// 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.
#pragma once
#include "wpi/cs/VideoSource.hpp"
#include "wpi/cs/cscore_cpp.hpp"
namespace wpi::cs {
/**
* A source that represents a video camera.
*/
class VideoCamera : public VideoSource {
public:
/**
* White balance.
*/
enum WhiteBalance {
/// Fixed indoor white balance.
kFixedIndoor = 3000,
/// Fixed outdoor white balance 1.
kFixedOutdoor1 = 4000,
/// Fixed outdoor white balance 2.
kFixedOutdoor2 = 5000,
/// Fixed fluorescent white balance 1.
kFixedFluorescent1 = 5100,
/// Fixed fluorescent white balance 2.
kFixedFlourescent2 = 5200
};
VideoCamera() = default;
/**
* Set the brightness, as a percentage (0-100).
*/
void SetBrightness(int brightness) {
m_status = 0;
SetCameraBrightness(m_handle, brightness, &m_status);
}
/**
* Get the brightness, as a percentage (0-100).
*/
int GetBrightness() {
m_status = 0;
return GetCameraBrightness(m_handle, &m_status);
}
/**
* Set the white balance to auto.
*/
void SetWhiteBalanceAuto() {
m_status = 0;
SetCameraWhiteBalanceAuto(m_handle, &m_status);
}
/**
* Set the white balance to hold current.
*/
void SetWhiteBalanceHoldCurrent() {
m_status = 0;
SetCameraWhiteBalanceHoldCurrent(m_handle, &m_status);
}
/**
* Set the white balance to manual, with specified color temperature.
*/
void SetWhiteBalanceManual(int value) {
m_status = 0;
SetCameraWhiteBalanceManual(m_handle, value, &m_status);
}
/**
* Set the exposure to auto aperture.
*/
void SetExposureAuto() {
m_status = 0;
SetCameraExposureAuto(m_handle, &m_status);
}
/**
* Set the exposure to hold current.
*/
void SetExposureHoldCurrent() {
m_status = 0;
SetCameraExposureHoldCurrent(m_handle, &m_status);
}
/**
* Set the exposure to manual, as a percentage (0-100).
*/
void SetExposureManual(int value) {
m_status = 0;
SetCameraExposureManual(m_handle, value, &m_status);
}
protected:
explicit VideoCamera(CS_Source handle) : VideoSource(handle) {}
};
} // namespace wpi::cs

View File

@@ -0,0 +1,51 @@
// 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.
#pragma once
#include "wpi/cs/VideoProperty.hpp"
#include "wpi/cs/VideoSink.hpp"
#include "wpi/cs/VideoSource.hpp"
#include "wpi/cs/cscore_cpp.hpp"
namespace wpi::cs {
/**
* An event generated by the library and provided to event listeners.
*/
class VideoEvent : public RawEvent {
public:
/**
* Returns the source associated with the event (if any).
*
* @return The source associated with the event (if any).
*/
VideoSource GetSource() const {
CS_Status status = 0;
return VideoSource{sourceHandle == 0 ? 0
: CopySource(sourceHandle, &status)};
}
/**
* Returns the sink associated with the event (if any).
*
* @return The sink associated with the event (if any).
*/
VideoSink GetSink() const {
CS_Status status = 0;
return VideoSink{sinkHandle == 0 ? 0 : CopySink(sinkHandle, &status)};
}
/**
* Returns the property associated with the event (if any).
*
* @return The property associated with the event (if any).
*/
VideoProperty GetProperty() const {
return VideoProperty{propertyHandle,
static_cast<VideoProperty::Kind>(propertyKind)};
}
};
} // namespace wpi::cs

View File

@@ -0,0 +1,69 @@
// 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.
#pragma once
#include <functional>
#include <utility>
#include "wpi/cs/VideoEvent.hpp"
#include "wpi/cs/cscore_cpp.hpp"
namespace wpi::cs {
/**
* An event listener. This calls back to a designated callback function when
* an event matching the specified mask is generated by the library.
*/
class VideoListener {
public:
VideoListener() = default;
/**
* Create an event listener.
*
* @param callback Callback function
* @param eventMask Bitmask of VideoEvent::Kind values
* @param immediateNotify Whether callback should be immediately called with
* a representative set of events for the current library state.
*/
VideoListener(std::function<void(const VideoEvent& event)> callback,
int eventMask, bool immediateNotify) {
CS_Status status = 0;
m_handle = AddListener(
[=](const RawEvent& event) {
callback(static_cast<const VideoEvent&>(event));
},
eventMask, immediateNotify, &status);
}
VideoListener(const VideoListener&) = delete;
VideoListener& operator=(const VideoListener&) = delete;
VideoListener(VideoListener&& other) noexcept : VideoListener() {
swap(*this, other);
}
VideoListener& operator=(VideoListener&& other) noexcept {
swap(*this, other);
return *this;
}
~VideoListener() {
CS_Status status = 0;
if (m_handle != 0) {
RemoveListener(m_handle, &status);
}
}
friend void swap(VideoListener& first, VideoListener& second) noexcept {
using std::swap;
swap(first.m_handle, second.m_handle);
}
private:
CS_Listener m_handle{0};
};
} // namespace wpi::cs

View File

@@ -0,0 +1,55 @@
// 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.
#pragma once
#include "wpi/cs/cscore_c.h"
#include "wpi/util/PixelFormat.hpp"
namespace wpi::cs {
/**
* Video mode
*/
struct VideoMode {
VideoMode() = default;
VideoMode(wpi::util::PixelFormat pixelFormat_, int width_, int height_,
int fps_)
: pixelFormat{pixelFormat_}, width{width_}, height{height_}, fps{fps_} {}
VideoMode(const CS_VideoMode& mode) // NOLINT
: pixelFormat{static_cast<wpi::util::PixelFormat>(mode.pixelFormat)},
width{mode.width},
height{mode.height},
fps{mode.fps} {}
operator CS_VideoMode() const { // NOLINT
CS_VideoMode mode;
mode.pixelFormat = static_cast<int>(pixelFormat);
mode.width = width;
mode.height = height;
mode.fps = fps;
return mode;
}
explicit operator bool() const {
return pixelFormat == wpi::util::PixelFormat::kUnknown;
}
bool operator==(const VideoMode& other) const {
return pixelFormat == other.pixelFormat && width == other.width &&
height == other.height && fps == other.fps;
}
bool CompareWithoutFps(const VideoMode& other) const {
return pixelFormat == other.pixelFormat && width == other.width &&
height == other.height;
}
wpi::util::PixelFormat pixelFormat = wpi::util::PixelFormat::kUnknown;
int width = 0;
int height = 0;
int fps = 0;
};
} // namespace wpi::cs

View File

@@ -0,0 +1,243 @@
// 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.
#pragma once
#include <string>
#include <string_view>
#include <vector>
#include "wpi/cs/cscore_cpp.hpp"
namespace wpi::cs {
/**
* @defgroup cscore_oo cscore C++ object-oriented API
*
* Recommended interface for C++, identical to Java API.
*
* <p>The classes are RAII and handle reference counting internally.
*
* @{
*/
// Forward declarations so friend declarations work correctly
class ImageSource;
class VideoEvent;
class VideoSink;
class VideoSource;
/**
* A source or sink property.
*/
class VideoProperty {
friend class ImageSource;
friend class VideoEvent;
friend class VideoSink;
friend class VideoSource;
public:
enum Kind {
/// No specific property.
kNone = CS_PROP_NONE,
/// Boolean property.
kBoolean = CS_PROP_BOOLEAN,
/// Integer property.
kInteger = CS_PROP_INTEGER,
/// String property.
kString = CS_PROP_STRING,
/// Enum property.
kEnum = CS_PROP_ENUM
};
VideoProperty() = default;
/**
* Returns property name.
*
* @return Property name.
*/
std::string GetName() const {
m_status = 0;
return GetPropertyName(m_handle, &m_status);
}
/**
* Returns property kind.
*
* @return Property kind.
*/
Kind GetKind() const { return m_kind; }
/**
* Returns true if property is valid.
*
* @return True if property is valid.
*/
explicit operator bool() const { return m_kind != kNone; }
/**
* Returns true if property is a boolean.
*
* @return True if property is a boolean.
*/
bool IsBoolean() const { return m_kind == kBoolean; }
/**
* Returns true if property is an integer.
*
* @return True if property is an integer.
*/
bool IsInteger() const { return m_kind == kInteger; }
/**
* Returns true if property is a string.
*
* @return True if property is a string.
*/
bool IsString() const { return m_kind == kString; }
/**
* Returns true if property is an enum.
*
* @return True if property is an enum.
*/
bool IsEnum() const { return m_kind == kEnum; }
/**
* Returns property value.
*
* @return Property value.
*/
int Get() const {
m_status = 0;
return GetProperty(m_handle, &m_status);
}
/**
* Sets property value.
*
* @param value Property value.
*/
void Set(int value) {
m_status = 0;
SetProperty(m_handle, value, &m_status);
}
/**
* Returns property minimum value.
*
* @return Property minimum value.
*/
int GetMin() const {
m_status = 0;
return GetPropertyMin(m_handle, &m_status);
}
/**
* Returns property maximum value.
*
* @return Property maximum value.
*/
int GetMax() const {
m_status = 0;
return GetPropertyMax(m_handle, &m_status);
}
/**
* Returns property step size.
*
* @return Property step size.
*/
int GetStep() const {
m_status = 0;
return GetPropertyStep(m_handle, &m_status);
}
/**
* Returns property default value.
*
* @return Property default value.
*/
int GetDefault() const {
m_status = 0;
return GetPropertyDefault(m_handle, &m_status);
}
/**
* Returns the string property value.
*
* <p>This function is string-specific.
*
* @return The string property value.
*/
std::string GetString() const {
m_status = 0;
return GetStringProperty(m_handle, &m_status);
}
/**
* Returns the string property value as a reference to the given buffer.
*
* This function is string-specific.
*
* @param buf The backing storage to which to write the property value.
* @return The string property value as a reference to the given buffer.
*/
std::string_view GetString(wpi::util::SmallVectorImpl<char>& buf) const {
m_status = 0;
return GetStringProperty(m_handle, buf, &m_status);
}
/**
* Sets the string property value.
*
* This function is string-specific.
*
* @param value String property value.
*/
void SetString(std::string_view value) {
m_status = 0;
SetStringProperty(m_handle, value, &m_status);
}
/**
* Returns the possible values for the enum property value.
*
* This function is enum-specific.
*
* @return The possible values for the enum property value.
*/
std::vector<std::string> GetChoices() const {
m_status = 0;
return GetEnumPropertyChoices(m_handle, &m_status);
}
/**
* Returns the last status.
*
* @return The last status.
*/
CS_Status GetLastStatus() const { return m_status; }
private:
explicit VideoProperty(CS_Property handle) : m_handle(handle) {
m_status = 0;
if (handle == 0) {
m_kind = kNone;
} else {
m_kind = static_cast<Kind>(
static_cast<int>(GetPropertyKind(handle, &m_status)));
}
}
VideoProperty(CS_Property handle, Kind kind)
: m_handle(handle), m_kind(kind) {}
mutable CS_Status m_status{0};
CS_Property m_handle{0};
Kind m_kind{kNone};
};
} // namespace wpi::cs

View File

@@ -0,0 +1,232 @@
// 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.
#pragma once
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "wpi/cs/VideoProperty.hpp"
#include "wpi/cs/VideoSource.hpp"
#include "wpi/cs/cscore_cpp.hpp"
#include "wpi/util/json_fwd.hpp"
namespace wpi::cs {
class VideoEvent;
/**
* A sink for video that accepts a sequence of frames.
*/
class VideoSink {
friend class VideoEvent;
friend class VideoSource;
public:
enum Kind {
/// Unknown sink type.
kUnknown = CS_SINK_UNKNOWN,
/// MJPEG video sink.
kMjpeg = CS_SINK_MJPEG,
/// CV video sink.
kCv = CS_SINK_CV,
/// Raw video sink.
kRaw = CS_SINK_RAW,
};
VideoSink() noexcept = default;
VideoSink(const VideoSink& sink)
: m_handle(sink.m_handle == 0 ? 0 : CopySink(sink.m_handle, &m_status)) {}
VideoSink(VideoSink&& other) noexcept : VideoSink() { swap(*this, other); }
VideoSink& operator=(VideoSink other) noexcept {
swap(*this, other);
return *this;
}
~VideoSink() {
m_status = 0;
if (m_handle != 0) {
ReleaseSink(m_handle, &m_status);
}
}
/**
* Returns true if the VideoSink is valid.
*
* @return True if the VideoSink is valid.
*/
explicit operator bool() const { return m_handle != 0; }
/**
* Returns the VideoSink handle.
*
* @return The VideoSink handle.
*/
int GetHandle() const { return m_handle; }
bool operator==(const VideoSink& other) const {
return m_handle == other.m_handle;
}
/**
* Get the kind of the sink.
*/
Kind GetKind() const {
m_status = 0;
return static_cast<VideoSink::Kind>(GetSinkKind(m_handle, &m_status));
}
/**
* Get the name of the sink. The name is an arbitrary identifier
* provided when the sink is created, and should be unique.
*/
std::string GetName() const {
m_status = 0;
return GetSinkName(m_handle, &m_status);
}
/**
* Get the sink description. This is sink-kind specific.
*/
std::string GetDescription() const {
m_status = 0;
return GetSinkDescription(m_handle, &m_status);
}
/**
* Get a property of the sink.
*
* @param name Property name
* @return Property (kind Property::kNone if no property with
* the given name exists)
*/
VideoProperty GetProperty(std::string_view name) {
m_status = 0;
return VideoProperty{GetSinkProperty(m_handle, name, &m_status)};
}
/**
* Enumerate all properties of this sink.
*/
std::vector<VideoProperty> EnumerateProperties() const;
/**
* Set properties from a JSON configuration string.
*
* The format of the JSON input is:
*
* <pre>
* {
* "properties": [
* {
* "name": property name
* "value": property value
* }
* ]
* }
* </pre>
*
* @param config configuration
* @return True if set successfully
*/
bool SetConfigJson(std::string_view config) {
m_status = 0;
return SetSinkConfigJson(m_handle, config, &m_status);
}
/**
* Set properties from a JSON configuration object.
*
* @param config configuration
* @return True if set successfully
*/
bool SetConfigJson(const wpi::util::json& config) {
m_status = 0;
return SetSinkConfigJson(m_handle, config, &m_status);
}
/**
* Get a JSON configuration string.
*
* @return JSON configuration string
*/
std::string GetConfigJson() const {
m_status = 0;
return GetSinkConfigJson(m_handle, &m_status);
}
/**
* Get a JSON configuration object.
*
* @return JSON configuration object
*/
wpi::util::json GetConfigJsonObject() const;
/**
* Configure which source should provide frames to this sink. Each sink
* can accept frames from only a single source, but a single source can
* provide frames to multiple clients.
*
* @param source Source
*/
void SetSource(VideoSource source) {
m_status = 0;
if (!source) {
SetSinkSource(m_handle, 0, &m_status);
} else {
SetSinkSource(m_handle, source.m_handle, &m_status);
}
}
/**
* Get the connected source.
*
* @return Connected source (empty if none connected).
*/
VideoSource GetSource() const {
m_status = 0;
auto handle = GetSinkSource(m_handle, &m_status);
return VideoSource{handle == 0 ? 0 : CopySource(handle, &m_status)};
}
/**
* Get a property of the associated source.
*
* @param name Property name
* @return Property (kind Property::kNone if no property with
* the given name exists or no source connected)
*/
VideoProperty GetSourceProperty(std::string_view name) {
m_status = 0;
return VideoProperty{GetSinkSourceProperty(m_handle, name, &m_status)};
}
CS_Status GetLastStatus() const { return m_status; }
/**
* Enumerate all existing sinks.
*
* @return Vector of sinks.
*/
static std::vector<VideoSink> EnumerateSinks();
friend void swap(VideoSink& first, VideoSink& second) noexcept {
using std::swap;
swap(first.m_status, second.m_status);
swap(first.m_handle, second.m_handle);
}
protected:
explicit VideoSink(CS_Sink handle) : m_handle(handle) {}
mutable CS_Status m_status = 0;
CS_Sink m_handle{0};
};
} // namespace wpi::cs

View File

@@ -0,0 +1,378 @@
// 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.
#pragma once
#include <stdint.h>
#include <string>
#include <utility>
#include <vector>
#include "wpi/cs/VideoProperty.hpp"
#include "wpi/cs/cscore_cpp.hpp"
#include "wpi/util/PixelFormat.hpp"
namespace wpi::cs {
class VideoEvent;
class VideoSink;
/**
* A source for video that provides a sequence of frames.
*/
class VideoSource {
friend class VideoEvent;
friend class VideoSink;
public:
/**
* Video source kind.
*/
enum Kind {
/// Unknown video source.
kUnknown = CS_SOURCE_UNKNOWN,
/// USB video source.
kUsb = CS_SOURCE_USB,
/// HTTP video source.
kHttp = CS_SOURCE_HTTP,
/// CV video source.
kCv = CS_SOURCE_CV,
/// Raw video source.
kRaw = CS_SOURCE_RAW,
};
/** Connection strategy. Used for SetConnectionStrategy(). */
enum ConnectionStrategy {
/**
* Automatically connect or disconnect based on whether any sinks are
* connected to this source. This is the default behavior.
*/
kConnectionAutoManage = CS_CONNECTION_AUTO_MANAGE,
/**
* Try to keep the connection open regardless of whether any sinks are
* connected.
*/
kConnectionKeepOpen = CS_CONNECTION_KEEP_OPEN,
/**
* Never open the connection. If this is set when the connection is open,
* close the connection.
*/
kConnectionForceClose = CS_CONNECTION_FORCE_CLOSE
};
VideoSource() noexcept = default;
VideoSource(const VideoSource& source)
: m_handle(
source.m_handle == 0 ? 0 : CopySource(source.m_handle, &m_status)) {
}
VideoSource(VideoSource&& other) noexcept : VideoSource() {
swap(*this, other);
}
VideoSource& operator=(VideoSource other) noexcept {
swap(*this, other);
return *this;
}
~VideoSource() {
m_status = 0;
if (m_handle != 0) {
ReleaseSource(m_handle, &m_status);
}
}
explicit operator bool() const { return m_handle != 0; }
int GetHandle() const { return m_handle; }
bool operator==(const VideoSource& other) const {
return m_handle == other.m_handle;
}
/**
* Get the kind of the source.
*/
Kind GetKind() const {
m_status = 0;
return static_cast<VideoSource::Kind>(GetSourceKind(m_handle, &m_status));
}
/**
* Get the name of the source. The name is an arbitrary identifier
* provided when the source is created, and should be unique.
*/
std::string GetName() const {
m_status = 0;
return GetSourceName(m_handle, &m_status);
}
/**
* Get the source description. This is source-kind specific.
*/
std::string GetDescription() const {
m_status = 0;
return GetSourceDescription(m_handle, &m_status);
}
/**
* Get the last time a frame was captured.
* This uses the same time base as wpi::util::Now().
*
* @return Time in 1 us increments.
*/
uint64_t GetLastFrameTime() const {
m_status = 0;
return GetSourceLastFrameTime(m_handle, &m_status);
}
/**
* Sets the connection strategy. By default, the source will automatically
* connect or disconnect based on whether any sinks are connected.
*
* <p>This function is non-blocking; look for either a connection open or
* close event or call IsConnected() to determine the connection state.
*
* @param strategy connection strategy (auto, keep open, or force close)
*/
void SetConnectionStrategy(ConnectionStrategy strategy) {
m_status = 0;
SetSourceConnectionStrategy(
m_handle,
static_cast<CS_ConnectionStrategy>(static_cast<int>(strategy)),
&m_status);
}
/**
* Is the source currently connected to whatever is providing the images?
*/
bool IsConnected() const {
m_status = 0;
return IsSourceConnected(m_handle, &m_status);
}
/**
* Gets source enable status. This is determined with a combination of
* connection strategy and the number of sinks connected.
*
* @return True if enabled, false otherwise.
*/
bool IsEnabled() const {
m_status = 0;
return IsSourceEnabled(m_handle, &m_status);
}
/** Get a property.
*
* @param name Property name
* @return Property contents (of kind Property::kNone if no property with
* the given name exists)
*/
VideoProperty GetProperty(std::string_view name) {
m_status = 0;
return VideoProperty{GetSourceProperty(m_handle, name, &m_status)};
}
/**
* Enumerate all properties of this source.
*/
std::vector<VideoProperty> EnumerateProperties() const;
/**
* Get the current video mode.
*/
VideoMode GetVideoMode() const {
m_status = 0;
return GetSourceVideoMode(m_handle, &m_status);
}
/**
* Set the video mode.
*
* @param mode Video mode
*/
bool SetVideoMode(const VideoMode& mode) {
m_status = 0;
return SetSourceVideoMode(m_handle, mode, &m_status);
}
/**
* Set the video mode.
*
* @param pixelFormat desired pixel format
* @param width desired width
* @param height desired height
* @param fps desired FPS
* @return True if set successfully
*/
bool SetVideoMode(wpi::util::PixelFormat pixelFormat, int width, int height,
int fps) {
m_status = 0;
return SetSourceVideoMode(
m_handle, VideoMode{pixelFormat, width, height, fps}, &m_status);
}
/**
* Set the pixel format.
*
* @param pixelFormat desired pixel format
* @return True if set successfully
*/
bool SetPixelFormat(wpi::util::PixelFormat pixelFormat) {
m_status = 0;
return SetSourcePixelFormat(m_handle, pixelFormat, &m_status);
}
/**
* Set the resolution.
*
* @param width desired width
* @param height desired height
* @return True if set successfully
*/
bool SetResolution(int width, int height) {
m_status = 0;
return SetSourceResolution(m_handle, width, height, &m_status);
}
/**
* Set the frames per second (FPS).
*
* @param fps desired FPS
* @return True if set successfully
*/
bool SetFPS(int fps) {
m_status = 0;
return SetSourceFPS(m_handle, fps, &m_status);
}
/**
* Set video mode and properties from a JSON configuration string.
*
* The format of the JSON input is:
*
* <pre>
* {
* "pixel format": "MJPEG", "YUYV", etc
* "width": video mode width
* "height": video mode height
* "fps": video mode fps
* "brightness": percentage brightness
* "white balance": "auto", "hold", or value
* "exposure": "auto", "hold", or value
* "properties": [
* {
* "name": property name
* "value": property value
* }
* ]
* }
* </pre>
*
* @param config configuration
* @return True if set successfully
*/
bool SetConfigJson(std::string_view config) {
m_status = 0;
return SetSourceConfigJson(m_handle, config, &m_status);
}
/**
* Set video mode and properties from a JSON configuration object.
*
* @param config configuration
* @return True if set successfully
*/
bool SetConfigJson(const wpi::util::json& config) {
m_status = 0;
return SetSourceConfigJson(m_handle, config, &m_status);
}
/**
* Get a JSON configuration string.
*
* @return JSON configuration string
*/
std::string GetConfigJson() const {
m_status = 0;
return GetSourceConfigJson(m_handle, &m_status);
}
/**
* Get a JSON configuration object.
*
* @return JSON configuration object
*/
wpi::util::json GetConfigJsonObject() const;
/**
* Get the actual FPS.
*
* <p>SetTelemetryPeriod() must be called for this to be valid.
*
* @return Actual FPS averaged over the telemetry period.
*/
double GetActualFPS() const {
m_status = 0;
return wpi::cs::GetTelemetryAverageValue(
m_handle, CS_SOURCE_FRAMES_RECEIVED, &m_status);
}
/**
* Get the data rate (in bytes per second).
*
* <p>SetTelemetryPeriod() must be called for this to be valid.
*
* @return Data rate averaged over the telemetry period.
*/
double GetActualDataRate() const {
m_status = 0;
return wpi::cs::GetTelemetryAverageValue(m_handle, CS_SOURCE_BYTES_RECEIVED,
&m_status);
}
/**
* Enumerate all known video modes for this source.
*/
std::vector<VideoMode> EnumerateVideoModes() const {
CS_Status status = 0;
return EnumerateSourceVideoModes(m_handle, &status);
}
CS_Status GetLastStatus() const { return m_status; }
/**
* Enumerate all sinks connected to this source.
*
* @return Vector of sinks.
*/
std::vector<VideoSink> EnumerateSinks();
/**
* Enumerate all existing sources.
*
* @return Vector of sources.
*/
static std::vector<VideoSource> EnumerateSources();
friend void swap(VideoSource& first, VideoSource& second) noexcept {
using std::swap;
swap(first.m_status, second.m_status);
swap(first.m_handle, second.m_handle);
}
protected:
explicit VideoSource(CS_Source handle) : m_handle(handle) {}
mutable CS_Status m_status = 0;
/// Video source handle.
CS_Source m_handle{0};
};
} // namespace wpi::cs

View File

@@ -1,14 +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.
#pragma once
/* C API */
#include "wpi/cs/cscore_c.h"
#ifdef __cplusplus
/* C++ API */
#include "wpi/cs/cscore_cpp.hpp"
#include "wpi/cs/cscore_oo.hpp"
#endif /* __cplusplus */

View File

@@ -4,17 +4,12 @@
#pragma once
#include <stddef.h> // NOLINT
#include <stdint.h>
#include "wpi/util/RawFrame.h"
#include "wpi/util/PixelFormat.h"
#include "wpi/util/string.h"
#ifdef __cplusplus
#include <cstddef>
#else
#include <stddef.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif

View File

@@ -12,8 +12,11 @@
#include <string_view>
#include <vector>
#include "wpi/cs/RawEvent.hpp"
#include "wpi/cs/UsbCameraInfo.hpp"
#include "wpi/cs/VideoMode.hpp"
#include "wpi/cs/cscore_c.h"
#include "wpi/util/RawFrame.h"
#include "wpi/util/PixelFormat.hpp"
#include "wpi/util/SmallVector.hpp"
#include "wpi/util/json_fwd.hpp"
@@ -36,140 +39,6 @@ namespace wpi::cs {
* @{
*/
/**
* USB camera information
*/
struct UsbCameraInfo {
/** Device number (e.g. N in '/dev/videoN' on Linux) */
int dev = -1;
/** Path to device if available (e.g. '/dev/video0' on Linux) */
std::string path;
/** Vendor/model name of the camera as provided by the USB driver */
std::string name;
/** Other path aliases to device (e.g. '/dev/v4l/by-id/...' etc on Linux) */
std::vector<std::string> otherPaths;
/** USB Vendor Id */
int vendorId = -1;
/** USB Product Id */
int productId = -1;
};
/**
* Video mode
*/
struct VideoMode : public CS_VideoMode {
enum PixelFormat {
kUnknown = WPI_PIXFMT_UNKNOWN,
kMJPEG = WPI_PIXFMT_MJPEG,
kYUYV = WPI_PIXFMT_YUYV,
kRGB565 = WPI_PIXFMT_RGB565,
kBGR = WPI_PIXFMT_BGR,
kGray = WPI_PIXFMT_GRAY,
kY16 = WPI_PIXFMT_Y16,
kUYVY = WPI_PIXFMT_UYVY,
kBGRA = WPI_PIXFMT_BGRA,
};
VideoMode() {
pixelFormat = 0;
width = 0;
height = 0;
fps = 0;
}
VideoMode(PixelFormat pixelFormat_, int width_, int height_, int fps_) {
pixelFormat = pixelFormat_;
width = width_;
height = height_;
fps = fps_;
}
explicit operator bool() const { return pixelFormat == kUnknown; }
bool operator==(const VideoMode& other) const {
return pixelFormat == other.pixelFormat && width == other.width &&
height == other.height && fps == other.fps;
}
bool CompareWithoutFps(const VideoMode& other) const {
return pixelFormat == other.pixelFormat && width == other.width &&
height == other.height;
}
};
/**
* Listener event
*/
struct RawEvent {
enum Kind {
kSourceCreated = CS_SOURCE_CREATED,
kSourceDestroyed = CS_SOURCE_DESTROYED,
kSourceConnected = CS_SOURCE_CONNECTED,
kSourceDisconnected = CS_SOURCE_DISCONNECTED,
kSourceVideoModesUpdated = CS_SOURCE_VIDEOMODES_UPDATED,
kSourceVideoModeChanged = CS_SOURCE_VIDEOMODE_CHANGED,
kSourcePropertyCreated = CS_SOURCE_PROPERTY_CREATED,
kSourcePropertyValueUpdated = CS_SOURCE_PROPERTY_VALUE_UPDATED,
kSourcePropertyChoicesUpdated = CS_SOURCE_PROPERTY_CHOICES_UPDATED,
kSinkSourceChanged = CS_SINK_SOURCE_CHANGED,
kSinkCreated = CS_SINK_CREATED,
kSinkDestroyed = CS_SINK_DESTROYED,
kSinkEnabled = CS_SINK_ENABLED,
kSinkDisabled = CS_SINK_DISABLED,
kNetworkInterfacesChanged = CS_NETWORK_INTERFACES_CHANGED,
kTelemetryUpdated = CS_TELEMETRY_UPDATED,
kSinkPropertyCreated = CS_SINK_PROPERTY_CREATED,
kSinkPropertyValueUpdated = CS_SINK_PROPERTY_VALUE_UPDATED,
kSinkPropertyChoicesUpdated = CS_SINK_PROPERTY_CHOICES_UPDATED,
kUsbCamerasChanged = CS_USB_CAMERAS_CHANGED
};
RawEvent() = default;
explicit RawEvent(RawEvent::Kind kind_) : kind{kind_} {}
RawEvent(std::string_view name_, CS_Handle handle_, RawEvent::Kind kind_)
: kind{kind_}, name{name_} {
if (kind_ == kSinkCreated || kind_ == kSinkDestroyed ||
kind_ == kSinkEnabled || kind_ == kSinkDisabled) {
sinkHandle = handle_;
} else {
sourceHandle = handle_;
}
}
RawEvent(std::string_view name_, CS_Source source_, const VideoMode& mode_)
: kind{kSourceVideoModeChanged},
sourceHandle{source_},
name{name_},
mode{mode_} {}
RawEvent(std::string_view name_, CS_Source source_, RawEvent::Kind kind_,
CS_Property property_, CS_PropertyKind propertyKind_, int value_,
std::string_view valueStr_)
: kind{kind_},
sourceHandle{source_},
name{name_},
propertyHandle{property_},
propertyKind{propertyKind_},
value{value_},
valueStr{valueStr_} {}
Kind kind;
// Valid for kSource* and kSink* respectively
CS_Source sourceHandle = CS_INVALID_HANDLE;
CS_Sink sinkHandle = CS_INVALID_HANDLE;
// Source/sink/property name
std::string name;
// Fields for kSourceVideoModeChanged event
VideoMode mode;
// Fields for kSourceProperty* events
CS_Property propertyHandle;
CS_PropertyKind propertyKind;
int value;
std::string valueStr;
// Listener that was triggered
CS_Listener listener{0};
};
/**
* @defgroup cscore_property_func Property Functions
* @{
@@ -238,7 +107,7 @@ std::span<CS_Property> EnumerateSourceProperties(
VideoMode GetSourceVideoMode(CS_Source source, CS_Status* status);
bool SetSourceVideoMode(CS_Source source, const VideoMode& mode,
CS_Status* status);
bool SetSourcePixelFormat(CS_Source source, VideoMode::PixelFormat pixelFormat,
bool SetSourcePixelFormat(CS_Source source, wpi::util::PixelFormat pixelFormat,
CS_Status* status);
bool SetSourceResolution(CS_Source source, int width, int height,
CS_Status* status);
@@ -316,10 +185,10 @@ void SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
*/
CS_Sink CreateMjpegServer(std::string_view name, std::string_view listenAddress,
int port, CS_Status* status);
CS_Sink CreateCvSink(std::string_view name, VideoMode::PixelFormat pixelFormat,
CS_Sink CreateCvSink(std::string_view name, wpi::util::PixelFormat pixelFormat,
CS_Status* status);
CS_Sink CreateCvSinkCallback(std::string_view name,
VideoMode::PixelFormat pixelFormat,
wpi::util::PixelFormat pixelFormat,
std::function<void(uint64_t time)> processFrame,
CS_Status* status);

View File

@@ -1,431 +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.
#pragma once
#include <opencv2/core/mat.hpp>
#include "wpi/cs/cscore_oo.hpp"
#include "wpi/cs/cscore_raw.h"
#include "wpi/util/RawFrame.hpp"
namespace wpi::cs {
/**
* A source for user code to provide OpenCV images as video frames.
*
* 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:
CvSource() = default;
/**
* Create an OpenCV source.
*
* @param name Source name (arbitrary unique identifier)
* @param mode Video mode being generated
*/
CvSource(std::string_view name, const VideoMode& mode);
/**
* Create an OpenCV source.
*
* @param name Source name (arbitrary unique identifier)
* @param pixelFormat Pixel format
* @param width width
* @param height height
* @param fps fps
*/
CvSource(std::string_view name, VideoMode::PixelFormat pixelFormat, int width,
int height, int fps);
/**
* Put an OpenCV image and notify sinks
*
* <p>
* The image format is guessed from the number of channels. The channel
* mapping is as follows. 1: kGray 2: kYUYV 3: BGR 4: BGRA Any other channel
* numbers will throw an error. If your image is an in alternate format, use
* the overload that takes a PixelFormat.
*
* @param image OpenCV Image
*/
void PutFrame(cv::Mat& image);
/**
* Put an OpenCV image and notify sinks.
*
* <p>
* The format of the Mat must match the PixelFormat. You will corrupt memory
* if they dont. With skipVerification false, we will verify the number of
* channels matches the pixel format. If skipVerification is true, this step
* is skipped and is passed straight through.
*
* @param image OpenCV image
* @param pixelFormat The pixel format of the image
* @param skipVerification skip verifying pixel format
*/
void PutFrame(cv::Mat& image, VideoMode::PixelFormat pixelFormat,
bool skipVerification);
private:
static bool VerifyFormat(cv::Mat& image, VideoMode::PixelFormat pixelFormat);
};
/**
* 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.
*
* <p>WaitForFrame() must be called on the created sink to get each new
* image.
*
* @param name Source name (arbitrary unique identifier)
* @param pixelFormat The pixel format to read
*/
explicit CvSink(std::string_view name, VideoMode::PixelFormat pixelFormat =
VideoMode::PixelFormat::kBGR);
/**
* 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.
*
* @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::util::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 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::util::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 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::util::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 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::util::Now(), and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrameNoTimeoutDirect(cv::Mat& image);
/**
* 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. The data is backed by data in the CvSink. It will be invalidated by
* any grabFrame*() call on the sink.
*
* <p>If lastFrameTime is provided and non-zero, the sink will fill image with
* the first frame from the source that is not equal to lastFrameTime. If
* lastFrameTime is zero, the time of the current frame owned by the CvSource
* is used, and this function will block until the connected CvSource provides
* a new frame.
*
* @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::util::Now(), and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrameDirectLastTime(cv::Mat& image, uint64_t lastFrameTime,
double timeout = 0.225);
/**
* Get the last time a frame was grabbed. This uses the same time base as
* wpi::util::Now().
*
* @return Time in 1 us increments.
*/
[[nodiscard]]
uint64_t LastFrameTime();
/**
* Get the time source for the timestamp the last frame was grabbed at.
*
* @return Time source
*/
[[nodiscard]]
WPI_TimestampSource LastFrameTimeSource();
private:
constexpr int GetCvFormat(WPI_PixelFormat pixelFormat);
wpi::util::RawFrame rawFrame;
VideoMode::PixelFormat pixelFormat;
};
inline CvSource::CvSource(std::string_view name, const VideoMode& mode) {
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 = CreateRawSource(name, true, VideoMode{format, width, height, fps},
&m_status);
}
inline bool CvSource::VerifyFormat(cv::Mat& image,
VideoMode::PixelFormat pixelFormat) {
int channels = image.channels();
switch (pixelFormat) {
case VideoMode::PixelFormat::kBGR:
if (channels == 3) {
return true;
}
break;
case VideoMode::PixelFormat::kBGRA:
if (channels == 4) {
return true;
}
break;
case VideoMode::PixelFormat::kGray:
if (channels == 1) {
return true;
}
break;
case VideoMode::PixelFormat::kRGB565:
if (channels == 2) {
return true;
}
break;
case VideoMode::PixelFormat::kUYVY:
if (channels == 2) {
return true;
}
break;
case VideoMode::PixelFormat::kY16:
if (channels == 2) {
return true;
}
break;
case VideoMode::PixelFormat::kYUYV:
if (channels == 2) {
return true;
}
break;
case VideoMode::PixelFormat::kMJPEG:
if (channels == 1) {
return true;
}
break;
default:
break;
}
return false;
}
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();
VideoMode::PixelFormat format;
if (channels == 1) {
// 1 channel is assumed Grayscale
format = VideoMode::PixelFormat::kGray;
} else if (channels == 2) {
// 2 channels is assumed YUYV
format = VideoMode::PixelFormat::kYUYV;
} else if (channels == 3) {
// 3 channels is assumed BGR
format = VideoMode::PixelFormat::kBGR;
} else if (channels == 4) {
// 4 channels is assumed BGRA
format = VideoMode::PixelFormat::kBGRA;
} else {
// TODO Error
return;
}
PutFrame(finalImage, format, true);
}
inline void CvSource::PutFrame(cv::Mat& image,
VideoMode::PixelFormat pixelFormat,
bool skipVerification) {
// 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);
}
if (!skipVerification) {
if (!VerifyFormat(finalImage, pixelFormat)) {
// TODO Error
return;
}
}
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() * finalImage.channels();
frame.width = finalImage.cols;
frame.height = finalImage.rows;
frame.stride = finalImage.step;
frame.pixelFormat = pixelFormat;
m_status = 0;
PutSourceFrame(m_handle, frame, &m_status);
}
inline CvSink::CvSink(std::string_view name,
VideoMode::PixelFormat pixelFormat) {
m_handle = CreateRawSink(name, true, &m_status);
this->pixelFormat = pixelFormat;
}
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::GrabFrameNoTimeout(cv::Mat& image) {
cv::Mat tmpnam;
auto retVal = GrabFrameNoTimeoutDirect(tmpnam);
if (retVal <= 0) {
return retVal;
}
tmpnam.copyTo(image);
return retVal;
}
inline constexpr int CvSink::GetCvFormat(WPI_PixelFormat pixelFormat) {
int type = 0;
switch (pixelFormat) {
case WPI_PIXFMT_YUYV:
case WPI_PIXFMT_RGB565:
case WPI_PIXFMT_Y16:
case WPI_PIXFMT_UYVY:
type = CV_8UC2;
break;
case WPI_PIXFMT_BGR:
type = CV_8UC3;
break;
case WPI_PIXFMT_BGRA:
type = CV_8UC4;
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.stride = 0;
rawFrame.pixelFormat = pixelFormat;
auto timestamp = GrabSinkFrameTimeout(m_handle, rawFrame, timeout, &m_status);
if (m_status != CS_OK) {
return 0;
}
image =
cv::Mat{rawFrame.height, rawFrame.width,
GetCvFormat(static_cast<WPI_PixelFormat>(rawFrame.pixelFormat)),
rawFrame.data, static_cast<size_t>(rawFrame.stride)};
return timestamp;
}
inline uint64_t CvSink::GrabFrameNoTimeoutDirect(cv::Mat& image) {
rawFrame.height = 0;
rawFrame.width = 0;
rawFrame.stride = 0;
rawFrame.pixelFormat = pixelFormat;
auto timestamp = GrabSinkFrame(m_handle, rawFrame, &m_status);
if (m_status != CS_OK) {
return 0;
}
image =
cv::Mat{rawFrame.height, rawFrame.width,
GetCvFormat(static_cast<WPI_PixelFormat>(rawFrame.pixelFormat)),
rawFrame.data, static_cast<size_t>(rawFrame.stride)};
return timestamp;
}
inline uint64_t CvSink::GrabFrameDirectLastTime(cv::Mat& image,
uint64_t lastFrameTime,
double timeout) {
rawFrame.height = 0;
rawFrame.width = 0;
rawFrame.stride = 0;
rawFrame.pixelFormat = pixelFormat;
auto timestamp = GrabSinkFrameTimeoutLastTime(m_handle, rawFrame, timeout,
lastFrameTime, &m_status);
if (m_status != CS_OK) {
return 0;
}
image =
cv::Mat{rawFrame.height, rawFrame.width,
GetCvFormat(static_cast<WPI_PixelFormat>(rawFrame.pixelFormat)),
rawFrame.data, static_cast<size_t>(rawFrame.stride)};
return timestamp;
}
inline uint64_t CvSink::LastFrameTime() {
return rawFrame.timestamp;
}
inline WPI_TimestampSource CvSink::LastFrameTimeSource() {
return static_cast<WPI_TimestampSource>(rawFrame.timestampSrc);
}
} // namespace wpi::cs

File diff suppressed because it is too large Load Diff

View File

@@ -4,20 +4,11 @@
#pragma once
#include <functional>
#include "wpi/cs/cscore_c.h"
// NOLINTBEGIN
#ifdef __cplusplus
#include "wpi/cs/cscore_oo.hpp"
#include "wpi/util/RawFrame.hpp"
#endif
#ifdef __cplusplus
extern "C" {
#endif
// NOLINTEND
/**
* @defgroup cscore_raw_cfunc Raw Image Functions
@@ -50,192 +41,3 @@ CS_Source CS_CreateRawSource(const struct WPI_String* name, CS_Bool isCv,
#ifdef __cplusplus
} // extern "C"
#endif
#ifdef __cplusplus
namespace wpi::cs {
/**
* @defgroup cscore_raw_func Raw Image Functions
* @{
*/
CS_Source CreateRawSource(std::string_view name, bool isCv,
const VideoMode& mode, CS_Status* status);
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);
void PutSourceFrame(CS_Source source, const WPI_RawFrame& image,
CS_Status* status);
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);
uint64_t GrabSinkFrameTimeoutLastTime(CS_Sink sink, WPI_RawFrame& image,
double timeout, uint64_t lastFrameTime,
CS_Status* status);
/**
* A source for user code to provide video frames as raw bytes.
*
* This is a complex API, most cases should use CvSource.
*/
class RawSource : public ImageSource {
public:
RawSource() = default;
/**
* Create a raw frame source.
*
* @param name Source name (arbitrary unique identifier)
* @param mode Video mode being generated
*/
RawSource(std::string_view name, const VideoMode& mode);
/**
* Create a raw frame source.
*
* @param name Source name (arbitrary unique identifier)
* @param pixelFormat Pixel format
* @param width width
* @param height height
* @param fps fps
*/
RawSource(std::string_view name, VideoMode::PixelFormat pixelFormat,
int width, int height, int fps);
protected:
/**
* Put a raw image and notify sinks.
*
* @param image raw frame image
*/
void PutFrame(wpi::util::RawFrame& image);
};
/**
* A sink for user code to accept video frames as raw bytes.
*
* This is a complex API, most cases should use CvSource.
*/
class RawSink : public ImageSink {
public:
RawSink() = default;
/**
* Create a sink for accepting raw images.
*
* <p>GrabFrame() must be called on the created sink to get each new
* image.
*
* @param name Source name (arbitrary unique identifier)
*/
explicit RawSink(std::string_view name);
/**
* Create a sink for accepting raws 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().
*/
RawSink(std::string_view name,
std::function<void(uint64_t time)> processFrame);
protected:
/**
* 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::util::Now(), and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrame(wpi::util::RawFrame& image, double timeout = 0.225) const;
/**
* 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::util::Now(), and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrameNoTimeout(wpi::util::RawFrame& image) const;
/**
* 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.
*
* <p>If lastFrameTime is provided and non-zero, the sink will fill image with
* the first frame from the source that is not equal to lastFrameTime. If
* lastFrameTime is zero, the time of the current frame owned by the CvSource
* is used, and this function will block until the connected CvSource provides
* a new frame.
*
* @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::util::Now(), and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrameLastTime(wpi::util::RawFrame& image, uint64_t lastFrameTime,
double timeout = 0.225) const;
};
inline RawSource::RawSource(std::string_view name, const VideoMode& mode) {
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, false, VideoMode{format, width, height, fps},
&m_status);
}
inline void RawSource::PutFrame(wpi::util::RawFrame& image) {
m_status = 0;
PutSourceFrame(m_handle, image, &m_status);
}
inline RawSink::RawSink(std::string_view name) {
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, false, processFrame, &m_status);
}
inline uint64_t RawSink::GrabFrame(wpi::util::RawFrame& image,
double timeout) const {
m_status = 0;
return GrabSinkFrameTimeout(m_handle, image, timeout, &m_status);
}
inline uint64_t RawSink::GrabFrameNoTimeout(wpi::util::RawFrame& image) const {
m_status = 0;
return GrabSinkFrame(m_handle, image, &m_status);
}
inline uint64_t RawSink::GrabFrameLastTime(wpi::util::RawFrame& image,
uint64_t lastFrameTime,
double timeout) const {
m_status = 0;
return GrabSinkFrameTimeoutLastTime(m_handle, image, timeout, lastFrameTime,
&m_status);
}
/** @} */
} // namespace wpi::cs
#endif

View File

@@ -0,0 +1,42 @@
// 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.
#pragma once
#include <stdint.h>
#include <functional>
#include <string_view>
#include "wpi/cs/VideoMode.hpp"
#include "wpi/cs/cscore_c.h"
#include "wpi/util/RawFrame.h"
namespace wpi::cs {
/**
* @defgroup cscore_raw_func Raw Image Functions
* @{
*/
CS_Source CreateRawSource(std::string_view name, bool isCv,
const VideoMode& mode, CS_Status* status);
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);
void PutSourceFrame(CS_Source source, const WPI_RawFrame& image,
CS_Status* status);
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);
uint64_t GrabSinkFrameTimeoutLastTime(CS_Sink sink, WPI_RawFrame& image,
double timeout, uint64_t lastFrameTime,
CS_Status* status);
/** @} */
} // namespace wpi::cs

View File

@@ -37,6 +37,7 @@
#include "wpi/util/SmallString.hpp"
#include "wpi/util/StringExtras.hpp"
#include "wpi/util/fs.hpp"
#include "wpi/util/raw_istream.hpp"
#include "wpi/util/timestamp.hpp"
using namespace wpi::cs;
@@ -70,47 +71,47 @@ static inline struct v4l2_fract FPSToFract(int fps) {
}
// Conversion from v4l2_format pixelformat to VideoMode::PixelFormat
static VideoMode::PixelFormat ToPixelFormat(__u32 pixelFormat) {
static wpi::util::PixelFormat ToPixelFormat(__u32 pixelFormat) {
switch (pixelFormat) {
case V4L2_PIX_FMT_MJPEG:
return VideoMode::kMJPEG;
return wpi::util::PixelFormat::kMJPEG;
case V4L2_PIX_FMT_YUYV:
return VideoMode::kYUYV;
return wpi::util::PixelFormat::kYUYV;
case V4L2_PIX_FMT_RGB565:
return VideoMode::kRGB565;
return wpi::util::PixelFormat::kRGB565;
case V4L2_PIX_FMT_BGR24:
return VideoMode::kBGR;
return wpi::util::PixelFormat::kBGR;
case V4L2_PIX_FMT_ABGR32:
return VideoMode::kBGRA;
return wpi::util::PixelFormat::kBGRA;
case V4L2_PIX_FMT_GREY:
return VideoMode::kGray;
return wpi::util::PixelFormat::kGray;
case V4L2_PIX_FMT_Y16:
return VideoMode::kY16;
return wpi::util::PixelFormat::kY16;
case V4L2_PIX_FMT_UYVY:
return VideoMode::kUYVY;
return wpi::util::PixelFormat::kUYVY;
default:
return VideoMode::kUnknown;
return wpi::util::PixelFormat::kUnknown;
}
}
// Conversion from VideoMode::PixelFormat to v4l2_format pixelformat
static __u32 FromPixelFormat(VideoMode::PixelFormat pixelFormat) {
static __u32 FromPixelFormat(wpi::util::PixelFormat pixelFormat) {
switch (pixelFormat) {
case VideoMode::kMJPEG:
case wpi::util::PixelFormat::kMJPEG:
return V4L2_PIX_FMT_MJPEG;
case VideoMode::kYUYV:
case wpi::util::PixelFormat::kYUYV:
return V4L2_PIX_FMT_YUYV;
case VideoMode::kRGB565:
case wpi::util::PixelFormat::kRGB565:
return V4L2_PIX_FMT_RGB565;
case VideoMode::kBGR:
case wpi::util::PixelFormat::kBGR:
return V4L2_PIX_FMT_BGR24;
case VideoMode::kBGRA:
case wpi::util::PixelFormat::kBGRA:
return V4L2_PIX_FMT_ABGR32;
case VideoMode::kGray:
case wpi::util::PixelFormat::kGray:
return V4L2_PIX_FMT_GREY;
case VideoMode::kY16:
case wpi::util::PixelFormat::kY16:
return V4L2_PIX_FMT_Y16;
case VideoMode::kUYVY:
case wpi::util::PixelFormat::kUYVY:
return V4L2_PIX_FMT_UYVY;
default:
return 0;
@@ -553,7 +554,7 @@ void UsbCameraImpl::CameraThreadMain() {
int width = m_mode.width;
int height = m_mode.height;
bool good = true;
if (m_mode.pixelFormat == VideoMode::kMJPEG &&
if (m_mode.pixelFormat == wpi::util::PixelFormat::kMJPEG &&
!GetJpegSize(image, &width, &height)) {
SWARNING("invalid JPEG image received from camera");
good = false;
@@ -603,8 +604,8 @@ void UsbCameraImpl::CameraThreadMain() {
"Got valid copy time for frame - default to wpi::util::Now");
}
PutFrame(static_cast<VideoMode::PixelFormat>(m_mode.pixelFormat),
width, height, image, frameTime, timeSource);
PutFrame(m_mode.pixelFormat, width, height, image, frameTime,
timeSource);
}
}
@@ -821,7 +822,7 @@ CS_StatusValue UsbCameraImpl::DeviceCmdSetMode(
std::unique_lock<wpi::util::mutex>& lock, const Message& msg) {
VideoMode newMode;
if (msg.kind == Message::kCmdSetMode) {
newMode.pixelFormat = msg.data[0];
newMode.pixelFormat = static_cast<wpi::util::PixelFormat>(msg.data[0]);
newMode.width = msg.data[1];
newMode.height = msg.data[2];
newMode.fps = msg.data[3];
@@ -830,7 +831,7 @@ CS_StatusValue UsbCameraImpl::DeviceCmdSetMode(
m_modeSetFPS = true;
} else if (msg.kind == Message::kCmdSetPixelFormat) {
newMode = m_mode;
newMode.pixelFormat = msg.data[0];
newMode.pixelFormat = static_cast<wpi::util::PixelFormat>(msg.data[0]);
m_modeSetPixelFormat = true;
} else if (msg.kind == Message::kCmdSetResolution) {
newMode = m_mode;
@@ -1017,22 +1018,21 @@ void UsbCameraImpl::DeviceSetMode() {
: 0;
#endif
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vfmt.fmt.pix.pixelformat =
FromPixelFormat(static_cast<VideoMode::PixelFormat>(m_mode.pixelFormat));
vfmt.fmt.pix.pixelformat = FromPixelFormat(m_mode.pixelFormat);
if (vfmt.fmt.pix.pixelformat == 0) {
SWARNING("could not set format {}, defaulting to MJPEG",
m_mode.pixelFormat);
static_cast<int>(m_mode.pixelFormat));
vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
}
vfmt.fmt.pix.width = m_mode.width;
vfmt.fmt.pix.height = m_mode.height;
vfmt.fmt.pix.field = V4L2_FIELD_ANY;
if (DoIoctl(fd, VIDIOC_S_FMT, &vfmt) != 0) {
SWARNING("could not set format {} res {}x{}", m_mode.pixelFormat,
m_mode.width, m_mode.height);
SWARNING("could not set format {} res {}x{}",
static_cast<int>(m_mode.pixelFormat), m_mode.width, m_mode.height);
} else {
SINFO("set format {} res {}x{}", m_mode.pixelFormat, m_mode.width,
m_mode.height);
SINFO("set format {} res {}x{}", static_cast<int>(m_mode.pixelFormat),
m_mode.width, m_mode.height);
}
}
@@ -1079,10 +1079,10 @@ void UsbCameraImpl::DeviceCacheMode() {
if (DoIoctl(fd, VIDIOC_G_FMT, &vfmt) != 0) {
SERROR("could not read current video mode");
std::scoped_lock lock(m_mutex);
m_mode = VideoMode{VideoMode::kMJPEG, 320, 240, 30};
m_mode = VideoMode{wpi::util::PixelFormat::kMJPEG, 320, 240, 30};
return;
}
VideoMode::PixelFormat pixelFormat = ToPixelFormat(vfmt.fmt.pix.pixelformat);
wpi::util::PixelFormat pixelFormat = ToPixelFormat(vfmt.fmt.pix.pixelformat);
int width = vfmt.fmt.pix.width;
int height = vfmt.fmt.pix.height;
@@ -1104,13 +1104,13 @@ void UsbCameraImpl::DeviceCacheMode() {
// User set pixel format
if (pixelFormat != m_mode.pixelFormat) {
formatChanged = true;
pixelFormat = static_cast<VideoMode::PixelFormat>(m_mode.pixelFormat);
pixelFormat = static_cast<wpi::util::PixelFormat>(m_mode.pixelFormat);
}
} else {
// Default to MJPEG
if (pixelFormat != VideoMode::kMJPEG) {
if (pixelFormat != wpi::util::PixelFormat::kMJPEG) {
formatChanged = true;
pixelFormat = VideoMode::kMJPEG;
pixelFormat = wpi::util::PixelFormat::kMJPEG;
}
}
@@ -1319,8 +1319,8 @@ void UsbCameraImpl::DeviceCacheVideoModes() {
std::memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
for (fmt.index = 0; TryIoctl(fd, VIDIOC_ENUM_FMT, &fmt) >= 0; ++fmt.index) {
VideoMode::PixelFormat pixelFormat = ToPixelFormat(fmt.pixelformat);
if (pixelFormat == VideoMode::kUnknown) {
wpi::util::PixelFormat pixelFormat = ToPixelFormat(fmt.pixelformat);
if (pixelFormat == wpi::util::PixelFormat::kUnknown) {
continue;
}
@@ -1359,8 +1359,9 @@ void UsbCameraImpl::DeviceCacheVideoModes() {
// provide a set of discrete modes; list based on
// https://picamera.readthedocs.io/en/release-1.10/fov.html
if (modes.empty() && m_picamera) {
for (VideoMode::PixelFormat pixelFormat :
{VideoMode::kYUYV, VideoMode::kMJPEG, VideoMode::kBGR}) {
for (wpi::util::PixelFormat pixelFormat :
{wpi::util::PixelFormat::kYUYV, wpi::util::PixelFormat::kMJPEG,
wpi::util::PixelFormat::kBGR}) {
modes.emplace_back(pixelFormat, 1920, 1080, 30);
modes.emplace_back(pixelFormat, 2592, 1944, 15);
modes.emplace_back(pixelFormat, 1296, 972, 42);
@@ -1566,7 +1567,7 @@ void UsbCameraImpl::SetExposureManual(int value, CS_Status* status) {
bool UsbCameraImpl::SetVideoMode(const VideoMode& mode, CS_Status* status) {
Message msg{Message::kCmdSetMode};
msg.data[0] = mode.pixelFormat;
msg.data[0] = static_cast<int>(mode.pixelFormat);
msg.data[1] = mode.width;
msg.data[2] = mode.height;
msg.data[3] = mode.fps;
@@ -1574,10 +1575,10 @@ bool UsbCameraImpl::SetVideoMode(const VideoMode& mode, CS_Status* status) {
return *status == CS_OK;
}
bool UsbCameraImpl::SetPixelFormat(VideoMode::PixelFormat pixelFormat,
bool UsbCameraImpl::SetPixelFormat(wpi::util::PixelFormat pixelFormat,
CS_Status* status) {
Message msg{Message::kCmdSetPixelFormat};
msg.data[0] = pixelFormat;
msg.data[0] = static_cast<int>(pixelFormat);
*status = SendAndWait(std::move(msg));
return *status == CS_OK;
}

View File

@@ -17,11 +17,9 @@
#include "SourceImpl.hpp"
#include "UsbCameraBuffer.hpp"
#include "UsbCameraProperty.hpp"
#include "wpi/util/SmallVector.hpp"
#include "wpi/util/PixelFormat.hpp"
#include "wpi/util/condition_variable.hpp"
#include "wpi/util/mutex.hpp"
#include "wpi/util/raw_istream.hpp"
#include "wpi/util/raw_ostream.hpp"
namespace wpi::cs {
@@ -53,7 +51,7 @@ class UsbCameraImpl : public SourceImpl {
void SetExposureManual(int value, CS_Status* status) override;
bool SetVideoMode(const VideoMode& mode, CS_Status* status) override;
bool SetPixelFormat(VideoMode::PixelFormat pixelFormat,
bool SetPixelFormat(wpi::util::PixelFormat pixelFormat,
CS_Status* status) override;
bool SetResolution(int width, int height, CS_Status* status) override;
bool SetFPS(int fps, CS_Status* status) override;

View File

@@ -12,6 +12,7 @@
#include <string>
#include <optional>
#include "wpi/util/PixelFormat.hpp"
#include "wpi/util/StringMap.hpp"
#include "SourceImpl.hpp"
@@ -56,7 +57,7 @@ class UsbCameraImpl : public SourceImpl {
void SetExposureManual(int value, CS_Status* status) override;
bool SetVideoMode(const VideoMode& mode, CS_Status* status) override;
bool SetPixelFormat(VideoMode::PixelFormat pixelFormat,
bool SetPixelFormat(wpi::util::PixelFormat pixelFormat,
CS_Status* status) override;
bool SetResolution(int width, int height, CS_Status* status) override;
bool SetFPS(int fps, CS_Status* status) override;

View File

@@ -18,6 +18,7 @@
#include "Instance.hpp"
#include "c_util.hpp"
#include "wpi/cs/cscore_cpp.hpp"
#include "wpi/util/PixelFormat.hpp"
#include "UsbCameraImpl.hpp"
namespace wpi::cs {
@@ -88,7 +89,7 @@ void UsbCameraImpl::SetExposureManual(int value, CS_Status* status) {
bool UsbCameraImpl::SetVideoMode(const VideoMode& mode, CS_Status* status) {
return [m_objc setVideoMode:mode status:status];
}
bool UsbCameraImpl::SetPixelFormat(VideoMode::PixelFormat pixelFormat,
bool UsbCameraImpl::SetPixelFormat(wpi::util::PixelFormat pixelFormat,
CS_Status* status) {
return [m_objc setPixelFormat:pixelFormat status:status];
}

View File

@@ -13,6 +13,7 @@
#import "UvcControlImpl.hpp"
#include "wpi/cs/cscore_cpp.hpp"
#include "wpi/util/PixelFormat.hpp"
// Quirk: exposure auto is 3 for on, 1 for off
#define kPropertyAutoExposureOn 3
@@ -83,7 +84,7 @@ class UsbCameraImpl;
- (void)setExposureManual:(int)value status:(CS_Status*)status;
- (bool)setVideoMode:(const wpi::cs::VideoMode&)mode status:(CS_Status*)status;
- (bool)setPixelFormat:(wpi::cs::VideoMode::PixelFormat)pixelFormat
- (bool)setPixelFormat:(wpi::util::PixelFormat)pixelFormat
status:(CS_Status*)status;
- (bool)setResolutionWidth:(int)width
withHeight:(int)height

View File

@@ -10,6 +10,7 @@
#include "Notifier.hpp"
#include "Log.hpp"
#include "UsbCameraImpl.hpp"
#include "wpi/util/PixelFormat.hpp"
template <typename S, typename... Args>
inline void NamedLog(UsbCameraImplObjc* objc, unsigned int level,
@@ -414,7 +415,7 @@ using namespace wpi::cs;
});
return true;
}
- (bool)setPixelFormat:(wpi::cs::VideoMode::PixelFormat)pixelFormat
- (bool)setPixelFormat:(wpi::util::PixelFormat)pixelFormat
status:(CS_Status*)status {
dispatch_async_and_wait(self.sessionQueue, ^{
auto sharedThis = self.cppImpl.lock();
@@ -464,8 +465,8 @@ using namespace wpi::cs;
}
if (newMode != sharedThis->objcGetVideoMode()) {
OBJCDEBUG3("Trying Mode {} {} {} {}", newMode.pixelFormat, newMode.width,
newMode.height, newMode.fps);
OBJCDEBUG3("Trying Mode {} {} {} {}", static_cast<int>(newMode.pixelFormat),
newMode.width, newMode.height, newMode.fps);
int localFPS = 0;
AVCaptureDeviceFormat* newModeType = [self deviceCheckModeValid:&newMode
withFps:&localFPS];
@@ -693,13 +694,13 @@ using namespace wpi::cs;
propertyAutoCache[nameStr] = propID;
}
static wpi::cs::VideoMode::PixelFormat FourCCToPixelFormat(FourCharCode fourcc) {
static wpi::util::PixelFormat FourCCToPixelFormat(FourCharCode fourcc) {
switch (fourcc) {
case kCVPixelFormatType_422YpCbCr8_yuvs:
case kCVPixelFormatType_422YpCbCr8FullRange:
return wpi::cs::VideoMode::PixelFormat::kYUYV;
return wpi::util::PixelFormat::kYUYV;
default:
return wpi::cs::VideoMode::PixelFormat::kBGR;
return wpi::util::PixelFormat::kBGR;
}
}
@@ -768,7 +769,8 @@ static wpi::cs::VideoMode::PixelFormat FourCCToPixelFormat(FourCharCode fourcc)
return nil;
}
OBJCDEBUG3("Checking mode {} {} {} {}", toCheck->pixelFormat, toCheck->width,
OBJCDEBUG3("Checking mode {} {} {} {}",
static_cast<int>(toCheck->pixelFormat), toCheck->width,
toCheck->height, toCheck->fps);
std::vector<CameraModeStore>& platformModes =
sharedThis->objcGetPlatformVideoModes();

View File

@@ -36,6 +36,7 @@
#include "wpi/cs/cscore_cpp.hpp"
#include "wpi/util/ConvertUTF.hpp"
#include "wpi/util/MemAlloc.hpp"
#include "wpi/util/PixelFormat.hpp"
#include "wpi/util/SmallString.hpp"
#include "wpi/util/StringExtras.hpp"
#include "wpi/util/timestamp.hpp"
@@ -135,13 +136,13 @@ void UsbCameraImpl::SetExposureManual(int value, CS_Status* status) {
}
bool UsbCameraImpl::SetVideoMode(const VideoMode& mode, CS_Status* status) {
if (mode.pixelFormat == VideoMode::kUnknown) {
if (mode.pixelFormat == wpi::util::PixelFormat::kUnknown) {
*status = CS_UNSUPPORTED_MODE;
return false;
}
Message msg{Message::kCmdSetMode};
msg.data[0] = mode.pixelFormat;
msg.data[0] = static_cast<int>(mode.pixelFormat);
msg.data[1] = mode.width;
msg.data[2] = mode.height;
msg.data[3] = mode.fps;
@@ -152,14 +153,14 @@ bool UsbCameraImpl::SetVideoMode(const VideoMode& mode, CS_Status* status) {
return result == 0;
}
bool UsbCameraImpl::SetPixelFormat(VideoMode::PixelFormat pixelFormat,
bool UsbCameraImpl::SetPixelFormat(wpi::util::PixelFormat pixelFormat,
CS_Status* status) {
if (pixelFormat == VideoMode::kUnknown) {
if (pixelFormat == wpi::util::PixelFormat::kUnknown) {
*status = CS_UNSUPPORTED_MODE;
return false;
}
Message msg{Message::kCmdSetPixelFormat};
msg.data[0] = pixelFormat;
msg.data[0] = static_cast<int>(pixelFormat);
auto result =
m_messagePump->SendWindowMessage<CS_Status, Message::Kind, Message*>(
SetCameraMessage, msg.kind, &msg);
@@ -340,7 +341,7 @@ void UsbCameraImpl::ProcessFrame(IMFSample* videoSample,
}
std::string_view data_view{reinterpret_cast<char*>(ptr), length};
SourceImpl::PutFrame(static_cast<VideoMode::PixelFormat>(mode.pixelFormat),
SourceImpl::PutFrame(static_cast<wpi::util::PixelFormat>(mode.pixelFormat),
mode.width, mode.height, data_view, currentTime);
if (buffer2d) {
@@ -425,22 +426,22 @@ LRESULT UsbCameraImpl::PumpMain(HWND hwnd, UINT uiMsg, WPARAM wParam,
return 0l;
}
static wpi::cs::VideoMode::PixelFormat GetFromGUID(const GUID& guid) {
static wpi::util::PixelFormat GetFromGUID(const GUID& guid) {
// Compare GUID to one of the supported ones
if (IsEqualGUID(guid, MFVideoFormat_L8)) {
return wpi::cs::VideoMode::PixelFormat::kGray;
return wpi::util::PixelFormat::kGray;
} else if (IsEqualGUID(guid, MFVideoFormat_L16)) {
return wpi::cs::VideoMode::PixelFormat::kY16;
return wpi::util::PixelFormat::kY16;
} else if (IsEqualGUID(guid, MFVideoFormat_YUY2)) {
return wpi::cs::VideoMode::PixelFormat::kYUYV;
return wpi::util::PixelFormat::kYUYV;
} else if (IsEqualGUID(guid, MFVideoFormat_MJPG)) {
return wpi::cs::VideoMode::PixelFormat::kMJPEG;
return wpi::util::PixelFormat::kMJPEG;
} else if (IsEqualGUID(guid, MFVideoFormat_RGB565)) {
return wpi::cs::VideoMode::PixelFormat::kRGB565;
return wpi::util::PixelFormat::kRGB565;
} else if (IsEqualGUID(guid, MFVideoFormat_UYVY)) {
return wpi::cs::VideoMode::PixelFormat::kUYVY;
return wpi::util::PixelFormat::kUYVY;
} else {
return wpi::cs::VideoMode::PixelFormat::kUnknown;
return wpi::util::PixelFormat::kUnknown;
}
}
@@ -831,13 +832,13 @@ CS_StatusValue UsbCameraImpl::DeviceCmdSetMode(
std::unique_lock<wpi::util::mutex>& lock, const Message& msg) {
VideoMode newMode;
if (msg.kind == Message::kCmdSetMode) {
newMode.pixelFormat = msg.data[0];
newMode.pixelFormat = static_cast<wpi::util::PixelFormat>(msg.data[0]);
newMode.width = msg.data[1];
newMode.height = msg.data[2];
newMode.fps = msg.data[3];
} else if (msg.kind == Message::kCmdSetPixelFormat) {
newMode = m_mode;
newMode.pixelFormat = msg.data[0];
newMode.pixelFormat = static_cast<wpi::util::PixelFormat>(msg.data[0]);
} else if (msg.kind == Message::kCmdSetResolution) {
newMode = m_mode;
newMode.width = msg.data[0];
@@ -991,7 +992,7 @@ void UsbCameraImpl::DeviceCacheVideoModes() {
nativeType->GetGUID(MF_MT_SUBTYPE, &guid);
auto format = GetFromGUID(guid);
if (format == VideoMode::kUnknown) {
if (format == wpi::util::PixelFormat::kUnknown) {
count++;
// Don't put in unknowns
continue;

View File

@@ -25,6 +25,7 @@
#include "SourceImpl.hpp"
#include "UsbCameraProperty.hpp"
#include "WindowsMessagePump.hpp"
#include "wpi/util/PixelFormat.hpp"
#include "wpi/util/SmallVector.hpp"
#include "wpi/util/condition_variable.hpp"
#include "wpi/util/mutex.hpp"
@@ -61,7 +62,7 @@ class UsbCameraImpl : public SourceImpl,
void SetExposureManual(int value, CS_Status* status) override;
bool SetVideoMode(const VideoMode& mode, CS_Status* status) override;
bool SetPixelFormat(VideoMode::PixelFormat pixelFormat,
bool SetPixelFormat(wpi::util::PixelFormat pixelFormat,
CS_Status* status) override;
bool SetResolution(int width, int height, CS_Status* status) override;
bool SetFPS(int fps, CS_Status* status) override;

View File

@@ -4,7 +4,8 @@
#include <gtest/gtest.h>
#include "wpi/cs/cscore.h"
#include "wpi/cs/HttpCamera.hpp"
#include "wpi/cs/cscore_cpp.hpp"
namespace wpi::cs {

View File

@@ -2,6 +2,6 @@
// 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 "wpi/cs/cscore.h"
#include "wpi/cs/cscore_c.h"
#include "wpi/hal/HAL.h"
#include "wpi/nt/ntcore.h"

View File

@@ -7,6 +7,7 @@
#include <opencv2/imgproc/imgproc.hpp>
#include "wpi/cameraserver/CameraServer.hpp"
#include "wpi/cs/HttpCamera.hpp"
#include "wpi/framework/TimedRobot.hpp"
/**

View File

@@ -44,6 +44,14 @@ def wpiutil_extension(srcs = [], header_to_dat_deps = [], extra_hdrs = [], inclu
tmpl_class_names = [],
trampolines = [],
),
struct(
class_name = "PixelFormat",
yml_file = "semiwrap/PixelFormat.yml",
header_root = "$(execpath :robotpy-native-wpiutil.copy_headers)",
header_file = "$(execpath :robotpy-native-wpiutil.copy_headers)/wpi/util/PixelFormat.hpp",
tmpl_class_names = [],
trampolines = [],
),
struct(
class_name = "RawFrame_c",
yml_file = "semiwrap/RawFrame_c.yml",

View File

@@ -0,0 +1,20 @@
// 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.
#pragma once
/**
* Pixel formats
*/
enum WPI_PixelFormat {
WPI_PIXFMT_UNKNOWN = 0, // unknown
WPI_PIXFMT_MJPEG, // Motion-JPEG (compressed image data)
WPI_PIXFMT_YUYV, // YUV 4:2:2, 16 bpp
WPI_PIXFMT_RGB565, // RGB 5-6-5, 16 bpp
WPI_PIXFMT_BGR, // BGR 8-8-8, 24 bpp
WPI_PIXFMT_GRAY, // Grayscale, 8 bpp
WPI_PIXFMT_Y16, // Grayscale, 16 bpp
WPI_PIXFMT_UYVY, // YUV 4:2:2, 16 bpp
WPI_PIXFMT_BGRA, // BGRA 8-8-8-8-, 32 bpp
};

View File

@@ -0,0 +1,26 @@
// 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.
#pragma once
#include "wpi/util/PixelFormat.h"
namespace wpi::util {
/**
* Pixel formats
*/
enum class PixelFormat {
kUnknown = WPI_PIXFMT_UNKNOWN,
kMJPEG = WPI_PIXFMT_MJPEG,
kYUYV = WPI_PIXFMT_YUYV,
kRGB565 = WPI_PIXFMT_RGB565,
kBGR = WPI_PIXFMT_BGR,
kGray = WPI_PIXFMT_GRAY,
kY16 = WPI_PIXFMT_Y16,
kUYVY = WPI_PIXFMT_UYVY,
kBGRA = WPI_PIXFMT_BGRA,
};
} // namespace wpi::util

View File

@@ -30,21 +30,6 @@ typedef struct WPI_RawFrame { // NOLINT
int timestampSrc; // WPI_TimestampSource
} WPI_RawFrame;
/**
* Pixel formats
*/
enum WPI_PixelFormat {
WPI_PIXFMT_UNKNOWN = 0, // unknown
WPI_PIXFMT_MJPEG, // Motion-JPEG (compressed image data)
WPI_PIXFMT_YUYV, // YUV 4:2:2, 16 bpp
WPI_PIXFMT_RGB565, // RGB 5-6-5, 16 bpp
WPI_PIXFMT_BGR, // BGR 8-8-8, 24 bpp
WPI_PIXFMT_GRAY, // Grayscale, 8 bpp
WPI_PIXFMT_Y16, // Grayscale, 16 bpp
WPI_PIXFMT_UYVY, // YUV 4:2:2, 16 bpp
WPI_PIXFMT_BGRA, // BGRA 8-8-8-8-, 32 bpp
};
/**
* Timestamp metadata. Timebase is the same as wpi::util::Now
*/

View File

@@ -4,6 +4,7 @@
#pragma once
#include "wpi/util/PixelFormat.h"
#include "wpi/util/RawFrame.h"
#ifdef WPI_RAWFRAME_JNI

View File

@@ -71,6 +71,7 @@ Color = "wpi/util/Color.hpp"
Color8Bit = "wpi/util/Color8Bit.hpp"
StackTrace = "wpi/util/StackTrace.hpp"
Synchronization = "wpi/util/Synchronization.hpp"
PixelFormat = "wpi/util/PixelFormat.hpp"
RawFrame_c = "wpi/util/RawFrame.h"
RawFrame = "wpi/util/RawFrame.hpp"

View File

@@ -0,0 +1,2 @@
enums:
PixelFormat: