From patchwork Tue Sep 30 12:26:49 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Klug X-Patchwork-Id: 24527 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 0BC29C328C for ; Tue, 30 Sep 2025 13:32:02 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id ABB676B5F0; Tue, 30 Sep 2025 15:32:01 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Ox2pzvk+"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 366626B599 for ; Tue, 30 Sep 2025 15:31:59 +0200 (CEST) Received: from ideasonboard.com (unknown [94.31.94.171]) by perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id F3C7E16A; Tue, 30 Sep 2025 15:30:30 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1759239031; bh=ik1IurfUDpB0uQeOKgQlWDtvXgq2Tn5bYtZcu9frmRo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Ox2pzvk+ubytkKJwdyZKMvX2oidZxP3+ICCJggwaPa3hN9hrXQPxojB+Fc5ItlNaf CIuQqZfPtQpHQ+zhrTWxp4NHItZhi7VIMmVJ1jSrWT7qulHidhbV910+urTX6zPe5l 3jcXKsubXpCBBb1alPXnMLfnrUurH1uI0reHry5k= From: Stefan Klug To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug Subject: [PATCH v1 28/33] libcamera: dw100_vertexmap: Implement parametric dewarping Date: Tue, 30 Sep 2025 14:26:49 +0200 Message-ID: <20250930122726.1837524-29-stefan.klug@ideasonboard.com> X-Mailer: git-send-email 2.48.1 In-Reply-To: <20250930122726.1837524-1-stefan.klug@ideasonboard.com> References: <20250930122726.1837524-1-stefan.klug@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Implement functions to allow lens dewarping based on the common lens dewarp model used e.g. by OpenCV See https://docs.opencv.org/3.4/da/d54/group__imgproc__transform.html#ga7dfb72c9cf9780a347fbe3d1c47e5d5a Signed-off-by: Stefan Klug --- .../converter/converter_dw100_vertexmap.h | 17 ++++ .../converter/converter_dw100_vertexmap.cpp | 87 +++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/include/libcamera/internal/converter/converter_dw100_vertexmap.h b/include/libcamera/internal/converter/converter_dw100_vertexmap.h index 9c6e0ffa4513..7a12c4cb7a50 100644 --- a/include/libcamera/internal/converter/converter_dw100_vertexmap.h +++ b/include/libcamera/internal/converter/converter_dw100_vertexmap.h @@ -12,6 +12,9 @@ #include #include +#include "libcamera/internal/matrix.h" +#include "libcamera/internal/vector.h" + namespace libcamera { class Dw100VertexMap @@ -57,11 +60,20 @@ public: void setMode(const ScaleMode mode) { mode_ = mode; } ScaleMode mode() const { return mode_; } + int loadDewarpParams(const YamlObject &dict); + int setDewarpParams(const Matrix &cm, const Span &coeffs); + bool dewarpParamsValid() { return dewarpParamsValid_; } + + void setLensDewarpEnable(bool enable) { lensDewarpEnable_ = enable; } + bool lensDewarpEnable() { return lensDewarpEnable_; } + std::vector getVertexMap(); private: int getVerticesForLength(const int length); + Vector dewarpPoint(const Vector &p); + Rectangle scalerCrop_; Rectangle sensorCrop_; Transform transform_ = Transform::Identity; @@ -75,6 +87,11 @@ private: double effectiveScaleY_; Point effectiveOffset_; Rectangle effectiveScalerCrop_; + + Matrix dewarpM_ = Matrix::identity(); + std::array dewarpCoeffs_; + bool lensDewarpEnable_ = true; + bool dewarpParamsValid_ = false; }; } /* namespace libcamera */ diff --git a/src/libcamera/converter/converter_dw100_vertexmap.cpp b/src/libcamera/converter/converter_dw100_vertexmap.cpp index 10d9e34d98c5..5581d53e57c7 100644 --- a/src/libcamera/converter/converter_dw100_vertexmap.cpp +++ b/src/libcamera/converter/converter_dw100_vertexmap.cpp @@ -66,6 +66,14 @@ Vector2d point2Vec2d(const Point &p) return { { static_cast(p.x), static_cast(p.y) } }; } +void transformPoint(const Rectangle &from, const Rectangle &to, Vector2d &p) +{ + double sx = to.width / static_cast(from.width); + double sy = to.height / static_cast(from.height); + p.x() = (p.x() - from.x) * sx + to.x; + p.y() = (p.y() - from.y) * sy + to.y; +} + } /* namespace */ /** @@ -344,6 +352,17 @@ std::vector Dw100VertexMap::getVertexMap() p.x() += localScalerCrop.x; p.y() += localScalerCrop.y; + if (dewarpParamsValid_ && lensDewarpEnable_) { + /* + * We are in localScalerCrop coordinates. + * Tranform to sensor coordinates. + */ + transformPoint(localScalerCrop, effectiveScalerCrop_, p); + p = dewarpPoint(p); + /* tarnsform back to localScalerCrop */ + transformPoint(effectiveScalerCrop_, localScalerCrop, p); + } + /* Convert to fixed point */ uint32_t v = static_cast(p.y() * 16) << 16 | (static_cast(p.x() * 16) & 0xffff); @@ -354,6 +373,74 @@ std::vector Dw100VertexMap::getVertexMap() return res; } +int Dw100VertexMap::loadDewarpParams(const YamlObject &dict) +{ + Matrix m; + const auto &cm = dict["cm"].get>(); + if (!cm) { + LOG(Converter, Error) << "Dewarp parameters are missing 'cm' value"; + return -EINVAL; + } + + m = *cm; + + const auto &coeffs = dict["coefficients"].getList(); + if (!coeffs) { + LOG(Converter, Error) << "Dewarp parameters 'coefficients' value is not a list"; + return -EINVAL; + } + + const Span span{ *coeffs }; + return setDewarpParams(m, span); +} + +int Dw100VertexMap::setDewarpParams(const Matrix &cm, const Span &coeffs) +{ + dewarpM_ = cm; + dewarpCoeffs_.fill(0.0); + + if (coeffs.size() != 4 && coeffs.size() != 5 && + coeffs.size() != 8 && coeffs.size() != 12) { + LOG(Converter, Error) << "Dewarp 'coefficients' must have 4, 5, 8 or 12 values"; + dewarpParamsValid_ = false; + return -EINVAL; + } + std::copy(coeffs.begin(), coeffs.end(), dewarpCoeffs_.begin()); + + dewarpParamsValid_ = true; + return 0; +} + +Vector2d Dw100VertexMap::dewarpPoint(const Vector2d &p) +{ + double x, y; + double k1 = dewarpCoeffs_[0]; + double k2 = dewarpCoeffs_[1]; + double p1 = dewarpCoeffs_[2]; + double p2 = dewarpCoeffs_[3]; + double k3 = dewarpCoeffs_[4]; + double k4 = dewarpCoeffs_[5]; + double k5 = dewarpCoeffs_[6]; + double k6 = dewarpCoeffs_[7]; + double s1 = dewarpCoeffs_[8]; + double s2 = dewarpCoeffs_[9]; + double s3 = dewarpCoeffs_[10]; + double s4 = dewarpCoeffs_[11]; + + y = (p.y() - dewarpM_[1][2]) / dewarpM_[1][1]; + x = (p.x() - dewarpM_[0][2] - y * dewarpM_[0][1]) / dewarpM_[0][0]; + + double r2 = x * x + y * y; + double d = (1 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2) / + (1 + k4 * r2 + k5 * r2 * r2 + k6 * r2 * r2 * r2); + x = x * d + 2 * p1 * x * y + p2 * (r2 + 2 * x * x) + s1 * r2 + s2 * r2 * r2; + y = y * d + 2 * p2 * x * y + p1 * (r2 + 2 * y * y) + s3 * r2 + s4 * r2 * r2; + + Vector2d ret{ { x * dewarpM_[0][0] + y * dewarpM_[0][1] + dewarpM_[0][2], + y * dewarpM_[1][1] + dewarpM_[1][2] } }; + return ret; +} + int Dw100VertexMap::getVerticesForLength(const int length) { return (length + kDw100BlockSize - 1) / kDw100BlockSize + 1;