#include "control_module.h" | #include "control_module.h" | ||||
ControlModule::ControlModule(){ | |||||
ControlModule::ControlModule(): ControlModule(1.0, 1.0, 1.0) | |||||
{ | |||||
} | } | ||||
ControlModule::ControlModule(float forwardSpeed, float rotateSpeed, float moveSideSpeed) | ControlModule::ControlModule(float forwardSpeed, float rotateSpeed, float moveSideSpeed) | ||||
{ | { | ||||
motors[0] = 0.0; | |||||
motors[1] = 0.0; | |||||
motors[2] = 0.0; | |||||
motors[3] = 0.0; | |||||
this->forwardSpeed = forwardSpeed; | this->forwardSpeed = forwardSpeed; | ||||
this->rotateSpeed = rotateSpeed; | this->rotateSpeed = rotateSpeed; | ||||
this->moveSideSpeed = moveSideSpeed; | this->moveSideSpeed = moveSideSpeed; | ||||
} | } | ||||
void ControlModule::moveForward(){ | void ControlModule::moveForward(){ | ||||
for(int i = 0; i <= sizeof(motors)/sizeof(int); i++){ | |||||
for(int i = 0; i < 4; i++){ | |||||
motors[i] += forwardSpeed; | motors[i] += forwardSpeed; | ||||
} | } | ||||
}; | }; | ||||
void ControlModule::moveSide(int imageColumsMiddle, int contourColumsMiddle){ | void ControlModule::moveSide(int imageColumsMiddle, int contourColumsMiddle){ | ||||
float speed = moveSideSpeed * (contourColumsMiddle - imageColumsMiddle)/imageColumsMiddle; | |||||
float speed = moveSideSpeed * static_cast<float>(contourColumsMiddle - imageColumsMiddle)/static_cast<float>(imageColumsMiddle); | |||||
motors[0] += speed; | motors[0] += speed; | ||||
motors[1] -= speed; | motors[1] -= speed; | ||||
motors[2] -= speed; | motors[2] -= speed; | ||||
} | } | ||||
void ControlModule::rotate(int angle){ | void ControlModule::rotate(int angle){ | ||||
float speed = rotateSpeed * (angle + 90)/90; | |||||
float speed = rotateSpeed * (static_cast<float>(angle) + 90.0f)/90.0f; | |||||
motors[0] += speed; | motors[0] += speed; | ||||
motors[1] -= speed; | motors[1] -= speed; | ||||
motors[2] += speed; | motors[2] += speed; | ||||
} | } | ||||
void ControlModule::unit(){ | void ControlModule::unit(){ | ||||
float max = 10E-12; | |||||
float max = 1.0E-6; | |||||
for(int i = 0; i <= sizeof(motors)/sizeof(int); i++){ | for(int i = 0; i <= sizeof(motors)/sizeof(int); i++){ | ||||
if(motors[i] > max) | if(motors[i] > max) | ||||
max = motors[i]; | max = motors[i]; | ||||
} | } | ||||
for(int i = 0; i <= sizeof(motors)/sizeof(int); i++){ | |||||
motors[i] /= max; | |||||
//Avoid dividing by zero | |||||
if (max > 0.001) | |||||
{ | |||||
for(int i = 0; i < 4; i++){ | |||||
motors[i] /= max; | |||||
} | |||||
} | } | ||||
} | } | ||||
void ControlModule::calcSpeeds(int imageColumsMiddle, int contourColumsMiddle, int angle){ | void ControlModule::calcSpeeds(int imageColumsMiddle, int contourColumsMiddle, int angle){ | ||||
std::unique_lock<std::mutex> lock(mtx); | |||||
moveForward(); | moveForward(); | ||||
moveSide(imageColumsMiddle, contourColumsMiddle); | moveSide(imageColumsMiddle, contourColumsMiddle); | ||||
rotate(angle); | rotate(angle); | ||||
unit(); | unit(); | ||||
} | |||||
} | |||||
std::vector<float> ControlModule::readMotors() | |||||
{ | |||||
std::unique_lock<std::mutex> lock(mtx); | |||||
return std::vector<float> {motors[0], motors[1], motors[2], motors[3]}; | |||||
} |
#pragma once | #pragma once | ||||
#include <mutex> | |||||
#include <vector> | |||||
#include <iostream> | |||||
class ControlModule | class ControlModule | ||||
{ | { | ||||
private: | private: | ||||
mutable std::mutex mtx; | |||||
float motors[4]; //LeftFront; RightFront; LeftBack; RightBack | float motors[4]; //LeftFront; RightFront; LeftBack; RightBack | ||||
float forwardSpeed; | float forwardSpeed; | ||||
float rotateSpeed; | float rotateSpeed; | ||||
void unit(); //Brings the max Value to 1.0 | void unit(); //Brings the max Value to 1.0 | ||||
void calcSpeeds(int imageColumsMiddle, int contourColumsMiddle, int angle); //Funktion to be called | void calcSpeeds(int imageColumsMiddle, int contourColumsMiddle, int angle); //Funktion to be called | ||||
std::vector<float> readMotors(); | |||||
}; | }; |
#include "input.h" | #include "input.h" | ||||
// TODO: Wenn ihr in die Zeile den Pfad zum Testvideo statt der 0 packt, benmutzt er das Testvideo. | // TODO: Wenn ihr in die Zeile den Pfad zum Testvideo statt der 0 packt, benmutzt er das Testvideo. | ||||
Input::Input(int videoHeight, int videoWidth) : cap("C:\\Line-Following-Robot\\AutonomousMode\\Test_data\\video1.h264"), videoHeight(videoHeight), videoWidth(videoWidth) | |||||
Input::Input(int videoHeight, int videoWidth) : cap("C:\\Line-Following-Robot\\AutonomousMode\\Test_data\\video1.h264"), videoHeight(videoHeight), videoWidth(videoWidth)//Input::Input(int videoHeight, int videoWidth) : cap(0), videoHeight(videoHeight), videoWidth(videoWidth) | |||||
//Input::Input(int videoHeight, int videoWidth) : cap(0), videoHeight(videoHeight), videoWidth(videoWidth) | //Input::Input(int videoHeight, int videoWidth) : cap(0), videoHeight(videoHeight), videoWidth(videoWidth) | ||||
{ | { | ||||
std::unique_lock<std::mutex> lock(mtx); | |||||
this->cap.set(CAP_PROP_FRAME_HEIGHT, videoHeight); | this->cap.set(CAP_PROP_FRAME_HEIGHT, videoHeight); | ||||
this->cap.set(CAP_PROP_FRAME_WIDTH, videoWidth); | this->cap.set(CAP_PROP_FRAME_WIDTH, videoWidth); | ||||
} | } | ||||
Mat Input::readFile(String filePath) | Mat Input::readFile(String filePath) | ||||
{ | { | ||||
std::srand(std::time(0)); | |||||
std::unique_lock<std::mutex> lock(mtx); | |||||
std::srand(static_cast<unsigned int>(std::time(0))); | |||||
// Read all .jpg files from the specified folder | // Read all .jpg files from the specified folder | ||||
cv::String folder = filePath; | cv::String folder = filePath; | ||||
std::vector<cv::String> filenames; | std::vector<cv::String> filenames; | ||||
cv::glob(folder, filenames); | cv::glob(folder, filenames); | ||||
// Random shuffle | // Random shuffle | ||||
std::random_shuffle(filenames.begin(), filenames.end()); | |||||
std::random_device rd; | |||||
std::mt19937 g(rd()); | |||||
std::shuffle(filenames.begin(), filenames.end(), g); | |||||
Mat image = imread(filePath, IMREAD_COLOR); | Mat image = imread(filePath, IMREAD_COLOR); | ||||
if(image.empty()) | if(image.empty()) | ||||
{ | { | ||||
std::cout << "Could not read the image: " << filePath << std::endl; | |||||
return Mat(); | |||||
//To do:Exception handeling | |||||
stringstream sstream; | |||||
sstream << "Could not read the image: " << filePath << std::endl; | |||||
throw std::runtime_error(sstream.str()); | |||||
} | } | ||||
resize(image, image, Size(this->videoWidth, this->videoHeight)); | resize(image, image, Size(this->videoWidth, this->videoHeight)); | ||||
return image; | return image; | ||||
Mat Input::readWebcam() | Mat Input::readWebcam() | ||||
{ | { | ||||
std::unique_lock<std::mutex> lock(mtx); | |||||
Mat image; | Mat image; | ||||
if(!cap.isOpened()) { | if(!cap.isOpened()) { | ||||
cout << "Video capture not opened" << std::endl; | |||||
return Mat(); | |||||
stringstream sstream; | |||||
sstream << "Video capture not opened" << std::endl; | |||||
throw std::runtime_error(sstream.str()); | |||||
} | } | ||||
if(!cap.grab()) { | if(!cap.grab()) { | ||||
cout << "Could not grab frame from camera" << std::endl; | |||||
return Mat(); | |||||
stringstream sstream; | |||||
sstream << "Could not grab frame from camera" << std::endl; | |||||
throw std::runtime_error(sstream.str()); | |||||
} | } | ||||
cap.retrieve(image); | cap.retrieve(image); | ||||
return image; | return image; | ||||
void Input::freeWebcam() | void Input::freeWebcam() | ||||
{ | { | ||||
std::unique_lock<std::mutex> lock(mtx); | |||||
this->cap.release(); | this->cap.release(); | ||||
} | } |
#include <vector> | #include <vector> | ||||
#include <string> | #include <string> | ||||
#include <algorithm> | #include <algorithm> | ||||
#include <random> | |||||
#include <opencv2/opencv.hpp> | #include <opencv2/opencv.hpp> | ||||
#include <opencv2/core/utils/logger.hpp> | #include <opencv2/core/utils/logger.hpp> | ||||
{ | { | ||||
private: | private: | ||||
VideoCapture cap; | VideoCapture cap; | ||||
mutable std::mutex mtx; | |||||
public: | public: | ||||
int videoHeight; | int videoHeight; | ||||
int videoWidth; | int videoWidth; |
IntersectionHandler::~IntersectionHandler() | IntersectionHandler::~IntersectionHandler() | ||||
{ | { | ||||
} | |||||
const bool IntersectionHandler::foundLane(const FrameData& data) | |||||
{ | |||||
return data.boundingBoxes.size(); | |||||
} | |||||
const bool IntersectionHandler::foundIntersection(const FrameData& data) | |||||
{ | |||||
return data.boundingBoxes.size()>1; | |||||
} | } |
#pragma once | #pragma once | ||||
#include <utils.h> | |||||
class IntersectionHandler | class IntersectionHandler | ||||
{ | { | ||||
private: | private: | ||||
/* data */ | |||||
public: | public: | ||||
IntersectionHandler(/* args */); | IntersectionHandler(/* args */); | ||||
~IntersectionHandler(); | ~IntersectionHandler(); | ||||
const bool foundLane(const FrameData& data); | |||||
const bool foundIntersection(const FrameData& data); | |||||
}; | }; |
int main(void) | int main(void) | ||||
{ | { | ||||
//Disable opencv logging messages | //Disable opencv logging messages | ||||
cv::utils::logging::setLogLevel(cv::utils::logging::LOG_LEVEL_WARNING); | |||||
//cv::utils::logging::setLogLevel(cv::utils::logging::LOG_LEVEL_WARNING); | |||||
const int thresholdBinary = 140; | const int thresholdBinary = 140; | ||||
const int videoHeight = 720; | const int videoHeight = 720; | ||||
const int videoWidth = 1280; | const int videoWidth = 1280; | ||||
const int gaussKernelSize = 11; | const int gaussKernelSize = 11; | ||||
LFR lfr(videoHeight, videoWidth, thresholdBinary, gaussKernelSize); | |||||
lfr.saveOutputFlag = false; | |||||
lfr.videoFlag = true; | |||||
std::mutex mutex; | |||||
LFR lfr(videoHeight, videoWidth, thresholdBinary, gaussKernelSize, [&](std::exception const &ex) | |||||
{ | |||||
std::unique_lock<std::mutex> lock(mutex); | |||||
std::cerr<<"camera exception:"<<ex.what()<<std::endl; | |||||
return false; | |||||
}); | |||||
//To calculate the frame rate | |||||
std::chrono::milliseconds last = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()); | |||||
std::chrono::milliseconds now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()); | |||||
cv::Mat img; | |||||
lfr.addListener([&](LFR_Result result) | |||||
{ | |||||
std::unique_lock<std::mutex> lock(mutex); | |||||
if (!result.rawImage.empty()) | |||||
{ | |||||
img = result.rawImage; | |||||
//Resize to minimize latency using raspi over ssh | |||||
//cv::resize(result.rawImage, img, cv::Size(128, 64+32)); | |||||
//Calculate frame rate | |||||
now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()); | |||||
unsigned int deltaMs = static_cast<unsigned int>((now-last).count()); | |||||
double delta = static_cast<double>(deltaMs) / 1000.0; | |||||
double frameRate = 1.0 / static_cast<double>(delta); | |||||
if (result.validLane) | |||||
{ | |||||
std::cout << "Frame rate: " << frameRate << " angle: " << result.data.angle | |||||
<< " motor 1: " << result.motorSignals[0] << " motor 2: " << result.motorSignals[1] | |||||
<< " motor 3: " << result.motorSignals[2] << " motor 4: " << result.motorSignals[3] <<std::endl; | |||||
} | |||||
else | |||||
{ | |||||
std::cout << "No lane found." << std::endl;; | |||||
} | |||||
last = now; | |||||
} | |||||
}, &mutex); | |||||
lfr.startLoop(); | lfr.startLoop(); | ||||
//To end the video stream, write any char in the console. | |||||
char a; | |||||
std::cin >> a; | |||||
lfr.endLoop(); | |||||
for(int finished = false; finished != 'q';){ | |||||
finished = std::tolower(cv::waitKey(1)); | |||||
std::unique_lock<std::mutex> lock(mutex); | |||||
if(!img.empty()){ | |||||
cv::imshow("frame", img); | |||||
} | |||||
} | |||||
} | } |
#define right false | #define right false | ||||
#define left true | #define left true | ||||
LFR::LFR(int videoHeight, int videoWidth, int thresholdBinary, int gaussKernelSize) | |||||
: iAmLooping(false), input(videoHeight, videoWidth), processing(), controlModule(), interpreter(), intersectionHandler() | |||||
LFR::LFR(int videoHeight, int videoWidth, int thresholdBinary, int gaussKernelSize, ExceptionCallback cb): | |||||
stop(false), | |||||
input(videoHeight, videoWidth), | |||||
processing(), | |||||
controlModule(), | |||||
interpreter(), | |||||
intersectionHandler(), | |||||
cb(cb) | |||||
{ | { | ||||
this->iAmLooping = false; | |||||
this->thresholdBinary = thresholdBinary; | this->thresholdBinary = thresholdBinary; | ||||
this->gaussKernelSize = gaussKernelSize; | this->gaussKernelSize = gaussKernelSize; | ||||
this->videoFlag = false; | |||||
this->saveOutputFlag = false; | |||||
this->outputFileName = ""; | |||||
} | } | ||||
LFR::~LFR() | LFR::~LFR() | ||||
{ | { | ||||
if(iAmLooping) | |||||
{ | |||||
this->endLoop(); | |||||
} | |||||
endLoop(); | |||||
thread->join(); | |||||
} | } | ||||
void LFR::loop() | |||||
void LFR::removeListener(LFR::ListenerKey key) | |||||
{ | { | ||||
if(this->videoFlag) {namedWindow("Display window");} | |||||
while(iAmLooping) | |||||
std::lock_guard<std::mutex> lock(mutex); | |||||
auto it = std::find_if(std::begin(listeners), std::end(listeners), [&](auto const &val){ | |||||
return val.first == key; | |||||
}); | |||||
if(it != std::end(listeners)) | |||||
{ | { | ||||
Mat originalImage = input.readWebcam(); | |||||
Point roiOrigin(0, int(originalImage.rows*(3.25/6.0))); | |||||
Rect roi(roiOrigin.x, roiOrigin.y, originalImage.cols, originalImage.rows/6); | |||||
//Mat processedImage = originalImage(roi); | |||||
Mat processedImage = originalImage; | |||||
processing.processImage(processedImage, this->thresholdBinary, this->gaussKernelSize); | |||||
//processedImage = processedImage(roi); | |||||
FrameData data = processing.calculateLineSegments(processedImage, roi); | |||||
processing.filterReflections(data); | |||||
processing.calcAngles(data, originalImage.cols, originalImage.rows, left); | |||||
this->provideOutput(originalImage, processedImage, data, roi); | |||||
listeners.erase(it); | |||||
} | } | ||||
if(this->videoFlag) {destroyWindow("Display window");} | |||||
input.freeWebcam(); | |||||
} | |||||
void LFR::addListener(LFR::ListenerCallback cb, LFR::ListenerKey key) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(mutex); | |||||
listeners.emplace_back(key, std::move(cb)); | |||||
} | |||||
void LFR::setStop(bool val) | |||||
{ | |||||
std::lock_guard<std::mutex> lock(mutex); | |||||
stop = val; | |||||
} | |||||
void LFR::createThread() | |||||
{ | |||||
thread = std::make_unique<std::thread>([this](){ | |||||
while(true) | |||||
{ | |||||
LFR_Result result; | |||||
if(!stop && !listeners.empty()) | |||||
{ | |||||
try | |||||
{ | |||||
std::lock_guard<std::mutex> lock(mutex); | |||||
Mat originalImage = input.readWebcam(); | |||||
Point roiOrigin(0, int(originalImage.rows*(3.25/6.0))); | |||||
Rect roi(roiOrigin.x, roiOrigin.y, originalImage.cols, originalImage.rows/6); | |||||
Mat processedImage = originalImage; | |||||
processing.processImage(processedImage, this->thresholdBinary, this->gaussKernelSize); | |||||
FrameData data = processing.calculateLineSegments(processedImage, roi); | |||||
processing.filterReflections(data); | |||||
processing.calcAngles(data, originalImage.cols, originalImage.rows, left); | |||||
result.validLane = intersectionHandler.foundLane(data); | |||||
if (result.validLane) | |||||
{ | |||||
controlModule.calcSpeeds(1,1, data.angle); | |||||
processedImage = provideOutput(originalImage, processedImage, data, roi); | |||||
} | |||||
result.rawImage = originalImage; | |||||
result.processedImage = processedImage; | |||||
result.data = data; | |||||
result.motorSignals = controlModule.readMotors(); | |||||
} | |||||
catch(std::exception const &ex) | |||||
{ | |||||
if(!cb(ex)) | |||||
{ | |||||
//callback returned false -> exception not handled -> exit | |||||
exit(EXIT_FAILURE); | |||||
} | |||||
} | |||||
//Invoke the callback method (ListenerPair second -> ListenerCallback) | |||||
for(auto &val : listeners) | |||||
{ | |||||
val.second(result); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
break; | |||||
} | |||||
} | |||||
}); | |||||
} | } | ||||
void LFR::startLoop() | void LFR::startLoop() | ||||
{ | { | ||||
iAmLooping = true; | |||||
this->loopThread=thread(&LFR::loop, this); | |||||
if(thread) | |||||
{ | |||||
//Restart thread if it is running | |||||
setStop(true); | |||||
thread->join(); | |||||
setStop(false); | |||||
} | |||||
createThread(); | |||||
} | } | ||||
void LFR::endLoop() | void LFR::endLoop() | ||||
{ | { | ||||
iAmLooping = false; | |||||
this->loopThread.join(); | |||||
return; | |||||
setStop(true); | |||||
} | } | ||||
void LFR::provideOutput(Mat originalImage, Mat processedImage, const FrameData& frameData, const Rect& roi) | |||||
cv::Mat LFR::provideOutput(Mat originalImage, Mat processedImage, const FrameData& frameData, const Rect& roi) | |||||
{ | { | ||||
for(int i = 0; i < frameData.contours.size(); i++) | for(int i = 0; i < frameData.contours.size(); i++) | ||||
{ | { | ||||
P2.y = (int)round(P1.y + length * sin(frameData.angle * CV_PI / 180.0)); | P2.y = (int)round(P1.y + length * sin(frameData.angle * CV_PI / 180.0)); | ||||
cv::arrowedLine(originalImage, P1, P2, Scalar(0,0,255), 2, 8); | cv::arrowedLine(originalImage, P1, P2, Scalar(0,0,255), 2, 8); | ||||
} | } | ||||
if(this->videoFlag) | |||||
{ | |||||
imshow("Display window", originalImage); | |||||
imshow("processed:", processedImage); | |||||
char c = (char)waitKey(25); | |||||
} | |||||
if (this->saveOutputFlag && !(this->outputFileName.empty())) | |||||
{ | |||||
imwrite(this->outputFileName, originalImage); | |||||
} | |||||
return originalImage; | |||||
} | } |
#include <iostream> | #include <iostream> | ||||
#include <future> | #include <future> | ||||
#include <thread> | #include <thread> | ||||
#include <functional> | |||||
#include <opencv2/opencv.hpp> | #include <opencv2/opencv.hpp> | ||||
using namespace cv; | using namespace cv; | ||||
struct LFR_Result | |||||
{ | |||||
bool validLane; | |||||
cv::Mat rawImage; | |||||
cv::Mat processedImage; | |||||
FrameData data; | |||||
std::vector<float> motorSignals; | |||||
}; | |||||
class LFR | class LFR | ||||
{ | { | ||||
public: | |||||
using ListenerKey = void const*; | |||||
using ExceptionCallback = std::function<bool(std::exception const &ex)>; | |||||
using ListenerCallback = std::function<void(LFR_Result)>; | |||||
private: | |||||
using ListenerPair = std::pair<ListenerKey, ListenerCallback>; | |||||
using ListenerVector = std::vector<ListenerPair>; | |||||
Input input; | Input input; | ||||
Processing processing; | Processing processing; | ||||
ControlModule controlModule; | ControlModule controlModule; | ||||
Interpreter interpreter; | Interpreter interpreter; | ||||
IntersectionHandler intersectionHandler; | IntersectionHandler intersectionHandler; | ||||
volatile bool iAmLooping; | |||||
void loop(); | |||||
thread loopThread; | |||||
int thresholdBinary; | int thresholdBinary; | ||||
int gaussKernelSize; | int gaussKernelSize; | ||||
void provideOutput(Mat originalImage, Mat processedImage, const FrameData& frameData, const Rect& roi); | |||||
ListenerVector listeners; | |||||
ExceptionCallback cb; | |||||
bool stop; | |||||
std::unique_ptr<std::thread> thread; | |||||
mutable std::mutex mutex; | |||||
//void provideOutput(Mat originalImage, Mat processedImage, const FrameData& frameData, const Rect& roi); | |||||
void createThread(); | |||||
void setStop(bool val); | |||||
public: | public: | ||||
LFR() = delete; | LFR() = delete; | ||||
LFR(int videoHeight, int videoWidth, int thresholdBinary, int gaussKernelSize); | |||||
LFR(int videoHeight, int videoWidth, int thresholdBinary, int gaussKernelSize, ExceptionCallback cb); | |||||
~LFR(); | ~LFR(); | ||||
void startLoop(); | void startLoop(); | ||||
void endLoop(); | void endLoop(); | ||||
bool videoFlag; | |||||
bool saveOutputFlag; | |||||
std::string outputFileName; | |||||
void addListener(ListenerCallback cv, ListenerKey key); | |||||
void removeListener(ListenerKey key); | |||||
void isStopped() const noexcept; | |||||
Mat provideOutput(Mat originalImage, Mat processedImage, const FrameData& frameData, const Rect& roi); | |||||
}; | }; |