459 lines
16 KiB
Plaintext
459 lines
16 KiB
Plaintext
//
|
|
// Wrapper.mm
|
|
// ARTagTest
|
|
//
|
|
// Created by Raghulraj Palanisamy on 19/05/24.
|
|
//
|
|
#ifdef __cplusplus
|
|
#import <opencv2/opencv.hpp>
|
|
#import <opencv2/core.hpp>
|
|
#import <opencv2/videoio/cap_ios.h>
|
|
#import <opencv2/imgcodecs/ios.h>
|
|
#import <opencv2/imgproc/imgproc.hpp>
|
|
#import <opencv2/aruco.h>
|
|
#include <opencv2/imgproc.hpp>
|
|
#include <opencv2/core/types.hpp>
|
|
// #include "aruco.hpp"
|
|
#import <opencv2/calib3d.hpp>
|
|
#endif
|
|
|
|
#import "Wrapper.h"
|
|
#import <UIKit/UIKit.h>
|
|
#import <SceneKit/SceneKit.h>
|
|
#import <CoreVideo/CoreVideo.h>
|
|
#import <CoreMedia/CoreMedia.h>
|
|
|
|
using namespace cv;
|
|
using namespace std;
|
|
|
|
|
|
@implementation Wrapper {
|
|
cv::Mat gtpl;
|
|
}
|
|
|
|
+ (NSString *)openCVVersionString {
|
|
return [NSString stringWithFormat:@"ARTAG Library Version %s", CV_VERSION];
|
|
}
|
|
|
|
|
|
+ (UIImage *)processImage:(UIImage *)image {
|
|
cv::Mat cvImage;
|
|
UIImageToMat(image, cvImage);
|
|
|
|
// Check if the image is valid
|
|
if (cvImage.empty()) {
|
|
NSLog(@"Error: Image is empty");
|
|
return image;
|
|
}
|
|
|
|
// Perform OpenCV operations on cvImage
|
|
cv::Mat grayImage;
|
|
cv::cvtColor(cvImage, grayImage, cv::COLOR_BGR2GRAY);
|
|
|
|
// ArUco marker detection
|
|
std::vector<int> markerIds;
|
|
std::vector<std::vector<cv::Point2f>> markerCorners, rejectedCandidates;
|
|
|
|
cv::aruco::DetectorParameters detectorParams = cv::aruco::DetectorParameters();
|
|
cv::aruco::Dictionary dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_7X7_1000);
|
|
cv::aruco::ArucoDetector detector(dictionary, detectorParams);
|
|
detector.detectMarkers(grayImage, markerCorners, markerIds, rejectedCandidates);
|
|
|
|
if (!markerIds.empty()) {
|
|
NSLog(@"Detected ArTag IDs:");
|
|
for (int markerId : markerIds) {
|
|
NSLog(@"%d", markerId);
|
|
}
|
|
} else {
|
|
|
|
}
|
|
|
|
cv::Mat outputImage = grayImage.clone();
|
|
// Draw detected markers
|
|
if (!markerIds.empty()) {
|
|
//cv::aruco::drawDetectedMarkers(cvImage, markerCorners, markerIds);
|
|
|
|
|
|
cv::aruco::drawDetectedMarkers(outputImage, markerCorners, markerIds);
|
|
NSLog(@"Detected ArTag Marker");
|
|
|
|
// Draw custom text above each marker
|
|
for (size_t i = 0; i < markerIds.size(); i++) {
|
|
cv::Point2f corner = markerCorners[i][0]; // Use the first corner for positioning the text
|
|
std::string text = std::to_string(markerIds[i]); // Your custom text here
|
|
int fontFace = cv::FONT_HERSHEY_SIMPLEX;
|
|
double fontScale = 1.0;
|
|
int thickness = 2;
|
|
cv::Point textPosition(corner.x, corner.y - 20); // Adjust the Y-coordinate as needed
|
|
cv::Scalar textColor(0, 0, 255); // Red color for text
|
|
cv::putText(outputImage, text, textPosition, fontFace, fontScale, textColor, thickness);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// Example: Save the processed image
|
|
//UIImage *processedImage = MatToUIImage(grayImage);
|
|
// UIImageWriteToSavedPhotosAlbum(processedImage, nil, nil, nil);
|
|
// Convert processed Mat back to UIImage
|
|
UIImage *processedImage = MatToUIImage(outputImage);
|
|
return processedImage;
|
|
}
|
|
|
|
|
|
+ (NSArray *)detectMarkers:(UIImage *)image {
|
|
cv::Mat cvImage;
|
|
UIImageToMat(image, cvImage);
|
|
|
|
// Check if the image is valid
|
|
if (cvImage.empty()) {
|
|
NSLog(@"Error: Image is empty");
|
|
return nil;
|
|
}
|
|
|
|
// Convert the image to grayscale
|
|
cv::Mat grayImage;
|
|
cv::cvtColor(cvImage, grayImage, cv::COLOR_BGR2GRAY);
|
|
|
|
// ArUco marker detection
|
|
std::vector<int> markerIds;
|
|
std::vector<std::vector<cv::Point2f>> markerCorners, rejectedCandidates;
|
|
|
|
// Set up the ArUco detector
|
|
cv::aruco::DetectorParameters detectorParams = cv::aruco::DetectorParameters();
|
|
cv::aruco::Dictionary dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_7X7_1000);
|
|
cv::aruco::ArucoDetector detector(dictionary, detectorParams);
|
|
detector.detectMarkers(grayImage, markerCorners, markerIds, rejectedCandidates);
|
|
|
|
cv::Mat outputImage = grayImage.clone();
|
|
cv::aruco::drawDetectedMarkers(outputImage, markerCorners, markerIds);
|
|
|
|
if (!markerIds.empty()) {
|
|
NSLog(@"Detected ArTag IDs:");
|
|
for (int markerId : markerIds) {
|
|
NSLog(@"%d", markerId);
|
|
}
|
|
}
|
|
|
|
NSMutableArray *result = [NSMutableArray array];
|
|
for (size_t i = 0; i < markerIds.size(); i++) {
|
|
NSMutableDictionary *marker = [NSMutableDictionary dictionary];
|
|
marker[@"id"] = @(markerIds[i]);
|
|
NSMutableArray *corners = [NSMutableArray array];
|
|
for (const auto &corner : markerCorners[i]) {
|
|
[corners addObject:[NSValue valueWithCGPoint:CGPointMake(corner.x, corner.y)]];
|
|
}
|
|
marker[@"corners"] = corners;
|
|
[result addObject:marker];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
+ (NSDictionary *)drawDetectedMarkersOnImage:(UIImage *)image startScan:(BOOL)startScan CARD_SCANNING_TYPE:(int)CARD_SCANNING_TYPE{
|
|
cv::Mat colorImageRGBA;
|
|
cv::Mat colorImage;
|
|
cv::Mat grayImage;
|
|
cv::Mat camMatrix = cv::Mat::eye(3, 3, CV_64FC1);;
|
|
cv::Mat distCoeffs = cv::Mat::zeros(5, 1, CV_64FC1);
|
|
float markerLength = 0.1; // Adjust marker length based on your setup
|
|
|
|
// Set coordinate system
|
|
cv::Mat objPoints(4, 1, CV_32FC3);
|
|
objPoints.ptr<Vec3f>(0)[0] = Vec3f(-markerLength/2.f, markerLength/2.f, 0);
|
|
objPoints.ptr<Vec3f>(0)[1] = Vec3f(markerLength/2.f, markerLength/2.f, 0);
|
|
objPoints.ptr<Vec3f>(0)[2] = Vec3f(markerLength/2.f, -markerLength/2.f, 0);
|
|
objPoints.ptr<Vec3f>(0)[3] = Vec3f(-markerLength/2.f, -markerLength/2.f, 0);
|
|
|
|
UIImageToMat(image, colorImageRGBA);
|
|
NSMutableArray *markerIdsArray = [NSMutableArray array];
|
|
NSMutableArray *resultsArray = [NSMutableArray array];
|
|
// Check if the image is valid
|
|
if (colorImageRGBA.empty()) {
|
|
NSLog(@"Error: Image is empty");
|
|
//return image;
|
|
return @{@"image": image, @"markerIds": markerIdsArray};
|
|
}
|
|
|
|
// Convert the image to grayscale
|
|
|
|
cv::cvtColor(colorImageRGBA, grayImage, cv::COLOR_BGR2GRAY);
|
|
cv::cvtColor(colorImageRGBA, colorImage, cv::COLOR_RGBA2RGB);
|
|
|
|
|
|
// ArUco marker detection
|
|
std::vector<int> markerIds;
|
|
std::vector<std::vector<cv::Point2f>> markerCorners, rejectedCandidates;
|
|
|
|
// Set up the ArUco detector
|
|
cv::aruco::DetectorParameters detectorParams = cv::aruco::DetectorParameters();
|
|
cv::aruco::Dictionary dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_7X7_1000);
|
|
cv::aruco::ArucoDetector detector(dictionary, detectorParams);
|
|
detector.detectMarkers(colorImage, markerCorners, markerIds, rejectedCandidates);
|
|
|
|
size_t nMarkers = markerCorners.size();
|
|
//vector<Vec3d> rvecs(nMarkers), tvecs(nMarkers);
|
|
|
|
|
|
if (!markerIds.empty() && startScan) {
|
|
|
|
// for (size_t i = 0; i < nMarkers; i++) {
|
|
// solvePnP(objPoints, markerCorners.at(i), camMatrix, distCoeffs, rvecs.at(i), tvecs.at(i));
|
|
// }
|
|
|
|
cv::aruco::drawDetectedMarkers(colorImage, markerCorners, markerIds);
|
|
|
|
|
|
for (int markerId : markerIds) {
|
|
[markerIdsArray addObject:@(markerId)];
|
|
}
|
|
vector<cv::Vec3d> rvecs, tvecs;
|
|
|
|
// NSLog(@"Detected ArTag IDs:");
|
|
// for (int markerId : markerIds) {
|
|
// NSLog(@"%d", markerId);
|
|
// }
|
|
if (!camMatrix.empty() && !distCoeffs.empty()) {
|
|
cv::aruco::estimatePoseSingleMarkers(markerCorners, markerLength, camMatrix, distCoeffs, rvecs, tvecs);
|
|
|
|
if (markerCorners.size() > 0) {
|
|
|
|
for (size_t i =0; i< markerCorners.size(); i++ ){
|
|
std::vector<cv::Point2f>& corners = markerCorners[i]; // Reference to the i-th marker's corners
|
|
|
|
CGPoint topLeft = topLeftCorner(corners);
|
|
CGPoint topRight = topRightCorner(corners);
|
|
CGPoint bottomLeft = bottomLeftCorner(corners);
|
|
CGPoint bottomRight = bottomRightCorner(corners);
|
|
|
|
// Example printing results
|
|
// NSLog(@"Top Left: (%f, %f)", topLeft.x, topLeft.y);
|
|
// NSLog(@"Top Right: (%f, %f)", topRight.x, topRight.y);
|
|
// NSLog(@"Bottom Left: (%f, %f)", bottomLeft.x, bottomLeft.y);
|
|
// NSLog(@"Bottom Right: (%f, %f)", bottomRight.x, bottomRight.y);
|
|
|
|
// Calculate center points
|
|
double centerX = findCenterX(topLeft, topRight, bottomLeft, bottomRight);
|
|
double centerY = findCenterY(topLeft, topRight, bottomLeft, bottomRight);
|
|
|
|
|
|
int angle = onCalculateAngle(corners, 0);
|
|
|
|
NSLog(@"Angle: %d", angle);
|
|
|
|
NSString* options = onFindObjectiveOptions(angle, markerIds[i]); // to find the Objective Options from single marker
|
|
|
|
NSLog(@"Objective Options: %@", options);
|
|
NSLog(@"Objective ID: %d", markerIds[i]);
|
|
|
|
NSDictionary *markerDict = @{
|
|
@"card_id": @(markerIds[i]),
|
|
@"answer":options
|
|
};
|
|
NSLog(@"markerDict: %@", markerDict);
|
|
|
|
[resultsArray addObject:markerDict];
|
|
|
|
// Display options on the image
|
|
if (CARD_SCANNING_TYPE == 1 || CARD_SCANNING_TYPE == 2){
|
|
displayOptions(colorImage, options, centerX, centerY);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
displayMarkerID(colorImage, topLeft, markerIds[i]);
|
|
|
|
// Iterate through corners of the i-th marker
|
|
for (size_t j = 0; j < corners.size(); j++) {
|
|
const cv::Point2f& corner = corners[j]; // Access the j-th corner point
|
|
|
|
// Convert cv::Point2f to CGPoint using helper function
|
|
CGPoint point = convertPoint2fToCGPoint(corner);
|
|
|
|
// Use the CGPoint as needed (e.g., store in an NSArray, output to console, etc.)
|
|
// NSLog(@"Marker %zu, Point %zu: (%f, %f)", i, j, point.x, point.y);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < markerIds.size(); i++) {
|
|
|
|
// NSLog(@"Marker ID %d: rvec = (%f, %f, %f), tvec = (%f, %f, %f)",
|
|
// markerIds[i], rvecs[i][0], rvecs[i][1], rvecs[i][2],
|
|
// tvecs[i][0], tvecs[i][1], tvecs[i][2]);
|
|
|
|
// cv::drawFrameAxes(colorImage, camMatrix, distCoeffs, rvecs[i], tvecs[i], markerLength * 1.5f, 2);
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
UIImage *processedImage = MatToUIImage(colorImage);
|
|
//return processedImage;
|
|
return @{@"image": processedImage, @"markerIds": markerIdsArray, @"resultsArray": resultsArray};
|
|
}
|
|
|
|
|
|
// Function to convert cv::Point2f to CGPoint
|
|
CGPoint convertPoint2fToCGPoint(const cv::Point2f& point2f) {
|
|
float x = point2f.x;
|
|
float y = point2f.y;
|
|
return CGPointMake(x, y);
|
|
}
|
|
|
|
|
|
int onCalculateAngle(const std::vector<cv::Point2f>& corners, int trigger) {
|
|
if (corners.size() >= 2) {
|
|
cv::Point2f pt1 = corners[0];
|
|
cv::Point2f pt2 = corners[1];
|
|
|
|
double x = pt2.x - pt1.x;
|
|
double y = pt2.y - pt1.y;
|
|
|
|
double angle = atan2(y, x) * 180 / M_PI; // Convert radians to degrees
|
|
|
|
int angleInt = 0;
|
|
if (angle > 0) {
|
|
angleInt = (int)angle; // Angle from 0 to 180 degrees
|
|
} else if (angle < 0) {
|
|
angleInt = 180 + (180 + (int)angle); // Angle from -180 to -1 degrees
|
|
}
|
|
|
|
return angleInt;
|
|
} else {
|
|
NSLog(@"Insufficient corners provided");
|
|
return 0; // Return default angle or handle error
|
|
}
|
|
}
|
|
|
|
|
|
NSString* onFindObjectiveOptions(int angle, int id) {
|
|
if (angle > 46 && angle < 135) { // find the East
|
|
return @"B"; // B East
|
|
} else if (angle > 136 && angle < 225) { // find the North
|
|
return @"C"; //C North
|
|
} else if (angle > 226 && angle < 315) { // find the West
|
|
return @"D"; // D West
|
|
} else if (angle > 316 || angle < 45) { // find the South
|
|
return @"A"; // A South
|
|
}
|
|
return @""; // Default empty string if no condition is met
|
|
}
|
|
|
|
|
|
// Function to find the top left corner from marker corners
|
|
CGPoint topLeftCorner(const std::vector<cv::Point2f>& corners) {
|
|
if (corners.size() > 0) {
|
|
float x = corners[0].x;
|
|
float y = corners[0].y;
|
|
NSLog(@"Top Left Corner: (%f, %f)", x, y);
|
|
return CGPointMake(x, y);
|
|
} else {
|
|
NSLog(@"Invalid corner data");
|
|
return CGPointMake(0, 0); // Return default point or handle error
|
|
}
|
|
}
|
|
|
|
// Function to find the top right corner from marker corners
|
|
CGPoint topRightCorner(const std::vector<cv::Point2f>& corners) {
|
|
if (corners.size() > 1) {
|
|
float x = corners[1].x;
|
|
float y = corners[1].y;
|
|
NSLog(@"Top Right Corner: (%f, %f)", x, y);
|
|
return CGPointMake(x, y);
|
|
} else {
|
|
NSLog(@"Invalid corner data");
|
|
return CGPointMake(0, 0); // Return default point or handle error
|
|
}
|
|
}
|
|
|
|
// Function to find the bottom left corner from marker corners
|
|
CGPoint bottomLeftCorner(const std::vector<cv::Point2f>& corners) {
|
|
if (corners.size() > 2) {
|
|
float x = corners[2].x;
|
|
float y = corners[2].y;
|
|
NSLog(@"Bottom Left Corner: (%f, %f)", x, y);
|
|
return CGPointMake(x, y);
|
|
} else {
|
|
NSLog(@"Invalid corner data");
|
|
return CGPointMake(0, 0); // Return default point or handle error
|
|
}
|
|
}
|
|
|
|
// Function to find the bottom right corner from marker corners
|
|
CGPoint bottomRightCorner(const std::vector<cv::Point2f>& corners) {
|
|
if (corners.size() > 3) {
|
|
float x = corners[3].x;
|
|
float y = corners[3].y;
|
|
NSLog(@"Bottom Right Corner: (%f, %f)", x, y);
|
|
return CGPointMake(x, y);
|
|
} else {
|
|
NSLog(@"Invalid corner data");
|
|
return CGPointMake(0, 0); // Return default point or handle error
|
|
}
|
|
}
|
|
|
|
// Function to find the centerX based on the corners
|
|
double findCenterX(CGPoint topLeft, CGPoint topRight, CGPoint bottomLeft, CGPoint bottomRight) {
|
|
double centerXTotal = topLeft.x + topRight.x + bottomLeft.x + bottomRight.x;
|
|
return centerXTotal / 4;
|
|
}
|
|
|
|
// Function to find the centerY based on the corners
|
|
double findCenterY(CGPoint topLeft, CGPoint topRight, CGPoint bottomLeft, CGPoint bottomRight) {
|
|
double centerYTotal = topLeft.y + topRight.y + bottomLeft.y + bottomRight.y;
|
|
return centerYTotal / 4;
|
|
}
|
|
|
|
|
|
void displayMarkerID(cv::Mat& image, CGPoint topLeftCorner, int markerID) {
|
|
//cv::Point2f topLeftCorner = corners[0];
|
|
int fontFace = cv::FONT_HERSHEY_SIMPLEX;
|
|
double fontScale = 1;
|
|
int thickness = 2;
|
|
cv::Scalar color(0, 255, 0); // Green color for the text
|
|
|
|
// Draw the text
|
|
std::string text = std::to_string(markerID);
|
|
int baseline;
|
|
cv::Size textSize = cv::getTextSize(text, fontFace, fontScale, thickness, &baseline);
|
|
|
|
// Position the text at the top-left corner of the marker
|
|
cv::Point textOrg(topLeftCorner.x, topLeftCorner.y - textSize.height);
|
|
|
|
// Draw the text on the image
|
|
cv::putText(image, text, textOrg, fontFace, fontScale, color, thickness);
|
|
}
|
|
|
|
// Function to display options on the image
|
|
void displayOptions(cv::Mat &rgb, NSString *onFindObjectiveAnswer, double centerX, double centerY) {
|
|
// Convert NSString to std::string
|
|
std::string text([onFindObjectiveAnswer UTF8String]);
|
|
|
|
// Position for the text
|
|
cv::Point textOrg(centerX - 15, centerY + 15);
|
|
|
|
// Font parameters
|
|
int fontFace = cv::FONT_HERSHEY_COMPLEX;
|
|
double fontScale = 2;
|
|
cv::Scalar color(0, 255, 0); // Green color
|
|
int thickness = 2;
|
|
|
|
// Put the text on the image
|
|
cv::putText(rgb, text, textOrg, fontFace, fontScale, color, thickness);
|
|
}
|
|
|
|
@end
|
|
|
|
|