From patchwork Thu Oct 23 14:48:31 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Klug X-Patchwork-Id: 24757 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 031DFC3344 for ; Thu, 23 Oct 2025 14:50:18 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 80B9D60854; Thu, 23 Oct 2025 16:50:18 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="d84fuRgi"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id CC1FE607ED for ; Thu, 23 Oct 2025 16:50:16 +0200 (CEST) Received: from ideasonboard.com (unknown [IPv6:2a00:6020:448c:6c00:7328:357b:4ce1:72b6]) by perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id CB6821127; Thu, 23 Oct 2025 16:48:31 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761230911; bh=UpEvhD8cXzD3/aA5BBffZLonOzc8X/lKUqsIeTlFF1c=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=d84fuRgiFEbQ2YLovBKaoAUFtUazVMkpGoihP0eySAHDVqeFvTmp1ZrAzadC54VGi Ka1r7Q8A73d82Fm59sVSTt0uVzfIgrmOmJjvkGEOyR1U2RB74MxGyj4tRE5xvlt7ii Vnra/SqtEMohIX7N5W4a+XwaDDwZ6YOtRTS6jMn8= From: Stefan Klug To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug Subject: [PATCH v2 30/35] libcamera: dw100_vertexmap: Implement parametric dewarping Date: Thu, 23 Oct 2025 16:48:31 +0200 Message-ID: <20251023144841.403689-31-stefan.klug@ideasonboard.com> X-Mailer: git-send-email 2.48.1 In-Reply-To: <20251023144841.403689-1-stefan.klug@ideasonboard.com> References: <20251023144841.403689-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 --- 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 e72cb72bb9f1..b05ed8338a23 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 @@ -55,9 +58,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; @@ -71,6 +82,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 0e930479b6f7..f2be9a697ff3 100644 --- a/src/libcamera/converter/converter_dw100_vertexmap.cpp +++ b/src/libcamera/converter/converter_dw100_vertexmap.cpp @@ -184,8 +184,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: @@ -546,10 +544,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); @@ -563,4 +559,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 disabled 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 */