图像处理的核心操作

一. 图像的基础操作

1. 获取并修改像素值
首先我们需要读入一幅图像:

import cv2
import numpy as np
img = cv2.imread('C:/Users/www12/Desktop/xingkong.jpg')

你可以根据像素的行和列的坐标获取他的像素值。对 BGR 图像而言,返回值为 B,G,R 的值。对灰度图像而言,会返回他的灰度值。

import cv2
import numpy as np
img = cv2.imread('C:/Users/www12/Desktop/xingkong.jpg')
px=img[100,100]
print(px)
blue=img[100,100,0]
print(blue)

[ 8 21 23]
8

你可以以类似的方式修改像素值。

import cv2
import numpy as np
img = cv2.imread('C:/Users/www12/Desktop/xingkong.jpg')
img[100,100]=[255,255,255]
print(img[100,100])

[255 255 255]

(警告:Numpy 是经过优化了的进行快速矩阵运算的软件包。所以我们不推荐逐个获取像素值并修改,这样会很慢,能有矩阵运算就不要用循环。)

(注意:上面提到的方法被用来选取矩阵的一个区域,比如说前 5 行的后 3列。对于获取每一个像素值,也许使用 Numpy 的 array.item() 和 array.itemset() 会更好。但是返回值是标量。如果你想获得所有 B,G,R 的值,你需要使用 array.item() 分割他们。)

获取像素值及修改的更好方法。

import cv2
import numpy as np
img = cv2.imread('C:/Users/www12/Desktop/xingkong.jpg')
print(img.item(10, 10, 2))
img.itemset((10,10,2),100)
print(img.item(10, 10, 2))

26
100

2. 获取图像属性
图像的属性包括:行,列,通道,图像数据类型,像素数目等img.shape可以获取图像的形状。他的返回值是一个包含行数,列数,通道数的元组。

import cv2
import numpy as np
img = cv2.imread('C:/Users/www12/Desktop/xingkong.jpg')
print(img.shape)

(282, 436, 3)

(注意:如果图像是灰度图,返回值仅有行数和列数。所以通过检查这个返回值就可以知道加载的是灰度图还是彩色图。)

img.size 可以返回图像的像素数目:

import cv2
import numpy as np
img = cv2.imread('C:/Users/www12/Desktop/xingkong.jpg')
print(img.size)

368856

img.dtype 返回的是图像的数据类型.

import cv2
import numpy as np
img = cv2.imread('C:/Users/www12/Desktop/xingkong.jpg')
print(img.dtype)

uint8

(注意:在除虫(debug)时 img.dtype 非常重要。因为在 OpenCV-Python 代码中经出现数据类型的不一致。)

3. 图像ROI

import cv2
import numpy as np
img = cv2.imread('C:/Users/www12/Desktop/xingkong.jpg')
# 原图
cv2.imshow('image',img)
cv2.waitKey(0) 
cv2.destroyAllWindows()
# 提取自己需要的一部分
moon = img[0:120, 30:]
#变化后的图
img[120:240, 30:] = moon
cv2.imshow('image',img)
cv2.waitKey(0) 
cv2.destroyAllWindows()

原图:
在这里插入图片描述

变换后的图:
在这里插入图片描述

4. 拆分及合并图像通道
有时我们需要对 BGR 三个通道分别进行操作。这是你就需要把 BGR 拆分成单个通道。有时你需要把独立通道的图片合并成一个 BGR 图像。你可以这样做:

import cv2
import numpy as np
img = cv2.imread('C:/Users/www12/Desktop/xingkong.jpg')
b, g, r = cv2.split(img)
cv2.imshow('image',b)
cv2.waitKey(0) 
cv2.destroyAllWindows()
cv2.imshow('image',g)
cv2.waitKey(0) 
cv2.destroyAllWindows()
cv2.imshow('image',r)
cv2.waitKey(0) 
img = cv2.merge([b, g, r])
cv2.imshow('image',img)
cv2.waitKey(0) 
cv2.destroyAllWindows()

b:
在这里插入图片描述

g:
在这里插入图片描述

r:
在这里插入图片描述
合成:
在这里插入图片描述

或者还可以这样分离:

import cv2
import numpy as np
img = cv2.imread('C:/Users/www12/Desktop/xingkong.jpg')
b = img[:, :, 0]
g = img[:, :, 1]
r = img[:, :, 2]

假如你想使所有像素的红色通道值都为 0,你不必先拆分再赋值。你可以直接使用 Numpy 索引,这会更快。

import cv2
import numpy as np
img = cv2.imread('C:/Users/www12/Desktop/xingkong.jpg')
r = img[:, :, 2] = 0

(警告:cv2.split() 是一个比较耗时的操作。只有真正需要时才用它,能用Numpy 索引就尽量用。)

5. 为图像扩边(填充)

如果你想在图像周围创建一个边,就像相框一样,你可以使用 **cv2.copyMakeBorder()**函数。这经常在卷积运算或 0 填充时被用到。这个函数包括如下参数:

• src 输入图像
• top, bottom, left, right 对应边界的像素数目。
• borderType 要添加那种类型的边界,类型如下
– cv2.BORDER_CONSTANT 添加有颜色的常数值边界,还需要下一个参数(value)。
– cv2.BORDER_REFLECT 边界元素的镜像。比如: fedcba|abcdefgh|hgfedcb
– cv2.BORDER_REFLECT_101 or cv2.BORDER_DEFAULT跟上面一样,但稍作改动。例如: gfedcb|abcdefgh|gfedcba
– cv2.BORDER_REPLICATE 重复最后一个元素。例如: aaaaaa|abcdefgh|hhhhhhh
-cv2.BORDER_WRAP 不知道怎么说了, 就像这样: cdefgh|abcdefgh|abcdefg
• value 边界颜色,如果边界的类型是 cv2.BORDER_CONSTANT

import cv2
import numpy as np
from matplotlib import pyplot as plt
BLUE=[255,0,0]
img1=cv2.imread('C:/Users/www12/Desktop/xingkong.jpg')
b = img[:, :, 0]
g = img[:, :, 1]
r = img[:, :, 2]
img1 = cv2.merge([r, g, b])
replicate = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_REPLICATE)
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()

在这里插入图片描述
(注意:由于是使用 matplotlib 绘制,所以交换 R 和 B 的位置,OpenCV 中是按 BGR,matplotlib 中是按 RGB 排列)

二. 图像上的算术运算

1. 图像加法

你可以使用函数 cv2.add() 将两幅图像进行加法运算,当然也可以直接使用 numpy,res=img1+img。两幅图像的大小,类型必须一致,或者第二个图像可以使一个简单的标量值。

(注意:OpenCV 中的加法与 Numpy 的加法是有所不同的。OpenCV 的加法是一种饱和操作,而 Numpy 的加法是一种模操作。)

x = np.uint8([250])
y = np.uint8([10])
print(cv2.add(x, y))
print(x + y)

[[255]]
[4]

这种差别在你对两幅图像进行加法时会更加明显。

2.图像混合

这其实也是加法,但是不同的是两幅图像的权重不同,这就会给人一种混合或者透明的感觉。图像混合的计算公式如下:g (x) = (1 α) f0 (x) + αf1 (x)通过修改 α 的值(0 → 1),可以实现非常酷的混合。
现在我们把两幅图混合在一起。第一幅图的权重是 0.7,第二幅图的权重是 0.3。函数 cv2.addWeighted() 可以按下面的公式对图片进行混合操作。

在这里插入图片描述

import cv2
import numpy as np

img1 = cv2.imread('C:/Users/www12/Desktop/heibai.jpg')
img2 = cv2.imread('C:/Users/www12/Desktop/xingkong.jpg')
# print(img1.shape)
# print(img2.shape)
img1 = cv2.resize(img1, (300, 400), )
img2 = cv2.resize(img2, (300, 400), )

cv2.imshow('heibai',img1)
cv2.waitKey(0)
cv2.destroyAllWindows()

cv2.imshow('xingkong',img2)
cv2.waitKey(0)
cv2.destroyAllWindows()

#分配权重
dst = cv2.addWeighted(img1, 0.5, img2, 0.5, 0)

cv2.imshow('dst',dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

在这里插入图片描述
在这里插入图片描述
合成之后的图像是这样:
在这里插入图片描述
(注意:两张图像大小一定要是一样的!!!)

3. 按位运算

这里包括的按位操作有:AND,OR,NOT,XOR 等。当我们提取图像的一部分,选择非矩形 ROI 时这些操作会很有用。下面的例子就是教给我们如何改变一幅图的特定区域。

想把 OpenCV 的标志放到另一幅图像上。如果我使用加法,颜色会改变,如果使用混合,会得到透明效果,但是我不想要透明。如果他是矩形我可以象上一章那样使用 ROI。但是他不是矩形。但是我们可以通过下面的按位运算实现:

cv2.cvtColor() 是用来做图像类型的转换可以将图像由彩色的RGB转化为灰度,或者HSV等其他颜色通道。
该函数包含两个参数。
第一个为要进行转化的图片
第二个为需要进行转化的类型cv2.COLOR_BGR2GRAY为将图片有RGB转化为GRAY灰度

cv2.threshold() 函数对图像进行二值化
第一个参数为进行二值化的灰度图像
第二个参数为进行二值化的阈值
第三个为255 灰度颜色总个数,一般均为255
第四个为进行二值化的方式,opencv中包含多种方式可以进行选择

cv2.bitwise_not() 灰度图像进行非操作,二值化的图像均是由0,1,都成,取非之后将会对所有的值进行取反。原来的1变为0。原来的0变为1。即图像颜色由黑变为白,由白变为黑。

cv2.bitwise_and() 对图像进行与操作时需要添加mask 。mask 和进行操作的图像的大小相同。进行与操作的结果为mask中像素值为1保留带操作图像的像素值。mask中像素值为0则将操作图像的像素值变为0

cv2.add():这是一个饱和操作,相比较于np的+效果要更好。

import cv2
import numpy as np

#获取两张图片
img1 = cv2.imread('C:/Users/www12/Desktop/heibai.jpg')
img2 = cv2.imread('C:/Users/www12/Desktop/xingkong.jpg')

#将图片img1的所有像素取出赋予变量roi
rows, cols, channels = img2.shape
roi = img1[0:rows, 0:cols]

#对RGB图像进行灰度化
img2gray = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
#对灰度图像二值化
ret, mask = cv2.threshold(img2gray, 175, 255, cv2.THRESH_BINARY)
#对二值图像mask进行取反操作
mask_inv = cv2.bitwise_not(mask)
#取 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)

#将选择的两部分结合
dst = cv2.add(img1_bg,img2_fg)
cv2.imshow('res',dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

#将结合后的图片重新放回img2上
img1[50:rows+50, 100:cols+100] = dst
cv2.imshow('res',img1)
cv2.waitKey(0)
cv2.destroyAllWindows()

在这里插入图片描述
在这里插入图片描述

(灰度化与二值化的解释)

原图:
在这里插入图片描述
灰度化:
在这里插入图片描述
二值化:
在这里插入图片描述
对灰度图像进行非操作:
在这里插入图片描述
下面具体展示一下"掩膜"操作:
原图:
在这里插入图片描述
灰度化:
在这里插入图片描述
二值化:
在这里插入图片描述
取反操作:
在这里插入图片描述
取反之后的图像与原图进行与操作:
在这里插入图片描述
(图片是有点笑恐怖哈!!!)

整个过程代码如下:

import cv2
import numpy as np

#获取两张图片
img = cv2.imread('C:/Users/www12/Desktop/mask.jpg')
cv2.imshow('res',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
#对RGB图像进行灰度化
img1 = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
cv2.imshow('res',img1)
cv2.waitKey(0)
cv2.destroyAllWindows()
#对灰度图像二值化
ret, mask = cv2.threshold(img1, 175, 255, cv2.THRESH_BINARY)
cv2.imshow('res',mask)
cv2.waitKey(0)
cv2.destroyAllWindows()
#对二值图像mask进行取反操作
mask_inv = cv2.bitwise_not(mask)
cv2.imshow('res',mask_inv)
cv2.waitKey(0)
cv2.destroyAllWindows()
#与操作
img = cv2.bitwise_and(img,img,mask = mask_inv)
cv2.imshow('res',img)
cv2.waitKey(0)
cv2.destroyAllWindows()

小练习:创建一个幻灯片用来演示一幅图如何平滑的转换成另一幅图

import cv2
import numpy as np

# 定义回调函数
def nothing(x):
    pass

img1 = cv2.imread('C:/Users/www12/Desktop/heibai.jpg')
img2 = cv2.imread('C:/Users/www12/Desktop/xingkong.jpg')
img1 = cv2.resize(img1, (300, 400), )
img2 = cv2.resize(img2, (300, 400), )

# 创建一副黑色图像
img=np.zeros(img1.shape,np.uint8)
# 创建窗口
cv2.namedWindow('image')

switch='Bar'
cv2.createTrackbar(switch,'image',0,10,nothing)

while(1):
    k=cv2.waitKey(1)&0xFF
    if k==27:
        break
    s=cv2.getTrackbarPos(switch,'image')
    dst = cv2.addWeighted(img1, s/10, img2, (10-s)/10, 0)
    cv2.imshow('image',dst)
cv2.destroyAllWindows()

大致效果如图所示:
在这里插入图片描述

三. 程序性能检测及优化

在图像处理中你每秒钟都要做大量的运算,所以你的程序不仅要能给出正确的结果,同时还必须要快,这样才可以提高程序的性能。

除了 OpenCV,Python 也提供了一个叫 time 的的模块,你可以用它来测量程序的运行时间。另外一个叫做 profile 的模块会帮你得到一份关于你的程序的详细报告,其中包含了代码中每个函数运行需要的时间,以及每个函数被调用的次数。如果你正在使用 IPython 的话,所有这些特点都被以一种用户友好的方式整合在一起了。

1. 使用Opencv检测程序效率

cv2.getTickCount 函数返回从参考点到这个函数被执行的时钟数。所以当你在一个函数执行前后都调用它的话,你就会得到这个函数的执行时间(时钟数)。

cv2.getTickFrequency 返回时钟频率,或者说每秒钟的时钟数。所以你可以按照下面的方式得到一个函数运行了多少秒。

import cv2
import numpy as np

e1 = cv2.getTickCount()

img = cv2.imread('C:/Users/www12/Desktop/xingkong.jpg')
cv2.imshow('image',img)
cv2.waitKey(0)
cv2.destroyAllWindows()

e2 = cv2.getTickCount()

time = (e2 - e1) / cv2.getTickFrequency()

print(e1, e2, time)

14739180349491 14739200196211 1.984672

(注 意: 你 也 可 以 中 time 模 块 实 现 上 面 的 功 能。 但 是 要 用 的 函 数 是time.time() 而不是 cv2.getTickCount。比较一下这两个结果的差别吧。)

2 OpenCV 中的默认优化

OpenCV 中的很多函数都被优化过(使用 SSE2,AVX 等)。也包含一些没有被优化的代码。如果我们的系统支持优化的话要尽量利用只一点。在编译时优化是被默认开启的。因此 OpenCV 运行的就是优化后的代码,如果你把优化关闭的话就只能执行低效的代码了。你可以使用函数 **cv2.useOptimized()**来查看优化是否被开启了,使用函数 cv2.setUseOptimized() 来开启优化。

在这里插入图片描述
自行对比一下 差距还是挺大的!

优化后中值滤波的速度是原来的两倍。如果你查看源代码的话,你会发现中值滤波是被 SIMD 优化的。所以你可以在代码的开始处开启优化(你要记住优化是默认开启的)。

3 在 IPython 中检测程序效率

有时你需要比较两个相似操作的效率,这时你可以使用 IPython 为你提供的魔法命令**%time**。他会让代码运行好几次从而得到一个准确的(运行)时间。它也可以被用来测试单行代码的。

例如,想知道哪个运行得最快呢?
x = 5;
y = x ∗ ∗2 x = 5;
y = x ∗ x
x = np.uint([5]); y = x ∗ x
y = np.squre(x)

在这里插入图片描述
显而易见,y = x * x运行得最快,比 Nump 快了 20 倍。如果考虑到数组构建的话,能达到 100 倍的差。

(注意:Python 的标量计算比 Nump 的标量计算要快。对于仅包含一两个元素的操作 Python 标量比 Numpy 的数组要快。但是当数组稍微大一点时Numpy 就会胜出了。)

接下来我们来比较一下 cv2.countNonZero()np.count_nonzero()

(cv2.countNonZero()需要使用灰度图作为参数)
在这里插入图片描述

结果:OpenCV 的函数是比Numpy 函数运行速度快很多。

(注意:一般情况下 OpenCV 的函数要比 Numpy 函数快。所以对于相同的操作最好使用 OpenCV 的函数。当然也有例外,尤其是当使用 Numpy 对视图(而非复制)进行操作时。)

4 更多 IPython 的魔法命令

还有几个魔法命令可以用来检测程序的效率,profiling,line profiling,内存使用等。他们都有完善的文档, 有兴趣的可以自己去查询。

5 效率优化技术

有些技术和编程方法可以让我们最大的发挥 Python 和 Numpy 的威力。但是记住最重要的一点是:首先用简单的方式实现你的算法(结果正确最重要),当结果正确后,再使用上面的提到的方法找到程序的瓶颈来优化它。

1. 尽量避免使用循环,尤其双层三层循环,它们天生就是非常慢的。
2. 算法中尽量使用向量操作,因为 Numpy 和 OpenCV 都对向量操作进行了优化。
3. 利用高速缓存一致性。
4. 没有必要的话就不要复制数组。使用视图来代替复制。数组复制是非常浪费资源的。

就算进行了上述优化,如果你的程序还是很慢,或者说大的训话不可避免的话,你你应该尝试使用其他的包,来加速你的程序。

这里提供一些优化相关的超链接:
Python Optimization Techniques
Scipy Lecture Notes - Advanced Numpy
Timing and Profiling in IPython

四. OpenCV 中的数学工具

OpenCV库中还提供了一些专用功能,可以更好的处理计算机视觉中普遍出现的数学和其他问题。

在库的环境中它们被称为工具函数。

工具函数包含数学操作、测试、错误生成、内存与线程处理、优化及其他的工具。

这里就不一一列举了,后续实例中用到了再进行解释!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值