Skip to content

Matplotlib

plt.tight_layout() 是一个 Matplotlib 库中的函数,用于自动调整图像的子图参数以保证子图适应图像区域,以及子图之间的间隔合适,避免重叠或过分拥挤的布局。

中文路径

读取图片

python
# 读取图像,解决imread不能读取中文路径的问题
def cv_imread(file_path):
    cv_img = cv.imdecode(np.fromfile(file_path, dtype=np.uint8), -1)
    return cv_img

img1 = cv.imdecode(np.fromfile(imgFile, dtype=np.uint8), -1)
cv.imshow('demo', img1)
python
#带有中文的保存文件路径
saveFile2 = "D:/images/测试62.jpg" 
img_write = cv2.imencode(".jpg",img1)[1].tofile(saveFile2)

裁剪图片

python
import cv2 as cv
import numpy as np

imgFile = "C:/Users/diyworld/Desktop/work/images/3.PNG"
img = cv.imread(imgFile, 1)

x, y, w, h = 0, 100, 500, 200
imgCrop = img[y:y+h, x:x+w].copy()

cv.imshow("img", img)
cv.imshow("imgCrop", imgCrop)

cv.waitKey(0)
cv.destroyAllWindows()
python
# 选择区域并空格
roi = cv.selectROI(img, showCrosshair=True, fromCenter=False)
x, y, w, h = roi
imgSelectCrop = img[y:y+h, x:x+w].copy()
cv.imshow("img", img)
cv.imshow("imgCrop", imgCrop)
  • 如果使用 imgCrop = img[y:y+h, x:x+w],然后修改imgCrop,也会同时修改原始图像img的相应部分。这是因为imgCrop实际上是对原始图像的引用。
  • 如果使用 imgCrop = img[y:y+h, x:x+w].copy(),然后修改imgCrop,不会影响原始图像img,因为copy()创建了imgCrop的副本,而不是引用。

在OpenCV中,图像的索引是以行(y坐标)和列(x坐标)的方式进行的,因此在使用img[y:y+h, x:x+w]时,y先于x,这是由OpenCV的图像坐标系约定决定的。(修改图片大小 resize 依旧是x y。

通常,图像处理中使用的坐标系与常见的数学坐标系略有不同,它将图像的左上角定义为坐标原点(0,0),y轴向下延伸,x轴向右延伸。这意味着y坐标表示图像中的行(垂直方向),x坐标表示图像中的列(水平方向)。因此,在使用OpenCV裁剪图像时,你会先指定y坐标范围,然后是x坐标范围。

拼接

python
img1 = cv.imread("C:/Users/diyworld/Desktop/work/images/Lena.jpg")
img2 = cv.imread("C:/Users/diyworld/Desktop/work/images/Fruits.jpg")

img1 = cv.resize(img1, (400, 400))
img2 = cv.resize(img2, (300, 400))
img3 = cv.resize(img2, (400, 300))

imgH = np.hstack((img1, img2))
imgV = np.vstack((img1, img3))

横向拼接 hstack 必须高度一样,纵向拼接 vstack 必须宽度一样。

显示图片

python
plt.imshow(cv.cvtColor(img1, cv.COLOR_BGR2RGB))

转换opencv默认的 BGR 到 RGB 。

图片加法

python
img1 = cv.imread("C:/Users/diyworld/Desktop/work/images/Lena.jpg")
img2 = cv.imread("C:/Users/diyworld/Desktop/work/images/Baboon.jpg")
Mask = np.zeros((img1.shape[0], img1.shape[1]), dtype=np.uint8)
x,y,w,h = 100,100,200,200
Mask[y:y+h, x:x+w] = 255
imgAddMask1 = cv.add(img1, img2, mask=Mask)
imgAddMask2 = cv.add(img1, np.zeros(np.shape(img1), dtype=np.uint8), mask=Mask)
cv.imshow('MaskImage', Mask)
cv.imshow('imgAddMask1', imgAddMask1)
cv.imshow('imgAddMask2', imgAddMask2)

cv.add 函数是OpenCV中用于执行两个图像的像素级加法操作的函数。它将两个输入图像的对应像素相加,并可以使用一个可选的掩码图像来限制操作。

函数的参数如下:

  1. src1:第一个输入图像。
  2. src2:第二个输入图像。
  3. mask:可选的掩码图像。如果提供了掩码图像,只有在掩码图像中对应位置的像素值为非零时,才会执行相应位置的加法操作。

cv2.resize

python
cv.resize(img, (200, 300), fx=0.2, fy=0.8)

在OpenCV的cv2.resize函数中,以下参数是必须的:

  1. src(MatLike类型):表示输入图像,可以是原始图像的文件路径、numpy数组等。
  2. dsize(Size类型或None):表示目标图像的大小。如果指定了目标图像的大小,就使用这个大小进行缩放。可以传入一个二元元组,如(width, height),或者设置为None,此时必须指定缩放因子 fxfy。fx 和 fy 是沿着 x 轴和 y 轴的缩放因子。如果你指定了 dsize,那么 fx 和 fy 会被忽略。如果你指定了 fx 和 fy,那么 dsize 会被计算出来。

仿射平移

python
h, w = img.shape[:2]  # 输出图像大小
M = np.float32([[1, 0, 300], [0, 1, -200]])
imageMove = cv.warpAffine(img, M, (w, h))

cv2.warpAffine 函数的第二个参数 M 是一个2x3的仿射变换矩阵,用于定义图像的平移、旋转、缩放等变换操作。这个矩阵的形式如下:

codeM = |  a   b   c |
        |  d   e   f |

其中,a 和 e 控制缩放和旋转,c 和 f 控制平移。具体来说:

  • a 缩放因子(沿x轴方向)
  • b 剪切因子(沿x轴方向)
  • c 平移因子(沿x轴方向)
  • d 剪切因子(沿y轴方向)
  • e 缩放因子(沿y轴方向)
  • f 平移因子(沿y轴方向)

对于平移操作,通常情况下只需要设置 cf,分别表示沿x轴和y轴的平移量。其他元素可以保持为默认值,例如:

codeM = |  1   0   tx |
        |  0   1   ty |

其中,tx 表示沿x轴的平移量,ty 表示沿y轴的平移量。

M矩阵计算过程

M 矩阵的形式如下:

python
codeM = |  a   b   c |
        |  d   e   f |

具体地,对于图像中的每个像素位置 (x, y),我们应用矩阵变换,得到新的位置 (x', y')

python
x' = a*x + b*y + c
y' = d*x + e*y + f

cv.threshold

cv.threshold 是 OpenCV 库中用于图像阈值处理的函数,用于将图像分割为两个区域(例如,前景和背景)基于指定的阈值。函数的语法为:

python
retval, threshold = cv.threshold(src, thresh, maxval, type[, dst])
  • src: 输入图像,单通道图像(灰度图像)。
  • thresh: 阈值,用于将图像分割为两个区域。
  • maxval: 当像素值高于(或低于,取决于 type)阈值时,像素所赋的值。
  • type: 阈值处理类型,指定如何应用阈值。常用的类型包括:
    • cv.THRESH_BINARY: 如果像素值大于阈值,将像素设置为 maxval,否则为 0。
    • cv.THRESH_BINARY_INV: 如果像素值大于阈值,将像素设置为 0,否则为 maxval
    • cv.THRESH_TRUNC: 如果像素值大于阈值,将像素值截断为阈值。
    • cv.THRESH_TOZERO: 如果像素值小于阈值,将像素值设置为 0,否则不改变。
    • cv.THRESH_TOZERO_INV: 如果像素值小于阈值,将像素值设置为 0,否则不改变。
  • retval: 返回的阈值,通常不用。
  • threshold: 输出的二值化图像。
python
cv.threshold(imgGray, 127, 255, cv.THRESH_BINARY)
  • imgGray 是输入的灰度图像。
  • 127 是阈值,用于将图像分成两部分。
  • 255 是 maxval,表示当像素值高于阈值时,赋予的像素值。
  • cv.THRESH_BINARY 表示采用二值化阈值处理,即将像素值大于阈值的设置为 maxval(255),小于阈值的设置为 0。

这样就实现了将灰度图像二值化,分成了前景和背景两个区域,使得图像中的目标更容易被提取或分割出来。

THRESH_OTSU

这行代码使用了 OpenCV 的 cv.threshold 函数对图像进行阈值处理,同时结合了 Otsu's 二值化方法。

python
cv.threshold(img, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
  • img: 输入图像。
  • 0: 阈值。在 Otsu's 方法中不会使用该值,因此可以设为 0。
  • 255: 当像素值高于阈值时,像素所赋的值。
  • cv.THRESH_BINARY + cv.THRESH_OTSU: 结合了两种阈值处理方式。
    • cv.THRESH_BINARY: 如果像素值大于阈值,则将像素设置为 255,否则设置为 0
    • cv.THRESH_OTSU: 使用 Otsu's 方法确定阈值。Otsu's 方法是一种自动确定阈值的技术。该方法利用图像的直方图,尝试找到一个阈值,将图像分成两个类别(一般是前景和背景),以使类别内的方差最小。通过最小化类别内方差,Otsu's 方法确定了一个适合的全局阈值。这个阈值可以使得前景和背景之间的对比度最大化,从而在二值化图像中获得清晰的目标轮廓。
    • 结合了 cv.THRESH_BINARYcv.THRESH_OTSU 两种阈值处理方式。

Otsu's 方法是一种自动确定阈值的技术,它基于图像的直方图,试图找到一个阈值,将图像分成两个类别(一般是前景和背景),使得类别内的方差最小。这种方法通常用于图像分割,特别是在目标和背景的对比度不明显时非常有效。

adaptiveThreshold

cv.adaptiveThreshold 是 OpenCV 中用于自适应阈值处理的函数,它可以根据图像的局部特征自动调整阈值,而不是使用全局固定的阈值。这对于不同区域亮度或对比度不一致的图像非常有用。函数的语法如下:

pythonCopy code
thresholded_img = cv.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C)
  • src: 输入的单通道图像(一般是灰度图像)。
  • maxValue: 当像素值高于阈值时,像素所赋的值。
  • adaptiveMethod: 自适应阈值计算方法。可以是以下两个值之一:
    • cv.ADAPTIVE_THRESH_MEAN_C: 基于邻域均值的自适应阈值。
    • cv.ADAPTIVE_THRESH_GAUSSIAN_C: 基于邻域加权平均值的自适应阈值,权重由高斯窗口确定。
  • thresholdType: 阈值类型。在这里一般使用二进制阈值,可以是以下两个值之一:
    • cv.THRESH_BINARY: 如果像素值大于阈值,将像素设置为 maxValue,否则为 0。
    • cv.THRESH_BINARY_INV: 如果像素值大于阈值,将像素设置为 0,否则为 maxValue
  • blockSize: 邻域大小,用于计算阈值。它决定了在每个像素周围计算阈值时考虑的邻域大小。
  • C: 从计算得到的阈值中减去的常数。

轮廓

cv2.findContours

返回值

contours:是返回的轮廓。这个轮廓是一个数组。

hierarchy:是轮廓的层次信息,就是mode参数决定的返回的轮廓数据的组织结构。

len(contours)返回的就是检测到了几个轮廓。

len(contours[i])就是第i个轮廓长度,就是它有多少个像素点。

contours[i].shape返回的就是轮廓内点的形状,比如(4, 1, 2)就表示轮廓i有4个轮廓点, 每个点是1行2列。

如下所示: [[[79,270]]

​ [[79,383]]

​ [[195,383]]

​ [[195, 270]]

这其实就是一个方框轮廓的4个点的坐标值。

contours[i].shape 返回 (4, 1, 2) 是因为在 OpenCV 中,每个轮廓表示为一个多边形,即一个封闭的曲线。该多边形包含多个点,这些点的坐标被存储在 contours[i] 中。

contours[i] 是一个 Numpy 数组,它的形状 (4, 1, 2) 可以解释如下:

  • 第一个维度 (4):表示轮廓中点的数量。在这种情况下,轮廓有 4 个点。
  • 第二个维度 (1):通常在这里存储子轮廓的索引,但大多数情况下,contours[i] 中的值都是 1,表示没有子轮廓。
  • 第三个维度 (2):表示每个点的坐标维度。在这里,每个点由 (x, y) 坐标表示,所以有 2 个坐标维度。

所以,(4, 1, 2) 表示轮廓中有 4 个点,每个点有 (x, y) 坐标。这种形状对于表示封闭轮廓是很常见的,其中每个点都是轮廓的一部分,用于描述轮廓的形状。如果你需要访问每个点的坐标,你可以使用索引 contours[i][j],其中 j 可以是 0 到 3,分别代表第一个点到第四个点。

创建窗体 namedWindow()

namedWindow() 用于创建一个窗口,可以指定窗口的属性,如是否可调整大小。

python
import cv2
import numpy as np

# 创建一个可调整大小的窗口
cv2.namedWindow("自定义窗口", cv2.WINDOW_NORMAL)
# cv2.namedWindow("固定大小窗口", cv2.WINDOW_AUTOSIZE) # 默认行为

# 为了能看到窗口,我们通常会显示一张图片
dummy_image = np.zeros((200, 300, 3), dtype=np.uint8)
cv2.imshow("自定义窗口", dummy_image)

cv2.waitKey(0)
cv2.destroyAllWindows()

设置窗体大小 resizeWindow()

resizeWindow() 用于调整指定窗口的大小。窗口必须是用 cv2.WINDOW_NORMAL 标志创建的。

python
import cv2
import numpy as np

window_name = "可调整大小的窗口"
cv2.namedWindow(window_name, cv2.WINDOW_NORMAL) # 必须是 WINDOW_NORMAL
cv2.resizeWindow(window_name, 600, 400) # 设置宽度600, 高度400

dummy_image = np.zeros((400, 600, 3), dtype=np.uint8) # 创建一个匹配新大小的图像
cv2.imshow(window_name, dummy_image)

cv2.waitKey(0)
cv2.destroyAllWindows()

显示窗体 imshow()

imshow() 用于在指定的窗口中显示图像。如果窗口不存在,它会自动创建一个。

python
import cv2
import numpy as np

# 加载或创建一个图像
image_to_show = np.random.randint(0, 256, (300, 500, 3), dtype=np.uint8) # 随机彩色图像

# 显示图像,窗口名为 "图像显示"
cv2.imshow("图像显示", image_to_show)

cv2.waitKey(0) # 等待按键
cv2.destroyAllWindows() # 关闭窗口

等待用户输入 waitKey()

waitKey() 等待指定的毫秒数,看是否有键盘输入。如果参数为0,则无限等待。它返回按键的ASCII码,或者在超时或无按键时返回-1。

python
import cv2
import numpy as np

img = np.zeros((200, 300, 3), dtype=np.uint8)
cv2.imshow("等待输入窗口", img)

print("请按任意键继续,或按 'q' 键退出...")
key = cv2.waitKey(0) # 无限等待

if key == ord('q'): # ord('q') 获取 'q' 的ASCII码
    print("按下了 'q' 键,正在退出。")
elif key == -1:
    print("没有按键被按下 (这在 waitKey(0) 中不应该发生,除非窗口被外部关闭)")
else:
    print(f"按下了键的ASCII码: {key} (字符: {chr(key)})")

cv2.destroyAllWindows()

摧毁窗体 destroyAllWindows() / destroyWindow()

destroyAllWindows() 关闭所有由OpenCV创建的窗口。destroyWindow("窗口名") 关闭指定的窗口。

python
import cv2
import numpy as np

img1 = np.zeros((100, 150, 3), dtype=np.uint8)
img2 = np.ones((100, 150, 3), dtype=np.uint8) * 255

cv2.imshow("窗口1", img1)
cv2.imshow("窗口2", img2)

print("按任意键关闭 '窗口1'...")
cv2.waitKey(0)
cv2.destroyWindow("窗口1") # 只关闭窗口1

print("按任意键关闭所有剩余窗口...")
cv2.waitKey(0)
cv2.destroyAllWindows() # 关闭所有窗口 (此时是窗口2)

保存图片 imwrite(path, 图像变量)

imwrite() 用于将图像数据保存到文件。文件格式由扩展名决定(如 .jpg, .png)。

python
import cv2
import numpy as np

# 创建一个示例图像 (例如,一个带有红色正方形的蓝色背景图像)
image_to_save = np.full((200, 300, 3), (255, 0, 0), dtype=np.uint8) # BGR: 蓝色背景
cv2.rectangle(image_to_save, (50, 50), (150, 150), (0, 0, 255), -1) # 红色正方形

# 保存图像
# 注意:路径中不要使用中文或特殊字符,如果必须,请考虑其他方法或确保系统编码正确
file_path_png = "saved_image.png"
file_path_jpg = "saved_image.jpg"

success_png = cv2.imwrite(file_path_png, image_to_save)
success_jpg = cv2.imwrite(file_path_jpg, image_to_save, [cv2.IMWRITE_JPEG_QUALITY, 90]) # 对于jpg可以指定质量

if success_png:
    print(f"图像已成功保存为 {file_path_png}")
else:
    print(f"保存图像到 {file_path_png} 失败")

if success_jpg:
    print(f"图像已成功保存为 {file_path_jpg}")
else:
    print(f"保存图像到 {file_path_jpg} 失败")

# 验证 (可选)
# loaded_image = cv2.imread(file_path_png)
# if loaded_image is not None:
#     cv2.imshow("Loaded Image", loaded_image)
#     cv2.waitKey(0)
#     cv2.destroyAllWindows()

截取图像 img[Y轴范围, X轴范围]

OpenCV (Numpy) 中图像的截取(ROI - Region of Interest)使用Numpy的切片语法。注意顺序是 [startY:endY, startX:endX]

python
import cv2
import numpy as np

# 假设 GrayImg 是一个已加载的灰度图像
# 为演示,我们创建一个
GrayImg = np.random.randint(0, 256, (600, 800), dtype=np.uint8) # 高600, 宽800

# 截取图像:从y=100到y=499,从x=0到x=199
# 100:500 表示从索引100开始到499结束 (不包括500)
# 0:200 表示从索引0开始到199结束 (不包括200)
NewGrayImg = GrayImg[100:500, 0:200]

cv2.imshow("Original Gray Image", GrayImg)
cv2.imshow("Cropped Image (NewGrayImg)", NewGrayImg)
cv2.waitKey(0)
cv2.destroyAllWindows()

图像色阶值修改 (亮度调整)

cv2.add() 可以用来增加图像的亮度。如果像素值相加超过255,则截断为255。

python
import cv2
import numpy as np

# 假设 img 是一个已加载的图像
# 为演示,我们创建一个
img = np.random.randint(0, 150, (300, 400, 3), dtype=np.uint8) # 随机较暗图像

# 增加亮度的值 (可以是标量或与img同尺寸的数组)
brightness_increase_value = 50

# 使用 cv2.add 增加亮度
# 注意:img 和 brightness_increase_value (如果是数组) 必须具有相同的尺寸和类型,
# 或者 brightness_increase_value 是一个标量。
brighter_img = cv2.add(img, brightness_increase_value)

# cv2.add(图像1, 图像2): 将两个图像的像素值相加。
# 如果 图像1像素值 + 图像2像素值 > 255,则结果为 255。
# 这通常用于增加亮度或合并特征,但大面积增加可能导致过曝。

cv2.imshow("Original Image", img)
cv2.imshow("Brighter Image", brighter_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

图像合并 addWeighted(图形1, 权重1, 图形2, 权重2, Gamma)

addWeighted() 用于按权重合并两个图像。 公式: dst = src1*alpha + src2*beta + gamma

  • 图像1: 想合并的第一个图像
  • 权重1 (alpha): 组合成新的图像中,图像1的比例权重。
  • 图像2: 想合并的第二个图像
  • 权重2 (beta): 组合成新的图像中,图像2的比例权重。
    • 通常 权重1 + 权重2 = 1.0 (例如,alpha=0.7, beta=0.3)
  • Value (gamma): 合并之后的图像的亮度/偏置,添加到每个像素值上。
python
import cv2
import numpy as np

# 创建两个示例图像 (确保它们大小相同)
img1 = np.random.randint(0, 256, (300, 400, 3), dtype=np.uint8)
# 为了使img2与img1大小相同:
img2 = cv2.resize(np.random.randint(0,256, (200,250,3), dtype=np.uint8), (img1.shape[1], img1.shape[0]))


alpha = 0.6  # 图像1的权重
beta = 0.4   # 图像2的权重 (通常 alpha + beta = 1)
gamma = 10   # 亮度调整值

blended_image = cv2.addWeighted(img1, alpha, img2, beta, gamma)

cv2.imshow("Image 1", img1)
cv2.imshow("Image 2", img2)
cv2.imshow("Blended Image", blended_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

视频流 VideoCapture(索引 or 路径)

摄像头捕获

如果是视频文件,将摄像头的索引 (通常是0, 1, ...) 修改为视频文件的路径即可。 cv2.waitKey(1000//30): 如果用户按下了 'q' 键则结束。对键盘事件进行 delay(ms) 的等待(delay=0 则为无限等待),若触发则返回该按键的ASCII码(否则返回-1)。1000//30 约等于每秒30帧的延迟。

python
import cv2

# 0 通常是默认摄像头
cap = cv2.VideoCapture(0)
# 或者视频文件: cap = cv2.VideoCapture("path_to_your_video.mp4")

if not cap.isOpened():
    print("无法打开摄像头或视频文件")
    exit()

while True:
    # ret 是一个布尔值,表示是否成功读取到帧
    # frame 是读取到的视频帧 (一个Numpy数组)
    ret, frame = cap.read()

    if not ret:
        print("无法读取帧 (视频可能已结束或出错)")
        break

    # 在这里可以对 frame 进行处理
    # 例如,转换为灰度图像
    # gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    # cv2.imshow("Grayscale Video", gray_frame)

    cv2.imshow("Live Video", frame)

    # 等待大约 33ms (对应约 30fps)。如果按下 'q' 键,则退出循环。
    # waitKey 返回按键的ASCII码,& 0xFF 是为了兼容64位系统
    if cv2.waitKey(33) & 0xFF == ord('q'):
        break

# 释放摄像头资源
cap.release()
# 关闭所有OpenCV窗口
cv2.destroyAllWindows()

写入视频

VideoWriter 用于将一系列帧保存为视频文件。需要指定输出文件名、FourCC编解码器、帧率(fps)和帧大小。

python
import cv2

# 打开摄像头
cap = cv2.VideoCapture(0)
if not cap.isOpened():
    print("无法打开摄像头")
    exit()

# 获取视频帧的宽度和高度
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = 20.0 # 希望的输出帧率

# 定义编解码器并创建VideoWriter对象
# FourCC是一个4字节码,用于指定视频编解码器。
# 常见的有: 'XVID', 'MJPG', 'MP4V', 'DIVX', 'H264' (可用性取决于系统)
# 对于 .avi 文件, 'XVID' 或 'MJPG' 常用
# 对于 .mp4 文件, 'MP4V' (MPEG-4) 或 'X264' (H.264) 常用
fourcc = cv2.VideoWriter_fourcc(*'XVID') # AVI
# fourcc = cv2.VideoWriter_fourcc(*'MP4V') # MP4 (可能需要ffmpeg支持)
out = cv2.VideoWriter('output_video.avi', fourcc, fps, (frame_width, frame_height))
# out = cv2.VideoWriter('output_video.mp4', fourcc, fps, (frame_width, frame_height))


print("正在录制... 按 'q' 停止。")
while cap.isOpened():
    ret, frame = cap.read()
    if not ret:
        print("无法读取帧")
        break

    # 如果需要,可以在这里对帧进行处理
    # frame = cv2.flip(frame, 1) # 例如,水平翻转

    # 将帧写入输出文件
    out.write(frame)

    cv2.imshow('Recording Frame', frame)

    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# 释放所有资源
cap.release()
out.release()
cv2.destroyAllWindows()
print("录制完成,视频已保存为 output_video.avi (或 .mp4)")

鼠标事件 setMouseCallback(窗口, 事件回调函数, 传递给回调的数据)

OpenCV 支持对窗口上的鼠标动作做出响应。

  • setMouseCallback(窗口的名称, 回调函数, 传递给回调函数的参数)
  • 自定义回调函数格式: callback(event, x, y, flags, userdata)
    • event: 鼠标事件类型 (如 cv2.EVENT_LBUTTONDOWN)
    • x, y: 鼠标指针在窗口中的坐标
    • flags: 组合键状态 (如 cv2.EVENT_FLAG_CTRLKEY)
    • userdata: setMouseCallback 中传递的额外参数
python
import cv2
import numpy as np

# 示例代码已在下方给出,这里仅为概念图的替代
# 这个图可能展示了鼠标点击在窗口上触发事件的流程
# +-----------------+
# |   OpenCV Window |
# |                 |
# |    (x,y)        | ----> Mouse Click ----> Callback Function
# |     *           |                           (event, x, y, flags, userdata)
# |                 |                               |
# +-----------------+                               V
#                                           Process Event (e.g., draw circle)
#

# 第二张图可能展示了回调函数内部的逻辑
# def callback(event, x, y, flags, userdata):
#     if event == cv2.EVENT_LBUTTONDOWN:
#         # Draw a circle at (x,y)
#         cv2.circle(img, (x,y), 5, (0,255,0), -1)
#     print(f"Event: {event}, Pos: ({x},{y}), Flags: {flags}, Userdata: {userdata}")
python
# opencv是支持我们对窗口桑的鼠标动作做出响应
# setMouseCallback(窗口的名称,回调函数,给予回调函数的参数)
# cv2.setMouseCallback()
# 自定义的方法 事件 坐标  组合键  传递的参数
# callback(event,X,Y,   flags,userdata)
import cv2
# numpy是数据分析三大基础包之一  快速创建数组
import numpy as np

Flag = True
# 声明一个方法 作为回调函数
# 函数名称是随意的  参数名称也随意 参数必须5个
def C(event,x,y,flags,data):
    print(event,x,y,flags,data)
    # 回调函数中可以对于当前鼠标事件进行添加及判断
    # 添加你想执行的事件时 对于event鼠标类型进行判断
    # 当按下鼠标右键且鼠标移动时 关闭窗口
    # flags是组合键中先触发的值
    # if event == 0 and flags == 2: # event==0 is MOUSEMOVE, flags==2 is RBUTTONDOWN
    if event == cv2.EVENT_MOUSEMOVE and flags == cv2.EVENT_FLAG_RBUTTON:
        cv2.destroyAllWindows()
        global Flag
        Flag= False
    elif event == cv2.EVENT_LBUTTONDOWN:
        # 在点击位置画一个点
        cv2.circle(img, (x,y), 3, (0,255,0), -1)


# 创建窗口
cv2.namedWindow("Mouse",cv2.WINDOW_NORMAL)
cv2.resizeWindow("Mouse",640,480)

# 设置鼠标的回调函数  也可以理解绑定鼠标事件
cv2.setMouseCallback("Mouse",C,"这个位置是用来传递参数的")

# 需要创建一个图形  [R,G,B]
# 生成一个全黑的图像 0 矩阵的 行 列
img = np.zeros((480,640,3),np.uint8)# int8 0~255 u无符号的, 3通道BGR

while Flag:
    cv2.imshow("Mouse",img)
    key = cv2.waitKey(1)
    if key == ord('q'):
        break
    # Flag might be set to False by the callback
    if not Flag:
        break

cv2.destroyAllWindows()

绘制图形

画线 line(img, pt1, pt2, color[, thickness[, lineType[, shift]]])

python
import cv2
import numpy as np

# img = cv2.imread('./3.png') # 替换为你的图片路径
# 为演示,创建一个黑色背景
img = np.zeros((500, 500, 3), dtype=np.uint8)

# 画线
# img表示绘制在那个图形上方,
# pt1 point1起始位置, 点坐标(X Y)
# pt2 point2终点位置,
# color 线的颜色 BGR的矩阵设置
# 包裹内容是可选参数 写不写无所谓 都存在默认值[, thickness[, lineType[, shift]]]
# thickness 线宽 默认值为1
# lineType线型 点.双实线.. 只有线  线上方的锯齿大小 0(cv2.FILLED) 4 8(cv2.LINE_8) 16(cv2.LINE_AA 抗锯齿) 默认值是8值越小锯齿余额明显
# shift 坐标的缩放比例
cv2.line(img,(0,0),(200,300),(203,192,255),10,cv2.LINE_8) # 淡紫色线
cv2.line(img,(100,100),(300,400),(0,0,255),10,cv2.LINE_AA) # 红色抗锯齿线

cv2.imshow("Lines on Image",img)
cv2.waitKey(0)
cv2.destroyAllWindows()

画矩形 rectangle(img, pt1, pt2, color[, thickness[, lineType[, shift]]])

写法与画线完全相同,pt1pt2 是矩形对角线的两个点。

python
import cv2
import numpy as np

img = np.zeros((400, 500, 3), dtype=np.uint8) # 黑色背景

# pt1: 左上角坐标 (x1, y1)
# pt2: 右下角坐标 (x2, y2)
# color: BGR颜色
# thickness: 线宽。如果为 -1 或 cv2.FILLED, 则填充矩形。
cv2.rectangle(img, (50, 50), (200, 150), (0, 255, 0), 3)       # 绿色边框矩形
cv2.rectangle(img, (250, 80), (400, 200), (255, 0, 0), -1) # 蓝色填充矩形

cv2.imshow("Rectangles", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

画圆 circle(img, center, radius, color[, thickness[, lineType[, shift]]])

python
import cv2
import numpy as np

img = np.zeros((400, 400, 3), dtype=np.uint8) # 黑色背景

# img在那个图形上进行绘制
# center 圆心位置 (x, y)
# radius 半径大小
# color颜色 (BGR)
# thickness 线宽, -1 或 cv2.FILLED 表示填充
# lineType 线型, cv2.LINE_AA 表示抗锯齿
cv2.circle(img,(200,200),100,(0,0,255),5,cv2.LINE_AA) # 红色圆圈,抗锯齿
cv2.circle(img,(100,100),50,(0,255,255),-1)           # 黄色填充圆

cv2.imshow("Circles",img)
cv2.waitKey(0)
cv2.destroyAllWindows()

绘制文本 cv2.putText(img, text, org, fontFace, fontScale, color[, thickness[, lineType[, bottomLeftOrigin]]])

python
"""
    img: 要绘制文本的图像。这可以是任何OpenCV图像对象。
    text: 要绘制的文本字符串。OpenCV putText本身不直接支持中文,需要Pillow库辅助。
    org: 表示要绘制的位置,图像中文本字符串的左下角坐标。这通常是一个元组,包含x和y坐标值。
    fontFace: 字体类型,OpenCV提供了多种内置字体,如cv2.FONT_HERSHEY_SIMPLEX、cv2.FONT_HERSHEY_PLAIN等。
    fontScale: 字体缩放因子,用于调整字体大小。
    color: 文本的颜色,BGR格式的元组,如(255, 0, 0)表示蓝色。
    thickness: 线的粗细(以像素为单位),用于控制文本边框的厚度。
    lineType: 可选参数,用于指定线条的类型,如cv2.LINE_AA(抗锯齿)。
    bottomLeftOrigin: 可选参数,当为true时,图像数据原点位于左下角;否则位于左上角。这影响坐标系的定义。

提供使用示例和注意事项

在使用cv2.putText()时,需要注意以下几点:
    该函数没有返回值,直接在输入的图像上进行修改。
    如果要显示中文,需要使用自定义函数或下载特定字体文件(如simsun.ttc),并结合Pillow库。
    在使用变量参数时,确保变量的类型和值正确,以避免显示错误或不预期的结果。
"""
import cv2
import numpy as np

img = np.zeros((200, 400, 3), dtype=np.uint8) # 黑色背景

font = cv2.FONT_HERSHEY_SIMPLEX
bottom_left_corner_of_text = (30, 100) # (x,y)
font_scale = 1.5
font_color_bgr = (255, 255, 255) # 白色
line_thickness = 2
line_type = cv2.LINE_AA

cv2.putText(img, 'Hello OpenCV', bottom_left_corner_of_text, font, font_scale, font_color_bgr, line_thickness, line_type)

cv2.imshow("Text on Image",img)
cv2.waitKey(0)
cv2.destroyAllWindows()

控件 createTrackbar(trackbarName, windowName, value, count, onChange) 可拖动进度条

python
# Trackbar控件 可拖拽的进度条
import cv2
import numpy as np

window_name = "Trackbar Demo Win"
cv2.namedWindow(window_name,cv2.WINDOW_NORMAL)
cv2.resizeWindow(window_name,600,400)

# 修改图形的颜色
# 回调函数 (当trackbar值改变时调用)
def on_trackbar_change(value):
    # value 是当前trackbar的位置/值
    # print(f"Trackbar value changed to: {value}")
    pass # 在主循环中获取值并更新图像

# trackbarName 控件名称, windowName 放置在那个窗体上, value 初始值, count最大值, onChange 回调函数
cv2.createTrackbar("R",window_name,0,255,on_trackbar_change)
cv2.createTrackbar("G",window_name,0,255,on_trackbar_change)
cv2.createTrackbar("B",window_name,0,255,on_trackbar_change)

# 创建一个背景图
img = np.zeros((400,600,3),np.uint8)

while True:
    # 获取当前trackbar值
    r_val = cv2.getTrackbarPos("R",window_name)
    g_val = cv2.getTrackbarPos("G",window_name)
    b_val = cv2.getTrackbarPos("B",window_name)

    # 修改图像的组成颜色 (注意OpenCV颜色顺序是BGR)
    img[:] = [b_val,g_val,r_val]

    cv2.imshow(window_name,img)
    key = cv2.waitKey(1)
    if key == ord('q'):
        break

# cv2.waitKey(0) # waitKey(0) is not needed here due to the loop
cv2.destroyAllWindows()

显示中文

OpenCV 的 putText 不直接支持中文字符。需要使用 Pillow (PIL) 库来绘制中文,然后将 Pillow 图像转换回 OpenCV 格式。

python
# 使用opencv显示中文
# 因为opencv没有办法直接显示中文  使用pillow包 需要下载 pip install pillow
from PIL import Image, ImageDraw, ImageFont
import cv2
import numpy as np

# img = cv2.imread('./3.png') # 替换为你的图片路径
# 为演示,创建一个图像
img_bgr = np.full((300, 500, 3), (200, 200, 200), dtype=np.uint8) # 浅灰色背景

# 1. OpenCV图像 (BGR) 转换为 PIL图像 (RGB)
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
pil_img = Image.fromarray(img_rgb)

# 2. 创建Pillow绘图对象
draw = ImageDraw.Draw(pil_img)

# 3. 设置字体 (确保字体文件路径正确,例如 simkai.ttf, simsun.ttc)
# 你可能需要下载一个中文字体文件并放到你的项目目录或指定系统路径
try:
    # 尝试加载一个常见的中文字体,如果你的系统上有
    font_path = "simkai.ttf"  # 或者 "simsun.ttc", "msyh.ttc" (微软雅黑)
    # 如果找不到,你可能需要提供完整路径或将字体文件放在脚本同目录
    # font_path = "C:/Windows/Fonts/simsun.ttc" # Windows示例
    font = ImageFont.truetype(font_path, 30) # 字体和大小
except IOError:
    print(f"警告:字体文件 '{font_path}' 未找到。中文可能无法正确显示。")
    print("请下载中文字体文件 (如 simsun.ttc) 并将其路径提供给 ImageFont.truetype")
    font = ImageFont.load_default() # 使用默认字体作为后备

# 4. 在Pillow图像上绘制文本
# fill参数是RGB或RGBA颜色
draw.text((50,100),"你好,世界!Hello!",font=font,fill=(0,128,0,255)) # 绿色文字 (R,G,B,A)

# 5. PIL图像 (RGB) 转换回 OpenCV图像 (BGR)
img_bgr_with_text = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)

cv2.imshow("Image with Chinese Text",img_bgr_with_text)
cv2.waitKey(0)
cv2.destroyAllWindows()

图像的算数与位运算

  • 要求图像大小必须相同,通道也必须相同。
  • 算数运算:加、减、乘、除,方式基本相同。

图像的加法运算 add(图像1, 图像2)

  • 根据图像的矩阵 [B1,G1,R1] + [B2,G2,R2] -> [B1+B2, G1+G2, R1+R2]
  • 色阶值范围 0~255。
  • 例如:150 + 200 -> 350。如果两个图像对应像素点的值相加超过255,结果会被截断为255 (饱和运算)。
python
import cv2
import numpy as np

# A = cv2.imread('./3.png') # 替换为你的图片路径
# B = cv2.imread('./2.jpg') # 替换为你的图片路径

# 为演示,创建两个图像
A = np.random.randint(0, 128, (300, 400, 3), dtype=np.uint8) # 较暗的图像
B_orig = np.random.randint(0, 128, (350, 450, 3), dtype=np.uint8)

# 获取到图像的特征
print(f"Shape of A: {A.shape}")
print(f"Shape of B_orig: {B_orig.shape}")

# 确保图像大小和通道数相同
# 通过ndarray切片方式或resize在大图中切割/调整出与A相同大小的图像区域
if A.shape != B_orig.shape:
    print("图像A和B的尺寸不同,将调整B以匹配A。")
    B = cv2.resize(B_orig, (A.shape[1], A.shape[0])) # (width, height)
else:
    B = B_orig

print(f"Shape of resized B: {B.shape}")

# 加法运算
added_img = cv2.add(A,B)

cv2.imshow("Image A", A)
cv2.imshow("Image B (resized)", B)
cv2.imshow("Added Image",added_img)
cv2.waitKey(0)
cv2.destroyAllWindows() # 注意函数名是 destroyAllWindows

图像的减法运算 subtract(图像1, 图像2)

  • 例如:100 - 50 -> 50
  • 100 - 200 -> -100。如果结果小于0,则截断为0。
python
import cv2
import numpy as np

# A = cv2.imread('./3.png')
# B = cv2.imread('./2.jpg')
A = np.full((300, 400, 3), (150, 150, 150), dtype=np.uint8) # 灰色图像
B_orig = np.random.randint(50, 200, (350, 450, 3), dtype=np.uint8)


print(f"Shape of A: {A.shape}")
print(f"Shape of B_orig: {B_orig.shape}")

if A.shape != B_orig.shape:
    B = cv2.resize(B_orig, (A.shape[1], A.shape[0]))
else:
    B = B_orig
print(f"Shape of resized B: {B.shape}")

# 减法运算 (A - B)
subtracted_img = cv2.subtract(A,B)

cv2.imshow("Image A", A)
cv2.imshow("Image B (resized)", B)
cv2.imshow("Subtracted Image (A-B)",subtracted_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

图像的乘法 multiply(图像1, 图像2)

  • 对应像素值相乘后,通常会进行缩放(例如,除以255)以保持在0-255范围内,或者结果大于255时截断为255。cv2.multiply 的行为是截断。
  • 例如:50 * 2 (如果第二个参数是标量,需要通过 cv2.multiply(A, scalar_value_as_array)A * scalar_value (numpy操作,可能溢出))。
  • cv2.multiply(A, B) 会将对应像素值相乘,然后如果结果大于255,则截断为255。
python
import cv2
import numpy as np

# A = cv2.imread('./3.png')
# B = cv2.imread('./2.jpg')
A = np.random.randint(10, 50, (300, 400, 3), dtype=np.uint8)
B_orig = np.random.randint(2, 6, (350, 450, 3), dtype=np.uint8) # 小数值,避免快速饱和

print(f"Shape of A: {A.shape}")
print(f"Shape of B_orig: {B_orig.shape}")

if A.shape != B_orig.shape:
    B = cv2.resize(B_orig, (A.shape[1], A.shape[0]))
else:
    B = B_orig
print(f"Shape of resized B: {B.shape}")

# 乘法运算
multiplied_img = cv2.multiply(A,B)
# 注意:如果B是一个标量,例如2,可以这样做:
# multiplied_img_scalar = cv2.multiply(A, np.full(A.shape, 2, dtype=A.dtype))
# 或者,更简单地使用Numpy广播 (但要注意Numpy乘法可能溢出 uint8,cv2.multiply处理饱和)
# multiplied_img_numpy = A * 2 # Numpy乘法,如果A*2 > 255,会发生溢出 (e.g., 150*2 = 300, 300%256 = 44)
# 使用 cv2.multiply(A, B, scale=1.0/255.0) 可以进行归一化乘法

cv2.imshow("Image A", A)
cv2.imshow("Image B (resized)", B)
cv2.imshow("Multiplied Image",multiplied_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

图像的除法 divide(图像1, 图像2)

  • A / B。如果结果小于0(这在正像素值除法中不发生),则等于0。如果除数为0,结果可能为0或最大值,取决于实现。cv2.divide 中,除以0的像素结果为0。
python
import cv2
import numpy as np

# A = cv2.imread('./3.png')
# B = cv2.imread('./2.jpg')
A = np.random.randint(100, 255, (300, 400, 3), dtype=np.uint8)
B_orig = np.random.randint(1, 10, (350, 450, 3), dtype=np.uint8) # 除数,确保不为0的地方多一些

print(f"Shape of A: {A.shape}")
print(f"Shape of B_orig: {B_orig.shape}")

if A.shape != B_orig.shape:
    B = cv2.resize(B_orig, (A.shape[1], A.shape[0]))
else:
    B = B_orig
# 确保B中没有0,或者理解cv2.divide对除0的处理(结果为0)
B[B == 0] = 1 # 将B中的0替换为1,避免除以零的问题,如果想看到原始行为可以注释掉

print(f"Shape of resized B: {B.shape}")

# 除法运算
divided_img = cv2.divide(A,B)
# 也可以指定一个缩放因子:cv2.divide(A, B, scale=255.0)

cv2.imshow("Image A", A)
cv2.imshow("Image B (resized, non-zero)", B)
cv2.imshow("Divided Image",divided_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

图像的融合 addWeighted(src1, alpha, src2, beta, gamma)

  • src1: 图像1
  • alpha: 图像1权重
  • src2: 图像2
  • beta: 图像2权重
  • gamma: 图像的亮度偏置值。正值增加亮度,负值降低亮度。
  • dst = src1*alpha + src2*beta + gamma
python
# 图像的融合 将俩张图像进行合并
# 区分谁显示深浅 权重划分
import cv2
import numpy as np

# src1 图像1
# alpha 图像1权重
# src2 图像2
# beta 图像2权重
# gamma 图像的线性 向上 向下 图像的色阶值 正值表示向上 负值表示向下拉取
# cv2.addWeighted(src1, alpha, src2, beta, gamma)

# A = cv2.imread('./3.png')
# B = cv2.imread('./2.jpg')
A = np.random.randint(0, 255, (300, 400, 3), dtype=np.uint8)
B_orig = np.random.randint(0, 255, (350, 450, 3), dtype=np.uint8)

if A.shape != B_orig.shape:
    new_B = cv2.resize(B_orig, (A.shape[1], A.shape[0]))
else:
    new_B = B_orig

# 权重的取值范围 最好俩个加起来为1
alpha_val = 0.7
beta_val = 0.3 # 1.0 - alpha_val
gamma_val = -50 # 降低整体亮度

fused_img = cv2.addWeighted(A, alpha_val, new_B, beta_val, gamma_val)

cv2.imshow("Image A", A)
cv2.imshow("Image B (resized)", new_B)
cv2.imshow("Fused Image",fused_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

图像的非运算 bitwise_not(src)

  • Opencv中的图像位运算,对应逻辑运算:与、或、非、异或。
  • 非的操作效果相当于 255 - 色阶值 (对于8位图像)。
python
import cv2
import numpy as np

# A = cv2.imread('./3.png')
# B = cv2.imread('./2.jpg') # B未使用,但保留与原文一致
A = np.array([[0, 50, 100], [150, 200, 255]], dtype=np.uint8) # 示例单通道小图像
A_color = cv2.merge([A, A+20 if (A.max()+20)<255 else A, A-20 if (A.min()-20)>0 else A]) # 创建一个彩色图像用于显示

# 图像非运算
not_A = cv2.bitwise_not(A_color)

cv2.imshow("Original Image A",A_color)
cv2.imshow("Bitwise NOT A",not_A)
cv2.waitKey(0)
cv2.destroyAllWindows()

图像的与运算 bitwise_and(src1, src2)

  • 203 & 107 -> 75 (二进制与运算)
  • 图像对应位置的元素进行与操作。效果:黑&黑->黑,白&白->白。其他色阶根据自身的二进制位运算结果生成值。
python
import cv2
import numpy as np

# 与操作 图像对应位置的元素进行与操作 表示出来的效果就是 黑和黑-》黑  白和白->白 其他色阶根据自身的运算方式生成值
# A = cv2.imread('./3.png')
# B = cv2.imread('./2.jpg')
img_A = np.zeros((300,400,3), dtype=np.uint8)
cv2.rectangle(img_A, (50,50), (250,250), (255,255,255), -1) # 白色方块

img_B_orig = np.zeros((350,450,3), dtype=np.uint8)
cv2.circle(img_B_orig, (225,150), 100, (255,255,255), -1) # 白色圆

if img_A.shape != img_B_orig.shape:
    img_B = cv2.resize(img_B_orig, (img_A.shape[1], img_A.shape[0]))
else:
    img_B = img_B_orig

# 位与运算
anded_img = cv2.bitwise_and(img_A, img_B)

# 10  not -10  0~255  255-10->245 (这是对bitwise_not的注释,与and无关)
cv2.imshow("Image A", img_A)
cv2.imshow("Image B (resized)", img_B)
cv2.imshow("Bitwise AND",anded_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

图像的或运算 bitwise_or(src1, src2)

  • 204 | 170 (二进制或运算,原文用 ^ 符号表示异或,这里改为 | 对应或运算)
    • 204 (11001100) OR 170 (10101010) = 11101110 (238)
python
import cv2
import numpy as np

img_A = np.zeros((300,400,3), dtype=np.uint8)
cv2.rectangle(img_A, (50,50), (250,250), (255,0,0), -1) # 蓝色方块

img_B_orig = np.zeros((350,450,3), dtype=np.uint8)
cv2.circle(img_B_orig, (225,150), 100, (0,255,0), -1) # 绿色圆

if img_A.shape != img_B_orig.shape:
    img_B = cv2.resize(img_B_orig, (img_A.shape[1], img_A.shape[0]))
else:
    img_B = img_B_orig

# 位或运算
ored_img = cv2.bitwise_or(img_A, img_B)

cv2.imshow("Image A", img_A)
cv2.imshow("Image B (resized)", img_B)
cv2.imshow("Bitwise OR",ored_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

图像的异或运算 bitwise_xor(src1, src2)

  • 204 ^ 170 -> 102 (二进制异或运算)
    • 204 (11001100) XOR 170 (10101010) = 01100110 (102)
python
import cv2
import numpy as np

img_A = np.zeros((300,400,3), dtype=np.uint8)
cv2.rectangle(img_A, (50,50), (250,250), (255,255,0), -1) # 黄色 (Blue=0, Green=255, Red=255) 方块

img_B_orig = np.zeros((350,450,3), dtype=np.uint8)
cv2.circle(img_B_orig, (225,150), 100, (0,255,255), -1) # 青色 (Blue=255, Green=255, Red=0) 圆

if img_A.shape != img_B_orig.shape:
    img_B = cv2.resize(img_B_orig, (img_A.shape[1], img_A.shape[0]))
else:
    img_B = img_B_orig

# 位异或运算
xored_img = cv2.bitwise_xor(img_A, img_B)

cv2.imshow("Image A", img_A)
cv2.imshow("Image B (resized)", img_B)
cv2.imshow("Bitwise XOR",xored_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

图形的放大与缩小 resize(src, dsize[, dst[, fx[, fy[, interpolation]]]])

  • src: 进行缩放的图像
  • dsize: 缩放之后的图像大小 (width, height) 元组或列表。
  • dst: 缩放之后的输出图像 (可选)。
  • fx, fy: x轴及y轴的缩放比例。如果指定了 dsize,则 fxfy 会被忽略或根据 dsize 计算。
  • interpolation: 插值算法,用于确定如何进行像素插值。
    • cv2.INTER_NEAREST: 最近邻插值。速度快,效果差。
    • cv2.INTER_LINEAR: 双线性插值(默认使用)。使用原图中的4个点进行插值。
    • cv2.INTER_AREA: 区域插值。缩小图像时效果最好,计算时间较长。放大时类似INTER_NEAREST。
    • cv2.INTER_CUBIC: 立方插值(超过4x4像素邻域内的双三次插值)。三次插值,依据原图中的16个点进行计算。效果较好。
    • cv2.INTER_LANCZOS4: Lanczos插值(超过8x8像素邻域的Lanczos插值)。效果最好,计算量最大。
python
import cv2
import numpy as np

# img = cv2.imread('./3.png') # 替换为你的图片路径
img = np.random.randint(0, 256, (300, 400, 3), dtype=np.uint8) # 示例图像
print(f"Original shape: {img.shape}") # (height, width, channels) (300, 400, 3)

# 放大一倍 通过像素值设置固定值 (dsize = (width, height))
# new_img_fixed = cv2.resize(img,(img.shape[1]*2, img.shape[0]*2),interpolation=cv2.INTER_LANCZOS4)
new_img_fixed = cv2.resize(img,(800, 600),interpolation=cv2.INTER_CUBIC) # (width, height)
print(f"Resized by dsize shape: {new_img_fixed.shape}")

# 根据图像的大小 进行等比例缩放 (使用 fx, fy)
# dsize 必须为 None 或 (0,0) 才能使 fx, fy 生效
fx_scale = 0.5
fy_scale = 0.5
new_img_scaled = cv2.resize(img, dsize=None, fx=fx_scale, fy=fy_scale, interpolation=cv2.INTER_AREA)
# new_img_scaled = cv2.resize(img, (0,0), fx=fx_scale, fy=fy_scale, interpolation=cv2.INTER_AREA) # (0,0)也行
print(f"Resized by fx, fy shape: {new_img_scaled.shape}")

cv2.imshow("Original Image", img)
cv2.imshow("Resized Fixed (Enlarged)", new_img_fixed)
cv2.imshow("Resized Scaled (Shrunk)", new_img_scaled)
cv2.waitKey(0)
cv2.destroyAllWindows()

图形的翻转 cv2.flip(src, flipCode)

python
import cv2
import numpy as np
"""
    当 flipCode > 0 (例如 1): 表示绕y轴翻转 (水平镜像)。
    当 flipCode == 0: 表示绕x轴翻转 (垂直镜像)。
    当 flipCode < 0 (例如 -1): 表示同时绕x轴和y轴翻转 (水平和垂直镜像)。
"""
# img = cv2.imread('./3.png') # 替换为你的图片路径
img = np.random.randint(0,256, (300,400,3), dtype=np.uint8)
cv2.putText(img, "TOP-LEFT", (10,30), cv2.FONT_HERSHEY_SIMPLEX, 1, (255,255,255),2)


# cv2.flip(src,flipCode)
# 会生成一个新的图像
flipped_horizontal = cv2.flip(img, 1)  # 水平翻转
flipped_vertical = cv2.flip(img, 0)    # 垂直翻转
flipped_both = cv2.flip(img, -1)       # 水平并垂直翻转

cv2.imshow("Original", img)
cv2.imshow("Flipped Horizontal (y-axis)", flipped_horizontal)
cv2.imshow("Flipped Vertical (x-axis)", flipped_vertical)
cv2.imshow("Flipped Both Axes", flipped_both)
cv2.waitKey(0)
cv2.destroyAllWindows()

图像的旋转 cv2.rotate(src, rotateCode)

python
import cv2
import numpy as np
"""
    输入图像 src
    旋转的 rotateCode:
     - cv2.ROTATE_90_CLOCKWISE: 顺时针旋转90度
     - cv2.ROTATE_180: 旋转180度
     - cv2.ROTATE_90_COUNTERCLOCKWISE: 逆时针旋转90度
"""
# img = cv2.imread('./3.png') # 替换为你的图片路径
img = np.random.randint(0,256, (300,400,3), dtype=np.uint8) # 注意:旋转后宽高会交换(90度)
cv2.putText(img, "ORIG", (10,30), cv2.FONT_HERSHEY_SIMPLEX, 1, (255,255,255),2)

# cv2.rotate(src, rotateCode)
# 会生成一个新的图像
rotated_90_cw = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)
rotated_180 = cv2.rotate(img, cv2.ROTATE_180)
rotated_90_ccw = cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE)

cv2.imshow("Original", img)
cv2.imshow("Rotated 90 Clockwise", rotated_90_cw)
cv2.imshow("Rotated 180", rotated_180)
cv2.imshow("Rotated 90 Counter-Clockwise", rotated_90_ccw)
cv2.waitKey(0)
cv2.destroyAllWindows()

图像的仿射平移 cv2.warpAffine()

仿射变换,如平移,是通过一个2x3的变换矩阵 M 来实现的。 [[1, 0, tx], [0, 1, ty]] 表示在x方向平移 tx,在y方向平移 ty

python
# 仿射变化
# 图像平移
"""
  + 具体的作法是通过一个矩阵和原图片坐标进行计算 得到一个新的坐标 将新的坐标给与
图像,完成变换    关键点:矩阵
cv2.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]])
M 变换矩阵 (2x3)
dsize 输出图像大小 (width, height)
dst输出图像 大小和类型与输入图像相同 (可选)
flags 插值方法的组合 用于执行如果重新采样 值于resize相同 (e.g., cv2.INTER_LINEAR)
borderMode 边界外推法标志  像素外推方法 用于执行如何处理输出图像的边缘的像素 (e.g., cv2.BORDER_CONSTANT)
borderValue 填充的边界值 默认为0 (当 borderMode=cv2.BORDER_CONSTANT 时)
"""

import cv2
import numpy as np

# img = cv2.imread('./3.png') # 替换为你的图片路径
img = np.random.randint(0,256, (300,400,3), dtype=np.uint8)
cv2.putText(img, "Obj", (50,150), cv2.FONT_HERSHEY_SIMPLEX, 2, (255,255,255),3)

# 获取到原图大小 (height, width, channels)
rows, cols, channels = img.shape

# 生成一个平移矩阵
# M = np.float32([[1, 0, 100], [0, 1, 0]]) # x方向平移100, y方向平移0
tx = 100 # 平移x量
ty = 50  # 平移y量
M = np.float32([[1, 0, tx],
                [0, 1, ty]])

# dsize 是 (width, height)
# 会生成一个新的图像
translated_img = cv2.warpAffine(img, M, (cols, rows)) # 输出图像大小与原图相同
# 如果想让平移后的图像完整显示,可能需要更大的dsize
# translated_img_larger = cv2.warpAffine(img, M, (cols + tx, rows + ty))


cv2.imshow("Original Image", img)
cv2.imshow("Translated Image", translated_img)
# cv2.imshow("Translated Image (Larger Canvas)", translated_img_larger)
cv2.waitKey(0)
cv2.destroyAllWindows()

图像的变换矩阵 getRotationMatrix2D(center, angle, scale)

用于生成一个旋转和缩放的2x3仿射变换矩阵。

  • center: 旋转中心点 (x, y)。
  • angle: 旋转角度 (度数)。正值表示逆时针旋转。
  • scale: 各向同性的缩放因子。
python
# 变换矩阵
import cv2
import numpy as np

# center 中心点 以图片的那个点作为旋转中心 X Y
# angle角度 旋转的角度 按照逆时针进行旋转
# scale缩放比例 将图形进行什么缩放
# opencv提供的变换矩阵Api
# cv2.getRotationMatrix2D(center, angle, scale)

# img = cv2.imread('./3.png') # 替换为你的图片路径
img = np.random.randint(0,256, (300,400,3), dtype=np.uint8)
cv2.putText(img, "RotateMe", (80,150), cv2.FONT_HERSHEY_SIMPLEX, 1, (255,255,255),2)

# 获取图像尺寸 (height, width)
rows, cols = img.shape[:2]

# 如果以图像中心点设置
center_x = cols / 2
center_y = rows / 2
rotation_angle = 30  # 逆时针旋转30度
scale_factor = 0.8   # 缩小到80%

# 返回值为2x3矩阵 M
M = cv2.getRotationMatrix2D((center_x, center_y), rotation_angle, scale_factor)
print("Rotation Matrix M:\n", M)

# 应用仿射变换
# dsize是 (width, height)
rotated_scaled_img = cv2.warpAffine(img, M, (cols, rows))

cv2.imshow("Original Image", img)
cv2.imshow("Rotated and Scaled Image", rotated_scaled_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

通过三个点确定变换矩阵 getAffineTransform(src_points, dst_points)

给定源图像中的三个点及其在目标图像中对应的新位置,getAffineTransform 可以计算出相应的2x3仿射变换矩阵。

  • src_points: 原图像中的三个点坐标,Numpy float32 数组,形状 (3, 2)
  • dst_points: 目标图像中对应的三个点坐标,Numpy float32 数组,形状 (3, 2)
python
import cv2
import numpy as np

# 通过三个点 可以确定变换之后的位置 相当于解方程三个点对应三个方程 就能解除便宜的参数和旋转角度
# src原图像的三个点
# dst表示变换之后的三个点
# cv2.getAffineTransform(src, dst)

# img = cv2.imread('./3.png') # 替换为你的图片路径
img = np.zeros((400, 500, 3), dtype=np.uint8)
# 在原图画一些参考点/形状
cv2.circle(img, (100,100), 5, (0,0,255), -1) # red
cv2.circle(img, (200,100), 5, (0,255,0), -1) # green
cv2.circle(img, (100,200), 5, (255,0,0), -1) # blue
cv2.putText(img, "Original Points", (10,30), cv2.FONT_HERSHEY_SIMPLEX, 1, (255,255,255),2)


rows, cols = img.shape[:2]

# 定义源图像中的三个点 (x,y)
src_points = np.float32([[100,100],[200,100],[100,200]])
# 定义目标图像中这三个点的新位置
dst_points = np.float32([[50,150],[250,180],[80,300]])

# 返回一个2x3的仿射变换矩阵 M
M = cv2.getAffineTransform(src_points, dst_points)
print("Affine Transform Matrix M:\n", M)

# 应用仿射变换
# dsize是 (width, height)
transformed_img = cv2.warpAffine(img, M, (cols, rows))

cv2.imshow("Original Image with Source Points", img)
cv2.imshow("Transformed Image based on 3 Points", transformed_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

滤波器

卷积 filter2D()

filter2D() 用用户定义的核(kernel)对图像进行卷积。

  • cv2.cvtColor(src, code): 色彩空间转换,例如 cv2.COLOR_BGR2GRAY 将BGR图像转为灰度图。
python
# 卷积操作
import cv2
import numpy as np

# 色彩控件转换模式 将彩色图像转化为灰阶图像
# cv2.cvtColor(src, code cv2.COLOR_BGR2GRAY)

# img = cv2.imread('./3.png') # 替换为你的图片路径
img_bgr = np.random.randint(0,256, (300,400,3), dtype=np.uint8)
gray_img = cv2.cvtColor(img_bgr,cv2.COLOR_BGR2GRAY)
"""
filter2D卷积函数
src: 输入图像 要进行卷积操作的图像
ddepth: 输出图像深度。目标图像的深度。可以是输入图像的深度,也可以更浅或者更深。
        通常设为 -1 表示输出图像深度与输入图像相同。
kernel: 二维卷积核。通常是一个二维浮点数Numpy数组,定义了滤波器的行为。
dst: 卷积操作之后的输出图像 (可选)
"""
# 定义一些常见的卷积核
kernel_identity = np.array([[0, 0, 0],
                            [0, 1, 0],
                            [0, 0, 0]], dtype=np.float32)

# 轮廓提取/边缘检测 (Laplacian-like)
kernel_outline = np.array([[-1,-1,-1],
                           [-1, 8,-1],
                           [-1,-1,-1]], dtype=np.float32)

# 锐化
kernel_sharpen = np.array([[ 0,-1, 0],
                           [-1, 5,-1],
                           [ 0,-1, 0]], dtype=np.float32)
# kernel_sharpen_alt = np.array([[-1,-1,-1],[-1,9,-1],[-1,-1,-1]], dtype=np.float32)


# 浮雕 (Emboss)
kernel_emboss = np.array([[-2,-1, 0],
                          [-1, 1, 1],
                          [ 0, 1, 2]], dtype=np.float32)

# 应用卷积
# ddepth = -1 表示输出图像与输入图像有相同的深度
identity_output = cv2.filter2D(gray_img, -1, kernel_identity)
outline_output = cv2.filter2D(gray_img, -1, kernel_outline)
sharpen_output = cv2.filter2D(gray_img, -1, kernel_sharpen)
emboss_output = cv2.filter2D(gray_img, -1, kernel_emboss)


cv2.imshow("Original Grayscale", gray_img)
# cv2.imshow("Identity Filter", identity_output) # Should be same as original
cv2.imshow("Outline Filter", outline_output)
cv2.imshow("Sharpen Filter", sharpen_output)
cv2.imshow("Emboss Filter", emboss_output)

cv2.waitKey(0)
cv2.destroyAllWindows()
# cv2.warpPerspective(src, M, dsize) # This line seems out of place here

均值滤波 cv2.blur(src, ksize)

用一个归一化的方框滤波器(box filter)对图像进行模糊。ksize 是卷积核的大小,例如 (5,5)。 滤波盒(卷积核)中的所有元素值相同 (1/ (ksize_width*ksize_height)),然后与图像区域求和。 例如,对于 ksize=(3,3),核是:

[[1/9, 1/9, 1/9],
 [1/9, 1/9, 1/9],
 [1/9, 1/9, 1/9]]
python
import cv2
import numpy as np

# img = cv2.imread('./noisy_image.png') # 替换为带有噪点的图片
img = np.random.randint(0,256, (300,400,3), dtype=np.uint8)
# 人为添加一些噪点以便观察效果
noise = np.zeros_like(img)
cv2.randn(noise, (0,0,0), (30,30,30)) # 平均值为0,标准差为30的噪声
noisy_img = cv2.add(img, noise)


# ksize: 卷积核大小 (width, height), 必须是奇数
kernel_size = (5,5) # 5x5 的均值滤波核

blurred_img = cv2.blur(noisy_img, kernel_size)

cv2.imshow("Noisy Image", noisy_img)
cv2.imshow("Mean Blurred Image", blurred_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

方盒滤波 cv2.boxFilter(src, ddepth, ksize[, normalize[, borderType]])

与均值滤波类似,但可以选择是否归一化。如果 normalize=True (默认),它等同于 cv2.blur()。如果 normalize=False,则核内元素为1,求和后不除以面积,可能导致像素值超出255(会被截断)。

python
# 方盒滤波 不用写矩阵核  只需要告诉方盒滤波 卷积核大小
import cv2
import numpy as np

# img = cv2.imread('./3.png') # 替换为你的图片路径
img = np.random.randint(0,256, (300,400,3), dtype=np.uint8)

"""
cv2.boxFilter(src, ddepth, ksize[, dst[, anchor[, normalize[, borderType]]]])
src: 输入图像 原图 进行操作图形
ddepth: 图像深度。-1 表示与原图相同。
ksize: 模糊内核大小 (width, height),值越大 图像越模糊。通常是奇数,如 (3,3), (5,5), (7,7)。
dst: 目标图像  需要核原图一样的大小 如果为空输入图像处理 (可选)
anchor: 锚点位置 默认值是在卷积核的中心 (可选)
normalize: 标志位。True (默认): 内核被其面积归一化 (等同于均值滤波)。
                      False: 不归一化,直接求和 (可能导致像素值溢出)。
borderType: 边界模式 用于外推图像边界之外的像素 (可选)
"""
ksize = (9,9)
# 归一化 (等同于 cv2.blur)
box_filtered_normalized = cv2.boxFilter(img, -1, ksize, normalize=True)

# 不归一化 (像素值会变大,通常导致图像变白/过曝)
# 为了避免立即全白,使用一个较小的ksize或较暗的图像
dark_img = (img / 4).astype(np.uint8) # 使图像变暗,以便观察非归一化效果
box_filtered_unnormalized = cv2.boxFilter(dark_img, -1, (3,3), normalize=False)


cv2.imshow("Original Image", img)
cv2.imshow("Box Filter (Normalized)", np.hstack((img, box_filtered_normalized)))

cv2.imshow("Dark Image", dark_img)
cv2.imshow("Box Filter (Unnormalized on dark)", box_filtered_unnormalized)

cv2.waitKey(0)
cv2.destroyAllWindows()
# cv2.warpPerspective(src, M, dsize)b # This line seems out of place

高斯滤波 cv2.GaussianBlur(src, ksize, sigmaX[, sigmaY[, borderType]])

使用高斯核进行模糊,能有效去除高斯噪声。高斯核的权重中心大,边缘小。

  • ksize: 高斯核的大小 (width, height)。必须是正奇数。
  • sigmaX: X方向的高斯核标准差。
  • sigmaY: Y方向的高斯核标准差。如果为0,则从 sigmaX 计算;如果两者都为0,则从 ksize 计算。
  • sigma 值越大,图像越模糊。
python
import cv2
import numpy as np

# img = cv2.imread('./3.png') # 替换为你的图片路径
# img_gaussian_noise = cv2.imread('./gaussian.png') # 替换为带高斯噪声的图片

# 为演示,创建带高斯噪声的图像
img_clean = np.random.randint(0,256, (300,400,3), dtype=np.uint8)
gaussian_noise = np.zeros_like(img_clean, dtype=np.int16) # 使用int16避免uint8溢出
cv2.randn(gaussian_noise, 0, 25) # 均值为0,标准差为25的高斯噪声
img_with_gaussian_noise = cv2.add(img_clean, gaussian_noise.astype(np.uint8), dtype=cv2.CV_8U)


"""
高斯滤波 去噪
src: 图像
ksize: 高斯核的大小(e.g., (3,3), (5,5), (9,9)),必须是正奇数。指明周围多少个像素点进行计算。
sigmaX: X轴标准差。
sigmaY: Y轴的标准差。如果为0,则设为与sigmaX相同。如果sigmaX和sigmaY都为0,则根据ksize计算。
sigma值越大,图像越糊。
cv2.GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType]]])
"""
# ksize 必须是正奇数
ksize = (9,9)
sigmaX_val = 0 # 让OpenCV根据ksize自动计算sigmaX和sigmaY
# sigmaX_val = 1.5 # 也可以手动指定

gaussian_blurred_img = cv2.GaussianBlur(img_with_gaussian_noise, ksize, sigmaX_val)


cv2.imshow("Original with Gaussian Noise", img_with_gaussian_noise)
cv2.imshow("Gaussian Blurred", gaussian_blurred_img)
# cv2.imshow("Win",np.hstack((img_with_gaussian_noise,gaussian_blurred_img))) # 如果想并排显示

cv2.waitKey(0)
cv2.destroyAllWindows()

中值滤波 medianBlur(img, ksize)

计算卷积核区域内像素的中值,并用该中值替换中心像素。对去除椒盐噪声(salt-and-pepper noise)特别有效。

  • ksize: 核的大小,必须是奇数整数 (e.g., 3, 5, 7)。
python
# 中值滤波去噪  计算方式与高斯完全不同 高斯通过函数计算均值
# 中值滤波 直接讲矩阵中的值从小到达排列成一个数组  例如[132,140,142,145,150,153,160,170,175]
# 取其中间值 150 作为卷积后的结果值
import cv2
import numpy as np

# img_papper_noise = cv2.imread('./papper.png') # 替换为带椒盐噪声的图片

# 为演示,创建带椒盐噪声的图像
img_clean = np.random.randint(0,256, (300,400,3), dtype=np.uint8)
img_with_pepper_noise = img_clean.copy()
# 添加椒盐噪声
salt_vs_pepper = 0.5
amount = 0.04 # 4%的像素受噪声影响
num_salt = np.ceil(amount * img_clean.size * salt_vs_pepper)
num_pepper = np.ceil(amount * img_clean.size * (1.0 - salt_vs_pepper))

# Salt noise (white pixels)
coords_salt = [np.random.randint(0, i - 1, int(num_salt)) for i in img_clean.shape[:2]]
img_with_pepper_noise[coords_salt[0], coords_salt[1], :] = (255,255,255)

# Pepper noise (black pixels)
coords_pepper = [np.random.randint(0, i - 1, int(num_pepper)) for i in img_clean.shape[:2]]
img_with_pepper_noise[coords_pepper[0], coords_pepper[1], :] = (0,0,0)


# 因为高斯滤波会将周围值作为参考点进行计算,而中值滤波直接取中间值,
# 所以周围的极端值(椒盐噪声)并不会对中值造成很大影响,因此对椒盐去噪效果更好。
# ksize 必须是奇数 > 1, 例如 3, 5, 7
ksize_median = 5 # 使用 5x5 的核

median_blurred_img = cv2.medianBlur(img_with_pepper_noise, ksize_median)

# 对比高斯滤波对椒盐噪声的效果
# gaussian_blurred_for_pepper = cv2.GaussianBlur(img_with_pepper_noise,(ksize_median,ksize_median),0)

cv2.imshow("Original with Pepper Noise", img_with_pepper_noise)
cv2.imshow("Median Blurred", median_blurred_img)
# cv2.imshow("Gaussian Blurred on Pepper Noise", gaussian_blurred_for_pepper)
# cv2.imshow("Comparison", np.hstack((img_with_pepper_noise, median_blurred_img)))

cv2.waitKey(0)
cv2.destroyAllWindows()

双边滤波 bilateralFilter(src, d, sigmaColor, sigmaSpace)

双边滤波是一种非线性、边缘保留的降噪平滑滤波器。它同时考虑了空间邻近度和像素值相似度。

  • 图像并不会变得很模糊,如果图形中存在噪点,噪点可以被去除,同时保留边缘(美颜效果)。
  • d: 滤波过程中每个像素邻域的直径。必须是奇数。如果为非正数,则从 sigmaSpace 计算。
  • sigmaColor: 颜色空间滤波器的sigma值。这个参数控制滤波器在颜色空间中的平滑程度。
    • 较大的值意味着颜色相差较远的像素也会相互影响,只要它们足够近。会导致图形边缘模糊,但能平滑更大范围的颜色。 (e.g., 75, 100, 150)
  • sigmaSpace: 坐标空间滤波器的sigma值。这个参数控制滤波器在坐标空间中的平滑程度。
    • 较大的值意味着距离较远的像素也会相互影响,只要它们的颜色足够相似。也会导致图形边缘模糊。(e.g., 75, 100, 150)
python
# 双边滤波  即计算了均值  又保留了色差  对于图像进行了美颜处理
# 图像并不会变糊 如果图形中存在噪点 噪点又可以去除
import cv2
import numpy as np

# img = cv2.imread('./3.png') # 替换为你的图片路径
img = np.random.randint(0,256, (300,400,3), dtype=np.uint8)
# 为了更好地观察效果,可以先轻微模糊再加噪点,模拟真实场景
# img = cv2.GaussianBlur(img, (5,5), 0)
noise = np.zeros_like(img)
cv2.randn(noise, 0, 15)
noisy_img = cv2.add(img, noise)


"""
d: 滤波器的直径。必须是奇数。例如 5, 7, 9.
sigmaColor: 颜色空间滤波器sigma值。较大的值会让滤波器更加宽松,
            保留更多颜色细节,但也可能导致边缘区域的颜色混合。
            典型值 50-150。
sigmaSpace: 坐标空间中sigma值。较大的值会让滤波器考虑更远距离的像素,
            保留更多空间细节,但也可能导致边缘模糊。
            典型值 50-150。
cv2.bilateralFilter(src, d, sigmaColor, sigmaSpace)
"""
d_val = 9          # 直径
sigmaColor_val = 75
sigmaSpace_val = 75

bilateral_filtered_img = cv2.bilateralFilter(noisy_img, d_val, sigmaColor_val, sigmaSpace_val)

cv2.imshow("Noisy Image", noisy_img)
cv2.imshow("Bilateral Filtered Image", bilateral_filtered_img)
# cv2.imshow("Comparison", np.hstack((noisy_img, bilateral_filtered_img)))

cv2.waitKey(0)
cv2.destroyAllWindows()

索贝尔算子 Sobel()

用于边缘检测,通过计算图像梯度(一阶导数)来找到边缘。

  • 参考链接: Sobel Operator
  • ddepth: 输出图像的深度。通常使用 cv2.CV_64F 以避免信息丢失(因为梯度可能有负值),之后再转换回 cv2.CV_8U
  • dx, dy: x和y方向上的差分阶数 (0, 1, 或 2)。通常计算x方向梯度时 dx=1, dy=0,y方向梯度时 dx=0, dy=1
  • ksize: Sobel核的大小,必须是1, 3, 5, 或 7。
python
# 索贝尔算子 Sobel
import cv2
import numpy as np
"""
src: 边缘检测的初始图,通常是灰度图。
ddepth: 输出图形的深度。-1表示与源图相同,但为了处理梯度中的负值和更大的动态范围,
        通常使用 cv2.CV_16S、cv2.CV_32F、cv2.CV_64F。
dx: 在X方向上进行差分的阶数。通常是1表示一阶差分。
dy: 在Y方向上进行差分的阶数。通常是1表示一阶差分。
ksize: 核大小 (e.g., 3, 5, 7)。
scale: 缩放因子,用于调整计算结果的尺度。默认是1。
delta: 偏移量,添加到结果像素之前的固定标量值。默认为0。
cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])
"""
# img_color = cv2.imread('./chess.png') # 替换为你的图片路径, 例如棋盘格
img_color = np.zeros((300,400,3), dtype=np.uint8)
cv2.rectangle(img_color, (50,50), (200,200), (255,255,255), -1) # 白色方块
img = cv2.cvtColor(img_color, cv2.COLOR_BGR2GRAY)

# Sobel 要分别计算X Y的梯度
# 使用 cv2.CV_64F 来保留负值梯度,之后再处理
# 计算X轴方向的梯度 (检测垂直方向的边缘)
sobel_x = cv2.Sobel(img, cv2.CV_64F, dx=1, dy=0, ksize=3)
# 计算Y轴方向的梯度 (检测水平方向的边缘)
sobel_y = cv2.Sobel(img, cv2.CV_64F, dx=0, dy=1, ksize=3)

# 将梯度转换回uint8以便显示
abs_sobel_x = cv2.convertScaleAbs(sobel_x) # 取绝对值并转为 CV_8U
abs_sobel_y = cv2.convertScaleAbs(sobel_y)

# 因为索贝尔算子同时计算 X Y梯度容易出错 (直接Sobel(img, CV_64F, 1, 1, ksize=3)效果不同)
# 所以分别计算X和Y的梯度,然后进行合并
# 合并方式1: 加权和
gradient_combined_weighted = cv2.addWeighted(abs_sobel_x, 0.5, abs_sobel_y, 0.5, gamma=0)
# 合并方式2: 计算梯度幅值 (更常用)
# gradient_magnitude = cv2.magnitude(sobel_x, sobel_y)
# gradient_magnitude_uint8 = cv2.convertScaleAbs(gradient_magnitude)


cv2.imshow("Original Grayscale", img)
cv2.imshow("Sobel X Gradient (abs)", abs_sobel_x)
cv2.imshow("Sobel Y Gradient (abs)", abs_sobel_y)
# cv2.imshow("Sobel X & Y (combined display)", np.hstack((abs_sobel_x, abs_sobel_y)))
cv2.imshow("Sobel Combined (Weighted)", gradient_combined_weighted)
# cv2.imshow("Sobel Gradient Magnitude", gradient_magnitude_uint8)

cv2.waitKey(0)
cv2.destroyAllWindows()

沙尔算子 Scharr()

与Sobel类似,但使用固定的3x3 Scharr核,有时能提供比3x3 Sobel更精确的梯度估计。 参数与Sobel类似,但 ksize 固定为3x3,所以不需指定。

python
# 沙尔算子
import cv2
import numpy as np

# img_color = cv2.imread('./3.png') # 替换为你的图片路径
img_color = np.zeros((300,400,3), dtype=np.uint8)
cv2.circle(img_color, (200,150), 100, (255,255,255), -1) # 白色圆形
img = cv2.cvtColor(img_color, cv2.COLOR_BGR2GRAY)


# 计算X方向梯度
scharr_x = cv2.Scharr(img, cv2.CV_64F, dx=1, dy=0)
# 计算Y方向梯度
scharr_y = cv2.Scharr(img, cv2.CV_64F, dx=0, dy=1)

# 转换回uint8
abs_scharr_x = cv2.convertScaleAbs(scharr_x)
abs_scharr_y = cv2.convertScaleAbs(scharr_y)

# 合并梯度
scharr_combined = cv2.addWeighted(abs_scharr_x, 0.5, abs_scharr_y, 0.5, gamma=0)
# scharr_magnitude = cv2.convertScaleAbs(cv2.magnitude(scharr_x, scharr_y))

cv2.imshow("Original Grayscale", img)
cv2.imshow("Scharr X Gradient (abs)", abs_scharr_x)
cv2.imshow("Scharr Y Gradient (abs)", abs_scharr_y)
# cv2.imshow("Scharr X & Y (Combined Display)", np.hstack((abs_scharr_x, abs_scharr_y)))
cv2.imshow("Scharr Combined (Weighted)", scharr_combined)
# cv2.imshow("Scharr Magnitude", scharr_magnitude)

cv2.waitKey(0)
cv2.destroyAllWindows()

边缘检测 Canny()

Canny边缘检测是一种多阶段算法,用于检测图像中的多种边缘。 cv2.Canny(image, threshold1, threshold2)

  • image: 输入图像 (通常是灰度图)。
  • threshold1: 第一个阈值,用于滞后阈值法中的低阈值。
  • threshold2: 第二个阈值,用于滞后阈值法中的高阈值。
  • 边缘强度高于 threshold2 的像素被认为是强边缘,低于 threshold1 的被抑制。介于两者之间的,如果连接到强边缘,则保留。
  • 阈值越小,检测到的细节(和噪声)越丰富。
python
# Canny
import cv2
import numpy as np

# 导入图片
# img_color = cv2.imread('./3.png') # 替换为你的图片路径
img_color = np.random.randint(0,256, (300,400,3), dtype=np.uint8)
img = cv2.cvtColor(img_color, cv2.COLOR_BGR2GRAY) # Canny通常在灰度图上操作

# 阈值越小, 细节越丰富
# 推荐的 threshold2:threshold1 比例是 2:1 或 3:1
edges1 = cv2.Canny(img, 100, 200) # 常用的一组阈值
edges2 = cv2.Canny(img, 50, 150)  # 更低的阈值,可能检测更多细节/噪声

cv2.imshow('Original Grayscale', img)
cv2.imshow('Canny Edges (100, 200)', edges1)
cv2.imshow('Canny Edges (50, 150)', edges2)
# cv2.imshow('Canny Comparison', np.hstack((edges1, edges2)))

cv2.waitKey(0)
cv2.destroyAllWindows()

形态学操作

形态学操作是基于形状的图像处理技术,通常在二值图像上进行。它们使用一个称为“结构元素”或“核”的小型二值图像来探测输入图像。

腐蚀 erode(src, kernel[, iterations])

腐蚀操作会“侵蚀”前景对象的边界。前景像素(通常是白色)只有当结构元素下的所有像素都为前景像素时才保持为前景,否则变为背景(黑色)。

  • 效果:使物体变小,可以去除小的白色噪点,分离连接的物体。
  • kernel: 结构元素 (核),通常用 np.ones((rows, cols), np.uint8)cv2.getStructuringElement() 创建。
  • iterations: 腐蚀操作的次数。
python
# 腐蚀 膨胀
import cv2
import numpy as np

# src = cv2.imread('./x2.bmp') # 替换为你的二值图像路径
# 为演示,创建一个带白色物体的黑色背景图像
src = np.zeros((300,400), dtype=np.uint8)
cv2.rectangle(src, (50,50), (200,150), 255, -1) # 白色矩形
cv2.circle(src, (300,100), 30, 255, -1)      # 白色小圆 (可能被腐蚀掉)
# 添加一些白色噪点
for _ in range(100):
    x, y = np.random.randint(0, src.shape[1]), np.random.randint(0, src.shape[0])
    cv2.circle(src, (x,y), 1, 255, -1)


"""
图像腐蚀
src: 输入图像 (通常是二值图)
kernel: 用于腐蚀操作的结构元素 (矩阵)。
anchor: 矩阵的锚点位置。默认是结构元素的中心点。
iterations: 腐蚀次数。默认为1。
borderType: 处理图像边缘的情况。默认为cv2.BORDER_CONSTANT。
borderValue: 边界值。默认为0。
cv2.erode(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]])
"""
# 创建一个结构元素 (核)
kernel = np.ones((5,5), np.uint8) # 5x5 的方形核
# kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5)) # 矩形核
# kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5)) # 椭圆核
# kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (5,5)) # 十字形核

eroded_img = cv2.erode(src, kernel, iterations=1)

# 膨胀 (通常用于恢复腐蚀后的物体大小,或填充物体内部小洞)
# dilated_after_erode = cv2.dilate(eroded_img, kernel, iterations=1)

cv2.imshow('Original Binary Image', src)
cv2.imshow('Eroded Image', eroded_img)
# cv2.imshow('Dilated after Erode (Opening-like)', dilated_after_erode)
# cv2.imshow('Comparison', np.hstack((src, eroded_img)))

cv2.waitKey(0)
cv2.destroyAllWindows()

膨胀 dilate(src, kernel[, iterations])

膨胀操作会“扩张”前景对象的边界。如果结构元素下的至少一个像素是前景像素,则锚点对应的像素变为前景。

  • 效果:使物体变大,可以填充物体内部的小黑洞,连接断开的物体部分。
python
# 膨胀 腐蚀  (通常用于 开运算: 腐蚀后膨胀 -> 去除小噪点; 闭运算: 膨胀后腐蚀 -> 填充小孔洞)
import cv2
import numpy as np

# src = cv2.imread('./x4.bmp') # 替换为你的二值图像路径
# 为演示,创建一个带黑色孔洞的白色物体
src = np.full((300,400), 255, dtype=np.uint8) # 全白背景
cv2.rectangle(src, (0,0), (400,300), 0, 20) # 黑色边框,模拟物体边界
cv2.circle(src, (100,100), 10, 0, -1) # 黑色小孔
cv2.line(src, (150,50), (150,150), 0, 3) # 物体内部的黑色细线


"""
图像膨胀
src: 输入图像
kernel: 用于膨胀操作的结构元素。
anchor: 锚点位置。
iterations: 膨胀次数。
cv2.dilate(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]])
"""
kernel = np.ones((5,5),np.uint8)

# 膨胀操作,例如 iterations=2
dilated_img = cv2.dilate(src, kernel, iterations=1)

# 腐蚀操作 (如果想做闭运算:先膨胀后腐蚀)
# eroded_after_dilate = cv2.erode(dilated_img, kernel, iterations=1)

cv2.imshow('Original Binary Image with Holes/Gaps', src)
cv2.imshow('Dilated Image', dilated_img)
# cv2.imshow('Eroded after Dilate (Closing-like)', eroded_after_dilate)
# cv2.imshow('Comparison', np.hstack((dilated_img, eroded_after_dilate))) # 原文是这个,但可能src更有对比意义

cv2.waitKey(0)
cv2.destroyAllWindows()

通用形态学函数 cv2.morphologyEx()

cv2.morphologyEx(src, op, kernel[, anchor[, iterations[, borderType[, borderValue]]]])

  • src: 原图

  • op: 代表操作类型。各种形态学运算的操作规则均是将腐蚀和膨胀操作进行组合而得到的。

    • cv2.MORPH_ERODE: 腐蚀 (cv2.erode(src, kernel, iterations=...))
    • cv2.MORPH_DILATE: 膨胀 (cv2.dilate(src, kernel, iterations=...))
    • cv2.MORPH_OPEN: 开运算 (先腐蚀后膨胀: dilate(erode(src))) - 去除小的白色噪点。
    • cv2.MORPH_CLOSE: 闭运算 (先膨胀后腐蚀: erode(dilate(src))) - 填充前景物体内的小黑洞,连接邻近物体。
    • cv2.MORPH_GRADIENT: 形态学梯度 (膨胀图像 - 腐蚀图像: dilate(src) - erode(src)) - 提取物体轮廓。
    • cv2.MORPH_TOPHAT: 顶帽运算 (原图像 - 开运算图像: src - open(src)) - 分离出比周围区域亮的小区域(噪声或细节)。
    • cv2.MORPH_BLACKHAT: 黑帽运算 (闭运算图像 - 原图像: close(src) - src) - 分离出比周围区域暗的小区域(孔洞或细节)。
    • cv2.MORPH_HITMISS: 击中击不中 (前景背景腐蚀运算的交集) - 用于特定模式匹配。
  • kernel, anchor, iterations, borderType, borderValue 参数与 cv2.erode() 内相应参数的含义一致。

开运算 cv2.MORPH_OPEN

开运算进行的操作是先将图像腐蚀,再对腐蚀的结果进行膨胀。 功能:可以用于去除小的白色噪点、平滑物体轮廓、断开细小连接。

python
import numpy as np
import cv2

# img1 = cv2.imread("opening1.png",-1) # 图像带有很多小的白色噪点
# img2 = cv2.imread("opening2.png",-1) # 图像有细微的连接或毛刺

# 示例图像1: 带白色噪点的黑色背景
img1 = np.zeros((200,300), dtype=np.uint8)
cv2.rectangle(img1, (50,50), (150,150), 255, -1) # 主要物体
# 添加白色噪点
for _ in range(50):
    x, y = np.random.randint(0, img1.shape[1]), np.random.randint(0, img1.shape[0])
    cv2.circle(img1, (x,y), np.random.randint(1,3), 255, -1)

# 示例图像2: 带有细小连接的物体
img2 = np.zeros((200,300), dtype=np.uint8)
cv2.rectangle(img2, (30,80), (100,120), 255, -1)
cv2.rectangle(img2, (130,80), (200,120), 255, -1)
cv2.line(img2, (95,100), (135,100), 255, 2) # 细小连接


kernel = np.ones((5,5),dtype=np.uint8) # 5x5的核
# kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))

# 对img1去噪
r1 = cv2.morphologyEx(img1, cv2.MORPH_OPEN, kernel, iterations=1)
# 对img2断开连接 (可能需要调整kernel大小或迭代次数)
r2 = cv2.morphologyEx(img2, cv2.MORPH_OPEN, kernel, iterations=1)


cv2.imshow("Original Image 1 (with noise)",img1)
cv2.imshow("Open Operation on Image 1",r1)
cv2.imshow("Original Image 2 (with thin bridge)",img2)
cv2.imshow("Open Operation on Image 2",r2)
# cv2.imwrite("opened_image1.jpg",r1)

cv2.waitKey(0)
cv2.destroyAllWindows()

闭运算 cv2.MORPH_CLOSE

闭运算是先膨胀、后腐蚀的运算。 功能:它有助于关闭前景物体内部的小孔,或去除物体上的小黑点,还可以将不同的前景图像进行连接。

python
import numpy as np
import cv2

# img1 = cv2.imread("close1.png",-1) # 图像前景物体内有小孔
# img2 = cv2.imread("close2.png",-1) # 图像前景物体有小的断裂

# 示例图像1: 前景物体内有小孔
img1 = np.full((200,300), 255, dtype=np.uint8) # 白色背景
cv2.rectangle(img1, (50,50), (250,150), 0, -1) # 黑色物体
cv2.circle(img1, (100,100), 5, 255, -1) # 物体内部的白色小孔 (目标是黑色物体,所以孔是白色)
# 如果目标是白色物体,孔是黑色:
img1_alt = np.zeros((200,300), dtype=np.uint8) # 黑色背景
cv2.rectangle(img1_alt, (50,50), (250,150), 255, -1) # 白色物体
cv2.circle(img1_alt, (100,100), 5, 0, -1) # 物体内部的黑色小孔

# 示例图像2: 前景物体有断裂
img2 = np.zeros((200,300), dtype=np.uint8)
cv2.rectangle(img2, (30,80), (100,120), 255, -1)
cv2.rectangle(img2, (120,80), (190,120), 255, -1) # 有一个10像素的断裂


kernel = np.ones((10,10),dtype=np.uint8) # 较大的核
# kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (10,10))


# 对img1_alt填充小孔 (针对白色物体上的黑色小孔)
r1 = cv2.morphologyEx(img1_alt, cv2.MORPH_CLOSE, kernel, iterations=1)
# 对img2连接断裂
r2 = cv2.morphologyEx(img2, cv2.MORPH_CLOSE, kernel, iterations=1) # iterations=1可能就够

cv2.imshow("Original Image 1 (white obj, black hole)",img1_alt)
cv2.imshow("Close Operation on Image 1",r1)
cv2.imshow("Original Image 2 (broken)",img2)
cv2.imshow("Close Operation on Image 2",r2)

cv2.waitKey(0)
cv2.destroyAllWindows()

形态学梯度运算 cv2.MORPH_GRADIENT

形态学梯度运算是用图像的膨胀图像减去腐蚀图像 (dilate(src) - erode(src))。 功能:该操作可以获取原始图像中前景图像的边缘或轮廓。

python
import numpy as np
import cv2

# o = cv2.imread('src.jpg',-1) # 替换为你的二值或灰度图像
# 示例图像
o = np.zeros((200,300), dtype=np.uint8)
cv2.circle(o, (100,100), 50, 255, -1)
cv2.rectangle(o, (150,50), (250,150), 255, -1)

kernel = np.ones((3,3),dtype=np.uint8) # 较小的核通常效果好
# kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))

# 形态学梯度运算
r = cv2.morphologyEx(o, cv2.MORPH_GRADIENT, kernel)

cv2.imshow("Original Image",o)
cv2.imshow("Morphological Gradient (Outline)",r)

cv2.waitKey(0)
cv2.destroyAllWindows()

顶帽运算 (礼帽运算) cv2.MORPH_TOPHAT

顶帽运算是用原始图像减去其开运算图像 (src - open(src))。 功能:能够获取图像的噪声信息(特别是比周围亮的噪点),或者得到比原始图像的边缘更亮的边缘信息。

python
import numpy as np
import cv2

# o = cv2.imread('erode.png',-1) # 图像有小的亮点或细节
# o2 = cv2.imread("beauty.jpg",-1) # 演示在灰度图上提取亮细节

# 示例图像1: 带有小亮点的图像
o = np.zeros((200,300), dtype=np.uint8)
cv2.rectangle(o, (50,50), (250,150), 128, -1) # 中等灰度物体
cv2.circle(o, (100,80), 3, 200, -1) # 较亮的点
cv2.circle(o, (150,100), 5, 220, -1) # 更亮的点

# 示例图像2 (灰度图)
o2_color = np.random.randint(0, 100, (200,300,3), dtype=np.uint8)
cv2.putText(o2_color, "Detail", (50,100), cv2.FONT_HERSHEY_SIMPLEX, 2, (180,180,180), 5) # 稍亮的文字
o2 = cv2.cvtColor(o2_color, cv2.COLOR_BGR2GRAY)


# 礼帽运算使用原始图像减开运算图像得到礼帽图像,礼帽图像是原始图像中的噪声信息 (或亮细节)。
kernel = np.ones((9,9),dtype=np.uint8) # 核的大小影响检测到的细节/噪声的大小
# kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9,9))

r = cv2.morphologyEx(o,cv2.MORPH_TOPHAT,kernel)
r2 = cv2.morphologyEx(o2,cv2.MORPH_TOPHAT,kernel, iterations=1) # iterations通常为1

cv2.imshow("Original Image 1 (dark with bright spots)",o)
cv2.imshow("TopHat Result 1",r)
cv2.imshow("Original Image 2 (gray with bright text)",o2)
cv2.imshow("TopHat Result 2",r2)

cv2.waitKey(0)
cv2.destroyAllWindows()

黑帽运算 cv2.MORPH_BLACKHAT

黑帽运算是用闭运算图像减去原始图像 (close(src) - src)。 功能:能够获取图像内部的小孔,或前景色中的小黑点,或者得到比原始图像的边缘更暗的边缘部分。

python
import numpy as np
import cv2

# o = cv2.imread('hole.png',-1) # 图像有小的暗点或孔洞
# o2 = cv2.imread("beauty.jpg",-1)

# 示例图像1: 带有小暗点的图像
o = np.full((200,300), 200, dtype=np.uint8) # 亮灰色背景
cv2.rectangle(o, (50,50), (250,150), 100, -1) # 中等灰度物体
cv2.circle(o, (100,80), 3, 30, -1) # 较暗的点 (孔)
cv2.circle(o, (150,100), 5, 10, -1) # 更暗的点 (孔)

# 示例图像2 (灰度图)
o2_color = np.random.randint(150, 255, (200,300,3), dtype=np.uint8) # 较亮的背景
cv2.putText(o2_color, "Shadow", (50,100), cv2.FONT_HERSHEY_SIMPLEX, 2, (50,50,50), 5) # 较暗的文字
o2 = cv2.cvtColor(o2_color, cv2.COLOR_BGR2GRAY)

# 黑帽运算使用闭运算图像减原始图像得到黑帽图像,黑帽图像是原始图像中的小孔(噪声或暗细节)。
kernel = np.ones((9,9),dtype=np.uint8) # 核的大小影响检测到的细节/噪声的大小
# kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9,9))

r = cv2.morphologyEx(o,cv2.MORPH_BLACKHAT,kernel)
r2 = cv2.morphologyEx(o2,cv2.MORPH_BLACKHAT,kernel)

cv2.imshow("Original Image 1 (bright with dark spots)",o)
cv2.imshow("BlackHat Result 1",r)
cv2.imshow("Original Image 2 (gray with dark text)",o2)
cv2.imshow("BlackHat Result 2",r2)

cv2.waitKey(0)
cv2.destroyAllWindows()

模板匹配 cv2.matchTemplate(image, templ, method)

在一幅大图像中查找模板图像出现的位置。

  • image: 输入图像 (大图)。
  • templ: 模板图像 (小图)。
  • method: 匹配方法。
    • cv2.TM_SQDIFF: 平方差匹配法。计算平方差和,值越小越匹配。
    • cv2.TM_SQDIFF_NORMED: 标准化平方差匹配法。结果在[0,1]之间,0表示完美匹配。
    • cv2.TM_CCORR: 相关性匹配法。值越大越匹配。
    • cv2.TM_CCORR_NORMED: 标准化相关性匹配法。结果在[0,1]之间,1表示完美匹配。
    • cv2.TM_CCOEFF: 相关系数匹配法。值越大越匹配。
    • cv2.TM_CCOEFF_NORMED: 标准化相关系数匹配法。结果在[0,1]之间,1表示完美匹配。
  • result: 计算出的匹配结果,一个灰度图,每个像素表示模板在该位置的匹配程度。
  • mask: 可选掩模 (仅对 TM_SQDIFFTM_CCORR_NORMED 有效)。
python
# 模板匹配  单模板
import cv2
import numpy as np

"""
image: 输入图像
templ: 模板图像
method: 匹配方法
result: 计算出的匹配结果
mask: 可选参数。掩模,只有cv2.TM_SQDIFF和cv2.TM_CCORR_NORMED支持
cv2.matchTemplate(image, templ, method[, result[, mask]])
"""
# 源图  模板图
# img = cv2.imread('./WIndos.png') # 替换为你的大图路径
# templ = cv2.imread('./VS.png')   # 替换为你的模板路径

# 为演示,创建源图和模板
img = np.zeros((400, 500, 3), dtype=np.uint8)
cv2.rectangle(img, (100, 100), (200, 180), (0, 255, 0), -1) # 绿色矩形作为背景中的一部分
cv2.putText(img, "Main Scene", (10,30), cv2.FONT_HERSHEY_SIMPLEX, 1, (255,255,255), 2)

# 创建模板 (从img中截取或单独创建)
# templ = img[120:170, 120:180].copy() # 从img中截取一个50x60的模板
templ = np.zeros((50,60,3), dtype=np.uint8)
cv2.ellipse(templ, (30,25), (25,15), 0, 0, 360, (0,0,255), -1) # 红色椭圆作为模板
cv2.putText(templ, "T", (20,35), cv2.FONT_HERSHEY_SIMPLEX, 1, (255,255,255), 2)

# 将模板放置到主场景中,以便能找到它
img[250:250+templ.shape[0], 300:300+templ.shape[1]] = templ


# 获取模板图像宽 高 绘制一个矩形
h, w = templ.shape[:2] # OpenCV通常是 (height, width, channels)
print(f"Template width: {w}, height: {h}")

# 进行模板匹配
# method = cv2.TM_SQDIFF_NORMED # 对于此方法,最小值是最佳匹配
method = cv2.TM_CCOEFF_NORMED # 对于此方法,最大值是最佳匹配
res_map = cv2.matchTemplate(img, templ, method)

# res_map 是一个灰度图,其大小为 (W-w+1, H-h+1)
# W, H是大图宽高, w, h是模板宽高

# 使用minMaxLoc函数找到匹配结果中的最小值和最大值,以及它们的坐标位置
# minValue和maxValue分别存储最小和最大值
# minLoc和maxLoc分别存储最小值和最大值的坐标 (左上角)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res_map)

# 根据选择的method确定最佳匹配点
if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
    top_left = min_loc
    match_val = min_val
    print(f"Best match (min value for SQDIFF methods): {match_val} at {top_left}")
else: # TM_CCORR, TM_CCORR_NORMED, TM_CCOEFF, TM_CCOEFF_NORMED
    top_left = max_loc
    match_val = max_val
    print(f"Best match (max value for CCORR/CCOEFF methods): {match_val} at {top_left}")


# 根据左上角坐标和模板的宽高,计算右下角坐标
bottom_right = (top_left[0] + w, top_left[1] + h)

# 在原图中画出矩形标记匹配位置
img_display = img.copy() # 复制一份用于显示,避免在原图上重复画
cv2.rectangle(img_display, top_left, bottom_right, (255, 0, 255), 2) # 紫色框

cv2.imshow("Original Image", img)
cv2.imshow("Template", templ)
cv2.imshow("Matching Result Map", res_map) # 可以看到匹配强度图
cv2.imshow("Detected Match", img_display)
cv2.waitKey(0)
cv2.destroyAllWindows()

带缩放的模板匹配:参考CSDN博客 (这通常涉及多尺度模板匹配,即在不同缩放比例的图像上进行模板匹配)

python
# 匹配多个 (当模板在图像中出现多次时)
import cv2
import numpy as np

# img = cv2.imread("./Winss.png") # 替换为你的大图路径
# templ = cv2.imread("./7.png")   # 替换为你的模板路径

# 为演示,创建源图和模板
img = np.zeros((400, 600, 3), dtype=np.uint8)
cv2.putText(img, "Multi-Match", (10,30), cv2.FONT_HERSHEY_SIMPLEX, 1, (255,255,255), 2)

templ = np.zeros((40,50,3), dtype=np.uint8)
cv2.circle(templ, (25,20), 15, (0,255,255), -1) # 黄色圆圈模板

# 在img中放置多个模板实例
img[50:50+templ.shape[0], 80:80+templ.shape[1]] = templ
img[150:150+templ.shape[0], 200:200+templ.shape[1]] = templ
img[100:100+templ.shape[0], 350:350+templ.shape[1]] = templ
img[250:250+templ.shape[0], 450:450+templ.shape[1]] = templ


h, w = templ.shape[:2]

# 使用 TM_CCOEFF_NORMED 或 TM_CCORR_NORMED 效果较好
results_map = cv2.matchTemplate(img, templ, cv2.TM_CCOEFF_NORMED)

# 设置一个阈值来找到所有匹配项
threshold = 0.8 # 根据实际情况调整,越接近1要求越严格

# 找到所有超过阈值的匹配位置
# np.where 返回满足条件的元素的索引 (y_coords, x_coords)
locations = np.where(results_map >= threshold)

img_display = img.copy()
# locations 是一个元组 (array_of_y_indices, array_of_x_indices)
# 我们需要将它们配对成 (x,y) 坐标
for pt in zip(*locations[::-1]): # *locations[::-1] 将 (Ys, Xs) 变为 (Xs, Ys) 然后zip
    # pt is (x, y) which is the top-left corner of the match
    cv2.rectangle(img_display, pt, (pt[0] + w, pt[1] + h), (0, 0, 255), 2) # 红色框

print(f"Found {len(locations[0])} matches above threshold {threshold}")

cv2.imshow("Original Image", img)
cv2.imshow("Template", templ)
cv2.imshow("Result Map", results_map)
cv2.imshow("Multiple Detections", img_display)
cv2.waitKey(0)
cv2.destroyAllWindows()

cvtColor 色彩空间转换

cv2.cvtColor(src, code) 用于转换图像的色彩空间。

python
import cv2
import numpy as np

# img_bgr = cv2.imread('./3.png') # 替换为你的彩色图片路径
img_bgr = np.random.randint(0, 256, (300,400,3), dtype=np.uint8) # 随机BGR图像

# 将彩色图像 (BGR) 转化为灰阶图像
gray_img = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)

# 其他常见转换:
# BGR to HSV
hsv_img = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV)
# BGR to RGB (如果需要与Pillow或Matplotlib交互)
rgb_img = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)

cv2.imshow("Original BGR", img_bgr)
cv2.imshow("Grayscale", gray_img)
cv2.imshow("HSV", hsv_img)
cv2.imshow("RGB", rgb_img) # 注意:imshow期望BGR,所以RGB图颜色会反
cv2.waitKey(0)
cv2.destroyAllWindows()

识别

OpenCV 包含预训练的级联分类器(Haar cascades, LBP cascades)用于对象检测,如人脸、眼睛、车牌等。 分类器文件通常位于 OpenCV 安装目录的 data 文件夹下。 例如路径: C:\Users\YourUser\AppData\Local\Programs\Python\PythonXX\Lib\site-packages\cv2\data (路径可能因安装方式和系统而异)。 你需要找到这些 .xml 文件并将它们复制到你的项目目录,或者提供它们的完整路径。

加载级联分类器 CascadeClassifier(路径_到_xml_文件)

CascadeClassifier 类用于加载预训练的分类器。

python
# # CascadeClassifier函数方法加载级联分类器
# # 确保 .xml 文件路径正确
# try:
#     # 尝试从cv2.data模块获取路径 (推荐方式,如果OpenCV安装正确)
#     import cv2
#     haar_cascade_path = cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
#     Cas = cv2.CascadeClassifier(haar_cascade_path)
#     if Cas.empty():
#         print(f"错误: 无法加载级联分类器文件: {haar_cascade_path}")
#         print("请确保OpenCV已正确安装,或者提供 .xml 文件的正确路径。")
#     else:
#         print(f"级联分类器 {haar_cascade_path} 加载成功。")
# except Exception as e:
#     print(f"加载级联分类器时发生错误: {e}")
#     print("请将 'haarcascade_frontalface_default.xml' 文件复制到脚本目录或提供其完整路径。")
#     # Cas = cv2.CascadeClassifier('./haarcascade_frontalface_default.xml') # 如果文件在同目录下

使用 detectMultiScale 方法进行检测: objects = Cas.detectMultiScale(image[, scaleFactor[, minNeighbors[, flags[, minSize[, maxSize]]]]])

  • image: 需要进行分析的图像 (通常是灰度图,Haar cascades 对灰度图效果更好)。
  • scaleFactor: 图像分析时每次迭代图像尺寸的缩放比例 (e.g., 1.1, 1.2)。值越小,检测越慢但可能更准。
  • minNeighbors: 每个候选矩形应该保留多少个邻居。该值越大,检测结果越少但误报也越少(判断更准确)。(e.g., 3-6)
  • flags: 老的OpenCV参数,通常忽略,使用默认值即可。
  • minSize: 检测对象的最小尺寸 (width, height)
  • maxSize: 检测对象的最大尺寸 (width, height)
  • 返回值 objects: 一个列表,每个元素是一个检测到的对象区域,格式为 [x, y, w, h] (左上角x, 左上角y, 宽度, 高度)。 例如 [[x1, y1, w1, h1], [x2, y2, w2, h2], ...]

车牌识别

(注意:OpenCV 自带的 haarcascade_russian_plate_number.xml 对特定类型的俄罗斯车牌有效,对其他国家或类型的车牌效果可能不佳。)

python
import cv2
import numpy as np

# img_color = cv2.imread('./456.webp') # 替换为你的车牌图片路径
# 为演示,创建一个带"车牌"的图像
img_color = np.full((300, 500, 3), (180,180,180), dtype=np.uint8) # 灰色背景
cv2.rectangle(img_color, (100,120), (300,180), (250,230,100), -1) # 模拟车牌区域 (浅蓝色)
cv2.putText(img_color, "PLATE", (130,160), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,0), 2)

# 确保 haarcascade_russian_plate_number.xml 文件存在
# plate_cascade_path = 'haarcascade_russian_plate_number.xml'
# 如果从cv2.data获取:
try:
    plate_cascade_path = cv2.data.haarcascades + 'haarcascade_russian_plate_number.xml'
    RussianPlateCascade = cv2.CascadeClassifier(plate_cascade_path)
    if RussianPlateCascade.empty():
        print(f"错误: 无法加载车牌级联分类器: {plate_cascade_path}")
        use_dummy_plates = True
    else:
        print("车牌级联分类器加载成功。")
        use_dummy_plates = False
except Exception as e:
    print(f"加载车牌分类器出错: {e}. 将使用虚拟检测框。")
    use_dummy_plates = True


if img_color is None:
    print("图像加载失败。")
    exit()

gray_img = cv2.cvtColor(img_color, cv2.COLOR_BGR2GRAY)

if not use_dummy_plates:
    # 识别出车牌 (不支持中文路径)
    # 返回矩阵 [[x,y,w,h], ...] 表示车牌的起始点坐标 及宽高
    plates = RussianPlateCascade.detectMultiScale(gray_img, scaleFactor=1.1, minNeighbors=5, minSize=(25,25))
    print(f"检测到的车牌: {plates}")
else:
    # 如果分类器加载失败,使用一个虚拟检测框
    plates = np.array([[100, 120, 200, 60]]) if img_color is not None else []


# 绘制一个矩形
img_display = img_color.copy()
for (x,y,w,h) in plates:
    cv2.rectangle(img_display,(x,y),(x+w,y+h),(0,0,255), 2) # 红色框

cv2.imshow("Image with Detected Plates",img_display)
cv2.waitKey(0)
cv2.destroyAllWindows()

人脸识别 haarcascade_frontalface_default

python
# 人脸验证  加载级联分类器(将多个简单的分类器按照一定的顺序给与关联 眼睛-鼻子-嘴巴...)
# 使用分类器识别图像
import cv2
import numpy as np

# img_color = cv2.imread('./bai.jpeg') # 替换为你的含有人脸的图片路径
# 为演示,创建一个带"人脸"的图像
img_color = np.full((400, 500, 3), (200,200,180), dtype=np.uint8) # 背景
cv2.circle(img_color, (250,200), 80, (150,180,220), -1) # 模拟脸部肤色
cv2.circle(img_color, (220,180), 10, (0,0,0), -1) # 左眼
cv2.circle(img_color, (280,180), 10, (0,0,0), -1) # 右眼
cv2.ellipse(img_color, (250,240), (30,10), 0, 0, 180, (0,0,0), -1) # 嘴

# CascadeClassifier函数方法加载级联分类器
try:
    face_cascade_path = cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
    FaceCascade = cv2.CascadeClassifier(face_cascade_path)
    if FaceCascade.empty():
        print(f"错误: 无法加载人脸级联分类器: {face_cascade_path}")
        use_dummy_faces = True
    else:
        print("人脸级联分类器加载成功。")
        use_dummy_faces = False
except Exception as e:
    print(f"加载人脸分类器出错: {e}. 将使用虚拟检测框。")
    use_dummy_faces = True


if img_color is None:
    print("图像加载失败。")
    exit()

gray_img = cv2.cvtColor(img_color, cv2.COLOR_BGR2GRAY)

"""
参数 (detectMultiScale)
 image: 需要进行分析的图像
 scaleFactor: 图像分析时缩放比例
 minNeighbors: 最少要保留多少个检测结果/特征 才可以判定为人脸。该值越大,判断越准确。
 flags: 老的OpenCV参数,忽略,使用默认值即可。
 minSize: 最小尺寸 (width, height)
 maxSize: 最大尺寸 (width, height)
 `plats = Cas.detectMultiScale(image[, scaleFactor[, minNeighbors[, flags[, minSize[, maxSize]]]]])`
 返回值 plats: 表示特征所在位置的区域坐标数组。数组中每一个元素都是一个目标区域。
 每一个目标区域中都存在四个值: 左上角横坐标X, 左上角纵坐标Y, 宽W, 高H。
 例如: [[829, 561, 309, 103]]
"""
if not use_dummy_faces:
    faces = FaceCascade.detectMultiScale(gray_img, scaleFactor=1.1, minNeighbors=5, minSize=(30,30))
    print(f"检测到的人脸: {faces}")
else:
    # 如果分类器加载失败,使用一个虚拟检测框
    # (x,y,w,h) for the dummy face relative to the drawn face
    # Center (250,200), radius 80. So x approx 250-80=170, y approx 200-80=120. w,h approx 160.
    faces = np.array([[170, 120, 160, 160]]) if img_color is not None else []


# 绘制矩形
img_display = img_color.copy()
for (x,y,w,h) in faces:
    # img, pt1 (top-left), pt2 (bottom-right), color(BGR), thickness
    cv2.rectangle(img_display,(x,y),(x+w,y+h),(255,0,0),3) # 蓝色框

cv2.imshow("Image with Detected Faces",img_display)
cv2.waitKey(0)
cv2.destroyAllWindows()

视频识别

(视频中人脸识别,用圆绘制)

python
# 视频识别
import cv2

# CascadeClassifier函数方法加载级联分类器
try:
    face_cascade_path = cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
    FaceCascade = cv2.CascadeClassifier(face_cascade_path)
    if FaceCascade.empty():
        print(f"错误: 无法加载人脸级联分类器: {face_cascade_path}. 实时检测将无法工作。")
        # exit() # 如果没有分类器,演示无法进行
except Exception as e:
    print(f"加载人脸分类器时发生错误: {e}. 实时检测将无法工作。")
    # exit()

# VideoCapture加载视频 or摄像头
# Vd = cv2.VideoCapture("./XW.mp4") # 替换为你的视频文件路径
Vd = cv2.VideoCapture(0) # 使用默认摄像头

if not Vd.isOpened():
    print("无法打开视频源 (文件或摄像头)。")
    exit()

keep_running = True
print("按空格键或 'q' 键退出...")

while keep_running:
    # ret: 是否成功读取到帧 (True/False)
    # frame: 读取到的视频帧
    ret, frame = Vd.read()

    if not ret:
        print("无法读取帧,视频结束或摄像头断开。")
        break

    if not FaceCascade.empty(): # 仅当分类器加载成功时才进行检测
        gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        faces = FaceCascade.detectMultiScale(gray_frame, scaleFactor=1.1, minNeighbors=10, minSize=(50,50))

        for (x,y,w,h) in faces:
            # 绘制矩形
            # cv2.rectangle(frame,(x,y),(x+w,y+h),(255,0,0),3)
            # 绘制圆形: img, center, radius, color, thickness
            # center_x = x + w//2
            # center_y = y + h//2
            # radius = w//2 # 或 h//2,或 min(w,h)//2
            cv2.circle(frame, (x + w//2, y + h//2), w//2, (0,0,255), 3) # 红色圆
    else: # 如果分类器未加载,显示提示
        cv2.putText(frame, "Face Cascade NOT LOADED", (20,40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255),2)


    cv2.imshow("Video Face Detection",frame)
    key = cv2.waitKey(1) & 0xFF # 等待1ms

    if key == ord('q') or key == 32: # 32 is ASCII for spacebar
        keep_running = False
        break

Vd.release()
cv2.destroyAllWindows()

如何使用python的opencv实现人脸识别_python_脚本之家

人脸录入

python
import os
import cv2 # 使用 cv2 即可,cv 是旧的别名
import numpy as np # 虽然没直接用,但 cv2 内部依赖

# 人脸录入部分是为了得到人脸的数据,然后后面对人脸进行训练。
# 人脸数据信息主要有三个部分。1、人脸图片。 2、人脸名字。 3、编号(编号需要和名字需要统一。因为训练数据是拿序号和人脸进行比对)
# 1、人脸图片(打开摄像头、只取人脸部分、给用户实时展示人脸的画面(把人脸框出来)、保存和退出)
# 2、人脸名字。(用户输入)
# 3、编号。(文件名称记录)

# 创建data文件夹(如果不存在)
data_folder = "./data_faces" # 修改文件夹名以区分
if not os.path.exists(data_folder):
    os.makedirs(data_folder)
    print(f"文件夹 '{data_folder}' 已创建。")

# 打开摄像头
cap = cv2.VideoCapture(0)

if not cap.isOpened():
    print('连接摄像头失败')
    exit()

# 输入人脸的名字
name = input("请输入姓名(拼音或英文,避免中文路径问题):")
if not name.strip(): # 检查是否为空或只有空格
    print('姓名不能为空,请重新输入!!!')
    cap.release()
    exit()
print('姓名输入完成:按 s 保存人脸,按 q 退出。')

# 加载级联检测器,人脸特征分类器
try:
    face_cascade_path = cv2.data.haarcascades + 'haarcascade_frontalface_alt2.xml'
    face_classifier = cv2.CascadeClassifier(face_cascade_path)
    if face_classifier.empty():
        print(f"错误:无法加载人脸分类器 {face_cascade_path}")
        cap.release()
        exit()
except Exception as e:
    print(f"加载人脸分类器时出错: {e}")
    cap.release()
    exit()


# 循环读取摄像头的每一帧画面,然后识别画面中的人脸,识别后只保存人脸部分,作为训练数据。
img_count = 0
max_images = 20 # 最多保存20张图片
print(f"将为 '{name}' 采集 {max_images} 张人脸图片。")

while True:
    # 每一帧画面
    flag, frame = cap.read()
    if not flag:
        print('读取失败')
        break
    # 转成灰度图像
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    faces = face_classifier.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(60, 60))

    # 框出人脸
    # 在显示帧上绘制矩形
    display_frame = frame.copy()
    for (x, y, w, h) in faces:
        cv2.rectangle(
            img=display_frame, pt1=(x, y), pt2=(x+w, y+h),
            color=(0, 255, 0), thickness=2 # 绿色框,线宽2
        )

    cv2.imshow("Face Enrollment - Press 's' to save, 'q' to quit", display_frame)

    # 保存和退出
    k = cv2.waitKey(1) & 0xFF
    if k == ord('s'):
        if len(faces) == 0:
            print('没有检测到人脸,请调整!!!')
        elif len(faces) > 1:
            print('检测到多个人脸,请确保只有一张人脸在框内!!!')
        else: # 正好检测到一张人脸
            x, y, w, h = faces[0]
            # 保存人脸部分 (灰度图)
            face_roi_gray = gray[y: y + h, x: x + w]

            # 获取或分配编号
            # name_map 得到一个字典  {name1: 1, name2: 2, name3: 3 }
            existing_files = os.listdir(data_folder)
            name_map = {}
            max_id = 0
            for f_name in existing_files:
                parts = f_name.split('.')
                if len(parts) >= 3: # id.name.count.jpg
                    try:
                        f_id = int(parts[0])
                        f_username = parts[1]
                        name_map.setdefault(f_username, f_id) # 第一个遇到的id作为该名字的id
                        if f_id > max_id:
                            max_id = f_id
                    except ValueError:
                        continue # 文件名格式不符

            if name in name_map:
                user_id = name_map[name]
            else:
                user_id = max_id + 1

            # 文件名  格式为 id.name.count.jpg
            img_count += 1
            file_name = f"{user_id}.{name}.{img_count}.jpg"
            file_path = os.path.join(data_folder, file_name)

            # 使用 imencode 解决中文路径问题 (虽然这里name是英文/拼音,但这是好习惯)
            # cv2.imencode('.jpg', face_roi_gray)[1].tofile(file_path)
            cv2.imwrite(file_path, face_roi_gray) # 如果路径全英文,imwrite也行
            print(f"图片已保存: {file_path}")

            if img_count >= max_images:
                print(f"已为 '{name}' 采集 {max_images} 张图片。录入完成。")
                break

    elif k == ord('q'):
        print("退出录入程序。")
        break

cap.release()
cv2.destroyAllWindows()

人脸数据训练

python
# 训练  拿到图片数据特征
import cv2
import os
import numpy as np

data_folder = "./data_faces" # 与录入时一致

if not os.path.exists(data_folder) or not os.listdir(data_folder):
    print(f"文件夹 '{data_folder}' 为空或不存在。请先运行人脸录入程序。")
    exit()

# 获取图片完整路径   f是文件名   os.path.join(路径, 文件)  路径+文件
image_paths = [os.path.join(data_folder, f) for f in os.listdir(data_folder) if f.endswith('.jpg')]

faces_data = []
ids = []

for image_path in image_paths:
    # 读取灰度图像
    # face_img = cv2.imdecode(np.fromfile(image_path, dtype=np.uint8), cv2.IMREAD_GRAYSCALE)
    # 如果路径全英文
    face_img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)

    if face_img is None:
        print(f"无法读取图像: {image_path}")
        continue

    faces_data.append(face_img)

    # 获取用户ID (从文件名中提取,例如 "1.John.1.jpg" -> ID=1)
    try:
        user_id = int(os.path.basename(image_path).split('.')[0])
        ids.append(user_id)
    except ValueError:
        print(f"文件名格式错误,无法提取ID: {image_path}")
        # faces_data.pop() # 移除对应的无效图像数据
        continue # 跳过这个图像

if not faces_data or not ids:
    print("没有有效的图像数据或ID用于训练。")
    exit()

print(f"准备训练 {len(faces_data)} 张人脸图片...")
# print(f"IDs: {ids}")

# 创建LBPH人脸识别器
recognizer = cv2.face.LBPHFaceRecognizer_create()

# 训练
recognizer.train(faces_data, np.array(ids))

# 保存训练好的模型
training_data_file = "./Training_data.yml" # 保存为 .yml 或 .xml
recognizer.write(training_data_file)
print(f"训练完成,模型已保存到 {training_data_file}")

人脸识别

python
# 人脸识别
"""
    通过摄像头识别人脸(LBPH识别器(训练的数据,和当前的人脸图片))得到编号。编号和人脸的对应关系,标记人脸和名字并展示画面
    创建识别器,加载训练数据
    读取文件构造编号和人脸的关系
    打开摄像头
    循环取每帧画面
    人脸检测且提取人脸部分
    遍历人脸进行识别
    框出人脸部分且实时展示画面(画面上带有名字和置信度)
    关闭摄像头和窗口
"""
import os
import cv2
# from cv2 import face # cv2.face 即可
from PIL import Image, ImageDraw, ImageFont # 用于显示中文名
import numpy as np

data_folder = "./data_faces" # 与录入时一致
training_data_file = "./Training_data.yml" # 与训练时一致

if not os.path.exists(training_data_file):
    print(f"训练数据 '{training_data_file}' 未找到。请先运行训练程序。")
    exit()

# 创建识别器并加载训练数据
recognizer = cv2.face.LBPHFaceRecognizer_create()
recognizer.read(training_data_file)

# 读取文件构造编号和人脸名字的关系。以字典的形式保存 {id: name}
name_map = {}
if os.path.exists(data_folder):
    for f_name in os.listdir(data_folder):
        parts = f_name.split('.')
        if len(parts) >= 2: # id.name...
            try:
                user_id = int(parts[0])
                user_name = parts[1]
                if user_id not in name_map: # 确保每个ID只映射一个名字
                    name_map[user_id] = user_name
            except ValueError:
                continue
else:
    print(f"数据文件夹 '{data_folder}' 未找到,无法映射ID到姓名。")

print(f"ID到姓名的映射: {name_map}")

# 加载人脸检测器
try:
    face_cascade_path = cv2.data.haarcascades + 'haarcascade_frontalface_alt2.xml'
    face_classifier = cv2.CascadeClassifier(face_cascade_path)
    if face_classifier.empty():
        print(f"错误:无法加载人脸分类器 {face_cascade_path}")
        exit()
except Exception as e:
    print(f"加载人脸分类器时出错: {e}")
    exit()

# Pillow字体设置 (用于显示中文名)
try:
    font_path_pil = "simkai.ttf" # 或者 simsun.ttc, msyh.ttc 等
    pil_font = ImageFont.truetype(font_path_pil, size=24)
except IOError:
    print(f"Pillow字体 '{font_path_pil}' 未找到,将使用默认字体。")
    pil_font = ImageFont.load_default()


# 打开摄像头
cap = cv2.VideoCapture(0)
if not cap.isOpened():
    print('连接摄像头失败')
    exit()
print("按 'q' 键退出识别程序。")

# 循环取每帧画面
while True:
    ret, frame = cap.read()
    if not ret:
        print('读取失败')
        break

    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    faces = face_classifier.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(60, 60))

    display_frame = frame.copy() # 用于绘制

    # 遍历检测到的人脸进行识别
    for (x, y, w, h) in faces:
        face_roi_gray = gray[y: y+h, x: x+w]
        # 返回图片编号和置信度。置信度为两张图片的相似程度 (LBPH中,值越小越相似)
        user_id, confidence = recognizer.predict(face_roi_gray)

        # 框出人脸且实时展示画面
        box_color = (0, 0, 255) # 默认为红色 (未知)
        display_text = "Unknown"

        # LBPH的置信度越低越好。阈值需要实验调整,通常低于50-70认为是可靠匹配。
        if confidence < 70: # 阈值示例,需要调整
            if user_id in name_map:
                display_text = f"{name_map[user_id]} ({confidence:.2f})"
                box_color = (0, 255, 0) # 绿色 (已知)
            else:
                display_text = f"ID:{user_id} ({confidence:.2f})" # ID已知但无名
                box_color = (0, 255, 255) # 黄色 (ID已知,名未知)
        else:
            display_text = f"Unknown ({confidence:.2f})"


        # 绘制矩形框
        cv2.rectangle(display_frame, (x,y), (x+w,y+h), box_color, 2)

        # 使用Pillow在OpenCV图像上绘制中文文本
        # 1. OpenCV (BGR) to Pillow (RGB)
        pil_image = Image.fromarray(cv2.cvtColor(display_frame, cv2.COLOR_BGR2RGB))
        draw = ImageDraw.Draw(pil_image)
        # 2. Draw text
        draw.text((x, y - 30 if y - 30 > 0 else y + 10), display_text, font=pil_font, fill=box_color)
        # 3. Pillow (RGB) back to OpenCV (BGR)
        display_frame = cv2.cvtColor(np.array(pil_image), cv2.COLOR_RGB2BGR)

        # 如果不用Pillow,只能显示英文/数字
        # cv2.putText(display_frame, display_text, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, box_color, 2)

        # 原文中的画圆代码 (可选)
        # cv2.circle(
        #         img=display_frame, center=(x+w//2, y+h//2),
        #         radius=w // 2,
        #         color=box_color, thickness=1
        #     )

    cv2.imshow("Face Recognition", display_frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

文字提取

(此部分原文为空,文字提取通常涉及OCR技术,如Tesseract OCR。OpenCV 本身不直接提供强大的OCR功能,但可以用于预处理图像以提高OCR效果。)

python
# 示例:使用OpenCV进行预处理,为OCR做准备 (OCR本身需要额外库如pytesseract)
# import cv2
# import numpy as np
# # import pytesseract # 需要安装 Tesseract OCR 引擎和 pytesseract Python 包

# # 1. 加载图像
# # image = cv2.imread('text_image.png')
# # if image is None:
# #     print("无法加载图像")
# #     exit()

# # # 2. 预处理
# # # 转为灰度图
# # gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# # # (可选) 二值化,阈值方法可能需要调整
# # # _, binary_img = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY_INV)
# # # 或者自适应阈值
# # binary_img = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
# #                                   cv2.THRESH_BINARY_INV, 11, 2)


# # # (可选) 去噪
# # # preprocessed_img = cv2.medianBlur(binary_img, 3)
# # preprocessed_img = binary_img # 或 gray,取决于OCR引擎偏好

# # cv2.imshow("Preprocessed for OCR", preprocessed_img)
# # cv2.waitKey(0)
# # cv2.destroyAllWindows()

# # # 3. 使用Tesseract进行OCR (需要pytesseract和Tesseract安装)
# # # try:
# # #     # 指定Tesseract OCR的路径 (如果不在系统PATH中)
# # #     # pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
# # #     custom_config = r'--oem 3 --psm 6 -l chi_sim+eng' # 示例配置:简体中文+英文
# # #     text = pytesseract.image_to_string(preprocessed_img, config=custom_config)
# # #     print("提取到的文本:")
# # #     print(text)
# # # except Exception as e:
# # #     print(f"OCR错误: {e}")
# # #     print("请确保Tesseract OCR已安装并配置正确。")

print("文字提取 (OCR) 通常需要 Tesseract OCR 等专用库。OpenCV主要用于图像预处理。")

绘制多边形 cv2.polylines(img, pts, isClosed, color[, thickness])

python
"""
img: 图像数据。要绘制线条的图像。
pts: 顶点坐标。一个包含多边形所有顶点坐标的列表或Numpy数组。
     对于单个多边形,通常是形状为 (N, 1, 2) 的Numpy数组,N是顶点数。
     对于多个多边形,是一个包含这些数组的列表: [array1, array2, ...]。
isClosed: 是否闭合。布尔值。True表示多边形的起点和终点会被连接。
color: 线条颜色 (BGR)。
thickness: 线宽。
lineType: 线条的类型 (可选)。
shift: 顶点坐标中小数点的位数 (可选)。
"""
import cv2
import numpy as np

img = np.zeros((300, 400, 3), dtype=np.uint8) # 黑色背景

# 定义一个多边形的顶点 (例如一个三角形)
# pts 必须是 int32 类型的 Numpy 数组,形状 (num_vertices, 1, 2)
triangle_pts = np.array([[100,50],[50,150],[150,150]], np.int32)
triangle_pts = triangle_pts.reshape((-1,1,2)) # Reshape to (3,1,2)

# 定义另一个多边形 (例如一个不规则四边形)
quad_pts = np.array([[200,100],[250,50],[350,120],[280,180]], np.int32)
quad_pts = quad_pts.reshape((-1,1,2))


# 绘制单个多边形 (三角形,闭合)
cv2.polylines(img, [triangle_pts], isClosed=True, color=(0,255,0), thickness=3, lineType=cv2.LINE_AA)

# 绘制另一个多边形 (四边形,不闭合的折线)
cv2.polylines(img, [quad_pts], isClosed=False, color=(0,0,255), thickness=5)


# 绘制填充的多边形 (使用 cv2.fillPoly)
# fillPoly 的 pts 参数是一个包含多边形顶点数组的列表
pentagon_pts = np.array([[50,200],[100,250],[150,280],[120,220],[80,260]], np.int32)
# fillPoly 不需要 reshape to (N,1,2), 直接 (N,2) 即可,但作为列表元素
cv2.fillPoly(img, [pentagon_pts], color=(255,255,0)) # 青色填充


cv2.imshow("Polylines and FillPoly", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

引入外部资源

需要安装对应包。例如,如果要使用 Pillow (PIL) 来处理中文字符或进行更复杂的图像操作:

bash
pip install pillow

如果需要 NumPy (OpenCV 通常会自动安装它作为依赖):

bash
pip install numpy

如果需要 Tesseract OCR 进行文字识别:

  1. 安装 Tesseract OCR 引擎 (从官网下载对应系统的安装包)。
  2. 安装 pytesseract Python 包:
    bash
    pip install pytesseract

零碎小点

  • cv2.imread(路径, 通道): 图像在读取时也可以直接修改通道值。
    • cv2.IMREAD_COLOR (或 1): 加载彩色图像 (BGR),忽略alpha通道。默认。
    • cv2.IMREAD_GRAYSCALE (或 0): 以灰度模式加载图像。
    • cv2.IMREAD_UNCHANGED (或 -1): 加载图像,包括alpha通道(如果存在)。
  • 图像读取的路径通常不支持中文及特殊字符。如果必须使用,考虑将文件名转换为不含中文的,或者使用 cv2.imdecode(np.fromfile(filepath, dtype=np.uint8), flags) 这种方式读取。
  • numpy.hstack((img1, img2, ...))numpy.vstack((img1, img2, ...)) 可以水平或垂直堆叠图像以同时显示多张(确保它们有兼容的维度)。
  • 在 Jupyter Notebook 等环境中,Shift+Tab 通常可以显示函数/方法的使用介绍。
  • ord('c') 获取字符 'c' 的ASCII码。
  • image.shape 获取到图像的特征 (高度, 宽度, 通道数)。对于灰度图,是 (高度, 宽度)。
  • 如果在使用方法时,形参是跳着使用的(即不按顺序提供所有位置参数),则必须明确指定参数名,例如 cv2.resize(img, fx=0.5, fy=0.5, interpolation=cv2.INTER_AREA) (这里 dsize 被跳过,所以 fx, fy 需要指定名称)。

封装代码

  • 经常使用的代码,如果每次都重写会很繁琐。可以将其封装在一个函数或类中,然后在需要使用的地方导入并调用。
  1. 创建一个 .py 文件 (例如 my_cv_utils.py),在其中书写封装的函数或类。
    python
    # my_cv_utils.py
    import cv2
    import numpy as np
    from PIL import Image, ImageDraw, ImageFont
    
    def display_image(window_name, image):
        """简单封装imshow, waitKey, destroyAllWindows"""
        cv2.imshow(window_name, image)
        cv2.waitKey(0)
        cv2.destroyAllWindows()
    
    def draw_text_chinese(image_bgr, text, position, font_path="simkai.ttf", font_size=20, color_rgb=(0,0,0)):
        """在OpenCV图像上绘制中文 (需要Pillow)"""
        img_rgb = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB)
        pil_img = Image.fromarray(img_rgb)
        draw = ImageDraw.Draw(pil_img)
        try:
            font = ImageFont.truetype(font_path, font_size)
        except IOError:
            font = ImageFont.load_default()
            print(f"Warning: Font {font_path} not found, using default.")
        draw.text(position, text, font=font, fill=color_rgb)
        return cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
  2. 在需要使用这些功能的地方,导入外部文件中的函数/类。
    python
    # main_script.py
    # from my_cv_utils import * # 导入所有
    from my_cv_utils import display_image, draw_text_chinese # 导入指定的函数
    
    import cv2
    import numpy as np
    
    my_image = np.zeros((200,400,3), dtype=np.uint8)
    my_image = draw_text_chinese(my_image, "你好 OpenCV", (50,50), color_rgb=(255,255,255))
    # display_image("测试封装函数", my_image)
    cv2.imshow("测试封装函数", my_image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
  3. 注意:在封装的文件中,需要导入其内部函数所依赖的所有包 (如 cv2, numpy, PIL 等)。

画板

  • 窗体底色不变。
  • 鼠标左键点击开始进行绘制直线。
  • 如果松开了左键,则绘制结束。
  • 结束后或绘制前,可以通过Trackbar修改线条颜色和粗细。
python
# 窗体底色不变  鼠标左侧点击开始进行绘制直线 如果松开了左键 则绘制结束
# 判断event==cv2.EVENT_LBUTTONDOWN (点击) 和 event==cv2.EVENT_MOUSEMOVE with flags==cv2.EVENT_FLAG_LBUTTON (拖动)
# 结束后 or绘制前 可以修改线条颜色
import cv2
import numpy as np

# 全局变量
drawing = False  # True表示鼠标按下正在绘制
last_x, last_y = -1, -1
current_color = (0, 0, 0) # BGR 默认为黑色
current_thickness = 1 # 默认为1

# 创建窗体和图像
window_name = "Simple Paint"
# img = cv2.imread('./3.png') # 可以加载一个背景图
# 为演示,创建一个白色背景
img = np.full((480, 640, 3), (255, 255, 255), dtype=np.uint8)
img_display = img.copy() # 用于实时显示,最终绘制在img上

cv2.namedWindow(window_name)
# cv2.resizeWindow(window_name,600,400) # 调整窗口大小

# Trackbar的回调函数 (只用于创建,实际值在主循环获取)
def on_color_change(value):
    pass
def on_thickness_change(value):
    pass

# 创建Trackbars用于颜色和粗细
cv2.createTrackbar("R", window_name, 0, 255, on_color_change)
cv2.createTrackbar("G", window_name, 0, 255, on_color_change)
cv2.createTrackbar("B", window_name, 0, 255, on_color_change)
cv2.createTrackbar("Thickness", window_name, 1, 20, on_thickness_change) # 粗细从1到20

# 鼠标回调函数
def paint_draw(event, x, y, flags, param):
    global drawing, last_x, last_y, current_color, current_thickness, img, img_display

    if event == cv2.EVENT_LBUTTONDOWN:
        drawing = True
        last_x, last_y = x, y
        # 可以在按下时就画一个点
        cv2.circle(img, (x,y), current_thickness//2 if current_thickness > 1 else 1, current_color, -1)
        img_display = img.copy()


    elif event == cv2.EVENT_MOUSEMOVE:
        if drawing:
            # 在img_display上预览线条
            img_display = img.copy() # 从上次确认的图像开始画预览线
            cv2.line(img_display, (last_x, last_y), (x, y), current_color, current_thickness)
            # 不在此处更新img,只在LBUTTONUP时确认

    elif event == cv2.EVENT_LBUTTONUP:
        if drawing:
            # 鼠标松开,将最终线条画在img上
            cv2.line(img, (last_x, last_y), (x, y), current_color, current_thickness)
            img_display = img.copy() # 更新显示图像
        drawing = False
        last_x, last_y = -1, -1


# 绑定鼠标事件
cv2.setMouseCallback(window_name, paint_draw)

print("简单的画板程序。拖动鼠标左键进行绘制。")
print("使用Trackbars调整颜色和线条粗细。按 'q' 退出,按 'c' 清空画板。")

while True:
    # 获取当前trackbar值
    r = cv2.getTrackbarPos("R", window_name)
    g = cv2.getTrackbarPos("G", window_name)
    b = cv2.getTrackbarPos("B", window_name)
    current_color = (b, g, r) # OpenCV是BGR顺序

    thickness_val = cv2.getTrackbarPos("Thickness", window_name)
    current_thickness = thickness_val if thickness_val > 0 else 1 # 确保粗细至少为1

    cv2.imshow(window_name, img_display) # 显示带预览的图像

    key = cv2.waitKey(1) & 0xFF
    if key == ord('q'):
        break
    elif key == ord('c'): # 清空画板
        img = np.full((480, 640, 3), (255, 255, 255), dtype=np.uint8) # 重置为白色
        img_display = img.copy()
        print("画板已清空。")


cv2.destroyAllWindows()

Released under the MIT License.