Python OpenCV 图像矫正的原理与实现

题目描述

目录hw1下的图像是一些胶片的照片,请将其进行度量矫正。

推荐流程:采用Canny算子,检测边缘点;采用Hough直线检测,根据边缘点检测胶片边缘对应的4条直线;4条直线在图像平面中的交点为胶片图像的4个顶点。根据4个顶点与真实世界中胶片的位置(假设胶片图像长宽比为4:3),得到两个平面之间的单应变换矩阵,并根据单应变换矩阵实现图像矫正。

基本思路

  • 使用Canny算子,检测边缘点;
  • 以边缘点作为输入,采用Hough直线检测,检测出最多点共线的四条直线,这四条直线的交点就是照片中屏幕的四个顶点;
  • 假设胶片图像长宽比为4:3,那么此时已知四个匹配的点,可以求解出两个平面之间的单应变换矩阵;
  • 从而可以使用原图像、单应变换矩阵,对原图像进行变换,即可实现图像矫正。
  • 实现日志

    Canny边缘检测:Python OpenCV Canny边缘检测算法的原理与实现_乔卿的博客-CSDN博客

    Hough直线检测:Python OpenCV Hough直线检测算法的原理与实现_乔卿的博客-CSDN博客

    在具体实现时,发现对于给定的图像,几乎不可能通过调整阈值的方式,使得Hough检测到的直线刚好是屏幕边框。经过多轮调整,在下界为180、上界为260时取得了较为理想的结果,如下图所示。

    对于三张图像,经过实验,最终选择的最佳阈值为:

    correct('images/1.jpeg', 180, 260)
    correct('images/2.jpeg', 30, 100)
    correct('images/3.jpeg', 100, 160)

    但即便是最佳阈值,也无法做到仅检测出四条线。思考过后,决定加入一步人工筛选。有两种可行的技术方案:

  • 人工筛选直线
  • 人工筛选交点
  • 考虑到如果筛选交点的话,工作量明显比筛选直线更大,所以选择人工筛选直线。后面有时间的话考虑加入图形化界面,目前因时间原因,选择专注于算法本身,暂不考虑可视化编程。直接显示出下图用于筛选:

    这里符合条件的直线id为2、3、6、7。求解得到的交点:

    我们假设目标图像是4:3的,也就是其大小为(800, 600),从而我们可以确定目标图像中四个关键点位置为[0, 0], [800, 0], [0, 600], [800, 600]。为了保证交点与目标点一一对应,最为高效的解决方案是,我们筛选图像的时候,按照上、左、下、右的顺序即可。

    核心代码

    def correct(image_path, threshold1, threshold2):
        # 读取图像并转换为灰度图像
        image = cv2.imread(image_path)
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    
        # 使用Canny算子检测边缘
        edges = canny_detect(gray, threshold1, threshold2, show=False)
    
        # 使用Hough检测直线
        lines = hough_detect(image, edges, show=False)
    
        # 手动筛选
        for id, line in enumerate(lines):
            rho, theta = line[0]
            x1, y1, x2, y2 = convert_polar_to_two_points(rho, theta)
            temp_image = image.copy()
            cv2.line(temp_image, (x1, y1), (x2, y2), (255, 0, 0), 7)
            plt.subplot(5, 5, id + 1)
            plt.imshow(temp_image)
            plt.title('{}'.format(id))
            plt.xticks([])
            plt.yticks([])
        plt.show()
        choose = input('请输入您选择的直线的id,以空格分隔:').split(' ')
    
        # 求解交点
        crossover_points = []
        assert len(choose) == 4
        for i in range(4):
            for j in range(i+1, 4):
                rho1, theta1 = lines[int(choose[i])][0]
                rho2, theta2 = lines[int(choose[j])][0]
                # 如果角度差太小,认为它们是平行线
                if abs(theta2 - theta1) > np.pi / 8 and abs(theta2 - theta1) < np.pi * 7 / 8:
                    crossover_points.append(cal_crossover(rho1, theta1, rho2, theta2))
    
        # 确定变换前后的坐标
        before = np.float32(crossover_points)
        after = np.float32([[0, 0], [800, 0], [0, 600], [800, 600]])
    
        # 单应变换
        h = cv2.getPerspectiveTransform(before, after)
        result = cv2.warpPerspective(image, h, (800, 600))
    
        cv2.imwrite(image_path.split('.')[0] + '_correct.jpeg', result)
        return result

    完整代码

    import cv2
    import numpy as np
    from matplotlib import pyplot as plt
    
    
    def canny_detect(gray, threshold1=100, threshold2=200, show=True):
        # 获取边缘检测结果
        edges = cv2.Canny(gray, threshold1, threshold2, apertureSize=3)
    
        if show:
            # 绘制原图
            plt.subplot(121)
            plt.imshow(gray, cmap='gray')
            plt.title('Original Image')
            plt.xticks([])
            plt.yticks([])
    
            # 绘制边缘图
            plt.subplot(122)
            plt.imshow(edges, cmap='gray')
            plt.title('Edge Image')
            plt.xticks([])
            plt.yticks([])
    
            plt.show()
    
        # 返回的是一个二值图像
        return edges
    
    
    def hough_detect(image, edges, show=True):
        # 使用Hough检测直线
        lines = cv2.HoughLines(edges, 1, np.pi/180, 200)
    
        if show:
            # 绘制直线
            for line in lines:
                rho, theta = line[0]
                x1, y1, x2, y2 = convert_polar_to_two_points(rho, theta)
                cv2.line(image, (x1, y1), (x2, y2), (0, 0, 255), 2)
    
            cv2.imshow('line,jpg', image)
            cv2.waitKey()
    
        return lines
    
    
    # 将极坐标转换为两点
    def convert_polar_to_two_points(rho, theta):
        a = np.cos(theta)
        b = np.sin(theta)
        x0 = a * rho
        y0 = b * rho
        x1 = int(x0 + 10000 * (-b))
        y1 = int(y0 + 10000 * (a))
        x2 = int(x0 - 10000 * (-b))
        y2 = int(y0 - 10000 * (a))
        return x1, y1, x2, y2
    
    
    # 一般式 Ax+By+C=0
    def GeneralEquation(x1, y1, x2, y2):
        A = y2 - y1
        B = x1 - x2
        C = x2 * y1 - x1 * y2
        return A, B, C
    
    
    # 求解交点
    def cal_crossover(rho1, theta1, rho2, theta2):
        x11, y11, x12, y12 = convert_polar_to_two_points(rho1, theta1)
        x21, y21, x22, y22 = convert_polar_to_two_points(rho2, theta2)
        A1, B1, C1 = GeneralEquation(x11, y11, x12, y12)
        A2, B2, C2 = GeneralEquation(x21, y21, x22, y22)
        m = A1 * B2 - A2 * B1
        x = (C2 * B1 - C1 * B2) / m
        y = (C1 * A2 - C2 * A1) / m
        return [x, y]
    
    
    def correct(image_path, threshold1, threshold2):
        # 读取图像并转换为灰度图像
        image = cv2.imread(image_path)
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    
        # 使用Canny算子检测边缘
        edges = canny_detect(gray, threshold1, threshold2, show=False)
    
        # 使用Hough检测直线
        lines = hough_detect(image, edges, show=False)
    
        # 手动筛选,按照上、左、下、右的顺序
        for id, line in enumerate(lines):
            rho, theta = line[0]
            x1, y1, x2, y2 = convert_polar_to_two_points(rho, theta)
            temp_image = image.copy()
            cv2.line(temp_image, (x1, y1), (x2, y2), (255, 0, 0), 7)
            plt.subplot(5, 5, id + 1)
            plt.imshow(temp_image)
            plt.title('{}'.format(id))
            plt.xticks([])
            plt.yticks([])
        plt.show()
        choose = input('请输入您选择的直线的id,以空格分隔:').split(' ')
    
        # 求解交点
        crossover_points = []
        assert len(choose) == 4
        for i in range(4):
            for j in range(i+1, 4):
                rho1, theta1 = lines[int(choose[i])][0]
                rho2, theta2 = lines[int(choose[j])][0]
                # 如果角度差太小,认为它们是平行线
                if abs(theta2 - theta1) > np.pi / 8 and abs(theta2 - theta1) < np.pi * 7 / 8:
                    print(abs(theta2 - theta1) / np.pi)
                    print(choose[i], choose[j])
                    crossover_points.append(cal_crossover(rho1, theta1, rho2, theta2))
                    temp_image = image.copy()
                    cv2.circle(temp_image, (int(cal_crossover(rho1, theta1, rho2, theta2)[0]), int(cal_crossover(rho1, theta1, rho2, theta2)[1])), 10, (255, 0, 0), 2)
                    cv2.imshow('point', temp_image)
                    cv2.waitKey()
    
        # 确定变换前后的坐标
        before = np.float32(crossover_points)
        after = np.float32([[0, 0], [800, 0], [0, 600], [800, 600]])
    
        # 单应变换
        h = cv2.getPerspectiveTransform(before, after)
        result = cv2.warpPerspective(image, h, (800, 600))
    
        cv2.imwrite(image_path.split('.')[0] + '_correct.jpeg', result)
        return result
    
    
    # correct('images/1.jpeg', 180, 260)
    # correct('images/2.jpeg', 30, 100)
    correct('images/3.jpeg', 100, 160)

    矫正结果

    来源:乔卿

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python OpenCV 图像矫正的原理与实现

    发表评论