上周我们实现了如何进行直方图匹配。使用直方图匹配,我们可以获取一幅图像的颜色分布并将其与另一幅图像匹配。
色彩匹配的一个实际应用是通过色彩恒常性来执行基本色彩校正。颜色恒定性的目标是正确感知物体的颜色,而不管光源、照明等的差异(正如您想象的那样,说起来容易做起来难)。
摄影师和计算机视觉从业者可以通过使用颜色校正卡来帮助获得颜色稳定性,比如下面这张:
使用色彩校正/色彩恒常卡,我们可以:
在本指南的最后,您将了解如何使用颜色校正卡与直方图匹配来构建基本的颜色校正器的基础知识,而不管图像是在什么样的光照条件下捕获的。
在本教程的第一部分,我们将讨论什么是色彩校正和色彩恒定性,包括 OpenCV 如何促进自动色彩校正。 然后我们将为这个项目配置我们的开发环境并查看我们的项目目录结构。 然后,我们将实现一个利用 OpenCV 执行色彩校正的 Python 脚本。
人类视觉系统受到光照和光源的显着影响。颜色恒常性是指研究人类如何感知颜色。
例如,请查看维基百科关于颜色恒常性的文章中的以下图片:
看着这张卡片,似乎粉红色的阴影(左起第二个)比底部的粉红色要强得多——但事实证明,它们是相同的颜色!
两张卡具有相同的 RGB 值。然而,我们人类的色彩感知系统会受到照片其余部分的偏色(即在其上应用暖红色滤镜)的影响。
如果我们寻求标准化我们的图像处理环境,这会产生一些问题:为受控条件下捕获的图像编写代码要比在没有保证的动态条件下编写代码容易得多。
如果我们可以尽可能多地控制我们的图像捕获环境,那么编写代码来分析和处理这些从受控环境捕获的图像就会更容易。
假设我们可以控制环境的照明条件。在这种情况下,我们可以放弃昂贵的计算机视觉/深度学习算法,这有助于我们在非理想条件下获得理想的结果。相反,我们利用基本的图像处理程序,允许我们对参数进行硬编码,包括高斯模糊大小、Canny 边缘检测阈值等。
基本上,在受控环境中,我们可以使用基本的图像处理算法,这些算法更容易实现。问题是,我们需要对照明条件进行安全假设。色彩校正和白色平衡帮助我们实现这一点。
有一种方法可以帮助我们控制环境,即使光照条件改变了一点,那就是应用色彩校正。
色卡是摄影师最喜欢的工具:
摄影师将这些卡片放入拍摄的场景中。然后他们拍照,调整他们的灯光(同时仍然保持卡在相机的视野),然后继续拍摄,直到他们完成。
拍摄完成后,他们回到自己的电脑,将照片转移到他们的系统中,并使用Adobe Lightroom等工具在整个拍摄过程中实现颜色的一致性(如果你感兴趣,这里有一个关于如何做这个过程的教程)。
当然,作为计算机视觉从业者,我们没有使用 Adobe Lightroom 的奢侈,也不想通过手动调整色彩平衡来启动/停止我们的管道——这违背了使用软件来自动化现实世界流程的全部目的。
相反,利用这些相同的色彩校正卡,再加上一些直方图匹配,我们可以构建一个能够执行色彩校正的系统。
在本文的其余部分,您将使用直方图匹配和颜色校正卡(来自Pantone)来执行基本的颜色校正。
这张卡类似于摄影师使用的色彩校正卡,但Pantone 却用它来帮助消费者将场景中感知到的颜色与Pantone 销售的一种色调(最相似的颜色)相匹配。
其大意是:
出于我们的目的,我们将严格使用该卡进行色彩校正(但您可以根据需要轻松扩展它)。
虽然颜色匹配和颜色校正可能看起来是一个复杂的过程,但我们会发现,我们将能够用不到 100 行代码(包括注释)完成整个项目。
但是在开始写代码之前,让我们先回顾一下我们的项目目录结构。
我们今天要查看一个 Python 脚本,color_correction.py
。该脚本将:
链接:https://pan.baidu.com/s/1ja5RZUiV5Hyu-Z65JEJWzg
提取码:123a
# color_correction.py
# 用法
# python color_correction.py --reference reference.jpg --input examples/03.jpg
# 导入相关库
from imutils.perspective import four_point_transform
from skimage import exposure
import numpy as np
import argparse
import imutils
import cv2
import sys
def find_color_card(image):
# 加载ArUCo字典,获取ArUCo参数并检测输入图像中的标记
arucoDict = cv2.aruco.Dictionary_get(cv2.aruco.DICT_ARUCO_ORIGINAL)
arucoParams = cv2.aruco.DetectorParameters_create()
(corners, ids, rejected) = cv2.aruco.detectMarkers(image, arucoDict, parameters=arucoParams)
# 尝试提取颜色校正卡的坐标
try:
# 否则,这意味着四个ArUCo标记已经被发现,因此继续扁平化ArUCo id列表
ids = ids.flatten()
# 提取左上角的标记
i = np.squeeze(np.where(ids == 923))
topLeft = np.squeeze(corners[i])[0]
# 提取右上角的标记
i = np.squeeze(np.where(ids == 1001))
topRight = np.squeeze(corners[i])[1]
# 提取右下角的标记
i = np.squeeze(np.where(ids == 241))
bottomRight = np.squeeze(corners[i])[2]
# 提取左下角的标记
i = np.squeeze(np.where(ids == 1007))
bottomLeft = np.squeeze(corners[i])[3]
# 如果校色卡找不到了
except:
return None
# 构建参考点列表并应用透视变换以获得配色卡的自上而下的鸟瞰图
cardCoords = np.array([topLeft, topRight, bottomRight, bottomLeft])
card = four_point_transform(image, cardCoords)
# 将颜色匹配卡返回给调用函数
return card
# 构造参数解析器并解析参数
ap = argparse.ArgumentParser()
ap.add_argument("-r", "--reference", required=True, help="Path to the input reference image")
ap.add_argument("-i", "--input", required=True, help="Path to the input image to apply color correction to")
args = vars(ap.parse_args())
# 从磁盘加载参考图像和输入图像
print("[INFO] Loading images...")
ref = cv2.imread(args["reference"])
image = cv2.imread(args["input"])
# 调整参考和输入图像的大小
ref = imutils.resize(ref, width=600)
image = imutils.resize(image, width=600)
# 在屏幕上显示参考和输入图像
cv2.imshow("Reference", ref)
cv2.imshow("Input", image)
# 找出每张图片中的配色卡
print("[INFO] Finding color matching cards...")
refCard = find_color_card(ref)
imageCard = find_color_card(image)
# 如果在参考或输入图像中都没有找到配色卡,请退出程序
if refCard is None or imageCard is None:
print("[INFO] Could not find color matching cards in both images! Exiting...")
sys.exit(0)
# 分别在参考图像和输入图像中显示颜色匹配卡
cv2.imshow("Reference Color Card", refCard)
cv2.imshow("Input Color Card", imageCard)
# 将参考图像中的颜色匹配卡的直方图匹配应用到输入图像中的颜色匹配卡
print("[INFO] Matching images...")
imageCard = exposure.match_histograms(imageCard, refCard, multichannel=True)
# 直方图匹配后显示输入的颜色匹配卡
cv2.imshow("Input Color Card After Matching", imageCard)
cv2.waitKey(0)
打开项目目录结构中的 color_correction.py 文件,让我们开始工作:
# 导入库
from imutils.perspective import four_point_transform
from skimage import exposure
import numpy as np
import argparse
import imutils
import cv2
import sys
导入我们需要的 Python 包。值得注意的包括:
four_point_transform
:应用透视变换以获得输入配色卡的自上而下的鸟瞰图。exposure
:包含来自 scikit-image 的直方图匹配函数。imutils
:使用 OpenCV 执行图像处理的一组便利函数。cv2
:OpenCV Python库。导入后,我们可以继续定义 find_color_card
函数,该方法负责在输入图像中定位 Pantone 颜色匹配卡:
def find_color_card(image):
# 加载 ArUCo 字典,获取 ArUCo 参数,并检测输入图像中的标记
arucoDict = cv2.aruco.Dictionary_get(cv2.aruco.DICT_ARUCO_ORIGINAL)
arucoParams = cv2.aruco.DetectorParameters_create()
(corners, ids, rejected) = cv2.aruco.detectMarkers(image,
arucoDict, parameters=arucoParams)
我们的 find_color_card
函数只需要一个参数 image
,它是包含我们的颜色匹配卡的图像。执行 ArUco 标记检测以找到配色卡上的四个 ArUco 标记。
接下来,让我们按左上角、右上角、右下角和左下角的顺序(应用自上而下的透视变换所需的顺序)对四个 ArUco 标记进行排序:
# 尝试提取颜色校正卡的坐标
try:
# 否则,我们已经找到了四个 ArUco 标记,因此我们可以通过展平 ArUco ID 列表来继续
ids = ids.flatten()
# 提取左上角标记
i = np.squeeze(np.where(ids == 923))
topLeft = np.squeeze(corners[i])[0]
# 提取右上角的标记
i = np.squeeze(np.where(ids == 1001))
topRight = np.squeeze(corners[i])[1]
# 提取右下角标记
i = np.squeeze(np.where(ids == 241))
bottomRight = np.squeeze(corners[i])[2]
# 提取左下角标记
i = np.squeeze(np.where(ids == 1007))
bottomLeft = np.squeeze(corners[i])[3]
# 找不到校色卡,返回
except:
return None
首先,我们将整个代码块包装在一个 try/except 块中。我们这样做是为了防止使用 np.where 调用无法检测到所有四个标记。如果只有一个 np.where 调用失败,Python 将抛出错误。
我们的 try/except 块将捕获错误并返回 None,这意味着无法找到色彩校正卡。
否则,按左上角、右上角、右下角和左下角的顺序提取每个单独的 ArUco 标记。
注:您可能想知道我是如何知道每个标记的id
将是923、1001、241和1007的?在下一篇关于ArUco标记检测的教程中将解决这个问题。
如果我们找到了所有四个 ArUco 标记,我们现在可以应用透视变换:
# 建立我们的参考点列表并应用透视变换以获得配色卡的自上而下的鸟瞰图
cardCoords = np.array([topLeft, topRight,
bottomRight, bottomLeft])
card = four_point_transform(image, cardCoords)
# 将配色卡返回给调用函数
return card
从我们的 ArUco 标记坐标构建一个 NumPy 数组,然后应用four_point_transform
函数以获得颜色校正卡的自上而下的鸟瞰图。
卡片的自顶向下视图返回给调用函数。
实现了 find_color_card
函数后,让我们继续解析命令行参数:
# 构造参数解析器并解析参数
ap = argparse.ArgumentParser()
ap.add_argument("-r", "--reference", required=True,
help="path to the input reference image")
ap.add_argument("-i", "--input", required=True,
help="path to the input image to apply color correction to")
args = vars(ap.parse_args())
为了进行颜色匹配,我们需要两张图片:
我们的目标是获取 --input 图像并执行颜色匹配,使其分布与 --reference 图像的分布相匹配。
但在此之前,我们需要从磁盘加载参考图像和源图像:
# 从磁盘加载参考图像和输入图像
print("[INFO] loading images...")
ref = cv2.imread(args["reference"])
image = cv2.imread(args["input"])
# 调整参考和输入图像的大小
ref = imutils.resize(ref, width=600)
image = imutils.resize(image, width=600)
# 在我们的屏幕上显示参考和输入图像
cv2.imshow("Reference", ref)
cv2.imshow("Input", image)
从磁盘加载我们的输入图像,将它们调整为 600 像素的宽度(以更快地处理图像)来预处理它们。
然后将原始参考和图像显示到我们的屏幕上。
加载图像后,现在让我们将 find_color_card
函数应用于两个图像:
# 在每个图像中找到颜色匹配卡
print("[INFO] finding color matching cards...")
refCard = find_color_card(ref)
imageCard = find_color_card(image)
# 如果在参考图像或输入图像中都没有找到配色卡,请退出
if refCard is None or imageCard is None:
print("[INFO] could not find color matching card in both images")
sys.exit(0)
尝试在参考和图像中找到颜色匹配卡。
如果我们在任一图像中都找不到颜色匹配卡,我们退出脚本。
否则,我们可以放心地假设我们找到了颜色匹配卡,所以让我们应用颜色校正:
# 分别在参考图像和输入图像中显示颜色匹配卡
cv2.imshow("Reference Color Card", refCard)
cv2.imshow("Input Color Card", imageCard)
# 将参考图像中配色卡的直方图匹配应用于输入图像中的配色卡
print("[INFO] matching images...")
imageCard = exposure.match_histograms(imageCard, refCard,
multichannel=True)
# 在直方图匹配后显示我们输入的颜色匹配卡
cv2.imshow("Input Color Card After Matching", imageCard)
cv2.waitKey(0)
将我们的 refCard
和 imageCard
显示到我们的屏幕上。
然后我们应用 match_histograms
函数将颜色分布从 refCard
传输到 imageCard
。
最后,经过直方图匹配后的输出 imageCard
显示在我们的屏幕上。这个新的 imageCard
现在包含原始 imageCard
的颜色校正版本。
左 : 输 入 图 像 。 右 : 参 考 图 像 。 左:输入图像。右:参考图像。 左:输入图像。右:参考图像。
左 : 检 测 参 考 图 像 中 的 配 色 卡 。 中 : 从 输 入 图 像 中 提 取 色 卡 。 右 : 应 用 颜 色 匹 配 后 的 输 出 。 左:检测参考图像中的配色卡。中:从输入图像中提取色卡。右:应用颜色匹配后的输出。 左:检测参考图像中的配色卡。中:从输入图像中提取色卡。右:应用颜色匹配后的输出。
https://www.pyimagesearch.com/2021/02/15/automatic-color-correction-with-opencv-and-python/