通过上一节已经学会了使用OpenCV读取、显示、写入图像等基本操作,有了初步基础就可以学习更多的图像处理方法。
缩放、裁剪、补边
import cv2
img=cv2.imread('dog.jpg')
# 缩小为200x200的正方形
img_200x200=cv2.resize(img,(200,200))
# 不直接指定缩放后的大小,通过fx和fy指定缩放比例,0.5表示长宽各一半
# 插值方法默认为cv2.INTER_LINEAR,这里指定为最近邻插值
img_half=cv2.resize(img,(0,0),fx=0.5,fy=0.5,interpolation=cv2.INTER_NEAREST)
# 上下各贴50像素的黑边
img_add=cv2.copyMakeBorder(img,50,50,0,0,cv2.BORDER_CONSTANT,value=(0,0,0))
# 裁剪,从100行到200行,20列到150列
patch_img=img[100:200, 20:150]
# 在图片上显示字体
# cv2.putText(图像, 文字, (x, y), 字体, 大小, (b, g, r), 宽度)
font = cv2.FONT_HERSHEY_TRIPLEX
font_size = 0.6
img = cv2.putText(img, "img", (0, 20), font, font_size, (0, 255, 0), 1)
img_200x200 = cv2.putText(img_200x200, "img_200x200", (0, 20), font, font_size, (0, 255, 0), 1)
img_half = cv2.putText(img_half, "img_half", (0, 20), font, font_size, (0, 255, 0), 1)
img_add = cv2.putText(img_add, "img_add", (0, 20), font, font_size, (0, 255, 0), 1)
patch_img = cv2.putText(patch_img, "patch_img", (0, 20), font, font_size, (0, 255, 0), 1)
cv2.imshow("img",img)
cv2.imshow("img_200x200",img_200x200)
cv2.imshow("img_half",img_half)
cv2.imshow("img_add",img_add)
cv2.imshow("patch_img",patch_img)
cv2.waitKey(0)
# 最后释放窗口是个好习惯!
cv2.destroyAllWindows()
结果如下图:
从上图可以发现,我们本来写入给patch_img 的文字同样也写入到img原图上了,patch_img 是我们从img原图截取的一部分,这个现象说明对原图下标截取得到的新图本质上还是引用了原图的内存,而没有重新创建一个新图内存。而img_200x200,img_half,img_add则都是新创建的图像占据独立的内存。
ROI截取
#ROI,Range of instrest
roi = img[100:200,300:400] #截取100行到200行,列为300到400列的整块区域
img[50:150,200:300] = roi #将截取的roi移动到该区域 (50到100行,200到300列)
b = img[:,:,0] #截取整个蓝色通道
b,g,r = cv2.split(img) #截取三个通道,比较耗时
img = cv2.merge((b,g,r))
图像像素获取和编辑
像素值获取与设置:
img = cv2.imread(r"C:\Users\Administrator\Desktop\roi.jpg")
#获取和设置
pixel = img[100,100] #[57 63 68],获取(100,100)处的像素值
img[100,100]=[57,63,99] #设置像素值
b = img[100,100,0] #57, 获取(100,100)处,blue通道像素值
g = img[100,100,1] #63
r = img[100,100,2] #68
r = img[100,100,2]=99 #设置red通道值
#获取和设置
piexl = img.item(100,100,2)
img.itemset((100,100,2),99)
获取图片部分参数:
import cv2
img = cv2.imread(r"C:\Users\Administrator\Desktop\roi.jpg")
#rows,cols,channels
img.shape #返回(280, 450, 3), 宽280(rows),长450(cols),3通道(channels)
#size
img.size #返回378000,所有像素数量,=280*450*3
#type
img.dtype #dtype('uint8')
Gamma变换与直方图统计
仅仅观察图片,我们较难一眼就对像素中值的分布有细致的了解,这时候就需要直方图。
直方图:方便对图像的像素值分布了解更清晰,统计图片中像素值分布在[0-255]上的像素点个数,像素值接近0表示暗的部分,像素值接近255表示亮度大的部分。
如果直方图中的成分过于靠近0或者255,可能就出现了暗部细节不足或者亮部细节丢失的情况。比如下图:
该图整体偏暗,暗部细节是非常弱的。这个时候,一个常用方法是考虑用Gamma变换来提升暗部细节。Gamma变换是矫正相机直接成像和人眼感受图像差别的一种常用手段,简单来说就是通过非线性变换让图像从对曝光强度的线性响应变得更接近人眼感受到的响应。具体的定义和实现,以上面图片为例,执行计算直方图和Gamma变换的代码如下:
import numpy as np
import cv2
import matplotlib.pylab as plt
from mpl_toolkits.mplot3d import Axes3D
img=cv2.imread('bookpage.jpg')
# 分通道计算每个通道的直方图
hist_b=cv2.calcHist([img],[0],None,[256],[0,256]).flatten()
hist_g=cv2.calcHist([img],[1],None,[256],[0,256]).flatten()
hist_r=cv2.calcHist([img],[2],None,[256],[0,256]).flatten()
# Gamma变换的函数
def gamma_trans(img,gamma):
# 先归一化到1,之后利用gamma作为指数求出新值,再还原
gamma_table=[np.power(x/255.0,gamma)*255.0 for x in range(256)]
gamma_table=np.round(np.array(gamma_table)).astype(np.uint8)
# 用opencv的查表函数实现该映射
return cv2.LUT(img,gamma_table)
# 执行Gamma变换,小于1的值让暗细节大量提升,同时亮部细节少量提升
img_corrected=gamma_trans(img,0.5)
cv2.imshow("img",img)
cv2.imshow("img_corrected",img_corrected)
# cv2.waitKey(0)
# 分通道计算Gamma矫正后的直方图
hist_b_corrected=cv2.calcHist([img_corrected],[0],None,[256],[0,256]).flatten()
hist_g_corrected=cv2.calcHist([img_corrected],[1],None,[256],[0,256]).flatten()
hist_r_corrected=cv2.calcHist([img_corrected],[2],None,[256],[0,256]).flatten()
# 直方图可视化
fig=plt.figure()
pix_hists=[
[hist_b,hist_g,hist_r],
[hist_b_corrected,hist_g_corrected,hist_r_corrected]
]
pix_vals = range(256)
for sub_plt, pix_hist in zip([121, 122], pix_hists):
ax = fig.add_subplot(sub_plt, projection='3d')
for c, z, channel_hist in zip(['b', 'g', 'r'], [20, 10, 0], pix_hist):
cs = [c] * 256
ax.bar(pix_vals, channel_hist, zs=z, zdir='y', color=cs, alpha=0.618,
edgecolor='none', lw=0)
ax.set_xlabel('Pixel Values')
ax.set_xlim([0, 256])
ax.set_ylabel('Channels')
ax.set_zlabel('Counts')
plt.show()
gamma调整后的图与原图对比可以发现暗部细节明显清晰很多
而且从直方图上来看,像素值也从集中在0附近变得散开了一些。
图像的仿射变换
图像的仿射变换涉及到图像的形状位置角度的变化,是深度学习预处理中常用到的功能,在此简单回顾一下。仿射变换具体到图像中的应用,主要是对图像的缩放,旋转,剪切,翻转和平移的组合。在OpenCV中,仿射变换的矩阵是一个2×3的矩阵,其中左边的2×2子矩阵是线性变换矩阵,右边的2×1的两项是平移项:
对于图像上的任一位置(x,y),仿射变换执行的是如下的操作:
需要注意的是,对于图像而言,宽度方向是x,高度方向是y,坐标的顺序和图像像素对应下标一致。所以原点的位置不是左下角而是左上角,y的方向也不是向上,而是向下。在OpenCV中实现仿射变换是通过仿射变换矩阵和cv2.warpAffine()这个函数,还是通过代码来理解一下,例子中图片的分辨率为315×472:
import cv2
import numpy as np
# 读取一张照片
img = cv2.imread('dog.jpg')
img_h = img.shape[0]
img_w = img.shape[1]
print(img_w)
print(img_h)
# 沿着横纵轴放大1.6倍,然后平移(-50,-100),最后沿原图大小截取,等效于裁剪并放大
M_crop_elephant = np.array([
[1.6, 0, -50],
[0, 1.6, -100]
], dtype=np.float32)
dog_gain = cv2.warpAffine(img, M_crop_elephant, (img_w, img_h))
cv2.imwrite('dog_gain.jpg', dog_gain)
# x轴的剪切变换,角度15°
theta = 15 * np.pi / 180
M_shear = np.array([
[1, np.tan(theta), 0],
[0, 1, 0]
], dtype=np.float32)
img_sheared = cv2.warpAffine(img, M_shear, (img_w, img_h))
cv2.imwrite('dog_sheared.jpg', img_sheared)
# 顺时针旋转,角度15°
M_rotate = np.array([
[np.cos(theta), -np.sin(theta), 0],
[np.sin(theta), np.cos(theta), 0]
], dtype=np.float32)
img_rotated = cv2.warpAffine(img, M_rotate, (img_w, img_h))
cv2.imwrite('dog_rotated.jpg', img_rotated)
# 某种变换,具体旋转+缩放+旋转组合可以通过SVD分解理解
M = np.array([
[1, 1.5, -400],
[0.5, 2, -100]
], dtype=np.float32)
img_transformed = cv2.warpAffine(img, M, (img_w*2, img_h*2))
cv2.imwrite('dog_transformed.jpg', img_transformed)
结果如下图:
opencv的基本绘图
OpenCV提供了各种绘图的函数,可以在画面上绘制线段,圆,矩形和多边形等,还可以在图像上指定位置打印文字。 关于OpenCV在图像上的绘图操作有很多教程做了讲解,此处不再赘述