#include "zcv.h"
#include <opencv2/video.hpp>

#include <opencv2/features2d.hpp>
#include <opencv2/objdetect.hpp>

#include <opencv2/photo.hpp>


#include <cmath>
#include <numeric>
#include <sstream>

namespace ZCV {namespace {

    // 将核尺寸修正为正奇数。GaussianBlur、MedianBlur、Sobel 等常要求奇数核。
    int makeOddKernel(int k, int minValue = 1) {
        k = std::max(k, minValue);
        if (k % 2 == 0) {
            ++k;
        }
        return k;
    }

    // 将任意二值/灰度输入转换为 0/255 的 CV_8UC1 图像，方便形态学、轮廓和连通域处理。
    cv::Mat toBinary8U(const cv::Mat &image) {
        assertNotEmpty(image, "binaryImage");
        cv::Mat gray = ensureGray(image);
        if (gray.depth() != CV_8U) {
            gray = to8U(gray, true);
        }
        cv::Mat binary;
        cv::threshold(gray, binary, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
        return binary;
    }

    // 根据轮廓计算常用几何指标。
    ContourInfo makeContourInfo(const std::vector<cv::Point> &contour) {
        ContourInfo info;
        info.contour = contour;
        info.area = std::fabs(cv::contourArea(contour));
        info.perimeter = cv::arcLength(contour, true);
        info.boundingBox = cv::boundingRect(contour);
        if (contour.size() >= 5) {
            info.rotatedBox = cv::minAreaRect(contour);
        } else {
            info.rotatedBox = cv::RotatedRect(
                cv::Point2f(info.boundingBox.x + info.boundingBox.width * 0.5f,
                            info.boundingBox.y + info.boundingBox.height * 0.5f),
                cv::Size2f(static_cast<float>(info.boundingBox.width),
                           static_cast<float>(info.boundingBox.height)),
                0.0f);
        }

        cv::Moments m = cv::moments(contour);
        if (std::fabs(m.m00) > std::numeric_limits<double>::epsilon()) {
            info.center = cv::Point2f(static_cast<float>(m.m10 / m.m00),
                                      static_cast<float>(m.m01 / m.m00));
        } else {
            info.center = cv::Point2f(info.boundingBox.x + info.boundingBox.width * 0.5f,
                                      info.boundingBox.y + info.boundingBox.height *
                                      0.5f);
        }

        cv::Point2f circleCenter;
        cv::minEnclosingCircle(contour, circleCenter, info.enclosingRadius);
        if (info.perimeter > std::numeric_limits<double>::epsilon()) {
            info.circularity = 4.0 * CV_PI * info.area / (
                                   info.perimeter * info.perimeter);
        }
        return info;
    }

    // 将 cv::QRCodeDetector / BarcodeDetector 输出的 Mat 尽量解析为点列表。
    std::vector<cv::Point2f> matToPointList(const cv::Mat &points) {
        std::vector<cv::Point2f> out;
        if (points.empty()) {
            return out;
        }

        cv::Mat m;
        points.convertTo(m, CV_32F);

        if (m.channels() == 2) {
            cv::Mat continuous = m.isContinuous() ? m : m.clone();
            const auto *ptr = continuous.ptr<cv::Point2f>(0);
            out.assign(ptr, ptr + continuous.total());
            return out;
        }

        if (m.channels() == 1) {
            if (m.dims == 2 && m.cols >= 2) {
                for (int r = 0; r < m.rows; ++r) {
                    out.emplace_back(m.at<float>(r, 0), m.at<float>(r, 1));
                }
            } else if (m.total() >= 2) {
                cv::Mat flat = m.reshape(1, 1);
                for (int i = 0; i + 1 < flat.cols; i += 2) {
                    out.emplace_back(flat.at<float>(0, i), flat.at<float>(0, i + 1));
                }
            }
        }
        return out;
    }

    // 将条码检测输出拆成多个四边形。不同 OpenCV 小版本可能返回 CV_32FC2、Nx8 或三维数组。
    std::vector<std::vector<cv::Point2f>> splitQuadrangles(const cv::Mat &points) {
        std::vector<std::vector<cv::Point2f>> quads;
        if (points.empty()) {
            return quads;
        }

        cv::Mat m;
        points.convertTo(m, CV_32F);

        if (m.dims == 3 && m.size[1] == 4) {
            const int n = m.size[0];
            for (int i = 0; i < n; ++i) {
                std::vector<cv::Point2f> q;
                for (int j = 0; j < 4; ++j) {
                    const float *p = m.ptr<float>(i, j);
                    q.emplace_back(p[0], p[1]);
                }
                quads.push_back(q);
            }
            return quads;
        }

        std::vector<cv::Point2f> flat = matToPointList(m);
        for (std::size_t i = 0; i + 3 < flat.size(); i += 4) {
            quads.emplace_back(flat.begin() + static_cast<std::ptrdiff_t>(i),
                               flat.begin() + static_cast<std::ptrdiff_t>(i + 4));
        }
        return quads;
    }

    // 为 stack/grid 统一准备图像：转为 BGR，必要时缩放到指定尺寸。
    cv::Mat prepareCell(const cv::Mat &image, const cv::Size &cellSize) {
        cv::Mat bgr = ensureBgr(image);
        if (cellSize.width > 0 && cellSize.height > 0 && bgr.size() != cellSize) {
            cv::resize(bgr, bgr, cellSize, 0, 0, cv::INTER_AREA);
        }
        return bgr;
    }

    // 由四字符字符串生成 fourcc；不足 4 位时使用 MJPG。
    int fourccFromString(const std::string &fourcc) {
        std::string code = fourcc.size() >= 4 ? fourcc.substr(0, 4) : std::string("MJPG");
        return cv::VideoWriter::fourcc(code[0], code[1], code[2], code[3]);
    }

    } // namespace

    // =============================================================================
    // TickTimer
    // =============================================================================

    void TickTimer::start() {
        meter_.reset();
        meter_.start();
    }

    double TickTimer::stopMs() {
        meter_.stop();
        return meter_.getTimeMilli();
    }

    double TickTimer::elapsedMs() const {
        return meter_.getTimeMilli();
    }

    // =============================================================================
    // 2. 基础信息、类型转换与安全检查
    // =============================================================================

    std::string opencvVersionString() {
        return CV_VERSION;
    }

    std::string matTypeToString(int type) {
        const int depth = type & CV_MAT_DEPTH_MASK;
        const int channels = 1 + (type >> CV_CN_SHIFT);

        std::string depthStr;
        switch (depth) {
            case CV_8U: depthStr = "8U";
                break;
            case CV_8S: depthStr = "8S";
                break;
            case CV_16U: depthStr = "16U";
                break;
            case CV_16S: depthStr = "16S";
                break;
            case CV_32S: depthStr = "32S";
                break;
            case CV_32F: depthStr = "32F";
                break;
            case CV_64F: depthStr = "64F";
                break;
            default: depthStr = "User";
                break;
        }

        std::ostringstream oss;
        oss << "CV_" << depthStr << "C" << channels;
        return oss.str();
    }

    ImageInfo getImageInfo(const cv::Mat &image) {
        ImageInfo info;
        info.empty = image.empty();
        if (!image.empty()) {
            info.width = image.cols;
            info.height = image.rows;
            info.channels = image.channels();
            info.depth = image.depth();
            info.type = image.type();
            info.typeString = matTypeToString(image.type());
        }
        return info;
    }

    bool isGray(const cv::Mat &image) {
        return !image.empty() && image.channels() == 1;
    }

    bool isBgrLike(const cv::Mat &image) {
        return !image.empty() && (image.channels() == 3 || image.channels() == 4);
    }

    bool hasAlpha(const cv::Mat &image) {
        return !image.empty() && image.channels() == 4;
    }

    void assertNotEmpty(const cv::Mat &image, const std::string &name) {
        if (image.empty()) {
            throw std::invalid_argument("ZCV: " + name + " 为空，无法继续处理。");
        }
    }

    cv::Mat ensureGray(const cv::Mat &image) {
        assertNotEmpty(image, "image");
        if (image.channels() == 1) {
            return image.clone();
        }
        cv::Mat gray;
        if (image.channels() == 3) {
            cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);
        } else if (image.channels() == 4) {
            cv::cvtColor(image, gray, cv::COLOR_BGRA2GRAY);
        } else {
            throw std::invalid_argument("ZCV::ensureGray 仅支持 1/3/4 通道图像。");
        }
        return gray;
    }

    cv::Mat ensureBgr(const cv::Mat &image) {
        assertNotEmpty(image, "image");
        if (image.channels() == 3) {
            return image.clone();
        }
        cv::Mat bgr;
        if (image.channels() == 1) {
            cv::cvtColor(image, bgr, cv::COLOR_GRAY2BGR);
        } else if (image.channels() == 4) {
            cv::cvtColor(image, bgr, cv::COLOR_BGRA2BGR);
        } else {
            throw std::invalid_argument("ZCV::ensureBgr 仅支持 1/3/4 通道图像。");
        }
        return bgr;
    }

    cv::Mat ensureBgra(const cv::Mat &image) {
        assertNotEmpty(image, "image");
        if (image.channels() == 4) {
            return image.clone();
        }
        cv::Mat bgra;
        if (image.channels() == 1) {
            cv::cvtColor(image, bgra, cv::COLOR_GRAY2BGRA);
        } else if (image.channels() == 3) {
            cv::cvtColor(image, bgra, cv::COLOR_BGR2BGRA);
        } else {
            throw std::invalid_argument("ZCV::ensureBgra 仅支持 1/3/4 通道图像。");
        }
        return bgra;
    }

    cv::Mat to8U(const cv::Mat &image, bool normalize, double alpha, double beta) {
        assertNotEmpty(image, "image");
        cv::Mat out;
        if (image.depth() == CV_8U && !normalize && std::abs(alpha - 1.0) < 1e-12 &&
            std::abs(beta) < 1e-12) {
            return image.clone();
        }
        if (normalize) {
            cv::Mat norm;
            cv::normalize(image, norm, 0.0, 255.0, cv::NORM_MINMAX);
            norm.convertTo(out, CV_8U);
        } else {
            image.convertTo(out, CV_8U, alpha, beta);
        }
        return out;
    }

    cv::Mat toFloat32(const cv::Mat &image, double scale) {
        assertNotEmpty(image, "image");
        cv::Mat out;
        image.convertTo(out, CV_32F, scale);
        return out;
    }

    // =============================================================================
    // 3. 图像读写、显示与基础矩阵操作
    // =============================================================================

    cv::Mat readImage(const std::string &path, int flags, bool throwIfEmpty) {
        cv::Mat image = cv::imread(path, flags);
        if (image.empty() && throwIfEmpty) {
            throw std::runtime_error("ZCV::readImage 读取失败: " + path);
        }
        return image;
    }

    bool saveImage(const std::string &path, const cv::Mat &image,
                   const std::vector<int> &params) {
        assertNotEmpty(image, "image");
        try {
            return cv::imwrite(path, image, params);
        } catch (const cv::Exception &) {
            return false;
        }
    }

    std::vector<cv::Mat> readImages(const std::vector<std::string> &paths, int flags,
                                    bool skipFailed) {
        std::vector<cv::Mat> images;
        images.reserve(paths.size());
        for (const auto &path: paths) {
            cv::Mat img = cv::imread(path, flags);
            if (img.empty()) {
                if (skipFailed) {
                    continue;
                }
                throw std::runtime_error("ZCV::readImages 读取失败: " + path);
            }
            images.push_back(img);
        }
        return images;
    }

    void showImage(const std::string &windowName, const cv::Mat &image,
                   int delayMs, int windowFlags, bool destroyAfter) {
        assertNotEmpty(image, "image");
        cv::namedWindow(windowName, windowFlags);
        cv::imshow(windowName, image);
        cv::waitKey(delayMs);
        if (destroyAfter) {
            cv::destroyWindow(windowName);
        }
    }

    cv::Rect clampRect(const cv::Rect &roi, const cv::Size &imageSize) {
        if (imageSize.width <= 0 || imageSize.height <= 0) {
            return cv::Rect();
        }
        cv::Rect bounds(0, 0, imageSize.width, imageSize.height);
        cv::Rect clipped = roi & bounds;
        if (clipped.width <= 0 || clipped.height <= 0) {
            return cv::Rect();
        }
        return clipped;
    }

    cv::Mat crop(const cv::Mat &image, const cv::Rect &roi, bool safe, bool clone) {
        assertNotEmpty(image, "image");
        cv::Rect r = safe ? clampRect(roi, image.size()) : roi;
        if (r.width <= 0 || r.height <= 0) {
            throw std::out_of_range("ZCV::crop ROI 与图像没有有效交集。");
        }
        cv::Mat view = image(r);
        return clone ? view.clone() : view;
    }

    cv::Mat centerCrop(const cv::Mat &image, const cv::Size &size, bool clone) {
        assertNotEmpty(image, "image");
        int w = std::min(size.width, image.cols);
        int h = std::min(size.height, image.rows);
        if (w <= 0 || h <= 0) {
            throw std::invalid_argument("ZCV::centerCrop 目标尺寸无效。");
        }
        int x = (image.cols - w) / 2;
        int y = (image.rows - h) / 2;
        return crop(image, cv::Rect(x, y, w, h), true, clone);
    }

    std::vector<cv::Mat> splitChannels(const cv::Mat &image) {
        assertNotEmpty(image, "image");
        std::vector<cv::Mat> channels;
        cv::split(image, channels);
        return channels;
    }

    cv::Mat mergeChannels(const std::vector<cv::Mat> &channels) {
        if (channels.empty()) {
            throw std::invalid_argument("ZCV::mergeChannels 通道列表为空。");
        }
        cv::Mat out;
        cv::merge(channels, out);
        return out;
    }

    cv::Mat stackHorizontal(const std::vector<cv::Mat> &images, int gap,
                            const cv::Scalar &background) {
        if (images.empty()) {
            return cv::Mat();
        }
        gap = std::max(0, gap);

        int maxHeight = 0;
        int totalWidth = gap * static_cast<int>(images.size() - 1);
        std::vector<cv::Mat> prepared;
        prepared.reserve(images.size());
        for (const auto &img: images) {
            cv::Mat bgr = ensureBgr(img);
            maxHeight = std::max(maxHeight, bgr.rows);
            totalWidth += bgr.cols;
            prepared.push_back(bgr);
        }

        cv::Mat canvas(maxHeight, totalWidth, CV_8UC3, background);
        int x = 0;
        for (const auto &img: prepared) {
            img.copyTo(canvas(cv::Rect(x, 0, img.cols, img.rows)));
            x += img.cols + gap;
        }
        return canvas;
    }

    cv::Mat stackVertical(const std::vector<cv::Mat> &images, int gap,
                          const cv::Scalar &background) {
        if (images.empty()) {
            return cv::Mat();
        }
        gap = std::max(0, gap);

        int maxWidth = 0;
        int totalHeight = gap * static_cast<int>(images.size() - 1);
        std::vector<cv::Mat> prepared;
        prepared.reserve(images.size());
        for (const auto &img: images) {
            cv::Mat bgr = ensureBgr(img);
            maxWidth = std::max(maxWidth, bgr.cols);
            totalHeight += bgr.rows;
            prepared.push_back(bgr);
        }

        cv::Mat canvas(totalHeight, maxWidth, CV_8UC3, background);
        int y = 0;
        for (const auto &img: prepared) {
            img.copyTo(canvas(cv::Rect(0, y, img.cols, img.rows)));
            y += img.rows + gap;
        }
        return canvas;
    }

    cv::Mat makeGrid(const std::vector<cv::Mat> &images, int columns, cv::Size cellSize,
                     int gap, const cv::Scalar &background) {
        if (images.empty()) {
            return cv::Mat();
        }
        columns = std::max(1, columns);
        gap = std::max(0, gap);

        if (cellSize.width <= 0 || cellSize.height <= 0) {
            cellSize = images.front().size();
        }

        const int rows = static_cast<int>((images.size() + columns - 1) / columns);
        cv::Mat canvas(rows * cellSize.height + (rows - 1) * gap,
                       columns * cellSize.width + (columns - 1) * gap,
                       CV_8UC3, background);

        for (std::size_t i = 0; i < images.size(); ++i) {
            const int r = static_cast<int>(i) / columns;
            const int c = static_cast<int>(i) % columns;
            cv::Mat cell = prepareCell(images[i], cellSize);
            cell.copyTo(canvas(cv::Rect(c * (cellSize.width + gap),
                                        r * (cellSize.height + gap),
                                        cellSize.width, cellSize.height)));
        }
        return canvas;
    }

    // =============================================================================
    // 4. 几何变换
    // =============================================================================

    cv::Mat resizeTo(const cv::Mat &image, const cv::Size &targetSize, ResizeMode mode,
                     int interpolation, const cv::Scalar &borderColor) {
        assertNotEmpty(image, "image");
        if (targetSize.width <= 0 || targetSize.height <= 0) {
            throw std::invalid_argument("ZCV::resizeTo 目标尺寸无效。");
        }

        cv::Mat out;
        if (mode == ResizeMode::Stretch) {
            cv::resize(image, out, targetSize, 0, 0, interpolation);
            return out;
        }

        if (mode == ResizeMode::KeepAspectFit) {
            return letterbox(image, targetSize, borderColor, true, interpolation).image;
        }

        const double scale = std::max(targetSize.width / static_cast<double>(image.cols),
                                      targetSize.height / static_cast<double>(image.
                                          rows));
        cv::Mat resized;
        cv::resize(image, resized, cv::Size(), scale, scale, interpolation);
        return centerCrop(resized, targetSize, true);
    }

    cv::Mat resizeScale(const cv::Mat &image, double fx, double fy, int interpolation) {
        assertNotEmpty(image, "image");
        if (fx <= 0.0) {
            throw std::invalid_argument("ZCV::resizeScale fx 必须为正数。");
        }
        if (fy <= 0.0) {
            fy = fx;
        }
        cv::Mat out;
        cv::resize(image, out, cv::Size(), fx, fy, interpolation);
        return out;
    }

    LetterboxResult letterbox(const cv::Mat &image, const cv::Size &targetSize,
                              const cv::Scalar &color, bool allowScaleUp,
                              int interpolation) {
        assertNotEmpty(image, "image");
        if (targetSize.width <= 0 || targetSize.height <= 0) {
            throw std::invalid_argument("ZCV::letterbox 目标尺寸无效。");
        }

        double scale = std::min(targetSize.width / static_cast<double>(image.cols),
                                targetSize.height / static_cast<double>(image.rows));
        if (!allowScaleUp) {
            scale = std::min(scale, 1.0);
        }

        const int newW = std::max(1, static_cast<int>(std::round(image.cols * scale)));
        const int newH = std::max(1, static_cast<int>(std::round(image.rows * scale)));

        cv::Mat resized;
        cv::resize(image, resized, cv::Size(newW, newH), 0, 0, interpolation);

        const int padW = targetSize.width - newW;
        const int padH = targetSize.height - newH;
        const int left = padW / 2;
        const int right = padW - left;
        const int top = padH / 2;
        const int bottom = padH - top;

        LetterboxResult result;
        cv::copyMakeBorder(resized, result.image, top, bottom, left, right,
                           cv::BORDER_CONSTANT, color);
        result.scale = scale;
        result.padLeft = left;
        result.padTop = top;
        result.contentRect = cv::Rect(left, top, newW, newH);
        return result;
    }

    cv::Mat rotate90(const cv::Mat &image, RotateMode mode) {
        assertNotEmpty(image, "image");
        cv::Mat out;
        switch (mode) {
            case RotateMode::Clockwise90:
                cv::rotate(image, out, cv::ROTATE_90_CLOCKWISE);
                break;
            case RotateMode::CounterClockwise90:
                cv::rotate(image, out, cv::ROTATE_90_COUNTERCLOCKWISE);
                break;
            case RotateMode::Rotate180:
                cv::rotate(image, out, cv::ROTATE_180);
                break;
        }
        return out;
    }

    cv::Mat rotateAngle(const cv::Mat &image, double angleDeg, double scale,
                        bool expand, const cv::Scalar &borderValue) {
        assertNotEmpty(image, "image");
        cv::Point2f center((image.cols - 1) * 0.5f, (image.rows - 1) * 0.5f);
        cv::Mat M = cv::getRotationMatrix2D(center, angleDeg, scale);
        cv::Size outSize = image.size();

        if (expand) {
            cv::Rect2f bbox = cv::RotatedRect(center, image.size(),
                                              static_cast<float>(angleDeg)).
                    boundingRect2f();
            outSize = cv::Size(static_cast<int>(std::ceil(bbox.width * scale)),
                               static_cast<int>(std::ceil(bbox.height * scale)));
            M.at<double>(0, 2) += outSize.width * 0.5 - center.x;
            M.at<double>(1, 2) += outSize.height * 0.5 - center.y;
        }

        cv::Mat out;
        cv::warpAffine(image, out, M, outSize, cv::INTER_LINEAR, cv::BORDER_CONSTANT,
                       borderValue);
        return out;
    }

    cv::Mat translate(const cv::Mat &image, double dx, double dy,
                      const cv::Scalar &borderValue) {
        assertNotEmpty(image, "image");
        cv::Mat M = (cv::Mat_<double>(2, 3) << 1.0, 0.0, dx, 0.0, 1.0, dy);
        cv::Mat out;
        cv::warpAffine(image, out, M, image.size(), cv::INTER_LINEAR, cv::BORDER_CONSTANT,
                       borderValue);
        return out;
    }

    std::vector<cv::Point2f> orderQuadPoints(const std::vector<cv::Point2f> &points) {
        if (points.size() != 4) {
            throw std::invalid_argument("ZCV::orderQuadPoints 需要恰好 4 个点。");
        }

        std::vector<cv::Point2f> ordered(4);
        std::vector<double> sums(4), diffs(4);
        for (int i = 0; i < 4; ++i) {
            sums[i] = points[i].x + points[i].y;
            diffs[i] = points[i].x - points[i].y;
        }

        ordered[0] = points[static_cast<std::size_t>(
            std::min_element(sums.begin(), sums.end()) - sums.begin())]; // 左上
        ordered[2] = points[static_cast<std::size_t>(
            std::max_element(sums.begin(), sums.end()) - sums.begin())]; // 右下
        ordered[1] = points[static_cast<std::size_t>(
            std::max_element(diffs.begin(), diffs.end()) - diffs.begin())]; // 右上
        ordered[3] = points[static_cast<std::size_t>(
            std::min_element(diffs.begin(), diffs.end()) - diffs.begin())]; // 左下
        return ordered;
    }

    cv::Mat fourPointTransform(const cv::Mat &image,
                               const std::vector<cv::Point2f> &points,
                               int interpolation) {
        assertNotEmpty(image, "image");
        std::vector<cv::Point2f> p = orderQuadPoints(points);

        auto dist = [](const cv::Point2f &a, const cv::Point2f &b) {
            return static_cast<float>(cv::norm(a - b));
        };

        const int width = std::max(
            1, static_cast<int>(std::round(
                std::max(dist(p[0], p[1]), dist(p[2], p[3])))));
        const int height = std::max(
            1, static_cast<int>(std::round(
                std::max(dist(p[0], p[3]), dist(p[1], p[2])))));

        std::vector<cv::Point2f> dst = {
            {0.0f, 0.0f},
            {static_cast<float>(width - 1), 0.0f},
            {static_cast<float>(width - 1), static_cast<float>(height - 1)},
            {0.0f, static_cast<float>(height - 1)}
        };
        return warpPerspectiveFromPoints(image, p, dst, cv::Size(width, height),
                                         interpolation);
    }

    cv::Mat warpPerspectiveFromPoints(const cv::Mat &image,
                                      const std::vector<cv::Point2f> &srcPoints,
                                      const std::vector<cv::Point2f> &dstPoints,
                                      const cv::Size &dstSize,
                                      int interpolation) {
        assertNotEmpty(image, "image");
        if (srcPoints.size() != 4 || dstPoints.size() != 4) {
            throw std::invalid_argument(
                "ZCV::warpPerspectiveFromPoints 需要 4 个源点和 4 个目标点。");
        }
        cv::Mat H = cv::getPerspectiveTransform(srcPoints, dstPoints);
        cv::Mat out;
        cv::warpPerspective(image, out, H, dstSize, interpolation);
        return out;
    }

    // =============================================================================
    // 5. 图像增强与滤波
    // =============================================================================

    cv::Mat adjustBrightnessContrast(const cv::Mat &image, double alpha, double beta) {
        assertNotEmpty(image, "image");
        cv::Mat out;
        image.convertTo(out, -1, alpha, beta);
        return out;
    }

    cv::Mat gammaCorrect(const cv::Mat &image, double gamma) {
        assertNotEmpty(image, "image");
        if (gamma <= 0.0) {
            throw std::invalid_argument("ZCV::gammaCorrect gamma 必须为正数。");
        }

        cv::Mat src8 = image.depth() == CV_8U ? image.clone() : to8U(image, true);
        cv::Mat lut(1, 256, CV_8U);
        const double invGamma = 1.0 / gamma;
        for (int i = 0; i < 256; ++i) {
            lut.at<uchar>(0, i) = cv::saturate_cast<uchar>(
                std::pow(i / 255.0, invGamma) * 255.0);
        }
        cv::Mat out;
        cv::LUT(src8, lut, out);
        return out;
    }

    cv::Mat equalizeGray(const cv::Mat &image) {
        cv::Mat gray = ensureGray(image);
        if (gray.depth() != CV_8U) {
            gray = to8U(gray, true);
        }
        cv::Mat out;
        cv::equalizeHist(gray, out);
        return out;
    }

    cv::Mat clahe(const cv::Mat &image, double clipLimit, cv::Size tileGridSize) {
        cv::Mat gray = ensureGray(image);
        if (gray.depth() != CV_8U) {
            gray = to8U(gray, true);
        }
        cv::Ptr<cv::CLAHE> op = cv::createCLAHE(clipLimit, tileGridSize);
        cv::Mat out;
        op->apply(gray, out);
        return out;
    }

    cv::Mat normalizeMinMax(const cv::Mat &image, double alpha, double beta, int dtype) {
        assertNotEmpty(image, "image");
        cv::Mat out;
        cv::normalize(image, out, alpha, beta, cv::NORM_MINMAX, dtype);
        return out;
    }

    cv::Mat grayWorldWhiteBalance(const cv::Mat &image) {
        assertNotEmpty(image, "image");
        cv::Mat bgr = ensureBgr(image);
        cv::Mat f;
        bgr.convertTo(f, CV_32F);

        std::vector<cv::Mat> ch;
        cv::split(f, ch);
        cv::Scalar meanB = cv::mean(ch[0]);
        cv::Scalar meanG = cv::mean(ch[1]);
        cv::Scalar meanR = cv::mean(ch[2]);
        const double avg = (meanB[0] + meanG[0] + meanR[0]) / 3.0;

        ch[0] *= avg / std::max(meanB[0], 1e-6);
        ch[1] *= avg / std::max(meanG[0], 1e-6);
        ch[2] *= avg / std::max(meanR[0], 1e-6);

        cv::merge(ch, f);
        cv::Mat out;
        f.convertTo(out, bgr.type());
        return out;
    }

    cv::Mat sharpen(const cv::Mat &image) {
        assertNotEmpty(image, "image");
        cv::Mat kernel = (cv::Mat_<float>(3, 3) <<
                          0.0f, -1.0f, 0.0f,
                          -1.0f, 5.0f, -1.0f,
                          0.0f, -1.0f, 0.0f);
        cv::Mat out;
        cv::filter2D(image, out, image.depth(), kernel);
        return out;
    }

    cv::Mat unsharpMask(const cv::Mat &image, double amount, double sigma) {
        assertNotEmpty(image, "image");
        sigma = sigma > 0.0 ? sigma : 1.0;
        cv::Mat blurred;
        cv::GaussianBlur(image, blurred, cv::Size(0, 0), sigma);
        cv::Mat out;
        cv::addWeighted(image, 1.0 + amount, blurred, -amount, 0.0, out);
        return out;
    }

    cv::Mat denoise(const cv::Mat &image, float h, float hColor,
                    int templateWindowSize, int searchWindowSize) {
        assertNotEmpty(image, "image");
        // fastNlMeansDenoising 系列主要面向 8-bit 图像；非 8-bit 输入先归一化到 CV_8U。
        cv::Mat src = image.depth() == CV_8U ? image.clone() : to8U(image, true);
        cv::Mat out;
        if (src.channels() == 1) {
            cv::fastNlMeansDenoising(src, out, h, templateWindowSize, searchWindowSize);
        } else {
            cv::Mat bgr = ensureBgr(src);
            cv::fastNlMeansDenoisingColored(bgr, out, h, hColor, templateWindowSize,
                                            searchWindowSize);
        }
        return out;
    }

    cv::Mat applyColorMapToGray(const cv::Mat &image, int colorMap) {
        cv::Mat gray = ensureGray(image);
        if (gray.depth() != CV_8U) {
            gray = to8U(gray, true);
        }
        cv::Mat out;
        cv::applyColorMap(gray, out, colorMap);
        return out;
    }

    cv::Mat meanBlur(const cv::Mat &image, int ksize) {
        assertNotEmpty(image, "image");
        ksize = std::max(1, ksize);
        cv::Mat out;
        cv::blur(image, out, cv::Size(ksize, ksize));
        return out;
    }

    cv::Mat gaussianBlur(const cv::Mat &image, int ksize, double sigmaX) {
        assertNotEmpty(image, "image");
        ksize = makeOddKernel(ksize);
        cv::Mat out;
        cv::GaussianBlur(image, out, cv::Size(ksize, ksize), sigmaX);
        return out;
    }

    cv::Mat medianBlur(const cv::Mat &image, int ksize) {
        assertNotEmpty(image, "image");
        ksize = makeOddKernel(ksize);
        cv::Mat out;
        cv::medianBlur(image, out, ksize);
        return out;
    }

    cv::Mat bilateral(const cv::Mat &image, int d, double sigmaColor, double sigmaSpace) {
        assertNotEmpty(image, "image");
        cv::Mat out;
        cv::bilateralFilter(image, out, d, sigmaColor, sigmaSpace);
        return out;
    }

    cv::Mat sobelEdges(const cv::Mat &image, int dx, int dy, int ksize) {
        cv::Mat gray = ensureGray(image);
        if (gray.depth() != CV_8U) {
            gray = to8U(gray, true);
        }
        ksize = makeOddKernel(ksize, 1);
        cv::Mat grad16, out;
        cv::Sobel(gray, grad16, CV_16S, dx, dy, ksize);
        cv::convertScaleAbs(grad16, out);
        return out;
    }

    cv::Mat laplacianEdges(const cv::Mat &image, int ksize) {
        cv::Mat gray = ensureGray(image);
        if (gray.depth() != CV_8U) {
            gray = to8U(gray, true);
        }
        ksize = makeOddKernel(ksize, 1);
        cv::Mat lap16, out;
        cv::Laplacian(gray, lap16, CV_16S, ksize);
        cv::convertScaleAbs(lap16, out);
        return out;
    }

    cv::Mat calcGrayHist(const cv::Mat &image, int histSize, float rangeMin,
                         float rangeMax) {
        cv::Mat gray = ensureGray(image);
        if (gray.depth() != CV_8U) {
            gray = to8U(gray, true);
        }
        float range[] = {rangeMin, rangeMax};
        const float *ranges[] = {range};
        int channels[] = {0};
        cv::Mat hist;
        cv::calcHist(&gray, 1, channels, cv::Mat(), hist, 1, &histSize, ranges, true,
                     false);
        return hist;
    }

    cv::Mat drawGrayHist(const cv::Mat &hist, cv::Size canvasSize,
                         const cv::Scalar &color) {
        assertNotEmpty(hist, "hist");
        cv::Mat histNorm;
        cv::normalize(hist, histNorm, 0, canvasSize.height - 10, cv::NORM_MINMAX);
        histNorm.convertTo(histNorm, CV_32F);

        cv::Mat canvas(canvasSize, CV_8UC3, cv::Scalar(0, 0, 0));
        const int bins = histNorm.rows;
        const double binW = canvasSize.width / static_cast<double>(std::max(1, bins));

        for (int i = 1; i < bins; ++i) {
            cv::Point p1(static_cast<int>(std::round((i - 1) * binW)),
                         canvasSize.height - cvRound(histNorm.at<float>(i - 1, 0)));
            cv::Point p2(static_cast<int>(std::round(i * binW)),
                         canvasSize.height - cvRound(histNorm.at<float>(i, 0)));
            cv::line(canvas, p1, p2, color, 1, cv::LINE_AA);
        }
        return canvas;
    }

    // =============================================================================
    // 6. 二值化、边缘、形态学与距离变换
    // =============================================================================

    cv::Mat thresholdBinary(const cv::Mat &image, double thresh, double maxVal,
                            bool inverse) {
        cv::Mat gray = ensureGray(image);
        if (gray.depth() != CV_8U) {
            gray = to8U(gray, true);
        }
        cv::Mat out;
        cv::threshold(gray, out, thresh, maxVal,
                      inverse ? cv::THRESH_BINARY_INV : cv::THRESH_BINARY);
        return out;
    }

    cv::Mat thresholdOtsu(const cv::Mat &image, double maxVal, bool inverse) {
        cv::Mat gray = ensureGray(image);
        if (gray.depth() != CV_8U) {
            gray = to8U(gray, true);
        }
        cv::Mat out;
        const int type = (inverse ? cv::THRESH_BINARY_INV : cv::THRESH_BINARY) |
                         cv::THRESH_OTSU;
        cv::threshold(gray, out, 0, maxVal, type);
        return out;
    }

    cv::Mat adaptiveThresholdBinary(const cv::Mat &image, int blockSize, double C,
                                    bool inverse, int method) {
        cv::Mat gray = ensureGray(image);
        if (gray.depth() != CV_8U) {
            gray = to8U(gray, true);
        }
        blockSize = makeOddKernel(blockSize, 3);
        cv::Mat out;
        cv::adaptiveThreshold(gray, out, 255,
                              method,
                              inverse ? cv::THRESH_BINARY_INV : cv::THRESH_BINARY,
                              blockSize, C);
        return out;
    }

    cv::Mat canny(const cv::Mat &image, double threshold1, double threshold2,
                  int apertureSize, bool L2gradient) {
        cv::Mat gray = ensureGray(image);
        if (gray.depth() != CV_8U) {
            gray = to8U(gray, true);
        }
        apertureSize = makeOddKernel(apertureSize, 3);
        cv::Mat out;
        cv::Canny(gray, out, threshold1, threshold2, apertureSize, L2gradient);
        return out;
    }

    cv::Mat morph(const cv::Mat &image, MorphType type, int kernelSize, int iterations,
                  int shape) {
        assertNotEmpty(image, "image");
        kernelSize = makeOddKernel(kernelSize);
        iterations = std::max(1, iterations);
        cv::Mat kernel = cv::getStructuringElement(
            shape, cv::Size(kernelSize, kernelSize));
        cv::Mat out;

        switch (type) {
            case MorphType::Erode:
                cv::erode(image, out, kernel, cv::Point(-1, -1), iterations);
                break;
            case MorphType::Dilate:
                cv::dilate(image, out, kernel, cv::Point(-1, -1), iterations);
                break;
            case MorphType::Open:
                cv::morphologyEx(image, out, cv::MORPH_OPEN, kernel, cv::Point(-1, -1),
                                 iterations);
                break;
            case MorphType::Close:
                cv::morphologyEx(image, out, cv::MORPH_CLOSE, kernel, cv::Point(-1, -1),
                                 iterations);
                break;
            case MorphType::Gradient:
                cv::morphologyEx(image, out, cv::MORPH_GRADIENT, kernel,
                                 cv::Point(-1, -1), iterations);
                break;
            case MorphType::TopHat:
                cv::morphologyEx(image, out, cv::MORPH_TOPHAT, kernel, cv::Point(-1, -1),
                                 iterations);
                break;
            case MorphType::BlackHat:
                cv::morphologyEx(image, out, cv::MORPH_BLACKHAT, kernel,
                                 cv::Point(-1, -1), iterations);
                break;
            case MorphType::HitMiss: {
                cv::Mat bin = toBinary8U(image);
                cv::morphologyEx(bin, out, cv::MORPH_HITMISS, kernel, cv::Point(-1, -1),
                                 iterations);
                break;
            }
        }
        return out;
    }

    cv::Mat erodeImage(const cv::Mat &image, int kernelSize, int iterations, int shape) {
        return morph(image, MorphType::Erode, kernelSize, iterations, shape);
    }

    cv::Mat dilateImage(const cv::Mat &image, int kernelSize, int iterations, int shape) {
        return morph(image, MorphType::Dilate, kernelSize, iterations, shape);
    }

    cv::Mat fillHoles(const cv::Mat &binaryImage) {
        cv::Mat binary = toBinary8U(binaryImage);

        // 从外边界洪泛填充背景，再取反得到内部孔洞。
        cv::Mat padded;
        cv::copyMakeBorder(binary, padded, 1, 1, 1, 1, cv::BORDER_CONSTANT,
                           cv::Scalar(0));
        cv::floodFill(padded, cv::Point(0, 0), cv::Scalar(255));
        cv::Mat flooded = padded(cv::Rect(1, 1, binary.cols, binary.rows));

        cv::Mat holes;
        cv::bitwise_not(flooded, holes);
        cv::Mat filled;
        cv::bitwise_or(binary, holes, filled);
        return filled;
    }

    cv::Mat distanceTransformBinary(const cv::Mat &binaryImage, int distanceType,
                                    int maskSize) {
        cv::Mat binary = toBinary8U(binaryImage);
        cv::Mat dist;
        cv::distanceTransform(binary, dist, distanceType, maskSize);
        return dist;
    }

    // =============================================================================
    // 7. 轮廓、连通域与几何度量
    // =============================================================================

    std::vector<ContourInfo> findContoursInfo(const cv::Mat &binaryImage,
                                              int retrievalMode,
                                              int approximation,
                                              double minArea,
                                              bool sortByAreaDesc) {
        cv::Mat binary = toBinary8U(binaryImage);
        std::vector<std::vector<cv::Point>> contours;
        cv::findContours(binary, contours, retrievalMode, approximation);

        std::vector<ContourInfo> infos;
        for (const auto &c: contours) {
            ContourInfo info = makeContourInfo(c);
            if (info.area >= minArea) {
                infos.push_back(info);
            }
        }

        if (sortByAreaDesc) {
            std::sort(infos.begin(), infos.end(),
                      [](const ContourInfo &a, const ContourInfo &b) {
                          return a.area > b.area;
                      });
        }
        return infos;
    }

    bool largestContour(const cv::Mat &binaryImage, ContourInfo &outInfo,
                        double minArea) {
        std::vector<ContourInfo> infos = findContoursInfo(binaryImage, cv::RETR_EXTERNAL,
                                                          cv::CHAIN_APPROX_SIMPLE,
                                                          minArea, true);
        if (infos.empty()) {
            return false;
        }
        outInfo = infos.front();
        return true;
    }

    cv::Mat drawContourInfos(const cv::Mat &image,
                             const std::vector<ContourInfo> &contours,
                             const cv::Scalar &color, int thickness,
                             bool drawBoundingBox, bool drawIndex) {
        cv::Mat out = ensureBgr(image);
        for (std::size_t i = 0; i < contours.size(); ++i) {
            cv::drawContours(
                out, std::vector<std::vector<cv::Point>>{contours[i].contour}, -1, color,
                thickness);
            if (drawBoundingBox) {
                cv::rectangle(out, contours[i].boundingBox, cv::Scalar(0, 0, 255),
                              thickness);
            }
            if (drawIndex) {
                cv::putText(out, std::to_string(i), contours[i].center,
                            cv::FONT_HERSHEY_SIMPLEX, 0.6, cv::Scalar(255, 255, 0), 2,
                            cv::LINE_AA);
            }
        }
        return out;
    }

    cv::Mat contourMask(const cv::Size &size, const std::vector<cv::Point> &contour,
                        uchar fillValue) {
        if (size.width <= 0 || size.height <= 0) {
            throw std::invalid_argument("ZCV::contourMask 尺寸无效。");
        }
        cv::Mat mask(size, CV_8UC1, cv::Scalar(0));
        cv::drawContours(mask, std::vector<std::vector<cv::Point>>{contour}, -1,
                         cv::Scalar(fillValue), cv::FILLED);
        return mask;
    }

    std::vector<ComponentInfo> connectedComponentsInfo(const cv::Mat &binaryImage,
                                                       int connectivity,
                                                       int minArea) {
        cv::Mat binary = toBinary8U(binaryImage);
        cv::Mat labels, stats, centroids;
        const int count = cv::connectedComponentsWithStats(
            binary, labels, stats, centroids, connectivity, CV_32S);

        std::vector<ComponentInfo> components;
        for (int label = 1; label < count; ++label) { // 0 是背景。
            const int area = stats.at<int>(label, cv::CC_STAT_AREA);
            if (area < minArea) {
                continue;
            }
            ComponentInfo info;
            info.label = label;
            info.area = area;
            info.boundingBox = cv::Rect(stats.at<int>(label, cv::CC_STAT_LEFT),
                                        stats.at<int>(label, cv::CC_STAT_TOP),
                                        stats.at<int>(label, cv::CC_STAT_WIDTH),
                                        stats.at<int>(label, cv::CC_STAT_HEIGHT));
            info.centroid = cv::Point2d(centroids.at<double>(label, 0),
                                        centroids.at<double>(label, 1));
            components.push_back(info);
        }
        return components;
    }

    cv::Mat drawComponents(const cv::Mat &labels, int labelCount) {
        assertNotEmpty(labels, "labels");
        if (labels.type() != CV_32S) {
            throw std::invalid_argument("ZCV::drawComponents 需要 CV_32S 标签图。");
        }

        std::vector<cv::Scalar> colors(static_cast<std::size_t>(std::max(labelCount, 1)));
        colors[0] = cv::Scalar(0, 0, 0);
        for (int i = 1; i < labelCount; ++i) {
            colors[static_cast<std::size_t>(i)] = randomColor(i);
        }

        cv::Mat out(labels.size(), CV_8UC3, cv::Scalar(0, 0, 0));
        for (int y = 0; y < labels.rows; ++y) {
            const int *row = labels.ptr<int>(y);
            cv::Vec3b *dst = out.ptr<cv::Vec3b>(y);
            for (int x = 0; x < labels.cols; ++x) {
                int label = row[x];
                if (label >= 0 && label < labelCount) {
                    cv::Scalar c = colors[static_cast<std::size_t>(label)];
                    dst[x] = cv::Vec3b(static_cast<uchar>(c[0]), static_cast<uchar>(c[1]),
                                       static_cast<uchar>(c[2]));
                }
            }
        }
        return out;
    }

    double intersectionOverUnion(const cv::Rect2f &a, const cv::Rect2f &b) {
        const float x1 = std::max(a.x, b.x);
        const float y1 = std::max(a.y, b.y);
        const float x2 = std::min(a.x + a.width, b.x + b.width);
        const float y2 = std::min(a.y + a.height, b.y + b.height);
        const float interW = std::max(0.0f, x2 - x1);
        const float interH = std::max(0.0f, y2 - y1);
        const double inter = static_cast<double>(interW) * interH;
        const double uni = static_cast<double>(a.area()) + b.area() - inter;
        return uni <= 0.0 ? 0.0 : inter / uni;
    }

    cv::Scalar randomColor(int seed, int minValue, int maxValue) {
        minValue = std::clamp(minValue, 0, 255);
        maxValue = std::clamp(maxValue, minValue, 255);
        cv::RNG rng(static_cast<uint64_t>(seed) * 0x9E3779B97F4A7C15ULL + 0x12345ULL);
        return cv::Scalar(rng.uniform(minValue, maxValue + 1),
                          rng.uniform(minValue, maxValue + 1),
                          rng.uniform(minValue, maxValue + 1));
    }

    // =============================================================================
    // 8. 特征点、描述子与匹配
    // =============================================================================

    FeatureResult detectORB(const cv::Mat &image, int nfeatures) {
        cv::Mat gray = ensureGray(image);
        FeatureResult result;
        cv::Ptr<cv::ORB> orb = cv::ORB::create(std::max(1, nfeatures));
        orb->detectAndCompute(gray, cv::noArray(), result.keypoints, result.descriptors);
        return result;
    }

    FeatureResult detectAKAZE(const cv::Mat &image) {
        cv::Mat gray = ensureGray(image);
        FeatureResult result;
        cv::Ptr<cv::AKAZE> akaze = cv::AKAZE::create();
        akaze->detectAndCompute(gray, cv::noArray(), result.keypoints,
                                result.descriptors);
        return result;
    }

#if ZCV_HAS_SIFT
    FeatureResult detectSIFT(const cv::Mat &image, int nfeatures) {
        cv::Mat gray = ensureGray(image);
        FeatureResult result;
#ifdef ZCV_SIFT_IN_XFEATURES2D
        cv::Ptr<cv::Feature2D> sift = cv::xfeatures2d::SIFT::create(
            std::max(0, nfeatures));
#else
        cv::Ptr<cv::Feature2D> sift = cv::SIFT::create(std::max(0, nfeatures));
#endif
        sift->detectAndCompute(gray, cv::noArray(), result.keypoints, result.descriptors);
        return result;
    }
#endif

    std::vector<cv::DMatch> matchDescriptors(const cv::Mat &desc1, const cv::Mat &desc2,
                                             int normType, bool crossCheck,
                                             double ratio, int maxMatches) {
        std::vector<cv::DMatch> good;
        if (desc1.empty() || desc2.empty()) {
            return good;
        }

        if (normType < 0) {
            normType = (desc1.depth() == CV_8U) ? cv::NORM_HAMMING : cv::NORM_L2;
        }

        if (ratio > 0.0 && ratio < 1.0 && !crossCheck) {
            cv::BFMatcher matcher(normType, false);
            std::vector<std::vector<cv::DMatch>> knn;
            matcher.knnMatch(desc1, desc2, knn, 2);
            for (const auto &pair: knn) {
                if (pair.size() == 2 && pair[0].distance < ratio * pair[1].distance) {
                    good.push_back(pair[0]);
                }
            }
        } else {
            cv::BFMatcher matcher(normType, crossCheck);
            matcher.match(desc1, desc2, good);
        }

        std::sort(good.begin(), good.end(), [](const cv::DMatch &a, const cv::DMatch &b) {
            return a.distance < b.distance;
        });

        if (maxMatches > 0 && static_cast<int>(good.size()) > maxMatches) {
            good.resize(static_cast<std::size_t>(maxMatches));
        }
        return good;
    }

    MatchResult matchORB(const cv::Mat &image1, const cv::Mat &image2,
                         int nfeatures, double ratio,
                         bool estimateHomography, double ransacReprojThreshold) {
        MatchResult result;
        result.first = detectORB(image1, nfeatures);
        result.second = detectORB(image2, nfeatures);
        result.matches = matchDescriptors(result.first.descriptors,
                                          result.second.descriptors,
                                          cv::NORM_HAMMING, false, ratio, 0);

        if (estimateHomography && result.matches.size() >= 4) {
            std::vector<cv::Point2f> pts1, pts2;
            pts1.reserve(result.matches.size());
            pts2.reserve(result.matches.size());
            for (const auto &m: result.matches) {
                pts1.push_back(
                    result.first.keypoints[static_cast<std::size_t>(m.queryIdx)].pt);
                pts2.push_back(
                    result.second.keypoints[static_cast<std::size_t>(m.trainIdx)].pt);
            }
            cv::Mat mask;
            result.homography = cv::findHomography(pts1, pts2, cv::RANSAC,
                                                   ransacReprojThreshold, mask);
            if (!mask.empty()) {
                result.inlierMask.resize(static_cast<std::size_t>(mask.rows));
                result.inlierCount = 0;
                for (int i = 0; i < mask.rows; ++i) {
                    const bool inlier = mask.at<uchar>(i, 0) != 0;
                    result.inlierMask[static_cast<std::size_t>(i)] = static_cast<char>(
                        inlier);
                    if (inlier) {
                        ++result.inlierCount;
                    }
                }
            }
        }
        return result;
    }

    cv::Mat drawMatchResult(const cv::Mat &image1, const cv::Mat &image2,
                            const MatchResult &result, bool onlyInliers) {
        cv::Mat out;
        const std::vector<char> emptyMask;
        const std::vector<char> &mask = (onlyInliers && !result.inlierMask.empty()) ?
                                            result.inlierMask : emptyMask;
        cv::drawMatches(image1, result.first.keypoints,
                        image2, result.second.keypoints,
                        result.matches, out,
                        cv::Scalar::all(-1), cv::Scalar::all(-1),
                        mask,
                        cv::DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
        return out;
    }

    // =============================================================================
    // 9. 相机标定、畸变校正与传统目标检测
    // =============================================================================

    ChessboardResult findChessboard(const cv::Mat &image, cv::Size boardSize,
                                    bool refineSubPix, int flags) {
        assertNotEmpty(image, "image");
        if (boardSize.width <= 0 || boardSize.height <= 0) {
            throw std::invalid_argument("ZCV::findChessboard boardSize 无效。");
        }

        ChessboardResult result;
        result.boardSize = boardSize;
        cv::Mat gray = ensureGray(image);
        result.found = cv::findChessboardCorners(gray, boardSize, result.corners, flags);

        if (result.found && refineSubPix) {
            cv::cornerSubPix(gray, result.corners, cv::Size(11, 11), cv::Size(-1, -1),
                             cv::TermCriteria(
                                 cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 30,
                                 0.001));
        }

        result.debugImage = ensureBgr(image);
        cv::drawChessboardCorners(result.debugImage, boardSize, result.corners,
                                  result.found);
        return result;
    }

    CalibrationResult calibrateCameraFromChessboards(const std::vector<cv::Mat> &images,
                                                     cv::Size boardSize,
                                                     float squareSize,
                                                     int flags) {
        if (images.empty()) {
            throw std::invalid_argument("ZCV::calibrateCameraFromChessboards 图像列表为空。");
        }
        if (squareSize <= 0.0f) {
            throw std::invalid_argument(
                "ZCV::calibrateCameraFromChessboards squareSize 必须为正数。");
        }

        std::vector<cv::Point3f> objectTemplate;
        for (int y = 0; y < boardSize.height; ++y) {
            for (int x = 0; x < boardSize.width; ++x) {
                objectTemplate.emplace_back(x * squareSize, y * squareSize, 0.0f);
            }
        }

        std::vector<std::vector<cv::Point3f>> objectPoints;
        std::vector<std::vector<cv::Point2f>> imagePoints;
        cv::Size imageSize;

        for (const auto &img: images) {
            if (img.empty()) {
                continue;
            }
            imageSize = img.size();
            ChessboardResult found = findChessboard(img, boardSize, true);
            if (found.found) {
                objectPoints.push_back(objectTemplate);
                imagePoints.push_back(found.corners);
            }
        }

        if (objectPoints.empty()) {
            throw std::runtime_error("ZCV::calibrateCameraFromChessboards 未检测到可用棋盘格。");
        }

        CalibrationResult result;
        result.cameraMatrix = cv::Mat::eye(3, 3, CV_64F);
        result.distCoeffs = cv::Mat::zeros(8, 1, CV_64F);
        result.reprojectionError = cv::calibrateCamera(
            objectPoints, imagePoints, imageSize,
            result.cameraMatrix, result.distCoeffs,
            result.rvecs, result.tvecs, flags);
        result.usedImageCount = objectPoints.size();
        result.ok = true;
        return result;
    }

    CalibrationResult calibrateCameraFromChessboardFiles(
        const std::vector<std::string> &imagePaths,
        cv::Size boardSize,
        float squareSize,
        int flags) {
        std::vector<cv::Mat> images = readImages(imagePaths, cv::IMREAD_COLOR, true);
        return calibrateCameraFromChessboards(images, boardSize, squareSize, flags);
    }

    cv::Mat undistortImage(const cv::Mat &image, const cv::Mat &cameraMatrix,
                           const cv::Mat &distCoeffs, double alpha) {
        assertNotEmpty(image, "image");
        assertNotEmpty(cameraMatrix, "cameraMatrix");
        assertNotEmpty(distCoeffs, "distCoeffs");
        cv::Mat newCamera = cv::getOptimalNewCameraMatrix(
            cameraMatrix, distCoeffs, image.size(), alpha, image.size());
        cv::Mat out;
        cv::undistort(image, out, cameraMatrix, distCoeffs, newCamera);
        return out;
    }

    std::vector<cv::Rect> detectHaar(const cv::Mat &image, const std::string &cascadePath,
                                     double scaleFactor, int minNeighbors,
                                     cv::Size minSize, cv::Size maxSize) {
        assertNotEmpty(image, "image");
        cv::CascadeClassifier cascade;
        if (!cascade.load(cascadePath)) {
            throw std::runtime_error("ZCV::detectHaar 无法加载级联分类器: " + cascadePath);
        }

        cv::Mat gray = ensureGray(image);
        if (gray.depth() != CV_8U) {
            gray = to8U(gray, true);
        }
        cv::equalizeHist(gray, gray);

        std::vector<cv::Rect> rects;
        cascade.detectMultiScale(gray, rects, scaleFactor, minNeighbors, 0, minSize,
                                 maxSize);
        return rects;
    }

    cv::Mat drawRects(const cv::Mat &image, const std::vector<cv::Rect> &rects,
                      const cv::Scalar &color, int thickness) {
        cv::Mat out = ensureBgr(image);
        for (const auto &r: rects) {
            cv::rectangle(out, r, color, thickness);
        }
        return out;
    }

    // =============================================================================
    // 10. DNN
    // =============================================================================

    cv::dnn::Net loadDnnNet(const std::string &modelPath,
                            const std::string &configPath,
                            const std::string &framework,
                            int backend,
                            int target) {
        cv::dnn::Net net;
        if (configPath.empty() && framework.empty()) {
            net = cv::dnn::readNet(modelPath);
        } else {
            net = cv::dnn::readNet(modelPath, configPath, framework);
        }

        if (net.empty()) {
            throw std::runtime_error("ZCV::loadDnnNet 模型加载失败: " + modelPath);
        }
        net.setPreferableBackend(backend);
        net.setPreferableTarget(target);
        return net;
    }

    cv::Mat makeBlob(const cv::Mat &image, const DnnOptions &options) {
        assertNotEmpty(image, "image");
        return cv::dnn::blobFromImage(image,
                                      options.scale,
                                      options.inputSize,
                                      options.mean,
                                      options.swapRB,
                                      options.crop,
                                      options.ddepth);
    }

    DnnForwardResult forwardDnn(cv::dnn::Net &net, const cv::Mat &image,
                                const DnnOptions &options,
                                const std::vector<std::string> &outputNames) {
        if (net.empty()) {
            throw std::invalid_argument("ZCV::forwardDnn net 为空。");
        }

        cv::Mat blob = makeBlob(image, options);
        net.setPreferableBackend(options.backend);
        net.setPreferableTarget(options.target);
        net.setInput(blob, options.inputName);

        TickTimer timer;
        timer.start();
        DnnForwardResult result;
        if (outputNames.empty()) {
            net.forward(result.outputs, net.getUnconnectedOutLayersNames());
        } else {
            net.forward(result.outputs, outputNames);
        }
        result.inferenceMs = timer.stopMs();
        return result;
    }

    std::vector<ClassScore> topK(const cv::Mat &scores, int k) {
        assertNotEmpty(scores, "scores");
        k = std::max(1, k);

        cv::Mat flat = scores.reshape(1, 1);
        cv::Mat f;
        flat.convertTo(f, CV_32F);

        std::vector<ClassScore> values;
        values.reserve(static_cast<std::size_t>(f.cols));
        for (int i = 0; i < f.cols; ++i) {
            values.push_back({i, f.at<float>(0, i)});
        }

        const int n = std::min(k, static_cast<int>(values.size()));
        std::partial_sort(values.begin(), values.begin() + n, values.end(),
                          [](const ClassScore &a, const ClassScore &b) {
                              return a.score > b.score;
                          });
        values.resize(static_cast<std::size_t>(n));
        return values;
    }

    std::vector<int> nmsBoxes(const std::vector<cv::Rect> &boxes,
                              const std::vector<float> &confidences,
                              float scoreThreshold,
                              float nmsThreshold,
                              float eta,
                              int topKValue) {
        std::vector<int> indices;
        cv::dnn::NMSBoxes(boxes, confidences, scoreThreshold, nmsThreshold, indices, eta,
                          topKValue);
        return indices;
    }

    // =============================================================================
    // 11. 视频读取、写入与帧抽取
    // =============================================================================

    VideoInfo getCaptureInfo(cv::VideoCapture &cap) {
        VideoInfo info;
        info.opened = cap.isOpened();
        if (!info.opened) {
            return info;
        }

        info.width = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_WIDTH));
        info.height = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_HEIGHT));
        info.fps = cap.get(cv::CAP_PROP_FPS);
        info.frameCount = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_COUNT));
        if (info.fps > 0.0 && info.frameCount > 0) {
            info.durationSec = info.frameCount / info.fps;
        }
        try {
            info.backendName = cap.getBackendName();
        } catch (const cv::Exception &) {
            info.backendName.clear();
        }
        return info;
    }

    cv::VideoCapture openVideo(const std::string &path, int apiPreference,
                               bool throwIfFailed) {
        cv::VideoCapture cap(path, apiPreference);
        if (!cap.isOpened() && throwIfFailed) {
            throw std::runtime_error("ZCV::openVideo 打开视频失败: " + path);
        }
        return cap;
    }

    cv::VideoCapture openCamera(int index, int width, int height, double fps,
                                int apiPreference, bool throwIfFailed) {
        cv::VideoCapture cap(index, apiPreference);
        if (!cap.isOpened()) {
            if (throwIfFailed) {
                throw std::runtime_error(
                    "ZCV::openCamera 打开摄像头失败，index=" + std::to_string(index));
            }
            return cap;
        }
        if (width > 0) {
            cap.set(cv::CAP_PROP_FRAME_WIDTH, width);
        }
        if (height > 0) {
            cap.set(cv::CAP_PROP_FRAME_HEIGHT, height);
        }
        if (fps > 0.0) {
            cap.set(cv::CAP_PROP_FPS, fps);
        }
        return cap;
    }

    bool readFrameAt(const std::string &videoPath, int frameIndex, cv::Mat &frame,
                     int apiPreference) {
        frame.release();
        if (frameIndex < 0) {
            return false;
        }
        cv::VideoCapture cap(videoPath, apiPreference);
        if (!cap.isOpened()) {
            return false;
        }
        cap.set(cv::CAP_PROP_POS_FRAMES, frameIndex);
        return cap.read(frame) && !frame.empty();
    }

    cv::VideoWriter createVideoWriter(const std::string &path, cv::Size frameSize,
                                      double fps,
                                      const std::string &fourcc, bool isColor,
                                      int apiPreference) {
        if (frameSize.width <= 0 || frameSize.height <= 0) {
            throw std::invalid_argument("ZCV::createVideoWriter frameSize 无效。");
        }
        if (fps <= 0.0) {
            throw std::invalid_argument("ZCV::createVideoWriter fps 必须为正数。");
        }

        cv::VideoWriter writer;
        writer.open(path, apiPreference, fourccFromString(fourcc), fps, frameSize,
                    isColor);
        if (!writer.isOpened()) {
            throw std::runtime_error("ZCV::createVideoWriter 打开输出视频失败: " + path);
        }
        return writer;
    }

    bool writeVideoFromFrames(const std::string &path, const std::vector<cv::Mat> &frames,
                              double fps, const std::string &fourcc, bool isColor) {
        if (frames.empty()) {
            return false;
        }
        cv::Size size = frames.front().size();
        cv::VideoWriter writer = createVideoWriter(path, size, fps, fourcc, isColor);

        for (const auto &frame: frames) {
            if (frame.empty()) {
                continue;
            }
            cv::Mat prepared = isColor ? ensureBgr(frame) : ensureGray(frame);
            if (prepared.size() != size) {
                cv::resize(prepared, prepared, size);
            }
            writer.write(prepared);
        }
        writer.release();
        return true;
    }

    std::vector<cv::Mat> readAllFrames(const std::string &videoPath, int maxFrames,
                                       int step, int apiPreference) {
        step = std::max(1, step);
        std::vector<cv::Mat> frames;
        cv::VideoCapture cap = openVideo(videoPath, apiPreference, true);
        cv::Mat frame;
        int index = 0;
        while (cap.read(frame)) {
            if (index % step == 0) {
                frames.push_back(frame.clone());
                if (maxFrames > 0 && static_cast<int>(frames.size()) >= maxFrames) {
                    break;
                }
            }
            ++index;
        }
        return frames;
    }

    int extractFrames(const std::string &videoPath, const std::string &outputPattern,
                      int step, int maxFrames, int startFrame, int apiPreference) {
        step = std::max(1, step);
        cv::VideoCapture cap = openVideo(videoPath, apiPreference, true);
        if (startFrame > 0) {
            cap.set(cv::CAP_PROP_POS_FRAMES, startFrame);
        }

        cv::Mat frame;
        int current = startFrame;
        int saved = 0;
        while (cap.read(frame)) {
            if ((current - startFrame) % step == 0) {
                std::string path = cv::format(outputPattern.c_str(), saved);
                if (!cv::imwrite(path, frame)) {
                    throw std::runtime_error("ZCV::extractFrames 保存帧失败: " + path);
                }
                ++saved;
                if (maxFrames > 0 && saved >= maxFrames) {
                    break;
                }
            }
            ++current;
        }
        return saved;
    }

    // =============================================================================
    // 12. 二维码、条码、ArUco 与绘制辅助
    // =============================================================================

    QRCodeResult detectAndDecodeQRCode(const cv::Mat &image) {
        assertNotEmpty(image, "image");
        cv::QRCodeDetector detector;
        cv::Mat points;
        QRCodeResult result;
        result.text = detector.detectAndDecode(image, points, result.straight);
        result.success = !result.text.empty();
        result.points = matToPointList(points);
        return result;
    }

    cv::Mat drawQRCodeResult(const cv::Mat &image, const QRCodeResult &result,
                             const cv::Scalar &color) {
        cv::Mat out = ensureBgr(image);
        if (result.points.size() >= 4) {
            for (int i = 0; i < 4; ++i) {
                cv::line(out, result.points[static_cast<std::size_t>(i)],
                         result.points[static_cast<std::size_t>((i + 1) % 4)], color, 2,
                         cv::LINE_AA);
            }
            if (!result.text.empty()) {
                drawLabel(out, result.text,
                          cv::Point(cvRound(result.points[0].x),
                                    cvRound(result.points[0].y - 8.0f)));
            }
        }
        return out;
    }

#if ZCV_HAS_BARCODE
    std::vector<BarcodeResult> detectAndDecodeBarcodes(const cv::Mat &image,
                                                       const std::string &srPrototxt,
                                                       const std::string &srModel) {
        assertNotEmpty(image, "image");
        cv::barcode::BarcodeDetector detector = (srPrototxt.empty() || srModel.empty())
                                                    ? cv::barcode::BarcodeDetector()
                                                    : cv::barcode::BarcodeDetector(
                                                        srPrototxt, srModel);

        std::vector<std::string> decodedInfo;
        std::vector<std::string> decodedType;
        cv::Mat points;
        detector.detectAndDecodeWithType(image, decodedInfo, decodedType, points);

        std::vector<std::vector<cv::Point2f>> quads = splitQuadrangles(points);
        std::vector<BarcodeResult> results;
        const std::size_t n = decodedInfo.size();
        results.reserve(n);
        for (std::size_t i = 0; i < n; ++i) {
            BarcodeResult r;
            r.text = decodedInfo[i];
            r.type = i < decodedType.size() ? decodedType[i] : std::string();
            r.success = !r.text.empty();
            if (i < quads.size()) {
                r.points = quads[i];
            }
            results.push_back(r);
        }
        return results;
    }

    cv::Mat drawBarcodeResults(const cv::Mat &image,
                               const std::vector<BarcodeResult> &results,
                               const cv::Scalar &color) {
        cv::Mat out = ensureBgr(image);
        for (const auto &r: results) {
            if (r.points.size() >= 4) {
                for (int i = 0; i < 4; ++i) {
                    cv::line(out, r.points[static_cast<std::size_t>(i)],
                             r.points[static_cast<std::size_t>((i + 1) % 4)], color, 2,
                             cv::LINE_AA);
                }
                std::string label = r.type.empty() ? r.text : (r.type + ": " + r.text);
                if (!label.empty()) {
                    drawLabel(out, label,
                              cv::Point(cvRound(r.points[0].x),
                                        cvRound(r.points[0].y - 8.0f)));
                }
            }
        }
        return out;
    }
#endif

#if ZCV_HAS_ARUCO
    ArucoResult detectArucoMarkers(const cv::Mat &image, int dictionaryId,
                                   bool refineCorners) {
        assertNotEmpty(image, "image");
        ArucoResult result;

#if (CV_VERSION_MAJOR > 4) || (CV_VERSION_MAJOR == 4 && CV_VERSION_MINOR >= 7)
        cv::aruco::Dictionary dictionary = cv::aruco::getPredefinedDictionary(
            dictionaryId);
        cv::aruco::DetectorParameters params;
        if (refineCorners) {
            params.cornerRefinementMethod = cv::aruco::CORNER_REFINE_SUBPIX;
        }
        cv::aruco::ArucoDetector detector(dictionary, params);
        detector.detectMarkers(image, result.corners, result.ids, result.rejected);
#else
        cv::Ptr<cv::aruco::Dictionary> dictionary = cv::aruco::getPredefinedDictionary(
            dictionaryId);
        cv::Ptr<cv::aruco::DetectorParameters> params =
                cv::aruco::DetectorParameters::create();
        if (refineCorners) {
            params->cornerRefinementMethod = cv::aruco::CORNER_REFINE_SUBPIX;
        }
        cv::aruco::detectMarkers(image, dictionary, result.corners, result.ids, params,
                                 result.rejected);
#endif

        return result;
    }

    cv::Mat drawArucoResult(const cv::Mat &image, const ArucoResult &result,
                            const cv::Scalar &color) {
        cv::Mat out = ensureBgr(image);
        if (!result.corners.empty()) {
            cv::aruco::drawDetectedMarkers(out, result.corners, result.ids, color);
        }
        return out;
    }

    cv::Mat generateArucoMarker(int markerId, int sidePixels, int dictionaryId,
                                int borderBits) {
        if (sidePixels <= 0) {
            throw std::invalid_argument("ZCV::generateArucoMarker sidePixels 必须为正数。");
        }
        cv::Mat marker;
#if (CV_VERSION_MAJOR > 4) || (CV_VERSION_MAJOR == 4 && CV_VERSION_MINOR >= 7)
        cv::aruco::Dictionary dictionary = cv::aruco::getPredefinedDictionary(
            dictionaryId);
        cv::aruco::generateImageMarker(dictionary, markerId, sidePixels, marker,
                                       borderBits);
#else
        cv::Ptr<cv::aruco::Dictionary> dictionary = cv::aruco::getPredefinedDictionary(
            dictionaryId);
        cv::aruco::drawMarker(dictionary, markerId, sidePixels, marker, borderBits);
#endif
        return marker;
    }
#endif

    void drawLabel(cv::Mat &image, const std::string &text, cv::Point origin,
                   double fontScale, const cv::Scalar &textColor,
                   const cv::Scalar &bgColor, int thickness) {
        if (image.empty() || text.empty()) {
            return;
        }
        if (image.channels() == 1) {
            cv::Mat bgr;
            cv::cvtColor(image, bgr, cv::COLOR_GRAY2BGR);
            image = bgr;
        }

        int baseline = 0;
        cv::Size textSize = cv::getTextSize(text, cv::FONT_HERSHEY_SIMPLEX, fontScale,
                                            thickness, &baseline);
        origin.x = std::clamp(origin.x, 0, std::max(0, image.cols - 1));
        origin.y = std::clamp(origin.y, textSize.height + 2,
                              std::max(textSize.height + 2, image.rows - 1));

        cv::Rect bg(origin.x,
                    origin.y - textSize.height - baseline - 4,
                    textSize.width + 6,
                    textSize.height + baseline + 6);
        bg = clampRect(bg, image.size());
        if (bg.area() > 0) {
            cv::rectangle(image, bg, bgColor, cv::FILLED);
        }
        cv::putText(image, text, cv::Point(origin.x + 3, origin.y - 3),
                    cv::FONT_HERSHEY_SIMPLEX, fontScale, textColor, thickness,
                    cv::LINE_AA);
    }

    void drawRotatedRect(cv::Mat &image, const cv::RotatedRect &rect,
                         const cv::Scalar &color, int thickness) {
        if (image.empty()) {
            return;
        }
        cv::Point2f pts[4];
        rect.points(pts);
        for (int i = 0; i < 4; ++i) {
            cv::line(image, pts[i], pts[(i + 1) % 4], color, thickness, cv::LINE_AA);
        }
    }

    void drawCrosshair(cv::Mat &image, cv::Point center, int radius,
                       const cv::Scalar &color, int thickness) {
        if (image.empty()) {
            return;
        }
        cv::line(image, cv::Point(center.x - radius, center.y),
                 cv::Point(center.x + radius, center.y),
                 color, thickness, cv::LINE_AA);
        cv::line(image, cv::Point(center.x, center.y - radius),
                 cv::Point(center.x, center.y + radius),
                 color, thickness, cv::LINE_AA);
        cv::circle(image, center, radius, color, thickness, cv::LINE_AA);
    }

    cv::Mat drawGridOverlay(const cv::Mat &image, int gridSize, const cv::Scalar &color,
                            int thickness) {
        cv::Mat out = ensureBgr(image);
        gridSize = std::max(1, gridSize);
        for (int x = 0; x < out.cols; x += gridSize) {
            cv::line(out, cv::Point(x, 0), cv::Point(x, out.rows - 1), color, thickness,
                     cv::LINE_AA);
        }
        for (int y = 0; y < out.rows; y += gridSize) {
            cv::line(out, cv::Point(0, y), cv::Point(out.cols - 1, y), color, thickness,
                     cv::LINE_AA);
        }
        return out;
    }

} // namespace ZCV
