Spaces:
Running
on
Zero
Running
on
Zero
// base64 to image | |
/// Parameters used in the API | |
struct APIParams { | |
/// A list of images, base64 encoded | |
std::vector<std::string> data; | |
/// The maximum number of keypoints to detect for each image | |
std::vector<int> max_keypoints; | |
/// The timestamps of the images | |
std::vector<std::string> timestamps; | |
/// Whether to convert the images to grayscale | |
bool grayscale; | |
/// The height and width of each image | |
std::vector<std::vector<int>> image_hw; | |
/// The type of feature detector to use | |
int feature_type; | |
/// The rotations of the images | |
std::vector<double> rotates; | |
/// The scales of the images | |
std::vector<double> scales; | |
/// The reference points of the images | |
std::vector<std::vector<float>> reference_points; | |
/// Whether to binarize the descriptors | |
bool binarize; | |
}; | |
/** | |
* @brief Contains the results of a keypoint detector. | |
* | |
* @details Stores the keypoints and descriptors for each image. | |
*/ | |
class KeyPointResults { | |
public: | |
KeyPointResults() { | |
} | |
/** | |
* @brief Constructor. | |
* | |
* @param kp The keypoints for each image. | |
*/ | |
KeyPointResults(const std::vector<std::vector<cv::KeyPoint>>& kp, | |
const std::vector<cv::Mat>& desc) | |
: keypoints(kp), descriptors(desc) { | |
} | |
/** | |
* @brief Append keypoints to the result. | |
* | |
* @param kpts The keypoints to append. | |
*/ | |
inline void append_keypoints(std::vector<cv::KeyPoint>& kpts) { | |
keypoints.emplace_back(kpts); | |
} | |
/** | |
* @brief Append descriptors to the result. | |
* | |
* @param desc The descriptors to append. | |
*/ | |
inline void append_descriptors(cv::Mat& desc) { | |
descriptors.emplace_back(desc); | |
} | |
/** | |
* @brief Get the keypoints. | |
* | |
* @return The keypoints. | |
*/ | |
inline std::vector<std::vector<cv::KeyPoint>> get_keypoints() { | |
return keypoints; | |
} | |
/** | |
* @brief Get the descriptors. | |
* | |
* @return The descriptors. | |
*/ | |
inline std::vector<cv::Mat> get_descriptors() { | |
return descriptors; | |
} | |
private: | |
std::vector<std::vector<cv::KeyPoint>> keypoints; | |
std::vector<cv::Mat> descriptors; | |
std::vector<std::vector<float>> scores; | |
}; | |
/** | |
* @brief Decodes a base64 encoded string. | |
* | |
* @param base64 The base64 encoded string to decode. | |
* @return The decoded string. | |
*/ | |
std::string base64_decode(const std::string& base64) { | |
using namespace boost::archive::iterators; | |
using It = transform_width<binary_from_base64<std::string::const_iterator>, 8, 6>; | |
// Find the position of the last non-whitespace character | |
auto end = base64.find_last_not_of(" \t\n\r"); | |
if (end != std::string::npos) { | |
// Move one past the last non-whitespace character | |
end += 1; | |
} | |
// Decode the base64 string and return the result | |
return std::string(It(base64.begin()), It(base64.begin() + end)); | |
} | |
/** | |
* @brief Decodes a base64 string into an OpenCV image | |
* | |
* @param base64 The base64 encoded string | |
* @return The decoded OpenCV image | |
*/ | |
cv::Mat base64_to_image(const std::string& base64) { | |
// Decode the base64 string | |
std::string decodedStr = base64_decode(base64); | |
// Decode the image | |
std::vector<uchar> data(decodedStr.begin(), decodedStr.end()); | |
cv::Mat img = cv::imdecode(data, cv::IMREAD_GRAYSCALE); | |
// Check for errors | |
if (img.empty()) { | |
throw std::runtime_error("Failed to decode image"); | |
} | |
return img; | |
} | |
/** | |
* @brief Encodes an OpenCV image into a base64 string | |
* | |
* This function takes an OpenCV image and encodes it into a base64 string. | |
* The image is first encoded as a PNG image, and then the resulting | |
* bytes are encoded as a base64 string. | |
* | |
* @param img The OpenCV image | |
* @return The base64 encoded string | |
* | |
* @throws std::runtime_error if the image is empty or encoding fails | |
*/ | |
std::string image_to_base64(cv::Mat& img) { | |
if (img.empty()) { | |
throw std::runtime_error("Failed to read image"); | |
} | |
// Encode the image as a PNG | |
std::vector<uchar> buf; | |
if (!cv::imencode(".png", img, buf)) { | |
throw std::runtime_error("Failed to encode image"); | |
} | |
// Encode the bytes as a base64 string | |
using namespace boost::archive::iterators; | |
using It = | |
base64_from_binary<transform_width<std::vector<uchar>::const_iterator, 6, 8>>; | |
std::string base64(It(buf.begin()), It(buf.end())); | |
// Pad the string with '=' characters to a multiple of 4 bytes | |
base64.append((3 - buf.size() % 3) % 3, '='); | |
return base64; | |
} | |
/** | |
* @brief Callback function for libcurl to write data to a string | |
* | |
* This function is used as a callback for libcurl to write data to a string. | |
* It takes the contents, size, and nmemb as parameters, and writes the data to | |
* the string. | |
* | |
* @param contents The data to write | |
* @param size The size of the data | |
* @param nmemb The number of members in the data | |
* @param s The string to write the data to | |
* @return The number of bytes written | |
*/ | |
size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* s) { | |
size_t newLength = size * nmemb; | |
try { | |
// Resize the string to fit the new data | |
s->resize(s->size() + newLength); | |
} catch (std::bad_alloc& e) { | |
// If there's an error allocating memory, return 0 | |
return 0; | |
} | |
// Copy the data to the string | |
std::copy(static_cast<const char*>(contents), | |
static_cast<const char*>(contents) + newLength, | |
s->begin() + s->size() - newLength); | |
return newLength; | |
} | |
// Helper functions | |
/** | |
* @brief Helper function to convert a type to a Json::Value | |
* | |
* This function takes a value of type T and converts it to a Json::Value. | |
* It is used to simplify the process of converting a type to a Json::Value. | |
* | |
* @param val The value to convert | |
* @return The converted Json::Value | |
*/ | |
template <typename T> Json::Value toJson(const T& val) { | |
return Json::Value(val); | |
} | |
/** | |
* @brief Converts a vector to a Json::Value | |
* | |
* This function takes a vector of type T and converts it to a Json::Value. | |
* Each element in the vector is appended to the Json::Value array. | |
* | |
* @param vec The vector to convert to Json::Value | |
* @return The Json::Value representing the vector | |
*/ | |
template <typename T> Json::Value vectorToJson(const std::vector<T>& vec) { | |
Json::Value json(Json::arrayValue); | |
for (const auto& item : vec) { | |
json.append(item); | |
} | |
return json; | |
} | |
/** | |
* @brief Converts a nested vector to a Json::Value | |
* | |
* This function takes a nested vector of type T and converts it to a | |
* Json::Value. Each sub-vector is converted to a Json::Value array and appended | |
* to the main Json::Value array. | |
* | |
* @param vec The nested vector to convert to Json::Value | |
* @return The Json::Value representing the nested vector | |
*/ | |
template <typename T> | |
Json::Value nestedVectorToJson(const std::vector<std::vector<T>>& vec) { | |
Json::Value json(Json::arrayValue); | |
for (const auto& subVec : vec) { | |
json.append(vectorToJson(subVec)); | |
} | |
return json; | |
} | |
/** | |
* @brief Converts the APIParams struct to a Json::Value | |
* | |
* This function takes an APIParams struct and converts it to a Json::Value. | |
* The Json::Value is a JSON object with the following fields: | |
* - data: a JSON array of base64 encoded images | |
* - max_keypoints: a JSON array of integers, max number of keypoints for each | |
* image | |
* - timestamps: a JSON array of timestamps, one for each image | |
* - grayscale: a JSON boolean, whether to convert images to grayscale | |
* - image_hw: a nested JSON array, each sub-array contains the height and width | |
* of an image | |
* - feature_type: a JSON integer, the type of feature detector to use | |
* - rotates: a JSON array of doubles, the rotation of each image | |
* - scales: a JSON array of doubles, the scale of each image | |
* - reference_points: a nested JSON array, each sub-array contains the | |
* reference points of an image | |
* - binarize: a JSON boolean, whether to binarize the descriptors | |
* | |
* @param params The APIParams struct to convert | |
* @return The Json::Value representing the APIParams struct | |
*/ | |
Json::Value paramsToJson(const APIParams& params) { | |
Json::Value json; | |
json["data"] = vectorToJson(params.data); | |
json["max_keypoints"] = vectorToJson(params.max_keypoints); | |
json["timestamps"] = vectorToJson(params.timestamps); | |
json["grayscale"] = toJson(params.grayscale); | |
json["image_hw"] = nestedVectorToJson(params.image_hw); | |
json["feature_type"] = toJson(params.feature_type); | |
json["rotates"] = vectorToJson(params.rotates); | |
json["scales"] = vectorToJson(params.scales); | |
json["reference_points"] = nestedVectorToJson(params.reference_points); | |
json["binarize"] = toJson(params.binarize); | |
return json; | |
} | |
template <typename T> cv::Mat jsonToMat(Json::Value json) { | |
int rows = json.size(); | |
int cols = json[0].size(); | |
// Create a single array to hold all the data. | |
std::vector<T> data; | |
data.reserve(rows * cols); | |
for (int i = 0; i < rows; i++) { | |
for (int j = 0; j < cols; j++) { | |
data.push_back(static_cast<T>(json[i][j].asInt())); | |
} | |
} | |
// Create a cv::Mat object that points to the data. | |
cv::Mat mat(rows, cols, CV_8UC1, | |
data.data()); // Change the type if necessary. | |
// cv::Mat mat(cols, rows,CV_8UC1, data.data()); // Change the type if | |
// necessary. | |
return mat; | |
} | |
/** | |
* @brief Decodes the response of the server and prints the keypoints | |
* | |
* This function takes the response of the server, a JSON string, and decodes | |
* it. It then prints the keypoints and draws them on the original image. | |
* | |
* @param response The response of the server | |
* @return The keypoints and descriptors | |
*/ | |
KeyPointResults decode_response(const std::string& response, bool viz = true) { | |
Json::CharReaderBuilder builder; | |
Json::CharReader* reader = builder.newCharReader(); | |
Json::Value jsonData; | |
std::string errors; | |
// Parse the JSON response | |
bool parsingSuccessful = reader->parse( | |
response.c_str(), response.c_str() + response.size(), &jsonData, &errors); | |
delete reader; | |
if (!parsingSuccessful) { | |
// Handle error | |
std::cout << "Failed to parse the JSON, errors:" << std::endl; | |
std::cout << errors << std::endl; | |
return KeyPointResults(); | |
} | |
KeyPointResults kpts_results; | |
// Iterate over the images | |
for (const auto& jsonItem : jsonData) { | |
auto jkeypoints = jsonItem["keypoints"]; | |
auto jkeypoints_orig = jsonItem["keypoints_orig"]; | |
auto jdescriptors = jsonItem["descriptors"]; | |
auto jscores = jsonItem["scores"]; | |
auto jimageSize = jsonItem["image_size"]; | |
auto joriginalSize = jsonItem["original_size"]; | |
auto jsize = jsonItem["size"]; | |
std::vector<cv::KeyPoint> vkeypoints; | |
std::vector<float> vscores; | |
// Iterate over the keypoints | |
int counter = 0; | |
for (const auto& keypoint : jkeypoints_orig) { | |
if (counter < 10) { | |
// Print the first 10 keypoints | |
std::cout << keypoint[0].asFloat() << ", " << keypoint[1].asFloat() | |
<< std::endl; | |
} | |
counter++; | |
// Convert the Json::Value to a cv::KeyPoint | |
vkeypoints.emplace_back( | |
cv::KeyPoint(keypoint[0].asFloat(), keypoint[1].asFloat(), 0.0)); | |
} | |
if (viz && jsonItem.isMember("image_orig")) { | |
auto jimg_orig = jsonItem["image_orig"]; | |
cv::Mat img = jsonToMat<uchar>(jimg_orig); | |
cv::imwrite("viz_image_orig.jpg", img); | |
// Draw keypoints on the image | |
cv::Mat imgWithKeypoints; | |
cv::drawKeypoints(img, vkeypoints, imgWithKeypoints, cv::Scalar(0, 0, 255)); | |
// Write the image with keypoints | |
std::string filename = "viz_image_orig_keypoints.jpg"; | |
cv::imwrite(filename, imgWithKeypoints); | |
} | |
// Iterate over the descriptors | |
cv::Mat descriptors = jsonToMat<uchar>(jdescriptors); | |
kpts_results.append_keypoints(vkeypoints); | |
kpts_results.append_descriptors(descriptors); | |
} | |
return kpts_results; | |
} | |