完整教程:Opencv(十) : 图像镜像旋转

思维导图:

在这里插入图片描述

引言

在计算机视觉、图像处理及多媒体开发中,图像镜像旋转(又称图像翻转)是最基础且常用的操作之一。它通过改变图像像素的坐标分布,实现水平、垂直或双向的翻转效果,广泛应用于照片后期处理、视频剪辑、目标检测数据增强、游戏场景渲染等领域。

一、图像镜像旋转的核心原理

1.1 定义与本质

图像镜像旋转是指将图像围绕特定坐标轴(或同时围绕两个坐标轴)进行对称翻转,使得图像的像素点按照对称规则重新排列。其本质是像素坐标的线性变换——通过修改每个像素在图像坐标系中的位置,生成翻转后的新图像。

在数字图像中,我们通常采用“左上角为原点(0,0),x轴水平向右,y轴垂直向下”的坐标系(与OpenCV默认坐标系一致)。图像的宽度为width(x轴范围:0width-1),高度为`height`(y轴范围:0height-1),每个像素的位置用坐标(x, y)表示(x为列索引,y为行索引)。

1.2 三种翻转类型的数学原理

图像镜像旋转主要分为三种类型,每种类型对应不同的坐标变换规则,具体如下:

(1)垂直翻转(flipCode = 0)

垂直翻转又称“上下翻转”,是围绕图像的水平中轴线(x轴方向) 进行对称翻转。

  • 变换规则:原始像素(x, y)翻转后变为(x, height - y - 1)
  • 通俗理解:图像的上半部分与下半部分交换位置,比如顶部的像素会移动到对应的底部位置,底部像素则移动到顶部。

例:假设图像高度height = 5(y轴范围0~4),原始像素(2, 0)(顶部第0行)翻转后变为(2, 4)(底部第4行);原始像素(3, 2)(中间行)翻转后位置不变。

(2)水平翻转(flipCode > 0)

水平翻转又称“左右翻转”,是围绕图像的垂直中轴线(y轴方向) 进行对称翻转。

  • 变换规则:原始像素(x, y)翻转后变为(width - x - 1, y)
  • 通俗理解:图像的左半部分与右半部分交换位置,比如左侧的像素会移动到对应的右侧位置,右侧像素则移动到左侧。

例:假设图像宽度width = 6(x轴范围0~5),原始像素(0, 3)(左侧第0列)翻转后变为(5, 3)(右侧第5列);原始像素(2, 1)翻转后变为(3, 1)

(3)水平垂直翻转(flipCode < 0)

水平垂直翻转是水平翻转与垂直翻转的结合,相当于先进行水平翻转,再进行垂直翻转(或反之,顺序不影响结果)。

  • 变换规则:原始像素(x, y)翻转后变为(width - x - 1, height - y - 1)
  • 通俗理解:图像既左右翻转,又上下翻转,最终效果相当于围绕图像中心旋转180°。

例:图像width=6height=5,原始像素(0, 0)翻转后变为(5, 4);原始像素(2, 3)翻转后变为(3, 1)

1.3 数学公式总结

三种翻转类型的像素变换公式可统一表示为:
在这里插入图片描述

其中:

  • src:原始图像(source image);
  • dst:翻转后的目标图像(destination image);
  • i:目标图像的行索引(对应y轴坐标);
  • j:目标图像的列索引(对应x轴坐标);
  • height:原始图像的高度;
  • width:原始图像的宽度。

二、OpenCV cv2.flip()函数详解

OpenCV提供的cv2.flip()函数是实现图像镜像旋转的核心工具,支持所有三种翻转类型,具有运行高效、使用简洁的特点。

2.1 函数原型

cv2.flip(src, flipCode[, dst]) -> dst

2.2 参数说明

参数名类型说明
srcnumpy.ndarray输入图像(原始图像),可以是单通道(灰度图)、三通道(RGB/BGR图)或四通道(带透明通道图)。
flipCodeint翻转类型标志位,取值规则:
- flipCode = 0:垂直翻转(上下翻转);
- flipCode > 0:水平翻转(左右翻转)(常用1);
- flipCode < 0:水平垂直翻转(常用-1)。
dstnumpy.ndarray可选参数,输出图像(翻转后的图像)。若不指定,函数会自动创建与src尺寸、数据类型相同的数组。

2.3 返回值

dst:翻转后的目标图像,数据类型、通道数与输入图像src一致,尺寸与src完全相同(翻转操作不改变图像的宽高)。

2.4 函数特点

  1. 支持任意尺寸、任意通道数的图像;
  2. 原地操作(in-place):若指定dst参数,函数会直接将结果写入dst,避免额外内存分配;
  3. 计算高效:底层由C++实现,即使处理大尺寸图像(如4K图片)也能快速完成;
  4. 兼容性强:支持OpenCV支持的所有图像数据类型(如uint8float32等)。

三、实战案例:三种翻转类型的代码实现

下面通过完整的Python代码,演示三种翻转类型的具体实现,包括图像读取、翻转操作、结果显示与保存。

3.1 环境准备

首先确保已安装OpenCV库,若未安装,可通过以下命令安装:

pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple

3.2 通用代码框架

所有翻转案例的核心流程的一致:

  1. 读取图像(使用cv2.imread());
  2. 调用cv2.flip()实现翻转;
  3. 显示原始图像与翻转后的图像(使用cv2.imshow());
  4. 等待用户按键(使用cv2.waitKey());
  5. 释放窗口资源(使用cv2.destroyAllWindows());
  6. (可选)保存翻转后的图像(使用cv2.imwrite())。

3.3 案例1:垂直翻转(flipCode = 0)

代码实现
import cv2
import os
if __name__ == "__main__":
# 1. 定义图像路径(请根据实际情况修改)
img_path = "./lena.png"
save_path = "./lena_vertical_flip.png"
# 2. 读取图像
# cv2.imread()返回numpy.ndarray,默认以BGR格式读取
src_img = cv2.imread(img_path)
# 3. 检查图像是否读取成功
if src_img is None:
print(f"错误:无法读取图像,请检查路径 '{img_path}' 是否正确!")
# 若路径错误,尝试查找当前目录下的图片文件
current_files = [f for f in os.listdir(".") if f.endswith((".png", ".jpg", ".jpeg"))]
if current_files:
print(f"当前目录下可用的图片文件:{current_files}")
exit()
# 4. 垂直翻转(flipCode = 0)
vertical_flip_img = cv2.flip(src_img, flipCode=0)
# 5. 显示图像
# 创建两个窗口,分别显示原始图像和翻转后的图像
cv2.namedWindow("Original Image", cv2.WINDOW_NORMAL)  # 可调整窗口大小
cv2.namedWindow("Vertical Flip Image", cv2.WINDOW_NORMAL)
cv2.imshow("Original Image", src_img)
cv2.imshow("Vertical Flip Image", vertical_flip_img)
# 6. 等待用户按键(0表示无限等待,按任意键关闭窗口)
key = cv2.waitKey(0)
if key == ord("s"):
# 按"s"键保存翻转后的图像
cv2.imwrite(save_path, vertical_flip_img)
print(f"垂直翻转后的图像已保存至:{save_path}")
# 7. 释放窗口资源(必须调用,否则会占用内存)
cv2.destroyAllWindows()
效果说明

在这里插入图片描述
在这里插入图片描述

3.4 案例2:水平翻转(flipCode = 1)

代码实现
import cv2
import os
if __name__ == "__main__":
# 1. 定义图像路径
img_path = "./lena.png"
save_path = "./lena_horizontal_flip.png"
# 2. 读取图像(支持灰度图读取,添加0参数)
src_img = cv2.imread(img_path, 0)  # 0表示以灰度图格式读取
# 3. 检查图像读取状态
if src_img is None:
print(f"错误:无法读取图像,请检查路径 '{img_path}' 是否正确!")
exit()
# 4. 水平翻转(flipCode = 1,大于0即可,常用1)
horizontal_flip_img = cv2.flip(src_img, flipCode=1)
# 5. 显示图像
cv2.namedWindow("Original Gray Image", cv2.WINDOW_NORMAL)
cv2.namedWindow("Horizontal Flip Image", cv2.WINDOW_NORMAL)
cv2.imshow("Original Gray Image", src_img)
cv2.imshow("Horizontal Flip Image", horizontal_flip_img)
# 6. 保存与退出
key = cv2.waitKey(0)
if key == ord("s"):
cv2.imwrite(save_path, horizontal_flip_img)
print(f"水平翻转后的灰度图像已保存至:{save_path}")
cv2.destroyAllWindows()
效果说明

请添加图片描述

请添加图片描述

  • 原始图像为灰度图,水平翻转后,图像左右部分对称交换,比如人物的左眼会翻转到右侧,右眼会翻转到左侧;
  • 灰度图的翻转操作与彩色图完全一致,cv2.flip()会自动处理单通道数据。

3.5 案例3:水平垂直翻转(flipCode = -1)

代码实现
import cv2
import os
if __name__ == "__main__":
# 1. 定义图像路径
img_path = "./lena.png"
save_path = "./lena_both_flip.png"
# 2. 读取彩色图像
src_img = cv2.imread(img_path)
if src_img is None:
print(f"错误:无法读取图像,请检查路径 '{img_path}' 是否正确!")
exit()
# 3. 水平垂直翻转(flipCode = -1,小于0即可,常用-1)
both_flip_img = cv2.flip(src_img, flipCode=-1)
# 4. 显示对比(创建拼接图,方便直观对比)
# 拼接原始图像和翻转图像(水平拼接)
compare_img = cv2.hconcat([src_img, both_flip_img])
cv2.namedWindow("Original vs Both Flip", cv2.WINDOW_NORMAL)
cv2.imshow("Original vs Both Flip", compare_img)
# 5. 保存与退出
key = cv2.waitKey(0)
if key == ord("s"):
cv2.imwrite(save_path, both_flip_img)
cv2.imwrite("./lena_compare.png", compare_img)
print(f"水平垂直翻转后的图像已保存至:{save_path}")
print(f"对比图已保存至:./lena_compare.png")
cv2.destroyAllWindows()
效果说明

在这里插入图片描述

  • 水平垂直翻转后的图像同时实现了左右和上下翻转,效果相当于将原始图像旋转180°;
  • 代码中使用cv2.hconcat()将原始图像和翻转图像水平拼接,方便直观对比效果(类似的还有cv2.vconcat()垂直拼接)。

3.6 案例4:批量处理文件夹中的所有图像

在实际开发中,经常需要对文件夹中的所有图像进行批量翻转。以下代码实现批量读取指定文件夹的图片,执行水平翻转后,保存到新文件夹中。

import cv2
import os
import glob
if __name__ == "__main__":
# 1. 配置路径
input_dir = "./input_images/"  # 输入文件夹(存放原始图像)
output_dir = "./output_images/"  # 输出文件夹(存放翻转后的图像)
flip_type = 1  # 翻转类型:1=水平翻转,0=垂直翻转,-1=水平垂直翻转
# 2. 创建输出文件夹(若不存在)
if not os.path.exists(output_dir):
os.makedirs(output_dir)
print(f"创建输出文件夹:{output_dir}")
# 3. 获取文件夹中所有图片文件(支持png、jpg、jpeg格式)
img_paths = glob.glob(os.path.join(input_dir, "*.[pP][nN][gG]")) + \
glob.glob(os.path.join(input_dir, "*.[jJ][pP][gG]")) + \
glob.glob(os.path.join(input_dir, "*.[jJ][pP][eE][gG]"))
if not img_paths:
print(f"错误:在输入文件夹 '{input_dir}' 中未找到图片文件!")
exit()
# 4. 批量翻转并保存
total = len(img_paths)
for i, img_path in enumerate(img_paths):
# 读取图像
src_img = cv2.imread(img_path)
if src_img is None:
print(f"警告:跳过无法读取的文件:{img_path}")
continue
# 执行翻转
flip_img = cv2.flip(src_img, flipCode=flip_type)
# 获取文件名(保持原文件名,添加后缀标识)
filename = os.path.basename(img_path)
name, ext = os.path.splitext(filename)
if flip_type == 0:
save_filename = f"{name}_vertical_flip{ext}"
elif flip_type == 1:
save_filename = f"{name}_horizontal_flip{ext}"
else:
save_filename = f"{name}_both_flip{ext}"
# 保存图像
save_path = os.path.join(output_dir, save_filename)
cv2.imwrite(save_path, flip_img)
# 打印进度
print(f"进度:{i+1}/{total} | 处理完成:{filename} -> 保存为:{save_filename}")
print(f"\n批量处理完成!所有翻转后的图像已保存至:{output_dir}")
代码特点
  • 支持多种图片格式(png、jpg、jpeg),不区分大小写;
  • 自动创建输出文件夹,避免路径错误;
  • 对无法读取的文件进行跳过处理,提高鲁棒性;
  • 保存的文件名添加翻转类型后缀,便于区分。

四、进阶应用:结合其他图像处理操作

图像镜像旋转常与缩放、裁剪、滤波等操作结合使用,实现更复杂的图像处理需求。以下是两个典型的进阶案例。

4.1 案例:翻转 + 缩放 + 边缘检测

import cv2
import numpy as np
if __name__ == "__main__":
img_path = "./lena.png"
src_img = cv2.imread(img_path)
if src_img is None:
print("无法读取图像!")
exit()
# 1. 水平翻转
flip_img = cv2.flip(src_img, 1)
# 2. 缩放(缩小到原来的0.8倍)
scale = 0.8
h, w = flip_img.shape[:2]
resized_img = cv2.resize(flip_img, (int(w*scale), int(h*scale)), interpolation=cv2.INTER_LINEAR)
# 3. 灰度化
gray_img = cv2.cvtColor(resized_img, cv2.COLOR_BGR2GRAY)
# 4. 边缘检测(Canny算子)
edge_img = cv2.Canny(gray_img, threshold1=100, threshold2=200)
# 5. 显示结果
cv2.namedWindow("Flip + Resize + Edge Detection", cv2.WINDOW_NORMAL)
cv2.imshow("Flip + Resize + Edge Detection", edge_img)
cv2.waitKey(0)
cv2.imwrite("./lena_edge_flip.png", edge_img)
cv2.destroyAllWindows()

4.2 案例:翻转 + 图像拼接(制作对称全景图)

import cv2
import numpy as np
if __name__ == "__main__":
img_path = "./mountain.jpg"
src_img = cv2.imread(img_path)
if src_img is None:
print("无法读取图像!")
exit()
# 1. 水平翻转
flip_img = cv2.flip(src_img, 1)
# 2. 拼接原始图像和翻转图像,制作对称全景图
panorama_img = cv2.hconcat([src_img, flip_img])
# 3. 优化:添加中间过渡带(避免拼接痕迹)
h, w = src_img.shape[:2]
transition_width = 50  # 过渡带宽度(像素)
# 创建过渡掩码
mask = np.ones((h, w), dtype=np.float32)
mask[:, w-transition_width:] = np.linspace(1, 0, transition_width)
# 应用过渡掩码
left_part = src_img.astype(np.float32)
right_part = flip_img.astype(np.float32)
left_part[:, w-transition_width:] *= mask[:, w-transition_width:][:, :, np.newaxis]
right_part[:, :transition_width] *= (1 - mask[:, :transition_width])[:, :, np.newaxis]
# 拼接优化后的图像
optimized_panorama = left_part + right_part
optimized_panorama = np.clip(optimized_panorama, 0, 255).astype(np.uint8)
# 4. 显示对比
cv2.namedWindow("Original Panorama", cv2.WINDOW_NORMAL)
cv2.namedWindow("Optimized Panorama", cv2.WINDOW_NORMAL)
cv2.imshow("Original Panorama", panorama_img)
cv2.imshow("Optimized Panorama", optimized_panorama)
cv2.waitKey(0)
cv2.imwrite("./panorama_original.png", panorama_img)
cv2.imwrite("./panorama_optimized.png", optimized_panorama)
cv2.destroyAllWindows()

五、常见问题与解决方案

在使用cv2.flip()进行图像镜像旋转时,初学者常遇到图像读取失败、窗口无法关闭、翻转方向错误等问题。以下是详细的问题分析与解决方案。

5.1 问题1:图像读取失败(src_img is None)

原因:
  1. 图像路径错误(绝对路径/相对路径混淆);
  2. 图像文件损坏或格式不支持;
  3. 文件名包含中文或特殊字符;
  4. 未安装OpenCV或版本不兼容。
解决方案:
  1. 检查路径

    • 相对路径:确保图像文件与代码在同一目录,或使用./folder/img.png格式;
    • 绝对路径:在Windows系统中使用双反斜杠(C:\\Users\\xxx\\lena.png),在Linux/Mac系统中使用斜杠(/home/xxx/lena.png);
    • 推荐使用os.path.abspath()获取绝对路径,避免路径错误:
      import os
      img_path = os.path.abspath("./lena.png")
      print(f"图像绝对路径:{img_path}")
      src_img = cv2.imread(img_path)
  2. 检查文件

    • 确保图像文件后缀正确(如pngjpg),且未被修改后缀(如将jpg改为png);
    • 尝试用图片查看器打开文件,确认文件未损坏。
  3. 处理中文路径

    • 若路径包含中文,OpenCV默认不支持,可通过numpy读取文件后转换为OpenCV格式:
      import cv2
      import numpy as np
      from PIL import Image
      import os
      img_path = "./测试图片.png"
      # 用PIL读取中文路径图像
      pil_img = Image.open(img_path)
      # 转换为OpenCV格式(BGR)
      src_img = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)

5.2 问题2:窗口无法关闭或闪退

原因:
  1. 未调用cv2.waitKey():OpenCV的窗口需要等待用户按键才能保持显示,否则会立即闪退;
  2. 未调用cv2.destroyAllWindows():按键后窗口不会自动关闭,需要手动释放资源;
  3. cv2.waitKey()的参数设置错误(如设置为0表示无限等待,设置为1000表示等待1秒后自动关闭)。
解决方案:
# 正确的窗口显示流程
cv2.imshow("Window Name", img)
cv2.waitKey(0)  # 无限等待用户按键(按任意键关闭)
cv2.destroyAllWindows()  # 释放所有窗口资源

5.3 问题3:翻转方向与预期不符

原因:

flipCode参数的含义理解错误,或混淆了图像坐标系的方向(OpenCV默认左上角为原点)。

解决方案:
  • 牢记flipCode的取值规则:
    • 想实现“上下翻转”→flipCode=0
    • 想实现“左右翻转”→flipCode=1
    • 想实现“180°翻转”→flipCode=-1
  • 若仍有疑问,可通过小尺寸图像(如200x200的简单图形)测试,直观观察翻转效果。

5.4 问题4:保存的图像颜色失真(如RGB转BGR)

原因:

OpenCV读取图像时默认以BGR格式存储,而Python的PIL库、Matplotlib等工具默认以RGB格式显示/保存,直接混合使用会导致颜色失真。

解决方案:
  • 若用cv2.imwrite()保存图像:直接保存即可,cv2.imwrite()会自动处理BGR格式;
  • 若用Matplotlib显示图像:需要将BGR转换为RGB:
    import cv2
    import matplotlib.pyplot as plt
    src_img = cv2.imread("./lena.png")
    # BGR转RGB
    rgb_img = cv2.cvtColor(src_img, cv2.COLOR_BGR2RGB)
    plt.imshow(rgb_img)
    plt.show()

六、总结与拓展

6.1 核心知识点回顾

  1. 图像镜像旋转的本质是像素坐标的线性变换,分为垂直翻转(flipCode=0)、水平翻转(flipCode>0)、水平垂直翻转(flipCode<0)三种类型;
  2. OpenCV的cv2.flip()函数是实现翻转的核心工具,支持任意尺寸、任意通道数的图像,高效简洁;
  3. 实际开发中需注意图像路径、窗口显示、颜色格式等问题,避免常见错误;
  4. 图像翻转常与缩放、裁剪、滤波等操作结合,应用于数据增强、后期处理、游戏开发等多个领域。

6.2 拓展学习建议

  1. 图像旋转:除了镜像翻转,OpenCV还提供cv2.rotate()函数实现90°、180°、270°的旋转,以及cv2.warpAffine()实现任意角度的旋转,可进一步学习;
  2. 仿射变换:镜像翻转是仿射变换的特殊情况,学习cv2.getAffineTransform()函数,掌握更灵活的图像变换(如平移、旋转、缩放、剪切);
  3. 深度学习中的数据增强:结合albumentationstorchvision.transforms等库,实现翻转、旋转、裁剪、缩放等多种数据增强操作的组合;
  4. 实时视频翻转:使用cv2.VideoCapture()读取摄像头视频流,实时对每一帧进行翻转,实现视频镜像功能。
posted @ 2025-12-06 20:12  clnchanpin  阅读(63)  评论(0)    收藏  举报