python_opencv学习

之前碰过,现在想系统学一下.设计opencv-python,Numpy,Matploylib这几个库

开始

将涉及到的库安装后,导入

1
2
import cv2 #导入需要用cv2
import matplotlib.pyplot as plt

安装opencv-python时已经装了numpy了.

涉及方法

1
2
imread()  #传入图像路径
imsave() #保存图像名字以及image

我们读入一个图像并尝试打印

1
2
3
image = imread("image.jpg")
print(image)
print(type(image))

打印image

可以看到结果是多维数组,类型其实是Numpy的数组类型,所以搭配Numpy很好操作.

这里涉及到图像的知识,图像在计算机中的存储.

三信道矩阵

值得注意的:opencv得到的三通道像素点是BGR格式.

1
2
3
#得到image对象后
image[0,0] #0,0位置上像素 直接修改即可
image[0:100,0:200] = (0,0,0) #将一个矩形区域改为黑色

拆分和合并图像信道

1
2
3
4
5
6
7
8
split() #拆分信道
merge() #合并信道
#拆分信道 结果为bgr
#image打印得到一个numpy矩阵
b,g,r = cv2.split(image)
#合并信道
sum = cv2.merge([r,g,b])
cv2.imwrite('reverse.jpg',sum)

主要用于将三信道彩色图像拆分为b,g,r三个单信道,也就是三个灰度图.

访问图像属性

一张图像具有多个属性:行数、列数、通道数、图像数据类型、像素数等.

1
2
3
4
image = imread("")  #参数是一个图像地址
image.shape #图像属性
image.dtype #图像的数值类型
image.size #图像大小,其实就是高x宽

当意识到imread返回的其实是一个矩阵,我们就能利用numpy的一些方法.

image-20220111172001806

可以看到 image.shape 返回的是一组包含图片属性的元组。第一个元素是图片的高,表示图片有多少行像素。第二个元素是图片的宽,表示图片有多少列像素。最后一个元素 3 是图片的通道数,我们使用的是 RGB 彩色图片,所以一共有三个通道.是彩色图像

1
2
print(type(image))
# ndarray

image.dtype 获取像素值的数据类型,可以看到像素值的数据类型是 uint8 表示 8 位无符号整数。因为像素值的范围是 0 到 255,而 uint8 也是在这一范围.

属性说明
ndarray.ndim秩,即轴的数量或维度的数量
ndarray.shape数组的维度,对于矩阵,n 行 m 列
ndarray.size数组元素的总个数,相当于 .shape 中 n*m 的值
ndarray.dtypendarray 对象的元素类型
ndarray.itemsizendarray 对象中每个元素的大小,以字节为单位
ndarray.flagsndarray 对象的内存信息
ndarray.realndarray元素的实部
ndarray.imagndarray 元素的虚部
ndarray.data包含实际数组元素的缓冲区,由于一般通过数组的索引获取元素,所以通常不需要使用这个属性。

画图

画直线

首先创建一个图像,利用numpy数组

1
region = np.zeros((500,500,3),dtype=np.uint8)

默认dtype是浮点数,最好改为np.uint8或int.

返回值是一个全为0的三维矩阵,数据类型是uint8.

然后利用cv2line()

1
line = cv2.line(region,(50,50),(450,450),(0,0,255),5) #region是背景图像,也就是创建的图片,接着的参数是线的起始和终止点,bgr颜色和厚度

最后使用imwrite保存即可

1
cv2.imwrite('line.jpg',line) #line是一个numpy类型

当然也可以定义线的类型,详见官方文档

其他的创建数组方式有

1
2
3
4
5
6
numpy.empty()  #np.empty([3,2], dtype = int) 
numpy.ones() #np.ones([2,2], dtype = int)
numpy.randn() #numpy.random.randn(2,3)
numpy.arange() #从范围内创建数组
numpy.eye() #对角矩阵 参数是行列数
numpy.full() #任意值的初始化

画矩形

与画直线类似,需要定义左上角和右上角.厚度为负数则为填充图形.

1
2
3
region = np.zeros((500,500,3))
rectangle = cv.rectangle(region,(50,50),(150,150),(0,255,0),-1)
cv.imwrite("rectangle.jpg",rectangle)

矩形图像

画圆和椭圆类似,就是用的方法不一样而已

1
2
cv2.circle() #画圆
cv2.ellipse() #椭圆
1
2
3
4
 #画圆
region = np.zeros((500,500,3))
circle = cv.circle(region,(250,250),200,(0,255,0),-1)
cv.imwrite("circle.jpg",circle)

指定圆心与半径即可

1
2
3
region = np.zeros((500,500,3))
ellipse = cv2.ellipse(region, (200, 200), (150, 50), 0, 0, 180, (0, 255, 0), -1)
cv2.imwrite("ellipse.jpg", ellipse)

调用 cv2.ellipse 函数绘制椭圆。第一个参数 region 是要在上面绘制椭圆的图片。第二个参数 (200, 200) 是椭圆中心的坐标。第三个参数 (150, 50) 表示椭圆的轴长,其中 150 表示长轴,50 表示短轴。第四个参数 0 表示椭圆处于一个水平的状态,第四个参数表示沿顺时针方向椭圆的旋转角度 。第五个参数 0 表示沿顺时针方向绘制椭圆的起始角度,第六个参数 180 表示沿顺时针方向绘制椭圆的结束角度 ,所以第五、第六参数表示我们将绘制半个椭圆。第七个参数 (0, 255, 0) 表示绘制椭圆的颜色为绿色。第八个参数 -1 表示用绿色将这半个椭圆填充满

还有多边形和添加文本,不过说实话,感觉用处不大

1
2
3
pts = np.array([[[10, 60]], [[290, 250]], [[300, 350]], [[470, 390]]], np.int32)  #列表是顶点数*1*2
polylines = cv2.polylines(region, [pts], True, (255, 255, 0), 5)
cv2.imwrite("polylines.jpg", polylines)

True表示各个顶点连线闭合得到多边形,其余参数类似.

添加文字

1
cv2.putText()  #参数分别为添加文字的图像,添加的字符串,添加的位置
1
2
words = cv2.putText(region, "Vision", (100, 250), cv2.FONT_HERSHEY_SIMPLEX, 3, (0, 255, 0), 10, cv2.LINE_AA)
cv2.imwrite("words.jpg", words)

插入文本

第四个参数 cv2.FONT_HERSHEY_SIMPLEX 是字体类型,第五个参数 3 是字体的大小,第六个参数 (0, 255, 0) 是字体的颜色,第七个参数 10 是文字线条的粗细,第八个参数 cv2.LINE_AA 是线条的类型。

图像的处理

图像的缩放

1
2
3
cv2.resize()
resized = cv2.resize(image, (0, 0), fx=0.5, fy=0.5, interpolation = cv2.INTER_AREA)
cv2.imwrite("resized.jpg",resized)

使用 cv2.resize 函数来对图片的尺寸进行缩放,该函数的第一个参数 image 是要被缩放的图片。第二个参数 (0, 0) 是一个元组,表示输出图片的尺寸,元组内的第一个元素表示图片的宽,第二个表示图片的高。第三参数 fx = 0.5 表示沿图片的宽的缩放系数,第四参数 fy = 0.5 表示沿图片的高的缩放系数。第五个参数 interpolation = cv2.INTER_AREA) 是插值方法,表明图片是用哪种方法被缩放的,cv2.resize 默认的插值方法是 cv2.INTER_LINEAR,大家也可以尝试使用其他方法: cv2.INTER_NEARESTcv2.INTER_CUBICcv2.INTER_LANCZ0S4。我们也可以尝试定义输出图片尺寸来对图片进行缩放。

1
2
3
4
5
6
7
8
resized = cv2.resize(image, (0, 0), fx=0.5, fy=0.5, \
interpolation = cv2.INTER_AREA)
height, width = image.shape[:2]
# height width -> h w
w = 200
h = int(height*(w / width))
print(height,width)
cv2.imwrite("resized.jpg",resized)

当将此参数设置为 (0, 0) 时,图片的输出尺寸由后面两个缩放系数 fxfy 分别与函数的输入图片的宽和高相乘得出,当此参数不等于 (0, 0) 时,输出图片的尺寸等于我们自己设定的值

图像平移

图片的平移是让图片中所有的像素点沿着坐标轴移动相同的距离,视觉上看起来就是图片在进行上、下、左、右移动.

对图片进行平移时,我们要先确定图片向哪个方向移动、移动多少

图像平移

使用 cv2.warpAffine 函数对图片进行平移,第一个参数 image 是要被平移的图片。第二个参数 M 是我们刚才创建的矩阵 M。第三个参数 (width, height) 是输出的图片尺寸,这里我们输出的尺寸和输入的原始图片尺寸一样,width 表示图片的宽,height 表示图片的高

1
2
3
4
height,width = image.shape[:2]
M = np.float32([[1,0,100],[0,1,50]])
translation = cv2.warpAffine(image,M,(width,height))
cv2.imwrite('translation.jpg',translation)

图像的运算

算术运算和按位运算

图像的算术运算在图像增强中普遍应用,其可对图片进行增加亮度、减少亮度、增强对比度等操作以满足特定的图像处理需求,还可以通过将两幅图片进行算术运算以达到图像融合的效果。

图像的按位运算在图像分割、目标检测和识别、模式识别中得到广泛的应用

算术运算

使用 cv2.addcv2.subtractcv2.multiplycv2.divide 即可实现加、减、乘、除运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
M = img1.shape
x = np.ones(M,dtype=np.uint8)
x =x *150

sums = cv2.add(img1,x)
cv2.imwrite("sums.jpg",sums) #图像变亮 即像素增加
# 图像变暗
sub = cv2.subtract(img2,x)
cv2.imwrite("sub.jpg",sub)

# 相乘
mul = cv2.multiply(img1,x)
cv2.imwrite("mul.jpg",mul)

# 相除
div = cv2.divide(img1,x)
cv2.imwrite("div.jpg",div)

对于两张图片相加减,需要将图片大小调整为相同.

1
2
3
image2 = cv2.resize(image2, (image1.shape[1], image1.shape[0]), interpolation=cv2.INTER_CUBIC)
sums = cv2.add(image1, image2)
cv2.imwrite("sums.jpg", sums)
1
2
sums = cv2.add(image1, image2)
cv2.imwrite("sums.jpg", sums)
1
2
sub = cv2.subtract(image1, image2)
cv2.imwrite("sub.jpg", sub)
1
2
mul = cv2.multiply(image1, image2)
cv2.imwrite("mul.jpg", mul)
1
2
div = cv2.divide(image1, image2)
cv2.imwrite("div.jpg", div)

运算后的结果与单纯的十进制算术运算结果不同。这是因为我们用 8 位无符号整数表示 0 到 255,OpenCV 的算术运算函数是饱和运算,当运算结果大于 255 时函数输出 255,当运算结果小于 0 时函数输出 0。这里需要同 NumPy 的算术运算区分开,NumPy 的运算是取模运算,即当运算结果大于 255 时则最后得到的是运算结果对 256 取模

按位运算

and 运算是当两个像素值都大于 0 时为真,or 运算是当两个像素值有一个大于 0 时为真,xor 运算是当两个像素值有一个大于 0 但不同时都大于 0 则为真。not 则是一个取反的运算.

1
2
3
4
cv2.bitwise_and(a, b)
cv2.bitwise_or(a, b)
cv2.bitwise_xor(a, b)
cv2.bitwise_not(a)

图片的按位运算。两张图片的按位运算就是将两张尺寸通道数相同的图片中的每个像素值分别进行 andorxornot 运算

图片相与

图片相或

相与可以只取白色部分,或可以去掉白色部分

所以这就涉及到掩码(mask)

利用np.zeros创造黑图,创建一张黑色图片,因为这张图片的尺寸要和进行掩膜操作的原图片的尺寸一样.

1
2
mask = np.zeros(image1.shape[:2], np.uint8)
mask[100:300, 200:300] = 255

然后利用改变像素得到白图.使用 cv2.bitwise_and 函数进行掩膜操作,函数第一个和第二个参数相同,都是我们的原始图片 image1 ,第三个参数 mask 就是我们刚才创建的掩膜图片。该函数会把原图片中对应于掩膜图片的白色区域显示出来,其他区域会被黑色遮盖

1
2
imagemask = cv2.bitwise_and(image1, image1, mask = mask)
cv2.imwrite("imagemask.jpg", imagemask)

颜色空间

除了RGB还有HSV和LAB,描述色彩模型.

颜色空间是一种描述色彩的模型,其作用是在不同的标准下对不同颜色的说明。OpenCV 中有上百种颜色空间转换方法

图像的旋转

图像旋转

1
cv.getRotationMatrix2D() #参数分别为旋转中心,角度,缩放
1
2
3
4
5
img = cv.imread('messi5.jpg',0)
rows,cols = img.shape
# cols-1 和 rows-1 是坐标限制
M = cv.getRotationMatrix2D(((cols-1)/2.0,(rows-1)/2.0),90,1)
dst = cv.warpAffine(img,M,(cols,rows))
1
2
3
4
5
height,width = image.shape[:2]
M = cv2.getRotationMatrix2D((50,50),30,1)
rotation = cv2.warpAffine(image,M,(width,height))
cv2.imwrite("rotation1.jpg", rotation)

图像的翻转

1
2
3
4
5
6
7
8
9
# 翻转
turn = cv2.flip(image, 1) #1表示水平
cv2.imwrite("turn1.jpg", turn)

turn = cv2.flip(image, 0) # 0表示垂直
cv2.imwrite("turn2.jpg", turn)

turn = cv2.flip(image, -1)
cv2.imwrite("turn3.jpg", turn) # -1表示两个反向

此外还有仿射变换和透视变换

图像分割

阈值方法

通过设定一个值,通过这个值去除图像中的多于信息.这个值就叫阈值.

阈值法是一种图像分割的方法,阈值法因其简单、稳定、计算量小的特征使其在传统算法中得到广泛应用。阈值法是将灰度图像进行二值化以达到分割图像,获得特定感兴趣区域的方法。阈值法的通常做法是将灰度图转换为二值图,使得图片中的像素值只有 0 和 255 这两种值

简单阈值

为的选定一个阈值 T ,对于灰度图片中所有小于 T 的像素值设置为 0,将图片中所有大于 T 的像素值设置为 255。也可将大于阈值的像素值设置为 0,小于阈值的设置为 255

1
2
3
4
5
6
7
# 简单阈值
def simple():
image = cv2.imread("1.jpg")
grayimage = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
cv2.imwrite('gray.jpg',grayimage)
T,thresh = cv2.threshold(grayimage,200,255,cv2.THRESH_BINARY)
cv2.imwrite('thresh1.jpg',thresh)

输入上面得到的灰度图。第二个参数 200 是我们要设置的阈值,第三个参数 255 表示当图像中的像素值大于 200 那么该像素值将被替换成 255,否则像素值将会被设置为 0。第四个参数是设置阈值的方法,这里使用的是 cv2.THRESH_BINARY,该方法将会把所有大于第二个参数的像素值设置为第三个参数也就是 255

自适应阈值

自适应阈值法可以帮助我们优化简单阈值法的弊端。自适应阈值是基于一小片区域的像素值来确定阈值的,所以处理一张图片可以获得多个阈值

1
2
3
4
5
6
#自适应阈值
def adapt():
image = cv2.imread("1.jpg")
grayimage = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
thresh = cv2.adaptiveThreshold(grayimage,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,9,3)
cv2.imwrite('thresh1.jpg', thresh)

函数的第一个参数 grayimage 是要二值化的的灰度图。第二个参数 255 是像素的最大值,作用同 cv2.threshold 第三个参数一样。第三个参数 cv2.ADAPTIVE_THRESH_MEAN_C 是计算阈值的方法,该方法会计算像素邻近区域的平均值然后减去一个常数 C,这个常数是整数且是该函数的第六个参数。第四个参数 cv2.THRESH_BINARY 是前面讲过的设置阈值的方法。第五个参数 9 是用来计算阈值的区域的大小,该参数必须是奇数,这里我们设置其值为 9 表示我们将计算 9*9 区域内的阈值。第六个参数 3 就是前面提到的常数 C

也可以将第三个参数设置为 cv2.ADAPTIVE_THRESH_GAUSSIAN_C 表示用领域值的高斯加权总和减去常数 C

Otsu阈值

Otsu 方法是另一种自动计算阈值的方法,是一种寻找一个阈值将图片分为前景和背景的方法,其优点是不受图像的亮度和对比度的影响

1
2
3
4
5
6
7
8
9
#Otsu阈值
def Otsu():
image = cv2.imread("1.jpg")
grayimage = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
T,thresh = cv2.threshold(grayimage,0,255,cv2.THRESH_OTSU)
cv2.imwrite('thresh.jpg', thresh)
print(T)
T, thresh = cv2.threshold(grayimage, 0, 255, cv2.THRESH_OTSU+cv2.THRESH_BINARY_INV)
cv2.imwrite('rthresh.jpg', thresh)

还可以在 cv2.threshold 函数的第四个参数 cv2.THRESH_OTSU 后面添加设置阈值的方法来呈现不同的处理结果。

图像平滑

图像平滑也可称为滤波,是一种用于减少图像噪声且会让图像变得模糊的方法。图像平滑常常在图像预处理时使用,其主要目的是减少图像中的噪声,使得后续的处理操作能够获得更好的效果和可靠性。

像素值

每个数字表示像素值,红色方框是一个 3*3 矩阵或者称之为卷积核。图像平滑实际上是使用一个 m*k 的矩阵在图片上移动并与像素值进行相应计算来实现的

均值滤波

均值滤波就是线性滤波,是指用一个 m*m 的模板(卷积核)在图像上滑动,其中 m 是正整数且是奇数,当模板在滑动时,处于模板中心的像素值将会被其周围的像素值的平均值所替代。

1
2
3
4
5
6
# 均值滤波
image = cv2.imread("1.jpg")
blur = cv2.blur(image, (5, 5))
blur = cv2.resize(blur, (0, 0), fx=0.5, fy=0.3)
cv2.imshow('blur', blur)
cv2.waitKey(0)

高斯滤波

高斯滤波是为了克服平滑处理导致的图像边缘信息丢失的问题。高斯滤波比较类似均值滤波,但是高斯滤波在计算均值时使用了权重。卷积核的中心像素值是由相邻像素的加权平均得到的,越靠近中心像素的像素值的权重越大

通过 cv2.GaussianBlur 函数来实现高斯滤波。第一个参数 image 是要进行高斯平滑的图片,第二个参数 (5, 5) 是高斯卷积核的大小,第三个参数 0x 轴方向的高斯卷积核的标准差,这里设置为 0 表示让 OpenCV 根据卷积核大小自动计算

1
2
3
4
5
6
# 高斯滤波
image = cv2.imread("1.jpg")
blur = cv2.GaussianBlur(image, (5, 5), 0)
blur = cv2.resize(blur, (0, 0), fx=0.5, fy=0.3)
cv2.imshow('blur', blur)
cv2.waitKey(0)

中位滤波

中位滤波首先需要设定一个 m*m 的卷积核,当卷积核在图片上滑动时将卷积核中的每个值进行排序,取排序后的中间值作为卷积核中心的像素值。中位滤波对去除图片中的椒盐噪声有较好的效果.

椒盐噪声就是图片中存在许多白色和黑色的点,其会给图像处理带来一定的困难

1
2
3
4
5
6
7
# 中位滤波
def median():
image = cv2.imread("2.jpg")
blur = cv2.medianBlur(image, 3)
blur = cv2.resize(blur, (0, 0), fx=0.5, fy=0.3)
cv2.imshow('median', blur)
cv2.waitKey(0)

双边滤波

双边滤波是一种使用两个高斯函数,在减少噪声同时也可以保留边缘信息的平滑方法。双边滤波使用两个权重,距离卷积核的中心点越近的像素权重越大,相邻区域内像素的强度与中心像素的强度越相似,其所获得的权重就越大。

1
2
3
4
5
6
7
# 双边滤波
def bila():
image = cv2.imread("2.jpg")
blur = cv2.bilateralFilter(image,9,45,45)
blur = cv2.resize(blur, (0, 0), fx=0.5, fy=0.3)
cv2.imshow('median', blur)
cv2.waitKey(0)

第二个参数 9 是在进行滤波过程中每个像素其相邻区域像素的直径,第三个参数 45 是颜色标准差,这个值越大表示更多的相邻区域内颜色在进行滤波时会被考虑。第四个参数 45 是坐标空间的标准差,这个值越大表示距离中心像素较远的像素会影响计算.

边缘检测

边缘是图像中的一种重要信息,边缘将图片分成不同的区域,每个区域代表不同的物体。边缘有一个重要的特征:在边缘两侧区域的像素灰度值差异较大,而边缘上的像素灰度值差异不明显。

为了检测到边缘,我们需要找到一种方法确定哪些区域是存在灰度值的“突变”的,使用梯度可以找到灰度值变化明显的地方,梯度变化的幅度可以用来检测边缘

Sobel算子

使用 cv2.Sobel 函数来计算水平方向和垂直方向的梯度,值得注意的是梯度值可能为负数(由大到小).所以需要取绝对值.同时最后要将两个方向的图重合,利用相或操作.

1
2
3
4
5
6
7
8
9
10
11
12
def sobel():
x = cv2.Sobel(image, cv2.CV_64F, 1, 0)
y = cv2.Sobel(image, cv2.CV_64F, 0, 1)
x = np.uint8(np.absolute(x)) # 梯度值可能为负数,所以求绝对值
y = np.uint8(np.absolute(y)) # 梯度值可能为负数,所以求绝对值
# 梯度值有正负之分,如果直接将其保存为图片,那么 OpenCV
# 会将小于 0 的值设置为 0导致丢失值为负数的梯度
cv2.imshow("horizon", x)
cv2.imshow("vertical", y)
cv2.imshow("edge", cv2.bitwise_or(x,y))
cv2.waitKey(0)

Laplacian算子

Laplacian 算子只产生一个结果即与水平和垂直方向无关。因为这个算子对噪声比较敏感,所以当图片中存在噪声时最好先对图片进行降噪处理。我们使用 cv2.Laplacian 函数对图片进行边缘检测.

1
2
3
4
def lap():
edge = cv2.Laplacian(image,cv2.CV_64F)
cv2.imshow('edge',edge)
cv2.waitKey(0)

Canny算子

Canny 边缘检测是一种比较流行的算法,其在进行边缘检测过程中包含多个步骤:对图片进行降噪处理、计算水平和垂直方向的梯度、剔除干扰的边缘、使用双阈值确定哪些像素在边缘上

1
2
3
4
def canny():
edge = cv2.Canny(image,20,140)
cv2.imshow('edge',edge)
cv2.waitKey(0)

二个参数 20 和第三个参数 140 是要设置的阈值。小于 20 的梯度值将被划分为非边缘,大于 140 的梯度值将被划分为边缘;介于 20140 中间的值,如果该值与大于 140 的值相邻接,那么判定其为边缘。

直方图

图像的直方图是一种通过简单的统计分析手段对像素值的分布情况进行可视化的方法。通过绘制图像直方图我们可以直观的了解图像的一些特征

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import cv2 as cv
import matplotlib.pyplot as plt
import numpy as np
image = cv.imread("1.jpg")
print(image)
image = cv.cvtColor(image,cv.COLOR_BGR2GRAY)
print('灰度图',image)
# 照片 通道 掩码 256份 像素范围0-255
# fig = cv.calcHist([image],[0],None,[256],[0,256])
mask = np.zeros(image.shape[:2],dtype="uint8")
mask = cv.circle(mask,(150,150),100,255,-1)
# cv.imshow('black',mask)
mask_area = cv.bitwise_and(image,image,mask=mask)
fig = cv.calcHist([image],[0],None,[256],[0,256])
plt.figure()
plt.plot(fig)
# plt.show()
# plt.savefig("histogram_mask.png")
plt.savefig("histogram.png")

这是灰度图的直方图.

也可以对三通道彩图处理

1
2
3
4
5
6
7
8
image = cv.imread("1.jpg")
channel = ['b','g','r']
# plt.figure()
for i in range(3):
fig = cv2.calcHist([image],[i],None,[256],[0,256])
plt.plot(fig,color=channel[i])
plt.xlim([0,256])
plt.show()

二维直方图

即同时考虑多个通道的直方图.

1
2
3
4
fig = cv.calcHist([image],[0,1],None,[16,16],[0,256,0,256])
# plt.imshow(fig)
plt.colorbar(plt.imshow(fig))
plt.show()

均衡化

拍摄的照片会出现曝光过度或者曝光不足导致图片的内容缺乏辨识度,这种情况我们可以通过均衡化的方法来缓解这种问题

首先将三通道分离为三个单通道

1
2
3
4
5
6
7
8
9
splits = cv2.split(image)
plt.figure()
for i in range(3):
splits[i] = cv2.equalizeHist(splits[i])
fig = cv2.calcHist([splits[i]], [0], None, [256], [0,256])
plt.plot(fig, color = channel[i])
plt.xlim([0,256])
equalized = cv2.merge(splits)
cv2.imwrite("equalized.png", equalized)

抠图

轮廓提取和分割

利用这按位运算和边缘检测方法实现从图片中抠取特定区域,然后将其融合进一张新的图片中去。要想从一张图片中抠取特定的区域我们需要用到轮廓提取的方法,轮廓是描述物体外形的一段闭合的曲线,是物体的一个重要特征,轮廓特征在图像处理中广泛应用,在图像分割中我们利用轮廓特征将物体从背景中分割开。

  1. 首先需要边缘检测,利用之前的算子得到边缘.

  2. 使用cv.findContour根据边缘得到轮廓.

    cv2.findContours 有两个返回值 contourshcontours 是一个列表,其中每一个元素对应检测到的轮廓,另一个 h 表示检测到的轮廓所对应的属性.

    第一个参数 edgecv2.Canny 函数输出的边缘图,这个参数必须是二值图且函数会修改输入的图片。第二个参数 cv2.RETR_EXTERNAL 是检索轮廓的方式,表示只检测最外层的轮廓;可以尝试使用 cv2.RETR_LISTcv2.RETR_CCOMPcv2.RETR_TREE 参数。第三个参数 cv2.CHAIN_APPROX_SIMPLE 表示逼近轮廓的方法,或者可以称为存储轮廓的方法,这个方法将会对轮廓上的所有坐标点进行压缩,只保留水平、垂直、对角线方向上的端点坐标;可以尝试 cv2.CHAIN_APPROX_NONEcv2.CHAIN_APPROX_TC89_L1cv2.CHAIN_APPROX_TC89_KCOS 方法。

  3. drawCountours绘制轮廓.根据findContours得到的轮廓,规定颜色和粗细.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
image = cv.imread('1.jpg')
edge = cv.Canny(cv.cvtColor(image, cv.COLOR_BGR2GRAY), 60, 150)
cv.imshow('edge.img',edge)
# cv.imshow('edge.jpg', edge)
# cv.waitKey(0)
# 第一个参数 edge 是 cv2.Canny 函数输出的边缘图,这个参数必须是二值图且
# 函数会修改输入的图片。第二个参数 cv2.RETR_EXTERNAL 是检索轮廓的方式,
# 表示只检测最外层的轮廓;可以尝试使用 cv2.RETR_LIST,cv2.RETR_CCOMP 或
# cv2.RETR_TREE 参数。第三个参数 cv2.CHAIN_APPROX_SIMPLE 表示逼近轮廓的方
# 法,或者可以称为存储轮廓的方法,这个方法将会对轮廓上的所有坐标点进行压缩,只保留
# 水平、垂直、对角线方向上的端点坐标;可以尝试 cv2.CHAIN_APPROX_NONE,
# cv2.CHAIN_APPROX_TC89_L1 和 cv2.CHAIN_APPROX_TC89_KCOS 方法。
countours,h = cv.findContours(edge,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)
# cv2.findContours 有两个返回值 contours 和 h。
# contours 是一个列表,其中每一个元素对应检测到的轮廓,
# 另一个 h 表示检测到的轮廓所对应的属性
drawc = image.copy()
cv.drawContours(drawc,countours,-1,(255,0,0),2)

分割物体

上一步得到了轮廓.现在直接根据轮廓绘制即可.

1
2
3
mask = np.zeros(image.shape,dtype="uint8")
mask = cv2.drawContours(mask, contours, 1, (255,255,255), -1)#这里的1表示第二个物体的轮廓
cv2.imwrite("mask.jpg", mask)

这样可以根据轮廓画出白色图形.

cv2.drawContours 的第三个参数 1 表示绘制检测到的第二个物体的轮廓,第四个参数 (255, 255, 255) 表示轮廓的颜色为白色,第五个参数 -1 表示将整个轮廓内的区域填充为白色。然后保存图片

然后将这个图形与原图直接相与.

1
2
divide = cv2.bitwise_and(mask, image1)
cv2.imwrite("divide.jpg", divide)

剪裁和图片合并

要从 divide 中裁剪出辣椒。我们使用 cv2.boundingRect 函数获得这个辣椒最小的外接矩形的坐标。

1
(x, y, w, h) = cv2.boundingRect(contours[1])

得到截取图形的矩形尺寸

1
2
3
4
cut_pepper = divide[y:y + h, x:x + w]
cv2.imwrite("cutpepper.jpg", cut_pepper)
cut_not = cv2.bitwise_not(cut_mask)
cv2.imwrite("cut_not.jpg", cut_not)

将截取的区域颜色变换,方便后续与或.

注意图像运算要求图像大小相同.

将要合并的图像截取相同大小.

1
2
cut_image2 = image2[50:50 + h, 50:50 + w]
cv2.imwrite("cut_image2.jpg", cut_image2)

将两个图像相与

1
2
cut_image2 = cv2.bitwise_and(cut_not, cut_image2)
cv2.imwrite("cut_image2.jpg", cut_image2)

再将分割的图像相或

1
2
cut_image2 = cv2.bitwise_or(cut_pepper, cut_image2)
cv2.imwrite("cut_image2.jpg", cut_image2)

最后直接修改原图相应位置的像素

1
2
image2[50:50 + h, 50:50 + w] = cut_image2[:,:]
cv2.imwrite("merge.jpg", image2)

官方文档

OpenCV中文官方文档 (woshicver.com)

Matplotlib — Visualization with Python

NumPy

国内也有很多翻译的文档,都挺不错的,基本用法是都覆盖的.

或者直接看菜鸟驿站.

-------------本文结束感谢您的阅读-------------
感谢阅读.

欢迎关注我的其它发布渠道