mirror of
https://github.com/hairu/freelss.git
synced 2026-02-20 02:41:21 +01:00
Added the ability to choose output types and the ability to show what points came from the left laser versus the right
This commit is contained in:
159
config/freelss
Executable file
159
config/freelss
Executable file
@@ -0,0 +1,159 @@
|
||||
#! /bin/sh
|
||||
### BEGIN INIT INFO
|
||||
# Provides: freelss
|
||||
# Required-Start: $remote_fs $syslog $named $network $time
|
||||
# Required-Stop: $remote_fs $syslog $named $network $time
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Example initscript
|
||||
# Description: This file should be used to construct scripts to be
|
||||
# placed in /etc/init.d.
|
||||
### END INIT INFO
|
||||
|
||||
# Author: Foo Bar <foobar@baz.org>
|
||||
#
|
||||
# Please remove the "Author" lines above and replace them
|
||||
# with your own name if you copy and modify this script.
|
||||
|
||||
# Do NOT "set -e"
|
||||
|
||||
# PATH should only include /usr/* if it runs after the mountnfs.sh script
|
||||
PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin
|
||||
DESC="ATLAS 3D"
|
||||
NAME=freelss
|
||||
DAEMON=/usr/local/bin/$NAME
|
||||
DAEMON_ARGS="--options args"
|
||||
PIDFILE=/var/run/$NAME.pid
|
||||
SCRIPTNAME=/etc/init.d/$NAME
|
||||
|
||||
# Exit if the package is not installed
|
||||
[ -x "$DAEMON" ] || exit 0
|
||||
|
||||
# Read configuration variable file if it is present
|
||||
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
|
||||
|
||||
# Load the VERBOSE setting and other rcS variables
|
||||
. /lib/init/vars.sh
|
||||
|
||||
# Define LSB log_* functions.
|
||||
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
|
||||
# and status_of_proc is working.
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
#
|
||||
# Function that starts the daemon/service
|
||||
#
|
||||
do_start()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been started
|
||||
# 1 if daemon was already running
|
||||
# 2 if daemon could not be started
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
|
||||
|| return 1
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
|
||||
$DAEMON_ARGS \
|
||||
|| return 2
|
||||
# Add code here, if necessary, that waits for the process to be ready
|
||||
# to handle requests from services started subsequently which depend
|
||||
# on this one. As a last resort, sleep for some time.
|
||||
}
|
||||
|
||||
#
|
||||
# Function that stops the daemon/service
|
||||
#
|
||||
do_stop()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been stopped
|
||||
# 1 if daemon was already stopped
|
||||
# 2 if daemon could not be stopped
|
||||
# other if a failure occurred
|
||||
start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
|
||||
RETVAL="$?"
|
||||
[ "$RETVAL" = 2 ] && return 2
|
||||
# Wait for children to finish too if this is a daemon that forks
|
||||
# and if the daemon is only ever run from this initscript.
|
||||
# If the above conditions are not satisfied then add some other code
|
||||
# that waits for the process to drop all resources that could be
|
||||
# needed by services started subsequently. A last resort is to
|
||||
# sleep for some time.
|
||||
start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
|
||||
[ "$?" = 2 ] && return 2
|
||||
# Many daemons don't delete their pidfiles when they exit.
|
||||
rm -f $PIDFILE
|
||||
return "$RETVAL"
|
||||
}
|
||||
|
||||
#
|
||||
# Function that sends a SIGHUP to the daemon/service
|
||||
#
|
||||
do_reload() {
|
||||
#
|
||||
# If the daemon can reload its configuration without
|
||||
# restarting (for example, when it is sent a SIGHUP),
|
||||
# then implement that here.
|
||||
#
|
||||
start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
|
||||
return 0
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
|
||||
do_start
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
stop)
|
||||
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
status)
|
||||
status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
|
||||
;;
|
||||
#reload|force-reload)
|
||||
#
|
||||
# If do_reload() is not implemented then leave this commented out
|
||||
# and leave 'force-reload' as an alias for 'restart'.
|
||||
#
|
||||
#log_daemon_msg "Reloading $DESC" "$NAME"
|
||||
#do_reload
|
||||
#log_end_msg $?
|
||||
#;;
|
||||
restart|force-reload)
|
||||
#
|
||||
# If the "reload" option is implemented then remove the
|
||||
# 'force-reload' alias
|
||||
#
|
||||
log_daemon_msg "Restarting $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1)
|
||||
do_start
|
||||
case "$?" in
|
||||
0) log_end_msg 0 ;;
|
||||
1) log_end_msg 1 ;; # Old process is still running
|
||||
*) log_end_msg 1 ;; # Failed to start
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
# Failed to stop
|
||||
log_end_msg 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
#echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
|
||||
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
|
||||
exit 3
|
||||
;;
|
||||
esac
|
||||
|
||||
:
|
||||
@@ -85,8 +85,7 @@ bool Calibrator::detectLaserX(real * laserX, PixelLocation& topLocation, PixelLo
|
||||
bool detected = false;
|
||||
ImageProcessor imageProcessor;
|
||||
int firstRowLaserCol = 0;
|
||||
int numSuspectedBadLaserLocations = 0;
|
||||
int numImageProcessingRetries = 0;
|
||||
real percentPixelsOverThreshold = 0;
|
||||
int numLocations = 0;
|
||||
int maxNumLocations = camera->getImageHeight();
|
||||
PixelLocation * laserLocations = new PixelLocation[maxNumLocations];
|
||||
@@ -99,8 +98,7 @@ bool Calibrator::detectLaserX(real * laserX, PixelLocation& topLocation, PixelLo
|
||||
laserLocations,
|
||||
maxNumLocations,
|
||||
firstRowLaserCol,
|
||||
numSuspectedBadLaserLocations,
|
||||
numImageProcessingRetries,
|
||||
percentPixelsOverThreshold,
|
||||
NULL);
|
||||
|
||||
//
|
||||
|
||||
@@ -230,6 +230,20 @@ static void SavePreset(RequestInfo * reqInfo)
|
||||
preset->framesPerRevolution = ToInt(framesPerRevolution.c_str());
|
||||
}
|
||||
|
||||
preset->generatePly = !reqInfo->arguments[WebContent::GENERATE_PLY].empty();
|
||||
preset->generateStl = !reqInfo->arguments[WebContent::GENERATE_STL].empty();
|
||||
preset->generateXyz = !reqInfo->arguments[WebContent::GENERATE_XYZ].empty();
|
||||
|
||||
if (reqInfo->arguments[WebContent::SEPARATE_LASERS_BY_COLOR].empty())
|
||||
{
|
||||
preset->laserMergeAction = Preset::LMA_PREFER_RIGHT_LASER;
|
||||
}
|
||||
else
|
||||
{
|
||||
preset->laserMergeAction = Preset::LMA_SEPARATE_BY_COLOR;
|
||||
preset->generatePly = true;
|
||||
}
|
||||
|
||||
/** Save the properties */
|
||||
SaveProperties();
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ namespace freelss
|
||||
|
||||
const real ImageProcessor::RED_HUE_LOWER_THRESHOLD = 30;
|
||||
const real ImageProcessor::RED_HUE_UPPER_THRESHOLD = 329;
|
||||
const int ImageProcessor::NUM_LASER_RANGE_THRESHOLD = 3;
|
||||
const unsigned ImageProcessor::RANGE_DISTANCE_THRESHOLD = 5;
|
||||
|
||||
struct GaussPrivate
|
||||
@@ -116,22 +115,7 @@ ImageProcessor::~ImageProcessor()
|
||||
}
|
||||
|
||||
int ImageProcessor::process(const Image& before, const Image& after, Image * debuggingImage, PixelLocation * laserLocations,
|
||||
int maxNumLocations, int& firstRowLaserCol, int & numSuspectedBadLaserLocations, int & numImageProcessingRetries, const char * debuggingCsvFile)
|
||||
{
|
||||
int numBad = 0;
|
||||
int numLocations = 0;
|
||||
|
||||
numLocations = subProcess(before, after, debuggingImage, laserLocations, maxNumLocations, m_laserMagnitudeThreshold, firstRowLaserCol, numBad, debuggingCsvFile);
|
||||
|
||||
numSuspectedBadLaserLocations += numBad;
|
||||
|
||||
return numLocations;
|
||||
}
|
||||
|
||||
// We want to detect a red, white, to red transition in the image
|
||||
int ImageProcessor::subProcess(const Image& before, const Image& after, Image * debuggingImage, PixelLocation * laserLocations,
|
||||
int maxNumLocations, real laserThreshold, int& firstRowLaserCol, int & numSuspectedBadLaserLocations,
|
||||
const char * debuggingCsvFile)
|
||||
int maxNumLocations, int& firstRowLaserCol, real & percentPixelsOverThreshold, const char * debuggingCsvFile)
|
||||
{
|
||||
const real MAX_MAGNITUDE_SQ = 255 * 255 * 3; // The maximum pixel magnitude sq we can see
|
||||
const real INV_MAX_MAGNITUDE_SQ = 1.0f / MAX_MAGNITUDE_SQ;
|
||||
@@ -153,14 +137,15 @@ int ImageProcessor::subProcess(const Image& before, const Image& after, Image *
|
||||
rowOut.open(debuggingCsvFile, std::ios::out);
|
||||
}
|
||||
|
||||
unsigned width = before.getWidth();
|
||||
unsigned height = before.getHeight();
|
||||
const unsigned width = before.getWidth();
|
||||
const unsigned height = before.getHeight();
|
||||
unsigned components = before.getNumComponents();
|
||||
unsigned rowStep = width * components;
|
||||
|
||||
int numLocations = 0;
|
||||
|
||||
int numMerged = 0;
|
||||
int numPixelsOverThreshold = 0;
|
||||
|
||||
// The location that we last detected a laser line
|
||||
int prevLaserCol = firstRowLaserCol;
|
||||
@@ -180,7 +165,7 @@ int ImageProcessor::subProcess(const Image& before, const Image& after, Image *
|
||||
for (unsigned iCol = 0; iCol < rowStep; iCol += components)
|
||||
{
|
||||
// Perform image subtraction
|
||||
#if 1
|
||||
#if 0
|
||||
const int r = (int)br[iCol + 0] - (int)ar[iCol + 0];
|
||||
const int magSq = r * r;
|
||||
real mag = 255.0f * (magSq * 0.000015379f);
|
||||
@@ -193,7 +178,7 @@ int ImageProcessor::subProcess(const Image& before, const Image& after, Image *
|
||||
#endif
|
||||
if (writeDebugImage)
|
||||
{
|
||||
if (mag > laserThreshold)
|
||||
if (mag > m_laserMagnitudeThreshold)
|
||||
{
|
||||
dr[iCol + 0] = mag;
|
||||
dr[iCol + 1] = mag;
|
||||
@@ -208,8 +193,11 @@ int ImageProcessor::subProcess(const Image& before, const Image& after, Image *
|
||||
}
|
||||
|
||||
// Compare it against the threshold
|
||||
if (mag > laserThreshold)
|
||||
if (mag > m_laserMagnitudeThreshold)
|
||||
{
|
||||
// Flag that this pixel was over the threshold value
|
||||
numPixelsOverThreshold++;
|
||||
|
||||
// The start of pixels with laser in them
|
||||
if (m_laserRanges[numLaserRanges].startCol == -1)
|
||||
{
|
||||
@@ -292,11 +280,6 @@ int ImageProcessor::subProcess(const Image& before, const Image& after, Image *
|
||||
|
||||
numLocations++;
|
||||
|
||||
// Detect if we think this may have been a bad detection
|
||||
if (numLaserRanges > NUM_LASER_RANGE_THRESHOLD)
|
||||
{
|
||||
numSuspectedBadLaserLocations++;
|
||||
}
|
||||
}
|
||||
|
||||
// Increment to the next row
|
||||
@@ -319,6 +302,9 @@ int ImageProcessor::subProcess(const Image& before, const Image& after, Image *
|
||||
rowOut.close();
|
||||
}
|
||||
|
||||
// Compute the number of pixels over the threshold
|
||||
percentPixelsOverThreshold = 100.0f * numPixelsOverThreshold / (width * height);
|
||||
|
||||
return numLocations;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,11 +38,11 @@ public:
|
||||
* This can be helpful when debugging laser detection issues.
|
||||
* @param laserLocations - Output variable to store the laser locations.
|
||||
* @param maxNumLocations - The maximum number of locations to store in @p laserLocations.
|
||||
* @param thresholdFactor - Scales the laser threshold by this amount. default = 1.0
|
||||
* @param percentPixelsOverThreshold - The percentage of pixels that were over the threshold amount.
|
||||
* @return Returns the number of locations written to @p laserLocations.
|
||||
*/
|
||||
int process(const Image& before, const Image& after, Image * debuggingImage, PixelLocation * laserLocations, int maxNumLocations,
|
||||
int& firstRowLaserCol, int & numSuspectedBadLaserLocations, int & numImageProcessingRetries, const char * debuggingCsvFile);
|
||||
int& firstRowLaserCol, real & percentPixelsOverThreshold, const char * debuggingCsvFile);
|
||||
|
||||
private:
|
||||
|
||||
@@ -58,14 +58,10 @@ private:
|
||||
|
||||
real detectLaserRangeCenter(const ImageProcessor::LaserRange& range, unsigned char * ar, unsigned char * br);
|
||||
|
||||
int subProcess(const Image& before, const Image& after, Image * debuggingImage, PixelLocation * laserLocations,
|
||||
int maxNumLocations, real laserThreshold, int& firstRowLaserCol, int & numSuspectedBadLaserLocations, const char * debuggingCsvFile);
|
||||
|
||||
/** Converts the RGB color to HSV */
|
||||
static void toHsv(unsigned char r, unsigned char g, unsigned char b, Hsv * hsv);
|
||||
static const real RED_HUE_LOWER_THRESHOLD;
|
||||
static const real RED_HUE_UPPER_THRESHOLD;
|
||||
static const int NUM_LASER_RANGE_THRESHOLD;
|
||||
static const unsigned RANGE_DISTANCE_THRESHOLD;
|
||||
|
||||
/** The LaserRanges for each column */
|
||||
|
||||
@@ -51,8 +51,15 @@ void LaserResultsMerger::merge(std::vector<NeutralFileRecord> & out,
|
||||
std::vector<NeutralFileRecord> & rightLaserResults,
|
||||
int numFramesPerRevolution,
|
||||
int numFramesBetweenLaserPlanes,
|
||||
int maxPointY)
|
||||
int maxPointY,
|
||||
Preset::LaserMergeAction mergeAction)
|
||||
{
|
||||
// Sanity check
|
||||
if (mergeAction != Preset::LMA_PREFER_RIGHT_LASER && mergeAction != Preset::LMA_SEPARATE_BY_COLOR)
|
||||
{
|
||||
throw Exception("Unsupported Laser Merge Action");
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
if (leftLaserResults.empty() && rightLaserResults.empty())
|
||||
{
|
||||
@@ -92,9 +99,14 @@ void LaserResultsMerger::merge(std::vector<NeutralFileRecord> & out,
|
||||
{
|
||||
NeutralFileRecord& right = rightLaserResults[iRight];
|
||||
right.pseudoFrame = right.frame;
|
||||
//right.point.r = 0;
|
||||
//right.point.g = 0;
|
||||
//right.point.b = 0;
|
||||
|
||||
// Make the Right laser black
|
||||
if (mergeAction == Preset::LMA_SEPARATE_BY_COLOR)
|
||||
{
|
||||
right.point.r = 0;
|
||||
right.point.g = 0;
|
||||
right.point.b = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t iLeft = 0; iLeft < leftLaserResults.size(); iLeft++)
|
||||
@@ -110,9 +122,13 @@ void LaserResultsMerger::merge(std::vector<NeutralFileRecord> & out,
|
||||
left.pseudoFrame -= m_numFramesPerRevolution;
|
||||
}
|
||||
|
||||
//left.point.r = 255;
|
||||
//left.point.g = 0;
|
||||
//left.point.b = 0;
|
||||
// Make the Left laser red
|
||||
if (mergeAction == Preset::LMA_SEPARATE_BY_COLOR)
|
||||
{
|
||||
left.point.r = 255;
|
||||
left.point.g = 0;
|
||||
left.point.b = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int numCulledPoints = 0;
|
||||
@@ -141,12 +157,15 @@ void LaserResultsMerger::merge(std::vector<NeutralFileRecord> & out,
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
left.point.r = 175;
|
||||
left.point.g = 175;
|
||||
left.point.b = 175;
|
||||
out.push_back(left);
|
||||
*/
|
||||
// Make the culled left laser results gray
|
||||
if (mergeAction == Preset::LMA_SEPARATE_BY_COLOR)
|
||||
{
|
||||
left.point.r = 175;
|
||||
left.point.g = 175;
|
||||
left.point.b = 175;
|
||||
out.push_back(left);
|
||||
}
|
||||
|
||||
numCulledPoints++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Preset.h"
|
||||
|
||||
namespace freelss
|
||||
{
|
||||
|
||||
@@ -39,7 +41,8 @@ public:
|
||||
std::vector<NeutralFileRecord> & rightLaserResults,
|
||||
int numFramesPerRevolution,
|
||||
int numFramesBetweenLaserPlanes,
|
||||
int maxPointY);
|
||||
int maxPointY,
|
||||
Preset::LaserMergeAction mergeAction);
|
||||
|
||||
private:
|
||||
|
||||
|
||||
148
src/Main.cpp
148
src/Main.cpp
@@ -28,6 +28,12 @@
|
||||
#include "PresetManager.h"
|
||||
#include "PropertyReaderWriter.h"
|
||||
#include "Setup.h"
|
||||
#include <algorithm>
|
||||
|
||||
static bool SortRecordByRow(const freelss::NeutralFileRecord& a, const freelss::NeutralFileRecord& b)
|
||||
{
|
||||
return a.pixel.y < b.pixel.y;
|
||||
}
|
||||
|
||||
void ReleaseSingletons()
|
||||
{
|
||||
@@ -53,6 +59,17 @@ int main(int argc, char **argv)
|
||||
{
|
||||
int retVal = 0;
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid < 0)
|
||||
{
|
||||
std::cerr << "Error forking process!!!" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
else if (pid != 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Initialize the Raspberry Pi hardware
|
||||
bcm_host_init();
|
||||
|
||||
@@ -74,10 +91,9 @@ int main(int argc, char **argv)
|
||||
|
||||
while (true)
|
||||
{
|
||||
freelss::Thread::usleep(100000);
|
||||
freelss::Thread::usleep(500000); // Sleep 500ms
|
||||
}
|
||||
|
||||
|
||||
ReleaseSingletons();
|
||||
}
|
||||
catch (freelss::Exception& ex)
|
||||
@@ -120,6 +136,124 @@ time_t ScanResult::getScanDate() const
|
||||
return files.front().creationTime;
|
||||
}
|
||||
|
||||
bool NeutralFileRecord::readNextFrame(std::vector<NeutralFileRecord>& out, const std::vector<NeutralFileRecord>& results, size_t & resultIndex)
|
||||
{
|
||||
out.clear();
|
||||
|
||||
if (resultIndex >= results.size() || results.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int pseudoStep = results[resultIndex].pseudoFrame;
|
||||
while (pseudoStep == results[resultIndex].pseudoFrame && resultIndex < results.size())
|
||||
{
|
||||
out.push_back(results[resultIndex]);
|
||||
resultIndex++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void NeutralFileRecord::computeAverage(const std::vector<NeutralFileRecord>& bin, NeutralFileRecord& out)
|
||||
{
|
||||
out = bin.front();
|
||||
|
||||
real32 invSize = 1.0f / bin.size();
|
||||
|
||||
real32 rotation = 0;
|
||||
real32 pixelLocationX = 0;
|
||||
real32 pixelLocationY = 0;
|
||||
real32 ptX = 0;
|
||||
real32 ptY = 0;
|
||||
real32 ptZ = 0;
|
||||
real32 ptR = 0;
|
||||
real32 ptG = 0;
|
||||
real32 ptB = 0;
|
||||
|
||||
for (size_t iBin = 0; iBin < bin.size(); iBin++)
|
||||
{
|
||||
const NeutralFileRecord& br = bin[iBin];
|
||||
|
||||
rotation += invSize * br.rotation;
|
||||
pixelLocationX += invSize * br.pixel.x;
|
||||
pixelLocationY += invSize * br.pixel.y;
|
||||
ptX += invSize * br.point.x;
|
||||
ptY += invSize * br.point.y;
|
||||
ptZ += invSize * br.point.z;
|
||||
ptR += invSize * br.point.r;
|
||||
ptG += invSize * br.point.g;
|
||||
ptB += invSize * br.point.b;
|
||||
}
|
||||
|
||||
out.rotation = rotation;
|
||||
out.pixel.x = pixelLocationX; // TODO: We should probably round these values
|
||||
out.pixel.y = pixelLocationY;
|
||||
out.point.x = ptX;
|
||||
out.point.y = ptY;
|
||||
out.point.z = ptZ;
|
||||
out.point.r = ptR;
|
||||
out.point.g = ptG;
|
||||
out.point.b = ptB;
|
||||
}
|
||||
|
||||
void NeutralFileRecord::lowpassFilter(std::vector<NeutralFileRecord>& output, std::vector<NeutralFileRecord>& frame, unsigned maxNumRows, unsigned numRowBins)
|
||||
{
|
||||
output.clear();
|
||||
|
||||
// Sanity check
|
||||
if (frame.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort by image row
|
||||
std::sort(frame.begin(), frame.end(), SortRecordByRow);
|
||||
|
||||
unsigned binSize = maxNumRows / numRowBins;
|
||||
|
||||
// Holds the current contents of the bin
|
||||
std::vector<NeutralFileRecord> bin;
|
||||
unsigned nextBinY = frame.front().pixel.y + binSize;
|
||||
|
||||
// unsigned bin = frame.front().pixel.y / numRowBins;
|
||||
for (size_t iFr = 0; iFr < frame.size(); iFr++)
|
||||
{
|
||||
NeutralFileRecord& record = frame[iFr];
|
||||
|
||||
if (record.pixel.y < nextBinY)
|
||||
{
|
||||
bin.push_back(record);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Average the bin results and add it to the output
|
||||
if (!bin.empty())
|
||||
{
|
||||
NeutralFileRecord out;
|
||||
computeAverage(bin, out);
|
||||
|
||||
output.push_back(out);
|
||||
bin.clear();
|
||||
}
|
||||
|
||||
nextBinY = record.pixel.y + binSize;
|
||||
bin.push_back(record);
|
||||
}
|
||||
}
|
||||
|
||||
// Process any results still left in the bin
|
||||
if (!bin.empty())
|
||||
{
|
||||
NeutralFileRecord out;
|
||||
computeAverage(bin, out);
|
||||
|
||||
output.push_back(out);
|
||||
bin.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void LoadProperties()
|
||||
{
|
||||
PropertyReaderWriter reader;
|
||||
@@ -196,6 +330,11 @@ std::string ToString(int value)
|
||||
return sstr.str();
|
||||
}
|
||||
|
||||
std::string ToString(bool value)
|
||||
{
|
||||
return value ? "1" : "0";
|
||||
}
|
||||
|
||||
real ToReal(const std::string& str)
|
||||
{
|
||||
return atof(str.c_str());
|
||||
@@ -206,6 +345,11 @@ int ToInt(const std::string& str)
|
||||
return atoi(str.c_str());
|
||||
}
|
||||
|
||||
bool ToBool(const std::string& str)
|
||||
{
|
||||
return str == "1" || str == "true";
|
||||
}
|
||||
|
||||
bool EndsWith(const std::string& str, const std::string& ending)
|
||||
{
|
||||
if (str.length() >= ending.length())
|
||||
|
||||
17
src/Main.h
17
src/Main.h
@@ -206,6 +206,21 @@ struct Ray
|
||||
|
||||
struct NeutralFileRecord
|
||||
{
|
||||
/** Populates frameC with the next frame from the results vector starting at index resultIndex */
|
||||
static bool readNextFrame(std::vector<NeutralFileRecord>& out, const std::vector<NeutralFileRecord>& results, size_t & resultIndex);
|
||||
|
||||
/**
|
||||
* Reduce the number of result rows and filter out some of the noise
|
||||
* @param maxNumRows - The number of rows in the image the produced the frame.
|
||||
* @param numRowBins - The total number of row bins in the entire image, not necessarily what is returned by this function.
|
||||
*/
|
||||
static void lowpassFilter(std::vector<NeutralFileRecord>& output, std::vector<NeutralFileRecord>& frame, unsigned maxNumRows, unsigned numRowBins);
|
||||
|
||||
/**
|
||||
* Computes the average of all the records in the bin.
|
||||
*/
|
||||
static void computeAverage(const std::vector<NeutralFileRecord>& bin, NeutralFileRecord& out);
|
||||
|
||||
PixelLocation pixel;
|
||||
ColoredPoint point;
|
||||
real rotation;
|
||||
@@ -232,8 +247,10 @@ double GetTimeInSeconds();
|
||||
|
||||
std::string ToString(real value);
|
||||
std::string ToString(int value);
|
||||
std::string ToString(bool value);
|
||||
real ToReal(const std::string& str);
|
||||
int ToInt(const std::string& str);
|
||||
bool ToBool(const std::string& str);
|
||||
bool EndsWith(const std::string& str, const std::string& ending);
|
||||
void HtmlEncode(std::string& data);
|
||||
void LoadProperties();
|
||||
|
||||
15
src/Makefile
15
src/Makefile
@@ -13,7 +13,8 @@ OBJECTS=WebContent.o PropertyReaderWriter.o Main.o \
|
||||
NeutralFileWriter.o NeutralFileReader.o RaspicamCamera.o \
|
||||
Camera.o ScanResultsWriter.o MmalStillCamera.o \
|
||||
MmalVideoCamera.o PixelLocationWriter.o StlWriter.o TurnTable.o \
|
||||
Laser.o Preset.o PresetManager.o Setup.o LaserResultsMerger.o
|
||||
Laser.o Preset.o PresetManager.o Setup.o LaserResultsMerger.o\
|
||||
XyzWriter.o
|
||||
|
||||
all: freelss
|
||||
|
||||
@@ -113,10 +114,15 @@ Setup.o: Setup.cpp Setup.h Main.h
|
||||
LaserResultsMerger.o: LaserResultsMerger.cpp LaserResultsMerger.h Main.h
|
||||
$(CC) -c $(CFLAGS) LaserResultsMerger.cpp
|
||||
|
||||
XyzWriter.o: XyzWriter.cpp XyzWriter.h Main.h
|
||||
$(CC) -c $(CFLAGS) XyzWriter.cpp
|
||||
|
||||
github:
|
||||
mkdir -p ../../github
|
||||
mkdir -p ../../github/src
|
||||
cp -f *.h *.cpp Makefile ../../github/src
|
||||
mkdir -p ../../github/config
|
||||
cp ../config/freelss ../../github/config/
|
||||
cp -f *.h *.cpp Makefile ../../github/src/
|
||||
|
||||
run: freelss
|
||||
sudo ./freelss
|
||||
@@ -127,5 +133,10 @@ dist: clean
|
||||
install: freelss
|
||||
sudo cp -f freelss /usr/local/bin/freelss
|
||||
|
||||
startup: install
|
||||
sudo cp ../config/freelss /etc/init.d/freelss
|
||||
sudo update-rc.d freelss remove
|
||||
sudo update-rc.d freelss defaults
|
||||
|
||||
clean:
|
||||
rm -f *.o *.gch *~ freelss
|
||||
|
||||
@@ -29,7 +29,7 @@ Preset::Preset() :
|
||||
name("Preset"),
|
||||
laserSide(Laser::RIGHT_LASER),
|
||||
cameraMode(Camera::CM_VIDEO_1P2MP),
|
||||
laserThreshold(3),
|
||||
laserThreshold(10),
|
||||
minLaserWidth(3),
|
||||
maxLaserWidth(40),
|
||||
maxObjectSize(215.9), // 8.5"
|
||||
@@ -38,7 +38,11 @@ Preset::Preset() :
|
||||
laserDelay(180000),
|
||||
stabilityDelay(0),
|
||||
id(-1),
|
||||
framesPerRevolution(800)
|
||||
framesPerRevolution(800),
|
||||
generateXyz (false),
|
||||
generateStl(true),
|
||||
generatePly(true),
|
||||
laserMergeAction (LMA_PREFER_RIGHT_LASER)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
@@ -57,6 +61,10 @@ void Preset::encodeProperties(std::vector<Property>& properties, bool isActivePr
|
||||
properties.push_back(Property("presets." + name + ".laserDelay", ToString(laserDelay)));
|
||||
properties.push_back(Property("presets." + name + ".stabilityDelay", ToString(stabilityDelay)));
|
||||
properties.push_back(Property("presets." + name + ".framesPerRevolution", ToString(framesPerRevolution)));
|
||||
properties.push_back(Property("presets." + name + ".generateXyz", ToString(generateXyz)));
|
||||
properties.push_back(Property("presets." + name + ".generateStl", ToString(generateStl)));
|
||||
properties.push_back(Property("presets." + name + ".generatePly", ToString(generatePly)));
|
||||
properties.push_back(Property("presets." + name + ".laserMergeAction", ToString((int)laserMergeAction)));
|
||||
|
||||
if (isActivePreset)
|
||||
{
|
||||
@@ -124,6 +132,22 @@ void Preset::decodeProperties(const std::vector<Property>& properties, const std
|
||||
{
|
||||
framesPerRevolution = ToInt(prop.value);
|
||||
}
|
||||
else if (EndsWith(prop.name, name + ".generateXyz"))
|
||||
{
|
||||
generateXyz = ToBool(prop.value);
|
||||
}
|
||||
else if (EndsWith(prop.name, name + ".generateStl"))
|
||||
{
|
||||
generateStl = ToBool(prop.value);
|
||||
}
|
||||
else if (EndsWith(prop.name, name + ".generatePly"))
|
||||
{
|
||||
generatePly = ToBool(prop.value);
|
||||
}
|
||||
else if (EndsWith(prop.name, name + ".laserMergeAction"))
|
||||
{
|
||||
laserMergeAction = (LaserMergeAction)ToInt(prop.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,10 @@ namespace freelss
|
||||
class Preset
|
||||
{
|
||||
public:
|
||||
|
||||
/** The action that should be taken to merge laser results */
|
||||
enum LaserMergeAction {LMA_PREFER_RIGHT_LASER, LMA_SEPARATE_BY_COLOR };
|
||||
|
||||
Preset();
|
||||
|
||||
/** Encodes property information to the properties vector */
|
||||
@@ -59,6 +63,10 @@ public:
|
||||
int stabilityDelay;
|
||||
int id;
|
||||
int framesPerRevolution;
|
||||
bool generateXyz;
|
||||
bool generateStl;
|
||||
bool generatePly;
|
||||
LaserMergeAction laserMergeAction;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
209
src/Scanner.cpp
209
src/Scanner.cpp
@@ -29,6 +29,7 @@
|
||||
#include "PixelLocationWriter.h"
|
||||
#include "NeutralFileReader.h"
|
||||
#include "StlWriter.h"
|
||||
#include "XyzWriter.h"
|
||||
#include "LaserResultsMerger.h"
|
||||
|
||||
namespace freelss
|
||||
@@ -63,9 +64,8 @@ Scanner::Scanner() :
|
||||
m_filename(""),
|
||||
m_progress(0.0),
|
||||
m_status(),
|
||||
m_maxNumScanTries(3), // TODO: Place this in Database
|
||||
m_badLaserLocationThreshold(15), // TODO: Place this in Database
|
||||
m_numSuspectedBadLaserLocations(0),
|
||||
m_maxNumFrameRetries(5), // TODO: Place this in Database
|
||||
m_maxPercentPixelsOverThreshold(3), // TODO: Place this in Database
|
||||
m_columnPoints(NULL),
|
||||
m_startTimeSec(0),
|
||||
m_remainingTime(0),
|
||||
@@ -202,8 +202,6 @@ void Scanner::run()
|
||||
|
||||
m_filename = sstr.str();
|
||||
|
||||
m_numSuspectedBadLaserLocations = 0;
|
||||
|
||||
m_firstRowRightLaserCol = m_camera->getImageWidth() * 0.5;
|
||||
m_firstRowLeftLaserCol = m_camera->getImageWidth() * 0.5;
|
||||
|
||||
@@ -213,7 +211,7 @@ void Scanner::run()
|
||||
double time1 = 0;
|
||||
|
||||
Setup * setup = Setup::get();
|
||||
Preset& preset = PresetManager::get()->getActivePreset();
|
||||
Preset preset = PresetManager::get()->getActivePreset();
|
||||
|
||||
// Read the laser selection
|
||||
m_laserSelection = preset.laserSide;
|
||||
@@ -315,14 +313,6 @@ void Scanner::run()
|
||||
|
||||
numFrames = ceil(rangeRadians / frameRadians);
|
||||
|
||||
// Start the output thread
|
||||
m_scanResultsWriter.setBaseFilePath(m_filename);
|
||||
std::cout << "Starting output thread..." << std::endl;
|
||||
time1 = GetTimeInSeconds();
|
||||
m_scanResultsWriter.execute();
|
||||
timingStats.fileWritingTime += GetTimeInSeconds() - time1;
|
||||
std::cout << "Output thread started" << std::endl;
|
||||
|
||||
m_numFramesBetweenLaserPlanes = m_radiansBetweenLaserPlanes / frameRadians;
|
||||
|
||||
std::cout << "Angle between laser planes: " << RADIANS_TO_DEGREES(m_radiansBetweenLaserPlanes)
|
||||
@@ -379,7 +369,6 @@ void Scanner::run()
|
||||
}
|
||||
|
||||
m_rangeFout.close();
|
||||
std::cout << m_numSuspectedBadLaserLocations << " laser locations are suspected as being bad." << std::endl;
|
||||
|
||||
m_turnTable->setMotorEnabled(false);
|
||||
|
||||
@@ -397,7 +386,7 @@ void Scanner::run()
|
||||
std::vector<NeutralFileRecord> results;
|
||||
LaserResultsMerger merger;
|
||||
merger.merge(results, leftLaserResults, rightLaserResults, maxFramesPerRevolution,
|
||||
m_numFramesBetweenLaserPlanes, Camera::getInstance()->getImageHeight());
|
||||
m_numFramesBetweenLaserPlanes, Camera::getInstance()->getImageHeight(), preset.laserMergeAction);
|
||||
|
||||
// Sort by pseudo-step and row
|
||||
std::cout << "Sort 2... " << std::endl;
|
||||
@@ -407,23 +396,46 @@ void Scanner::run()
|
||||
std::cout << "Merged " << leftLaserResults.size() << " left laser and " << rightLaserResults.size() << " right laser results into " << results.size() << " results." << std::endl;
|
||||
std::cout << "Constructing mesh..." << std::endl;
|
||||
|
||||
// Finish writing to the output files
|
||||
std::cout << "Writing PLY file..." << std::endl;
|
||||
time1 = GetTimeInSeconds();
|
||||
for (size_t iRec = 0; iRec < results.size(); iRec++)
|
||||
timingStats.laserMergeTime += GetTimeInSeconds() - time1;
|
||||
|
||||
// Write the PLY file
|
||||
std::cout << "Starting output thread..." << std::endl;
|
||||
if (preset.generatePly)
|
||||
{
|
||||
m_scanResultsWriter.write(results[iRec]);
|
||||
std::cout << "Writing PLY file..." << std::endl;
|
||||
time1 = GetTimeInSeconds();
|
||||
|
||||
m_scanResultsWriter.setBaseFilePath(m_filename);
|
||||
m_scanResultsWriter.execute();
|
||||
|
||||
for (size_t iRec = 0; iRec < results.size(); iRec++)
|
||||
{
|
||||
m_scanResultsWriter.write(results[iRec]);
|
||||
}
|
||||
|
||||
finishWritingToOutput();
|
||||
timingStats.fileWritingTime += GetTimeInSeconds() - time1;
|
||||
}
|
||||
|
||||
finishWritingToOutput();
|
||||
timingStats.fileWritingTime += GetTimeInSeconds() - time1;
|
||||
// Generate the XYZ file
|
||||
if (preset.generateXyz)
|
||||
{
|
||||
std::cout << "Generating XYZ file..." << std::endl;
|
||||
time1 = GetTimeInSeconds();
|
||||
XyzWriter xyzWriter;
|
||||
xyzWriter.write(m_filename, results);
|
||||
timingStats.fileWritingTime += GetTimeInSeconds() - time1;
|
||||
}
|
||||
|
||||
// Mesh the contents
|
||||
std::cout << "Generating STL mesh..." << std::endl;
|
||||
time1 = GetTimeInSeconds();
|
||||
StlWriter stlWriter;
|
||||
stlWriter.write(m_filename, results, m_range > 359);
|
||||
timingStats.meshBuildTime = GetTimeInSeconds() - time1;
|
||||
// Generate the STL file
|
||||
if (preset.generateStl)
|
||||
{
|
||||
std::cout << "Generating STL mesh..." << std::endl;
|
||||
time1 = GetTimeInSeconds();
|
||||
StlWriter stlWriter;
|
||||
stlWriter.write(m_filename, results, m_range > 359);
|
||||
timingStats.meshBuildTime = GetTimeInSeconds() - time1;
|
||||
}
|
||||
|
||||
logTimingStats(timingStats);
|
||||
|
||||
@@ -468,35 +480,20 @@ void Scanner::generateDebugInfo(Laser::LaserSide laserSide)
|
||||
std::string debuggingCsv = std::string(DEBUG_OUTPUT_DIR) + "/0.csv";
|
||||
|
||||
int firstRowLaserCol = m_camera->getImageWidth() * 0.5;
|
||||
int numSuspectedBadLaserLocations = 0;
|
||||
int numImageProcessingRetries = 0;
|
||||
real percentPixelsOverThreshold = 0;
|
||||
int numLocations = m_imageProcessor.process(m_image1,
|
||||
m_image2,
|
||||
&debuggingImage,
|
||||
m_laserLocations,
|
||||
m_maxNumLocations,
|
||||
firstRowLaserCol,
|
||||
numSuspectedBadLaserLocations,
|
||||
numImageProcessingRetries,
|
||||
percentPixelsOverThreshold,
|
||||
debuggingCsv.c_str());
|
||||
|
||||
std::string baseFilename = std::string(DEBUG_OUTPUT_DIR) + "/";
|
||||
|
||||
// Write the laser off image
|
||||
//Image::writeJpeg(m_image1, baseFilename + "1.jpg");
|
||||
|
||||
// Write the laser on image
|
||||
//Image::writeJpeg(m_image2, baseFilename + "2.jpg");
|
||||
|
||||
PixelLocationWriter locWriter;
|
||||
|
||||
// Write the difference image
|
||||
//locWriter.writeImage(debuggingImage, debuggingImage.getWidth(), debuggingImage.getHeight(), baseFilename + "3.png");
|
||||
|
||||
// Write the pixel image
|
||||
//locWriter.writePixels(m_laserLocations, numLocations, m_image1.getWidth(), m_image1.getHeight(), baseFilename + "4.png");
|
||||
|
||||
// Overlay the pixels onto the debug image and write that as a new image
|
||||
PixelLocationWriter locWriter;
|
||||
Image::overlayPixels(debuggingImage, m_laserLocations, numLocations);
|
||||
locWriter.writeImage(debuggingImage, debuggingImage.getWidth(), debuggingImage.getHeight(), baseFilename + "5.png");
|
||||
|
||||
@@ -534,52 +531,80 @@ void Scanner::singleScan(std::vector<NeutralFileRecord> & leftLaserResults, std:
|
||||
// Scan with the Right laser
|
||||
if (m_laserSelection == Laser::RIGHT_LASER || m_laserSelection == Laser::ALL_LASERS)
|
||||
{
|
||||
// Turn on the right laser
|
||||
time1 = GetTimeInSeconds();
|
||||
m_laser->turnOn(Laser::RIGHT_LASER);
|
||||
timingStats->laserTime += GetTimeInSeconds() - time1;
|
||||
bool goodResult = false;
|
||||
|
||||
// Take a picture with the right laser on
|
||||
time1 = GetTimeInSeconds();
|
||||
acquireImage(&m_image2);
|
||||
timingStats->imageAcquisitionTime += GetTimeInSeconds() - time1;
|
||||
for (int iTry = 0; iTry < m_maxNumFrameRetries && ! goodResult; iTry++)
|
||||
{
|
||||
// Turn on the right laser
|
||||
time1 = GetTimeInSeconds();
|
||||
m_laser->turnOn(Laser::RIGHT_LASER);
|
||||
timingStats->laserTime += GetTimeInSeconds() - time1;
|
||||
|
||||
// Turn off the right laser
|
||||
time1 = GetTimeInSeconds();
|
||||
m_laser->turnOff(Laser::RIGHT_LASER);
|
||||
timingStats->laserTime += GetTimeInSeconds() - time1;
|
||||
// Take a picture with the right laser on
|
||||
time1 = GetTimeInSeconds();
|
||||
acquireImage(&m_image2);
|
||||
timingStats->imageAcquisitionTime += GetTimeInSeconds() - time1;
|
||||
|
||||
// Process the right laser results
|
||||
processScan(rightLaserResults, frame, rotation, rightLocMapper, Laser::RIGHT_LASER, m_firstRowRightLaserCol, timingStats);
|
||||
// Turn off the right laser
|
||||
time1 = GetTimeInSeconds();
|
||||
m_laser->turnOff(Laser::RIGHT_LASER);
|
||||
timingStats->laserTime += GetTimeInSeconds() - time1;
|
||||
|
||||
// Process the right laser results
|
||||
goodResult = processScan(rightLaserResults, frame, rotation, rightLocMapper, Laser::RIGHT_LASER, m_firstRowRightLaserCol, timingStats);
|
||||
|
||||
// If it didn't succeed, take the first image again
|
||||
if (!goodResult)
|
||||
{
|
||||
// Take a picture with the laser off
|
||||
time1 = GetTimeInSeconds();
|
||||
acquireImage(&m_image1);
|
||||
timingStats->imageAcquisitionTime += GetTimeInSeconds() - time1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scan with the Left laser
|
||||
if (m_laserSelection == Laser::LEFT_LASER || m_laserSelection == Laser::ALL_LASERS)
|
||||
{
|
||||
// Turn on the left laser
|
||||
time1 = GetTimeInSeconds();
|
||||
m_laser->turnOn(Laser::LEFT_LASER);
|
||||
timingStats->laserTime += GetTimeInSeconds() - time1;
|
||||
bool goodResult = false;
|
||||
|
||||
// Take a picture with the left laser on
|
||||
time1 = GetTimeInSeconds();
|
||||
acquireImage(&m_image2);
|
||||
timingStats->imageAcquisitionTime += GetTimeInSeconds() - time1;
|
||||
for (int iTry = 0; iTry < m_maxNumFrameRetries && ! goodResult; iTry++)
|
||||
{
|
||||
// Turn on the left laser
|
||||
time1 = GetTimeInSeconds();
|
||||
m_laser->turnOn(Laser::LEFT_LASER);
|
||||
timingStats->laserTime += GetTimeInSeconds() - time1;
|
||||
|
||||
// Turn off the left laser
|
||||
time1 = GetTimeInSeconds();
|
||||
m_laser->turnOff(Laser::LEFT_LASER);
|
||||
timingStats->laserTime += GetTimeInSeconds() - time1;
|
||||
// Take a picture with the left laser on
|
||||
time1 = GetTimeInSeconds();
|
||||
acquireImage(&m_image2);
|
||||
timingStats->imageAcquisitionTime += GetTimeInSeconds() - time1;
|
||||
|
||||
// Process the left laser results
|
||||
processScan(leftLaserResults, frame, rotation, leftLocMapper, Laser::LEFT_LASER, m_firstRowLeftLaserCol, timingStats);
|
||||
// Turn off the left laser
|
||||
time1 = GetTimeInSeconds();
|
||||
m_laser->turnOff(Laser::LEFT_LASER);
|
||||
timingStats->laserTime += GetTimeInSeconds() - time1;
|
||||
|
||||
// Process the left laser results
|
||||
goodResult = processScan(leftLaserResults, frame, rotation, leftLocMapper, Laser::LEFT_LASER, m_firstRowLeftLaserCol, timingStats);
|
||||
|
||||
// If it didn't succeed, take the first image again
|
||||
if (!goodResult)
|
||||
{
|
||||
// Take a picture with the laser off
|
||||
time1 = GetTimeInSeconds();
|
||||
acquireImage(&m_image1);
|
||||
timingStats->imageAcquisitionTime += GetTimeInSeconds() - time1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Scanner::processScan(std::vector<NeutralFileRecord> & results, int frame, float rotation, LocationMapper& locMapper, Laser::LaserSide laserSide, int & firstRowLaserCol, TimingStats * timingStats)
|
||||
bool Scanner::processScan(std::vector<NeutralFileRecord> & results, int frame, float rotation, LocationMapper& locMapper, Laser::LaserSide laserSide, int & firstRowLaserCol, TimingStats * timingStats)
|
||||
{
|
||||
int numLocationsMapped = 0;
|
||||
int numSuspectedBadLaserLocations = 0;
|
||||
real percentPixelsOverThreshold = 0;
|
||||
|
||||
// Send the pictures off for processing
|
||||
double time1 = GetTimeInSeconds();
|
||||
@@ -589,13 +614,23 @@ void Scanner::processScan(std::vector<NeutralFileRecord> & results, int frame, f
|
||||
m_laserLocations,
|
||||
m_maxNumLocations,
|
||||
firstRowLaserCol,
|
||||
numSuspectedBadLaserLocations,
|
||||
timingStats->numImageProcessingRetries,
|
||||
percentPixelsOverThreshold,
|
||||
NULL);
|
||||
|
||||
|
||||
timingStats->imageProcessingTime += GetTimeInSeconds() - time1;
|
||||
|
||||
// If we had major problems with this frame, try it again
|
||||
std::cout << "percentPixelsOverThreshold: " << percentPixelsOverThreshold << std::endl;
|
||||
if (percentPixelsOverThreshold > m_maxPercentPixelsOverThreshold)
|
||||
{
|
||||
std::cout << "!! Many bad laser locations suspected, pctOverThreshold=" << percentPixelsOverThreshold
|
||||
<< ", maxPct=" << m_maxPercentPixelsOverThreshold << ", rescanning..." << std::endl;
|
||||
|
||||
timingStats->numFrameRetries++;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::cout << "Detected " << numLocations << " laser pixels." << std::endl;
|
||||
|
||||
// Lookup the 3D locations for the laser points
|
||||
@@ -611,11 +646,6 @@ void Scanner::processScan(std::vector<NeutralFileRecord> & results, int frame, f
|
||||
{
|
||||
std::cout << "Discarded " << numLocations - numLocationsMapped << " points." << std::endl;
|
||||
}
|
||||
|
||||
if (numSuspectedBadLaserLocations > m_badLaserLocationThreshold)
|
||||
{
|
||||
std::cout << "!! Many bad laser locations suspected, num=" << numSuspectedBadLaserLocations << std::endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -628,7 +658,6 @@ void Scanner::processScan(std::vector<NeutralFileRecord> & results, int frame, f
|
||||
if (numLocationsMapped > 0)
|
||||
{
|
||||
time1 = GetTimeInSeconds();
|
||||
m_numSuspectedBadLaserLocations += numSuspectedBadLaserLocations;
|
||||
|
||||
if (m_writeRangeCsvEnabled)
|
||||
{
|
||||
@@ -654,6 +683,8 @@ void Scanner::processScan(std::vector<NeutralFileRecord> & results, int frame, f
|
||||
}
|
||||
timingStats->fileWritingTime += GetTimeInSeconds() - time1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Scanner::rotatePoints(ColoredPoint * points, float theta, int numPoints)
|
||||
@@ -739,7 +770,7 @@ void Scanner::logTimingStats(const Scanner::TimingStats& stats)
|
||||
double totalTime = now - stats.startTime;
|
||||
|
||||
double accountedTime = stats.meshBuildTime + stats.imageAcquisitionTime + stats.imageProcessingTime + stats.pointMappingTime
|
||||
+ stats.pointProcessingTime + stats.rotationTime + stats.fileWritingTime + stats.laserTime;
|
||||
+ stats.pointProcessingTime + stats.rotationTime + stats.fileWritingTime + stats.laserTime + stats.laserMergeTime;
|
||||
|
||||
double unaccountedTime = totalTime - accountedTime;
|
||||
double rate = totalTime / stats.numFrames;
|
||||
@@ -752,10 +783,10 @@ void Scanner::logTimingStats(const Scanner::TimingStats& stats)
|
||||
std::cout << "Point Mapping:\t" << (100.0 * stats.pointMappingTime / totalTime) << "%" << std::endl;
|
||||
std::cout << "Point Rotating:\t" << (100.0 * stats.pointProcessingTime / totalTime) << "%" << std::endl;
|
||||
std::cout << "Table Rotation:\t" << (100.0 * stats.rotationTime / totalTime) << "%" << std::endl;
|
||||
std::cout << "Laser Merging:\t" << (100.0 * stats.laserMergeTime / totalTime) << "%" << std::endl;
|
||||
std::cout << "File Writing:\t" << (100.0 * stats.fileWritingTime / totalTime) << "%" << std::endl;
|
||||
std::cout << "Mesh Construction:\t" << (100.0 * stats.meshBuildTime / totalTime) << "%" << std::endl;
|
||||
std::cout << "Num Scan Retries:\t" << stats.numScanRetries << std::endl;
|
||||
std::cout << "Num Image Processing Retries:\t" << stats.numImageProcessingRetries << std::endl;
|
||||
std::cout << "Num Frame Retries:\t" << stats.numFrameRetries << std::endl;
|
||||
std::cout << "Num Frames:\t" << stats.numFrames << std::endl;
|
||||
std::cout << "Total Time (min):\t" << (totalTime / 60.0) << std::endl << std::endl;
|
||||
}
|
||||
|
||||
@@ -93,8 +93,8 @@ private:
|
||||
double fileWritingTime;
|
||||
double meshBuildTime;
|
||||
double laserTime;
|
||||
int numScanRetries;
|
||||
int numImageProcessingRetries;
|
||||
double laserMergeTime;
|
||||
int numFrameRetries;
|
||||
int numFrames;
|
||||
};
|
||||
|
||||
@@ -113,12 +113,13 @@ private:
|
||||
|
||||
void finishWritingToOutput();
|
||||
|
||||
void processScan(std::vector<NeutralFileRecord> & results, int frame, float rotation, LocationMapper& locMapper, Laser::LaserSide laserSide, int & firstRowLaserCol, TimingStats * timingStats);
|
||||
/**
|
||||
* Returns true if the scan was processed successfully and false if there was a problem and the frame needs to be again.
|
||||
*/
|
||||
bool processScan(std::vector<NeutralFileRecord> & results, int frame, float rotation, LocationMapper& locMapper, Laser::LaserSide laserSide, int & firstRowLaserCol, TimingStats * timingStats);
|
||||
|
||||
void writeRangePoints(ColoredPoint * points, int numLocationsMapped,Laser::LaserSide laserSide);
|
||||
|
||||
|
||||
|
||||
private:
|
||||
/** Unowned objects */
|
||||
Laser * m_laser;
|
||||
@@ -162,14 +163,11 @@ private:
|
||||
/** Protection for the running, progress, and any other status parameters */
|
||||
CriticalSection m_status;
|
||||
|
||||
/** The maximum number of times to try a scan (at a particular rotation) before giving up */
|
||||
const int m_maxNumScanTries;
|
||||
/** The maximum number of times to try a frame (at a particular rotation) before giving up */
|
||||
const int m_maxNumFrameRetries;
|
||||
|
||||
/** The threshold for number of suspected bad laser locations before a rescan is required */
|
||||
const int m_badLaserLocationThreshold;
|
||||
|
||||
// Diagnostic info
|
||||
int m_numSuspectedBadLaserLocations;
|
||||
/** The threshold for percentage of pixels over the threshold */
|
||||
const real m_maxPercentPixelsOverThreshold;
|
||||
|
||||
/** The image points for every column */
|
||||
ColoredPoint * m_columnPoints;
|
||||
|
||||
@@ -60,7 +60,7 @@ Setup::Setup() :
|
||||
leftLaserLocation.y = 82.55;
|
||||
leftLaserLocation.z = 260.35;
|
||||
|
||||
rightLaserLocation.x = 114.3;
|
||||
rightLaserLocation.x = 125.73;
|
||||
rightLaserLocation.y = 82.55;
|
||||
rightLaserLocation.z = 260.35;
|
||||
}
|
||||
|
||||
@@ -36,11 +36,6 @@ static real32 DistanceSquared(const ColoredPoint& a, const ColoredPoint&b)
|
||||
return dx * dx + dy * dy + dz * dz;
|
||||
}
|
||||
|
||||
static bool SortRecordByRow(const NeutralFileRecord& a, const NeutralFileRecord& b)
|
||||
{
|
||||
return a.pixel.y < b.pixel.y;
|
||||
}
|
||||
|
||||
|
||||
StlWriter::StlWriter() :
|
||||
m_maxEdgeDistMmSq(12.4 * 12.2), // TODO: Make this a setting or autodetect it based off the distance to table and the detail level
|
||||
@@ -52,122 +47,6 @@ StlWriter::StlWriter() :
|
||||
m_normal[2] = 0;
|
||||
}
|
||||
|
||||
void StlWriter::computeAverage(const std::vector<NeutralFileRecord>& bin, NeutralFileRecord& out)
|
||||
{
|
||||
out = bin.front();
|
||||
|
||||
real32 invSize = 1.0f / bin.size();
|
||||
|
||||
real32 rotation = 0;
|
||||
real32 pixelLocationX = 0;
|
||||
real32 pixelLocationY = 0;
|
||||
real32 ptX = 0;
|
||||
real32 ptY = 0;
|
||||
real32 ptZ = 0;
|
||||
real32 ptR = 0;
|
||||
real32 ptG = 0;
|
||||
real32 ptB = 0;
|
||||
|
||||
for (size_t iBin = 0; iBin < bin.size(); iBin++)
|
||||
{
|
||||
const NeutralFileRecord& br = bin[iBin];
|
||||
|
||||
rotation += invSize * br.rotation;
|
||||
pixelLocationX += invSize * br.pixel.x;
|
||||
pixelLocationY += invSize * br.pixel.y;
|
||||
ptX += invSize * br.point.x;
|
||||
ptY += invSize * br.point.y;
|
||||
ptZ += invSize * br.point.z;
|
||||
ptR += invSize * br.point.r;
|
||||
ptG += invSize * br.point.g;
|
||||
ptB += invSize * br.point.b;
|
||||
}
|
||||
|
||||
out.rotation = rotation;
|
||||
out.pixel.x = pixelLocationX; // TODO: We should probably round these values
|
||||
out.pixel.y = pixelLocationY;
|
||||
out.point.x = ptX;
|
||||
out.point.y = ptY;
|
||||
out.point.z = ptZ;
|
||||
out.point.r = ptR;
|
||||
out.point.g = ptG;
|
||||
out.point.b = ptB;
|
||||
}
|
||||
|
||||
void StlWriter::lowpassFilter(std::vector<NeutralFileRecord>& output, std::vector<NeutralFileRecord>& frame, unsigned maxNumRows, unsigned numRowBins)
|
||||
{
|
||||
output.clear();
|
||||
|
||||
// Sanity check
|
||||
if (frame.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort by image row
|
||||
std::sort(frame.begin(), frame.end(), SortRecordByRow);
|
||||
|
||||
unsigned binSize = maxNumRows / numRowBins;
|
||||
|
||||
// Holds the current contents of the bin
|
||||
std::vector<NeutralFileRecord> bin;
|
||||
unsigned nextBinY = frame.front().pixel.y + binSize;
|
||||
|
||||
// unsigned bin = frame.front().pixel.y / numRowBins;
|
||||
for (size_t iFr = 0; iFr < frame.size(); iFr++)
|
||||
{
|
||||
NeutralFileRecord& record = frame[iFr];
|
||||
|
||||
if (record.pixel.y < nextBinY)
|
||||
{
|
||||
bin.push_back(record);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Average the bin results and add it to the output
|
||||
if (!bin.empty())
|
||||
{
|
||||
NeutralFileRecord out;
|
||||
computeAverage(bin, out);
|
||||
|
||||
output.push_back(out);
|
||||
bin.clear();
|
||||
}
|
||||
|
||||
nextBinY = record.pixel.y + binSize;
|
||||
bin.push_back(record);
|
||||
}
|
||||
}
|
||||
|
||||
// Process any results still left in the bin
|
||||
if (!bin.empty())
|
||||
{
|
||||
NeutralFileRecord out;
|
||||
computeAverage(bin, out);
|
||||
|
||||
output.push_back(out);
|
||||
bin.clear();
|
||||
}
|
||||
}
|
||||
|
||||
bool StlWriter::readNextStep(std::vector<NeutralFileRecord>& out, const std::vector<NeutralFileRecord>& results, size_t & resultIndex)
|
||||
{
|
||||
out.clear();
|
||||
|
||||
if (resultIndex >= results.size() || results.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int pseudoStep = results[resultIndex].pseudoFrame;
|
||||
while (pseudoStep == results[resultIndex].pseudoFrame && resultIndex < results.size())
|
||||
{
|
||||
out.push_back(results[resultIndex]);
|
||||
resultIndex++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void StlWriter::write(const std::string& baseFilename, const std::vector<NeutralFileRecord>& results, bool connectLastFrameToFirst)
|
||||
{
|
||||
@@ -178,13 +57,6 @@ void StlWriter::write(const std::string& baseFilename, const std::vector<Neutral
|
||||
throw Exception("Error opening STL file for writing: " + stlFilename);
|
||||
}
|
||||
|
||||
std::string xyzFilename = baseFilename + ".xyz";
|
||||
std::ofstream xyz (xyzFilename.c_str());
|
||||
if (!xyz.is_open())
|
||||
{
|
||||
throw Exception("Error opening STL file for writing: " + xyzFilename);
|
||||
}
|
||||
|
||||
uint32 maxNumRows = Camera::getInstance()->getImageHeight(); // TODO: Make this use the image height that generated the result and not the current Camera
|
||||
m_imageWidth = Camera::getInstance()->getImageWidth();
|
||||
uint32 numRowBins = 400; // TODO: Autodetect this or have it in Database
|
||||
@@ -208,27 +80,16 @@ void StlWriter::write(const std::string& baseFilename, const std::vector<Neutral
|
||||
|
||||
size_t resultIndex = 0;
|
||||
size_t totNumPoints = 0;
|
||||
while (readNextStep(frameC, results, resultIndex))
|
||||
while (NeutralFileRecord::readNextFrame(frameC, results, resultIndex))
|
||||
{
|
||||
// Reduce the number of result rows and filter out some of the noise
|
||||
lowpassFilter(* currentFrame, frameC, maxNumRows, numRowBins);
|
||||
NeutralFileRecord::lowpassFilter(* currentFrame, frameC, maxNumRows, numRowBins);
|
||||
|
||||
totNumPoints += currentFrame->size();
|
||||
|
||||
// Sort the results in decreasing Y
|
||||
//std::sort(currentFrame->begin(), currentFrame->end(), SortRecordByYValue);
|
||||
|
||||
// Write the filtered results to the XYZ file
|
||||
for (size_t iRec = 0; iRec < currentFrame->size(); iRec++)
|
||||
{
|
||||
const NeutralFileRecord& rec = currentFrame->at(iRec);
|
||||
const ColoredPoint & pt = rec.point;
|
||||
|
||||
xyz << pt.x << " " << pt.y << " " << pt.z << " "
|
||||
<< pt.normal.x << " " << pt.normal.y << " " << pt.normal.z
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
if (iStep > 0)
|
||||
{
|
||||
writeTrianglesForColumn(* currentFrame, * lastFrame, fout, numTriangles);
|
||||
@@ -272,12 +133,10 @@ void StlWriter::write(const std::string& baseFilename, const std::vector<Neutral
|
||||
catch (...)
|
||||
{
|
||||
fout.close();
|
||||
xyz.close();
|
||||
throw;
|
||||
}
|
||||
|
||||
fout.close();
|
||||
xyz.close();
|
||||
}
|
||||
|
||||
void StlWriter::writeHeader(std::ofstream& fout)
|
||||
|
||||
@@ -23,14 +23,12 @@
|
||||
namespace freelss
|
||||
{
|
||||
|
||||
class NeutralFileReader;
|
||||
|
||||
class StlWriter
|
||||
{
|
||||
public:
|
||||
StlWriter();
|
||||
|
||||
|
||||
void write(const std::string& filename, const std::vector<NeutralFileRecord>& results, bool connectLastFrameToFirst);
|
||||
|
||||
private:
|
||||
@@ -39,18 +37,6 @@ private:
|
||||
void writeTrianglesForColumn(const std::vector<NeutralFileRecord>& currentFrame, const std::vector<NeutralFileRecord>& lastFrame, std::ofstream& fout, uint32& numTriangles);
|
||||
void writeTriangle(const ColoredPoint& pt1, const ColoredPoint& pt2, const ColoredPoint& pt3, bool flipNormal, std::ofstream& fout);
|
||||
|
||||
/**
|
||||
* Reduce the number of result rows and filter out some of the noise
|
||||
* @param maxNumRows - The number of rows in the image the produced the frame.
|
||||
* @param numRowBins - The total number of row bins in the entire image, not necessarily what is returned by this function.
|
||||
*/
|
||||
void lowpassFilter(std::vector<NeutralFileRecord>& output, std::vector<NeutralFileRecord>& frame, unsigned maxNumRows, unsigned numRowBins);
|
||||
|
||||
/**
|
||||
* Computes the average of all the records in the bin.
|
||||
*/
|
||||
void computeAverage(const std::vector<NeutralFileRecord>& bin, NeutralFileRecord& out);
|
||||
|
||||
bool isValidTriangle(const ColoredPoint& pt1, const ColoredPoint& pt2, const ColoredPoint& pt3);
|
||||
|
||||
/**
|
||||
@@ -60,7 +46,6 @@ private:
|
||||
*/
|
||||
bool isInwardFacingFace(const NeutralFileRecord& p1, const NeutralFileRecord& p2, const NeutralFileRecord& p3);
|
||||
|
||||
bool readNextStep(std::vector<NeutralFileRecord>& frameC, const std::vector<NeutralFileRecord>& results, size_t & resultIndex);
|
||||
|
||||
/** The max triangle edge distance in mm sq */
|
||||
real32 m_maxEdgeDistMmSq;
|
||||
|
||||
@@ -229,6 +229,10 @@ const std::string WebContent::DIRECTION_VALUE = "DIRECTION_VALUE";
|
||||
const std::string WebContent::RESPONSE_DELAY = "RESPONSE_DELAY";
|
||||
const std::string WebContent::STEP_DELAY = "STEP_DELAY";
|
||||
const std::string WebContent::FRAMES_PER_REVOLUTION = "FRAMES_PER_REVOLUTION";
|
||||
const std::string WebContent::GENERATE_XYZ = "GENERATE_XYZ";
|
||||
const std::string WebContent::GENERATE_STL = "GENERATE_STL";
|
||||
const std::string WebContent::GENERATE_PLY = "GENERATE_PLY";
|
||||
const std::string WebContent::SEPARATE_LASERS_BY_COLOR = "SEPARATE_LASERS_BY_COLOR";
|
||||
|
||||
const std::string WebContent::CAMERA_X_DESCR = "X-compoment of camera location. ie: The camera is always at X = 0.";
|
||||
const std::string WebContent::CAMERA_Y_DESCR = "Y-component of camera location. ie: The distance from camera center to the XZ plane";
|
||||
@@ -254,6 +258,10 @@ const std::string WebContent::STEP_DELAY_DESCR = "The amount of time between ste
|
||||
const std::string WebContent::DIRECTION_PIN_DESCR = "The wiringPi pin number for the stepper motor direction or rotation";
|
||||
const std::string WebContent::RESPONSE_DELAY_DESCR = "The time it takes for the stepper controller to recognize a pin value change in microseconds";
|
||||
const std::string WebContent::FRAMES_PER_REVOLUTION_DESCR = "The number of frames that should be taken for a scan. Default is 800.";
|
||||
const std::string WebContent::GENERATE_XYZ_DESCR = "Whether to generate an XYZ point cloud file from the scan.";
|
||||
const std::string WebContent::GENERATE_STL_DESCR = "Whether to generate an STL mesh from the scan.";
|
||||
const std::string WebContent::GENERATE_PLY_DESCR = "Whether to generate a PLY point clould from the scan.";
|
||||
const std::string WebContent::SEPARATE_LASERS_BY_COLOR_DESCR = "Calibration debugging option to separate the results from different lasers by color (requires PLY).";
|
||||
|
||||
std::string WebContent::scan(const std::vector<ScanResult>& pastScans)
|
||||
{
|
||||
@@ -587,6 +595,10 @@ std::string WebContent::settings(const std::string& message)
|
||||
sstr << setting(WebContent::STABILITY_DELAY, "Stability Delay", preset.stabilityDelay, STABILITY_DELAY_DESCR, "μs");
|
||||
sstr << setting(WebContent::MAX_LASER_WIDTH, "Max Laser Width", preset.maxLaserWidth, MAX_LASER_WIDTH_DESCR, "px.");
|
||||
sstr << setting(WebContent::MIN_LASER_WIDTH, "Min Laser Width", preset.minLaserWidth, MIN_LASER_WIDTH_DESCR, "px.");
|
||||
sstr << checkbox(WebContent::GENERATE_PLY, "Generate PLY File", preset.generatePly, GENERATE_PLY_DESCR);
|
||||
sstr << checkbox(WebContent::GENERATE_STL, "Generate STL File", preset.generateStl, GENERATE_STL_DESCR);
|
||||
sstr << checkbox(WebContent::GENERATE_XYZ, "Generate XYZ File", preset.generateXyz, GENERATE_XYZ_DESCR);
|
||||
sstr << checkbox(WebContent::SEPARATE_LASERS_BY_COLOR, "Separate the Lasers", preset.laserMergeAction == Preset::LMA_SEPARATE_BY_COLOR, SEPARATE_LASERS_BY_COLOR_DESCR);
|
||||
|
||||
sstr << "</form>\
|
||||
<form action=\"/settings\" method=\"POST\" enctype=\"application/x-www-form-urlencoded\">\
|
||||
@@ -627,6 +639,28 @@ std::string WebContent::setting(const std::string& name, const std::string& labe
|
||||
return sstr.str();
|
||||
}
|
||||
|
||||
std::string WebContent::checkbox(const std::string& name, const std::string& label, bool checked, const std::string& description)
|
||||
{
|
||||
std::stringstream sstr;
|
||||
sstr << "<div><div class=\"settingsText\">"
|
||||
<< label
|
||||
<< "</div><input class=\"settingsInput\" name=\""
|
||||
<< name
|
||||
<< "\" type=\"checkbox\"";
|
||||
|
||||
if (checked)
|
||||
{
|
||||
sstr << " checked";
|
||||
}
|
||||
|
||||
sstr << "> ";
|
||||
|
||||
sstr << "</div><div class=\"settingsDescr\">" << description << "</div>\n";
|
||||
|
||||
|
||||
return sstr.str();
|
||||
}
|
||||
|
||||
std::string WebContent::setting(const std::string& name, const std::string& label,
|
||||
int value, const std::string& description, const std::string& units, bool readOnly)
|
||||
{
|
||||
|
||||
@@ -70,7 +70,10 @@ public:
|
||||
static const std::string RESPONSE_DELAY;
|
||||
static const std::string STEP_DELAY;
|
||||
static const std::string FRAMES_PER_REVOLUTION;
|
||||
|
||||
static const std::string GENERATE_XYZ;
|
||||
static const std::string GENERATE_STL;
|
||||
static const std::string GENERATE_PLY;
|
||||
static const std::string SEPARATE_LASERS_BY_COLOR;
|
||||
private:
|
||||
static std::string setting(const std::string& name, const std::string& label,
|
||||
const std::string& value, const std::string& description, const std::string& units = "", bool readOnly = false);
|
||||
@@ -81,6 +84,8 @@ private:
|
||||
static std::string setting(const std::string& name, const std::string& label,
|
||||
real value, const std::string& description, const std::string& units = "", bool readOnly = false);
|
||||
|
||||
static std::string checkbox(const std::string& name, const std::string& label, bool checked, const std::string& description);
|
||||
|
||||
static std::string scanResult(size_t index, const ScanResult& result);
|
||||
|
||||
static const std::string CSS;
|
||||
@@ -109,6 +114,10 @@ private:
|
||||
static const std::string DIRECTION_PIN_DESCR;
|
||||
static const std::string RESPONSE_DELAY_DESCR;
|
||||
static const std::string FRAMES_PER_REVOLUTION_DESCR;
|
||||
static const std::string GENERATE_XYZ_DESCR;
|
||||
static const std::string GENERATE_STL_DESCR;
|
||||
static const std::string GENERATE_PLY_DESCR;
|
||||
static const std::string SEPARATE_LASERS_BY_COLOR_DESCR;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
75
src/XyzWriter.cpp
Normal file
75
src/XyzWriter.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
****************************************************************************
|
||||
* Copyright (c) 2014 Uriah Liggett <hairu526@gmail.com> *
|
||||
* This file is part of FreeLSS. *
|
||||
* *
|
||||
* FreeLSS is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* FreeLSS is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with FreeLSS. If not, see <http://www.gnu.org/licenses/>. *
|
||||
****************************************************************************
|
||||
*/
|
||||
|
||||
#include "Main.h"
|
||||
#include "XyzWriter.h"
|
||||
#include "Camera.h"
|
||||
|
||||
namespace freelss
|
||||
{
|
||||
|
||||
void XyzWriter::write(const std::string& baseFilename, const std::vector<NeutralFileRecord>& results)
|
||||
{
|
||||
std::string xyzFilename = baseFilename + ".xyz";
|
||||
std::ofstream xyz (xyzFilename.c_str());
|
||||
if (!xyz.is_open())
|
||||
{
|
||||
throw Exception("Error opening STL file for writing: " + xyzFilename);
|
||||
}
|
||||
|
||||
uint32 maxNumRows = Camera::getInstance()->getImageHeight(); // TODO: Make this use the image height that generated the result and not the current Camera
|
||||
uint32 numRowBins = 400; // TODO: Autodetect this or have it in Database
|
||||
|
||||
try
|
||||
{
|
||||
int iFrame = 0;
|
||||
|
||||
std::vector<NeutralFileRecord> frameA;
|
||||
std::vector<NeutralFileRecord> currentFrame;
|
||||
|
||||
size_t resultIndex = 0;
|
||||
while (NeutralFileRecord::readNextFrame(frameA, results, resultIndex))
|
||||
{
|
||||
// Reduce the number of result rows and filter out some of the noise
|
||||
NeutralFileRecord::lowpassFilter(currentFrame, frameA, maxNumRows, numRowBins);
|
||||
|
||||
// Write the filtered results to the XYZ file
|
||||
for (size_t iRec = 0; iRec < currentFrame.size(); iRec++)
|
||||
{
|
||||
const NeutralFileRecord& rec = currentFrame[iRec];
|
||||
const ColoredPoint & pt = rec.point;
|
||||
|
||||
xyz << pt.x << " " << pt.y << " " << pt.z << " "
|
||||
<< pt.normal.x << " " << pt.normal.y << " " << pt.normal.z
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
xyz.close();
|
||||
throw;
|
||||
}
|
||||
|
||||
xyz.close();
|
||||
}
|
||||
|
||||
|
||||
} // ns freelss
|
||||
36
src/XyzWriter.h
Normal file
36
src/XyzWriter.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
****************************************************************************
|
||||
* Copyright (c) 2015 Uriah Liggett <hairu526@gmail.com> *
|
||||
* This file is part of FreeLSS. *
|
||||
* *
|
||||
* FreeLSS is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* FreeLSS is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with FreeLSS. If not, see <http://www.gnu.org/licenses/>. *
|
||||
****************************************************************************
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace freelss
|
||||
{
|
||||
|
||||
/**
|
||||
* Writes the scan results as an XYZ file.
|
||||
*/
|
||||
class XyzWriter
|
||||
{
|
||||
public:
|
||||
|
||||
void write(const std::string& filename, const std::vector<NeutralFileRecord>& results);
|
||||
};
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user