#ifndef ZCPPWIN_ZCV_H #define ZCPPWIN_ZCV_H /* * zcv.h * ----------------------------------------------------------------------------- * 基于 OpenCV 4.x / opencv_contrib 4.x 的常用计算机视觉工具库声明文件。 * * 设计目标: * 1. 将 OpenCV 中高频使用的图像、视频、特征、DNN、二维码/条码、ArUco、标定等 * 操作整理成统一的 ZCV 命名空间,减少项目中的重复样板代码。 * 2. 保持“薄封装”原则:函数内部仍然使用 OpenCV 原生数据结构,如 cv::Mat、cv::Rect、 * cv::Point2f、cv::dnn::Net,便于和既有 OpenCV 代码互操作。 * 3. 尽量兼容 OpenCV 4.x 的不同小版本。对 contrib 中可能不存在或 API 变化较大的模块, * 使用 __has_include 与版本宏做条件编译。 * * 使用方式: * #include "zcv.h" * cv::Mat img = ZCV::readImage("test.jpg"); * cv::Mat gray = ZCV::ensureGray(img); * cv::Mat edges = ZCV::canny(gray, 80, 160); * * 编译提示: * CMake 中通常写法: * find_package(OpenCV 4 REQUIRED) * add_library(zcv zcv.cpp) * target_include_directories(zcv PUBLIC ${OpenCV_INCLUDE_DIRS}) * target_link_libraries(zcv PUBLIC ${OpenCV_LIBS}) */ #include #include #include #include #include #include // #include #include // #include // #include #include // #include // #include #include // #include // #include // #include #include // #include #include // ---------------------------- 可选 contrib 模块检测 ---------------------------- // ArUco 在 OpenCV 4.x 的不同小版本中经历过头文件与 API 调整;优先使用传统 aruco.hpp, // 若新版本只提供 objdetect 下的头文件,也尝试包含。 #if __has_include() #include #define ZCV_HAS_ARUCO 1 #elif __has_include() && __has_include() #include #include #define ZCV_HAS_ARUCO 1 #else #define ZCV_HAS_ARUCO 0 #endif // barcode 模块属于 contrib/objdetect 体系,不同版本头文件位置可能不同。 #if __has_include() #include #define ZCV_HAS_BARCODE 1 #elif __has_include() #include #define ZCV_HAS_BARCODE 1 #else #define ZCV_HAS_BARCODE 0 #endif // SIFT 在较新的 OpenCV 4.x 中位于 features2d;较旧版本可能在 xfeatures2d。 #if (CV_VERSION_MAJOR > 4) || (CV_VERSION_MAJOR == 4 && CV_VERSION_MINOR >= 4) #define ZCV_HAS_SIFT 1 #elif __has_include() #include #define ZCV_HAS_SIFT 1 #define ZCV_SIFT_IN_XFEATURES2D 1 #else #define ZCV_HAS_SIFT 0 #endif namespace ZCV { // ============================================================================= // 1. 基础枚举与数据结构 // ============================================================================= /** 图像缩放模式。 */ enum class ResizeMode { Stretch, ///< 直接拉伸到目标尺寸,可能改变宽高比。 KeepAspectFit, ///< 保持宽高比完整放入目标画布,不足区域填充颜色,类似 letterbox。 KeepAspectFill ///< 保持宽高比并填满目标尺寸,多余区域居中裁剪。 }; /** 常用形态学操作类型。 */ enum class MorphType { Erode, ///< 腐蚀:缩小白色前景区域。 Dilate, ///< 膨胀:扩大白色前景区域。 Open, ///< 开运算:先腐蚀后膨胀,常用于去除小噪声。 Close, ///< 闭运算:先膨胀后腐蚀,常用于填补小孔洞。 Gradient, ///< 形态学梯度:突出目标边界。 TopHat, ///< 顶帽:原图 - 开运算结果,突出小亮区域。 BlackHat, ///< 黑帽:闭运算结果 - 原图,突出小暗区域。 HitMiss ///< 击中击不中,仅适用于二值单通道图像。 }; /** 90 度倍数旋转模式。 */ enum class RotateMode { Clockwise90, ///< 顺时针旋转 90 度。 CounterClockwise90, ///< 逆时针旋转 90 度。 Rotate180 ///< 旋转 180 度。 }; /** 图像基本信息,便于调试日志输出。 */ struct ImageInfo { bool empty = true; ///< cv::Mat 是否为空。 int width = 0; ///< 图像宽度,单位:像素。 int height = 0; ///< 图像高度,单位:像素。 int channels = 0; ///< 通道数。 int depth = -1; ///< OpenCV 深度,例如 CV_8U、CV_32F。 int type = -1; ///< OpenCV 完整类型,例如 CV_8UC3。 std::string typeString; ///< 便于阅读的类型字符串,例如 "CV_8UC3"。 }; /** letterbox 缩放的返回结果。 */ struct LetterboxResult { cv::Mat image; ///< 输出图像,尺寸等于 targetSize。 double scale = 1.0; ///< 原图到缩放图的比例。 int padLeft = 0; ///< 左侧填充像素数。 int padTop = 0; ///< 顶部填充像素数。 cv::Rect contentRect; ///< 原图缩放后在目标画布中的区域。 }; /** 轮廓摘要信息。 */ struct ContourInfo { std::vector contour; ///< 原始轮廓点。 double area = 0.0; ///< 轮廓面积,来自 cv::contourArea。 double perimeter = 0.0; ///< 轮廓周长,来自 cv::arcLength。 cv::Rect boundingBox; ///< 水平外接矩形。 cv::RotatedRect rotatedBox; ///< 最小面积旋转外接矩形。 cv::Point2f center; ///< 根据矩计算出的质心;矩退化时取外接框中心。 float enclosingRadius = 0.0f; ///< 最小外接圆半径。 double circularity = 0.0; ///< 圆形度,4*pi*area/perimeter^2,越接近 1 越圆。 }; /** 连通域摘要信息。 */ struct ComponentInfo { int label = -1; ///< 连通域标签,0 通常是背景。 int area = 0; ///< 像素面积。 cv::Rect boundingBox; ///< 外接矩形。 cv::Point2d centroid; ///< 质心。 }; /** 特征检测结果。 */ struct FeatureResult { std::vector keypoints; ///< 关键点列表。 cv::Mat descriptors; ///< 描述子矩阵。 }; /** 两幅图像特征匹配结果。 */ struct MatchResult { FeatureResult first; ///< 第一幅图像特征。 FeatureResult second; ///< 第二幅图像特征。 std::vector matches; ///< 通过筛选的匹配。 cv::Mat homography; ///< 若启用单应性估计,则保存 3x3 H 矩阵。 std::vector inlierMask; ///< RANSAC 内点掩码,与 matches 一一对应。 int inlierCount = 0; ///< 内点数量。 }; /** 棋盘格角点检测结果。 */ struct ChessboardResult { bool found = false; ///< 是否成功找到所有内角点。 cv::Size boardSize; ///< 棋盘格内角点数量,width x height。 std::vector corners; ///< 角点坐标。 cv::Mat debugImage; ///< 绘制角点后的调试图。 }; /** 相机标定结果。 */ struct CalibrationResult { bool ok = false; ///< 标定是否执行成功。 double reprojectionError = 0.0; ///< cv::calibrateCamera 返回的重投影误差。 cv::Mat cameraMatrix; ///< 3x3 内参矩阵。 cv::Mat distCoeffs; ///< 畸变系数。 std::vector rvecs; ///< 每张标定图的旋转向量。 std::vector tvecs; ///< 每张标定图的平移向量。 std::size_t usedImageCount = 0; ///< 实际参与标定的图片数量。 }; /** 视频文件或摄像头的摘要信息。 */ struct VideoInfo { bool opened = false; ///< 是否成功打开。 int width = 0; ///< 帧宽。 int height = 0; ///< 帧高。 double fps = 0.0; ///< 帧率;摄像头或部分文件可能返回 0。 int frameCount = 0; ///< 总帧数;实时流可能返回 0 或不可靠值。 double durationSec = 0.0; ///< 估算时长,frameCount / fps。 std::string backendName; ///< OpenCV 使用的视频后端名称。 }; /** QRCodeDetector 单个二维码识别结果。 */ struct QRCodeResult { bool success = false; ///< 是否检测并解码出非空字符串。 std::string text; ///< 解码文本,OpenCV 通常返回 UTF-8 字符串。 std::vector points; ///< 二维码四个角点。 cv::Mat straight; ///< 透视校正后的二维码图像,可为空。 }; #if ZCV_HAS_ARUCO /** ArUco marker 检测结果。 */ struct ArucoResult { std::vector ids; ///< marker ID 列表。 std::vector> corners; ///< 每个 marker 的四个角点。 std::vector> rejected; ///< 被拒绝的候选区域,便于调参。 }; #endif #if ZCV_HAS_BARCODE /** 条形码检测与解码结果。 */ struct BarcodeResult { bool success = false; ///< 是否成功解码。 std::string text; ///< 解码文本。 std::string type; ///< 条码类型,如 EAN_13、CODE_128 等,具体取决于 OpenCV。 std::vector points; ///< 条码旋转矩形四个点;可能因版本差异为空。 }; #endif /** DNN 前处理参数。 */ struct DnnOptions { cv::Size inputSize; ///< 网络输入尺寸;空尺寸表示不强制 resize。 double scale = 1.0; ///< 像素缩放因子,例如 1/255.0。 cv::Scalar mean = cv::Scalar(); ///< 均值,按 BGR 或 RGB 顺序取决于 swapRB。 bool swapRB = false; ///< 是否交换 R/B 通道,常用于 BGR -> RGB。 bool crop = false; ///< blobFromImage 是否中心裁剪。 int ddepth = CV_32F; ///< blob 数据深度,一般为 CV_32F。 int backend = cv::dnn::DNN_BACKEND_OPENCV; ///< 推理后端。 int target = cv::dnn::DNN_TARGET_CPU; ///< 推理目标设备。 std::string inputName; ///< setInput 的输入层名称,通常留空。 }; /** DNN 推理结果。 */ struct DnnForwardResult { std::vector outputs; ///< 网络输出 blob 列表。 double inferenceMs = 0.0; ///< 单次 forward 耗时,单位毫秒。 }; /** 分类分数。 */ struct ClassScore { int classId = -1; ///< 类别索引。 float score = 0.0f; ///< 置信度或概率。 }; /** 简单计时器,基于 cv::TickMeter。 */ class TickTimer { public: void start(); ///< 清零并开始计时。 double stopMs(); ///< 停止计时并返回毫秒数。 double elapsedMs() const; ///< 返回当前累计毫秒数。 private: cv::TickMeter meter_; }; // ============================================================================= // 2. 基础信息、类型转换与安全检查 // ============================================================================= std::string opencvVersionString(); std::string matTypeToString(int type); ImageInfo getImageInfo(const cv::Mat& image); bool isGray(const cv::Mat& image); bool isBgrLike(const cv::Mat& image); bool hasAlpha(const cv::Mat& image); void assertNotEmpty(const cv::Mat& image, const std::string& name = "image"); cv::Mat ensureGray(const cv::Mat& image); cv::Mat ensureBgr(const cv::Mat& image); cv::Mat ensureBgra(const cv::Mat& image); cv::Mat to8U(const cv::Mat& image, bool normalize = true, double alpha = 1.0, double beta = 0.0); cv::Mat toFloat32(const cv::Mat& image, double scale = 1.0 / 255.0); // ============================================================================= // 3. 图像读写、显示与基础矩阵操作 // ============================================================================= cv::Mat readImage(const std::string& path, int flags = cv::IMREAD_COLOR, bool throwIfEmpty = true); bool saveImage(const std::string& path, const cv::Mat& image, const std::vector& params = std::vector()); std::vector readImages(const std::vector& paths, int flags = cv::IMREAD_COLOR, bool skipFailed = true); void showImage(const std::string& windowName, const cv::Mat& image, int delayMs = 0, int windowFlags = cv::WINDOW_AUTOSIZE, bool destroyAfter = false); cv::Rect clampRect(const cv::Rect& roi, const cv::Size& imageSize); cv::Mat crop(const cv::Mat& image, const cv::Rect& roi, bool safe = true, bool clone = true); cv::Mat centerCrop(const cv::Mat& image, const cv::Size& size, bool clone = true); std::vector splitChannels(const cv::Mat& image); cv::Mat mergeChannels(const std::vector& channels); cv::Mat stackHorizontal(const std::vector& images, int gap = 0, const cv::Scalar& background = cv::Scalar(0, 0, 0)); cv::Mat stackVertical(const std::vector& images, int gap = 0, const cv::Scalar& background = cv::Scalar(0, 0, 0)); cv::Mat makeGrid(const std::vector& images, int columns, cv::Size cellSize = cv::Size(), int gap = 0, const cv::Scalar& background = cv::Scalar(0, 0, 0)); // ============================================================================= // 4. 几何变换 // ============================================================================= cv::Mat resizeTo(const cv::Mat& image, const cv::Size& targetSize, ResizeMode mode = ResizeMode::Stretch, int interpolation = cv::INTER_LINEAR, const cv::Scalar& borderColor = cv::Scalar(114, 114, 114)); cv::Mat resizeScale(const cv::Mat& image, double fx, double fy = 0.0, int interpolation = cv::INTER_LINEAR); LetterboxResult letterbox(const cv::Mat& image, const cv::Size& targetSize, const cv::Scalar& color = cv::Scalar(114, 114, 114), bool allowScaleUp = true, int interpolation = cv::INTER_LINEAR); cv::Mat rotate90(const cv::Mat& image, RotateMode mode); cv::Mat rotateAngle(const cv::Mat& image, double angleDeg, double scale = 1.0, bool expand = true, const cv::Scalar& borderValue = cv::Scalar(0, 0, 0)); cv::Mat translate(const cv::Mat& image, double dx, double dy, const cv::Scalar& borderValue = cv::Scalar(0, 0, 0)); std::vector orderQuadPoints(const std::vector& points); cv::Mat fourPointTransform(const cv::Mat& image, const std::vector& points, int interpolation = cv::INTER_LINEAR); cv::Mat warpPerspectiveFromPoints(const cv::Mat& image, const std::vector& srcPoints, const std::vector& dstPoints, const cv::Size& dstSize, int interpolation = cv::INTER_LINEAR); // ============================================================================= // 5. 图像增强与滤波 // ============================================================================= cv::Mat adjustBrightnessContrast(const cv::Mat& image, double alpha = 1.0, double beta = 0.0); cv::Mat gammaCorrect(const cv::Mat& image, double gamma); cv::Mat equalizeGray(const cv::Mat& image); cv::Mat clahe(const cv::Mat& image, double clipLimit = 2.0, cv::Size tileGridSize = cv::Size(8, 8)); cv::Mat normalizeMinMax(const cv::Mat& image, double alpha = 0.0, double beta = 255.0, int dtype = -1); cv::Mat grayWorldWhiteBalance(const cv::Mat& image); cv::Mat sharpen(const cv::Mat& image); cv::Mat unsharpMask(const cv::Mat& image, double amount = 1.0, double sigma = 1.0); cv::Mat denoise(const cv::Mat& image, float h = 10.0f, float hColor = 10.0f, int templateWindowSize = 7, int searchWindowSize = 21); cv::Mat applyColorMapToGray(const cv::Mat& image, int colorMap = cv::COLORMAP_JET); cv::Mat meanBlur(const cv::Mat& image, int ksize = 3); cv::Mat gaussianBlur(const cv::Mat& image, int ksize = 5, double sigmaX = 0.0); cv::Mat medianBlur(const cv::Mat& image, int ksize = 5); cv::Mat bilateral(const cv::Mat& image, int d = 9, double sigmaColor = 75.0, double sigmaSpace = 75.0); cv::Mat sobelEdges(const cv::Mat& image, int dx = 1, int dy = 0, int ksize = 3); cv::Mat laplacianEdges(const cv::Mat& image, int ksize = 3); cv::Mat calcGrayHist(const cv::Mat& image, int histSize = 256, float rangeMin = 0.0f, float rangeMax = 256.0f); cv::Mat drawGrayHist(const cv::Mat& hist, cv::Size canvasSize = cv::Size(512, 300), const cv::Scalar& color = cv::Scalar(255, 255, 255)); // ============================================================================= // 6. 二值化、边缘、形态学与距离变换 // ============================================================================= cv::Mat thresholdBinary(const cv::Mat& image, double thresh, double maxVal = 255.0, bool inverse = false); cv::Mat thresholdOtsu(const cv::Mat& image, double maxVal = 255.0, bool inverse = false); cv::Mat adaptiveThresholdBinary(const cv::Mat& image, int blockSize = 31, double C = 5.0, bool inverse = false, int method = cv::ADAPTIVE_THRESH_GAUSSIAN_C); cv::Mat canny(const cv::Mat& image, double threshold1, double threshold2, int apertureSize = 3, bool L2gradient = false); cv::Mat morph(const cv::Mat& image, MorphType type, int kernelSize = 3, int iterations = 1, int shape = cv::MORPH_RECT); cv::Mat erodeImage(const cv::Mat& image, int kernelSize = 3, int iterations = 1, int shape = cv::MORPH_RECT); cv::Mat dilateImage(const cv::Mat& image, int kernelSize = 3, int iterations = 1, int shape = cv::MORPH_RECT); cv::Mat fillHoles(const cv::Mat& binaryImage); cv::Mat distanceTransformBinary(const cv::Mat& binaryImage, int distanceType = cv::DIST_L2, int maskSize = 3); // ============================================================================= // 7. 轮廓、连通域与几何度量 // ============================================================================= std::vector findContoursInfo(const cv::Mat& binaryImage, int retrievalMode = cv::RETR_EXTERNAL, int approximation = cv::CHAIN_APPROX_SIMPLE, double minArea = 0.0, bool sortByAreaDesc = true); bool largestContour(const cv::Mat& binaryImage, ContourInfo& outInfo, double minArea = 0.0); cv::Mat drawContourInfos(const cv::Mat& image, const std::vector& contours, const cv::Scalar& color = cv::Scalar(0, 255, 0), int thickness = 2, bool drawBoundingBox = true, bool drawIndex = false); cv::Mat contourMask(const cv::Size& size, const std::vector& contour, uchar fillValue = 255); std::vector connectedComponentsInfo(const cv::Mat& binaryImage, int connectivity = 8, int minArea = 1); cv::Mat drawComponents(const cv::Mat& labels, int labelCount); double intersectionOverUnion(const cv::Rect2f& a, const cv::Rect2f& b); cv::Scalar randomColor(int seed, int minValue = 40, int maxValue = 255); // ============================================================================= // 8. 特征点、描述子与匹配 // ============================================================================= FeatureResult detectORB(const cv::Mat& image, int nfeatures = 1000); FeatureResult detectAKAZE(const cv::Mat& image); #if ZCV_HAS_SIFT FeatureResult detectSIFT(const cv::Mat& image, int nfeatures = 0); #endif std::vector matchDescriptors(const cv::Mat& desc1, const cv::Mat& desc2, int normType = -1, bool crossCheck = false, double ratio = 0.75, int maxMatches = 0); MatchResult matchORB(const cv::Mat& image1, const cv::Mat& image2, int nfeatures = 1500, double ratio = 0.75, bool estimateHomography = true, double ransacReprojThreshold = 3.0); cv::Mat drawMatchResult(const cv::Mat& image1, const cv::Mat& image2, const MatchResult& result, bool onlyInliers = true); // ============================================================================= // 9. 相机标定、畸变校正与传统目标检测 // ============================================================================= ChessboardResult findChessboard(const cv::Mat& image, cv::Size boardSize, bool refineSubPix = true, int flags = cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_NORMALIZE_IMAGE); CalibrationResult calibrateCameraFromChessboards(const std::vector& images, cv::Size boardSize, float squareSize, int flags = 0); CalibrationResult calibrateCameraFromChessboardFiles(const std::vector& imagePaths, cv::Size boardSize, float squareSize, int flags = 0); cv::Mat undistortImage(const cv::Mat& image, const cv::Mat& cameraMatrix, const cv::Mat& distCoeffs, double alpha = 0.0); std::vector detectHaar(const cv::Mat& image, const std::string& cascadePath, double scaleFactor = 1.1, int minNeighbors = 3, cv::Size minSize = cv::Size(), cv::Size maxSize = cv::Size()); cv::Mat drawRects(const cv::Mat& image, const std::vector& rects, const cv::Scalar& color = cv::Scalar(0, 255, 0), int thickness = 2); // ============================================================================= // 10. DNN:模型加载、blob 前处理、推理与后处理辅助 // ============================================================================= cv::dnn::Net loadDnnNet(const std::string& modelPath, const std::string& configPath = std::string(), const std::string& framework = std::string(), int backend = cv::dnn::DNN_BACKEND_OPENCV, int target = cv::dnn::DNN_TARGET_CPU); cv::Mat makeBlob(const cv::Mat& image, const DnnOptions& options); DnnForwardResult forwardDnn(cv::dnn::Net& net, const cv::Mat& image, const DnnOptions& options, const std::vector& outputNames = std::vector()); std::vector topK(const cv::Mat& scores, int k = 5); std::vector nmsBoxes(const std::vector& boxes, const std::vector& confidences, float scoreThreshold = 0.25f, float nmsThreshold = 0.45f, float eta = 1.0f, int topK = 0); // ============================================================================= // 11. 视频读取、写入与帧抽取 // ============================================================================= VideoInfo getCaptureInfo(cv::VideoCapture& cap); cv::VideoCapture openVideo(const std::string& path, int apiPreference = cv::CAP_ANY, bool throwIfFailed = true); cv::VideoCapture openCamera(int index = 0, int width = 0, int height = 0, double fps = 0.0, int apiPreference = cv::CAP_ANY, bool throwIfFailed = true); bool readFrameAt(const std::string& videoPath, int frameIndex, cv::Mat& frame, int apiPreference = cv::CAP_ANY); cv::VideoWriter createVideoWriter(const std::string& path, cv::Size frameSize, double fps, const std::string& fourcc = "MJPG", bool isColor = true, int apiPreference = cv::CAP_ANY); bool writeVideoFromFrames(const std::string& path, const std::vector& frames, double fps, const std::string& fourcc = "MJPG", bool isColor = true); std::vector readAllFrames(const std::string& videoPath, int maxFrames = -1, int step = 1, int apiPreference = cv::CAP_ANY); int extractFrames(const std::string& videoPath, const std::string& outputPattern, int step = 1, int maxFrames = -1, int startFrame = 0, int apiPreference = cv::CAP_ANY); // ============================================================================= // 12. 二维码、条码、ArUco 与绘制辅助 // ============================================================================= QRCodeResult detectAndDecodeQRCode(const cv::Mat& image); cv::Mat drawQRCodeResult(const cv::Mat& image, const QRCodeResult& result, const cv::Scalar& color = cv::Scalar(0, 255, 0)); #if ZCV_HAS_BARCODE std::vector detectAndDecodeBarcodes(const cv::Mat& image, const std::string& srPrototxt = std::string(), const std::string& srModel = std::string()); cv::Mat drawBarcodeResults(const cv::Mat& image, const std::vector& results, const cv::Scalar& color = cv::Scalar(255, 0, 0)); #endif #if ZCV_HAS_ARUCO ArucoResult detectArucoMarkers(const cv::Mat& image, int dictionaryId = cv::aruco::DICT_4X4_50, bool refineCorners = true); cv::Mat drawArucoResult(const cv::Mat& image, const ArucoResult& result, const cv::Scalar& color = cv::Scalar(0, 255, 0)); cv::Mat generateArucoMarker(int markerId, int sidePixels = 300, int dictionaryId = cv::aruco::DICT_4X4_50, int borderBits = 1); #endif void drawLabel(cv::Mat& image, const std::string& text, cv::Point origin, double fontScale = 0.6, const cv::Scalar& textColor = cv::Scalar(255, 255, 255), const cv::Scalar& bgColor = cv::Scalar(0, 0, 0), int thickness = 1); void drawRotatedRect(cv::Mat& image, const cv::RotatedRect& rect, const cv::Scalar& color = cv::Scalar(0, 255, 0), int thickness = 2); void drawCrosshair(cv::Mat& image, cv::Point center, int radius = 10, const cv::Scalar& color = cv::Scalar(0, 0, 255), int thickness = 1); cv::Mat drawGridOverlay(const cv::Mat& image, int gridSize = 50, const cv::Scalar& color = cv::Scalar(80, 80, 80), int thickness = 1); } // namespace ZCV #endif //ZCPPWIN_ZCV_H