傅里叶变换是图像处理的基础,Joseph Fourier(约瑟夫 ⋅ \cdot ⋅傅里叶)是一维18世纪的法国数学家,他发现并推广了很多数学概念,在数学上,他认为一切都可以用波形来描述。具体而言,他观察到所有的波形都可以由一系列简单且频率不同的正弦曲线叠加得到
也就是说,原始图像由许多频率组成,我们可以分离这些频率来理解图像和提取感兴趣的数据,傅里叶变换的概念是许多常见的图像处理操作的基础,比如边缘检测或线段和形状检测
下面我们要先介绍两个概念:高通滤波器和低通滤波器,上面提到的那些操作都是以这两个概念和傅里叶变换为基础。
高通滤波器(HPF)是检测图像的某个区域,然后根据像素与周围像素亮度差值来提升(Boost)该像素的亮度的滤波器
对滤波操作,我们首先要知道核(kernel)的概念
核是指一组权重的集合,它会应用在源图像的一个区域,并由此生成目标图像的一个像素。与卷积神经网络中的卷积核一样,通过选定核的权重与图像进行卷积达到对图像特征的提取
下面我们就通过一段程序来实验一些高通滤波器的作用
import cv2
import numpy as np
from scipy import ndimage
kernel_3x3 = np.array([[-1,-1,-1],
[-1,8,-1],
[-1,-1,-1]])
kernel_5x5 = np.array([[-1,-1,-1,-1,-1],
[-1,1,2,1,-1],
[-1,2,4,2,-1],
[-1,1,2,1,-1],
[-1,-1,-1,-1,-1]])
img = cv2.imread('images/cat.jpg',0)
k3 = ndimage.convolve(img, kernel_3x3)
k5 = ndimage.convolve(img, kernel_5x5)
blurred = cv2.GaussianBlur(img, (11,11), 0)
g_hpf = img - blurred
cv2.imshow('3x3',k3)
cv2.imshow('5x5',k5)
cv2.imshow('g_hpf',g_hpf)
cv2.waitKey(0)
原始图像:
经过高通滤波器之后的图像:
可以看到我们把图像的边缘给提取出来了,还是能依稀的看到猫的轮廓
高通滤波器是根据像素与邻近像素的亮度差值来提升该像素的亮度。低通滤波器(Low Pass Filter,LPF)则是在像素与周围像素的亮度差值小于一个特定值时,平滑该像素的亮度。它主要用于去噪和模糊化。
下面,我们在之前Cameo框架中增加我们的模块来增加一个低通滤波的功能
import cv2
import numpy
import utils
def strokeEdges(src, dst, blurKsize = 7, edgeKsize = 5):
if blurKsize >= 3:
blurredSrc = cv2.medianBlur(src, blurKsize)
graySrc = cv2.cvtColor(blurredSrc,cv2.COLOR_BGR2GRAY)
else:
graySrc = cv2.cvtColor(src,cv2.COLOR_BGR2GRAY)
cv2.Laplacian(graySrc,cv2.CV_8U,graySrc,ksize=edgeKsize)
normalizedInverseAlpha = (1.0 / 255) * (255 - graySrc)
channels = cv2.split(src)
for channel in channels:
channel[:] = channel * normalizedInverseAlpha
cv2.merge(channels, dst)
class VConvolutionFilter(object):
"""
对V进行卷积的滤波器
"""
def __init__(self,kernel):
self._kernel = kernel
def apply(self,src,dst):
"""
使用BGR或灰色源图像应用过滤器
"""
cv2.filter2D(src,-1,self._kernel,dst)
class SharpenFilter(VConvolutionFilter):
"""
具有1像素半径的锐化滤波器
"""
def __init__(self):
kernel = numpy.array([[-1,-1,-1],
[-1,9,-1],
[-1,-1,-1]])
VConvolutionFilter.__init__(self,kernel)
class FindEdgesFilter(VConvolutionFilter):
"""
一个半径为1像素的边缘检验滤波器
"""
def __init__(self):
kernel = numpy.array([[-1,-1,-1],
[-1,8,-1],
[-1,-1,-1]])
VConvolutionFilter.__init__(self,kernel)
class BlurFilter(VConvolutionFilter):
"""
一个2像素半径的模糊滤波器
"""
def __init__(self):
kernel = numpy.array([[0.04,0.04,0.04,0.04,0.04],
[0.04, 0.04, 0.04, 0.04, 0.04],
[0.04, 0.04, 0.04, 0.04, 0.04],
[0.04, 0.04, 0.04, 0.04, 0.04],
[0.04, 0.04, 0.04, 0.04, 0.04]])
VConvolutionFilter.__init__(self,kernel)
class EmbossFilter(VConvolutionFilter):
"""
具有1像素半径的浮雕过滤器(锐化、边缘检测、模糊等滤波器都使用了高度对称的核,而不对称的核会得到一些有趣的效果,
它同时具有模糊和锐化的作用,这会产生一种脊状或者浮雕的效果)
"""
def __init__(self):
kernel = numpy.array([[-2,-1,0],
[-1,-1,-1],
[0,1,2]])
VConvolutionFilter.__init__(self,kernel)
class BGRFuncFilter(object):
def __init__(self, vFunc=None, bFunc=None, gFunc=None, rFunc=None, dtype=numpy.uint8) :
length = numpy.iinfo(dtype).max + 1
self._bLookupArray = utils.createLookupArray(utils.createCompositeFunc(bFunc, vFunc), length)
self._gLookupArray = utils.createLookupArray(utils.createCompositeFunc(gFunc, vFunc), length)
self._rLookupArray = utils.createLookupArray(utils.createCompositeFunc(rFunc, vFunc), length)
def apply(self, src, dst) :
"""应用滤波器在RGB图像上"""
b, g, r = cv2.split(src)
utils.applyLookupArray(self._bLookupArray, b, b)
utils.applyLookupArray(self._gLookupArray, g, g)
utils.applyLookupArray(self._rLookupArray, r, r)
cv2.merge([ b, g, r ], dst)
class BGRCurveFilter(BGRFuncFilter):
def __init__(self, vPoints=None, bPoints=None, gPoints=None, rPoints=None, dtype=numpy.uint8):
BGRFuncFilter.__init__(self, utils.createCurveFunc(vPoints), utils.createCurveFunc(bPoints),
utils.createCurveFunc(gPoints), utils.createCurveFunc(rPoints), dtype)
class BGRPortraCurveFilter(BGRCurveFilter):
def __init__(self, dtype = numpy.uint8):
BGRCurveFilter.__init__(
self,
vPoints = [ (0, 0), (23, 20), (157, 173), (255, 255) ],
bPoints = [ (0, 0), (41, 46), (231, 228), (255, 255) ],
gPoints = [ (0, 0), (52, 47), (189, 196), (255, 255) ],
rPoints = [ (0, 0), (69, 69), (213, 218), (255, 255) ],
dtype = dtype)
定义完滤波器后编写一些框架所需的工具类
import cv2, numpy, scipy.interpolate
def createCurveFunc(points):
"""Return a function derived from control points."""
if points is None:
return None
num_points = len(points)
if num_points < 2:
return None
xs, ys = zip(*points)
if num_points < 4:
kind = 'linear'
# 'quadratic' is not implemented.
else:
kind = 'cubic'
return scipy.interpolate.interp1d(xs, ys, kind, bounds_error=False)
def createLookupArray(func, length = 256):
"""Return a lookup for whole-number inputs to a function. The lookup values are clamped to [0, length - 1]."""
if func is None:
return None
lookup_array = numpy.empty(length)
i = 0
while i < length:
func_i = func(i)
lookup_array[i] = min(max(0, func_i), length - 1)
i += 1
return lookup_array
def applyLookupArray(lookup_array, src, dst):
"""Map a source to a destination using a lookup."""
if lookup_array is None:
return
dst[:] = lookup_array[src]
def createCompositeFunc(func0, func1):
"""Return a composite of two functions."""
if func0 is None:
return func1
if func1 is None:
return func0
return lambda x: func0(func1(x))
def createFlatView(array):
"""Return a 1D view of an array of any dimensionality."""
flat_view = array.view()
flat_view.shape = array.size
return flat_view
import cv2
import filters
from managers import WindowManager, CaptureManager
class Cameo(object):
def __init__(self):
self._windowManager = WindowManager('Cameo', self.onKeypress)
self._captureManager = CaptureManager(cv2.VideoCapture(0), self._windowManager, True)
self._curveFilter = filters.BGRPortraCurveFilter()
def run(self):
self._windowManager.createWindow()
while self._windowManager.isWindowCreated:
self._captureManager.enterFrame()
frame = self._captureManager.frame
filters.strokeEdges(frame,frame)
self._curveFilter.apply(frame, frame)
self._captureManager.exitFrame()
self._windowManager.processEvents()
def onKeypress(self,keycode):
if keycode == 32: # space
self._captureManager.writeImage('cameo/screenshot.png')
elif keycode == 9: # tab
if not self._captureManager.isWritingVideo:
self._captureManager.startWritingVideo('cameo/screenshot.avi')
else:
self._captureManager.stopWritingVideo()
elif keycode == 27: # escape
self._windowManager.destoryWindow()
# 读帧
if __name__ == "__main__":
Cameo().run()
上面3个文件编写好后,就可以实现我们的效果:描绘边缘并模拟肖像胶卷色彩,并且可以通过修改代码更换所需要的滤波器来实现不同的效果
感觉把现实风格转换成了美漫风格有木有。