[{"id":36091,"web_url":"https://patchwork.libcamera.org/comment/36091/","msgid":"<175944223744.3754595.11097382497099183341@ping.linuxembedded.co.uk>","date":"2025-10-02T21:57:17","subject":"Re: [PATCH v1 28/33] libcamera: dw100_vertexmap: Implement\n\tparametric dewarping","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Quoting Stefan Klug (2025-09-30 13:26:49)\n> Implement functions to allow lens dewarping based on the common lens\n> dewarp model used e.g. by OpenCV\n> \n> See https://docs.opencv.org/3.4/da/d54/group__imgproc__transform.html#ga7dfb72c9cf9780a347fbe3d1c47e5d5a\n> \n> Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>\n> ---\n>  .../converter/converter_dw100_vertexmap.h     | 17 ++++\n>  .../converter/converter_dw100_vertexmap.cpp   | 87 +++++++++++++++++++\n>  2 files changed, 104 insertions(+)\n> \n> diff --git a/include/libcamera/internal/converter/converter_dw100_vertexmap.h b/include/libcamera/internal/converter/converter_dw100_vertexmap.h\n> index 9c6e0ffa4513..7a12c4cb7a50 100644\n> --- a/include/libcamera/internal/converter/converter_dw100_vertexmap.h\n> +++ b/include/libcamera/internal/converter/converter_dw100_vertexmap.h\n> @@ -12,6 +12,9 @@\n>  #include <libcamera/geometry.h>\n>  #include <libcamera/transform.h>\n>  \n> +#include \"libcamera/internal/matrix.h\"\n> +#include \"libcamera/internal/vector.h\"\n> +\n>  namespace libcamera {\n>  \n>  class Dw100VertexMap\n> @@ -57,11 +60,20 @@ public:\n>         void setMode(const ScaleMode mode) { mode_ = mode; }\n>         ScaleMode mode() const { return mode_; }\n>  \n> +       int loadDewarpParams(const YamlObject &dict);\n> +       int setDewarpParams(const Matrix<double, 3, 3> &cm, const Span<const double> &coeffs);\n> +       bool dewarpParamsValid() { return dewarpParamsValid_; }\n> +\n> +       void setLensDewarpEnable(bool enable) { lensDewarpEnable_ = enable; }\n> +       bool lensDewarpEnable() { return lensDewarpEnable_; }\n> +\n>         std::vector<uint32_t> getVertexMap();\n>  \n>  private:\n>         int getVerticesForLength(const int length);\n>  \n> +       Vector<double, 2> dewarpPoint(const Vector<double, 2> &p);\n> +\n>         Rectangle scalerCrop_;\n>         Rectangle sensorCrop_;\n>         Transform transform_ = Transform::Identity;\n> @@ -75,6 +87,11 @@ private:\n>         double effectiveScaleY_;\n>         Point effectiveOffset_;\n>         Rectangle effectiveScalerCrop_;\n> +\n> +       Matrix<double, 3, 3> dewarpM_ = Matrix<double, 3, 3>::identity();\n> +       std::array<double, 12> dewarpCoeffs_;\n> +       bool lensDewarpEnable_ = true;\n> +       bool dewarpParamsValid_ = false;\n>  };\n>  \n>  } /* namespace libcamera */\n> diff --git a/src/libcamera/converter/converter_dw100_vertexmap.cpp b/src/libcamera/converter/converter_dw100_vertexmap.cpp\n> index 10d9e34d98c5..5581d53e57c7 100644\n> --- a/src/libcamera/converter/converter_dw100_vertexmap.cpp\n> +++ b/src/libcamera/converter/converter_dw100_vertexmap.cpp\n> @@ -66,6 +66,14 @@ Vector2d point2Vec2d(const Point &p)\n>         return { { static_cast<double>(p.x), static_cast<double>(p.y) } };\n>  }\n>  \n> +void transformPoint(const Rectangle &from, const Rectangle &to, Vector2d &p)\n\nI wonder if this ends up going to geometry in the future. No need now\nwithout another user.\n\n> +{\n> +       double sx = to.width / static_cast<double>(from.width);\n> +       double sy = to.height / static_cast<double>(from.height);\n> +       p.x() = (p.x() - from.x) * sx + to.x;\n> +       p.y() = (p.y() - from.y) * sy + to.y;\n> +}\n> +\n>  } /* namespace */\n>  \n>  /**\n> @@ -344,6 +352,17 @@ std::vector<uint32_t> Dw100VertexMap::getVertexMap()\n>                         p.x() += localScalerCrop.x;\n>                         p.y() += localScalerCrop.y;\n>  \n> +                       if (dewarpParamsValid_ && lensDewarpEnable_) {\n> +                               /*\n> +                                * We are in localScalerCrop coordinates.\n> +                                * Tranform to sensor coordinates.\n\nTransform\n\n> +                                */\n> +                               transformPoint(localScalerCrop, effectiveScalerCrop_, p);\n> +                               p = dewarpPoint(p);\n> +                               /* tarnsform back to localScalerCrop */\n\nTransform\n\n> +                               transformPoint(effectiveScalerCrop_, localScalerCrop, p);\n> +                       }\n> +\n>                         /* Convert to fixed point */\n>                         uint32_t v = static_cast<uint32_t>(p.y() * 16) << 16 |\n>                                      (static_cast<uint32_t>(p.x() * 16) & 0xffff);\n> @@ -354,6 +373,74 @@ std::vector<uint32_t> Dw100VertexMap::getVertexMap()\n>         return res;\n>  }\n>  \n> +int Dw100VertexMap::loadDewarpParams(const YamlObject &dict)\n> +{\n> +       Matrix<double, 3, 3> m;\n> +       const auto &cm = dict[\"cm\"].get<Matrix<double, 3, 3>>();\n> +       if (!cm) {\n> +               LOG(Converter, Error) << \"Dewarp parameters are missing 'cm' value\";\n> +               return -EINVAL;\n> +       }\n> +\n> +       m = *cm;\n> +\n> +       const auto &coeffs = dict[\"coefficients\"].getList<double>();\n> +       if (!coeffs) {\n> +               LOG(Converter, Error) << \"Dewarp parameters 'coefficients' value is not a list\";\n> +               return -EINVAL;\n> +       }\n> +\n> +       const Span<const double> span{ *coeffs };\n> +       return setDewarpParams(m, span);\n> +}\n> +\n> +int Dw100VertexMap::setDewarpParams(const Matrix<double, 3, 3> &cm, const Span<const double> &coeffs)\n> +{\n> +       dewarpM_ = cm;\n> +       dewarpCoeffs_.fill(0.0);\n> +\n> +       if (coeffs.size() != 4 && coeffs.size() != 5 &&\n> +           coeffs.size() != 8 && coeffs.size() != 12) {\n> +               LOG(Converter, Error) << \"Dewarp 'coefficients' must have 4, 5, 8 or 12 values\";\n> +               dewarpParamsValid_ = false;\n> +               return -EINVAL;\n> +       }\n> +       std::copy(coeffs.begin(), coeffs.end(), dewarpCoeffs_.begin());\n> +\n> +       dewarpParamsValid_ = true;\n> +       return 0;\n> +}\n> +\n> +Vector2d Dw100VertexMap::dewarpPoint(const Vector2d &p)\n> +{\n> +       double x, y;\n> +       double k1 = dewarpCoeffs_[0];\n> +       double k2 = dewarpCoeffs_[1];\n> +       double p1 = dewarpCoeffs_[2];\n> +       double p2 = dewarpCoeffs_[3];\n> +       double k3 = dewarpCoeffs_[4];\n> +       double k4 = dewarpCoeffs_[5];\n> +       double k5 = dewarpCoeffs_[6];\n> +       double k6 = dewarpCoeffs_[7];\n> +       double s1 = dewarpCoeffs_[8];\n> +       double s2 = dewarpCoeffs_[9];\n> +       double s3 = dewarpCoeffs_[10];\n> +       double s4 = dewarpCoeffs_[11];\n\nI'm scared to ask - but how could we document the coefficients. Perhaps\nwe need a top level dewarp doxygen page sometime.\n\n> +\n> +       y = (p.y() - dewarpM_[1][2]) / dewarpM_[1][1];\n> +       x = (p.x() - dewarpM_[0][2] - y * dewarpM_[0][1]) / dewarpM_[0][0];\n> +\n> +       double r2 = x * x + y * y;\n> +       double d = (1 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2) /\n> +                  (1 + k4 * r2 + k5 * r2 * r2 + k6 * r2 * r2 * r2);\n> +       x = x * d + 2 * p1 * x * y + p2 * (r2 + 2 * x * x) + s1 * r2 + s2 * r2 * r2;\n> +       y = y * d + 2 * p2 * x * y + p1 * (r2 + 2 * y * y) + s3 * r2 + s4 * r2 * r2;\n> +\n> +       Vector2d ret{ { x * dewarpM_[0][0] + y * dewarpM_[0][1] + dewarpM_[0][2],\n> +                       y * dewarpM_[1][1] + dewarpM_[1][2] } };\n> +       return ret;\n\nCan this be directly returned?\n\n\treturn { { x * dewarpM_[0][0] + y * dewarpM_[0][1] + dewarpM_[0][2],\n\t\t   y * dewarpM_[1][1] + dewarpM_[1][2] } };\n\nOr does the construction get more difficult ?\n\n\nBut actually for this patch -  I think I'll already add with the typos\nfixed:\n\nReviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\n> +}\n> +\n>  int Dw100VertexMap::getVerticesForLength(const int length)\n>  {\n>         return (length + kDw100BlockSize - 1) / kDw100BlockSize + 1;\n> -- \n> 2.48.1\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 57C30C328C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu,  2 Oct 2025 21:57:23 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 71F5D6B5A2;\n\tThu,  2 Oct 2025 23:57:22 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 33DBD6B5A2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu,  2 Oct 2025 23:57:21 +0200 (CEST)","from pendragon.ideasonboard.com\n\t(cpc89244-aztw30-2-0-cust6594.18-1.cable.virginm.net [86.31.185.195])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 143D8F06;\n\tThu,  2 Oct 2025 23:55:51 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"MBdEq8kk\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1759442151;\n\tbh=SjJ2gCGQxyV6fczIxfZeH8t70jTLl3391O8iik8ui1g=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=MBdEq8kkf181PjwNpc1vzzLELrMQgFpOrmEU5z56jEUPfJHk52qI7AlVHYJzbuW5o\n\tVTJEtdEnOV2hXaptCdD2wUwTSAaHHmgfPbhPJqkNFPkwzW7VqwR5++xuMHdnOvwzcr\n\tqcv3MvtYZn0kBhBcTTR1liZI4jV0LmhOqz7eYLng=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20250930122726.1837524-29-stefan.klug@ideasonboard.com>","References":"<20250930122726.1837524-1-stefan.klug@ideasonboard.com>\n\t<20250930122726.1837524-29-stefan.klug@ideasonboard.com>","Subject":"Re: [PATCH v1 28/33] libcamera: dw100_vertexmap: Implement\n\tparametric dewarping","From":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Cc":"Stefan Klug <stefan.klug@ideasonboard.com>","To":"Stefan Klug <stefan.klug@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","Date":"Thu, 02 Oct 2025 22:57:17 +0100","Message-ID":"<175944223744.3754595.11097382497099183341@ping.linuxembedded.co.uk>","User-Agent":"alot/0.9.1","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":36099,"web_url":"https://patchwork.libcamera.org/comment/36099/","msgid":"<64180f43-c6d5-42c4-a294-8d7f221345cc@ideasonboard.com>","date":"2025-10-03T08:09:32","subject":"Re: [PATCH v1 28/33] libcamera: dw100_vertexmap: Implement\n\tparametric dewarping","submitter":{"id":216,"url":"https://patchwork.libcamera.org/api/people/216/","name":"Barnabás Pőcze","email":"barnabas.pocze@ideasonboard.com"},"content":"Hi\n\n\n2025. 10. 02. 23:57 keltezéssel, Kieran Bingham írta:\n> Quoting Stefan Klug (2025-09-30 13:26:49)\n>> Implement functions to allow lens dewarping based on the common lens\n>> dewarp model used e.g. by OpenCV\n>>\n>> See https://docs.opencv.org/3.4/da/d54/group__imgproc__transform.html#ga7dfb72c9cf9780a347fbe3d1c47e5d5a\n>>\n>> Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>\n>> ---\n>>   .../converter/converter_dw100_vertexmap.h     | 17 ++++\n>>   .../converter/converter_dw100_vertexmap.cpp   | 87 +++++++++++++++++++\n>>   2 files changed, 104 insertions(+)\n>>\n>> diff --git a/include/libcamera/internal/converter/converter_dw100_vertexmap.h b/include/libcamera/internal/converter/converter_dw100_vertexmap.h\n>> index 9c6e0ffa4513..7a12c4cb7a50 100644\n>> --- a/include/libcamera/internal/converter/converter_dw100_vertexmap.h\n>> +++ b/include/libcamera/internal/converter/converter_dw100_vertexmap.h\n>> @@ -12,6 +12,9 @@\n>>   #include <libcamera/geometry.h>\n>>   #include <libcamera/transform.h>\n>>\n>> +#include \"libcamera/internal/matrix.h\"\n>> +#include \"libcamera/internal/vector.h\"\n>> +\n>>   namespace libcamera {\n>>\n>>   class Dw100VertexMap\n>> @@ -57,11 +60,20 @@ public:\n>>          void setMode(const ScaleMode mode) { mode_ = mode; }\n>>          ScaleMode mode() const { return mode_; }\n>>\n>> +       int loadDewarpParams(const YamlObject &dict);\n>> +       int setDewarpParams(const Matrix<double, 3, 3> &cm, const Span<const double> &coeffs);\n>> +       bool dewarpParamsValid() { return dewarpParamsValid_; }\n>> +\n>> +       void setLensDewarpEnable(bool enable) { lensDewarpEnable_ = enable; }\n>> +       bool lensDewarpEnable() { return lensDewarpEnable_; }\n>> +\n>>          std::vector<uint32_t> getVertexMap();\n>>\n>>   private:\n>>          int getVerticesForLength(const int length);\n>>\n>> +       Vector<double, 2> dewarpPoint(const Vector<double, 2> &p);\n>> +\n>>          Rectangle scalerCrop_;\n>>          Rectangle sensorCrop_;\n>>          Transform transform_ = Transform::Identity;\n>> @@ -75,6 +87,11 @@ private:\n>>          double effectiveScaleY_;\n>>          Point effectiveOffset_;\n>>          Rectangle effectiveScalerCrop_;\n>> +\n>> +       Matrix<double, 3, 3> dewarpM_ = Matrix<double, 3, 3>::identity();\n>> +       std::array<double, 12> dewarpCoeffs_;\n>> +       bool lensDewarpEnable_ = true;\n>> +       bool dewarpParamsValid_ = false;\n>>   };\n>>\n>>   } /* namespace libcamera */\n>> diff --git a/src/libcamera/converter/converter_dw100_vertexmap.cpp b/src/libcamera/converter/converter_dw100_vertexmap.cpp\n>> index 10d9e34d98c5..5581d53e57c7 100644\n>> --- a/src/libcamera/converter/converter_dw100_vertexmap.cpp\n>> +++ b/src/libcamera/converter/converter_dw100_vertexmap.cpp\n>> @@ -66,6 +66,14 @@ Vector2d point2Vec2d(const Point &p)\n>>          return { { static_cast<double>(p.x), static_cast<double>(p.y) } };\n>>   }\n>>\n>> +void transformPoint(const Rectangle &from, const Rectangle &to, Vector2d &p)\n> \n> I wonder if this ends up going to geometry in the future. No need now\n> without another user.\n\nThis is already implemented in `Rectangle::transformedBetween()`.\nThat works on rectangles, but to transform a point, one can use\nthe top left corner and ignore the sizes.\n\n\nRegards,\nBarnabás Pőcze\n\n\n> \n>> +{\n>> +       double sx = to.width / static_cast<double>(from.width);\n>> +       double sy = to.height / static_cast<double>(from.height);\n>> +       p.x() = (p.x() - from.x) * sx + to.x;\n>> +       p.y() = (p.y() - from.y) * sy + to.y;\n>> +}\n>> +\n>>   } /* namespace */\n>>\n>>   /**\n>> @@ -344,6 +352,17 @@ std::vector<uint32_t> Dw100VertexMap::getVertexMap()\n>>                          p.x() += localScalerCrop.x;\n>>                          p.y() += localScalerCrop.y;\n>>\n>> +                       if (dewarpParamsValid_ && lensDewarpEnable_) {\n>> +                               /*\n>> +                                * We are in localScalerCrop coordinates.\n>> +                                * Tranform to sensor coordinates.\n> \n> Transform\n> \n>> +                                */\n>> +                               transformPoint(localScalerCrop, effectiveScalerCrop_, p);\n>> +                               p = dewarpPoint(p);\n>> +                               /* tarnsform back to localScalerCrop */\n> \n> Transform\n> \n>> +                               transformPoint(effectiveScalerCrop_, localScalerCrop, p);\n>> +                       }\n>> +\n>>                          /* Convert to fixed point */\n>>                          uint32_t v = static_cast<uint32_t>(p.y() * 16) << 16 |\n>>                                       (static_cast<uint32_t>(p.x() * 16) & 0xffff);\n>> @@ -354,6 +373,74 @@ std::vector<uint32_t> Dw100VertexMap::getVertexMap()\n>>          return res;\n>>   }\n>>\n>> +int Dw100VertexMap::loadDewarpParams(const YamlObject &dict)\n>> +{\n>> +       Matrix<double, 3, 3> m;\n>> +       const auto &cm = dict[\"cm\"].get<Matrix<double, 3, 3>>();\n>> +       if (!cm) {\n>> +               LOG(Converter, Error) << \"Dewarp parameters are missing 'cm' value\";\n>> +               return -EINVAL;\n>> +       }\n>> +\n>> +       m = *cm;\n>> +\n>> +       const auto &coeffs = dict[\"coefficients\"].getList<double>();\n>> +       if (!coeffs) {\n>> +               LOG(Converter, Error) << \"Dewarp parameters 'coefficients' value is not a list\";\n>> +               return -EINVAL;\n>> +       }\n>> +\n>> +       const Span<const double> span{ *coeffs };\n>> +       return setDewarpParams(m, span);\n>> +}\n>> +\n>> +int Dw100VertexMap::setDewarpParams(const Matrix<double, 3, 3> &cm, const Span<const double> &coeffs)\n>> +{\n>> +       dewarpM_ = cm;\n>> +       dewarpCoeffs_.fill(0.0);\n>> +\n>> +       if (coeffs.size() != 4 && coeffs.size() != 5 &&\n>> +           coeffs.size() != 8 && coeffs.size() != 12) {\n>> +               LOG(Converter, Error) << \"Dewarp 'coefficients' must have 4, 5, 8 or 12 values\";\n>> +               dewarpParamsValid_ = false;\n>> +               return -EINVAL;\n>> +       }\n>> +       std::copy(coeffs.begin(), coeffs.end(), dewarpCoeffs_.begin());\n>> +\n>> +       dewarpParamsValid_ = true;\n>> +       return 0;\n>> +}\n>> +\n>> +Vector2d Dw100VertexMap::dewarpPoint(const Vector2d &p)\n>> +{\n>> +       double x, y;\n>> +       double k1 = dewarpCoeffs_[0];\n>> +       double k2 = dewarpCoeffs_[1];\n>> +       double p1 = dewarpCoeffs_[2];\n>> +       double p2 = dewarpCoeffs_[3];\n>> +       double k3 = dewarpCoeffs_[4];\n>> +       double k4 = dewarpCoeffs_[5];\n>> +       double k5 = dewarpCoeffs_[6];\n>> +       double k6 = dewarpCoeffs_[7];\n>> +       double s1 = dewarpCoeffs_[8];\n>> +       double s2 = dewarpCoeffs_[9];\n>> +       double s3 = dewarpCoeffs_[10];\n>> +       double s4 = dewarpCoeffs_[11];\n> \n> I'm scared to ask - but how could we document the coefficients. Perhaps\n> we need a top level dewarp doxygen page sometime.\n> \n>> +\n>> +       y = (p.y() - dewarpM_[1][2]) / dewarpM_[1][1];\n>> +       x = (p.x() - dewarpM_[0][2] - y * dewarpM_[0][1]) / dewarpM_[0][0];\n>> +\n>> +       double r2 = x * x + y * y;\n>> +       double d = (1 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2) /\n>> +                  (1 + k4 * r2 + k5 * r2 * r2 + k6 * r2 * r2 * r2);\n>> +       x = x * d + 2 * p1 * x * y + p2 * (r2 + 2 * x * x) + s1 * r2 + s2 * r2 * r2;\n>> +       y = y * d + 2 * p2 * x * y + p1 * (r2 + 2 * y * y) + s3 * r2 + s4 * r2 * r2;\n>> +\n>> +       Vector2d ret{ { x * dewarpM_[0][0] + y * dewarpM_[0][1] + dewarpM_[0][2],\n>> +                       y * dewarpM_[1][1] + dewarpM_[1][2] } };\n>> +       return ret;\n> \n> Can this be directly returned?\n> \n> \treturn { { x * dewarpM_[0][0] + y * dewarpM_[0][1] + dewarpM_[0][2],\n> \t\t   y * dewarpM_[1][1] + dewarpM_[1][2] } };\n> \n> Or does the construction get more difficult ?\n> \n> \n> But actually for this patch -  I think I'll already add with the typos\n> fixed:\n> \n> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n> \n>> +}\n>> +\n>>   int Dw100VertexMap::getVerticesForLength(const int length)\n>>   {\n>>          return (length + kDw100BlockSize - 1) / kDw100BlockSize + 1;\n>> --\n>> 2.48.1\n>>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 86B66BF415\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri,  3 Oct 2025 08:09:39 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 413B76B5F3;\n\tFri,  3 Oct 2025 10:09:38 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 197DC6B599\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri,  3 Oct 2025 10:09:36 +0200 (CEST)","from [192.168.33.17] (185.221.140.76.nat.pool.zt.hu\n\t[185.221.140.76])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 4B4FE1808;\n\tFri,  3 Oct 2025 10:08:05 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"sC1yiG9q\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1759478885;\n\tbh=u6JpN5dPi02BobNFUqmFPH5b5HuHq2jyEWa1SmoCLl4=;\n\th=Date:Subject:To:References:From:In-Reply-To:From;\n\tb=sC1yiG9qfJTQWl4p0c5UAXM+aJwGPMWpGnLs9gf3dJQ12eMx6Akszjj7ABnIpF83S\n\tWQRqONTB9ThxnuNdpDQCEp7zTabibG42L/W2BmiMPKYxeJBdoUi1KyqyQ32Wac7GhU\n\tPl7exRYwz75kiC853CS6+aZ7Q63D6YVEWchLNElk=","Message-ID":"<64180f43-c6d5-42c4-a294-8d7f221345cc@ideasonboard.com>","Date":"Fri, 3 Oct 2025 10:09:32 +0200","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH v1 28/33] libcamera: dw100_vertexmap: Implement\n\tparametric dewarping","To":"Kieran Bingham <kieran.bingham@ideasonboard.com>,\n\tStefan Klug <stefan.klug@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","References":"<20250930122726.1837524-1-stefan.klug@ideasonboard.com>\n\t<20250930122726.1837524-29-stefan.klug@ideasonboard.com>\n\t<pcB48_4kpjJqT1HtLw65aigNzDxXY4mnlohVZzncdteiNSs3CWEy-GOxPUBaTDIjJJrto_Ea-2ttwWihOJqabw==@protonmail.internalid>\n\t<175944223744.3754595.11097382497099183341@ping.linuxembedded.co.uk>","From":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","Content-Language":"en-US, hu-HU","In-Reply-To":"<175944223744.3754595.11097382497099183341@ping.linuxembedded.co.uk>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"8bit","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":36134,"web_url":"https://patchwork.libcamera.org/comment/36134/","msgid":"<175974195423.3214037.11335055581926455199@localhost>","date":"2025-10-06T09:12:34","subject":"Re: [PATCH v1 28/33] libcamera: dw100_vertexmap: Implement\n\tparametric dewarping","submitter":{"id":184,"url":"https://patchwork.libcamera.org/api/people/184/","name":"Stefan Klug","email":"stefan.klug@ideasonboard.com"},"content":"Quoting Barnabás Pőcze (2025-10-03 10:09:32)\n> Hi\n> \n> \n> 2025. 10. 02. 23:57 keltezéssel, Kieran Bingham írta:\n> > Quoting Stefan Klug (2025-09-30 13:26:49)\n> >> Implement functions to allow lens dewarping based on the common lens\n> >> dewarp model used e.g. by OpenCV\n> >>\n> >> See https://docs.opencv.org/3.4/da/d54/group__imgproc__transform.html#ga7dfb72c9cf9780a347fbe3d1c47e5d5a\n> >>\n> >> Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>\n> >> ---\n> >>   .../converter/converter_dw100_vertexmap.h     | 17 ++++\n> >>   .../converter/converter_dw100_vertexmap.cpp   | 87 +++++++++++++++++++\n> >>   2 files changed, 104 insertions(+)\n> >>\n> >> diff --git a/include/libcamera/internal/converter/converter_dw100_vertexmap.h b/include/libcamera/internal/converter/converter_dw100_vertexmap.h\n> >> index 9c6e0ffa4513..7a12c4cb7a50 100644\n> >> --- a/include/libcamera/internal/converter/converter_dw100_vertexmap.h\n> >> +++ b/include/libcamera/internal/converter/converter_dw100_vertexmap.h\n> >> @@ -12,6 +12,9 @@\n> >>   #include <libcamera/geometry.h>\n> >>   #include <libcamera/transform.h>\n> >>\n> >> +#include \"libcamera/internal/matrix.h\"\n> >> +#include \"libcamera/internal/vector.h\"\n> >> +\n> >>   namespace libcamera {\n> >>\n> >>   class Dw100VertexMap\n> >> @@ -57,11 +60,20 @@ public:\n> >>          void setMode(const ScaleMode mode) { mode_ = mode; }\n> >>          ScaleMode mode() const { return mode_; }\n> >>\n> >> +       int loadDewarpParams(const YamlObject &dict);\n> >> +       int setDewarpParams(const Matrix<double, 3, 3> &cm, const Span<const double> &coeffs);\n> >> +       bool dewarpParamsValid() { return dewarpParamsValid_; }\n> >> +\n> >> +       void setLensDewarpEnable(bool enable) { lensDewarpEnable_ = enable; }\n> >> +       bool lensDewarpEnable() { return lensDewarpEnable_; }\n> >> +\n> >>          std::vector<uint32_t> getVertexMap();\n> >>\n> >>   private:\n> >>          int getVerticesForLength(const int length);\n> >>\n> >> +       Vector<double, 2> dewarpPoint(const Vector<double, 2> &p);\n> >> +\n> >>          Rectangle scalerCrop_;\n> >>          Rectangle sensorCrop_;\n> >>          Transform transform_ = Transform::Identity;\n> >> @@ -75,6 +87,11 @@ private:\n> >>          double effectiveScaleY_;\n> >>          Point effectiveOffset_;\n> >>          Rectangle effectiveScalerCrop_;\n> >> +\n> >> +       Matrix<double, 3, 3> dewarpM_ = Matrix<double, 3, 3>::identity();\n> >> +       std::array<double, 12> dewarpCoeffs_;\n> >> +       bool lensDewarpEnable_ = true;\n> >> +       bool dewarpParamsValid_ = false;\n> >>   };\n> >>\n> >>   } /* namespace libcamera */\n> >> diff --git a/src/libcamera/converter/converter_dw100_vertexmap.cpp b/src/libcamera/converter/converter_dw100_vertexmap.cpp\n> >> index 10d9e34d98c5..5581d53e57c7 100644\n> >> --- a/src/libcamera/converter/converter_dw100_vertexmap.cpp\n> >> +++ b/src/libcamera/converter/converter_dw100_vertexmap.cpp\n> >> @@ -66,6 +66,14 @@ Vector2d point2Vec2d(const Point &p)\n> >>          return { { static_cast<double>(p.x), static_cast<double>(p.y) } };\n> >>   }\n> >>\n> >> +void transformPoint(const Rectangle &from, const Rectangle &to, Vector2d &p)\n> > \n> > I wonder if this ends up going to geometry in the future. No need now\n> > without another user.\n> \n> This is already implemented in `Rectangle::transformedBetween()`.\n> That works on rectangles, but to transform a point, one can use\n> the top left corner and ignore the sizes.\n\nYes, but then you'd loose the subpixel accuracy because Rectangle is int\nonly. That would lead to visible artifacts.\n\nBest regards,\nStefan\n\n> \n> \n> Regards,\n> Barnabás Pőcze\n> \n> \n> > \n> >> +{\n> >> +       double sx = to.width / static_cast<double>(from.width);\n> >> +       double sy = to.height / static_cast<double>(from.height);\n> >> +       p.x() = (p.x() - from.x) * sx + to.x;\n> >> +       p.y() = (p.y() - from.y) * sy + to.y;\n> >> +}\n> >> +\n> >>   } /* namespace */\n> >>\n> >>   /**\n> >> @@ -344,6 +352,17 @@ std::vector<uint32_t> Dw100VertexMap::getVertexMap()\n> >>                          p.x() += localScalerCrop.x;\n> >>                          p.y() += localScalerCrop.y;\n> >>\n> >> +                       if (dewarpParamsValid_ && lensDewarpEnable_) {\n> >> +                               /*\n> >> +                                * We are in localScalerCrop coordinates.\n> >> +                                * Tranform to sensor coordinates.\n> > \n> > Transform\n> > \n> >> +                                */\n> >> +                               transformPoint(localScalerCrop, effectiveScalerCrop_, p);\n> >> +                               p = dewarpPoint(p);\n> >> +                               /* tarnsform back to localScalerCrop */\n> > \n> > Transform\n> > \n> >> +                               transformPoint(effectiveScalerCrop_, localScalerCrop, p);\n> >> +                       }\n> >> +\n> >>                          /* Convert to fixed point */\n> >>                          uint32_t v = static_cast<uint32_t>(p.y() * 16) << 16 |\n> >>                                       (static_cast<uint32_t>(p.x() * 16) & 0xffff);\n> >> @@ -354,6 +373,74 @@ std::vector<uint32_t> Dw100VertexMap::getVertexMap()\n> >>          return res;\n> >>   }\n> >>\n> >> +int Dw100VertexMap::loadDewarpParams(const YamlObject &dict)\n> >> +{\n> >> +       Matrix<double, 3, 3> m;\n> >> +       const auto &cm = dict[\"cm\"].get<Matrix<double, 3, 3>>();\n> >> +       if (!cm) {\n> >> +               LOG(Converter, Error) << \"Dewarp parameters are missing 'cm' value\";\n> >> +               return -EINVAL;\n> >> +       }\n> >> +\n> >> +       m = *cm;\n> >> +\n> >> +       const auto &coeffs = dict[\"coefficients\"].getList<double>();\n> >> +       if (!coeffs) {\n> >> +               LOG(Converter, Error) << \"Dewarp parameters 'coefficients' value is not a list\";\n> >> +               return -EINVAL;\n> >> +       }\n> >> +\n> >> +       const Span<const double> span{ *coeffs };\n> >> +       return setDewarpParams(m, span);\n> >> +}\n> >> +\n> >> +int Dw100VertexMap::setDewarpParams(const Matrix<double, 3, 3> &cm, const Span<const double> &coeffs)\n> >> +{\n> >> +       dewarpM_ = cm;\n> >> +       dewarpCoeffs_.fill(0.0);\n> >> +\n> >> +       if (coeffs.size() != 4 && coeffs.size() != 5 &&\n> >> +           coeffs.size() != 8 && coeffs.size() != 12) {\n> >> +               LOG(Converter, Error) << \"Dewarp 'coefficients' must have 4, 5, 8 or 12 values\";\n> >> +               dewarpParamsValid_ = false;\n> >> +               return -EINVAL;\n> >> +       }\n> >> +       std::copy(coeffs.begin(), coeffs.end(), dewarpCoeffs_.begin());\n> >> +\n> >> +       dewarpParamsValid_ = true;\n> >> +       return 0;\n> >> +}\n> >> +\n> >> +Vector2d Dw100VertexMap::dewarpPoint(const Vector2d &p)\n> >> +{\n> >> +       double x, y;\n> >> +       double k1 = dewarpCoeffs_[0];\n> >> +       double k2 = dewarpCoeffs_[1];\n> >> +       double p1 = dewarpCoeffs_[2];\n> >> +       double p2 = dewarpCoeffs_[3];\n> >> +       double k3 = dewarpCoeffs_[4];\n> >> +       double k4 = dewarpCoeffs_[5];\n> >> +       double k5 = dewarpCoeffs_[6];\n> >> +       double k6 = dewarpCoeffs_[7];\n> >> +       double s1 = dewarpCoeffs_[8];\n> >> +       double s2 = dewarpCoeffs_[9];\n> >> +       double s3 = dewarpCoeffs_[10];\n> >> +       double s4 = dewarpCoeffs_[11];\n> > \n> > I'm scared to ask - but how could we document the coefficients. Perhaps\n> > we need a top level dewarp doxygen page sometime.\n> > \n> >> +\n> >> +       y = (p.y() - dewarpM_[1][2]) / dewarpM_[1][1];\n> >> +       x = (p.x() - dewarpM_[0][2] - y * dewarpM_[0][1]) / dewarpM_[0][0];\n> >> +\n> >> +       double r2 = x * x + y * y;\n> >> +       double d = (1 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2) /\n> >> +                  (1 + k4 * r2 + k5 * r2 * r2 + k6 * r2 * r2 * r2);\n> >> +       x = x * d + 2 * p1 * x * y + p2 * (r2 + 2 * x * x) + s1 * r2 + s2 * r2 * r2;\n> >> +       y = y * d + 2 * p2 * x * y + p1 * (r2 + 2 * y * y) + s3 * r2 + s4 * r2 * r2;\n> >> +\n> >> +       Vector2d ret{ { x * dewarpM_[0][0] + y * dewarpM_[0][1] + dewarpM_[0][2],\n> >> +                       y * dewarpM_[1][1] + dewarpM_[1][2] } };\n> >> +       return ret;\n> > \n> > Can this be directly returned?\n> > \n> >       return { { x * dewarpM_[0][0] + y * dewarpM_[0][1] + dewarpM_[0][2],\n> >                  y * dewarpM_[1][1] + dewarpM_[1][2] } };\n> > \n> > Or does the construction get more difficult ?\n> > \n> > \n> > But actually for this patch -  I think I'll already add with the typos\n> > fixed:\n> > \n> > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n> > \n> >> +}\n> >> +\n> >>   int Dw100VertexMap::getVerticesForLength(const int length)\n> >>   {\n> >>          return (length + kDw100BlockSize - 1) / kDw100BlockSize + 1;\n> >> --\n> >> 2.48.1\n> >>\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 2597ABF415\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon,  6 Oct 2025 09:12:42 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 8CD016B5AA;\n\tMon,  6 Oct 2025 11:12:40 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 74CE662C35\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon,  6 Oct 2025 11:12:37 +0200 (CEST)","from ideasonboard.com (unknown\n\t[IPv6:2a00:6020:448c:6c00:2e2:f331:f320:caae])\n\tby perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id 888E91741; \n\tMon,  6 Oct 2025 11:11:04 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"QNH7F7WH\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1759741864;\n\tbh=S18ew3o8qAzZqxftKCNpLhLjAaJtLgbwhizrM8remIQ=;\n\th=In-Reply-To:References:Subject:From:To:Date:From;\n\tb=QNH7F7WH5tRZ6i+QSx9920vmfjOodO/YUUWP0dLMm4uNb10wgl8waBJhmNxqteg+X\n\tZaJh9uItZ2tzdrR14duZpS9ZQRY9gu2qgNCGyvrD1zGKGT94RAIwtWYpEkP5dTx/UY\n\tf765VGYp0i41MWQnsd2Er3S+4fJ2uD7gZ83LmAJw=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<64180f43-c6d5-42c4-a294-8d7f221345cc@ideasonboard.com>","References":"<20250930122726.1837524-1-stefan.klug@ideasonboard.com>\n\t<20250930122726.1837524-29-stefan.klug@ideasonboard.com>\n\t<pcB48_4kpjJqT1HtLw65aigNzDxXY4mnlohVZzncdteiNSs3CWEy-GOxPUBaTDIjJJrto_Ea-2ttwWihOJqabw==@protonmail.internalid>\n\t<175944223744.3754595.11097382497099183341@ping.linuxembedded.co.uk>\n\t<64180f43-c6d5-42c4-a294-8d7f221345cc@ideasonboard.com>","Subject":"Re: [PATCH v1 28/33] libcamera: dw100_vertexmap: Implement\n\tparametric dewarping","From":"Stefan Klug <stefan.klug@ideasonboard.com>","To":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>,\n\tKieran Bingham <kieran.bingham@ideasonboard.com>, \n\tlibcamera-devel@lists.libcamera.org","Date":"Mon, 06 Oct 2025 11:12:34 +0200","Message-ID":"<175974195423.3214037.11335055581926455199@localhost>","User-Agent":"alot/0.12.dev8+g2c003385c862.d20250602","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]