diff --git a/AutonomousMode/ControlModule/control_module.cpp b/AutonomousMode/ControlModule/control_module.cpp index 2d2ab87..b6666b0 100644 --- a/AutonomousMode/ControlModule/control_module.cpp +++ b/AutonomousMode/ControlModule/control_module.cpp @@ -1,10 +1,15 @@ #include "control_module.h" -ControlModule::ControlModule(){ +ControlModule::ControlModule(): ControlModule(1.0, 1.0, 1.0) +{ } 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->rotateSpeed = rotateSpeed; this->moveSideSpeed = moveSideSpeed; @@ -15,13 +20,13 @@ ControlModule::~ControlModule() } void ControlModule::moveForward(){ - for(int i = 0; i <= sizeof(motors)/sizeof(int); i++){ + for(int i = 0; i < 4; i++){ motors[i] += forwardSpeed; } }; void ControlModule::moveSide(int imageColumsMiddle, int contourColumsMiddle){ - float speed = moveSideSpeed * (contourColumsMiddle - imageColumsMiddle)/imageColumsMiddle; + float speed = moveSideSpeed * static_cast(contourColumsMiddle - imageColumsMiddle)/static_cast(imageColumsMiddle); motors[0] += speed; motors[1] -= speed; motors[2] -= speed; @@ -29,7 +34,7 @@ void ControlModule::moveSide(int imageColumsMiddle, int contourColumsMiddle){ } void ControlModule::rotate(int angle){ - float speed = rotateSpeed * (angle + 90)/90; + float speed = rotateSpeed * (static_cast(angle) + 90.0f)/90.0f; motors[0] += speed; motors[1] -= speed; motors[2] += speed; @@ -37,19 +42,32 @@ void ControlModule::rotate(int angle){ } void ControlModule::unit(){ - float max = 10E-12; + float max = 1.0E-6; for(int i = 0; i <= sizeof(motors)/sizeof(int); i++){ if(motors[i] > max) 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){ + std::unique_lock lock(mtx); moveForward(); moveSide(imageColumsMiddle, contourColumsMiddle); rotate(angle); unit(); -} \ No newline at end of file +} + +std::vector ControlModule::readMotors() +{ + std::unique_lock lock(mtx); + return std::vector {motors[0], motors[1], motors[2], motors[3]}; +} \ No newline at end of file diff --git a/AutonomousMode/ControlModule/control_module.h b/AutonomousMode/ControlModule/control_module.h index ab99c88..fc7d5b6 100644 --- a/AutonomousMode/ControlModule/control_module.h +++ b/AutonomousMode/ControlModule/control_module.h @@ -1,8 +1,12 @@ #pragma once +#include +#include +#include class ControlModule { private: + mutable std::mutex mtx; float motors[4]; //LeftFront; RightFront; LeftBack; RightBack float forwardSpeed; float rotateSpeed; @@ -17,4 +21,5 @@ public: void unit(); //Brings the max Value to 1.0 void calcSpeeds(int imageColumsMiddle, int contourColumsMiddle, int angle); //Funktion to be called + std::vector readMotors(); }; \ No newline at end of file diff --git a/AutonomousMode/Input/input.cpp b/AutonomousMode/Input/input.cpp index fee41a1..3005c0c 100644 --- a/AutonomousMode/Input/input.cpp +++ b/AutonomousMode/Input/input.cpp @@ -1,9 +1,10 @@ #include "input.h" // 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) { + std::unique_lock lock(mtx); this->cap.set(CAP_PROP_FRAME_HEIGHT, videoHeight); this->cap.set(CAP_PROP_FRAME_WIDTH, videoWidth); } @@ -15,22 +16,25 @@ Input::~Input() Mat Input::readFile(String filePath) { - std::srand(std::time(0)); + std::unique_lock lock(mtx); + std::srand(static_cast(std::time(0))); // Read all .jpg files from the specified folder cv::String folder = filePath; std::vector filenames; cv::glob(folder, filenames); // 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); 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)); return image; @@ -38,16 +42,19 @@ Mat Input::readFile(String filePath) Mat Input::readWebcam() { + std::unique_lock lock(mtx); Mat image; 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()) { - 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); return image; @@ -55,5 +62,6 @@ Mat Input::readWebcam() void Input::freeWebcam() { + std::unique_lock lock(mtx); this->cap.release(); } diff --git a/AutonomousMode/Input/input.h b/AutonomousMode/Input/input.h index c4accac..32b467a 100644 --- a/AutonomousMode/Input/input.h +++ b/AutonomousMode/Input/input.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -15,7 +16,7 @@ class Input { private: VideoCapture cap; - + mutable std::mutex mtx; public: int videoHeight; int videoWidth; diff --git a/AutonomousMode/IntersectionHandler/intersection_handler.cpp b/AutonomousMode/IntersectionHandler/intersection_handler.cpp index a6b66e0..1786040 100644 --- a/AutonomousMode/IntersectionHandler/intersection_handler.cpp +++ b/AutonomousMode/IntersectionHandler/intersection_handler.cpp @@ -6,4 +6,14 @@ IntersectionHandler::IntersectionHandler(/* args */) 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; } \ No newline at end of file diff --git a/AutonomousMode/IntersectionHandler/intersection_handler.h b/AutonomousMode/IntersectionHandler/intersection_handler.h index 2f18184..9080418 100644 --- a/AutonomousMode/IntersectionHandler/intersection_handler.h +++ b/AutonomousMode/IntersectionHandler/intersection_handler.h @@ -1,11 +1,14 @@ #pragma once +#include class IntersectionHandler { private: - /* data */ public: IntersectionHandler(/* args */); ~IntersectionHandler(); + + const bool foundLane(const FrameData& data); + const bool foundIntersection(const FrameData& data); }; \ No newline at end of file diff --git a/AutonomousMode/autonomous_mode_main.cpp b/AutonomousMode/autonomous_mode_main.cpp index 63abfd1..a3f4414 100644 --- a/AutonomousMode/autonomous_mode_main.cpp +++ b/AutonomousMode/autonomous_mode_main.cpp @@ -4,19 +4,63 @@ int main(void) { //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 videoHeight = 720; const int videoWidth = 1280; 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 lock(mutex); + std::cerr<<"camera exception:"<(std::chrono::system_clock::now().time_since_epoch()); + std::chrono::milliseconds now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); + + cv::Mat img; + lfr.addListener([&](LFR_Result result) + { + std::unique_lock 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::system_clock::now().time_since_epoch()); + unsigned int deltaMs = static_cast((now-last).count()); + double delta = static_cast(deltaMs) / 1000.0; + double frameRate = 1.0 / static_cast(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] <> a; - lfr.endLoop(); + + for(int finished = false; finished != 'q';){ + finished = std::tolower(cv::waitKey(1)); + std::unique_lock lock(mutex); + if(!img.empty()){ + cv::imshow("frame", img); + } + } } diff --git a/AutonomousMode/lfr.cpp b/AutonomousMode/lfr.cpp index 4343ae2..c288716 100644 --- a/AutonomousMode/lfr.cpp +++ b/AutonomousMode/lfr.cpp @@ -3,63 +3,123 @@ #define right false #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->gaussKernelSize = gaussKernelSize; - - this->videoFlag = false; - this->saveOutputFlag = false; - this->outputFileName = ""; } LFR::~LFR() { - if(iAmLooping) + endLoop(); + thread->join(); +} + +void LFR::removeListener(LFR::ListenerKey key) +{ + std::lock_guard 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)) { - this->endLoop(); + listeners.erase(it); } } -void LFR::loop() +void LFR::addListener(LFR::ListenerCallback cb, LFR::ListenerKey key) { - if(this->videoFlag) {namedWindow("Display window");} - while(iAmLooping) - { - Mat originalImage = input.readWebcam(); + std::lock_guard lock(mutex); + listeners.emplace_back(key, std::move(cb)); +} - 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; +void LFR::setStop(bool val) +{ + std::lock_guard lock(mutex); + stop = val; +} - 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); - } - if(this->videoFlag) {destroyWindow("Display window");} - input.freeWebcam(); +void LFR::createThread() +{ + thread = std::make_unique([this](){ + while(true) + { + LFR_Result result; + if(!stop && !listeners.empty()) + { + try + { + std::lock_guard 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() { - 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() { - 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++) { @@ -83,14 +143,5 @@ void LFR::provideOutput(Mat originalImage, Mat processedImage, const FrameData& 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); } - 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; } diff --git a/AutonomousMode/lfr.h b/AutonomousMode/lfr.h index 22140fb..46dba23 100644 --- a/AutonomousMode/lfr.h +++ b/AutonomousMode/lfr.h @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -15,34 +16,54 @@ using namespace cv; +struct LFR_Result +{ + bool validLane; + cv::Mat rawImage; + cv::Mat processedImage; + FrameData data; + std::vector motorSignals; +}; + class LFR { +public: + using ListenerKey = void const*; + using ExceptionCallback = std::function; + using ListenerCallback = std::function; + +private: + using ListenerPair = std::pair; + using ListenerVector = std::vector; + Input input; Processing processing; ControlModule controlModule; Interpreter interpreter; IntersectionHandler intersectionHandler; - volatile bool iAmLooping; - void loop(); - thread loopThread; + int thresholdBinary; int gaussKernelSize; - void provideOutput(Mat originalImage, Mat processedImage, const FrameData& frameData, const Rect& roi); + ListenerVector listeners; + ExceptionCallback cb; + bool stop; + std::unique_ptr thread; + mutable std::mutex mutex; + + //void provideOutput(Mat originalImage, Mat processedImage, const FrameData& frameData, const Rect& roi); + void createThread(); + void setStop(bool val); public: - LFR() = delete; - LFR(int videoHeight, int videoWidth, int thresholdBinary, int gaussKernelSize); + LFR(int videoHeight, int videoWidth, int thresholdBinary, int gaussKernelSize, ExceptionCallback cb); ~LFR(); void startLoop(); 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); }; \ No newline at end of file