From patchwork Fri Jul 24 14:56:18 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Plowman X-Patchwork-Id: 8978 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 6E367BD86F for ; Fri, 24 Jul 2020 14:56:42 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3A5B261220; Fri, 24 Jul 2020 16:56:42 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=raspberrypi.com header.i=@raspberrypi.com header.b="obYuQlm7"; dkim-atps=neutral Received: from mail-wr1-x42c.google.com (mail-wr1-x42c.google.com [IPv6:2a00:1450:4864:20::42c]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id DE1746123B for ; Fri, 24 Jul 2020 16:56:40 +0200 (CEST) Received: by mail-wr1-x42c.google.com with SMTP id r2so3503109wrs.8 for ; Fri, 24 Jul 2020 07:56:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=raspberrypi.com; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=9Qul+YVzNIOpRkBcJtc0uvzNviGiy+CJSxvosrLYLoU=; b=obYuQlm7xUY9SDaN87VRbdT17itdjBkJRtZdg9wR1o87qUa1grN7afY7EpwaDe4NLo EvbVp7WQQj4YawIbM3icRzkQH8i+vGl9VsCA1X7g/vATYpK2f2bgFVy6DmOReHhzJnPY OSU/QLkwAhOo45FmIAlZoZBYPIBUDJT9+QinhNTANLQ9Z96y5M7WPInRkl2Gdu/+0TEc 9NIxhnHRtcdDxhWWNrza8XtohM/jhE6mEkQStgt+I+SFsyr6HS8+VleQuX/Pgwvum64Q j/dzdfYHM2ut4573d3N2erafNHmqvtSTGNuYR+Dvpcaad1JyvBkSKOubMRay4cN7DUfT 7tJg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=9Qul+YVzNIOpRkBcJtc0uvzNviGiy+CJSxvosrLYLoU=; b=BMFoZOOUIFRJcct5bhtKE7ZiJhiJYCu/JV+w4K71HOcon52yGox4msf51npibxmY4Q zzH/LmEzfjHk0liTiB1ss6DwCkjV08EvJYsr08Lps/TMk3OR3tjEPeXwjoVU/03sbEEM dkSjlZs6UACkT9d1riMsCJyZj9OKjmtBiZowNq2wv4Gfdby1gPKgMAaSyx7dSW3w6I2t TFrgilC0oUzKlAgyEI3b2gNOGeE80Xc+UuP6yx29/Lg7fpMgTUauukzG86jHD428JL1Q IZsTJTc5gXdGOyH8iJyKqo0/boxbbVc08eIBGLj1OT23wq+jnGcxBrdKmNvzpp0AN9LM 2yTA== X-Gm-Message-State: AOAM533Rk2AYP/X8fAEBQ+fjjQX7QMze5fc3YEbSNDmTtayEjZWtK7ap Uj4RR/j+VVjW0YX7MEV6SRgFJ0W8j0IWkg== X-Google-Smtp-Source: ABdhPJzXHGLyODKv1dKbmBP3XAFG8nITlbQfouZg9HolBq8RxqvInUfuSE9LpH7IDtdVJq3Z6afA6g== X-Received: by 2002:adf:c386:: with SMTP id p6mr8926727wrf.344.1595602600146; Fri, 24 Jul 2020 07:56:40 -0700 (PDT) Received: from pi4-davidp.lan (plowpeople3.plus.com. [80.229.223.72]) by smtp.gmail.com with ESMTPSA id h5sm2073156wrc.97.2020.07.24.07.56.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 24 Jul 2020 07:56:39 -0700 (PDT) From: David Plowman To: libcamera-devel@lists.libcamera.org Date: Fri, 24 Jul 2020 15:56:18 +0100 Message-Id: <20200724145618.26304-4-david.plowman@raspberrypi.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200724145618.26304-1-david.plowman@raspberrypi.com> References: <20200724145618.26304-1-david.plowman@raspberrypi.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 3/3] libcamera: qcam: Improve colour information in DNG files 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" This patch improves the colour information recorded in DNG files using the ColourCorrectionMatrix metadata for the image. Note that we are not supplying a full calibration using two illuminants, nonetheless the single matrix here appears to be respected by a number of tools. Signed-off-by: David Plowman Reviewed-by: Laurent Pinchart --- src/qcam/dng_writer.cpp | 138 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/src/qcam/dng_writer.cpp b/src/qcam/dng_writer.cpp index 61505d3..4f638ec 100644 --- a/src/qcam/dng_writer.cpp +++ b/src/qcam/dng_writer.cpp @@ -34,6 +34,97 @@ struct FormatInfo { unsigned int stride); }; +struct Matrix3d { + Matrix3d() + { + } + + Matrix3d(float m0, float m1, float m2, + float m3, float m4, float m5, + float m6, float m7, float m8) + { + m[0] = m0, m[1] = m1, m[2] = m2; + m[3] = m3, m[4] = m4, m[5] = m5; + m[6] = m6, m[7] = m7, m[8] = m8; + } + + Matrix3d(Span const &span) + : Matrix3d(span[0], span[1], span[2], + span[3], span[4], span[5], + span[6], span[7], span[8]) + { + } + + static Matrix3d diag(float diag0, float diag1, float diag2) + { + return Matrix3d(diag0, 0, 0, 0, diag1, 0, 0, 0, diag2); + } + + static Matrix3d identity() + { + return Matrix3d(1, 0, 0, 0, 1, 0, 0, 0, 1); + } + + Matrix3d transpose() const + { + return { m[0], m[3], m[6], m[1], m[4], m[7], m[2], m[5], m[8] }; + } + + Matrix3d cofactors() const + { + return { m[4] * m[8] - m[5] * m[7], + -(m[3] * m[8] - m[5] * m[6]), + m[3] * m[7] - m[4] * m[6], + -(m[1] * m[8] - m[2] * m[7]), + m[0] * m[8] - m[2] * m[6], + -(m[0] * m[7] - m[1] * m[6]), + m[1] * m[5] - m[2] * m[4], + -(m[0] * m[5] - m[2] * m[3]), + m[0] * m[4] - m[1] * m[3] }; + } + + Matrix3d adjugate() const + { + return cofactors().transpose(); + } + + float determinant() const + { + return m[0] * (m[4] * m[8] - m[5] * m[7]) - + m[1] * (m[3] * m[8] - m[5] * m[6]) + + m[2] * (m[3] * m[7] - m[4] * m[6]); + } + + Matrix3d inverse() const + { + return adjugate() * (1.0 / determinant()); + } + + Matrix3d operator*(Matrix3d const &other) const + { + Matrix3d result; + for (unsigned int i = 0; i < 3; i++) { + for (unsigned int j = 0; j < 3; j++) { + result.m[i * 3 + j] = + m[i * 3 + 0] * other.m[0 + j] + + m[i * 3 + 1] * other.m[3 + j] + + m[i * 3 + 2] * other.m[6 + j]; + } + } + return result; + } + + Matrix3d operator*(float f) const + { + Matrix3d result; + for (unsigned int i = 0; i < 9; i++) + result.m[i] = m[i] * f; + return result; + } + + float m[9]; +}; + void packScanlineSBGGR10P(void *output, const void *input, unsigned int width) { const uint8_t *in = static_cast(input); @@ -315,6 +406,53 @@ int DNGWriter::write(const char *filename, const Camera *camera, TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT); + /* + * Fill in some reasonable colour information in the DNG. We supply + * the "neutral" colour values which determine the white balance, and the + * "ColorMatrix1" which converts XYZ to (un-white-balanced) camera RGB. + * Note that this is not a "proper" colour calibration for the DNG, + * nonetheless, many tools should be able to render the colours better. + */ + float neutral[3] = { 1, 1, 1 }; + Matrix3d wbGain = Matrix3d::identity(); + /* From http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html */ + const Matrix3d rgb2xyz(0.4124564, 0.3575761, 0.1804375, + 0.2126729, 0.7151522, 0.0721750, + 0.0193339, 0.1191920, 0.9503041); + Matrix3d ccm = Matrix3d::identity(); + /* + * Pick a reasonable number eps to protect against singularities. It + * should be comfortably larger than the point at which we run into + * numerical trouble, yet smaller than any plausible gain that we might + * apply to a colour, either explicitly or as part of the colour matrix. + */ + const double eps = 1e-2; + + if (metadata.contains(controls::ColourGains)) { + Span const &colourGains = metadata.get(controls::ColourGains); + if (colourGains[0] > eps && colourGains[1] > eps) { + wbGain = Matrix3d::diag(colourGains[0], 1, colourGains[1]); + neutral[0] = 1.0 / colourGains[0]; /* red */ + neutral[2] = 1.0 / colourGains[1]; /* blue */ + } + } + if (metadata.contains(controls::ColourCorrectionMatrix)) { + Span const &coeffs = metadata.get(controls::ColourCorrectionMatrix); + Matrix3d ccm_supplied(coeffs); + if (ccm_supplied.determinant() > eps) + ccm = ccm_supplied; + } + + /* + * rgb2xyz is known to be invertible, and we've ensured above that both + * the ccm and wbGain matrices are non-singular, so the product of all + * three is guaranteed to be invertible too. + */ + Matrix3d colorMatrix1 = (rgb2xyz * ccm * wbGain).inverse(); + + TIFFSetField(tif, TIFFTAG_COLORMATRIX1, 9, colorMatrix1.m); + TIFFSetField(tif, TIFFTAG_ASSHOTNEUTRAL, 3, neutral); + /* * Reserve space for the SubIFD and ExifIFD tags, pointing to the IFD * for the raw image and EXIF data respectively. The real offsets will