mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-07-04 03:11:43 +00:00
Renamed folders for consistency, using sim/athena/shared schema (#27)
Rename the following folders: hal/lib/Athena -> hal/lib/athena hal/lib/Desktop -> hal/lib/sim hal/lib/Shared -> hal/lib/shared wpilibc/Athena -> wpilibc/athena wpilibc/simulation -> wpilibc/sim Windows users may need to run gradlew clean after updating.
This commit is contained in:
committed by
Peter Johnson
parent
54092378e9
commit
e71f454b9d
617
wpilibc/athena/src/Vision/AxisCamera.cpp
Normal file
617
wpilibc/athena/src/Vision/AxisCamera.cpp
Normal file
@@ -0,0 +1,617 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2014-2016. All Rights Reserved. */
|
||||
/* 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 "Vision/AxisCamera.h"
|
||||
|
||||
#include "WPIErrors.h"
|
||||
|
||||
#include <Timer.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
static const unsigned int kMaxPacketSize = 1536;
|
||||
static const unsigned int kImageBufferAllocationIncrement = 1000;
|
||||
|
||||
static const std::string kWhiteBalanceStrings[] = {
|
||||
"auto", "hold", "fixed_outdoor1", "fixed_outdoor2",
|
||||
"fixed_indoor", "fixed_fluor1", "fixed_fluor2",
|
||||
};
|
||||
|
||||
static const std::string kExposureControlStrings[] = {
|
||||
"auto", "hold", "flickerfree50", "flickerfree60",
|
||||
};
|
||||
|
||||
static const std::string kResolutionStrings[] = {
|
||||
"640x480", "480x360", "320x240", "240x180", "176x144", "160x120",
|
||||
};
|
||||
|
||||
static const std::string kRotationStrings[] = {
|
||||
"0", "180",
|
||||
};
|
||||
|
||||
/**
|
||||
* AxisCamera constructor.
|
||||
*
|
||||
* @param cameraHost The host to find the camera at, typically an IP address
|
||||
*/
|
||||
AxisCamera::AxisCamera(std::string const& cameraHost)
|
||||
: m_cameraHost(cameraHost) {
|
||||
m_captureThread = std::thread(&AxisCamera::Capture, this);
|
||||
}
|
||||
|
||||
AxisCamera::~AxisCamera() {
|
||||
m_done = true;
|
||||
m_captureThread.join();
|
||||
}
|
||||
|
||||
/*
|
||||
* Return true if the latest image from the camera has not been retrieved by
|
||||
* calling GetImage() yet.
|
||||
*
|
||||
* @return true if the image has not been retrieved yet.
|
||||
*/
|
||||
bool AxisCamera::IsFreshImage() const { return m_freshImage; }
|
||||
|
||||
/**
|
||||
* Get an image from the camera and store it in the provided image.
|
||||
*
|
||||
* @param image The imaq image to store the result in. This must be an HSL or
|
||||
* RGB image.
|
||||
* @return 1 upon success, zero on a failure
|
||||
*/
|
||||
int AxisCamera::GetImage(Image* image) {
|
||||
if (m_imageData.size() == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::lock_guard<priority_mutex> lock(m_imageDataMutex);
|
||||
|
||||
Priv_ReadJPEGString_C(image, m_imageData.data(), m_imageData.size());
|
||||
|
||||
m_freshImage = false;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an image from the camera and store it in the provided image.
|
||||
*
|
||||
* @param image The image to store the result in. This must be an HSL or RGB
|
||||
* image
|
||||
* @return 1 upon success, zero on a failure
|
||||
*/
|
||||
int AxisCamera::GetImage(ColorImage* image) {
|
||||
return GetImage(image->GetImaqImage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a new image object and fill it with the latest image from the
|
||||
* camera.
|
||||
*
|
||||
* The returned pointer is owned by the caller and is their responsibility to
|
||||
* delete.
|
||||
*
|
||||
* @return a pointer to an HSLImage object
|
||||
*/
|
||||
HSLImage* AxisCamera::GetImage() {
|
||||
auto image = new HSLImage();
|
||||
GetImage(image);
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy an image into an existing buffer.
|
||||
*
|
||||
* This copies an image into an existing buffer rather than creating a new image
|
||||
* in memory. That way a new image is only allocated when the image being copied
|
||||
* is larger than the destination.
|
||||
*
|
||||
* This method is called by the PCVideoServer class.
|
||||
*
|
||||
* @param imageData The destination image.
|
||||
* @param numBytes The size of the destination image.
|
||||
* @return 0 if failed (no source image or no memory), 1 if success.
|
||||
*/
|
||||
int AxisCamera::CopyJPEG(char** destImage, unsigned int& destImageSize,
|
||||
unsigned int& destImageBufferSize) {
|
||||
std::lock_guard<priority_mutex> lock(m_imageDataMutex);
|
||||
if (destImage == nullptr) {
|
||||
wpi_setWPIErrorWithContext(NullParameter, "destImage must not be nullptr");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (m_imageData.size() == 0) return 0; // if no source image
|
||||
|
||||
if (destImageBufferSize <
|
||||
m_imageData.size()) // if current destination buffer too small
|
||||
{
|
||||
if (*destImage != nullptr) delete[] * destImage;
|
||||
destImageBufferSize = m_imageData.size() + kImageBufferAllocationIncrement;
|
||||
*destImage = new char[destImageBufferSize];
|
||||
if (*destImage == nullptr) return 0;
|
||||
}
|
||||
// copy this image into destination buffer
|
||||
if (*destImage == nullptr) {
|
||||
wpi_setWPIErrorWithContext(NullParameter, "*destImage must not be nullptr");
|
||||
}
|
||||
|
||||
std::copy(m_imageData.begin(), m_imageData.end(), *destImage);
|
||||
destImageSize = m_imageData.size();
|
||||
;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a change in the brightness of the camera images.
|
||||
*
|
||||
* @param brightness valid values 0 .. 100
|
||||
*/
|
||||
void AxisCamera::WriteBrightness(int brightness) {
|
||||
if (brightness < 0 || brightness > 100) {
|
||||
wpi_setWPIErrorWithContext(ParameterOutOfRange,
|
||||
"Brightness must be from 0 to 100");
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<priority_mutex> lock(m_parametersMutex);
|
||||
|
||||
if (m_brightness != brightness) {
|
||||
m_brightness = brightness;
|
||||
m_parametersDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The configured brightness of the camera images
|
||||
*/
|
||||
int AxisCamera::GetBrightness() {
|
||||
std::lock_guard<priority_mutex> lock(m_parametersMutex);
|
||||
return m_brightness;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a change in the white balance on the camera.
|
||||
*
|
||||
* @param whiteBalance Valid values from the <code>WhiteBalance</code> enum.
|
||||
*/
|
||||
void AxisCamera::WriteWhiteBalance(AxisCamera::WhiteBalance whiteBalance) {
|
||||
std::lock_guard<priority_mutex> lock(m_parametersMutex);
|
||||
|
||||
if (m_whiteBalance != whiteBalance) {
|
||||
m_whiteBalance = whiteBalance;
|
||||
m_parametersDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The configured white balances of the camera images
|
||||
*/
|
||||
AxisCamera::WhiteBalance AxisCamera::GetWhiteBalance() {
|
||||
std::lock_guard<priority_mutex> lock(m_parametersMutex);
|
||||
return m_whiteBalance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a change in the color level of the camera images.
|
||||
*
|
||||
* @param colorLevel valid values are 0 .. 100
|
||||
*/
|
||||
void AxisCamera::WriteColorLevel(int colorLevel) {
|
||||
if (colorLevel < 0 || colorLevel > 100) {
|
||||
wpi_setWPIErrorWithContext(ParameterOutOfRange,
|
||||
"Color level must be from 0 to 100");
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<priority_mutex> lock(m_parametersMutex);
|
||||
|
||||
if (m_colorLevel != colorLevel) {
|
||||
m_colorLevel = colorLevel;
|
||||
m_parametersDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The configured color level of the camera images
|
||||
*/
|
||||
int AxisCamera::GetColorLevel() {
|
||||
std::lock_guard<priority_mutex> lock(m_parametersMutex);
|
||||
return m_colorLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a change in the camera's exposure mode.
|
||||
*
|
||||
* @param exposureControl A mode to write in the <code>Exposure</code> enum.
|
||||
*/
|
||||
void AxisCamera::WriteExposureControl(
|
||||
AxisCamera::ExposureControl exposureControl) {
|
||||
std::lock_guard<priority_mutex> lock(m_parametersMutex);
|
||||
|
||||
if (m_exposureControl != exposureControl) {
|
||||
m_exposureControl = exposureControl;
|
||||
m_parametersDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The configured exposure control mode of the camera
|
||||
*/
|
||||
AxisCamera::ExposureControl AxisCamera::GetExposureControl() {
|
||||
std::lock_guard<priority_mutex> lock(m_parametersMutex);
|
||||
return m_exposureControl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a change in the exposure priority of the camera.
|
||||
*
|
||||
* @param exposurePriority Valid values are 0, 50, 100.
|
||||
* 0 = Prioritize image quality
|
||||
* 50 = None
|
||||
* 100 = Prioritize frame rate
|
||||
*/
|
||||
void AxisCamera::WriteExposurePriority(int exposurePriority) {
|
||||
if (exposurePriority != 0 && exposurePriority != 50 &&
|
||||
exposurePriority != 100) {
|
||||
wpi_setWPIErrorWithContext(ParameterOutOfRange,
|
||||
"Exposure priority must be from 0, 50, or 100");
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<priority_mutex> lock(m_parametersMutex);
|
||||
|
||||
if (m_exposurePriority != exposurePriority) {
|
||||
m_exposurePriority = exposurePriority;
|
||||
m_parametersDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The configured exposure priority of the camera
|
||||
*/
|
||||
int AxisCamera::GetExposurePriority() {
|
||||
std::lock_guard<priority_mutex> lock(m_parametersMutex);
|
||||
return m_exposurePriority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the maximum frames per second that the camera should send
|
||||
* Write 0 to send as many as possible.
|
||||
*
|
||||
* @param maxFPS The number of frames the camera should send in a second,
|
||||
* exposure permitting.
|
||||
*/
|
||||
void AxisCamera::WriteMaxFPS(int maxFPS) {
|
||||
std::lock_guard<priority_mutex> lock(m_parametersMutex);
|
||||
|
||||
if (m_maxFPS != maxFPS) {
|
||||
m_maxFPS = maxFPS;
|
||||
m_parametersDirty = true;
|
||||
m_streamDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The configured maximum FPS of the camera
|
||||
*/
|
||||
int AxisCamera::GetMaxFPS() {
|
||||
std::lock_guard<priority_mutex> lock(m_parametersMutex);
|
||||
return m_maxFPS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write resolution value to camera.
|
||||
*
|
||||
* @param resolution The camera resolution value to write to the camera.
|
||||
*/
|
||||
void AxisCamera::WriteResolution(AxisCamera::Resolution resolution) {
|
||||
std::lock_guard<priority_mutex> lock(m_parametersMutex);
|
||||
|
||||
if (m_resolution != resolution) {
|
||||
m_resolution = resolution;
|
||||
m_parametersDirty = true;
|
||||
m_streamDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The configured resolution of the camera (not necessarily the same
|
||||
* resolution as the most recent image, if it was changed recently.)
|
||||
*/
|
||||
AxisCamera::Resolution AxisCamera::GetResolution() {
|
||||
std::lock_guard<priority_mutex> lock(m_parametersMutex);
|
||||
return m_resolution;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the rotation value to the camera.
|
||||
*
|
||||
* If you mount your camera upside down, use this to adjust the image for you.
|
||||
*
|
||||
* @param rotation The angle to rotate the camera
|
||||
* (<code>AxisCamera::Rotation::k0</code>
|
||||
* or <code>AxisCamera::Rotation::k180</code>)
|
||||
*/
|
||||
void AxisCamera::WriteRotation(AxisCamera::Rotation rotation) {
|
||||
std::lock_guard<priority_mutex> lock(m_parametersMutex);
|
||||
|
||||
if (m_rotation != rotation) {
|
||||
m_rotation = rotation;
|
||||
m_parametersDirty = true;
|
||||
m_streamDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The configured rotation mode of the camera
|
||||
*/
|
||||
AxisCamera::Rotation AxisCamera::GetRotation() {
|
||||
std::lock_guard<priority_mutex> lock(m_parametersMutex);
|
||||
return m_rotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the compression value to the camera.
|
||||
*
|
||||
* @param compression Values between 0 and 100.
|
||||
*/
|
||||
void AxisCamera::WriteCompression(int compression) {
|
||||
if (compression < 0 || compression > 100) {
|
||||
wpi_setWPIErrorWithContext(ParameterOutOfRange,
|
||||
"Compression must be from 0 to 100");
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<priority_mutex> lock(m_parametersMutex);
|
||||
|
||||
if (m_compression != compression) {
|
||||
m_compression = compression;
|
||||
m_parametersDirty = true;
|
||||
m_streamDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The configured compression level of the camera
|
||||
*/
|
||||
int AxisCamera::GetCompression() {
|
||||
std::lock_guard<priority_mutex> lock(m_parametersMutex);
|
||||
return m_compression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method called in the capture thread to receive images from the camera
|
||||
*/
|
||||
void AxisCamera::Capture() {
|
||||
int consecutiveErrors = 0;
|
||||
|
||||
// Loop on trying to setup the camera connection. This happens in a background
|
||||
// thread so it shouldn't effect the operation of user programs.
|
||||
while (!m_done) {
|
||||
std::string requestString =
|
||||
"GET /mjpg/video.mjpg HTTP/1.1\n"
|
||||
"User-Agent: HTTPStreamClient\n"
|
||||
"Connection: Keep-Alive\n"
|
||||
"Cache-Control: no-cache\n"
|
||||
"Authorization: Basic RlJDOkZSQw==\n\n";
|
||||
m_captureMutex.lock();
|
||||
m_cameraSocket = CreateCameraSocket(requestString, consecutiveErrors > 5);
|
||||
if (m_cameraSocket != -1) {
|
||||
ReadImagesFromCamera();
|
||||
consecutiveErrors = 0;
|
||||
} else {
|
||||
consecutiveErrors++;
|
||||
}
|
||||
m_captureMutex.unlock();
|
||||
Wait(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function actually reads the images from the camera.
|
||||
*/
|
||||
void AxisCamera::ReadImagesFromCamera() {
|
||||
char* imgBuffer = nullptr;
|
||||
int imgBufferLength = 0;
|
||||
|
||||
// TODO: these recv calls must be non-blocking. Otherwise if the camera
|
||||
// fails during a read, the code hangs and never retries when the camera comes
|
||||
// back up.
|
||||
|
||||
int counter = 2;
|
||||
while (!m_done) {
|
||||
char initialReadBuffer[kMaxPacketSize] = "";
|
||||
char intermediateBuffer[1];
|
||||
char* trailingPtr = initialReadBuffer;
|
||||
int trailingCounter = 0;
|
||||
while (counter) {
|
||||
// TODO: fix me... this cannot be the most efficient way to approach this,
|
||||
// reading one byte at a time.
|
||||
if (recv(m_cameraSocket, intermediateBuffer, 1, 0) == -1) {
|
||||
wpi_setErrnoErrorWithContext("Failed to read image header");
|
||||
close(m_cameraSocket);
|
||||
return;
|
||||
}
|
||||
strncat(initialReadBuffer, intermediateBuffer, 1);
|
||||
// trailingCounter ensures that we start looking for the 4 byte string
|
||||
// after
|
||||
// there is at least 4 bytes total. Kind of obscure.
|
||||
// look for 2 blank lines (\r\n)
|
||||
if (nullptr != strstr(trailingPtr, "\r\n\r\n")) {
|
||||
--counter;
|
||||
}
|
||||
if (++trailingCounter >= 4) {
|
||||
trailingPtr++;
|
||||
}
|
||||
}
|
||||
counter = 1;
|
||||
char* contentLength = strstr(initialReadBuffer, "Content-Length: ");
|
||||
if (contentLength == nullptr) {
|
||||
wpi_setWPIErrorWithContext(IncompatibleMode,
|
||||
"No content-length token found in packet");
|
||||
close(m_cameraSocket);
|
||||
if (imgBuffer) delete[] imgBuffer;
|
||||
return;
|
||||
}
|
||||
contentLength = contentLength + 16; // skip past "content length"
|
||||
int readLength = atol(contentLength); // get the image byte count
|
||||
|
||||
// Make sure buffer is large enough
|
||||
if (imgBufferLength < readLength) {
|
||||
if (imgBuffer) delete[] imgBuffer;
|
||||
imgBufferLength = readLength + kImageBufferAllocationIncrement;
|
||||
imgBuffer = new char[imgBufferLength];
|
||||
if (imgBuffer == nullptr) {
|
||||
imgBufferLength = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Read the image data for "Content-Length" bytes
|
||||
int bytesRead = 0;
|
||||
int remaining = readLength;
|
||||
while (bytesRead < readLength) {
|
||||
int bytesThisRecv =
|
||||
recv(m_cameraSocket, &imgBuffer[bytesRead], remaining, 0);
|
||||
bytesRead += bytesThisRecv;
|
||||
remaining -= bytesThisRecv;
|
||||
}
|
||||
|
||||
// Update image
|
||||
{
|
||||
std::lock_guard<priority_mutex> lock(m_imageDataMutex);
|
||||
|
||||
m_imageData.assign(imgBuffer, imgBuffer + imgBufferLength);
|
||||
m_freshImage = true;
|
||||
}
|
||||
|
||||
if (WriteParameters()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
close(m_cameraSocket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a request to the camera to set all of the parameters. This is called
|
||||
* in the capture thread between each frame. This strategy avoids making lots
|
||||
* of redundant HTTP requests, accounts for failed initial requests, and
|
||||
* avoids blocking calls in the main thread unless necessary.
|
||||
*
|
||||
* This method does nothing if no parameters have been modified since it last
|
||||
* completely successfully.
|
||||
*
|
||||
* @return <code>true</code> if the stream should be restarted due to a
|
||||
* parameter changing.
|
||||
*/
|
||||
bool AxisCamera::WriteParameters() {
|
||||
if (m_parametersDirty) {
|
||||
std::stringstream request;
|
||||
request << "GET /axis-cgi/admin/param.cgi?action=update";
|
||||
|
||||
m_parametersMutex.lock();
|
||||
request << "&ImageSource.I0.Sensor.Brightness=" << m_brightness;
|
||||
request << "&ImageSource.I0.Sensor.WhiteBalance="
|
||||
<< kWhiteBalanceStrings[m_whiteBalance];
|
||||
request << "&ImageSource.I0.Sensor.ColorLevel=" << m_colorLevel;
|
||||
request << "&ImageSource.I0.Sensor.Exposure="
|
||||
<< kExposureControlStrings[m_exposureControl];
|
||||
request << "&ImageSource.I0.Sensor.ExposurePriority=" << m_exposurePriority;
|
||||
request << "&Image.I0.Stream.FPS=" << m_maxFPS;
|
||||
request << "&Image.I0.Appearance.Resolution="
|
||||
<< kResolutionStrings[m_resolution];
|
||||
request << "&Image.I0.Appearance.Compression=" << m_compression;
|
||||
request << "&Image.I0.Appearance.Rotation=" << kRotationStrings[m_rotation];
|
||||
m_parametersMutex.unlock();
|
||||
|
||||
request << " HTTP/1.1" << std::endl;
|
||||
request << "User-Agent: HTTPStreamClient" << std::endl;
|
||||
request << "Connection: Keep-Alive" << std::endl;
|
||||
request << "Cache-Control: no-cache" << std::endl;
|
||||
request << "Authorization: Basic RlJDOkZSQw==" << std::endl;
|
||||
request << std::endl;
|
||||
|
||||
int socket = CreateCameraSocket(request.str(), false);
|
||||
if (socket == -1) {
|
||||
wpi_setErrnoErrorWithContext("Error setting camera parameters");
|
||||
} else {
|
||||
close(socket);
|
||||
m_parametersDirty = false;
|
||||
|
||||
if (m_streamDirty) {
|
||||
m_streamDirty = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a socket connected to camera.
|
||||
*
|
||||
* Used to create a connection to the camera for both capturing images and
|
||||
* setting parameters.
|
||||
*
|
||||
* @param requestString The initial request string to send upon successful
|
||||
* connection.
|
||||
* @param setError If true, rais an error if there's a problem creating the
|
||||
* connection. This is only enabled after several
|
||||
* unsucessful connections, so a single one doesn't cause
|
||||
* an error message to be printed if it immediately
|
||||
* recovers.
|
||||
* @return -1 if failed, socket handle if successful.
|
||||
*/
|
||||
int AxisCamera::CreateCameraSocket(std::string const& requestString,
|
||||
bool setError) {
|
||||
struct addrinfo* address = nullptr;
|
||||
int camSocket;
|
||||
|
||||
/* create socket */
|
||||
if ((camSocket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
|
||||
if (setError)
|
||||
wpi_setErrnoErrorWithContext("Failed to create the camera socket");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (getaddrinfo(m_cameraHost.c_str(), "80", nullptr, &address) == -1) {
|
||||
if (setError) {
|
||||
wpi_setErrnoErrorWithContext("Failed to create the camera socket");
|
||||
close(camSocket);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* connect to server */
|
||||
if (connect(camSocket, address->ai_addr, address->ai_addrlen) == -1) {
|
||||
if (setError)
|
||||
wpi_setErrnoErrorWithContext("Failed to connect to the camera");
|
||||
freeaddrinfo(address);
|
||||
close(camSocket);
|
||||
return -1;
|
||||
}
|
||||
|
||||
freeaddrinfo(address);
|
||||
|
||||
int sent = send(camSocket, requestString.c_str(), requestString.size(), 0);
|
||||
if (sent == -1) {
|
||||
if (setError)
|
||||
wpi_setErrnoErrorWithContext("Failed to send a request to the camera");
|
||||
close(camSocket);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return camSocket;
|
||||
}
|
||||
375
wpilibc/athena/src/Vision/BaeUtilities.cpp
Normal file
375
wpilibc/athena/src/Vision/BaeUtilities.cpp
Normal file
@@ -0,0 +1,375 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2014-2016. All Rights Reserved. */
|
||||
/* 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 <math.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "Servo.h"
|
||||
#include "Timer.h"
|
||||
#include "Vision/BaeUtilities.h"
|
||||
|
||||
/** @file
|
||||
* Utility functions
|
||||
*/
|
||||
|
||||
/**
|
||||
* debug output flag options:
|
||||
* DEBUG_OFF, DEBUG_MOSTLY_OFF, DEBUG_SCREEN_ONLY, DEBUG_FILE_ONLY,
|
||||
* DEBUG_SCREEN_AND_FILE
|
||||
*/
|
||||
static DebugOutputType dprintfFlag = DEBUG_OFF;
|
||||
|
||||
/**
|
||||
* Set the debug flag to print to screen, file on cRIO, both or neither.
|
||||
*
|
||||
* @param tempString The format string.
|
||||
*/
|
||||
void SetDebugFlag(DebugOutputType flag) { dprintfFlag = flag; }
|
||||
|
||||
/**
|
||||
* Debug print to a file and/or a terminal window.
|
||||
*
|
||||
* Call like you would call printf.
|
||||
* Set functionName in the function if you want the correct function name to
|
||||
* print out.
|
||||
* The file line number will also be printed.
|
||||
*
|
||||
* @param tempString The format string.
|
||||
*/
|
||||
void dprintf(const char* tempString, ...) /* Variable argument list */
|
||||
{
|
||||
va_list args; /* Input argument list */
|
||||
int line_number; /* Line number passed in argument */
|
||||
int type;
|
||||
const char* functionName; /* Format passed in argument */
|
||||
const char* fmt; /* Format passed in argument */
|
||||
char text[512]; /* Text string */
|
||||
char outtext[512]; /* Text string */
|
||||
FILE* outfile_fd; /* Output file pointer */
|
||||
char filepath[128]; /* Text string */
|
||||
int fatalFlag = 0;
|
||||
const char* filename;
|
||||
int index;
|
||||
int tempStringLen;
|
||||
|
||||
if (dprintfFlag == DEBUG_OFF) {
|
||||
return;
|
||||
}
|
||||
|
||||
va_start(args, tempString);
|
||||
|
||||
tempStringLen = strlen(tempString);
|
||||
filename = tempString;
|
||||
for (index = 0; index < tempStringLen; index++) {
|
||||
if (tempString[index] == ' ') {
|
||||
printf("ERROR in dprintf: malformed calling sequence (%s)\n", tempString);
|
||||
va_end(args);
|
||||
return;
|
||||
}
|
||||
if (tempString[index] == '\\' || tempString[index] == '/')
|
||||
filename = tempString + index + 1;
|
||||
}
|
||||
|
||||
/* Extract function name */
|
||||
functionName = va_arg(args, const char*);
|
||||
|
||||
/* Extract line number from argument list */
|
||||
line_number = va_arg(args, int);
|
||||
|
||||
/* Extract information type from argument list */
|
||||
type = va_arg(args, int);
|
||||
|
||||
/* Extract format from argument list */
|
||||
fmt = va_arg(args, const char*);
|
||||
|
||||
vsprintf(text, fmt, args);
|
||||
|
||||
va_end(args);
|
||||
|
||||
/* Format output statement */
|
||||
switch (type) {
|
||||
case DEBUG_TYPE:
|
||||
sprintf(outtext, "[%s:%s@%04d] DEBUG %s\n", filename, functionName,
|
||||
line_number, text);
|
||||
break;
|
||||
case INFO_TYPE:
|
||||
sprintf(outtext, "[%s:%s@%04d] INFO %s\n", filename, functionName,
|
||||
line_number, text);
|
||||
break;
|
||||
case ERROR_TYPE:
|
||||
sprintf(outtext, "[%s:%s@%04d] ERROR %s\n", filename, functionName,
|
||||
line_number, text);
|
||||
break;
|
||||
case CRITICAL_TYPE:
|
||||
sprintf(outtext, "[%s:%s@%04d] CRITICAL %s\n", filename, functionName,
|
||||
line_number, text);
|
||||
break;
|
||||
case FATAL_TYPE:
|
||||
fatalFlag = 1;
|
||||
sprintf(outtext, "[%s:%s@%04d] FATAL %s\n", filename, functionName,
|
||||
line_number, text);
|
||||
break;
|
||||
default:
|
||||
printf("ERROR in dprintf: malformed calling sequence\n");
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
sprintf(filepath, "%s.debug", filename);
|
||||
|
||||
/* Write output statement */
|
||||
switch (dprintfFlag) {
|
||||
default:
|
||||
case DEBUG_OFF:
|
||||
break;
|
||||
case DEBUG_MOSTLY_OFF:
|
||||
if (fatalFlag) {
|
||||
if ((outfile_fd = fopen(filepath, "a+")) != nullptr) {
|
||||
fwrite(outtext, sizeof(char), strlen(outtext), outfile_fd);
|
||||
fclose(outfile_fd);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DEBUG_SCREEN_ONLY:
|
||||
printf("%s", outtext);
|
||||
break;
|
||||
case DEBUG_FILE_ONLY:
|
||||
if ((outfile_fd = fopen(filepath, "a+")) != nullptr) {
|
||||
fwrite(outtext, sizeof(char), strlen(outtext), outfile_fd);
|
||||
fclose(outfile_fd);
|
||||
}
|
||||
break;
|
||||
case DEBUG_SCREEN_AND_FILE: // BOTH
|
||||
printf("%s", outtext);
|
||||
if ((outfile_fd = fopen(filepath, "a+")) != nullptr) {
|
||||
fwrite(outtext, sizeof(char), strlen(outtext), outfile_fd);
|
||||
fclose(outfile_fd);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Normalizes a value in a range, used for drive input.
|
||||
* @param position The position in the range, starting at 0
|
||||
* @param range The size of the range that position is in
|
||||
* @return The normalized position from -1 to +1
|
||||
*/
|
||||
double RangeToNormalized(double position, int range) {
|
||||
return (((position * 2.0) / (double)range) - 1.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert a normalized value to the corresponding value in a range.
|
||||
* This is used to convert normalized values to the servo command range.
|
||||
* @param normalizedValue The normalized value (in the -1 to +1 range)
|
||||
* @param minRange The minimum of the range (0 is default)
|
||||
* @param maxRange The maximum of the range (1 is default)
|
||||
* @return The value in the range corresponding to the input normalized value
|
||||
*/
|
||||
float NormalizeToRange(float normalizedValue, float minRange, float maxRange) {
|
||||
float range = maxRange - minRange;
|
||||
float temp = (float)((normalizedValue / 2.0) + 0.5) * range;
|
||||
return (temp + minRange);
|
||||
}
|
||||
float NormalizeToRange(float normalizedValue) {
|
||||
return (float)((normalizedValue / 2.0) + 0.5);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Displays an activity indicator to console.
|
||||
* Call this function like you would call printf.
|
||||
* @param fmt The format string
|
||||
*/
|
||||
void ShowActivity(char* fmt, ...) {
|
||||
static char activity_indication_string[] = "|/-\\";
|
||||
static int ai = 3;
|
||||
va_list args;
|
||||
char text[1024];
|
||||
|
||||
va_start(args, fmt);
|
||||
|
||||
vsprintf(text, fmt, args);
|
||||
|
||||
ai = ai == 3 ? 0 : ai + 1;
|
||||
|
||||
printf("%c %s \r", activity_indication_string[ai], text);
|
||||
fflush(stdout);
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
#define PI 3.14159265358979
|
||||
/**
|
||||
* @brief Calculate sine wave increments (-1.0 to 1.0).
|
||||
* The first time this is called, it sets up the time increment. Subsequent
|
||||
* calls will give values along the sine wave depending on current time. If
|
||||
* the wave is stopped and restarted, it must be reinitialized with a new
|
||||
* "first call".
|
||||
*
|
||||
* @param period length of time to complete a complete wave
|
||||
* @param sinStart Where to start the sine wave (0.0 = 2 pi, pi/2 = 1.0, etc.)
|
||||
*/
|
||||
double SinPosition(double* period, double sinStart) {
|
||||
double rtnVal;
|
||||
static double sinePeriod = 0.0;
|
||||
static double timestamp;
|
||||
double sinArg;
|
||||
|
||||
// 1st call
|
||||
if (period != nullptr) {
|
||||
sinePeriod = *period;
|
||||
timestamp = GetTime();
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Multiplying by 2*pi to the time difference makes sinePeriod work if it's
|
||||
// measured in seconds.
|
||||
// Adding sinStart to the part multiplied by PI, but not by 2, allows it to
|
||||
// work as described in the comments.
|
||||
sinArg = PI * ((2.0 * (GetTime() - timestamp)) + sinStart) / sinePeriod;
|
||||
rtnVal = sin(sinArg);
|
||||
return (rtnVal);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Find the elapsed time since a specified time.
|
||||
* @param startTime The starting time
|
||||
* @return How long it has been since the starting time
|
||||
*/
|
||||
double ElapsedTime(double startTime) {
|
||||
double realTime = GetTime();
|
||||
return (realTime - startTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize pan parameters
|
||||
* @param period The number of seconds to complete one pan
|
||||
*/
|
||||
void panInit() {
|
||||
double period = 3.0; // number of seconds for one complete pan
|
||||
SinPosition(&period, 0.0); // initial call to set up time
|
||||
}
|
||||
|
||||
void panInit(double period) {
|
||||
if (period < 0.0) period = 3.0;
|
||||
SinPosition(&period, 0.0); // initial call to set up time
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move the horizontal servo back and forth.
|
||||
* @param panServo The servo object to move
|
||||
* @param sinStart The position on the sine wave to begin the pan
|
||||
*/
|
||||
void panForTarget(Servo* panServo) { panForTarget(panServo, 0.0); }
|
||||
|
||||
void panForTarget(Servo* panServo, double sinStart) {
|
||||
float normalizedSinPosition = (float)SinPosition(nullptr, sinStart);
|
||||
float newServoPosition = NormalizeToRange(normalizedSinPosition);
|
||||
panServo->Set(newServoPosition);
|
||||
// ShowActivity ("pan x: normalized %f newServoPosition = %f" ,
|
||||
// normalizedSinPosition, newServoPosition );
|
||||
}
|
||||
|
||||
/** @brief Read a file and return non-comment output string
|
||||
*
|
||||
* Call the first time with 0 lineNumber to get the number of lines to read
|
||||
* Then call with each lineNumber to get one camera parameter. There should
|
||||
* be one property=value entry on each line, i.e. "exposure=auto"
|
||||
*
|
||||
* @param inputFile filename to read
|
||||
* @param outputString one string
|
||||
* @param lineNumber if 0, return number of lines; else return that line
|
||||
* number
|
||||
* @return int number of lines or -1 if error
|
||||
**/
|
||||
int processFile(char* inputFile, char* outputString, int lineNumber) {
|
||||
FILE* infile;
|
||||
const int stringSize = 80; // max size of one line in file
|
||||
char inputStr[stringSize];
|
||||
inputStr[0] = '\0';
|
||||
int lineCount = 0;
|
||||
|
||||
if (lineNumber < 0) return (-1);
|
||||
|
||||
if ((infile = fopen(inputFile, "r")) == nullptr) {
|
||||
printf("Fatal error opening file %s\n", inputFile);
|
||||
return (0);
|
||||
}
|
||||
|
||||
while (!feof(infile)) {
|
||||
if (fgets(inputStr, stringSize, infile) != nullptr) {
|
||||
// Skip empty lines
|
||||
if (emptyString(inputStr)) continue;
|
||||
// Skip comment lines
|
||||
if (inputStr[0] == '#' || inputStr[0] == '!') continue;
|
||||
|
||||
lineCount++;
|
||||
if (lineNumber == 0)
|
||||
continue;
|
||||
else {
|
||||
if (lineCount == lineNumber) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// close file
|
||||
fclose(infile);
|
||||
// if number lines requested return the count
|
||||
if (lineNumber == 0) return (lineCount);
|
||||
// check for input out of range
|
||||
if (lineNumber > lineCount) return (-1);
|
||||
// return the line selected; lineCount guaranteed to be greater than zero
|
||||
stripString(inputStr);
|
||||
strcpy(outputString, inputStr);
|
||||
return (lineCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignore empty string.
|
||||
* @param string to check if empty
|
||||
**/
|
||||
int emptyString(char* string) {
|
||||
int i, len;
|
||||
|
||||
if (string == nullptr) return (1);
|
||||
|
||||
len = strlen(string);
|
||||
for (i = 0; i < len; i++) {
|
||||
// Ignore the following characters
|
||||
if (string[i] == '\n' || string[i] == '\r' || string[i] == '\t' ||
|
||||
string[i] == ' ')
|
||||
continue;
|
||||
return (0);
|
||||
}
|
||||
return (1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove special characters from string.
|
||||
* @param string to process
|
||||
**/
|
||||
void stripString(char* string) {
|
||||
int i, j, len;
|
||||
|
||||
if (string == nullptr) return;
|
||||
|
||||
len = strlen(string);
|
||||
for (i = 0, j = 0; i < len; i++) {
|
||||
// Remove the following characters from the string
|
||||
if (string[i] == '\n' || string[i] == '\r' || string[i] == '\"') continue;
|
||||
// Copy anything else
|
||||
string[j++] = string[i];
|
||||
}
|
||||
string[j] = '\0';
|
||||
}
|
||||
242
wpilibc/athena/src/Vision/BinaryImage.cpp
Normal file
242
wpilibc/athena/src/Vision/BinaryImage.cpp
Normal file
@@ -0,0 +1,242 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2014-2016. All Rights Reserved. */
|
||||
/* 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 "Vision/BinaryImage.h"
|
||||
#include <cstring>
|
||||
#include "WPIErrors.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
/**
|
||||
* Get then number of particles for the image.
|
||||
*
|
||||
* @return the number of particles found for the image.
|
||||
*/
|
||||
int BinaryImage::GetNumberParticles() {
|
||||
int numParticles = 0;
|
||||
int success = imaqCountParticles(m_imaqImage, 1, &numParticles);
|
||||
wpi_setImaqErrorWithContext(success, "Error counting particles");
|
||||
return numParticles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single particle analysis report.
|
||||
*
|
||||
* Get one (of possibly many) particle analysis reports for an image.
|
||||
*
|
||||
* @param particleNumber Which particle analysis report to return.
|
||||
* @return the selected particle analysis report
|
||||
*/
|
||||
ParticleAnalysisReport BinaryImage::GetParticleAnalysisReport(
|
||||
int particleNumber) {
|
||||
ParticleAnalysisReport par;
|
||||
GetParticleAnalysisReport(particleNumber, &par);
|
||||
return par;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single particle analysis report.
|
||||
*
|
||||
* Get one (of possibly many) particle analysis reports for an image.
|
||||
* This version could be more efficient when copying many reports.
|
||||
*
|
||||
* @param particleNumber Which particle analysis report to return.
|
||||
* @param par the selected particle analysis report
|
||||
*/
|
||||
void BinaryImage::GetParticleAnalysisReport(int particleNumber,
|
||||
ParticleAnalysisReport* par) {
|
||||
int success;
|
||||
int numParticles = 0;
|
||||
|
||||
success = imaqGetImageSize(m_imaqImage, &par->imageWidth, &par->imageHeight);
|
||||
wpi_setImaqErrorWithContext(success, "Error getting image size");
|
||||
if (StatusIsFatal()) return;
|
||||
|
||||
success = imaqCountParticles(m_imaqImage, 1, &numParticles);
|
||||
wpi_setImaqErrorWithContext(success, "Error counting particles");
|
||||
if (StatusIsFatal()) return;
|
||||
|
||||
if (particleNumber >= numParticles) {
|
||||
wpi_setWPIErrorWithContext(ParameterOutOfRange, "particleNumber");
|
||||
return;
|
||||
}
|
||||
|
||||
par->particleIndex = particleNumber;
|
||||
// Don't bother measuring the rest of the particle if one fails
|
||||
bool good = ParticleMeasurement(particleNumber, IMAQ_MT_CENTER_OF_MASS_X,
|
||||
&par->center_mass_x);
|
||||
good = good && ParticleMeasurement(particleNumber, IMAQ_MT_CENTER_OF_MASS_Y,
|
||||
&par->center_mass_y);
|
||||
good = good &&
|
||||
ParticleMeasurement(particleNumber, IMAQ_MT_AREA, &par->particleArea);
|
||||
good = good && ParticleMeasurement(particleNumber, IMAQ_MT_BOUNDING_RECT_TOP,
|
||||
&par->boundingRect.top);
|
||||
good = good && ParticleMeasurement(particleNumber, IMAQ_MT_BOUNDING_RECT_LEFT,
|
||||
&par->boundingRect.left);
|
||||
good =
|
||||
good && ParticleMeasurement(particleNumber, IMAQ_MT_BOUNDING_RECT_HEIGHT,
|
||||
&par->boundingRect.height);
|
||||
good =
|
||||
good && ParticleMeasurement(particleNumber, IMAQ_MT_BOUNDING_RECT_WIDTH,
|
||||
&par->boundingRect.width);
|
||||
good = good && ParticleMeasurement(particleNumber, IMAQ_MT_AREA_BY_IMAGE_AREA,
|
||||
&par->particleToImagePercent);
|
||||
good = good && ParticleMeasurement(particleNumber,
|
||||
IMAQ_MT_AREA_BY_PARTICLE_AND_HOLES_AREA,
|
||||
&par->particleQuality);
|
||||
|
||||
if (good) {
|
||||
/* normalized position (-1 to 1) */
|
||||
par->center_mass_x_normalized =
|
||||
NormalizeFromRange(par->center_mass_x, par->imageWidth);
|
||||
par->center_mass_y_normalized =
|
||||
NormalizeFromRange(par->center_mass_y, par->imageHeight);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an ordered vector of particles for the image.
|
||||
*
|
||||
* Create a vector of particle analysis reports sorted by size for an image.
|
||||
* The vector contains the actual report structures.
|
||||
*
|
||||
* @return a pointer to the vector of particle analysis reports. The caller
|
||||
* must delete the vector when finished using it.
|
||||
*/
|
||||
vector<ParticleAnalysisReport>*
|
||||
BinaryImage::GetOrderedParticleAnalysisReports() {
|
||||
auto particles = new vector<ParticleAnalysisReport>;
|
||||
int particleCount = GetNumberParticles();
|
||||
for (int particleIndex = 0; particleIndex < particleCount; particleIndex++) {
|
||||
particles->push_back(GetParticleAnalysisReport(particleIndex));
|
||||
}
|
||||
// TODO: This is pretty inefficient since each compare in the sort copies
|
||||
// both reports being compared... do it manually instead... while we're
|
||||
// at it, we should provide a version that allows a preallocated buffer of
|
||||
// ParticleAnalysisReport structures
|
||||
sort(particles->begin(), particles->end(), CompareParticleSizes);
|
||||
return particles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a binary image to flash.
|
||||
*
|
||||
* Writes the binary image to flash on the cRIO for later inspection.
|
||||
*
|
||||
* @param fileName the name of the image file written to the flash.
|
||||
*/
|
||||
void BinaryImage::Write(const char* fileName) {
|
||||
RGBValue colorTable[256];
|
||||
memset(colorTable, 0, sizeof(colorTable));
|
||||
colorTable[0].R = 0;
|
||||
colorTable[1].R = 255;
|
||||
colorTable[0].G = colorTable[1].G = 0;
|
||||
colorTable[0].B = colorTable[1].B = 0;
|
||||
colorTable[0].alpha = colorTable[1].alpha = 0;
|
||||
imaqWriteFile(m_imaqImage, fileName, colorTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Measure a single parameter for an image.
|
||||
*
|
||||
* Get the measurement for a single parameter about an image by calling the
|
||||
* imaqMeasureParticle function for the selected parameter.
|
||||
*
|
||||
* @param particleNumber which particle in the set of particles
|
||||
* @param whatToMeasure the imaq MeasurementType (what to measure)
|
||||
* @param result the value of the measurement
|
||||
* @return false on failure, true on success
|
||||
*/
|
||||
bool BinaryImage::ParticleMeasurement(int particleNumber,
|
||||
MeasurementType whatToMeasure,
|
||||
int* result) {
|
||||
double resultDouble;
|
||||
bool success =
|
||||
ParticleMeasurement(particleNumber, whatToMeasure, &resultDouble);
|
||||
*result = (int)resultDouble;
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Measure a single parameter for an image.
|
||||
*
|
||||
* Get the measurement for a single parameter about an image by calling the
|
||||
* imaqMeasureParticle function for the selected parameter.
|
||||
*
|
||||
* @param particleNumber which particle in the set of particles
|
||||
* @param whatToMeasure the imaq MeasurementType (what to measure)
|
||||
* @param result the value of the measurement
|
||||
* @returns true on failure, false on success
|
||||
*/
|
||||
bool BinaryImage::ParticleMeasurement(int particleNumber,
|
||||
MeasurementType whatToMeasure,
|
||||
double* result) {
|
||||
int success;
|
||||
success = imaqMeasureParticle(m_imaqImage, particleNumber, 0, whatToMeasure,
|
||||
result);
|
||||
wpi_setImaqErrorWithContext(success, "Error measuring particle");
|
||||
return !StatusIsFatal();
|
||||
}
|
||||
|
||||
// Normalizes to [-1,1]
|
||||
double BinaryImage::NormalizeFromRange(double position, int range) {
|
||||
return (position * 2.0 / (double)range) - 1.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The compare helper function for sort.
|
||||
*
|
||||
* This function compares two particle analysis reports as a helper for the sort
|
||||
* function.
|
||||
*
|
||||
* @param particle1 The first particle to compare
|
||||
* @param particle2 the second particle to compare
|
||||
* @returns true if particle1 is greater than particle2
|
||||
*/
|
||||
bool BinaryImage::CompareParticleSizes(ParticleAnalysisReport particle1,
|
||||
ParticleAnalysisReport particle2) {
|
||||
// we want descending sort order
|
||||
return particle1.particleToImagePercent > particle2.particleToImagePercent;
|
||||
}
|
||||
|
||||
BinaryImage* BinaryImage::RemoveSmallObjects(bool connectivity8, int erosions) {
|
||||
auto result = new BinaryImage();
|
||||
int success =
|
||||
imaqSizeFilter(result->GetImaqImage(), m_imaqImage, connectivity8,
|
||||
erosions, IMAQ_KEEP_LARGE, nullptr);
|
||||
wpi_setImaqErrorWithContext(success, "Error in RemoveSmallObjects");
|
||||
return result;
|
||||
}
|
||||
|
||||
BinaryImage* BinaryImage::RemoveLargeObjects(bool connectivity8, int erosions) {
|
||||
auto result = new BinaryImage();
|
||||
int success =
|
||||
imaqSizeFilter(result->GetImaqImage(), m_imaqImage, connectivity8,
|
||||
erosions, IMAQ_KEEP_SMALL, nullptr);
|
||||
wpi_setImaqErrorWithContext(success, "Error in RemoveLargeObjects");
|
||||
return result;
|
||||
}
|
||||
|
||||
BinaryImage* BinaryImage::ConvexHull(bool connectivity8) {
|
||||
auto result = new BinaryImage();
|
||||
int success =
|
||||
imaqConvexHull(result->GetImaqImage(), m_imaqImage, connectivity8);
|
||||
wpi_setImaqErrorWithContext(success, "Error in convex hull operation");
|
||||
return result;
|
||||
}
|
||||
|
||||
BinaryImage* BinaryImage::ParticleFilter(ParticleFilterCriteria2* criteria,
|
||||
int criteriaCount) {
|
||||
auto result = new BinaryImage();
|
||||
int numParticles;
|
||||
ParticleFilterOptions2 filterOptions = {0, 0, 0, 1};
|
||||
int success = imaqParticleFilter4(result->GetImaqImage(), m_imaqImage,
|
||||
criteria, criteriaCount, &filterOptions,
|
||||
nullptr, &numParticles);
|
||||
wpi_setImaqErrorWithContext(success, "Error in particle filter operation");
|
||||
return result;
|
||||
}
|
||||
485
wpilibc/athena/src/Vision/ColorImage.cpp
Normal file
485
wpilibc/athena/src/Vision/ColorImage.cpp
Normal file
@@ -0,0 +1,485 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2014-2016. All Rights Reserved. */
|
||||
/* 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 "Vision/ColorImage.h"
|
||||
|
||||
#include "WPIErrors.h"
|
||||
|
||||
ColorImage::ColorImage(ImageType type) : ImageBase(type) {}
|
||||
|
||||
/**
|
||||
* Perform a threshold operation on a ColorImage.
|
||||
*
|
||||
* Perform a threshold operation on a ColorImage using the ColorMode supplied
|
||||
* as a parameter.
|
||||
*
|
||||
* @param colorMode The type of colorspace this operation should be performed in
|
||||
* @return a pointer to a binary image
|
||||
*/
|
||||
BinaryImage* ColorImage::ComputeThreshold(ColorMode colorMode, int low1,
|
||||
int high1, int low2, int high2,
|
||||
int low3, int high3) {
|
||||
auto result = new BinaryImage();
|
||||
Range range1 = {low1, high1}, range2 = {low2, high2}, range3 = {low3, high3};
|
||||
|
||||
int success = imaqColorThreshold(result->GetImaqImage(), m_imaqImage, 1,
|
||||
colorMode, &range1, &range2, &range3);
|
||||
wpi_setImaqErrorWithContext(success, "ImaqThreshold error");
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a threshold in RGB space.
|
||||
*
|
||||
* @param redLow Red low value
|
||||
* @param redHigh Red high value
|
||||
* @param greenLow Green low value
|
||||
* @param greenHigh Green high value
|
||||
* @param blueLow Blue low value
|
||||
* @param blueHigh Blue high value
|
||||
* @return A pointer to a BinaryImage that represents the result of the
|
||||
* threshold operation.
|
||||
*/
|
||||
BinaryImage* ColorImage::ThresholdRGB(int redLow, int redHigh, int greenLow,
|
||||
int greenHigh, int blueLow,
|
||||
int blueHigh) {
|
||||
return ComputeThreshold(IMAQ_RGB, redLow, redHigh, greenLow, greenHigh,
|
||||
blueLow, blueHigh);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a threshold in RGB space.
|
||||
*
|
||||
* @param threshold a reference to the Threshold object to use.
|
||||
* @return A pointer to a BinaryImage that represents the result of the
|
||||
* threshold operation.
|
||||
*/
|
||||
BinaryImage* ColorImage::ThresholdRGB(Threshold& t) {
|
||||
return ComputeThreshold(IMAQ_RGB, t.plane1Low, t.plane1High, t.plane2Low,
|
||||
t.plane2High, t.plane3Low, t.plane3High);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a threshold in HSL space.
|
||||
*
|
||||
* @param hueLow Low value for hue
|
||||
* @param hueHigh High value for hue
|
||||
* @param saturationLow Low value for saturation
|
||||
* @param saturationHigh High value for saturation
|
||||
* @param luminenceLow Low value for luminence
|
||||
* @param luminenceHigh High value for luminence
|
||||
* @return a pointer to a BinaryImage that represents the result of the
|
||||
* threshold operation.
|
||||
*/
|
||||
BinaryImage* ColorImage::ThresholdHSL(int hueLow, int hueHigh,
|
||||
int saturationLow, int saturationHigh,
|
||||
int luminenceLow, int luminenceHigh) {
|
||||
return ComputeThreshold(IMAQ_HSL, hueLow, hueHigh, saturationLow,
|
||||
saturationHigh, luminenceLow, luminenceHigh);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a threshold in HSL space.
|
||||
*
|
||||
* @param threshold a reference to the Threshold object to use.
|
||||
* @return A pointer to a BinaryImage that represents the result of the
|
||||
* threshold operation.
|
||||
*/
|
||||
BinaryImage* ColorImage::ThresholdHSL(Threshold& t) {
|
||||
return ComputeThreshold(IMAQ_HSL, t.plane1Low, t.plane1High, t.plane2Low,
|
||||
t.plane2High, t.plane3Low, t.plane3High);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a threshold in HSV space.
|
||||
*
|
||||
* @param hueLow Low value for hue
|
||||
* @param hueHigh High value for hue
|
||||
* @param saturationLow Low value for saturation
|
||||
* @param saturationHigh High value for saturation
|
||||
* @param valueLow Low value
|
||||
* @param valueHigh High value
|
||||
* @return a pointer to a BinaryImage that represents the result of the
|
||||
* threshold operation.
|
||||
*/
|
||||
BinaryImage* ColorImage::ThresholdHSV(int hueLow, int hueHigh,
|
||||
int saturationLow, int saturationHigh,
|
||||
int valueLow, int valueHigh) {
|
||||
return ComputeThreshold(IMAQ_HSV, hueLow, hueHigh, saturationLow,
|
||||
saturationHigh, valueLow, valueHigh);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a threshold in HSV space.
|
||||
*
|
||||
* @param threshold a reference to the Threshold object to use.
|
||||
* @return A pointer to a BinaryImage that represents the result of the
|
||||
* threshold operation.
|
||||
*/
|
||||
BinaryImage* ColorImage::ThresholdHSV(Threshold& t) {
|
||||
return ComputeThreshold(IMAQ_HSV, t.plane1Low, t.plane1High, t.plane2Low,
|
||||
t.plane2High, t.plane3Low, t.plane3High);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a threshold in HSI space.
|
||||
*
|
||||
* @param hueLow Low value for hue
|
||||
* @param hueHigh High value for hue
|
||||
* @param saturationLow Low value for saturation
|
||||
* @param saturationHigh High value for saturation
|
||||
* @param valueLow Low intensity
|
||||
* @param valueHigh High intensity
|
||||
* @return a pointer to a BinaryImage that represents the result of the
|
||||
* threshold operation.
|
||||
*/
|
||||
BinaryImage* ColorImage::ThresholdHSI(int hueLow, int hueHigh,
|
||||
int saturationLow, int saturationHigh,
|
||||
int intensityLow, int intensityHigh) {
|
||||
return ComputeThreshold(IMAQ_HSI, hueLow, hueHigh, saturationLow,
|
||||
saturationHigh, intensityLow, intensityHigh);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a threshold in HSI space.
|
||||
*
|
||||
* @param threshold a reference to the Threshold object to use.
|
||||
* @return A pointer to a BinaryImage that represents the result of the
|
||||
* threshold operation.
|
||||
*/
|
||||
BinaryImage* ColorImage::ThresholdHSI(Threshold& t) {
|
||||
return ComputeThreshold(IMAQ_HSI, t.plane1Low, t.plane1High, t.plane2Low,
|
||||
t.plane2High, t.plane3Low, t.plane3High);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a color plane from the image.
|
||||
*
|
||||
* @param mode The ColorMode to use for the plane extraction
|
||||
* @param planeNumber Which plane is to be extracted
|
||||
* @return A pointer to a MonoImage that represents the extracted plane.
|
||||
*/
|
||||
MonoImage* ColorImage::ExtractColorPlane(ColorMode mode, int planeNumber) {
|
||||
auto result = new MonoImage();
|
||||
if (m_imaqImage == nullptr) wpi_setWPIError(NullParameter);
|
||||
int success = imaqExtractColorPlanes(
|
||||
m_imaqImage, mode, (planeNumber == 1) ? result->GetImaqImage() : nullptr,
|
||||
(planeNumber == 2) ? result->GetImaqImage() : nullptr,
|
||||
(planeNumber == 3) ? result->GetImaqImage() : nullptr);
|
||||
wpi_setImaqErrorWithContext(success, "Imaq ExtractColorPlanes failed");
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the first color plane for an image.
|
||||
*
|
||||
* @param mode The color mode in which to operate
|
||||
* @return a pointer to a MonoImage that is the extracted plane.
|
||||
*/
|
||||
MonoImage* ColorImage::ExtractFirstColorPlane(ColorMode mode) {
|
||||
return ExtractColorPlane(mode, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the second color plane for an image.
|
||||
*
|
||||
* @param mode The color mode in which to operate
|
||||
* @return a pointer to a MonoImage that is the extracted plane.
|
||||
*/
|
||||
MonoImage* ColorImage::ExtractSecondColorPlane(ColorMode mode) {
|
||||
return ExtractColorPlane(mode, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the third color plane for an image.
|
||||
*
|
||||
* @param mode The color mode in which to operate
|
||||
* @return a pointer to a MonoImage that is the extracted plane.
|
||||
*/
|
||||
MonoImage* ColorImage::ExtractThirdColorPlane(ColorMode mode) {
|
||||
return ExtractColorPlane(mode, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the red plane from an RGB image.
|
||||
*
|
||||
* @return a pointer to a MonoImage that is the extraced plane.
|
||||
*/
|
||||
MonoImage* ColorImage::GetRedPlane() {
|
||||
return ExtractFirstColorPlane(IMAQ_RGB);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the green plane from an RGB image.
|
||||
*
|
||||
* @return a pointer to a MonoImage that is the extraced plane.
|
||||
*/
|
||||
MonoImage* ColorImage::GetGreenPlane() {
|
||||
return ExtractSecondColorPlane(IMAQ_RGB);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the blue plane from an RGB image.
|
||||
*
|
||||
* @return a pointer to a MonoImage that is the extraced plane.
|
||||
*/
|
||||
MonoImage* ColorImage::GetBluePlane() {
|
||||
return ExtractThirdColorPlane(IMAQ_RGB);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the Hue plane from an HSL image.
|
||||
*
|
||||
* @return a pointer to a MonoImage that is the extraced plane.
|
||||
*/
|
||||
MonoImage* ColorImage::GetHSLHuePlane() {
|
||||
return ExtractFirstColorPlane(IMAQ_HSL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the Hue plane from an HSV image.
|
||||
*
|
||||
* @return a pointer to a MonoImage that is the extraced plane.
|
||||
*/
|
||||
MonoImage* ColorImage::GetHSVHuePlane() {
|
||||
return ExtractFirstColorPlane(IMAQ_HSV);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the Hue plane from an HSI image.
|
||||
*
|
||||
* @return a pointer to a MonoImage that is the extraced plane.
|
||||
*/
|
||||
MonoImage* ColorImage::GetHSIHuePlane() {
|
||||
return ExtractFirstColorPlane(IMAQ_HSI);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the Luminance plane from an HSL image.
|
||||
*
|
||||
* @return a pointer to a MonoImage that is the extraced plane.
|
||||
*/
|
||||
MonoImage* ColorImage::GetLuminancePlane() {
|
||||
return ExtractThirdColorPlane(IMAQ_HSL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the Value plane from an HSV image.
|
||||
*
|
||||
* @return a pointer to a MonoImage that is the extraced plane.
|
||||
*/
|
||||
MonoImage* ColorImage::GetValuePlane() {
|
||||
return ExtractThirdColorPlane(IMAQ_HSV);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the Intensity plane from an HSI image.
|
||||
*
|
||||
* @return a pointer to a MonoImage that is the extraced plane.
|
||||
*/
|
||||
MonoImage* ColorImage::GetIntensityPlane() {
|
||||
return ExtractThirdColorPlane(IMAQ_HSI);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace a plane in the ColorImage with a MonoImage.
|
||||
*
|
||||
* Replaces a single plane in the image with a MonoImage.
|
||||
*
|
||||
* @param mode The ColorMode in which to operate
|
||||
* @param plane The pointer to the replacement plane as a MonoImage
|
||||
* @param planeNumber The plane number (1, 2, 3) to replace
|
||||
*/
|
||||
void ColorImage::ReplacePlane(ColorMode mode, MonoImage* plane,
|
||||
int planeNumber) {
|
||||
int success = imaqReplaceColorPlanes(
|
||||
m_imaqImage, (const Image*)m_imaqImage, mode,
|
||||
(planeNumber == 1) ? plane->GetImaqImage() : nullptr,
|
||||
(planeNumber == 2) ? plane->GetImaqImage() : nullptr,
|
||||
(planeNumber == 3) ? plane->GetImaqImage() : nullptr);
|
||||
wpi_setImaqErrorWithContext(success, "Imaq ReplaceColorPlanes failed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the first color plane with a MonoImage.
|
||||
*
|
||||
* @param mode The color mode in which to operate.
|
||||
* @param plane A pointer to a MonoImage that will replace the specified color
|
||||
* plane.
|
||||
*/
|
||||
void ColorImage::ReplaceFirstColorPlane(ColorMode mode, MonoImage* plane) {
|
||||
ReplacePlane(mode, plane, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the second color plane with a MonoImage.
|
||||
*
|
||||
* @param mode The color mode in which to operate.
|
||||
* @param plane A pointer to a MonoImage that will replace the specified color
|
||||
* plane.
|
||||
*/
|
||||
void ColorImage::ReplaceSecondColorPlane(ColorMode mode, MonoImage* plane) {
|
||||
ReplacePlane(mode, plane, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the third color plane with a MonoImage.
|
||||
*
|
||||
* @param mode The color mode in which to operate.
|
||||
* @param plane A pointer to a MonoImage that will replace the specified color
|
||||
* plane.
|
||||
*/
|
||||
void ColorImage::ReplaceThirdColorPlane(ColorMode mode, MonoImage* plane) {
|
||||
ReplacePlane(mode, plane, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the red color plane with a MonoImage.
|
||||
*
|
||||
* @param mode The color mode in which to operate.
|
||||
* @param plane A pointer to a MonoImage that will replace the specified color
|
||||
* plane.
|
||||
*/
|
||||
void ColorImage::ReplaceRedPlane(MonoImage* plane) {
|
||||
ReplaceFirstColorPlane(IMAQ_RGB, plane);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the green color plane with a MonoImage.
|
||||
*
|
||||
* @param mode The color mode in which to operate.
|
||||
* @param plane A pointer to a MonoImage that will replace the specified color
|
||||
* plane.
|
||||
*/
|
||||
void ColorImage::ReplaceGreenPlane(MonoImage* plane) {
|
||||
ReplaceSecondColorPlane(IMAQ_RGB, plane);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the blue color plane with a MonoImage.
|
||||
*
|
||||
* @param mode The color mode in which to operate.
|
||||
* @param plane A pointer to a MonoImage that will replace the specified color
|
||||
* plane.
|
||||
*/
|
||||
void ColorImage::ReplaceBluePlane(MonoImage* plane) {
|
||||
ReplaceThirdColorPlane(IMAQ_RGB, plane);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the Hue color plane in a HSL image with a MonoImage.
|
||||
*
|
||||
* @param mode The color mode in which to operate.
|
||||
* @param plane A pointer to a MonoImage that will replace the specified color
|
||||
* plane.
|
||||
*/
|
||||
void ColorImage::ReplaceHSLHuePlane(MonoImage* plane) {
|
||||
return ReplaceFirstColorPlane(IMAQ_HSL, plane);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the Hue color plane in a HSV image with a MonoImage.
|
||||
*
|
||||
* @param mode The color mode in which to operate.
|
||||
* @param plane A pointer to a MonoImage that will replace the specified color
|
||||
* plane.
|
||||
*/
|
||||
void ColorImage::ReplaceHSVHuePlane(MonoImage* plane) {
|
||||
return ReplaceFirstColorPlane(IMAQ_HSV, plane);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the first Hue plane in a HSI image with a MonoImage.
|
||||
*
|
||||
* @param mode The color mode in which to operate.
|
||||
* @param plane A pointer to a MonoImage that will replace the specified color
|
||||
* plane.
|
||||
*/
|
||||
void ColorImage::ReplaceHSIHuePlane(MonoImage* plane) {
|
||||
return ReplaceFirstColorPlane(IMAQ_HSI, plane);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the Saturation color plane in an HSL image with a MonoImage.
|
||||
*
|
||||
* @param mode The color mode in which to operate.
|
||||
* @param plane A pointer to a MonoImage that will replace the specified color
|
||||
* plane.
|
||||
*/
|
||||
void ColorImage::ReplaceHSLSaturationPlane(MonoImage* plane) {
|
||||
return ReplaceSecondColorPlane(IMAQ_HSL, plane);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the Saturation color plane in a HSV image with a MonoImage.
|
||||
*
|
||||
* @param mode The color mode in which to operate.
|
||||
* @param plane A pointer to a MonoImage that will replace the specified color
|
||||
* plane.
|
||||
*/
|
||||
void ColorImage::ReplaceHSVSaturationPlane(MonoImage* plane) {
|
||||
return ReplaceSecondColorPlane(IMAQ_HSV, plane);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the Saturation color plane in a HSI image with a MonoImage.
|
||||
*
|
||||
* @param mode The color mode in which to operate.
|
||||
* @param plane A pointer to a MonoImage that will replace the specified color
|
||||
* plane.
|
||||
*/
|
||||
void ColorImage::ReplaceHSISaturationPlane(MonoImage* plane) {
|
||||
return ReplaceSecondColorPlane(IMAQ_HSI, plane);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the Luminance color plane in an HSL image with a MonoImage.
|
||||
*
|
||||
* @param mode The color mode in which to operate.
|
||||
* @param plane A pointer to a MonoImage that will replace the specified color
|
||||
* plane.
|
||||
*/
|
||||
void ColorImage::ReplaceLuminancePlane(MonoImage* plane) {
|
||||
return ReplaceThirdColorPlane(IMAQ_HSL, plane);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the Value color plane in an HSV with a MonoImage.
|
||||
*
|
||||
* @param mode The color mode in which to operate.
|
||||
* @param plane A pointer to a MonoImage that will replace the specified color
|
||||
* plane.
|
||||
*/
|
||||
void ColorImage::ReplaceValuePlane(MonoImage* plane) {
|
||||
return ReplaceThirdColorPlane(IMAQ_HSV, plane);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the Intensity color plane in a HSI image with a MonoImage.
|
||||
*
|
||||
* @param mode The color mode in which to operate.
|
||||
* @param plane A pointer to a MonoImage that will replace the specified color
|
||||
* plane.
|
||||
*/
|
||||
void ColorImage::ReplaceIntensityPlane(MonoImage* plane) {
|
||||
return ReplaceThirdColorPlane(IMAQ_HSI, plane);
|
||||
}
|
||||
|
||||
// TODO: frcColorEqualize(Image* dest, const Image* source, int
|
||||
// colorEqualization) needs to be modified
|
||||
// The colorEqualization parameter is discarded and is set to TRUE in the call
|
||||
// to imaqColorEqualize.
|
||||
void ColorImage::Equalize(bool allPlanes) {
|
||||
// Note that this call uses NI-defined TRUE and FALSE
|
||||
int success = imaqColorEqualize(m_imaqImage, (const Image*)m_imaqImage,
|
||||
(allPlanes) ? TRUE : FALSE);
|
||||
wpi_setImaqErrorWithContext(success, "Imaq ColorEqualize error");
|
||||
}
|
||||
|
||||
void ColorImage::ColorEqualize() { Equalize(true); }
|
||||
|
||||
void ColorImage::LuminanceEqualize() { Equalize(false); }
|
||||
2406
wpilibc/athena/src/Vision/FrcError.cpp
Normal file
2406
wpilibc/athena/src/Vision/FrcError.cpp
Normal file
File diff suppressed because it is too large
Load Diff
23
wpilibc/athena/src/Vision/HSLImage.cpp
Normal file
23
wpilibc/athena/src/Vision/HSLImage.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2014-2016. All Rights Reserved. */
|
||||
/* 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 "Vision/HSLImage.h"
|
||||
|
||||
/**
|
||||
* Create a new image that uses the Hue, Saturation, and Luminance planes.
|
||||
*/
|
||||
HSLImage::HSLImage() : ColorImage(IMAQ_IMAGE_HSL) {}
|
||||
|
||||
/**
|
||||
* Create a new image by loading a file.
|
||||
*
|
||||
* @param fileName The path of the file to load.
|
||||
*/
|
||||
HSLImage::HSLImage(const char* fileName) : ColorImage(IMAQ_IMAGE_HSL) {
|
||||
int success = imaqReadFile(m_imaqImage, fileName, nullptr, nullptr);
|
||||
wpi_setImaqErrorWithContext(success, "Imaq ReadFile error");
|
||||
}
|
||||
69
wpilibc/athena/src/Vision/ImageBase.cpp
Normal file
69
wpilibc/athena/src/Vision/ImageBase.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2014-2016. All Rights Reserved. */
|
||||
/* 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 "Vision/ImageBase.h"
|
||||
#include "nivision.h"
|
||||
|
||||
/**
|
||||
* Create a new instance of an ImageBase.
|
||||
*
|
||||
* ImageBase is the base of all the other image classes. The constructor
|
||||
* creates any type of image and stores the pointer to it in the class.
|
||||
*
|
||||
* @param type The type of image to create
|
||||
*/
|
||||
ImageBase::ImageBase(ImageType type) {
|
||||
m_imaqImage = imaqCreateImage(type, DEFAULT_BORDER_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees memory associated with an ImageBase.
|
||||
*
|
||||
* Destructor frees the imaq image allocated with the class.
|
||||
*/
|
||||
ImageBase::~ImageBase() {
|
||||
if (m_imaqImage) imaqDispose(m_imaqImage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an image to a file with the given filename.
|
||||
*
|
||||
* @param fileName The name of the file to write
|
||||
*/
|
||||
void ImageBase::Write(const char* fileName) {
|
||||
int success = imaqWriteFile(m_imaqImage, fileName, nullptr);
|
||||
wpi_setImaqErrorWithContext(success, "Imaq Image writeFile error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the height of an image.
|
||||
*
|
||||
* @return The height of the image in pixels.
|
||||
*/
|
||||
int ImageBase::GetHeight() {
|
||||
int height;
|
||||
imaqGetImageSize(m_imaqImage, nullptr, &height);
|
||||
return height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the width of an image.
|
||||
*
|
||||
* @return The width of the image in pixels.
|
||||
*/
|
||||
int ImageBase::GetWidth() {
|
||||
int width;
|
||||
imaqGetImageSize(m_imaqImage, &width, nullptr);
|
||||
return width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the internal IMAQ Image data structure.
|
||||
*
|
||||
* @return A pointer to the internal IMAQ Image data structure.
|
||||
*/
|
||||
Image* ImageBase::GetImaqImage() { return m_imaqImage; }
|
||||
49
wpilibc/athena/src/Vision/MonoImage.cpp
Normal file
49
wpilibc/athena/src/Vision/MonoImage.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2014-2016. All Rights Reserved. */
|
||||
/* 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 "Vision/MonoImage.h"
|
||||
#include "nivision.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
MonoImage::MonoImage() : ImageBase(IMAQ_IMAGE_U8) {}
|
||||
|
||||
/**
|
||||
* Look for ellipses in an image.
|
||||
*
|
||||
* Given some input parameters, look for any number of ellipses in an image.
|
||||
*
|
||||
* @param ellipseDescriptor Ellipse descriptor
|
||||
* @param curveOptions Curve options
|
||||
* @param shapeDetectionOptions Shape detection options
|
||||
* @param roi Region of Interest
|
||||
* @returns a vector of EllipseMatch structures (0 length vector on no match)
|
||||
*/
|
||||
vector<EllipseMatch>* MonoImage::DetectEllipses(
|
||||
EllipseDescriptor* ellipseDescriptor, CurveOptions* curveOptions,
|
||||
ShapeDetectionOptions* shapeDetectionOptions, ROI* roi) {
|
||||
int numberOfMatches;
|
||||
EllipseMatch* e =
|
||||
imaqDetectEllipses(m_imaqImage, ellipseDescriptor, curveOptions,
|
||||
shapeDetectionOptions, roi, &numberOfMatches);
|
||||
auto ellipses = new vector<EllipseMatch>;
|
||||
if (e == nullptr) {
|
||||
return ellipses;
|
||||
}
|
||||
for (int i = 0; i < numberOfMatches; i++) {
|
||||
ellipses->push_back(e[i]);
|
||||
}
|
||||
imaqDispose(e);
|
||||
return ellipses;
|
||||
}
|
||||
|
||||
vector<EllipseMatch>* MonoImage::DetectEllipses(
|
||||
EllipseDescriptor* ellipseDescriptor) {
|
||||
vector<EllipseMatch>* ellipses =
|
||||
DetectEllipses(ellipseDescriptor, nullptr, nullptr, nullptr);
|
||||
return ellipses;
|
||||
}
|
||||
23
wpilibc/athena/src/Vision/RGBImage.cpp
Normal file
23
wpilibc/athena/src/Vision/RGBImage.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2014-2016. All Rights Reserved. */
|
||||
/* 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 "Vision/RGBImage.h"
|
||||
|
||||
/**
|
||||
* Create a new image that uses Red, Green, and Blue planes.
|
||||
*/
|
||||
RGBImage::RGBImage() : ColorImage(IMAQ_IMAGE_RGB) {}
|
||||
|
||||
/**
|
||||
* Create a new image by loading a file.
|
||||
*
|
||||
* @param fileName The path of the file to load.
|
||||
*/
|
||||
RGBImage::RGBImage(const char* fileName) : ColorImage(IMAQ_IMAGE_RGB) {
|
||||
int success = imaqReadFile(m_imaqImage, fileName, nullptr, nullptr);
|
||||
wpi_setImaqErrorWithContext(success, "Imaq ReadFile error");
|
||||
}
|
||||
19
wpilibc/athena/src/Vision/Threshold.cpp
Normal file
19
wpilibc/athena/src/Vision/Threshold.cpp
Normal file
@@ -0,0 +1,19 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2014-2016. All Rights Reserved. */
|
||||
/* 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 "Vision/Threshold.h"
|
||||
|
||||
Threshold::Threshold(int new_plane1Low, int new_plane1High, int new_plane2Low,
|
||||
int new_plane2High, int new_plane3Low,
|
||||
int new_plane3High) {
|
||||
plane1Low = new_plane1Low;
|
||||
plane1High = new_plane1High;
|
||||
plane2Low = new_plane2Low;
|
||||
plane2High = new_plane2High;
|
||||
plane3Low = new_plane3Low;
|
||||
plane3High = new_plane3High;
|
||||
}
|
||||
832
wpilibc/athena/src/Vision/VisionAPI.cpp
Normal file
832
wpilibc/athena/src/Vision/VisionAPI.cpp
Normal file
@@ -0,0 +1,832 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2014-2016. All Rights Reserved. */
|
||||
/* 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 <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "Vision/BaeUtilities.h"
|
||||
#include "Vision/FrcError.h"
|
||||
#include "Vision/VisionAPI.h"
|
||||
|
||||
int VisionAPI_debugFlag = 1;
|
||||
#define DPRINTF \
|
||||
if (VisionAPI_debugFlag) dprintf
|
||||
|
||||
/** @file
|
||||
* Image Management functions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Create an image object
|
||||
* Supports IMAQ_IMAGE_U8, IMAQ_IMAGE_I16, IMAQ_IMAGE_SGL, IMAQ_IMAGE_COMPLEX,
|
||||
* IMAQ_IMAGE_RGB, IMAQ_IMAGE_HSL, IMAQ_IMAGE_RGB_U64
|
||||
* The border size is defaulted to 3 so that convolutional algorithms work at
|
||||
* the edges.
|
||||
* When you are finished with the created image, dispose of it by calling
|
||||
* frcDispose().
|
||||
* To get extended error information, call GetLastError().
|
||||
*
|
||||
* @param type Type of image to create
|
||||
* @return Image* On success, this function returns the created image. On
|
||||
* failure, it returns nullptr.
|
||||
*/
|
||||
Image* frcCreateImage(ImageType type) {
|
||||
return imaqCreateImage(type, DEFAULT_BORDER_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Dispose of one object. Supports any object created on the heap.
|
||||
*
|
||||
* @param object object to dispose of
|
||||
* @return On success: 1. On failure: 0. To get extended error information, call
|
||||
* GetLastError().
|
||||
*/
|
||||
int frcDispose(void* object) { return imaqDispose(object); }
|
||||
|
||||
/**
|
||||
* @brief Dispose of a list of objects. Supports any object created on the heap.
|
||||
*
|
||||
* @param functionName The name of the function
|
||||
* @param ... A list of pointers to structures that need to be
|
||||
* disposed of. The last pointer in the list should always
|
||||
* be set to nullptr.
|
||||
*
|
||||
* @return On success: 1. On failure: 0. To get extended error information, call
|
||||
* GetLastError().
|
||||
*/
|
||||
int frcDispose(const char* functionName, ...) /* Variable argument list */
|
||||
{
|
||||
va_list disposalPtrList; /* Input argument list */
|
||||
void* disposalPtr; /* For iteration */
|
||||
int success, returnValue = 1;
|
||||
|
||||
va_start(disposalPtrList, functionName); /* start of variable list */
|
||||
disposalPtr = va_arg(disposalPtrList, void*);
|
||||
while (disposalPtr != nullptr) {
|
||||
success = imaqDispose(disposalPtr);
|
||||
if (!success) {
|
||||
returnValue = 0;
|
||||
}
|
||||
disposalPtr = va_arg(disposalPtrList, void*);
|
||||
}
|
||||
va_end(disposalPtrList);
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy an image object.
|
||||
* Supports IMAQ_IMAGE_U8, IMAQ_IMAGE_I16, IMAQ_IMAGE_SGL, IMAQ_IMAGE_RGB,
|
||||
* IMAQ_IMAGE_HSL.
|
||||
*
|
||||
* @param dest Copy of image. On failure, dest is nullptr. Must have already
|
||||
* been created using frcCreateImage(). When you are finished with
|
||||
* the created image, dispose of it by calling frcDispose().
|
||||
* @param source Image to copy
|
||||
*
|
||||
* @return On success: 1. On failure: 0. To get extended error information, call
|
||||
* GetLastError().
|
||||
*/
|
||||
int frcCopyImage(Image* dest, const Image* source) {
|
||||
return imaqDuplicate(dest, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Crop image without changing the scale.
|
||||
* Supports IMAQ_IMAGE_U8, IMAQ_IMAGE_I16, IMAQ_IMAGE_SGL, IMAQ_IMAGE_RGB,
|
||||
* IMAQ_IMAGE_HSL.
|
||||
*
|
||||
* @param dest Modified image
|
||||
* @param source Image to crop
|
||||
* @param rect region to process, or IMAQ_NO_RECT
|
||||
*
|
||||
* @return On success: 1. On failure: 0. To get extended error information, call
|
||||
* GetLastError().
|
||||
*/
|
||||
int frcCrop(Image* dest, const Image* source, Rect rect) {
|
||||
return imaqScale(dest, source, 1, 1, IMAQ_SCALE_LARGER, rect);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Scales the entire image larger or smaller.
|
||||
* Supports IMAQ_IMAGE_U8, IMAQ_IMAGE_I16, IMAQ_IMAGE_SGL, IMAQ_IMAGE_RGB,
|
||||
* IMAQ_IMAGE_HSL.
|
||||
*
|
||||
* @param dest Modified image
|
||||
* @param source Image to scale
|
||||
* @param xScale the horizontal reduction ratio
|
||||
* @param yScale the vertical reduction ratio
|
||||
* @param scaleMode IMAQ_SCALE_LARGER or IMAQ_SCALE_SMALLER
|
||||
*
|
||||
* @return On success: 1. On failure: 0. To get extended error information, call
|
||||
* GetLastError().
|
||||
*/
|
||||
int frcScale(Image* dest, const Image* source, int xScale, int yScale,
|
||||
ScalingMode scaleMode) {
|
||||
Rect rect = IMAQ_NO_RECT;
|
||||
return imaqScale(dest, source, xScale, yScale, scaleMode, rect);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates image object from the information in a file. The file can be
|
||||
* in one of the following formats:
|
||||
* PNG, JPEG, JPEG2000, TIFF, AIPD, or BMP.
|
||||
* Supports IMAQ_IMAGE_U8, IMAQ_IMAGE_I16, IMAQ_IMAGE_SGL, IMAQ_IMAGE_COMPLEX,
|
||||
* IMAQ_IMAGE_RGB, IMAQ_IMAGE_HSL, IMAQ_IMAGE_RGB_U64.
|
||||
*
|
||||
* @param image Image read in
|
||||
* @param fileName File to read. Cannot be nullptr
|
||||
*
|
||||
* @return On success: 1. On failure: 0. To get extended error information, call
|
||||
* GetLastError().
|
||||
*/
|
||||
int frcReadImage(Image* image, const char* fileName) {
|
||||
return imaqReadFile(image, fileName, nullptr, nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Write image to a file.
|
||||
* Supports IMAQ_IMAGE_U8, IMAQ_IMAGE_I16, IMAQ_IMAGE_SGL, IMAQ_IMAGE_COMPLEX,
|
||||
* IMAQ_IMAGE_RGB, IMAQ_IMAGE_HSL, IMAQ_IMAGE_RGB_U64.
|
||||
*
|
||||
* The file type is determined by the extension, as follows:
|
||||
*
|
||||
* Extension File Type
|
||||
* aipd or .apd AIPD
|
||||
* .bmp BMP
|
||||
* .jpg or .jpeg JPEG
|
||||
* .jp2 JPEG2000
|
||||
* .png PNG
|
||||
* .tif or .tiff TIFF
|
||||
*
|
||||
*
|
||||
* The following are the supported image types for each file type:
|
||||
*
|
||||
* File Types Image Types
|
||||
* AIPD all image types
|
||||
* BMP, JPEG 8-bit, RGB
|
||||
* PNG, TIFF, JPEG2000 8-bit, 16-bit, RGB, RGBU64
|
||||
*
|
||||
* @param image Image to write
|
||||
* @param fileName File to read. Cannot be nullptr. The extension determines the
|
||||
* file format that is written.
|
||||
*
|
||||
* @return On success: 1. On failure: 0. To get extended error information, call
|
||||
* GetLastError().
|
||||
*/
|
||||
int frcWriteImage(const Image* image, const char* fileName) {
|
||||
RGBValue* colorTable = nullptr;
|
||||
return imaqWriteFile(image, fileName, colorTable);
|
||||
}
|
||||
|
||||
/* Measure Intensity functions */
|
||||
|
||||
/**
|
||||
* @brief Measures the pixel intensities in a rectangle of an image.
|
||||
* Outputs intensity based statistics about an image such as Max, Min, Mean and
|
||||
* Std Dev of pixel value.
|
||||
* Supports IMAQ_IMAGE_U8, IMAQ_IMAGE_I16, IMAQ_IMAGE_SGL.
|
||||
*
|
||||
* Parameter Discussion:
|
||||
* Relevant parameters of the HistogramReport include:
|
||||
* min, max, mean and stdDev
|
||||
* min/max —Setting both min and max to 0 causes the function to
|
||||
* set min to 0 and the max to 255 for 8-bit images and to the actual
|
||||
* minimum value and maximum value of the image for all other image
|
||||
* types.
|
||||
* max—Setting both min and max to 0 causes the function to set max
|
||||
* to 255 for 8-bit images and to the actual maximum value of the
|
||||
* image for all other image types.
|
||||
*
|
||||
* @param image Image whose histogram the function calculates.
|
||||
* @param numClasses The number of classes into which the function separates the
|
||||
* pixels. Determines the number of elements in the histogram
|
||||
* array returned
|
||||
* @param min The minimum pixel value to consider for the histogram. The
|
||||
* function does not count pixels with values less than min.
|
||||
* @param max The maximum pixel value to consider for the histogram. The
|
||||
* function does not count pixels with values greater than
|
||||
* max.
|
||||
* @param rect Region of interest in the image. If not included, the
|
||||
* entire image is used.
|
||||
* @return On success, this function returns a report describing the pixel value
|
||||
* classification. When you are finished with the report, dispose of it
|
||||
* by calling frcDispose(). On failure, this function returns nullptr.
|
||||
* To get extended error information, call GetLastError().
|
||||
*/
|
||||
HistogramReport* frcHistogram(const Image* image, int numClasses, float min,
|
||||
float max) {
|
||||
Rect rect = IMAQ_NO_RECT;
|
||||
return frcHistogram(image, numClasses, min, max, rect);
|
||||
}
|
||||
|
||||
HistogramReport* frcHistogram(const Image* image, int numClasses, float min,
|
||||
float max, Rect rect) {
|
||||
int success;
|
||||
int fillValue = 1;
|
||||
|
||||
/* create the region of interest */
|
||||
ROI* pRoi = imaqCreateROI();
|
||||
success = imaqAddRectContour(pRoi, rect);
|
||||
if (!success) {
|
||||
GetLastVisionError();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* make a mask from the ROI */
|
||||
Image* pMask = frcCreateImage(IMAQ_IMAGE_U8);
|
||||
success = imaqROIToMask(pMask, pRoi, fillValue, nullptr, nullptr);
|
||||
if (!success) {
|
||||
GetLastVisionError();
|
||||
frcDispose(__FUNCTION__, pRoi, nullptr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* get a histogram report */
|
||||
HistogramReport* pHr = nullptr;
|
||||
pHr = imaqHistogram(image, numClasses, min, max, pMask);
|
||||
|
||||
/* clean up */
|
||||
frcDispose(__FUNCTION__, pRoi, pMask, nullptr);
|
||||
|
||||
return pHr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the histogram, or pixel distribution, of a color image.
|
||||
* Supports IMAQ_IMAGE_RGB, IMAQ_IMAGE_HSL.
|
||||
*
|
||||
* @param image Image whose histogram the function calculates.
|
||||
* @param numClasses The number of classes into which the function separates the
|
||||
* pixels. Determines the number of elements in the histogram
|
||||
* array returned
|
||||
* @param mode The color space in which to perform the histogram. Possible
|
||||
* values include IMAQ_RGB and IMAQ_HSL.
|
||||
* @param mask An optional mask image. This image must be an IMAQ_IMAGE_U8
|
||||
* image. The function calculates the histogram using only
|
||||
* those pixels in the image whose corresponding pixels in the
|
||||
* mask are non-zero. Set this parameter to nullptr to
|
||||
* calculate the histogram of the entire image, or use the
|
||||
* simplified call.
|
||||
*
|
||||
* @return On success, this function returns a report describing the
|
||||
* classification of each plane in a HistogramReport. When you are
|
||||
* finished with the report, dispose of it by calling frcDispose().
|
||||
* On failure, this function returns nullptr. To get extended error
|
||||
* information, call imaqGetLastError().
|
||||
*/
|
||||
ColorHistogramReport* frcColorHistogram(const Image* image, int numClasses,
|
||||
ColorMode mode) {
|
||||
return frcColorHistogram(image, numClasses, mode, nullptr);
|
||||
}
|
||||
|
||||
ColorHistogramReport* frcColorHistogram(const Image* image, int numClasses,
|
||||
ColorMode mode, Image* mask) {
|
||||
return imaqColorHistogram2((Image*)image, numClasses, mode, nullptr, mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Measures the pixel intensities in a rectangle of an image.
|
||||
* Outputs intensity based statistics about an image such as Max, Min, Mean and
|
||||
* Std Dev of pixel value.
|
||||
* Supports IMAQ_IMAGE_U8 (grayscale) IMAQ_IMAGE_RGB (color) IMAQ_IMAGE_HSL
|
||||
* (color-HSL).
|
||||
*
|
||||
* @param image The image whose pixel value the function queries
|
||||
* @param pixel The coordinates of the pixel that the function queries
|
||||
* @param value On return, the value of the specified image pixel. This
|
||||
* parameter cannot be nullptr. This data structure contains
|
||||
* either grayscale, RGB, HSL, Complex or RGBU64Value depending on
|
||||
* the type of image.
|
||||
* @return On success: 1. On failure: 0. To get extended error information, call
|
||||
* GetLastError().
|
||||
*/
|
||||
int frcGetPixelValue(const Image* image, Point pixel, PixelValue* value) {
|
||||
return imaqGetPixel(image, pixel, value);
|
||||
}
|
||||
|
||||
/* Particle Analysis functions */
|
||||
|
||||
/**
|
||||
* @brief Filters particles out of an image based on their measurements.
|
||||
* Supports IMAQ_IMAGE_U8, IMAQ_IMAGE_I16, IMAQ_IMAGE_SGL.
|
||||
*
|
||||
* @param dest The destination image. If dest is used, it must be the
|
||||
* same size as the Source image. It will contain only the
|
||||
* filtered particles.
|
||||
* @param source The image containing the particles to filter.
|
||||
* @param criteria An array of criteria to apply to the particles in the
|
||||
* source image. This array cannot be nullptr. See the
|
||||
* NIVisionCVI.chm help file for definitions of criteria.
|
||||
* @param criteriaCount The number of elements in the criteria array.
|
||||
* @param options Binary filter options, including rejectMatches,
|
||||
* rejectBorder, and connectivity8.
|
||||
* @param rect Area of image to filter. If omitted, the default is
|
||||
* entire image.
|
||||
* @param numParticles On return, the number of particles left in the image
|
||||
* @return On success: 1. On failure: 0. To get extended error information, call
|
||||
* GetLastError().
|
||||
*/
|
||||
int frcParticleFilter(Image* dest, Image* source,
|
||||
const ParticleFilterCriteria2* criteria,
|
||||
int criteriaCount, const ParticleFilterOptions* options,
|
||||
int* numParticles) {
|
||||
Rect rect = IMAQ_NO_RECT;
|
||||
return frcParticleFilter(dest, source, criteria, criteriaCount, options, rect,
|
||||
numParticles);
|
||||
}
|
||||
|
||||
int frcParticleFilter(Image* dest, Image* source,
|
||||
const ParticleFilterCriteria2* criteria,
|
||||
int criteriaCount, const ParticleFilterOptions* options,
|
||||
Rect rect, int* numParticles) {
|
||||
ROI* roi = imaqCreateROI();
|
||||
imaqAddRectContour(roi, rect);
|
||||
return imaqParticleFilter3(dest, source, criteria, criteriaCount, options,
|
||||
roi, numParticles);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Performs morphological transformations on binary images.
|
||||
* Supports IMAQ_IMAGE_U8.
|
||||
*
|
||||
* @param dest The destination image. The border size of the
|
||||
* destination image is not important.
|
||||
* @param source The image on which the function performs the
|
||||
* morphological operations. The calculation modifies
|
||||
* the border of the source image. The border must be
|
||||
* at least half as large as the larger dimension of
|
||||
* the structuring element. The connected source
|
||||
* image for a morphological transformation must have
|
||||
* been created with a border capable of supporting
|
||||
* the size of the structuring element. A 3 by 3
|
||||
* structuring element requires a minimal border of 1,
|
||||
* a 5 by 5 structuring element requires a minimal
|
||||
* border of 2, and so on.
|
||||
* @param method The morphological transform to apply.
|
||||
* @param structuringElement The structuring element used in the operation. Omit
|
||||
* this parameter if you do not want a custom
|
||||
* structuring element.
|
||||
* @return On success: 1. On failure: 0. To get extended error information, call
|
||||
* GetLastError().
|
||||
*/
|
||||
int frcMorphology(Image* dest, Image* source, MorphologyMethod method) {
|
||||
return imaqMorphology(dest, source, method, nullptr);
|
||||
}
|
||||
|
||||
int frcMorphology(Image* dest, Image* source, MorphologyMethod method,
|
||||
const StructuringElement* structuringElement) {
|
||||
return imaqMorphology(dest, source, method, structuringElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Eliminates particles that touch the border of the image.
|
||||
* Supports IMAQ_IMAGE_U8.
|
||||
*
|
||||
* @param dest The destination image.
|
||||
* @param source The source image. If the image has a border, the
|
||||
* function sets all border pixel values to 0.
|
||||
* @param connectivity8 specifies the type of connectivity used by the algorithm
|
||||
* for particle detection. The connectivity mode directly
|
||||
* determines whether an adjacent pixel belongs to the same
|
||||
* particle or a different particle. Set to TRUE to use
|
||||
* connectivity-8 to determine whether particles are
|
||||
* touching. Set to FALSE to use connectivity-4 to
|
||||
* determine whether particles are touching. The default
|
||||
* setting for the simplified call is TRUE
|
||||
* @return On success: 1. On failure: 0. To get extended error information, call
|
||||
* GetLastError().
|
||||
*/
|
||||
int frcRejectBorder(Image* dest, Image* source) {
|
||||
return imaqRejectBorder(dest, source, TRUE);
|
||||
}
|
||||
|
||||
int frcRejectBorder(Image* dest, Image* source, int connectivity8) {
|
||||
return imaqRejectBorder(dest, source, connectivity8);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Counts the number of particles in a binary image.
|
||||
* Supports IMAQ_IMAGE_U8, IMAQ_IMAGE_I16, IMAQ_IMAGE_SGL.
|
||||
* @param image binary (thresholded) image
|
||||
* @param numParticles On return, the number of particles.
|
||||
* @return On success: 1. On failure: 0. To get extended error information, call
|
||||
* GetLastError().
|
||||
*/
|
||||
int frcCountParticles(Image* image, int* numParticles) {
|
||||
return imaqCountParticles(image, 1, numParticles);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Conduct measurements for a single particle in an images.
|
||||
* Supports IMAQ_IMAGE_U8, IMAQ_IMAGE_I16, IMAQ_IMAGE_SGL.
|
||||
*
|
||||
* @param image image with the particle to analyze. This function
|
||||
* modifies the source image. If you need the original
|
||||
* image, create a copy of the image using frcCopy()
|
||||
* before using this function.
|
||||
* @param particleNumber The number of the particle to get information on
|
||||
* @param par on return, a particle analysis report containing
|
||||
* information about the particle. This structure must be
|
||||
* created by the caller.
|
||||
* @return On success: 1. On failure: 0. To get extended error information, call
|
||||
* GetLastError().
|
||||
*/
|
||||
int frcParticleAnalysis(Image* image, int particleNumber,
|
||||
ParticleAnalysisReport* par) {
|
||||
int success = 0;
|
||||
|
||||
/* image information */
|
||||
int height, width;
|
||||
if (!imaqGetImageSize(image, &width, &height)) {
|
||||
return success;
|
||||
}
|
||||
par->imageWidth = width;
|
||||
par->imageHeight = height;
|
||||
par->particleIndex = particleNumber;
|
||||
|
||||
/* center of mass point of the largest particle */
|
||||
double returnDouble;
|
||||
success = imaqMeasureParticle(image, particleNumber, 0,
|
||||
IMAQ_MT_CENTER_OF_MASS_X, &returnDouble);
|
||||
if (!success) {
|
||||
return success;
|
||||
}
|
||||
par->center_mass_x = (int)returnDouble; // pixel
|
||||
|
||||
success = imaqMeasureParticle(image, particleNumber, 0,
|
||||
IMAQ_MT_CENTER_OF_MASS_Y, &returnDouble);
|
||||
if (!success) {
|
||||
return success;
|
||||
}
|
||||
par->center_mass_y = (int)returnDouble; // pixel
|
||||
|
||||
/* particle size statistics */
|
||||
success = imaqMeasureParticle(image, particleNumber, 0, IMAQ_MT_AREA,
|
||||
&returnDouble);
|
||||
if (!success) {
|
||||
return success;
|
||||
}
|
||||
par->particleArea = returnDouble;
|
||||
|
||||
success = imaqMeasureParticle(image, particleNumber, 0,
|
||||
IMAQ_MT_BOUNDING_RECT_TOP, &returnDouble);
|
||||
if (!success) {
|
||||
return success;
|
||||
}
|
||||
par->boundingRect.top = (int)returnDouble;
|
||||
|
||||
success = imaqMeasureParticle(image, particleNumber, 0,
|
||||
IMAQ_MT_BOUNDING_RECT_LEFT, &returnDouble);
|
||||
if (!success) {
|
||||
return success;
|
||||
}
|
||||
par->boundingRect.left = (int)returnDouble;
|
||||
|
||||
success = imaqMeasureParticle(image, particleNumber, 0,
|
||||
IMAQ_MT_BOUNDING_RECT_HEIGHT, &returnDouble);
|
||||
if (!success) {
|
||||
return success;
|
||||
}
|
||||
par->boundingRect.height = (int)returnDouble;
|
||||
|
||||
success = imaqMeasureParticle(image, particleNumber, 0,
|
||||
IMAQ_MT_BOUNDING_RECT_WIDTH, &returnDouble);
|
||||
if (!success) {
|
||||
return success;
|
||||
}
|
||||
par->boundingRect.width = (int)returnDouble;
|
||||
|
||||
/* particle quality statistics */
|
||||
success = imaqMeasureParticle(image, particleNumber, 0,
|
||||
IMAQ_MT_AREA_BY_IMAGE_AREA, &returnDouble);
|
||||
if (!success) {
|
||||
return success;
|
||||
}
|
||||
par->particleToImagePercent = returnDouble;
|
||||
|
||||
success = imaqMeasureParticle(image, particleNumber, 0,
|
||||
IMAQ_MT_AREA_BY_PARTICLE_AND_HOLES_AREA,
|
||||
&returnDouble);
|
||||
if (!success) {
|
||||
return success;
|
||||
}
|
||||
par->particleQuality = returnDouble;
|
||||
|
||||
/* normalized position (-1 to 1) */
|
||||
par->center_mass_x_normalized = RangeToNormalized(par->center_mass_x, width);
|
||||
par->center_mass_y_normalized = RangeToNormalized(par->center_mass_y, height);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/* Image Enhancement functions */
|
||||
|
||||
/**
|
||||
* @brief Improves contrast on a grayscale image.
|
||||
* Supports IMAQ_IMAGE_U8, IMAQ_IMAGE_I16.
|
||||
* @param dest The destination image.
|
||||
* @param source The image to equalize
|
||||
* @param min the smallest value used for processing. After processing, all
|
||||
* pixel values that are less than or equal to the Minimum in the
|
||||
* original image are set to 0 for an 8-bit image. In 16-bit and
|
||||
* floating-point images, these pixel values are set to the
|
||||
* smallest pixel value found in the original image.
|
||||
* @param max the largest value used for processing. After processing, all
|
||||
* pixel values that are greater than or equal to the Maximum in
|
||||
* the original image are set to 255 for an 8-bit image. In 16-bit
|
||||
* and floating-point images, these pixel values are set to the
|
||||
* largest pixel value found in the original image.
|
||||
* @param mask an 8-bit image that specifies the region of the small image
|
||||
* that will be copied. Only those pixels in the Image Src (Small)
|
||||
* image that correspond to an equivalent non-zero pixel in the
|
||||
* mask image are copied. All other pixels keep their original
|
||||
* values. The entire image is processed if Image Mask is nullptr
|
||||
* or this parameter is omitted.
|
||||
* @return On success: 1. On failure: 0. To get extended error information, call
|
||||
* GetLastError().
|
||||
*
|
||||
* option defaults:
|
||||
* searchRect = IMAQ_NO_RECT
|
||||
* minMatchScore = DEFAULT_MINMAX_SCORE (800)
|
||||
*/
|
||||
int frcEqualize(Image* dest, const Image* source, float min, float max) {
|
||||
return frcEqualize(dest, source, min, max, nullptr);
|
||||
}
|
||||
|
||||
int frcEqualize(Image* dest, const Image* source, float min, float max,
|
||||
const Image* mask) {
|
||||
return imaqEqualize(dest, source, min, max, mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Improves contrast on a color image.
|
||||
* Supports IMAQ_IMAGE_RGB, IMAQ_IMAGE_HSL
|
||||
*
|
||||
* option defaults: colorEqualization = TRUE to equalize all three planes of the
|
||||
* image
|
||||
* @return On success: 1. On failure: 0. To get extended error information, call
|
||||
* GetLastError().
|
||||
* @param dest The destination image.
|
||||
* @param source The image to equalize
|
||||
* @param colorEqualization Set this parameter to TRUE to equalize all three
|
||||
* planes of the image (the default). Set this parameter to FALSE to equalize
|
||||
* only the luminance plane.
|
||||
*/
|
||||
int frcColorEqualize(Image* dest, const Image* source) {
|
||||
return imaqColorEqualize(dest, source, TRUE);
|
||||
}
|
||||
|
||||
int frcColorEqualize(Image* dest, const Image* source, int colorEqualization) {
|
||||
return imaqColorEqualize(dest, source, TRUE);
|
||||
}
|
||||
|
||||
/* Image Conversion functions */
|
||||
|
||||
/**
|
||||
* @brief Automatically thresholds a grayscale image into a binary image for
|
||||
* Particle Analysis based on a smart threshold.
|
||||
* Supports IMAQ_IMAGE_RGB, IMAQ_IMAGE_I16
|
||||
* @param dest The destination image.
|
||||
* @param source The image to threshold
|
||||
* @param windowWidth The width of the rectangular window around the pixel
|
||||
* on which the function performs the local threshold.
|
||||
* This number must be at least 3 and cannot be larger
|
||||
* than the width of source
|
||||
* @param windowHeight The height of the rectangular window around the pixel
|
||||
* on which the function performs the local threshold.
|
||||
* This number must be at least 3 and cannot be larger
|
||||
* than the height of source
|
||||
* @param method Specifies the local thresholding method the function
|
||||
* uses. Value can be IMAQ_NIBLACK (which computes
|
||||
* thresholds for each pixel based on its local
|
||||
* statistics using the Niblack local thresholding
|
||||
* algorithm.), or IMAQ_BACKGROUND_CORRECTION (which
|
||||
* does background correction first to eliminate
|
||||
* non-uniform lighting effects, then performs
|
||||
* thresholding using the Otsu thresholding algorithm)
|
||||
* @param deviationWeight Specifies the k constant used in the Niblack local
|
||||
* thresholding algorithm, which determines the weight
|
||||
* applied to the variance calculation. Valid k constants
|
||||
* range from 0 to 1. Setting this value to 0 will
|
||||
* increase the performance of the function because the
|
||||
* function will not calculate the variance for any of
|
||||
* the pixels. The function ignores this value if method
|
||||
* is not set to IMAQ_NIBLACK
|
||||
* @param type Specifies the type of objects for which you want to
|
||||
* look. Values can be IMAQ_BRIGHT_OBJECTS or
|
||||
* IMAQ_DARK_OBJECTS.
|
||||
* @param replaceValue Specifies the replacement value the function uses for
|
||||
* the pixels of the kept objects in the destination
|
||||
* image.
|
||||
* @return On success: 1. On failure: 0. To get extended error information, call
|
||||
* GetLastError().
|
||||
*/
|
||||
int frcSmartThreshold(Image* dest, const Image* source,
|
||||
unsigned int windowWidth, unsigned int windowHeight,
|
||||
LocalThresholdMethod method, double deviationWeight,
|
||||
ObjectType type) {
|
||||
float replaceValue = 1.0;
|
||||
return imaqLocalThreshold(dest, source, windowWidth, windowHeight, method,
|
||||
deviationWeight, type, replaceValue);
|
||||
}
|
||||
|
||||
int frcSmartThreshold(Image* dest, const Image* source,
|
||||
unsigned int windowWidth, unsigned int windowHeight,
|
||||
LocalThresholdMethod method, double deviationWeight,
|
||||
ObjectType type, float replaceValue) {
|
||||
return imaqLocalThreshold(dest, source, windowWidth, windowHeight, method,
|
||||
deviationWeight, type, replaceValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Converts a grayscale image to a binary image for Particle Analysis
|
||||
* based on a fixed threshold.
|
||||
* The function sets pixels values outside of the given range to 0. The function
|
||||
* sets pixel values within the range to a given value or leaves the values
|
||||
* unchanged. Use the simplified call to leave pixel values unchanged.
|
||||
* Supports IMAQ_IMAGE_RGB, IMAQ_IMAGE_I16.
|
||||
*
|
||||
* @param dest The destination image.
|
||||
* @param source The image to threshold
|
||||
* @param rangeMin The lower boundary of the range of pixel values to keep
|
||||
* @param rangeMax The upper boundary of the range of pixel values to keep.
|
||||
*
|
||||
* @return int - error code: 0 = error. To get extended error information, call
|
||||
* GetLastError().
|
||||
*/
|
||||
int frcSimpleThreshold(Image* dest, const Image* source, float rangeMin,
|
||||
float rangeMax) {
|
||||
int newValue = 255;
|
||||
return frcSimpleThreshold(dest, source, rangeMin, rangeMax, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Converts a grayscale image to a binary image for Particle Analysis
|
||||
* based on a fixed threshold.
|
||||
* The function sets pixels values outside of the given range to 0. The function
|
||||
* sets pixel values within the range to the given value.
|
||||
* Supports IMAQ_IMAGE_RGB, IMAQ_IMAGE_I16.
|
||||
*
|
||||
* @param dest The destination image.
|
||||
* @param source The image to threshold
|
||||
* @param rangeMin The lower boundary of the range of pixel values to keep
|
||||
* @param rangeMax The upper boundary of the range of pixel values to keep.
|
||||
* @param newValue The replacement value for pixels within the range. Use the
|
||||
* simplified call to leave the pixel values unchanged
|
||||
*
|
||||
* @return int - error code: 0 = error. To get extended error information, call
|
||||
* GetLastError().
|
||||
*/
|
||||
int frcSimpleThreshold(Image* dest, const Image* source, float rangeMin,
|
||||
float rangeMax, float newValue) {
|
||||
int useNewValue = TRUE;
|
||||
return imaqThreshold(dest, source, rangeMin, rangeMax, useNewValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Applies a threshold to the Red, Green, and Blue values of a RGB image
|
||||
* or the Hue, Saturation, Luminance values for a HSL image.
|
||||
* Supports IMAQ_IMAGE_RGB, IMAQ_IMAGE_HSL.
|
||||
* This simpler version filters based on a hue range in the HSL mode.
|
||||
*
|
||||
* @param dest The destination image. This must be a IMAQ_IMAGE_U8 image.
|
||||
* @param source The image to threshold
|
||||
* @param mode The color space to perform the threshold in. valid values
|
||||
* are: IMAQ_RGB, IMAQ_HSL.
|
||||
* @param plane1Range The selection range for the first plane of the image. Set
|
||||
* this parameter to nullptr to use a selection range from 0
|
||||
* to 255.
|
||||
* @param plane2Range The selection range for the second plane of the image. Set
|
||||
* this parameter to nullptr to use a selection range from 0
|
||||
* to 255.
|
||||
* @param plane3Range The selection range for the third plane of the image. Set
|
||||
* this parameter to nullptr to use a selection range from 0
|
||||
* to 255.
|
||||
*
|
||||
* @return On success: 1. On failure: 0. To get extended error information, call
|
||||
* GetLastError().
|
||||
*/
|
||||
int frcColorThreshold(Image* dest, const Image* source, ColorMode mode,
|
||||
const Range* plane1Range, const Range* plane2Range,
|
||||
const Range* plane3Range) {
|
||||
int replaceValue = 1;
|
||||
return imaqColorThreshold(dest, source, replaceValue, mode, plane1Range,
|
||||
plane2Range, plane3Range);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Applies a threshold to the Red, Green, and Blue values of a RGB image
|
||||
* or the Hue, Saturation, Luminance values for a HSL image.
|
||||
* Supports IMAQ_IMAGE_RGB, IMAQ_IMAGE_HSL.
|
||||
* The simpler version filters based on a hue range in the HSL mode.
|
||||
*
|
||||
* @param dest The destination image. This must be a IMAQ_IMAGE_U8
|
||||
* image.
|
||||
* @param source The image to threshold
|
||||
* @param replaceValue Value to assign to selected pixels. Defaults to 1 if
|
||||
* simplified call is used.
|
||||
* @param mode The color space to perform the threshold in. valid values
|
||||
* are: IMAQ_RGB, IMAQ_HSL.
|
||||
* @param plane1Range The selection range for the first plane of the image.
|
||||
* Set this parameter to nullptr to use a selection range
|
||||
* from 0 to 255.
|
||||
* @param plane2Range The selection range for the second plane of the image.
|
||||
* Set this parameter to nullptr to use a selection range
|
||||
* from 0 to 255.
|
||||
* @param plane3Range The selection range for the third plane of the image.
|
||||
* Set this parameter to nullptr to use a selection range
|
||||
* from 0 to 255.
|
||||
*
|
||||
* @return On success: 1. On failure: 0. To get extended error information, call
|
||||
* GetLastError().
|
||||
*/
|
||||
int frcColorThreshold(Image* dest, const Image* source, int replaceValue,
|
||||
ColorMode mode, const Range* plane1Range,
|
||||
const Range* plane2Range, const Range* plane3Range) {
|
||||
return imaqColorThreshold(dest, source, replaceValue, mode, plane1Range,
|
||||
plane2Range, plane3Range);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief A simpler version of ColorThreshold that thresholds hue range in the
|
||||
* HSL mode. Supports IMAQ_IMAGE_RGB, IMAQ_IMAGE_HSL.
|
||||
* @param dest The destination image.
|
||||
* @param source The image to threshold
|
||||
* @param hueRange The selection range for the hue (color).
|
||||
* @param minSaturation The minimum saturation value (1-255). If not used,
|
||||
* DEFAULT_SATURATION_THRESHOLD is the default.
|
||||
*
|
||||
* @return On success: 1. On failure: 0. To get extended error information, call
|
||||
* GetLastError().
|
||||
*/
|
||||
int frcHueThreshold(Image* dest, const Image* source, const Range* hueRange) {
|
||||
return frcHueThreshold(dest, source, hueRange, DEFAULT_SATURATION_THRESHOLD);
|
||||
}
|
||||
|
||||
int frcHueThreshold(Image* dest, const Image* source, const Range* hueRange,
|
||||
int minSaturation) {
|
||||
// assume HSL mode
|
||||
ColorMode mode = IMAQ_HSL;
|
||||
// Set saturation 100 - 255
|
||||
Range satRange;
|
||||
satRange.minValue = minSaturation;
|
||||
satRange.maxValue = 255;
|
||||
// Set luminance 100 - 255
|
||||
Range lumRange;
|
||||
lumRange.minValue = 100;
|
||||
lumRange.maxValue = 255;
|
||||
// Replace pixels with 1 if pass threshold filter
|
||||
int replaceValue = 1;
|
||||
return imaqColorThreshold(dest, source, replaceValue, mode, hueRange,
|
||||
&satRange, &lumRange);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Extracts the Red, Green, Blue, or Hue, Saturation or Luminance
|
||||
* information from a color image.
|
||||
* Supports IMAQ_IMAGE_RGB, IMAQ_IMAGE_HSL, IMAQ_IMAGE_RGB_U64.
|
||||
*
|
||||
* @param image The source image that the function extracts the planes from.
|
||||
* @param mode The color space that the function extracts the planes from.
|
||||
* Valid values are IMAQ_RGB, IMAQ_HSL, IMAQ_HSV, IMAQ_HSI.
|
||||
* @param plane1 On return, the first extracted plane. Set this parameter to
|
||||
* nullptr if you do not need this information. RGB-Red,
|
||||
* HSL/HSV/HSI-Hue.
|
||||
* @param plane2 On return, the second extracted plane. Set this parameter to
|
||||
* nullptr if you do not need this information. RGB-Green,
|
||||
* HSL/HSV/HSI-Saturation.
|
||||
* @param plane3 On return, the third extracted plane. Set this parameter to
|
||||
* nullptr if you do not need this information. RGB-Blue,
|
||||
* HSL-Luminance, HSV-Value, HSI-Intensity.
|
||||
*
|
||||
* @return error code: 0 = error. To get extended error information, call
|
||||
* GetLastError().
|
||||
*/
|
||||
int frcExtractColorPlanes(const Image* image, ColorMode mode, Image* plane1,
|
||||
Image* plane2, Image* plane3) {
|
||||
return imaqExtractColorPlanes(image, mode, plane1, plane2, plane3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Extracts the Hue information from a color image. Supports
|
||||
* IMAQ_IMAGE_RGB, IMAQ_IMAGE_HSL, IMAQ_IMAGE_RGB_U64
|
||||
*
|
||||
* @param image The source image that the function extracts the plane
|
||||
* from.
|
||||
* @param huePlane On return, the extracted hue plane.
|
||||
* @param minSaturation the minimum saturation level required 0-255 (try 50)
|
||||
*
|
||||
* @return On success: 1. On failure: 0. To get extended error information, call
|
||||
* GetLastError().
|
||||
*/
|
||||
int frcExtractHuePlane(const Image* image, Image* huePlane) {
|
||||
return frcExtractHuePlane(image, huePlane, DEFAULT_SATURATION_THRESHOLD);
|
||||
}
|
||||
|
||||
int frcExtractHuePlane(const Image* image, Image* huePlane, int minSaturation) {
|
||||
return frcExtractColorPlanes(image, IMAQ_HSL, huePlane, nullptr, nullptr);
|
||||
}
|
||||
Reference in New Issue
Block a user