OPENCV摄像头畸变矫正(以形状识别为例)/从畸变校正到形状检测:我的摄像头图像处理实践

发布于:2025-03-24 ⋅ 阅读:(25) ⋅ 点赞:(0)

请直接从正文看

我是看了一位老哥的分享才对参数了解的:

全(中文)网都错的相机畸变参数到底怎么写?——相机畸变校正公式的正确写法-CSDN博客

然后我总结的就是我的摄像头的畸变系数是-10%

是亚博的一款摄像头,但是我的参数设定看来,要-0.35才可以

然后调整了畸变的结果是和墙边的桌子边线平行

然后我还使用了图像压缩,我一开始就只用的矫正畸变但是看起来效果不怎么样

但是当我尝试图像在y轴压缩时,直接就看起来正常了

正文: 

最近在做图像处理相关的项目时,遇到了一个棘手的问题:摄像头拍摄的图像存在畸变,导致后续的图像分析和处理效果不佳。经过一番研究和实践,我终于找到了解决问题的方法,并且还实现了形状检测功能。今天就来和大家分享一下我的经验。

一、相机畸变校正的探索

在开始之前,我先简单介绍一下相机畸变。相机畸变是指由于镜头的光学特性,导致拍摄的图像与实际物体形状不一致的现象。常见的畸变有桶形畸变和枕形畸变,它们会让图像的边缘部分看起来变形。

一开始,我参考了网上的资料,发现很多人都在使用畸变系数来校正图像。但是,我发现全网对于畸变系数的设定似乎都存在一些误解。很多人直接使用 -10%(即 -0.1)作为畸变系数,但在我实际测试中,这个值并不适用。

我使用的是亚博的一款摄像头,经过多次试验,我发现只有将畸变系数设置为 -0.35 时,校正后的图像效果才最接近真实场景。为了验证这一点,我将校正后的图像与墙边的桌子边线进行对比,发现它们已经能够很好地平行对齐了。

二、畸变校正代码实现

为了实现畸变校正,我编写了以下代码:

import cv2
import numpy as np

def undistort_image(image, distortion_coefficient=-0.35):
    """
    对图像进行畸变校正

    参数:
        image: 输入图像
        distortion_coefficient: 畸变系数,默认为 -0.35

    返回:
        校正后的图像
    """
    # 获取图像尺寸
    h, w = image.shape[:2]

    # 构造相机内参矩阵
    camera_matrix = np.array([[w, 0, w/2],
                              [0, h, h/2],
                              [0, 0, 1]], dtype=np.float32)

    # 构造畸变系数数组
    dist_coeffs = np.array([distortion_coefficient, 0, 0, 0], dtype=np.float32)

    # 进行畸变校正
    undistorted_image = cv2.undistort(image, camera_matrix, dist_coeffs)

    return undistorted_image

这段代码的核心是 cv2.undistort 函数,它可以根据相机内参矩阵和畸变系数对图像进行校正。通过调整畸变系数,我们可以得到最佳的校正效果。

三、图像压缩与形状检测

在畸变校正的基础上,我还尝试了图像压缩和形状检测。我发现仅仅进行畸变校正的效果并不理想,但当我对图像进行 y 轴压缩后,图像看起来更加正常了。这让我意识到,有时候我们需要结合多种方法来优化图像处理效果。

为了实现形状检测,我编写了以下代码:

def ShapeDetection(imgContour, cnt):
    # 计算轮廓的周长
    perimeter = cv2.arcLength(cnt, True)
    
    # 多边形近似
    approx = cv2.approxPolyDP(cnt, 0.02 * perimeter, True)
    corners = len(approx)
    
    # 计算轮廓的面积
    area = cv2.contourArea(cnt)
    if area < 500:
        return None
    
    x, y, w, h = cv2.boundingRect(approx)
    
    # 三角形检测
    if corners == 3:
        # 计算三角形的边长
        pts = approx.reshape(3, 2)
        d1 = np.linalg.norm(pts[0] - pts[1])
        d2 = np.linalg.norm(pts[1] - pts[2])
        d3 = np.linalg.norm(pts[2] - pts[0])
        
        # 计算三角形的角度
        angles = []
        for i in range(3):
            p1 = pts[i]
            p2 = pts[(i + 1) % 3]
            p3 = pts[(i + 2) % 3]
            v1 = p2 - p1
            v2 = p3 - p1
            cos_theta = np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))
            angle = np.arccos(np.clip(cos_theta, -1, 1)) * 180 / np.pi
            angles.append(angle)
        
        # 判断是否为有效三角形
        if all(angle < 170 for angle in angles) and all(d > 5 for d in [d1, d2, d3]):
            cv2.drawContours(imgContour, [approx], -1, (255, 0, 0), 3)
            cv2.putText(imgContour, "Triangle", (x + w // 2 - 40, y + h // 2 + 10),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2)
            return "Triangle"
    
    # 矩形检测
    elif corners == 4:
        pts = approx.reshape(4, 2)
        angles = []
        for i in range(4):
            p1 = pts[i]
            p2 = pts[(i + 1) % 4]
            p3 = pts[(i + 2) % 4]
            v1 = p2 - p1
            v2 = p3 - p2
            cos_theta = np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))
            angle = np.arccos(np.clip(cos_theta, -1, 1)) * 180 / np.pi
            angles.append(angle)
        
        # 判断是否为矩形
        if all(abs(90 - a) < 10 for a in angles):
            rotated_rect = cv2.minAreaRect(approx)
            width, height = rotated_rect[1]
            aspect_ratio = width / float(height)
            
            if 0.85 < aspect_ratio < 1.15:
                cv2.drawContours(imgContour, [approx], -1, (255, 0, 0), 3)
                cv2.putText(imgContour, "Square", (x + w // 2 - 40, y + h // 2 + 10),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2)
                return "Square"
            else:
                cv2.drawContours(imgContour, [approx], -1, (255, 0, 0), 3)
                cv2.putText(imgContour, "Rectangle", (x + w // 2 - 40, y + h // 2 + 10),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2)
                return "Rectangle"
    
    # 圆形检测
    elif corners >= 8:
        (cx, cy), radius = cv2.minEnclosingCircle(cnt)
        circularity = 4 * np.pi * area / (perimeter ** 2)
        if 0.7 < circularity < 1.2 and radius > 10:
            cv2.drawContours(imgContour, [cnt], -1, (255, 0, 0), 3)
            cv2.putText(imgContour, "Circle", (int(cx) - 40, int(cy) + 10),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2)
            return "Circle"
    
    return None

这段代码通过计算轮廓的周长、多边形近似、角度等信息,实现了对三角形、矩形、正方形和圆形的检测。在检测到形状后,会在图像上绘制轮廓并标注形状类型。

四、完整流程与效果展示

最后,我将畸变校正、图像压缩和形状检测结合起来,实现了完整的图像处理流程。以下是完整的代码:

import cv2
import numpy as np
import time
from collections import defaultdict

# 畸变校正函数
def undistort_image(image, distortion_coefficient=-0.35):
    """
    对图像进行畸变校正

    参数:
        image: 输入图像
        distortion_coefficient: 畸变系数,默认为 -0.35

    返回:
        校正后的图像
    """
    # 获取图像尺寸
    h, w = image.shape[:2]

    # 构造相机内参矩阵
    camera_matrix = np.array([[w, 0, w/2],
                              [0, h, h/2],
                              [0, 0, 1]], dtype=np.float32)

    # 构造畸变系数数组
    dist_coeffs = np.array([distortion_coefficient, 0, 0, 0], dtype=np.float32)

    # 进行畸变校正
    undistorted_image = cv2.undistort(image, camera_matrix, dist_coeffs)

    return undistorted_image

# 形状检测函数
def ShapeDetection(imgContour, cnt):
    # 计算轮廓的周长
    perimeter = cv2.arcLength(cnt, True)
    
    # 多边形近似
    approx = cv2.approxPolyDP(cnt, 0.02 * perimeter, True)
    corners = len(approx)
    
    # 计算轮廓的面积
    area = cv2.contourArea(cnt)
    if area < 500:
        return None
    
    x, y, w, h = cv2.boundingRect(approx)
    
    # 三角形检测
    if corners == 3:
        # 计算三角形的边长
        pts = approx.reshape(3, 2)
        d1 = np.linalg.norm(pts[0] - pts[1])
        d2 = np.linalg.norm(pts[1] - pts[2])
        d3 = np.linalg.norm(pts[2] - pts[0])
        
        # 计算三角形的角度
        angles = []
        for i in range(3):
            p1 = pts[i]
            p2 = pts[(i + 1) % 3]
            p3 = pts[(i + 2) % 3]
            v1 = p2 - p1
            v2 = p3 - p1
            cos_theta = np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))
            angle = np.arccos(np.clip(cos_theta, -1, 1)) * 180 / np.pi
            angles.append(angle)
        
        # 判断是否为有效三角形
        if all(angle < 170 for angle in angles) and all(d > 5 for d in [d1, d2, d3]):
            cv2.drawContours(imgContour, [approx], -1, (255, 0, 0), 3)
            cv2.putText(imgContour, "Triangle", (x + w // 2 - 40, y + h // 2 + 10),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2)
            return "Triangle"
    
    # 矩形检测
    elif corners == 4:
        pts = approx.reshape(4, 2)
        angles = []
        for i in range(4):
            p1 = pts[i]
            p2 = pts[(i + 1) % 4]
            p3 = pts[(i + 2) % 4]
            v1 = p2 - p1
            v2 = p3 - p2
            cos_theta = np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))
            angle = np.arccos(np.clip(cos_theta, -1, 1)) * 180 / np.pi
            angles.append(angle)
        
        # 判断是否为矩形
        if all(abs(90 - a) < 10 for a in angles):
            rotated_rect = cv2.minAreaRect(approx)
            width, height = rotated_rect[1]
            aspect_ratio = width / float(height)
            
            if 0.85 < aspect_ratio < 1.15:
                cv2.drawContours(imgContour, [approx], -1, (255, 0, 0), 3)
                cv2.putText(imgContour, "Square", (x + w // 2 - 40, y + h // 2 + 10),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2)
                return "Square"
            else:
                cv2.drawContours(imgContour, [approx], -1, (255, 0, 0), 3)
                cv2.putText(imgContour, "Rectangle", (x + w // 2 - 40, y + h // 2 + 10),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2)
                return "Rectangle"
    
    # 圆形检测
    elif corners >= 8:
        (cx, cy), radius = cv2.minEnclosingCircle(cnt)
        circularity = 4 * np.pi * area / (perimeter ** 2)
        if 0.7 < circularity < 1.2 and radius > 10:
            cv2.drawContours(imgContour, [cnt], -1, (255, 0, 0), 3)
            cv2.putText(imgContour, "Circle", (int(cx) - 40, int(cy) + 10),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2)
            return "Circle"
    
    return None

# 摄像头捕获
cap = cv2.VideoCapture(1)

# 初始化时间变量
start_time = time.time()
# 裁剪区域 (根据实际情况调整)
x_start = 210  # 裁剪区域的起始x坐标
y_start = 60  # 裁剪区域的起始y坐标
x_end = 390    # 裁剪区域的结束x坐标
y_end = 340    # 裁剪区域的结束x坐标

while True:
    ret, frame = cap.read()
    if not ret:
        break
    # 对图像进行畸变校正
    frame = undistort_image(frame)
    # 压缩图像尺寸,最后试出来的
    frame = cv2.resize(frame, (640, 400))
    # 裁剪图像
    frame = frame[y_start:y_end, x_start:x_end]
    # 后续处理使用校正后的图像
    imgContour = frame.copy()
    # 转换为灰度图像
    imgGray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    # 高斯模糊
    imgBlur = cv2.GaussianBlur(imgGray, (7, 7), 1)
    # 二值化处理
    _, imgThresh = cv2.threshold(imgBlur, 95, 255, cv2.THRESH_BINARY_INV)
    # 边缘检测
    edges = cv2.Canny(imgBlur, 30, 150)
    # 边缘和二值化图像合并
    imgCanny = cv2.bitwise_or(edges, imgThresh)
    # 闭运算
    kernel = np.ones((3, 3), np.uint8)
    imgCanny = cv2.morphologyEx(imgCanny, cv2.MORPH_CLOSE, kernel)
    # 轮廓检测
    contours, _ = cv2.findContours(imgCanny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    # 初始化形状计数器
    shape_counts = defaultdict(int)

    for cnt in contours:
        shape_type = ShapeDetection(imgContour, cnt)
        if shape_type:
            shape_counts[shape_type] += 1

    # 打印形状计数
    current_time = time.time()
    if current_time - start_time >= 1.0:
        # 记录每秒的形状计数
        if shape_counts:
            # 找到每种形状的众数数量
            triangle_count = shape_counts.get("Triangle", 0)
            rectangle_count = shape_counts.get("Rectangle", 0)
            square_count = shape_counts.get("Square", 0)
            circle_count = shape_counts.get("Circle", 0)
            
            print(f"Triangles: {triangle_count}, Rectangles: {rectangle_count}, Squares: {square_count}, Circles: {circle_count}")
        else:
            print("No shapes detected")
        start_time = current_time

    cv2.imshow("Contours", imgContour)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

运行这段代码后,我们可以看到摄像头拍摄的图像经过畸变校正、压缩和形状检测后,能够清晰地识别出三角形、矩形、正方形和圆形,并在图像上标注出来。同时,程序还会实时统计每秒检测到的各种形状的数量。

五、总结

结合图像压缩和形状检测,可以进一步优化图像处理效果。

如果你也遇到了类似的问题,不妨尝试调整畸变系数,并结合其他图像处理方法来达到最佳效果。希望我的经验能对你有所帮助!