Skip to content

Commit

Permalink
Merge pull request #11 from rursprung/implement-first-version-of-apps
Browse files Browse the repository at this point in the history
implement first version of the apps + change API to be a class
  • Loading branch information
Nic822 authored Apr 4, 2024
2 parents 17072f8 + d646fce commit 3250342
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 46 deletions.
4 changes: 3 additions & 1 deletion apps/livecam/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
add_executable(banana-app-live main.cpp)

target_link_libraries(banana-app-static PRIVATE banana-lib)
target_link_libraries(banana-app-live
PRIVATE banana-lib
)
67 changes: 63 additions & 4 deletions apps/livecam/main.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,66 @@
#include <iostream>
#include <filesystem>
#include <stdexcept>
#include <format>

int main() {
// TODO: implement live camera feed
std::cerr << "not yet implemented!" << std::endl;
return 1;
#include <opencv2/opencv.hpp>

#include <banana-lib/lib.hpp>

const cv::Size kWindowSize = {768, 512};

[[nodiscard]]
auto GetVideoCaptureFromArgs(int const argc, char const * const argv[]) -> cv::VideoCapture {
if(argc > 2) {
throw std::runtime_error(std::format("expected 0 or 1 arguments but got {}!", argc-1));
} else if(argc == 2) {
try {
// numeric value => it's the index of a video device
return cv::VideoCapture{std::stoi(argv[1])};
} catch (std::invalid_argument const& ex) {
// non-numeric value => treat it as a path, url or similar and let OpenCV check if it's valid
return cv::VideoCapture{argv[1]};
}
} else { // argc == 1
return cv::VideoCapture{0};
}
}

void ShowAnalysisResult(banana::AnnotatedAnalysisResult const& analysis_result) {
std::string const windowName = "analysis result | press q to quit";
cv::namedWindow(windowName, cv::WINDOW_KEEPRATIO);
cv::imshow(windowName, analysis_result.annotated_image);
cv::resizeWindow(windowName, kWindowSize);
}

int main(int const argc, char const * const argv[]) {
banana::Analyzer const analyzer{};
try {
auto cap = GetVideoCaptureFromArgs(argc, argv);
if(!cap.isOpened()) {
std::cerr << "can't use camera" << std::endl;
return 1;
}

while (true) {
cv::Mat frame;
cap >> frame;
auto const analysisResult = analyzer.AnalyzeAndAnnotateImage(frame);

if (analysisResult) {
ShowAnalysisResult(analysisResult.value());
} else {
std::cerr << "failed to analyse the image: " << analysisResult.error().ToString() << std::endl;
return 1;
}

if (cv::pollKey() == 'q') {
return 0;
}
}
} catch (std::exception const& ex) {
std::cerr << ex.what() << std::endl;
std::cerr << "Usage: " << argv[0] << " [capture_device_id|video_path]" << std::endl;
return 1;
}
}
4 changes: 3 additions & 1 deletion apps/static-image/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
add_executable(banana-app-static main.cpp)

target_link_libraries(banana-app-static PRIVATE banana-lib)
target_link_libraries(banana-app-static
PRIVATE banana-lib
)
56 changes: 52 additions & 4 deletions apps/static-image/main.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,55 @@
#include <iostream>
#include <filesystem>
#include <stdexcept>
#include <format>

int main() {
// TODO: implement static image feed (either single pictures and/or a saved video)
std::cerr << "not yet implemented!" << std::endl;
return 1;
#include <opencv2/opencv.hpp>

#include <banana-lib/lib.hpp>

const cv::Size kWindowSize = {768, 512};

[[nodiscard]]
auto GetPathFromArgs(int const argc, char const * const argv[]) -> std::filesystem::path {
if(argc != 2) {
throw std::runtime_error(std::format("expected 1 argument but got {}!", argc-1));
}

auto const image_path = std::filesystem::path(argv[1]);
if (!std::filesystem::exists(image_path)) {
throw std::runtime_error(std::format("specified path does not exist: {}", image_path.string()));
}

return image_path;
}

void ShowAnalysisResult(banana::AnnotatedAnalysisResult const& analysis_result) {
std::string const windowName = "analysis result | press q to quit";
cv::namedWindow(windowName, cv::WINDOW_KEEPRATIO);
cv::imshow(windowName, analysis_result.annotated_image);
cv::resizeWindow(windowName, kWindowSize);
cv::waitKey();
}

int main(int const argc, char const * const argv[]) {
banana::Analyzer const analyzer{};
try {
auto const path = GetPathFromArgs(argc, argv);
auto const img = cv::imread(path.string());

auto const analysisResult = analyzer.AnalyzeAndAnnotateImage(img);

if(analysisResult) {
ShowAnalysisResult(analysisResult.value());
} else {
std::cerr << "failed to analyse the image: " << analysisResult.error().ToString() << std::endl;
return 1;
}
} catch (std::exception const& ex) {
std::cerr << ex.what() << std::endl;
std::cerr << "Usage: " << argv[0] << " [image_path]" << std::endl;
return 1;
}

return 0;
}
76 changes: 57 additions & 19 deletions include/banana-lib/lib.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,30 @@ namespace banana {

/**
* All errors which may occur during analysis of the image.
*
* Note that this is a class wrapping an enum (instead of an `enum class`) to be able to provide methods on the values.
*/
enum class AnalysisError {
/// the requested functionality has not yet been implemented. note: will be removed once implemented!
kNotYetImplementedError,
class AnalysisError {
public:
/**
* Implementation Detail. Use `AnalysisError` to access the enum constants and interact with them.
*/
enum Value {
/// the requested functionality has not yet been implemented. note: will be removed once implemented!
kNotYetImplementedError,
};

AnalysisError() = default;
constexpr AnalysisError(Value const value) : value(value) { }
constexpr explicit operator Value() const { return value; }
explicit operator bool() const = delete;

[[nodiscard]]
auto ToString() const -> std::string;

explicit operator std::string() const;
private:
Value value;
};

/**
Expand All @@ -35,23 +55,41 @@ namespace banana {
std::list<AnalysisResult> banana;
};

/**
* Analyse an image for the presence of bananas and their properties.
*
* @param image an image possibly containing bananas
* @return the analysis results for each banana which has been found. If no banana has been found this list is empty.
*/
[[nodiscard]]
auto AnalyzeImage(cv::Mat const& image) -> std::expected<std::list<AnalysisResult>, AnalysisError>;
class Analyzer {
public:
Analyzer() = default;

/**
* Analyse an image for the presence of bananas and their properties.
*
* @param image an image possibly containing bananas
* @return the result of the analysis and the annotated image, see the description of AnnotatedAnalysisResult for more details.
*/
[[nodiscard]]
auto AnalyzeAndAnnotateImage(cv::Mat const& image) -> std::expected<AnnotatedAnalysisResult, AnalysisError>;
/**
* Analyse an image for the presence of bananas and their properties.
*
* @param image an image possibly containing bananas
* @return the analysis results for each banana which has been found. If no banana has been found this list is empty.
*/
[[nodiscard]]
auto AnalyzeImage(cv::Mat const &image) const -> std::expected<std::list<AnalysisResult>, AnalysisError>;

/**
* Analyse an image for the presence of bananas and their properties.
*
* @param image an image possibly containing bananas
* @return the result of the analysis and the annotated image, see the description of AnnotatedAnalysisResult for more details.
*/
[[nodiscard]]
auto AnalyzeAndAnnotateImage(cv::Mat const &image) const -> std::expected<AnnotatedAnalysisResult, AnalysisError>;

private:
/**
* Annotate an image with the result from a previous analysis (the analysis must come from the same image).
* This is meant for visualisation to users and is not guaranteed to produce stable results.
*
* @param image a previously analysed image
* @param analysis_result the result of the previous analysis (done using AnalyzeImage).
* @return a copy of the original image with annotations.
* @see AnalyzeImage
*/
[[nodiscard]]
auto AnnotateImage(cv::Mat const& image, std::list<AnalysisResult> const& analysis_result) const -> cv::Mat;
};

}

Expand Down
4 changes: 3 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ target_include_directories(
PUBLIC "${OpenCV_INCLUDE_DIRS}"
)

target_link_libraries(banana-lib ${OpenCV_LIBS})
target_link_libraries(banana-lib
PUBLIC ${OpenCV_LIBS}
)
37 changes: 21 additions & 16 deletions src/lib.cpp
Original file line number Diff line number Diff line change
@@ -1,33 +1,38 @@
#include <stdexcept>

#include <banana-lib/lib.hpp>

namespace banana {

auto AnalyzeImage(cv::Mat const& image) -> std::expected<std::list<AnalysisResult>, AnalysisError> {
auto Analyzer::AnalyzeImage(cv::Mat const& image) const -> std::expected<std::list<AnalysisResult>, AnalysisError> {
return std::unexpected(AnalysisError::kNotYetImplementedError);
}

/**
* Annotate an image with the result from a previous analysis (the analysis must come from the same image).
* This is meant for visualisation to users and is not guaranteed to produce stable results.
*
* @param image a previously analysed image
* @param analysis_result the result of the previous analysis (done using AnalyzeImage).
* @return a copy of the original image with annotations.
* @see AnalyzeImage
*/
[[nodiscard]]
auto AnnotateImage(cv::Mat const& image, std::list<AnalysisResult> const& analysis_result) -> cv::Mat {
auto Analyzer::AnnotateImage(cv::Mat const& image, std::list<AnalysisResult> const& analysis_result) const -> cv::Mat {
auto annotated_image = cv::Mat{image};

// TODO: use analysis_result to annotate the image

return annotated_image;
}

auto AnalyzeAndAnnotateImage(cv::Mat const& image) -> std::expected<AnnotatedAnalysisResult, AnalysisError> {
return AnalyzeImage(image)
.and_then([&image](std::list<AnalysisResult> const& analysis_result) -> std::expected<AnnotatedAnalysisResult, AnalysisError> {
return AnnotatedAnalysisResult{AnnotateImage(image, analysis_result), analysis_result};
auto Analyzer::AnalyzeAndAnnotateImage(cv::Mat const& image) const -> std::expected<AnnotatedAnalysisResult, AnalysisError> {
return this->AnalyzeImage(image)
.and_then([&image, this](std::list<AnalysisResult> const& analysis_result) -> std::expected<AnnotatedAnalysisResult, AnalysisError> {
return AnnotatedAnalysisResult{this->AnnotateImage(image, analysis_result), analysis_result};
});
}

auto AnalysisError::ToString() const -> std::string {
switch(value) {
case kNotYetImplementedError:
return "not yet implemented!";
default:
throw std::runtime_error("unknown AnalysisError type!");
}
}

AnalysisError::operator std::string() const {
return this->ToString();
}
}

0 comments on commit 3250342

Please sign in to comment.