1. 项目概述
1.1 从图像处理到图形学:Pillow到底能做什么
如果你刚接触计算机图形学,又不想一上来就啃OpenGL、DirectX那种庞然大物,那么Pillow是一个非常合适的切入点。
Pillow(PIL分支的延续版本)是Python生态里最老牌、最基础的图像处理库。它不像OpenCV那样强调查帧、特征提取这些视觉任务,也不像NumPy那样只管数值计算,它专注的事情很纯粹:读写图片文件、做基本的像素操作、几何变换、颜色处理和图像合成。
这些功能恰好覆盖了计算机图形学里最基础的那一层——光栅图像的处理与合成。比如你刷到过的那种“把一张半透明的水印盖到另一张图上”的效果,或者“把一张正方形的图片改造成圆角头像”,再或者“把一张模糊的图变清晰”的锐化操作,本质都是图形学里光栅化之后要做的事情。在真正的渲染管线里,这些操作对应的是图像的后期处理(Post-Processing)阶段。
换句话说,用Pillow做图形学实验,它是很好的第一站。它体积小、依赖少、API直观,适合先建立“图像到底是什么”的直觉,再往里钻更复杂的知识。
1.2 编写本书的目标面向的读者
这篇内容面向两类人:一是刚接触编程或者刚开始学图形学的学生,想用Python快速验证一些图像处理想法;二是做Web、爬虫、办公自动化这类方向,偶尔需要处理图片但不想引入重型依赖的开发者。
对于前者,我会尽量把每个操作背后的图形学概念讲清楚——不只是给出代码,而是解释“为什么这么写”“这个参数代表什么”。对于后者,我直接给可用的代码和踩坑笔记,你直接抄作业就行。
2. 整体设计思路与Pillow选型考量
2.1 为什么用Pillow而不是直接用OpenCV或NumPy
很多人在入门图像处理时会纠结工具选型。我的观点是:先看你到底要解决什么问题。
如果你要做人脸识别、物体检测、视频流分析,那OpenCV的Cascade、DNN模块、VideoCapture这些功能确实无法替代。但如果你只是想把一张图裁成合适大小、转个格式、调个颜色或者拼几张图,那直接用Pillow就够了。OpenCV在这里的问题在于:它的图像默认是BGR通道顺序,而不是我们更熟悉的RGB,第一次用的人很容易在通道顺序上踩坑;同时它的安装体积也不小,在跑批任务或者部署到轻量环境时显得头重脚轻。
NumPy本身不处理图像格式,它负责的是一维和二维数组的数学运算。Pillow读取图像后转换为Pillow对象,你可以直接从这个对象拿到像素数组用NumPy操作,也可以反过来把NumPy数组转成Pillow对象。两者是配合关系,不是替代关系。
所以在我的项目里,流程一般是:
from PIL import Image import numpy as np # 读取图像并转成 NumPy 数组 img = Image.open("input.png") arr = np.array(img) # 做你需要的数值运算 # ... # 转回 Pillow 对象并保存 result = Image.fromarray(arr.astype("uint8")) result.save("output.png")既能利用NumPy的性能和灵活性,又能享受Pillow在文件格式编解码、保存压缩、基础滤镜这些方面的成熟实现,两边的好处都拿。
2.2 图形学处理的完整流程拆解
一个典型的Pillow图形学处理流程大致可以分成四个阶段,我把它拆开来说:
阶段一:图像输入与预处理
这个阶段的任务是读取图像、做基本校验(尺寸、模式、格式)、必要时进行格式转换(比如RGBA转RGB)。很多人忽视这一阶段,直接上来就处理,结果后面往往因为通道数不一致、尺寸不匹配出现各种诡异报错。
阶段二:像素级操作
像素是光栅图像的最小单位,图形学里很多效果本质上都是对像素值的数学变换。灰度化就是取RGB三个通道的加权平均值;二值化就是按阈值把灰度图映射成0和255两个值;反相就是用255减去每个通道的像素值。这些操作在Pillow里有的可以直接用内置的ImageOps模块,有的需要配合NumPy自己写。
阶段三:几何变换
包括缩放(Resize)、旋转(Rotate)、裁剪(Crop)、镜像(Flip)以及更通用的仿射变换(Affine Transform)。这部分最能体现图形学的特点——它涉及坐标系变换和插值算法。后面我会专门展开讲,因为这是新手最容易产生疑惑的地方。
阶段四:图像合成
把两张或多张图像按照一定的规则合并到一起。最常见的就是Pillow官方教程里那个著名的Image.blend示例,还有带透明通道的paste和alph_composite操作。合成阶段还涉及混合模式(Multiply、Screen、Overlay等),有的模式Pillow没有直接内置,需要自己通过通道运算实现。
这四个阶段不是孤立的,实际项目里通常是一条链:读取 → 预处理 → 像素操作 → 几何变换 → 合成 → 保存输出。我在下面章节的安排也按照这个思路来。
3. 核心细节解析:你必须掌握的图形学基础概念
3.1 坐标系与图像存储的底层逻辑
开始写代码之前,有一个概念必须先搞清楚:数字图像在计算机里是怎么存的。
一张RGB模式的图像,本质是一个三维数组:宽 × 高 × 3个颜色通道。所谓“宽600像素、高400像素”的图,在内存里就是400行、600列的二维网格,每个网格里是RGB三个数值(0-255)。灰度图呢,就是一个二维数组,每个元素是0-255的亮度值。RGBA模式就是在RGB基础上多了Alpha(透明度)通道。
这里有个容易混淆的地方:图像的行列顺序和数学坐标系是不一致的。在数学坐标系里,原点在左下角,X向右、Y向上;在图像存储里,原点在左上角,X向右、Y向下。所以处理像素位置时,array[row][col]里的第一个索引是高度方向(y),第二个是宽度方向(x)。我在刚开始接触时就经常搞反,后来养成了一个习惯:凡是涉及坐标和数组索引转换的地方,先画一个示意图,然后再写代码。
在Pillow里,类似Image.crop(box)的接口,box参数接收的是一个四元组(left, upper, right, lower),它就是按照“左上角起点 + 右下角终点”来描述的矩形区域。注意它的right和lower是“不包含”的边界,也就是说crop((0, 0, 10, 10))剪切出来的区域是第0到9行、第0到9列,总共10×10的尺寸。这一点很像Python的range()规则——左闭右开。
3.2 颜色模型与色彩空间转换
图形学里常涉及几种颜色模型:RGB(加法混色)、CMYK(减法混色,主要和印刷有关)、HSV/HSL(色调、饱和度、亮度,更接近人类感知)、YUV/YCbCr(亮度和色度分离,视频编码常用的色彩空间)。
Pillow内置了多种模式转换,最常用的就是:
img = Image.open("cat.jpg") # RGB转灰度 gray = img.convert("L") # RGB转HSV,注意Pillow的HSV模式不可直接写入文件,需要转回RGB保存 hsv = img.convert("HSV") # RGBA转RGB,去掉透明度 rgb = img.convert("RGB")为什么灰度化之后的图像处理起来更容易?因为灰度图把三个通道降为一个通道,数据维度都变了,后续处理的计算量大幅下降;而且很多经典算法(边缘检测、二值化、形态学操作)的基础都是用灰度图做的。你去看OpenCV里Canny边缘检测函数的流程,第一步就是转灰度。
另外,HSV色彩空间有个特别有用的地方:如果你要在一张图中只保留某种颜色的物体,在RGB空间设置颜色范围很困难(因为RGB三个通道互相牵制、受亮度影响大),但在HSV空间只需要设定色调范围,比如只保留H在20到35之间的区域(黄色区域),效果就很稳定。这个技巧我后面做颜色筛选的时候还会用到。
3.3 插值算法:缩放和旋转背后的数学
几何变换是图形学比较有代表性的部分,而插值算法是几何变换的核心。当你把一张100×100的图放大到200×200时,目标像素的位置往往落在源图像两个像素之间的非整数坐标上,这时候你得猜:这个位置的像素值应该是多少?“猜”的方式——也就是插值算法。
Pillow的resize()接口支持三种插值方式:
| 参数 | 名称 | 速度 | 质量 | 适用场景 |
|---|---|---|---|---|
Image.NEAREST | 最近邻插值 | 最快 | 最差(容易产生锯齿) | 像素风、放大图片保持硬边 |
Image.BILINEAR | 双线性插值 | 中 | 中 | 一般缩放需求 |
Image.BICUBIC | 双三次插值 | 最慢 | 最好(边缘更平滑) | 高质量缩放 |
Image.LANCZOS | Lanczos重采样 | 中慢 | 质量高(适用缩小) | 缩小图片(缩略图) |
从实际效果来说,缩小图用LANCZOS不容易出现摩尔纹,放大图用BICUBIC更平滑。最近邻只适合做风格化效果(比如像素艺术)。
我还经常看到有人用thumbnail()而不是resize()来做缩小。两者区别:thumbnail()会自动保持宽高比,并且只允许缩小不允许放大,适合生成缩略图;resize()则完全按你给的尺寸来,宽高比例变化会造成形变。搞反了的话,生成的图会很难看。
旋转时插值带来的一个副作用是:图旋转后会产生“空洞”像素(源图对应位置没有数据),Pillow默认会用某种插值策略补上这些空洞。另外,rotate(expand=True)参数非常实用——如果不加,旋转后的图像尺寸不变,边缘会被裁掉;加了之后,输出图像会自动根据旋转角度扩边,完整显示旋转后的内容。这两个细节我注意到很少有人提到,但对实际项目影响挺大。
4. 实操过程:从环境搭建到核心功能实现
4.1 环境准备与基础读写
首先是安装。Pillow在绝大部分Python环境里都能一条命令搞定:
pip install pillow如果你是在国内用豆瓣源安装,速度会快很多:
pip install -i https://pypi.doubanio.com/simple pillow然后验证一下版本,习惯上Pillow的版本号会直接影响有些API行为(比如传统Image类的一些接口在新版本中别用旧写法):
from PIL import Image import PIL print(PIL.__version__)接下来就是最基础的四步:读取、查看信息、保存。
img = Image.open("input.jpg") # 常用属性 print(img.size) # (宽, 高) print(img.mode) # 例如 RGB、RGBA、L print(img.format) # 例如 JPEG、PNG print(img.info) # 一些附加元数据 # 显示图片(调起系统看图工具,适合本地调试) img.show() # 保存时转换格式 img.save("output.png")这里有一个不太起眼但很关键的坑:Image.open()是“惰性加载”的,它只在load()被调用时才真正把像素数据读入内存。如果你在打开文件之后、加载之前在中间做了点什么,有时会碰到OSError: image file is truncated这类错误。稳妥的写法是,需要真实操作之前调用一次img.load()或者在打开后立刻做一次尺寸访问,强制加载完成。
保存JPG时还可以控制质量参数:
img.save("output_compressed.jpg", quality=85, optimize=True)quality取值范围是0-100,数值越大画质越好体积也越大。95以上肉眼基本看不出区别,但体积会显著上升。我默认用85,在体积和画质之间比较平衡。optimize参数会启用额外的优化算法,让文件更小一点,代价是保存时间略为变长。
4.2 像素级操作:小接口背后的图形学原理
Pillow的ImageOps模块和Image的point()、split()、merge()方法,覆盖了大量像素级操作。
灰度化与反转
from PIL import ImageOps gray = ImageOps.grayscale(img) # 灰度化 invert = ImageOps.invert(gray) # 反转(只支持灰度或RGB)用公式来看,灰度化本质是Y = 0.299R + 0.587G + 0.114B,权重依据人眼对绿色更敏感、蓝色不太敏感的视觉特性。反转就是out = 255 - in,对应照片底片效果。
二值化
bw = gray.point(lambda x: 255 if x > 128 else 0)这里point()对每个像素都应用一次lambda函数。128是一个常见阈值,但实际项目里往往需要根据图像亮度分布来定——如果你处理的那张图整体偏暗,128会把太多像素归为黑色,这时可以先用ImageOps.autocontrast()做自动对比度拉伸,再进行固定阈值二值化。
通道拆分与合并
r, g, b = img.split() # 交换红蓝通道 b, g, r = img.split() inverted_color = Image.merge("RGB", (b, g, r))交换通道这种操作是学习通道概念时最好的实验。你做一次红蓝互换就会发现图片色调整个变了,因为颜色本身就是通道数字的组合结果。这正是图形学“颜色模型”概念的微观体现。
阈值的动态计算
如果你希望阈值是自适应的,可以用NumPy算个平均值:
import numpy as np arr = np.array(gray) threshold = arr.mean() * 0.9 # 0.9 的系数可以自行调整 bw = gray.point(lambda x: 255 if x > threshold else 0)这里0.9的系数是我在实践里调出来的——直接用均值当阈值会把很多东西打成黑,稍微留一点冗余效果更自然。你可以根据图像的直方图再做针对性调整。
4.3 几何变换的完整操作指南
几何变换主要包括裁剪、缩放、旋转、翻转和仿射变换。
裁剪
# 截取左上角 100x100 区域 box = (0, 0, 100, 100) crop_img = img.crop(box)裁剪坐标(left, upper, right, lower)我用左上角坐标加尺寸的方式来记,避免搞混:left和upper是左上角,right = left + 宽,lower = upper + 高。
旋转与翻转
# 逆时针旋转45度,自动扩边 rotated = img.rotate(45, expand=True) # 90° 旋转(顺时针) right_rotated = img.rotate(-90, expand=True) # 水平翻转 flipped = img.transpose(Image.FLIP_LEFT_RIGHT)有一个和rotate相关的小陷阱:rotate的默认参数expand=False且center=None。如果你不设expand,大角度旋转后四个角的内容会被砍掉。翻转属于坐标轴镜像:水平翻转对应x' = width-1-x,垂直翻转对应y' = height-1-y。
通用仿射变换
Pillow还提供了一个更通用的transform()接口,接收一个仿射变换矩阵:
from PIL import Image # 定义仿射矩阵:a, b, c, d, e, f # 输出坐标 x' = a*x + b*y + c # 输出坐标 y' = d*x + e*y + f matrix = (1, 0.5, 0, # 水平错切 0, 1, 0) # 垂直方向不变 sheared = img.transform(img.size, Image.AFFINE, matrix)仿射变换是图形学坐标变换章节的重点:平移、旋转、缩放、错切,它们都可以统一用一个3×3矩阵表示。Pillow的transform接口隐藏了矩阵最后一行(即0, 0, 1),所以只传六个参数。理解了这一点,你就明白了rotate、resize这些操作底层的逻辑其实都是同一套数学。
我用错切(shear)做过立体字效果,也用它生成过小册子封面的透视设计稿——虽然真正的透视投影需要求逆矩阵(因为transform内部是反向映射,从输出到输入),但只要把矩阵稍做推导就能轻松实现。
4.4 图像合成:从水印到混合模式实践
图像合成最能体现Pillow在实际业务的用武之地。常见需求包括:为商品图片批量加logo水印、制作拼图、生成宣传卡片。
透明水印的核心逻辑
background = Image.open("background.jpg").convert("RGBA") watermark = Image.open("logo.png").convert("RGBA") # logo要求带Alpha通道 # 调整水印大小(保持宽高比) watermark.thumbnail((200, 200)) # 粘贴到右下角,留 20px 边距 margin = 20 pos = (background.width - watermark.width - margin, background.height - watermark.height - margin) background.paste(watermark, pos, watermark) # 第三个参数是mask,对应alpha通道关键在第三参数mask。这里的watermark既是要粘贴的内容,同时它自身的Alpha通道也作为透明蒙版传给paste,于是背景图上只有水印不透明的部分被覆盖,透明区域保留原图。如果省略mask参数,你会得到一个不透明的矩形块,水印效果就废了。
带透明度控制的渐变叠加
overlay = Image.new("RGBA", background.size, (0, 0, 0, 0)) # 在 overlay 上画一个半透明矩形 from PIL import ImageDraw draw = ImageDraw.Draw(overlay) draw.rectangle([0, 0, background.width, background.height], fill=(0, 0, 0, 128)) # 128/255 的黑色透明度 combined = Image.alpha_composite(background, overlay)Image.alpha_composite是用两张RGBA图的Alpha通道做一次正规的alpha混合,数学公式是outA = srcA + dstA * (1 - srcA),这里不用你手算,库内部自己处理好了。它适合做“整图叠加”“浮层压暗”这类操作。
Image.blend与Image.composite的区别
Image.blend(im1, im2, alpha)要求两张图的尺寸和模式一致,alpha是0到1的全局权重因子。而Image.composite(im1, im2, mask)用的是逐像素mask,mask的每个像素值决定该位置上两张图的混合权重,比blend更灵活。如果你需要对不同区域施加不同的混合权重,用composite。
混合模式(正片叠底、滤色、叠加等)Pillow没有提供现成接口,但你知道原理之后自己写也很简单。正片叠底(Multiply)就是每个通道对应像素相乘再除以255:
import numpy as np arr1 = np.array(im1, dtype=np.float32) arr2 = np.array(im2, dtype=np.float32) multiply = (arr1 * arr2 / 255.0).astype(np.uint8) result = Image.fromarray(multiply)我常把这个操作和渐变遮罩配合起来做“老照片暗角”效果,用遮罩控制相乘的权重,效果比纯色叠加自然得多。
4.5 滤镜与增强:快速可用的图形学特效
Pillow内置了一组常用的图像增强工具,集中在ImageFilter和ImageEnhance模块。
高斯模糊与边缘检测
from PIL import ImageFilter, ImageEnhance # 高斯模糊,radius 控制模糊半径 blurred = img.filter(ImageFilter.GaussianBlur(radius=5)) # 边缘检测(这两个都能输出轮廓感很强的图) edges1 = img.filter(ImageFilter.FIND_EDGES) edges2 = img.filter(ImageFilter.CONTOUR)高斯模糊是实现景深效果、去噪、柔化皮肤的基础操作。它就是对每个像素邻域做高斯核的加权平均,radius越大卷积核范围越大、模糊越强。做轮廓提取算法时,我强烈建议先把图转灰度再FIND_EDGES,否则彩色图的三通道各自检测,出来的边缘会带颜色信息干扰判断。
增强类操作
# 对比度增强,factor=1.0 不变,小于1为降低,大于1为增强 enhancer = ImageEnhance.Contrast(img) higher_contrast = enhancer.enhance(1.5) # 亮度、颜色饱和度同理 ImageEnhance.Brightness(img).enhance(0.8) ImageEnhance.Color(img).enhance(1.2)enhance(factor)里的factor是线性调整系数,比如对比度增强背后的原理就是把每个像素值与128(中值)的距离乘以factor,然后重新映射。数值越大,亮的地方更亮、暗的地方更暗。
5. 常见问题与排查技巧实录
5.1 模式(Mode)不一致导致的崩溃
问题现象:paste时报ValueError: cannot determine truth value of a Relational,或者blend时报TypeError: images do not match。
原因:两张图的模式或尺寸不同,一个RGB一个RGBA,或者尺寸不一致。
解决办法:
# 统一模式 im1 = im1.convert("RGBA") im2 = im2.convert("RGBA") # 统一尺寸(这里以 im1 为基准做缩放) im2 = im2.resize(im1.size, Image.LANCZOS)注意这里的顺序:先统一模式,再统一尺寸。如果反过来,你先把im2缩放了但它还是RGB模式,接着还要再转一次RGBA。
5.2 保存JPG时的RGBA报错
问题现象:cannot write mode RGBA as JPEG。
原因:JPEG格式不支持Alpha通道。
解决办法:要么转为RGB再保存,要么直接把透明通道丢掉。
rgb_img = rgba_img.convert("RGB") rgb_img.save("output.jpg", quality=88)如果你确实需要保存带透明信息的图片,换PNG格式保存,rgba_img.save("output.png")即可。另外,PNG保存时如果你设置透明信息不正常,会出出现黑底——检查一下你的图是否确实有正确的Alpha通道数值,而不是全靠RGB值去表示透明。
5.3 坐标和尺寸搞混导致截错区域
问题现象:crop出来的图片内容明显偏移或者尺寸不对。
原因:对(left, upper, right, lower)的“右下角坐标是排除边界”这一点不敏感,或者用成了(x, y, width, height)。
解决办法:写下坐标时自己算一遍:
left = 50 upper = 30 width = 200 height = 150 box = (left, upper, left + width, upper + height)每次这样写,基本不会错。
5.4 搞混rotate的方向
问题现象:想让图片顺时针转90度,结果变成了逆时针。
原因:Pillow的rotate是逆时针方向,且角度为正时逆时针。
解决办法:需要顺时针时传负角度,比如rotate(-90, expand=True)。如果你习惯按顺时针理解,那就记一条规则:角度为正=逆时针,角度为负=顺时针。
5.5 处理大图时的内存失控
问题现象:批量处理几千张图,程序越来越慢,甚至卡死。
原因:循环里每次Image.open()后如果不close,文件对象持续堆积;而且如果你把每张图都转成NumPy数组再叠加处理,内存占用成倍增长。
解决办法:
from PIL import Image for path in image_paths: with Image.open(path) as img: processed = img.rotate(45, expand=True) processed.save("out_" + path)用with语句管理资源,让Image在退出代码块时自动释放。另外,能直接基于Pillow接口完成的操作就不要先去转NumPy数组,因为转数组要拷贝一份连续内存数据,大图场景下多一次拷贝就多几百MB的开销。
5.6 颜色发灰问题:RGB和BGR的陷阱
问题现象:用NumPy把Pillow的图转给某个处理库,结果输出图片颜色变成歪的(蓝色和红色互换)。
原因:Pillow用的是RGB通道顺序,而很多CV库默认BGR。两者通道互换,自然颜色就错了。
解决办法:如果是转给OpenCV这类库,先手动调换通道:
import numpy as np import cv2 pillow_img = Image.open("input.png") arr = np.array(pillow_img) # RGB -> BGR arr_bgr = arr[:, :, ::-1] cv2.imwrite("output_cv.png", arr_bgr)只要牢记一个核心规则:沟通两个库时,随时检查通道顺序,可以少掉很多头发。
6. 实际项目中的扩展思路
Pillow本身不是万能的,但它可以很自然地和其他工具串起来。这里分享三个我实际做过并且觉得比较有代表性的扩展方向。
方向一:结合NumPy做批量特效处理
上面已经提过几次,再补充一个细节:当你要做“给一万张图统一增加亮度”时,直接用Pillow的Enhance循环一万次会很慢,但如果你先转成NumPy数组,用向量化运算同时处理所有像素——一个简单的np.clip(arr + 30, 0, 255)搞定,效率能提升几十倍。图形学底层处理的本质就是大量的矩阵运算,而NumPy正是为此而生的。
方向二:批量生成缩略图的工程化脚本
实际开发里我写过一个统一的缩略图处理脚本,核心逻辑就这几行:
import os from PIL import Image input_dir = "raw_images" output_dir = "thumbs" os.makedirs(output_dir, exist_ok=True) for filename in os.listdir(input_dir): if not filename.lower().endswith((".jpg", ".jpeg", ".png")): continue with Image.open(os.path.join(input_dir, filename)) as img: img.thumbnail((300, 300), Image.LANCZOS) img.save(os.path.join(output_dir, filename), optimize=True)因为thumbnail会自动保持宽高比,即使原始图片规格完全不同,生成的缩略图在视觉感知上都是有序、统一的。
方向三:验证图形学算法用的教学环境
如果你正在学仿射变换,用Pillow写代码去理解坐标映射和插值的效果,可以非常直观地体会到:为什么旋转一个细长矩形会产生锯齿?为什么缩小时反而要用更平滑的插值算法?这些体验是纯理论推导感受不到的。
你可以写个小实验程序:对同一张图分别用NEAREST、BILINEAR、BICUBIC放大6倍,然后对比输出;再用rotate的expand参数测试旋转中心的变化,这些实验都能帮助建立坐标系变换的空间感。
7. 一些实操过程中的切身经验
做图像处理这几年,踩过的坑不少,沉淀下来几条经验:
第一,处理前先统一模式,统一尺寸,统一通道顺序。文件格式转换在最开始就做完,不要拖到处理了一半再补,否则排查半天都定位不到问题。
第二,能直接查看的中间结果就输出来看。哪怕只是临时保存一下,我就用im.save("/tmp/step_result.png")把前后阶段的结果放在那里对比,人眼看着比什么log都好用。调试图形学相关代码尤其需要这么做,因为像素数值很难靠想象去跟踪。
第三,自己写像素操作之前,先想想Pillow是不是已经内置了。很多新手一上来就用双循环遍历每个像素改颜色,慢不说,还容易写错边界。Pillow的point()、split()、merge()、paste()配合NumPy的向量化运算,基本能覆盖绝大多数需求,而且性能好得不是一点半点。
第四,API文档永远是好老师,但不要忘了版本差异。Pillow 8.0以后有些接口的行为和写法有所调整,网上很多博客里的老代码可能在新版本下报错。遇到这种情况,直接在Python里用help(Image)或者dir(Image)查当前版本的接口签名,比在网上找答案更可靠。
第五,把小小的颜色公式记在脑子里。灰度化权重、反相公式、alpha混合公式,这些就是图形学这座大厦的砖块。看起来简单,但真正的价值在于你面对复杂问题时,能快速在头脑里把这些基础公式组合出解决方案。
如果你刚开始学这个方向,我的建议是先别急着上重型框架,用Pillow把图像的读入、像素访问、坐标转换、合成混合这些最底层的东西揉熟,再去接触更复杂的渲染管线,你会发现自己对每一层都在做什么心里有数,不再只是漫无目的地调用API。这一套基础练扎实之后,再回头看OpenGL那些复杂的顶点缓冲区和着色器代码,至少不会被“图像到底是怎么被表示和处理”这个问题所困扰。