使用opencv和色卡进行色彩校正

Sum*_*-Al 6 python opencv image-processing scikit-image

我一直在寻找进行一些基本颜色校正的自动化方法,并且发现了这篇博客文章。

https://www.pyimagesearch.com/2021/02/15/automatic-color- Correction-with-opencv-and-python/

在此输入图像描述

python color_correction.py --reference ref.jpg  --input input.jpg
Run Code Online (Sandbox Code Playgroud)

总结这篇博文,它可以识别给定输入图像中的 Pantone 色卡,修改直方图以匹配具有实际颜色的参考 Pantone 色卡上的颜色。由于照明引起的任何颜色偏移都将在输入的色卡中进行调整。

我有一个查询作为您在博客文章中描述的用例的扩展。虽然直方图匹配在裁剪到色卡边界的两个图像之间发生得很好,但它现在仅应用于存在色卡的裁剪后的输入图像。我想在整个输入图像上应用这种直方图变换 - 也超出色卡 - 我该如何去做呢? 在此输入图像描述 我们可以保存 match_histpgram 函数的转换并将其应用于整个图像吗?

编辑1:这是我尝试过的。 https://github.com/Sum-Al/color_ Correction

cod*_*kas 5

如果您遵循skimage 教程,您可以得出以下方法,该方法使用任何类型的图像而不是调色板:

import matplotlib.pyplot as plt
import numpy as np
from skimage import data
from skimage import exposure
from skimage.exposure import match_histograms

reference = np.array(data.coffee(), dtype=np.uint8)
image = np.array(data.chelsea(), dtype=np.uint8)
matched = match_histograms(image, reference, channel_axis=-1)

test = match_histograms(matched, image, channel_axis=-1)

fig, (ax1, ax2, ax3) = plt.subplots(nrows=1, ncols=3, figsize=(8, 3), sharex=True, sharey=True)
for aa in (ax1, ax2, ax3):
    aa.set_axis_off()

ax1.imshow(image)
ax1.set_title('Source')
ax2.imshow(matched)
ax2.set_title('Reference')
ax3.imshow(test)
ax3.set_title('Matched')

plt.tight_layout()
plt.show()
Run Code Online (Sandbox Code Playgroud)

产生以下结果: 直方图匹配

您还可以查看相应的直方图:

fig, axes = plt.subplots(nrows=3, ncols=3, figsize=(8, 8))


for i, img in enumerate((image, matched, test)):
    for c, c_color in enumerate(('red', 'green', 'blue')):
        img_hist, bins = exposure.histogram(img[..., c], source_range='dtype')
        axes[c, i].plot(bins, img_hist / img_hist.max())
        img_cdf, bins = exposure.cumulative_distribution(img[..., c])
        axes[c, i].plot(bins, img_cdf)
        axes[c, 0].set_ylabel(c_color)

axes[0, 0].set_title('Source')
axes[0, 1].set_title('Reference')
axes[0, 2].set_title('Matched')

plt.tight_layout()
plt.show()
Run Code Online (Sandbox Code Playgroud)

正如您所观察到的,匹配过程后参考图像和“匹配”图像的直方图看起来很相似。

直方图

编辑:请注意,从版本 0.19 开始,该multichannel参数已被弃用,取而代之的是该参数。参考channel_axis

编辑2:如果你想存储这个“转换”,你有两个选择:

第一个也是最简单的方法是在应用匹配时继续传递参考图像。

另一种方法是存储由 的函数计算的每个通道的相关分位数skimage,该分位数在幕后_match_cumulative_cdf使用,并以与该函数相同的方式应用插值。match_histograms

def _match_cumulative_cdf(source, template):
    """
    Return modified source array so that the cumulative density function of
    its values matches the cumulative density function of the template.
    """
    src_values, src_unique_indices, src_counts = np.unique(source.ravel(),
                                                           return_inverse=True,
                                                           return_counts=True)
    tmpl_values, tmpl_counts = np.unique(template.ravel(), return_counts=True)

    # calculate normalized quantiles for each array
    src_quantiles = np.cumsum(src_counts) / source.size
    tmpl_quantiles = np.cumsum(tmpl_counts) / template.size

    interp_a_values = np.interp(src_quantiles, tmpl_quantiles, tmpl_values)
    return interp_a_values[src_unique_indices].reshape(source.shape)
Run Code Online (Sandbox Code Playgroud)


daz*_*act 5

好的,这是最终的工作脚本。也感谢“code-lukas”提示。\n您只需要一个已经优化的颜色输入图像和另一个未进行颜色优化的图像。两张图像都带有色卡,使用ArUCo 标记\n(您可以将它们粘贴在每个图像卡的角上进行检测)

\n

https://github.com/dazzafact/image_color_ Correction

\n

输入颜色优化:

\n

输入参考,颜色优化

\n

输入未进行颜色优化

\n

输入未进行颜色优化

\n

色彩优化的输出图像

\n

最终颜色优化的输出图像

\n

使用此参数使用脚本

\n
\n

python color_ Correction.py --reference ref.jpg --输入 input.jpg --输出 out.jpg

\n
\n

https://github.com/dazzafact/image_color_ Correction

\n
from imutils.perspective import four_point_transform\nfrom skimage import exposure\nimport numpy as np\nimport argparse\nimport imutils\nimport cv2\nimport sys\nfrom os.path import exists\nimport os.path as pathfile\nfrom PIL import Image\n\n\ndef find_color_card(image):\n    # load the ArUCo dictionary, grab the ArUCo parameters, and\n    # detect the markers in the input image\n    arucoDict = cv2.aruco.Dictionary_get(cv2.aruco.DICT_ARUCO_ORIGINAL)\n    arucoParams = cv2.aruco.DetectorParameters_create()\n    (corners, ids, rejected) = cv2.aruco.detectMarkers(image,\n                                                       arucoDict, parameters=arucoParams)\n\n    # try to extract the coordinates of the color correction card\n    try:\n        # otherwise, we\'ve found the four ArUco markers, so we can\n        # continue by flattening the ArUco IDs list\n        ids = ids.flatten()\n\n        # extract the top-left marker\n        i = np.squeeze(np.where(ids == 923))\n        topLeft = np.squeeze(corners[i])[0]\n\n        # extract the top-right marker\n        i = np.squeeze(np.where(ids == 1001))\n        topRight = np.squeeze(corners[i])[1]\n\n        # extract the bottom-right marker\n        i = np.squeeze(np.where(ids == 241))\n        bottomRight = np.squeeze(corners[i])[2]\n\n        # extract the bottom-left marker\n        i = np.squeeze(np.where(ids == 1007))\n        bottomLeft = np.squeeze(corners[i])[3]\n\n    # we could not find color correction card, so gracefully return\n    except:\n        return None\n\n    # build our list of reference points and apply a perspective\n    # transform to obtain a top-down, bird\xe2\x80\x99s-eye view of the color\n    # matching card\n    cardCoords = np.array([topLeft, topRight,\n                           bottomRight, bottomLeft])\n    card = four_point_transform(image, cardCoords)\n    # return the color matching card to the calling function\n    return card\n\n\ndef _match_cumulative_cdf_mod(source, template, full):\n    """\n    Return modified full image array so that the cumulative density function of\n    source array matches the cumulative density function of the template.\n    """\n    src_values, src_unique_indices, src_counts = np.unique(source.ravel(),\n                                                           return_inverse=True,\n                                                           return_counts=True)\n    tmpl_values, tmpl_counts = np.unique(template.ravel(), return_counts=True)\n\n    # calculate normalized quantiles for each array\n    src_quantiles = np.cumsum(src_counts) / source.size\n    tmpl_quantiles = np.cumsum(tmpl_counts) / template.size\n\n    interp_a_values = np.interp(src_quantiles, tmpl_quantiles, tmpl_values)\n\n    # Here we compute values which the channel RGB value of full image will be modified to.\n    interpb = []\n    for i in range(0, 256):\n        interpb.append(-1)\n\n    # first compute which values in src image transform to and mark those values.\n\n    for i in range(0, len(interp_a_values)):\n        frm = src_values[i]\n        to = interp_a_values[i]\n        interpb[frm] = to\n\n    # some of the pixel values might not be there in interp_a_values, interpolate those values using their\n    # previous and next neighbours\n    prev_value = -1\n    prev_index = -1\n    for i in range(0, 256):\n        if interpb[i] == -1:\n            next_index = -1\n            next_value = -1\n            for j in range(i + 1, 256):\n                if interpb[j] >= 0:\n                    next_value = interpb[j]\n                    next_index = j\n            if prev_index < 0:\n                interpb[i] = (i + 1) * next_value / (next_index + 1)\n            elif next_index < 0:\n                interpb[i] = prev_value + ((255 - prev_value) * (i - prev_index) / (255 - prev_index))\n            else:\n                interpb[i] = prev_value + (i - prev_index) * (next_value - prev_value) / (next_index - prev_index)\n        else:\n            prev_value = interpb[i]\n            prev_index = i\n\n    # finally transform pixel values in full image using interpb interpolation values.\n    wid = full.shape[1]\n    hei = full.shape[0]\n    ret2 = np.zeros((hei, wid))\n    for i in range(0, hei):\n        for j in range(0, wid):\n            ret2[i][j] = interpb[full[i][j]]\n    return ret2\n\n\ndef match_histograms_mod(inputCard, referenceCard, fullImage):\n    """\n        Return modified full image, by using histogram equalizatin on input and\n         reference cards and applying that transformation on fullImage.\n    """\n    if inputCard.ndim != referenceCard.ndim:\n        raise ValueError(\'Image and reference must have the same number \'\n                         \'of channels.\')\n    matched = np.empty(fullImage.shape, dtype=fullImage.dtype)\n    for channel in range(inputCard.shape[-1]):\n        matched_channel = _match_cumulative_cdf_mod(inputCard[..., channel], referenceCard[..., channel],\n                                                    fullImage[..., channel])\n        matched[..., channel] = matched_channel\n    return matched\n\n\n# construct the argument parser and parse the arguments\nap = argparse.ArgumentParser()\nap.add_argument("-r", "--reference", required=True,\n                help="path to the input reference image")\nap.add_argument("-v", "--view", required=False, default=False, action=\'store_true\',\n                help="Image Preview?")\nap.add_argument("-o", "--output", required=False, default=False,\n                help="Image Output Path")\nap.add_argument("-i", "--input", required=True,\n                help="path to the input image to apply color correction to")\nargs = vars(ap.parse_args())\n\n# load the reference image and input images from disk\nprint("[INFO] loading images...")\n# raw = cv2.imread(args["reference"])\n# img1 = cv2.imread(args["input"])\nfile_exists = pathfile.isfile(args["reference"])\nprint(file_exists)\n\nif not file_exists:\n    print(\'[WARNING] Referenz File not exisits \'+str(args["reference"]))\n    sys.exit()\n\n\nraw = cv2.imread(args["reference"])\nimg1 = cv2.imread(args["input"])\n# resize the reference and input images\n\n#raw = imutils.resize(raw, width=301)\n#img1 = imutils.resize(img1, width=301)\nraw = imutils.resize(raw, width=600)\nimg1 = imutils.resize(img1, width=600)\n# display the reference and input images to our screen\nif args[\'view\']:\n    cv2.imshow("Reference", raw)\n    cv2.imshow("Input", img1)\n\n# find the color matching card in each image\nprint("[INFO] finding color matching cards...")\nrawCard = find_color_card(raw)\nimageCard = find_color_card(img1)\n# if the color matching card is not found in either the reference\n# image or the input image, gracefully exit\nif rawCard is None or imageCard is None:\n    print("[INFO] could not find color matching card in both images")\n    sys.exit(0)\n\n# show the color matching card in the reference image and input image,\n# respectively\nif args[\'view\']:\n    cv2.imshow("Reference Color Card", rawCard)\n    cv2.imshow("Input Color Card", imageCard)\n# apply histogram matching from the color matching card in the\n# reference image to the color matching card in the input image\nprint("[INFO] matching images...")\n\n# imageCard2 = exposure.match_histograms(img1, ref,\n# inputCard = exposure.match_histograms(inputCard, referenceCard, multichannel=True)\nresult2 = match_histograms_mod(imageCard, rawCard, img1)\n \n# show our input color matching card after histogram matching\ncv2.imshow("Input Color Card After Matching", inputCard)\n\n\nif args[\'view\']:\n    cv2.imshow("result2", result2)\n\nif args[\'output\']:\n    file_ok = exists(args[\'output\'].lower().endswith((\'.png\', \'.jpg\', \'.jpeg\', \'.tiff\', \'.bmp\', \'.gif\')))\n\n    if file_ok:\n        cv2.imwrite(args[\'output\'], result2)\n        print("[SUCCESSUL] Your Image was written to: "+args[\'output\']+"")\n    else:\n        print("[WARNING] Sorry, But this is no valid Image Name "+args[\'output\']+"\\nPlease Change Parameter!")\n\nif args[\'view\']:\n    cv2.waitKey(0)\n\nif not args[\'view\']:\n    if not args[\'output\']:\n        print(\'[EMPTY] You Need at least one Paramter "--view" or "--output".\')\n
Run Code Online (Sandbox Code Playgroud)\n