相机自动对焦功能在数字智能手机中的工作原理及机器学习扩展代码
介绍
虽然相机作为捕捉图像的工具已有150多年的历史,但智能手机的出现极大地改变了相机的使用,并将其与软件编程集成在一起。如今,相机不再完成“简单”任务,例如拍摄静止照片或拍摄视频,它们实际上复制了人眼执行的大部分复杂任务。
事实上,计算机视觉中使用的大多数算法和公式都是基于对人眼的感知。例如,通过对R,B和G像素值求平均来对RGB图像进行灰度级处理,将图像的通道减少到1通道的方法,使用以下公式:灰色←0.299⋅R+0.587⋅G+ 0.114 ⋅B。注意:显然与平均R,G和B像素值不同(如公式所示:Gray←0.33⋅R+0.33⋅G+0.33⋅B)。
但为什么?这是因为与色度相比,我们的眼睛对亮度/亮度更敏感。灰度级图像时使用的实际公式与计算给定像素的亮度/亮度值相同。注意:亮度值是YUV和YCrCb颜色空间中的Y值。
图1
自动对焦是相机的一个有趣功能。它实际上可以复制我们眼睛的聚焦能力。为了更好地可视化,请在不同平面上拍摄两个物体(水瓶和插座),如图1所示。现在把注意力集中在水瓶上。不知不觉中,你的眼睛模糊了后面的墙壁插座。对墙壁插座重复相同的操作,我们的眼睛会自动模糊水瓶。现在在家中复制类似的设置并打开手机的相机应用程序。通过点击您想要关注的区域,相机会像我们的眼睛一样自动模糊其他物体。这是相机自动对焦的神奇之处。今天,相机使用许多算法来聚焦图像。我将重点介绍一种基本的自动对焦算法:基于对比度的自动对焦。
基于对比度的自动对焦如何在高水平上工作?
基于对比度的自动对焦基本上测量不同相机镜头位置的对比度。相机镜头将一直移动,直到达到其定义为具有最大对比度的点。我们可以将对比度定义为图像中的锐度。为了更好地可视化,这里是各种相机镜头位置的虚拟图像(比较黑色方块的边缘):
图2
为了概括对比度,我们可以为每个图像分配0到1之间的任意对比度值,其中0表示未聚焦/低对比度图像,1表示聚焦/高对比度图像。
图3
现在,相机循环通过变化的相机镜头位置,计算图像内的对比度,然后比较并选择具有最高对比度值的相机镜头位置。
我们如何定义对比度?
有许多不同的方法来定义对比度。实际上,算法的复杂性是基于用于定义对比度的方法确定的。今天我将介绍一种非常基本的方法,它不需要额外的硬件,如传感器。请注意,公司通常利用手机内置的其他硬件组件来提高算法的复杂性。
为图像指定对比度值的一种基本方法是Canny边缘检测。实际上,这就是OpenCV如何计算自动对焦的最佳相机镜头位置。对于每个镜头位置,我们绘制图像的边缘并根据边缘的清晰度分配对比度值。例如,下面的图像序列显示执行Canny边缘检测算法后的输出:
图4
如您所见,图像聚焦越少,边缘越不清晰。事实上,在设置Canny边缘检测算法的默认设置后,您会注意到它无法检测左侧两个日益未聚焦的图像的边缘。显然,通过降低算法的最小阈值,我们将能够看到边缘。但是为了自动对焦,我们可以丢弃不检测边缘的相机镜头位置。
如果您想进一步探索OpenCV的自动聚焦算法,请查看以下代码:https://github.com/opencv/opencv/blob/master/samples/cpp/autofocus.cpp。
结论
基于对比度的自动对焦算法需要许多计算 - > L * O(对比度),其中L是可能的相机镜头位置的数量,O(对比度)是用于确定图像内对比度的算法的复杂度。在上面的例子中,确定边缘是否清晰的复杂性需要大于O(m * n),其中m和n是帧的尺寸。由于这种高复杂性,许多创建相机驱动程序(Qualcomm,Apple等)的公司研究新的和更有效的方法来确定图像是否聚焦。一些其他常见的算法包括PDAF,激光等。事实上,作为一个侧面实验,
利用机器学习进行扩展
作为扩展,我决定使用基于机器学习的方法来定义对比度。因为每帧的计算边缘计算量很大,所以我决定设计一个神经网络,这应该会降低基于对比度的自动对焦算法的复杂性。
我决定尝试前馈和卷积神经网络。
我开始尝试使用前馈神经网络,因为这更容易实现。我创建了一个4层的小型神经网络。在这个神经网络中,我为一个大小为224乘224像素的3通道图像添加了一个输入层。然后我继续将尺寸flatten 为单个维度,并添加了1024个神经元的隐藏层。我没有直接跳到最后一层,而是决定追加128个神经元的另一个隐藏层。最后,我用2个神经元的输出层完成了神经网络,以指示聚焦或未聚焦。
主要Python代码如下:
from keras import layers
my_model = keras.models.Sequential()
my_model.add(layers.Dense(3, input_shape=(224,224, 3), activation='relu'))
my_model.add(layers.Flatten())
my_model.add(layers.Dense(1024, activation='relu'))
my_model.add(layers.Dropout(0.5))
my_model.add(layers.Dense(128, activation='relu'))
my_model.add(layers.Dropout(0.5))
my_model.add(layers.Dense(2, activation='softmax'))
my_model.summary()
'''
SET UP TRAINING AND VALIDATION DATA
'''
from keras.preprocessing.image import ImageDataGenerator, load_img
train_datagen = ImageDataGenerator(
rescale=1./255,
rotation_range=20,
width_shift_range=0.2,
height_shift_range=0.2,
horizontal_flip=True,
vertical_flip=True,
fill_mode='nearest')
validation_datagen = ImageDataGenerator(rescale=1./255)
train_batchsize = 20
val_batchsize = 5
train_generator = train_datagen.flow_from_directory(
'train_data',
target_size=(224, 224),
batch_size=train_batchsize,
class_mode='categorical')
validation_generator = validation_datagen.flow_from_directory(
'validation_data',
target_size=(224, 224),
batch_size=val_batchsize,
class_mode='categorical',
shuffle=False)
# Compile
try:
my_model = keras.models.load_model('autofocus_ff.h5') # trains from last epoch if model exists
except:
print "Training new model..."
my_model.compile(loss='categorical_crossentropy',
optimizer='adam',
metrics=['acc'])
# Train
checkpoint = keras.callbacks.ModelCheckpoint('autofocus_ff.h5', monitor='val_loss', verbose=1, period=1)
my_model_hist = my_model.fit_generator(
train_generator,
steps_per_epoch=2*train_generator.samples/train_generator.batch_size ,
epochs=25,
validation_data=validation_generator,
validation_steps=validation_generator.samples/validation_generator.batch_size,
verbose=1,
callbacks=[checkpoint])
# Save the Model
my_model.save('autofocus_ff.h5')
在我开始训练这个前馈神经网络后,我发现模型的验证准确性和损失在每个时期都不再提高。
这促使我尝试使用卷积神经网络(CNN),因为它们似乎可以为物体检测提供更好的结果。Keras有许多预先训练过的CNN模型,我选择根据给定的问题微调vgg16模型。我采用了vgg16模型的5个卷积块,然后添加了一个全局平均池图层,将尺寸减小到1,同时还有效地减少了可训练参数的数量。最后,我添加了一个2个神经元的输出层来表示聚焦或未聚焦的置信度。
Python实现的过程如下:
from keras.applications import VGG16
#Load the VGG model
vgg16 = VGG16(include_top=False, input_shape=(224, 224, 3)) # load vgg16 with all defaults except top
'''
MARK ALL LAYERS AS NON-TRAINABLE SO TRAINING DOESN'T ALTER WEIGHTS
'''
for layer in vgg16.layers:
layer.trainable = False
'''
TUNE MODEL TO AUTOFOCUS NEEDS
'''
from keras import layers
# my_model_output = vgg16.layers[10].output # Only use the first three blocks of convolution
my_model_output = vgg16.output # use all 5 conv layers of vgg
my_model_output = layers.GlobalAveragePooling2D()(my_model_output) # Then add a GlobalAveragePooling layer to get 1d representation
# my_model_output = layers.Dense(128, activation='relu')(my_model_output) # add a Dense layer just to increase trainable params
# my_model_output = layers.Dropout(0.5)(my_model_output) # Add regularization to help decrease overfitting data
my_model_output = layers.Dense(2, activation='softmax')(my_model_output) # binary classification prediction layer
my_model = keras.models.Model(inputs=vgg16.input, outputs=my_model_output) #created my neural network
my_model.summary()
'''
SET UP TRAINING AND VALIDATION DATA
'''
from keras.preprocessing.image import ImageDataGenerator, load_img
train_datagen = ImageDataGenerator(
rescale=1./255,
rotation_range=20,
width_shift_range=0.2,
height_shift_range=0.2,
horizontal_flip=True,
vertical_flip=True,
fill_mode='nearest')
validation_datagen = ImageDataGenerator(rescale=1./255)
train_batchsize = 20
val_batchsize = 5
train_generator = train_datagen.flow_from_directory(
'train_data',
target_size=(224, 224),
batch_size=train_batchsize,
class_mode='categorical')
validation_generator = validation_datagen.flow_from_directory(
'validation_data',
target_size=(224, 224),
batch_size=val_batchsize,
class_mode='categorical',
shuffle=False)
'''
COMPILE AND TRAIN MODEL
'''
# Compile
try:
my_model = keras.models.load_model('autofocus_vgg16.h5') # trains from last epoch if model exists
except:
print "Training new model..."
my_model.compile(loss='categorical_crossentropy',
optimizer='adam',
metrics=['acc'])
# Train
checkpoint = keras.callbacks.ModelCheckpoint('autofocus_vgg16.h5', monitor='val_loss', verbose=1, period=1)
my_model_hist = my_model.fit_generator(
train_generator,
steps_per_epoch=2*train_generator.samples/train_generator.batch_size ,
epochs=25,
validation_data=validation_generator,
validation_steps=validation_generator.samples/validation_generator.batch_size,
verbose=1,
callbacks=[checkpoint])
# Save the Model
my_model.save('autofocus_vgg16.h5')
# Compile
try:
my_model = keras.models.load_model('autofocus_vgg16.h5') # trains from last epoch if model exists
except:
print "Training new model..."
my_model.compile(loss='categorical_crossentropy',
optimizer='adam',
metrics=['acc'])
# Train
checkpoint = keras.callbacks.ModelCheckpoint('autofocus_vgg16.h5', monitor='val_loss', verbose=1, period=1)
my_model_hist = my_model.fit_generator(
train_generator,
steps_per_epoch=2*train_generator.samples/train_generator.batch_size ,
epochs=25,
validation_data=validation_generator,
validation_steps=validation_generator.samples/validation_generator.batch_size,
verbose=1,
callbacks=[checkpoint])
# Save the Model
my_model.save('autofocus_vgg16.h5')
'''
PREDICT
'''
import numpy as np
import keras
from keras.models import load_model
from keras.preprocessing import image
model = keras.models.load_model('autofocus_vgg16.h5')
def load_image_as_tensor(img_path):
img = image.load_img(img_path, target_size=(224, 224))
img = image.img_to_array(img)
img = np.expand_dims(img, axis=0)
img /= 255.
return img
def predict(img_path):
img = load_image_as_tensor(img_path)
prob = model.predict(img)
classes = np.argmax(prob, axis=1)
label_map = (validation_generator.class_indices)
print img_path
print prob, classes, label_map
predict('test_blur/black_square.jpg')
predict('test_blur/black_square9.jpg')
predict('test_blur/black_square17.jpg')
predict('test_blur/black_square25.jpg')
predict('test_blur/black_square33.jpg')
predict('test_blur/black_square41.jpg')
# 100% Accurate for these 6 test images
与前馈神经网络相比,该模型的准确性要好得多,我完成了25个时期,验证准确率为0.76,验证损失为0.49。虽然这些数字不是很好,但我觉得这些数字反映了缺乏训练数据。我只使用了1400张训练图像,如果我将其增加到大约6000-7000张图像,我觉得我的准确度会有显着提高。我在图2所示的6个图像上测试了我的模型。模型正确地将每个图像标记为聚焦或未聚焦。