Python OpenCV学习第三天:几何变换+阈值处理+图像滤波

Python OpenCV学习第三天:几何变换+阈值处理+图像滤波

寒烟似雪
3天前发布

Python OpenCV学习第三天:给图片加滤镜原来这么简单

昨天把图像读写和摄像头那点事搞明白了,今天继续往下啃几何变换和图像预处理。本来以为这些都是套公式的函数调用,结果写代码的时候才发现好多细节没注意到,比如旋转个图片居然会被裁掉一半,滤波参数调错了效果差十万八千里。不过今天写出来的滤镜小程序还挺有意思的,能给图片加模糊、锐化、黑白这些效果,终于有点做东西的实感了。

今日目标完成情况

  • 图像几何变换(缩放、旋转、平移、翻转)全掌握
  • 搞懂简单阈值和自适应阈值的区别及适用场景
  • 学会4种常用图像滤波的用法和各自的特点
  • 完成了第一个交互型小项目:键盘控制的图像滤镜

一、图像几何变换:想怎么转就怎么转

几何变换就是改变图片的大小、位置和角度,都是后面做目标检测、图像拼接的基础。

1. 缩放:cv2.resize()

最简单也最常用,注意不同插值方法的区别,用错了图片会糊:

import cv2
import numpy as np

img = cv2.imread("test.jpg")

# 按比例缩放(推荐)
img_resized = cv2.resize(img, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_AREA)
# fx=水平缩放比例,fy=垂直缩放比例,None表示不指定固定尺寸

# 指定尺寸缩放
img_fixed = cv2.resize(img, (400, 300), interpolation=cv2.INTER_CUBIC)

这里要特别说明:插值方法选不对会严重影响效果

  • 缩小图片:用cv2.INTER_AREA,效果最自然
  • 放大图片:用cv2.INTER_CUBIC(慢但清晰)或cv2.INTER_LINEAR(快但稍糊)
  • 默认是INTER_LINEAR,一般够用

2. 翻转:cv2.flip()

一行代码搞定,做数据增强的时候特别有用:

img_flip_h = cv2.flip(img, 1)  # 水平翻转(左右反过来)
img_flip_v = cv2.flip(img, 0)  # 垂直翻转(上下反过来)
img_flip_both = cv2.flip(img, -1)  # 同时水平+垂直翻转

3. 旋转:最容易出问题的一个

我一开始以为传个角度就行,结果转完发现图片缺了一半!原来OpenCV的旋转是保持画布大小不变,超出画布的部分直接被裁掉了。

基础旋转(会裁边)

# 获取图片中心坐标
h, w = img.shape[:2]
center = (w//2, h//2)

# 获取旋转矩阵:(旋转中心, 旋转角度, 缩放比例)
# 角度是逆时针为正,比如90就是逆时针转90度
M = cv2.getRotationMatrix2D(center, 45, 1.0)

# 执行旋转
img_rotated = cv2.warpAffine(img, M, (w, h))

不裁边的旋转(进阶)

如果不想裁边,就要自己计算旋转后的新画布大小,我找了个通用写法,直接抄就行:

def rotate_image(img, angle):
    h, w = img.shape[:2]
    center = (w//2, h//2)
    
    # 获取旋转矩阵
    M = cv2.getRotationMatrix2D(center, angle, 1.0)
    
    # 计算旋转后的新尺寸
    cos = np.abs(M[0, 0])
    sin = np.abs(M[0, 1])
    new_w = int(h * sin + w * cos)
    new_h = int(h * cos + w * sin)
    
    # 调整旋转矩阵的平移量,让图片居中
    M[0, 2] += (new_w / 2) - center[0]
    M[1, 2] += (new_h / 2) - center[1]
    
    # 执行旋转
    return cv2.warpAffine(img, M, (new_w, new_h))

4. 平移

和旋转类似,需要先构造平移矩阵:

# 平移矩阵:[[1, 0, 水平平移量], [0, 1, 垂直平移量]]
# 正数向右/向下平移,负数向左/向上平移
M = np.float32([[1, 0, 50], [0, 1, 30]])
img_translated = cv2.warpAffine(img, M, (w, h))

二、图像阈值处理:把图片变成黑白

阈值处理就是把彩色图转成二值图(只有黑和白),是提取目标物体的第一步。

1. 简单阈值:cv2.threshold()

全局用同一个阈值,适合光照均匀的图片:

# 先转成灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 简单阈值:(灰度图, 阈值, 最大值, 阈值类型)
# 阈值类型:
# cv2.THRESH_BINARY:大于阈值变255(白),小于变0(黑)
# cv2.THRESH_BINARY_INV:反过来,大于阈值变0,小于变255
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)

2. 自适应阈值:cv2.adaptiveThreshold()

解决光照不均匀的问题!比如拍的书本照片,中间亮两边暗,用简单阈值会有一大块黑的,自适应阈值就不会。

thresh_adaptive = cv2.adaptiveThreshold(
    gray, 255,
    cv2.ADAPTIVE_THRESH_GAUSSIAN_C,  # 自适应方法:高斯加权平均
    cv2.THRESH_BINARY,
    11,  # 块大小:必须是奇数,越大处理越粗糙
    2    # 常数:减去这个值,微调阈值
)

我自己测试了一下,用一张窗边拍的照片,简单阈值出来的效果惨不忍睹,自适应阈值就清晰很多,这个真的太有用了。

三、图像滤波:给图片“磨个皮”

滤波主要用来去噪,不同的滤波对应不同的噪声类型,别用混了。

1. 均值滤波:cv2.blur()

最简单的滤波,取周围像素的平均值,去噪的同时会把边缘也弄模糊:

img_blur = cv2.blur(img, (5, 5))  # (5,5)是卷积核大小,必须是奇数

2. 高斯滤波:cv2.GaussianBlur()

最常用的滤波,对高斯噪声(比如图片上的颗粒感)效果很好,比均值滤波保留更多边缘:

img_gaussian = cv2.GaussianBlur(img, (5, 5), 0)  # 0表示自动计算标准差

3. 中值滤波:cv2.medianBlur()

去椒盐噪声神器! 就是图片上那种随机的白点黑点,用这个效果最好:

img_median = cv2.medianBlur(img, 5)  # 卷积核大小必须是奇数

4. 双边滤波:cv2.bilateralFilter()

最智能的滤波,去噪的同时能保持边缘清晰,就是速度慢一点:

img_bilateral = cv2.bilateralFilter(img, 9, 75, 75)

四、今日小项目:键盘控制的图像滤镜

把今天学的所有功能整合起来,写一个能通过键盘切换不同滤镜的小程序,按不同的键就能看到不同的效果:

import cv2
import numpy as np

def main():
    img = cv2.imread("test.jpg")
    if img is None:
        print("图片读取失败!")
        return
    
    cv2.imshow("原图", img)
    
    while True:
        key = cv2.waitKey(0) & 0xFF
        
        if key == ord('q'):
            break
        elif key == ord('1'):
            # 均值模糊
            res = cv2.blur(img, (5, 5))
            cv2.imshow("滤镜效果", res)
        elif key == ord('2'):
            # 高斯模糊
            res = cv2.GaussianBlur(img, (5, 5), 0)
            cv2.imshow("滤镜效果", res)
        elif key == ord('3'):
            # 中值模糊
            res = cv2.medianBlur(img, 5)
            cv2.imshow("滤镜效果", res)
        elif key == ord('4'):
            # 双边滤波
            res = cv2.bilateralFilter(img, 9, 75, 75)
            cv2.imshow("滤镜效果", res)
        elif key == ord('5'):
            # 二值化
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            ret, res = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
            cv2.imshow("滤镜效果", res)
        elif key == ord('6'):
            # 自适应阈值
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            res = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)
            cv2.imshow("滤镜效果", res)
        elif key == ord('r'):
            # 重置为原图
            cv2.imshow("滤镜效果", img)
    
    cv2.destroyAllWindows()

if __name__ == "__main__":
    main()

mo8loxdo.png

运行之后按1-6就能切换不同的滤镜,按r重置,按q退出,特别方便。

五、今日学到的关键点

  1. 缩放图片的时候,缩小用INTER_AREA,放大用INTER_CUBIC,效果最好
  2. 默认的旋转会裁掉边缘,不想裁边就要自己计算新的画布大小
  3. 简单阈值适合光照均匀的图片,光照不均一定要用自适应阈值
  4. 不同的滤波对应不同的噪声:椒盐噪声用中值滤波,高斯噪声用高斯滤波
  5. 所有卷积核的大小都必须是奇数,这个是硬性规定

六、明天要学的

  1. Canny边缘检测和Sobel算子
  2. Harris和Shi-Tomasi角点检测
  3. 轮廓检测和轮廓特征计算
  4. 写一个能自动框出图片中物体的小程序

今天学了大概两个半小时,主要是旋转不裁边的那个函数花了点时间理解,不过搞懂之后就觉得很简单了。现在终于能对图片做一些有意思的处理了.

© 版权声明
THE END
喜欢就支持一下吧
点赞 0 分享 收藏
评论 抢沙发
OωO
取消