如何通过直方图判断照片的曝光

384 阅读5分钟

拿到一张照片,不管是黑白还是彩色,它的明暗分布总是最先刺激视觉系统的特征,无论是在拍摄还是后期处理过程中,我们都可以通过直方图直观地了解当前照片的明暗分布情况。

所谓的直方图其实就是一张照片中亮度值的统计分布结果。当下电子设备中常用的 RGB 色彩模式通常采用24比特模式(24 bits per pixel),即每个像素的 R、G、B 通道各由 8 bit 表示,因此 RGB 的取值范围是(0~255),通过统计一张照片中所有像素亮度值的分布,就可以获得其亮度直方图(对于黑白照片是亮度直方图,彩色照片则可以是三个通道各有一个直方图,不过彩色与黑白之间本来就是可以相互转换的)。

RGB_color_solid_cube.png

直方图只是粗暴地统计了所有像素的亮度水平,通过它很难直接推断出一张照片的曝光水平,一些大概的准则也只有在实践中结合经验才能有效运用。为了找到其中的关联,我去抓去了某摄影爱好者网站上的图片,其中每张图片会有不同的标签及点赞数,点赞数可以看做是对一张照片的评价,当然这一评价不只是针对曝光水平的,照片的主题、构图、色彩等等可能占有更大的比重,更有甚者,有些照片可能拥有非常诡异的曝光数据然而却具有更高的艺术价值。

考虑到这些原因,想要用点赞数量作为曝光水平好坏的指标本身就是一个大胆的假设,但是不试试怎么知道你错得有多离谱呢?(最后也证明这个假设是行不通的,本文权当是失败经验总结,不过还是有不少收获)

这次的尝试过程很简单,首先抓取数据和图片,然后提取直方图,最后找个机器学习算法进行分类。

数据

数据记录共 7.8w 条,粗略估算了一下,如果下载所有图片可能有近20G,考虑到这并不是一个非常严格的实验而且电脑容量有限,只过滤了点赞数超过50的照片,共约 1w 张,图片下载下来一共2G 左右。

预处理

NumPy 和 SciPy 来处理图片数据,Pandas 用作数据结构化存储与操作。

首先是提取图片的直方图信息,matplotlib 可以直接通过 hist 方法将直方图绘制出来,我们先看看评价排名靠前和靠后的照片有什么不同:

import matplotlib.pyplot as plt  
import matplotlib.gridspec as gridspec

def display_images(images):  
    fig = plt.figure(figsize=(10, 3*len(images)))
    grd = gridspec.GridSpec(len(images), 3)
    for i, img in enumerate(images):
        isGray = False
        if len(img.shape) == 2:
            isGray = True
        ax = fig.add_subplot(grd[i, 0])
        if isGray:
            ax.imshow(img, cmap=plt.cm.gray)
        else:
            ax.imshow(img)

        ax = fig.add_subplot(grd[i, 1])
        ax.hist(img.flatten(), bins=256, normed=1,fc='k', ec='k',range=(0.0,256))  
        ax.set_xlim(0, 255)

        if not isGray:
            ax = fig.add_subplot(grd[i, 2])
            ax.hist(img[:,:,0].flatten(), bins=256, normed=1, fc='r', ec='r')
            ax.hist(img[:,:,1].flatten(), bins=256, normed=1, fc='g', ec='g')
            ax.hist(img[:,:,2].flatten(), bins=256, normed=1, fc='b', ec='b')
            ax.set_xlim(0, 255)
    plt.tight_layout()

WX20170411-154931@2x.png

WX20170411-155456@2x.png

前两张和后两张,评价数量2000和50好像还是有点差别的。

导入到 Pandas

计算直方图并更新到 DataFrame 中:

from sklearn.preprocessing import normalize  
from scipy import misc  
import numpy as np

def cal_lum_hist(imgid):  
    try:
        p = "images/{}.jpg".format(imgid)
        img = misc.imread(p, flatten=True)
        hist, _ = np.histogram(img, bins=range(256))
        return normalize(hist.astype(np.float64).reshape(1, -1)).ravel()
    except BaseException as e:
        print(e)
        return None

ImageDF = ImageDF.imgid.apply(cal_lum_hist)  

根据点赞数量计算分数,即给训练数据设定标签。这是比较重要的一步,直接决定了分类结果的好坏。最直接的方法是将样本平均分为两类(或多类),这样可以保持不同类别下样本数量的平衡。我第一次选择了分为三类(挺好、很好、非常好):

ImageDF.fav.quantile([0.3, 0.6])  
"""
0.3     81  
0.6    153  
Name: fav, dtype: float64  
"""

于是分别以此界线设定分数:

def cal_score(df, b, u):  
    s = df.fav.quantile([b, u])
    def _inner(fav):
        if fav >= s[u]:
            return 2
        elif fav >= s[b]:
            return 1
        else:
            return 0
    return _inner
# 展示划分后样本数量
def score_size(df, n):  
    for s in range(n):
        print("Score = {}, Size = {}".format(s, df[df['score'] == s].shape[0]))
ImageDF['score'] = ImageDF.fav.apply(cal_score(ImageDF, 0.3, 0.6))  
score_size(imageDF, 3)  
"""
Score = 0, Size = 3080  
Score = 1, Size = 3102  
Score = 2, Size = 4163  
"""

然后分离训练样本和测试样本:

def data_split(df, n):  
    return (
        (list(df[:n]['norm_hist']), list(df[:n]['score'])),
        (list(df[n:]['norm_hist']), list(df[n:]['score'])))

# 随机打乱顺序
ImageDF = ImageDF.reindex(np.random.permutation(ImageDF.index))

# 十折交叉验证
train, test = data_split(ImageDF, len(ImageDF) // 10 * 9)

# SVM
from sklearn import svm  
clf = svm.SVC()  
clf.fit(*train)  
clf.score(*test)  

以上就是一次训练、测试的流程,结果发现,准确率最高的情况也只是在50%附近徘徊。

Picture1.png

注:2:0:2和1:0:1 是二分类的结果。

总结

根据上面的结果来看,根据直方图推断图片曝光的假设是失败了。其中得到较高准确率的2:6:2(实际上如果是1:9:1准确率更是可以高达80%)明显是因为样本偏差。现在再来看一下点赞数量的分布情况:

download (1).png

很明显绝大部分点赞数量集中在我所截取的50左右,统计所有记录的评价结果:

AllRecordDF.fav.describe()  
"""
count    78779.000000  
mean        33.258470  
std        101.544808  
min          0.000000  
25%          2.000000  
50%          5.000000  
75%         20.000000  
max       2933.000000  
Name: fav, dtype: float64  
"""

75%的评价分数不过20,而平均分被拉到33,这本身就是一个非常不平衡的样本,而我又只截取了其中的一部分。

既然不能只考虑曝光水平,那就应该将整张图片的所有特征考虑在内,也许通过神经网络模型可以得到一个更好的预测结果,用来评价你新拍的照片水平如何,虽然这已经失去了直方图在拍摄过程中的实时指导意义。