图像的数组表示
图像是有规则的二维数据,可以用numpy 库将图像转换成数组对象 :
from PIL import Image
import numpy as np
im=np.array(Image.open("D://np.jpg"))
print(im.shape,im.dtype)
结果:
图像转换对应的ndarray 类型是3 维数据,如(569, 813, 3),其中,前两维表示图像的长度和宽度,单位是像素,第三维表示每个像素点的RGB 值,每个RGB 值是一个单字节整数。
像素处理
一.convert()函数:
PIL 库包括图像转换函数,能够改变图像单个像素的表示形式。使用convert()函数
lmage.convert(mode) #使用不同的参数,转换图像为新的模式,返回新副本
1.RGB模式("RGB"):RGB模式是最常见的彩色图像模式,它使用红色、绿色和蓝色三个通道来表示图像的颜色。
2.’L’模式表示将像素从RGB 的3 字节形式转变为单一数值形式,这个数值范围在0 到255,表示灰度色彩变化。
3.RGBA模式("RGBA"):RGBA模式也是一种彩色图像模式,与RGB模式类似,但多了一个Alpha通道。Alpha通道表示图像的透明度,取值范围为0到255,0代表完全透明,255代表完全不透明。
4.CMYK模式("CMYK"):CMYK模式主要用于印刷领域,使用青色、品红色、黄色和黑色四个通道来表示颜色。它与RGB模式的颜色表示方式不同。
5.HSV模式("HSV"):HSV模式是一种基于色调(Hue)、饱和度(Saturation)和亮度(Value)的颜色表示方式。它可以更直观地表示颜色的属性。
以"L"类型举例:
from PIL import Image
import numpy as np
im = np.array(Image.open("D://np.jpg").convert('L'))
print(im.shape, im.dtype)
结果:
此时,图像从彩色变为带有灰度的黑白色。转换后,图像的ndarray 类型变为二维数据,每个像素点色彩只由一个整数表示。
二.numpy访问像素值
通过对图像的数组转换,可以利用numpy 访问图像上任意像素值,例如,获取访问位于坐标(20, 300)像素的颜色值或获取图像中最大和最小的像素值。也可以采用切片方式获取指定行或列的元素值,甚至修改这些值。
>>> im[20][300] 28 >>> print(int(im.min()),int(im.max())) 0 255 >>> print(im[10,:]) [ 2 4 6 7 7 ... 13 12]
三.图像变换
将图像读入ndarray 数组对象后,可以通过任意数学操作来获取相应的图像变换。以灰度变换为例,分别对灰度变化后的图像进行反变换、区间变化和像素值平方处理。
注:有些数学变换会改变图像的数据类型,如变成浮点数等,所以在重新生成PIL 图像前要先将数据类型通过numpy.uint()变换成整数
>>>im0 = np.array(Image.open('np.jpg').convert('L'))
>>>im1 = 255 - im0 #反变换
>>>im2 = (100/255)*im0 + 150 #区间变换
>>>im3 = 255*(im1/255)**2 #像素平方处理
>>>pil_im = Image.fromarray(np.uint(im1)) #对im1,im2,im3执行
>>>pil_im.show()
原图:
处理过后:
四.手绘效果的实现
- 为了实现手绘风格,即黑白轮廓描绘,首先需要读取原图像的明暗变化,即灰度值。从直观视觉感受上定义,图像灰度值显著变化的地方就是梯度,它描述了图像灰度变化的强度。
- 通常可以使用梯度计算来提取图像轮廓,numpy 中提供了直接获取灰度图像梯度的函数gradient(),传入图像数组表示即可返回代表x 和y 各自方向上梯度变化的二维元组。
numpy.gradient(f, *varargs, axis=None, edge_order=1)
f,包含标量函数样本的n维数组
varargs:标量或数组列表,可选
edge_order:{1,2}, 可选。使用n阶精确的边界差来计算梯度。默认值:1。
axis:沿着给定的轴计算梯度
返回: f关于每一维的梯度
将光源定义为三个参数:方位角vec_az、俯视角vec_el 和深度权值depth。两个角度的设定和单位向量构成了基础的柱坐标系,体现物体相对于虚拟光源的位置。
通过np.gradient()函数计算图像梯度值作为新色彩计算的基础。为了更直观的进行计算,可以把角度对应的柱坐标转化为xyz 立体坐标系。dx、dy、dz 是像素点在施加模拟光源后在x、y、z 方向上明暗度变化的加权向量。
A 是梯度幅值,也是梯度大小。各个方向上总梯度除以幅值得到每个像素单元的梯度值。利用每个单元的梯度值和方向加权向量合成灰度值,clip 函数用预防溢出,并归一化到0‐255 区间。最后从数组中恢复图像并保存。
from PIL import Image
import numpy as np
im = Image.open("D://np.jpg").convert('L')
a = np.asarray(im).astype('float')
depth = 10.0 # 设置深度值(0-100)
grad = np.gradient(a) # 取图像灰度的梯度值
grad_x, grad_y, *_ = grad # 分别取横纵图像梯度值
grad_x = grad_x * depth / 100
grad_y = grad_y * depth / 100
vec_el = np.pi / 2.2 # 光源的俯视角度,弧度值
vec_az = np.pi / 4 # 光源的方位角度,弧度值
dx = np.cos(vec_el) * np.cos(vec_az) # 光源对x轴的影响
dy = np.cos(vec_el) * np.sin(vec_az) # 光源对y轴的影响
dz = np.sin(vec_el) # 光源对z轴的影响
A = np.sqrt(grad_x**2 + grad_y**2 + 1.0)
uni_x = grad_x / A
uni_y = grad_y / A
uni_z = 1.0 / A
a2 = 255 * (dx * uni_x + dy * uni_y + dz * uni_z) # 光源归一化
a2 = a2.clip(0, 255)
im2 = Image.fromarray(a2.astype('uint8')) # 重构图像
im2.save('D://npHandDraw2.jpg') # 保存图像
原图:
结果:
可以进入如下链接学习:
Python实现图像手绘效果的方法详解_python_脚本之家 (jb51.net)
手绘图像的基本思想是利用像素之间的梯度值(而不是像素本身)重构每个像素值。为了体现光照效果,设计一个光源,建立光源对各点梯度值的影响函数,进而运算出新的像素值,从而体现边界点灰度变化,形成手绘效果。
具体来说,为了更好体现立体感,增加一个z方向梯度值,并给x 和y 方向梯度值赋权值depth。这种坐标空间变化相当于给物体加上一个虚拟光源,根据灰度值大小模拟各部分相对于人视角的远近程度,使画面显得有“深度”。
在利用梯度重构图像时,对应不同梯度取0‐255 之间不同的灰度值,depth 的作用就在于调节这个对应关系。depth 较小时,背景区域接近白色,画面显示轮廓描绘;depth 较大时,整体画面灰度值较深,近似于浮雕效果。