OpenCV核心操作
图像的基本操作
获取并修改像素值
可以根据像素的行与列的坐标来获取它的像素值。对BGR图像而言,返回值为B,G,R 的值。对灰度图像而言,会返回它的灰度值。
import cv2
import numpy as np
img = cv2.imread('miss.jpg')
px = img[100, 100] # 某一像素点的颜色BGR表示
print(px)
blue = img[100, 100, 0] # 某一像素点的Blue颜色分量
print(blue)
当然了,也可以用这种方法更改某一点的像素值:
<!–hexoPostRenderEscape:
import cv2
import numpy as np
img = cv2.imread('miss.jpg')
img[100,100]=[255,255,255]
print(img[100,100])
:hexoPostRenderEscape–>
上面用于更改某一点的像素值的方法被用来选取矩阵的一个区域,比如说前5行的后3
列。对于获取每一个像素值,可以使用Numpy 的array.item() 和array.
itemset() 来进行遍历操作。但这两个函数的返回值都是标量。
import cv2
import numpy as np
img = cv2.imread('miss.jpg')
print(img.item(10,10,2)) # 输出(10,10,2)处像素点的值
img.itemset((10,10,2),100) # 将(10,10,2)处的值设为100
print(img.item(10,10,2))
获取的图像的属性
图像的属性包括:行,列,通道,图像数据类型,像素数目等。
img.shape 可以获取图像的形状。他的返回值是一个包含行数,列数,颜色通道数的元组。
<!–hexoPostRenderEscape:
import cv2
import numpy as np
img = cv2.imread('miss.jpg')
print(img.shape)
:hexoPostRenderEscape–>如果图像是灰度图,img.shape的返回值就只有行数和列数。所以通过检查这个返回值就可以知道加载的是灰度图还是彩色图。
img.size 可以返回图像的像素数目。
import cv2
import numpy as np
img = cv2.imread('miss.jpg')
print(img.size)
img.dtype 返回的是图像的数据类型。
<!–hexoPostRenderEscape:
import cv2
import numpy as np
img = cv2.imread('miss.jpg')
print(img.dtype)
:hexoPostRenderEscape–>
图像ROI
有时我们需要对一幅图像的特定区域进行操作。例如我们要检测一副图像中眼睛的位置,我们首先应该在图像中找到脸,再在脸的区域中找眼睛,而不是直接在一幅图像中搜索眼睛。灵活的使用ROI操作会提高程序的准确性和性能。ROI 是使用Numpy 索引来获得的。
图像不存在或内容为空时不能用[ ]取出其中的像素点,会报'NoneType' object is not subscriptable这样的错误。
import cv2
import numpy as np
img = cv2.imread('ball.png')
ball=img[280:340,330:390] # 框定一个矩形取出选中的像素区域
img[273:333,100:160]=ball # 将拷贝得到的子图复制到图像中的另一块区域
拆分和合并图像通道
有时我们需要对BGR 三个通道分别进行操作,这时就需要把BGR拆分成单个通道进行处理,可以使用cv2.split()。
也可以使用cv2.merge()把独立通道的图片合并成一个BGR 图像。
import cv2
import numpy as np
img = cv2.imread('miss.jpg')
b,g,r = cv2.split(img) # 将BGR图像拆分成三个通道
img=cv2.merge(b,g) # 合并两个通道
b=img[:,:,0]
cv2.split() 是一个比较耗时的操作,只有真正需要时才用它,平时尽量使用numpy索引取像素点。
事实上,如果我们想让图像所有像素点的红色通道值都为0的话,不用拆分通道后再对其进行操作,可以直接使用numpy索引实现。
<!–hexoPostRenderEscape:
import cv2
import numpy as np
img = cv2.imread('miss.jpg')
img[:,:,2]=0
cv2.imwrite('paint.jpg', img)
:hexoPostRenderEscape–>
为图像括边
如果你想在图像周围创建一个边,就像相框一样,你可以使用cv2.copyMakeBorder()函数。这经常在卷积运算或0 填充时被用到。
import cv2
import numpy as np
from matplotlib import pyplot as plt
BLUE=[255,0,0]
img1=cv2.imread('ball.png')
# 设置不同的括边方式
replicate = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_REPLICATE) # 复制源图像img1的像素值并为其进行括边
reflect = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_REFLECT)
reflect101 = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_REFLECT_101)
wrap = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_WRAP)
constant= cv2.copyMakeBorder(img1,10,10,10,10,cv2.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()
图像上的算术运算
图像加法
可以使用函数cv2.add()对两幅图像进行加法运算,当然也可以直接使用numpy,即res=img1+img。两幅图像的大小,类型必须一致,或者第二个图像是一个简单的标量值。
OpenCV 中的加法与Numpy 的加法是有所不同的。OpenCV 的加法是一种饱和操作,而Numpy 的加法是一种模操作。
import numpy as np
import cv2
x = np.uint8([250])
y = np.uint8([10])
print(cv2.add(x, y)) # OpenCV图像加法,250+10 = 260 => 255
print(x+y) # numpy取模加法,250+10 = 260 % 256 = 4
图像混合
图像混合其实也是一种图像加法,但是不同的是两幅图像的权重不同,这就会给人一种混合或者透明的感觉。图像混合的计算公式如下:
g(x) = (1-\alpha)f_0(x) + \alpha f_1(x)通过改变
$\alpha$的值可以实现图像的平滑效果。
下面我们把两幅图混合在一起。第一幅图的权重是0.7,第二幅图的权重是0.3。函数cv2.addWeighted()可以按下面的公式对图片进行混合操作:
dst = \alpha.img1 + \beta.img2 + \gamma
import cv2
import numpy as np
img1=cv2.imread('ml.png')
img2=cv2.imread('opencv-logo.png')
dst=cv2.addWeighted(img1,0.7,img2,0.3, 0) # 第一幅图的权重是0.7,第二幅图的权重是0.3
cv2.namedWindow('dst', cv2.WINDOW_NORMAL)
cv2.imshow('dst',dst)
cv2.waitKey(0)
cv2.destroyAllWindow()
按位操作
这里包括的按位操作有:AND,OR,NOT,XOR 等。当我们提取图像的一部分,选择非矩形ROI 时这些操作会很有用
现在我想把OpenCV 的标志放到另一幅图像上。如果使用加法,颜色会改变,如果使用混合,会得到透明效果,但是我不想要透明。如果它是矩形那么我可以使用ROI。但事实上它不是矩形。
import cv2
import numpy as np
# 加载图像
img1 = cv2.imread('ball.png')
img2 = cv2.imread('opencv-logo.png')
# 想把logo放在img1的左上角,所以这里先创建一个ROI区域
rows,cols,channels = img2.shape # 根据img2的Logo尺寸在img1上开一个ROI区域
roi = img1[0:rows, 0:cols ]
img2gray = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY) # 先将彩色图转换为灰度图(图像阈值化处理的前一步骤)
# 图像阈值化处理
# 创建一个图像的掩膜和它翻转后的图像的掩膜(制作掩膜的目的是保护被掩膜覆盖的区域,之后的操作将只影响掩膜之外的区域)
# 使用cv2.threshold()将img2gray中大于175像素的值设为0(黑色),小于175像素的设为255(白色),cv2.THRESH_BINARY表示采用的是二进制阈值化模式
ret, mask = cv2.threshold(img2gray, 175, 255, cv2.THRESH_BINARY)
mask_inv = cv2.bitwise_not(mask) # 对掩膜进行'按位非'操作,黑色=>白色
# 剔除logo图片中ROI区域之外的像素,取roi 中与mask 中不为零的值对应的像素的值,其他值为0
# 注意这里必须有mask=mask 或者mask=mask_inv, 这里的mask= 参数不能缺省
img1_bg = cv2.bitwise_and(roi,roi,mask = mask)
# 取roi 中与mask_inv 中不为零的值对应的像素的值,其他值为0。
img2_fg = cv2.bitwise_and(img2,img2,mask = mask_inv) # 从logo图片img2中取出掩膜mask_inv覆盖的区域作为前景
dst = cv2.add(img1_bg,img2_fg) # 将img1_bg背景和img2_fg前景进行图像拼接操作,生成最终将要嵌入到源图像中的图像dst
img1[0:rows, 0:cols] = dst # 改变源图像中设置了ROI的区域
cv2.namedWindow('res', cv2.WINDOW_NORMAL)
cv2.namedWindow('mask', cv2.WINDOW_NORMAL)
cv2.namedWindow('mask_inv', cv2.WINDOW_NORMAL)
cv2.namedWindow('img1_bg', cv2.WINDOW_NORMAL)
cv2.namedWindow('img2_fg', cv2.WINDOW_NORMAL)
cv2.imshow('res',img1)
cv2.imshow('mask',mask)
cv2.imshow('mask_inv',mask_inv)
cv2.imshow('img1_bg',img1_bg)
cv2.imshow('img2_fg',img2_fg)
cv2.waitKey(0)
cv2.destroyAllWindows()
程序性能检测及优化
在图像处理中每秒钟都要做大量的运算,所以给出的程序不仅要能得到正确的处理结果,同时还必须要快。
使用OpenCV检测程序效率
import cv2
import numpy as np
e1 = cv2.getTickCount()
# func() 函数执行代码段
e2 = cv2.getTickCount() # 返回从参考点到这个函数被执行的时钟数
# (e2 - e1)得到两次调用getTickFrequency()之间的时间间隔
time = (e2 - e1)/ cv2.getTickFrequency() # cv2.getTickFrequency() 是时钟频率,即每秒钟的时钟数
看一个例子:
<!–hexoPostRenderEscape:
import cv2
import numpy as np
img1 = cv2.imread('miss.jpg')
e1 = cv2.getTickCount()
for i in range(10):
if i!=0:
img1 = cv2.medianBlur(img1,1)
e2 = cv2.getTickCount()
t = (e2 - e1)/cv2.getTickFrequency()
print(t)
:hexoPostRenderEscape–>
OpenCV 中的默认优化
事实上,OpenCV 中的很多函数都被优化过(SSE2,AVX 等),当然也包含一些没有被优化的代码。如果我们的系统支持优化的话那自然要充分利用这一点。在编译时优化是默认开启的,因此OpenCV运行的就是优化后的代码。你可以使用函数cv2.useOptimized()来查看优化是否被开启了,如果没有开启,使用函数cv2.setUseOptimized()来开启优化。
import cv2
import numpy as np
img = cv2.imread('miss.jpg', 0)
cv2.useOptimized() # 查看是否开启了优化(默认开启)
# 观察同一段程序运行的时间,直观感受优化后的效率提升
%timeit res = cv2.medianBlur(img,49) # 图像中值滤波
cv2.setUseOptimized(False) # 关闭优化
cv2.useOptimized()
%timeit res = cv2.medianBlur(img,49) # 很明显这段语句相比上面开启了优化后的语句执行时间变长了
再来比较一下cv2.countNonZero()和np.count_nonzero()这两个函数的运行效率,可见,opencv中的函数较numpy做了更多的优化。
import cv2
import numpy as np
img = cv2.imread('miss.jpg',0)
%timeit z = cv2.countNonZero(img)
# 4.7 µs ± 5.65 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit z = np.count_nonzero(img)
# 640 ns ± 2.29 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!