简单实现图片颜色风格转换
Posted mini梁翊洲MAX
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了简单实现图片颜色风格转换相关的知识,希望对你有一定的参考价值。
今天我们尝试用两种简单的,快速的方法实现图片的风格转换,这不仅是一个有趣的实践,里面使用的一些方法在以后的图像处理中也能经常看见。
Lab颜色空间转换
第一种方法的原理出自Reinhard的文章《Color Transfer between Images》,我们可以分别利用 Lab* 颜色空间以及每个 L*、a* 和 b* 通道的均值和标准差,在两个图像之间传输颜色。
“Lab颜色空间是什么,我只听说过RGB颜色空间啊!”相信很多人都有这样的疑问,其实还有许多类似的彩色模型。这些具体是怎么样的,我暂时无法解释清楚。我在这里就简单介绍下Lab,L代表亮度,a和b代表颜色,Lab就是由这三个变量共同决定的。我们可以看到,它把亮度单独拿出来当作一个参考,人对颜色的感知会随着环境的亮度而改变,以此我们大概能理解“Lab颜色空间是基于人对颜色的感觉来设计的,更具体地说,它是感知均匀的”这一个官方说明。
详细的计算过程在下面代码的color_transfer()函数里都清楚地写了出来,可以自己尝试把它的公式列出来。
以前,我总听说火烧云这一个说法。于是,这次的例子我们就以火为源图像,云朵为目标图像,看看转换的结果吧。
import numpy as np
import cv2
import argparse
def color_transfer(source, target):
source = cv2.cvtColor(source, cv2.COLOR_BGR2LAB).astype("float32")
target = cv2.cvtColor(target, cv2.COLOR_BGR2LAB).astype("float32")
# compute color statistics for the source and target images
(lMeanSrc, lStdSrc, aMeanSrc, aStdSrc, bMeanSrc, bStdSrc) = image_stats(source)
(lMeanTar, lStdTar, aMeanTar, aStdTar, bMeanTar, bStdTar) = image_stats(target)
(l, a, b) = cv2.split(target)
l -= lMeanTar
a -= aMeanTar
b -= bMeanTar
l = (lStdTar / lStdSrc) * l
a = (aStdTar / aStdSrc) * a
b = (bStdTar / bStdSrc) * b
l += lMeanSrc
a += aMeanSrc
b += bMeanSrc
l = np.clip(l, 0, 255)
a = np.clip(a, 0, 255)
b = np.clip(b, 0, 255)
transfer = cv2.merge([l, a, b])
transfer = cv2.cvtColor(transfer.astype("uint8"), cv2.COLOR_LAB2BGR)
return transfer
def image_stats(image):
(l, a, b) = cv2.split(image)
(lMean, lStd) = (l.mean(), l.std())
(aMean, aStd) = (a.mean(), a.std())
(bMean, bStd) = (b.mean(), b.std())
return (lMean, lStd, aMean, aStd, bMean, bStd)
ap=argparse.ArgumentParser()
ap.add_argument("-s","--source",required=True)
ap.add_argument("-t","--target",required=True)
args=vars(ap.parse_args())
source=cv2.imread(args["source"])
target=cv2.imread(args["target"])
cv2.imshow("Source",source)
cv2.imshow("Target",target)
transfer=color_transfer(source,target)
这个算法非常快,但有一个特别的缺点——它依赖于全局颜色统计,因此具有相似像素强度值的大区域会显着影响平均值,而且它的改变也是全局的变化,非常生硬。
直方图匹配
我们看到了上面有这么一个缺点,那么我们如何处理才能使颜色转换不是那么生硬地全局统一变换呢?当然方法有很多,但是这篇文章我们只专注“简单”二字,所以我接下来将介绍直方图匹配。
直方图匹配,或者说直方图规定化,大家可以稍微记住这个名词,因为可以和下一篇的直方图均衡化进行一个类比。关于它的原理我打算拿出教材中的一个计算列表,带着大家算一遍,应该就能完全明白它的过程,毕竟当初我就是这么理解的。
计算示例:其实也有对应的数学公式来描述这一过程,但我相信大部分人看到这些就头疼。我们就直接引入一个简单的例子吧,我们假设一个8位的像素图,表格中的小数表示该像素级所占整体的比例。
在正式开始之前,我还得介绍几个概念。累计直方图指的是小于等于该像素级所有像素的数量,比如2像素级就会统计0,1,2所有的数量。映射的规则也可以按照下面的解释简单理解,但并不完全正确,SML映射关注附近距离最近的点,GML映射关注小于某一值的点。
我们以原始像素级1为切入点看一下,其比例为0.25,对应累计为0.44 。规定直方图(即我们参考的图)只有三个像素级,直方图和累积也都列写了出来。根据SML映射,累计值为0.44的像素级1(原始)在规定累计直方图里更接近像素级3(参考)对应的0.2,所以像素级为1在新的图里变成了像素级3。根据GML映射,累计值为0.44的像素级1(原始)在规定累计直方图里远大于像素级3(参考)对应的0.2,但小于像素级5(参考)对应的0.8,所以像素级为1在新的图里变成了像素级5 。
根据结果我们可以看到GML的映射结果是更符合变换后的图像的。
好,那让我们试一下,就以广州的小蛮腰为例子。
万幸的是,我们不需要使用代码来一步步实现上述的计算,而是调用一下match_histograms()函数即可。在这个函数之前有一个判断multi,用来判别我们输入的是彩色图还是灰度图。
from skimage import exposure
import matplotlib.pyplot as plt
import argparse
import cv2
ap=argparse.ArgumentParser()
ap.add_argument("-s","--source",required=True)
ap.add_argument("-t","--target",required=True)
args=vars(ap.parse_args())
source=cv2.imread(args["source"])
target=cv2.imread(args["target"])
multi=True if source.shape[-1]>1 else False
matched=exposure.match_histograms(target,source,multichannel=
multi)
cv2.imshow("source",source)
cv2.imshow("target",target)
cv2.imshow("matched",matched)
cv2.waitKey(0)
变换1:
变换2:
我个人觉得效果是好了不少,不知道大家怎么想的。当然,这篇文章里我们只是介绍了简单的,快速的风格转换应用。相对的,也有一些现在非常流行的GAN之类的方法,我们一步步来。
“本站所有文章均为原创,欢迎转载,请注明文章出处:https://blog.csdn.net/kasami_/article/details/123948422。百度和各类采集站皆不可信,搜索请谨慎鉴别。技术类文章一般都有时效性,本人习惯不定期对自己的博文进行修正和更新,因此请访问出处以查看本文的最新版本。”
以上是关于简单实现图片颜色风格转换的主要内容,如果未能解决你的问题,请参考以下文章