From 6ff32bf8cc14009611b133d366bbbbbc48b6fd72 Mon Sep 17 00:00:00 2001 From: imuncle Date: Thu, 21 Oct 2021 20:03:29 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=89=8B=E7=9C=BC=E6=A0=87?= =?UTF-8?q?=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AboutUs.ui | 4 +- CMakeLists.txt | 37 + Calibrate/binocular_calib.cpp | 357 ++++ Calibrate/binocular_calib.h | 54 + Calibrate/monocular_calib.cpp | 226 +++ Calibrate/monocular_calib.h | 42 + CameraCalibration.cpp | 798 +++++++++ CameraCalibration.h | 78 + ...meraCalibration.ui => CameraCalibration.ui | 84 +- .../chessboard.cpp | 0 .../chessboard.h | 0 .../findCorner.cpp | 16 +- .../findCorner.h | 0 LICENSE | 674 -------- QImgViewWidget.cpp | 93 + QImgViewWidget.h | 44 + README.md | 134 +- TStoneCalibration.pro | 81 - camera_calibration/CameraCalibration.cpp | 1411 ---------------- camera_calibration/CameraCalibration.h | 131 -- camera_calibration/README.md | 30 - camera_calibration/double_capture.cpp | 219 --- camera_calibration/double_capture.h | 51 - camera_calibration/double_capture.ui | 107 -- camera_calibration/double_capture_linux.cpp | 339 ---- camera_calibration/double_capture_linux.h | 97 -- camera_calibration/screenshot.jpg | Bin 67224 -> 0 bytes camera_calibration/single_capture.cpp | 122 -- camera_calibration/single_capture.h | 44 - camera_calibration/single_capture.ui | 85 - camera_calibration/single_capture_linux.cpp | 181 -- camera_calibration/single_capture_linux.h | 67 - camera_calibration/v4l2.hpp | 458 ----- .../choose_two_dir.cpp => choose_two_dir.cpp | 4 +- .../choose_two_dir.h => choose_two_dir.h | 0 .../choose_two_dir.ui => choose_two_dir.ui | 0 .../choose_yaml.cpp => choose_yaml.cpp | 0 .../choose_yaml.h => choose_yaml.h | 0 .../choose_yaml.ui => choose_yaml.ui | 0 guide/new_tool.jpg | Bin 11044 -> 0 bytes guide/new_ui.jpg | Bin 34469 -> 0 bytes guide/new_ui_3.jpg | Bin 12683 -> 0 bytes guide/new_ui_dir.jpg | Bin 28072 -> 0 bytes hand_eye_calibration/HandEyeCalibration.cpp | 1495 ----------------- hand_eye_calibration/HandEyeCalibration.h | 103 -- hand_eye_calibration/HandEyeCalibration.ui | 633 ------- hand_eye_calibration/README.md | 23 - .../TSAIleastSquareCalibration.cpp | 114 -- .../TSAIleastSquareCalibration.h | 19 - hand_eye_calibration/res/AddImage.png | Bin 952 -> 0 bytes hand_eye_calibration/res/camera.png | Bin 2034 -> 0 bytes hand_eye_calibration/res/capture.png | Bin 795 -> 0 bytes hand_eye_calibration/res/delete.png | Bin 1818 -> 0 bytes hand_eye_calibration/res/hand_eye_image.qrc | 13 - hand_eye_calibration/res/play.png | Bin 1097 -> 0 bytes hand_eye_calibration/res/robot.png | Bin 1716 -> 0 bytes hand_eye_calibration/res/undistort.png | Bin 2320 -> 0 bytes hand_eye_calibration/res/yes.png | Bin 786 -> 0 bytes hand_eye_calibration/screenshot.jpg | Bin 118141 -> 0 bytes icon.ico | Bin 9662 -> 0 bytes main.cpp | 4 +- mainwindow.cpp | 55 - mainwindow.h | 34 - mainwindow.ui | 137 -- {camera_calibration/res => res}/AddImage.png | Bin {camera_calibration/res => res}/camera.png | Bin {camera_calibration/res => res}/capture.png | Bin {camera_calibration/res => res}/delete.png | Bin {camera_calibration/res => res}/image.qrc | 1 - {camera_calibration/res => res}/play.png | Bin {camera_calibration/res => res}/undistort.png | Bin {camera_calibration/res => res}/yes.png | Bin types.h | 40 + 73 files changed, 1820 insertions(+), 6919 deletions(-) create mode 100644 CMakeLists.txt create mode 100644 Calibrate/binocular_calib.cpp create mode 100644 Calibrate/binocular_calib.h create mode 100644 Calibrate/monocular_calib.cpp create mode 100644 Calibrate/monocular_calib.h create mode 100644 CameraCalibration.cpp create mode 100644 CameraCalibration.h rename camera_calibration/CameraCalibration.ui => CameraCalibration.ui (88%) rename {camera_calibration => DetectCorner}/chessboard.cpp (100%) rename {camera_calibration => DetectCorner}/chessboard.h (100%) rename {camera_calibration => DetectCorner}/findCorner.cpp (97%) rename {camera_calibration => DetectCorner}/findCorner.h (100%) delete mode 100644 LICENSE create mode 100644 QImgViewWidget.cpp create mode 100644 QImgViewWidget.h delete mode 100644 TStoneCalibration.pro delete mode 100644 camera_calibration/CameraCalibration.cpp delete mode 100644 camera_calibration/CameraCalibration.h delete mode 100644 camera_calibration/README.md delete mode 100644 camera_calibration/double_capture.cpp delete mode 100644 camera_calibration/double_capture.h delete mode 100644 camera_calibration/double_capture.ui delete mode 100644 camera_calibration/double_capture_linux.cpp delete mode 100644 camera_calibration/double_capture_linux.h delete mode 100644 camera_calibration/screenshot.jpg delete mode 100644 camera_calibration/single_capture.cpp delete mode 100644 camera_calibration/single_capture.h delete mode 100644 camera_calibration/single_capture.ui delete mode 100644 camera_calibration/single_capture_linux.cpp delete mode 100644 camera_calibration/single_capture_linux.h delete mode 100644 camera_calibration/v4l2.hpp rename camera_calibration/choose_two_dir.cpp => choose_two_dir.cpp (85%) rename camera_calibration/choose_two_dir.h => choose_two_dir.h (100%) rename camera_calibration/choose_two_dir.ui => choose_two_dir.ui (100%) rename camera_calibration/choose_yaml.cpp => choose_yaml.cpp (100%) rename camera_calibration/choose_yaml.h => choose_yaml.h (100%) rename camera_calibration/choose_yaml.ui => choose_yaml.ui (100%) delete mode 100644 guide/new_tool.jpg delete mode 100644 guide/new_ui.jpg delete mode 100644 guide/new_ui_3.jpg delete mode 100644 guide/new_ui_dir.jpg delete mode 100644 hand_eye_calibration/HandEyeCalibration.cpp delete mode 100644 hand_eye_calibration/HandEyeCalibration.h delete mode 100644 hand_eye_calibration/HandEyeCalibration.ui delete mode 100644 hand_eye_calibration/README.md delete mode 100644 hand_eye_calibration/TSAIleastSquareCalibration.cpp delete mode 100644 hand_eye_calibration/TSAIleastSquareCalibration.h delete mode 100644 hand_eye_calibration/res/AddImage.png delete mode 100644 hand_eye_calibration/res/camera.png delete mode 100644 hand_eye_calibration/res/capture.png delete mode 100644 hand_eye_calibration/res/delete.png delete mode 100644 hand_eye_calibration/res/hand_eye_image.qrc delete mode 100644 hand_eye_calibration/res/play.png delete mode 100644 hand_eye_calibration/res/robot.png delete mode 100644 hand_eye_calibration/res/undistort.png delete mode 100644 hand_eye_calibration/res/yes.png delete mode 100644 hand_eye_calibration/screenshot.jpg delete mode 100644 icon.ico delete mode 100644 mainwindow.cpp delete mode 100644 mainwindow.h delete mode 100644 mainwindow.ui rename {camera_calibration/res => res}/AddImage.png (100%) rename {camera_calibration/res => res}/camera.png (100%) rename {camera_calibration/res => res}/capture.png (100%) rename {camera_calibration/res => res}/delete.png (100%) rename {camera_calibration/res => res}/image.qrc (88%) rename {camera_calibration/res => res}/play.png (100%) rename {camera_calibration/res => res}/undistort.png (100%) rename {camera_calibration/res => res}/yes.png (100%) create mode 100644 types.h diff --git a/AboutUs.ui b/AboutUs.ui index a8ef785..e1cb357 100644 --- a/AboutUs.ui +++ b/AboutUs.ui @@ -56,7 +56,7 @@ - <html><head/><body><p>产品: TStoneCalibration</p><p>版本: 1.2.6</p><p>开发: 基于<a href="https://www.qt.io/"><span style=" text-decoration: underline; color:#0000ff;">Qt</span></a>和开源项目<a href="https://opencv.org/"><span style=" text-decoration: underline; color:#0000ff;">OpenCV</span></a>开发</p></body></html> + <html><head/><body><p>产品: TStoneCalibration</p><p>版本: 1.3.0</p><p>开发: 基于<a href="https://www.qt.io/"><span style=" text-decoration: underline; color:#0000ff;">Qt</span></a>和开源项目<a href="https://opencv.org/"><span style=" text-decoration: underline; color:#0000ff;">OpenCV</span></a>开发</p></body></html> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop @@ -93,7 +93,7 @@ - <html><head/><body><p>实验室官网: <a href="http://ri.cuhk.edu.hk/"><span style=" text-decoration: underline; color:#0000ff;">http://ri.cuhk.edu.hk/</span></a></p><p>联系我们: big.uncle@foxmail.com</p><p><span style=" font-size:7pt;">Copyright © 2020 CUHK CURI Embedded AI Group</span></p></body></html> + <html><head/><body><p>实验室官网: <a href="http://ri.cuhk.edu.hk/"><span style=" text-decoration: underline; color:#0000ff;">http://ri.cuhk.edu.hk/</span></a></p><p>联系我们: big.uncle@foxmail.com</p><p><span style=" font-size:7pt;">Copyright © 2021 CUHK CURI Embedded AI Group</span></p></body></html> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..61e7c55 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 2.8.11) + +project(TStoneCalib) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# init_qt: Let's do the CMake job for us +set(CMAKE_AUTOMOC ON) # For meta object compiler +set(CMAKE_AUTORCC ON) # Resource files +set(CMAKE_AUTOUIC ON) # UI files + +# Find includes in corresponding build directories +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +# Find the QtWidgets library +find_package(Qt5 REQUIRED COMPONENTS Widgets Core Gui) +find_package(OpenCV REQUIRED) + +include_directories( + DetectCorner + Calibrate + ${OpenCV_INCLUDE_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} +) + +file(GLOB project_SOURCES "*.cpp" "res/*.qrc" "DetectCorner/*.cpp" "Calibrate/*.cpp") + +add_executable(${PROJECT_NAME} ${project_SOURCES}) + +target_link_libraries(${PROJECT_NAME} +${OpenCV_LIBRARIES} +Qt5::Core +Qt5::Gui +Qt5::Widgets +) diff --git a/Calibrate/binocular_calib.cpp b/Calibrate/binocular_calib.cpp new file mode 100644 index 0000000..452f5d4 --- /dev/null +++ b/Calibrate/binocular_calib.cpp @@ -0,0 +1,357 @@ +#include "binocular_calib.h" + +BinoCalib::BinoCalib(): + has_param_(false), + chessboard_size_(0), + fisheye_flag_(false), + map_init_(false) +{ + +} + +BinoCalib::~BinoCalib() +{ + +} + +int BinoCalib::calibrate(std::vector& imgs, bool fisheye, bool tangential, bool k3) +{ + if(imgs.size() <= 3) + { + return 1; + } + fisheye_flag_ = fisheye; + cv::Mat img = cv::imread(imgs[0].left_path); + cv::Size img_size = img.size(); + std::vector> left_img_points; + std::vector> right_img_points; + std::vector> world_points; + std::vector left_tvecsMat; + std::vector left_rvecsMat; + std::vector right_tvecsMat; + std::vector right_rvecsMat; + std::vector > left_imagePoints; + std::vector > right_imagePoints; + std::vector > objectPoints; + std::vector left_rvecs; + std::vector left_tvecs; + std::vector right_rvecs; + std::vector right_tvecs; + for (unsigned int j = 0; j < imgs.size(); j++) + { + left_img_points.push_back(imgs[j].left_img_points); + right_img_points.push_back(imgs[j].right_img_points); + world_points.push_back(imgs[j].world_points); + std::vector img_p, img_p_; + std::vector obj_p; + for(unsigned int i = 0; i < imgs[j].left_img_points.size(); i++) + { + img_p.push_back(imgs[j].left_img_points[i]); + img_p_.push_back(imgs[j].right_img_points[i]); + obj_p.push_back(imgs[j].world_points[i]); + } + left_imagePoints.push_back(img_p); + right_imagePoints.push_back(img_p_); + objectPoints.push_back(obj_p); + } + cameraMatrix1 = cv::Mat::zeros(cv::Size(3, 3), CV_32F); + cameraMatrix2 = cv::Mat::zeros(cv::Size(3, 3), CV_32F); + if(fisheye == false) + { + distCoefficients1 = cv::Mat::zeros(cv::Size(5, 1), CV_32F); + distCoefficients2 = cv::Mat::zeros(cv::Size(5, 1), CV_32F); + } + + // 单目标定 + if(fisheye == false) + { + int flag = 0; + if(!tangential) + flag |= cv::CALIB_ZERO_TANGENT_DIST; + if(!k3) + flag |= cv::CALIB_FIX_K3; + cv::calibrateCamera(world_points, left_img_points, img_size, cameraMatrix1, distCoefficients1, left_rvecsMat, left_tvecsMat, flag); + cv::calibrateCamera(world_points, right_img_points, img_size, cameraMatrix2, distCoefficients2, right_rvecsMat, right_tvecsMat, flag); + } + else + { + int flag = 0; + flag |= cv::fisheye::CALIB_RECOMPUTE_EXTRINSIC; + flag |= cv::fisheye::CALIB_FIX_SKEW; + cv::fisheye::calibrate(objectPoints, left_imagePoints, img_size, K1, D1, left_rvecs, left_tvecs, flag); + cv::fisheye::calibrate(objectPoints, right_imagePoints, img_size, K2, D2, right_rvecs, right_tvecs, flag); + } + for(unsigned int i = 0; i < imgs.size(); i++) + { + if(fisheye == false) + { + imgs[i].left_tvec = left_tvecsMat[i]; + imgs[i].left_rvec = left_rvecsMat[i]; + imgs[i].right_tvec = right_tvecsMat[i]; + imgs[i].right_rvec = right_rvecsMat[i]; + } + else + { + imgs[i].left_fish_tvec = left_tvecs[i]; + imgs[i].left_fish_rvec = left_rvecs[i]; + imgs[i].right_fish_tvec = right_tvecs[i]; + imgs[i].right_fish_rvec = right_rvecs[i]; + } + } + + // 评估标定结果 + std::vector left_error, right_error; + for(unsigned int i = 0; i < imgs.size(); i++) + { + std::vector world_p = imgs[i].world_points; + std::vector left_img_p = imgs[i].left_img_points, right_img_p = imgs[i].right_img_points; + std::vector left_reproject_img_p, right_reproject_img_p; + std::vector left_fisheye_reproject_p, right_fisheye_reproject_p; + if(fisheye == false) + { + projectPoints(world_p, left_rvecsMat[i], left_tvecsMat[i], cameraMatrix1, distCoefficients1, left_reproject_img_p); + projectPoints(world_p, right_rvecsMat[i], right_tvecsMat[i], cameraMatrix2, distCoefficients2, right_reproject_img_p); + } + else + { + cv::fisheye::projectPoints(objectPoints[i], left_fisheye_reproject_p, left_rvecs[i], left_tvecs[i], K1, D1); + cv::fisheye::projectPoints(objectPoints[i], right_fisheye_reproject_p, right_rvecs[i], right_tvecs[i], K2, D2); + } + float left_err = 0, right_err = 0; + for (unsigned int j = 0; j < world_p.size(); j++) + { + if(fisheye == false) + { + left_err += sqrt((left_img_p[j].x-left_reproject_img_p[j].x)*(left_img_p[j].x-left_reproject_img_p[j].x)+ + (left_img_p[j].y-left_reproject_img_p[j].y)*(left_img_p[j].y-left_reproject_img_p[j].y)); + right_err += sqrt((right_img_p[j].x-right_reproject_img_p[j].x)*(right_img_p[j].x-right_reproject_img_p[j].x)+ + (right_img_p[j].y-right_reproject_img_p[j].y)*(right_img_p[j].y-right_reproject_img_p[j].y)); + } + else + { + left_err += sqrt((left_img_p[j].x-left_fisheye_reproject_p[j].x)*(left_img_p[j].x-left_fisheye_reproject_p[j].x)+ + (left_img_p[j].y-left_fisheye_reproject_p[j].y)*(left_img_p[j].y-left_fisheye_reproject_p[j].y)); + right_err += sqrt((right_img_p[j].x-right_fisheye_reproject_p[j].x)*(right_img_p[j].x-right_fisheye_reproject_p[j].x)+ + (right_img_p[j].y-right_fisheye_reproject_p[j].y)*(right_img_p[j].y-right_fisheye_reproject_p[j].y)); + } + } + left_error.push_back(left_err/world_p.size()); + right_error.push_back(right_err/world_p.size()); + } + int max_idx = max_element(left_error.begin(), left_error.end()) - left_error.begin(); + float max_error = left_error[max_idx]; + max_idx = max_element(right_error.begin(), right_error.end()) - right_error.begin(); + max_error = std::max(max_error, right_error[max_idx]); + int width = 480 / imgs.size(); + cv::Mat error_plot = cv::Mat(260, 560, CV_32FC3, cv::Scalar(255,255,255)); + cv::rectangle(error_plot, cv::Rect(40, 20, 480, 200), cv::Scalar(0, 0, 0),1, cv::LINE_8,0); + cv::putText(error_plot, "0", cv::Point(20, 220), cv::FONT_HERSHEY_SIMPLEX, 0.3, cv::Scalar(0, 0, 0), 1, 8, 0); + char *chCode; + chCode = new(std::nothrow)char[20]; + sprintf(chCode, "%.2lf", max_error*200/195); + std::string strCode(chCode); + delete []chCode; + cv::putText(error_plot, strCode, cv::Point(10, 20), cv::FONT_HERSHEY_SIMPLEX, 0.3, cv::Scalar(0, 0, 0), 1, 8, 0); + for(unsigned int i = 0; i < imgs.size(); i++) + { + int height = 195*left_error[i]/max_error; + cv::rectangle(error_plot, cv::Rect(i*width+43, 220-height, width/2-4, height), cv::Scalar(255,0,0), -1, cv::LINE_8,0); + height = 195*right_error[i]/max_error; + cv::rectangle(error_plot, cv::Rect(i*width+41+width/2, 220-height, width/2-4, height), cv::Scalar(0,255,0), -1, cv::LINE_8,0); + cv::putText(error_plot, std::to_string(i), cv::Point(i*width+40+width/2-2, 240), cv::FONT_HERSHEY_SIMPLEX, 0.3, cv::Scalar(0, 0, 0), 1, 8, 0); + } + + // 双目标定 + if(fisheye == false) + { + int flag = 0; + if(!tangential) + flag |= cv::CALIB_ZERO_TANGENT_DIST; + if(!k3) + flag |= cv::CALIB_FIX_K3; + flag |= cv::CALIB_USE_INTRINSIC_GUESS; + cv::stereoCalibrate(world_points, left_img_points, right_img_points, + cameraMatrix1, distCoefficients1, + cameraMatrix2, distCoefficients2, + img_size, R, T, cv::noArray(), cv::noArray(), flag); + cv::stereoRectify(cameraMatrix1, distCoefficients1, + cameraMatrix2, distCoefficients2, + img_size, R, T, + R1, R2, P1, P2, Q, cv::CALIB_ZERO_DISPARITY, 0, img_size); + } + else + { + int flag = 0; + flag |= cv::fisheye::CALIB_FIX_SKEW; + flag |= cv::fisheye::CALIB_USE_INTRINSIC_GUESS; + try { + cv::fisheye::stereoCalibrate(objectPoints, left_imagePoints, right_imagePoints, + K1, D1, + K2, D2, + img_size, R, T, flag); + } catch (cv::Exception& e) { + return 2; + } + + cv::fisheye::stereoRectify(K1, D1, + K2, D2, + img_size, R, T, + R1, R2, P1, P2, Q, 0, img_size); + } + has_param_ = true; + cv::imshow("error", error_plot); +} + +void BinoCalib::exportParam(std::string file_name) +{ + cv::FileStorage fs_write(file_name, cv::FileStorage::WRITE); + if(fisheye_flag_ == false) + { + fs_write << "left_camera_Matrix" << cameraMatrix1 << "left_camera_distCoeffs" << distCoefficients1; + fs_write << "right_camera_Matrix" << cameraMatrix2 << "right_camera_distCoeffs" << distCoefficients2; + fs_write << "Rotate_Matrix" << R << "Translate_Matrix" << T; + fs_write << "R1" << R1 << "R2" << R2 << "P1" << P1 << "P2" << P2 << "Q" << Q; + } + else + { + cv::Mat camera_matrix = cv::Mat::zeros(cv::Size(3,3), CV_64F); + camera_matrix.at(0,0) = K1(0,0); + camera_matrix.at(0,2) = K1(0,2); + camera_matrix.at(1,1) = K1(1,1); + camera_matrix.at(1,2) = K1(1,2); + camera_matrix.at(2,2) = 1; + cv::Mat Dist = (cv::Mat_(1,4) << D1[0], D1[1], D1[2], D1[3]); + fs_write << "left_camera_Matrix" << camera_matrix << "left_camera_distCoeffs" << Dist; + camera_matrix.at(0,0) = K2(0,0); + camera_matrix.at(0,2) = K2(0,2); + camera_matrix.at(1,1) = K2(1,1); + camera_matrix.at(1,2) = K2(1,2); + Dist = (cv::Mat_(1,4) << D2[0], D2[1], D2[2], D2[3]); + fs_write << "right_camera_Matrix" << camera_matrix << "right_camera_distCoeffs" << Dist; + fs_write << "Rotate_Matrix" << R << "Translate_Matrix" << T; + fs_write << "R1" << R1 << "R2" << R2 << "P1" << P1 << "P2" << P2 << "Q" << Q; + } + fs_write.release(); +} + +void BinoCalib::setCameraParam(cv::Mat intrinsic1, cv::Mat distCoefficients1_, + cv::Mat intrinsic2, cv::Mat distCoefficients2_, bool fisheye, + cv::Mat R_, cv::Mat T_, cv::Mat R1_, cv::Mat R2_, cv::Mat P1_, cv::Mat P2_, cv::Mat Q_) +{ + R = R_; + T = T_; + P1 = P1_; + P2 = P2_; + R1 = R1_; + R2 = R2_; + Q = Q_; + if(fisheye == false) + { + cameraMatrix1 = intrinsic1; + distCoefficients1 = distCoefficients1_; + cameraMatrix2 = intrinsic2; + distCoefficients2 = distCoefficients2_; + } + else + { + K1(0,0) = intrinsic1.at(0,0); + K1(0,1) = 0; + K1(0,2) = intrinsic1.at(0,2); + K1(1,0) = 0; + K1(1,1) = intrinsic1.at(1,1); + K1(1,2) = intrinsic1.at(1,2); + K1(1,0) = 0; + K1(2,1) = 0; + K1(2,2) = 1; + D1[0] = distCoefficients1_.at(0,0); + D1[1] = distCoefficients1_.at(0,1); + D1[2] = distCoefficients1_.at(0,2); + D1[3] = distCoefficients1_.at(0,3); + + K2(0,0) = intrinsic2.at(0,0); + K2(0,1) = 0; + K2(0,2) = intrinsic2.at(0,2); + K2(1,0) = 0; + K2(1,1) = intrinsic2.at(1,1); + K2(1,2) = intrinsic2.at(1,2); + K2(1,0) = 0; + K2(2,1) = 0; + K2(2,2) = 1; + D2[0] = distCoefficients2_.at(0,0); + D2[1] = distCoefficients2_.at(0,1); + D2[2] = distCoefficients2_.at(0,2); + D2[3] = distCoefficients2_.at(0,3); + } + has_param_ = true; +} + +void BinoCalib::undistort(cv::Mat& left, cv::Mat& right) +{ + if(!has_param_) return; + if(map_init_ == false) + { + if(fisheye_flag_ == false) + { + cv::initUndistortRectifyMap(cameraMatrix1, distCoefficients1, R1, P1, left.size(), CV_16SC2, left_mapx, left_mapy); + cv::initUndistortRectifyMap(cameraMatrix2, distCoefficients2, R2, P2, left.size(), CV_16SC2, right_mapx, right_mapy); + } + else + { + cv::fisheye::initUndistortRectifyMap(K1, D1, R1, P1, left.size(), CV_16SC2, left_mapx, left_mapy); + cv::fisheye::initUndistortRectifyMap(K2, D2, R2, P2, left.size(), CV_16SC2, right_mapx, right_mapy); + } + map_init_ = true; + } + cv::remap(left, left, left_mapx, left_mapy, cv::INTER_LINEAR); + cv::remap(right, right, right_mapx, right_mapy, cv::INTER_LINEAR); +} + +void BinoCalib::showCorners(cv::Mat& left, cv::Mat& right, Stereo_Img_t& img) +{ + // 非矫正模式下显示角点位置 + for(unsigned int i = 0; i < img.left_img_points.size(); i++) + { + cv::circle(left, img.left_img_points[i], 5, cv::Scalar(0,0,255), 2); + } + for(unsigned int i = 0; i < img.right_img_points.size(); i++) + { + cv::circle(right, img.right_img_points[i], 5, cv::Scalar(0,0,255), 2); + } + if(has_param_) + { + // 显示标定后角点的重投影 + if(fisheye_flag_ == false) + { + std::vector reproject_img_p; + projectPoints(img.world_points, img.left_rvec, img.left_tvec, cameraMatrix1, distCoefficients1, reproject_img_p); + for(unsigned int i = 0; i < reproject_img_p.size(); i++) + { + cv::circle(left, reproject_img_p[i], 3, cv::Scalar(255,0,0), 2); + } + projectPoints(img.world_points, img.right_rvec, img.right_tvec, cameraMatrix2, distCoefficients2, reproject_img_p); + for(unsigned int i = 0; i < reproject_img_p.size(); i++) + { + cv::circle(right, reproject_img_p[i], 3, cv::Scalar(255,0,0), 2); + } + } + else + { + std::vector reproject_img_p; + std::vector w_p_; + for(unsigned int i = 0; i < img.world_points.size(); i++) + { + w_p_.push_back(img.world_points[i]); + } + cv::fisheye::projectPoints(w_p_, reproject_img_p, img.left_fish_rvec, img.left_fish_tvec, K1, D1); + for(unsigned int i = 0; i < reproject_img_p.size(); i++) + { + cv::circle(left, reproject_img_p[i], 3, cv::Scalar(255,0,0), 2); + } + cv::fisheye::projectPoints(w_p_, reproject_img_p, img.right_fish_rvec, img.right_fish_tvec, K2, D2); + for(unsigned int i = 0; i < reproject_img_p.size(); i++) + { + cv::circle(right, reproject_img_p[i], 3, cv::Scalar(255,0,0), 2); + } + } + } +} \ No newline at end of file diff --git a/Calibrate/binocular_calib.h b/Calibrate/binocular_calib.h new file mode 100644 index 0000000..8431f62 --- /dev/null +++ b/Calibrate/binocular_calib.h @@ -0,0 +1,54 @@ +#ifndef BINOCULAR_CALIB_H +#define BINOCULAR_CALIB_H + +#include +#include "types.h" + +class BinoCalib +{ + public: + BinoCalib(); + ~BinoCalib(); + int calibrate(std::vector& imgs, bool fisheye, bool tangential, bool k3); + void setChessboardSize(double chessboard_size) + { + chessboard_size_ = chessboard_size; + } + bool isChessboardSizeValid() + { + if(chessboard_size_ > 0) return true; + else return false; + } + void undistort(cv::Mat& left, cv::Mat &right); + void setCameraParam(cv::Mat intrinsic1, cv::Mat distCoefficients1_, + cv::Mat intrinsic2, cv::Mat distCoefficients2_, bool fisheye, + cv::Mat R, cv::Mat T, cv::Mat R1, cv::Mat R2, cv::Mat P1, cv::Mat P2, cv::Mat Q); + void reset() + { + chessboard_size_ = 0; + has_param_ = false; + } + void exportParam(std::string file_name); + void showCorners(cv::Mat& left, cv::Mat& right, Stereo_Img_t& img); + double getChessboardSize() + { + return chessboard_size_; + } + bool hasParam() + { + return has_param_; + } + private: + cv::Mat cameraMatrix1, cameraMatrix2; + cv::Mat distCoefficients1, distCoefficients2; + cv::Matx33d K1, K2; + cv::Vec4d D1, D2; + bool has_param_; + bool fisheye_flag_; + float chessboard_size_; + cv::Mat R, T, R1, R2, P1, P2, Q; + bool map_init_; + cv::Mat left_mapx, left_mapy, right_mapx, right_mapy; +}; + +#endif \ No newline at end of file diff --git a/Calibrate/monocular_calib.cpp b/Calibrate/monocular_calib.cpp new file mode 100644 index 0000000..4a2ae85 --- /dev/null +++ b/Calibrate/monocular_calib.cpp @@ -0,0 +1,226 @@ +#include "monocular_calib.h" + +MonoCalib::MonoCalib(): + has_param_(false), + chessboard_size_(20.0), + fisheye_flag_(false), + map_init_(false) +{ + +} + +MonoCalib::~MonoCalib() +{ + +} + +int MonoCalib::calibrate(std::vector& imgs, bool fisheye, bool tangential, bool k3) +{ + if(imgs.size() <= 3) + { + return 1; + } + cv::Mat img = cv::imread(imgs[0].file_path); + cv::Size img_size = img.size(); + fisheye_flag_ = fisheye; + std::vector> img_points; + std::vector> world_points; + std::vector tvecsMat; + std::vector rvecsMat; + std::vector > imagePoints; + std::vector > objectPoints; + std::vector rvecs; + std::vector tvecs; + for (unsigned int j = 0; j < imgs.size(); j++) + { + img_points.push_back(imgs[j].img_points); + world_points.push_back(imgs[j].world_points); + std::vector img_p; + std::vector obj_p; + for(unsigned int i = 0; i < imgs[j].img_points.size(); i++) + { + img_p.push_back(imgs[j].img_points[i]); + obj_p.push_back(imgs[j].world_points[i]); + } + imagePoints.push_back(img_p); + objectPoints.push_back(obj_p); + } + intrinsic_ = cv::Mat::zeros(cv::Size(3, 3), CV_32F); + if(fisheye_flag_ == false) + distCoefficients_ = cv::Mat::zeros(cv::Size(5, 1), CV_32F); + + if(fisheye_flag_ == false) + { + int flag = 0; + if(!tangential) + flag |= cv::CALIB_ZERO_TANGENT_DIST; + if(!k3) + flag |= cv::CALIB_FIX_K3; + cv::calibrateCamera(world_points, img_points, img_size, intrinsic_, distCoefficients_, rvecsMat, tvecsMat, flag); + } + else + { + int flag = 0; + flag |= cv::fisheye::CALIB_RECOMPUTE_EXTRINSIC; + flag |= cv::fisheye::CALIB_FIX_SKEW; + try { + cv::fisheye::calibrate(objectPoints, imagePoints, img_size, K, D, rvecs, tvecs, flag); + } catch (cv::Exception& e) { + return 2; + } + } + has_param_ = true; + for(unsigned int i = 0; i < imgs.size(); i++) + { + if(fisheye_flag_ == false) + { + imgs[i].tvec = tvecsMat[i]; + imgs[i].rvec = rvecsMat[i]; + } + else + { + imgs[i].fish_tvec = tvecs[i]; + imgs[i].fish_rvec = rvecs[i]; + } + } + // 评估标定结果 + std::vector error; + for(unsigned int i = 0; i < imgs.size(); i++) + { + std::vector world_p = imgs[i].world_points; + std::vector img_p = imgs[i].img_points, reproject_img_p; + std::vector fisheye_reproject_p; + if(fisheye_flag_ == false) + projectPoints(world_p, rvecsMat[i], tvecsMat[i], intrinsic_, distCoefficients_, reproject_img_p); + else + cv::fisheye::projectPoints(objectPoints[i], fisheye_reproject_p, rvecs[i], tvecs[i], K, D); + float err = 0; + for (unsigned int j = 0; j < img_p.size(); j++) + { + if(fisheye_flag_ == false) + err += sqrt((img_p[j].x-reproject_img_p[j].x)*(img_p[j].x-reproject_img_p[j].x)+ + (img_p[j].y-reproject_img_p[j].y)*(img_p[j].y-reproject_img_p[j].y)); + else + err += sqrt((img_p[j].x-fisheye_reproject_p[j].x)*(img_p[j].x-fisheye_reproject_p[j].x)+ + (img_p[j].y-fisheye_reproject_p[j].y)*(img_p[j].y-fisheye_reproject_p[j].y)); + } + error.push_back(err/img_p.size()); + } + int max_idx = max_element(error.begin(), error.end()) - error.begin(); + float max_error = error[max_idx]; + int width = 240 / imgs.size(); + cv::Mat error_plot = cv::Mat(260, 320, CV_32FC3, cv::Scalar(255,255,255)); + cv::rectangle(error_plot, cv::Rect(40, 20, 240, 200), cv::Scalar(0, 0, 0),1, cv::LINE_8,0); + cv::putText(error_plot, "0", cv::Point(20, 220), cv::FONT_HERSHEY_SIMPLEX, 0.3, cv::Scalar(0, 0, 0), 1, 8, 0); + char *chCode; + chCode = new(std::nothrow)char[20]; + sprintf(chCode, "%.2lf", max_error*200/195); + std::string strCode(chCode); + delete []chCode; + cv::putText(error_plot, strCode, cv::Point(10, 20), cv::FONT_HERSHEY_SIMPLEX, 0.3, cv::Scalar(0, 0, 0), 1, 8, 0); + for(unsigned int i = 0; i < imgs.size(); i++) + { + int height = 195*error[i]/max_error; + cv::rectangle(error_plot, cv::Rect(i*width+41, 220-height, width-2, height), cv::Scalar(255,0,0), -1, cv::LINE_8,0); + cv::putText(error_plot, std::to_string(i), cv::Point(i*width+40, 240), cv::FONT_HERSHEY_SIMPLEX, 0.3, cv::Scalar(0, 0, 0), 1, 8, 0); + } + cv::imshow("error", error_plot); + return 0; +} + +void MonoCalib::exportParam(std::string file_name) +{ + cv::FileStorage fs_write(file_name, cv::FileStorage::WRITE); + if(fisheye_flag_ == false) + fs_write << "cameraMatrix" << intrinsic_ << "distCoeffs" << distCoefficients_; + else + { + cv::Mat camera_matrix = cv::Mat::zeros(cv::Size(3,3), CV_64F); + camera_matrix.at(0,0) = K(0,0); + camera_matrix.at(0,2) = K(0,2); + camera_matrix.at(1,1) = K(1,1); + camera_matrix.at(1,2) = K(1,2); + camera_matrix.at(2,2) = 1; + cv::Mat Dist = (cv::Mat_(1,4) << D[0], D[1], D[2], D[3]); + fs_write << "cameraMatrix" << camera_matrix << "distCoeffs" << Dist; + } + fs_write.release(); +} + +void MonoCalib::setCameraParam(cv::Mat intrinsic, cv::Mat distCoefficients, bool fisheye) +{ + if(fisheye == false) + { + intrinsic_ = intrinsic; + distCoefficients_ = distCoefficients; + } + else + { + K(0,0) = intrinsic.at(0,0); + K(0,1) = 0; + K(0,2) = intrinsic.at(0,2); + K(1,0) = 0; + K(1,1) = intrinsic.at(1,1); + K(1,2) = intrinsic.at(1,2); + K(1,0) = 0; + K(2,1) = 0; + K(2,2) = 1; + D[0] = distCoefficients.at(0,0); + D[1] = distCoefficients.at(0,1); + D[2] = distCoefficients.at(0,2); + D[3] = distCoefficients.at(0,3); + } + has_param_ = true; +} + +void MonoCalib::undistort(cv::Mat& img) +{ + if(!has_param_) return; + if(map_init_ == false) + { + if(fisheye_flag_ == false) + { + cv::initUndistortRectifyMap(intrinsic_, distCoefficients_, cv::noArray(), intrinsic_, img.size(), CV_16SC2, mapx, mapy); + } + else + { + cv::fisheye::initUndistortRectifyMap(K, D, cv::noArray(), intrinsic_, img.size(), CV_16SC2, mapx, mapy); + } + map_init_ = true; + } + cv::remap(img, img, mapx, mapy, cv::INTER_LINEAR); +} + +void MonoCalib::showCorners(cv::Mat& left, Img_t& img) +{ + for(unsigned int i = 0; i < img.img_points.size(); i++) + { + cv::circle(left, img.img_points[i], 5, cv::Scalar(0,0,255), 2); + } + if(has_param_ == true) + { + if(fisheye_flag_ == false) + { + std::vector reproject_img_p; + projectPoints(img.world_points, img.rvec, img.tvec, intrinsic_, distCoefficients_, reproject_img_p); + for(unsigned int i = 0; i < reproject_img_p.size(); i++) + { + cv::circle(left, reproject_img_p[i], 3, cv::Scalar(255,0,0), 2); + } + } + else + { + std::vector reproject_img_p; + std::vector w_p_; + for(unsigned int i = 0; i < img.world_points.size(); i++) + { + w_p_.push_back(img.world_points[i]); + } + cv::fisheye::projectPoints(w_p_, reproject_img_p, img.fish_rvec, img.fish_tvec, K, D); + for(unsigned int i = 0; i < reproject_img_p.size(); i++) + { + cv::circle(left, reproject_img_p[i], 3, cv::Scalar(255,0,0), 2); + } + } + } +} \ No newline at end of file diff --git a/Calibrate/monocular_calib.h b/Calibrate/monocular_calib.h new file mode 100644 index 0000000..62c6add --- /dev/null +++ b/Calibrate/monocular_calib.h @@ -0,0 +1,42 @@ +#ifndef MONOCULAR_CALIB_H +#define MONOCULAR_CALIB_H + +#include "types.h" +#include + +class MonoCalib +{ + public: + MonoCalib(); + ~MonoCalib(); + int calibrate(std::vector& imgs, bool fisheye, bool tangential, bool k3); + void undistort(cv::Mat& img); + void setCameraParam(cv::Mat intrinsic, cv::Mat distCoefficients, bool fisheye); + void reset() + { + has_param_ = false; + map_init_ = false; + } + void exportParam(std::string file_name); + void showCorners(cv::Mat& left, Img_t& img); + double getChessboardSize() + { + return chessboard_size_; + } + bool hasParam() + { + return has_param_; + } + private: + cv::Mat intrinsic_; + cv::Mat distCoefficients_; + bool has_param_; + bool fisheye_flag_; + double chessboard_size_; + cv::Matx33d K; + cv::Vec4d D; + cv::Mat mapx, mapy; + bool map_init_; +}; + +#endif \ No newline at end of file diff --git a/CameraCalibration.cpp b/CameraCalibration.cpp new file mode 100644 index 0000000..447b95c --- /dev/null +++ b/CameraCalibration.cpp @@ -0,0 +1,798 @@ +#include "CameraCalibration.h" +#include "ui_CameraCalibration.h" + +cv::Mat find_corner_thread_img; +struct Chessboarder_t find_corner_thread_chessboard; + +CameraCalibration::CameraCalibration(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::CameraCalibration) +{ + ui->setupUi(this); + setMinimumSize(this->width(), this->height()); + ui->addImage->setFlat(true); + ui->calibrate->setFlat(true); + ui->export_1->setFlat(true); + ui->delete_1->setFlat(true); + ui->undistort->setFlat(true); + findcorner_thread = new FindcornerThread(); + connect(findcorner_thread, SIGNAL(isDone()), this, SLOT(DealThreadDone())); + ShowIntro(); + QFont font = ui->menu->font(); + font.setPixelSize(12); + ui->listWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); // 按ctrl多选 + connect(ui->addImage, SIGNAL(clicked()), this, SLOT(addImage())); + connect(ui->delete_1, SIGNAL(clicked()), this, SLOT(deleteImage())); + connect(ui->calibrate, SIGNAL(clicked()), this, SLOT(calibrate())); + connect(ui->fisheye, SIGNAL(stateChanged(int)), this, SLOT(fisheyeModeSwitch(int))); + connect(ui->export_1, SIGNAL(clicked()), this, SLOT(exportParam())); + connect(ui->undistort, SIGNAL(clicked()), this, SLOT(distortModeSwitch())); + connect(ui->listWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(chooseImage(QListWidgetItem*, QListWidgetItem*))); + connect(ui->double_camera, SIGNAL(toggled(bool)), this, SLOT(reset())); + connect(ui->single_camera, SIGNAL(toggled(bool)), this, SLOT(reset())); + connect(ui->double_undistort, SIGNAL(toggled(bool)), this, SLOT(reset())); + connect(ui->single_undistort, SIGNAL(toggled(bool)), this, SLOT(reset())); + saveImage = ui->menu->addAction("导出矫正图片"); + font = saveImage->font(); + font.setPixelSize(12); + saveImage->setFont(font); + about = ui->menu->addAction("关于"); + about->setFont(font); + connect(saveImage, SIGNAL(triggered()), this, SLOT(saveUndistort())); + connect(about, SIGNAL(triggered()), this, SLOT(showIntro())); +} + +CameraCalibration::~CameraCalibration() +{ + delete ui; + deleteLater(); +} + +void CameraCalibration::resizeEvent(QResizeEvent* event) +{ + QSize window_size = event->size(); + int left_list_width = window_size.width()*5/16; + int left_list_height = window_size.height()-80; + if(left_list_width > 500) left_list_width = 500; + ui->scrollArea->setGeometry(0,80,left_list_width, left_list_height); + ui->scrollAreaWidgetContents->setGeometry(0,0,left_list_width-2, left_list_height-2); + ui->intro->setGeometry(0,0,left_list_width-2, 100); + ui->listWidget->setGeometry(0,0,left_list_width-2, left_list_height-2); + + ui->Image->setGeometry(left_list_width,80,window_size.width()-left_list_width, left_list_height); +} + +void CameraCalibration::showIntro() +{ + a = new AboutUs(this); + QFont font = a->font(); + font.setPixelSize(12); + a->setFont(font); + a->setWindowTitle("关于TStoneCalibration"); + a->show(); +} + +// 显示简单的文字引导 +void CameraCalibration::ShowIntro() +{ + ui->intro->setFixedHeight(100); + if(ui->double_camera->isChecked() || ui->double_undistort->isChecked()) + { + QString str = "点击左上角添加图片"; + str += "\n"; + str += "左右相机图片分别放于两个文件夹"; + str += "\n"; + str += "对应图片名称需一致。"; + str += "\n"; + str += "\n"; + str += "单/双目纠正模式可纠正无棋盘的图片。"; + ui->intro->setText(str); + } + else if(ui->single_camera->isChecked() || ui->single_undistort->isChecked()) + { + QString str = "点击左上角添加图片"; + str += "\n"; + str += "15至20张较为适宜"; + str += "\n"; + str += "\n"; + str += "单/双目纠正模式可纠正无棋盘的图片。"; + ui->intro->setText(str); + } +} + +// 隐藏文字指导 +void CameraCalibration::HiddenIntro() +{ + ui->intro->setText(""); + ui->intro->setFixedHeight(0); +} + +// 导出矫正图片 +void CameraCalibration::saveUndistort() +{ + if((!mono_calib.hasParam() && ui->single_undistort->isChecked()) || + (!bino_calib.hasParam() && ui->double_undistort->isChecked())) + { + QMessageBox::warning(this, "警告", "还未获取到相机参数,请点击UNDISTORT按钮加载yaml文件。", QMessageBox::Yes, QMessageBox::Yes); + return; + } + QString srcDirPath = QFileDialog::getExistingDirectory(this, "选择保存路径", "./"); + if(srcDirPath.length() <= 0) + return; + QTextCodec *code = QTextCodec::codecForName("GB2312");//解决中文路径问题 + if(ui->double_camera->isChecked() || ui->double_undistort->isChecked()) + { + QDir dir; + dir.mkdir(srcDirPath+"/left/"); + dir.mkdir(srcDirPath+"/right/"); + for(unsigned int i = 0; i < stereo_imgs.size(); i++) + { + struct Stereo_Img_t img = stereo_imgs[i]; + cv::Mat left_dst = cv::imread(img.left_path); + cv::Mat right_dst = cv::imread(img.right_path); + bino_calib.undistort(left_dst, right_dst); + QString save_name = srcDirPath + "/left/" + img.left_file_name; + std::string str = code->fromUnicode(save_name).data(); + cv::imwrite(str, left_dst); + save_name = srcDirPath + "/right/" + img.left_file_name; + str = code->fromUnicode(save_name).data(); + cv::imwrite(str, right_dst); + cv::Mat combian_img; + cv::hconcat(left_dst, right_dst, combian_img); + for(int i = 1; i < 10; i++) + { + cv::line(combian_img, cv::Point(0, combian_img.rows*i/10), cv::Point(combian_img.cols-1, combian_img.rows*i/10), cv::Scalar(0,0,255), 2); + } + save_name = srcDirPath + "/" + img.left_file_name; + str = code->fromUnicode(save_name).data(); + cv::imwrite(str, combian_img); + } + } + else + { + for(unsigned int i = 0; i < imgs.size(); i++) + { + cv::Mat dst = cv::imread(imgs[i].file_path); + mono_calib.undistort(dst); + QString save_name = srcDirPath + "/" + imgs[i].file_name; + std::string str = code->fromUnicode(save_name).data(); + cv::imwrite(str, dst); + } + } +} + +// 切换模式时删除所有图片缓存 +void CameraCalibration::reset() +{ + ShowIntro(); + ui->k_2->setEnabled(true); + ui->k_3->setEnabled(true); + ui->tangential->setEnabled(true); + ui->calibrate->setEnabled(true); + ui->export_1->setEnabled(true); + bino_calib.reset(); + mono_calib.reset(); + distort_flag = false; + disconnect(ui->listWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(chooseImage(QListWidgetItem*, QListWidgetItem*))); + ui->listWidget->clear(); + connect(ui->listWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(chooseImage(QListWidgetItem*, QListWidgetItem*))); + if(imgs.size() > 0) + imgs.clear(); + if(stereo_imgs.size() > 0) + stereo_imgs.clear(); + list_select_ = false; +} + +// 显示选择的图像 +void CameraCalibration::chooseImage(QListWidgetItem* item, QListWidgetItem*) +{ + list_select_ = true; + int id = ui->listWidget->row(item); + if(ui->double_camera->isChecked()) + { + // 如果是双目模式 + struct Stereo_Img_t img = stereo_imgs[id]; + cv::Mat left_dst = cv::imread(img.left_path); + cv::Mat right_dst = cv::imread(img.right_path); + if(distort_flag == true && bino_calib.hasParam()) + { + // 对左右图像进行矫正 + bino_calib.undistort(left_dst, right_dst); + // 绘制横线以观察左右图像极线是否对齐 + for(int i = 1; i < 10; i++) + { + cv::line(left_dst, cv::Point(0, left_dst.rows*i/10), cv::Point(left_dst.cols-1, left_dst.rows*i/10), cv::Scalar(0,0,255), 2); + cv::line(right_dst, cv::Point(0, right_dst.rows*i/10), cv::Point(right_dst.cols-1, right_dst.rows*i/10), cv::Scalar(0,0,255), 2); + } + } + else + { + bino_calib.showCorners(left_dst, right_dst, img); + } + cv::Mat combian_img; + cv::hconcat(left_dst, right_dst, combian_img); + QImage qimage = Mat2QImage(combian_img); + ui->Image->SetImage(qimage); + ui->Image->repaint(); + } + else if(ui->single_camera->isChecked()) + { + // 单目逻辑同双目 + cv::Mat img; + std::vector corners; + std::vector w_p; + cv::Mat rvecs, tvecs; + cv::Vec3d fish_rvecs, fish_tvecs; + img = cv::imread(imgs[id].file_path); + + if(distort_flag == true && mono_calib.hasParam()) + { + mono_calib.undistort(img); + } + else + { + mono_calib.showCorners(img, imgs[id]); + } + QImage qimage = Mat2QImage(img); + ui->Image->SetImage(qimage); + ui->Image->repaint(); + } + else if(ui->single_undistort->isChecked()) + { + // 单目纠正 + cv::Mat img = cv::imread(imgs[id].file_path); + if(distort_flag == true && mono_calib.hasParam()) + { + mono_calib.undistort(img); + } + QImage qimage = Mat2QImage(img); + ui->Image->SetImage(qimage); + ui->Image->repaint(); + } + else + { + // 双目纠正 + struct Stereo_Img_t img = stereo_imgs[id]; + cv::Mat left_dst = cv::imread(img.left_path); + cv::Mat right_dst = cv::imread(img.right_path); + if(distort_flag == true && bino_calib.hasParam()) + { + bino_calib.undistort(left_dst, right_dst); + for(int i = 1; i < 10; i++) + { + cv::line(left_dst, cv::Point(0, left_dst.rows*i/10), cv::Point(left_dst.cols-1, left_dst.rows*i/10), cv::Scalar(0,0,255), 2); + cv::line(right_dst, cv::Point(0, right_dst.rows*i/10), cv::Point(right_dst.cols-1, right_dst.rows*i/10), cv::Scalar(0,0,255), 2); + } + } + cv::Mat combian_img; + cv::hconcat(left_dst, right_dst, combian_img); + QImage qimage = Mat2QImage(combian_img); + ui->Image->SetImage(qimage); + ui->Image->repaint(); + } +} + +// 角点寻找线程结束时的回调函数 +void CameraCalibration::DealThreadDone() +{ + findcorner_thread->quit(); + findcorner_thread->wait(); + thread_done = true; +} + +void CameraCalibration::receiveYamlPath(QString str) +{ + cv::FileStorage fs_read(str.toStdString(), cv::FileStorage::READ); + if(ui->single_undistort->isChecked()) + { + if(fs_read["cameraMatrix"].empty() || fs_read["distCoeffs"].empty()) + { + QMessageBox::critical(this, "错误", "YAML文件格式错误", QMessageBox::Yes, QMessageBox::Yes); + return; + } + cv::Mat cameraMatrix, distCoeffs; + fs_read["cameraMatrix"] >> cameraMatrix; + fs_read["distCoeffs"] >> distCoeffs; + mono_calib.setCameraParam(cameraMatrix, distCoeffs, fisheye_flag); + } + else + { + if(fs_read["left_camera_Matrix"].empty() || fs_read["left_camera_distCoeffs"].empty() || fs_read["right_camera_Matrix"].empty() || fs_read["right_camera_distCoeffs"].empty()) + { + QMessageBox::critical(this, "错误", "YAML文件格式错误", QMessageBox::Yes, QMessageBox::Yes); + return; + } + if(fs_read["Rotate_Matrix"].empty() || fs_read["Translate_Matrix"].empty() || fs_read["R1"].empty() || fs_read["R2"].empty() || fs_read["P1"].empty() || + fs_read["P2"].empty() || fs_read["Q"].empty()) + { + QMessageBox::critical(this, "错误", "YAML文件格式错误", QMessageBox::Yes, QMessageBox::Yes); + return; + } + cv::Mat left_in, right_in, left_d, right_d, r, t, r1, r2, p1, p2, q; + fs_read["left_camera_Matrix"] >> left_in; + fs_read["right_camera_Matrix"] >> right_in; + fs_read["left_camera_distCoeffs"] >> left_d; + fs_read["right_camera_distCoeffs"] >> right_d; + fs_read["Rotate_Matrix"] >> r; + fs_read["Translate_Matrix"] >> t; + fs_read["R1"] >> r1; + fs_read["R2"] >> r2; + fs_read["P1"] >> p1; + fs_read["P2"] >> p2; + fs_read["Q"] >> q; + bino_calib.setCameraParam(left_in, left_d, right_in, right_d, fisheye_flag, r, t, r1, r2, p1, p2, q); + } +} + +// 加载双目图像 +void CameraCalibration::receiveFromDialog(QString str) +{ + HiddenIntro(); + QStringList list = str.split(","); + QString left_src = list[0]; + QString right_src = list[1]; + double chessboard_size = list[2].toDouble(); + bino_calib.setChessboardSize(chessboard_size); + QDir dir(left_src); + dir.setFilter(QDir::Files | QDir::NoSymLinks); + QStringList filters; + filters << "*.png" << "*.jpg" << "*.jpeg" << "*.jpe" << "*.pbm" << "*.pgm" << "*.ppm" << "*.tiff" << "*.tif" << "*.bmp" << "*.dib"; + dir.setNameFilters(filters); + QStringList imagesList = dir.entryList(); + if(ui->double_camera->isChecked()) + { + if(imagesList.length() <= 3) + { + QMessageBox::critical(this, "错误", "至少需要四组图片", QMessageBox::Yes, QMessageBox::Yes); + return; + } + QProgressDialog *dialog = new QProgressDialog(tr("检测角点..."),tr("取消"),0,imagesList.length(),this); + dialog->setWindowModality(Qt::WindowModal); + dialog->setMinimumDuration(0); + dialog->setWindowTitle("请稍候"); + dialog->setValue(0); + QFont font = dialog->font(); + font.setPixelSize(12); + dialog->setFont(font); + dialog->show(); + QTextCodec *code = QTextCodec::codecForName("GB2312");//解决中文路径问题 + for(int i = 0; i < imagesList.length(); i++) + { + QString left_file_name = left_src + "/" + imagesList[i]; + QString right_file_name = right_src + "/" + imagesList[i]; + std::string str = code->fromUnicode(right_file_name).data(); + cv::Mat right_image = cv::imread(str); + if(right_image.empty()) + { + dialog->setValue(i+1); + continue; + } + str = code->fromUnicode(left_file_name).data(); + cv::Mat left_image = cv::imread(str); + if(left_image.cols != right_image.cols || left_image.rows != right_image.rows) + { + QMessageBox::critical(this, "错误", "左右相机图片尺寸不一致", QMessageBox::Yes, QMessageBox::Yes); + delete dialog; + return; + } + find_corner_thread_img = left_image; + thread_done = false; + findcorner_thread->start(); + while(thread_done == false) + { + if(dialog->wasCanceled()) + { + DealThreadDone(); + delete dialog; + return; + } + // 强制Windows消息循环,防止程序出现未响应情况 + QCoreApplication::processEvents(); + } + struct Chessboarder_t left_chess = find_corner_thread_chessboard; + // 如果检测到多个棋盘,则剔除该图片 + if(left_chess.chessboard.size() != 1) + { + dialog->setValue(i+1); + continue; + } + find_corner_thread_img = right_image; + thread_done = false; + findcorner_thread->start(); + while(thread_done == false) + { + if(dialog->wasCanceled()) + { + DealThreadDone(); + delete dialog; + return; + } + QCoreApplication::processEvents(); + } + struct Chessboarder_t right_chess = find_corner_thread_chessboard; + // 如果检测到多个棋盘,则剔除该图片 + if(right_chess.chessboard.size() != 1) + { + dialog->setValue(i+1); + continue; + } + // 如果左右图片检测到的棋盘尺寸不对,则剔除该组图片 + if(left_chess.chessboard[0].rows != right_chess.chessboard[0].rows || + left_chess.chessboard[0].cols != right_chess.chessboard[0].cols) + { + dialog->setValue(i+1); + continue; + } + // 缓存图片及角点 + struct Stereo_Img_t img_; + img_.left_path = code->fromUnicode(left_file_name).data(); + img_.right_path = code->fromUnicode(right_file_name).data(); + img_.left_file_name = imagesList[i]; + img_.right_file_name = imagesList[i]; + for (unsigned int j = 0; j < left_chess.chessboard.size(); j++) + { + for (int u = 0; u < left_chess.chessboard[j].rows; u++) + { + for (int v = 0; v < left_chess.chessboard[j].cols; v++) + { + img_.left_img_points.push_back(left_chess.corners.p[left_chess.chessboard[j].at(u, v)]); + img_.world_points.push_back(cv::Point3f(u*chessboard_size, v*chessboard_size, 0)); + img_.right_img_points.push_back(right_chess.corners.p[right_chess.chessboard[j].at(u, v)]); + } + } + } + stereo_imgs.push_back(img_); + img_size = left_image.size(); + int img_height = left_image.rows*90/left_image.cols; + cv::resize(left_image, left_image, cv::Size(90, img_height)); + cv::resize(right_image, right_image, cv::Size(90, img_height)); + cv::Mat combian_img = cv::Mat::zeros(cv::Size(180, img_height), CV_8UC3); + cv::Mat new_roi = combian_img(cv::Rect(0, 0, 90, img_height)); + left_image.convertTo(new_roi, new_roi.type()); + new_roi = combian_img(cv::Rect(90, 0, 90, img_height)); + right_image.convertTo(new_roi, new_roi.type()); + QPixmap pixmap = QPixmap::fromImage(Mat2QImage(combian_img)); + QListWidgetItem* temp = new QListWidgetItem(); + temp->setSizeHint(QSize(220, img_height)); + temp->setIcon(QIcon(pixmap)); + temp->setText(imagesList[i]); + ui->listWidget->addItem(temp); + ui->listWidget->setIconSize(QSize(180, img_height)); + dialog->setValue(i+1); + } + delete dialog; + } + else if(ui->double_undistort->isChecked()) + { + QTextCodec *code = QTextCodec::codecForName("GB2312");//解决中文路径问题 + for(int i = 0; i < imagesList.length(); i++) + { + QString left_file_name = left_src + "/" + imagesList[i]; + QString right_file_name = right_src + "/" + imagesList[i]; + std::string str = code->fromUnicode(right_file_name).data(); + cv::Mat right_image = cv::imread(str); + if(right_image.empty()) + { + continue; + } + str = code->fromUnicode(left_file_name).data(); + cv::Mat left_image = cv::imread(str); + if(left_image.cols != right_image.cols || left_image.rows != right_image.rows) + { + QMessageBox::critical(this, "错误", "左右相机图片尺寸不一致", QMessageBox::Yes, QMessageBox::Yes); + return; + } + struct Stereo_Img_t img_; + img_.left_path = code->fromUnicode(left_file_name).data(); + img_.right_path = code->fromUnicode(right_file_name).data(); + img_.left_file_name = imagesList[i]; + img_.right_file_name = imagesList[i]; + stereo_imgs.push_back(img_); + img_size = left_image.size(); + int img_height = left_image.rows*90/left_image.cols; + cv::resize(left_image, left_image, cv::Size(90, img_height)); + cv::resize(right_image, right_image, cv::Size(90, img_height)); + cv::Mat combian_img = cv::Mat::zeros(cv::Size(180, img_height), CV_8UC3); + cv::Mat new_roi = combian_img(cv::Rect(0, 0, 90, img_height)); + left_image.convertTo(new_roi, new_roi.type()); + new_roi = combian_img(cv::Rect(90, 0, 90, img_height)); + right_image.convertTo(new_roi, new_roi.type()); + QPixmap pixmap = QPixmap::fromImage(Mat2QImage(combian_img)); + QListWidgetItem* temp = new QListWidgetItem(); + temp->setSizeHint(QSize(220, img_height)); + temp->setIcon(QIcon(pixmap)); + temp->setText(imagesList[i]); + ui->listWidget->addItem(temp); + ui->listWidget->setIconSize(QSize(180, img_height)); + } + } +} + +// 鱼眼相机切换 +void CameraCalibration::fisheyeModeSwitch(int state) +{ + fisheye_flag = state==0 ? false : true; + if(fisheye_flag == true) + { + ui->k_2->setDisabled(true); + ui->k_3->setDisabled(true); + ui->tangential->setDisabled(true); + } + else + { + ui->k_2->setEnabled(true); + ui->k_3->setEnabled(true); + ui->tangential->setEnabled(true); + } +} + +// 畸变矫正切换 +void CameraCalibration::distortModeSwitch() +{ + if((!mono_calib.hasParam() && ui->single_undistort->isChecked()) || + (!bino_calib.hasParam() && ui->double_undistort->isChecked())) + { + chooseYaml = new choose_yaml(this); + chooseYaml->setWindowTitle("选择相机参数文件"); + QFont font = chooseYaml->font(); + font.setPixelSize(12); + chooseYaml->setFont(font); + chooseYaml->show(); + connect(chooseYaml, SIGNAL(SendSignal(QString)), this, SLOT(receiveYamlPath(QString))); + return; + } + static int cnt = 0; + cnt++; + if(cnt%2 == 1) + { + distort_flag = true; + } + else + { + distort_flag = false; + } + if(list_select_) + { + int id = ui->listWidget->selectionModel()->selectedIndexes()[0].row(); + chooseImage(ui->listWidget->item(id), ui->listWidget->item(id)); + } +} + +// Mat格式转QImage格式 +QImage CameraCalibration::Mat2QImage(cv::Mat cvImg) +{ + QImage qImg; + if(cvImg.channels()==3) + { + cv::cvtColor(cvImg,cvImg,cv::COLOR_BGR2RGB); + qImg =QImage((const unsigned char*)(cvImg.data), + cvImg.cols, cvImg.rows, + cvImg.cols*cvImg.channels(), + QImage::Format_RGB888); + } + else if(cvImg.channels()==1) + { + qImg =QImage((const unsigned char*)(cvImg.data), + cvImg.cols,cvImg.rows, + cvImg.cols*cvImg.channels(), + QImage::Format_Indexed8); + } + else + { + qImg =QImage((const unsigned char*)(cvImg.data), + cvImg.cols,cvImg.rows, + cvImg.cols*cvImg.channels(), + QImage::Format_RGB888); + } + return qImg; +} + +// 单目相机图片加载,逻辑同双目相机图片加载 +void CameraCalibration::addImage() +{ + HiddenIntro(); + if(ui->double_camera->isChecked() || ui->double_undistort->isChecked()) + { + d = new choose_two_dir(this); + d->setWindowTitle("选择图片文件夹"); + QFont font = d->font(); + font.setPixelSize(12); + d->setFont(font); + d->show(); + connect(d, SIGNAL(SendSignal(QString)), this, SLOT(receiveFromDialog(QString))); + return; + } + else if(ui->single_camera->isChecked()) + { + QStringList path_list = QFileDialog::getOpenFileNames(this, tr("选择图片"), tr("./"), tr("图片文件(*.jpg *.png *.pgm);;所有文件(*.*);;")); + QProgressDialog *dialog = new QProgressDialog(tr("检测角点..."),tr("取消"),0,path_list.size(), this); + dialog->setWindowModality(Qt::WindowModal); + dialog->setMinimumDuration(0); + dialog->setWindowTitle("请稍候"); + dialog->setValue(0); + QFont font = dialog->font(); + font.setPixelSize(12); + dialog->setFont(font); + dialog->show(); + QTextCodec *code = QTextCodec::codecForName("GB2312");//解决中文路径问题 + for(int i = 0; i < path_list.size(); i++) + { + QFileInfo file = QFileInfo(path_list[i]); + QString file_name = file.fileName(); + std::string str = code->fromUnicode(path_list[i]).data(); + cv::Mat img = cv::imread(str); + find_corner_thread_img = img; + thread_done = false; + findcorner_thread->start(); + while(thread_done == false) + { + if(dialog->wasCanceled()) + { + DealThreadDone(); + delete dialog; + return; + } + QCoreApplication::processEvents(); + } + struct Chessboarder_t chess = find_corner_thread_chessboard; + if(chess.chessboard.size() != 1) + { + dialog->setValue(i+1); + continue; + } + struct Img_t img_; + img_.file_path = str; + img_.file_name = file_name; + std::vector img_p; + std::vector world_p; + for (unsigned int j = 0; j < chess.chessboard.size(); j++) + { + for (int u = 0; u < chess.chessboard[j].rows; u++) + { + for (int v = 0; v < chess.chessboard[j].cols; v++) + { + img_p.push_back(chess.corners.p[chess.chessboard[j].at(u, v)]); + world_p.push_back(cv::Point3f(u*mono_calib.getChessboardSize(), v*mono_calib.getChessboardSize(), 0)); + } + } + } + img_.img_points = img_p; + img_.world_points = world_p; + imgs.push_back(img_); + img_size = img.size(); + int img_height = img.rows*124/img.cols; + cv::resize(img, img, cv::Size(124, img_height)); + QPixmap pixmap = QPixmap::fromImage(Mat2QImage(img)); + QListWidgetItem* temp = new QListWidgetItem(); + temp->setSizeHint(QSize(200, img_height)); + temp->setIcon(QIcon(pixmap)); + temp->setText(file_name); + ui->listWidget->addItem(temp); + ui->listWidget->setIconSize(QSize(124, img_height)); + dialog->setValue(i+1); + } + delete dialog; + } + else if(ui->single_undistort->isChecked()) + { + QStringList path_list = QFileDialog::getOpenFileNames(this, tr("选择图片"), tr("./"), tr("图片文件(*.jpg *.png *.pgm);;所有文件(*.*);;")); + QTextCodec *code = QTextCodec::codecForName("GB2312");//解决中文路径问题 + for(int i = 0; i < path_list.size(); i++) + { + QFileInfo file = QFileInfo(path_list[i]); + QString file_name = file.fileName(); + std::string str = code->fromUnicode(path_list[i]).data(); + cv::Mat img = cv::imread(str); + img_size = img.size(); + Img_t single_img; + single_img.file_path = str; + single_img.file_name = file_name; + imgs.push_back(single_img); + int img_height = img.rows*124/img.cols; + cv::resize(img, img, cv::Size(124, img_height)); + QPixmap pixmap = QPixmap::fromImage(Mat2QImage(img)); + QListWidgetItem* temp = new QListWidgetItem(); + temp->setSizeHint(QSize(200, img_height)); + temp->setIcon(QIcon(pixmap)); + temp->setText(file_name); + ui->listWidget->addItem(temp); + ui->listWidget->setIconSize(QSize(124, img_height)); + } + } +} + +// 删除选中的图片 +void CameraCalibration::deleteImage() +{ + std::vector delete_idx; + foreach(QModelIndex index,ui->listWidget->selectionModel()->selectedIndexes()){ + delete_idx.push_back(index.row()); + } + if(delete_idx.size() == 0) + return; + std::sort(delete_idx.begin(), delete_idx.end()); + disconnect(ui->listWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(chooseImage(QListWidgetItem*, QListWidgetItem*))); + for(int i = delete_idx.size()-1; i >= 0; i--) + { + ui->listWidget->takeItem(delete_idx[i]); + if(ui->double_camera->isChecked()) + stereo_imgs.erase(stereo_imgs.begin()+delete_idx[i]); + else + imgs.erase(imgs.begin()+delete_idx[i]); + } + connect(ui->listWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(chooseImage(QListWidgetItem*, QListWidgetItem*))); + // 如果图片全部删除,则重新显示文字引导 + if(ui->listWidget->count() == 0) + { + reset(); + } +} + +// 相机标定 +void CameraCalibration::calibrate() +{ + if(!ui->double_camera->isChecked()) + { + // 单目标定 + int state = mono_calib.calibrate(imgs, fisheye_flag, ui->tangential->isChecked(), ui->k_3->isChecked()); + if(state == 1) + { + QMessageBox::critical(this, "错误", "至少需要四张图片", QMessageBox::Yes, QMessageBox::Yes); + return; + } + else if(state == 2) + { + QMessageBox::critical(this, "错误", "请采集更多方位的图片或者检查角点识别!", QMessageBox::Yes, QMessageBox::Yes); + return; + } + } + else + { + if(!bino_calib.isChessboardSizeValid()) + { + bool ok; + double chessboard_size = QInputDialog::getDouble(this,tr("角点间距"),tr("请输入角点间距(mm)"),20,0,1000,2,&ok); + if(!ok) + { + chessboard_size = 0; + return; + } + bino_calib.setChessboardSize(chessboard_size); + } + // 双目标定 + int state = bino_calib.calibrate(stereo_imgs, fisheye_flag, ui->tangential->isChecked(), ui->k_3->isChecked()); + if(state == 1) + { + QMessageBox::critical(this, "错误", "至少需要四组图片", QMessageBox::Yes, QMessageBox::Yes); + return; + } + else if(state == 2) + { + QMessageBox::critical(this, "错误", "请采集更多方位的图片!", QMessageBox::Yes, QMessageBox::Yes); + return; + } + } +} + +// 导出标定参数 +void CameraCalibration::exportParam() +{ + if((!mono_calib.hasParam() && ui->single_undistort->isChecked()) || + (!bino_calib.hasParam() && ui->double_undistort->isChecked())) + { + QMessageBox::critical(this, "错误", "请先标定相机", QMessageBox::Yes); + return; + } + QString strSaveName = QFileDialog::getSaveFileName(this,tr("保存的文件"),tr("calibration_param.yaml"),tr("yaml files(*.yaml)")); + if(strSaveName.isNull() || strSaveName.isEmpty() || strSaveName.length() == 0) + return; + cv::FileStorage fs_write(strSaveName.toStdString(), cv::FileStorage::WRITE); + if(ui->double_camera->isChecked()) + { + bino_calib.exportParam(strSaveName.toStdString()); + } + else + { + mono_calib.exportParam(strSaveName.toStdString()); + } +} diff --git a/CameraCalibration.h b/CameraCalibration.h new file mode 100644 index 0000000..bc46d86 --- /dev/null +++ b/CameraCalibration.h @@ -0,0 +1,78 @@ +#ifndef CameraCalibration_H +#define CameraCalibration_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "findCorner.h" +#include "chessboard.h" +#include "choose_two_dir.h" +#include "choose_yaml.h" +#include "types.h" +#include "monocular_calib.h" +#include "binocular_calib.h" +#include "AboutUs.h" + +QT_BEGIN_NAMESPACE +namespace Ui { class CameraCalibration; } +QT_END_NAMESPACE + +extern cv::Mat find_corner_thread_img; +extern struct Chessboarder_t find_corner_thread_chessboard; + +class CameraCalibration : public QMainWindow +{ + Q_OBJECT + +public: + CameraCalibration(QWidget *parent = nullptr); + ~CameraCalibration(); + +private slots: + void addImage(); + void deleteImage(); + void calibrate(); + void fisheyeModeSwitch(int state); + void exportParam(); + void distortModeSwitch(); + void receiveFromDialog(QString str); + void receiveYamlPath(QString str); + void chooseImage(QListWidgetItem* item, QListWidgetItem*); + void reset(); + void saveUndistort(); + void DealThreadDone(); + void showIntro(); + +private: + Ui::CameraCalibration *ui; + choose_two_dir *d; + choose_yaml *chooseYaml; + AboutUs* a; + QImage Mat2QImage(cv::Mat cvImg); + void ShowIntro(); + void HiddenIntro(); + QAction *saveImage; + QAction *about; + std::vector imgs; + std::vector stereo_imgs; + MonoCalib mono_calib; + BinoCalib bino_calib; + cv::Size img_size; + bool fisheye_flag = false; + bool distort_flag = false; + FindcornerThread *findcorner_thread; + bool thread_done = false; + bool list_select_ = false; +protected: + void resizeEvent(QResizeEvent* event) override; +}; +#endif // CameraCalibration_H diff --git a/camera_calibration/CameraCalibration.ui b/CameraCalibration.ui similarity index 88% rename from camera_calibration/CameraCalibration.ui rename to CameraCalibration.ui index a78f2ff..972e0e9 100644 --- a/camera_calibration/CameraCalibration.ui +++ b/CameraCalibration.ui @@ -44,7 +44,7 @@ 10 15 60 - 40 + 60 @@ -87,27 +87,6 @@ - - - - 10 - 50 - 60 - 25 - - - - - 8 - - - - PointingHandCursor - - - 添加 - - @@ -324,12 +303,6 @@ 150 - - - 248 - 16777215 - - @@ -345,12 +318,6 @@ 518 - - - 248 - 16777215 - - @@ -375,40 +342,21 @@ - - - - 250 - 80 - 550 - 520 - - - - - 微软雅黑 - - - - Image - - + + - 10 - 15 - 530 - 495 + 250 + 80 + 550 + 520 - - - - - Qt::AlignCenter - + + Image + - + @@ -539,7 +487,7 @@ 0 0 800 - 22 + 28 @@ -550,6 +498,14 @@ + + + QImgViewWidget + QWidget +
QImgViewWidget.h
+ 1 +
+
diff --git a/camera_calibration/chessboard.cpp b/DetectCorner/chessboard.cpp similarity index 100% rename from camera_calibration/chessboard.cpp rename to DetectCorner/chessboard.cpp diff --git a/camera_calibration/chessboard.h b/DetectCorner/chessboard.h similarity index 100% rename from camera_calibration/chessboard.h rename to DetectCorner/chessboard.h diff --git a/camera_calibration/findCorner.cpp b/DetectCorner/findCorner.cpp similarity index 97% rename from camera_calibration/findCorner.cpp rename to DetectCorner/findCorner.cpp index 10dfa28..883ab71 100644 --- a/camera_calibration/findCorner.cpp +++ b/DetectCorner/findCorner.cpp @@ -18,7 +18,7 @@ void FindcornerThread::run() struct Chessboarder_t findCorner(cv::Mat img, int sigma) { if (img.channels() == 3) - cv::cvtColor(img, img, CV_BGR2GRAY); + cv::cvtColor(img, img, cv::COLOR_BGR2GRAY); cv::Mat du = (cv::Mat_(3, 3) << -1, 0, 1, -1, 0, 1, -1, 0, 1); cv::Mat dv = du.t(); cv::Mat img_dv, img_du; @@ -26,8 +26,8 @@ struct Chessboarder_t findCorner(cv::Mat img, int sigma) cv::Mat img_weight = cv::Mat::zeros(img.size(), CV_64F); cv::Mat img_double; img.convertTo(img_double, CV_64F); - cv::filter2D(img_double, img_du, img_double.depth(), du, cvPoint(-1, -1)); - cv::filter2D(img_double, img_dv, img_double.depth(), dv, cvPoint(-1, -1)); + cv::filter2D(img_double, img_du, img_double.depth(), du, cv::Point(-1, -1)); + cv::filter2D(img_double, img_dv, img_double.depth(), dv, cv::Point(-1, -1)); for (int i = 0; i < img.rows; i++) { for (int j = 0; j < img.cols; j++) @@ -122,8 +122,8 @@ void secondDerivCornerMetric(cv::Mat I, int sigma, cv::Mat* cxy, cv::Mat* c45, c cv::GaussianBlur(I, Ig, cv::Size(sigma*7+1, sigma*7+1), sigma, sigma); cv::Mat du = (cv::Mat_(1, 3) << 1, 0, -1); cv::Mat dv = du.t(); - cv::filter2D(Ig, *Ix, Ig.depth(), du, cvPoint(-1, -1)); - cv::filter2D(Ig, *Iy, Ig.depth(), dv, cvPoint(-1, -1)); + cv::filter2D(Ig, *Ix, Ig.depth(), du, cv::Point(-1, -1)); + cv::filter2D(Ig, *Iy, Ig.depth(), dv, cv::Point(-1, -1)); cv::Mat I_45 = I.clone(); cv::Mat I_n45 = I.clone(); @@ -140,10 +140,10 @@ void secondDerivCornerMetric(cv::Mat I, int sigma, cv::Mat* cxy, cv::Mat* c45, c } } - cv::filter2D(*Ix, *Ixy, Ix->depth(), dv, cvPoint(-1, -1)); + cv::filter2D(*Ix, *Ixy, Ix->depth(), dv, cv::Point(-1, -1)); cv::Mat I_45_x, I_45_y; - cv::filter2D(I_45, I_45_x, I_45.depth(), du, cvPoint(-1, -1)); - cv::filter2D(I_45, I_45_y, I_45.depth(), dv, cvPoint(-1, -1)); + cv::filter2D(I_45, I_45_x, I_45.depth(), du, cv::Point(-1, -1)); + cv::filter2D(I_45, I_45_y, I_45.depth(), dv, cv::Point(-1, -1)); for (int i = 0; i < I.rows; i++) { for (int j = 0; j < I.cols; j++) diff --git a/camera_calibration/findCorner.h b/DetectCorner/findCorner.h similarity index 100% rename from camera_calibration/findCorner.h rename to DetectCorner/findCorner.h diff --git a/LICENSE b/LICENSE deleted file mode 100644 index f288702..0000000 --- a/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/QImgViewWidget.cpp b/QImgViewWidget.cpp new file mode 100644 index 0000000..d80e73d --- /dev/null +++ b/QImgViewWidget.cpp @@ -0,0 +1,93 @@ +#include "QImgViewWidget.h" +#include +#include + +QImgViewWidget::QImgViewWidget(QWidget * parent):QWidget(parent) +{ + zoom_scale_ = 1.0f; + move_start_ = false; + is_moving_ = false; +} + +void QImgViewWidget::SetImage(const QImage& img) +{ + // reset the transformation + ResetTransform(); + QSize view_size = size(); + // loading by QImage + img_display_ = img; + pix_ori_ = QPixmap::fromImage(img_display_); + if(pix_ori_.isNull()) return; + pix_display_ = pix_ori_.scaled(zoom_scale_ * size(), Qt::KeepAspectRatio); +} + +void QImgViewWidget::ResetTransform() +{ + // reset the zoom scale and move step + zoom_scale_ = 1.0f; + move_step_ = QPoint(0, 0); +} + +void QImgViewWidget::paintEvent(QPaintEvent* event) +{ + // rander the loading image + // TODO(Ethan): It's slow and memory confusing when zooming in + if(pix_display_.isNull()) return; + QPainter painter(this); + painter.drawPixmap(move_step_.x() +(width() - pix_display_.width()) / 2, move_step_ .y()+ (height() - pix_display_.height()) / 2, pix_display_); +} + +void QImgViewWidget::wheelEvent(QWheelEvent* event) +{ + // zoom in and out when scrolling mouse + if (event->delta() > 0) { + zoom_scale_ *= 1.1; + } + else { + zoom_scale_ *= 0.9; + } + // TODO(Ethan): It's slow and memory confusing when zooming in + if(!pix_ori_.isNull()) + pix_display_ = pix_ori_.scaled(zoom_scale_ * size(), Qt::KeepAspectRatio); + update(); +} + +void QImgViewWidget::mousePressEvent(QMouseEvent* event) +{ + if (event->button() == Qt::LeftButton) { + if (!move_start_) { + move_start_ = true; + is_moving_ = false; + mouse_point_ = event->globalPos(); + } + } +} + +void QImgViewWidget::mouseReleaseEvent(QMouseEvent* event) +{ + if (event->button() == Qt::LeftButton) { + if(move_start_) { + move_start_ = false; + is_moving_ = false; + } + } +} + +void QImgViewWidget::mouseMoveEvent(QMouseEvent* event) +{ + // move image when moving mouse + if (move_start_) { + const QPoint mos_pt = event->globalPos(); + move_step_ += mos_pt - mouse_point_; + is_moving_ = true; + mouse_point_ = mos_pt; + repaint(); + } +} + +void QImgViewWidget::resizeEvent(QResizeEvent* event) +{ + if(!pix_ori_.isNull()) + pix_display_ = pix_ori_.scaled(zoom_scale_ * size(), Qt::KeepAspectRatio); + update(); +} diff --git a/QImgViewWidget.h b/QImgViewWidget.h new file mode 100644 index 0000000..2d0ce3f --- /dev/null +++ b/QImgViewWidget.h @@ -0,0 +1,44 @@ +#ifndef QIMGVIEW_WIDGET_H +#define QIMGVIEW_WIDGET_H + +#include +#include + +// widget for displaying 2d image +class QImgViewWidget : + public QWidget +{ +public: + QImgViewWidget(QWidget * parent = 0); + ~QImgViewWidget() = default; + + /**\brief load image date from file path */ + void SetImage(const QImage& img); + + void ResetTransform(); +private: + /* image data for displaying */ + QImage img_display_; + /* original pixmap data*/ + QPixmap pix_ori_; + /* pixmap data for displaying */ + QPixmap pix_display_; + + /* zoom scale controlled by mouse wheel */ + float zoom_scale_; + /* move step controlled by mouse moving*/ + QPoint move_step_; + bool move_start_; + bool is_moving_; + QPoint mouse_point_; + +protected: + void paintEvent(QPaintEvent* event) override; + void wheelEvent(QWheelEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void resizeEvent(QResizeEvent* event) override; +}; + +#endif \ No newline at end of file diff --git a/README.md b/README.md index 6d0201c..f7bce9d 100644 --- a/README.md +++ b/README.md @@ -1,132 +1,32 @@ # TStoneCalibration 计算机视觉标定工具箱 -计算机视觉标定工具箱项目由香港中文大学天石机器人研究所嵌入式AI组发起,基于Qt和OpenCV开发,目标是做一款可以满足各种视觉设备标定的工具箱。 +计算机视觉标定工具箱项目由香港中文大学天石机器人研究所嵌入式AI组发起,目标是做一款可以满足各种视觉设备标定的工具箱。 目前已开发的标定工具有: * 光学相机内参标定工具 -## 文件结构 -|文件名/文件夹名|功能| -|:--|:--| -|main.cpp|程序入口| -|mainwindow.cpp|工具箱主界面| -|AboutUs.cpp|【关于我们】对话框的实现| -|camera_calibration|光学相机内参标定工具| - -每一个标定工具文件夹里有更详细的README介绍文件。 - -## 下载 -Release页面有最新的Windows程序下载。 - -## Contribute -### 环境配置 -1. 安装Qt以及Qt Creator,使用Qt Creator打开`TStoneCalibration.pro` - -2. 修改`TStoneCalibration.pro`中OpenCV的头文件路径和动态链接库的路径为你当前电脑里对应的路径 -```txt -INCLUDEPATH += C:/Users/uncle/Desktop/OpenCV/install/include\ - C:/Users/uncle/Desktop/OpenCV/install/include/opencv\ - C:/Users/uncle/Desktop/OpenCV/install/include/opencv2 - -LIBS += C:/Users/uncle/Desktop/OpenCV/install/x86/mingw/lib/libopencv_core310.dll.a\ - C:/Users/uncle/Desktop/OpenCV/install/x86/mingw/lib/libopencv_calib3d310.dll.a\ - C:/Users/uncle/Desktop/OpenCV/install/x86/mingw/lib/libopencv_highgui310.dll.a\ - C:/Users/uncle/Desktop/OpenCV/install/x86/mingw/lib/libopencv_imgcodecs310.dll.a\ - C:/Users/uncle/Desktop/OpenCV/install/x86/mingw/lib/libopencv_imgproc310.dll.a\ - C:/Users/uncle/Desktop/OpenCV/install/x86/mingw/lib/libopencv_features2d310.dll.a -``` -修改上述内容。 - -3. 如果你在Linux上开发,需安装`v4l2`库。 +## 编译 +项目基于`Qt`和`OpenCV 4`,在Ubuntu 20.04下开发。 +* 安装Qt ```bash -sudo apt-get install libv4l-dev -``` - -### 开发 -在此基础上可以很容易添加新的工具,添加流程如下(以下假设要添加一个名为`Hand_eye_calibration`的工具): - -#### 1.新建文件夹 -在根目录下新建一个名为`hand_eye_calibration`的文件夹。 - -#### 2.创建程序入口函数 -使用Qt Creator在该文件夹下新建一个UI,命名为HandEyeCalibration - -![new_ui](guide/new_ui.jpg) - -![new_ui](guide/new_ui_dir.jpg) - -这会自动创建`handeyecalibration.cpp`,`handeyecalibration.h`,`handeyecalibration.ui`三个文件。 - -#### 3.在工具箱界面中添加图标 -打开`mainwindow.ui`,添加工具图标。 - -![new_ui](guide/new_ui_3.jpg) - -该图标由一个QPushButton和一个QLable组成,QPushButton的名字我设置为`HandEyeCalib`。 - -#### 4.添加相关逻辑 -打开`mainwindow.h`,添加头文件: -```cpp -#include "hand_eye_calibration/handeyecalibration.h" +sudo apt-get install qt5-default ``` - -添加私有成员变量和槽函数 -```cpp -class MainWindow : public QMainWindow -{ - Q_OBJECT - -public: - explicit MainWindow(QWidget *parent = nullptr); - ~MainWindow(); - -private slots: - void startCameraCalib(); - void showIntro(); - void startHandEyeCalib(); // 添加这一行 - -private: - Ui::MainWindow *ui; - CameraCalibration* camera_calibration; - QAction *about; - AboutUs *a; - HandEyeCalibration *hand_eye; // 添加这一行 -}; +* 安装OpenCV 4 +```bash +sudo apt-get install libopencv-core4.2 ``` -打开`mainwindow.cpp`,添加槽函数的实现: -```cpp -MainWindow::MainWindow(QWidget *parent) : - QMainWindow(parent), - ui(new Ui::MainWindow) -{ - ui->setupUi(this); - setFixedSize(this->width(), this->height()); - ui->CameraCalib->setFlat(true); - connect(ui->CameraCalib, SIGNAL(clicked()), this, SLOT(startCameraCalib())); - about = ui->menu->addAction("关于"); - QFont font = about->font(); - font.setPixelSize(12); - about->setFont(font); - connect(about, SIGNAL(triggered()), this, SLOT(showIntro())); - // 添加下面这两行,关联按钮信号和槽函数 - ui->HandEyeCalib->setFlat(true); // 设置QPushButton颜色透明 - connect(ui->HandEyeCalib, SIGNAL(clicked()), this, SLOT(startHandEyeCalib())); -} +也可以使用OpenCV 3,里面有一些OpenCV的宏定义需要修改一下。 -void MainWindow::startHandEyeCalib() -{ - hand_eye = new HandEyeCalibration(); - QFont font = hand_eye->font(); - font.setPixelSize(12); - hand_eye->setFont(font); - hand_eye->setWindowTitle("手眼标定"); - hand_eye->show(); -} +* 编译 +```bash +mkdir build +cd build +cmake .. +make ``` -#### 5.编译运行 -![new_tool](guide/new_tool.jpg) +## 下载 +Release页面有最新的Windows程序下载。 -添加成功!你可以专心开发你的工具了。 \ No newline at end of file diff --git a/TStoneCalibration.pro b/TStoneCalibration.pro deleted file mode 100644 index adaeaf5..0000000 --- a/TStoneCalibration.pro +++ /dev/null @@ -1,81 +0,0 @@ -QT += core gui -QT += multimedia multimediawidgets -QT += network -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets - -CONFIG += c++11 - -# The following define makes your compiler emit warnings if you use -# any Qt feature that has been marked deprecated (the exact warnings -# depend on your compiler). Please consult the documentation of the -# deprecated API in order to know how to port your code away from it. -DEFINES += QT_DEPRECATED_WARNINGS - -# You can also make your code fail to compile if it uses deprecated APIs. -# In order to do so, uncomment the following line. -# You can also select to disable deprecated APIs only up to a certain version of Qt. -#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 - -RC_ICONS = icon.ico - -SOURCES += \ - AboutUs.cpp \ - camera_calibration/CameraCalibration.cpp \ - camera_calibration/chessboard.cpp \ - camera_calibration/choose_two_dir.cpp \ - camera_calibration/double_capture.cpp \ - camera_calibration/double_capture_linux.cpp \ - camera_calibration/findCorner.cpp \ - camera_calibration/single_capture_linux.cpp \ - hand_eye_calibration/HandEyeCalibration.cpp \ - hand_eye_calibration/TSAIleastSquareCalibration.cpp \ - main.cpp \ - camera_calibration/single_capture.cpp \ - camera_calibration/choose_yaml.cpp \ - mainwindow.cpp - -HEADERS += \ - AboutUs.h \ - camera_calibration/CameraCalibration.h \ - camera_calibration/chessboard.h \ - camera_calibration/choose_two_dir.h \ - camera_calibration/double_capture.h \ - camera_calibration/double_capture_linux.h \ - camera_calibration/findCorner.h \ - camera_calibration/single_capture.h \ - camera_calibration/single_capture_linux.h \ - camera_calibration/v4l2.hpp \ - camera_calibration/choose_yaml.h \ - hand_eye_calibration/HandEyeCalibration.h \ - hand_eye_calibration/TSAIleastSquareCalibration.h \ - mainwindow.h - -FORMS += \ - AboutUs.ui \ - camera_calibration/CameraCalibration.ui \ - camera_calibration/choose_two_dir.ui \ - camera_calibration/double_capture.ui \ - camera_calibration/single_capture.ui \ - camera_calibration/choose_yaml.ui \ - hand_eye_calibration/HandEyeCalibration.ui \ - mainwindow.ui - -INCLUDEPATH += C:/Users/uncle/Desktop/OpenCV_3.2.0/include\ - C:/Users/uncle/Desktop/OpenCV_3.2.0/include/opencv\ - C:/Users/uncle/Desktop/OpenCV_3.2.0/include/opencv2 - -LIBS += C:/Users/uncle/Desktop/OpenCV_3.2.0/x64/mingw/lib/libopencv_core320.dll.a\ - C:/Users/uncle/Desktop/OpenCV_3.2.0/x64/mingw/lib/libopencv_calib3d320.dll.a\ - C:/Users/uncle/Desktop/OpenCV_3.2.0/x64/mingw/lib/libopencv_highgui320.dll.a\ - C:/Users/uncle/Desktop/OpenCV_3.2.0/x64/mingw/lib/libopencv_imgcodecs320.dll.a\ - C:/Users/uncle/Desktop/OpenCV_3.2.0/x64/mingw/lib/libopencv_imgproc320.dll.a\ - C:/Users/uncle/Desktop/OpenCV_3.2.0/x64/mingw/lib/libopencv_features2d320.dll.a - -# Default rules for deployment. -qnx: target.path = /tmp/$${TARGET}/bin -else: unix:!android: target.path = /opt/$${TARGET}/bin -!isEmpty(target.path): INSTALLS += target - -RESOURCES += \ - camera_calibration/res/image.qrc \ - hand_eye_calibration/res/hand_eye_image.qrc diff --git a/camera_calibration/CameraCalibration.cpp b/camera_calibration/CameraCalibration.cpp deleted file mode 100644 index f8cdbf7..0000000 --- a/camera_calibration/CameraCalibration.cpp +++ /dev/null @@ -1,1411 +0,0 @@ -#include "CameraCalibration.h" -#include "ui_CameraCalibration.h" - -cv::Mat find_corner_thread_img; -struct Chessboarder_t find_corner_thread_chessboard; - -CameraCalibration::CameraCalibration(QWidget *parent) - : QMainWindow(parent) - , ui(new Ui::CameraCalibration) -{ - ui->setupUi(this); - setFixedSize(this->width(), this->height()); - ui->addImage->setFlat(true); - ui->calibrate->setFlat(true); - ui->export_1->setFlat(true); - ui->delete_1->setFlat(true); - ui->undistort->setFlat(true); - ui->Camera->setFlat(true); - findcorner_thread = new FindcornerThread(); - connect(findcorner_thread, SIGNAL(isDone()), this, SLOT(DealThreadDone())); - ShowIntro(); - QMenu *menu = new QMenu(); - QAction *open_camera = menu->addAction("打开相机"); - QFont font = menu->font(); - font.setPixelSize(12); - menu->setFont(font); - connect(open_camera, SIGNAL(triggered()), this,SLOT(OpenCamera())); - ui->Camera->setMenu(menu); - ui->listWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); // 按ctrl多选 - connect(ui->addImage, SIGNAL(clicked()), this, SLOT(addImage())); - connect(ui->delete_1, SIGNAL(clicked()), this, SLOT(deleteImage())); - chessboard_size = 0; - connect(ui->calibrate, SIGNAL(clicked()), this, SLOT(calibrate())); - connect(ui->fisheye, SIGNAL(stateChanged(int)), this, SLOT(fisheyeModeSwitch(int))); - connect(ui->export_1, SIGNAL(clicked()), this, SLOT(exportParam())); - connect(ui->undistort, SIGNAL(clicked()), this, SLOT(distortModeSwitch())); - connect(ui->listWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(chooseImage(QListWidgetItem*, QListWidgetItem*))); - connect(ui->double_camera, SIGNAL(toggled(bool)), this, SLOT(reset())); - connect(ui->single_camera, SIGNAL(toggled(bool)), this, SLOT(reset())); - connect(ui->double_undistort, SIGNAL(toggled(bool)), this, SLOT(undistortReset())); - connect(ui->single_undistort, SIGNAL(toggled(bool)), this, SLOT(undistortReset())); - saveImage = ui->menu->addAction("导出矫正图片"); - font = saveImage->font(); - font.setPixelSize(12); - saveImage->setFont(font); - connect(saveImage, SIGNAL(triggered()), this, SLOT(saveUndistort())); -} - -CameraCalibration::~CameraCalibration() -{ - delete ui; -} - -// 显示简单的文字引导 -void CameraCalibration::ShowIntro() -{ - ui->intro->setFixedHeight(100); - if(ui->double_camera->isChecked() || ui->double_undistort->isChecked()) - { - QString str = "点击左上角添加图片"; - str += "\n"; - str += "左右相机图片分别放于两个文件夹"; - str += "\n"; - str += "对应图片名称需一致。"; - str += "\n"; - str += "\n"; - str += "单/双目纠正模式可纠正无棋盘的图片。"; - ui->intro->setText(str); - } - else if(ui->single_camera->isChecked() || ui->single_undistort->isChecked()) - { - QString str = "点击左上角添加图片"; - str += "\n"; - str += "15至20张较为适宜"; - str += "\n"; - str += "\n"; - str += "单/双目纠正模式可纠正无棋盘的图片。"; - ui->intro->setText(str); - } -} - -// 隐藏文字指导 -void CameraCalibration::HiddenIntro() -{ - ui->intro->setText(""); - ui->intro->setFixedHeight(0); -} - -// 使用相机采集图片,根据标定模式选择采集模式 -void CameraCalibration::OpenCamera() -{ - if(ui->single_camera->isChecked()) - { -#ifdef Q_OS_LINUX - int camcount = v4l2.GetDeviceCount(); - if(camcount < 1) - { - QMessageBox::warning(this, "警告", "没有检测到摄像头,请插上摄像头后再打开该窗口", QMessageBox::Yes); - return; - } - single_c = new single_capture_linux(); -#elif defined(Q_OS_WIN32) - QList camera_list = QCameraInfo::availableCameras(); - if(camera_list.length() < 1) - { - QMessageBox::warning(this, "警告", "没有检测到摄像头,请插上摄像头后再打开该窗口", QMessageBox::Yes); - return; - } - single_c = new single_capture(); -#endif - single_c->setWindowTitle("单个相机拍照"); - QFont font = single_c->font(); - font.setPixelSize(12); - single_c->setFont(font); - single_c->show(); - } - else - { -#ifdef Q_OS_LINUX - int camcount = v4l2_left.GetDeviceCount(); - if(camcount < 2) - { - QMessageBox::warning(this, "警告", "摄像头少于两个,请插上摄像头后再打开该窗口", QMessageBox::Yes); - return; - } - double_c = new double_capture_linux(); -#elif defined(Q_OS_WIN32) - QList camera_list = QCameraInfo::availableCameras(); - if(camera_list.length() < 2) - { - QMessageBox::warning(this, "警告", "摄像头少于两个,请插上摄像头后再打开该窗口", QMessageBox::Yes); - return; - } - double_c = new double_capture(); -#endif - double_c->setWindowTitle("两个相机拍照"); - QFont font = double_c->font(); - font.setPixelSize(12); - double_c->setFont(font); - double_c->show(); - } -} - -// 导出矫正图片 -void CameraCalibration::saveUndistort() -{ - if(calibrate_flag == false) - { - if(ui->single_undistort->isChecked() || ui->double_undistort->isChecked()) - { - QMessageBox::warning(this, "警告", "还未获取到相机参数,请点击UNDISTORT按钮加载yaml文件。", QMessageBox::Yes, QMessageBox::Yes); - return; - } - else - { - QMessageBox::critical(NULL, "错误", "请先标定", QMessageBox::Yes, QMessageBox::Yes); - return; - } - } - QString srcDirPath = QFileDialog::getExistingDirectory(nullptr, "选择保存路径", "./"); - if(srcDirPath.length() <= 0) - return; - QTextCodec *code = QTextCodec::codecForName("GB2312");//解决中文路径问题 - if(ui->double_camera->isChecked() || ui->double_undistort->isChecked()) - { - QDir dir; - dir.mkdir(srcDirPath+"/left/"); - dir.mkdir(srcDirPath+"/right/"); - for(unsigned int i = 0; i < stereo_imgs.size(); i++) - { - struct Stereo_Img_t img = stereo_imgs[i]; - cv::Mat left_dst = img.left_img.clone(); - cv::Mat right_dst = img.right_img.clone(); - if(fisheye_flag == false) - { - cv::Mat mapx, mapy; - cv::initUndistortRectifyMap(cameraMatrix, distCoefficients, R1, P1, img_size, CV_16SC2, mapx, mapy); - cv::remap(img.left_img, left_dst, mapx, mapy, cv::INTER_LINEAR); - cv::initUndistortRectifyMap(cameraMatrix2, distCoefficients2, R2, P2, img_size, CV_16SC2, mapx, mapy); - cv::remap(img.right_img, right_dst, mapx, mapy, cv::INTER_LINEAR); - } - else - { - cv::Mat mapx, mapy; - cv::fisheye::initUndistortRectifyMap(K, D, R1, P1, img_size, CV_16SC2, mapx, mapy); - cv::remap(img.left_img, left_dst, mapx, mapy, cv::INTER_LINEAR); - cv::fisheye::initUndistortRectifyMap(K2, D2, R2, P2, img_size, CV_16SC2, mapx, mapy); - cv::remap(img.right_img, right_dst, mapx, mapy, cv::INTER_LINEAR); - } - QString save_name = srcDirPath + "/left/" + img.left_file_name; - std::string str = code->fromUnicode(save_name).data(); - cv::imwrite(str, left_dst); - save_name = srcDirPath + "/right/" + img.left_file_name; - str = code->fromUnicode(save_name).data(); - cv::imwrite(str, right_dst); - for(int i = 1; i < 10; i++) - { - cv::line(left_dst, cv::Point(0, left_dst.rows*i/10), cv::Point(left_dst.cols-1, left_dst.rows*i/10), cv::Scalar(0,0,255), 2); - cv::line(right_dst, cv::Point(0, right_dst.rows*i/10), cv::Point(right_dst.cols-1, right_dst.rows*i/10), cv::Scalar(0,0,255), 2); - } - cv::Mat combian_img = cv::Mat::zeros(cv::Size(img.left_img.cols*2, img.left_img.rows), CV_8UC3); - cv::Mat new_roi = combian_img(cv::Rect(0, 0, img.left_img.cols, img.left_img.rows)); - left_dst.convertTo(new_roi, new_roi.type()); - new_roi = combian_img(cv::Rect(img.left_img.cols, 0, img.left_img.cols, img.left_img.rows)); - right_dst.convertTo(new_roi, new_roi.type()); - save_name = srcDirPath + "/" + img.left_file_name; - str = code->fromUnicode(save_name).data(); - cv::imwrite(str, combian_img); - } - } - else - { - for(unsigned int i = 0; i < imgs.size(); i++) - { - cv::Mat dst; - if(fisheye_flag == false) - cv::undistort(imgs[i].img, dst, cameraMatrix, distCoefficients); - else - cv::fisheye::undistortImage(imgs[i].img, dst, K, D, K, imgs[i].img.size()); - QString save_name = srcDirPath + "/" + imgs[i].file_name; - std::string str = code->fromUnicode(save_name).data(); - cv::imwrite(str, dst); - } - } -} - -// 切换模式时删除所有图片缓存 -void CameraCalibration::reset() -{ - ShowIntro(); - ui->k_2->setEnabled(true); - ui->k_3->setEnabled(true); - ui->tangential->setEnabled(true); - ui->calibrate->setEnabled(true); - ui->export_1->setEnabled(true); - chessboard_size = 0; - calibrate_flag = false; - distort_flag = false; - disconnect(ui->listWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(chooseImage(QListWidgetItem*, QListWidgetItem*))); - ui->listWidget->clear(); - connect(ui->listWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(chooseImage(QListWidgetItem*, QListWidgetItem*))); - if(imgs.size() > 0) - imgs.clear(); - if(stereo_imgs.size() > 0) - stereo_imgs.clear(); -} - -void CameraCalibration::undistortReset() -{ - ShowIntro(); - ui->k_2->setDisabled(true); - ui->k_3->setDisabled(true); - ui->tangential->setDisabled(true); - ui->calibrate->setDisabled(true); - ui->export_1->setDisabled(true); - chessboard_size = 0; - calibrate_flag = false; - distort_flag = false; - disconnect(ui->listWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(chooseImage(QListWidgetItem*, QListWidgetItem*))); - ui->listWidget->clear(); - connect(ui->listWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(chooseImage(QListWidgetItem*, QListWidgetItem*))); - if(imgs.size() > 0) - imgs.clear(); - if(stereo_imgs.size() > 0) - stereo_imgs.clear(); -} - -// 显示选择的图像 -void CameraCalibration::chooseImage(QListWidgetItem* item, QListWidgetItem*) -{ - int id = ui->listWidget->row(item); - if(ui->double_camera->isChecked()) - { - // 如果是双目模式 - struct Stereo_Img_t img = stereo_imgs[id]; - cv::Mat left_dst = img.left_img.clone(); - cv::Mat right_dst = img.right_img.clone(); - if(distort_flag == true && calibrate_flag == true) - { - // 对左右图像进行矫正 - if(fisheye_flag == false) - { - cv::Mat mapx, mapy; - cv::initUndistortRectifyMap(cameraMatrix, distCoefficients, R1, P1, img_size, CV_16SC2, mapx, mapy); - cv::remap(img.left_img, left_dst, mapx, mapy, cv::INTER_LINEAR); - cv::initUndistortRectifyMap(cameraMatrix2, distCoefficients2, R2, P2, img_size, CV_16SC2, mapx, mapy); - cv::remap(img.right_img, right_dst, mapx, mapy, cv::INTER_LINEAR); - } - else - { - cv::Mat mapx, mapy; - cv::fisheye::initUndistortRectifyMap(K, D, R1, P1, img_size, CV_16SC2, mapx, mapy); - cv::remap(img.left_img, left_dst, mapx, mapy, cv::INTER_LINEAR); - cv::fisheye::initUndistortRectifyMap(K2, D2, R2, P2, img_size, CV_16SC2, mapx, mapy); - cv::remap(img.right_img, right_dst, mapx, mapy, cv::INTER_LINEAR); - } - // 绘制横线以观察左右图像极线是否对齐 - for(int i = 1; i < 10; i++) - { - cv::line(left_dst, cv::Point(0, left_dst.rows*i/10), cv::Point(left_dst.cols-1, left_dst.rows*i/10), cv::Scalar(0,0,255), 2); - cv::line(right_dst, cv::Point(0, right_dst.rows*i/10), cv::Point(right_dst.cols-1, right_dst.rows*i/10), cv::Scalar(0,0,255), 2); - } - } - else - { - // 非矫正模式下显示角点位置 - for(unsigned int i = 0; i < img.left_img_points.size(); i++) - { - cv::circle(left_dst, img.left_img_points[i], 5, cv::Scalar(0,0,255), 2); - } - for(unsigned int i = 0; i < img.right_img_points.size(); i++) - { - cv::circle(right_dst, img.right_img_points[i], 5, cv::Scalar(0,0,255), 2); - } - if(calibrate_flag == true) - { - // 显示标定后角点的重投影 - if(fisheye_flag == false) - { - std::vector reproject_img_p; - projectPoints(img.world_points, img.left_rvec, img.left_tvec, cameraMatrix, distCoefficients, reproject_img_p); - for(unsigned int i = 0; i < reproject_img_p.size(); i++) - { - cv::circle(left_dst, reproject_img_p[i], 3, cv::Scalar(255,0,0), 2); - } - projectPoints(img.world_points, img.right_rvec, img.right_tvec, cameraMatrix2, distCoefficients2, reproject_img_p); - for(unsigned int i = 0; i < reproject_img_p.size(); i++) - { - cv::circle(right_dst, reproject_img_p[i], 3, cv::Scalar(255,0,0), 2); - } - } - else - { - std::vector reproject_img_p; - std::vector w_p_; - for(unsigned int i = 0; i < img.world_points.size(); i++) - { - w_p_.push_back(img.world_points[i]); - } - cv::fisheye::projectPoints(w_p_, reproject_img_p, img.left_fish_rvec, img.left_fish_tvec, K, D); - for(unsigned int i = 0; i < reproject_img_p.size(); i++) - { - cv::circle(left_dst, reproject_img_p[i], 3, cv::Scalar(255,0,0), 2); - } - cv::fisheye::projectPoints(w_p_, reproject_img_p, img.right_fish_rvec, img.right_fish_tvec, K2, D2); - for(unsigned int i = 0; i < reproject_img_p.size(); i++) - { - cv::circle(right_dst, reproject_img_p[i], 3, cv::Scalar(255,0,0), 2); - } - } - } - } - int height = img.left_img.rows*265/img.left_img.cols; - cv::resize(left_dst, left_dst, cv::Size(265, height)); - cv::resize(right_dst, right_dst, cv::Size(265, height)); - cv::Mat combian_img = cv::Mat::zeros(cv::Size(530, height), CV_8UC3); - cv::Mat new_roi = combian_img(cv::Rect(0, 0, 265, height)); - left_dst.convertTo(new_roi, new_roi.type()); - new_roi = combian_img(cv::Rect(265, 0, 265, height)); - right_dst.convertTo(new_roi, new_roi.type()); - QImage qimage = Mat2QImage(combian_img); - ui->Image->setPixmap(QPixmap::fromImage(qimage)); - } - else if(ui->single_camera->isChecked()) - { - // 单目逻辑同双目 - cv::Mat img; - std::vector corners; - std::vector w_p; - cv::Mat rvecs, tvecs; - cv::Vec3d fish_rvecs, fish_tvecs; - img = imgs[id].img.clone(); - corners = imgs[id].img_points; - w_p = imgs[id].world_points; - tvecs = imgs[id].tvec; - rvecs = imgs[id].rvec; - fish_rvecs = imgs[id].fish_rvec; - fish_tvecs = imgs[id].fish_tvec; - for(unsigned int i = 0; i < corners.size(); i++) - { - cv::circle(img, corners[i], 5, cv::Scalar(0,0,255), 2); - } - if(calibrate_flag == true) - { - if(fisheye_flag == false) - { - std::vector reproject_img_p; - projectPoints(w_p, rvecs, tvecs, cameraMatrix, distCoefficients, reproject_img_p); - for(unsigned int i = 0; i < reproject_img_p.size(); i++) - { - cv::circle(img, reproject_img_p[i], 3, cv::Scalar(255,0,0), 2); - } - } - else - { - std::vector reproject_img_p; - std::vector w_p_; - for(unsigned int i = 0; i < w_p.size(); i++) - { - w_p_.push_back(w_p[i]); - } - cv::fisheye::projectPoints(w_p_, reproject_img_p, fish_rvecs, fish_tvecs, K, D); - for(unsigned int i = 0; i < reproject_img_p.size(); i++) - { - cv::circle(img, reproject_img_p[i], 3, cv::Scalar(255,0,0), 2); - } - } - } - if(distort_flag == true && calibrate_flag == true) - { - cv::Mat dst; - if(fisheye_flag == false) - cv::undistort(img, dst, cameraMatrix, distCoefficients); - else - cv::fisheye::undistortImage(img, dst, K, D, K, img.size()); - img = dst; - } - if(img.cols > img.rows) - cv::resize(img, img, cv::Size(530, img.rows*530/img.cols)); - else - cv::resize(img, img, cv::Size(img.cols*495/img.rows, 495)); - QImage qimage = Mat2QImage(img); - ui->Image->setPixmap(QPixmap::fromImage(qimage)); - } - else if(ui->double_undistort->isChecked()) - { - // 如果是双目模式 - struct Stereo_Img_t img = stereo_imgs[id]; - cv::Mat left_dst = img.left_img.clone(); - cv::Mat right_dst = img.right_img.clone(); - if(distort_flag == true) - { - // 对左右图像进行矫正 - if(fisheye_flag == false) - { - cv::Mat mapx, mapy; - cv::initUndistortRectifyMap(cameraMatrix, distCoefficients, R1, P1, img_size, CV_16SC2, mapx, mapy); - cv::remap(img.left_img, left_dst, mapx, mapy, cv::INTER_LINEAR); - cv::initUndistortRectifyMap(cameraMatrix2, distCoefficients2, R2, P2, img_size, CV_16SC2, mapx, mapy); - cv::remap(img.right_img, right_dst, mapx, mapy, cv::INTER_LINEAR); - } - else - { - cv::Mat mapx, mapy; - cv::fisheye::initUndistortRectifyMap(K, D, R1, P1, img_size, CV_16SC2, mapx, mapy); - cv::remap(img.left_img, left_dst, mapx, mapy, cv::INTER_LINEAR); - cv::fisheye::initUndistortRectifyMap(K2, D2, R2, P2, img_size, CV_16SC2, mapx, mapy); - cv::remap(img.right_img, right_dst, mapx, mapy, cv::INTER_LINEAR); - } - // 绘制横线以观察左右图像极线是否对齐 - for(int i = 1; i < 10; i++) - { - cv::line(left_dst, cv::Point(0, left_dst.rows*i/10), cv::Point(left_dst.cols-1, left_dst.rows*i/10), cv::Scalar(0,0,255), 2); - cv::line(right_dst, cv::Point(0, right_dst.rows*i/10), cv::Point(right_dst.cols-1, right_dst.rows*i/10), cv::Scalar(0,0,255), 2); - } - } - int height = img.left_img.rows*265/img.left_img.cols; - cv::resize(left_dst, left_dst, cv::Size(265, height)); - cv::resize(right_dst, right_dst, cv::Size(265, height)); - cv::Mat combian_img = cv::Mat::zeros(cv::Size(530, height), CV_8UC3); - cv::Mat new_roi = combian_img(cv::Rect(0, 0, 265, height)); - left_dst.convertTo(new_roi, new_roi.type()); - new_roi = combian_img(cv::Rect(265, 0, 265, height)); - right_dst.convertTo(new_roi, new_roi.type()); - QImage qimage = Mat2QImage(combian_img); - ui->Image->setPixmap(QPixmap::fromImage(qimage)); - } - else - { - // 单目逻辑同双目 - cv::Mat img = imgs[id].img.clone(); - if(distort_flag == true) - { - cv::Mat dst; - if(fisheye_flag == false) - cv::undistort(img, dst, cameraMatrix, distCoefficients); - else - cv::fisheye::undistortImage(img, dst, K, D, K, img.size()); - img = dst; - } - if(img.cols > img.rows) - cv::resize(img, img, cv::Size(530, img.rows*530/img.cols)); - else - cv::resize(img, img, cv::Size(img.cols*495/img.rows, 495)); - QImage qimage = Mat2QImage(img); - ui->Image->setPixmap(QPixmap::fromImage(qimage)); - } -} - -// 角点寻找线程结束时的回调函数 -void CameraCalibration::DealThreadDone() -{ - findcorner_thread->quit(); - findcorner_thread->wait(); - thread_done = true; -} - -void CameraCalibration::receiveYamlPath(QString str) -{ - cv::FileStorage fs_read(str.toStdString(), cv::FileStorage::READ); - if(ui->single_undistort->isChecked()) - { - if(fs_read["cameraMatrix"].empty() || fs_read["distCoeffs"].empty()) - { - QMessageBox::critical(this, "错误", "YAML文件格式错误", QMessageBox::Yes, QMessageBox::Yes); - return; - } - if(fisheye_flag == false) - { - fs_read["cameraMatrix"] >> cameraMatrix; - fs_read["distCoeffs"] >> distCoefficients; - calibrate_flag = true; - } - else - { - cv::Mat camera_matrix, Dist; - fs_read["cameraMatrix"] >> camera_matrix; - fs_read["distCoeffs"] >> Dist; - K(0,0) = camera_matrix.at(0,0); - K(0,1) = 0; - K(0,2) = camera_matrix.at(0,2); - K(1,0) = 0; - K(1,1) = camera_matrix.at(1,1); - K(1,2) = camera_matrix.at(1,2); - K(1,0) = 0; - K(2,1) = 0; - K(2,2) = 1; - D[0] = Dist.at(0,0); - D[1] = Dist.at(0,1); - D[2] = Dist.at(0,2); - D[3] = Dist.at(0,3); - calibrate_flag = true; - } - } - else - { - if(fs_read["left_camera_Matrix"].empty() || fs_read["left_camera_distCoeffs"].empty() || fs_read["right_camera_Matrix"].empty() || fs_read["right_camera_distCoeffs"].empty()) - { - QMessageBox::critical(this, "错误", "YAML文件格式错误", QMessageBox::Yes, QMessageBox::Yes); - return; - } - if(fs_read["Rotate_Matrix"].empty() || fs_read["Translate_Matrix"].empty() || fs_read["R1"].empty() || fs_read["R2"].empty() || fs_read["P1"].empty() || - fs_read["P2"].empty() || fs_read["Q"].empty()) - { - QMessageBox::critical(this, "错误", "YAML文件格式错误", QMessageBox::Yes, QMessageBox::Yes); - return; - } - if(fisheye_flag == false) - { - fs_read["left_camera_Matrix"] >> cameraMatrix; - fs_read["left_camera_distCoeffs"] >> distCoefficients; - fs_read["right_camera_Matrix"] >> cameraMatrix2; - fs_read["right_camera_distCoeffs"] >> distCoefficients2; - fs_read["Rotate_Matrix"] >> R; - fs_read["Translate_Matrix"] >> T; - fs_read["R1"] >> R1; - fs_read["R2"] >> R2; - fs_read["P1"] >> P1; - fs_read["P2"] >> P2; - fs_read["Q"] >> Q; - if(cameraMatrix.at(0,0) == 0 || cameraMatrix2.at(0,0) == 0) - { - QMessageBox::critical(this, "错误", "YAML文件格式错误", QMessageBox::Yes, QMessageBox::Yes); - return; - } - calibrate_flag = true; - } - else - { - cv::Mat camera_matrix, Dist; - fs_read["left_camera_Matrix"] >> camera_matrix; - fs_read["left_camera_distCoeffs"] >> Dist; - K(0,0) = camera_matrix.at(0,0); - K(0,1) = 0; - K(0,2) = camera_matrix.at(0,2); - K(1,0) = 0; - K(1,1) = camera_matrix.at(1,1); - K(1,2) = camera_matrix.at(1,2); - K(1,0) = 0; - K(2,1) = 0; - K(2,2) = 1; - D[0] = Dist.at(0,0); - D[1] = Dist.at(0,1); - D[2] = Dist.at(0,2); - D[3] = Dist.at(0,3); - - fs_read["right_camera_Matrix"] >> camera_matrix; - fs_read["right_camera_distCoeffs"] >> Dist; - K2(0,0) = camera_matrix.at(0,0); - K2(0,1) = 0; - K2(0,2) = camera_matrix.at(0,2); - K2(1,0) = 0; - K2(1,1) = camera_matrix.at(1,1); - K2(1,2) = camera_matrix.at(1,2); - K2(1,0) = 0; - K2(2,1) = 0; - K2(2,2) = 1; - D2[0] = Dist.at(0,0); - D2[1] = Dist.at(0,1); - D2[2] = Dist.at(0,2); - D2[3] = Dist.at(0,3); - fs_read["Rotate_Matrix"] >> R; - fs_read["Translate_Matrix"] >> T; - fs_read["R1"] >> R1; - fs_read["R2"] >> R2; - fs_read["P1"] >> P1; - fs_read["P2"] >> P2; - fs_read["Q"] >> Q; - if(cameraMatrix.at(0,0) == 0 || cameraMatrix2.at(0,0) == 0) - { - QMessageBox::critical(this, "错误", "YAML文件格式错误", QMessageBox::Yes, QMessageBox::Yes); - return; - } - calibrate_flag = true; - } - } -} - -// 加载双目图像 -void CameraCalibration::receiveFromDialog(QString str) -{ - HiddenIntro(); - QStringList list = str.split(","); - QString left_src = list[0]; - QString right_src = list[1]; - chessboard_size = list[2].toDouble(); - QDir dir(left_src); - dir.setFilter(QDir::Files | QDir::NoSymLinks); - QStringList filters; - filters << "*.png" << "*.jpg" << "*.jpeg"; - dir.setNameFilters(filters); - QStringList imagesList = dir.entryList(); - if(ui->double_camera->isChecked()) - { - if(imagesList.length() <= 3) - { - QMessageBox::critical(NULL, "错误", "至少需要四组图片", QMessageBox::Yes, QMessageBox::Yes); - return; - } - QProgressDialog *dialog = new QProgressDialog(tr("检测角点..."),tr("取消"),0,imagesList.length(),this); - dialog->setWindowModality(Qt::WindowModal); - dialog->setMinimumDuration(0); - dialog->setWindowTitle("请稍候"); - dialog->setValue(0); - QFont font = dialog->font(); - font.setPixelSize(12); - dialog->setFont(font); - dialog->show(); - QTextCodec *code = QTextCodec::codecForName("GB2312");//解决中文路径问题 - for(int i = 0; i < imagesList.length(); i++) - { - QString left_file_name = left_src + "/" + imagesList[i]; - QString right_file_name = right_src + "/" + imagesList[i]; - std::string str = code->fromUnicode(right_file_name).data(); - cv::Mat right_image = cv::imread(str); - if(right_image.empty()) - { - dialog->setValue(i+1); - continue; - } - str = code->fromUnicode(left_file_name).data(); - cv::Mat left_image = cv::imread(str); - if(left_image.cols != right_image.cols || left_image.rows != right_image.rows) - { - QMessageBox::critical(NULL, "错误", "左右相机图片尺寸不一致", QMessageBox::Yes, QMessageBox::Yes); - delete dialog; - return; - } - find_corner_thread_img = left_image; - thread_done = false; - findcorner_thread->start(); - while(thread_done == false) - { - if(dialog->wasCanceled()) - { - DealThreadDone(); - delete dialog; - return; - } - // 强制Windows消息循环,防止程序出现未响应情况 - QCoreApplication::processEvents(); - } - struct Chessboarder_t left_chess = find_corner_thread_chessboard; - // 如果检测到多个棋盘,则剔除该图片 - if(left_chess.chessboard.size() != 1) - { - dialog->setValue(i+1); - continue; - } - find_corner_thread_img = right_image; - thread_done = false; - findcorner_thread->start(); - while(thread_done == false) - { - if(dialog->wasCanceled()) - { - DealThreadDone(); - delete dialog; - return; - } - QCoreApplication::processEvents(); - } - struct Chessboarder_t right_chess = find_corner_thread_chessboard; - // 如果检测到多个棋盘,则剔除该图片 - if(right_chess.chessboard.size() != 1) - { - dialog->setValue(i+1); - continue; - } - // 如果左右图片检测到的棋盘尺寸不对,则剔除该组图片 - if(left_chess.chessboard[0].rows != right_chess.chessboard[0].rows || - left_chess.chessboard[0].cols != right_chess.chessboard[0].cols) - { - dialog->setValue(i+1); - continue; - } - // 缓存图片及角点 - struct Stereo_Img_t img_; - img_.left_img = left_image; - img_.right_img = right_image; - img_.left_file_name = imagesList[i]; - img_.right_file_name = imagesList[i]; - for (unsigned int j = 0; j < left_chess.chessboard.size(); j++) - { - for (int u = 0; u < left_chess.chessboard[j].rows; u++) - { - for (int v = 0; v < left_chess.chessboard[j].cols; v++) - { - img_.left_img_points.push_back(left_chess.corners.p[left_chess.chessboard[j].at(u, v)]); - img_.world_points.push_back(cv::Point3f(u*chessboard_size, v*chessboard_size, 0)); - img_.right_img_points.push_back(right_chess.corners.p[right_chess.chessboard[j].at(u, v)]); - } - } - } - stereo_imgs.push_back(img_); - img_size = left_image.size(); - int img_height = left_image.rows*90/left_image.cols; - cv::resize(left_image, left_image, cv::Size(90, img_height)); - cv::resize(right_image, right_image, cv::Size(90, img_height)); - cv::Mat combian_img = cv::Mat::zeros(cv::Size(180, img_height), CV_8UC3); - cv::Mat new_roi = combian_img(cv::Rect(0, 0, 90, img_height)); - left_image.convertTo(new_roi, new_roi.type()); - new_roi = combian_img(cv::Rect(90, 0, 90, img_height)); - right_image.convertTo(new_roi, new_roi.type()); - QPixmap pixmap = QPixmap::fromImage(Mat2QImage(combian_img)); - QListWidgetItem* temp = new QListWidgetItem(); - temp->setSizeHint(QSize(220, img_height)); - temp->setIcon(QIcon(pixmap)); - temp->setText(imagesList[i]); - ui->listWidget->addItem(temp); - ui->listWidget->setIconSize(QSize(180, img_height)); - dialog->setValue(i+1); - } - delete dialog; - } - else if(ui->double_undistort->isChecked()) - { - QTextCodec *code = QTextCodec::codecForName("GB2312");//解决中文路径问题 - for(int i = 0; i < imagesList.length(); i++) - { - QString left_file_name = left_src + "/" + imagesList[i]; - QString right_file_name = right_src + "/" + imagesList[i]; - std::string str = code->fromUnicode(right_file_name).data(); - cv::Mat right_image = cv::imread(str); - if(right_image.empty()) - { - continue; - } - str = code->fromUnicode(left_file_name).data(); - cv::Mat left_image = cv::imread(str); - if(left_image.cols != right_image.cols || left_image.rows != right_image.rows) - { - QMessageBox::critical(NULL, "错误", "左右相机图片尺寸不一致", QMessageBox::Yes, QMessageBox::Yes); - return; - } - struct Stereo_Img_t img_; - img_.left_img = left_image; - img_.right_img = right_image; - img_.left_file_name = imagesList[i]; - img_.right_file_name = imagesList[i]; - stereo_imgs.push_back(img_); - img_size = left_image.size(); - int img_height = left_image.rows*90/left_image.cols; - cv::resize(left_image, left_image, cv::Size(90, img_height)); - cv::resize(right_image, right_image, cv::Size(90, img_height)); - cv::Mat combian_img = cv::Mat::zeros(cv::Size(180, img_height), CV_8UC3); - cv::Mat new_roi = combian_img(cv::Rect(0, 0, 90, img_height)); - left_image.convertTo(new_roi, new_roi.type()); - new_roi = combian_img(cv::Rect(90, 0, 90, img_height)); - right_image.convertTo(new_roi, new_roi.type()); - QPixmap pixmap = QPixmap::fromImage(Mat2QImage(combian_img)); - QListWidgetItem* temp = new QListWidgetItem(); - temp->setSizeHint(QSize(220, img_height)); - temp->setIcon(QIcon(pixmap)); - temp->setText(imagesList[i]); - ui->listWidget->addItem(temp); - ui->listWidget->setIconSize(QSize(180, img_height)); - } - } -} - -// 鱼眼相机切换 -void CameraCalibration::fisheyeModeSwitch(int state) -{ - fisheye_flag = state==0 ? false : true; - if(fisheye_flag == true) - { - ui->k_2->setDisabled(true); - ui->k_3->setDisabled(true); - ui->tangential->setDisabled(true); - } - else - { - ui->k_2->setEnabled(true); - ui->k_3->setEnabled(true); - ui->tangential->setEnabled(true); - } -} - -// 畸变矫正切换 -void CameraCalibration::distortModeSwitch() -{ - if(ui->single_undistort->isChecked() || ui->double_undistort->isChecked()) - { - if(calibrate_flag == false) - { - chooseYaml = new choose_yaml(); - chooseYaml->setWindowTitle("选择相机参数文件"); - QFont font = chooseYaml->font(); - font.setPixelSize(12); - chooseYaml->setFont(font); - chooseYaml->show(); - connect(chooseYaml, SIGNAL(SendSignal(QString)), this, SLOT(receiveYamlPath(QString))); - return; - } - } - static int cnt = 0; - cnt++; - if(cnt%2 == 1) - { - distort_flag = true; - } - else - { - distort_flag = false; - } - int id = ui->listWidget->selectionModel()->selectedIndexes()[0].row(); - chooseImage(ui->listWidget->item(id), ui->listWidget->item(id)); -} - -// Mat格式转QImage格式 -QImage CameraCalibration::Mat2QImage(cv::Mat cvImg) -{ - QImage qImg; - if(cvImg.channels()==3) - { - cv::cvtColor(cvImg,cvImg,CV_BGR2RGB); - qImg =QImage((const unsigned char*)(cvImg.data), - cvImg.cols, cvImg.rows, - cvImg.cols*cvImg.channels(), - QImage::Format_RGB888); - } - else if(cvImg.channels()==1) - { - qImg =QImage((const unsigned char*)(cvImg.data), - cvImg.cols,cvImg.rows, - cvImg.cols*cvImg.channels(), - QImage::Format_Indexed8); - } - else - { - qImg =QImage((const unsigned char*)(cvImg.data), - cvImg.cols,cvImg.rows, - cvImg.cols*cvImg.channels(), - QImage::Format_RGB888); - } - return qImg; -} - -// 单目相机图片加载,逻辑同双目相机图片加载 -void CameraCalibration::addImage() -{ - HiddenIntro(); - if(ui->double_camera->isChecked() || ui->double_undistort->isChecked()) - { - d = new choose_two_dir(); - d->setWindowTitle("选择图片文件夹"); - QFont font = d->font(); - font.setPixelSize(12); - d->setFont(font); - d->show(); - connect(d, SIGNAL(SendSignal(QString)), this, SLOT(receiveFromDialog(QString))); - return; - } - else if(ui->single_camera->isChecked()) - { - if(chessboard_size == 0) - { - bool ok; - chessboard_size = QInputDialog::getDouble(this,tr("角点间距"),tr("请输入角点间距(mm)"),20,0,1000,2,&ok); - if(!ok) - { - chessboard_size = 0; - return; - } - } - QStringList path_list = QFileDialog::getOpenFileNames(this, tr("选择图片"), tr("./"), tr("图片文件(*.jpg *.png *.pgm);;所有文件(*.*);")); - QProgressDialog *dialog = new QProgressDialog(tr("检测角点..."),tr("取消"),0,path_list.size(),this); - dialog->setWindowModality(Qt::WindowModal); - dialog->setMinimumDuration(0); - dialog->setWindowTitle("请稍候"); - dialog->setValue(0); - QFont font = dialog->font(); - font.setPixelSize(12); - dialog->setFont(font); - dialog->show(); - QTextCodec *code = QTextCodec::codecForName("GB2312");//解决中文路径问题 - for(int i = 0; i < path_list.size(); i++) - { - QFileInfo file = QFileInfo(path_list[i]); - QString file_name = file.fileName(); - std::string str = code->fromUnicode(path_list[i]).data(); - cv::Mat img = cv::imread(str); - find_corner_thread_img = img; - thread_done = false; - findcorner_thread->start(); - while(thread_done == false) - { - if(dialog->wasCanceled()) - { - DealThreadDone(); - delete dialog; - return; - } - QCoreApplication::processEvents(); - } - struct Chessboarder_t chess = find_corner_thread_chessboard; - if(chess.chessboard.size() != 1) - { - dialog->setValue(i+1); - continue; - } - struct Img_t img_; - img_.img = img; - img_.file_name = file_name; - std::vector img_p; - std::vector world_p; - for (unsigned int j = 0; j < chess.chessboard.size(); j++) - { - for (int u = 0; u < chess.chessboard[j].rows; u++) - { - for (int v = 0; v < chess.chessboard[j].cols; v++) - { - img_p.push_back(chess.corners.p[chess.chessboard[j].at(u, v)]); - world_p.push_back(cv::Point3f(u*chessboard_size, v*chessboard_size, 0)); - } - } - } - img_.img_points = img_p; - img_.world_points = world_p; - imgs.push_back(img_); - img_size = img.size(); - int img_height = img.rows*124/img.cols; - cv::resize(img, img, cv::Size(124, img_height)); - QPixmap pixmap = QPixmap::fromImage(Mat2QImage(img)); - QListWidgetItem* temp = new QListWidgetItem(); - temp->setSizeHint(QSize(200, img_height)); - temp->setIcon(QIcon(pixmap)); - temp->setText(file_name); - ui->listWidget->addItem(temp); - ui->listWidget->setIconSize(QSize(124, img_height)); - dialog->setValue(i+1); - } - delete dialog; - } - else if(ui->single_undistort->isChecked()) - { - QStringList path_list = QFileDialog::getOpenFileNames(this, tr("选择图片"), tr("./"), tr("图片文件(*.jpg *.png *.pgm);;所有文件(*.*);")); - QTextCodec *code = QTextCodec::codecForName("GB2312");//解决中文路径问题 - for(int i = 0; i < path_list.size(); i++) - { - QFileInfo file = QFileInfo(path_list[i]); - QString file_name = file.fileName(); - std::string str = code->fromUnicode(path_list[i]).data(); - cv::Mat img = cv::imread(str); - img_size = img.size(); - Img_t single_img; - single_img.img = img; - single_img.file_name = file_name; - imgs.push_back(single_img); - int img_height = img.rows*124/img.cols; - cv::resize(img, img, cv::Size(124, img_height)); - QPixmap pixmap = QPixmap::fromImage(Mat2QImage(img)); - QListWidgetItem* temp = new QListWidgetItem(); - temp->setSizeHint(QSize(200, img_height)); - temp->setIcon(QIcon(pixmap)); - temp->setText(file_name); - ui->listWidget->addItem(temp); - ui->listWidget->setIconSize(QSize(124, img_height)); - } - } -} - -// 删除选中的图片 -void CameraCalibration::deleteImage() -{ - std::vector delete_idx; - foreach(QModelIndex index,ui->listWidget->selectionModel()->selectedIndexes()){ - delete_idx.push_back(index.row()); - } - if(delete_idx.size() == 0) - return; - std::sort(delete_idx.begin(), delete_idx.end()); - disconnect(ui->listWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(chooseImage(QListWidgetItem*, QListWidgetItem*))); - for(int i = delete_idx.size()-1; i >= 0; i--) - { - ui->listWidget->takeItem(delete_idx[i]); - if(ui->double_camera->isChecked()) - stereo_imgs.erase(stereo_imgs.begin()+delete_idx[i]); - else - imgs.erase(imgs.begin()+delete_idx[i]); - } - connect(ui->listWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(chooseImage(QListWidgetItem*, QListWidgetItem*))); - // 如果图片全部删除,则重新显示文字引导 - if(ui->listWidget->count() == 0) - ShowIntro(); -} - -// 相机标定 -void CameraCalibration::calibrate() -{ - if(chessboard_size == 0) - { - bool ok; - chessboard_size = QInputDialog::getDouble(this,tr("角点间距"),tr("请输入角点间距(mm)"),20,0,1000,2,&ok); - if(!ok) - { - chessboard_size = 0; - return; - } - } - if(!ui->double_camera->isChecked()) - { - // 单目标定 - if(imgs.size() <= 3) - { - QMessageBox::critical(NULL, "错误", "至少需要四张图片", QMessageBox::Yes, QMessageBox::Yes); - return; - } - std::vector> img_points; - std::vector> world_points; - std::vector tvecsMat; - std::vector rvecsMat; - std::vector > imagePoints; - std::vector > objectPoints; - std::vector rvecs; - std::vector tvecs; - for (unsigned int j = 0; j < imgs.size(); j++) - { - img_points.push_back(imgs[j].img_points); - world_points.push_back(imgs[j].world_points); - std::vector img_p; - std::vector obj_p; - for(unsigned int i = 0; i < imgs[j].img_points.size(); i++) - { - img_p.push_back(imgs[j].img_points[i]); - obj_p.push_back(imgs[j].world_points[i]); - } - imagePoints.push_back(img_p); - objectPoints.push_back(obj_p); - } - cameraMatrix = cv::Mat::zeros(cv::Size(3, 3), CV_32F); - if(fisheye_flag == false) - distCoefficients = cv::Mat::zeros(cv::Size(5, 1), CV_32F); - - if(fisheye_flag == false) - { - int flag = 0; - if(!ui->tangential->isChecked()) - flag |= CV_CALIB_ZERO_TANGENT_DIST; - if(!ui->k_3->isChecked()) - flag |= CV_CALIB_FIX_K3; - cv::calibrateCamera(world_points, img_points, img_size, cameraMatrix, distCoefficients, rvecsMat, tvecsMat, flag); - } - else - { - int flag = 0; - flag |= cv::fisheye::CALIB_RECOMPUTE_EXTRINSIC; - flag |= cv::fisheye::CALIB_FIX_SKEW; - try { - cv::fisheye::calibrate(objectPoints, imagePoints, img_size, K, D, rvecs, tvecs, flag); - } catch (cv::Exception& e) { - QMessageBox::critical(NULL, "错误", "请采集更多方位的图片或者检查角点识别!", QMessageBox::Yes, QMessageBox::Yes); - imgs.clear(); - ui->listWidget->clear(); - return; - } - } - for(unsigned int i = 0; i < imgs.size(); i++) - { - if(fisheye_flag == false) - { - imgs[i].tvec = tvecsMat[i]; - imgs[i].rvec = rvecsMat[i]; - } - else - { - imgs[i].fish_tvec = tvecs[i]; - imgs[i].fish_rvec = rvecs[i]; - } - } - // 评估标定结果 - std::vector error; - for(unsigned int i = 0; i < imgs.size(); i++) - { - std::vector world_p = imgs[i].world_points; - std::vector img_p = imgs[i].img_points, reproject_img_p; - std::vector fisheye_reproject_p; - if(fisheye_flag == false) - projectPoints(world_p, rvecsMat[i], tvecsMat[i], cameraMatrix, distCoefficients, reproject_img_p); - else - cv::fisheye::projectPoints(objectPoints[i], fisheye_reproject_p, rvecs[i], tvecs[i], K, D); - float err = 0; - for (unsigned int j = 0; j < img_p.size(); j++) - { - if(fisheye_flag == false) - err += sqrt((img_p[j].x-reproject_img_p[j].x)*(img_p[j].x-reproject_img_p[j].x)+ - (img_p[j].y-reproject_img_p[j].y)*(img_p[j].y-reproject_img_p[j].y)); - else - err += sqrt((img_p[j].x-fisheye_reproject_p[j].x)*(img_p[j].x-fisheye_reproject_p[j].x)+ - (img_p[j].y-fisheye_reproject_p[j].y)*(img_p[j].y-fisheye_reproject_p[j].y)); - } - error.push_back(err/img_p.size()); - } - int max_idx = max_element(error.begin(), error.end()) - error.begin(); - float max_error = error[max_idx]; - int width = 240 / imgs.size(); - cv::Mat error_plot = cv::Mat(260, 320, CV_32FC3, cv::Scalar(255,255,255)); - cv::rectangle(error_plot, cv::Rect(40, 20, 240, 200), cv::Scalar(0, 0, 0),1, cv::LINE_8,0); - cv::putText(error_plot, "0", cv::Point(20, 220), cv::FONT_HERSHEY_SIMPLEX, 0.3, cv::Scalar(0, 0, 0), 1, 8, 0); - char *chCode; - chCode = new(std::nothrow)char[20]; - sprintf(chCode, "%.2lf", max_error*200/195); - std::string strCode(chCode); - delete []chCode; - cv::putText(error_plot, strCode, cv::Point(10, 20), cv::FONT_HERSHEY_SIMPLEX, 0.3, cv::Scalar(0, 0, 0), 1, 8, 0); - error1 = 0; - for(unsigned int i = 0; i < imgs.size(); i++) - { - error1 += error[i]; - int height = 195*error[i]/max_error; - cv::rectangle(error_plot, cv::Rect(i*width+41, 220-height, width-2, height), cv::Scalar(255,0,0), -1, cv::LINE_8,0); - cv::putText(error_plot, std::to_string(i), cv::Point(i*width+40, 240), cv::FONT_HERSHEY_SIMPLEX, 0.3, cv::Scalar(0, 0, 0), 1, 8, 0); - } - error1 /= imgs.size(); - cv::imshow("error", error_plot); - } - else - { - // 双目标定 - if(stereo_imgs.size() <= 3) - { - QMessageBox::critical(NULL, "错误", "至少需要四组图片", QMessageBox::Yes, QMessageBox::Yes); - return; - } - std::vector> left_img_points; - std::vector> right_img_points; - std::vector> world_points; - std::vector left_tvecsMat; - std::vector left_rvecsMat; - std::vector right_tvecsMat; - std::vector right_rvecsMat; - std::vector > left_imagePoints; - std::vector > right_imagePoints; - std::vector > objectPoints; - std::vector left_rvecs; - std::vector left_tvecs; - std::vector right_rvecs; - std::vector right_tvecs; - for (unsigned int j = 0; j < stereo_imgs.size(); j++) - { - left_img_points.push_back(stereo_imgs[j].left_img_points); - right_img_points.push_back(stereo_imgs[j].right_img_points); - world_points.push_back(stereo_imgs[j].world_points); - std::vector img_p, img_p_; - std::vector obj_p; - for(unsigned int i = 0; i < stereo_imgs[j].left_img_points.size(); i++) - { - img_p.push_back(stereo_imgs[j].left_img_points[i]); - img_p_.push_back(stereo_imgs[j].right_img_points[i]); - obj_p.push_back(stereo_imgs[j].world_points[i]); - } - left_imagePoints.push_back(img_p); - right_imagePoints.push_back(img_p_); - objectPoints.push_back(obj_p); - } - cameraMatrix = cv::Mat::zeros(cv::Size(3, 3), CV_32F); - cameraMatrix2 = cv::Mat::zeros(cv::Size(3, 3), CV_32F); - if(fisheye_flag == false) - { - distCoefficients = cv::Mat::zeros(cv::Size(5, 1), CV_32F); - distCoefficients2 = cv::Mat::zeros(cv::Size(5, 1), CV_32F); - } - // 开始标定 - if(fisheye_flag == false) - { - int flag = 0; - if(!ui->tangential->isChecked()) - flag |= CV_CALIB_ZERO_TANGENT_DIST; - if(!ui->k_3->isChecked()) - flag |= CV_CALIB_FIX_K3; - cv::calibrateCamera(world_points, left_img_points, img_size, cameraMatrix, distCoefficients, left_rvecsMat, left_tvecsMat, flag); - cv::calibrateCamera(world_points, right_img_points, img_size, cameraMatrix2, distCoefficients2, right_rvecsMat, right_tvecsMat, flag); - } - else - { - int flag = 0; - flag |= cv::fisheye::CALIB_RECOMPUTE_EXTRINSIC; - flag |= cv::fisheye::CALIB_FIX_SKEW; - cv::fisheye::calibrate(objectPoints, left_imagePoints, img_size, K, D, left_rvecs, left_tvecs, flag); - cv::fisheye::calibrate(objectPoints, right_imagePoints, img_size, K2, D2, right_rvecs, right_tvecs, flag); - } - for(unsigned int i = 0; i < stereo_imgs.size(); i++) - { - if(fisheye_flag == false) - { - stereo_imgs[i].left_tvec = left_tvecsMat[i]; - stereo_imgs[i].left_rvec = left_rvecsMat[i]; - stereo_imgs[i].right_tvec = right_tvecsMat[i]; - stereo_imgs[i].right_rvec = right_rvecsMat[i]; - } - else - { - stereo_imgs[i].left_fish_tvec = left_tvecs[i]; - stereo_imgs[i].left_fish_rvec = left_rvecs[i]; - stereo_imgs[i].right_fish_tvec = right_tvecs[i]; - stereo_imgs[i].right_fish_rvec = right_rvecs[i]; - } - } - // 评估标定结果 - std::vector left_error, right_error; - for(unsigned int i = 0; i < stereo_imgs.size(); i++) - { - std::vector world_p = stereo_imgs[i].world_points; - std::vector left_img_p = stereo_imgs[i].left_img_points, right_img_p = stereo_imgs[i].right_img_points; - std::vector left_reproject_img_p, right_reproject_img_p; - std::vector left_fisheye_reproject_p, right_fisheye_reproject_p; - if(fisheye_flag == false) - { - projectPoints(world_p, left_rvecsMat[i], left_tvecsMat[i], cameraMatrix, distCoefficients, left_reproject_img_p); - projectPoints(world_p, right_rvecsMat[i], right_tvecsMat[i], cameraMatrix2, distCoefficients2, right_reproject_img_p); - } - else - { - cv::fisheye::projectPoints(objectPoints[i], left_fisheye_reproject_p, left_rvecs[i], left_tvecs[i], K, D); - cv::fisheye::projectPoints(objectPoints[i], right_fisheye_reproject_p, right_rvecs[i], right_tvecs[i], K2, D2); - } - float left_err = 0, right_err = 0; - for (unsigned int j = 0; j < world_p.size(); j++) - { - if(fisheye_flag == false) - { - left_err += sqrt((left_img_p[j].x-left_reproject_img_p[j].x)*(left_img_p[j].x-left_reproject_img_p[j].x)+ - (left_img_p[j].y-left_reproject_img_p[j].y)*(left_img_p[j].y-left_reproject_img_p[j].y)); - right_err += sqrt((right_img_p[j].x-right_reproject_img_p[j].x)*(right_img_p[j].x-right_reproject_img_p[j].x)+ - (right_img_p[j].y-right_reproject_img_p[j].y)*(right_img_p[j].y-right_reproject_img_p[j].y)); - } - else - { - left_err += sqrt((left_img_p[j].x-left_fisheye_reproject_p[j].x)*(left_img_p[j].x-left_fisheye_reproject_p[j].x)+ - (left_img_p[j].y-left_fisheye_reproject_p[j].y)*(left_img_p[j].y-left_fisheye_reproject_p[j].y)); - right_err += sqrt((right_img_p[j].x-right_fisheye_reproject_p[j].x)*(right_img_p[j].x-right_fisheye_reproject_p[j].x)+ - (right_img_p[j].y-right_fisheye_reproject_p[j].y)*(right_img_p[j].y-right_fisheye_reproject_p[j].y)); - } - } - left_error.push_back(left_err/world_p.size()); - right_error.push_back(right_err/world_p.size()); - } - int max_idx = max_element(left_error.begin(), left_error.end()) - left_error.begin(); - float max_error = left_error[max_idx]; - max_idx = max_element(right_error.begin(), right_error.end()) - right_error.begin(); - max_error = std::max(max_error, right_error[max_idx]); - int width = 480 / stereo_imgs.size(); - cv::Mat error_plot = cv::Mat(260, 560, CV_32FC3, cv::Scalar(255,255,255)); - cv::rectangle(error_plot, cv::Rect(40, 20, 480, 200), cv::Scalar(0, 0, 0),1, cv::LINE_8,0); - cv::putText(error_plot, "0", cv::Point(20, 220), cv::FONT_HERSHEY_SIMPLEX, 0.3, cv::Scalar(0, 0, 0), 1, 8, 0); - char *chCode; - chCode = new(std::nothrow)char[20]; - sprintf(chCode, "%.2lf", max_error*200/195); - std::string strCode(chCode); - delete []chCode; - cv::putText(error_plot, strCode, cv::Point(10, 20), cv::FONT_HERSHEY_SIMPLEX, 0.3, cv::Scalar(0, 0, 0), 1, 8, 0); - error1 = 0; error2 = 0; - for(unsigned int i = 0; i < stereo_imgs.size(); i++) - { - error1 += left_error[i]; - error2 += right_error[i]; - int height = 195*left_error[i]/max_error; - cv::rectangle(error_plot, cv::Rect(i*width+43, 220-height, width/2-4, height), cv::Scalar(255,0,0), -1, cv::LINE_8,0); - height = 195*right_error[i]/max_error; - cv::rectangle(error_plot, cv::Rect(i*width+41+width/2, 220-height, width/2-4, height), cv::Scalar(0,255,0), -1, cv::LINE_8,0); - cv::putText(error_plot, std::to_string(i), cv::Point(i*width+40+width/2-2, 240), cv::FONT_HERSHEY_SIMPLEX, 0.3, cv::Scalar(0, 0, 0), 1, 8, 0); - } - error1 /= stereo_imgs.size(); - error2 /= stereo_imgs.size(); - - if(fisheye_flag == false) - { - int flag = 0; - if(!ui->tangential->isChecked()) - flag |= CV_CALIB_ZERO_TANGENT_DIST; - if(!ui->k_3->isChecked()) - flag |= CV_CALIB_FIX_K3; - flag |= CV_CALIB_USE_INTRINSIC_GUESS; - cv::stereoCalibrate(world_points, left_img_points, right_img_points, - cameraMatrix, distCoefficients, - cameraMatrix2, distCoefficients2, - img_size, R, T, cv::noArray(), cv::noArray(), flag); - cv::stereoRectify(cameraMatrix, distCoefficients, - cameraMatrix2, distCoefficients2, - img_size, R, T, - R1, R2, P1, P2, Q,0,0, img_size); - } - else - { - int flag = 0; - flag |= cv::fisheye::CALIB_FIX_SKEW; - flag |= cv::fisheye::CALIB_USE_INTRINSIC_GUESS; - try { - cv::fisheye::stereoCalibrate(objectPoints, left_imagePoints, right_imagePoints, - K, D, - K2, D2, - img_size, R, T, flag); - } catch (cv::Exception& e) { - QMessageBox::critical(NULL, "错误", "请采集更多方位的图片!", QMessageBox::Yes, QMessageBox::Yes); - stereo_imgs.clear(); - ui->listWidget->clear(); - return; - } - - cv::fisheye::stereoRectify(K, D, - K2, D2, - img_size, R, T, - R1, R2, P1, P2, Q, 0, img_size); - } - cv::imshow("error", error_plot); - } - calibrate_flag = true; -} - -// 导出标定参数 -void CameraCalibration::exportParam() -{ - if(calibrate_flag == false) - { - QMessageBox::critical(NULL, "错误", "请先标定相机", QMessageBox::Yes); - return; - } - QString strSaveName = QFileDialog::getSaveFileName(this,tr("保存的文件"),tr("calibration_param.yaml"),tr("yaml files(*.yaml)")); - if(strSaveName.isNull() || strSaveName.isEmpty() || strSaveName.length() == 0) - return; - cv::FileStorage fs_write(strSaveName.toStdString(), cv::FileStorage::WRITE); - if(ui->double_camera->isChecked()) - { - if(fisheye_flag == false) - { - fs_write << "left_camera_Matrix" << cameraMatrix << "left_camera_distCoeffs" << distCoefficients; - fs_write << "right_camera_Matrix" << cameraMatrix2 << "right_camera_distCoeffs" << distCoefficients2; - fs_write << "Rotate_Matrix" << R << "Translate_Matrix" << T; - fs_write << "R1" << R1 << "R2" << R2 << "P1" << P1 << "P2" << P2 << "Q" << Q; - } - else - { - cv::Mat camera_matrix = cv::Mat::zeros(cv::Size(3,3), CV_64F); - camera_matrix.at(0,0) = K(0,0); - camera_matrix.at(0,2) = K(0,2); - camera_matrix.at(1,1) = K(1,1); - camera_matrix.at(1,2) = K(1,2); - camera_matrix.at(2,2) = 1; - cv::Mat Dist = (cv::Mat_(1,4) << D[0], D[1], D[2], D[3]); - fs_write << "left_camera_Matrix" << camera_matrix << "left_camera_distCoeffs" << Dist; - camera_matrix.at(0,0) = K2(0,0); - camera_matrix.at(0,2) = K2(0,2); - camera_matrix.at(1,1) = K2(1,1); - camera_matrix.at(1,2) = K2(1,2); - Dist = (cv::Mat_(1,4) << D2[0], D2[1], D2[2], D2[3]); - fs_write << "right_camera_Matrix" << camera_matrix << "right_camera_distCoeffs" << Dist; - fs_write << "Rotate_Matrix" << R << "Translate_Matrix" << T; - fs_write << "R1" << R1 << "R2" << R2 << "P1" << P1 << "P2" << P2 << "Q" << Q; - } - fs_write << "left_average_reprojection_err" << error1 << "right_average_reprojection_err" << error2; - } - else - { - if(fisheye_flag == false) - fs_write << "cameraMatrix" << cameraMatrix << "distCoeffs" << distCoefficients; - else - { - cv::Mat camera_matrix = cv::Mat::zeros(cv::Size(3,3), CV_64F); - camera_matrix.at(0,0) = K(0,0); - camera_matrix.at(0,2) = K(0,2); - camera_matrix.at(1,1) = K(1,1); - camera_matrix.at(1,2) = K(1,2); - camera_matrix.at(2,2) = 1; - cv::Mat Dist = (cv::Mat_(1,4) << D[0], D[1], D[2], D[3]); - fs_write << "cameraMatrix" << camera_matrix << "distCoeffs" << Dist; - } - fs_write << "average_reprojection_err" << error1; - } - fs_write.release(); -} diff --git a/camera_calibration/CameraCalibration.h b/camera_calibration/CameraCalibration.h deleted file mode 100644 index b94a3f0..0000000 --- a/camera_calibration/CameraCalibration.h +++ /dev/null @@ -1,131 +0,0 @@ -#ifndef CameraCalibration_H -#define CameraCalibration_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "findCorner.h" -#include "chessboard.h" -#include "choose_two_dir.h" -#ifdef Q_OS_LINUX -#include "single_capture_linux.h" -#elif defined(Q_OS_WIN32) -#include "single_capture.h" -#endif - -#ifdef Q_OS_LINUX -#include "double_capture_linux.h" -#elif defined(Q_OS_WIN32) -#include "double_capture.h" -#endif -#include "choose_yaml.h" - -QT_BEGIN_NAMESPACE -namespace Ui { class CameraCalibration; } -QT_END_NAMESPACE - -struct Img_t -{ - cv::Mat img; - QString file_name; - std::vector img_points; - std::vector world_points; - cv::Mat tvec; - cv::Mat rvec; - cv::Vec3d fish_tvec; - cv::Vec3d fish_rvec; -}; - -struct Stereo_Img_t -{ - cv::Mat left_img; - QString left_file_name; - std::vector left_img_points; - cv::Mat left_tvec; - cv::Mat left_rvec; - cv::Vec3d left_fish_tvec; - cv::Vec3d left_fish_rvec; - - cv::Mat right_img; - QString right_file_name; - std::vector right_img_points; - cv::Mat right_tvec; - cv::Mat right_rvec; - cv::Vec3d right_fish_tvec; - cv::Vec3d right_fish_rvec; - - std::vector world_points; -}; - -extern cv::Mat find_corner_thread_img; -extern struct Chessboarder_t find_corner_thread_chessboard; - -class CameraCalibration : public QMainWindow -{ - Q_OBJECT - -public: - CameraCalibration(QWidget *parent = nullptr); - ~CameraCalibration(); - -private slots: - void addImage(); - void deleteImage(); - void calibrate(); - void fisheyeModeSwitch(int state); - void exportParam(); - void distortModeSwitch(); - void receiveFromDialog(QString str); - void receiveYamlPath(QString str); - void chooseImage(QListWidgetItem* item, QListWidgetItem*); - void reset(); - void undistortReset(); - void saveUndistort(); - void OpenCamera(); - void DealThreadDone(); - -private: - Ui::CameraCalibration *ui; - choose_two_dir *d; - choose_yaml *chooseYaml; -#ifdef Q_OS_LINUX - single_capture_linux *single_c; - double_capture_linux *double_c; -#elif defined(Q_OS_WIN32) - single_capture *single_c; - double_capture *double_c; -#endif - QImage Mat2QImage(cv::Mat cvImg); - void ShowIntro(); - void HiddenIntro(); - QAction *saveImage; - std::vector imgs; - std::vector stereo_imgs; - double chessboard_size; - cv::Mat cameraMatrix; - cv::Mat distCoefficients; - cv::Matx33d K; - cv::Vec4d D; - cv::Mat cameraMatrix2; - cv::Mat distCoefficients2; - double error1, error2; - cv::Matx33d K2; - cv::Vec4d D2; - cv::Size img_size; - bool fisheye_flag = false; - bool calibrate_flag = false; - bool distort_flag = false; - cv::Mat R, T, R1, R2, P1, P2, Q; - FindcornerThread *findcorner_thread; - bool thread_done = false; -}; -#endif // CameraCalibration_H diff --git a/camera_calibration/README.md b/camera_calibration/README.md deleted file mode 100644 index e2ff0de..0000000 --- a/camera_calibration/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# camera_calibration -传统相机和鱼眼相机标定程序 - -基于Qt和OpenCV开发,角点检测部分为MATLAB的角点检测算法的C++实现,并优化实现了多棋盘检测和大畸变鱼眼镜头下棋盘的检测。 - -## 功能列表: -* 角点检测,支持多棋盘检测(代码逻辑见[基于生长的棋盘格角点检测](https://github.com/imuncle/imuncle.github.io/issues/113)) -* 单目传统相机标定,采用张正友标定法,支持模式选择,用户可选择径向畸变参数的个数和是否标定切向畸变 -* 单目鱼眼相机标定 -* 双目传统/鱼眼相机标定,无需预先单独标定单个相机 -* 支持打开摄像头采集图片 -* 支持畸变矫正预览和重投影预览 -* 支持导出畸变矫正后的图片 - -## 文件结构 -|文件名/文件夹名|功能| -|:--|:--| -|CameraCalibration.cpp|程序主界面和主要逻辑的实现| -|findCorner.cpp|棋盘角点检测算法的实现| -|chessboard.cpp|根据检测到的角点进行棋盘生长| -|sigle_capture.cpp|Windows上单个相机采集图像的实现| -|double_capture.cpp|Windows上双目相机采集图片的实现| -|sigle_capture_linux.cpp|Linux上单个相机采集图像的实现| -|double_capture_linux.cpp|Linux上双目相机采集图片的实现| -|choose_two_dir.cpp|立体标定图像文件夹选择对话框| -|res|图标资源文件夹| - -## 软件运行截图: - -![screenshot](screenshot.jpg) \ No newline at end of file diff --git a/camera_calibration/double_capture.cpp b/camera_calibration/double_capture.cpp deleted file mode 100644 index 21c31be..0000000 --- a/camera_calibration/double_capture.cpp +++ /dev/null @@ -1,219 +0,0 @@ -#include "double_capture.h" -#include "ui_double_capture.h" -#include - -double_capture::double_capture(QWidget *parent) : - QMainWindow(parent), - ui(new Ui::double_capture) -{ - ui->setupUi(this); - ui->capture->setFlat(true); - connect(ui->capture, SIGNAL(clicked()), this, SLOT(capture())); - last_left.deviceName() = QString(""); - last_right.deviceName() = QString(""); - camera_list = QCameraInfo::availableCameras(); - if(camera_list.length() > 1) - { - Camera_left = new QCamera(camera_list[0]); - Camera_right = new QCamera(camera_list[1]); - } - else - { - QString str(""); - Camera_left = new QCamera(QCameraInfo(str.toUtf8())); - Camera_right = new QCamera(QCameraInfo(str.toUtf8())); - QMessageBox::warning(this, "警告", "摄像头少于两个,请插上摄像头后再打开该窗口", QMessageBox::Yes); - } - for(int i = 0; i < camera_list.length(); i++) { - QAction *camera = ui->menu_1->addAction(camera_list[i].description()); - camera->setCheckable(true); - connect(camera, &QAction::triggered,[=](){ - if(last_left.deviceName() != camera_list[i].deviceName()) - { - open_left(camera_list[i]); - } - else - { - close_left(); - } - }); - QAction *camera_1 = ui->menu_2->addAction(camera_list[i].description()); - camera_1->setCheckable(true); - connect(camera_1, &QAction::triggered,[=](){ - if(last_right.deviceName() != camera_list[i].deviceName()) - { - open_right(camera_list[i]); - } - else - { - close_right(); - } - }); - } - CameraViewFinder_left = new QCameraViewfinder(ui->left_img); - CameraViewFinder_left->resize(ui->left_img->width(), ui->left_img->height()); - CameraViewFinder_right = new QCameraViewfinder(ui->right_img); - CameraViewFinder_right->resize(ui->right_img->width(), ui->right_img->height()); -} - -double_capture::~double_capture() -{ - delete ui; - Camera_left->stop(); - Camera_right->stop(); -} - -void double_capture::close_left() -{ - last_left = QCameraInfo(QString("").toUtf8()); - Camera_left->stop(); - left_open = false; -} - -void double_capture::open_left(QCameraInfo info) -{ - QList actions = ui->menu_1->actions(); - if(info.deviceName() == last_right.deviceName()) - { - QMessageBox::warning(NULL, "警告", "摄像头"+info.description()+"已经打开!", QMessageBox::Yes); - close_left(); - for(int i = 0; i < actions.length(); i++) - { - actions[i]->setChecked(false); - } - return; - } - for(int i = 0; i < actions.length(); i++) - { - if(camera_list[i].deviceName() == info.deviceName()) - { - actions[i]->setChecked(true); - } - else - actions[i]->setChecked(false); - } - Camera_left->stop(); - Camera_left = new QCamera(info); - Camera_left->setViewfinder(CameraViewFinder_left); - //显示摄像头取景器 - CameraViewFinder_left->show(); - //开启摄像头 - Camera_left->start(); - if(Camera_left->status() != QCamera::ActiveStatus) - { - QMessageBox::warning(this, "警告", "摄像头打开失败!", QMessageBox::Yes); - close_left(); - for(int i = 0; i < actions.length(); i++) - { - actions[i]->setChecked(false); - } - return; - } - //创建获取一帧数据对象 - CameraImageCapture_left = new QCameraImageCapture(Camera_left); - //关联图像获取信号 - connect(CameraImageCapture_left, &QCameraImageCapture::imageCaptured, this, &double_capture::left_take_photo); - left_open = true; - last_left = info; -} - -void double_capture::close_right() -{ - Camera_right->stop(); - left_open = false; - last_right = QCameraInfo(QString("").toUtf8()); -} - -void double_capture::open_right(QCameraInfo info) -{ - QList actions = ui->menu_2->actions(); - if(info.deviceName() == last_left.deviceName()) - { - QMessageBox::warning(NULL, "警告", "摄像头"+info.description()+"已经打开!", QMessageBox::Yes); - close_right(); - for(int i = 0; i < actions.length(); i++) - { - actions[i]->setChecked(false); - } - return; - } - for(int i = 0; i < actions.length(); i++) - { - if(camera_list[i].deviceName() == info.deviceName()) - { - actions[i]->setChecked(true); - } - else - actions[i]->setChecked(false); - } - Camera_right->stop(); - Camera_right = new QCamera(info); - Camera_right->setViewfinder(CameraViewFinder_right); - //显示摄像头取景器 - CameraViewFinder_right->show(); - //开启摄像头 - Camera_right->start(); - if(Camera_right->status() != QCamera::ActiveStatus) - { - QMessageBox::warning(this, "警告", "摄像头打开失败!", QMessageBox::Yes); - close_right(); - for(int i = 0; i < actions.length(); i++) - { - actions[i]->setChecked(false); - } - return; - } - //创建获取一帧数据对象 - CameraImageCapture_right = new QCameraImageCapture(Camera_right); - //关联图像获取信号 - connect(CameraImageCapture_right, &QCameraImageCapture::imageCaptured, this, &double_capture::right_take_photo); - right_open = true; - last_right = info; -} - -void double_capture::capture() -{ - if(left_open == false || right_open == false) - { - QMessageBox::critical(this, "错误", "两个相机都打开后才能拍照!", QMessageBox::Yes); - return; - } - if(save_path == "") - { - save_path = QFileDialog::getExistingDirectory(nullptr, "选择保存路径", "./"); - QDir dir; - dir.mkdir(save_path+"/left/"); - dir.mkdir(save_path+"/right/"); - return; - } - CameraImageCapture_left->capture(); - CameraImageCapture_right->capture(); -} - -void double_capture::left_take_photo(int, const QImage &image) -{ - //使用系统时间来命名图片的名称,时间是唯一的,图片名也是唯一的 - QDateTime dateTime(QDateTime::currentDateTime()); - QString time = dateTime.toString("yyyy-MM-dd-hh-mm-ss"); - //创建图片保存路径名 - QString filename = save_path + "/left/" + QString("./%1.jpg").arg(time); - //保存一帧数据 - image.save(filename); -} - -void double_capture::right_take_photo(int, const QImage &image) -{ - //使用系统时间来命名图片的名称,时间是唯一的,图片名也是唯一的 - QDateTime dateTime(QDateTime::currentDateTime()); - QString time = dateTime.toString("yyyy-MM-dd-hh-mm-ss"); - //创建图片保存路径名 - QString filename = save_path + "/right/" + QString("./%1.jpg").arg(time); - //保存一帧数据 - image.save(filename); -} - -void double_capture::closeEvent ( QCloseEvent *) -{ - Camera_left->stop(); - Camera_right->stop(); -} diff --git a/camera_calibration/double_capture.h b/camera_calibration/double_capture.h deleted file mode 100644 index 516f5d3..0000000 --- a/camera_calibration/double_capture.h +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef DOUBLE_CAPTURE_H -#define DOUBLE_CAPTURE_H - -#include -#include -#include -#include -#include -#include - -namespace Ui { -class double_capture; -} - -class double_capture : public QMainWindow -{ - Q_OBJECT - -public: - explicit double_capture(QWidget *parent = nullptr); - ~double_capture(); - -private slots: - void capture(); - void left_take_photo(int id, const QImage &image); - void right_take_photo(int id, const QImage &image); - -private: - Ui::double_capture *ui; - //摄像头对象指针 - QCamera* Camera_left; - QCamera* Camera_right; - QCameraInfo last_left; - QCameraInfo last_right; - //摄像头的取景器 - QCameraViewfinder* CameraViewFinder_left; - QCameraViewfinder* CameraViewFinder_right; - QList camera_list; - void open_left(QCameraInfo info); - void close_left(); - void open_right(QCameraInfo info); - void close_right(); - void closeEvent(QCloseEvent *event); - //记录摄像头内容 - QCameraImageCapture* CameraImageCapture_left; - QCameraImageCapture* CameraImageCapture_right; - QString save_path; - bool left_open = false, right_open = false; -}; - -#endif // DOUBLE_CAPTURE_H diff --git a/camera_calibration/double_capture.ui b/camera_calibration/double_capture.ui deleted file mode 100644 index 76e08b8..0000000 --- a/camera_calibration/double_capture.ui +++ /dev/null @@ -1,107 +0,0 @@ - - - double_capture - - - - 0 - 0 - 1000 - 480 - - - - MainWindow - - - - :/icon/camera.png:/icon/camera.png - - - - - - 0 - 0 - 500 - 480 - - - - - - - Qt::AlignCenter - - - - - - 500 - 0 - 500 - 480 - - - - - - - Qt::AlignCenter - - - - - - 470 - 350 - 60 - 60 - - - - PointingHandCursor - - - - - - - :/icon/capture.png:/icon/capture.png - - - - 48 - 48 - - - - - - - - 0 - 0 - 1000 - 22 - - - - - 相机1 - - - - - 相机2 - - - - - - - - - - - diff --git a/camera_calibration/double_capture_linux.cpp b/camera_calibration/double_capture_linux.cpp deleted file mode 100644 index 8a7a3c8..0000000 --- a/camera_calibration/double_capture_linux.cpp +++ /dev/null @@ -1,339 +0,0 @@ -#include "double_capture_linux.h" -#include "ui_double_capture.h" -#ifdef Q_OS_LINUX - -V4L2 v4l2_left; -V4L2 v4l2_right; - -double_capture_linux::double_capture_linux(QWidget *parent) : - QMainWindow(parent), - ui(new Ui::double_capture) -{ - ui->setupUi(this); - ui->capture->setFlat(true); - connect(ui->capture, SIGNAL(clicked()), this, SLOT(capture())); - capture_thread_l = new LeftCaptureThread(); - connect(capture_thread_l, SIGNAL(CaptureDone(QImage, int)), this, SLOT(DealLeftCaptureDone(QImage, int))); - capture_thread_r = new RightCaptureThread(); - connect(capture_thread_r, SIGNAL(CaptureDone(QImage, int)), this, SLOT(DealRightCaptureDone(QImage, int))); - err11_l = err19_l = err11_r = err19_r = 0; - int camcount = v4l2_left.GetDeviceCount(); - for(int i = 0; i < camcount; i++) - { - QAction *camera = ui->menu_1->addAction(v4l2_left.GetCameraName(i)); - camera->setCheckable(true); - connect(camera, &QAction::triggered,[=](){ - if(last_left_id != i) - { - open_left(i); - } - else - { - close_left(); - } - }); - QAction *camera_1 = ui->menu_2->addAction(v4l2_left.GetCameraName(i)); - camera_1->setCheckable(true); - connect(camera_1, &QAction::triggered,[=](){ - if(last_right_id != i) - { - open_right(i); - } - else - { - close_right(); - } - }); - } -} - -double_capture_linux::~double_capture_linux() -{ - delete ui; -} - -void double_capture_linux::open_left(int id) -{ - QList actions = ui->menu_1->actions(); - if(id == last_right_id) - { - QMessageBox::warning(NULL, "警告", "摄像头"+actions[id]->text()+"已经打开!", QMessageBox::Yes); - close_left(); - for(int i = 0; i < actions.length(); i++) - { - actions[i]->setChecked(false); - } - return; - } - close_left(); - last_left_id = id; - - for(int i = 0; i < actions.length(); i++) - { - if(id == i) - { - actions[i]->setChecked(true); - } - else - actions[i]->setChecked(false); - } - v4l2_left.StartRun(id); - capture_thread_l->init(id); - capture_thread_l->start(); - open_left_flag = true; -} - -void double_capture_linux::close_left() -{ - last_left_id = -1; - capture_thread_l->stop(); - capture_thread_l->quit(); - capture_thread_l->wait(); - v4l2_left.StopRun(); - open_left_flag = false; -} - -void double_capture_linux::open_right(int id) -{ - QList actions = ui->menu_2->actions(); - if(id == last_left_id) - { - QMessageBox::warning(NULL, "警告", "摄像头"+actions[id]->text()+"已经打开!", QMessageBox::Yes); - close_right(); - for(int i = 0; i < actions.length(); i++) - { - actions[i]->setChecked(false); - } - return; - } - close_right(); - last_right_id = id; - - for(int i = 0; i < actions.length(); i++) - { - if(id == i) - { - actions[i]->setChecked(true); - } - else - actions[i]->setChecked(false); - } - v4l2_right.StartRun(id); - capture_thread_r->init(id); - capture_thread_r->start(); - open_right_flag = true; -} - -void double_capture_linux::close_right() -{ - last_right_id = -1; - capture_thread_r->stop(); - capture_thread_r->quit(); - capture_thread_r->wait(); - v4l2_right.StopRun(); - open_right_flag = false; -} - -void double_capture_linux::DealLeftCaptureDone(QImage image, int result) -{ - //超时后关闭视频 - //超时代表着VIDIOC_DQBUF会阻塞,直接关闭视频即可 - if(result == -1) - { - close_left(); - - ui->left_img->clear(); - ui->left_img->setText("获取设备图像超时!"); - } - - if(!image.isNull()) - { - q_left_image = image; - ui->left_img->clear(); - switch(result) - { - case 0: //Success - err11_l = err19_l = 0; - if(image.isNull()) - ui->left_img->setText("画面丢失!"); - else - ui->left_img->setPixmap(QPixmap::fromImage(image.scaled(ui->left_img->size()))); - - break; - case 11: //Resource temporarily unavailable - err11_l++; - if(err11_l == 10) - { - ui->left_img->clear(); - ui->left_img->setText("设备已打开,但获取视频失败!\n请尝试切换USB端口后断开重试!"); - } - break; - case 19: //No such device - err19_l++; - if(err19_l == 10) - { - ui->left_img->clear(); - ui->left_img->setText("设备丢失!"); - } - break; - } - } -} - -void double_capture_linux::DealRightCaptureDone(QImage image, int result) -{ - //超时后关闭视频 - //超时代表着VIDIOC_DQBUF会阻塞,直接关闭视频即可 - if(result == -1) - { - close_right(); - - ui->right_img->clear(); - ui->right_img->setText("获取设备图像超时!"); - } - - if(!image.isNull()) - { - q_right_image = image; - ui->right_img->clear(); - switch(result) - { - case 0: //Success - err11_r = err19_r = 0; - if(image.isNull()) - ui->right_img->setText("画面丢失!"); - else - ui->right_img->setPixmap(QPixmap::fromImage(image.scaled(ui->right_img->size()))); - - break; - case 11: //Resource temporarily unavailable - err11_r++; - if(err11_r == 10) - { - ui->right_img->clear(); - ui->right_img->setText("设备已打开,但获取视频失败!\n请尝试切换USB端口后断开重试!"); - } - break; - case 19: //No such device - err19_r++; - if(err19_r == 10) - { - ui->right_img->clear(); - ui->right_img->setText("设备丢失!"); - } - break; - } - } -} - -void double_capture_linux::capture() -{ - if(open_left_flag == false || open_right_flag == false) - { - QMessageBox::critical(this, "错误", "两个相机都打开后才能拍照!", QMessageBox::Yes); - return; - } - if(save_path == "") - { - save_path = QFileDialog::getExistingDirectory(nullptr, "选择保存路径", "./"); - QDir dir; - dir.mkdir(save_path+"/left/"); - dir.mkdir(save_path+"/right/"); - return; - } - //使用系统时间来命名图片的名称,时间是唯一的,图片名也是唯一的 - QDateTime dateTime(QDateTime::currentDateTime()); - QString time = dateTime.toString("yyyy-MM-dd-hh-mm-ss"); - //创建图片保存路径名 - QString filename = save_path + "/left/" + QString("%1.jpg").arg(time); - //保存一帧数据 - q_left_image.save(filename); - filename = save_path + "/right/" + QString("%1.jpg").arg(time); - q_right_image.save(filename); -} - -void double_capture_linux::closeEvent ( QCloseEvent *) -{ - close_left(); - close_right(); -} - -LeftCaptureThread::LeftCaptureThread() -{ - stopped = false; - majorindex = -1; -} - -void LeftCaptureThread::stop() -{ - stopped = true; -} - -void LeftCaptureThread::init(int index) -{ - stopped = false; - majorindex = index; -} - -void LeftCaptureThread::run() -{ - if(majorindex != -1) - { - while(!stopped) - { - msleep(1000/30); - - QImage img; - int ret = v4l2_left.GetFrame(); - if(ret == 0) - { - int WV = v4l2_left.GetCurResWidth(); - int HV = v4l2_left.GetCurResHeight(); - img = QImage(v4l2_left.rgb24, WV, HV, QImage::Format_RGB888); - } - - emit CaptureDone(img, ret); - } - } -} - -RightCaptureThread::RightCaptureThread() -{ - stopped = false; - majorindex = -1; -} - -void RightCaptureThread::stop() -{ - stopped = true; -} - -void RightCaptureThread::init(int index) -{ - stopped = false; - majorindex = index; -} - -void RightCaptureThread::run() -{ - if(majorindex != -1) - { - while(!stopped) - { - msleep(1000/30); - - QImage img; - int ret = v4l2_right.GetFrame(); - if(ret == 0) - { - int WV = v4l2_right.GetCurResWidth(); - int HV = v4l2_right.GetCurResHeight(); - img = QImage(v4l2_right.rgb24, WV, HV, QImage::Format_RGB888); - } - - emit CaptureDone(img, ret); - } - } -} - -#endif diff --git a/camera_calibration/double_capture_linux.h b/camera_calibration/double_capture_linux.h deleted file mode 100644 index 4dad5d5..0000000 --- a/camera_calibration/double_capture_linux.h +++ /dev/null @@ -1,97 +0,0 @@ -#ifndef DOUBLE_CAPTURE_LINUX_H -#define DOUBLE_CAPTURE_LINUX_H - -#include - -#ifdef Q_OS_LINUX -#include "v4l2.hpp" -#include -#include -#include -#include - -class LeftCaptureThread : public QThread -{ - Q_OBJECT -public: - LeftCaptureThread(); - - QImage majorImage; - void stop(); - void init(int index); - -protected: - void run(); - -private: - volatile int majorindex; - volatile bool stopped; - -signals: - void CaptureDone(QImage image, int result); -}; - -class RightCaptureThread : public QThread -{ - Q_OBJECT -public: - RightCaptureThread(); - - QImage majorImage; - void stop(); - void init(int index); - -protected: - void run(); - -private: - volatile int majorindex; - volatile bool stopped; - -signals: - void CaptureDone(QImage image, int result); -}; - -extern V4L2 v4l2_left; -extern V4L2 v4l2_right; - -namespace Ui { -class double_capture; -} - -class double_capture_linux : public QMainWindow -{ - Q_OBJECT - -public: - explicit double_capture_linux(QWidget *parent = nullptr); - ~double_capture_linux(); - -private slots: - void capture(); - void DealLeftCaptureDone(QImage image, int result); - void DealRightCaptureDone(QImage image, int result); - -private: - Ui::double_capture *ui; - LeftCaptureThread *capture_thread_l; - RightCaptureThread *capture_thread_r; - int err11_l, err19_l; - int err11_r, err19_r; - void open_left(int id); - void close_left(); - void open_right(int id); - void close_right(); - void closeEvent(QCloseEvent *event); - QString save_path; - bool open_left_flag = false; - int last_left_id; - QImage q_left_image; - bool open_right_flag = false; - int last_right_id; - QImage q_right_image; -}; - -#endif - -#endif // DOUBLE_CAPTURE_LINUX_H diff --git a/camera_calibration/screenshot.jpg b/camera_calibration/screenshot.jpg deleted file mode 100644 index 04f3cbf527b51d16c7a53c1e92bc67fe6833ab0c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 67224 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 -#include - -single_capture::single_capture(QWidget *parent) : - QMainWindow(parent), - ui(new Ui::single_capture) -{ - ui->setupUi(this); - ui->capture->setFlat(true); - connect(ui->capture, SIGNAL(clicked()), this, SLOT(capture())); - last_info.deviceName() = QString(""); - camera_list = QCameraInfo::availableCameras(); - if(camera_list.length() > 0) - Camera = new QCamera(camera_list[0]); - else - { - QString str(""); - Camera = new QCamera(QCameraInfo(str.toUtf8())); - QMessageBox::warning(this, "警告", "没有检测到摄像头,请插上摄像头后再打开该窗口", QMessageBox::Yes); - return; - } - for(int i = 0; i < camera_list.length(); i++) { - QAction *camera = ui->menu->addAction(camera_list[i].description()); - camera->setCheckable(true); - connect(camera, &QAction::triggered,[=](){ - if(last_info.deviceName() != camera_list[i].deviceName()) - { - open(camera_list[i]); - } - else - { - close(); - } - }); - } - CameraViewFinder = new QCameraViewfinder(ui->image); - CameraViewFinder->resize(ui->image->width(), ui->image->height()); -} - -single_capture::~single_capture() -{ - Camera->stop(); - delete Camera; - delete ui; -} - -void single_capture::close() -{ - Camera->stop(); - last_info = QCameraInfo(QString("").toUtf8()); - open_flag = false; -} - -void single_capture::open(QCameraInfo info) -{ - QList actions = ui->menu->actions(); - for(int i = 0; i < actions.length(); i++) - { - if(camera_list[i].deviceName() == info.deviceName()) - { - actions[i]->setChecked(true); - } - else - actions[i]->setChecked(false); - } - Camera->stop(); - Camera = new QCamera(info); - Camera->setViewfinder(CameraViewFinder); - //显示摄像头取景器 - CameraViewFinder->show(); - //开启摄像头 - try { - Camera->start(); - } catch (QEvent *e) { - QMessageBox::critical(this, "错误", "摄像头打开失败", QMessageBox::Yes); - } - if(Camera->status() != QCamera::ActiveStatus) - { - QMessageBox::warning(this, "警告", "摄像头打开失败!", QMessageBox::Yes); - close(); - return; - } - //创建获取一帧数据对象 - CameraImageCapture = new QCameraImageCapture(Camera); - //关联图像获取信号 - connect(CameraImageCapture, &QCameraImageCapture::imageCaptured, this, &single_capture::take_photo); - open_flag = true; - last_info = info; -} - -void single_capture::capture() -{ - if(open_flag == false) - { - QMessageBox::critical(this, "错误", "请先选择相机!", QMessageBox::Yes); - return; - } - if(save_path == "") - { - save_path = QFileDialog::getExistingDirectory(nullptr, "选择保存路径", "./"); - return; - } - CameraImageCapture->capture(); -} - -void single_capture::take_photo(int, const QImage &image) -{ - //使用系统时间来命名图片的名称,时间是唯一的,图片名也是唯一的 - QDateTime dateTime(QDateTime::currentDateTime()); - QString time = dateTime.toString("yyyy-MM-dd-hh-mm-ss"); - //创建图片保存路径名 - QString filename = save_path + QString("./%1.jpg").arg(time); - //保存一帧数据 - image.save(filename); -} - -void single_capture::closeEvent ( QCloseEvent *) -{ - Camera->stop(); -} diff --git a/camera_calibration/single_capture.h b/camera_calibration/single_capture.h deleted file mode 100644 index 2ee3b03..0000000 --- a/camera_calibration/single_capture.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef SINGLE_CAPTURE_H -#define SINGLE_CAPTURE_H - -#include -#include -#include -#include -#include -#include - -namespace Ui { -class single_capture; -} - -class single_capture : public QMainWindow -{ - Q_OBJECT - -public: - explicit single_capture(QWidget *parent = nullptr); - ~single_capture(); - -private slots: - void capture(); - void take_photo(int id, const QImage &image); - -private: - Ui::single_capture *ui; - //摄像头对象指针 - QCamera* Camera; - QCameraInfo last_info; - //摄像头的取景器 - QCameraViewfinder* CameraViewFinder; - QList camera_list; - void open(QCameraInfo info); - void close(); - void closeEvent(QCloseEvent *event); - //记录摄像头内容 - QCameraImageCapture* CameraImageCapture; - QString save_path; - bool open_flag = false; -}; - -#endif // SINGLE_CAPTURE_H diff --git a/camera_calibration/single_capture.ui b/camera_calibration/single_capture.ui deleted file mode 100644 index 2c3e1a7..0000000 --- a/camera_calibration/single_capture.ui +++ /dev/null @@ -1,85 +0,0 @@ - - - single_capture - - - - 0 - 0 - 640 - 480 - - - - MainWindow - - - - :/icon/camera.png:/icon/camera.png - - - - - - 0 - 0 - 640 - 480 - - - - - - - Qt::AlignCenter - - - - - - 290 - 350 - 60 - 60 - - - - PointingHandCursor - - - - - - - :/icon/capture.png:/icon/capture.png - - - - 48 - 48 - - - - - - - - 0 - 0 - 640 - 22 - - - - - 选择相机 - - - - - - - - - - diff --git a/camera_calibration/single_capture_linux.cpp b/camera_calibration/single_capture_linux.cpp deleted file mode 100644 index 097a366..0000000 --- a/camera_calibration/single_capture_linux.cpp +++ /dev/null @@ -1,181 +0,0 @@ -#include "single_capture_linux.h" -#include "ui_single_capture.h" -#ifdef Q_OS_LINUX - -V4L2 v4l2; - -single_capture_linux::single_capture_linux(QWidget *parent) : - QMainWindow(parent), - ui(new Ui::single_capture) -{ - ui->setupUi(this); - ui->capture->setFlat(true); - connect(ui->capture, SIGNAL(clicked()), this, SLOT(capture())); - capture_thread = new SingleCaptureThread(); - connect(capture_thread, SIGNAL(SingleCaptureDone(QImage, int)), this, SLOT(DealCaptureDone(QImage, int))); - err11 = err19 = 0; - int camcount = v4l2.GetDeviceCount(); - for(int i = 0; i < camcount; i++) - { - QAction *camera = ui->menu->addAction(v4l2.GetCameraName(i)); - camera->setCheckable(true); - connect(camera, &QAction::triggered,[=](){ - if(last_id != i) - { - open_camera(i); - } - else - { - close_camera(); - } - }); - } -} - -single_capture_linux::~single_capture_linux() -{ - delete ui; -} - -void single_capture_linux::open_camera(int id) -{ - close_camera(); - last_id = id; - QList actions = ui->menu->actions(); - for(int i = 0; i < actions.length(); i++) - { - if(id == i) - { - actions[i]->setChecked(true); - } - else - actions[i]->setChecked(false); - } - v4l2.StartRun(id); - capture_thread->init(id); - capture_thread->start(); - open_flag = true; -} - -void single_capture_linux::close_camera() -{ - last_id = -1; - capture_thread->stop(); - capture_thread->quit(); - capture_thread->wait(); - v4l2.StopRun(); - open_flag = false; -} - -void single_capture_linux::DealCaptureDone(QImage image, int result) -{ - //超时后关闭视频 - //超时代表着VIDIOC_DQBUF会阻塞,直接关闭视频即可 - if(result == -1) - { - close_camera(); - - ui->image->clear(); - ui->image->setText("获取设备图像超时!"); - } - - if(!image.isNull()) - { - q_image = image; - ui->image->clear(); - switch(result) - { - case 0: //Success - err11 = err19 = 0; - if(image.isNull()) - ui->image->setText("画面丢失!"); - else - ui->image->setPixmap(QPixmap::fromImage(image.scaled(ui->image->size()))); - - break; - case 11: //Resource temporarily unavailable - err11++; - if(err11 == 10) - { - ui->image->clear(); - ui->image->setText("设备已打开,但获取视频失败!\n请尝试切换USB端口后断开重试!"); - } - break; - case 19: //No such device - err19++; - if(err19 == 10) - { - ui->image->clear(); - ui->image->setText("设备丢失!"); - } - break; - } - } -} - -void single_capture_linux::capture() -{ - if(open_flag == false) - { - QMessageBox::critical(this, "错误", "请先选择相机!", QMessageBox::Yes); - return; - } - if(save_path == "") - { - save_path = QFileDialog::getExistingDirectory(nullptr, "选择保存路径", "./"); - return; - } - //使用系统时间来命名图片的名称,时间是唯一的,图片名也是唯一的 - QDateTime dateTime(QDateTime::currentDateTime()); - QString time = dateTime.toString("yyyy-MM-dd-hh-mm-ss"); - //创建图片保存路径名 - QString filename = save_path +"/" + QString("%1.jpg").arg(time); - //保存一帧数据 - q_image.save(filename); -} - -void single_capture_linux::closeEvent ( QCloseEvent *) -{ - close_camera(); -} - -SingleCaptureThread::SingleCaptureThread() -{ - stopped = false; - majorindex = -1; -} - -void SingleCaptureThread::stop() -{ - stopped = true; -} - -void SingleCaptureThread::init(int index) -{ - stopped = false; - majorindex = index; -} - -void SingleCaptureThread::run() -{ - if(majorindex != -1) - { - while(!stopped) - { - msleep(1000/30); - - QImage img; - int ret = v4l2.GetFrame(); - if(ret == 0) - { - int WV = v4l2.GetCurResWidth(); - int HV = v4l2.GetCurResHeight(); - img = QImage(v4l2.rgb24, WV, HV, QImage::Format_RGB888); - } - - emit SingleCaptureDone(img, ret); - } - } -} - -#endif diff --git a/camera_calibration/single_capture_linux.h b/camera_calibration/single_capture_linux.h deleted file mode 100644 index dd1f19e..0000000 --- a/camera_calibration/single_capture_linux.h +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef SINGLE_CAPTURE_LINUX_H -#define SINGLE_CAPTURE_LINUX_H - -#include - -#ifdef Q_OS_LINUX -#include "v4l2.hpp" -#include -#include -#include -#include - -class SingleCaptureThread : public QThread -{ - Q_OBJECT -public: - SingleCaptureThread(); - - QImage majorImage; - void stop(); - void init(int index); - -protected: - void run(); - -private: - volatile int majorindex; - volatile bool stopped; - -signals: - void SingleCaptureDone(QImage image, int result); -}; - -extern V4L2 v4l2; - -namespace Ui { -class single_capture; -} - -class single_capture_linux : public QMainWindow -{ - Q_OBJECT - -public: - explicit single_capture_linux(QWidget *parent = nullptr); - ~single_capture_linux(); - -private slots: - void capture(); - void DealCaptureDone(QImage image, int result); - -private: - Ui::single_capture *ui; - SingleCaptureThread *capture_thread; - int err11, err19; - void open_camera(int id); - void close_camera(); - void closeEvent(QCloseEvent *event); - QString save_path; - bool open_flag = false; - int last_id; - QImage q_image; -}; - -#endif - -#endif // SINGLE_CAPTURE_LINUX_H diff --git a/camera_calibration/v4l2.hpp b/camera_calibration/v4l2.hpp deleted file mode 100644 index 18c7601..0000000 --- a/camera_calibration/v4l2.hpp +++ /dev/null @@ -1,458 +0,0 @@ -#ifndef V4L2_HPP -#define V4L2_HPP - -#include -#ifdef Q_OS_LINUX - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -class V4L2 -{ -public: - int GetDeviceCount() - { - char devname[15] = ""; - int count = 0; - int i; - for(i = 0; i < 100; i++) - { - sprintf(devname, "%s%d", "/dev/video", i); - if(test_device_exist(devname) == 0) - count++; - - memset(devname, 0, sizeof(devname)); - } - - return count; - } - char *GetDeviceName(int index) - { - memset(devName, 0, sizeof(devName)); - sprintf(devName, "%s%d", "/dev/video", index); - return devName; - } - char *GetCameraName(int index) - { - if(videoIsRun > 0) - return (char*)""; - - memset(camName, 0, sizeof(camName)); - - char devname[15] = ""; - strcpy(devname, GetDeviceName(index)); - - int fd = open(devname, O_RDWR); - if(ioctl(fd, VIDIOC_QUERYCAP, &cap) != -1) - { - strcpy(camName, (char *)cap.card); - } - close(fd); - - return camName; - } - - int StartRun(int index) - { - if(videoIsRun > 0) - return -1; - - char *devname = GetDeviceName(index); - fd = open(devname, O_RDWR); - if(fd == -1) - return -1; - - deviceIsOpen = 1; - - StartVideoPrePare(); - StartVideoStream(); - - strcpy(runningDev, devname); - videoIsRun = 1; - - return 0; - } - int GetFrame() - { - if(videoIsRun > 0) - { - fd_set fds; - struct timeval tv; - int r; - - FD_ZERO (&fds); - FD_SET (fd, &fds); - - /* Timeout. */ - tv.tv_sec = 7; - tv.tv_usec = 0; - - r = select (fd + 1, &fds, NULL, NULL, &tv); - - if (0 == r) - return -1; - else if(-1 == r) - return errno; - - memset(&buffer, 0, sizeof(buffer)); - buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - buffer.memory = V4L2_MEMORY_MMAP; - - if (ioctl(fd, VIDIOC_DQBUF, &buffer) == -1) { - perror("GetFrame VIDIOC_DQBUF Failed"); - return errno; - } - else - { - convert_yuv_to_rgb_buffer((unsigned char*)buffers[buffer.index].start, rgb24, WIDTH, HEIGHT); - - if (ioctl(fd, VIDIOC_QBUF, &buffer) < 0) { - perror("GetFrame VIDIOC_QBUF Failed"); - return errno; - } - - return 0; - } - } - - return 0; - } - int StopRun() - { - if(videoIsRun > 0) - { - EndVideoStream(); - EndVideoStreamClear(); - } - - memset(runningDev, 0, sizeof(runningDev)); - videoIsRun = -1; - deviceIsOpen = -1; - - if(close(fd) != 0) - return -1; - - return 0; - } - - char *GetDevFmtDesc(int index) - { - memset(devFmtDesc, 0, sizeof(devFmtDesc)); - - fmtdesc.index=index; - fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; - - if(ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) != -1) - { - char fmt[5] = ""; - sprintf(fmt, "%c%c%c%c", - (__u8)(fmtdesc.pixelformat&0XFF), - (__u8)((fmtdesc.pixelformat>>8)&0XFF), - (__u8)((fmtdesc.pixelformat>>16)&0XFF), - (__u8)((fmtdesc.pixelformat>>24)&0XFF)); - - strncpy(devFmtDesc, fmt, 4); - } - - return devFmtDesc; - } - - int GetDevFmtWidth() - { - format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if(ioctl (fd, VIDIOC_G_FMT, &format) == -1) - { - perror("GetDevFmtWidth:"); - return -1; - } - return format.fmt.pix.width; - } - int GetDevFmtHeight() - { - format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if(ioctl (fd, VIDIOC_G_FMT, &format) == -1) - { - perror("GetDevFmtHeight:"); - return -1; - } - return format.fmt.pix.height; - } - int GetDevFmtSize() - { - format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if(ioctl (fd, VIDIOC_G_FMT, &format) == -1) - { - perror("GetDevFmtSize:"); - return -1; - } - return format.fmt.pix.sizeimage; - } - int GetDevFmtBytesLine() - { - format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if(ioctl (fd, VIDIOC_G_FMT, &format) == -1) - { - perror("GetDevFmtBytesLine:"); - return -1; - } - return format.fmt.pix.bytesperline; - } - - int GetResolutinCount() - { - fmtdesc.index = 0; - fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; - - if(ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == -1) - return -1; - - frmsizeenum.pixel_format = fmtdesc.pixelformat; - int i = 0; - for(i = 0; ; i++) - { - frmsizeenum.index = i; - if(ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) == -1) - break; - } - return i; - } - int GetResolutionWidth(int index) - { - fmtdesc.index = 0; - fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; - - if(ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == -1) - return -1; - - frmsizeenum.pixel_format = fmtdesc.pixelformat; - - frmsizeenum.index = index; - if(ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) != -1) - return frmsizeenum.discrete.width; - else - return -1; - } - int GetResolutionHeight(int index) - { - fmtdesc.index = 0; - fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; - - if(ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == -1) - return -1; - - frmsizeenum.pixel_format = fmtdesc.pixelformat; - - frmsizeenum.index = index; - if(ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) != -1) - return frmsizeenum.discrete.height; - else - return -1; - } - int GetCurResWidth() - { - format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if (ioctl(fd, VIDIOC_G_FMT, &format) == -1) - return -1; - return format.fmt.pix.width; - } - int GetCurResHeight() - { - format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if (ioctl(fd, VIDIOC_G_FMT, &format) == -1) - return -1; - return format.fmt.pix.height; - } - int videoIsRun = -1; - unsigned char *rgb24 = NULL; -private: - char runningDev[15] = ""; - char devName[15] = ""; - char camName[32] = ""; - char devFmtDesc[4] = ""; - - int fd = -1; - int deviceIsOpen = -1; - int WIDTH, HEIGHT; - - //V4l2相关结构体 - struct v4l2_capability cap; - struct v4l2_fmtdesc fmtdesc; - struct v4l2_frmsizeenum frmsizeenum; - struct v4l2_format format; - struct v4l2_requestbuffers reqbuf; - struct v4l2_buffer buffer; - - struct buffer{ - void *start; - unsigned int length; - }*buffers; - - void StartVideoPrePare() - { - //申请帧缓存区 - memset (&reqbuf, 0, sizeof (reqbuf)); - reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - reqbuf.memory = V4L2_MEMORY_MMAP; - reqbuf.count = 4; - - if (-1 == ioctl (fd, VIDIOC_REQBUFS, &reqbuf)) { - if (errno == EINVAL) - printf ("Video capturing or mmap-streaming is not supported\n"); - else - perror ("VIDIOC_REQBUFS"); - return; - } - - //分配缓存区 - buffers = (struct buffer*)calloc(reqbuf.count, sizeof (*buffers)); - if(buffers == NULL) - perror("buffers is NULL"); - else - assert (buffers != NULL); - - //mmap内存映射 - int i; - for (i = 0; i < (int)reqbuf.count; i++) { - memset (&buffer, 0, sizeof (buffer)); - buffer.type = reqbuf.type; - buffer.memory = V4L2_MEMORY_MMAP; - buffer.index = i; - - if (-1 == ioctl (fd, VIDIOC_QUERYBUF, &buffer)) { - perror ("VIDIOC_QUERYBUF"); - return; - } - - buffers[i].length = buffer.length; - - buffers[i].start = mmap (NULL, buffer.length, - PROT_READ | PROT_WRITE, - MAP_SHARED, - fd, buffer.m.offset); - - if (MAP_FAILED == buffers[i].start) { - perror ("mmap"); - return; - } - } - - //将缓存帧放到队列中等待视频流到来 - unsigned int ii; - for(ii = 0; ii < reqbuf.count; ii++){ - buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - buffer.memory = V4L2_MEMORY_MMAP; - buffer.index = ii; - if (ioctl(fd,VIDIOC_QBUF,&buffer)==-1){ - perror("VIDIOC_QBUF failed"); - } - } - - WIDTH = GetCurResWidth(); - HEIGHT = GetCurResHeight(); - rgb24 = (unsigned char*)malloc(WIDTH*HEIGHT*3*sizeof(char)); - assert(rgb24 != NULL); - } - void StartVideoStream() - { - enum v4l2_buf_type type; - type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if (ioctl(fd,VIDIOC_STREAMON,&type) == -1) { - perror("VIDIOC_STREAMON failed"); - } - } - void EndVideoStream() - { - //关闭视频流 - enum v4l2_buf_type type; - type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if (ioctl(fd,VIDIOC_STREAMOFF,&type) == -1) { - perror("VIDIOC_STREAMOFF failed"); - } - } - void EndVideoStreamClear() - { - //手动释放分配的内存 - int i; - for (i = 0; i < (int)reqbuf.count; i++) - munmap (buffers[i].start, buffers[i].length); - } - int test_device_exist(char *devName) - { - struct stat st; - if (-1 == stat(devName, &st)) - return -1; - - return 0; - } - int convert_yuv_to_rgb_pixel(int y, int u, int v) - { - unsigned int pixel32 = 0; - unsigned char *pixel = (unsigned char *)&pixel32; - int r, g, b; - r = y + (1.370705 * (v-128)); - g = y - (0.698001 * (v-128)) - (0.337633 * (u-128)); - b = y + (1.732446 * (u-128)); - if(r > 255) r = 255; - if(g > 255) g = 255; - if(b > 255) b = 255; - if(r < 0) r = 0; - if(g < 0) g = 0; - if(b < 0) b = 0; - pixel[0] = r ; - pixel[1] = g ; - pixel[2] = b ; - return pixel32; - } - - int convert_yuv_to_rgb_buffer(unsigned char *yuv, unsigned char *rgb, unsigned int width, unsigned int height) - { - unsigned int in, out = 0; - unsigned int pixel_16; - unsigned char pixel_24[3]; - unsigned int pixel32; - int y0, u, y1, v; - - for(in = 0; in < width * height * 2; in += 4) - { - pixel_16 = - yuv[in + 3] << 24 | - yuv[in + 2] << 16 | - yuv[in + 1] << 8 | - yuv[in + 0]; - y0 = (pixel_16 & 0x000000ff); - u = (pixel_16 & 0x0000ff00) >> 8; - y1 = (pixel_16 & 0x00ff0000) >> 16; - v = (pixel_16 & 0xff000000) >> 24; - pixel32 = convert_yuv_to_rgb_pixel(y0, u, v); - pixel_24[0] = (pixel32 & 0x000000ff); - pixel_24[1] = (pixel32 & 0x0000ff00) >> 8; - pixel_24[2] = (pixel32 & 0x00ff0000) >> 16; - rgb[out++] = pixel_24[0]; - rgb[out++] = pixel_24[1]; - rgb[out++] = pixel_24[2]; - pixel32 = convert_yuv_to_rgb_pixel(y1, u, v); - pixel_24[0] = (pixel32 & 0x000000ff); - pixel_24[1] = (pixel32 & 0x0000ff00) >> 8; - pixel_24[2] = (pixel32 & 0x00ff0000) >> 16; - rgb[out++] = pixel_24[0]; - rgb[out++] = pixel_24[1]; - rgb[out++] = pixel_24[2]; - } - return 0; - } -}; - -#endif - -#endif // V4L2_HPP diff --git a/camera_calibration/choose_two_dir.cpp b/choose_two_dir.cpp similarity index 85% rename from camera_calibration/choose_two_dir.cpp rename to choose_two_dir.cpp index 59a61e3..d312d21 100644 --- a/camera_calibration/choose_two_dir.cpp +++ b/choose_two_dir.cpp @@ -41,7 +41,7 @@ void choose_two_dir::cancel() void choose_two_dir::choose_left_dir() { - QString srcDirPath = QFileDialog::getExistingDirectory(nullptr, "Choose Directory", "./"); + QString srcDirPath = QFileDialog::getExistingDirectory(this, "Choose Directory", "./", QFileDialog::ShowDirsOnly); if(srcDirPath.isEmpty()) { return; @@ -51,7 +51,7 @@ void choose_two_dir::choose_left_dir() void choose_two_dir::choose_right_dir() { - QString srcDirPath = QFileDialog::getExistingDirectory(nullptr, "Choose Directory", "./"); + QString srcDirPath = QFileDialog::getExistingDirectory(this, "Choose Directory", "./", QFileDialog::ShowDirsOnly); if(srcDirPath.isEmpty()) { return; diff --git a/camera_calibration/choose_two_dir.h b/choose_two_dir.h similarity index 100% rename from camera_calibration/choose_two_dir.h rename to choose_two_dir.h diff --git a/camera_calibration/choose_two_dir.ui b/choose_two_dir.ui similarity index 100% rename from camera_calibration/choose_two_dir.ui rename to choose_two_dir.ui diff --git a/camera_calibration/choose_yaml.cpp b/choose_yaml.cpp similarity index 100% rename from camera_calibration/choose_yaml.cpp rename to choose_yaml.cpp diff --git a/camera_calibration/choose_yaml.h b/choose_yaml.h similarity index 100% rename from camera_calibration/choose_yaml.h rename to choose_yaml.h diff --git a/camera_calibration/choose_yaml.ui b/choose_yaml.ui similarity index 100% rename from camera_calibration/choose_yaml.ui rename to choose_yaml.ui diff --git a/guide/new_tool.jpg b/guide/new_tool.jpg deleted file mode 100644 index 4db46846cc0e042ce8e6961aedcd086b074ce6b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11044 zcmeHt2Ur!!vhbWhPKO*MgJdLw1PPLJP6vsS5djqtNe2)C$vJ1qAn5=uNKz!H1Biek z$p9z_5+w6LTvzw)?%V&}?|%2*|9jK@b*QSYuBxf7ndzS6iQ`!SrlO#%0DwRsz~UqU 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ⅇ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 diff --git a/guide/new_ui_3.jpg b/guide/new_ui_3.jpg deleted file mode 100644 index 7abe1b06828ad5da8173bff0424d53055f24b723..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12683 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 diff --git a/guide/new_ui_dir.jpg b/guide/new_ui_dir.jpg deleted file mode 100644 index 42e0ca57d8b953773087ae06d578568b8b7651e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28072 zcmdqJ1wdU(k}!NO?he7--5qjq3GVJ8xI+jME(CY?-~@LkxCIYxAp}V9AVC8`{+svS z%)B@IW@o?sW_Q2+`!uJjtE#K5PSfXf*L_%eSO>5aWff!r5C{aAK`-Fp10V$;As`?k zz#}0dA|fLrp`hWRqoJar5#ius;*k+kkdqOUl2X#LGf+~q(vXre3NW#9a`E!=QZNXK z337|F^YC*2R02XqMn*$LBSc3hulxL2!A}3{oR_yJlhFJfYJ+avz1b?4^(||C|FW~PUpj|?Gr#g6; zj-$y?dwwtRpTc2S_ZTmRg4JdleAx%3EX6;Per)n3JxBRdW$@w*p>+ENlRMXnh5_6) z%WlkxPXgMTE8$_c4DDHzmqJZTBb&Bdml^*wHzeygdHP_?Rs+M>Bjc9X#y!SMG_;@+ zA(99J2;EqijCI%vk^Ns>yRJ*aZH`u+hf=HoY_XKB%!7V9o9ocw!0_40D=|yRbxF{f z0xF)qag1VP9I!3#vtjNfX1y+-seHPnrwp%dt7V`~kRnt3&yd}$YM)66jNneSRs)AL z1lm^EoVMMQ8Zb1tv4l89%vHUn z`L1}4rc!1xIptB`;vUrg!N z5$RL~_ScQ&53JH0yOy(%VqAZxZ2{Zx480QlkKICiuELDYf76dB=03fBWO;z2*(UuV zs$I+gIFTylCv2qkh~am6562mcV~!=0MgDHb3! z%DQM^9ehjJxm+~_Kfhq*=I}0;>My1IqVNv|!?Uz4?`_Zc#8T*vw7glQ;6K;FWWF#a zS3W>Y+F1m@D9QV7CPsPYEHk~m7Pq&(mdNwIWlQwKjN6OFqVF%>6_GpciW6eWi+-5) zc+pb$1WK;<~HeAwgoKQ+FvzM>~_Pr`4T4q77=J=Ql0P~YsV zZVfX<`lVj5B61>PE}hR4E|$){joIwU{Pf-R-Vza)IlyZ-fplV9$3N>?z4v>j@O&dK z-^xni4YxsGm@svUSX{B4@MoSF^CYovug{)*F`uX|7uAoin){ZmJsm%GApf+tnOSoY zkGEo3G5c=IZy-yZ=sq+iQOiGjxx&tVwQ+`m2{u^oBYWjT8g?s)?FwF3YA9z$z+xA)#yQpjVT(wNR*C=26b zCaW((P^Z#Ie!duGeB$ZqiOnYcq$i$fHjNWsyNW@j*H6@o_bG!YWHj+(eBx6yQ+uyPf-t%i1ZyThwB?(XFoFiirV~@#YJpi7xKhBQX zA$fnM_kSskv;gP7;QB|Mfho4@dUIMaUtPS()PT-z9>o5QF2?#Dncq99H=c^`GwL0m zZ;3IPPaWwM|05NIA`_npl%HnswG1hVyrOuWk4TVxrEk?(FoXBPvC<~F0^t#0SgYFuMTSRv-D?;noW5` zDwSerTNWP_`F2Ph|u=Q7x9KlIWq4? znOg?In@plzaE0g)HmqZ9yAM==?QsDb3l@(K1iedDEpP!OB&KTWp9>H zcPr(*P$puVZk8YBga(OrFYQtP^8vXVhk%ngV1|&G*EKrt!jf`3%3eDnRB(vXZko5> zgcGL(85NAqq8;;-bX#+Hb$NAL{dLTbh3(7az^By!D=&-a+jmBf?Dd>E7WkvU1iZOOpJjdLMlff*qjqPI2X(YL<$Dlz?l;gKny@O@Ol4vp#5>J!S`O%kC%eg zf{hk-<8JX%Lr(YEUfjmC0lf&!(WDfkIR8tY2Owst-7;r3cu+_OaiZRr4NZZT;O#}@;?U{Z!K0H4ErI&AXFYl+* zAYbWfQ~L~&9scD2Y+|wzf?Q(%le^1DAM7R z9fpBATopRp$#t;4q>{y(yM(L^rkKxRr?S@IArsq=ArT{8)4#Q~4S6_}E2zlCWsDXl zt^d3$!*8^Q=ZpL1o4Yu@8_Vy0eK_jeVTp2YM|SkfNTkLDZbI7>iR=|v&2kUTi=_*tI<$xA;9sQ@F-IdT2 zd_wu}xSov({4|cF%?7mTj{+r#11*n@iW%HR)l5I|HrOkhf9%MJbY3QEOH5xA44}2P zLVLRXdPy%k>(r{*Klaxic}h$FZJb;7;$S717N_=9WKw-GrA8lS0U++KN&ZxKK%o?X znJ%eFuXZYb^+NQ3kH+SO^#h=`$Rx#POBc<&IfsX4;f<-UBT#41n3B?pp~W@4@SBMl za>urKrhIz(eA)O?*Z+@*s#mYT-;w^U(7&7_<<%4OL{q_jkK;B4gqxd#->`8{SXP`9 z@#IZ+Qa|itQI;J1_b59dPtcTLO<-SolerGHepc0?auS@Ki^z)Qdq+%sLUPt~@DuZ? zLvDNtrnbGgnY_DG=gHY^WlcEycg8gKD7P2B@P!B#_LRi(gPC-71_# z$xt?cgtViD190zB#SOwH@8}+Y#P81I^!eI>6$4!8f}xTKyj z)^FtqooP+y9Q>LvU*9}=0O-KM47(v7lI>K+Sk>XW(kun|(swi?QCa@qK$wdaJ~uo7a_HX($L)mZG~|EKiz(l42W6@XexU|2A!WMJQN2|; zi_f-JrSH?;rm<|gUsrS|jdexa5rI{u7oEgA=2cVFc$KAK96rjJDF;)-Zn5Mvt>|b9 zH7f6a?NyUj_p>nh9buahfMhWv!EeOxgiqg=e7 zrn|p0jIq}LkH(h!^cw6Zpxx*~o2M_WT+tlIGcoDjrzIjH<@qgM>FLQ?bXa{Sa7Uo; z;Io^NX$3)I*6>#yfy9yxio<(& zL?zCoNyqxp<)`vEqd!$em$#0eZyq2x7#uv!I#_IKT(FwPG&~h27dMZDx+{dz<>Z(54(Nx^ z$DsQS<3V+8nNRJPhbQ|5HJ)E{mUXRGzqkpJ#?1Y^$I8(p4F3U%S@ch<9Xz6k21%=5 zZ&3JWQS9J`q#$cwGU|%&!h^Rk#fksGmv|@B;n%}-Q>N?76zufLh4Xt!%eM;k- z&P1}>E5EjlB74QDA<<4gW+KhEv)?IUehy`m*3;elB&J2cD`B~m ztt_q1hpjN{A6vlumDQI_nt}dD1TphhmDf!Q)U+BBl)`rOnfV+w?r+N3Gs~zWkf{&r zI4YACyZ6uKqll>=fX;V{xW&jb!BHw?;Vv(QmjU#9hu61}ZFirb111mswD5EL z0SO)z78Vx%=K~JFz+&^@P;uqsQcEOGb4#h4yCx)Uz_|t1^s(tst^G%Pkxc+I|X zx))kP>Rp2J3TA%$;(+iN=6;qu4$CllUk=s36A7_>wmBof?(LI{xB#Z5=N^>gKdk)s z0+y&exOk237&#JK{co<9yu^}I^No7D58CjsxWfH*P6BYn&>Nvt09Ijs}wp&s8Eh$ z8smMyU3FrwfCCv*p5zPL2r4FfR55?sP*u3LZZBWyX@U%z;hXwr^RbMNPPeY~jOerB zuBoMDkF?L0=MypMhXs}7b1i%u9lRPd$J|`@`#;>};>;57AY}27_$143q9U9JIa5A-4tK6(I< zP&p4B>CP_K!-{Op4Z_(>Q86mD1j8QjQoL(SbfQTXrpt2%{N4`tAqx9-d21^(?u z`In-wMkJN!o8Ci;NU%M#acu4aDh9+cycZ8sA)>#6?PjXQbWMiA){_5FOh8;_e z)VJ)nU%DLqyQc58beBEyKl^%rJX8*ce^*K2<{Pb{N3CBA-c>h-ZytmDLi*uMiA!&L zW$B62#1qq`*>Wvi5*0MBMb)(|EYvtX@#Ax98xOy-l?bG-Jti-42Bop#lZ2M1Q^&YP zQr9k8IvtMmk?HFvDsT$^`U?ki9&d~Fz0;MQI+3Bt;+(dVoG7&5z)$d@egX*oUA3kakH8#j@qwbzx4&E;a={syKJiCj2 znX+FYkn+0R67%9~>+qyp`R21_SDlfhr(*U+85TTjf+~age9t~C1zN6r6H-qh3qg0k@wS*@Wq~wMl=9OWAUHezfwpY)9VP*Bkiu3+*?lt!P#3 zF~S_0RH!Ca3-8$fZuTg)Ko*FiSZGx7Q8}I*frS49;JwDVuMU^_VdrUQ?m^Z|h*I<# ziJ6{5bP1FnX%m#c!p2&9Y`jBsO6caIj>^U)Q zcPew17;doBHZk92vS@@$Eob?OKXtXw>#)@~-YPf0PsgdhVY4uG&)`{X^!sJbd}-Ur zk5a)RJ64oB+Wx-$RQRY3Td?(1iqGMEQm;lD z+vrLu%${3=pW<=yUTTHz_Ce%A#K?&>ppUEOl; zf&-QKR8|qq?02n#jmt@B11UkuHCkl{=JxSwuJm%}x$6c@Stey^K2eTG{0a*VJc(gW zG*M6Z1xPudjF)Aq;cW4};u31Z?#s3x%%RdkzewNItE?g^HGej1TqO~Y>oxbur)Wf! zi%+Agq(4l5079$?pWPR>28bge=@_GJlDQtfT`j1a+7M=GOf`|krH%99Tt=_79pF`X zY?@+yJ%(u^FrLImy?NB|OYQCD?~OZO6YA@~hKF$W^lg^E^$S*DMBdfkMmentPJtdTG1@*6%Gcd_(h}&a6PP9{{+J z{onwQu!8>Glk^dHB6dr+w=2kIt0s`BMRnm$57tosqo#^wLEG8No*%A47}FCI*;(I^4WXe;#RY);7jr$g zM38DKdR_L(db}MsbGwgfuq0*5U%PO+H?`J=e)~Oa5O%h7^Tu=-1E$@}Xas|{_Eb+F zw7$@pVOQ4o+DEOP5tu>OLq#RCe#Oh*)U$B#XzW3DW|yn^24P64jg3JEVJ67m$WRD~ zt6!h(B`4dsk{~tHxD4G*+JeNhHbdL7}|K3!mu#g z&zL+*C#M>L>$GJa0W)@G*U%*&h`fc*`*oL2wYYWe46?|E)AfqS>?r?>z$;lmfuhHH z6FAV;?2IyKCz{pH40sm=r}W1uKiDv(|aiz308u{>&Clu6$5H2%A{hCv(|Qy zyXXSrnz$bT8G={}!R~fNrE~cr-ev#8_PzV3@Tb#zD%J6^N^exeQ2O(wtqYYJomw;A zTf9hK8CvnE)8t$oArcfeg(KnOfk-VlcS^%4_7}$uUj>R#yX8)MEWrjQ2P)fVF*SQ{ zoZ`43={r{3ybP|~A4_@MOZ9Vh^O2S+j@0{9-YCysv$X8`&f?ttqAOln|8DtI)l8y} zQo37`L1=WW=EtDec<>Jl$F!CC8H_+t{&PRM!es|pMXH<{2K7&q!B-UW(K4YXQl2%Q zO9AwS`VQyOiPMr{<_!x3Rxmg)hR#1LN7x&GXv<*te7S)=c)P7O+|_o=gF@Tp+~sK{ zF>+f&&wB7PY_?Vp^O63Q+(J-lwNvqHlUJPijrF8Yc;I!gjSUJ-8`iuCY9}D3t#2x_F&|v1Y=m1FbxaT ze=)Vd?(%xiGXK?}cEgPr?RCVFsm_x1EGl|a(R4r1tm5K%fBZ|=)OUX6cG2cWc_X1U z%$xzQNm*`%uM|`k{~L*A^AE{Ck@&A8A@NGLdcrNVogr3Ke6179IhV@Bs9u|wCEs`7 z+1;v29!^MJNan|W_C$KSQEGdeVXNInKzby12Ev*D=|-E-w>(;;VIT?hw-t#Wdm^YHXbLtONet6g zOm{{l>`z>E5h(8R-3(hF(Y+0X0&IT6Ry;{6ZZFl3YIj}7^#hl_vz?~l*Tm+{yb@&? zxKY6?tOotapJShws?<^~N0}WEomS@b$D9dOY`m2h(oBh;oo;#7(r%LCq%r-G*VDXy znSclr?&AxJvDtHa7Yyf4IeUxxsYjoJUSTJgA&Xz#O}naXAjN~++2lVTt5dFoay8uM z!aLD=V2rTxe4Df!&o!24%E>qJCIIAlYEKS(ced9#IeTE^;zu5BOgjvK8 zKNZq-i$@q81t?r~A4NrmAxT3}GwkFjd?FC$xMFg@d}nc`>G3GGN&UDS%ZSiLT1{0# zm{VSR)yL*v3+oeU(>!Fa&k+Gp%Mx^>YVPd%6sW$kRM`lBwVpR%r3a(h`2ctrX$eZw z0!CvRf>jtXAYaLd)=0D@}i<4$A3{!N!==G60uO(g5 z`~v;wos>h}XKp$0k)`LBx=#($*AAnJ;%pO8A8_=xKkNLXdru(zjiiJepbGx? z7xiVk6wqUz*REQo{{V==o^Fb zBK9ejF4t8@ny~)B)oxm!GcQp8_MIFcg4yf=V<6;qRm4C9SohHna^@5EC zN9j2JRI`a-U}Spq;O?~zI$1x+65g)2Dfnw8S$k|niB~>gmo0q7cGCpWjXTR5 z#kLH{PpdFr=!A7tNUdq-A2Ux~rP%n6{%>J0LS(GI=62-&@c;DGmymw5Y#U#+uOwaT z3?;{0bDa4_h$`p*6QuO3dS$YJOW{pnT!*xQw|e?3nLX<}@Aa@lZQL?@j_p&PrFj_l zqHlEC{y`9eU4Obxe5BRP7%H>K zzh3sZ!&QZ!T&8f^E!~}VBK2Ax0{wbNQUm6ib?p&UN}2ZVz~m#CiLBJx0w7k9=<`DHZ!v*g9Kbn?Sc4W-bJd5H1YB~m{RAUR=$_^tD zHW7vbzIcu_%cDQ~zFekHRSsLQUsYiBE0?;xMbn8i=#6!@i?Z88sH8)MZWlkg>q|E{ zc;n(ERQe4)%`QS%o&>#t$-D_fvm-Ai8o!`>bjZB6te|qeG0ON+=zOgvw{}D!->}QS zwBDcu54lSZ-ZOt`6C2lm)2JZb`p|T)DQA-*WOS~nT`{3oD9B8tN+(D# z`T_WkXF)*qKN@Ks09oQIxfq_Qi>LQFm#q$5$@w?#Yd$0d_BY`_cGGUO%qfk>mL6ST zUrnbK$-TWO5O*cFyeMh)k@gL~wj&Z;Mp|>-)62PtIMy-UL$?p@$Hp-N6DEEkaTw1M zJWQUV8h90m@eBWc;91LB*8P{qw?Xc7juXk*P8ereX5UMoQ?M%+v3T)9=7K>zhQPQ7 z1ECK(#dZkFu+UoGx+UM6qnw0ewMqaX&LUzz1$7STGB-6Ze%ip{SokAOr;m0PC-?&u zm2X^YlPF(x?Fa&7BGu&!#kkg^)`>|ohONuouUwehvuOt?D(6q$po6=MAAop{y?`$$ z&gUYgMT2kO!ryV+2C8lsdC4ZeoqqfUdE&a6+Nd_aag!KExCDQgC0^?_+NfDR}&H zp<6bToM1Jx4Hw8?PlZ0Fg8m!cSTFpF?%$k4D&~XUJsPeE`Ka*`RN^zHj~~`>@BpOM z28`bE>zuF*P)bp*YiHh)3FBdxNub1cBmx9%axLs(MRf_#%eJ>l5eiK7j37Eh6_USj!?_u z2bAHDmdT!T!J`)igF)5huU<$6^U0toucE>8&2T~Jt%;ez;&PU-PmAUVNTfC;llif%AwKXKm_I(pGlSl6XD}WMAZ#vMxO_3w3&wKFUq|9a zVS_K?l#Wzxl5TRrMmVs+Ak?{+rpa5!!x$hxE2$6`P`{xAIt&#LfmFrV0DzZ4ltn)5TQK8yaFJvpKF$3Ty4Lol@RfBF*gQ({B*}db zvYZ$Vh8F z>p@zW>2TOHtUZD)G~B+61=#28x}mXR>faEh(1{1Yog?tMg35A}Iv0$j8ddQjR)(iw z=BIWEXM9}WCoFi=sY96HsBMrCdNW0-c)(b8KglybT8 zIcRJB9stPIW79GgdbMxqjSH;yMMNSjWDmgeZHmW%uWcAPO6r?G=ZDYphCp8lUGrs8 z*odjtuz}#@ci;n15(g=&MfVd*G%5*ciPEg*(C*beq35e4d3uHAF5A5J5TIOg2>97{NT|LdKR{6Ngw~)4((K~l zVkV%i7ihMt_3d(>(rhh8|0AUqDN?&@|0Dq-B!09)hWN=*G5Kam&E7C0Jc+hLdBy%5 zf1zoTa0RFwuq`{o(7D-*b|cbnCHupB2?hVn&R0v?t2qO z4wFcLioekL&TQ^);0e7tzx1v~NpQwrD1YSt$5V>qw5WN|N`L>vdHn!@j$U@hoMGmk zkpD`#tL;{UX-(7`8{=T@s6YMJ1IT#1c*E*l010&PLxw_IB(V%EfWs!UI#>xjK2$D) zp~4MH`LT5TMRylh;~?s$R;2iz=U<(Pz~V)8l;(}#yqa)Ozm}Y3{~;1JZ0);}&zT

tV1i(SO#G*(5vg+kwrq!B;jgL@s8pw`aaqt&Qz`bb_q3x)Ct zWuzF8tR+j1Vt^^6fwUwL);46R7#ZQ>Fr_Eh#Hj>ErKRYg#9Dl3fL|8gY=fVslLL_? zP<$Np@}Q`RgCUu)p|~h|&LpuJi{A4-DRad)x8}27|IX!|`R@HJ^iPfMKivft2L$PzEQXZ*X5hMhXr$$UC1}Iu$3kQ>M z%#hBa;FnIFRl?CNMN_=vYldSFnuK%*^cZ9q#dtz$F?X5M=hghC!Z0!>FOlPX@6j}1 z>Crj>eyZ*Z(uLXI3@W(xvzZQ1H(`Y(PC4vnfsZ!5E++w+?w|_63ke61%9!z;7-7#M zo%&Eyl=Mc6zLH>qgK(1oPc>u^zl-?|L62c~HVt|op_uLW?dH*18=582d6Sc|Czx2u zpt??<0auBoM6*@a&d;G$At71shCA~V^*JK-u!UD&fHBG$HEh6h-W=X}h{Q3YsHQ1c z;-d=h$C<7IEnO@IAghue4EvIznIk>*7`*miD*;Y;Po}gs>&#y12)J9R07wc7vkqP1 z))rhaNh59QbG085!}6g$&6M_QrzfTAEbu*Pl|)AIQ^tuf;$4rY&kX<4*Gs0Q@T*Ad z=38N@d7rUCx=D{5(<6eZfGXC%esqtbqimXGQ6mV3fu>k z#b_f=Ai3;iADg9Lb*~Rna+{6yz*>;p$8i}er=4g2)vSWRk^|-%wYBgto}7XG*lGYr zPxae=v?f6oPn32*7>RaTPQ96#dBM{Yi=UWqG!s!RM!w2VQ=%6&u-G5lOgnYgF^|Ex zF;X7_X$s-w4=$B)up?J=H+-tci~W_{uEzUmaacG#WNEF_LNM>E0;dy!$+v!;zP^di zUreUxSkneskzPBA<{@h1G4WE`i9YI3gN>2hg0C43r+7-cvyJzor>>RD0_;TPShb-r zgfAmpiTl)siN1pKM5NR|&uytO99-mJ zTm>X)B0lCY-4m)Rfer#20SNB`D=TZ$<+23R?$2(FoSZ&Ao&SLOXBqxWXjE8O#3qSd z+kFA}J-e^)b`cBAaZ>oC z_KJFC6K zC|p>;N0y8h#;%8dHvc@QnU`5{(E~ylD)nd(BPsJv#r{I5W{!|4YyfN%7zk2SLDc=` zO-2!qWrY+ZtPK~eFs0pN^QjF#!dZI_UY%7k^KPf$^>sxlQuWcE!^?YV=9+rFOr8Fr z6$uz4+pxRYl3g?cqH8=gq-$w<}T6^y8`3Ti2s_8V+HA-t(0QtEt4 zteRSMG0)h(^$6T4C;YtyAQ=-}fRQKk5!QJ3uDq+jd{A$k)+9U zm!a5%&OR1aKEg+dRN~)dkDhWXft_U-_*|z>B9!!9p1VdslQDKO4 z438A(Wpc7UmrQ}Z)M-|m3Dz%0-cY72!ZT;nE=6csGOr?2v-IK@<>W25br4l$IY500 zEMdFKKF|5_?Yny(8@NXtd&o94HUt^X&w&;g-^9YsE4Pvay}&P>dt&HO`e^4P18n2E zXTYPt>1$BE9`imN!Z{4sEA@LVe3>WeBYqw+hq~(TboP9_%lTf{KI$q6C1Jk@XN#8^A0Q zV>>4tu6=(@z_kqT^QT#M+WMls)2*s1q6eU!(>$0=GjPR4xI60SgS$^xV2+EFF8%AX zsYLx>ln7VG!2(zmdiH-#a0pGfVV#1$v;XhjW(u)J$uJ)PjLdU5X`3jMaw=swTuoHp z!Koq|%*@CE&`SOA9axS*1 zG_3W(4828NaIpF@M8V)hO1_A9PjPGiJGY)=^Xc|xd=RGl*d|5B-bos7HQ>DQ)h!u7C}2lP_LD=?v8*II$C)gure>C z4S?Q$gY$Mof*>0assRZw@6ii@DMoBs0^zayrsW49N3Dk*VMtnn5-!U90az~-;z{}+ zjk(`@GMB!ZaCL$Lu`2C|;L?)4LoS&r)Jnf$6k7RoQUaOled3hsH&p1zG&r6`F!}9^ z9a}=9E}^H7Xx7*)tN^Gx3nQ!uMslUWz%Z6Hl;!S{q!~bQ;#XbMQ)eG6TWUNE4^vK} zpU!~|>%xZ=V zPI*6cRk9K49CIv2d9g_xs`+ukwud?#Q5%q$C&uoM-k|A#Mjb15nO;ldfoc?Z4b&>x zCicqtq_%J-+d`9t?Ul;;@`to3%y%+s=;_1K3 z{h4uC^s@@@nd(1Oa{3d`&3Obdc= zPBaP5{yxj-U-UP19UOOkIBY&RYX9Nf{NcFkpu2g@xqS@tHv#BwiO8o(sBZ`*fP@9W z;q+Ef2EuX8333x{44ny9RInOyIAQ|I3iykNKbBe>bXsO~S3edIwBmasHL?_Xs5zcm)dwz(Ti* z*r1+Suu#7&=nshe_Tyt906W2anu<$8eFMw2PA%{Rx>J;I*89x`@>f4TtOuYrE1lX~ z4?$WQ2z0rJ7^h;?Ssrg5XbHz-*an$pwCv!FL}T~(KF(rhqbPYlTX~0v)JpN9{@-Ai3S>Vb@vDzUi(Q zHL1P3{H>QsBR?b=&0t5LsvohJ(QhdTFe|iaD32yOX(hQ8Wu?%r{^yA}mME*iJ)u^n zng?*T9!M@oR1DI98w%Ac*hsW>?{>=mZH#bOPZacMMJFRtfQn&5z zoF`x;K)fW|p2$J+pI%Nzi0hf(91T|@EI6XTp{aBiSy5#0592C4Qt|sbykR|Pwa&Xx z9gb`s*iYeG@a3Ba8VJ$9v^DmnFj2tPY9rybqPR8%b6hfVJa6^b1!5r&2!?`wz6J5A zR%DSjWjYrvCt!Q=(*+S~=uo#pL>L4Zn7=r!g0N41nt8(XhPf-2S|D^+loHJO&FnWr z|LKGXHFTF1<6JVknY~ZQ?JPJXDdJ_mTw1FvwR3_2T_OI(wfn6Xc>13X{dM#(2dTID zO-3xV$DJGl+LK%9Kn8b~pWTtU!u0&VGhBULw-ZgNlM0A|I%c5BTJVSo(eTVBLID+* zkDI4Sc0$J92vN@eEr^|Z8P(U4{#xIfu#%=m!h_Fn(O2Po{wXGQn@M%l!e?uPsu7QV z{IJrL)C%{y^3|_)Eh5BKlSBW93E~vd#ZRv-$puxTZ{?>A($ZBES=jN@9vR8Ri#VU? z7=KZG0Pr1r+_ZID!#F>w`XY9Z-BgV0=2dlMmR}IZoeUa$XR(}3Ebkl;3cc}N61z8@ zH?io1ciil2_Ii0yY&q>Zt?&k^X;FGL27M z>-L+vv*6;MsVYn9l&_Y&nYco!l|)hjBFJ>}weMQYmd5RDUrs*`XjgaAHTdB%60?{-k-Y75N4yyB4FeGG zmpdS2d5aqaE3NR9CKGCbnS3CCfeZC={)BitHcVBsEIWFQy)ME@OlbU!=JSCREMe3^ zIhh6(+eKUK*Q8pTFeS=Pw|eA=D)1&zlkiot%_1`R64W*Nu>`D0er*!7vd&2{JIRi9 z(U7f%Bv($Ml~vu=Js`C80SE{5fZ9u8e{e>=W3p6G=~o>(r$w1Sq>iC=Fo2b{VZ5J^l0^BH~D@nx^!OCw?7=TGe8jN<|{@qP?#Vx22nb*c+$ z_a`WdEJXXYpw?NTWG4i4Kzu;*1j@qISKgzhEHftq7x}r{W<~pf97TqRuSqHHjow}> z5q0i?gQg0i!!MY=h`!l%c z^Bls6+>>AxlNdUgPrqrgz3^h1m@`_DWXiKO3&APIeKL8HG}~z8Jav|UL=b|1Cw7xs z)gPMA?X)W<rW@oCm(=UUC(dH&brr+|9`{&PNzsb??c|L zJqKb^i=D{yF=r%UWDNEyer%FnR!QdO>&0tb7!m|$11A9|EsaaZ+(f1~f0RvJ9FLU& zW2e4w;OMd(ozEvFFNO^XK^I`to;#{W($&zOhV=xbjO=l6HBRL7;peh_2wySVOXlhQ zSe1CSiwL5Wg;WgnG=&z zw~U?p>pf);y3&%$`D~3-<6VSc_%9daamWp(Tbrk|doo*@6G4K=#Te!T`y&-vV+_J7 z4U9J**}z5cCGuOR(YRC07pp(SGrVrYl&LLtMe^rvS#01mI3*b0bh@G=UZq2~RiDo= zlM>^RHrDJ~56|f5l`SfNJCE|IjogAmEita8;#C(gmyjqEQVT#oA7ok@m~J23b?*|d zvxpCju$$t${eB~(uFDFvOhxJ1UOk=}j3QvwtF!orKwF$26vB1MAX8J!^=6?;TY(eJ$WK`+hJnhPyBIC>se{CLEw7D<@q|qXl4q| z+vmGXe(cAA+U<0e*j{*62Yyiw)54gE=(F`6zEVB-1ux9Y{D6upmG00D2RNzxUB*oJ z-kH$?SxLf#_xv(doyn->%@Vu9@1jbaz-F0P`D5N)L$C)5w&-*Or-A*3@e@?|p8AU9 z{{BM!jXU1#5!xOtX~TN zTz@dlG$Pef9<9^6E|1n5?ZMFN05r-VGWJLm4Z#37Mzs`d^DnMwHeoG!I02z}jB5X1 zHP;=~boTAP2`vfIgRnG1htQiw5D1}HL#WcEDkZoe0R$IF2%w-#Q7KX+6e)rry+)~m z=wc{J5ip>v3E+wX()Ho)?7TO>dGF2d{r7g}GxyFpx1G87+;h*IJNK4gk~w`32OB$& z!5Nk3H)%&S>@W6zn?Z-r3wPL~ULXDCWTSCpmW&;;nj``zAsR1)YskMQnCs$0- z+^L~f#(}T^+2eqwvS%~230+m-X2%`JuDeXj&=^#DS=(nNAXs!`hTJw!rX0mFaPiN# z;zgYoFEk6`1tr?=%K|NYNNt!whi@V)$TJUS6s@srkEBX2eu|T8OEm5RQ`jf3pBU?g z>jS@+qJ$K{IUaJJ>fZqd^0HmtmMbg5Hcris{V;s8Rpns~B)81NcQt>SO@cIZug^W7 z0wSw$TCC`*qzF-XaeA6`vUpPp(J7X#|LFg}3l3I(NLoqyi2dmS-l9~#X^^RRj(4hU zG4ssZO2gD|#W_DVvE65J$M`QqCO}FtgCP3Wd5lh_d~MmL@v{EJ&7q4PKabdfjf$$N zxVk0ExPp7$`iY*3?5Ii$e%Q>UN!kuCGe9)J(0YW2_XS9aM~pP1dC-7DZ~Fa4yMM49-tS>$y6=S5zlY^C13XzD#U`&F(okB_x6 zH!c_6u@tiAtgcE7Q6g93@P#NJKwPp&h&>G)q;!^Y*9R%fwjA;G8LhZ#?)^3C(D@Y2 zAy+N!WDt}1siE8#+^Ic^p`ToDb{c+MZokG5UW8>%ZeiAKN2k*$chY z_*5|sDU_X!Pgs_ABgk=KXFjFMi;6AdcyLXO zZfA$c$*wf^;QT_7YKT#5)} zsPL=_oPD)dTFS^{NS7+99>!-R3XYrncr2pHFzE;exvk({uSO#Bj!p9{h0H(IgZc18Tkkd4JDVe^T0eMHA482Bd&x?COZ zQpz=^(FH{wr`ok&G=U1C&Ut!D{qYF~X+fkPmPGZI`JJ<)$iyi~mM}!?1-;Kjd9Rp< z+)oR;R(epDAuzdib~FCxzYN>+@R*=)r$jRD09Nvn&%TZSn-<|u+wpt}`#w?V>yP7M zxxKsjJ0M1cqnRx)Pi_DIL{DESoCor^CdUbW4D`R>!mvx806%MTPhgex(<#T3xt+6F zL`tQjSR5sqC!Cxra^$v?=}*f{wL{UQE0@{di)3U5(`Ap@t_p)|gkvNJSVNer$=OlT z5ZA<t+`FSuWExr$#>x|&eF5nuTCZYoz?2!4pL!LnCH zZX5EPfk+3=IT>@^IQ`PXKVK#^uXd(fy26oU)}lTq8GIfK;SHo5`L3%dOK zVR#^vJ(J)=*;3fAV`t>eCuMVbReETZvfe0E zuB*$6j!tHE%QTOtb$O395rzj85pH!=tpkoF~2|(pYm7>gMP#iFV_p` ziHWCkR{3Yx=v+lHJ&9@bmEoCz(IE~$V)}1jy}FKRy1;BQukt2)f(|rD_UvKVh8DHJVC9m8ca`G@LWe-s-JYou3 zm9+d*V^Be-Ozw7;_FI(HQ*7kdRIP!)=*_xI9ZzMe9r?H-_ykJj>2fwj2OeIjndwzX zF|(k9VL+Ls&4F~wEsdoTBBm7%Z1Udm&Ubao%Fe3e|-b-fjGk;^Wa2fAE+dV4 zy6^ldtl6n>QNWv{=J`LPcjhranR4U}YvW%Y={?aOL&qkLE2B0d(jaV+f>@p7RK8`U z2;LiBw`etMu`RB*%Hx18tg++QeWjDa${Z?1TG`xi$~s8-aJ>gi=b}LPI7IFJ0@bzA zN$#SOY(Z=S^$&Ngt68O-3Wr2q$J^1o6`-j5ELUp#o(+crfwS84iT$CsiomBF1xSm( zXkDtPt7J`^xKz_3>GM!-bdmSt#ofI?8g=y~F2ejG+q+z6%>3Pr_|h&h<;P&Te1G zDe`7(atuIY%hUj#g~t$5lPMF#PdeZMF+SYf`sNQDPvE9kMhAoWp)!&-S{7If#z_WK zt}%{z;Uk)&2Y=%phG@IJ$4ETa{z=az5_*q%ovQbeX&E*( zN^q8@BLrPPZ^K#%5x@9HNRpq>uzS+7%(bWK=5!Dzn`z7?@za9_x};+(QuBum5?aCwpa-vG{SqxmAv5+g|%DI6U2`a>vcc6EMT8mU2%xx5? z{vtz00Oa{<5x;A&9TVT}Yoy@#aoi%mD>i`X_5meG61wL@dRV)TML%xaZZC~VgZO!A z;JB|l&{!%x6RsJEP7ZZc-K^V_xG1W6+)@N{BOrgp=W2xuR5M#THEo1J+cWFD90F2B zjop2n8z=7-KGAGZaD}{IXUM5K!4*eXyZXL}(;`;>>Qm7I$F^QOw7ppyZN#9JTegW2 zT8#JCv5^78B3CxDf~u0<#GP+bb5N-(iDv9?+ZVoz>vK`+$8aMms5Weo;n&ub3=_5y zA`q#w9o>mIJVKCqC3b06r_aeY^&oy)%#8K5>TmM|8xuF@3)hFz8z7gm$MJUHNzC<4 z!HiD&;HfhCul;3_{I%Rf5q(V&9Yr?F{oh}zpJO48JP?A;!;)~tqaNOGOB)@^d{8xv`C+63ZLBx5ZU z%q=|kd;{*wbf1(t&D8wO;1WDgy>YaC&w2dJyxh}y-d@#A_*s*YyaDXVemf7=zHW?A zD|<=lfYZ+c^%cs2W0RrRjHWs82=ia#tKP4Tt$4T1<*6-z4iXOFzc?5u7QD&QRxDP!gb+fKpDnk>F`@ISgkX}s$YxAF2j@H>d7N(2xvQzkw)offa)g#heAH*smR&2Qc zY~wr7ozk5hZgn!~#FS#^_3IC60No{V7uWk`&0V%GH%1>D-yC0| zHXb^`o%-WDP&nSH&{&qei{$Ts@_NED`ORcdT7iPfgTPpU-tRyTqWtx7s6Dtxshz`y zmVCz_c<|9XbfIZVZ54BFs&M|zus7J@u_BO~3@_Y{!$66*_@_b%EPKANTMy-&#+%Tj nswAF1J&0^oZFtOrPR|DukQ+hl5Cf5NThc6l_vvV2zt8*!PH|~Q diff --git a/hand_eye_calibration/HandEyeCalibration.cpp b/hand_eye_calibration/HandEyeCalibration.cpp deleted file mode 100644 index 4aa23bb..0000000 --- a/hand_eye_calibration/HandEyeCalibration.cpp +++ /dev/null @@ -1,1495 +0,0 @@ -#include "HandEyeCalibration.h" -#include "ui_HandEyeCalibration.h" -#include "TSAIleastSquareCalibration.h" -#include - -extern cv::Mat find_corner_thread_img; -extern struct Chessboarder_t find_corner_thread_chessboard; - -HandEyeCalibration::HandEyeCalibration(QWidget *parent) - : QMainWindow(parent) - , ui(new Ui::HandEyeCalibration) -{ - ui->setupUi(this); - setFixedSize(this->width(), this->height()); - ui->addRobot->setFlat(true); - ui->addImage->setFlat(true); - ui->calibrate->setFlat(true); - ui->export_1->setFlat(true); - ui->delete_1->setFlat(true); - ui->undistort->setFlat(true); - ui->Camera->setFlat(true); - // ui->Robot->setFlat(true); - findcorner_thread = new FindcornerThread(); - connect(findcorner_thread, SIGNAL(isDone()), this, SLOT(DealThreadDone())); - ShowIntro(); - QMenu *menu = new QMenu(); - QAction *open_camera = menu->addAction("打开相机"); - QFont font = menu->font(); - font.setPixelSize(12); - menu->setFont(font); - connect(open_camera, SIGNAL(triggered()), this,SLOT(OpenCamera())); - ui->Camera->setMenu(menu); - ui->listWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); // 按ctrl多选 - connect(ui->addRobot, SIGNAL(clicked()), this, SLOT(addRobot())); - connect(ui->addImage, SIGNAL(clicked()), this, SLOT(addImage())); - connect(ui->delete_1, SIGNAL(clicked()), this, SLOT(deleteImage())); - chessboard_size = 0; - connect(ui->calibrate, SIGNAL(clicked()), this, SLOT(calibrate())); - connect(ui->fisheye, SIGNAL(stateChanged(int)), this, SLOT(fisheyeModeSwitch(int))); - connect(ui->export_1, SIGNAL(clicked()), this, SLOT(exportParam())); - connect(ui->undistort, SIGNAL(clicked()), this, SLOT(distortModeSwitch())); - connect(ui->listWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(chooseImage(QListWidgetItem*, QListWidgetItem*))); - connect(ui->double_camera, SIGNAL(toggled(bool)), this, SLOT(reset())); - connect(ui->single_camera, SIGNAL(toggled(bool)), this, SLOT(reset())); - connect(ui->double_undistort, SIGNAL(toggled(bool)), this, SLOT(undistortReset())); - connect(ui->single_undistort, SIGNAL(toggled(bool)), this, SLOT(undistortReset())); - saveImage = ui->menu->addAction("导出矫正图片"); - font = saveImage->font(); - font.setPixelSize(12); - saveImage->setFont(font); - connect(saveImage, SIGNAL(triggered()), this, SLOT(saveUndistort())); -} - -HandEyeCalibration::~HandEyeCalibration() -{ - delete ui; -} - -// 显示简单的文字引导 -void HandEyeCalibration::ShowIntro() -{ - ui->intro->setFixedHeight(100); - if(ui->double_camera->isChecked() || ui->double_undistort->isChecked()) - { - QString str = "点击左上角添加图片"; - str += "\n"; - str += "左右相机图片分别放于两个文件夹"; - str += "\n"; - str += "对应图片名称需一致。"; - str += "\n"; - str += "\n"; - str += "单/双目纠正模式可纠正无棋盘的图片。"; - ui->intro->setText(str); - } - else if(ui->single_camera->isChecked() || ui->single_undistort->isChecked()) - { - QString str = "点击左上角添加图片"; - str += "\n"; - str += "15至20张较为适宜"; - str += "\n"; - str += "\n"; - str += "单/双目纠正模式可纠正无棋盘的图片。"; - ui->intro->setText(str); - } -} - -// 隐藏文字指导 -void HandEyeCalibration::HiddenIntro() -{ - ui->intro->setText(""); - ui->intro->setFixedHeight(0); -} - -// 使用相机采集图片,根据标定模式选择采集模式 -void HandEyeCalibration::OpenCamera() -{ - if(ui->single_camera->isChecked()) - { -#ifdef Q_OS_LINUX - int camcount = v4l2.GetDeviceCount(); - if(camcount < 1) - { - QMessageBox::warning(this, "警告", "没有检测到摄像头,请插上摄像头后再打开该窗口", QMessageBox::Yes); - return; - } - single_c = new single_capture_linux(); -#elif defined(Q_OS_WIN32) - QList camera_list = QCameraInfo::availableCameras(); - if(camera_list.length() < 1) - { - QMessageBox::warning(this, "警告", "没有检测到摄像头,请插上摄像头后再打开该窗口", QMessageBox::Yes); - return; - } - single_c = new single_capture(); -#endif - single_c->setWindowTitle("单个相机拍照"); - QFont font = single_c->font(); - font.setPixelSize(12); - single_c->setFont(font); - single_c->show(); - } - else - { -#ifdef Q_OS_LINUX - int camcount = v4l2_left.GetDeviceCount(); - if(camcount < 2) - { - QMessageBox::warning(this, "警告", "摄像头少于两个,请插上摄像头后再打开该窗口", QMessageBox::Yes); - return; - } - double_c = new double_capture_linux(); -#elif defined(Q_OS_WIN32) - QList camera_list = QCameraInfo::availableCameras(); - if(camera_list.length() < 2) - { - QMessageBox::warning(this, "警告", "摄像头少于两个,请插上摄像头后再打开该窗口", QMessageBox::Yes); - return; - } - double_c = new double_capture(); -#endif - double_c->setWindowTitle("两个相机拍照"); - QFont font = double_c->font(); - font.setPixelSize(12); - double_c->setFont(font); - double_c->show(); - } -} - -// 导出矫正图片 -void HandEyeCalibration::saveUndistort() -{ - if(calibrate_flag == false) - { - if(ui->single_undistort->isChecked() || ui->double_undistort->isChecked()) - { - QMessageBox::warning(this, "警告", "还未获取到相机参数,请点击UNDISTORT按钮加载yaml文件。", QMessageBox::Yes, QMessageBox::Yes); - return; - } - else - { - QMessageBox::critical(NULL, "错误", "请先标定", QMessageBox::Yes, QMessageBox::Yes); - return; - } - } - QString srcDirPath = QFileDialog::getExistingDirectory(nullptr, "选择保存路径", "./"); - if(srcDirPath.length() <= 0) - return; - QTextCodec *code = QTextCodec::codecForName("GB2312");//解决中文路径问题 - if(ui->double_camera->isChecked() || ui->double_undistort->isChecked()) - { - QDir dir; - dir.mkdir(srcDirPath+"/left/"); - dir.mkdir(srcDirPath+"/right/"); - for(unsigned int i = 0; i < stereo_imgs.size(); i++) - { - struct Stereo_Img_t img = stereo_imgs[i]; - cv::Mat left_dst = img.left_img.clone(); - cv::Mat right_dst = img.right_img.clone(); - if(fisheye_flag == false) - { - cv::Mat mapx, mapy; - cv::initUndistortRectifyMap(cameraMatrix, distCoefficients, R1, P1, img_size, CV_16SC2, mapx, mapy); - cv::remap(img.left_img, left_dst, mapx, mapy, cv::INTER_LINEAR); - cv::initUndistortRectifyMap(cameraMatrix2, distCoefficients2, R2, P2, img_size, CV_16SC2, mapx, mapy); - cv::remap(img.right_img, right_dst, mapx, mapy, cv::INTER_LINEAR); - } - else - { - cv::Mat mapx, mapy; - cv::fisheye::initUndistortRectifyMap(K, D, R1, P1, img_size, CV_16SC2, mapx, mapy); - cv::remap(img.left_img, left_dst, mapx, mapy, cv::INTER_LINEAR); - cv::fisheye::initUndistortRectifyMap(K2, D2, R2, P2, img_size, CV_16SC2, mapx, mapy); - cv::remap(img.right_img, right_dst, mapx, mapy, cv::INTER_LINEAR); - } - QString save_name = srcDirPath + "/left/" + img.left_file_name; - std::string str = code->fromUnicode(save_name).data(); - cv::imwrite(str, left_dst); - save_name = srcDirPath + "/right/" + img.left_file_name; - str = code->fromUnicode(save_name).data(); - cv::imwrite(str, right_dst); - for(int i = 1; i < 10; i++) - { - cv::line(left_dst, cv::Point(0, left_dst.rows*i/10), cv::Point(left_dst.cols-1, left_dst.rows*i/10), cv::Scalar(0,0,255), 2); - cv::line(right_dst, cv::Point(0, right_dst.rows*i/10), cv::Point(right_dst.cols-1, right_dst.rows*i/10), cv::Scalar(0,0,255), 2); - } - cv::Mat combian_img = cv::Mat::zeros(cv::Size(img.left_img.cols*2, img.left_img.rows), CV_8UC3); - cv::Mat new_roi = combian_img(cv::Rect(0, 0, img.left_img.cols, img.left_img.rows)); - left_dst.convertTo(new_roi, new_roi.type()); - new_roi = combian_img(cv::Rect(img.left_img.cols, 0, img.left_img.cols, img.left_img.rows)); - right_dst.convertTo(new_roi, new_roi.type()); - save_name = srcDirPath + "/" + img.left_file_name; - str = code->fromUnicode(save_name).data(); - cv::imwrite(str, combian_img); - } - } - else - { - for(unsigned int i = 0; i < imgs.size(); i++) - { - cv::Mat dst; - if(fisheye_flag == false) - cv::undistort(imgs[i].img, dst, cameraMatrix, distCoefficients); - else - cv::fisheye::undistortImage(imgs[i].img, dst, K, D, K, imgs[i].img.size()); - QString save_name = srcDirPath + "/" + imgs[i].file_name; - std::string str = code->fromUnicode(save_name).data(); - cv::imwrite(str, dst); - } - } -} - -// 切换模式时删除所有图片缓存 -void HandEyeCalibration::reset() -{ - ShowIntro(); - ui->k_2->setEnabled(true); - ui->k_3->setEnabled(true); - ui->tangential->setEnabled(true); - ui->calibrate->setEnabled(true); - ui->export_1->setEnabled(true); - chessboard_size = 0; - calibrate_flag = false; - distort_flag = false; - disconnect(ui->listWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(chooseImage(QListWidgetItem*, QListWidgetItem*))); - ui->listWidget->clear(); - connect(ui->listWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(chooseImage(QListWidgetItem*, QListWidgetItem*))); - if(imgs.size() > 0) - imgs.clear(); - if(stereo_imgs.size() > 0) - stereo_imgs.clear(); -} - -void HandEyeCalibration::undistortReset() -{ - ShowIntro(); - ui->k_2->setDisabled(true); - ui->k_3->setDisabled(true); - ui->tangential->setDisabled(true); - ui->calibrate->setDisabled(true); - ui->export_1->setDisabled(true); - chessboard_size = 0; - calibrate_flag = false; - distort_flag = false; - disconnect(ui->listWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(chooseImage(QListWidgetItem*, QListWidgetItem*))); - ui->listWidget->clear(); - connect(ui->listWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(chooseImage(QListWidgetItem*, QListWidgetItem*))); - if(imgs.size() > 0) - imgs.clear(); - if(stereo_imgs.size() > 0) - stereo_imgs.clear(); -} - -// 显示选择的图像 -void HandEyeCalibration::chooseImage(QListWidgetItem* item, QListWidgetItem*) -{ - int id = ui->listWidget->row(item); - if(ui->double_camera->isChecked()) - { - // 如果是双目模式 - struct Stereo_Img_t img = stereo_imgs[id]; - cv::Mat left_dst = img.left_img.clone(); - cv::Mat right_dst = img.right_img.clone(); - if(distort_flag == true && calibrate_flag == true) - { - // 对左右图像进行矫正 - if(fisheye_flag == false) - { - cv::Mat mapx, mapy; - cv::initUndistortRectifyMap(cameraMatrix, distCoefficients, R1, P1, img_size, CV_16SC2, mapx, mapy); - cv::remap(img.left_img, left_dst, mapx, mapy, cv::INTER_LINEAR); - cv::initUndistortRectifyMap(cameraMatrix2, distCoefficients2, R2, P2, img_size, CV_16SC2, mapx, mapy); - cv::remap(img.right_img, right_dst, mapx, mapy, cv::INTER_LINEAR); - } - else - { - cv::Mat mapx, mapy; - cv::fisheye::initUndistortRectifyMap(K, D, R1, P1, img_size, CV_16SC2, mapx, mapy); - cv::remap(img.left_img, left_dst, mapx, mapy, cv::INTER_LINEAR); - cv::fisheye::initUndistortRectifyMap(K2, D2, R2, P2, img_size, CV_16SC2, mapx, mapy); - cv::remap(img.right_img, right_dst, mapx, mapy, cv::INTER_LINEAR); - } - // 绘制横线以观察左右图像极线是否对齐 - for(int i = 1; i < 10; i++) - { - cv::line(left_dst, cv::Point(0, left_dst.rows*i/10), cv::Point(left_dst.cols-1, left_dst.rows*i/10), cv::Scalar(0,0,255), 2); - cv::line(right_dst, cv::Point(0, right_dst.rows*i/10), cv::Point(right_dst.cols-1, right_dst.rows*i/10), cv::Scalar(0,0,255), 2); - } - } - else - { - // 非矫正模式下显示角点位置 - for(unsigned int i = 0; i < img.left_img_points.size(); i++) - { - cv::circle(left_dst, img.left_img_points[i], 10, cv::Scalar(0,0,255), 2); - } - for(unsigned int i = 0; i < img.right_img_points.size(); i++) - { - cv::circle(right_dst, img.right_img_points[i], 10, cv::Scalar(0,0,255), 2); - } - if(calibrate_flag == true) - { - // 显示标定后角点的重投影 - if(fisheye_flag == false) - { - std::vector reproject_img_p; - projectPoints(img.world_points, img.left_rvec, img.left_tvec, cameraMatrix, distCoefficients, reproject_img_p); - for(unsigned int i = 0; i < reproject_img_p.size(); i++) - { - cv::circle(left_dst, reproject_img_p[i], 3, cv::Scalar(255,0,0), 2); - } - projectPoints(img.world_points, img.right_rvec, img.right_tvec, cameraMatrix2, distCoefficients2, reproject_img_p); - for(unsigned int i = 0; i < reproject_img_p.size(); i++) - { - cv::circle(right_dst, reproject_img_p[i], 3, cv::Scalar(255,0,0), 2); - } - } - else - { - std::vector reproject_img_p; - std::vector w_p_; - for(unsigned int i = 0; i < img.world_points.size(); i++) - { - w_p_.push_back(img.world_points[i]); - } - cv::fisheye::projectPoints(w_p_, reproject_img_p, img.left_fish_rvec, img.left_fish_tvec, K, D); - for(unsigned int i = 0; i < reproject_img_p.size(); i++) - { - cv::circle(left_dst, reproject_img_p[i], 3, cv::Scalar(255,0,0), 2); - } - cv::fisheye::projectPoints(w_p_, reproject_img_p, img.right_fish_rvec, img.right_fish_tvec, K2, D2); - for(unsigned int i = 0; i < reproject_img_p.size(); i++) - { - cv::circle(right_dst, reproject_img_p[i], 3, cv::Scalar(255,0,0), 2); - } - } - } - } - int height = img.left_img.rows*265/img.left_img.cols; - cv::resize(left_dst, left_dst, cv::Size(265, height)); - cv::resize(right_dst, right_dst, cv::Size(265, height)); - cv::Mat combian_img = cv::Mat::zeros(cv::Size(530, height), CV_8UC3); - cv::Mat new_roi = combian_img(cv::Rect(0, 0, 265, height)); - left_dst.convertTo(new_roi, new_roi.type()); - new_roi = combian_img(cv::Rect(265, 0, 265, height)); - right_dst.convertTo(new_roi, new_roi.type()); - QImage qimage = Mat2QImage(combian_img); - ui->Image->setPixmap(QPixmap::fromImage(qimage)); - } - else if(ui->single_camera->isChecked()) - { - // 单目逻辑同双目 - cv::Mat img; - std::vector corners; - std::vector w_p; - cv::Mat rvecs, tvecs; - cv::Vec3d fish_rvecs, fish_tvecs; - img = imgs[id].img.clone(); - corners = imgs[id].img_points; - w_p = imgs[id].world_points; - tvecs = imgs[id].tvec; - rvecs = imgs[id].rvec; - fish_rvecs = imgs[id].fish_rvec; - fish_tvecs = imgs[id].fish_tvec; - for(unsigned int i = 0; i < corners.size(); i++) - { - cv::circle(img, corners[i], 10, cv::Scalar(0,0,255), 2); - } - if(calibrate_flag == true) - { - if(fisheye_flag == false) - { - std::vector reproject_img_p; - projectPoints(w_p, rvecs, tvecs, cameraMatrix, distCoefficients, reproject_img_p); - for(unsigned int i = 0; i < reproject_img_p.size(); i++) - { - cv::circle(img, reproject_img_p[i], 3, cv::Scalar(255,0,0), 2); - } - } - else - { - std::vector reproject_img_p; - std::vector w_p_; - for(unsigned int i = 0; i < w_p.size(); i++) - { - w_p_.push_back(w_p[i]); - } - cv::fisheye::projectPoints(w_p_, reproject_img_p, fish_rvecs, fish_tvecs, K, D); - for(unsigned int i = 0; i < reproject_img_p.size(); i++) - { - cv::circle(img, reproject_img_p[i], 3, cv::Scalar(255,0,0), 2); - } - } - } - if(distort_flag == true && calibrate_flag == true) - { - cv::Mat dst; - if(fisheye_flag == false) - cv::undistort(img, dst, cameraMatrix, distCoefficients); - else - cv::fisheye::undistortImage(img, dst, K, D, K, img.size()); - img = dst; - } - if(img.cols > img.rows) - cv::resize(img, img, cv::Size(530, img.rows*530/img.cols)); - else - cv::resize(img, img, cv::Size(img.cols*495/img.rows, 495)); - QImage qimage = Mat2QImage(img); - ui->Image->setPixmap(QPixmap::fromImage(qimage)); - } - else if(ui->double_undistort->isChecked()) - { - // 如果是双目模式 - struct Stereo_Img_t img = stereo_imgs[id]; - cv::Mat left_dst = img.left_img.clone(); - cv::Mat right_dst = img.right_img.clone(); - if(distort_flag == true) - { - // 对左右图像进行矫正 - if(fisheye_flag == false) - { - cv::Mat mapx, mapy; - cv::initUndistortRectifyMap(cameraMatrix, distCoefficients, R1, P1, img_size, CV_16SC2, mapx, mapy); - cv::remap(img.left_img, left_dst, mapx, mapy, cv::INTER_LINEAR); - cv::initUndistortRectifyMap(cameraMatrix2, distCoefficients2, R2, P2, img_size, CV_16SC2, mapx, mapy); - cv::remap(img.right_img, right_dst, mapx, mapy, cv::INTER_LINEAR); - } - else - { - cv::Mat mapx, mapy; - cv::fisheye::initUndistortRectifyMap(K, D, R1, P1, img_size, CV_16SC2, mapx, mapy); - cv::remap(img.left_img, left_dst, mapx, mapy, cv::INTER_LINEAR); - cv::fisheye::initUndistortRectifyMap(K2, D2, R2, P2, img_size, CV_16SC2, mapx, mapy); - cv::remap(img.right_img, right_dst, mapx, mapy, cv::INTER_LINEAR); - } - // 绘制横线以观察左右图像极线是否对齐 - for(int i = 1; i < 10; i++) - { - cv::line(left_dst, cv::Point(0, left_dst.rows*i/10), cv::Point(left_dst.cols-1, left_dst.rows*i/10), cv::Scalar(0,0,255), 2); - cv::line(right_dst, cv::Point(0, right_dst.rows*i/10), cv::Point(right_dst.cols-1, right_dst.rows*i/10), cv::Scalar(0,0,255), 2); - } - } - int height = img.left_img.rows*265/img.left_img.cols; - cv::resize(left_dst, left_dst, cv::Size(265, height)); - cv::resize(right_dst, right_dst, cv::Size(265, height)); - cv::Mat combian_img = cv::Mat::zeros(cv::Size(530, height), CV_8UC3); - cv::Mat new_roi = combian_img(cv::Rect(0, 0, 265, height)); - left_dst.convertTo(new_roi, new_roi.type()); - new_roi = combian_img(cv::Rect(265, 0, 265, height)); - right_dst.convertTo(new_roi, new_roi.type()); - QImage qimage = Mat2QImage(combian_img); - ui->Image->setPixmap(QPixmap::fromImage(qimage)); - } - else - { - // 单目逻辑同双目 - cv::Mat img = imgs[id].img.clone(); - if(distort_flag == true) - { - cv::Mat dst; - if(fisheye_flag == false) - cv::undistort(img, dst, cameraMatrix, distCoefficients); - else - cv::fisheye::undistortImage(img, dst, K, D, K, img.size()); - img = dst; - } - if(img.cols > img.rows) - cv::resize(img, img, cv::Size(530, img.rows*530/img.cols)); - else - cv::resize(img, img, cv::Size(img.cols*495/img.rows, 495)); - QImage qimage = Mat2QImage(img); - ui->Image->setPixmap(QPixmap::fromImage(qimage)); - } -} - -// 角点寻找线程结束时的回调函数 -void HandEyeCalibration::DealThreadDone() -{ - findcorner_thread->quit(); - findcorner_thread->wait(); - thread_done = true; -} - -void HandEyeCalibration::receiveYamlPath(QString str) -{ - cv::FileStorage fs_read(str.toStdString(), cv::FileStorage::READ); - if(ui->single_undistort->isChecked()) - { - if(fs_read["cameraMatrix"].empty() || fs_read["distCoeffs"].empty()) - { - QMessageBox::critical(this, "错误", "YAML文件格式错误", QMessageBox::Yes, QMessageBox::Yes); - return; - } - if(fisheye_flag == false) - { - fs_read["cameraMatrix"] >> cameraMatrix; - fs_read["distCoeffs"] >> distCoefficients; - calibrate_flag = true; - } - else - { - cv::Mat camera_matrix, Dist; - fs_read["cameraMatrix"] >> camera_matrix; - fs_read["distCoeffs"] >> Dist; - K(0,0) = camera_matrix.at(0,0); - K(0,1) = 0; - K(0,2) = camera_matrix.at(0,2); - K(1,0) = 0; - K(1,1) = camera_matrix.at(1,1); - K(1,2) = camera_matrix.at(1,2); - K(1,0) = 0; - K(2,1) = 0; - K(2,2) = 1; - D[0] = Dist.at(0,0); - D[1] = Dist.at(0,1); - D[2] = Dist.at(0,2); - D[3] = Dist.at(0,3); - calibrate_flag = true; - } - } - else - { - if(fs_read["left_camera_Matrix"].empty() || fs_read["left_camera_distCoeffs"].empty() || fs_read["right_camera_Matrix"].empty() || fs_read["right_camera_distCoeffs"].empty()) - { - QMessageBox::critical(this, "错误", "YAML文件格式错误", QMessageBox::Yes, QMessageBox::Yes); - return; - } - if(fs_read["Rotate_Matrix"].empty() || fs_read["Translate_Matrix"].empty() || fs_read["R1"].empty() || fs_read["R2"].empty() || fs_read["P1"].empty() || - fs_read["P2"].empty() || fs_read["Q"].empty()) - { - QMessageBox::critical(this, "错误", "YAML文件格式错误", QMessageBox::Yes, QMessageBox::Yes); - return; - } - if(fisheye_flag == false) - { - fs_read["left_camera_Matrix"] >> cameraMatrix; - fs_read["left_camera_distCoeffs"] >> distCoefficients; - fs_read["right_camera_Matrix"] >> cameraMatrix2; - fs_read["right_camera_distCoeffs"] >> distCoefficients2; - fs_read["Rotate_Matrix"] >> R; - fs_read["Translate_Matrix"] >> T; - fs_read["R1"] >> R1; - fs_read["R2"] >> R2; - fs_read["P1"] >> P1; - fs_read["P2"] >> P2; - fs_read["Q"] >> Q; - if(cameraMatrix.at(0,0) == 0 || cameraMatrix2.at(0,0) == 0) - { - QMessageBox::critical(this, "错误", "YAML文件格式错误", QMessageBox::Yes, QMessageBox::Yes); - return; - } - calibrate_flag = true; - } - else - { - cv::Mat camera_matrix, Dist; - fs_read["left_camera_Matrix"] >> camera_matrix; - fs_read["left_camera_distCoeffs"] >> Dist; - K(0,0) = camera_matrix.at(0,0); - K(0,1) = 0; - K(0,2) = camera_matrix.at(0,2); - K(1,0) = 0; - K(1,1) = camera_matrix.at(1,1); - K(1,2) = camera_matrix.at(1,2); - K(1,0) = 0; - K(2,1) = 0; - K(2,2) = 1; - D[0] = Dist.at(0,0); - D[1] = Dist.at(0,1); - D[2] = Dist.at(0,2); - D[3] = Dist.at(0,3); - - fs_read["right_camera_Matrix"] >> camera_matrix; - fs_read["right_camera_distCoeffs"] >> Dist; - K2(0,0) = camera_matrix.at(0,0); - K2(0,1) = 0; - K2(0,2) = camera_matrix.at(0,2); - K2(1,0) = 0; - K2(1,1) = camera_matrix.at(1,1); - K2(1,2) = camera_matrix.at(1,2); - K2(1,0) = 0; - K2(2,1) = 0; - K2(2,2) = 1; - D2[0] = Dist.at(0,0); - D2[1] = Dist.at(0,1); - D2[2] = Dist.at(0,2); - D2[3] = Dist.at(0,3); - fs_read["Rotate_Matrix"] >> R; - fs_read["Translate_Matrix"] >> T; - fs_read["R1"] >> R1; - fs_read["R2"] >> R2; - fs_read["P1"] >> P1; - fs_read["P2"] >> P2; - fs_read["Q"] >> Q; - if(cameraMatrix.at(0,0) == 0 || cameraMatrix2.at(0,0) == 0) - { - QMessageBox::critical(this, "错误", "YAML文件格式错误", QMessageBox::Yes, QMessageBox::Yes); - return; - } - calibrate_flag = true; - } - } -} - -// 加载双目图像 -void HandEyeCalibration::receiveFromDialog(QString str) -{ - HiddenIntro(); - QStringList list = str.split(","); - QString left_src = list[0]; - QString right_src = list[1]; - chessboard_size = list[2].toDouble(); - QDir dir(left_src); - dir.setFilter(QDir::Files | QDir::NoSymLinks); - QStringList filters; - filters << "*.png" << "*.jpg" << "*.jpeg"; - dir.setNameFilters(filters); - QStringList imagesList = dir.entryList(); - if(ui->double_camera->isChecked()) - { - if(imagesList.length() <= 3) - { - QMessageBox::critical(NULL, "错误", "至少需要四组图片", QMessageBox::Yes, QMessageBox::Yes); - return; - } - QProgressDialog *dialog = new QProgressDialog(tr("检测角点..."),tr("取消"),0,imagesList.length(),this); - dialog->setWindowModality(Qt::WindowModal); - dialog->setMinimumDuration(0); - dialog->setWindowTitle("请稍候"); - dialog->setValue(0); - QFont font = dialog->font(); - font.setPixelSize(12); - dialog->setFont(font); - dialog->show(); - for(int i = 0; i < imagesList.length(); i++) - { - QString left_file_name = left_src + "/" + imagesList[i]; - QString right_file_name = right_src + "/" + imagesList[i]; - cv::Mat right_image = cv::imread(right_file_name.toStdString()); - if(right_image.empty()) - { - dialog->setValue(i+1); - continue; - } - cv::Mat left_image = cv::imread(left_file_name.toStdString()); - if(left_image.cols != right_image.cols || left_image.rows != right_image.rows) - { - QMessageBox::critical(NULL, "错误", "左右相机图片尺寸不一致", QMessageBox::Yes, QMessageBox::Yes); - delete dialog; - return; - } - find_corner_thread_img = left_image; - thread_done = false; - findcorner_thread->start(); - while(thread_done == false) - { - if(dialog->wasCanceled()) - { - DealThreadDone(); - delete dialog; - return; - } - // 强制Windows消息循环,防止程序出现未响应情况 - QCoreApplication::processEvents(); - } - struct Chessboarder_t left_chess = find_corner_thread_chessboard; - // 如果检测到多个棋盘,则剔除该图片 - if(left_chess.chessboard.size() != 1) - { - dialog->setValue(i+1); - continue; - } - find_corner_thread_img = right_image; - thread_done = false; - findcorner_thread->start(); - while(thread_done == false) - { - if(dialog->wasCanceled()) - { - DealThreadDone(); - delete dialog; - return; - } - QCoreApplication::processEvents(); - } - struct Chessboarder_t right_chess = find_corner_thread_chessboard; - // 如果检测到多个棋盘,则剔除该图片 - if(right_chess.chessboard.size() != 1) - { - dialog->setValue(i+1); - continue; - } - // 如果左右图片检测到的棋盘尺寸不对,则剔除该组图片 - if(left_chess.chessboard[0].rows != right_chess.chessboard[0].rows || - left_chess.chessboard[0].cols != right_chess.chessboard[0].cols) - { - dialog->setValue(i+1); - continue; - } - // 缓存图片及角点 - struct Stereo_Img_t img_; - img_.left_img = left_image; - img_.right_img = right_image; - img_.left_file_name = imagesList[i]; - img_.right_file_name = imagesList[i]; - for (unsigned int j = 0; j < left_chess.chessboard.size(); j++) - { - for (int u = 0; u < left_chess.chessboard[j].rows; u++) - { - for (int v = 0; v < left_chess.chessboard[j].cols; v++) - { - img_.left_img_points.push_back(left_chess.corners.p[left_chess.chessboard[j].at(u, v)]); - img_.world_points.push_back(cv::Point3f(u*chessboard_size, v*chessboard_size, 0)); - img_.right_img_points.push_back(right_chess.corners.p[right_chess.chessboard[j].at(u, v)]); - } - } - } - stereo_imgs.push_back(img_); - img_size = left_image.size(); - int img_height = left_image.rows*90/left_image.cols; - cv::resize(left_image, left_image, cv::Size(90, img_height)); - cv::resize(right_image, right_image, cv::Size(90, img_height)); - cv::Mat combian_img = cv::Mat::zeros(cv::Size(180, img_height), CV_8UC3); - cv::Mat new_roi = combian_img(cv::Rect(0, 0, 90, img_height)); - left_image.convertTo(new_roi, new_roi.type()); - new_roi = combian_img(cv::Rect(90, 0, 90, img_height)); - right_image.convertTo(new_roi, new_roi.type()); - QPixmap pixmap = QPixmap::fromImage(Mat2QImage(combian_img)); - QListWidgetItem* temp = new QListWidgetItem(); - temp->setSizeHint(QSize(220, img_height)); - temp->setIcon(QIcon(pixmap)); - temp->setText(imagesList[i]); - ui->listWidget->addItem(temp); - ui->listWidget->setIconSize(QSize(180, img_height)); - dialog->setValue(i+1); - } - delete dialog; - } - else if(ui->double_undistort->isChecked()) - { - for(int i = 0; i < imagesList.length(); i++) - { - QString left_file_name = left_src + "/" + imagesList[i]; - QString right_file_name = right_src + "/" + imagesList[i]; - cv::Mat right_image = cv::imread(right_file_name.toStdString()); - if(right_image.empty()) - { - continue; - } - cv::Mat left_image = cv::imread(left_file_name.toStdString()); - if(left_image.cols != right_image.cols || left_image.rows != right_image.rows) - { - QMessageBox::critical(NULL, "错误", "左右相机图片尺寸不一致", QMessageBox::Yes, QMessageBox::Yes); - return; - } - struct Stereo_Img_t img_; - img_.left_img = left_image; - img_.right_img = right_image; - img_.left_file_name = imagesList[i]; - img_.right_file_name = imagesList[i]; - stereo_imgs.push_back(img_); - img_size = left_image.size(); - int img_height = left_image.rows*90/left_image.cols; - cv::resize(left_image, left_image, cv::Size(90, img_height)); - cv::resize(right_image, right_image, cv::Size(90, img_height)); - cv::Mat combian_img = cv::Mat::zeros(cv::Size(180, img_height), CV_8UC3); - cv::Mat new_roi = combian_img(cv::Rect(0, 0, 90, img_height)); - left_image.convertTo(new_roi, new_roi.type()); - new_roi = combian_img(cv::Rect(90, 0, 90, img_height)); - right_image.convertTo(new_roi, new_roi.type()); - QPixmap pixmap = QPixmap::fromImage(Mat2QImage(combian_img)); - QListWidgetItem* temp = new QListWidgetItem(); - temp->setSizeHint(QSize(220, img_height)); - temp->setIcon(QIcon(pixmap)); - temp->setText(imagesList[i]); - ui->listWidget->addItem(temp); - ui->listWidget->setIconSize(QSize(180, img_height)); - } - } -} - -// 鱼眼相机切换 -void HandEyeCalibration::fisheyeModeSwitch(int state) -{ - fisheye_flag = state==0 ? false : true; - if(fisheye_flag == true) - { - ui->k_2->setDisabled(true); - ui->k_3->setDisabled(true); - ui->tangential->setDisabled(true); - } - else - { - ui->k_2->setEnabled(true); - ui->k_3->setEnabled(true); - ui->tangential->setEnabled(true); - } -} - -// 畸变矫正切换 -void HandEyeCalibration::distortModeSwitch() -{ - if(ui->single_undistort->isChecked() || ui->double_undistort->isChecked()) - { - if(calibrate_flag == false) - { - chooseYaml = new choose_yaml(); - chooseYaml->setWindowTitle("选择相机参数文件"); - QFont font = chooseYaml->font(); - font.setPixelSize(12); - chooseYaml->setFont(font); - chooseYaml->show(); - connect(chooseYaml, SIGNAL(SendSignal(QString)), this, SLOT(receiveYamlPath(QString))); - return; - } - } - static int cnt = 0; - cnt++; - if(cnt%2 == 1) - { - distort_flag = true; - } - else - { - distort_flag = false; - } - int id = ui->listWidget->selectionModel()->selectedIndexes()[0].row(); - chooseImage(ui->listWidget->item(id), ui->listWidget->item(id)); -} - -// Mat格式转QImage格式 -QImage HandEyeCalibration::Mat2QImage(cv::Mat cvImg) -{ - QImage qImg; - if(cvImg.channels()==3) - { - cv::cvtColor(cvImg,cvImg,CV_BGR2RGB); - qImg =QImage((const unsigned char*)(cvImg.data), - cvImg.cols, cvImg.rows, - cvImg.cols*cvImg.channels(), - QImage::Format_RGB888); - } - else if(cvImg.channels()==1) - { - qImg =QImage((const unsigned char*)(cvImg.data), - cvImg.cols,cvImg.rows, - cvImg.cols*cvImg.channels(), - QImage::Format_Indexed8); - } - else - { - qImg =QImage((const unsigned char*)(cvImg.data), - cvImg.cols,cvImg.rows, - cvImg.cols*cvImg.channels(), - QImage::Format_RGB888); - } - return qImg; -} - -// 单目相机图片加载,逻辑同双目相机图片加载 -void HandEyeCalibration::addImage() -{ - HiddenIntro(); - if(ui->double_camera->isChecked() || ui->double_undistort->isChecked()) - { - d = new choose_two_dir(); - d->setWindowTitle("选择图片文件夹"); - QFont font = d->font(); - font.setPixelSize(12); - d->setFont(font); - d->show(); - connect(d, SIGNAL(SendSignal(QString)), this, SLOT(receiveFromDialog(QString))); - return; - } - else if(ui->single_camera->isChecked()) - { - if(chessboard_size == 0) - { - bool ok; - chessboard_size = QInputDialog::getDouble(this,tr("角点间距"),tr("请输入角点间距(mm)"),20,0,1000,2,&ok); - if(!ok) - { - chessboard_size = 0; - return; - } - } - QStringList path_list = QFileDialog::getOpenFileNames(this, tr("选择图片"), tr("./"), tr("图片文件(*.jpg *.png *.pgm);;所有文件(*.*);")); - QProgressDialog *dialog = new QProgressDialog(tr("检测角点..."),tr("取消"),0,path_list.size(),this); - dialog->setWindowModality(Qt::WindowModal); - dialog->setMinimumDuration(0); - dialog->setWindowTitle("请稍候"); - dialog->setValue(0); - QFont font = dialog->font(); - font.setPixelSize(12); - dialog->setFont(font); - dialog->show(); - for(int i = 0; i < path_list.size(); i++) - { - QFileInfo file = QFileInfo(path_list[i]); - QString file_name = file.fileName(); - cv::Mat img = cv::imread(path_list[i].toStdString()); - find_corner_thread_img = img; - thread_done = false; - findcorner_thread->start(); - while(thread_done == false) - { - if(dialog->wasCanceled()) - { - DealThreadDone(); - delete dialog; - return; - } - QCoreApplication::processEvents(); - } - struct Chessboarder_t chess = find_corner_thread_chessboard; - if(chess.chessboard.size() != 1) - { - dialog->setValue(i+1); - continue; - } - struct Img_t img_; - img_.img = img; - img_.file_name = file_name; - std::vector img_p; - std::vector world_p; - for (unsigned int j = 0; j < chess.chessboard.size(); j++) - { - for (int u = 0; u < chess.chessboard[j].rows; u++) - { - for (int v = 0; v < chess.chessboard[j].cols; v++) - { - img_p.push_back(chess.corners.p[chess.chessboard[j].at(u, v)]); - world_p.push_back(cv::Point3f(u*chessboard_size, v*chessboard_size, 0)); - } - } - } - img_.img_points = img_p; - img_.world_points = world_p; - imgs.push_back(img_); - img_size = img.size(); - int img_height = img.rows*124/img.cols; - cv::resize(img, img, cv::Size(124, img_height)); - QPixmap pixmap = QPixmap::fromImage(Mat2QImage(img)); - QListWidgetItem* temp = new QListWidgetItem(); - temp->setSizeHint(QSize(200, img_height)); - temp->setIcon(QIcon(pixmap)); - temp->setText(file_name); - ui->listWidget->addItem(temp); - ui->listWidget->setIconSize(QSize(124, img_height)); - dialog->setValue(i+1); - } - delete dialog; - } - else if(ui->single_undistort->isChecked()) - { - QStringList path_list = QFileDialog::getOpenFileNames(this, tr("选择图片"), tr("./"), tr("图片文件(*.jpg *.png *.pgm);;所有文件(*.*);")); - for(int i = 0; i < path_list.size(); i++) - { - QFileInfo file = QFileInfo(path_list[i]); - QString file_name = file.fileName(); - cv::Mat img = cv::imread(path_list[i].toStdString()); - img_size = img.size(); - Img_t single_img; - single_img.img = img; - single_img.file_name = file_name; - imgs.push_back(single_img); - int img_height = img.rows*124/img.cols; - cv::resize(img, img, cv::Size(124, img_height)); - QPixmap pixmap = QPixmap::fromImage(Mat2QImage(img)); - QListWidgetItem* temp = new QListWidgetItem(); - temp->setSizeHint(QSize(200, img_height)); - temp->setIcon(QIcon(pixmap)); - temp->setText(file_name); - ui->listWidget->addItem(temp); - ui->listWidget->setIconSize(QSize(124, img_height)); - } - } -} - -// 删除选中的图片 -void HandEyeCalibration::deleteImage() -{ - std::vector delete_idx; - foreach(QModelIndex index,ui->listWidget->selectionModel()->selectedIndexes()){ - delete_idx.push_back(index.row()); - } - if(delete_idx.size() == 0) - return; - std::sort(delete_idx.begin(), delete_idx.end()); - disconnect(ui->listWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(chooseImage(QListWidgetItem*, QListWidgetItem*))); - for(int i = delete_idx.size()-1; i >= 0; i--) - { - ui->listWidget->takeItem(delete_idx[i]); - if(ui->double_camera->isChecked()) - stereo_imgs.erase(stereo_imgs.begin()+delete_idx[i]); - else - imgs.erase(imgs.begin()+delete_idx[i]); - } - connect(ui->listWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(chooseImage(QListWidgetItem*, QListWidgetItem*))); - // 如果图片全部删除,则重新显示文字引导 - if(ui->listWidget->count() == 0) - ShowIntro(); -} - -// 相机标定 -void HandEyeCalibration::calibrate() -{ - if(chessboard_size == 0) - { - bool ok; - chessboard_size = QInputDialog::getDouble(this,tr("角点间距"),tr("请输入角点间距(mm)"),20,0,1000,2,&ok); - if(!ok) - { - chessboard_size = 0; - return; - } - } - if(!ui->double_camera->isChecked()) - { - // 单目标定 - if(imgs.size() <= 3) - { - QMessageBox::critical(NULL, "错误", "至少需要四张图片", QMessageBox::Yes, QMessageBox::Yes); - return; - } - std::vector> img_points; - std::vector> world_points; - std::vector tvecsMat; - std::vector rvecsMat; - std::vector > imagePoints; - std::vector > objectPoints; - std::vector rvecs; - std::vector tvecs; - for (unsigned int j = 0; j < imgs.size(); j++) - { - img_points.push_back(imgs[j].img_points); - world_points.push_back(imgs[j].world_points); - std::vector img_p; - std::vector obj_p; - for(unsigned int i = 0; i < imgs[j].img_points.size(); i++) - { - img_p.push_back(imgs[j].img_points[i]); - obj_p.push_back(imgs[j].world_points[i]); - } - imagePoints.push_back(img_p); - objectPoints.push_back(obj_p); - } - cameraMatrix = cv::Mat::zeros(cv::Size(3, 3), CV_32F); - if(fisheye_flag == false) - distCoefficients = cv::Mat::zeros(cv::Size(5, 1), CV_32F); - - if(fisheye_flag == false) - { - int flag = 0; - if(!ui->tangential->isChecked()) - flag |= CV_CALIB_ZERO_TANGENT_DIST; - if(!ui->k_3->isChecked()) - flag |= CV_CALIB_FIX_K3; - cv::calibrateCamera(world_points, img_points, img_size, cameraMatrix, distCoefficients, rvecsMat, tvecsMat, flag); - } - else - { - int flag = 0; - flag |= cv::fisheye::CALIB_RECOMPUTE_EXTRINSIC; - flag |= cv::fisheye::CALIB_FIX_SKEW; - try { - cv::fisheye::calibrate(objectPoints, imagePoints, img_size, K, D, rvecs, tvecs, flag); - } catch (cv::Exception& e) { - QMessageBox::critical(NULL, "错误", "请采集更多方位的图片或者检查角点识别!", QMessageBox::Yes, QMessageBox::Yes); - imgs.clear(); - ui->listWidget->clear(); - return; - } - } - for(unsigned int i = 0; i < imgs.size(); i++) - { - if(fisheye_flag == false) - { - imgs[i].tvec = tvecsMat[i]; - imgs[i].rvec = rvecsMat[i]; - } - else - { - imgs[i].fish_tvec = tvecs[i]; - imgs[i].fish_rvec = rvecs[i]; - } - } - // 评估标定结果 - std::vector error; - for(unsigned int i = 0; i < imgs.size(); i++) - { - std::vector world_p = imgs[i].world_points; - std::vector img_p = imgs[i].img_points, reproject_img_p; - std::vector fisheye_reproject_p; - if(fisheye_flag == false) - projectPoints(world_p, rvecsMat[i], tvecsMat[i], cameraMatrix, distCoefficients, reproject_img_p); - else - cv::fisheye::projectPoints(objectPoints[i], fisheye_reproject_p, rvecs[i], tvecs[i], K, D); - float err = 0; - for (unsigned int j = 0; j < img_p.size(); j++) - { - if(fisheye_flag == false) - err += sqrt((img_p[j].x-reproject_img_p[j].x)*(img_p[j].x-reproject_img_p[j].x)+ - (img_p[j].y-reproject_img_p[j].y)*(img_p[j].y-reproject_img_p[j].y)); - else - err += sqrt((img_p[j].x-fisheye_reproject_p[j].x)*(img_p[j].x-fisheye_reproject_p[j].x)+ - (img_p[j].y-fisheye_reproject_p[j].y)*(img_p[j].y-fisheye_reproject_p[j].y)); - } - error.push_back(err/img_p.size()); - } - int max_idx = max_element(error.begin(), error.end()) - error.begin(); - float max_error = error[max_idx]; - int width = 240 / imgs.size(); - cv::Mat error_plot = cv::Mat(260, 320, CV_32FC3, cv::Scalar(255,255,255)); - cv::rectangle(error_plot, cv::Rect(40, 20, 240, 200), cv::Scalar(0, 0, 0),1, cv::LINE_8,0); - cv::putText(error_plot, "0", cv::Point(20, 220), cv::FONT_HERSHEY_SIMPLEX, 0.3, cv::Scalar(0, 0, 0), 1, 8, 0); - char *chCode; - chCode = new(std::nothrow)char[20]; - sprintf(chCode, "%.2lf", max_error*200/195); - std::string strCode(chCode); - delete []chCode; - cv::putText(error_plot, strCode, cv::Point(10, 20), cv::FONT_HERSHEY_SIMPLEX, 0.3, cv::Scalar(0, 0, 0), 1, 8, 0); - error1 = 0; - for(unsigned int i = 0; i < imgs.size(); i++) - { - error1 += error[i]; - int height = 195*error[i]/max_error; - cv::rectangle(error_plot, cv::Rect(i*width+41, 220-height, width-2, height), cv::Scalar(255,0,0), -1, cv::LINE_8,0); - cv::putText(error_plot, std::to_string(i), cv::Point(i*width+40, 240), cv::FONT_HERSHEY_SIMPLEX, 0.3, cv::Scalar(0, 0, 0), 1, 8, 0); - } - error1 /= imgs.size(); - cv::imshow("error", error_plot); - } - else - { - // 双目标定 - if(stereo_imgs.size() <= 3) - { - QMessageBox::critical(NULL, "错误", "至少需要四组图片", QMessageBox::Yes, QMessageBox::Yes); - return; - } - std::vector> left_img_points; - std::vector> right_img_points; - std::vector> world_points; - std::vector left_tvecsMat; - std::vector left_rvecsMat; - std::vector right_tvecsMat; - std::vector right_rvecsMat; - std::vector > left_imagePoints; - std::vector > right_imagePoints; - std::vector > objectPoints; - std::vector left_rvecs; - std::vector left_tvecs; - std::vector right_rvecs; - std::vector right_tvecs; - for (unsigned int j = 0; j < stereo_imgs.size(); j++) - { - left_img_points.push_back(stereo_imgs[j].left_img_points); - right_img_points.push_back(stereo_imgs[j].right_img_points); - world_points.push_back(stereo_imgs[j].world_points); - std::vector img_p, img_p_; - std::vector obj_p; - for(unsigned int i = 0; i < stereo_imgs[j].left_img_points.size(); i++) - { - img_p.push_back(stereo_imgs[j].left_img_points[i]); - img_p_.push_back(stereo_imgs[j].right_img_points[i]); - obj_p.push_back(stereo_imgs[j].world_points[i]); - } - left_imagePoints.push_back(img_p); - right_imagePoints.push_back(img_p_); - objectPoints.push_back(obj_p); - } - cameraMatrix = cv::Mat::zeros(cv::Size(3, 3), CV_32F); - cameraMatrix2 = cv::Mat::zeros(cv::Size(3, 3), CV_32F); - if(fisheye_flag == false) - { - distCoefficients = cv::Mat::zeros(cv::Size(5, 1), CV_32F); - distCoefficients2 = cv::Mat::zeros(cv::Size(5, 1), CV_32F); - } - // 开始标定 - if(fisheye_flag == false) - { - int flag = 0; - if(!ui->tangential->isChecked()) - flag |= CV_CALIB_ZERO_TANGENT_DIST; - if(!ui->k_3->isChecked()) - flag |= CV_CALIB_FIX_K3; - cv::calibrateCamera(world_points, left_img_points, img_size, cameraMatrix, distCoefficients, left_rvecsMat, left_tvecsMat, flag); - cv::calibrateCamera(world_points, right_img_points, img_size, cameraMatrix2, distCoefficients2, right_rvecsMat, right_tvecsMat, flag); - } - else - { - int flag = 0; - flag |= cv::fisheye::CALIB_RECOMPUTE_EXTRINSIC; - flag |= cv::fisheye::CALIB_FIX_SKEW; - cv::fisheye::calibrate(objectPoints, left_imagePoints, img_size, K, D, left_rvecs, left_tvecs, flag); - cv::fisheye::calibrate(objectPoints, right_imagePoints, img_size, K2, D2, right_rvecs, right_tvecs, flag); - } - for(unsigned int i = 0; i < stereo_imgs.size(); i++) - { - if(fisheye_flag == false) - { - stereo_imgs[i].left_tvec = left_tvecsMat[i]; - stereo_imgs[i].left_rvec = left_rvecsMat[i]; - stereo_imgs[i].right_tvec = right_tvecsMat[i]; - stereo_imgs[i].right_rvec = right_rvecsMat[i]; - } - else - { - stereo_imgs[i].left_fish_tvec = left_tvecs[i]; - stereo_imgs[i].left_fish_rvec = left_rvecs[i]; - stereo_imgs[i].right_fish_tvec = right_tvecs[i]; - stereo_imgs[i].right_fish_rvec = right_rvecs[i]; - } - } - // 评估标定结果 - std::vector left_error, right_error; - for(unsigned int i = 0; i < stereo_imgs.size(); i++) - { - std::vector world_p = stereo_imgs[i].world_points; - std::vector left_img_p = stereo_imgs[i].left_img_points, right_img_p = stereo_imgs[i].right_img_points; - std::vector left_reproject_img_p, right_reproject_img_p; - std::vector left_fisheye_reproject_p, right_fisheye_reproject_p; - if(fisheye_flag == false) - { - projectPoints(world_p, left_rvecsMat[i], left_tvecsMat[i], cameraMatrix, distCoefficients, left_reproject_img_p); - projectPoints(world_p, right_rvecsMat[i], right_tvecsMat[i], cameraMatrix2, distCoefficients2, right_reproject_img_p); - } - else - { - cv::fisheye::projectPoints(objectPoints[i], left_fisheye_reproject_p, left_rvecs[i], left_tvecs[i], K, D); - cv::fisheye::projectPoints(objectPoints[i], right_fisheye_reproject_p, right_rvecs[i], right_tvecs[i], K2, D2); - } - float left_err = 0, right_err = 0; - for (unsigned int j = 0; j < world_p.size(); j++) - { - if(fisheye_flag == false) - { - left_err += sqrt((left_img_p[j].x-left_reproject_img_p[j].x)*(left_img_p[j].x-left_reproject_img_p[j].x)+ - (left_img_p[j].y-left_reproject_img_p[j].y)*(left_img_p[j].y-left_reproject_img_p[j].y)); - right_err += sqrt((right_img_p[j].x-right_reproject_img_p[j].x)*(right_img_p[j].x-right_reproject_img_p[j].x)+ - (right_img_p[j].y-right_reproject_img_p[j].y)*(right_img_p[j].y-right_reproject_img_p[j].y)); - } - else - { - left_err += sqrt((left_img_p[j].x-left_fisheye_reproject_p[j].x)*(left_img_p[j].x-left_fisheye_reproject_p[j].x)+ - (left_img_p[j].y-left_fisheye_reproject_p[j].y)*(left_img_p[j].y-left_fisheye_reproject_p[j].y)); - right_err += sqrt((right_img_p[j].x-right_fisheye_reproject_p[j].x)*(right_img_p[j].x-right_fisheye_reproject_p[j].x)+ - (right_img_p[j].y-right_fisheye_reproject_p[j].y)*(right_img_p[j].y-right_fisheye_reproject_p[j].y)); - } - } - left_error.push_back(left_err/world_p.size()); - right_error.push_back(right_err/world_p.size()); - } - int max_idx = max_element(left_error.begin(), left_error.end()) - left_error.begin(); - float max_error = left_error[max_idx]; - max_idx = max_element(right_error.begin(), right_error.end()) - right_error.begin(); - max_error = std::max(max_error, right_error[max_idx]); - int width = 480 / stereo_imgs.size(); - cv::Mat error_plot = cv::Mat(260, 560, CV_32FC3, cv::Scalar(255,255,255)); - cv::rectangle(error_plot, cv::Rect(40, 20, 480, 200), cv::Scalar(0, 0, 0),1, cv::LINE_8,0); - cv::putText(error_plot, "0", cv::Point(20, 220), cv::FONT_HERSHEY_SIMPLEX, 0.3, cv::Scalar(0, 0, 0), 1, 8, 0); - char *chCode; - chCode = new(std::nothrow)char[20]; - sprintf(chCode, "%.2lf", max_error*200/195); - std::string strCode(chCode); - delete []chCode; - cv::putText(error_plot, strCode, cv::Point(10, 20), cv::FONT_HERSHEY_SIMPLEX, 0.3, cv::Scalar(0, 0, 0), 1, 8, 0); - error1 = 0; error2 = 0; - for(unsigned int i = 0; i < stereo_imgs.size(); i++) - { - error1 += left_error[i]; - error2 += right_error[i]; - int height = 195*left_error[i]/max_error; - cv::rectangle(error_plot, cv::Rect(i*width+43, 220-height, width/2-4, height), cv::Scalar(255,0,0), -1, cv::LINE_8,0); - height = 195*right_error[i]/max_error; - cv::rectangle(error_plot, cv::Rect(i*width+41+width/2, 220-height, width/2-4, height), cv::Scalar(0,255,0), -1, cv::LINE_8,0); - cv::putText(error_plot, std::to_string(i), cv::Point(i*width+40+width/2-2, 240), cv::FONT_HERSHEY_SIMPLEX, 0.3, cv::Scalar(0, 0, 0), 1, 8, 0); - } - error1 /= stereo_imgs.size(); - error2 /= stereo_imgs.size(); - - if(fisheye_flag == false) - { - int flag = 0; - if(!ui->tangential->isChecked()) - flag |= CV_CALIB_ZERO_TANGENT_DIST; - if(!ui->k_3->isChecked()) - flag |= CV_CALIB_FIX_K3; - flag |= CV_CALIB_USE_INTRINSIC_GUESS; - cv::stereoCalibrate(world_points, left_img_points, right_img_points, - cameraMatrix, distCoefficients, - cameraMatrix2, distCoefficients2, - img_size, R, T, cv::noArray(), cv::noArray(), flag); - cv::stereoRectify(cameraMatrix, distCoefficients, - cameraMatrix2, distCoefficients2, - img_size, R, T, - R1, R2, P1, P2, Q,0,0, img_size); - } - else - { - int flag = 0; - flag |= cv::fisheye::CALIB_FIX_SKEW; - flag |= cv::fisheye::CALIB_USE_INTRINSIC_GUESS; - try { - cv::fisheye::stereoCalibrate(objectPoints, left_imagePoints, right_imagePoints, - K, D, - K2, D2, - img_size, R, T, flag); - } catch (cv::Exception& e) { - QMessageBox::critical(NULL, "错误", "请采集更多方位的图片!", QMessageBox::Yes, QMessageBox::Yes); - stereo_imgs.clear(); - ui->listWidget->clear(); - return; - } - - cv::fisheye::stereoRectify(K, D, - K2, D2, - img_size, R, T, - R1, R2, P1, P2, Q, 0, img_size); - } - D = D2; - cv::imshow("error", error_plot); - } - - // hand-eye calibration - std::vector cTo; - cv::Mat cRo_i, cPo_i; - cv::Mat cTc = cv::Mat::zeros(3, 3, CV_64F); - cTc.at(0, 1) = +1; - cTc.at(1, 0) = +1; - cTc.at(2, 2) = -1; - if (!ui->double_camera->isChecked()) // - { - for(unsigned int i = 0; i < imgs.size(); i++) - { - if(fisheye_flag == false) - { - cPo_i = imgs[i].tvec; - cv::Rodrigues(imgs[i].rvec, cRo_i); - } - else - { - cPo_i = imgs[i].fish_tvec; - cv::Rodrigues(imgs[i].fish_rvec, cRo_i); - } - - cv::Mat cTo_i = cv::Mat::zeros(cv::Size(4, 4), CV_64F); - for (int r = 0; r < 3; ++ r) { - for (int c = 0; c < 3; ++ c) { - cTo_i.at(r, c) = cRo_i.at(r, c); - } - cTo_i.at(r, 3) = cPo_i.at(r, 0) / 1000.0; - } - cTo_i.at(3, 3) = 1; - cTo.push_back(cTo_i); - } - } else { - for(unsigned int i = 0; i < stereo_imgs.size(); i++) - { - if(fisheye_flag == false) - { - cPo_i = stereo_imgs[i].left_tvec; - cv::Rodrigues(stereo_imgs[i].left_rvec, cRo_i); - } - else - { - cPo_i = stereo_imgs[i].left_fish_tvec; - cv::Rodrigues(stereo_imgs[i].left_fish_rvec, cRo_i); - } - - cv::Mat cTo_i = cv::Mat::zeros(cv::Size(4, 4), CV_64F); - for (int r = 0; r < 3; ++ r) { - for (int c = 0; c < 3; ++ c) { - cTo_i.at(r, c) = cRo_i.at(r, c); - } - cTo_i.at(r, 3) = cPo_i.at(r, 0) / 1000.0; - } - cTo_i.at(3, 3) = 1; - cTo.push_back(cTo_i); - } - } - - TSAIleastSquareCalibration::runCalibration(robot_poses, cTo, ui->k_eye_in_hand->isChecked(), extrinsicMatrix); - - calibrate_flag = true; -} - -// 导出标定参数 -void HandEyeCalibration::exportParam() -{ - if(calibrate_flag == false) - { - QMessageBox::critical(NULL, "错误", "请先标定相机", QMessageBox::Yes); - return; - } - QString strSaveName = QFileDialog::getSaveFileName(this,tr("保存的文件"),tr("calibration_param.yaml"),tr("yaml files(*.yaml)")); - if(strSaveName.isNull() || strSaveName.isEmpty() || strSaveName.length() == 0) - return; - cv::FileStorage fs_write(strSaveName.toStdString(), cv::FileStorage::WRITE); - if(ui->double_camera->isChecked()) - { - if(fisheye_flag == false) - { - fs_write << "left_camera_Matrix" << cameraMatrix << "left_camera_distCoeffs" << distCoefficients; - fs_write << "right_camera_Matrix" << cameraMatrix2 << "right_camera_distCoeffs" << distCoefficients2; - fs_write << "Rotate_Matrix" << R << "Translate_Matrix" << T; - fs_write << "R1" << R1 << "R2" << R2 << "P1" << P1 << "P2" << P2 << "Q" << Q; - } - else - { - cv::Mat camera_matrix = cv::Mat::zeros(cv::Size(3,3), CV_64F); - camera_matrix.at(0,0) = K(0,0); - camera_matrix.at(0,2) = K(0,2); - camera_matrix.at(1,1) = K(1,1); - camera_matrix.at(1,2) = K(1,2); - camera_matrix.at(2,2) = 1; - cv::Mat Dist = (cv::Mat_(1,4) << D[0], D[1], D[2], D[3]); - fs_write << "left_camera_Matrix" << camera_matrix << "left_camera_distCoeffs" << Dist; - camera_matrix.at(0,0) = K2(0,0); - camera_matrix.at(0,2) = K2(0,2); - camera_matrix.at(1,1) = K2(1,1); - camera_matrix.at(1,2) = K2(1,2); - Dist = (cv::Mat_(1,4) << D2[0], D2[1], D2[2], D2[3]); - fs_write << "right_camera_Matrix" << camera_matrix << "right_camera_distCoeffs" << Dist; - fs_write << "Rotate_Matrix" << R << "Translate_Matrix" << T; - fs_write << "R1" << R1 << "R2" << R2 << "P1" << P1 << "P2" << P2 << "Q" << Q; - } - fs_write << "left_average_reprojection_err" << error1 << "right_average_reprojection_err" << error2; - } - else - { - if(fisheye_flag == false) - fs_write << "cameraMatrix" << cameraMatrix << "distCoeffs" << distCoefficients; - else - { - cv::Mat camera_matrix = cv::Mat::zeros(cv::Size(3,3), CV_64F); - camera_matrix.at(0,0) = K(0,0); - camera_matrix.at(0,2) = K(0,2); - camera_matrix.at(1,1) = K(1,1); - camera_matrix.at(1,2) = K(1,2); - camera_matrix.at(2,2) = 1; - cv::Mat Dist = (cv::Mat_(1,4) << D[0], D[1], D[2], D[3]); - fs_write << "cameraMatrix" << camera_matrix << "disCoeffs" << Dist; - } - fs_write << "average_reprojection_err" << error1; - } - - fs_write << "extrinsicMatrix" << extrinsicMatrix; - - fs_write.release(); -} - - -// 单目相机图片加载,逻辑同双目相机图片加载 -void HandEyeCalibration::addRobot() -{ - robot_poses.clear(); - QStringList path_list = QFileDialog::getOpenFileNames(this, tr("选择文件"), tr("./"), tr("文本文件(*.txt);;所有文件(*.*);")); - for(int i = 0; i < path_list.size(); i++) - { - cv::Mat robot_pose = cv::Mat::zeros(cv::Size(4, 4), CV_64F); - QFileInfo file = QFileInfo(path_list[i]); - QString file_name = file.fileName(); - std::ifstream in(path_list[i].toStdString()); - if (in.is_open()) - { - in >> robot_pose.at(0, 0) >> robot_pose.at(0, 1) >> robot_pose.at(0, 2) >> robot_pose.at(0, 3) \ - >> robot_pose.at(1, 0) >> robot_pose.at(1, 1) >> robot_pose.at(1, 2) >> robot_pose.at(1, 3) \ - >> robot_pose.at(2, 0) >> robot_pose.at(2, 1) >> robot_pose.at(2, 2) >> robot_pose.at(2, 3) \ - >> robot_pose.at(3, 0) >> robot_pose.at(3, 1) >> robot_pose.at(3, 2) >> robot_pose.at(3, 3); - } - in.close(); - robot_poses.push_back(robot_pose); - } -} diff --git a/hand_eye_calibration/HandEyeCalibration.h b/hand_eye_calibration/HandEyeCalibration.h deleted file mode 100644 index 0d538aa..0000000 --- a/hand_eye_calibration/HandEyeCalibration.h +++ /dev/null @@ -1,103 +0,0 @@ -#ifndef HandEyeCalibration_H -#define HandEyeCalibration_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "../camera_calibration/CameraCalibration.h" -#include "../camera_calibration/findCorner.h" -#include "../camera_calibration/chessboard.h" -#include "../camera_calibration/choose_two_dir.h" -#ifdef Q_OS_LINUX -#include "../camera_calibration/single_capture_linux.h" -#elif defined(Q_OS_WIN32) -#include "../camera_calibration/single_capture.h" -#endif - -#ifdef Q_OS_LINUX -#include "../camera_calibration/double_capture_linux.h" -#elif defined(Q_OS_WIN32) -#include "../camera_calibration/double_capture.h" -#endif -#include "../camera_calibration/choose_yaml.h" - -QT_BEGIN_NAMESPACE -namespace Ui { class HandEyeCalibration; } -QT_END_NAMESPACE - -extern cv::Mat find_corner_thread_img; -extern struct Chessboarder_t find_corner_thread_chessboard; - -class HandEyeCalibration : public QMainWindow -{ - Q_OBJECT - -public: - HandEyeCalibration(QWidget *parent = nullptr); - ~HandEyeCalibration(); - -private slots: - void addRobot(); - void addImage(); - void deleteImage(); - void calibrate(); - void fisheyeModeSwitch(int state); - void exportParam(); - void distortModeSwitch(); - void receiveFromDialog(QString str); - void receiveYamlPath(QString str); - void chooseImage(QListWidgetItem* item, QListWidgetItem*); - void reset(); - void undistortReset(); - void saveUndistort(); - void OpenCamera(); - void DealThreadDone(); - -private: - Ui::HandEyeCalibration *ui; - choose_two_dir *d; - //choose_robot_dir *d_robot; - choose_yaml *chooseYaml; -#ifdef Q_OS_LINUX - single_capture_linux *single_c; - double_capture_linux *double_c; -#elif defined(Q_OS_WIN32) - single_capture *single_c; - double_capture *double_c; -#endif - QImage Mat2QImage(cv::Mat cvImg); - void ShowIntro(); - void HiddenIntro(); - QAction *saveImage; - std::vector robot_poses; - std::vector imgs; - std::vector stereo_imgs; - double chessboard_size; - cv::Mat extrinsicMatrix; - cv::Mat cameraMatrix; - cv::Mat distCoefficients; - cv::Matx33d K; - cv::Vec4d D; - cv::Mat cameraMatrix2; - cv::Mat distCoefficients2; - double error1, error2; - cv::Matx33d K2; - cv::Vec4d D2; - cv::Size img_size; - bool fisheye_flag = false; - bool calibrate_flag = false; - bool distort_flag = false; - cv::Mat R, T, R1, R2, P1, P2, Q; - FindcornerThread *findcorner_thread; - bool thread_done = false; -}; -#endif // HandEyeCalibration_H diff --git a/hand_eye_calibration/HandEyeCalibration.ui b/hand_eye_calibration/HandEyeCalibration.ui deleted file mode 100644 index 6391193..0000000 --- a/hand_eye_calibration/HandEyeCalibration.ui +++ /dev/null @@ -1,633 +0,0 @@ - - - HandEyeCalibration - - - - 0 - 0 - 1087 - 635 - - - - 相机标定 - - - - :/icon/camera.png:/icon/camera.png - - - - - - 0 - 0 - 221 - 80 - - - - - 微软雅黑 - - - - FILE - - - Qt::AlignCenter - - - - - 10 - 15 - 60 - 40 - - - - PointingHandCursor - - - - res/AddImage.png - :/icon/AddImage.pngres/AddImage.png - - - - 50 - 50 - - - - - - - 80 - 15 - 60 - 60 - - - - PointingHandCursor - - - - - - - :/icon/delete.png - :/icon/delete.png:/icon/delete.png - - - - 35 - 35 - - - - - - - 10 - 50 - 60 - 25 - - - - - 8 - - - - PointingHandCursor - - - 添加 - - - - - - 150 - 15 - 60 - 40 - - - - PointingHandCursor - - - - :/icon/robot.png - :/icon/robot.png:/icon/robot.png - - - - - - 150 - 50 - 60 - 25 - - - - - 8 - - - - PointingHandCursor - - - 添加 - - - - - - - 230 - 0 - 361 - 80 - - - - - 微软雅黑 - - - - OPTIONS - - - Qt::AlignCenter - - - - - 10 - 20 - 100 - 16 - - - - 2 Coefficients - - - - - true - - - - 10 - 50 - 100 - 16 - - - - 3 Coefficients - - - false - - - - - - 130 - 20 - 100 - 16 - - - - Tangential - - - true - - - - - - 130 - 50 - 101 - 16 - - - - Fisheye - - - - - - 240 - 20 - 121 - 21 - - - - eye-in-hand - - - - - true - - - - 240 - 50 - 121 - 21 - - - - eye-to-hand - - - true - - - - - - - 620 - 0 - 80 - 80 - - - - - 微软雅黑 - - - - CALIBRATE - - - Qt::AlignCenter - - - - - 10 - 15 - 60 - 60 - - - - PointingHandCursor - - - - - - - :/icon/play.png - :/icon/play.png:/icon/play.png - - - - 48 - 48 - - - - - - - - 700 - 0 - 80 - 80 - - - - - 微软雅黑 - - - - EXPORT - - - Qt::AlignCenter - - - - - 10 - 15 - 60 - 60 - - - - PointingHandCursor - - - - - - - :/icon/yes.png - res/yes.png:/icon/yes.png - - - - 48 - 48 - - - - - - - - 0 - 80 - 250 - 520 - - - - - 130 - 100 - - - - - 500000 - 500000 - - - - true - - - - - 0 - 0 - 248 - 518 - - - - - 130 - 150 - - - - - 248 - 16777215 - - - - - - 0 - 0 - 248 - 518 - - - - - 248 - 518 - - - - - 248 - 16777215 - - - - - - - 0 - 0 - 248 - 100 - - - - - 微软雅黑 - - - - - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - - 260 - 80 - 811 - 520 - - - - - 微软雅黑 - - - - Image - - - - - 90 - 15 - 651 - 495 - - - - - - - Qt::AlignCenter - - - - - - - 780 - 0 - 80 - 80 - - - - - 微软雅黑 - - - - UNDISTORT - - - Qt::AlignCenter - - - - - 10 - 15 - 60 - 60 - - - - PointingHandCursor - - - - - - - :/icon/undistort.png - :/icon/undistort.png:/icon/undistort.png - - - - 40 - 40 - - - - - - - - 860 - 0 - 211 - 80 - - - - - 微软雅黑 - - - - MODE - - - Qt::AlignCenter - - - - - 10 - 20 - 85 - 16 - - - - 单目标定 - - - true - - - - - - 10 - 50 - 85 - 16 - - - - 双目标定 - - - - - - 110 - 50 - 89 - 16 - - - - 双目纠正 - - - - - - 110 - 20 - 89 - 16 - - - - 单目纠正 - - - - - - - - 0 - 0 - 1087 - 26 - - - - - 菜单 - - - - - - - - - - diff --git a/hand_eye_calibration/README.md b/hand_eye_calibration/README.md deleted file mode 100644 index fea3819..0000000 --- a/hand_eye_calibration/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# hand_eye_calibration -机器人和相机的手眼机标定程序 - -基于Qt和OpenCV开发,标定算法来源"A New Technique for Fully Autonomous and Efficient 3D Robotics Hand-Eye Calibration, Tsai, R.Y. and Lenz, R.K" - -## 功能列表: -* 支持手眼在眼标定(Eye-in-Hand) -* 支持手眼到眼标定(Eye-to-Hand) - -## 文件结构 -|文件名/文件夹名|功能| -|:--|:--| -|HandEyeCalibration.cpp|程序主界面和主要逻辑的实现| -|TSAIleastSquareCalibration.cpp棋盘角点检测算法的实现| -|res|图标资源文件夹| - -## 软件运行截图: - -![screenshot](screenshot.jpg) - -## 说明 -* 暂时只支持导入标定,需要导入图片和机器人位姿文件 -* 先导入照片,然后选择与列表中对应编号的机器人位姿文件(有些图片会检测角点失败,不在列表中) diff --git a/hand_eye_calibration/TSAIleastSquareCalibration.cpp b/hand_eye_calibration/TSAIleastSquareCalibration.cpp deleted file mode 100644 index 31c8237..0000000 --- a/hand_eye_calibration/TSAIleastSquareCalibration.cpp +++ /dev/null @@ -1,114 +0,0 @@ -#include "TSAIleastSquareCalibration.h" - -TSAIleastSquareCalibration::TSAIleastSquareCalibration() -{ - -} - -void TSAIleastSquareCalibration::crossprod(cv::Mat a, cv::Mat &ax) -{ - ax = cv::Mat::zeros(cv::Size(3, 3), CV_64F); - if (a.rows == 3 && a.cols == 1) { - ax.at(0, 1) = -a.at(2, 0); - ax.at(0, 2) = a.at(1, 0); - - ax.at(1, 0) = a.at(2, 0); - ax.at(1, 2) = -a.at(0, 0); - - ax.at(2, 0) = -a.at(1, 0); - ax.at(2, 1) = a.at(0, 0); - } -} - -void TSAIleastSquareCalibration::runCalibration( - std::vector eHa, - std::vector cHb, - int mode, // 1 for eye in hand, 0 for eye to hand - cv::Mat &extrinsicMatrix) -{ - cv::Mat invM, rgij, rcij, rngij, rncij, Pgij, Pcij, Pskew, invA; - std::vector Hgij; - std::vector Hcij; - int n = cHb.size(); - cv::Mat A = cv::Mat::zeros(cv::Size(3, 3 * (n - 1)), CV_64F); - cv::Mat b = cv::Mat::zeros(cv::Size(1, 3 * (n - 1)), CV_64F); - // we have n-1 linearly independent relations between the views - for (int i = 1; i < n; ++ i) { - // Transformations between views - if (mode == 1) { - invert(eHa[i], invM); - Hgij.push_back(invM * eHa[i-1]); - } else { - invert(eHa[i-1], invM); - Hgij.push_back(eHa[i] * invM); - } - invert(cHb[i-1], invM); - Hcij.push_back(cHb[i] * invM); - // turn it into angle axis representation (rodrigues formula: P is - // the eigenvector of the rotation matrix with eigenvalue 1 - cv::Rodrigues(Hgij[i-1](cv::Range(0, 3), cv::Range(0, 3)), rgij); - cv::Rodrigues(Hcij[i-1](cv::Range(0, 3), cv::Range(0, 3)), rcij); - double theta_gij = norm(rgij); - double theta_cij = norm(rcij); - rngij = rgij / theta_gij; - rncij = rcij / theta_cij; - // Tsai uses a modified version of this - Pgij = 2 * sin(theta_gij / 2) * rngij; - Pcij = 2 * sin(theta_cij / 2) * rncij; - // Now we know that - // skew(Pgij+Pcij)*x = Pcij-Pgij which is equivalent to Ax = b - // So we need to construct vector b and matrix A to solve this - // overdetermined system. (Note we need >=3 Views to have at least 2 - // linearly independent inter-view relations. - crossprod(Pgij + Pcij, Pskew); - for (int r = 0; r < 3; ++ r) { - for (int c = 0; c < 3; ++ c) { - A.at(3 * i - 3 + r, c) = Pskew.at(r, c); - } - b.at(3 * i - 3 + r, 0) = Pcij.at(r, 0) - Pgij.at(r, 0); - } - } - - // Computing Rotation - invert(A, invA, cv::DECOMP_SVD); - cv::Mat Pcg_prime = invA * b; - // Computing residus - cv::Mat err = A * Pcg_prime - b; - double residus_TSAI_rotation = sqrt(norm(err) / (n - 1)); - cv::Mat Pcg = 2 * Pcg_prime / (sqrt(1 + norm(Pcg_prime) * norm(Pcg_prime))); - double norm_Pcg = norm(Pcg); - crossprod(Pcg, Pskew); - cv::Mat Rcg = (1 - norm_Pcg * norm_Pcg / 2) * cv::Mat::eye(3, 3, CV_64F) + 0.5 * (Pcg * Pcg.t() + sqrt(4 - norm_Pcg * norm_Pcg) * Pskew); - - // Computing Translation - cv::Mat I3 = cv::Mat::eye(3, 3, CV_64F); - for (int i = 1; i < n; ++ i) - { - for (int r = 0; r < 3; ++ r) { - for (int c = 0; c < 3; ++ c) { - A.at(3 * i - 3 + r, c) = Hgij[i-1].at(r, c) - I3.at(r, c); - } - Pcij.at(r, 0) = Hcij[i-1].at(r, 3); - Pgij.at(r, 0) = Hgij[i-1].at(r, 3); - } - cv::Mat Pp = Rcg * Pcij - Pgij; - for (int r = 0; r < 3; ++ r) { - b.at(3 * i - 3 + r, 0) = Pp.at(r, 0); - } - } - invert(A, invA, cv::DECOMP_SVD); - cv::Mat Tcg = invA * b; - // Computing residus - err = A * Tcg - b; - double residus_TSAI_translation = sqrt(norm(err)/(n-1)); - // Estimated transformation - - extrinsicMatrix = cv::Mat::zeros(cv::Size(4, 4), CV_64F); - for (int r = 0; r < 3; ++ r) { - for (int c = 0; c < 3; ++ c) { - extrinsicMatrix.at(r, c) = Rcg.at(r, c); - } - extrinsicMatrix.at(r, 3) = Tcg.at(r, 0); - } - extrinsicMatrix.at(3, 3) = 1; -} diff --git a/hand_eye_calibration/TSAIleastSquareCalibration.h b/hand_eye_calibration/TSAIleastSquareCalibration.h deleted file mode 100644 index 7902faa..0000000 --- a/hand_eye_calibration/TSAIleastSquareCalibration.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef TSAILEASTSQUARECALIBRATION_H -#define TSAILEASTSQUARECALIBRATION_H - -#include -#include "../camera_calibration/CameraCalibration.h" - -QT_BEGIN_NAMESPACE -namespace Ui { class TSAIleastSquareCalibration; } -QT_END_NAMESPACE - -class TSAIleastSquareCalibration -{ -public: - TSAIleastSquareCalibration(); - static void runCalibration(std::vector Hg, std::vector Hc, int mode, cv::Mat &extrinsicMatrix); - static void crossprod(cv::Mat a, cv::Mat &ax); -}; - -#endif // TSAILEASTSQUARECALIBRATION_H diff --git a/hand_eye_calibration/res/AddImage.png b/hand_eye_calibration/res/AddImage.png deleted file mode 100644 index 9e103928e1dc1410b787b44e339bad219abc6c80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 952 zcmV;p14sOcP)GD4}u^<=|vAp15wdKjkQuV1cYKS4Qfk~o?0|2 z6j~|;L8(R1lSeQ50}5U$NQ)xV}J4H|Hl{hFD5p7)uVoi(b}Y8e0? z!2jJQ3WY*h-64VOX5&Igk8$a^o^05ztTk-8Rcpv!+jY6|dq;l%xg`m+$&_l|EG;c* z`g}egSx(qT+h8sSgdk~`65g-8(Z18?1|qSVpjNAO)Z6Y8h)%##U88=oClb`_^^OGX zz9zW96CGi{=tNK|m9()^sYHUt$HyapQ;#1-{KKO~V4Q=;1z>%m1VV_|if!9&TX72< zYaH{FjSJuB$Fwk0uIt7K9LH(Pc?(=f5%Y7jHGwH6jHw91g^@`F^rBMX1K6j&M*NRH zomba8Ctsry5hoz_n}vclK7Cg83mFu2HfNUxY2JTGkqiO*rVete7Wb`~QP7t(X*r)uC(AAfQf9s;n z!nni~kugHRrCz;AAn{R!>ZlYsed|zOxdd4w7I;F>UQzEtKJP1a3xj2OR52n5KzY$VEP>d9KQt@6=l$7srOK`T0O&* zd14RaiDWNCzy#d5GN?hT3(LBLE>4{Vn^BSL2!!z1^W3z-5@|3DD^NgoKRu*^4%>58 zdIJI;nIH7rB%t(-J^2zFCv*4v0|g|S!^p!AiIZ3jI~Q{vCU7-@gvH`VC=OkYytH|2 z6ZY9QCd~*4XxMRBd%kAG2o#{lVdljmG)I!_F+C>en4Fu}!;Ze0p}7I5J1!R4(8G`j z5*5%7dZIMa)lZ)v1ZAU0Efc~1CYaJvqywNAwe0CBst5<+6pWt zYg}D=LXZ3R?t{T#44DPMm~bRh>pi2jwUyLpG_y#Ol<9Q3Bq|r}X>V`;I)lN;N;z@_ zJ0o|RrD3L=j-{p|^=K-!fc&cs^RDBOWC;7RvZq8%)6WqsdW6wI^A@~(~qO>A1VZfhZv=5uW!bggC}rX z-D!CLq=O>`5co&|)Ya7?G-M4Hh&&BFKrImM^(w^0$00T@?$HLI)cvij47Ih!bHK;P z2QLH$8uiK}*#naqqSFqb)oP(ut7%PcZf?{(n~c5*ZKnm`YEcmi3a+5M{5ri-Jovcr z-5`~&#D)zU5Ex`U941sdhQX8qD3wYaPCA65q9TNbhC(cnKq8SqED=+?Ipj5R$mMd> z%H=34E5mDBwjlX~WZ2o+nWLTdnKA%{LV;C5!FbZi2}wyZgoTHXD{6EnrR;OR%!RkN zw^S(Hf`F3PflhSeDm}2P+3`tqHhZ6)5=S$ zLl6)UfOYHF(>5B72Cr;*1@*UX;pMe!F=x)4N#&)CFX!iDWMm|qK>;wC%(~@kCO<9} zykh8HRzQ zi!+u;mrfes`@%8|4-fwgaPZu5*vxvupc8E zz<$g)x!?66{nC%9sg*+@5TK;E1g}O!;I;jc@DEyHa&DcbAE}Y=S_W`3;RtrVxgFbg z?4Xy+C1MHwxY`jrRy&L<+D* zcH(OBRn$~hV^#1flpgcIoF|wj0Spck2;Q$kNofiE{QVIW7zDomA_Rwrn&dwv098fT z(4qA39=I+us$78ST$ zF8r5xBh>dEHV4f!sRhq}+l#^$y$%hWpiM_qNV9V@7_5r%2^o!5dR5jd%&*I{jU(+`9@q%rQjeFjhS%5B8 z2h#V-aJ9G?zJ9)FsISLL=}IhJ?1Tdw?cnNYZ6rw1dKbwTG#Jv`pt-e;{w?#1OzcZY zK>SBCxO$8a{$Q{%d%)+pXN@WWrZh);qYAp8`q6&7!(bj48;5gev*Ey*1+FavR25h= zK;WmoK}1JKBjv**v@La@6E5vf*^>xkTq+-<2^O$K>2z3 z7`!`(oSYo^`uWlRy&4TvDixZVn&|J;ZtdaWfqC=(JIS-Nvk@N~2NxF?tXpqhDd)`T zGtlex;ZstT7(L_f{?l&AlIV)xm`_JX2V@5mQCL`rwDfdrePipS8*J2J$}Tjc0jcX` zR#p~js%sz?iy;z=AQDkGG6K}4M^USwKZAmU5fc-Gr2&&aLO)1r1mNJ{Fr0lh+h)~^ zFPhyVqmJd}<@9Ak*`zdzx7Lx|7kH$_E9dG3GvZHobB zA^_Ff(NWPoU0q$g(dx8JC=|y2`O{Mzkw`QX8qCh|&z>HHhlT&Uv9WPae}Dhyh6<{a zlhfBM7Axer=byKcNK8A{nytc2$F{aMl$MkZ4Gj&|_Vn~Dr@|QhvtVy;zuDH-HpIrp zX0e$$rqzuk$%g*^{uZ51CmT&P@n0j;D#`NTj{z)iL(3vO?g7i%&vNho07mLlldEQA Q@c;k-07*qoM6N<$f;Xq*ivR!s diff --git a/hand_eye_calibration/res/capture.png b/hand_eye_calibration/res/capture.png deleted file mode 100644 index bc69cc0082603cba82ed1fe32520835a4a644d63..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 795 zcmV+$1LXXPP)KTA!Zju0H)g<0MnbY-Xx^zOSpQySi$OSuSI<96tkrzAc!tz#(8gumM=o0`?K` z7&v7la}wCpJh7plfTzGS;8NS%T?AeMt1>~o1I9CTo!VPqMdp~-!0MQ88-eS_cyg_mexkw+PAeXB-`2s9XS>ShoG__NWnph{E7`TBzWs?jR z7-C?WE@gHh|AZ_#VYdN#mH{gp@Na+*z`f?NaO%Vpa}Rey>L=C{_A0P1f$kcx-@u>~ zPfh<0O`XD6<=<5%0geM_Qiy}^CxElYF;moQ5Lnmb?BV3f9G^5VH#YfHsH3~a^XZVN zLEt=aBs2X8-RuHzv~L1oUcVE#-2!R?xaEB-+2#J3x`qXQ0&~1s7Xsf}2rK};df$El zbNjZycVM0eVm|Pr1(fup??igiFP0h>xDRac5GZ(c@ra48sk$6Fc>ruJMh{i$LfQ(C z6$bISW(zLnP{RUB8}3FBD41V1@7Dyu>(sXwq!!hKSQF-?5FtlvN)feE4GXx5|C?08 zRm=p;T5-!sDXb|(C!UxE{EAsg#Z9TjUO;6LS9#MsD$7h3qz;{UhD&|%{+B<{d?Opm zD*20>r8!uj=2?-o*BibkH3Q$X* ztmJNM>5)bIV`7!sVH68HfVDx_#y9V%dSvRxya4WmeX&#w7~H>q)szMLN0>k`5a`=P Ze*m4tk}=>w!5#nr002ovPDHLkV1macV|)Mr diff --git a/hand_eye_calibration/res/delete.png b/hand_eye_calibration/res/delete.png deleted file mode 100644 index 238b4d0429735426b1351676c4372b67d62abf1b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1818 zcmV+#2j%#QP)xxEA)!iZ70+U&tyIw}qP2($D&B~W794EVBK4>^)>|1F1?vH| zC}=BE)LQGb1*eQKR>T8AN3n{NJ;J1r1n6im+0HuEx)#DO>(ZJ2{`}>=?|YwpzhmD9 zga7%!UNrx1-MXc=qrtiP9kbi*?fJWve<1*+Qke??(_iEvU>LT{YPF_`)esHf?d{!N zE|=ev$z(6(7aSKAKPqwp@As?CW*aU_002Y-s8p&rj^nZ;Crp5>>?~37$3~2W@{1RF z-@!JU?UERg7XdiMaoqUaBY6-W9xf&}oIG&?;^)QlzFTcJ+X^uv?E)y3%AOd8`9CX; z<9K(c)A@}`rGl?>a>T@a?9=k71)?WS1`NYY05I!$A%#M5qpq&b>@I*?0JU0O3;D2nny@oe`P93BRXJ{rAMFa^zqtU#K<9Jha(i|8# zJOo8ow9UEvGw}0~Q)d{48HXZ*1ONasf*>AEOqdD5@4t(pE86DF*T)^Dr-}+`nx2Xx zf(ifxL2w@~mppw+g)7+%!+atwfL_-)DmK!4 z*hJy0^U|?%A#qE}(?s~BGFEJcv@-{v#&2zo2d}BT`Plc_k)4{}aQFJH-84;qE-8TC z-`^4z9pO77M!2`62FOg_+(6aTZe&?@yQBdA27@Vd!YJLS*bfD|QUmNZfxGo)TZsne1iE~jf8MNz*<4N%fAG&pop zqOf(98esM01ipcJ+iW&pkQN}oaJEm#!0?zQaUE8HM~)Uqja>p9#|5!0+hz~&Yy!x8 zz~AWq-5bIEqoyqvUXW5&pxNF8o8prBuM|d_rteEy0lmLI_q9Pm(`TkElBNQ=g-~-% zc#+kcEs&P*Ip4(lnoOp8NdZWb+!r{Y_kuZV7Izo`bI$}B%Qk{qt@f&}u69TYKoCTF zkG`)b&0F_zhXK^ysDb^f(_9S0$WW5!ZIJhXR;%4&=+$HS!cCvzk{qM0ETLN!Rd8ri zW+P3}-6RJfNivmq)qi!uwiLnB5xt3X3xR*hOL*pGuJZgP$WK4Wm`oD~>J0X%<<79ab8?}vY3S$34<09vhfZdbK(e@fP70l?Npt6*}%EI~ta zJo6AFf4Sjl4*XI%SMVdmr~Y$-@eK`opMp6n6QA1s@KwH}^!p;dp%=DzZfKB~5J{5L zyt;HgygFxxU=y>o6lje)L5SAoS{yCVNzuvegDu#7sJWuJtdL=tnUYt4B*`ebqEr6b zyq(BrG-?u0?DDqkwO9Xk^$5c-!s}j&3J?SlA(P2YulshF;2!WaNq6>HYtwD_s%{;i zX?i)TF7gu6YPDe)h82HtJQG#hW4p}cO;)Premcvt$p0N9186jw9ypHQS)8`&Uu)ju zOj~iV*8Kt7KU%}TN{?V!_8h7vGC*Kppw~mk!@EPq3?s(HMWgB?+ZFD~g=;0{_03I9 z0Tzp;5!DeHfEOf5GM3}GypdBrfVaX!p{rVn8po~O_0kGBy5j)f2gK7fo$ZOJM*zIA zR;x`=cqvw5GMOfzPoPWR)9C*HCpQsnmWD^SFITzj^)>~^aZ^~9J?2UBysgIoe?$0T z7&aLIh5~@z)1Xi6IgYdAI9}#-I!~L;=1MU-q5#AM>L{PT0QS|pUZ?jZ4FCWD07*qo IM6N<$f(uw|`~Uy| diff --git a/hand_eye_calibration/res/hand_eye_image.qrc b/hand_eye_calibration/res/hand_eye_image.qrc deleted file mode 100644 index 4218634..0000000 --- a/hand_eye_calibration/res/hand_eye_image.qrc +++ /dev/null @@ -1,13 +0,0 @@ - - - AddImage.png - play.png - yes.png - delete.png - undistort.png - capture.png - camera.png - ../../icon.ico - robot.png - - diff --git a/hand_eye_calibration/res/play.png b/hand_eye_calibration/res/play.png deleted file mode 100644 index ed78fcf73354ed10948ead255f97a76ed97ab05f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1097 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 KMNUMnLSTaTASZbM diff --git a/hand_eye_calibration/res/undistort.png b/hand_eye_calibration/res/undistort.png deleted file mode 100644 index ee31e2de73db57ee96d3008d8ddc26e181b375fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2320 zcmV+r3GeoaP)=O{Nc>-+cciFNTa<`ra)=gl`7O0Y8I?E`rgX zO4hZX+cNoz7a2Jk&j3cXbdIh`iTMO90N`hknJ`0UCCW)s3=#Rnl z5`ZoS7A8!+%S~uC>jA^6C*VZ@^&o!AZVmssoV>CCG$Xnaptb67j)+QDi%E2_MQdwGabRCFVNZ`TvM!lmy{|x{}H+O~wiB2%L+ihVL zaVmPWMb=HY8B>}0HUizopi4gHzyC}2e&{vK_E)GiM|Xf|zGT^OwyhzWzg_@9g-U-? zbf~OpkGL`ms~da0SU+k>$I#j#!{&l%27@0j&BK3FiAze}+ux9~dRBMTXcvaQp|X3b zRyO8`*a83n!}=Kks~MQ$HQH!K>{$l?kbpb9fX&e`^FcwpH*Plk#tUCIIvrzRb;8tt zc|#)0X4v-ue$6(+tK5W+U25Ht7FZ9$V|lbOm6ZWLCTo13B$LVRn`(yDK=w>b6WvLW z69KUhVC^~&J#U+#7B_urT{EI<0eU=@th+ClvHRYDW>`xBtndR<+>+Gj6&;PTjPmi#N*)Ngkekd4iXkomWE zTlo8KJf+sP71o#0OS?ckD?=k&Y_s8c$6X?pdc&|=byevWJepyhB0*2bH--*4Zy;zy zUywju!VJ$VXG|axn?=m8XJ}}@ZH68lsPXxHS|s*YFnyFVya#x-vqzHj#HLWJDgZ|G0Rie) z8qgJhhvwwgT(O;JJMuKx0vu`SW2FD|7g7>VwZOp>(b=wTvXXWXoRrWCQZymKt1P(`D8;2zClc+YCGIT5?k())Gi8CC!Ll0%NY-7T#ZuVYKpDqDe{r)m@*jn|gUL zcD@=JT66Rf5O#Y8P>r%mc!SMWQ=0f(|JdJO#&rN5Tu3oz4xA)E=!Htl2Co3JeWo`Z zPZc$H#W&vjvAYX-ImC`{;`DW4{0a%NTcvWZ#(AO3mh)>Atsl_JZB*YO_7X+|&DK^T z{IrM>{{VQO+kx|9yrKz4V!vbNCsY%`Eid(jQMW`kZt>9(y+i8G=Zql2MP{guT;Efy zUh!&ljxMk)79zn`=088@wkdsyOf_X8aP(@rcX+Wot7gR30$g~pCt6P|hjXT`k4c}H zcXmVl2bCI~Pp=BftUO4cm|RYtYE6F0S91{()3U{!b()!%C)&cV78udQJ}nr_-HT6w z=meOjD;hyGvAZ>t$qiLLPKeHx6@lr*OVTKF*yihPd z#SkA8vx*XR1bRJgh90icc86UvVt)Ym5yyFmzL{tZy-`kXz=&=m&}G}yTQUG5)J@#^h;3tV z_D(=HAYb&FffAScO4}+~-|ZKnGC&A?ib4N`k3PDZ>`m6z z){dbx-%TtVK{N$GsPAnt_W2}QKHaRJaC?Jg+bW8AAdM|`y^~B$L{!m%X5a*Yce};= z94ObumLhaoiz#swz@=N`C#O`>PIr2$f||fI6TnwW?WT%eAl{$6c6s-?EsdVWNvhrc zMR0=Z2i=nL%V{NBiy7Z`rZES+r1MfnPFQ6PzuJR)NZ=j@Zf5XjMD$te+U0k>8eI_g z4XZ;Pb^>PipV|~{uji&RUc;K3?^d&ZeMV4xT2p$!k)6)`FJ}a~*+97|P5BX1c1q*S z3PxO6~nRfbiH4`W|ol&H&L%zIe%@HPA>9y@XP!LYEVOCR_>7}MJ`LbOB zC}JnVKqHEv6{QJXBToil&VP# qA*Xx!+y+t{iEpYu;wIC}`o94c>v2PkpC-Tn00002qj diff --git a/hand_eye_calibration/res/yes.png b/hand_eye_calibration/res/yes.png deleted file mode 100644 index 29d085962e5f6924ac009f89d418345a5fea2f1f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 786 zcmV+t1MU2YP)j5D`H{1QAmaL_`n~K}1UP zcGFn2pcjwcym|8vc=7DngEo>K*_J?El6||~KiR_OIy>{e-+VKhmk`$Yht{}$6aa&E zuyCMoAZ7=O5sKNs*y*`+pyJ5kw$)q+Lzg=M)0Qn}oP|rL7N?x|I*42Wn96E0xVkKw zev!dv{#eZqK@hnCs3>U@VEreS@Y7&p`1geUu^&V(04hq(5-{0YB>iv=9{p=IKlO?a z9H3&$8Dg9Zo^|@hY>c%h)o1^60|lrwsxOJ?ylzq1#dLeBSawQu-TX`ege{+faak)qVCDz5#G+As0-`HP zijOoxLe+LNaj9K%zUoBy<3h>D0Iurtg5EZh0MHTN9YqTv?CJwB-O!2;`L>BZ zLi3|TVzK-Nz=@v80Zy0=>pm0TOj~?^2kLc0?EF3tU?fmP4+zt??lJSNEW`(nkoT*I zTzC+run?w_cNw^ym3ZwA2vs``xE_SiK@j6L19!3%uLbZP5NiA+*q8@lolcxC9`<^m ztFtm5>M9``@;GQ|oHj%$zVg{L#D{XAE5e+W$3S?+giTQ)R`U%T_KZ*ttbjPm23i0% zhdm&z&Uep`1mGo^s&<&kZGo{>2a#syR}J7L4yo}&E*mWnw)KO^LVTnh@CzMkj30E# zm; z9_jbjhi=*9#A7=U=kO$Q3xFiL7^gJ@KC>)5>y(Dt~0V^>WK#8mD QegFUf07*qoM6N<$f+K2TSpWb4 diff --git a/hand_eye_calibration/screenshot.jpg b/hand_eye_calibration/screenshot.jpg deleted file mode 100644 index e71a101597b58f9ed7b18819318c1dde789ba4de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118141 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%0DqMHE;qERzMdUt z2x1ayUpgCJswO|Yp2Q?IZ`ELo|LMz7sgDY&LFMO=iHRW@h3B$SkOS^p<2WF*8gCsv zSxTIN5)74m;k=BOWsa4z;iJW6XAe&&`AT2yg4#tD`3`ODeqF?&Sh&+-+v%iY2sH=@ zSzBw+Xa5o{S%bWG{EXAvKRxdU`=Cnx_7;%XHqXJt6E*!gBF*%E`9=oKcvZL;bH(S8 zk$dm@Fy@_O!AYtc|i>C8-)wRZcwJo<6O$ z<0YB5(<}Z8;(AUC82&R=h%p#LSmY~!_>;({u((>`?m4R{oxg>UFCuNZP-9mruwxUb zY{)!}`HtjjP^|YiCDbLfq}M%&n3R!ac(LN*p2A&g`iiKtG$WL*f;N!WD-qu@lPT7i z07eS(Yk02djPlV7nHapz<>sm0HL_J2p+l*#_xRic)LLt~Vv;HrB7C>uCVR`#g?F?@ zXkcrtcFvZ*)f`>zxkub_CfksjaL4LM>NWG)JIV|Obr73fsR>84qGkE4-p}Zw?`6|o z9kt+!7ooF6Rmj~jhU&>@vPWO&9%tN2@zeKH-)zqysc-p`q;hY3pt%1&5_FBuXD*9rc~gtXL%w3MMq*Fb-=78_6Ud80d|lT$GyUz_5QhdMIYvEx+Vu2_bY1hYa-z~ zsJe}4rRuI?#VO^oHR-jYo7+F?c9EdgBYPXQM zp|B+cOeUEMTQ-a?n=Z=|QFLU9I-<;EL6;eAqQ-^2BpPD&c%NrpLI}`HS879Uo2Td8 zbDwkW=lR}q&v|Yc<_Y1On#u^jDrQW}Fi$ZIV-QSeVwwcQcKYy-3i(%7KvqCjKvqCj zU?VD^P;dj*yplPggha9QoWgs^B}H!q%Y~~vFT&Nk^9f^hFYN_hZ~0vsKR@R`+?O!y zsAJoaKF}FcA4uV6S#8+U+OcN-E5F`>-uKSJ?m!62YQ2zb$tEio>McSWoU_!UasbTspx(_0{?v>M!<2cYBKaTxga78%xE{s35uwt|{Vj{2EmmHJ<#@C$xl zn09o)GsBaTOjqP2CjamloC^Q7s_p4NNxs-e>iFnq@^k>po2R7mr|s*5XTO|}Xd0`7 zG=FFIPvSlIKIe`=I@I+zh&1U z;t)0O92uu$N5v7SaE?R0ZwB1W-$8lxDA@8^h4mvHf)^(#hR~%c^3CWPloUg(`Ev&+ z==#1l-X@+c&8>7?={gThEx#>m7zaLA`-VoTPyPNN@ac9ax)_caL#+8NI|GpC^b&`E z_%v~CYnWJ;dvQGIqvlP}|Me{O&8c}FvO;5V=QowrgZK4bV#szlAv?D%BD)wzuA?35 z+7D9ShSz$*+}sm)ew8)_x<@Y%gPJo#Y1N2ie)P@EucW>>hBnI)SN?*tkJgV9)3@Q+ z)f+T->YA=g=0{(A4vt^H1vm!Q3qQKOY7DWQ|M_Qrpljm(bo8qks^dB%&W}hX`Xckd zH$T$;hV_CEe~KGFuE{FiOndCe&-WooX`nSS61R9B*CC6onC1`e&3s1ws_b~ZSn7}C zJzcUL>bru(kYTTYJjY?l`s4mk-Ljwhx`j2=+|U+x{xn|?6#ln<`a=CQA6(w4h%ulK z&X>Ts%fz2s;|1%X_v6lQKY5KfdQN{r9GUrLbS!26a_;S>KA0~V3#y>_@}0Qz;~dHg zegHa)jaV>;C~s_`7;r8v%^$3`J#=ozt0D5odGNXlyIiT(WIkY7&F;nh5~ZG$ zq}7P~$4aG!%a~OtH0DHMmN{evWCdgeWCb?90ze;zdED}El int main(int argc, char *argv[]) { QApplication a(argc, argv); - MainWindow w; + CameraCalibration w; QFont font = w.font(); font.setPixelSize(12); w.setFont(font); diff --git a/mainwindow.cpp b/mainwindow.cpp deleted file mode 100644 index cefd3d0..0000000 --- a/mainwindow.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include "mainwindow.h" -#include "ui_mainwindow.h" - -MainWindow::MainWindow(QWidget *parent) : - QMainWindow(parent), - ui(new Ui::MainWindow) -{ - ui->setupUi(this); - setFixedSize(this->width(), this->height()); - ui->CameraCalib->setFlat(true); - connect(ui->CameraCalib, SIGNAL(clicked()), this, SLOT(startCameraCalib())); - ui->HandEyeCalib->setFlat(true); - connect(ui->HandEyeCalib, SIGNAL(clicked()), this, SLOT(startHandEyeCalib())); - about = ui->menu->addAction("关于"); - QFont font = about->font(); - font.setPixelSize(12); - about->setFont(font); - connect(about, SIGNAL(triggered()), this, SLOT(showIntro())); - -} - -MainWindow::~MainWindow() -{ - delete ui; -} - -void MainWindow::showIntro() -{ - a = new AboutUs(); - QFont font = a->font(); - font.setPixelSize(12); - a->setFont(font); - a->setWindowTitle("关于TStoneCalibration"); - a->show(); -} - -void MainWindow::startCameraCalib() -{ - camera_calibration = new CameraCalibration(); - QFont font = camera_calibration->font(); - font.setPixelSize(12); - camera_calibration->setFont(font); - camera_calibration->setWindowTitle("相机标定"); - camera_calibration->show(); -} - -void MainWindow::startHandEyeCalib() -{ - hand_eye_calibration = new HandEyeCalibration(); - QFont font = hand_eye_calibration->font(); - font.setPixelSize(12); - hand_eye_calibration->setFont(font); - hand_eye_calibration->setWindowTitle("手眼标定"); - hand_eye_calibration->show(); -} diff --git a/mainwindow.h b/mainwindow.h deleted file mode 100644 index 40650a2..0000000 --- a/mainwindow.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include -#include "camera_calibration/CameraCalibration.h" -#include "hand_eye_calibration/HandEyeCalibration.h" -#include "AboutUs.h" - -namespace Ui { -class MainWindow; -} - -class MainWindow : public QMainWindow -{ - Q_OBJECT - -public: - explicit MainWindow(QWidget *parent = nullptr); - ~MainWindow(); - -private slots: - void startCameraCalib(); - void startHandEyeCalib(); - void showIntro(); - -private: - Ui::MainWindow *ui; - CameraCalibration* camera_calibration; - HandEyeCalibration* hand_eye_calibration; - QAction *about; - AboutUs *a; -}; - -#endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui deleted file mode 100644 index eca84a3..0000000 --- a/mainwindow.ui +++ /dev/null @@ -1,137 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 320 - 240 - - - - ArrowCursor - - - MainWindow - - - - - - 10 - 10 - 80 - 50 - - - - PointingHandCursor - - - - - - - :/icon/camera.png:/icon/camera.png - - - - 48 - 48 - - - - - - - 10 - 60 - 80 - 20 - - - - - 微软雅黑 - - - - 相机标定 - - - Qt::AlignCenter - - - - - - 120 - 10 - 80 - 50 - - - - PointingHandCursor - - - - - - - :/icon/camera.png:/icon/camera.png - - - - 48 - 48 - - - - - - - 120 - 60 - 80 - 20 - - - - - 微软雅黑 - - - - 手眼标定 - - - Qt::AlignCenter - - - - - - - 0 - 0 - 320 - 26 - - - - - 帮助 - - - - - - - - - - - diff --git a/camera_calibration/res/AddImage.png b/res/AddImage.png similarity index 100% rename from camera_calibration/res/AddImage.png rename to res/AddImage.png diff --git a/camera_calibration/res/camera.png b/res/camera.png similarity index 100% rename from camera_calibration/res/camera.png rename to res/camera.png diff --git a/camera_calibration/res/capture.png b/res/capture.png similarity index 100% rename from camera_calibration/res/capture.png rename to res/capture.png diff --git a/camera_calibration/res/delete.png b/res/delete.png similarity index 100% rename from camera_calibration/res/delete.png rename to res/delete.png diff --git a/camera_calibration/res/image.qrc b/res/image.qrc similarity index 88% rename from camera_calibration/res/image.qrc rename to res/image.qrc index 7b58f52..67f4df1 100644 --- a/camera_calibration/res/image.qrc +++ b/res/image.qrc @@ -7,6 +7,5 @@ undistort.png capture.png camera.png - ../../icon.ico diff --git a/camera_calibration/res/play.png b/res/play.png similarity index 100% rename from camera_calibration/res/play.png rename to res/play.png diff --git a/camera_calibration/res/undistort.png b/res/undistort.png similarity index 100% rename from camera_calibration/res/undistort.png rename to res/undistort.png diff --git a/camera_calibration/res/yes.png b/res/yes.png similarity index 100% rename from camera_calibration/res/yes.png rename to res/yes.png diff --git a/types.h b/types.h new file mode 100644 index 0000000..dd1427e --- /dev/null +++ b/types.h @@ -0,0 +1,40 @@ +#ifndef TYPES_H +#define TYPES_H + +#include +#include + +struct Img_t +{ + std::string file_path; + QString file_name; + std::vector img_points; + std::vector world_points; + cv::Mat tvec; + cv::Mat rvec; + cv::Vec3d fish_tvec; + cv::Vec3d fish_rvec; +}; + +struct Stereo_Img_t +{ + std::string left_path; + QString left_file_name; + std::vector left_img_points; + cv::Mat left_tvec; + cv::Mat left_rvec; + cv::Vec3d left_fish_tvec; + cv::Vec3d left_fish_rvec; + + std::string right_path; + QString right_file_name; + std::vector right_img_points; + cv::Mat right_tvec; + cv::Mat right_rvec; + cv::Vec3d right_fish_tvec; + cv::Vec3d right_fish_rvec; + + std::vector world_points; +}; + +#endif \ No newline at end of file