Jupyter中matplotlib保存图片空白?用gcf()解决导出问题
2026/9/18 22:50:43 网站建设 项目流程

简介:这份PDF文档围绕Jupyter Notebook中的文件读写与图片处理展开,面向Python数据分析初学者及需要快速上手交互式编程的读者。内容以精简的代码示例贯穿,介绍通过pandas的read_csv读取CSV文件、使用matplotlib的imread和imshow加载并展示图片,同时重点演示如何借助plt.gcf()结合savefig()正确导出非空白的PNG/JPG图片,并补充了如何让Notebook单格输出多个结果的设置方法。资源仅含1个PDF文件,大小约114KB,体量轻巧,适合跟着实例逐行敲写与验证。这份资料已吸引10981人学习,实用性受到广泛认可;读者可从中获得可直接迁移的读写代码模板、避免常见保存空白的排错思路,以及理解Jupyter展示机制的关键技巧,对日常数据处理与可视化工作有直接帮助。

1. 别急着点保存:Jupyter 里读文件和存图片的坑

用 Jupyter Notebook 做数据分析时,读文件和存图片的频率比很多人想象得高。你可能已经习惯用pd.read_csv()读表格、用plt.imshow()显示图片,但真正到保存结果这一步,不少人会踩到一个反直觉的坑:直接调用plt.savefig('./66.png'),生成的图片文件打开后是空白的。原因不在保存逻辑,而在于 Jupyter 对 matplotlib 图形对象的生命周期管理和普通 Python 脚本不一样。这篇文章围绕 Jupyter 里“读文件、读图片、导出文件、导出图片”这条链路,把每一环节的 API 选择、参数含义和状态机制拆开讲清楚,并给出可直接复制的代码。适合数据分析和机器学习从业者,尤其是经常在 Notebook 里做可视化、需要把中间结果落盘的人。

2. 用 pandas 读文件:read_csv 的正确打开方式

2.1 从 read_csv 说起:最小可运行代码

先看最基础的用法。在 Jupyter 单元格里执行下面的代码,会把当前工作目录下的test.csv读入内存,生成一个DataFrame

import pandas as pd data = pd.read_csv('./test.csv') data.head()

read_csv()的返回类型是pandas.core.frame.DataFramehead()的作用是显示前 5 行,方便在 Notebook 里快速预览数据形态。这里有一个容易忽略的点:read_csv()默认把第一行当作列名,如果你的 CSV 没有表头,要显式传入header=None,否则第一行数据会被当成字段名,后续分析全部错位。

2.2 参数表:哪些参数在实战中必须关注

read_csv()参数有几十个,但实际项目中高频使用的就那么几个。下面这张表列出的都是我在真实数据文件上验证过的配置项,按使用频率排序:

参数默认值作用典型场景
filepath_or_buffer文件路径或 URL本地相对路径、绝对路径、HTTP 链接
sep,分隔符制表符用\t,分号用;
headerinfer指定表头行无表头时设None
encodingutf-8文件编码Windows 下常见gbk/gb2312
dtypeNone指定列数据类型防止 ID 列被读成数值
parse_datesFalse自动解析日期列时间序列分析
usecolsNone只读指定列大文件减少内存占用
nrowsNone只读前 N 行快速探查数据

补充一个常见的坑:读含中文的 CSV 文件时,如果直接pd.read_csvUnicodeDecodeError,优先试encoding='gbk'。这不只是 Windows 的问题,从 Excel 导出的 CSV 在 macOS 上也可能保存为gbkgb18030编码,报错后不要急着改数据,先换编码参数。

2.3 大文件读取与内存控制

对于几百 MB 甚至上 GB 的 CSV,直接read_csv()会把整个文件加载进内存,容易把 Notebook 内核撑爆。常见的做法是分块读取,配合usecols只加载需要的列:

chunk_iter = pd.read_csv( './large_data.csv', usecols=['user_id', 'click_time', 'item_id'], dtype={'user_id': 'int32', 'item_id': 'int32'}, chunksize=50000 ) result = [] for chunk in chunk_iter: # 按块做聚合,再合并结果 result.append(chunk.groupby('user_id')['click_time'].count()) summary = pd.concat(result).groupby(level=0).sum()

这里chunksize=50000表示每次迭代只处理 5 万行,内存占用被控制在一个稳定值,处理完一个 chunk 后内存会被回收。dtype显式指定为int32,相比默认的int64能省一半内存。需要注意,分块聚合后要用groupby(level=0).sum()再做一次汇总,因为同一个user_id可能分散在不同 chunk 里。

提示:Jupyter 单元格执行后没有任何反应,先检查是不是文件路径写错了。read_csv('./test.csv')中的./是相对当前工作目录的路径。在 Notebook 里用!pwd查看当前工作目录,!ls查看目录下文件,比反复猜测路径更高效。

3. 读取图片:imread 与 imshow 的分工

3.1 imread 返回的是什么

matplotlib.pyplot.imread()用于读取图片文件,返回值是一个 NumPy 数组。对于一张彩色图片,数组的 shape 是(高度, 宽度, 通道数),数据类型通常是uint8(取值范围 0 到 255)。对于灰度图,shape 是(高度, 宽度),没有通道维度。下面用代码验证:

import matplotlib.pyplot as plt img = plt.imread('./d.jpg') print(type(img)) # <class 'numpy.ndarray'> print(img.shape) # 例如 (1080, 1920, 3) print(img.dtype) # uint8

这里imread()只做“读取”,并不负责“显示”。如果你只执行上面的代码,Notebook 单元格里只会显示ndarray的类型信息,不会弹出图片。要看到图片内容,必须用plt.imshow(img)

3.2 显示图片与图像数据的关系

plt.imshow(img) plt.axis('off') # 关闭坐标轴,否则会显示 x/y 轴刻度 plt.show()

imshow()接收的数组必须是合法的图像数据。如果用plt.imread()读取,数据类型天然合法。但如果你自己用 NumPy 生成数组再显示,要注意两个细节:第一,dtype必须是uint8float(范围 0 到 1),否则颜色会异常;第二,RGB 三通道的顺序不能乱,matplotlib要求通道顺序是 R(红)、G(绿)、B(蓝),如果你用 OpenCV 读取图片,得到的是 BGR 顺序,直接imshow()会导致红蓝通道互换。

3.3 生成测试图:绕开外部文件依赖

有时候你手头没有现成的图片文件,但又想测试图像处理流程。一个可靠的方式是用np.random.randint()生成一张噪声图:

import numpy as np s = np.random.randint(0, 255, (300, 400, 3), dtype=np.uint8) img_shape = s.shape print(f"生成的图像尺寸: {img_shape[1]} x {img_shape[0]}, 通道数: {img_shape[2]}")

这里的关键参数是(300, 400, 3):300 是高度,400 是宽度,3 是 RGB 通道数。dtype=np.uint8把灰度范围限制在 0-255,这样imshow()不需要额外做数据转换。很多图像处理任务想快速验证算法逻辑,用这种噪声图比反复找真实图片更高效,因为像素值完全可控。

注意:np.random.randint生成的是随机噪声,不是自然图像。如果你拿它测试边缘检测、目标识别等算法,结果没有参考意义,只适合验证“数据格式是否正确 + 显示流程是否跑通”。

4. 导出图片:savefig 空白之谜与 gcf() 解决方案

4.1 为什么直接 plt.savefig 会生成空白图

这是整篇文章的核心问题。下面的代码几乎所有初学者都写过:

plt.imshow(s) plt.savefig('./save.png') # 生成的文件是空白的

现象是:Notebook 里图片显示正常,但保存到磁盘的save.png打开后是纯白或纯透明。原因在于 matplotlib 的绘图状态机制:plt.savefig()保存的是“当前 figure 对象”,但在 Jupyter 中,如果使用了%matplotlib inline,Notebook 的显示机制会在单元格结束时自动关闭当前 figure,并把画布内容渲染到输出区域。当你调用plt.savefig()时,虽然执行顺序在显示之前,但实际的 figure 状态可能已经被 Jupyter 的后端干扰了。

这个问题的本质是:plt.savefig()是 pyplot 接口的一个快捷方式,它内部隐含获取“当前 figure”。在 Jupyter 的交互式环境下,“当前 figure”的指向并不总是你刚画完的那一个。解决思路是显式地拿到 figure 对象的引用,再调用该对象的savefig()方法。

4.2 用 gcf() 获取当前图形对象

import matplotlib.pyplot as plt import numpy as np # 生成一张测试图 s = np.random.randint(0, 255, (300, 400, 3), dtype=np.uint8) # gcf: Get Current Figure,获取当前图形对象 fig = plt.gcf() plt.imshow(s) plt.axis('off') # 调用 figure 对象的 savefig 方法 fig.savefig('./output.png', dpi=150, bbox_inches='tight')

执行这段代码后会生成一个非空白的output.png。核心是plt.gcf()这一行:它返回 matplotlib 内部维护的“当前 figure”对象,后续的fig.savefig()保存的就是这个对象。相比plt.savefig(),这种方式明确指定了操作对象,绕开了 Jupyter 对 pyplot 全局状态的干扰。

dpi=150控制输出分辨率,数值越大图片越清晰,但文件体积也越大。bbox_inches='tight'是另一个常用参数,它会自动裁剪图片周围的空白区域,保证保存的内容紧贴图像边界。如果不设置这个参数,保存的图片四周会有大块白边。

4.3 保存参数对比表

参数默认值作用建议
dpifigure.dpi(默认 100)输出分辨率印刷需求设 300,屏幕展示 150 足够
bbox_inchesNone是否裁剪空白区域常用'tight',所见即所得
transparentFalse是否透明背景在网页嵌入时用到
format由扩展名推断强制保存格式'png''jpg''pdf''svg'
facecolor'white'画布背景色'none'可配合透明背景

一个容易被忽略的点是:fig.savefig()支持的文件格式取决于文件扩展名。你写成./output.png就保存为 PNG,写成./output.jpg就保存为 JPEG。Jupyter 场景下 PNG 是首选,因为 JPEG 不支持透明通道,且会有压缩痕迹。如果你要插入 LaTeX 或 Word 文档,保存为 PDF 或 SVG 是矢量格式,放大不失真。

4.4 在绘图场景中的 gcf() 用法

plt.imshow()只是显示图片,更常见的组合是先画一条曲线,再保存。比如画一个折线图:

import numpy as np import matplotlib.pyplot as plt x = np.linspace(0, 10, 100) y = np.sin(x) fig = plt.gcf() # 先获取 figure,再画图 plt.plot(x, y, label='sin(x)') plt.xlabel('x') plt.ylabel('y') plt.legend() fig.savefig('./curve.png', dpi=200, bbox_inches='tight')

注意这里获取fig的时机放在plt.plot()之前。原因在于plt.gcf()返回的永远是当前活跃的 figure,如果你的 Notebook 之前已经画过图,且没有关闭 figure,gcf()可能返回的是旧的图形对象,导致新画的曲线被加到了旧 figure 上。先获取再绘图,可以保证fig和后面的绘图操作属于同一个对象。

4.5 批量导出多张图片时的对象隔离

实际项目中经常要在循环里生成多张图表。如果每次都调plt.gcf(),会发现所有图片都保存在同一个 figure 上。正确的做法是在循环里显式创建新 figure:

import os os.makedirs('./figures', exist_ok=True) for i in range(3): fig = plt.figure(figsize=(6, 4)) data = np.random.randn(100) plt.plot(np.cumsum(data), label=f'run-{i}') plt.legend() fig.savefig(f'./figures/plot_{i}.png', dpi=150) plt.close(fig) # 显式关闭,释放内存

plt.figure(figsize=(6, 4))创建了一个独立的新 figure,figsize控制图片尺寸(英寸)。循环结束后用plt.close(fig)关闭当前 figure,这一步很重要,否则内存会被持续占用,Notebook 长时间运行会越来越卡。os.makedirs('./figures', exist_ok=True)的作用是自动创建输出目录,避免因为目录不存在而保存失败。

提示:如果保存路径中包含中文或特殊字符,建议在代码文件头部加# -*- coding: utf-8 -*-,某些系统下savefig会因为路径编码问题抛出OSError

5. 让 Notebook 输出多个结果:ast_node_interactivity 与图片处理扩展

5.1 默认行为:只显示最后一个表达式

Jupyter Notebook 的默认配置是每个单元格只显示最后一个表达式的返回值。比如下面的代码:

1 + 1 2 + 2 3 + 3

执行后只会在单元格下方显示6,前面的24不会显示。这在数据探索阶段很烦人,因为你可能想同时看到多个变量的值,被迫用print()逐行输出,代码显得很冗余。

5.2 InteractiveShell 的分行显示设置

通过修改 IPython 的交互模式,可以让所有表达式的输出都显示出来:

from IPython.core.interactiveshell import InteractiveShell InteractiveShell.ast_node_interactivity = 'all'

ast_node_interactivity是 IPython 的一个配置项,默认值是'last_expr'(只显示最后一个表达式),改为'all'后,单元格内所有表达式的返回值都会被渲染。设置后,下面这段代码会同时显示三个数字:

1 + 1 2 + 2 3 + 3

输出区域会依次显示246。这对数据探索、模型评估时查看多个指标非常有用。注意这个设置只对当前 Notebook 会话生效,重启内核后需要重新执行一次。如果你希望所有 Notebook 都默认开启,需要修改 IPython 配置文件,但在团队协作场景下不建议这么改,因为不同成员的配置不一致会导致“别人跑不出来同样的结果”。

5.3 多张图片同时显示的实现方式

ast_node_interactivity='all'对图片同样有效。执行下面的代码,单元格下方会一次性显示三张图:

import matplotlib.pyplot as plt import numpy as np fig1, ax1 = plt.subplots() ax1.imshow(np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8)) fig2, ax2 = plt.subplots() ax2.imshow(np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8)) fig3, ax3 = plt.subplots() ax3.imshow(np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8))

这里用了plt.subplots(),它返回一个元组(fig, ax),其中fig是 Figure 对象,ax是 Axes 对象(坐标轴区域)。如果不用plt.subplots()而是直接plt.figure(),在同一个单元格里画多张图,输出区域通常只会显示最后一个 figure。subplots()显式创建了三个独立的fig,配合ast_node_interactivity='all',三个 figure 都会作为独立的输出对象显示。

5.4 实际项目中的落地技巧

结合前四章的内容,一个完整的“读文件 → 处理 → 导出图片 → 多结果展示”流程可以这样组织:

import numpy as np import pandas as pd import matplotlib.pyplot as plt from IPython.core.interactiveshell import InteractiveShell InteractiveShell.ast_node_interactivity = 'all' # 1. 读取数据 df = pd.read_csv('./test.csv', encoding='utf-8') # 2. 提取一列作为纵轴数据,构造横轴索引 y = df['value'].to_numpy() x = np.arange(len(y)) # 3. 画图并保存 fig = plt.gcf() plt.plot(x, y) plt.xlabel('index') plt.ylabel('value') plt.title('data overview') fig.savefig('./data_overview.png', dpi=150, bbox_inches='tight') # 4. 同时输出数据摘要和图片,一次看全 print(f"数据行数: {len(df)}") print(f"缺失值统计:\n{df.isnull().sum()}") df.describe()

这个单元格把前三章的技能串成了完整的分析链路。df['value'].to_numpy()将 pandas 的 Series 转成 NumPy 数组,转成 ndarray 后可以配合np.arange生成横轴索引。df.describe()在设置了ast_node_interactivity='all'后会直接显示统计结果,不需要额外加print()。保存图片的代码被放在describe()之前,确保fig对象在绘图后立即落盘,避免最后单元格里的其他输出干扰 matplotlib 的图形状态。

如果图片保存后仍然空白,可以用下面的代码做一次快速诊断:

import os file_path = './data_overview.png' print(f"文件是否存在: {os.path.exists(file_path)}") print(f"文件大小: {os.path.getsize(file_path)} 字节")

文件存在但大小为 0 字节,说明savefig()没有正确写入数据,优先检查fig = plt.gcf()是否放在了绘图命令之前。文件完全不存在,检查输出目录是否有写权限,用os.makedirs('./output', exist_ok=True)创建目录后再保存。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询