在图像处理领域,OpenCV 是一个功能强大的库,为我们提供了丰富的工具来操作和处理图像。今天,我们将深入探讨 OpenCV 中的图像算术运算,通过具体的代码示例来理解这些功能的应用。
关注公众号:资小库 回复opencv获取源码,及项目图片等
一、图像相加(add
函数)
在add
函数中,我们主要进行了图像的相加操作。
def add():
img1 = cv.imread("../img/img1.jpg")
img2 = cv.imread("../img/img2.jpg")
首先,使用cv.imread
函数读取两张图像img1
和img2
。这两张图像是我们进行后续算术运算的基础。
print(img1.shape,img2.shape)
通过shape
属性,我们可以获取图像的尺寸信息,包括高度、宽度和通道数。了解这些信息对于后续的图像处理操作非常重要,比如确保要进行运算的图像尺寸相同。
img2 = cv.rotate(img2, cv.ROTATE_90_CLOCKWISE)
height, width = img1.shape[:2]
img2 = cv.resize(img2, (width, height))
由于原始的两张图像尺寸和方向可能不一致,所以我们对img2
进行了旋转(使用cv.rotate
函数将其顺时针旋转 90 度)和大小调整(使用cv.resize
函数将其尺寸调整为与img1
相同)。
img3 = img1 + img2
这里使用了 Numpy 的加法操作对两张图像进行相加。需要注意的是,Numpy 的这种加法是一种模运算。例如,如果一个像素的 RGB 值分别为 (200, 150, 100) 和 (100, 100, 50),相加后的结果如果超过了 255(如第一个通道 200 + 100 = 300),那么最终的结果会对 256 取模,得到 (44, 250, 150)。
x = np.uint8([10])
y = np.uint8([250])
print(cv.add(x,y))
接下来,使用cv.add
函数进行加法操作。cv.add
是 OpenCV 中的加法函数,它的加法是饱和操作。也就是说,如果相加后的结果超过了 255,那么结果会被截断为 255。例如对于上面的x
和y
,Numpy 相加可能会取模,而cv.add
的结果是 [255]
openImg(cv.add(img1,img2))
使用cv.add
函数对调整后的img1
和img2
进行相加,并通过openImg
函数显示结果
openImg(cv.addWeighted(img1,0.3,img2,0.7,0))
cv.addWeighted
函数用于按照一定的权重比例对两张图像进行加权求和。在这里,img1
的权重是 0.3,img2
的权重是 0.7,最后一个参数 0 是添加的常量。
二、图像添加徽标
def logo():
img = cv.imread("../img/img2.jpg")
logo = cv.imread("../img/img3.jpg")
同样先读取主图像img
和徽标图像logo
。
rows, cols, channels = logo.shape
roi = img [0:rows,0:cols]
获取徽标图像的行数、列数和通道数,然后从主图像中切出与徽标图像大小相同的区域roi
,这个区域将用于放置徽标。
img2gray = cv.cvtColor(logo, cv.COLOR_BGR2GRAY)
ret, mask = cv.threshold(img2gray, 10, 255, cv.THRESH_BINARY)
将徽标图像转换为灰度图像(cv.cvtColor
函数),然后使用阈值处理(cv.threshold
函数)将灰度图像转换为二值图像mask
。在这个二值图像中,像素值大于 10 的变为 255(白色),小于 10 的变为 0(黑色)。
mask_inv = cv.bitwise_not(mask)
对mask
进行取反操作,得到mask_inv
,原来的白色部分变成黑色,黑色部分变成白色。
img1_bg = cv.bitwise_and(roi, roi, mask=mask_inv)
img2_fg = cv.bitwise_and(logo, logo, mask=mask)
使用按位与操作(cv.bitwise_and
函数),img1_bg
将roi
中对应于mask_inv
中黑色部分(值为 0)的像素变为 0(变黑),img2_fg
从徽标图像中提取出对应于mask
中白色部分(值为 255)的像素。
dst = cv.add(img1_bg, img2_fg)
img[0:rows, 0:cols] = dst
最后,将提取出的徽标部分img2_fg
添加到主图像的对应区域roi
中,实现徽标的添加。
三、提取透明图像
def logo_2():
img = cv.imread("../img/img2.jpg")
logo = cv.imread("../img/img3.jpg")
rows, cols, channels = logo.shape
roi = img [0:rows, 0:cols]
与logo
函数开始部分类似,读取图像并切出对应区域。
img2gray = cv.cvtColor(logo, cv.COLOR_BGR2GRAY)
ret, mask = cv.threshold(img2gray, 240, 255, cv.THRESH_BINARY)
同样将徽标图像转换为灰度图像并进行阈值处理,这里阈值设置为 240,目的是更好地提取徽标中的主要部分。
indices = np.where(img1_bg == 0)
img1_bg[indices] = logo[indices]
img[0:rows, 0:cols] = img1_bg
通过np.where
函数找到img1_bg
中值为 0 的像素坐标,然后将徽标图像logo
中对应坐标的像素值赋给img1_bg
,最后将处理后的区域放回主图像中,实现类似透明效果的徽标添加。
四:学习中的问题
roi = img [0:rows,0:cols] 这句使用的是什么语法
这段代码 roi = img[0:rows, 0:cols]
使用的是 Python 中对数组(或者说是类似数组的对象,比如图像矩阵)进行切片(slice)的语法。具体解释如下:
img
是一个数组(通常是图像的像素矩阵,可以是 NumPy 数组或类似的数据结构)。roi
是一个变量,用来存储从img
中切出来的部分,称为感兴趣区域(Region of Interest)。
现在来分解 img[0:rows, 0:cols]
:
img[0:rows, 0:cols]
表示从img
中切出一个子数组。0:rows
表示沿着第一个轴(通常是行)从索引 0 开始到索引rows-1
结束的范围。这里rows
是一个变量或者常数,表示行数的上限。0:cols
表示沿着第二个轴(通常是列)从索引 0 开始到索引cols-1
结束的范围。同样,cols
是表示列数的变量或常数。
因此,img[0:rows, 0:cols]
实际上是从 img
的左上角开始,取出行数为 rows
,列数为 cols
的子数组或子矩阵。
这种切片语法在 Python 中非常常见,不仅仅适用于 NumPy 数组,还适用于 Python 中的列表等可迭代对象。
为什么在opencv中0表示黑色,255表示白色
在OpenCV(Open Source Computer Vision Library)中,像素值的范围通常是从0到255,这是因为像素的灰度值通常用一个8位整数(即一个字节)来表示。这种表示方式有几个历史和技术上的原因:
-
灰度范围的表示:
- 0 表示最暗的灰度,即黑色。
- 255 表示最亮的灰度,即白色。
- 在这个8位灰度范围内,0代表了完全没有光,而255代表了所有可见光的最大强度。
-
数值计算的方便性:
- 使用0到255的范围可以方便地进行数值计算和像素操作,因为这正好是一个八位无符号整数(8-bit unsigned integer)的表示范围。
-
历史原因:
- 这种表示方式源于早期计算机图像处理的标准化,例如在黑白电视时代,8位灰度图像就是一种常见的格式。
- 许多传统的图像处理算法和技术,如阈值处理、直方图均衡化等,都是基于这种0到255的像素值范围设计的。
因此,尽管在不同的图像处理库和应用中可能有不同的约定和表示方式,但在OpenCV中,0表示黑色,255表示白色的约定是一种标准化的选择,使得图像处理和计算更加方便和统一。
openCV bitwise_and函数参数是什么
在 OpenCV 中,bitwise_and
函数用于执行图像的按位与(bitwise AND)操作。它的参数通常是两个图像数组(数组可以是单通道或多通道的)或者一个图像数组和一个标量。具体来说,参数如下:
- src1: 第一个输入图像数组或标量。
- src2: 第二个输入图像数组或标量。
- dst: 输出图像数组,用于存储按位与操作的结果。
如果 src1
和 src2
是图像数组,则它们必须具有相同的尺寸和类型(单通道或多通道)。如果其中一个是标量,那么该标量将与另一个数组的每个像素进行按位与操作。
使用示例(Python 中的 OpenCV):
import cv2
import numpy as np
# 创建两个示例图像数组
img1 = np.array([[1, 0, 1],
[0, 1, 0],
[1, 0, 1]], dtype=np.uint8)
img2 = np.array([[1, 1, 0],
[0, 0, 1],
[1, 1, 0]], dtype=np.uint8)
# 执行按位与操作
result = cv2.bitwise_and(img1, img2)
print("Result of bitwise AND:")
print(result)
在这个例子中,img1
和 img2
是两个输入图像数组,它们分别表示如下:
img1:
[[1 0 1]
[0 1 0]
[1 0 1]]
img2:
[[1 1 0]
[0 0 1]
[1 1 0]]
执行 bitwise_and
后,输出的 result
图像数组将是按位与操作的结果:
Result of bitwise AND:
[[1 0 0]
[0 0 0]
[1 0 0]]
这是因为按位与操作的规则是对应位置上的两个像素都为 1 时,结果对应位置为 1;否则为 0。
源码中bitwise_and_test函数也验证了这一点
opencv中灰度图像的转换原理
在OpenCV中,灰度图像的转换原理涉及到将彩色图像转换为灰度图像的过程。下面是转换的基本原理和步骤:
-
彩色图像表示:
- 彩色图像通常由红(R)、绿(G)、蓝(B)三个颜色通道组成,每个通道使用一个8位整数(0到255)表示像素的强度。
-
灰度图像的生成:
- 灰度图像是一种只有一个通道的图像,每个像素仅使用一个8位整数(0到255)来表示灰度强度,其中0代表黑色,255代表白色。
-
颜色通道混合:
- OpenCV中通常使用以下加权平均方法将彩色图像转换为灰度图像: [ \text{灰度值} = 0.299 \times R + 0.587 \times G + 0.114 \times B ] 这个公式是根据人眼对不同颜色敏感度的心理视觉模型确定的,其中红色对比绿色和蓝色更敏感。
-
像素级别的转换:
- 对于每个像素,应用上述公式,将其彩色值转换为灰度值。这个过程在整个图像上进行,从而生成一张灰度图像,其中每个像素都代表了相应位置上的灰度强度。
-
OpenCV函数实现:
- 在OpenCV中,可以使用
cv2.cvtColor()
函数来实现颜色空间转换。具体转换为灰度图像时,使用的是cv2.COLOR_BGR2GRAY
或cv2.COLOR_RGB2GRAY
参数,具体取决于图像的颜色通道顺序。
- 在OpenCV中,可以使用
import cv2
# 读取彩色图像
color_image = cv2.imread('input_image.jpg')
# 转换为灰度图像
gray_image = cv2.cvtColor(color_image, cv2.COLOR_BGR2GRAY)
# 显示结果
cv2.imshow('Color Image', color_image)
cv2.imshow('Gray Image', gray_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
彩色图像转变为灰度图像的公式
彩色图像到灰度图像的转换公式通常是基于一种加权平均的方式,其中不同颜色通道的值以不同的权重来计算。在多种公式中,最常见的是使用下列线性组合公式:
[ \text{灰度值} = 0.299 \times R + 0.587 \times G + 0.114 \times B ]
这里:
( R ) 是红色通道的像素值(0到255之间)。
( G ) 是绿色通道的像素值(0到255之间)。
( B ) 是蓝色通道的像素值(0到255之间)。
这些权重(0.299, 0.587, 和 0.114)是根据人眼对不同颜色的感知强度而来的,反映了人眼在视觉感知中对于红色、绿色和蓝色的敏感度差异。
五:全部源码
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
from gui.A_GetStartedWithImages import openImg
"""
图像的算术运算
"""
def add():
img1 = cv.imread("../img/img1.jpg")
img2 = cv.imread("../img/img2.jpg")
# 打印图像大小
print(img1.shape,img2.shape)
# 图像2是个横图,图像1为纵图,先旋转一下
img2 = cv.rotate(img2, cv.ROTATE_90_CLOCKWISE)
# 获取宽 高
height, width = img1.shape[:2]
# 将图像1的宽高给图像2
img2 = cv.resize(img2, (width, height))
print(img1.shape,img2.shape)
# Numpy 添加是模运算 必须图像矩阵大小一致
img3 = img1+img2
openImg(img3)
x = np.uint8([10])
y = np.uint8([250])
# >255的数据会被强制变成255
print(cv.add(x,y))
# OpenCV 添加是饱和操作
# 感觉被高曝光了一样,在将两个图象相加时会发现 OpenCV 函数能够提供更好的结果,所以尽可能地选择 OpenCV 函数。
openImg(cv.add(img1,img2))
# 上面图像相加两者应该算是55开的,也可以调整比例混合
'''
src1: 第一个输入图像,通常为CV_8U、CV_16U、CV_32F之一,应与dst具有相同的通道数和深度。
alpha: 第一个输入图像的权重系数。
src2: 第二个输入图像,大小和类型应与src1相同。
beta: 第二个输入图像的权重系数。
gamma: 一个可选的增量,它被添加到每个输出像素上。
dst: 输出图像,与src1和src2有相同的大小和类型。
'''
openImg(cv.addWeighted(img1,0.3,img2,0.7,0))
def logo():
# 读取图像数据
img = cv.imread("../img/img2.jpg")
logo = cv.imread("../img/img3.jpg")
# 获取logo图片的高,宽,通道,对于数组的行数,列数
rows, cols, channels = logo.shape
# 获取一个数组 0-rows,0-cols 切出这么一块区域
roi = img [0:rows,0:cols]
# 将图像进行灰度处理
# 计算公式 [ \text{灰度值} = 0.299 \times R + 0.587 \times G + 0.114 \times B ]
img2gray = cv.cvtColor(logo, cv.COLOR_BGR2GRAY)
# 颜色去生成数组,表示10以上的全部变成255也就是黑色,10一下的全部变成0也就是白色,10一下的基本是透明色,相加区别也不大
ret, mask = cv.threshold(img2gray, 10, 255, cv.THRESH_BINARY)
# 取反0变成255 255变成0 10变成245,本来全是255,现在全变成0
mask_inv = cv.bitwise_not(mask)
# 现在使 ROI 中的徽标区域变黑 按位与操作,参考下面的例子,了解按位与操作,任何
# 如果提供了 mask 参数,那么只有在 mask 中对应位置的像素值为非零时,src1 和 src2 对应位置的像素才会参与按位与操作。如果 mask 中的像素值为零,那么对应位置的输出像素将被置为零。
img1_bg = cv.bitwise_and(roi, roi, mask=mask_inv)
# 经过上面处理img1_bg应该都变成了0
# 仅从徽标图像中获取徽标区域。 mask因为都是255,则都参与按位与操作,但是因为logo的rgb都基本不是1,所有img2_fg 约等于 logo 不排除有个别mask可能等于0
img2_fg = cv.bitwise_and(logo, logo, mask=mask)
# 在 ROI 中放置徽标并修改主图像
# 所以这里白色加任何颜色都等于任何颜色
dst = cv.add(img1_bg, img2_fg)
#将这部分数组替换成那部分数组
# 虽然替换了一大顿,效果应该等于 img[0:rows, 0:cols] = logo这段代码的效果,但是重点学习过程
img[0:rows, 0:cols] = dst
cv.imshow('res', img)
cv.waitKey(0)
cv.destroyAllWindows()
# 写个小demo,提取透明图像
def logo_2():
# 读取图像数据
img = cv.imread("../img/img2.jpg")
logo = cv.imread("../img/img3.jpg")
# 获取logo图片的高,宽,通道,对于数组的行数,列数
rows, cols, channels = logo.shape
# 获取一个数组 0-rows,0-cols 切出这么一块区域
roi = img [0:rows,0:cols]
# 将图像进行灰度处理
# 计算公式 [ \text{灰度值} = 0.299 \times R + 0.587 \times G + 0.114 \times B ]
img2gray = cv.cvtColor(logo, cv.COLOR_BGR2GRAY)
# 颜色去生成数组,表示10以上的全部变成255也就是黑色,10一下的全部变成0也就是白色,10一下的基本是透明色,相加区别也不大
ret, mask = cv.threshold(img2gray, 240, 255, cv.THRESH_BINARY)
# 现在使 ROI 中的徽标区域变黑 按位与操作,参考下面的例子,了解按位与操作,任何
# 如果提供了 mask 参数,那么只有在 mask 中对应位置的像素值为非零时,src1 和 src2 对应位置的像素才会参与按位与操作。如果 mask 中的像素值为零,那么对应位置的输出像素将被置为零。
img1_bg = cv.bitwise_and(roi, roi, mask=mask)
# 寻找黑色的地方
indices = np.where(img1_bg == 0)
# 坐标点替换
img1_bg[indices] = logo[indices]
#将这部分数组替换成那部分数组
# 虽然替换了一大顿,效果应该等于 img[0:rows, 0:cols] = logo这段代码的效果,但是重点学习过程
img[0:rows, 0:cols] = img1_bg
cv.imshow('res', img)
cv.waitKey(0)
cv.destroyAllWindows()
def bitwise_and_test():
# 相同的就是原来的值,不同的就是0
# 创建两个示例图像数组
img1 = np.array([[10, 0, 1],
[0, 1, 0],
[1, 0, 1]], dtype=np.uint8)
print(cv.bitwise_not(img1))
img2 = np.array([[10, 1, 0],
[0, 0, 1],
[1, 1, 0]], dtype=np.uint8)
# 执行按位与操作
result = cv.bitwise_and(img1, img2)
print("Result of bitwise AND:")
print(result)
if __name__ == '__main__':
logo_2()
关注公众号:资小库,问题快速答疑解惑