Python 中常用图像数据结构

news2024/11/28 8:38:12

(原文:https://blog.iyatt.com/?p=13222 )

1 测试环境

Python 3.12.1

numpy 1.26.3
opencv-python 4.9.0.80
pillow 10.2.0
matplotlib 3.8.2

注:

  • 基于 2022.1.16 和 2022.4.9 的三篇博文再次验证并重写,原文已删除
  • 测试使用的图片文件为 AI 绘制

2 图像数据结构

2.1 OpenCV 打开图片并显示

Python 版 OpenCV 中图像数据是用的 NumPy 数组存储,通道顺序为 BGRA(蓝 绿 红 透明度),三通道则为 BGR。

import cv2

image_path = 'demo.png' # 图片路径

img = cv2.imread(image_path) # 打开图片文件
cv2.imshow('my image', # 窗口标题
           img) # 图像数据
cv2.waitKey(0) # 阻塞窗口,按任意键继续
cv2.destroyAllWindows() # 关闭所有窗口

file

2.2 Matplotlib 打开图片并显示

Matplotlib 和 OpenCV 一样都是采用的 NumPy 数组存储图像数据,只是通道顺序为 RGB。

import matplotlib.pyplot as plt

image_path = 'demo.png'

image = plt.imread(image_path)
plt.axis('off') # 不显示坐标轴
plt.imshow(image)
plt.show()

file

2.3 Pillow 打开图片用 OpenCV 显示

Pillow 是 Python 中较为常用的图像库。

from PIL import Image
import cv2
import numpy as np

image_path = 'demo.png'

pillow_image = Image.open(image_path)
opencv_image = cv2.cvtColor(
    np.array(pillow_image), # Pillow 图像数据结构转 NumPy
    cv2.COLOR_RGB2BGR # 通道顺序由 RGB 转为 BGR
)
cv2.imshow('Pillow Image To OpenCV Image', opencv_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

2.4 OpenCV 打开图片用 Tkinter 显示(OpenCV 转 Pillow)

Tkinter 是 Python 的官方 GUI 库,Pillow 的图像数据支持直接在 Tkinter 中显示,因此这里把 OpenCV 图像转为 Pillow 再到 Tkinter 中显示。

import cv2
import tkinter as tk
from PIL import Image, ImageTk

image_path = 'demo.png'

class Application(tk.Frame):
    def __init__(self, master):
        super().__init__(master)
        self.master = master

    def interface(self):
        global pillow_image # 注意 Tkinter 显示的图片要使用全局变量

        opencv_image = cv2.imread(image_path)
        pillow_image = ImageTk.PhotoImage(
            Image.fromarray(
                cv2.cvtColor(
                    opencv_image,
                    cv2.COLOR_BGR2RGB
                )
            )
        )

        tk.Label(self.master, image=pillow_image).pack()

if __name__ == '__main__':
    root = tk.Tk()
    root.title('OpenCV 打开图片并在 Tkinter 中显示') # 窗口标题
    app = Application(root)
    app.interface()
    root.mainloop()

file

2.5 Pillow 打开图片并使用 Tkinter 显示

import tkinter as tk
from PIL import Image, ImageTk

image_path = 'demo.png'

class Application(tk.Frame):
    def __init__(self, master):
        super().__init__(master)
        self.master = master

    def interface(self):
        global pillow_image # 注意 Tkinter 显示的图片要使用全局变量

        pillow_image = ImageTk.PhotoImage(
            Image.open(image_path)
        )

        tk.Label(self.master, image=pillow_image).pack()

if __name__ == '__main__':
    root = tk.Tk()
    root.title('Pillow 打开图片并在 Tkinter 中显示')
    app = Application(root)
    app.interface()
    root.mainloop()

2.6 Matplotlib 打开图片用 OpenCV 显示

Matplotlib 和 OpenCV 都是使用 NumPy 数组保存图像数据,两者转换只需要修改通道顺序即可,非常方便。

import matplotlib.pyplot as plt
import cv2

image_path = 'demo.png'

matplotlib_image = plt.imread(image_path)
opencv_image = cv2.cvtColor(
    matplotlib_image,
    cv2.COLOR_RGB2BGR
)

cv2.imshow(
    'Matplotlib To OpenCV',
    opencv_image    
)
cv2.waitKey(0)
cv2.destroyAllWindows()

2.7 OpenCV 打开图片用 Matplotlib 显示

import matplotlib.pyplot as plt
import cv2

image_path = 'demo.png'

opencv_image = cv2.imread(image_path)
matplotlib_image = cv2.cvtColor(
    opencv_image,
    cv2.COLOR_RGB2BGR
)

plt.imshow(matplotlib_image)
plt.axis('off')
plt.show()

2.8 Matplotlib 打开图片用 Tkinter 显示

import matplotlib.pyplot as plt
import tkinter as tk
from PIL import Image, ImageTk

image_path = 'demo.png'

class Application(tk.Frame):
    def __init__(self, master):
        super().__init__(master)
        self.master = master

    def interface(self):
        global pillow_image # 注意 Tkinter 显示的图片要使用全局变量

        matplotlib_image = plt.imread(image_path)
        pillow_image = ImageTk.PhotoImage(
            Image.fromarray(
                (matplotlib_image * 255).astype('uint8') # 把 float32 转为 uint8
            )
        )

        tk.Label(self.master, image=pillow_image).pack()

if __name__ == '__main__':
    root = tk.Tk()
    root.title('Matplotlib 打开图片并在 Tkinter 中显示') # 窗口标题
    app = Application(root)
    app.interface()
    root.mainloop()

3 基于 NumPy 数组的图像数据结构操作

OpenCV 和 Matplotlib 中图像数据都是使用 NumPy,这里试着创建一个 NumPy 数组来操作,更好理解其结构。

这里创建一个 2x2 分辨率的图片,4 个点分别定义为黑色RGB(0,0,0),白色RGB(255,255,255),红色RGB(255,0,0),紫色RGB(255,0,255),先用 Matplotlib 示例,通道顺序就是 RGB

import matplotlib.pyplot as plt
import numpy as np

data = np.array([
    [[0, 0, 0], [255, 255, 25]],
    [[255, 0, 0], [255, 0, 255]]
])

print('形状:', data.shape)
plt.imshow(data)
plt.show()

file
形状是 2x2 分辨率,3 通道(RGB)
file

对这种图像数据结构的切片操作格式如下

image[y1:y2:ys, x1:x2:xs, c1:c2:cs]

逗号分隔的三部分分别是操作 y 轴、x 轴、颜色通道,每部分冒号分隔的 1 和 2 对应起始和结束,s 对应步长,可以省略。

3.1 颜色通道顺序转换

前面 Matplotlib 和 OpenCV 图像数据互相转换是使用的 OpenCV 的 cvtColor 函数,这里可以尝试基于 NumPy 数组操作,将 cs 设为 -1,则会逆向通道顺序。
Matplotlib 显示图像会自动调整比例,但是 OpenCV 会原比例显示,所以这里需要放大图像再显示。

import cv2
import numpy as np

data = np.array([
    [[0, 0, 0], [255, 255, 25]],
    [[255, 0, 0], [255, 0, 255]]
], dtype=np.uint8)

new_data = data[:,:,::-1] # 通道顺序逆向
new_data = cv2.resize(
    new_data,
    (255, 255), # 放大后的分辨率
    interpolation=cv2.INTER_NEAREST # 最近邻插值法,直接复制原图像像素,不计算衔接边缘
)
cv2.imshow('my data', new_data)
cv2.waitKey(0)
cv2.destroyAllWindows()

file

3.2 图像部分截取

import matplotlib.pyplot as plt

image_path = 'demo.png'

img = plt.imread(image_path)
plt.axis('off')

roi = img[14:556, 219:633] # 截取 y 取值 14~556,x 取值到 219~633 的部分
plt.imshow(roi)
plt.show()

file

3.3 颜色通道分离

3.3.1 OpenCV

下面的示例中从图片文件读取,然后将图像数据的三色通道分离,另外创建一个等大小的空数据通道,然后再尝试用空数据填充 G、B 通道和分离出来的 R 通道合并生成一个新的彩色图片,新生成的图片中缺失了绿色和蓝色通道则变为了“黑红”图片。

import cv2
import numpy as np

image_path = 'demo.png'

image = cv2.imread(image_path)
B = image[:, :, 0:1] # 截取 0 通道[0,1),前开后闭,即蓝色
G = image[:, :, 1:2] # 截取 1 通道,绿色
R = image[:, :, 2:3] # 截取 2 通道,红色
ZERO = np.zeros(B.shape, dtype=B.dtype) # 创建一个空数据的通道

R_image = cv2.merge([ZERO, ZERO, R])
cv2.imshow('R image', R_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

file

3.3.2 Matplotlib

import matplotlib.pyplot as plt
import numpy as np

image_path = 'demo.png'

image = plt.imread(image_path)
R = image[:, :, 0] # 直接截取单个通道,或者 0:1 也行
G = image[:, :, 1]
B = image[:, :, 2]
ZERO = np.zeros(B.shape, dtype=R.dtype) # 创建一个空数据的通道

B_image = np.dstack((ZERO, ZERO, B))
plt.imshow(B_image)
plt.axis('off')
plt.show()

file

3.4 深拷贝和浅拷贝

图像数据的深拷贝和浅拷贝,在基于 NumPy 数组的前提下,也就是 NumPy 数组的深拷贝和浅拷贝。直接用等号赋值实际得到的是为原数组起的一个别名,通过原来的数组名和新起的名字操作的都是同一块地址,实际就是浅拷贝。使用 copy 方法拷贝则是深拷贝,深拷贝不是创建一个别名,而是新开辟空间,并复制原来数组的数据到新空间,新旧数组是独立的空间。

import numpy as np

array = np.array([
    [[0, 0, 255]]
])

array1 = array
array2 = array.copy()

print('array 地址/是否只读:', array.__array_interface__['data'])
print('array1 地址/是否只读:', array1.__array_interface__['data'])
print('array2 地址/是否只读:', array2.__array_interface__['data'])

file

3.5 贴图

import matplotlib.pyplot as plt

image_path = 'demo.png'
src = plt.imread(image_path)

copy_image = src.copy() # 深拷贝
copy_image[628:810, 194:548] = [255, 255, 255] # x:194~548。y:628~810 填充为白色RGB(255,255,255)
copy_image[713:1017, 239:478] = src[104:408, 289:528] # 截取原图人脸部分 x:289~528,y:104~408,贴到拷贝图像的 x:239~478,y:713~1017

plt.axis('off')
plt.imshow(copy_image)
plt.show()

file

3.6.1 透明度通道

前面的操作都是前三个基色的通道,没有涉及第 4 个通道透明度,下面这张胡子图片就是具有 4 通道的图片,可以右键另存为用于测试。
file

import matplotlib.pyplot as plt
import numpy as np

image_path = 'demo.png'
beard_path = 'demo1.png' # 胡子图片文件

src = plt.imread(image_path)
beard = plt.imread(beard_path)

# 为原图添加 alpha 通道(透明度)
image_with_alpha = np.dstack([
    src,
    np.ones((src.shape[0], src.shape[1]), dtype=src.dtype)
])

beard_h, beard_w = beard.shape[:2] # 获取胡子图片的尺寸
mask_boolean = beard[:, :, 3] == 1 # alpha 值为 1 的像素点即为完全不透明的值
image_with_alpha[523:523+beard_h, 94:94+beard_w][mask_boolean] = beard[mask_boolean] # 将胡子不透明的部分像素值嵌入图像中


plt.axis('off')
plt.imshow(image_with_alpha)
plt.show()

file

结合透明度信息后,就不会完全照搬把贴的图片拿上去挡住,不透明的部分就显示原图的内容。这里使用 Matplotlib 读取的图片数据类型为 float32(OpenCV 是 uint8,为 0-255 的整数),每个通道的像素点数据为 0-1 的小数,alpha 通道为 1 就是完全呈现 RGB 的值,alpha 为 0 就是完全不呈现 RGB 值,中间就是过渡。
上面写的例子其实很有局限性,用的胡子图片比较特殊,透明度的值是极化的,要么完全透明,要么完全不透明,所以可以采用上面的方法判断不透明的就直接复制替换原图的部分,但是如果透明度是 0-1 之间的不完全透明,也不是完全不透明,就不能用这种方法处理。不完全透明的情况下,贴上去的图不能完全遮挡原图,也就是原图和贴上去的图的像素值信息都要显示出来。

要解决上面提到的问题就得从透明度本身的性质着手,先只考虑一个像素点的情况,假如原图的像素点值为[1, 0, 0, 1],就是完全显示红色的点,然后我要将一个 [0, 0, 1, 0.6] 的点贴上去,这个点本身是纯蓝色,但是透明度为 0.6,即只呈现蓝色的 60%,那么剩下的 40% 就显示背景(即原图),那么最终显示的就应该是$$[1 \times 0.4 + 0 \times 0.6, 0 \times 0.4 + 0 \times 0.6, 0 \times 0.4 + 1 \times 0.6, 1 \times 0.4 + 0.6 \times 0.6]$$,就有下面的代码:

import matplotlib.pyplot as plt
import numpy as np

image_path = 'demo.png'
beard_path = 'demo1.png' # 胡子图片文件

src = plt.imread(image_path)
beard = plt.imread(beard_path)

# 为原图添加 alpha 通道(透明度)
image_with_alpha = np.dstack([
    src,
    np.ones((src.shape[0], src.shape[1]), dtype=src.dtype)
])

beard_h, beard_w = beard.shape[:2] # 获取胡子图片的尺寸

beard_alpha1 = beard[:, :, 3] # 取出胡子图片的透明度
beard_alpha2 = 1 - beard_alpha1 # 计算出原图被贴图位置应该具有的透明度
for c in range(4):
    image_with_alpha[523:523+beard_h, 94:94+beard_w, c] = beard_alpha2 * image_with_alpha[523:523+beard_h, 94:94+beard_w, c] + beard_alpha1 * beard[:, :, c]
plt.axis('off')
plt.imshow(image_with_alpha)
plt.show()

这里我也不能保证我的思路是对的,只是使用胡子图片验证没问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1425501.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

嵌入式学习第十五天

内存管理: 1.malloc void *malloc(size_t size); 功能: 申请堆区空间 参数: size:申请堆区空间的大小 返回值: 返回获得的空间的首地址 失败返回NULL 2.free void free(void *ptr); 功能: 释放堆区空间 注…

复刻桌面小电视【包含代码分析】

宗旨:开源、分享、学习、进步,生命不息,折腾不止。 复刻小电视 感谢各位大佬的开源项目,让我有了学习的机会,如果侵权,请联系我删除。本人能力有限,如果有什么不对的地方,欢迎指正…

Vue3-Composition-API(二)

一、computed函数使用 1.computed 在前面我们讲解过计算属性computed:当我们的某些属性是依赖其他状态时,我们可以使用计算属性来处理 在前面的Options API中,我们是使用computed选项来完成的; 在Composition API中&#xff0c…

《C程序设计》上机实验报告(四)之一维数组

1.运行程序 #include <stdio.h> void main( ) { int a[5],i,j; for(i1;i<5;i) a[i]0; for(i1;i<5;i) for(j1;j<5;j) a[j]a[i]1; printf("%d %d\n",a[0],a[3]); } 要求&#xff1a; &#xff08;1&#xff09;输入并调试上述源程序&#xff0c;…

安装 vant-ui 实现底部导航栏 Tabbar

本例子使用vue3 介绍 vant-ui 地址&#xff1a;介绍 - Vant 4 (vant-ui.github.io) Vant 是一个轻量、可定制的移动端组件库 安装 通过 npm 安装&#xff1a; # Vue 3 项目&#xff0c;安装最新版 Vant npm i vant # Vue 2 项目&#xff0c;安装 Vant 2 npm i vantlatest-v…

数据可视化 pycharts实现中国各省市地图数据可视化

自用版 数据格式如下&#xff1a; 运行效果如下&#xff1a; import pandas as pd from pyecharts.charts import Map, TreeMap, Timeline, Page, WordCloud from pyecharts import options as opts from pyecharts.commons.utils import JsCode from pyecharts.globals im…

api接口1688商品详情接口采集商品详情数据商品价格详情页数据可支持高并发调用演示示例

接入1688商品详情API接口的步骤如下&#xff1a; 注册账号&#xff1a;首先&#xff0c;你需要在1688开放平台注册一个账号。 创建应用&#xff1a;登录后&#xff0c;在控制台中找到“我的应用”&#xff0c;点击“创建应用”。 获取API密钥&#xff1a;创建应用后&#xff…

C语言数据结构之二叉树

少年恃险若平地 独倚长剑凌清秋 &#x1f3a5;烟雨长虹&#xff0c;孤鹜齐飞的个人主页 &#x1f525;个人专栏 &#x1f3a5;前期回顾-栈和队列 期待小伙伴们的支持与关注&#xff01;&#xff01;&#xff01; 目录 树的定义与判定 树的定义 树的判定 树的相关概念 树的运用…

网络工程师必学知识:2、IPv4和IPv6地址划分

网络工程师必学知识&#xff1a;2、IPv4和IPv6地址划分 1.概述&#xff1a;2.IPv4&#xff1a;地址划分&#xff1a;有类划分&#xff0c;无类划分。一、有类划分&#xff1a;分为5类。ABCDE&#xff0c;掩码分别位8、16、24、28、27取值范围&#xff1a;出类别bit不变&#xf…

我是赵士杰,自述我的 Java 之旅:四年编码,千言万语中成长

你好我的朋友&#xff0c;请先容许我作一个简单介绍&#xff1a;我是赵士杰&#xff0c;一名 Java 攻城狮&#xff0c;欢迎关注我的微信公众号【技术人阿杰】。 不知不觉中&#xff0c;我在撰写技术博客领域已经投入了四年的精力&#xff0c;这也让我从一个默默无名之辈成长为了…

种草日记|林曦老师的冬日好物分享

冬天将尽春天就要来了&#xff0c;换季的时候最容易引起皮肤干燥、头发毛躁不舒服的问题&#xff0c;今天就来说说林曦老师推荐的冬日护理爱用好物。大家都要“如婴儿乎”&#xff0c;照顾好自己哦&#xff5e;      1、Aco甘油保湿霜    Aco甘油保湿霜好大一罐&#x…

《Vue3 基础知识》 使用 GoGoCod 升级到Vue3+ElementPlus 适配处理

此篇为 《Vue2ElementUI 自动转 Vue3ElementPlus&#xff08;GoGoCode&#xff09;》 的扩展&#xff01; Vue3 适配 Vue3 不兼容适配 Vue 3 迁移指南 在此&#xff0c;本章只讲述项目或组件库中遇到的问题&#xff1b; Vue3 移除 o n &#xff0c; on&#xff0c; on&#…

【Web前端实操21】商城官网_白色导航

今日份实现白色导航栏部分&#xff0c;也就是第三部分&#xff0c;效果如图中划线所示&#xff1a; 本次实现代码如之前的全局样式不再赘述&#xff0c;如有需要可以去我博客的Web前端实操19或者20自行查看。 本次主要更新mi.css和index.htm。 实现导航栏所需要的CSS样…

【图解面试】深入解析数据类型转换

将值从一种数据类型转换到另一种数据类型通常称为数据类型转换。在面试过程中大多数都是以代码输出题出现&#xff0c;但是要了解到具体的转换规则&#xff0c;彻底搞懂底层原理&#xff0c;才能应对变来变去的值类型~ 转布尔类型 Boolean类型有两个字面值&#xff1a; true …

调整Activation Function参数对神经网络的影响

目录 介绍&#xff1a; 数据集&#xff1a; 模型一&#xff08;tanh&#xff09; &#xff1a; 模型二&#xff08;relu&#xff09;&#xff1a; 模型三&#xff08;sigmoid&#xff09; &#xff1a; 模型四&#xff08;多层tanh&#xff09;&#xff1a; 模型五&am…

使用“快速开始”将数据传输到新的 iPhone 或 iPad

使用“快速开始”将数据传输到新的 iPhone 或 iPad 使用 iPhone 或 iPad 自动设置你的新 iOS 设备。 使用“快速开始”的过程会同时占用两台设备&#xff0c;因此请务必选择在几分钟内都不需要使用当前设备的时候进行设置。 确保你当前的设备已连接到无线局域网&#xff0c;并…

一篇带你彻底搞懂 Python 编程进阶之闭包

前言 在Python编程语言中&#xff0c;闭包是强大而灵活的语法&#xff0c;它为开发者提供了一种优雅而高效的方式来处理函数和代码结构。作为自动化测试和测试开发同学&#xff0c;弄懂它的作用及工作原理很有必要&#xff0c;面试中提及到的概率非常之大。 关于函数名的本质 …

APT攻击是什么?如何进行防护

随着网络技术的飞速发展&#xff0c;APT&#xff08;Advanced Persistent Threat&#xff09;攻击已经成为网络安全领域的一个重大问题。APT攻击是一种高度复杂的网络攻击&#xff0c;其目标是长期潜伏并逐步深入到目标网络中&#xff0c;以窃取敏感信息、破坏关键基础设施或制…

MyBatis 的注解实现方法

MyBatis 的注解实现方法 MyBatis 的注解实现方法引入依赖添加配置创建表创建实体类创建mapper接口InsertDeleteSelectResults和ResultMap通过配置文件解决 UpdateOptions MyBatis 的注解实现方法 引入依赖 在springBoot项目中下载了EditStarters插件的,可以直接在配置文件处右…

幻兽帕鲁怎么选择服务器

想要部署属于自己的幻兽帕鲁&#xff0c;首先需要拥有一台服务器&#xff0c;服务器是幻兽帕鲁运行的基础。游戏所需的服务器取决于游戏的规模、用户数量和功能需求。以下是一些通常需要考虑的服务器要求&#xff1a; 计算性能&#xff1a;包括cpu、内存、硬盘&#xff0c;cpu…