2016-09-05 12:00:04 -07:00
|
|
|
/*----------------------------------------------------------------------------*/
|
2019-05-17 17:35:09 -07:00
|
|
|
/* Copyright (c) 2016-2019 FIRST. All Rights Reserved. */
|
2016-09-05 12:00:04 -07:00
|
|
|
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
|
|
|
|
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
|
|
|
|
/* the project. */
|
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
#include "Frame.h"
|
|
|
|
|
|
2018-07-27 21:51:40 -07:00
|
|
|
#include <cstdlib>
|
|
|
|
|
|
2017-08-25 17:48:06 -07:00
|
|
|
#include <opencv2/core/core.hpp>
|
|
|
|
|
#include <opencv2/highgui/highgui.hpp>
|
|
|
|
|
#include <opencv2/imgproc/imgproc.hpp>
|
2016-12-20 20:48:31 -08:00
|
|
|
|
2018-10-31 20:22:58 -07:00
|
|
|
#include "Instance.h"
|
2016-12-20 20:48:31 -08:00
|
|
|
#include "Log.h"
|
2016-09-05 12:00:04 -07:00
|
|
|
#include "SourceImpl.h"
|
|
|
|
|
|
|
|
|
|
using namespace cs;
|
|
|
|
|
|
2018-07-29 12:53:41 -07:00
|
|
|
Frame::Frame(SourceImpl& source, const wpi::Twine& error, Time time)
|
2016-12-20 20:48:31 -08:00
|
|
|
: m_impl{source.AllocFrameImpl().release()} {
|
|
|
|
|
m_impl->refcount = 1;
|
2018-07-29 12:53:41 -07:00
|
|
|
m_impl->error = error.str();
|
2016-12-20 20:48:31 -08:00
|
|
|
m_impl->time = time;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Frame::Frame(SourceImpl& source, std::unique_ptr<Image> image, Time time)
|
|
|
|
|
: m_impl{source.AllocFrameImpl().release()} {
|
|
|
|
|
m_impl->refcount = 1;
|
|
|
|
|
m_impl->error.resize(0);
|
|
|
|
|
m_impl->time = time;
|
|
|
|
|
m_impl->images.push_back(image.release());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Image* Frame::GetNearestImage(int width, int height) const {
|
|
|
|
|
if (!m_impl) return nullptr;
|
2019-07-07 19:17:14 -07:00
|
|
|
std::lock_guard lock(m_impl->mutex);
|
2016-12-20 20:48:31 -08:00
|
|
|
Image* found = nullptr;
|
|
|
|
|
|
|
|
|
|
// Ideally we want the smallest image at least width/height in size
|
|
|
|
|
for (auto i : m_impl->images) {
|
|
|
|
|
if (i->IsLarger(width, height) && (!found || (i->IsSmaller(*found))))
|
|
|
|
|
found = i;
|
|
|
|
|
}
|
|
|
|
|
if (found) return found;
|
|
|
|
|
|
|
|
|
|
// Find the largest image (will be less than width/height)
|
|
|
|
|
for (auto i : m_impl->images) {
|
|
|
|
|
if (!found || (i->IsLarger(*found))) found = i;
|
|
|
|
|
}
|
|
|
|
|
if (found) return found;
|
|
|
|
|
|
|
|
|
|
// Shouldn't reach this, but just in case...
|
|
|
|
|
return m_impl->images.empty() ? nullptr : m_impl->images[0];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Image* Frame::GetNearestImage(int width, int height,
|
2018-07-27 21:51:40 -07:00
|
|
|
VideoMode::PixelFormat pixelFormat,
|
|
|
|
|
int jpegQuality) const {
|
2016-12-20 20:48:31 -08:00
|
|
|
if (!m_impl) return nullptr;
|
2019-07-07 19:17:14 -07:00
|
|
|
std::lock_guard lock(m_impl->mutex);
|
2016-12-20 20:48:31 -08:00
|
|
|
Image* found = nullptr;
|
|
|
|
|
|
|
|
|
|
// We want the smallest image at least width/height (or the next largest),
|
|
|
|
|
// but the primary search order is in order of conversion cost.
|
|
|
|
|
// If we don't find exactly what we want, we prefer non-JPEG source images
|
|
|
|
|
// (because JPEG source images require a decompression step).
|
|
|
|
|
// While the searching takes a little time, it pales in comparison to the
|
|
|
|
|
// image processing to come, so it's worth spending a little extra time
|
|
|
|
|
// looking for the most efficient conversion.
|
|
|
|
|
|
2018-07-27 21:51:40 -07:00
|
|
|
// 1) Same width, height, pixelFormat, and (possibly) JPEG quality
|
|
|
|
|
// (e.g. exactly what we want)
|
2016-12-20 20:48:31 -08:00
|
|
|
for (auto i : m_impl->images) {
|
2018-07-27 21:51:40 -07:00
|
|
|
if (i->Is(width, height, pixelFormat, jpegQuality)) return i;
|
2016-12-20 20:48:31 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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) {
|
|
|
|
|
for (auto i : m_impl->images) {
|
|
|
|
|
if (i->Is(width, height, VideoMode::kBGR)) return i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (auto i : m_impl->images) {
|
|
|
|
|
if (i->Is(width, height) && i->pixelFormat != VideoMode::kMJPEG) return i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3) Different width, height, same pixelFormat (only if non-JPEG) (resample)
|
|
|
|
|
if (pixelFormat != VideoMode::kMJPEG) {
|
|
|
|
|
// 3a) Smallest image at least width/height in size
|
|
|
|
|
for (auto i : m_impl->images) {
|
|
|
|
|
if (i->IsLarger(width, height) && i->pixelFormat == pixelFormat &&
|
|
|
|
|
(!found || (i->IsSmaller(*found))))
|
|
|
|
|
found = i;
|
|
|
|
|
}
|
|
|
|
|
if (found) return found;
|
|
|
|
|
|
|
|
|
|
// 3b) Largest image (less than width/height)
|
|
|
|
|
for (auto i : m_impl->images) {
|
|
|
|
|
if (i->pixelFormat == pixelFormat && (!found || (i->IsLarger(*found))))
|
|
|
|
|
found = i;
|
|
|
|
|
}
|
|
|
|
|
if (found) return found;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4) Different width, height, different (but non-JPEG) pixelFormat
|
|
|
|
|
// (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 &&
|
|
|
|
|
(!found || (i->IsSmaller(*found))))
|
|
|
|
|
found = i;
|
|
|
|
|
}
|
|
|
|
|
if (found) return found;
|
|
|
|
|
|
|
|
|
|
// 4b) Largest image (less than width/height)
|
|
|
|
|
for (auto i : m_impl->images) {
|
|
|
|
|
if (i->pixelFormat != VideoMode::kMJPEG &&
|
|
|
|
|
(!found || (i->IsLarger(*found))))
|
|
|
|
|
found = i;
|
|
|
|
|
}
|
|
|
|
|
if (found) return found;
|
|
|
|
|
|
2018-07-27 21:51:40 -07:00
|
|
|
// 5) Same width, height, JPEG pixelFormat (decompression). As there may be
|
|
|
|
|
// multiple JPEG images, find the highest quality one.
|
2016-12-20 20:48:31 -08:00
|
|
|
for (auto i : m_impl->images) {
|
2018-07-27 21:51:40 -07:00
|
|
|
if (i->Is(width, height, VideoMode::kMJPEG) &&
|
|
|
|
|
(!found || i->jpegQuality > found->jpegQuality)) {
|
|
|
|
|
found = i;
|
|
|
|
|
// consider one without a quality setting to be the highest quality
|
|
|
|
|
// (e.g. directly from the camera)
|
|
|
|
|
if (i->jpegQuality == -1) break;
|
|
|
|
|
}
|
2016-12-20 20:48:31 -08:00
|
|
|
}
|
2018-07-27 21:51:40 -07:00
|
|
|
if (found) return found;
|
2016-12-20 20:48:31 -08:00
|
|
|
|
|
|
|
|
// 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 &&
|
|
|
|
|
(!found || (i->IsSmaller(*found))))
|
|
|
|
|
found = i;
|
|
|
|
|
}
|
|
|
|
|
if (found) return found;
|
|
|
|
|
|
|
|
|
|
// 6b) Largest image (less than width/height)
|
|
|
|
|
for (auto i : m_impl->images) {
|
|
|
|
|
if (i->pixelFormat != VideoMode::kMJPEG &&
|
|
|
|
|
(!found || (i->IsLarger(*found))))
|
|
|
|
|
found = i;
|
|
|
|
|
}
|
|
|
|
|
if (found) return found;
|
|
|
|
|
|
|
|
|
|
// Shouldn't reach this, but just in case...
|
|
|
|
|
return m_impl->images.empty() ? nullptr : m_impl->images[0];
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-27 21:51:40 -07:00
|
|
|
Image* Frame::ConvertImpl(Image* image, VideoMode::PixelFormat pixelFormat,
|
|
|
|
|
int requiredJpegQuality, int defaultJpegQuality) {
|
|
|
|
|
if (!image ||
|
|
|
|
|
image->Is(image->width, image->height, pixelFormat, requiredJpegQuality))
|
|
|
|
|
return image;
|
2016-12-20 20:48:31 -08:00
|
|
|
Image* cur = image;
|
|
|
|
|
|
|
|
|
|
// If the source image is a JPEG, we need to decode it before we can do
|
|
|
|
|
// 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) {
|
|
|
|
|
cur = ConvertMJPEGToBGR(cur);
|
|
|
|
|
if (pixelFormat == VideoMode::kBGR) return cur;
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-27 21:51:40 -07:00
|
|
|
// Color convert
|
2016-12-20 20:48:31 -08:00
|
|
|
switch (pixelFormat) {
|
|
|
|
|
case VideoMode::kRGB565:
|
2016-12-23 21:01:21 -08:00
|
|
|
// If source is YUYV or Gray, need to convert to BGR first
|
2016-12-20 20:48:31 -08:00
|
|
|
if (cur->pixelFormat == VideoMode::kYUYV) {
|
|
|
|
|
// Check to see if BGR version already exists...
|
|
|
|
|
if (Image* newImage =
|
|
|
|
|
GetExistingImage(cur->width, cur->height, VideoMode::kBGR))
|
|
|
|
|
cur = newImage;
|
|
|
|
|
else
|
|
|
|
|
cur = ConvertYUYVToBGR(cur);
|
2016-12-23 21:01:21 -08:00
|
|
|
} else if (cur->pixelFormat == VideoMode::kGray) {
|
|
|
|
|
// Check to see if BGR version already exists...
|
|
|
|
|
if (Image* newImage =
|
|
|
|
|
GetExistingImage(cur->width, cur->height, VideoMode::kBGR))
|
|
|
|
|
cur = newImage;
|
|
|
|
|
else
|
|
|
|
|
cur = ConvertGrayToBGR(cur);
|
2016-12-20 20:48:31 -08:00
|
|
|
}
|
|
|
|
|
return ConvertBGRToRGB565(cur);
|
2016-12-23 21:01:21 -08:00
|
|
|
case VideoMode::kGray:
|
|
|
|
|
// If source is YUYV or RGB565, need to convert to BGR first
|
|
|
|
|
if (cur->pixelFormat == VideoMode::kYUYV) {
|
|
|
|
|
// Check to see if BGR version already exists...
|
|
|
|
|
if (Image* newImage =
|
|
|
|
|
GetExistingImage(cur->width, cur->height, VideoMode::kBGR))
|
|
|
|
|
cur = newImage;
|
|
|
|
|
else
|
|
|
|
|
cur = ConvertYUYVToBGR(cur);
|
|
|
|
|
} else if (cur->pixelFormat == VideoMode::kRGB565) {
|
|
|
|
|
// Check to see if BGR version already exists...
|
|
|
|
|
if (Image* newImage =
|
|
|
|
|
GetExistingImage(cur->width, cur->height, VideoMode::kBGR))
|
|
|
|
|
cur = newImage;
|
|
|
|
|
else
|
|
|
|
|
cur = ConvertRGB565ToBGR(cur);
|
|
|
|
|
}
|
|
|
|
|
return ConvertBGRToGray(cur);
|
2016-12-20 20:48:31 -08:00
|
|
|
case VideoMode::kBGR:
|
|
|
|
|
case VideoMode::kMJPEG:
|
2017-08-25 17:48:06 -07:00
|
|
|
if (cur->pixelFormat == VideoMode::kYUYV) {
|
2016-12-20 20:48:31 -08:00
|
|
|
cur = ConvertYUYVToBGR(cur);
|
2017-08-25 17:48:06 -07:00
|
|
|
} else if (cur->pixelFormat == VideoMode::kRGB565) {
|
2016-12-20 20:48:31 -08:00
|
|
|
cur = ConvertRGB565ToBGR(cur);
|
2017-08-25 17:48:06 -07:00
|
|
|
} else if (cur->pixelFormat == VideoMode::kGray) {
|
2016-12-23 21:01:21 -08:00
|
|
|
if (pixelFormat == VideoMode::kBGR)
|
|
|
|
|
return ConvertGrayToBGR(cur);
|
|
|
|
|
else
|
2018-07-27 21:51:40 -07:00
|
|
|
return ConvertGrayToMJPEG(cur, defaultJpegQuality);
|
2016-12-23 21:01:21 -08:00
|
|
|
}
|
2016-12-20 20:48:31 -08:00
|
|
|
break;
|
|
|
|
|
case VideoMode::kYUYV:
|
|
|
|
|
default:
|
|
|
|
|
return nullptr; // Unsupported
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compress if destination is JPEG
|
|
|
|
|
if (pixelFormat == VideoMode::kMJPEG)
|
2018-07-27 21:51:40 -07:00
|
|
|
cur = ConvertBGRToMJPEG(cur, defaultJpegQuality);
|
2016-12-20 20:48:31 -08:00
|
|
|
|
|
|
|
|
return cur;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Image* Frame::ConvertMJPEGToBGR(Image* image) {
|
|
|
|
|
if (!image || image->pixelFormat != VideoMode::kMJPEG) return nullptr;
|
|
|
|
|
|
|
|
|
|
// Allocate an BGR image
|
|
|
|
|
auto newImage =
|
|
|
|
|
m_impl->source.AllocImage(VideoMode::kBGR, image->width, image->height,
|
|
|
|
|
image->width * image->height * 3);
|
|
|
|
|
|
|
|
|
|
// Decode
|
|
|
|
|
cv::Mat newMat = newImage->AsMat();
|
|
|
|
|
cv::imdecode(image->AsInputArray(), cv::IMREAD_COLOR, &newMat);
|
|
|
|
|
|
|
|
|
|
// Save the result
|
|
|
|
|
Image* rv = newImage.release();
|
|
|
|
|
if (m_impl) {
|
2019-07-07 19:17:14 -07:00
|
|
|
std::lock_guard lock(m_impl->mutex);
|
2016-12-20 20:48:31 -08:00
|
|
|
m_impl->images.push_back(rv);
|
|
|
|
|
}
|
|
|
|
|
return rv;
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-23 21:01:21 -08:00
|
|
|
Image* Frame::ConvertMJPEGToGray(Image* image) {
|
|
|
|
|
if (!image || image->pixelFormat != VideoMode::kMJPEG) return nullptr;
|
|
|
|
|
|
|
|
|
|
// Allocate an grayscale image
|
|
|
|
|
auto newImage =
|
|
|
|
|
m_impl->source.AllocImage(VideoMode::kGray, image->width, image->height,
|
|
|
|
|
image->width * image->height);
|
|
|
|
|
|
|
|
|
|
// Decode
|
|
|
|
|
cv::Mat newMat = newImage->AsMat();
|
|
|
|
|
cv::imdecode(image->AsInputArray(), cv::IMREAD_GRAYSCALE, &newMat);
|
|
|
|
|
|
|
|
|
|
// Save the result
|
|
|
|
|
Image* rv = newImage.release();
|
|
|
|
|
if (m_impl) {
|
2019-07-07 19:17:14 -07:00
|
|
|
std::lock_guard lock(m_impl->mutex);
|
2016-12-23 21:01:21 -08:00
|
|
|
m_impl->images.push_back(rv);
|
|
|
|
|
}
|
|
|
|
|
return rv;
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-20 20:48:31 -08:00
|
|
|
Image* Frame::ConvertYUYVToBGR(Image* image) {
|
|
|
|
|
if (!image || image->pixelFormat != VideoMode::kYUYV) return nullptr;
|
|
|
|
|
|
|
|
|
|
// Allocate a BGR image
|
|
|
|
|
auto newImage =
|
|
|
|
|
m_impl->source.AllocImage(VideoMode::kBGR, image->width, image->height,
|
|
|
|
|
image->width * image->height * 3);
|
|
|
|
|
|
|
|
|
|
// Convert
|
|
|
|
|
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_YUV2BGR_YUYV);
|
|
|
|
|
|
|
|
|
|
// Save the result
|
|
|
|
|
Image* rv = newImage.release();
|
|
|
|
|
if (m_impl) {
|
2019-07-07 19:17:14 -07:00
|
|
|
std::lock_guard lock(m_impl->mutex);
|
2016-12-20 20:48:31 -08:00
|
|
|
m_impl->images.push_back(rv);
|
|
|
|
|
}
|
|
|
|
|
return rv;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Image* Frame::ConvertBGRToRGB565(Image* image) {
|
|
|
|
|
if (!image || image->pixelFormat != VideoMode::kBGR) return nullptr;
|
|
|
|
|
|
|
|
|
|
// Allocate a RGB565 image
|
|
|
|
|
auto newImage =
|
|
|
|
|
m_impl->source.AllocImage(VideoMode::kRGB565, image->width, image->height,
|
|
|
|
|
image->width * image->height * 2);
|
|
|
|
|
|
|
|
|
|
// Convert
|
|
|
|
|
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_RGB2BGR565);
|
|
|
|
|
|
|
|
|
|
// Save the result
|
|
|
|
|
Image* rv = newImage.release();
|
|
|
|
|
if (m_impl) {
|
2019-07-07 19:17:14 -07:00
|
|
|
std::lock_guard lock(m_impl->mutex);
|
2016-12-20 20:48:31 -08:00
|
|
|
m_impl->images.push_back(rv);
|
|
|
|
|
}
|
|
|
|
|
return rv;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Image* Frame::ConvertRGB565ToBGR(Image* image) {
|
|
|
|
|
if (!image || image->pixelFormat != VideoMode::kRGB565) return nullptr;
|
|
|
|
|
|
|
|
|
|
// Allocate a BGR image
|
|
|
|
|
auto newImage =
|
|
|
|
|
m_impl->source.AllocImage(VideoMode::kBGR, image->width, image->height,
|
|
|
|
|
image->width * image->height * 3);
|
|
|
|
|
|
|
|
|
|
// Convert
|
|
|
|
|
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_BGR5652RGB);
|
|
|
|
|
|
|
|
|
|
// Save the result
|
|
|
|
|
Image* rv = newImage.release();
|
|
|
|
|
if (m_impl) {
|
2019-07-07 19:17:14 -07:00
|
|
|
std::lock_guard lock(m_impl->mutex);
|
2016-12-20 20:48:31 -08:00
|
|
|
m_impl->images.push_back(rv);
|
|
|
|
|
}
|
|
|
|
|
return rv;
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-23 21:01:21 -08:00
|
|
|
Image* Frame::ConvertBGRToGray(Image* image) {
|
|
|
|
|
if (!image || image->pixelFormat != VideoMode::kBGR) return nullptr;
|
|
|
|
|
|
|
|
|
|
// Allocate a Grayscale image
|
|
|
|
|
auto newImage =
|
|
|
|
|
m_impl->source.AllocImage(VideoMode::kGray, image->width, image->height,
|
|
|
|
|
image->width * image->height);
|
|
|
|
|
|
|
|
|
|
// Convert
|
|
|
|
|
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_BGR2GRAY);
|
|
|
|
|
|
|
|
|
|
// Save the result
|
|
|
|
|
Image* rv = newImage.release();
|
|
|
|
|
if (m_impl) {
|
2019-07-07 19:17:14 -07:00
|
|
|
std::lock_guard lock(m_impl->mutex);
|
2016-12-23 21:01:21 -08:00
|
|
|
m_impl->images.push_back(rv);
|
|
|
|
|
}
|
|
|
|
|
return rv;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Image* Frame::ConvertGrayToBGR(Image* image) {
|
2019-04-27 20:17:17 -07:00
|
|
|
if (!image || image->pixelFormat != VideoMode::kGray) return nullptr;
|
2016-12-23 21:01:21 -08:00
|
|
|
|
|
|
|
|
// Allocate a BGR image
|
|
|
|
|
auto newImage =
|
|
|
|
|
m_impl->source.AllocImage(VideoMode::kBGR, image->width, image->height,
|
|
|
|
|
image->width * image->height * 3);
|
|
|
|
|
|
|
|
|
|
// Convert
|
|
|
|
|
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_GRAY2BGR);
|
|
|
|
|
|
|
|
|
|
// Save the result
|
|
|
|
|
Image* rv = newImage.release();
|
|
|
|
|
if (m_impl) {
|
2019-07-07 19:17:14 -07:00
|
|
|
std::lock_guard lock(m_impl->mutex);
|
2016-12-23 21:01:21 -08:00
|
|
|
m_impl->images.push_back(rv);
|
|
|
|
|
}
|
|
|
|
|
return rv;
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-20 20:48:31 -08:00
|
|
|
Image* Frame::ConvertBGRToMJPEG(Image* image, int quality) {
|
|
|
|
|
if (!image || image->pixelFormat != VideoMode::kBGR) return nullptr;
|
|
|
|
|
if (!m_impl) return nullptr;
|
2019-07-07 19:17:14 -07:00
|
|
|
std::lock_guard lock(m_impl->mutex);
|
2016-12-20 20:48:31 -08:00
|
|
|
|
|
|
|
|
// Allocate a JPEG image. We don't actually know what the resulting size
|
|
|
|
|
// will be; while the destination will automatically grow, doing so will
|
|
|
|
|
// cause an extra malloc, so we don't want to be too conservative here.
|
|
|
|
|
// 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);
|
|
|
|
|
|
2016-12-23 21:01:21 -08:00
|
|
|
// Compress
|
|
|
|
|
if (m_impl->compressionParams.empty()) {
|
|
|
|
|
m_impl->compressionParams.push_back(CV_IMWRITE_JPEG_QUALITY);
|
|
|
|
|
m_impl->compressionParams.push_back(quality);
|
|
|
|
|
} else {
|
|
|
|
|
m_impl->compressionParams[1] = quality;
|
|
|
|
|
}
|
|
|
|
|
cv::imencode(".jpg", image->AsMat(), newImage->vec(),
|
|
|
|
|
m_impl->compressionParams);
|
|
|
|
|
|
|
|
|
|
// Save the result
|
|
|
|
|
Image* rv = newImage.release();
|
|
|
|
|
m_impl->images.push_back(rv);
|
|
|
|
|
return rv;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Image* Frame::ConvertGrayToMJPEG(Image* image, int quality) {
|
|
|
|
|
if (!image || image->pixelFormat != VideoMode::kGray) return nullptr;
|
|
|
|
|
if (!m_impl) return nullptr;
|
2019-07-07 19:17:14 -07:00
|
|
|
std::lock_guard lock(m_impl->mutex);
|
2016-12-23 21:01:21 -08:00
|
|
|
|
|
|
|
|
// Allocate a JPEG image. We don't actually know what the resulting size
|
|
|
|
|
// will be; while the destination will automatically grow, doing so will
|
|
|
|
|
// cause an extra malloc, so we don't want to be too conservative here.
|
|
|
|
|
// 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);
|
|
|
|
|
|
2016-12-20 20:48:31 -08:00
|
|
|
// Compress
|
|
|
|
|
if (m_impl->compressionParams.empty()) {
|
|
|
|
|
m_impl->compressionParams.push_back(CV_IMWRITE_JPEG_QUALITY);
|
|
|
|
|
m_impl->compressionParams.push_back(quality);
|
|
|
|
|
} else {
|
|
|
|
|
m_impl->compressionParams[1] = quality;
|
|
|
|
|
}
|
|
|
|
|
cv::imencode(".jpg", image->AsMat(), newImage->vec(),
|
|
|
|
|
m_impl->compressionParams);
|
|
|
|
|
|
|
|
|
|
// Save the result
|
|
|
|
|
Image* rv = newImage.release();
|
|
|
|
|
m_impl->images.push_back(rv);
|
|
|
|
|
return rv;
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-27 21:51:40 -07:00
|
|
|
Image* Frame::GetImageImpl(int width, int height,
|
|
|
|
|
VideoMode::PixelFormat pixelFormat,
|
|
|
|
|
int requiredJpegQuality, int defaultJpegQuality) {
|
2016-12-20 20:48:31 -08:00
|
|
|
if (!m_impl) return nullptr;
|
2019-07-07 19:17:14 -07:00
|
|
|
std::lock_guard lock(m_impl->mutex);
|
2018-07-27 21:51:40 -07:00
|
|
|
Image* cur = GetNearestImage(width, height, pixelFormat, requiredJpegQuality);
|
|
|
|
|
if (!cur || cur->Is(width, height, pixelFormat, requiredJpegQuality))
|
|
|
|
|
return cur;
|
2016-12-20 20:48:31 -08:00
|
|
|
|
2018-10-31 20:22:58 -07:00
|
|
|
WPI_DEBUG4(Instance::GetInstance().logger,
|
|
|
|
|
"converting image from " << cur->width << "x" << cur->height
|
|
|
|
|
<< " type " << cur->pixelFormat << " to "
|
|
|
|
|
<< width << "x" << height << " type "
|
|
|
|
|
<< pixelFormat);
|
2016-12-20 20:48:31 -08:00
|
|
|
|
|
|
|
|
// If the source image is a JPEG, we need to decode it before we can do
|
|
|
|
|
// anything else with it. Note that if the destination format is JPEG, we
|
2018-07-27 21:51:40 -07:00
|
|
|
// still need to do this (unless the width/height/compression were the same,
|
|
|
|
|
// in which case we already returned the existing JPEG above).
|
2016-12-20 20:48:31 -08:00
|
|
|
if (cur->pixelFormat == VideoMode::kMJPEG) cur = ConvertMJPEGToBGR(cur);
|
|
|
|
|
|
|
|
|
|
// Resize
|
|
|
|
|
if (!cur->Is(width, height)) {
|
|
|
|
|
// Allocate an image.
|
|
|
|
|
auto newImage = m_impl->source.AllocImage(
|
|
|
|
|
cur->pixelFormat, width, height,
|
|
|
|
|
width * height * (cur->size() / (cur->width * cur->height)));
|
|
|
|
|
|
|
|
|
|
// Resize
|
|
|
|
|
cv::Mat newMat = newImage->AsMat();
|
|
|
|
|
cv::resize(cur->AsMat(), newMat, newMat.size(), 0, 0);
|
|
|
|
|
|
|
|
|
|
// Save the result
|
|
|
|
|
cur = newImage.release();
|
|
|
|
|
m_impl->images.push_back(cur);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert to output format
|
2018-07-27 21:51:40 -07:00
|
|
|
return ConvertImpl(cur, pixelFormat, requiredJpegQuality, defaultJpegQuality);
|
2016-12-20 20:48:31 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Frame::GetCv(cv::Mat& image, int width, int height) {
|
|
|
|
|
Image* rawImage = GetImage(width, height, VideoMode::kBGR);
|
|
|
|
|
if (!rawImage) return false;
|
|
|
|
|
rawImage->AsMat().copyTo(image);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-13 00:16:24 -07:00
|
|
|
void Frame::ReleaseFrame() {
|
2016-12-20 20:48:31 -08:00
|
|
|
for (auto image : m_impl->images)
|
|
|
|
|
m_impl->source.ReleaseImage(std::unique_ptr<Image>(image));
|
|
|
|
|
m_impl->images.clear();
|
|
|
|
|
m_impl->source.ReleaseFrameImpl(std::unique_ptr<Impl>(m_impl));
|
|
|
|
|
m_impl = nullptr;
|
2016-10-13 00:16:24 -07:00
|
|
|
}
|