首先导入必要的库,使用Opencv读入图像,避免复杂的图像解析,同时使用Opencv作为算法的对比,由于使用环境为jupyter使用matplotlib直接可视化
import cv2
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
图片的存储
图片实质上就是一个矩阵,一个640*320的灰白图像其实就是一个(640,320)的矩阵,每个坐标点的值就代表该像素点的灰度。
通常我们使用0-256的值来表示灰度的深浅,在三通道图像中表达某个通道的深浅,256=16*16,一次当我们使用16进制来表达三通道图像某个像素点的颜色时通常写作#ffffab这种形式
图片平移
图片平移是最简单的操作了,直接使用坐标加减即可。
例如将一个图像向右平移10个像素点实质就是把所有的像素点的横坐标加上10,如(0,0)坐标就会变成(10,0)。
在下图的实现中不对图像进行resize,超出图像原本范围的全部舍弃。
#使用matplotlib来显示opencv读取的图像
dave = cv2.imread("dave.jpg")
dave = cv2.cvtColor(dave,cv2.COLOR_BGR2RGB)
plt.axis("off")
plt.imshow(dave)
plt.show()
def translate(src,translate_x,translate_y):
src_h,src_w = src.shape[:2]
dst = np.zeros(src.shape,dtype=np.uint8)
for row in range(src_h):
for col in range(src_w):
h = int(row-translate_y)
w = int(col-translate_x)
if h<src_h and h>=0 and w<src_w and w>=0:
dst[row][col] = src[h][w]
return dst
new_image = translate(dave,50,30)
plt.axis("off")
plt.imshow(new_image)
plt.show()
图片缩放
在不考虑复用性的前提下,实验性的进行不插值缩放
#在该代码中由于opencv读取默认为BGR将其转化为RGB图像
gss = cv2.imread("Green_Sea_Shell.png")
gss = cv2.cvtColor(gss,cv2.COLOR_BGR2RGB)
plt.axis("off")
plt.imshow(gss)
plt.show()
我们试着打印图像的分辨率,发现其为160*160的三通道图像,然后开始我们的实验性缩放
print(gss.shape)
#此处使用uint8格式的数据类型,gss将其转化为uint16是考虑到超出255的范围会出错,在结束后将其转化为原先的uint8类型
new_image = np.zeros((80,80,3),dtype=np.uint8)
gss = gss.astype(np.uint16)
(160, 160, 3)
试着直接比较两种插值,简化边界条件直接取四邻域均值
> PS:此处这并非线性插值,只是取其领域均值看相比直接的缩放是否会减少其锯齿感
for i in range(80):
for j in range(80):
#取靠近的四个像素点颜色的均值
pixel_sum = gss[i*2+1][j*2]+gss[i*2-1][j*2]+gss[i][j*2+1]+gss[i][j*2-1]+gss[i*2][j*2]
new_image[i][j] = (pixel_sum)/5
plt.subplot(131)
plt.axis("off")
plt.title("Average")
plt.imshow(new_image)
for i in range(80):
for j in range(80):
new_image[i][j] = gss[i*2][j*2]
plt.subplot(132)
plt.axis("off")
plt.imshow(new_image)
plt.title("non_linear")
gss = gss.astype(np.uint8)
new_image = cv2.resize(gss,(80,80))
plt.subplot(133)
plt.title("opencv resize")
plt.axis("off")
plt.imshow(new_image)
plt.show()
从左至右分别为 均值插值 非线性差值 opencv实现的resize
可以看出opencv默认的差值与非线性插值的区别,非线性插值的锯齿感更为明显
new_image = np.zeros((320,320,3),dtype=np.uint8)
for i in range(320):
for j in range(320):
new_image[i][j] = gss[int(i/2)][int(j/2)]
plt.subplot(121)
plt.axis("off")
plt.title("non_linear")
plt.imshow(new_image)
plt.subplot(122)
plt.axis("off")
plt.title("origin")
plt.imshow(gss)
plt.show()
上图为将其非线性插值放大,与原图的对比
对上面的代码进行整理,处理下边界条件实现的不插值resize
def resize(orign_image,shape):
src_height, src_width = orign_image.shape[:2]
scale_height, scale_width = shape
sw = src_width/scale_width
sh = src_height/scale_height
if len(orign_image.shape)<3:
scale_image = np.zeros(shape,dtype=np.uint8)
else:
scale_image = np.zeros((shape[0],shape[1],orign_image.shape[2]),dtype=np.uint8)
def ceil(length, bound):
if length>=bound:
return int(bound-1)
elif length<0:
return 0
else:
return int(length)
for i in range(scale_height):
for j in range(scale_width):
scale_image[i][j] = orign_image[ceil(i*sh,src_height)][ceil(j*sw,src_width)]
return scale_image
使用上面的代码可以实现不插值缩放,但是其复用性不强,将坐标变换单独抽离出来,实现下面的线性插值
def bilinear_interpolate(im, y, x):
x = np.asarray(x)
y = np.asarray(y)
x0 = np.floor(x).astype(int)
x1 = x0 + 1
y0 = np.floor(y).astype(int)
y1 = y0 + 1
x0 = np.clip(x0, 0, im.shape[1]-1);
x1 = np.clip(x1, 0, im.shape[1]-1);
y0 = np.clip(y0, 0, im.shape[0]-1);
y1 = np.clip(y1, 0, im.shape[0]-1);
Ia = im[ y0, x0 ]
Ib = im[ y1, x0 ]
Ic = im[ y0, x1 ]
Id = im[ y1, x1 ]
wa = (x1-x) * (y1-y)
wb = (x1-x) * (y-y0)
wc = (x-x0) * (y1-y)
wd = (x-x0) * (y-y0)
return wa*Ia + wb*Ib + wc*Ic + wd*Id
def bilinear_resize(src,dsize):
src_h,src_w = src.shape[:2]
fh = dsize[0]/src_h
fw = dsize[1]/src_w
if len(src.shape)>3:
dst = np.zeros(dsize,dtype=np.uint8)
else:
dst = np.zeros(dsize+src.shape[2:],dtype=np.uint8)
for row in range(dst.shape[0]):
for col in range(dst.shape[1]):
dst[row][col] = bilinear_interpolate(src,row/fh,col/fw)
return dst
使用著名的lenna图来作为两种插值方式的对比
lenna = cv2.imread("lena.jpg")
lenna = cv2.cvtColor(lenna,cv2.COLOR_BGR2RGB)
plt.imshow(lenna)
plt.axis("off")
plt.show()
print(lenna.shape)
(512, 512, 3)
resize_lenna = cv2.resize(lenna,(64,64))
plt.axis("off")
plt.imshow(resize_lenna)
plt.show()
nearst_lenna = resize(lenna,(512,512))
plt.subplot(121)
plt.axis("off")
plt.imshow(nearst_lenna)
bilinear_lenna = bilinear_resize(lenna,(512,512))
plt.subplot(122)
plt.axis("off")
plt.imshow(bilinear_lenna)
plt.show()
图片旋转的实现
在下面的代码中使用numpy进行矩阵操作完成了图片的旋转
首先简单的使用书上的旋转矩阵将原坐标映射到新的坐标上
def rotate_image(src,rotate_angel):
src_h, src_w = src.shape[:2]
dsize = src.shape
dst = np.zeros(src.shape,dtype=np.uint8)
def _rotate_coodinate(x,y,angel):
import math
angel = angel/180*math.pi
coodinate = np.array([x,y,1])
rotate_matrix = np.array([[math.cos(angel),math.sin(angel),1],[-math.sin(angel),math.cos(angel),1],[0,0,1]])
coodinate = coodinate.dot(rotate_matrix)
x,y,_ = coodinate
return int(x),int(y)
for row in range(src_h):
for col in range(src_w):
dst_x,dst_y = _rotate_coodinate(col,row,rotate_angel)
if dst_x < 0 or dst_x >= src_w or dst_y<0 or dst_y>=src_h:
pass
else:
dst[dst_y][dst_x] = src[row][col]
return dst
new_image = rotate_image(lenna,-40)
plt.axis("off")
plt.imshow(new_image)
plt.show()
此处代码的缺陷有2点:
* 图片偏离中心
* 未反向映射导致,float转int时图像存在黑点
对上述代码进行修改,进行反向映射.
使用矩阵将其移动回中心,同时旋转矩阵为之前的逆
def rotate_image(src,rotate_angel):
src_h, src_w = src.shape[:2]
dsize = src.shape
dst = np.zeros(src.shape,dtype=np.uint8)
def _rotate_coodinate(x,y,angel):
import math
angel = angel/180*math.pi
coodinate = np.array([x,y,1])
rotate_matrix = np.array([[math.cos(angel),-math.sin(angel),0],[math.sin(angel),math.cos(angel),0],[0,0,1]])
rotate_center_first = np.array([[1,0,0],[0,-1,0],[-0.5*dsize[1],0.5*dsize[0],1]])
rotate_center_last = np.array([[1,0,0],[0,-1,0],[0.5*dsize[1],0.5*dsize[0],1]])
coodinate = coodinate.dot(rotate_center_first).dot(rotate_matrix).dot(rotate_center_last)
x,y,_ = coodinate
return int(x),int(y)
for row in range(src_h):
for col in range(src_w):
dst_x,dst_y = _rotate_coodinate(col,row,rotate_angel)
if dst_x < 0 or dst_x >= src_w or dst_y<0 or dst_y>=src_h:
pass
else:
dst[row][col] = src[dst_x][dst_y]
return dst
new_image = rotate_image(lenna,-40)
plt.imshow(new_image)
plt.axis("off")
plt.show()
仿射变换
垂直变换
将单个点的坐标(x,y)转换为下面矩阵
乘上下面矩阵进行垂直方向的偏移变换
def transform(src,s_v):
src_h, src_w = src.shape[:2]
dsize = src.shape
dst = np.zeros(src.shape,dtype=np.uint8)
def _vertical_transform(x,y,s_v):
coodinate = np.array([x,y,1])
transform_matrix = np.array([[1,0,0],[s_v,1,0],[0,0,1]])
x,y,_ = coodinate.dot(transform_matrix)
if x>=dsize[1]:
x = dsize[1]-1
if x<0:
x = 0
if y>=dsize[0]:
y = dsize[0]-1
if y<0:
y = 0
return int(x),int(y)
for row in range(dst.shape[0]):
for col in range(dst.shape[1]):
dst_x,dst_y = _vertical_transform(col,row,s_v)
dst[dst_y][dst_x] = src[row][col]
return dst
new_image = transform(lenna,0.5)
plt.imshow(new_image)
plt.axis("off")
plt.show()
水平变换
def transform(src,s_h):
src_h, src_w = src.shape[:2]
dsize = src.shape
dst = np.zeros(src.shape,dtype=np.uint8)
def _vertical_transform(x,y,s_v):
coodinate = np.array([x,y,1])
transform_matrix = np.array([[1,s_h,0],[0,1,0],[0,0,1]])
x,y,_ = coodinate.dot(transform_matrix)
if x>=dsize[1]:
x = dsize[1]-1
if x<0:
x = 0
if y>=dsize[0]:
y = dsize[0]-1
if y<0:
y = 0
return int(x),int(y)
for row in range(dst.shape[0]):
for col in range(dst.shape[1]):
dst_x,dst_y = _vertical_transform(col,row,s_h)
dst[dst_y][dst_x] = src[row][col]
return dst
new_image = transform(lenna,0.3)
plt.imshow(new_image)
plt.axis("off")
plt.show()