Patch Detail
Show a patch.
GET /api/1.1/patches/18800/?format=api
{ "id": 18800, "url": "https://patchwork.libcamera.org/api/1.1/patches/18800/?format=api", "web_url": "https://patchwork.libcamera.org/patch/18800/", "project": { "id": 1, "url": "https://patchwork.libcamera.org/api/1.1/projects/1/?format=api", "name": "libcamera", "link_name": "libcamera", "list_id": "libcamera_core", "list_email": "libcamera-devel@lists.libcamera.org", "web_url": "", "scm_url": "", "webscm_url": "" }, "msgid": "<20230706013926.218131-2-ben.benson@raspberrypi.com>", "date": "2023-07-06T01:39:24", "name": "[libcamera-devel,1/3] utils: raspberrypi: ctt: Improved color matrix fitting", "commit_ref": null, "pull_url": null, "state": "accepted", "archived": false, "hash": "23d5aedceacbe8b9b95576357b497a1205ea035b", "submitter": { "id": 171, "url": "https://patchwork.libcamera.org/api/1.1/people/171/?format=api", "name": "Ben Benson", "email": "ben.benson@raspberrypi.com" }, "delegate": null, "mbox": "https://patchwork.libcamera.org/patch/18800/mbox/", "series": [ { "id": 3963, "url": "https://patchwork.libcamera.org/api/1.1/series/3963/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=3963", "date": "2023-07-06T01:39:23", "name": "Raspberry Pi Color Matrix and Tuning Tool Improvements", "version": 1, "mbox": "https://patchwork.libcamera.org/series/3963/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/18800/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/18800/checks/", "tags": {}, "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 51B0BC328C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 7 Jul 2023 13:41:25 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id C78E0628BD;\n\tFri, 7 Jul 2023 15:41:23 +0200 (CEST)", "from mail-wm1-x335.google.com (mail-wm1-x335.google.com\n\t[IPv6:2a00:1450:4864:20::335])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 9133561E34\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 7 Jul 2023 14:07:45 +0200 (CEST)", "by mail-wm1-x335.google.com with SMTP id\n\t5b1f17b1804b1-3fbc5d5742eso19560125e9.3\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 07 Jul 2023 05:07:45 -0700 (PDT)", "from localhost.localdomain ([93.93.133.154])\n\tby smtp.gmail.com with ESMTPSA id\n\tz13-20020a7bc7cd000000b003fc0062f0f8sm1155915wmk.9.2023.07.07.05.07.44\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tFri, 07 Jul 2023 05:07:44 -0700 (PDT)" ], "DKIM-Signature": [ "v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1688737283;\n\tbh=2Mogpen63STWa50YQSSO/K7AzmteN0FFW/s81WsDJxg=;\n\th=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=Iukeuk+LVhyLgMF71LFJMq5B7A9q9TnOUMxKbjAvxv3R0CdrZHfvuU2NUgt2UBo8/\n\tuqj6kzmcyhRSSVj9B9lDQp8r1JS5CRq+bEmEQ7B2bG57FX+kUumF59TOadsIqLgQfJ\n\tt+4QjHQS0h5g3fzLCqLAnZESnO+poVAqeZA2gyoKwfz8pIRhBA48Ia3BXxv6ygOpQb\n\t7EWeDNnXF5RZ75NbjcZl1P3gGnlz4BaMEuyg7Tq3fIJjb9Aymfx9ELUNIOa6h3tl47\n\tPTHQFNUsjl/Q0oPhLt+GEkJ6JzYZ2HBE74gpIrlhGyGFAfWxH1B/VqCyINfdkqHpwX\n\tt5VpHnsb3GeFQ==", "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1688731665; x=1691323665;\n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=eTAarbQDEqHf9g87p3Vbym91uqJ1g20prQRcYvbk7u8=;\n\tb=OccmR5Dc3OY1GSkoXK7e5Lc2ILqWehX7laxOtrHcGlYIajWg2KLJItjsDpGOSGWOGC\n\t5QERzJHHLi4qXm0hjah1CVVY/6TP4LL0xTVGKm5iG9cFvxK2sn2NLORCQp3gB9SU72jX\n\tJPlVtwNITz803aCrCCPLUBw29sEYmjvF14MlsDd3FKr3EmjGQAub5uVgoiRQv4lWOfQZ\n\taEyDRkWtc5HpA+pczlcnHerNVRjKSdGCyP+MXDpS0OyZOP9fhT0k/bglErU/DNnpVIGK\n\t8LTopvZS5gLmL29/xrL2huGxnbP4/bpvxUD05R/SrVExPC9IFB3KkPz9siczHK0ir2fC\n\tVXcw==" ], "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (2048-bit key; \n\tunprotected) header.d=raspberrypi.com\n\theader.i=@raspberrypi.com\n\theader.b=\"OccmR5Dc\"; dkim-atps=neutral", "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20221208; t=1688731665; x=1691323665;\n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc\n\t:subject:date:message-id:reply-to;\n\tbh=eTAarbQDEqHf9g87p3Vbym91uqJ1g20prQRcYvbk7u8=;\n\tb=AJdniyZwabSdkvlAVWvPLduL+dQ32fQA6/nAwzuop7Swb20orYYVChZCmPAKND+Dvf\n\tuBslyZF6UYkxQN4jwGmeHGpGTyRK8AK/Y5f+L+UFqz9x0ncXQxj3ZOIp4lXNCw0b2gdB\n\tB6XdP6ccyttRwEDQ2MrsTFFzLsF4e+k+th4NlCSnoEJaxO22kDfg1+zO766wy994TN9r\n\tskdvZuBanGTnA7w9g9jIg9whW/I7jDtsrfcMZA9dupYe+s5DLq3x2U6txyEdl82rmNyl\n\t3vFzEElO4xr3Ynz2iz+8FW3GkX790IxPPTWpWIw+l2nxp7xNsF2SOWkiwizCE/NTw1Zh\n\t9tyQ==", "X-Gm-Message-State": "ABy/qLY0zg4dfNl9N3TZJho0fLfJWwLDRDJXIFsLtge/6rRVGNqqyHrd\n\taEGtJWZH4rhosrHyDNQCDIJg8o5t8K4jTX2MmwwI8g==", "X-Google-Smtp-Source": "APBJJlH0II3DB2XjD8X0bUbXvbnaQs7C/os+iqjk7yTsyIZTztGoLY0NdrvPN9KDdJvBB/IuymoJgA==", "X-Received": "by 2002:a05:600c:2111:b0:3fb:b832:d79a with SMTP id\n\tu17-20020a05600c211100b003fbb832d79amr3849254wml.38.1688731664750; \n\tFri, 07 Jul 2023 05:07:44 -0700 (PDT)", "To": "libcamera-devel@lists.libcamera.org", "Date": "Thu, 6 Jul 2023 02:39:24 +0100", "Message-Id": "<20230706013926.218131-2-ben.benson@raspberrypi.com>", "X-Mailer": "git-send-email 2.34.1", "In-Reply-To": "<20230706013926.218131-1-ben.benson@raspberrypi.com>", "References": "<20230706013926.218131-1-ben.benson@raspberrypi.com>", "MIME-Version": "1.0", "Content-Type": "text/plain; charset=UTF-8", "Content-Transfer-Encoding": "8bit", "X-Mailman-Approved-At": "Fri, 07 Jul 2023 15:41:21 +0200", "Subject": "[libcamera-devel] [PATCH 1/3] utils: raspberrypi: ctt: Improved\n\tcolor matrix fitting", "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>", "From": "Ben Benson via libcamera-devel <libcamera-devel@lists.libcamera.org>", "Reply-To": "Ben Benson <ben.benson@raspberrypi.com>", "Cc": "Ben Benson <ben.benson@raspberrypi.com>", "Errors-To": "libcamera-devel-bounces@lists.libcamera.org", "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>" }, "content": "Added code which optimises the color matrices based off\ndelta E values for the calibration images. Working in LAB\ncolor space.\n\nSigned-off-by Ben Benson <ben.benson@raspberrypi.com>\n---\n utils/raspberrypi/ctt/colors.py | 30 +++\n utils/raspberrypi/ctt/ctt_ccm.py | 258 +++++++++++++++++++++----\n utils/raspberrypi/ctt/ctt_visualise.py | 38 ++++\n 3 files changed, 291 insertions(+), 35 deletions(-)\n create mode 100644 utils/raspberrypi/ctt/colors.py\n create mode 100644 utils/raspberrypi/ctt/ctt_visualise.py", "diff": "diff --git a/utils/raspberrypi/ctt/colors.py b/utils/raspberrypi/ctt/colors.py\nnew file mode 100644\nindex 00000000..bcd87e35\n--- /dev/null\n+++ b/utils/raspberrypi/ctt/colors.py\n@@ -0,0 +1,30 @@\n+# SIMPLE CODE TO CONVERT RGB VALUES TO LAB\n+def RGB_to_LAB(RGB): # where RGB is a 1x3 array. e.g RGB = [100, 255, 230]\n+ num = 0\n+ XYZ = [0, 0, 0]\n+ # converted all the three R, G, B to X, Y, Z\n+ X = RGB[0] * 0.4124 + RGB[1] * 0.3576 + RGB[2] * 0.1805\n+ Y = RGB[0] * 0.2126 + RGB[1] * 0.7152 + RGB[2] * 0.0722\n+ Z = RGB[0] * 0.0193 + RGB[1] * 0.1192 + RGB[2] * 0.9505\n+\n+ XYZ[0] = X / 255 * 100\n+ XYZ[1] = Y / 255 * 100 # XYZ Must be in range 0 -> 100, so scale down from 255\n+ XYZ[2] = Z / 255 * 100\n+ XYZ[0] = XYZ[0] / 95.047 # ref_X = 95.047 Observer= 2°, Illuminant= D65\n+ XYZ[1] = XYZ[1] / 100.0 # ref_Y = 100.000\n+ XYZ[2] = XYZ[2] / 108.883 # ref_Z = 108.883\n+ num = 0\n+ for value in XYZ:\n+ if value > 0.008856:\n+ value = value ** (0.3333333333333333)\n+ else:\n+ value = (7.787 * value) + (16 / 116)\n+ XYZ[num] = value\n+ num = num + 1\n+\n+ # L, A, B, values calculated below\n+ L = (116 * XYZ[1]) - 16\n+ a = 500 * (XYZ[0] - XYZ[1])\n+ b = 200 * (XYZ[1] - XYZ[2])\n+\n+ return [L, a, b]\ndiff --git a/utils/raspberrypi/ctt/ctt_ccm.py b/utils/raspberrypi/ctt/ctt_ccm.py\nindex 376cc712..bd44b4d8 100644\n--- a/utils/raspberrypi/ctt/ctt_ccm.py\n+++ b/utils/raspberrypi/ctt/ctt_ccm.py\n@@ -6,27 +6,66 @@\n \n from ctt_image_load import *\n from ctt_awb import get_alsc_patches\n-\n-\n+import colors\n+from scipy.optimize import minimize\n+from ctt_visualise import visualise_macbeth_chart\n+import numpy as np\n \"\"\"\n takes 8-bit macbeth chart values, degammas and returns 16 bit\n \"\"\"\n+\n+'''\n+This program has many options from which to derive the color matrix from.\n+The first is average. This minimises the average delta E across all patches of\n+the macbeth chart. Testing across all cameras yeilded this as the most color\n+accurate and vivid. Other options are avalible however.\n+Maximum minimises the maximum Delta E of the patches. It iterates through till\n+a minimum maximum is found (so that there is\n+not one patch that deviates wildly.)\n+This yeilds generally good results but overall the colors are less accurate\n+Have a fiddle with maximum and see what you think.\n+The final option allows you to select the patches for which to average across.\n+This means that you can bias certain patches, for instance if you want the\n+reds to be more accurate.\n+'''\n+\n+matrix_selection_types = [\"average\", \"maximum\", \"patches\"]\n+typenum = 0 # select from array above, 0 = average, 1 = maximum, 2 = patches\n+test_patches = [1, 2, 5, 8, 9, 12, 14]\n+\n+'''\n+Enter patches to test for. Can also be entered twice if you\n+would like twice as much bias on one patch.\n+'''\n+\n+\n def degamma(x):\n- x = x / ((2**8)-1)\n- x = np.where(x < 0.04045, x/12.92, ((x+0.055)/1.055)**2.4)\n- x = x * ((2**16)-1)\n+ x = x / ((2 ** 8) - 1) # takes 255 and scales it down to one\n+ x = np.where(x < 0.04045, x / 12.92, ((x + 0.055) / 1.055) ** 2.4)\n+ x = x * ((2 ** 16) - 1) # takes one and scales up to 255\n return x\n \n \n+def gamma(x):\n+ # return (x * * (1 / 2.4) * 1.055 - 0.055)\n+ e = []\n+ for i in range(len(x)):\n+ e.append(((x[i] / 255) ** (1 / 2.4) * 1.055 - 0.055) * 255)\n+ return e\n+\n+\n \"\"\"\n FInds colour correction matrices for list of images\n \"\"\"\n+\n+\n def ccm(Cam, cal_cr_list, cal_cb_list):\n+ global matrix_selection_types, typenum\n imgs = Cam.imgs\n \"\"\"\n standard macbeth chart colour values\n \"\"\"\n- m_rgb = np.array([ # these are in sRGB\n+ m_rgb = np.array([ # these are in RGB\n [116, 81, 67], # dark skin\n [199, 147, 129], # light skin\n [91, 122, 156], # blue sky\n@@ -34,7 +73,7 @@ def ccm(Cam, cal_cr_list, cal_cb_list):\n [130, 128, 176], # blue flower\n [92, 190, 172], # bluish green\n [224, 124, 47], # orange\n- [68, 91, 170], # purplish blue\n+ [68, 91, 170], # purplish blue\n [198, 82, 97], # moderate red\n [94, 58, 106], # purple\n [159, 189, 63], # yellow green\n@@ -52,16 +91,22 @@ def ccm(Cam, cal_cr_list, cal_cb_list):\n [82, 84, 86], # neutral 3.5\n [49, 49, 51] # black 2\n ])\n-\n \"\"\"\n convert reference colours from srgb to rgb\n \"\"\"\n- m_srgb = degamma(m_rgb)\n+ m_srgb = degamma(m_rgb) # now in 16 bit color.\n+\n+ m_lab = []\n+ for col in m_srgb:\n+ m_lab.append(colors.RGB_to_LAB(col / 256))\n+ # This produces matrix of LAB values for ideal color chart)\n+\n \"\"\"\n reorder reference values to match how patches are ordered\n \"\"\"\n m_srgb = np.array([m_srgb[i::6] for i in range(6)]).reshape((24, 3))\n-\n+ m_lab = np.array([m_lab[i::6] for i in range(6)]).reshape((24, 3))\n+ m_rgb = np.array([m_rgb[i::6] for i in range(6)]).reshape((24, 3))\n \"\"\"\n reformat alsc correction tables or set colour_cals to None if alsc is\n deactivated\n@@ -76,8 +121,8 @@ def ccm(Cam, cal_cr_list, cal_cb_list):\n \"\"\"\n normalise tables so min value is 1\n \"\"\"\n- cr_tab = cr_tab/np.min(cr_tab)\n- cb_tab = cb_tab/np.min(cb_tab)\n+ cr_tab = cr_tab / np.min(cr_tab)\n+ cb_tab = cb_tab / np.min(cb_tab)\n colour_cals[cr['ct']] = [cr_tab, cb_tab]\n \n \"\"\"\n@@ -94,6 +139,8 @@ def ccm(Cam, cal_cr_list, cal_cb_list):\n the function will simply return the macbeth patches\n \"\"\"\n r, b, g = get_alsc_patches(Img, colour_cals, grey=False)\n+ # 256 values for each patch of sRGB values\n+\n \"\"\"\n do awb\n Note: awb is done by measuring the macbeth chart in the image, rather\n@@ -101,34 +148,123 @@ def ccm(Cam, cal_cr_list, cal_cb_list):\n and the ccm matrices will be more accurate.\n \"\"\"\n r_greys, b_greys, g_greys = r[3::4], b[3::4], g[3::4]\n- r_g = np.mean(r_greys/g_greys)\n- b_g = np.mean(b_greys/g_greys)\n+ r_g = np.mean(r_greys / g_greys)\n+ b_g = np.mean(b_greys / g_greys)\n r = r / r_g\n b = b / b_g\n-\n \"\"\"\n normalise brightness wrt reference macbeth colours and then average\n each channel for each patch\n \"\"\"\n- gain = np.mean(m_srgb)/np.mean((r, g, b))\n+ gain = np.mean(m_srgb) / np.mean((r, g, b))\n Cam.log += '\\nGain with respect to standard colours: {:.3f}'.format(gain)\n- r = np.mean(gain*r, axis=1)\n- b = np.mean(gain*b, axis=1)\n- g = np.mean(gain*g, axis=1)\n-\n+ r = np.mean(gain * r, axis=1)\n+ b = np.mean(gain * b, axis=1)\n+ g = np.mean(gain * g, axis=1)\n \"\"\"\n calculate ccm matrix\n \"\"\"\n+ # ==== All of below should in sRGB ===##\n+ sumde = 0\n ccm = do_ccm(r, g, b, m_srgb)\n+ # This is the initial guess that our optimisation code works with.\n+\n+ r1 = ccm[0]\n+ r2 = ccm[1]\n+ g1 = ccm[3]\n+ g2 = ccm[4]\n+ b1 = ccm[6]\n+ b2 = ccm[7]\n+ '''\n+ COLOR MATRIX LOOKS AS BELOW\n+ R1 R2 R3 Rval Outr\n+ G1 G2 G3 * Gval = G\n+ B1 B2 B3 Bval B\n+ Will be optimising 6 elements and working out the third element using 1-r1-r2 = r3\n+ '''\n+\n+ x0 = [r1, r2, g1, g2, b1, b2]\n+ '''\n+ We use our old CCM as the initial guess for the program to find the\n+ optimised matrix\n+ '''\n+ result = minimize(guess, x0, args=(r, g, b, m_lab), tol=0.0000000001)\n+ '''\n+ This produces a color matrix which has the lowest delta E possible,\n+ based off the input data. Note it is impossible for this to reach\n+ zero since inperfect data.\n+ '''\n+\n+ Cam.log += (\"\\n \\n Optimised Matrix Below: \\n \\n\")\n+ [r1, r2, g1, g2, b1, b2] = result.x\n+ # The new, optimised color correction matrix values\n+ optimised_ccm = [r1, r2, (1 - r1 - r2), g1, g2, (1 - g1 - g2), b1, b2, (1 - b1 - b2)]\n+ # This is the optimised Color Matrix (preserving greys by summing rows up to 1)\n+ Cam.log += str(optimised_ccm)\n+ Cam.log += \"\\n Old Color Correction Matrix Below \\n\"\n+ Cam.log += str(ccm)\n+\n+ formatted_ccm = np.array(ccm).reshape((3, 3))\n+\n+ '''\n+ below is a whole load of code that then applies the latest color\n+ matrix, and returns LAB values for color. This can then be used\n+ to calculate the final delta E\n+ '''\n+ optimised_ccm_rgb = [] # Original Color Corrected Matrix RGB / LAB\n+ optimised_ccm_lab = []\n+ for w in range(24):\n+ RGB = np.array([r[w], g[w], b[w]])\n+ ccm_applied_rgb = np.dot(formatted_ccm, (RGB / 256))\n+ optimised_ccm_rgb.append(gamma(ccm_applied_rgb))\n+ optimised_ccm_lab.append(colors.RGB_to_LAB(ccm_applied_rgb))\n+\n+ formatted_optimised_ccm = np.array(ccm).reshape((3, 3))\n+ after_gamma_rgb = []\n+ after_gamma_lab = []\n+ for w in range(24):\n+ RGB = np.array([r[w], g[w], b[w]])\n+ optimised_ccm_applied_rgb = np.dot(formatted_optimised_ccm, RGB / 256)\n+ after_gamma_rgb.append(gamma(optimised_ccm_applied_rgb))\n+ after_gamma_lab.append(colors.RGB_to_LAB(optimised_ccm_applied_rgb))\n+ '''\n+ Gamma After RGB / LAB\n+ We now want to spit out some data that shows\n+ how the optimisation has improved the color matricies\n+ '''\n+ Cam.log += \"Here are the Improvements\"\n+\n+ # CALCULATE WORST CASE delta e\n+ old_worst_delta_e = 0\n+ bavg = transform_and_evaluate(formatted_ccm, r, g, b, m_lab)\n+ new_worst_delta_e = 0\n+ aavg = transform_and_evaluate(formatted_optimised_ccm, r, g, b, m_lab)\n+ for i in range(24):\n+ old_delta_e = deltae(optimised_ccm_lab[i], m_lab[i]) # Current Old Delta E\n+ new_delta_e = deltae(after_gamma_lab[i], m_lab[i]) # Current New Delta E\n+ if old_delta_e > old_worst_delta_e:\n+ old_worst_delta_e = old_delta_e\n+ if new_delta_e > new_worst_delta_e:\n+ new_worst_delta_e = new_delta_e\n+\n+ Cam.log += \"Before color correction matrix was optimised, we got an average delta E of \" + str(bavg) + \" and a maximum delta E of \" + str(old_worst_delta_e)\n+ Cam.log += \"After color correction matrix was optimised, we got an average delta E of \" + str(aavg) + \" and a maximum delta E of \" + str(new_worst_delta_e)\n+\n+ visualise_macbeth_chart(m_rgb, optimised_ccm_rgb, after_gamma_rgb, str(Img.col) + str(matrix_selection_types[typenum]))\n+ '''\n+ The program will also save some visualisations of improvements.\n+ Very pretty to look at. Top rectangle is ideal, Left square is\n+ before optimisation, right square is after.\n+ '''\n \n \"\"\"\n if a ccm has already been calculated for that temperature then don't\n overwrite but save both. They will then be averaged later on\n- \"\"\"\n+ \"\"\" # NOW GOING TO USE OPTIMISED COLOR MATRIX, optimised_ccm\n if Img.col in ccm_tab.keys():\n- ccm_tab[Img.col].append(ccm)\n+ ccm_tab[Img.col].append(optimised_ccm)\n else:\n- ccm_tab[Img.col] = [ccm]\n+ ccm_tab[Img.col] = [optimised_ccm]\n Cam.log += '\\n'\n \n Cam.log += '\\nFinished processing images'\n@@ -137,8 +273,8 @@ def ccm(Cam, cal_cr_list, cal_cb_list):\n \"\"\"\n for k, v in ccm_tab.items():\n tab = np.mean(v, axis=0)\n- tab = np.where((10000*tab) % 1 <= 0.05, tab+0.00001, tab)\n- tab = np.where((10000*tab) % 1 >= 0.95, tab-0.00001, tab)\n+ tab = np.where((10000 * tab) % 1 <= 0.05, tab + 0.00001, tab)\n+ tab = np.where((10000 * tab) % 1 >= 0.95, tab - 0.00001, tab)\n ccm_tab[k] = list(np.round(tab, 5))\n Cam.log += '\\nMatrix calculated for colour temperature of {} K'.format(k)\n \n@@ -156,6 +292,50 @@ def ccm(Cam, cal_cr_list, cal_cb_list):\n return ccms\n \n \n+def guess(x0, r, g, b, m_lab): # provides a method of numerical feedback for the optimisation code\n+ [r1, r2, g1, g2, b1, b2] = x0\n+ ccm = np.array([r1, r2, (1 - r1 - r2),\n+ g1, g2, (1 - g1 - g2),\n+ b1, b2, (1 - b1 - b2)]).reshape((3, 3)) # format the matrix correctly\n+ return transform_and_evaluate(ccm, r, g, b, m_lab)\n+\n+\n+def transform_and_evaluate(ccm, r, g, b, m_lab): # Transforms colors to LAB and applies the correction matrix\n+ # create list of matrix changed colors\n+ realrgb = []\n+ for i in range(len(r)):\n+ RGB = np.array([r[i], g[i], b[i]])\n+ rgb_post_ccm = np.dot(ccm, RGB) # This is RGB values after the color correction matrix has been applied\n+ realrgb.append(colors.RGB_to_LAB(rgb_post_ccm))\n+ # now compare that with m_lab and return numeric result, averaged for each patch\n+ return (sumde(realrgb, m_lab) / 24) # returns an average result of delta E\n+\n+\n+def sumde(listA, listB):\n+ global typenum, test_patches\n+ sumde = 0\n+ maxde = 0\n+ patchde = []\n+ for i in range(len(listA)):\n+ if maxde < (deltae(listA[i], listB[i])):\n+ maxde = deltae(listA[i], listB[i])\n+ patchde.append(deltae(listA[i], listB[i]))\n+ sumde += deltae(listA[i], listB[i])\n+ '''\n+ The different options specified at the start allow for\n+ the maximum to be returned, average or specific patches\n+ '''\n+ if typenum == 0:\n+ return sumde\n+ if typenum == 1:\n+ return maxde\n+ if typenum == 2:\n+ output = 0\n+ for y in range(len(test_patches)):\n+ output += patchde[test_patches[y]] # grabs the specific patches (no need for averaging here)\n+ return output\n+\n+\n \"\"\"\n calculates the ccm for an individual image.\n ccms are calculate in rgb space, and are fit by hand. Although it is a 3x3\n@@ -164,12 +344,14 @@ calculation.\n Should you want to fit them in another space (e.g. LAB) we wish you the best of\n luck and send us the code when you are done! :-)\n \"\"\"\n+\n+\n def do_ccm(r, g, b, m_srgb):\n rb = r-b\n gb = g-b\n- rb_2s = (rb*rb)\n- rb_gbs = (rb*gb)\n- gb_2s = (gb*gb)\n+ rb_2s = (rb * rb)\n+ rb_gbs = (rb * gb)\n+ gb_2s = (gb * gb)\n \n r_rbs = rb * (m_srgb[..., 0] - b)\n r_gbs = gb * (m_srgb[..., 0] - b)\n@@ -191,7 +373,7 @@ def do_ccm(r, g, b, m_srgb):\n b_rb = np.sum(b_rbs)\n b_gb = np.sum(b_gbs)\n \n- det = rb_2*gb_2 - rb_gb*rb_gb\n+ det = rb_2 * gb_2 - rb_gb * rb_gb\n \n \"\"\"\n Raise error if matrix is singular...\n@@ -201,19 +383,19 @@ def do_ccm(r, g, b, m_srgb):\n if det < 0.001:\n raise ArithmeticError\n \n- r_a = (gb_2*r_rb - rb_gb*r_gb)/det\n- r_b = (rb_2*r_gb - rb_gb*r_rb)/det\n+ r_a = (gb_2 * r_rb - rb_gb * r_gb) / det\n+ r_b = (rb_2 * r_gb - rb_gb * r_rb) / det\n \"\"\"\n Last row can be calculated by knowing the sum must be 1\n \"\"\"\n r_c = 1 - r_a - r_b\n \n- g_a = (gb_2*g_rb - rb_gb*g_gb)/det\n- g_b = (rb_2*g_gb - rb_gb*g_rb)/det\n+ g_a = (gb_2 * g_rb - rb_gb * g_gb) / det\n+ g_b = (rb_2 * g_gb - rb_gb * g_rb) / det\n g_c = 1 - g_a - g_b\n \n- b_a = (gb_2*b_rb - rb_gb*b_gb)/det\n- b_b = (rb_2*b_gb - rb_gb*b_rb)/det\n+ b_a = (gb_2 * b_rb - rb_gb * b_gb) / det\n+ b_b = (rb_2 * b_gb - rb_gb * b_rb) / det\n b_c = 1 - b_a - b_b\n \n \"\"\"\n@@ -222,3 +404,9 @@ def do_ccm(r, g, b, m_srgb):\n ccm = [r_a, r_b, r_c, g_a, g_b, g_c, b_a, b_b, b_c]\n \n return ccm\n+\n+\n+def deltae(colorA, colorB):\n+ return ((colorA[0] - colorB[0]) ** 2 + (colorA[1] - colorB[1]) ** 2 + (colorA[2] - colorB[2]) ** 2) ** 0.5\n+ # return ((colorA[1]-colorB[1]) * * 2 + (colorA[2]-colorB[2]) * * 2) * * 0.5\n+ # UNCOMMENT IF YOU WANT TO NEGLECT LUMINANCE FROM CALCULATION OF DELTA E\ndiff --git a/utils/raspberrypi/ctt/ctt_visualise.py b/utils/raspberrypi/ctt/ctt_visualise.py\nnew file mode 100644\nindex 00000000..fe5381c6\n--- /dev/null\n+++ b/utils/raspberrypi/ctt/ctt_visualise.py\n@@ -0,0 +1,38 @@\n+# Some code that will save virtual macbeth charts that show the difference between optimised matricies and non optimised matricies\n+import numpy as np\n+from PIL import Image\n+'''make patch 200 by 200 pixels:\n+200x100 will be correct color\n+100x100 will be new color, 100x100 old color\n+if 50px gap between pixels, image will be 7 gaps + 6 patches wide, and 5 gaps + 4 patches high.\n+ y --->\n+x\n+|\n+|\n+'''\n+\n+\n+def visualise_macbeth_chart(macbeth_rgb, original_rgb, new_rgb, output_filename):\n+ image = np.zeros((1050, 1550, 3), dtype=np.uint8)\n+ colorindex = -1\n+ for y in range(6):\n+ for x in range(4): # Creates 6 x 4 grid of macbeth chart\n+ colorindex += 1\n+ xlocation = 50 + 250 * x # Means there is 50px of black gap between each square, more like the real macbeth chart.\n+ ylocation = 50 + 250 * y\n+ for g in range(200):\n+ for i in range(100):\n+ image[xlocation + i, ylocation + g] = macbeth_rgb[colorindex]\n+ xlocation = 150 + 250 * x\n+ ylocation = 50 + 250 * y\n+ for i in range(100):\n+ for g in range(100):\n+ image[xlocation + i, ylocation + g] = original_rgb[colorindex] # Smaller squares below to compare the old colors with the new ones\n+ xlocation = 150 + 250 * x\n+ ylocation = 150 + 250 * y\n+ for i in range(100):\n+ for g in range(100):\n+ image[xlocation + i, ylocation + g] = new_rgb[colorindex]\n+\n+ img = Image.fromarray(image, 'RGB')\n+ img.save(str(output_filename) + 'Generated Macbeth Chart.png')\n", "prefixes": [ "libcamera-devel", "1/3" ] }