diff --git a/camCalib/camCalib.vcxproj b/camCalib/camCalib.vcxproj index 913e852..4e65280 100644 --- a/camCalib/camCalib.vcxproj +++ b/camCalib/camCalib.vcxproj @@ -147,11 +147,13 @@ + + diff --git a/camCalib/camCalib.vcxproj.filters b/camCalib/camCalib.vcxproj.filters index 6a84349..8908570 100644 --- a/camCalib/camCalib.vcxproj.filters +++ b/camCalib/camCalib.vcxproj.filters @@ -27,6 +27,9 @@ 源文件 + + 源文件 + @@ -38,5 +41,8 @@ 头文件 + + 头文件 + \ No newline at end of file diff --git a/camCalib/detectCB.onnx b/camCalib/detectCB.onnx new file mode 100644 index 0000000..3f04036 Binary files /dev/null and b/camCalib/detectCB.onnx differ diff --git a/camCalib/onnxDetector.cpp b/camCalib/onnxDetector.cpp new file mode 100644 index 0000000..9903c49 --- /dev/null +++ b/camCalib/onnxDetector.cpp @@ -0,0 +1,302 @@ +#include "onnxDetector.h" +#include + +using namespace std; +using namespace cv; +using namespace cv::dnn; + +class OnnxDetectorImpl : + public OnnxDetector +{ +public: + OnnxDetectorImpl() : + classNum_(1), imgStride_(32), confidence_(0.25f), + scoreThreshold_(0.45f), nmsThreshold_(0.3f){}; + ~OnnxDetectorImpl() {}; + +public: + virtual int loadOnnxModel(const char* filename, cv::Size2f inferSize) override; + virtual std::vector detect(cv::Mat image) override; + +private: + vector preProcess(Mat& input_image); + vector postProcess(Mat& input_image, vector& outputs); + +private: + int classNum_; + int imgStride_; + float confidence_; + float scoreThreshold_; + float nmsThreshold_; + cv::Size2f size_; + cv::dnn::Net network_; +}; + +void letterbox(const cv::Mat& image, cv::Mat& outImage, + const cv::Size& newShape = cv::Size(640, 640), + const cv::Scalar& color = cv::Scalar(114, 114, 114), + bool auto_ = true, + bool scaleFill = false, + bool scaleUp = true, + int stride = 32) +{ + cv::Size shape = image.size(); + float r = std::min((float)newShape.height / (float)shape.height, + (float)newShape.width / (float)shape.width); + if (!scaleUp) + r = std::min(r, 1.0f); + + float ratio[2]{ r, r }; + int newUnpad[2]{ (int)std::round((float)shape.width * r), + (int)std::round((float)shape.height * r) }; + + auto dw = (float)(newShape.width - newUnpad[0]); + auto dh = (float)(newShape.height - newUnpad[1]); + + if (auto_) + { + dw = (float)((int)dw % stride); + dh = (float)((int)dh % stride); + } + else if (scaleFill) + { + dw = 0.0f; + dh = 0.0f; + newUnpad[0] = newShape.width; + newUnpad[1] = newShape.height; + ratio[0] = (float)newShape.width / (float)shape.width; + ratio[1] = (float)newShape.height / (float)shape.height; + } + + dw /= 2.0f; + dh /= 2.0f; + + if (shape.width != newUnpad[0] && shape.height != newUnpad[1]) + { + cv::resize(image, outImage, cv::Size(newUnpad[0], newUnpad[1])); + } + + int top = int(std::round(dh - 0.1f)); + int bottom = int(std::round(dh + 0.1f)); + int left = int(std::round(dw - 0.1f)); + int right = int(std::round(dw + 0.1f)); + cv::copyMakeBorder(outImage, outImage, top, bottom, left, right, cv::BORDER_CONSTANT, color); +} + +// Ԥ +vector OnnxDetectorImpl::preProcess(Mat& input_image) +{ + Mat blob; + blobFromImage(input_image, blob, 1. / 255., Size(size_), Scalar(), false, false); + + network_.setInput(blob); + + vector names = network_.getUnconnectedOutLayersNames(); + + vector outputs; + network_.forward(outputs, names); + + return outputs; +} + +// +#if 1 +vector OnnxDetectorImpl::postProcess(Mat& input_image, vector& outputs) +{ + vector class_ids; + vector confidences; + vector boxes; + + float x_factor = input_image.cols / size_.width; + float y_factor = input_image.rows / size_.height; + + float* data = (float*)outputs[0].data; + + const int dimensions = 5 + classNum_; + //const int rows = 25200; + const int rows =(1 + 4 + 16) * (size_.width / imgStride_) * (size_.height / imgStride_); + for (int i = 0; i < rows; ++i) + { +#if 1 + float confidence = data[rows * 4 + i]; + if (confidence > confidence_) + { + int centerX = (int)(data[rows * 0 + i] * x_factor); + int centerY = (int)(data[rows * 1 + i] * y_factor); + int width = (int)(data[rows * 2 + i] * x_factor); + int height = (int)(data[rows * 3 + i] * y_factor); + int left = centerX - width / 2; + int top = centerY - height / 2; + + boxes.emplace_back(left, top, width, height); + confidences.emplace_back(confidence); + class_ids.emplace_back(0); + } +#else + float confidence = data[4]; + if (confidence >= confidence_) + { + float* classes_scores = data + 5; + Mat scores(1, classNum_, CV_32FC1, classes_scores); + Point class_id; + double max_class_score; + minMaxLoc(scores, 0, &max_class_score, 0, &class_id); + if (max_class_score > scoreThreshold_) + { + confidences.push_back(confidence); + class_ids.push_back(class_id.x); + + float cx = data[0]; + float cy = data[1]; + float w = data[2]; + float h = data[3]; + int left = int((cx - 0.5 * w) * x_factor); + int top = int((cy - 0.5 * h) * y_factor); + int width = int(w * x_factor); + int height = int(h * y_factor); + boxes.push_back(Rect(left, top, width, height)); + } + } +#endif + //data += dimensions; + } + + vector res2d; + vector indices; + NMSBoxes(boxes, confidences, scoreThreshold_, nmsThreshold_, indices); + for (int i = 0; i < indices.size(); i++) + { + int idx = indices[i]; + Rect box = boxes[idx]; + + int left = box.x; + int top = box.y; + int width = box.width; + int height = box.height; + //rectangle(output_image, Point(left, top), Point(left + width, top + height), BLUE, 3 * THICKNESS); + + //string label = format("%.2f", confidences[idx]); + //label = class_name[class_ids[idx]] + ":" + label; + //draw_label(output_image, label, left, top); + + Detection2d d2d; + d2d.bbox = box; + d2d.classIdx = idx; + d2d.confidence = confidences[idx]; + + res2d.push_back(d2d); + } + return res2d; +} +#else +inline float sigmoid(float x) +{ + return 1.f / (1.f + exp(-x)); +} + +vector OnnxDetectorImpl::postProcess(Mat& cv_src, vector& outs) +{ + std::vector classIds; + std::vector confidences; + std::vector boxes; + + int strides[] = { 8, 16, 32 }; + std::vector > anchors = { + { 10,13, 16,30, 33,23 }, + { 30,61, 62,45, 59,119 }, + { 116,90, 156,198, 373,326 } + }; + + for (size_t k = 0; k < outs.size(); k++) { + + float* data = outs[k].ptr(); + int stride = strides[k]; + int num_classes = outs[k].size[4] - 5; + for (int i = 0; i < outs[k].size[2]; i++) { + for (int j = 0; j < outs[k].size[3]; j++) { + for (int a = 0; a < outs[k].size[1]; ++a) { + + float* record = data + a * outs[k].size[2] * outs[k].size[3] * outs[k].size[4] + + i * outs[k].size[3] * outs[k].size[4] + j * outs[k].size[4]; + float* cls_ptr = record + 5; + for (int cls = 0; cls < num_classes; cls++) { + + float score = sigmoid(cls_ptr[cls]) * sigmoid(record[4]); + if (score > scoreThreshold_) { + + float cx = (sigmoid(record[0]) * 2.f - 0.5f + (float)j) * (float)stride; + float cy = (sigmoid(record[1]) * 2.f - 0.5f + (float)i) * (float)stride; + float w = pow(sigmoid(record[2]) * 2.f, 2) * anchors[k][2 * a]; + float h = pow(sigmoid(record[3]) * 2.f, 2) * anchors[k][2 * a + 1]; + float x1 = std::max(0, std::min(cv_src.cols, int((cx - w / 2.f) * (float)cv_src.cols / (float)size_.width))); + float y1 = std::max(0, std::min(cv_src.rows, int((cy - h / 2.f) * (float)cv_src.rows / (float)size_.width))); + float x2 = std::max(0, std::min(cv_src.cols, int((cx + w / 2.f) * (float)cv_src.cols / (float)size_.width))); + float y2 = std::max(0, std::min(cv_src.rows, int((cy + h / 2.f) * (float)cv_src.rows / (float)size_.width))); + classIds.push_back(cls); + confidences.push_back(score); + boxes.push_back(cv::Rect(cv::Point(x1, y1), cv::Point(x2, y2))); + } + } + } + } + } + } + + vector res2d; + std::vector indices; + cv::dnn::NMSBoxes(boxes, confidences, scoreThreshold_, nmsThreshold_, indices); + for (size_t i = 0; i < indices.size(); i++){ + + int idx = indices[i]; + cv::Rect box = boxes[idx]; + //drawPred(classIds[idx], confidences[idx], box.x, box.y, + // box.x + box.width, box.y + box.height, cv_src, classes); + + Detection2d d2d; + d2d.classIdx = classIds[idx]; + d2d.bbox = box; + d2d.confidence = confidences[idx]; + res2d.push_back(d2d); + } + + return res2d; +} +#endif + +int OnnxDetectorImpl::loadOnnxModel(const char* filename, cv::Size2f inferSize) { + + std::vector< std::pair > backends = cv::dnn::getAvailableBackends(); + for (std::pair& det : backends) { + std::cout << "Detected Valid Backends: " << det.first << ", " << det.second << std::endl; + } + + size_ = inferSize; + network_ = cv::dnn::readNet(filename); + + //std::vector< std::pair > backends = cv::dnn::getAvailableBackends(); + //for (std::pair& det : backends) { + // std::cout << "Detected Valid Backends: " << det.first << ", " << det.second << std::endl; + //} + //network_.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA); + //network_.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA); + + return 0; +} + +std::vector OnnxDetectorImpl::detect(cv::Mat image) { + + std::vector res2d; + if (image.empty() || network_.empty()) + return res2d; + + const int width = std::max(image.rows, image.cols); + cv::Mat img = cv::Mat::zeros(image.rows, image.cols, image.type()); + image.copyTo(img(cv::Rect(0, 0, image.cols, image.rows))); + + vector detections = preProcess(img); + return postProcess(img, detections); +} + +OnnxDetector* OnnxDetector::CreateInstance() { + return new OnnxDetectorImpl(); +} \ No newline at end of file diff --git a/camCalib/onnxDetector.h b/camCalib/onnxDetector.h new file mode 100644 index 0000000..e36bf4f --- /dev/null +++ b/camCalib/onnxDetector.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +typedef struct Detection2d { + int classIdx; + float confidence; + cv::Rect bbox; +}_detection2d; + +class OnnxDetector +{ +public: + static OnnxDetector* CreateInstance(); + +public: + virtual int loadOnnxModel(const char* filename, cv::Size2f inferSize) = 0; + virtual std::vector detect(cv::Mat image) = 0; + +}; + diff --git a/camCalib/sourceCode/MonoLaserCalibrate.cpp b/camCalib/sourceCode/MonoLaserCalibrate.cpp index 0827e0d..6545e4f 100644 --- a/camCalib/sourceCode/MonoLaserCalibrate.cpp +++ b/camCalib/sourceCode/MonoLaserCalibrate.cpp @@ -7,6 +7,9 @@ #include "MonoLaserCalibrate.h" #include "../lineDetection_steger.h" +#include "../onnxDetector.h" +OnnxDetector* cbDetector = nullptr; + /*Breif:角点检测函数*/ void detectCorners(const cv::Mat& img, const cv::Size& patternSize, @@ -42,11 +45,116 @@ void detectCirclePoints(const cv::Mat& img, //需要将圆点外的背景去除,否则复杂的背景会导致检测失败 //使用连通域检测 - cv::Mat grayBin; + cv::Mat roiGray; + { +#if 1 + if (nullptr == cbDetector) { + cbDetector = OnnxDetector::CreateInstance(); + cbDetector->loadOnnxModel("detectCB.onnx", cv::Size(640, 640)); + } + std::vector result2d = cbDetector->detect(img); + for (size_t i = 0; i < result2d.size(); i++) { + cv::Rect roi = result2d[i].bbox; + if (roi.x < 0) + roi.x = 0; + if (roi.y < 0) + roi.y = 0; + if ((roi.x + roi.width) > gray.cols) + roi.width = gray.cols - roi.x; + if ((roi.y + roi.height) > gray.rows) + roi.height = gray.rows - roi.y; + + if (roiGray.empty()) { + roiGray = cv::Mat::zeros(gray.rows, gray.cols, CV_8UC1); + gray(roi).copyTo(roiGray(roi)); + } + else { + gray(roi).copyTo(roiGray(roi)); + } + } + +#ifdef _DEBUG + cv::Mat result = img.clone(); + for (size_t i = 0; i < result2d.size(); i++) { + cv::rectangle(img, result2d[i].bbox, cv::Scalar(0, 255, 0), 2); + } + cv::imwrite("_cbResult.png", result); +#endif + +#else + cv::Mat grayBin; + cv::adaptiveThreshold(gray, grayBin, 255, + cv::ADAPTIVE_THRESH_GAUSSIAN_C, cv::THRESH_BINARY_INV, 9, 2); + + cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, { 3,3 }); + cv::morphologyEx(grayBin, grayBin, cv::MORPH_OPEN, kernel); + cv::morphologyEx(grayBin, grayBin, cv::MORPH_CLOSE, kernel); + + //cv::connectedComponents(); + std::vector> contours; + std::vector hierarchy; + cv::findContours(grayBin, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE); + +#ifdef _DEBUG + cv::Mat result = grayBin.clone(); +#endif + + cv::Rect roi; + const int N = patternSize.area(); + for (int i = 0; i < contours.size(); ++i) { + // 检查当前轮廓是否为父轮廓且子轮廓数量为N + int childCount = 0; + int childIdx = hierarchy[i][2]; // 第一个子轮廓索引 + while (childIdx != -1) { + childCount++; + childIdx = hierarchy[childIdx][0]; // 下一个同级子轮廓 + } + + if (childCount >= N) { // 子数量匹配 +#ifdef _DEBUG + cv::drawContours(result, contours, i, cv::Scalar(0, 255, 0), 2); +#endif + + cv::Point lt(10000, 10000), rb(0, 0); + for (const cv::Point pt : contours[i]) { + lt.x = std::min(lt.x, pt.x); + rb.x = std::max(rb.x, pt.x); + lt.y = std::min(lt.y, pt.y); + rb.y = std::max(rb.y, pt.y); + } + roi = cv::Rect(lt, rb); + if (roiGray.empty()) { + roiGray = cv::Mat::zeros(gray.rows, gray.cols, CV_8UC1); + gray(roi).copyTo(roiGray(roi)); + } + else { + gray(roi).copyTo(roiGray(roi)); + } + + // 标记子轮廓(可选) + childIdx = hierarchy[i][2]; + while (childIdx != -1) { +#ifdef _DEBUG + cv::drawContours(result, contours, childIdx, cv::Scalar(255, 0, 0), 1); +#endif + childIdx = hierarchy[childIdx][0]; + } + } + } + +#ifdef _DEBUG + cv::imwrite("_cbContour.png", result); +#endif +#endif +#ifdef _DEBUG + if (false == roiGray.empty()) + cv::imwrite("_roiGray.png", roiGray); +#endif + } // 检测圆形网格 - bool found = cv::findCirclesGrid(gray, patternSize, corners, cv::CALIB_CB_SYMMETRIC_GRID); + bool found = cv::findCirclesGrid(roiGray.empty() ? gray : roiGray, patternSize, corners, cv::CALIB_CB_SYMMETRIC_GRID); return; }