C#棋盘格标定工具实现(界面winForm,算法Opencv)
在项目实施中,需要用到棋盘格标定,获得实际坐标,由于现场情况和棋盘格不统一,造成标定点不统一,进而造成标定文件错误,进而影响计算的情况,本文实现一种简易的棋盘格标定工具,便于该项目调试人员根据现场情况灵活标定。本文分为四个部分,基于C++,OpenCV的算法设计,基于C#,Winform的软件界面设计,C#对C++ Dll文件的调用,以及日志Lib的实现。
首先打开Visual Studio 新建项目解决方案,命名为CalibrateTools,默认项目作为上位机软件界面面目,然后添加C++空项目CCV,作为标定算法
一、标定算法开发
在CCV项目中添加类命名为CalibrateCV,标定算法的相关的头文件和算法文件如下。Opencv版本为3.3:
1 #include"CommonHead.h" 2 using namespace cv; 3 using namespace std; 4 class CalibrateCV 5 { 6 public: 7 CalibrateCV(); 8 virtual ~CalibrateCV(); 9 protected: 10 Mat CAMERAM;//相机内参 11 Mat DISTC;//畸变信息 12 Mat WARPM;//仿射坐标转换,用于将图像坐标转换为世界坐标。 13 int XNum;//棋盘格列数 14 int YNum;//棋盘格行数 15 Size BoardSize;//棋盘格尺寸 16 Mat SRC,BACKMAT; 17 int W,H; 18 19 Point2f P1, P2, P3,P4;//标定点机器人坐标 20 21 int I1R, I2R, I3R,I4R;//标定点行列数 22 int I1C, I2C, I3C, I4C; 23 24 int L1, L2, L3, L4;//标定点索引 25 public: 26 int SetBoardData(char* data); 27 int SetBoardSize(char* data); 28 int SetMatrix(int cam); 29 int SetCalibratePoint(char* data); 30 int DoCalibrate(int cam); 31 void Read_Matrix(int index_camera, Mat &CameraM, Mat &distc, Mat &WarpM); 32 int Calibration(Mat src, int index_camera, Mat &back); 33 int SetCam(char* data, int height, int width); 34 int GetBackMat(char* data); 35 };
1 #pragma once 2 #include "opencv2/opencv.hpp" 3 #include"opencv2/highgui/highgui.hpp" 4 #include<direct.h> 5 #include <sstream> 6 #include<map> 7 #include<omp.h> 8 #include<time.h> 9 #include<string> 10 #include <iostream> 11 #include<fstream> 12 #include<io.h> 13 #include<stdio.h> 14 #include<stdlib.h> 15 #include<cmath>
1 // stdafx.h : include file for standard system include files, 2 // or project specific include files that are used frequently, but 3 // are changed infrequently 4 // 5 6 #pragma once 7 #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers 8 // Windows Header Files: 9 #include <windows.h> 10 #include <opencv2/opencv.hpp> 11 // TODO: reference additional headers your program requires here
1 #include "CalibrateCV.h" 2 3 CalibrateCV::CalibrateCV() {} 4 CalibrateCV::~CalibrateCV() {} 5 /***************1、设置函数,设置源图像,长宽**************/ 6 int CalibrateCV::SetCam(char* data, int height, int width) 7 { 8 Mat temp(height, width, CV_8UC3, (void*)data); 9 SRC = temp; 10 return 0; 11 } 12 int CalibrateCV::GetBackMat(char* data) 13 { 14 int w = BACKMAT.cols * BACKMAT.channels(); 15 if (false) 16 { 17 w += 3; 18 w = (w >> 2) << 2; 19 } 20 int c = BACKMAT.cols * BACKMAT.channels(); 21 for (int i = 0; i < BACKMAT.rows; i++) 22 { 23 memcpy(data + i * w, BACKMAT.data + i * c, c); 24 } 25 return 0; 26 } 27 int CalibrateCV::SetBoardData(char* data) 28 { 29 int* idata = (int*)(data); 30 XNum = idata[0]; 31 YNum = idata[1]; 32 33 I1R = idata[2]; 34 I1C= idata[3]; 35 I2R = idata[4]; 36 I2C = idata[5]; 37 I3R = idata[6]; 38 I3C = idata[7]; 39 I4R = idata[8]; 40 I4C = idata[9]; 41 42 //点数组索引=(行-1)*36+(列-1) 43 L1 = (I1R - 1) * XNum + (I1C - 1); 44 L2 = (I2R - 1) * XNum + (I2C - 1); 45 L3 = (I3R - 1) * XNum + (I3C - 1); 46 L4 = (I4R - 1) * XNum + (I4C - 1); 47 48 return 0; 49 } 50 int CalibrateCV::SetBoardSize(char* data) 51 { 52 float* idata = (float*)(data); 53 BoardSize = Size(idata[0], idata[1]); 54 return 0; 55 } 56 int CalibrateCV::SetMatrix(int cam) 57 { 58 CAMERAM = Mat(3, 3, CV_32FC1, Scalar::all(0)); 59 DISTC = Mat(1, 5, CV_32FC1, Scalar::all(0)); 60 WARPM = Mat(3, 3, CV_32FC1, Scalar::all(0)); 61 Read_Matrix(cam, CAMERAM, DISTC, WARPM); 62 return 0; 63 } 64 int CalibrateCV::SetCalibratePoint(char* data) 65 { 66 67 float* idata = (float*)(data); 68 P1.x = idata[0]; 69 P1.y = idata[1]; 70 71 P2.x = idata[2]; 72 P2.y = idata[3]; 73 74 P3.x = idata[4]; 75 P3.y = idata[5]; 76 77 P4.x = idata[6]; 78 P4.y = idata[7]; 79 80 81 82 return 0; 83 } 84 void CalibrateCV::Read_Matrix(int index_camera, Mat &CameraM, Mat &distc, Mat &WarpM) 85 { 86 vector<float> m1; 87 vector<string> strs; 88 ifstream inf; 89 inf.open("Calibrat" + std::to_string(index_camera) + ".txt", ios_base::in); 90 if (!inf) 91 return; 92 //("打开相机参数文件失败"); 93 //std::cout << "error" << endl; 94 string str; 95 //int times = 0; 96 while (getline(inf, str)) 97 { 98 size_t s = str.find("#"); 99 if (s != str.npos) 100 continue; 101 //times = 1; 102 size_t s1 = str.find("["); 103 size_t s2 = str.find(";"); 104 size_t s3 = str.find("]"); 105 if (s1 != str.npos) 106 { 107 if (s2 != str.npos)//有[ 并且有 ; 108 { 109 str = str.substr(s1 + 1, s2 - 1); 110 size_t s = str.find(","); 111 for (; s != str.npos;) 112 { 113 strs.push_back(str.substr(0, s)); 114 str = str.substr(s + 1); 115 s = str.find(","); 116 } 117 strs.push_back(str); 118 } 119 else 120 { 121 if (s3 != str.npos) 122 { 123 str = str.substr(s1 + 1, s3 - 1); 124 size_t s = str.find(","); 125 for (; s != str.npos;) 126 { 127 strs.push_back(str.substr(0, s)); 128 str = str.substr(s + 1); 129 s = str.find(","); 130 } 131 strs.push_back(str); 132 } 133 } 134 135 } 136 else 137 { 138 if (s2 != str.npos)//没有[ 有 ; 139 { 140 str = str.substr(0, s2 - 1); 141 size_t s = str.find(","); 142 for (; s != str.npos;) 143 { 144 strs.push_back(str.substr(0, s)); 145 str = str.substr(s + 1); 146 s = str.find(","); 147 } 148 strs.push_back(str); 149 } 150 else 151 { 152 if (s3 != str.npos)//没有[ 有 ] 153 { 154 str = str.substr(0, s3); 155 size_t s = str.find(","); 156 for (; s != str.npos;) 157 { 158 strs.push_back(str.substr(0, s)); 159 str = str.substr(s + 1); 160 s = str.find(","); 161 } 162 strs.push_back(str); 163 } 164 } 165 } 166 } 167 168 for (int i = 0; i < strs.size(); i++) 169 { 170 istringstream iss(strs[i]); 171 float t; 172 iss >> t; 173 m1.push_back(t); 174 } 175 176 //获取相机内参 177 if (m1.size() == 0) 178 return; 179 int index = 0; 180 for (int i = 0; i < CameraM.cols; i++) 181 { 182 for (int j = 0; j < CameraM.rows; j++) 183 { 184 CameraM.at<float>(i, j) = m1[index]; 185 index++; 186 } 187 } 188 189 //获取相机畸变参数 190 for (int i = 0; i < distc.cols; i++) 191 { 192 for (int j = 0; j < distc.rows; j++) 193 { 194 distc.at<float>(j, i) = m1[index]; 195 index++; 196 } 197 } 198 //获取透视变换 199 for (int i = 0; i < WarpM.cols; i++) 200 { 201 for (int j = 0; j < WarpM.rows; j++) 202 { 203 WarpM.at<float>(i, j) = m1[index]; 204 index++; 205 } 206 } 207 inf.close(); 208 } 209 int CalibrateCV::Calibration(Mat src, int index_camera, Mat &back) 210 { 211 Mat color_src, gray; 212 213 if (src.channels() > 1) 214 { 215 color_src = src.clone(); 216 cvtColor(src, gray, CV_RGB2GRAY); 217 } 218 else 219 { 220 gray = src.clone(); 221 cvtColor(src, color_src, CV_GRAY2RGB); 222 } 223 vector<Point2f> corners;//保存检测到的角点坐标信息,像素坐标 224 Size board_size = Size(XNum, YNum); 225 bool patter = findChessboardCorners(gray, board_size, corners, CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_NORMALIZE_IMAGE);//CV_CALIB_CB_ADAPTIVE_THRESH 226 if (!patter) 227 { 228 back = color_src.clone(); 229 return -1; 230 } 231 //对像素坐标进行亚精度操作,提高精度 232 cornerSubPix(gray, corners, Size(11, 11), Size(-1, -1), TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1)); 233 // 把找到的角点描绘出来 234 drawChessboardCorners(color_src, board_size, corners, patter); 235 236 //单个棋盘格大小 mm 237 Size square_size = Size(5, 5); 238 //保存各角点的物理坐标 世界坐标系 239 //std::vector<cv::Point3f> object_points; 240 //创建 3*3 全零的 float 矩阵,用来保存相机内参 241 Mat cameraMatrix = Mat(3, 3, CV_32FC1, Scalar::all(0)); 242 //创建 1行5列的全零 float 矩阵,用来保存畸变参数 243 Mat distcoeffs = Mat(1, 5, CV_32FC1, Scalar::all(0)); 244 // 创建图像旋转向量 245 vector<Mat> tvecsMat; 246 //创建图像平移向量 247 vector<Mat> rvecsMat; 248 //保存各角点的物理坐标 世界坐标系 249 vector<Point3f> realPoints; 250 251 //按照从左到右,从上到下的顺序排列角点坐标 252 for (int i = 0; i < board_size.height; i++) 253 { 254 for (int j = 0; j < board_size.width; j++) 255 { 256 Point3f tempPoint; 257 tempPoint.x = i * square_size.width; 258 tempPoint.y = j * square_size.height; 259 tempPoint.z = 0; 260 realPoints.push_back(tempPoint); 261 } 262 } 263 264 vector<vector<Point2f>> image_points; 265 image_points.push_back(corners); 266 vector<vector<Point3f>> object_points; 267 object_points.push_back(realPoints); 268 269 double eps = calibrateCamera(object_points, image_points, src.size(), cameraMatrix, distcoeffs, rvecsMat, 270 tvecsMat, CV_CALIB_FIX_K3); 271 Mat dst1; 272 undistort(src, dst1, cameraMatrix, distcoeffs); 273 vector<Point2f> corner; 274 bool patters = findChessboardCorners(dst1, board_size, corner, CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_NORMALIZE_IMAGE); 275 if (!patters) 276 { 277 cvtColor(dst1, dst1, COLOR_GRAY2BGR); 278 back = dst1.clone(); 279 return -2; 280 } 281 282 //点数组索引=(行-1)*36+(列-1) 283 vector<cv::Point2f> image_point; 284 285 image_point.push_back(corner[L1]); 286 image_point.push_back(corner[L2]); 287 image_point.push_back(corner[L3]); 288 image_point.push_back(corner[L4]); 289 290 vector<Point2f>CalibratePoints; 291 CalibratePoints.push_back(P1); 292 CalibratePoints.push_back(P2); 293 CalibratePoints.push_back(P3); 294 CalibratePoints.push_back(P4); 295 Mat M = cv::getPerspectiveTransform(image_point, CalibratePoints);//透视变换矩阵 296 ofstream fout; 297 fout.open("calibrat" + std::to_string(index_camera) + ".txt"); 298 fout << "#cameraMatrix:\n" << cameraMatrix << endl; 299 fout << "#distcoeffs:\n" << distcoeffs << endl; 300 fout << "#M\n" << M << endl; 301 fout << endl; 302 fout.close(); 303 back = color_src.clone(); 304 return 0; 305 } 306 int CalibrateCV::DoCalibrate(int cam) 307 { 308 int rtn = Calibration(SRC, cam, BACKMAT); 309 return rtn; 310 }
生成dll文件,添加类CCV
1 #pragma once 2 #ifdef CCV_EXPORTS 3 #define CCV_API __declspec(dllexport) 4 #else 5 #define CCV_API __declspec(dllimport) 6 #endif 7 EXTERN_C CCV_API int SetCam(char* data, int height, int width); 8 EXTERN_C CCV_API int SetBoardData(char* data); 9 EXTERN_C CCV_API int SetCalibratePoint(char* data); 10 EXTERN_C CCV_API int GetImage(char* data); 11 EXTERN_C CCV_API int DoCalibrate(int cam);
1 #include "stdafx.h" 2 #include "CCV.h" 3 #include"CalibrateCV.h" 4 5 //摆串 6 CalibrateCV CalMV; 7 CCV_API int SetCam(char* data, int height, int width) 8 { 9 return CalMV.SetCam(data, height, width); 10 } 11 CCV_API int SetBoardData(char* data) 12 { 13 return CalMV.SetBoardData(data); 14 } 15 CCV_API int GetImage(char* data) 16 { 17 return CalMV.GetBackMat(data); 18 } 19 CCV_API int DoCalibrate(int cam) 20 { 21 return CalMV.DoCalibrate(cam); 22 } 23 CCV_API int SetCalibratePoint(char* data) 24 { 25 return CalMV.SetCalibratePoint(data); 26 }
添加导出cpp文件
1 #include "stdafx.h" 2 BOOL APIENTRY DllMain(HMODULE hModule, 3 DWORD ul_reason_for_call, 4 LPVOID lpReserved 5 ) 6 { 7 switch (ul_reason_for_call) 8 { 9 case DLL_PROCESS_ATTACH: 10 case DLL_THREAD_ATTACH: 11 case DLL_THREAD_DETACH: 12 case DLL_PROCESS_DETACH: 13 break; 14 } 15 return TRUE; 16 }
二、CCV项目配置,修改配置类型为动态库。然后测试生成
三、软件界面设计
软件界面具体如下:
四、C#调用C++算法文件设计
1 public class Detection 2 { 3 #region sys dll 4 [DllImport("kernel32.dll")] 5 static private extern IntPtr LoadLibrary(string lpFileName); 6 [DllImport("kernel32.dll")] 7 static private extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName); 8 [DllImport("kernel32.dll")] 9 static private extern int GetLastError(); 10 [DllImport("kernel32", EntryPoint = "FreeLibrary", SetLastError = true)] 11 static private extern bool FreeLibrary(IntPtr hModule); 12 static private Delegate GetFunctionAddress(IntPtr dllModule, string functionName, Type t) 13 { 14 IntPtr address = GetProcAddress(dllModule, functionName); 15 if (address == IntPtr.Zero) 16 return null; 17 else 18 return Marshal.GetDelegateForFunctionPointer(address, t); 19 } 20 #endregion sys dll 21 22 #region 算法导出函数 23 public static IntPtr hSdkDll; 24 25 #region 标定 26 27 [DllImport("CCV.dll", EntryPoint = "SetCalibratePoint", CallingConvention = CallingConvention.StdCall)] 28 public static extern int SetCalibratePoint(float[] points, int cam); 29 [DllImport("CCV.dll", EntryPoint = "DoCalibrate", CallingConvention = CallingConvention.StdCall)] 30 public static extern int DoCalibrate(int cam); 31 [DllImport("CCV.dll", EntryPoint = "SetCam", CallingConvention = CallingConvention.StdCall)] 32 public static extern int SetCam(byte[] srcValues, int h, int w,int cam); 33 [DllImport("CCV.dll", EntryPoint = "GetImage", CallingConvention = CallingConvention.StdCall)] 34 public static extern int GetImage(byte[] dstValues,int cam); 35 [DllImport("CCV.dll", EntryPoint = "SetBoardData", CallingConvention = CallingConvention.StdCall)] 36 public static extern int SetBoardData(int[] values); 37 38 39 #endregion 40 #endregion 41 42 #region 初始化与释放 43 public static bool Initial() 44 { 45 try 46 { 47 hSdkDll = LoadLibrary("CCV.dll"); 48 int a = GetLastError(); 49 if (hSdkDll == IntPtr.Zero) 50 { 51 MyLogHelper.Instance.Error("算法加载异常:" + a.ToString()); 52 return false; 53 } 54 else 55 { 56 return true; 57 } 58 } 59 catch (Exception ex) 60 { 61 MyLogHelper.Instance.Error("算法初始化失败:"+ex.ToString()); 62 return false; 63 } 64 } 65 public static void Uninitial() 66 { 67 if (hSdkDll != IntPtr.Zero) 68 FreeLibrary(hSdkDll); 69 hSdkDll = IntPtr.Zero; 70 } 71 #endregion 72 }
五、解决方案,项目目录
整个项目代码:链接: https://pan.baidu.com/s/1x8SAg92Rpfht0kvVQKorqQ 提取码: rwt2