1. 1. Opencv基础学习笔记
    1. 1.1. 图像基本操作
      1. 1.1.1. 图像读取
      2. 1.1.2. 图像保存
      3. 1.1.3. 视频读取
      4. 1.1.4. 截取部分图像数据
      5. 1.1.5. 颜色通道提取
      6. 1.1.6. 调整对比度和亮度
      7. 1.1.7. 绘制基本图形和文本
      8. 1.1.8. 边界填充
      9. 1.1.9. 图像融合
    2. 1.2. 图像阈值与平滑处理
      1. 1.2.1. 图像阈值
      2. 1.2.2. 图像滤波
        1. 1.2.2.1. 中值滤波
        2. 1.2.2.2. 双边滤波
    3. 1.3. 图像形态学
      1. 1.3.1. 腐蚀(变黑)
      2. 1.3.2. 膨胀(变白)
      3. 1.3.3. 开运算与闭运算
      4. 1.3.4. 形态学梯度计算
      5. 1.3.5. 礼帽与黑帽
      6. 1.3.6. 提取图片中水平线和垂直线
    4. 1.4. 图像梯度计算
      1. 1.4.1. 图像卷积
      2. 1.4.2. Sobel算子
      3. 1.4.3. Scharr算子
      4. 1.4.4. laplacian算子
      5. 1.4.5. 算子对比
    5. 1.5. 边缘检测
      1. 1.5.1. Canny边缘检测
      2. 1.5.2. 霍夫圆
      3. 1.5.3. 像素重映射
    6. 1.6. 图像金字塔
    7. 1.7. 直方图与傅里叶变换
      1. 1.7.1. 直方图
      2. 1.7.2. 直方图均衡化
      3. 1.7.3. 直方图比较方法
      4. 1.7.4. 模板匹配
      5. 1.7.5. 图像轮廓
      6. 1.7.6. 轮廓特征
      7. 1.7.7. 轮廓周围绘制矩形
      8. 1.7.8. 凸包(Convex Hull)
      9. 1.7.9. 图像矩
      10. 1.7.10. 点多边形测试
      11. 1.7.11. 傅里叶变换
    8. 1.8. 项目

Opencv基础学习笔记

Opencv基础部分,以前学过,做个笔记用于复习;

当然我建议学C++版本,更齐全,Python版本的课程教学内容缺太多了;

这里只看到第35节的基础部分就结束了(因为之前只学到这)

[opencv_C++] 入门强推!!!【B站最全】_哔哩哔哩_bilibili

图像基本操作

BGR:Blue Green Red

HSV:Hue(色调) Satration(饱和度) Value(明度)

Shape:[500, 500, 3] — [h, w, RGB]

图像读取

  • cv2.IMREAD_COLOR:彩色图像
  • cv2.IMREAD_GRAYSCALE:灰度图像
1
2
3
4
5
6
7
8
9
10
11
import cv2
# 彩色图片
img = cv2.imread("1.jpg", cv2.IMREAD_COLOR)
# 灰度图片
# img = cv2.imread("1.jpg", cv2.IMREAD_GRAYSCALE)
# 展示img的通道值
# print(img.shape)
cv2.imshow("Image", img)
cv2.waitKey(0)
# 按下任意键关闭所有窗口
cv2.destroyAllWindows()

图像保存

1
2
3
4
5
6
7
8
9
10
11
12
13
import cv2
# 读取图片1
img1 = cv.imread("1.jpg")
# 展示图片1
cv2.imshow("Image1", img1)
# 图片1保存为图片2
cv2.imwrite("2.jpg", img)
# 读取图片2
img2 = cv.imread("2.jpg")
# 展示图片2
cv2.imshow("Image2", img2)
cv2.waitKey(0)
cv2.destroyAllWindows()
1
2
3
4
5
6
7
8
import cv2
img1 = cv.imread("1.jpg")
type(img1)
# numpy.ndarray
img1.size
# 207000
img1.dtype
# dtype('uint8')

视频读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import cv2
# 打开本地摄像头
vc = cv2.VideoCapture(0)
if vc.isOpened():
open, frame = vc.read()
else:
open = False
while open:
ret, frame = vc.read()
if frame is None:
break
if ret == True:
# BGR转灰度
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
cv2.imshow("res", gray)
# 按下Q键退出
if cv2.waitKey(10) & 0xFF == ord('q'):
break
vc.release()
cv2.destroyAllWindows()

截取部分图像数据

1
2
3
4
5
6
7
8
import cv2
img1 = cv2.imread("1.jpg")
cv2.imshow("img1", img1)
# 截取200x200
img2 = img1[0:200, 0:200]
cv2.imshow("img2", img2)
cv2.waitKey(0)
cv2.destroyAllWindows()

颜色通道提取

1
# split,把多通道图像分为多个单通道图像
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import cv2
img1 = cv2.imread("1.jpg")
cv2.imshow("img1", img1)
# 颜色通道提取
b, g, r = cv2.split(img1)
print(b, g, r)
# 通道合并
img2 = cv2.merge((b, g, r))
cv2.imshow("img2", img2)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 只保留B
# cur_img = img1.copy()
# cur_img[:, :, 1] = 0
# cur_img[:, :, 2] = 0
# cv2.imshow("B", cur_img)
# 只保留G
# cur_img = img1.copy()
# cur_img[:, :, 0] = 0
# cur_img[:, :, 2] = 0
# cv2.imshow("G", cur_img)
# 只保留R
# cur_img = img1.copy()
# cur_img[:, :, 0] = 0
# cur_img[:, :, 1] = 0
# cv2.imshow("R", cur_img)

调整对比度和亮度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#导入所需的库
import cv2

#读取输入图像
image = cv2.imread('1.jpg')

#定义alpha和beta
alpha = 1.5 #对比度控制
beta = 10 #亮度控制

#调用convertScaleAbs函数
adjusted = cv2.convertScaleAbs(image, alpha=alpha, beta=beta)

#显示输出图像
cv2.imshow('adjusted', adjusted)
cv2.waitKey()
cv2.destroyAllWindows()

绘制基本图形和文本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
## 直线
# image:需绘制直线的图像
# starting cordinates :直线的起点
# ending cordinates :直线的终点
# color :线的颜色
# thickness :线的粗细像素

# cv2.line(image,starting cordinates,ending cordinates,color,thickness)

import numpy as np
import cv2
# 创建一个黑色面板
image = np.zeros((512,512,3),np.uint8)
# 画直线
cv2.line(image,(0,0),(511,511),(255,0,0),3)
#色彩空间转换
image = cv2.cvtColor(image,cv2.COLOR_BGR2RGB)
cv2.imshow("绘制直线",image)
cv2.waitKey(0)

## 矩形
# image:需绘制矩形的图像
# starting vertex :矩形的左上角顶点
# opposit vertex :矩形的右下角顶点
# color :线的颜色
# thickness :线的粗细像素,-1表示填充

# cv2.rectangle(image,starting vertex,opposit vertex,color,thickness)

import numpy as np
import cv2
# 创建一个黑色面板
image = np.zeros((512,512,3),np.uint8)
# 画大矩形
cv2.rectangle(image,(100,100),(400,400),(255,0,0),2)
# 画小矩形
cv2.rectangle(image,(200,200),(300,300),(255,0,0),-1)
# 色彩空间转换
image = cv2.cvtColor(image,cv2.COLOR_BGR2RGB)
cv2.imshow("绘制矩形",image)
cv2.waitKey(0)

## 圆
# image:需绘制圆的图像
# center:所绘制圆的圆心坐标
# radius:圆的半径
# color:圆的颜色
# fill:线的粗细像素,-1表示填充

# cv2.cirlce(image,center,radius,color,fill)

import numpy as np
import cv2
#创建一个黑色面板
image = np.zeros((512,512,3),np.uint8)
#画空心圆
cv2.circle(image,(150,150),100,(255,0,0),2)
#画实心圆
cv2.circle(image,(350,350),100,(255,0,0),-1)
#色彩空间转换
image = cv2.cvtColor(image,cv2.COLOR_BGR2RGB)
cv2.imshow("绘制圆",image)
cv2.waitKey(0)

## 椭圆
# image:绘制椭圆的图像。
# centerCoordinates:它是椭圆的中心坐标。
# axesLength:它包含两个变量的元组,分别包含椭圆的长轴和短轴的长度
# angle:椭圆旋转角度,以度为单位。
# startAngle:椭圆弧的起始角度,以度为单位。
# endAngle:椭圆弧的终止角度,以度为单位。
# color:它是要绘制的形状边界线的颜色。
# thickness:是形状边界线的粗细像素。-1表示将用指定的颜色填充形状。

# cv2.ellipse(image, centerCoordinates, axesLength, angle, startAngle, endAngle, color, thickness)

import numpy as np
import cv2
# 创建一个黑色面板
image = np.zeros((512,512,3),np.uint8)
# 画半个椭圆
cv2.ellipse(image,(150,150),(100,50),0,0,180,(255,0,0),-1)
# 画45度角的整个椭圆
cv2.ellipse(image,(350,350),(100,50),45,0,360,(255,0,0),-1)
# 色彩空间转换
image = cv2.cvtColor(image,cv2.COLOR_BGR2RGB)
cv2.imshow("绘制椭圆",image)
cv2.waitKey(0)

## 画多边形
# img:绘制椭圆的图像。
# pts:绘制的顶点
# color:线的颜色
# thickness: 厚度

# cv2.polylines(image,[pts],True,color,thickness)

import numpy as np
import cv2
# 创建一个黑色面板
image = np.zeros((512,512,3),np.uint8)
# 构建多边形的顶点
pts = np.array([[200,100],[200,500],[50,300],[500,200],[500,400]],np.int32)
# 以折线画图
cv2.polylines(image,[pts],True,(255,0,0),3)
# 色彩空间转换
image = cv2.cvtColor(image,cv2.COLOR_BGR2RGB)
cv2.imshow("绘制多边形",image)
cv2.waitKey(10000)

# 绘制英文文本
# img:表示在那个图片上放置文本内容
# text:要绘制的文本内容
# org:文本在图片中的左下角坐标
# fontFace:字体类型即字体,通过cv2.FONT_可查看字体类型
# fontScale:字体大小
# color:字体颜色
# thickness:字体粗细
# lineType:边界类型
# bottomLeftOrigin:默认为True,表示图片数据源在左下角;为False,表示图片数据源在左上角

# 字体
# cv2.FONT_HERSHEY_SIMPLEX - 正常大小无衬线字体
# cv2.FONT_HERSHEY_PLAIN - 小号无衬线字体
# cv2.FONT_HERSHEY_DUPLEX - 正常大小无衬线字体比(更复杂)
# cv2.FONT_HERSHEY_COMPLEX - 正常大小有衬线字体
# cv2.FONT_HERSHEY_TRIPLEX - 正常大小有衬线字体(更复杂)
# cv2.FONT_HERSHEY_COMPLEX_SMALL
# cv2.FONT_HERSHEY_SCRIPT_SIMPLEX - 手写风格字体。

# cv2.putText(img, text, org, fontFace, fontScale, color[, thickness[, lineType[, bottomLeftOrigin]]])

import cv2
import numpy as np
# 创建纯黑的背景图用来画图形
img = np.zeros((800, 800, 3), np.uint8)
# 绘制文本
cv2.putText(img, 'Hello OpenCV', (50,400), cv2.FONT_HERSHEY_COMPLEX, 2, [0, 0, 255])
cv2.imshow('draw', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

## 绘制中文
# 安装pillow:pip install pillow
# 使用Pillow包绘制中文文本.

from PIL import ImageFont, ImageDraw, Image
# 纯白背景
img = np.full((500, 500, 3), fill_value=255, dtype=np.uint8)
# 导入字体文件,要自己下字体
font = ImageFont.truetype('./msyh.ttc', 35)
# 创建一个pillow的图片
img_pil = Image.fromarray(img)
# 绘制pillow图片
draw = ImageDraw.Draw(img_pil)
# 利用draw去绘制中文
draw.text((100, 250), '快跑,别用Python,用C++!!!', font=font, fill=(0, 255, 0))
# 重新变回ndarray
img = np.array(img_pil)
cv2.imshow('img' ,img)
cv2.waitKey(0)
cv2.destroyAllWindows()

边界填充

改变图片最外围的边框

  • BORDER_REPLICATE:复制法,复制最边缘像素
  • BORDER_REFLECT:反射法,对图像中的像素进行镜像复制fedcba|abcdefgh|hgfedcb
  • BORDER_REFLECT_101:反射法,以最边缘像素为轴,对称gfedcb|abcdefgh|gfedcba
  • BORDER_WRAP外包装法cdefgh|abcdefgh|abcdefg
  • BORDER_CONSTANT:常量法,常数值填充

图像融合

1
2
3
4
5
6
7
8
9
10
import cv2
img_cat = cv2.imread('1.jpg')
img_dog = cv2.imread('2.jpg')
# 设置同样大小
img_dog = cv2.resize(img_dog, (639, 640))
# 设置权重, res = x1 * a + x2 * b + c
res = cv2.addWeighted(img_cat, 0.4, img_dog, 0.6, 0)
cv2.imshow("res", res)
cv2.waitKey(0)
cv2.destroyAllWindows()

图像阈值与平滑处理

图像阈值

1
2
3
4
5
6
# src: 输入图,只能输入单通道图像,通常来说为灰度图
# dst: 输出图
# thresh: 阈值,0-255
# maxval: 像素最大可得值
# type:二值化操作的类型
ret, dst = cv2.threshold(src, thresh, maxval, type)

type为如下操作

  • cv2.THRESH_BINARY 超过阈值部分取maxval,其他取0

  • cv2.THRESH_BINARY_INV 超过阈值部分取0,其他取maxval

  • cv2.THRESH_TRUNC 大于阈值部分设为阈值,其他不变

  • cv2.THRESH_TOZERO 大于阈值部分不变,其他为0

  • cv2.THRESH_TOZERO_INV 大于阈值部分为0,其他不变

常常配合createTrackbar使用

1
2
3
4
5
6
7
8
9
10
11
12
# name:滑动条的名字,
# dst:滑动条被放置的窗口的名字,
# default:滑动条默认值,
# max_value:滑动条的最大值,
# fun:回调函数,每次滑动都会调用回调函数。
cv2.createTrackbar(name, dst, default, max_value, fun)

# 得到滑动条的数值
cv2.getTrackbarPos()

# 设置滑动条的默认值
cv2.setTrackbarPos(name, dst, default)

图像滤波

滤波的意义是为了让图片更加平滑,减少噪声

中值滤波

双边滤波

双边滤波计算某一个像素点的新值时,不仅考虑距离信息(距离越远,权重越小),还考虑色彩信息(色彩差别越大,权重越小)

1
dst = cv2.bilateralFilter(src, d, sigmaColor, sigmaSpace, borderType)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import cv2
import numpy as np
img1 = cv2.imread('2.jpg')
img1 = cv2.resize(img1, (200, 200))
# 均值滤波
# 简单的平均卷积操作
blur = cv2.blur(img1, (100, 100))
# 方框滤波
# 和均值差不多,可以选择归一化
box1 = cv2.boxFilter(img1, -1, (100, 100), normalize=True)
# box2 = cv2.boxFilter(img1, -1, (3,3), normalize=False)
# 高斯滤波
# 高斯模糊的卷积核里的数值满足高斯分布,即更重视中间的
gaussian = cv2.GaussianBlur(img1, (9, 9), 1)
# 中值滤波
# 相当于中值替代
med = cv2.medianBlur(img1, 9)
res = np.hstack((img1, blur, box1, gaussian, med))
cv2.imshow("img, blur, box, gaussian, med", res)
cv2.waitKey(0)

图像形态学

腐蚀(变黑)

去除小型噪声、分离粘连物体、细化边缘

1
2
3
4
5
6
7
8
9
10
11
12
13
import cv2
import numpy as np
img2 = cv2.imread('2.jpg')
img2 = cv2.resize(img2, (200, 200))
kernel = np.ones((30, 30), np.uint8)
# 越来越黑
# 在kernel(30,30)的区域获取最大值赋值到对应pixel
erosion_1 = cv2.erode(img2, kernel, iterations=1)
erosion_2 = cv2.erode(img2, kernel, iterations=2)
erosion_3 = cv2.erode(img2, kernel, iterations=3)
res = np.hstack((img2, erosion_1, erosion_2, erosion_3))
cv2.imshow('res', res)
cv2.waitKey(0)

膨胀(变白)

连接断裂的物体部分、填充物体内部的小孔或空洞、扩大物体尺寸、去除孤立噪声点

1
2
3
4
5
6
7
8
9
10
11
12
13
import cv2
import numpy as np
img2 = cv2.imread('2.jpg')
img2 = cv2.resize(img2, (200, 200))
kernel = np.ones((30, 30), np.uint8)
# 越来越白
# 在kernel(30,30)的区域获取最小值赋值到对应pixel
dilate_1 = cv2.dilate(img2, kernel, iterations=1)
dilate_2 = cv2.dilate(img2, kernel, iterations=2)
dilate_3 = cv2.dilate(img2, kernel, iterations=3)
res = np.hstack((img2, dilate_1, dilate_2, dilate_3))
cv2.imshow('res', res)
cv2.waitKey(0)

开运算与闭运算

开运算:消除细小噪声与孤立点、分离粘连物体、平滑物体外轮廓

作用:医学图像中去除组织切片的小斑点、文本识别前清除文档扫描件的墨渍或断裂笔画

闭运算:填充内部空洞与缝隙、连接临近物体、平滑内轮廓

作用:工业检测中填补零件表面的微小缺陷、遥感图像中连接断裂的道路或河流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import cv2
import numpy as np
img2 = cv2.imread('2.jpg')
img2 = cv2.resize(img2, (200, 200))
# 原图
cv2.imshow('img2', img2)
# 开:先腐蚀,再膨胀
kernel = np.ones((10, 10), np.uint8)
opening = cv2.morphologyEx(img2, cv2.MORPH_OPEN, kernel)
cv2.imshow('Opening', opening)
# 闭:先膨胀,再腐蚀
kernel = np.ones((10, 10), np.uint8)
closing = cv2.morphologyEx(img2, cv2.MORPH_CLOSE, kernel)
cv2.imshow('Closing', closing)
cv2.waitKey(0)
cv2.destroyAllWindows()

形态学梯度计算

边缘检测、特征提取、图像增强与锐化、光照估计与校正

目标分割:结合梯度信息与区域生长算法,实现语义分割

1
2
3
4
5
6
7
8
9
10
11
12
import cv2
import numpy as np
img2 = cv2.imread('2.jpg')
img2 = cv2.resize(img2, (200, 200))

# 梯度 = 膨胀 - 腐蚀,用膨胀的图像减去腐蚀的图像
kernel = np.ones((4, 4), np.uint8)
gradient = cv2.morphologyEx(img2, cv2.MORPH_GRADIENT, kernel)

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

礼帽与黑帽

突出图像中的局部亮暗特征或校正光照不均匀

  • 礼帽 = 原始输入 - 开运算
    • 突出原图像中更亮的局域

作用:提取比周围背景更亮的细小物体、消除不均匀光照的影响,增强微弱目标特征

黑帽可以用于提取原图片里面的有特征的线/数字

  • 黑帽 = 闭运算 - 原始输入
    • 突出原图像中更暗的局域

作用:提取比周围更暗的区域、通过闭运算填充亮背景中的暗水印,再减去原图实现去水印

1
2
3
4
5
6
7
8
9
10
11
12
13
import cv2
import numpy as np
img2 = cv2.imread('2.jpg')
img2 = cv2.resize(img2, (200, 200))
kernel = np.ones((15, 15),np.uint8)
# 礼帽
tophat = cv2.morphologyEx(img2, cv2.MORPH_TOPHAT, kernel)
cv2.imshow('Please', tophat)
# 黑帽
blackhat = cv2.morphologyEx(img2, cv2.MORPH_BLACKHAT, kernel)
cv2.imshow('Black', blackhat)
cv2.waitKey(0)
cv2.destroyAllWindows()

提取图片中水平线和垂直线

  • 读取图片,转灰度图像cv_BGR2GRAY

  • 转二值图像adaptiveThreshold

  • 获取形状和尺寸的结构元素getStructuringElement

1
2
3
4
5
6
7
# MODE
# 矩形:MORPH_RECT;
# 交叉形:MORPH_CROSS;
# 椭圆形:MORPH_ELLIPSE;
# SIZE -- 内核的尺寸,比如(11,1)
# POINT -- 锚点的位置,比如(-1,-1)
kernel = cv2.getStructuringElement(MODE, SIZE, POINT)
  • 开操作(腐蚀+膨胀)

  • bitwise,完成

图像梯度计算

图像卷积

Sobel算子

一阶微分算子,适用于噪声较多的图像,边缘较粗,适合实时性要求较高

1
2
3
4
# ddepth:图像的深度
# dx和dy分别表示水平和竖直方向
# ksize是Sobel算子的大小
dst = cv2.Sobel(src, ddepth, dx, dy, ksize)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import cv2
import numpy as np
img2 = cv2.imread('2.jpg', cv2.IMREAD_GRAYSCALE)
# sobel 求导x
sobelx = cv2.Sobel(img2, cv2.CV_64F, 1, 0, ksize=3)
sobelx = cv2.convertScaleAbs(sobelx)
# sobel 求导y
sobely = cv2.Sobel(img2, cv2.CV_64F, 0, 1, ksize=3)
sobely = cv2.convertScaleAbs(sobely)
# sobel 求导x,y
sobelxy=cv2.Sobel(img2, cv2.CV_64F, 1, 1, ksize=3)
sobelxy = cv2.convertScaleAbs(sobelxy)
sob = np.hstack((sobelx, sobely, sobelxy))
cv2.imshow('sob', sob)
cv2.waitKey(0)
cv2.destroyAllWindows()

Scharr算子

laplacian算子

单独使用效果差,需配合高斯滤波(LOG算子)以抑制噪声

处理流程:

  • 高斯模糊-去噪声GaussianBlur()
  • 转换为灰度图像cvtColor()
  • 拉普拉斯-二阶导数计算Laplacian()
  • 取绝对值,显示结果convertScaleAbs()

算子对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 几种算子对比
import cv2
import numpy as np
img2 = cv2.imread('2.jpg', cv2.IMREAD_GRAYSCALE)
# sobel 求导x
sobelx = cv2.Sobel(img2, cv2.CV_64F, 1, 0, ksize=3)
sobelx = cv2.convertScaleAbs(sobelx)
# sobel 求导y
sobely = cv2.Sobel(img2, cv2.CV_64F, 0, 1, ksize=3)
sobely = cv2.convertScaleAbs(sobely)
# sobel 根据权重设置x, y
sobelxy = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0)

# scharr 求导x
scharrx = cv2.Scharr(img2, cv2.CV_64F, 1, 0)
scharrx = cv2.convertScaleAbs(scharrx)
# scharr 求导y
scharry = cv2.Scharr(img2, cv2.CV_64F, 0, 1)
scharry = cv2.convertScaleAbs(scharry)
# scharr 根据权重设置x, y
scharrxy = cv2.addWeighted(scharrx, 0.5, scharry, 0.5, 0)

# 拉普拉斯
laplacian = cv2.Laplacian(img2, cv2.CV_64F)
laplacian = cv2.convertScaleAbs(laplacian)

res = np.hstack((sobelxy, scharrxy, laplacian))
cv2.imshow('res', res)
cv2.waitKey(0)
cv2.destroyAllWindows()

可以看出Scharr要比Sobel显示的边缘更多,而Laplacian显示的更少

边缘检测

Canny边缘检测

处理流程:

  • 高斯模糊,用于平滑图像,滤除噪声

  • 灰度转换

  • 计算图像中每个像素点的梯度强度和方向

  • 非极大值(Non-Maximum Suppression)抑制,消除边缘检测带来的杂散响应

  • 应用双阈值(Double-Threshold)检测来确定真实的和潜在的边缘。

通过抑制孤立的弱边缘最终完成边缘检测。

  1. 高斯滤波器
  1. 计算梯度和方向
  1. 非极大值抑制

线性插值法distance(g1, g2)表示两点之间的距离

​ 设g1的梯度幅值M(g1), g2的梯度幅值M(g2)

​ 则dtmp1可以得到:M(dtmp1) = w * M(g2) + (1 - w) * M(g1)

​ 其中w = distance(dtmp1, g2) / distance(g1, g2)

  1. 双阈值检测

梯度 > maxVal,则为maxVal;梯度 < minVal,则为minVal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"""
# image:输入原图(必须为单通道图)
# threshold1、2用于检测图像中明显的边缘
# apertureSize:Sobel算子的大小
# 参数(布尔值):
true:使用更精确的L2范数进行计算(即两个方向的倒数的平方和再开放)
false:使用L1范数(直接将两个方向导数的绝对值相加)。

cv2.Canny(image, threshold1, threshold2, [, edges[, apertureSize[, L2gradient]]])
"""

import cv2
import numpy as np
img2 = cv2.imread('2.jpg', cv2.IMREAD_GRAYSCALE)

v1 = cv2.Canny(img2, 160, 200)
v2 = cv2.Canny(img2, 0, 20)

res = np.hstack((v1, v2))
cv2.imshow('res', res)

cv2.waitKey(0)
cv2.destroyAllWindows()

霍夫圆

前提:边缘检测已经做完

  1. 把原图做一次Canny 边缘检测,得到边缘检测的二值图
  2. 对原始图像执行一次 Sobel 算子,计算出所有像素的邻域梯度值
  3. 初始化圆心空间 N(a, b),令所有的 N(a, b)=0。若要求圆心在图像中,则a,b值的范围分别对应图像的宽高,N(a,b)表示一共有a * b个
  4. 遍历 Canny 边缘二值图中的所有非零像素点,沿着梯度方向 (切线的垂直方向,根据Sobel 算子计算出的垂直梯度及水平梯度得来)固定搜索半径范围画线,将线段经过的所有累加器中的点 (a,b) 的 N(a,b)+=1。
  1. 统计排序 N(a,b),得到可能的圆心(N(a,b) 越大,越有可能是圆心

大白话:给定一个半径R,对于所有的白色点,都i会进行半径为R的画圆;颜色越深,越令人信服。

1
2
3
4
5
6
7
8
9
10
11
# Circles:用来存储HoughCircles的结果,类型为list,list中对象格式为x,y,r
# image:输入图像,即源图像,8位单通道图像,如果使用彩色图像,需要先转换成灰度图像
# method:定义检测图像中圆的方法。目前唯一实现的方法是cv2.HOUGH_GRADIENT
# dp:图像像素分辨率与参数空间分辨率的比值(官方文档上写的是图像分辨率与累加器分辨率的比值,它把参数空间认为是一个累加器,毕竟里面存储的都是经过的像素点的数量),dp=1,则参数空间与图像像素空间(分辨率)一样大,dp=2,参数空间的分辨率只有像素空间的一半大;#通过设置dp可以减少计算量
# minDist:检测到的圆中心(x,y)坐标之间的最小距离。如果minDist太小,则会保留大部分圆心相近的圆。如果minDist太大,则会将圆心相近的圆进行合并(若两圆心距离 < minDist,则认为是同一个圆)
# param1:canny 边缘检测的高阈值,低阈值被自动置为高阈值的一半,默认为 100
# param2:累加平面某点是否是圆心的判定阈值。大于该阈值才判断为圆。当值设置的很小是,检测到的圆越多。默认值为 100
# minRadius:半径的最小大小(以像素为单位)默认为 0
# maxRadius:半径的最大大小(以像素为单位)默认为 0

circles=cv2.HoughCircles(image, cv2.HOUGH_GRADIENT, dp=1, minDist=10, param1=None, param2=None, minRadius=None, maxRadius=None)

opencv —— HoughCircles 霍夫圆变换原理及圆检测 - 狂奔的小学生 - 博客园

opencv 十一 霍夫圆检测原理及高级使用案例(含优化步骤)-CSDN博客

像素重映射

重映射是从图像中的一个位置获取像素并将其放置在新图像中的另一位置的过程。

图像金字塔

图像金字塔是指一组图像且不同分辨率的子图集合,它是图像多尺度表达的一种,以多分辨率来解释图像的结构,主要用于图像的分割或压缩。

实际应用中需注意:

  • 金字塔层数:通常4-6层,过多会导致信息冗余。
  • 结构元素:高斯核大小影响平滑效果(常用5×5)

图像增强:分层调整细节与对比度
图像融合:无缝拼接不同图像
目标检测:多尺度定位物体
图像压缩:分层存储与渐进传输
噪声抑制与光照校正

  • 向下采样方法(缩小)
  • 向上采样方法(放大)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# pyrUp()放大图像,空白的像素点补0
# pyrDown()缩小图像,将奇数行和列去掉
import cv2
img2 = cv2.imread('2.jpg')

print(img2.shape)
up = cv2.pyrUp(img2)
print(up.shape)
down = cv2.pyrDown(img2)
print(down.shape)

cv2.waitKey(0)
cv2.destroyAllWindows()
# (360, 360, 3)
# (720, 720, 3)
# (180, 180, 3)
  • 拉普拉斯金字塔:lap = img2 - down

直方图与傅里叶变换

直方图

它是一种提高图像对比度的方法,拉伸图像灰度值范围

直方图的横轴表示亮度,从左到右表示亮度从低到高

直方图的纵轴表示像素数量,从下到上表示像素从少到多。

直方图在某个亮度区间的凸起越高,就表示在这个亮度区间内的像素越多

若直方图的凸起主要集中在左侧,则说明这张照片的亮度整体偏低

作用:

  • 图像分析与诊断

    • 亮度与对比度评估
    • 曝光检测
  • 图像增强

    • 直方图均衡化
  • 图像分割与阈值处理

    • 阈值选择
    • 掩膜区域分析
  • 颜色分析与处理

    • 通道分离统计
    • 颜色校正
  • 图像匹配与检索

    • 相似度比较
    • 特征提取

四种基本类型:

  • RGB直方图
  • 通道直方图
  • 明度直方图
  • 颜色直方图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
## images: 原图像图像格式为 uint8 或 float32。当传入函数时应 用中括号 [] 括来例如[img]
## channels: 同样用中括号括来它会告函数我们统幅图 像的直方图。如果入图像是灰度图它的值就是 [0]如果是彩色图像 的传入的参数可以是 [0][1][2] 它们分别对应着 BGR。
## mask: 掩模图像。统整幅图像的直方图就把它为 None。但是如 果你想统图像某一分的直方图的你就制作一个掩模图像并 使用它。
## histSize:BIN 的数目。也应用中括号括来
## ranges: 像素值范围常为 [0,255]

# cv2.calcHist(images,channels,mask,histSize,ranges)

import cv2
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
img2 = cv2.imread('2.jpg')
gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
hist = cv2.calcHist([gray], [0], None, [256], [0, 256])

plt.hist(img2.ravel(), 256)
plt.show()

cv2.waitKey(0)
cv2.destroyAllWindows()
# 可以看出这张图片的灰度颜色大部分接近于150左右

mask功能:一张图片有时候不需要统计整张图片,而是统计最中间或者做边界的一部分图片,那么就可以在参数中加入mask,原理是选中的部分为1,非选中的部分为0

1
2
3
4
5
6
7
8
# 创建mask
mask = np.zeros(img.shape[:2], np.uint8)
mask[100:150,100:150] = 255
plt.imshow(mask, 'gray')

img=cv2.imread('1', 0)
mask_img=cv2.bitwise_and(img, img, mask=mask)#图片之间与操作
plt.imshow(mask_img)

直方图均衡化

直方图均衡化会使图片突出的地方更加突出,可以增加图片的特征

计算原理:新灰度值 = 旧灰度值 * 累计概率

累计概率 = 当前灰度值的概率 + 小于当前灰度值的概率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# img必须是灰度图像
# equalizeHist(img)

import cv2
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt

img2 = cv2.imread('2.jpg', 0)
plt.hist(img2.ravel(), 256)
plt.show()

cv2.imshow("img2", img2)

equ = cv2.equalizeHist(img2)
plt.hist(equ.ravel(), 256)
plt.show()

cv2.imshow("equ", equ)

cv2.waitKey(0)
cv2.destroyAllWindows()

均衡化后的图片亮的地方更加亮,暗的地方更加暗,使图片的立体感更加强烈,但也有一定问题(自己搜吧)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import cv2
import numpy as np

img2 = cv2.imread('2.jpg', 0)
equ = cv2.equalizeHist(img2)

# 自适应直方图均衡化
# clipLimit颜色对比度的阈值, titleGridSize进行像素均衡化的网格大小,即在多少网格下进行直方图的均衡化操作
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
res_clahe = clahe.apply(img2)
res = np.hstack((img2, equ, res_clahe))
cv2.imshow('res', res)

cv2.waitKey(0)
cv2.destroyAllWindows()

直方图比较方法

  1. 首先把图像从RGB色彩空间转换为HSV色彩空间,cvtColor
  2. 计算图像的直方图,然后归一化到[0 ~ 1]之间,calcHist和normalize
  3. 使用下列四方法之一进行比较,compareHist
  • Correlation:相关性比较
  • Chi-Square:卡方比较
  • Intersection:十字交叉性
  • Bhattacharyya distance:巴氏距离

模板匹配

模板在原图像上从原点开始滑动,计算模板与(图像被模板覆盖的地方)的差别程度

假如原图形是AxB大小,而模板是axb大小,则输出结果的矩阵是(A-a+1)x(B-b+1)

1
2
3
4
5
6
7
8
9
10
11
import cv2
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt

img1 = cv2.imread('1.jpg', 0)
template = cv2.imread('2.jpg', 0)
h, w = template.shape[:2]
plt.subplot(121),plt.imshow(img,'gray'),plt.title("img")
plt.subplot(122),plt.imshow(template,'gray'),plt.title("template")
plt.show()
  • TM_SQDIFF:计算平方不同,计算出来的值越小,越相关
  • TM_CCORR:计算相关性,计算出来的值越大,越相关
  • TM_CCOEFF:计算相关系数,计算出来的值越大,越相关
  • TM_SQDIFF_NORMED:计算归一化平方不同,计算出来的值越接近0,越相关
  • TM_CCORR_NORMED:计算归一化相关性,计算出来的值越接近1,越相关
  • TM_CCOEFF_NORMED:计算归一化相关系数,计算出来的值越接近1,越相关
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import cv2
import numpy as np
img_rgb=cv2.imread("mario.jpg")
img_gray=cv2.cvtColor(img_rgb,cv2.COLOR_BGR2GRAY)
template=cv2.imread('mario_coin.jpg',0)
h,w=template.shape[:2]
res=cv2.matchTemplate(img_gray,template,cv2.TM_CCOEFF_NORMED)
threshold=0.8
loc=np.where(res>=threshold)
for pt in zip(*loc[::-1]):
bottom_right=(pt[0]+w,pt[1]+h)
cv2.rectangle(img_rgb,pt,bottom_right,(255,255,0),2)
cv2.imshow("img_rgb",img_rgb)
cv2.waitKey(0)

图像轮廓

1
2
3
4
5
6
7
8
9
10
11
12
13
# mode:轮廓检索模式
# RETR_EXTERNAL:只检索最外面的轮廓;
# RETR_LIST:检索所有的轮廓,并将其保存到一条链表当中;
# RETR_CCOMP:检索所有的轮廓,并将他们组织为两层:顶层是各部分的外部边界,第二层是空洞的边界;
# RETR_TREE:检索所有的轮廓,并重构嵌套轮廓的整个层次;
# method:轮廓逼近方法
# CHAIN_APPROX_NONE:以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列)。
# CHAIN_APPROX_SIMPLE:压缩水平的、垂直的和斜的部分,也就是函数只保留他们的终点部分。

## cv2.findContours(img, mode, method)

# 绘制轮廓
## cv2.drawContours
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import cv2
img2 = cv2.imread('2.jpg')
# 转灰度
gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
# 取色阈
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 找边缘
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
draw_img = img2.copy()
# 绘制边缘,-1表示显示所有轮廓
res = cv2.drawContours(draw_img, contours, -1, (0, 0, 255), 2)
cv2.imshow('res', res)

cv2.waitKey(0)
cv2.destroyAllWindows()

轮廓特征

1
2
3
4
5
6
# 书接上回,这里可以用于计算符合面积或者周长的各种形状
cnt = contours[0]
#面积
cv2.contourArea(cnt)
#周长,True表示闭合的
cv2.arcLength(cnt,True)

轮廓周围绘制矩形

用最少的线来代替,在几个点之间的直线,如果能用更少的点来表示,就采用更少的点

1
2
3
4
5
6
7
# approxPolyDP
epsilon = 0.15 * cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt,epsilon,True)

draw_img2 = img2.copy()
res = cv2.drawContours(draw_img, [approx], -1, (0, 0, 255), 2)
cv.imshow('res', res)

凸包(Convex Hull)

图像矩

OpenCV-Python快速入门(十一):图像矩_python图像的矩-CSDN博客

点多边形测试

1
cv2.pointPolygonTest

傅里叶变换

高频:变化剧烈的灰度分量,例如边界
低频:变化缓慢的灰度分量,例如一片大海

滤波
低通滤波器:只保留低频,会使得图像模糊
高通滤波器:只保留高频,会使得图像细节增强

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import cv2
import numpy as np
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt

img2 = cv2.imread('2.jpg', 0)
# 先转换成np.float32 格式
img_float32 = np.float32(img2)
# 傅里叶变换
dft = cv2.dft(img_float32, flags=cv2.DFT_COMPLEX_OUTPUT)
# 转换到中心位置
dft_shift = np.fft.fftshift(dft)
# 得到灰度图能表示的形式
magnitude_spectrum = 20 * np.log(cv2.magnitude(dft_shift[:, :, 0], dft_shift[:, :, 1]))

plt.subplot(121), plt.imshow(img2, cmap='gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(magnitude_spectrum, cmap='gray')
plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
plt.show()

cv2.waitKey(0)
cv2.destroyAllWindows()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import numpy as np
import cv2
from matplotlib import pyplot as plt

img = cv2.imread('1.jpg',0)

img_float32 = np.float32(img)

dft = cv2.dft(img_float32, flags = cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)

rows, cols = img.shape
crow, ccol = int(rows/2) , int(cols/2)

# 低通滤波
mask = np.zeros((rows, cols, 2), np.uint8)
mask[crow-30:crow+30, ccol-30:ccol+30] = 1

## # 高通滤波
## mask = np.ones((rows, cols, 2), np.uint8)
## mask[crow-30:crow+30, ccol-30:ccol+30] = 0

# IDFT
fshift = dft_shift*mask
f_ishift = np.fft.ifftshift(fshift)
img_back = cv2.idft(f_ishift)
img_back = cv2.magnitude(img_back[:,:,0],img_back[:,:,1])

plt.subplot(121),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(img_back, cmap = 'gray')
plt.title('Result'), plt.xticks([]), plt.yticks([])

plt.show()

项目

本科参与的一个项目,其实不难。

描述:给定一个绿色圆形标靶,测出其与相机的角度和距离;再给电机反馈这些参数。

相机标定法,借助matlab以及相机对着棋盘进行标定,获得如下参数。

**相机内参:**焦距(fx,fy);主点坐标(cx,cy);畸变系数(径向畸变k1,k2,k3和切向畸变p1,p2)

相机内参:旋转矩阵(3x3);平移向量(3x1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import cv2
import numpy as np
import math
import serial
import time

# -------------------- 1. 初始化相机和标定参数 --------------------
# 相机内参(假设已通过标定获得)
camera_matrix = np.array([
[800, 0, 320], # fx, fy为焦距(像素单位),cx, cy为主点坐标
[0, 800, 240],
[0, 0, 1]
])
dist_coeffs = np.array([0.12, -0.05, 0.0005, 0.0005, 0.0]) # 畸变系数

# 标靶实际半径(单位:米)
TARGET_RADIUS = 0.5

# 初始化摄像头
cap = cv2.VideoCapture(0) # 0为默认摄像头
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)

# -------------------- 2. 初始化串口(USART) --------------------
# 根据实际串口修改
ser = serial.Serial('COM3', 115200, timeout=1) # 波特率需与电机控制器匹配
time.sleep(2) # 等待串口初始化

# -------------------- 3. 主循环:检测标靶并计算参数 --------------------
while True:
ret, frame = cap.read()
if not ret:
break

# 畸变校正
undistorted_frame = cv2.undistort(frame, camera_matrix, dist_coeffs)

# --- 检测绿色圆形标靶(最大圆)---
# 转换为HSV颜色空间提取绿色区域
hsv = cv2.cvtColor(undistorted_frame, cv2.COLOR_BGR2HSV)
lower_green = np.array([35, 50, 50]) # HSV绿色范围下限
upper_green = np.array([85, 255, 255]) # HSV绿色范围上限
# 颜色或亮度阈值分割,取绿色,过滤
mask = cv2.inRange(hsv, lower_green, upper_green)

# 形态学处理(去噪)
# 闭运算,先膨胀,再腐蚀 --- > 填充目标内部的小孔或空洞、平滑目标边界、连接断裂的物体边缘
kernel = np.ones((5, 5), np.uint8)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)

# 霍夫圆变换检测所有圆形
circles = cv2.HoughCircles(mask, cv2.HOUGH_GRADIENT, dp=1, minDist=50,
param1=50, param2=30, minRadius=10, maxRadius=200)

if circles is not None:
# 筛选最大圆(假设标靶是最大的绿色圆形)
circles = np.uint16(np.around(circles))
largest_circle = max(circles[0, :], key=lambda x: x[2]) # 按半径排序
x, y, r = largest_circle

# 绘制检测结果
cv2.circle(undistorted_frame, (x, y), r, (0, 255, 0), 2)
cv2.circle(undistorted_frame, (x, y), 2, (0, 0, 255), 3)

# --- 计算距离 ---
# 相似三角形原理:distance = (fx * 实际半径) / 检测到的像素半径
distance = (camera_matrix[0, 0] * TARGET_RADIUS) / r

# --- 计算角度(偏航角和俯仰角)---
# 像素坐标转换为以光心为原点的坐标
u = x - camera_matrix[0, 2] # 水平偏移(x方向)
v = camera_matrix[1, 2] - y # 垂直偏移(y轴向下,取反)

# 计算角度(弧度)
theta_x = math.atan2(u, camera_matrix[0, 0]) # 偏航角(水平)
theta_y = math.atan2(v, camera_matrix[1, 1]) # 俯仰角(垂直)

# 转换为角度制
angle_x_deg = math.degrees(theta_x)
angle_y_deg = math.degrees(theta_y)

# --- 显示结果 ---
cv2.putText(undistorted_frame, f"Distance: {distance:.2f}m", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
cv2.putText(undistorted_frame, f"Angle X: {angle_x_deg:.2f}deg", (10, 60),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)

# --- 通过USART发送数据 ---
# 格式:角度(deg),距离(m)\n(如"15.30,2.45\n")
command = f"{angle_x_deg:.2f},{distance:.2f}\n".encode()
ser.write(command)
print(f"Sent to motor: {command.decode().strip()}")

# 显示实时画面
cv2.imshow("Target Detection", undistorted_frame)
if cv2.waitKey(1) == 27: # ESC键退出
break

# -------------------- 4. 释放资源 --------------------
cap.release()
cv2.destroyAllWindows()
ser.close()