OpenCV图像处理基础:打造你的视觉魔法棒

欢迎来到OpneCV的奇妙世界!如果你已经按照我们学历历程掌握了Python 、NumPy基础,并成功安装了OpenCV,那么你已经准备好进入激动人心的图像处理环节了。这部分我们将学习如何对图像施加各种“魔法”,改变它们的颜色、形状。去除噪声,为后续的分析和识别做好准备。(图部分来源于网络如有侵权联系必删)

本博客你将会学到

  • 颜色空间转换
  • 几何变换:缩放、平移、旋转、仿射变换、透视变换
  • 阈值处理
  • 图像平滑/模糊:均值滤波、高斯滤波、中值滤波、双边滤波

在开始之前,确保你已经准备好了

1.Python环境

2.OpenCV(opencv-python)

3.NumPy

4.一张你喜欢的图片作为实验对象

再次强调一下代码需要(英文路径!)

零、准备工作:加载并显示图像

在进行任何处理之前,我们需要加载一张图像。OpenCV的cv2.imshow()函数将用于显示图像。(英文路径!)

import cv2
import numpy as np

# 读取图像
# OpenCV 默认以 BGR 格式读取图像
img_bgr_original = cv2.imread('image.jpg') # 保留一个原始副本

# 检查图像是否成功加载
if img_bgr_original is None:
   print("错误:无法加载图像,请检查路径 'image.jpg' 是否正确。")
   exit()

# 直接使用 OpenCV 显示 BGR 图像
cv2.imshow('Original Image (BGR)', img_bgr_original)
cv2.waitKey(0) # 等待按键
cv2.destroyAllWindows() # 关闭所有 OpenCV 窗口

现在,我们有了img_bgr_original,可以开始我们的处理之旅了。为了在后续步骤中添加文本而不修改原始图像,我们通常会操作其副本。

辅助函数:用于调整大小和添加文本以便于并排显示

为了方便地将多个图像(可能尺寸不同,或包含灰度图)并排显示在一个窗口中,并给它们加上标签,我们定义一个辅助函数。

def prepare_for_display(image, text, target_width=300, target_height=250):
   """
  调整图像大小,将其转换为BGR(如果需要),并添加文本。
  """
   img_display = image.copy()

   # 如果是灰度图,转换为BGR以便与其他彩色图堆叠并显示彩色文本
   if len(img_display.shape) == 2:
       img_display = cv2.cvtColor(img_display, cv2.COLOR_GRAY2BGR)

   # 调整大小
   img_display = cv2.resize(img_display, (target_width, target_height))

   # 添加文本
   cv2.putText(img_display, text, (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1, cv2.LINE_AA) # 绿色文本
   return img_display

一、颜色空间转换(cv2.cvtColor())

计算机以数字方式表示颜色,而不同的“颜色空间”提供了不同的表示方法,适用于不同的任务。

BGR/RGB:最常见的颜色空间,由蓝(Blue)、绿(Green)、红(Red)三个通道组成。(OpenCV默认使用BGR)

Grayscale(灰度图):只有亮度信息,没有颜色信息。简化图像,常用于边缘检测、特征提取等。

HSV(Hue,Saturation,Value):Hue(色调),Saturation(饱和度),Value(明度)。HSV空间对于颜色分割和识别特定颜色的物体非常有用。

原理:

颜色空间转化是通过特定的数学公式将像素值从一个空间的表示映射到另一个空间的表示。例如,从BGR到灰度的转换通常是加权平均:

Gray = 0.114B + 0.587G + 0.299*R (OpenCV BGR顺序的系数)。

代码示例:

img_bgr = img_bgr_original.copy() # 使用副本

# 1. BGR 转换为灰度图
img_gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)

# 2. BGR 转换为 HSV
img_hsv = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV)

# 准备用于显示的图像
disp_original = prepare_for_display(img_bgr, "Original BGR")
disp_gray = prepare_for_display(img_gray, "Grayscale")
# HSV 图像本身不易直接观察,通常会将其转回BGR或单独看其通道
# 为了在此并排显示,我们将HSV转回BGR
disp_hsv_as_bgr = prepare_for_display(cv2.cvtColor(img_hsv, cv2.COLOR_HSV2BGR), "HSV (as BGR)")

#水平堆叠图像
combined_display = np.hstack((disp_original, disp_gray, disp_hsv_as_bgr))

cv2.imshow('Color Space Conversions', combined_display)
cv2.waitKey(0)
cv2.destroyAllWindows()

二、几何变换

几何变换改变图像中像素的空间位置,但不改变像素值本身。

1.缩放(cv2.resize())

改变图像的大小。

原理:缩放时,如果新尺寸与原尺寸不成整数倍,就需要“插值”来决定新像素的值。常见的插值方法有cv2.INTER_NEAREST, cv2.INTER_LINEAR (默认), cv2.INTER_CUBIC。(具体对比看我的另一篇博客,这里不展开详解)

代码示例:

img_bgr = img_bgr_original.copy()
height, width = img_bgr.shape[:2]

# 缩小到一半 (指定目标尺寸 dsize)
img_scaled_half = cv2.resize(img_bgr, (width // 2, height // 2), interpolation=cv2.INTER_LINEAR)

# 放大两倍 (使用缩放因子 fx, fy)
img_scaled_double = cv2.resize(img_bgr, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC)

# 准备显示 (固定高度,宽度自适应)
def prep_scale(image, text, target_h=200):
   h, w = image.shape[:2]
   scale = target_h / h
   resized_img = cv2.resize(image.copy(), (int(w * scale), target_h))
   cv2.putText(resized_img, text, (5,15), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0,255,0),1)
   return resized_img

disp_s_orig = prep_scale(img_bgr, f'Original ({width}x{height})')
disp_s_half = prep_scale(img_scaled_half, f'Half ({img_scaled_half.shape[1]}x{img_scaled_half.shape[0]})')
disp_s_double = prep_scale(img_scaled_double, f'Double ({img_scaled_double.shape[1]}x{img_scaled_double.shape[0]})')

combined_scaling = np.hstack((disp_s_orig, disp_s_half, disp_s_double))
cv2.imshow('Image Scaling', combined_scaling)
cv2.waitKey(0)
cv2.destroyAllWindows()

2.平移

将图像在X和Y方向上移动。

原理:平移需要一个2×3的变换矩阵 M = [[1, 0, tx], [0, 1, ty]]。tx 是水平位移,ty 是垂直位移。使用 cv2.warpAffine() 应用变换。

代码示例:

img_bgr = img_bgr_original.copy()
rows, cols = img_bgr.shape[:2]
tx = 50  # 水平平移50像素
ty = 100 # 垂直平移100像素

M_translate = np.float32([[1, 0, tx], [0, 1, ty]])
img_translated = cv2.warpAffine(img_bgr, M_translate, (cols, rows))

disp_t_orig = prepare_for_display(img_bgr, "Original")
disp_t_translated = prepare_for_display(img_translated, f"Translated (tx={tx}, ty={ty})")

combined_translation = np.hstack((disp_t_orig, disp_t_translated))
cv2.imshow('Image Translation', combined_translation)
cv2.waitKey(0)
cv2.destroyAllWindows()

3.旋转

围绕一个中心点旋转图像。

原理:使用cv2.getRotationMatrix2D()构建旋转矩阵,需要旋转中心、角度和缩放因子。同样使用cv2.warpAffine()。

代码示例:

img_bgr = img_bgr_original.copy()
rows, cols = img_bgr.shape[:2]
center = (cols // 2, rows // 2)
angle = 45
scale = 1.0

M_rotate = cv2.getRotationMatrix2D(center, angle, scale)
img_rotated = cv2.warpAffine(img_bgr, M_rotate, (cols, rows))

disp_r_orig = prepare_for_display(img_bgr, "Original")
disp_r_rotated = prepare_for_display(img_rotated, f"Rotated {angle} deg")

combined_rotation = np.hstack((disp_r_orig, disp_r_rotated))
cv2.imshow('Image Rotation', combined_rotation)
cv2.waitKey(0)
cv2.destroyAllWindows()

4.仿射变换

保持图像中的平行线关系。由原图中三个点及其在目标图像中对应的三个点确定。(如果你没看懂仿射变换在干什么,或者能干什么,可以看我的另一篇博客有解释)

原理: 使用 cv2.getAffineTransform() 从三对点计算2×3的变换矩阵 M。

img_bgr = img_bgr_original.copy()
rows, cols = img_bgr.shape[:2]

pts1_affine = np.float32([[50, 50], [cols-50, 50], [50, rows-50]])
pts2_affine = np.float32([[10, 100], [cols-50, 20], [100, rows-80]])

img_bgr_with_pts = img_bgr.copy() #副本上画点
for pt in pts1_affine:
   cv2.circle(img_bgr_with_pts, tuple(pt.astype(int)), 5, (0, 255, 0), -1)

M_affine = cv2.getAffineTransform(pts1_affine, pts2_affine)
img_affine_transformed = cv2.warpAffine(img_bgr, M_affine, (cols, rows))

disp_aff_orig = prepare_for_display(img_bgr_with_pts, "Original with Points")
disp_aff_transformed = prepare_for_display(img_affine_transformed, "Affine Transformed")

combined_affine = np.hstack((disp_aff_orig, disp_aff_transformed))
cv2.imshow('Affine Transformation', combined_affine)
cv2.waitKey(0)
cv2.destroyAllWindows()

原图像有左上角,右上角,左下角三个绿色的小圆点。

5.透视变换

可将图像的一个四边形区域映射到另一个四边形,不保持平行线关系。(如果没看懂,可以看我另一篇博客)

原理:使用 cv2.getPerspectiveTransform() 从四对点计算3×3的变换矩阵 M。

代码示例: (假设我们手动选取图像中的四边形角点)

img_bgr = img_bgr_original.copy()
rows, cols = img_bgr.shape[:2]

# 这些点需要你根据你的图像手动选取或通过其他方法检测
# 这里使用图像的角点和一些偏移作为示例
pts1_perspective = np.float32([
  [cols*0.1, rows*0.1], [cols*0.9, rows*0.2],
  [cols*0.2, rows*0.9], [cols*0.8, rows*0.8]
])

img_bgr_with_pts_persp = img_bgr.copy()
for pt in pts1_perspective:
   cv2.circle(img_bgr_with_pts_persp, tuple(pt.astype(int)), 5, (0, 0, 255), -1) # 红点

output_width = 300
output_height = 400
pts2_perspective = np.float32([
  [0, 0], [output_width - 1, 0],
  [0, output_height - 1], [output_width - 1, output_height - 1]
])

M_perspective = cv2.getPerspectiveTransform(pts1_perspective, pts2_perspective)
img_perspective_transformed = cv2.warpPerspective(img_bgr, M_perspective, (output_width, output_height))

disp_persp_orig = prepare_for_display(img_bgr_with_pts_persp, "Original with Quad Points", target_width=350, target_height=300)
disp_persp_transformed = prepare_for_display(img_perspective_transformed, "Perspective Transformed (Bird-eye)", target_width=350, target_height=300)

combined_perspective = np.hstack((disp_persp_orig, disp_persp_transformed))
cv2.imshow('Perspective Transformation', combined_perspective)
cv2.waitKey(0)
cv2.destroyAllWindows()

仔细看原图有小的红色点

三、阈值处理

将灰度图像转换为二值图像(只有黑白两色)。(具体作用和目的看我另外一篇博客)

原理:

  • cv2.threshold(src, thresh, maxval, type): 全局阈值。
  • cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C): 自适应阈值,对光照不均的图像效果更好。

代码示例:

img_bgr = img_bgr_original.copy()
img_gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)

ret, thresh_binary = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY)
thresh_adaptive_mean = cv2.adaptiveThreshold(img_gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C,
                                          cv2.THRESH_BINARY, 11, 2)
thresh_adaptive_gaussian = cv2.adaptiveThreshold(img_gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
                                              cv2.THRESH_BINARY, 11, 2)

# 准备 2x2 网格显示
w_th, h_th = 250, 200 # 为每个小图设定的宽高
disp_th_gray = prepare_for_display(img_gray, "Original Gray", w_th, h_th)
disp_th_binary = prepare_for_display(thresh_binary, "Simple Binary (T=127)", w_th, h_th)
disp_th_adapt_mean = prepare_for_display(thresh_adaptive_mean, "Adaptive Mean", w_th, h_th)
disp_th_adapt_gauss = prepare_for_display(thresh_adaptive_gaussian, "Adaptive Gaussian", w_th, h_th)

row1 = np.hstack((disp_th_gray, disp_th_binary))
row2 = np.hstack((disp_th_adapt_mean, disp_th_adapt_gauss))
combined_thresholding = np.vstack((row1, row2))

cv2.imshow('Thresholding Techniques', combined_thresholding)
cv2.waitKey(0)
cv2.destroyAllWindows()

四、图像平滑/模糊

主要用于减少图像中的噪声。(具体看我另外一篇博客)

原理

大多基于“卷积”操作,用一个“核”滑过图像。

  • 均值滤波 (cv2.blur()): 邻域像素平均值。
  • 高斯滤波 (cv2.GaussianBlur()): 高斯核加权平均,更好地保留边缘。
  • 中值滤波 (cv2.medianBlur()): 邻域像素中值,对椒盐噪声有效。
  • 双边滤波 (cv2.bilateralFilter()): 考虑空间距离和像素值差异,去噪同时保边。

代码示例:

img_bgr = img_bgr_original.copy()

# 给原始 BGR 图像添加高斯噪声
noise = np.zeros(img_bgr.shape, np.uint8)
cv2.randn(noise, (0,0,0), (30,30,30))
img_noisy_bgr = cv2.add(img_bgr, noise)

# 滤波操作
img_mean_blur = cv2.blur(img_noisy_bgr, (5, 5))
img_gaussian_blur = cv2.GaussianBlur(img_noisy_bgr, (5, 5), 0)
img_median_blur = cv2.medianBlur(img_noisy_bgr, 5)
img_bilateral_blur = cv2.bilateralFilter(img_noisy_bgr, 9, 75, 75)

# 准备 2x3 网格显示
w_sm, h_sm = 220, 180 # 调整每个小图的宽高
disp_sm_noisy = prepare_for_display(img_noisy_bgr, "Noisy", w_sm, h_sm)
disp_sm_mean = prepare_for_display(img_mean_blur, "Mean Blur", w_sm, h_sm)
disp_sm_gaussian = prepare_for_display(img_gaussian_blur, "Gaussian Blur", w_sm, h_sm)
disp_sm_median = prepare_for_display(img_median_blur, "Median Blur", w_sm, h_sm)
disp_sm_bilateral = prepare_for_display(img_bilateral_blur, "Bilateral Filter", w_sm, h_sm)
disp_sm_original_clean = prepare_for_display(img_bgr, "Original Clean", w_sm, h_sm)

row_blur1 = np.hstack((disp_sm_noisy, disp_sm_mean, disp_sm_gaussian))
row_blur2 = np.hstack((disp_sm_median, disp_sm_bilateral, disp_sm_original_clean))
combined_smoothing = np.vstack((row_blur1, row_blur2))

cv2.imshow('Image Smoothing (Gaussian Noise)', combined_smoothing)
cv2.waitKey(0)
cv2.destroyAllWindows()

# 示例:添加椒盐噪声并用中值滤波处理
img_salt_pepper_bgr = img_bgr_original.copy()
# 添加盐噪声
num_salt = np.ceil(0.02 * img_salt_pepper_bgr.size * 0.33) # 约2%的像素点,分通道
for _ in range(int(num_salt)):
   i = np.random.randint(0, img_salt_pepper_bgr.shape[0]-1)
   j = np.random.randint(0, img_salt_pepper_bgr.shape[1]-1)
   k = np.random.randint(0, 2) # choose one channel
   img_salt_pepper_bgr[i, j, k] = 255
# 添加胡椒噪声
num_pepper = np.ceil(0.02 * img_salt_pepper_bgr.size * 0.33)
for _ in range(int(num_pepper)):
   i = np.random.randint(0, img_salt_pepper_bgr.shape[0]-1)
   j = np.random.randint(0, img_salt_pepper_bgr.shape[1]-1)
   k = np.random.randint(0,2)
   img_salt_pepper_bgr[i, j, k] = 0

img_median_blur_sp = cv2.medianBlur(img_salt_pepper_bgr, 5)

disp_sp_orig = prepare_for_display(img_bgr_original.copy(), "Original", w_sm, h_sm)
disp_sp_noisy = prepare_for_display(img_salt_pepper_bgr, "Salt & Pepper Noise", w_sm, h_sm)
disp_sp_median_filtered = prepare_for_display(img_median_blur_sp, "Median Filtered", w_sm, h_sm)

combined_sp_noise = np.hstack((disp_sp_orig, disp_sp_noisy, disp_sp_median_filtered))
cv2.imshow('Median Filter for Salt & Pepper Noise', combined_sp_noise)
cv2.waitKey(0)
cv2.destroyAllWindows()

总结与展望

恭喜你!现在你已经学会了如何使用纯 OpenCV 来实现和展示这些核心的图像处理基础技术。通过直接操作和显示图像,你可以更直观地理解每个步骤的效果。

  • 颜色空间转换
  • 几何变换
  • 阈值处理
  • 图像平滑

这些都是计算机视觉中不可或缺的工具。

接下来该做什么?

  1. 深入实践: 更改 prepare_for_display 函数中的 target_width 和 target_height,或者修改滤波器的核大小、阈值等参数,观察效果。
  2. 组合应用: 尝试将这些技术链式应用,例如:灰度化 -> 高斯模糊 -> 自适应阈值。
  3. 探索更多: OpenCV 的功能远不止于此。准备好学习形态学转换、边缘检测等更高级的主题吧!

继续探索,OpenCV 的强大功能会让你在计算机视觉的道路上越走越远!

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇