Python从0到100(六十四):Python OpenCV-图像运算进阶实战
前言:
零基础学Python:Python从0到100最新最全教程。 想做这件事情很久了,这次我更新了自己所写过的所有博客,汇集成了Python从0到100,共一百节课,帮助大家一个月时间里从零基础到学习Python基础语法、Python爬虫、Web开发、 计算机视觉、机器学习、神经网络以及人工智能相关知识,成为学习学习和学业的先行者!
欢迎大家订阅专栏:零基础学Python:Python从0到100最新最全教程!
一、图像的基本操作
前言:
要使用OpenCV编写更好的优化代码,需要Numpy的丰富知识。
补充:图片路径问题
(1.相对路径
设置在当前文件夹下:
import cv2
image = cv2.imread("image.jpg")
注意:
读取的图片一定要保存在当前文件夹下
(2.绝对路径
图片可以不保存在当前文件夹下:
import cv2
image = cv2.imread("D:/opencv_python/image.jpg")
image1 = cv2.imread("D:\\opencv_python\\image.jpg")
注意:
上述两种形式都可以,但是路径内不要出现中文,否则会出现错误!
(3.调用os模块
如果工作路径中有中文,建议使用该方法:
import cv2
import os #导入os模块
os.chdir('D:/计算机视觉/OpenCV图像处理/') #此处路径中可以有中文字符
image = cv2.imread("image.jpg")
os.chdir()
改变当前目录到指定目录中
1.访问和修改像素值
加载彩色图像:
import numpy as np
import cv2 as cv
img = cv.imread("yanhuo.jpg") # 此处必须为中文
imread(“yanhuo.jpg”) 图片名字必须为中文~
你可以通过行和列坐标来访问像素值。对于 BGR 图像,它返回一个由蓝色、绿色和红色值组成的数组。对于灰度图像,只返回相应的灰度。
px = img[100,100]
print( px )
# 仅访问蓝色像素
blue=img[100,100,2]
print(blue)
你可以用相同的方式修改像素值。
# 还可以使用相同方法修改像素值
img[100,100]=[255,2555,255]
print(img[100,100])
警告:
Numpy是用于快速数组计算的优化库。因此,简单地访问每个像素值并对其进行修改将非常缓慢,因此不建议使用。
- 注意:
上面的方法通常用于选择数组的区域,例如前5行和后3列。对于单个像素访问,Numpy数组方法array.item()和array.itemset())被认为更好,但是它们始终返回标量。如果要访问所有B,G,R值,则需要分别调用所有的array.item()。
更好的像素访问和编辑方法:
print(img.item(100,100,2))
# 修改RED值
img.itemset((100,100,2),100)
print(img.item(100,100,2))
2.访问图像属性
图像属性包括行数,列数和通道数,图像数据类型,像素数等。
图像的形状可通过img.shape访问。它返回行,列和通道数的元组(如果图像是彩色的):
print( img.shape )
- 注意 如果图像是灰度的,则返回的元组仅包含行数和列数,因此这是检查加载的图像是灰度还是彩色的好方法。
像素总数可通过访问img.size
print( img.size )
图像数据类型通过img.dtype获得
print( img.dtype )
- 注意 img.dtype在调试时非常重要,因为OpenCV-Python代码中的大量错误是由无效的数据类型引起的。
3.图像感兴趣区域ROI
有时候,你不得不处理一些特定区域的图像。对于图像中的眼睛检测,首先对整个图像进行人脸检测。在获取人脸图像时,我们只选择人脸区域,搜索其中的眼睛,而不是搜索整个图像。它提高了准确性(因为眼睛总是在面部上:D )和性能(因为我们搜索的区域很小)。
使用Numpy索引再次获得ROI。在这里,我要选择部分图像并将其复制到图像中的另一个区域:
a=img[100:500,130:390]
img[273:673,100:360]=a
cv.imshow('tuxiang',img)
cv.waitKey(0)
检查以下结果:
4.拆分和合并图像通道
有时你需要分别处理图像的B,G,R通道。在这种情况下,你需要将BGR图像拆分为单个通道。在其他情况下,你可能需要将这些单独的频道加入BGR图片。你可以通过以下方式简单地做到这一点:
b,g,r=cv.split(img)
img=cv.merge((b,g,r))
b = img [:, :, 0]
# 假设你要将所有红色像素都设置为零,则无需先拆分通道。numpy索引更快:
# img [:, :, 2] = 0
警告:
cv.split()是一项耗时的操作(就时间而言)。因此,仅在必要时才这样做。否则请进行Numpy索引。
5.为图像设置边框(填充)
如果要在图像周围创建边框(如相框),则可以使用cv.copyMakeBorder()。但是它在卷积运算,零填充等方面有更多应用。此函数采用以下参数:
1. src - 输入图像
2. top,bottom,left,right 边界宽度(以相应方向上的像素数为单位)
3. borderType - 定义要添加哪种边框的标志。它可以是以下类型:
- cv.BORDER_CONSTANT - 添加恒定的彩色边框。该值应作为下一个参数给出。
- cv.BORDER_REFLECT -边框将是边框元素的镜像,如下所示: fedcba | abcdefgh | hgfedcb
- cv.BORDER_REFLECT_101或 cv.BORDER_DEFAULT与上述相同,但略有变化,例如: gfedcb | abcdefgh | gfedcba
- cv.BORDER_REPLICATE最后一个元素被复制,像这样: aaaaaa | abcdefgh | hhhhhhh
- cv.BORDER_WRAP难以解释,它看起来像这样: cdefgh | abcdefgh | abcdefg
- value -边框的颜色,如果边框类型为cv.BORDER_CONSTANT
# -*-coding:utf-8 -*-
# @Author:到点了,心疼徐哥哥
# 奥利给干!!!
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
BLUE = [255,255,0]
img1 = cv.imread('yanhuo.jpg')
replicate = cv.copyMakeBorder(img1,100,100,100,100,cv.BORDER_REPLICATE)
reflect = cv.copyMakeBorder(img1,100,100,100,100,cv.BORDER_REFLECT)
reflect101 = cv.copyMakeBorder(img1,100,100,100,100,cv.BORDER_REFLECT_101)
wrap = cv.copyMakeBorder(img1,100,100,100,100,cv.BORDER_WRAP)
constant= cv.copyMakeBorder(img1,100,100,100,100,cv.BORDER_CONSTANT,value=BLUE)
plt.subplot(231),plt.imshow(img1,'gray'),plt.title('ORIGINAL')
plt.subplot(232),plt.imshow(replicate,'gray'),plt.title('REPLICATE')
plt.subplot(233),plt.imshow(reflect,'gray'),plt.title('REFLECT')
plt.subplot(234),plt.imshow(reflect101,'gray'),plt.title('REFLECT_101')
plt.subplot(235),plt.imshow(wrap,'gray'),plt.title('WRAP')
plt.subplot(236),plt.imshow(constant,'gray'),plt.title('CONSTANT')
plt.show()
二、图像上的算术运算
1.图像加法
您可以通过OpenCV函数cv.add()
或仅通过numpy操作res = img1 + img2
添加两个图像。两个图像应具有相同的深度和类型,或者第二个图像可以只是一个标量值。
注意 OpenCV加法和Numpy加法之间有区别。OpenCV加法是饱和运算,而Numpy加法是模运算。
import numpy as np
import cv2 as cv
x=np.uint8([250])
y=np.uint8([10])
print(cv.add(x,y))
print(x+y)
2.图像融合
2.1图片一致
当添加两个图像时,它将更加可见。OpenCV功能将提供更好的结果。因此,始终最好坚持使用OpenCV功能。
在这里,我拍摄了两个图像,将它们融合在一起。第一幅图像的权重为0.7,第二幅图像的权重为0.3。cv.addWeighted()在图像上应用以下公式。
img1 = cv.imread('ml.png')
img2 = cv.imread('opencv-logo.png')
dst = cv.addWeighted(img1,0.7,img2,0.3,0)
cv.imshow('dst',dst)
cv.waitKey(0)
cv.destroyAllWindows()
注意: 这种方法,只适用于小、类型(高度/宽度/通道数)相同的图片之间!
cv2.addWeighted(src1, alpha, src2, beta, gamma[, dst[, dtype]])
:
- src1 – 输入图片1
- alpha – 图像1的加权系数(融合比例)
- src2 –输入图像2,必须与图片1的大小、类型(高度/宽度/通道数)相同。
- beta –图像2的加权系数(融合比例),beta = 1.0 - alpha
- dst – 两个图像加权和后的图像,即输出的图像
- gamma –加权和后的图像的偏移量
- dtype –输出数组的可选深度;当两个输入数组具有相同的深度时,dtype可以设置为-1(默认值),这将相当于src1.depth()
2.2图片不一致
但是如果这两张图片大小不相同,怎么解决?有两种方法可以解决这个问题:
- 重置其中一张图片的大小类型,使其与另一张图片大小类型相同;
- 在较大的图片中创建感兴趣区域roi,roi的大小类型应与另一张图片的相同。
注意: 方法1改变图片大小时,图片的分辨率也会发生变化,因此图片的内容会产生形变;方法2没有改变图片的大小,故不会有这种问题产生。
主要函数:
dst = cv2.resize(src, dsize[, dst[, fx[, fy[, interpolation]]]])
参数意义:
- scr:原图像
- dsize:输出的图像大小
- dst:输出的图像。当参数dsize不为0时,dst的大小为size;否则,它的大4. 小需要根据src的大小,fx和fy决定。
- fx:沿水平轴的比例因子
- fy:沿垂直轴的比例因子
参数dsize和参数(fx, fy)不能够同时为0
interpolation:插值方法,共5种:
- INTER_LINEAR - 双线性插值(默认)(放大图像推荐使用)
- INTER_NEAREST - 最近邻插值
- INTER_AREA - 基于像素局部的重采样插值(缩小图像推荐使用)。该方法对于图像抽取(image decimation)来说可能更好,但如果是放大图像,和最近邻插值效果类似。
- INTER_CUBIC - 基于4x4像素邻域的3次插值(放大图像推荐使用)
- INTER_LANCZOS4 - 基于8x8像素邻域的Lanczos(兰索斯)插值
方法1:
重置其中一张图片的大小类型,使其与另一张图片大小类型相同
import cv2
import numpy as np
import matplotlib.pyplot as plt
bear = cv2.imread('jokerxue.jpg')
sky = cv2.imread('yanhuo.jpg')
rows, cols = sky.shape[:2] #获取sky的高度、宽度
print(sky.shape[:2]) #(800, 1200)
print(bear.shape[:2]) #(224, 224)
bear_dst = cv2.resize(bear,(cols,rows),interpolation=cv2.INTER_NEAREST) #放大图像
add_img = cv2.addWeighted(bear_dst,0.7,sky,0.3,0) #图像融合
cv2.imshow('add_img',add_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
显示多张图片:
用到matplotlib库中的pyplot模块:matplotlib.pyplot.subplot(nrows, ncols, index):
此函数可以用于在一个窗口显示多幅图像。
参数:
- nrows和ncols表示一张图被分为nrows*ncols个区域
- index表示子图所处的位置,起始位置索引为1,即1<=index<=nrows*ncols。
提示:
此函数必须在imshow()
函数之前,要不然无效。
matplotlib.pyplot.axis(‘off’) # 不显示坐标轴
import cv2
import numpy as np
import matplotlib.pyplot as plt
bear = cv2.imread('jokerxue.jpg')
sky = cv2.imread('yanhuo.jpg')
rows, cols = sky.shape[:2] #获取sky的高度、宽度
print(sky.shape[:2]) #(800, 1200)
print(bear.shape[:2]) #(224, 224)
bear_dst = cv2.resize(bear,(cols,rows),interpolation=cv2.INTER_NEAREST) #放大图像
add_img = cv2.addWeighted(bear_dst,0.7,sky,0.3,0) #图像融合
# cv2.imshow('add_img',add_img)
# cv2.waitKey(0)
# cv2.destroyAllWindows()
# 显示图片
titles = ['BearBrown','Sky','add_img']
imgs = [bear,sky,add_img]
for i in range(len(imgs)):
plt.subplot(1,3,i+1)
imgs[i]=cv2.cvtColor(imgs[i],cv2.COLOR_BGR2RGB)
plt.imshow(imgs[i],'gray')
plt.title(titles[i])
plt.axis('off')
plt.show()
效果展示:
方法2:
在较大的图片中创建感兴趣区域roi,roi的大小类型应与另一张图片的相同
注意:必须把小的照片放到大的照片里面,位置关系很重要!
# -*-coding:utf-8 -*-
# @Author:到点了,心疼徐哥哥
# 奥利给干!!!
import cv2
import numpy as np
import matplotlib.pyplot as plt
import os
bear = cv2.imread('yanhuo.jpg')
sky = cv2.imread('jokerxue.jpg')
# print(sky.shape[:2]) #(800, 1200)
# print(bear.shape[:2])
# 根据小图像的大小,在大图像上创建感兴趣区域roi(放置位置任意取)
rows, cols = bear.shape[:2] #获取bear的高度、宽度
print(rows,cols)
roi = sky[0:rows, 0:cols]
dst = cv2.addWeighted(bear,0.3,roi,0.7,0) #图像融合
add_img = sky.copy() #对原图像进行拷贝
add_img[0:rows, 0:cols] = dst # 将融合后的区域放进原图
# 显示图片
titles = ['BearBrown','Sky','add_img']
imgs = [bear,sky,add_img]
for i in range(len(imgs)):
plt.subplot(1,3,i+1)
imgs[i]=cv2.cvtColor(imgs[i],cv2.COLOR_BGR2RGB)
plt.imshow(imgs[i],'gray')
plt.title(titles[i])
plt.axis('off')
plt.show()
效果展示:
3.按位运算
包括按位与(AND)、按位或(OR)、按位非(NOT)、按位异或(XOR)等运算。
按位运算的用途:比如要得到一个加logo的图像。 如果将两幅图片直接相加会改变图片的颜色,如果用图像混合,则会改变图片的透明度,这时候就需要用按位操作
,既不改变图像颜色,又不改变图像透明度,类似PS。
这里需要了解一个术语——掩膜(mask) 是用一副二值化图片对另外一幅图片进行局部的遮挡。
3.1主要函数
- cv2.bitwise_and():位与运算,有0则为0, 全为1则为1
- cv2.bitwise_not():或运算,有1则为1, 全为0则为0
- cv2.bitwise_or():非运算,非0为1, 非1为0
- cv2.bitwise_xor():异或运算,不同为1, 相同为0
ret, dst = cv2.threshold(src, thresh, maxval,type):
阈值(二值化操作),阈值又叫临界值,是指一个效应能够产生的的最低值或最高。
dst:
输出图像
src:
输入图像,只能输入单通道图像,一般为灰度图
thresh:
阈值
maxval:
当像素值大于阈值(或者小于阈值,根据type来决定),所赋予的值
type:
阈值操作的类型:
THRESH_BINARY: 二值阈值化
THRESH_TOZERO_INV: 反二值阈值化
THRESH_TRUNC: 截断阈值化
THRESH_TOZERO: 低于阈值被置为0
THRESH_TOZERO_INV: 超过阈值被置为0
3.2实例演示:
logo:
照片主体:
# -*-coding:utf-8 -*-
# @Author:到点了,心疼徐哥哥
# 奥利给干!!!
# 加载两张图片
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
import os
img1 = cv.imread('yanhuo.jpg')
img2 = cv.imread('logo.png')
# 我想把logo放在左上角,所以我创建了ROI
rows,cols,channels = img2.shape
roi = img1[0:rows, 0:cols ]
# 现在创建logo的掩码,并同时创建其相反掩码
img2gray = cv.cvtColor(img2,cv.COLOR_BGR2GRAY)
ret, mask = cv.threshold(img2gray, 10, 255, cv.THRESH_BINARY)
mask_inv = cv.bitwise_not(mask)
# 现在将ROI中logo的区域涂黑
img1_bg = cv.bitwise_and(roi,roi,mask = mask_inv)
# 仅从logo图像中提取logo区域
img2_fg = cv.bitwise_and(img2,img2,mask = mask)
# 将logo放入ROI并修改主图像
dst = cv.add(img1_bg,img2_fg)
img1[0:rows, 0:cols ] = dst
cv.imshow('res',img1)
cv.waitKey(0)
cv.destroyAllWindows()
效果展示:
3.3代码详析:
1.rows,cols,channels = img2.shape
把图片2像素的行数,列数以及通道数返回给rows,cols,channels。
2.oi = img1[0:rows, 0:cols ]
然后将图片1从第0行到rows行,第0列到cols列的区域设定为roi(即感兴趣区域)
3.cvtColor函数
此函数的作用是将一个图像从一个颜色空间转换到另一个颜色空间。
3.4过程拆解
过程拆分时,需要我们显示各个时期的图像,需要用到:
cv.namedWindow("image", cv.WINDOW_NORMAL)
cv.imshow("image", img2_fg)
cv.namedWindow()
函数:
用法:
cv2.namedWindow(‘窗口标题’,默认参数)
-
窗口大小可以改变
:cv2.namedWindow(“image”,cv2.WINDOW_NORMAL)
或者cv2.namedWindow(‘image’,cv2.WINDOW_GUI_NORMAL) -
窗口大小不可以改变
:cv2.namedWindow(“image”,cv2.WINDOW_AUTOSIZE) -
窗口大小自适应比例
:cv2.namedWindow(“image”,cv2.WINDOW_FREERATIO) -
窗口大小保持比例
:cv2.namedWindow(“image”,cv2.WINDOW_KEEPRATIO) -
显示色彩变成暗色
:cv2.namedWindow(‘image’,cv2.WINDOW_GUI_EXPANDED)
1.我们可以首先将logo的颜色空间进行转换
转换成灰度图像,然后使用阀值函数,将我们的logo提取出来,然后将其他部分变为黑色,即像素值为0。
img2gray = cv.cvtColor(img2,cv.COLOR_BGR2GRAY) # 颜色空间的转换
ret, mask = cv.threshold(img2gray, 10, 255, cv.THRESH_BINARY) # 掩码 黑色
2.然后我们得到它相反的图像
使用cv.bitwise_not,得到:
mask_inv = cv.bitwise_not(mask)
3.因为mask的性质就是非0的地方保留原样,其余为0
接下来我们利用mask_inv去取得messi那张图的背景部分,现在将ROI中logo的区域涂黑
img1_bg = cv.bitwise_and(roi,roi,mask = mask_inv)
4.仅从logo图像中提取logo区域
那么我们接下来就可以嵌入了,首先我们得原图像还原,那么只需要使用:
img2_fg = cv.bitwise_and(img2,img2,mask=mask)
5.将logo放入ROI并修改主图像
dst = cv.add(img1_bg,img2_fg)
img1[0:rows, 0:cols ] = dst
cv.imshow('res',img1)
cv.waitKey(0)
cv.destroyAllWindows()
得到最终的结果:
c_center)
- 点赞
- 收藏
- 关注作者
评论(0)