Python PIL搭配OpenCV处理图像报错 图像转视频与裁剪功能异常
报错根因拆解
- 接口类型不匹配:
cv2.imread()返回BGR格式的numpy数组,ImageTk.PhotoImage()要求输入为PIL.Image.Image实例,直接传入numpy数组会导致PIL无法识别图像模式,触发KeyError: None - 路径解析失效:OpenCV的图像读取接口不识别
~这类Shell路径缩写,对应路径下的文件会读取失败,返回值为None,传入下游接口后连锁引发属性读取报错 - 渲染体系不兼容:OpenCV的
cv2.imshow()仅支持numpy数组格式的图像输入,传入Tkinter体系的PhotoImage对象完全无法解析 - 逻辑冗余:视频生成环节已在循环中遍历读取2010-2020共11张图像,后续代码又重复追加了2010、2020两张帧,会导致输出视频首尾帧重复
- 原代码的
getImage()函数中,由于路径读取失败得到的None值传入ImageTk,初始化失败引发的AttributeError是连锁触发的次生报错
修正实现方案
核心需求为同时导出标注后的视频文件、导出裁剪处理后的图像文件,无需引入GUI交互逻辑,完全可以通过OpenCV + PIL基础接口打通全流程,避免Tkinter和OpenCV渲染体系混用带来的兼容问题:
- 打通色彩格式转换流程:OpenCV读取的图像为BGR顺序,PIL处理需要RGB格式,保存时再转回BGR保证色彩和原文件一致
- 用
os.path模块处理文件路径,自动创建输出目录,避免路径解析失败 - 删除冗余的重复加帧逻辑,将图像裁剪、保存的流程整合到统一的图像遍历环节
- 替换视频编码格式为兼容性更好的
mp4v,避免DIVX编码在部分系统上播放异常
修正后可直接运行的代码:
import cv2 import os import numpy as np from PIL import Image # 全局配置项 FONT = cv2.FONT_HERSHEY_SIMPLEX PIC_INPUT_ROOT = "./pics" OUTPUT_VIDEO_PATH = "data.avi" CROPPED_IMG_OUTPUT_ROOT = "./croppedpics" os.makedirs(CROPPED_IMG_OUTPUT_ROOT, exist_ok=True) # 自动创建裁剪图输出目录 def batch_process_images(): video_frame_list = [] frame_size = None # 遍历2010-2020的所有年份图像 for year in range(2010, 2021): input_pic_path = os.path.join(PIC_INPUT_ROOT, f"{year}.jpg") if not os.path.exists(input_pic_path): print(f"跳过不存在的文件: {input_pic_path}") continue # 读取原图像 img = cv2.imread(input_pic_path) height, width, layers = img.shape frame_size = (width, height) # 给视频帧加年份标注 annotated_img = cv2.putText( img, str(year), (100, 100), FONT, 1, (255, 255, 255), 1 ) video_frame_list.append(annotated_img) # 用PIL完成图像裁剪处理 img_rgb = cv2.cvtColor(annotated_img, cv2.COLOR_BGR2RGB) pil_img = Image.fromarray(img_rgb) # 按需求设置裁剪坐标(left, top, right, bottom) crop_left, crop_top = 5, height // 4 crop_right, crop_bottom = 164, 3 * height // 4 cropped_img = pil_img.crop((crop_left, crop_top, crop_right, crop_bottom)) # 转回OpenCV格式保存,保证色彩一致性 cropped_img_bgr = cv2.cvtColor(np.array(cropped_img), cv2.COLOR_RGB2BGR) cv2.imwrite( os.path.join(CROPPED_IMG_OUTPUT_ROOT, f"{year}_cropped.jpg"), cropped_img_bgr ) return video_frame_list, frame_size def export_video(frames, size): video_writer = cv2.VideoWriter( OUTPUT_VIDEO_PATH, cv2.VideoWriter_fourcc(*'mp4v'), 0.5, size ) for frame in frames: video_writer.write(frame) video_writer.release() print("视频文件导出完成") if __name__ == "__main__": processed_frames, output_size = batch_process_images() export_video(processed_frames, output_size)
额外注意事项
- 如果确实需要做本地GUI图像展示,不要跨渲染体系传对象:要么全程用OpenCV的
cv2.imshow()展示numpy数组格式的图像,要么全程用Tkinter的Label组件加载ImageTk的PhotoImage,禁止混合传递 - 路径不要用
~缩写,如果需要写用户目录下的绝对路径,要么写全系统可识别的完整路径,要么用os.path.expanduser()自动解析用户目录,避免OpenCV读取失败返回None
内容的提问来源于stack exchange,提问作者Charles Simons
相关产品推荐
相关产品推荐

