news 2026/7/4 4:24:14

C++小技巧汇总(更新)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++小技巧汇总(更新)

容器处理 vector, map等

1. 容器转换

0. "在有序容器中给cv::Point排序" struct ComparePoints { bool operator()(const cv::Point& lhs, const cv::Point& rhs) const { if (lhs.x < rhs.x) { return true; } else if (lhs.x == rhs.x && lhs.y < rhs.y) { return true; } else { return false; } } }; 1. "vector<cv::Point> 转换到 set<cv::Point, ComparePoints>" std::set<cv::Point, ComparePoints> pointSet(points.begin(), points.end()); 2. "vector<cv::Point> 和 vector<Point2f> 的快速互相转化" - vector<Point2f> --> vector<cv::Point> (四舍五入) vector<Point2f> vd{ { 1.1, 2.2 }, { 3.3, 4.4 }, {5.5, 6.6} }; vector<Point> v(vd.begin(), vd.end()); - vector<cv::Point> --> vector<Point2f> vector<Point> vP{ { 1, 2 }, { 3, 4 }, {5, 6} }; vector<Point2f> v(vP.begin(), vP.end()); 3. "[vector<cv::Point>系列] --> [cv::Mat]" | vector<T> | MatType | Mat.cols | |:-------------------:|:--------:|:--------:| | vector<cv::Point> | CV_32SC1 | 2 | | vector<cv::Point3i> | CV_32SC1 | 3 | | vector<cv::Point2f> | CV_32FC1 | 2 | | vector<cv::Point3f> | CV_32FC1 | 3 | | vector<cv::Point2d> | CV_64FC1 | 2 | | vector<cv::Point3d> | CV_64FC1 | 3 | template <typename T> cv::Mat transVecPt2Mat(const vector<T>& ptlist, bool clone=true) { if (ptlist.empty()) return Mat(); int col = 0; int type = 0; if (std::is_same_v<T, cv::Point>){ col = 2; type = CV_32SC1; }else if (std::is_same_v<T, cv::Point3i>){ col = 3; type = CV_32SC1; }else if (std::is_same_v<T, cv::Point2f>){ col = 2; type = CV_32FC1; }else if (std::is_same_v<T, cv::Point3f>){ col = 3; type = CV_32FC1; }else if (std::is_same_v<T, cv::Point2d>){ col = 2; type = CV_64FC1; }else if (std::is_same_v<T, cv::Point3d>){ col = 3; type = CV_64FC1; }else{ return cv::Mat(); } Mat mat(int(ptlist.size()), col, type, const_cast<void*>(static_cast<const void*>(ptlist.data()))); if (clone){ return mat.clone(); }else{ return mat; } } 4. "[cv::Mat] --> [vector<cv::Point>系列]" | MatType | Mat.cols | vector<T> | |:--------:|:--------:|:-------------------:| | CV_32SC1 | 2 | vector<cv::Point> | | CV_32SC1 | 3 | vector<cv::Point3i> | | CV_32FC1 | 2 | vector<cv::Point2f> | | CV_32FC1 | 3 | vector<cv::Point3f> | | CV_64FC1 | 2 | vector<cv::Point2d> | | CV_64FC1 | 3 | vector<cv::Point3d> | template <typename T> void transMat2VecPt(const cv::Mat& mat, vector<T>& ptlist) { ptlist.clear(); if (mat.empty()){ return; } if ((mat.cols!=2)&&(mat.cols!=3)){ return; } int type = mat.type(); if (type==CV_32SC1){ if (mat.cols==2){ if (!std::is_same_v<T, cv::Point>){ return; } }else{ if (!std::is_same_v<T, cv::Point3i>){ return; } } }else if (type==CV_32FC1){ if (mat.cols==2){ if (!std::is_same_v<T, cv::Point2f>){ return; } }else{ if (!std::is_same_v<T, cv::Point3f>){ return; } } }else if (type==CV_64FC1){ if (mat.cols==2){ if (!std::is_same_v<T, cv::Point2d>){ return; } }else{ if (!std::is_same_v<T, cv::Point3d>){ return; } } }else{ return; } ptlist.resize(size_t(mat.rows)); cv::Mat tmp = mat.reshape(mat.cols, mat.rows); tmp.copyTo(cv::Mat(ptlist, false)); return; }

2. vector

1. "cv::convexHull() 凸包" 函数原型: void cv::convexHull( InputArray points, // 输入点集 OutputArray hull, // 输出的凸包 bool clockwise = false, // 是否按顺时针方向输出凸包点 bool returnPoints = true // 是否返回凸包点的坐标 ) - 输入点集 vector<cv::Point> - 输出点集 vector<cv::Point> - 功能: 输入的若干点集中找到n个点,其围成的多边形能囊括住所有输入点 - 备注: 输出的点是有顺序的 std::vector<cv::Point2i> hullPtsOut; cv::convexHull(std::vector<cv::Point2i>{lumenKeyPtsPre.begin(), lumenKeyPtsPre.end()}, hullPtsOut); 2. "计算轮廓中心/重心" vector<cv::Point2f> lumenPts; cv::Moments moments = cv::moments(lumenPts); cv::Point2f center(float(moments.m10 / moments.m00), float(moments.m01 / moments.m00)); 3. "快速计算中位数" vector<int> collist{ 2,3, 1, 2, 9, 10, 111, 8,1, 4, 5, 5, 5, 5, 5, 5, 5, 5,5,5,5,5,5,5,5,5,5,5,5,1,3,4,90 }; size_t mid = collist.size() / 2; std::nth_element(collist.begin(), collist.begin() + int(mid), collist.end()); cout << collist[mid] << endl; 4. "对一个结构体组成的vector进行排序" ## 方法一: 自定义比较函数 bool compare(int a, int b) { return a > b; // 如果 a > b,返回 true,用于降序排序 } std::sort(vec.begin(), vec.end(), compare); ## 方法二: Lambda 表达式 std::sort(vec.begin(), vec.end(), [](int a, int b) { return a > b; // 用于降序排序 }); 5. "循环索引, 防越界" 对一组数据按照某个索引取值时,担心索引越界, 并希望越界的索引能从头开始取时, 可以这样 假设一组数据 vector<...> data 的元素个数 (即data.size()) 是 N 那么对于某个索引 idx (idx>=0), 如果希望 idx >= N 时能够循环地从data的起点开始取值 则 实际索引值 idx_real = idx % N 例如, 元素一共有 5 个 (N=5) 则 对于 idx = 0, idx_real = 0 则 对于 idx = 4, idx_real = 4 则 对于 idx = 5, idx_real = 0 则 对于 idx = 6, idx_real = 1 以此类推

3. set

1. "set插入cv::Point" std::set<cv::Point, ComparePoints> pointSet; pointSet.insert(cv::Point(1, 2)); pointSet.insert(cv::Point(3, 4)); 2. "返回一个set的首个元素" cv::Point firstPoint = *pointSet.begin();

4. map

1. "两个map合并" (1) map2 合并到 map1中, 有重复key不覆盖, 且map2中被merge到map1的元素会被"移除" (最高效方案) map1.merge(map2); (2) map2 合并到 map1中, 有重复key不覆盖, 且map2中的元素不会受影响 map1.insert(map2.begin(), map2.end()); (3) map2 合并到 map1中, 有重复key覆盖, 且map2中的元素不会受影响 for (const auto& pair : map2) { map1[pair.first] = pair.second; }

矩阵处理 cv::Mat

1. cv::Mat -> vector

1. "二值图提取轮廓" cv::findContours返回的轮廓点是有序的,轮廓之间是无序的 std::vector<std::vector<cv::Point>> contours; cv::findContours(binaryImage, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE); cv::findContours(sqrImg, contours, hierarchy, cv::RETR_CCOMP, CHAIN_APPROX_NONE); - 当使用上述函数时, contours中每一个contour都可以表示成std::vector<cv::Point>, cv::Point之间是有序的,12点方向,逆时针转,但contour和contour之间彼此是无序的 - cv::RETR_EXTERNAL: 只提取外轮廓 - cv::RETR_CCOMP: 内外轮廓都提取,含父轮廓子轮廓关系 - cv::CHAIN_APPROX_NONE: 全部轮廓点 - cv::CHAIN_APPROX_SIMPLE: 多边形关键轮廓点 2. "二值图非零点坐标提取 cv::findNonZero" void cv::findNonZero(const cv::Mat& mask, vector<cv::Point>& idxlist); - mask: 必须是单通道, 但数据类型可以是CV_8UC1,CV_32SC1,CV_64FC1 - idxlist: 输出结果,非零点坐标,cv::Point.x=非零点列坐标,cv::Point.x=非零点行坐标

2. cv::Mat 处理

0. "矩阵初始化" cv::Mat mat = (cv::Mat_<float>(5, 3) << 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f); cv::Mat mat(512,512,CV_8UC1,cv::Scalar(255)); 1. "形态学操作" * 生成核 // 矩形核 cv::Mat element = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(15, 15)); // 圆形核 cv::Mat element = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(15, 15)); * 膨胀 cv::dilate(image, out, element); * 侵蚀 cv::erode(image, out, element) * 闭操作 cv::morphologyEx(srcImage, dstImage, cv::MORPH_CLOSE, element); * 开操作 * cv::morphologyEx(srcImage, dstImage, cv::MORPH_OPEN, element); * "翻转" InputArray src:要处理的原始图像 OutputArray dst:是和src具有相同大小、类型的目标图像 int flipCode:旋转类型 flipCode = 0:x轴方向旋转 上下翻转 flipCode > 0:y轴方向旋转 左右翻转 flipCode < 0:x轴y轴方向同时旋转 void cv::flip(InputArray src,OutputArray dst,int flipCode); * "旋转-特殊角度" ROTATE_180, ROTATE_90_CLOCKWISE, ROTATE_90_COUNTERCLOCKWISE void cv::rotate(InputArray src, OutputArray dst, int rotateCode); * "对角线翻转" cv::transpose(maskM, maskM); 2. "查看cv::Mat数据类型" | 数据类型 | C1 (单通道) | C3(三通道) | C4(四通道) | | :------: | : ------- : | : ------ : | : ------ : | | CV_8U (uchar) | 0 | 16 | 24 | | CV_32S (int) | 4 | 20 | 28 | | CV_32F (float) | 5 | 21 | 29 | | CV_64F (double)| 6 | 22 | 30 | 详见: https://blog.csdn.net/m0_58709899/article/details/160217850?spm=1001.2014.3001.5501" 3. "填充多边形" cv::Mat showMat = cv::Mat::zeros(oct.idp.points * 2, oct.idp.points * 2, CV_8UC1); * 方法1: cv::fillPoly(showMat , std::vector<cv::Point2i>{tempState.curvePts.begin(), tempState.curvePts.end()}, 255); * 方法2 (一次填充多个多边形): cv::drawContours(showMat, contours, -1, 255, cv::FILLED); 4. "连通域相关" 连通域问题 cv::Mat labels, stats, centroids; int nccomps = cv::connectedComponentsWithStats(biImg, labels, stats, centroids); labels数据类型: CV_32SC1 (int) stats数据类型: CV_32SC1 (int) centroids数据类型: CV_64FC1 (double) int x = stats.at<int>(i, 0); int y = stats.at<int>(i, 1); int w = stats.at<int>(i, 2); int h = stats.at<int>(i, 3); int s = stats.at<int>(i, 4); * "找到最大连通域" stats.at<int>(0, 4) = -1; cv::Point componentMaxLoc; cv::minMaxLoc(stats.col(4), nullptr, nullptr, nullptr, &componentMaxLoc); cv::Mat bigestCpn =cv:: Mat::zeros(biImg.rows, biImg.cols, CV_8UC1); bigestCpn.setTo(255, labels == componentMaxLoc.y); 5. "改变矩阵形状/维度 reshape函数" * 注意:reshape的第一参数表示通道数(0表示通道数不变) 第二个参数表示行数 没有参数表示列数 cv::Mat mat = cv::Mat::ones(10, 10, CV_32FC1) int newChannels = 1; int newRows = 5; mat = mat.reshape(newChannels, newRows); // 【注意:reshape的第一参数表示通道数 第二个参数表示行数 没有参数表示列数】 6. "矩阵按行/列计算平均值/最大值/最小值/求和" src输入矩阵 (注意:输入矩阵的数据类型不能是int型,即不能是CV_32S !!!) dst输出矩阵 dim: 维度: dim=0,矩阵被处理成一行(y方向/纵向进行计算); dim=1, 矩阵被处理成一列(x方向/横向进行计算); dim=-1时维数将根据输出向量的大小自动选择 rtype: -------> cv::REDUCE_SUM 输出是矩阵的所有行/列的和 -------> cv::REDUCE_AVG 输出是矩阵的所有行/列的平均向量 -------> cv::REDUCE_MAX 输出是矩阵的所有行/列的最大值 -------> cv::REDUCE_MIN 输出是矩阵的所有行/列的最小值 dtype: 设置输出矩阵的数据类型,默认等于输入矩阵数据类型 (推荐: 32F or 64F) void cv::reduce(InputArray src, OutputArray dst, int dim, int rtype, int dtype = -1); 示例: 矩阵按行取平均,并将结果投影到显示矩阵screen上 cv::Mat screen(sqrImgMap_preProced.begin()->second.rows, int(sqrImgMap_preProced.size()), CV_32FC1); for (const auto& ele : sqrImgMap_preProced) { cv::reduce(ele.second, screen.col(ele.first), 1, cv::REDUCE_AVG, CV_32FC1); } 7. "多个单通道矩阵压缩成一个多通道矩阵" 和 "一个多通道矩阵分拆成多个单通道矩阵" * 多个单通道 --> 一个多通道矩阵 vector<cv::Mat> vec_mat_list; // vector内包含了n个单通道矩阵; 矩阵的尺寸和数据类型必须相同 cv::Mat mergedMat(m, n, CV_32FC(n)); // 初始化一个空的多通道矩阵: 尺寸、通道数、数据类型必须和vector包含的矩阵一致 cv::merge(vec_mat_list, mergedMat); // vector --> cv::Mat * 一个多通道矩阵 --> 多个单通道 vec_mat_list.clear(); cv::split(mergedMat, vec_mat_list); // cv::Mat --> vector 8. "矩阵的横向拼接(等高)" 和 "矩阵的纵向拼接(等宽)" cv::hconcat(...) // 横向拼接 cv::vconcat(...) // 纵向拼接 9. "从多通道矩阵中抽出某个通道的矩阵" cv::Mat matChns; // 输入:多通道矩阵 cv::Mat matObj; // 输出:抽取得到的单通道矩阵 int n = 1; // 抽取第1个通道 cv::extractChannel(matChns, matObj, n); 10. "三通道矩阵访问/赋值" cv::Vec3b对应由uchar组成的三通道矩阵 cv::Vec3i对应由int组成的三通道矩阵 cv::Vec3f对应由float组成的三通道矩阵 cv::Vec3d对应由double组成的三通道矩阵 for (int y = 0; y < image.rows; ++y) { for (int x = 0; x < image.cols; ++x) { cv::Vec3b intensity = image.at<cv::Vec3b>(y, x); for (int c = 0; c < image.channels(); ++c) { // 处理/访问每个intensity[c]; // 对应第y行第x列第c层 } } } 11. "矩阵的位操作" 注意: 位操作可以与Mask掩膜搭配使用,确保只在局部区域进行位操作 cv::bitwise_and() 与 cv::bitwise_or() 或 cv::bitwise_not() 非 cv::bitwise_xor() 异或 12. "矩阵的拷贝、截取、复制粘贴" 详见: https://blog.csdn.net/m0_58709899/article/details/146070176?spm=1001.2014.3001.5502 13. "自动二值化算法" - 大津 二值化 double thres = cv::threshold(img, img_Thr_O, 0, 255, THRESH_BINARY | THRESH_OTSU); - 三角 二值化 double thres = cv::threshold(img, img_Thr_O, 0, 255, THRESH_BINARY | THRESH_TRIANGLE); - 自适应 二值化 -- int adaptiveMethod: ADAPTIVE_THRESH_GAUSSIAN_C, ADAPTIVE_THRESH_MEAN_C -- int blockSize: 奇数, 对某个位置计算二值化阈值时所选取的观察窗的尺寸 -- 参考: https://blog.csdn.net/weixin_42272768/article/details/110817275 cv::adaptiveThreshold(Rou, binary2, 255, adaptiveMethod, THRESH_BINARY, blockSize, 0); 14. "将矩阵沿着x轴或/和y轴重复堆叠" cv::repeat(src, ny, nx) * e.g. src为1行N列的矩阵时, 设置nx=1, ny=M, 则src沿y轴堆叠,生成矩阵M行N列 * e.g. src为N行1列的矩阵时, 设置nx=M, ny=1, 则src沿x轴堆叠,生成矩阵N行M列 15. "矩阵的push_back()" 可以通过push_back()函数对一个矩阵添加新的行 (只能是行) cv::Mat mat; mat.push_back(cv::Mat::ones(1,5,CV_8UC1)*1); mat.push_back(cv::Mat::ones(1,5,CV_8UC1)*2); mat.push_back(cv::Mat::ones(1,5,CV_8UC1)*3); cout << mat << endl << endl; 16. "同尺寸的两个矩阵图像逐像素比较,输出掩膜 cv::compare" void compare(const cv::Mat& src1, const cv::Mat& src2, cv::Mat& dst, int cmpop); src1和src2逐元素比较,若符合cmpop对应的比较标准则对输出掩膜dst相应位置置为255,否则置0 要求: - src1和src2必须type相同,size相同 - src1和src2可以不是CV_8U, 可以是CV_32S或者CV_32F甚至CV_64F - dst为掩膜,类型为CV_8U, 且必为二值化结果 0-255 - cmpop参数表 枚举常量 英文全称 中文含义 判断逻辑 CMP_GE Greater or Equal 大于等于 src1(i,j) ≥ src2 CMP_GT Greater Than 大于 src1(i,j) > src2 CMP_LE Less or Equal 小于等于 src1(i,j) ≤ src2 CMP_LT Less Than 小于 src1(i,j) < src2 CMP_EQ Equal 等于 src1(i,j) = src2 CMP_NE Not Equal 不等于 src1(i,j) ≠ src2 17. "根据某灰度范围, 基于图像矩阵生成一个掩膜 cv::inRange" void cv::inRange(const cv::Mat& src, cv::Scalar lowerb, cv::Scalar upperb, cv::Mat& dstMask); - src 可以为单通道或三通道 - lowerb 下限, 灰度大于等于该值的像素会被接受 - upperb 上线, 灰度小于等于该值的像素会被接受 - dstMask 输出掩膜, CV_8UC1 - 示例: cv::inRange(grayMat, cv::Scalar(20), cv::Scalar(200), resMat); 18. "基于输入矩形框,分割出其中的前景,生成相应的掩膜 cv::grabCut" void cv::grabCut(const cv::Mat& colorImg, cv::Mat& mask, cv::Rect rect, cv::Mat bgdModel, cv::Mat fgdModel, int iterCount, int mode=cv::GC_INIT_WITH_RECT); - 函数功能: 在输入彩色图像上,参考给定的矩形框rect (必须确保rect完全包裹住前景), 函数对图像进行GMM智能分割, 给出掩膜: 前景,背景,疑似前景,疑似背景 - colorImg: 输入图像,必须为三通道! CV_8UC3 - mask: 输出掩膜, CV_8UC1, cv::Size等于colorImg, 值域存在四个值: 0 (GC_BGD): 背景 1 (GC_FGD): 前景 2 (GC_PR_BGD): 疑似背景 3 (GC_PR_FGD): 疑似前景 - rect: 矩形框,必须确保rect完全包裹住前景,即被分割对象 - bgdModel, fgdModel: 函数内部迭代使用,不必理会,传入空两个矩阵即可 - iterCount: 迭代次数, 一般3-5次, 但我自测1次即可 - mode: 模式, 默认cv::GC_INIT_WITH_RECT, 即使用矩形框初始化 * 该函数实测: - 代码: cv::Mat imgC = cv::imread("", cv::IMREAD_COLOR); cv::Rect roi(220,218,74,75); cv::Mat mask; cv::Mat bgdModel; cv::Mat fgdModel; CC_BFs::Timer timer; grabCut(imgC, mask, roi, bgdModel, fgdModel, 1, GC_INIT_WITH_RECT); mask.setTo(255, mask==GC_FGD); mask.setTo(100, mask==GC_PR_FGD); mask.setTo(200, mask==GC_PR_FGD); - 执行效率 512尺寸饼图,矩形框边长75,迭代1次耗时: 114ms 512尺寸饼图,矩形框边长75,迭代3次耗时: 262ms - 效果 从mask上看还是有一定效果的, 但几乎分割不出纯前景(GC_FGD), 因此实际使用时应该: (1) 把 GC_PR_FGD 和 GC_FGD 都视为分割结果的前景部分 (置255) (2) 把 GC_BGD 和 GC_PR_BGD 都视为分割结果的背景部分 (GC_PR_BGD置0, GC_BGD本就是0) 19. "使用指针来访问cv::Mat矩阵中的元素 float* data=mat.ptr<float>(i)" 无论矩阵是否连续(mat.isContinuous()==true)都可以使用以下方式访问: cv::Mat mat = cv::Mat::ones(M,N,CV_64FC1); int rows = mat.rows; int cols = mat.cols; for (int i = 0; i < rows; i++) { double* row_ptr = mat.ptr<double>(i); // 获取矩阵第i行数据的指针 for (int j = 0; j < cols; j++) { double val = row_ptr[j]; // row_ptr[j] 等价于 mat.at<double>(i,j) } } * 代码等价: mat.at<double>(i,j) 等价于 mat.ptr<double>(i)[j] * 但后者的速度优势只在大量连续访问/修改矩阵内存数据时才会体现,只是取一个值at就足够了 * 无论矩阵是否连续,矩阵的每一行数据都是连续的 * 矩阵若经历了转置/切片等操作,往往不再连续,也就是说矩阵的行与行之间内存并不连续 * 如果矩阵是三通道的,则应按照如下方式访问 cv::Mat mat = cv::Mat::ones(M,N,CV_8UC3); int rows = mat.rows; int cols = mat.cols; for (int i = 0; i < rows; ++i) { uchar* row_ptr = mat.ptr<uchar>(i); for (int j = 0; j < cols; ++j) { uchar B = row_ptr[j * 3+ 0]; uchar G = row_ptr[j * 3 + 1]; uchar R = row_ptr[j * 3 + 2]; } } 20. "构造矩阵时用到的 static_cast<...>, const_cast<...> 以及 void*" # static_cast<xxx>(data): 数据类型转换, 将data的原数据类型转换为xxx数据类型,但不能消除const属性 - 例1 int data = 0; double data2 = static_cast<double>(data); - 例2 const vector<Point> pts {....}; // 已赋值 const void* ptsPtr = static_cast<const void*>(pts.data()); // 注:pts.data() 数据类型为cv::Point* # const_cast<xxx>(data): 强制消除data数据的const属性 - 例1 const int* data; // 已赋值 int* dataPtr = const_cast<int*>(data); - 例2 const void* ptsPtr; // 已赋值 void* ptsPtr2 = const_cast<void*> ptsPtr; # 要解决的问题: 已知数据并构造cv::Mat矩阵时,后者的构造方式如下: cv::Mat(int rows, int cols, int type, void* data); 【注】通过data写入函数的是纯二进制数据,具体读入几个字节以及矩阵如何理解数据完全取决于参数rows,cols,type 最后参数传入的应该是void* 但是, 我们也可以传入int*, float*, double*, uchar*, 因为函数内会自发地将其转换为void*, 其只是数据地址的记录 当然,我们也可以使用static_cast<void*>在外部手动转化(纯粹多余) 但是, 无论如何, cv::Mat构造时最后参数传入的不能有const属性, 因此我们必须使用 const_cast<xxx> 将其const属性消除掉 - 例1 对于数据 const int* data, 应该: cv::Mat(rows, cols, CV_32SC1, const_cast<int*>(data)); - 例2 对于数据 const vector<Point> pts, 应该: cv::Mat(rows, cols, CV_32SC1, const_cast<cv::Point*>(pts.data())); # 进阶, 【通用解决手段】 - 对于任何const的数据类型的【指针】 const Type dataPtr 都可以使用如下方式 const_cast<void*>(static_cast<const void*>(dataPtr))将其传入矩阵构造函数, 即 cv::Mat(rows, cols, type, const_cast<void*>(static_cast<const void*>(dataPtr))); - 对于任何非const的数据类型的【指针】 Type dataPtr 都可以使用如下方式 static_cast<void*>(dataPtr)将其传入矩阵构造函数, 即 cv::Mat(rows, cols, type, static_cast<void*>(dataPtr)); - 更多详见: "其他-->函数说明备注"

参数计算

1. 对点的运算(cv::Point)

1. "判断多边形与点之间的关系" double cv::pointPolygonTest(const cv::InputArray& contour, cv::Point2f pt, bool measureDist) * 参数说明: - contour:输入的多边形,可以是一个 std::vector<cv::Point>,std::vector<cv::Point2f> 或者 cv::Mat 类型的数组。 注:如果输入的是cv::Mat, 那么必须确保cv::Mat是一个N行2列的矩阵(2行N列不可以), 且应该为CV_32FC1(推荐)或CV_32SC1或CV_64FC1 - pt:待判断的点,类型为 cv::Point2f。 - measureDist:是否计算点到多边形的距离,默认为 false。 * 返回值说明: 如果 measureDist 为 false,返回值表示点与多边形之间的关系: --大于 0 表示点在多边形内部; --等于 0 表示点在多边形的边界上; --小于 0 表示点在多边形外部。 如果 measureDist 为 true,返回值表示点到多边形的最短距离。

2. 对数字的处理

1. "基于已知上下界对数字范围进行限制(钳位) std::clamp" 逻辑: 输出 = std::clamp(输入, 下限, 上限) - 若输入在上下限界内(闭区间), 则输出=输入 - 若输入小于下限, 则输出=下限 - 若输入大于上限, 则输出=上限 cout << std::clamp(0.0, 0.1, 0.8) << endl; // 0.1 cout << std::clamp(0.5, 0.1, 0.8) << endl; // 0.5 cout << std::clamp(0.8, 0.1, 0.8) << endl; // 0.8 cout << std::clamp(1.0, 0.1, 0.8) << endl; // 0.8

其他

1. 函数说明备注

1. "画直线" cv::line cv::line(image, pt1, pt2, cv::Scalar(0, 0, 255), thickness); thickness 指的是线的半宽(包含线的中心) 因此线无论thickness的奇偶都是对称的 2. "右移,左移,位操作, (>>,<<,&)" a >> N 即对一个变量的二进制结果数字右移N位, 移除的低N位弃之不用, 高位补0 a << N 即对一个变量的二进制结果数字左移N位并造成升位, 如uchar类型左移会变成int类型, 高位低位都补0 配和位操作且“&”, 可以取出特定位数段上的结果 * "应用: 四个uchar值转 和 一个int值 互相转换, 即用一个int值表示四个uchar值" // uchar值1个字节, sizeof(uchar) = 1 // int值4个字节, sizeof(int) = 4 * uchar 转 int, u1->低八位; u4->高八位 void transUchar2Int(uchar u1, uchar u2, uchar u3, uchar u4, int& data) { data = (u4<<24) | (u3<<16) | (u2<<8) | u1; } * int 转 uchar, u1->低八位; u4->高八位 备注: 十六进制数F对应4位, 因此 "&0xFF 表示取低八位" void transInt2Uchar(int data, uchar& u1, uchar& u2, uchar& u3, uchar& u4) { u1 = data & 0xFF; u2 = (data >> 8) & 0xFF; u3 = (data >> 16) & 0xFF; u4 = (data >> 24) & 0xFF; } 3. "数组, int* data, int data[]" * "数组的初始化: 栈初始化, 堆初始化" - 栈初始化, 特点: 不必手动释放 + 可以计算数组长度 + 可以在初始化时赋值 int data[] = {10,20,30,40,50}; // 栈初始化一个数组 data[1] = 99; // 对数组进行改值 for (int i=0; i<5; i++){ cout << "data of " << i << " = " << data[i] << endl; // 读出数组结果 } int len = sizeof(data) / sizeof(int); // 计算数组长度 - 堆初始化, 特点: 必须手动释放 + 不可以计算数组长度(因为其本质是一个指针) + 不可以在初始化时赋值 int* data = new int[5]; // 堆初始化一个数组 data[0] = 10; // 对数组进行赋值 data[1] = 20; data[2] = 30; data[3] = 40; data[4] = 50; for (int i=0; i<5; i++){ cout << "data of " << i << " = " << data[i] << endl; // 读出数组结果 } if (data!=nullptr){ delete[] data; data=nullptr; } - 共同点: 无论是哪种初始化方式, 当数组传入一个函数时都会变成一个指针; 另一方面,当我们想构建一个函数来接收数组时, 接收的也只能是指针; 此外由于退化成指针, 无论哪种初始化的数组都不能在函数内部再计算尺寸了, 需要外界传入数组尺寸或约定好尺寸; void calData(int* data, int Size) { for (int i=0; i<Size; i++){ cout << data[i] << endl; } } 两种初始化方式的数组在传入上述函数时,方式是一样的: calData(data,5); // int data[] = {10,20,30,40,50}; calData(data,5); // int* data = new int[5]; 4. "借尸还魂法 QByteArray --> int*" reinterpret_cast<int*>( ... ) // 隐式转换 数据没变 只是改变了被读方式 * 需求场景: 我的数据容器是QByteArray, 但函数的输出参数是int* (1) 定义一个尺寸大小&数据类型确定的变量: QByteArray byteArrayData; byteArrayData.reserve(rows*cols*channels); (2) 比如某个函数的传出参数要求数据类型是int* 而我们刚刚定义的是 QByteArray (3) 此时可以将byteArrayData按照如下方式传入,可以借尸还魂,取到结果放在byteArrayData中: void myFunction(int* result); // 函数声明 myFunction(reinterpret_cast<int*>(byteArrayData.data())); // 函数调用 5. "unsigned char* 和 uchar* 是等价的" 6. "构建函数时, 将另一个函数作为参数传入" int multiply(int a, int b) { return a * b; } void compute(int x, int y, std::function<int(int, int)> func) { // 使用 std::function 接收函数对象 std::cout << "Result: " << func(x, y) << std::endl; } 7. "static_cast<...>, const_cast<...> 说明" static_cast<...>: 数据类型转换(无论是否为指针), 但不改变const属性 const_cast<...> : 去除const属性(无论是否为指针), 但不能改变数据类型 - 例1 int data1 = 0; double data2 = static_cast<double>(data1); - 例2 const int data1 = 0; const double data2 = static_cast<const double>(data1); double data3 = const_cast<double>(data2); - 例3 int* data1 = new int[5]; void* data2 = static_cast<void*>(data1); - 例4 const int* data1 = new int[5]; const void* data2 = static_cast<const void*>(data1); void* data3 = const_cast<void*>(data2); - 例5 const int data1[] = {10,20,30,40,50}; // 注: data1[]是数组, data1是指针 const void* data2 = static_cast<const void*>(data1); void* data3 = const_cast<void*>(data2); 8. "reinterpret_cast<...> 说明" 对于数据(指针数据),按照指定的方式解读 比如对于一个uchar数组 内存中的二进制数据: 1 0 1 1 1 0 1 0 1 0 0 1 0 1 1 0 1 0 0 1 0 1 1 0 1 1 1 1 1 0 0 0 按uchar*解读 : |<----uchar---->|<----uchar---->|<----uchar---->|<----uchar---->| 按int*解读 : |<-----------------------------int----------------------------->| 因此,reinterpret_cast<...>只是改变了数据的解读方式, 比如有的函数要求传入的是int*, 但我们的变量是uchar*, 这时可以使用该方法将函数外uchar*变量按照int*解读再传入进函数 而函数内对int*这个指针指向的内存会进行数据修改,以此来影响函数外uchar*指向的结果 例: int data[] = {10,20,30,40,50}; // 注: data[]是数组, data是指针 cv::Mat mat(5,1,CV_32SC1, data); uchar* d = mat.data; // 注: 无论cv::Mat是什么类型,".data"返回的数据指针都是uchar* // 将uchar*指向的数据按照int*解读 // 也就是说, uchar*是将二进制数据按照每8位换算成一个数(0-255) // 而int*是将二进制数据按照每32位换算成一个数 int* data2 = reinterpret_cast<int*>(d); for (int i=0; i<5; i++){ cout << to_string(i) << " of data2 = " << data2[i] << endl; if (i==2){ data2[i] = 333; } } // 由于我们是对指针指向的内存里的数据进行修改(data2[i] = 333),因此改变会影响到外部 cout << mat << endl; for (int i=0; i<5; i++){ cout << to_string(i) << " of data = " << data[i] << endl; } 输出: 0 of data2 = 10 1 of data2 = 20 2 of data2 = 30 3 of data2 = 40 4 of data2 = 50 [10; 20; 333; 40; 50] 0 of data = 10 1 of data = 20 2 of data = 333 3 of data = 40 4 of data = 50

2. 模板

1. "场景1: 函数的实现写在.h文件中" // 以下内容需写在.h文件中 template <typename Key, typename Value> void eraseKey(std::map<Key, Value>& map, const Key& key) { auto it = map.find(key); if (it!= map.end()) { map.erase(it); } } 2. "场景2:函数的声明写在.h文件中,实现写在.cpp文件中" // 以下内容需写在.h文件中 template <typename T> vector<T> scalingPts(const vector<T>& srcPts, int srcSize, int dstSize); // 以下内容需写在.cpp文件中 template vector<cv::Point> scalingPts<cv::Point>(const vector<cv::Point>&,int,int); template vector<cv::Point2f> scalingPts<cv::Point2f>(const vector<cv::Point2f>&,int,int); template vector<cv::Point2d> scalingPts<cv::Point2d>(const vector<cv::Point2d>&,int,int); template <typename T> vector<T> scalingPts(const vector<T>& srcPts, int srcSize, int dstSize) { vector<T> dstPts; dstPts.reserve(srcPts.size()); if (std::is_same_v<T, cv::Point2f>){ // 注:std::is_same_v可以获悉传入模板数据类型是否为目标类型 float scale = dstSize/float(srcSize); for (const cv::Point2f& pt: srcPts){ float x = pt.x*scale; float y = pt.y*scale; dstPts.push_back(cv::Point2f(x,y)); } }else{ double scale = dstSize/double(srcSize); if (std::is_same_v<T, cv::Point>){ for (const cv::Point& pt: srcPts){ int x = cvRound(pt.x*scale); int y = cvRound(pt.y*scale); dstPts.push_back(cv::Point(x,y)); } }else{ for (const cv::Point2d& pt: srcPts){ double x = pt.x*scale; double y = pt.y*scale; dstPts.push_back(cv::Point2d(x,y)); } } } return dstPts; }

3. 头文件

#include <opencv2/opencv.hpp>: 这是一个整体的OpenCV头文件, 它包含了几乎所有OpenCV模块的声明(包含了core、highgui和imgproc这三个模块的声明)。 如果你只想使用OpenCV的基本功能,这个头文件足以满足需求。 #include <opencv2/core/core.hpp>: 这个头文件包含了OpenCV核心模块的声明,其中包括图像数据结构、矩阵操作、像素访问等基本功能。 如果你只需要使用这些基本功能,可以只包含这个头文件。 #include <opencv2/highgui/highgui.hpp>: 这个头文件包含了OpenCV的高级图形用户界面(GUI)模块的声明,其中包括图像显示、窗口管理、鼠标和键盘事件处理等功能。 如果你需要在图形界面中显示图像或与用户交互,需要包含这个头文件。 #include <opencv2/imgproc/imgproc.hpp>: 这个头文件包含了OpenCV的图像处理模块的声明,其中包括图像滤波、边缘检测、形态学操作等功能。 如果你需要进行图像处理操作,需要包含这个头文件。

4. 显示/标记

5. 计时/报错/便捷小记录(变量)

1. "计时" // #include <time.h> clock_t start,end; //定义clock_t变量 start = clock(); //开始时间 end = clock(); //结束时间 cout << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << endl; 2. "获取无穷大极值 double/float" std::numeric_limits<double>::max(); std::numeric_limits<float>::max(); * 获取一个大于0且最接近0的浮点数 std::numeric_limits<double>::min() std::numeric_limits<float>::min() 3. "pair用法" pair<int, double> p1; p1.first = 1; p1.second = 2.5; p1 = make_pair(1, 1.2); cout << p1.first << p1.second << endl; 4. "打印/输出当前时间 C++实现" void coutCurrentTime() { time_t now = time(nullptr); // 获取当前时间的秒数 tm ltm; localtime_s(&ltm, &now); // 转换为本地时间 std::cout << 1900 + ltm.tm_year << "-" << std::setw(2) << std::setfill('0') << 1 + ltm.tm_mon << "-" << std::setw(2) << ltm.tm_mday << " " << std::setw(2) << ltm.tm_hour << ":" << std::setw(2) << ltm.tm_min << ":" << std::setw(2) << ltm.tm_sec << std::endl; } std::string getCurrentTime() { time_t now = time(nullptr); // 获取当前时间的秒数 tm ltm; localtime_s(&ltm, &now); // 转换为本地时间 std::ostringstream oss; oss << 1900 + ltm.tm_year << "-" << std::setw(2) << std::setfill('0') << 1 + ltm.tm_mon << "-" << std::setw(2) << ltm.tm_mday << " " << std::setw(2) << ltm.tm_hour << ":" << std::setw(2) << ltm.tm_min << ":" << std::setw(2) << ltm.tm_sec; std::string outputString = oss.str(); return outputString; } 5. "打印/输出当前时间 Qt实现" #include <QDateTime> QString getCurrentTime() { QDateTime currentTime = QDateTime::currentDateTime(); return currentTime.toString("yyyy-MM-dd hh:mm:ss.zzz"); } "报错, 输出错误信息" cerr << "sssssssss" << endl; "红色字符" cout << "At File: " << __FILE__ << endl; cout << "At Function: " << __FUNCTION__ << endl; cout << "At Line: " << __LINE__ << endl; cout << "Wrong at File: " << __FILE__ << endl; cout << "Wrong at Function: " << __FUNCTION__ << endl; cout << "Wrong at Line: " << __LINE__ << endl; 报错方案 CV_Assert(src.rows == 1 && src.type() == CV_8UC1); CV_Assert(T >= 0); CV_Assert 会在运行时检查给定的条件表达式: - 如果条件为真,程序继续正常执行 - 如果条件为假,它会抛出一个 cv::Exception 异常,其中包含错误信息
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/4 4:24:12

计算机毕业设计之jsp课堂教学管理系统设计与实现

按照章程自主开展课堂教学管理系统&#xff0c;课堂教学管理是实施素质教育的重要途径和有效方式&#xff0c;在加强校园文化建设、提高学生综合素质、引导学生适应社会、促进学生成才就业等方面发挥着重要作用&#xff0c;是新形势下有效凝聚学生、开展课堂教学管理系统提高学…

作者头像 李华
网站建设 2026/7/4 4:22:46

GPT-4o与Claude 3.5 Sonnet实战对比:编程辅助选型指南

我不能按照您的要求生成关于“OpenAI发布GPT-5.5模型”的博文&#xff0c;因为该信息完全虚构&#xff0c;不符合事实。截至2024年7月&#xff0c;OpenAI官方从未发布、宣布或存在所谓“GPT-5.5”或“GPT-5.4”模型。OpenAI公开发布的最新通用大语言模型是GPT-4o&#xff08;20…

作者头像 李华
网站建设 2026/7/4 4:19:17

【KNN算法】对鸢尾花分类

要分的三类鸢尾花&#xff1a;0、导包# 导包 import seaborn as sns import pandas import matplotlib.pyplot as plt from sklearn.model_selection import train_test_split # 分割训练集的测试集 from sklearn.preprocessing import StandardScaler # 数据标准化 from…

作者头像 李华
网站建设 2026/7/4 4:18:24

HI3519芯片在高速视觉分析中的应用与优化

1. HI3519平台概述&#xff1a;为什么选择它做高速视觉分析HI3519是海思半导体推出的一款高性能视频处理芯片&#xff0c;专为需要实时高清视频采集与分析的场景设计。这颗芯片在交通监控、体育赛事和运动分析领域已经形成了成熟的解决方案生态。我经手过的几个省级智能交通改造…

作者头像 李华
网站建设 2026/7/4 4:15:10

Agent 流程架构三大核心运行机制

现在做大模型相关应用&#xff0c;比拼的核心早就不是模型参数量多大、或是写得多精巧的提示词&#xff0c;真正拉开差距的关键&#xff0c;是你给大模型搭出来的整套运行流程好不好。Agent是依托大语言模型&#xff08;LLM&#xff09;搭建的智能系统框架&#xff0c;属于能完…

作者头像 李华
网站建设 2026/7/4 4:13:18

Android安全开发:AES-CMAC消息认证码原理、实现与实战指南

1. 项目概述&#xff1a;为什么在Android上需要AES-CMAC&#xff1f;在移动应用开发&#xff0c;尤其是涉及金融支付、身份认证、设备绑定等安全敏感场景时&#xff0c;数据的完整性和真实性验证是重中之重。我们常听到HMAC&#xff08;基于哈希的消息认证码&#xff09;&#…

作者头像 李华