ISP 图像信号处理
ISP 图像视频 颜色调整算法 去噪 滤波 黑夜到白天
分为传统算法、深度学习算法 AI ISP
包括:白平衡 颜色管理 色彩增强 光谱重建 等
ISP(Image Signal Processor,图像信号处理器)在图像和视频处理中扮演着关键角色,尤其在颜色调整、去噪、滤波以及从黑夜到白天的转换等方面。下面将分别介绍传统算法、深度学习算法以及AI ISP在这些方面的应用。!
- 传统算法:
颜色调整:传统算法中,颜色调整通常涉及白平衡、色彩校正和色彩增强等步骤。白平衡算法用于消除不同光源对图像颜色的影响,确保图像在不同光照条件下呈现出真实的颜色。色彩校正则用于调整图像的色相、饱和度和亮度等参数,以改善图像的视觉效果。色彩增强则通过强化图像中的某些颜色或减弱其他颜色来增强图像的对比度和清晰度。
去噪与滤波:在去噪方面,传统算法常采用均值滤波、高斯滤波、中值滤波等方法。这些滤波方法通过计算像素邻域的统计特性来估计并替换噪声像素,从而有效地减少图像中的噪声。此外,还有一些更复杂的去噪算法,如双边滤波和非局部均值滤波等,它们能够在保持图像细节的同时去除噪声。
黑夜到白天的转换:将黑夜图像转换为白天图像是一个具有挑战性的任务。传统算法通常通过增强图像的亮度、对比度和色彩来模拟白天的视觉效果。这可能涉及直方图均衡化、Gamma校正等技术,以改善图像的亮度和对比度分布。然而,这些方法可能无法完全恢复黑夜图像中丢失的细节和颜色信息。
*深度学习算法:
随着深度学习技术的发展,越来越多的深度学习算法被应用于ISP中。这些算法通常基于卷积神经网络(CNN)或其他深度学习模型,通过训练大量数据来学习从输入图像到目标图像的映射关系。
颜色调整:深度学习算法可以学习不同光照条件下的颜色分布和变化规律,从而更准确地调整图像的颜色。例如,一些算法可以通过训练模型来自动调整白平衡参数,以适应不同场景和光照条件。
去噪与滤波:深度学习算法在去噪方面表现出了强大的能力。通过训练噪声图像和对应的干净图像对,深度学习模型可以学习去除噪声的映射关系。这些算法通常比传统滤波方法具有更好的去噪效果和更高的保真度。
黑夜到白天的转换:深度学习算法可以通过训练大量黑夜和白天的图像对来学习如何将黑夜图像转换为白天图像。这些算法可以恢复丢失的细节和颜色信息,并生成更逼真的白天图像。然而,这种方法需要大量的训练数据和计算资源,并且可能受到训练数据分布的影响。
*AI ISP:
AI ISP是人工智能技术与ISP的结合体,旨在通过智能算法提升图像质量和性能。
颜色管理:AI ISP可以利用深度学习模型进行更精细的颜色管理。这些模型可以学习不同场景和光照条件下的颜色变化规律,从而更准确地调整图像的颜色。
色彩增强:AI ISP可以通过智能算法识别图像中的关键区域和细节,并对其进行有针对性的色彩增强。这有助于提升图像的视觉效果和吸引力。
光谱重建:AI ISP还可以利用深度学习算法进行光谱重建,即从输入的RGB图像中恢复出更丰富的光谱信息。这有助于提升图像的色彩准确性和真实性。
总之,无论是传统算法、深度学习算法还是AI ISP,都在不断提升图像和视频处理的质量和性能。随着技术的不断进步和创新,我们可以期待未来会有更多更优秀的算法和解决方案出现。
算法概述 与 实现
ISP(Image Signal Processor,图像信号处理器)是相机成像流程中的关键组成部分。它负责处理从相机传感器读取的原始图像数据,并生成最终的RGB图像输出。这个处理流程通常包括一系列算法步骤,用于改善图像质量、校正传感器缺陷以及增强视觉效果。
根据您提供的包结构,该ISP包包含了一系列用于处理图像信号的算法,分别对应不同的处理阶段。以下是对每个处理阶段的简要说明:
Black Level Correction(黑电平校正)
这个步骤用于消除图像中的固定模式噪声,特别是由于传感器暗电流引起的噪声。通过从每个像素值中减去一个固定的黑电平值,可以消除这种噪声,使图像更加清晰。
# =============================================================
# function: black_level_correction
# subtracts the black level channel wise
# =============================================================
def black_level_correction(raw, black_level, white_level, clip_range):
print("----------------------------------------------------")
print("Running black level correction...")
# make float32 in case if it was not
black_level = np.float32(black_level)
white_level = np.float32(white_level)
raw = np.float32(raw)
# create new data so that original raw data do not change
data = np.zeros(raw.shape)
# bring data in range 0 to 1
data[::2, ::2] = (raw[::2, ::2] - black_level[0]) / (white_level[0] - black_level[0])
data[::2, 1::2] = (raw[::2, 1::2] - black_level[1]) / (white_level[1] - black_level[1])
data[1::2, ::2] = (raw[1::2, ::2] - black_level[2]) / (white_level[2] - black_level[2])
data[1::2, 1::2] = (raw[1::2, 1::2]- black_level[3]) / (white_level[3] - black_level[3])
# bring within the bit depth range
data = data * clip_range[1]
# clip within the range
data = np.clip(data, clip_range[0], clip_range[1]) # upper level not necessary
data = np.float32(data)
return data
Bad Pixel Correction(坏像素校正)
由于传感器制造过程中的缺陷或使用过程中的损伤,图像中可能出现坏像素。这些像素可能表现出异常的亮度或颜色值。该算法能够检测和替换这些坏像素,以减少图像中的噪声和失真。
ISP(Image Signal Processing,图像信号处理)中的Bad Pixel Correction(坏像素校正)是一种重要的图像处理技术,主要用于纠正由于传感器制造过程中的不确定性或图像生成过程中的错误导致的像素值不准确的问题。
在图像传感器中,由于制造过程中的粉尘、制造故障、曝光不完全等原因,可能存在一定数量的缺陷像素,这些像素可能表现为一直为最暗值(死点)、一直为最亮值(亮点)或者变化规律与周围像素明显不同(漂移点)。这些坏点会严重影响图像的质量和准确性。
坏像素校正的过程通常分为两个步骤:首先,通过比较当前点的坐标与已知的静态或动态坏点表,检测并定位出图像中的坏点;然后,使用插值算法,如邻近像素的均值或加权均值,来替换这些坏点的像素值,从而消除它们对图像的影响。
值得注意的是,坏像素校正需要在颜色插补之前进行,以避免坏点的错误信息在插补过程中扩散到整个图像。此外,对于长期使用造成的后期瑕疵情况,某些校正方法可能无法适应,因此需要定期检查和更新坏点表。
总的来说,ISP中的坏像素校正是确保图像质量和准确性的重要环节,它通过检测和替换坏点,消除了图像中的错误和噪声,提高了图像的清晰度和视觉效果。
# =============================================================
# function: bad_pixel_correction
# correct for the bad (dead, stuck, or hot) pixels
# =============================================================
def bad_pixel_correction(data, neighborhood_size):
print("----------------------------------------------------")
print("Running bad pixel correction...")
if ((neighborhood_size % 2) == 0):
print("neighborhood_size shoud be odd number, recommended value 3")
return data
# convert to float32 in case they were not
# Being consistent in data format to be float32
data = np.float32(data)
# Separate out the quarter resolution images
D = {
} # Empty dictionary
D[0] = data[::2, ::2]
D[1] = data[::2, 1::2]
D[2] = data[1::2, ::2]
D[3] = data[1::2, 1::2]
# number of pixels to be padded at the borders
no_of_pixel_pad = math.floor(neighborhood_size / 2.)
for idx in range(0, len(D)): # perform same operation for each quarter
# display progress
print("bad pixel correction: Quarter " + str(idx+1) + " of 4")
img = D[idx]
width, height = utility.helpers(img).get_width_height()
# pad pixels at the borders
img = np.pad(img, \
(no_of_pixel_pad, no_of_pixel_pad),\
'reflect') # reflect would not repeat the border value
for i in range(no_of_pixel_pad, height + no_of_pixel_pad):
for j in range(no_of_pixel_pad, width + no_of_pixel_pad):
# save the middle pixel value
mid_pixel_val = img[i, j]
# extract the neighborhood
neighborhood = img[i - no_of_pixel_pad : i + no_of_pixel_pad+1,\
j - no_of_pixel_pad : j + no_of_pixel_pad+1]
# set the center pixels value same as the left pixel
# Does not matter replace with right or left pixel
# is used to replace the center pixels value
neighborhood[no_of_pixel_pad, no_of_pixel_pad] = neighborhood[no_of_pixel_pad, no_of_pixel_pad-1]
min_neighborhood = np.min(neighborhood)
max_neighborhood = np.max(neighborhood)
if (mid_pixel_val < min_neighborhood):
img[i,j] = min_neighborhood
elif (mid_pixel_val > max_neighborhood):
img[i,j] = max_neighborhood
else:
img[i,j] = mid_pixel_val
# Put the corrected image to the dictionary
D[idx] = img[no_of_pixel_pad : height + no_of_pixel_pad,\
no_of_pixel_pad : width + no_of_pixel_pad]
# Regrouping the data
data[::2, ::2] = D[0]
data[::2, 1::2] = D[1]
data[1::2, ::2] = D[2]
data[1::2, 1::2] = D[3]
return data
Channel Gain White Balance(通道增益白平衡)
白平衡是调整图像中不同颜色通道的相对增益,以消除光源颜色对图像的影响。这个步骤确保图像在不同光照条件下都能呈现出自然的白色。
ISP(Image Signal Processing,图像信号处理)中的Channel Gain White Balance(通道增益白平衡)是一种重要的图像处理技术,用于校正图像中的色彩偏移,使得图像中的白色物体能够呈现真实的白色。
白平衡在ISP中是一个关键步骤,它涉及到对图像中红、绿、蓝三个通道的增益进行调整。由于不同光源的颜色温度不同,图像中的颜色可能会受到光源的影响而发生偏移。为了还原真实的颜色,需要通过调整各通道的增益来校正这种偏移。
在通道增益白平衡的处理过程中,首先需要对光源的颜色温度进行分析和测量。然后,根据测量结果调整红、绿、蓝三个通道的增益,使得白色物体在图像中呈现为真正的白色。这种调整是基于对图像颜色的统计和分析,通过算法计算得出各通道的增益值。
通道增益白平衡的实现可以放在raw域或rgb域进行。在raw域处理时,可以直接对原始图像数据进行调整,避免了后续处理中可能引入的误差。而在rgb域处理时,则需要对已经转换为rgb格式的图像数据进行调整。
需要注意的是,通道增益白平衡的实现需要考虑到光源的具体特性以及相机的响应函数等因素。不同的光源和相机可能需要不同的增益调整策略,以达到最佳的白平衡效果。
此外,随着图像处理技术的不断发展,现代的ISP系统还可能采用更先进的算法和技术来优化白平衡的效果,例如基于深度学习的自动白平衡算法等。这些算法能够更好地适应不同的场景和光源条件,提高图像的色彩还原度和视觉效果。
总之,ISP中的通道增益白平衡是一种重要的图像处理技术,用于校正图像中的色彩偏移,使得图像能够呈现真实的颜色。通过合理的增益调整策略和技术手段,可以实现高质量的白平衡效果。
# =============================================================
# function: channel_gain_white_balance
# multiply with the white balance channel gains
# =============================================================
def channel_gain_white_balance(data, channel_gain):
print("----------------------------------------------------")
print("Running channel gain white balance...")
# convert into float32 in case they were not
data = np.float32(data)
channel_gain = np.float32(channel_gain)
# multiply with the channel gains
data[::2, ::2] = data[::2, ::2] * channel_gain[0]
data[::2, 1::2] = data[::2, 1::2] * channel_gain[1]
data[1::2, ::2] = data[1::2, ::2] * channel_gain[2]
data[1::2, 1::2] = data[1::2, 1::2] * channel_gain[3]
# clipping within range
data = np.clip(data, 0., None) # upper level not necessary
return data
镜头阴影校正 lens_shading_correction
镜头阴影(Lens Shading)或渐晕(Vignetting)是相机成像中常见的问题,通常表现为图像边缘较中心区域亮度偏低的现象。为了校正镜头阴影,通常需要在ISP(图像信号处理器)的pipeline中采用特定的算法。
以下是一些常见的镜头阴影校正方法:
图像分割:将输入图像划分为若干个区域,每个区域代表图像中的一个局部区域。这可以通过简单的网格划分或更复杂的图像分割算法来实现。
计算均值:对于每个区域,计算其亮度的均值。这可以采用简单的平均值计算方法,也可以选择其他更复杂的统计方法。
计算校正系数:对于每个区域,计算其校正系数。校正系数表示将该区域的亮度调整为均匀分布所需的缩放因子。校正系数的计算可以基于每个区域的均值和整个图像的均值之间的比例关系。
应用校正系数:根据计算出的校正系数,调整每个区域的亮度,以消除镜头阴影的影响。
此外,一些ISP解决方案中提供了更高级的镜头阴影校正方法,例如基于同心圆法或网格法的LSC(Lens Shading Correction)校正。这些方法通常能够更准确地校正镜头阴影,特别是在边缘和角落区域。
值得注意的是,镜头阴影校正通常需要根据不同的光圈值(F值)进行单独的校正。因为光圈的变化会影响光线的入射角度和分布,从而导致不同的镜头阴影效果。因此,在实际应用中,需要针对不同光圈值设置不同的校正参数,并在光圈变化时切换相应的校正参数。
# =============================================================
# class: lens_shading_correction
# Correct the lens shading / vignetting
# =============================================================
class lens_shading_correction:
def __init__(self, data, name="lens_shading_correction"):
# convert to float32 in case it was not
self.data = np.float32(data)
self.name = name
def flat_field_compensation(self, dark_current_image, flat_field_image):
# dark_current_image:
# is captured from the camera with cap on
# and fully dark condition, several images captured and
# temporally averaged
# flat_field_image:
# is found by capturing an image of a flat field test chart
# with certain lighting condition
# Note: flat_field_compensation is memory intensive procedure because
# both the dark_current_image and flat_field_image need to be
# saved in memory beforehand
print("----------------------------------------------------")
print("Running lens shading correction with flat field compensation...")
# convert to float32 in case it was not
dark_current_image = np.float32(dark_current_image)
flat_field_image = np.float32(flat_field_image)
temp = flat_field_image - dark_current_image
return np.average(temp) * np.divide((self.data - dark_current_image), temp)
def approximate_mathematical_compensation(self, params, clip_min=0, clip_max=65535):
# parms:
# parameters of a parabolic model y = a*(x-b)^2 + c
# For example, params = [0.01759, -28.37, -13.36]
# Note: approximate_mathematical_compensation require less memory
print("----------------------------------------------------")
print("Running lens shading correction with approximate mathematical compensation...")
width, height = utility.helpers(self.data).get_width_height()
center_pixel_pos = [height/2, width/2]
max_distance = utility.distance_euclid(center_pixel_pos, [height, width])
# allocate memory for output
temp = np.empty((height, width), dtype=np.float32)
for i in range(0, height):
for j in range(0, width):
distance = utility.distance_euclid(center_pixel_pos, [i, j]) / max_distance
# parabolic model
gain = params[0] * (distance - params[1])**2 + params[2]
temp[i, j] = self.data[i, j] * gain
temp = np.clip(temp, clip_min, clip_max)
return temp
def __str__(self):
return "lens shading correction. There are two methods: " + \
"\n (1) flat_field_compensation: requires dark_current_image and flat_field_image" + \
"\n (2) approximate_mathematical_compensation:"
Bayer Denoise(Bayer去噪)
Bayer去噪用于减少图像中的噪声,特别是在低光条件下。它通常涉及对图像进行滤波或应用其他算法来平滑像素值并减少噪声。
Bayer去噪(Bayer Denoising)是一种在图像处理中广泛应用的去噪技术,特别适用于从Bayer模式图像中去除噪声。Bayer模式是一种常用于图像传感器的颜色滤波阵列模式,它使得每个像素点只能捕获到红、绿、蓝三种颜色中的一种。因此,在将Bayer模式图像转换为全彩色图像时,需要通过插值等方法来估计缺失的颜色信息,这个过程中可能会引入噪声。
Bayer去噪算法的目标就是减少这种噪声,提高图像质量。其实现方式通常包括一系列步骤,例如对图像进行分解,分离出高频和低频成分,然后对这些成分进行阈值处理,去除噪声。接着,对处理后的图像进行逆变换,恢复到原始大小或进行进一步的处理。这个过程可能会根据具体的应用和需求进行多次迭代,以达到期望的去噪效果。
# =============================================================
# class: bayer_denoising
# Bayer Denoise(Bayer去噪)
# =============================================================
class bayer_denoising:
def __init__(self, data, name="bayer_denoising"):
# convert to float32 in case it was not
self.data = np.float32(data)
self.name = name
def utilize_hvs_behavior(self, bayer_pattern, initial_noise_level, hvs_min, hvs_max, threshold_red_blue, clip_range):
# Objective: bayer denoising
# Inputs:
# bayer_pattern: rggb, gbrg, grbg, bggr
# initial_noise_level:
# Output:
# denoised bayer raw output
# Source: Based on paper titled "Noise Reduction for CFA Image Sensors
# Exploiting HVS Behaviour," by Angelo Bosco, Sebastiano Battiato,
# Arcangelo Bruna and Rosetta Rizzo
# Sensors 2009, 9, 1692-1713; doi:10.3390/s90301692
print("----------------------------------------------------")
print("Running bayer denoising utilizing hvs behavior...")
# copy the self.data to raw and we will only work on raw
# to make sure no change happen to self.data
raw = self.data
raw = np.clip(raw, clip_range[0], clip_range[1])
width, height = utility.helpers(raw).get_width_height()
# First make the bayer_pattern rggb
# The algorithm is written only for rggb pattern, thus convert all other
# pattern to rggb. Furthermore, this shuffling does not affect the
# algorithm output
if (bayer_pattern != "rggb"):
raw = utility.helpers(self.data).shuffle_bayer_pattern(bayer_pattern, "rggb")
# fixed neighborhood_size
neighborhood_size = 5 # we are keeping this fixed
# bigger size such as 9 can be declared
# however, the code need to be changed then
# pad two pixels at the border
no_of_pixel_pad = math.floor(neighborhood_size / 2) # number of pixels to pad
raw = np.pad(raw, \
(no_of_pixel_pad, no_of_pixel_pad),\
'reflect') # reflect would not repeat the border value
# allocating space for denoised output
denoised_out = np.empty((height, width), dtype=np.float32)
texture_degree_debug = np.empty((height, width), dtype=np.float32)
for i in range(no_of_pixel_pad, height + no_of_pixel_pad):
for j in range(no_of_pixel_pad, width + no_of_pixel_pad):
# center pixel
center_pixel = raw[i, j]
# signal analyzer block
half_max = clip_range[1] / 2
if (center_pixel <= half_max):
hvs_weight = -(((hvs_max - hvs_min) * center_pixel) / half_max) + hvs_max
else:
hvs_weight = (((center_pixel - clip_range[1]) * (hvs_max - hvs_min))/(clip_range[1] - half_max)) + hvs_max
# noise level estimator previous value
if (j < no_of_pixel_pad+2):
noise_level_previous_red = initial_noise_level
noise_level_previous_blue = initial_noise_level
noise_level_previous_green = initial_noise_level
else:
noise_level_previous_green = noise_level_current_green
if ((i % 2) == 0): # red
noise_level_previous_red = noise_level_current_red
elif ((i % 2) != 0): # blue
noise_level_previous_blue = noise_level_current_blue
# Processings depending on Green or Red/Blue
# Red
if (((i %</