From patchwork Tue Nov 25 16:28:36 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Klug X-Patchwork-Id: 25203 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 C5784C3338 for ; Tue, 25 Nov 2025 16:30:08 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7815460AA9; Tue, 25 Nov 2025 17:30:08 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="UyOYPc9l"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id BD4CB60A9E for ; Tue, 25 Nov 2025 17:30:06 +0100 (CET) Received: from ideasonboard.com (unknown [IPv6:2a00:6020:448c:6c00:bae1:340c:573c:570b]) by perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id B8ACC13DE; Tue, 25 Nov 2025 17:27:57 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1764088077; bh=kpwkVK/+OFcGiXaPGaOG/FeTA2AsiOHaMDK2yIySh4M=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=UyOYPc9luX0ZcI3MqVhGqb4fFfAp0noiptR050mGGz8BAz6YlBh01NpgzRZtfO+ZG qTwfnYfLnDnqphl7Ypd7pQdWjV3dHkIDvl73awMmWp3Vm6vzSG+xNI/28DCOfoMBWw blGtqba9acDyOxm4I8/7umtiH1AACpzURohXsfdo= From: Stefan Klug To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug , Paul Elder Subject: [PATCH v3 24/29] libcamera: dw100_vertexmap: Implement parametric dewarping Date: Tue, 25 Nov 2025 17:28:36 +0100 Message-ID: <20251125162851.2301793-25-stefan.klug@ideasonboard.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251125162851.2301793-1-stefan.klug@ideasonboard.com> References: <20251125162851.2301793-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/4.12.0/d9/d0c/group__calib3d.html for an in depth explanation of the parameters. Signed-off-by: Stefan Klug Reviewed-by: Paul Elder Reviewed-by: Kieran Bingham --- Changes in v3: - Fixed typo in docs - Collected tag Changes in v2: - Adapt to vertex map implementation based on affine transforms - Improved documentation - dropped loadDewarpParams function as it was never used --- .../converter/converter_dw100_vertexmap.h | 16 +++ .../converter/converter_dw100_vertexmap.cpp | 98 +++++++++++++++++-- 2 files changed, 108 insertions(+), 6 deletions(-) diff --git a/include/libcamera/internal/converter/converter_dw100_vertexmap.h b/include/libcamera/internal/converter/converter_dw100_vertexmap.h index 428b3d74d4d2..49ecc81678a0 100644 --- a/include/libcamera/internal/converter/converter_dw100_vertexmap.h +++ b/include/libcamera/internal/converter/converter_dw100_vertexmap.h @@ -10,6 +10,9 @@ #include #include +#include "libcamera/internal/matrix.h" +#include "libcamera/internal/vector.h" + namespace libcamera { class Dw100VertexMap @@ -50,9 +53,17 @@ public: void setMode(const ScaleMode mode) { mode_ = mode; } ScaleMode mode() const { return mode_; } + 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: + Vector dewarpPoint(const Vector &p); + Rectangle scalerCrop_; Rectangle sensorCrop_; Transform transform_ = Transform::Identity; @@ -66,6 +77,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 427d710743b8..b6880ba40a20 100644 --- a/src/libcamera/converter/converter_dw100_vertexmap.cpp +++ b/src/libcamera/converter/converter_dw100_vertexmap.cpp @@ -183,8 +183,6 @@ int dw100VerticesForLength(const int length) * | | | | | Transpose) | | Rotate) | * +-------------+ +-------------+ +------------+ +-----------------+ * - * \todo Lens dewarp is not yet implemented. An identity map is used instead. - * * All parameters are clamped to valid values before creating the vertex map. * * The constrains process works as follows: @@ -544,10 +542,8 @@ std::vector Dw100VertexMap::getVertexMap() p = transformPoint(outputToSensor, p); - /* - * \todo: Transformations in sensor space to be added - * here. - */ + if (dewarpParamsValid_ && lensDewarpEnable_) + p = dewarpPoint(p); p = transformPoint(sensorToInput, p); @@ -561,4 +557,94 @@ std::vector Dw100VertexMap::getVertexMap() return res; } +/** + * \brief Set the dewarp parameters + * \param cm The camera matrix + * \param coeffs The dewarp coefficients + * + * Sets the dewarp parameters according to the commonly used dewarp model. See + * https://docs.opencv.org/4.12.0/d9/d0c/group__calib3d.html for further details + * on the model. The parameter \a coeffs must either hold 4,5,8 or 12 values. + * They represent the parameters k1,k2,p1,p2[,k3[,k4,k5,k6[,s1,s2,s3,s4]]] in + * the model. + * + * \return A negative number on error, 0 otherwise + */ +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; +} + +/** + * \fn Dw100VertexMap::dewarpParamsValid() + * \brief Returns if the dewarp parameters are valid + * + * \return True if the dewarp parameters are valid, fals otherwise + */ + +/** + * \fn Dw100VertexMap::setLensDewarpEnable() + * \brief Enables or disables lens dewarping + * \param[in] enable Enable or disable lens dewarping + */ + +/** + * \fn Dw100VertexMap::lensDewarpEnable() + * \brief Returns if lens dewarping is enabled + */ + +/** + * \brief Apply dewarp calculation to a point + * \param p The point to dewarp + * + * Applies the dewarp transformation to point \a p according to the commonly + * used dewarp model. See + * https://docs.opencv.org/4.12.0/d9/d0c/group__calib3d.html for further details + * on the model. + * + * \return The dewarped point + */ +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; + + return { { x * dewarpM_[0][0] + y * dewarpM_[0][1] + dewarpM_[0][2], + y * dewarpM_[1][1] + dewarpM_[1][2] } }; +} + } /* namespace libcamera */