用 OpenCV 实现图像中水平线检测与校正

2024-06-04 02:28

本文主要是介绍用 OpenCV 实现图像中水平线检测与校正,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

在本文中,我们将探讨如何使用 Python 和 OpenCV 库来检测图像中的水平线,并对图像进行旋转校正以使这些线条水平。这种技术可广泛应用于文档扫描、建筑摄影校正以及机器视觉中的各种场景。

环境准备

首先,确保您的环境中安装了 OpenCV 库。如果还没有安装,可以通过以下命令安装,要注意尽管代码里我们都是使用的cv2,但是安装包要选opencv-python:

pip install opencv-python

试验效果

原始图像

在这里插入图片描述

找出水平线

在这里插入图片描述

基于统计角度旋转

在这里插入图片描述

步骤概述

  1. 图像加载与预处理:加载图像,转换为灰度图,然后使用 Canny 算法检测边缘。
  2. 线条检测:应用霍夫变换来识别图像中的线条。
  3. 水平线条筛选:过滤出接近水平的线条。
  4. 线条可视化:在图像上绘制检测到的水平线。
  5. 计算需要的旋转角度:计算线条的加权平均角度,以确定图像应旋转的角度。
  6. 图像旋转校正:根据计算出的角度旋转图像,以校正线条至水平。

详细实现

  1. 图像加载与预处理
    加载图像并将其转换为灰度图,这是大多数图像处理任务的常见做法,因为它简化了接下来的处理步骤。
image = cv2.imread('test.png') # 读取图片
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 转换成灰度图像
  1. 边缘检测
    使用 Canny 算法进行边缘检测,这是一种广泛使用的边缘检测算法,因为它有效地识别图像中的线条和边缘。
edges = cv2.Canny(gray, 50, 150, apertureSize=3)

参数详解
gray:这是输入图像,Canny 边缘检测通常在灰度图像上进行,因为边缘检测是基于图像亮度变化的。
50:第一个阈值用于边缘检测的低阈值。这是用于Canny算法中的双阈值过程的较低边界。低于此阈值的像素点不会被视为边缘。
150:第二个阈值用于边缘检测的高阈值。这是用于Canny算法中的双阈值过程的较高边界。高于此阈值的像素点将被视为边缘的强候选者。
apertureSize=3:这是用于内部边缘检测的Sobel算子的大小。apertureSize定义了计算图像梯度所用的Sobel核的大小。常用的尺寸是3,但也可以使用更大的尺寸如5或7,这在处理较大的边缘时可以提供更平滑的结果。

  1. 线条检测与筛选
    通过霍夫变换检测线条,然后筛选出接近水平的线条。我们定义了一个函数 filter_horizontal_lines,它计算每条线的角度,并筛选出角度小于设定阈值的线条。
	def filter_horizontal_lines(lines, angle_threshold=10):horizontal_lines = []if lines is not None:for line in lines:x1, y1, x2, y2 = line[0]angle = np.degrees(np.arctan2(y2 - y1, x2 - x1))if abs(angle) < angle_threshold:horizontal_lines.append(line)return horizontal_lineslines = cv2.HoughLinesP(edges, 1, np.pi / 180, 100, minLineLength=100, maxLineGap=10)horizontal_lines = filter_horizontal_lines(lines)

HoughLinesP参数说明

# HoughLinesP使用概率霍夫变换检测图像中的线段 (注意HoughLines和HoughLinesP是两个函数方法)
lines = cv2.HoughLinesP(edges,             # 边缘图像,通常是Canny边缘检测的输出1,                 # rho - 累加器的距离精度,以像素为单位np.pi / 180,       # theta - 累加器的角度精度,以弧度为单位100,               # threshold - 累加器的阈值,仅返回大于此阈值的线段minLineLength=100, # minLineLength - 线段的最小长度maxLineGap=10      # maxLineGap - 同一线条上允许的最大间隙
)
  1. 计算旋转角度
    我们定义了一个函数 calculate_average_angle,它计算所有检测到的水平线条的加权平均角度。这个角度将用于图像旋转校正。注意这里的np.average(angles, weights=lengths)使用了加权,也就是这个函数会基于找到的线段长度,进行角度的加权平均,如果你只是单纯的关注线段的所有角度,可以删掉weights这个参数。
def calculate_average_angle(lines):"""计算线条的加权平均角度。参数:lines (list): 包含线条的列表,每条线条由两个点的坐标表示,格式为 [x1, y1, x2, y2]。返回:float: 线条的加权平均角度,以度为单位。如果没有符合条件的线条,则返回 0。"""angles = []lengths = []if lines:for line in lines:x1, y1, x2, y2 = line[0]# 计算线条的长度length = np.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)# 计算线条的角度,以度为单位angle = np.degrees(np.arctan2(y2 - y1, x2 - x1))# 角度校正,确保处理的角度是接近水平的if abs(angle) > 90:angle -= 180# 只考虑接近水平的线条if abs(angle) < 20:  # 可调整此阈值以更好地适应具体情况angles.append(angle)lengths.append(length)# 计算加权平均角度if lengths:average_angle = np.average(angles, weights=lengths)else:average_angle = 0return average_angleaverage_angle = calculate_average_angle(horizontal_lines) # 调用函数完成平均角度计算
  1. 图像旋转校正
    最后,我们基于返回的角度,旋转图像,使线条尽可能水平。我们使用 OpenCV 提供的仿射变换函数 cv2.warpAffine 来完成这个任务。
def rotate_image(image, angle):(h, w) = image.shape[:2]center = (w // 2, h // 2)M = cv2.getRotationMatrix2D(center, angle, 1.0)rotated = cv2.warpAffine(image, M, (w, h))return rotatedrotated_image = rotate_image(image, average_angle)
cv2.imwrite('rotated_image.jpg', rotated_image)

代码纯享版

import cv2
import numpy as npdef filter_horizontal_lines(lines, angle_threshold=10):horizontal_lines = []if lines is not None:for line in lines:x1, y1, x2, y2 = line[0]angle = np.degrees(np.arctan2(y2 - y1, x2 - x1))if abs(angle) < angle_threshold:horizontal_lines.append(line)return horizontal_lines
#
#
def calculate_average_angle(lines):angles = []lengths = []if lines:for line in lines:x1, y1, x2, y2 = line[0]length = np.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)angle = np.degrees(np.arctan2(y2 - y1, x2 - x1))# 角度校正,确保处理的角度是接近水平的if abs(angle) > 90:angle -= 180# 只考虑接近水平的线条if abs(angle) < 20:  # 可调整此阈值以更好地适应具体情况angles.append(angle)lengths.append(length)# 计算加权平均角度if lengths:average_angle = np.average(angles, weights=lengths)else:average_angle = 0return average_angle# def calculate_average_angle(lines):
#     angles = []
#     lengths = []
#     filtered_lines = []
# 
#     if lines:
#         # 计算每条线的长度和角度
#         for line in lines:
#             x1, y1, x2, y2 = line[0]
#             length = np.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
#             angle = np.degrees(np.arctan2(y2 - y1, x2 - x1))
# 
#             # 角度校正,确保处理的角度是接近水平的
#             if abs(angle) > 90:
#                 angle -= 180
#             if abs(angle) < 20:  # 只考虑接近水平的线条
#                 filtered_lines.append((angle, length))
# 
#         # 按长度排序,并取最长的前9条线
#         filtered_lines.sort(key=lambda x: x[1], reverse=True)
#         top_lines = filtered_lines[:9]
# 
#         # 分割角度和长度
#         angles, lengths = zip(*top_lines) if top_lines else ([], [])
# 
#     # 计算加权平均角度
#     if lengths:
#         average_angle = np.average(angles, weights=lengths)
#     else:
#         average_angle = 0
# 
#     return average_angle# 使用此函数时,确保传入的lines是过滤后只包含接近水平的线条def draw_lines(image, lines):for line in lines:x1, y1, x2, y2 = line[0]cv2.line(image, (x1, y1), (x2, y2), (0, 255, 0), 3)return imagedef rotate_image(image, angle):(h, w) = image.shape[:2]center = (w // 2, h // 2)M = cv2.getRotationMatrix2D(center, angle, 1.0)rotated = cv2.warpAffine(image, M, (w, h))return rotated# 加载图像
image = cv2.imread('test.png')# 转换为灰度图
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# 边缘检测
edges = cv2.Canny(gray, 50, 150, apertureSize=3)# 使用霍夫变换检测线条
lines = cv2.HoughLinesP(edges, 1, np.pi / 180, threshold=100, minLineLength=100, maxLineGap=10)# 过滤出横向线条
horizontal_lines = filter_horizontal_lines(lines)# 在原图上绘制检测到的横向线条
image_with_lines = draw_lines(np.copy(image), horizontal_lines)# 保存带线条的图像
cv2.imwrite('image_with_horizontal_lines.jpg', image_with_lines)# 计算线条的加权平均角度
average_angle = calculate_average_angle(horizontal_lines)
print("计算得到的加权平均角度为:", average_angle)# 旋转整个图像使线条水平
rotated_image = rotate_image(image, average_angle)  # 根据角度旋转 正角度表示逆时针旋转,而负角度表示顺时针旋转# 保存旋转后的图像
cv2.imwrite('rotated_image.jpg', rotated_image)print("旋转后的图像已保存为 'rotated_image.jpg'")

结论

通过上述步骤,我们能够自动检测并校正图像中的水平线,这对于许多自动化处理任务来说是非常有用的。本文介绍的方法仅依赖于 OpenCV,易于实现且效果显著。了解相关函数,通过适当调整参数,该技术可以适应不同的应用需求和条件。

这篇关于用 OpenCV 实现图像中水平线检测与校正的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1028803

相关文章

Qt 设置软件版本信息的实现

《Qt设置软件版本信息的实现》本文介绍了Qt项目中设置版本信息的三种常用方法,包括.pro文件和version.rc配置、CMakeLists.txt与version.h.in结合,具有一定的参考... 目录在运行程序期间设置版本信息可以参考VS在 QT 中设置软件版本信息的几种方法方法一:通过 .pro

HTML5实现的移动端购物车自动结算功能示例代码

《HTML5实现的移动端购物车自动结算功能示例代码》本文介绍HTML5实现移动端购物车自动结算,通过WebStorage、事件监听、DOM操作等技术,确保实时更新与数据同步,优化性能及无障碍性,提升用... 目录1. 移动端购物车自动结算概述2. 数据存储与状态保存机制2.1 浏览器端的数据存储方式2.1.

基于 HTML5 Canvas 实现图片旋转与下载功能(完整代码展示)

《基于HTML5Canvas实现图片旋转与下载功能(完整代码展示)》本文将深入剖析一段基于HTML5Canvas的代码,该代码实现了图片的旋转(90度和180度)以及旋转后图片的下载... 目录一、引言二、html 结构分析三、css 样式分析四、JavaScript 功能实现一、引言在 Web 开发中,

SpringBoot中使用Flux实现流式返回的方法小结

《SpringBoot中使用Flux实现流式返回的方法小结》文章介绍流式返回(StreamingResponse)在SpringBoot中通过Flux实现,优势包括提升用户体验、降低内存消耗、支持长连... 目录背景流式返回的核心概念与优势1. 提升用户体验2. 降低内存消耗3. 支持长连接与实时通信在Sp

Conda虚拟环境的复制和迁移的四种方法实现

《Conda虚拟环境的复制和迁移的四种方法实现》本文主要介绍了Conda虚拟环境的复制和迁移的四种方法实现,包括requirements.txt,environment.yml,conda-pack,... 目录在本机复制Conda虚拟环境相同操作系统之间复制环境方法一:requirements.txt方法

Spring Boot 实现 IP 限流的原理、实践与利弊解析

《SpringBoot实现IP限流的原理、实践与利弊解析》在SpringBoot中实现IP限流是一种简单而有效的方式来保障系统的稳定性和可用性,本文给大家介绍SpringBoot实现IP限... 目录一、引言二、IP 限流原理2.1 令牌桶算法2.2 漏桶算法三、使用场景3.1 防止恶意攻击3.2 控制资源

springboot下载接口限速功能实现

《springboot下载接口限速功能实现》通过Redis统计并发数动态调整每个用户带宽,核心逻辑为每秒读取并发送限定数据量,防止单用户占用过多资源,确保整体下载均衡且高效,本文给大家介绍spring... 目录 一、整体目标 二、涉及的主要类/方法✅ 三、核心流程图解(简化) 四、关键代码详解1️⃣ 设置

Nginx 配置跨域的实现及常见问题解决

《Nginx配置跨域的实现及常见问题解决》本文主要介绍了Nginx配置跨域的实现及常见问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来... 目录1. 跨域1.1 同源策略1.2 跨域资源共享(CORS)2. Nginx 配置跨域的场景2.1

Python中提取文件名扩展名的多种方法实现

《Python中提取文件名扩展名的多种方法实现》在Python编程中,经常会遇到需要从文件名中提取扩展名的场景,Python提供了多种方法来实现这一功能,不同方法适用于不同的场景和需求,包括os.pa... 目录技术背景实现步骤方法一:使用os.path.splitext方法二:使用pathlib模块方法三

CSS实现元素撑满剩余空间的五种方法

《CSS实现元素撑满剩余空间的五种方法》在日常开发中,我们经常需要让某个元素占据容器的剩余空间,本文将介绍5种不同的方法来实现这个需求,并分析各种方法的优缺点,感兴趣的朋友一起看看吧... css实现元素撑满剩余空间的5种方法 在日常开发中,我们经常需要让某个元素占据容器的剩余空间。这是一个常见的布局需求