OpenCV练习(2)图像校正

news2024/10/6 6:04:37
  • 1、傅里叶变换 + 霍夫变换+ 直线 + 角度 + 旋转
  • 2、边缘检测 + 霍夫变换 + 直线+角度 + 旋转
  • 3、四点透视 + 角度 + 旋转
  • 4、检测矩形轮廓 + 角度 + 旋转

1.目的

实现类似全能扫面王的图像校正功能

2. 基于轮廓提取和透射变换

基于轮廓提取和透射变换的矫正算法更适用于车牌身份证人民币书本、 发票一类矩形形状而且边界明显的物体矫正。所校正的内容必须要有清晰的完整轮廓。

基本步骤:

  1. 图片灰度化
  2. 二值化/canny边缘检测等操作
  3. 检测轮廓,并筛选出目标轮廓(通过横纵比或面积去除干扰轮廓)
  4. 获取图像顶点
  5. 透视变换

代码:

import cv2
import numpy as np

def contour_to_rect(contour):
    pts = contour.reshape(4, 2)
    print(pts)
    rect = np.zeros((4, 2), dtype = "float32")
    # top-left point has the smallest sum
    # bottom-right has the largest sum
    s = pts.sum(axis = 1)
#     print(s)
    rect[0] = pts[np.argmin(s)]
#     print(pts[np.argmin(s)])
    rect[2] = pts[np.argmax(s)]
    # compute the difference between the points:
    # the top-right will have the minumum difference
    # the bottom-left will have the maximum difference
    diff = np.diff(pts, axis = 1)
    rect[1] = pts[np.argmin(diff)]
    rect[3] = pts[np.argmax(diff)]
    return rect

# approximate the contour by a more primitive polygon shape
def approximate_contour(contour):
    peri = cv2.arcLength(contour, True)
    return cv2.approxPolyDP(contour, 0.032 * peri, True)

# 获取顶点坐标
def get_receipt_contour(contours):
    # loop over the contours
    for c in contours:
        approx = approximate_contour(c)
        # if our approximated contour has four points, we can assume it is receipt's rectangle
        if len(approx) == 4:
            return approx


def wrap_perspective(img, rect):
    # unpack rectangle points: top left, top right, bottom right, bottom left
    (tl, tr, br, bl) = rect
    # compute the width of the new image
    widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
    widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
    # compute the height of the new image
    heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
    heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
    # take the maximum of the width and height values to reach
    # our final dimensions
    maxWidth = max(int(widthA), int(widthB))
    maxHeight = max(int(heightA), int(heightB))
    # destination points which will be used to map the screen to a "scanned" view
    dst = np.array([
        [0, 0],
        [maxWidth - 1, 0],
        [maxWidth - 1, maxHeight - 1],
        [0, maxHeight - 1]], dtype = "float32")
    # calculate the perspective transform matrix
    M = cv2.getPerspectiveTransform(rect, dst)
    # warp the perspective to grab the screen
    return cv2.warpPerspective(img, M, (maxWidth, maxHeight))


if __name__ == "__main__":
    img = cv2.imread("./images/1.png")
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # 用高斯滤波处理原图像降噪
    blur = cv2.GaussianBlur(gray, (5, 5), 0)
	#canny边缘检测(仅针对这次的输入图片)
    edged = cv2.Canny(blur, 50, 150)
    contours, h = cv2.findContours(edged.copy(), mode=cv2.RETR_EXTERNAL, method=cv2.CHAIN_APPROX_SIMPLE)
    # img_contours = cv2.drawContours(img.copy(), contours, -1, (0, 0, 255), 3)

    largest_contours = sorted(contours, key=cv2.contourArea, reverse=True)[:5]
    # img_largest_contours = cv2.drawContours( img.copy(), largest_contours, -1, (0, 0, 255), 3)

    receipt_contour = get_receipt_contour(largest_contours)
    # img_receipt_contour = cv2.drawContours(img.copy(), [receipt_contour], -1, (0, 0, 255), 3)

    ori_img = img.copy()
    for coor in contour_to_rect(receipt_contour):
        cv2.circle(ori_img, (int(coor[0]), int(coor[1])), 1, (0, 0, 255), 4)
	# 进行透视变换
    scanned = wrap_perspective(img.copy(), contour_to_rect(receipt_contour))
2.1 图像灰度化 

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

blur = cv2.GaussianBlur(gray, (5, 5), 0)(如果有噪声可以加个高斯滤波)

2.2 canny边缘检测

edged = cv2.Canny(blur, 50, 150) (三个参数分别输入图像,最小阈值,最大阈值)

下图中左为输出,右为边缘检测结果。看起来效果还行

左为所有轮廓绘制,右为面积前五的绘制。可以看到,书中长方形的框并不在检测中,这是因为在进行边缘检测时色差不大导致没检测出来,后续使用闭运算处理将连断出填充完。

 

 但是对于,图像边框,或者说前景与背景色差不大时,很难检测出来,如下图放在白色桌面的安装指南

 

2.3 检测轮廓,并筛选出目标轮廓(通过横纵比或面积去除干扰轮廓) 

 contours, h = cv2.findContours(edged.copy(), mode=cv2.RETR_EXTERNAL, method=cv2.CHAIN_APPROX_SIMPLE)
  img_contours = cv2.drawContours(img.copy(), contours, -1, (0, 0, 255), 3)

检测图像轮廓并绘制所有的轮廓

# approximate the contour by a more primitive polygon shape
def approximate_contour(contour):
    peri = cv2.arcLength(contour, True)
    return cv2.approxPolyDP(contour, 0.032 * peri, True)

# 获取顶点坐标
def get_receipt_contour(contours):
    # loop over the contours
    for c in contours:
        approx = approximate_contour(c)
        # if our approximated contour has four points, we can assume it is receipt's rectangle
        if len(approx) == 4:
            return approx

在这个实现中,approximate_contour函数使用cv2.approxPolyDP来近似轮廓。cv2.approxPolyDP的第二个参数是精度参数,它是一个距离值,表示近似轮廓与原始轮廓之间的最大距离。这个值是原始轮廓周长的百分比,通常设置为一个很小的值(例如0.02)。第三个参数True表示近似出的多边形是闭合的。

get_receipt_contour函数然后使用这个近似函数来寻找一个四边形的轮廓,假设这个四边形是收据的形状。如果找到了一个有四个顶点的轮廓,它就返回这个轮廓。如果没有找到,函数将返回None

请注意,这个函数假设收据是一个四边形,并且是轮廓列表中面积最大的四边形。在实际应用中,可能需要根据收据的具体形状和图像中的其他元素来调整这个函数。

 2.4 获取图像顶点            

    receipt_contour = get_receipt_contour(largest_contours)
    img_receipt_contour = cv2.drawContours(img.copy(), [receipt_contour], -1, (0, 0, 255), 3)

def contour_to_rect(contour):
    pts = contour.reshape(4, 2)
    print(pts)
    rect = np.zeros((4, 2), dtype = "float32")
    # top-left point has the smallest sum
    # bottom-right has the largest sum
    s = pts.sum(axis = 1)
#     print(s)
    rect[0] = pts[np.argmin(s)]
#     print(pts[np.argmin(s)])
    rect[2] = pts[np.argmax(s)]
    # compute the difference between the points:
    # the top-right will have the minumum difference
    # the bottom-left will have the maximum difference
    diff = np.diff(pts, axis = 1)
    rect[1] = pts[np.argmin(diff)]
    rect[3] = pts[np.argmax(diff)]
    return rect

在您提供的contour_to_rect函数中,目标是将以四个顶点形式给出的轮廓转换为一个矩形,这个矩形以特定的顺序排列顶点,通常是顺时针或逆时针方向,从左上角开始。然而,在您提供的代码中存在一些问题,包括一个拼写错误和一个可能的逻辑错误。我将为您纠正这些问题,并提供一个完整的函数,该函数将返回一个按正确顺序排列顶点的矩形。

ori_img = img.copy() for coor in contour_to_rect(receipt_contour): cv2.circle(ori_img, (int(coor[0]), int(coor[1])), 1, (0, 0, 255), 4) 

将筛选的四个点在图像中标注

 

2.5 透视变换

scanned = wrap_perspective(img.copy(), contour_to_rect(receipt_contour))
def wrap_perspective(img, rect):
    # unpack rectangle points: top left, top right, bottom right, bottom left
    (tl, tr, br, bl) = rect
    # compute the width of the new image
    widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
    widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
    # compute the height of the new image
    heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
    heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
    # take the maximum of the width and height values to reach
    # our final dimensions
    maxWidth = max(int(widthA), int(widthB))
    maxHeight = max(int(heightA), int(heightB))
    # destination points which will be used to map the screen to a "scanned" view
    dst = np.array([
        [0, 0],
        [maxWidth - 1, 0],
        [maxWidth - 1, maxHeight - 1],
        [0, maxHeight - 1]], dtype = "float32")
    # calculate the perspective transform matrix
    M = cv2.getPerspectiveTransform(rect, dst)
    # warp the perspective to grab the screen
    return cv2.warpPerspective(img, M, (maxWidth, maxHeight))

 

您提供的wrap_perspective函数是用于执行透视变换的,这种变换通常用于将图像中的四边形区域(如收据、文档等)转换为俯视图,即“扫描”视图。这个函数接受两个参数:要变换的图像img和一个表示四边形顶点的numpy数组rect

以下是函数中每一步的解释:

  1. 解包四边形顶点:将四边形的顶点解包为tl(左上角)、tr(右上角)、br(右下角)和bl(左下角)。

  2. 计算新图像的宽度:通过计算对角线brbltrtl的距离来得到新图像的潜在宽度。使用欧几里得距离公式sqrt((x2 - x1)^2 + (y2 - y1)^2)

  3. 计算新图像的高度:同样,通过计算对角线trbrtlbl的距离来得到新图像的潜在高度。

  4. 确定最终尺寸:通过取宽度 和高度的最大值来确保四边形在变换后不会被裁剪。

  5. 定义目标点:创建一个目标点数组dst,这些点定义了变换后的四边形的顶点位置。这些点将图像映射到一个矩形的“扫描”视图。

  6. 计算透视变换矩阵:使用cv2.getPerspectiveTransform函数计算从原始四边形到目标矩形的透视变换矩阵M

  7. 执行透视变换:使用cv2.warpPerspective函数应用透视变换到图像img上,得到变换后的图像。

 

3. 基于霍夫直线探测和仿射变换

基于霍夫直线探测的矫正算法更适用于文本类等无明显边界图像的矫正。图像画面必须要干净

算法步骤

  1. 霍夫线变换探测出图像中的所有直线。
  2. 计算出每条直线的倾斜角,求他们的平均值。
  3. 根据倾斜角旋转矫正。
  4. 最后根据文本尺寸裁剪图片。

代码:

import cv2
import numpy as np


img = cv2.imread("./images/4.jpg")
src = img.copy()
gray = cv2.cvtColor(src, cv2.COLOR_RGB2GRAY);
edged = cv2.Canny(src, 50, 200, apertureSize=3);

# 第5个参数就是阈值,阈值越大,检测精度越高
plines = cv2.HoughLines(edged, 1, np.pi / 180, 200);
if plines is not None:
    plines = plines.reshape(-1, 2)
    print(len(plines), "lines detected")

    sum_theta = 0.0
    for rho, theta in plines:
        # rho, theta = line[0]
        a = np.cos(theta)
        b = np.sin(theta)
        x0 = a * rho
        y0 = b * rho
        x1 = int(x0 + 1000*(-b))
        y1 = int(y0 + 1000*(a))
        x2 = int(x0 - 1000*(-b))
        y2 = int(y0 - 1000*(a))
        sum_theta += theta
        # 绘制直线
        cv2.line(src, (x1, y1), (x2, y2), (0, 0, 255), 2)

# 计算平均角度
average_theta = sum_theta / len(plines)
angle = np.degrees(average_theta) - 90

# 计算旋转中心
center = (float(img.shape[1] / 2.0), float(img.shape[0] / 2.0))
# 计算对角线长度作为旋转后图像的尺寸
length = int(np.sqrt(img.shape[0]**2 + img.shape[1]**2))
# 计算旋转矩阵
M = cv2.getRotationMatrix2D(center, angle, 1)
# 进行仿射变换并填充背景色为白色
src_rotate = cv2.warpAffine(img, M, (length, length), borderValue=(255, 255, 255))
3.1 图像预处理

在此不赘述,根据实际情况来

3.2 霍夫线变换

# 第5个参数就是阈值,阈值越大,检测精度越高
plines = cv2.HoughLines(edged, 1, np.pi / 180, 200);

在OpenCV中,cv2.HoughLines函数用于检测图像中的直线。它使用霍夫变换(Hough Transform)来找到图像中直线的参数,然后将这些参数转换为图像中的直线。

您提供的代码片段plines = cv2.HoughLines(edged, 1, np.pi / 180, 200);是使用霍夫变换在经过Canny边缘检测的图像edged中检测直线的例子。

这里的参数解释如下:

  • edged:这是已经通过Canny边缘检测得到的图像。
  • 1:这是霍夫变换中使用的rho(极径)参数。它定义了检测直线时的搜索范围。较大的值会使检测到的直线更加稀疏,而较小的值则可能导致检测到的直线更加密集。
  • np.pi / 180:这是霍夫变换中使用的theta(角度)参数。它定义了检测直线时的角度范围。这里使用的是单位圆上的角度,即0到π弧度。
  • 200:这是霍夫变换中使用的阈值参数。它定义了直线检测的灵敏度。较大的值会使检测到的直线更加稀疏,而较小的值则可能导致检测到的直线更加密集。


if plines is not None:
    plines = plines.reshape(-1, 2)
    print(len(plines), "lines detected")

在OpenCV中,cv2.HoughLines函数返回一个包含检测到的直线的数组,如果没有任何直线被检测到,这个数组将为None。这种情况可能发生在以下几种情况下:

  1. 图像噪声:如果图像中的噪声非常强烈,可能会导致无法检测到任何直线。

  2. 参数设置cv2.HoughLines函数的参数设置(如阈值、角度范围等)可能不适合当前图像。如果参数设置不当,可能无法检测到任何直线。

  3. 图像内容:如果图像中没有明显的直线结构,例如在复杂的自然场景中,可能会检测不到直线。

  4. 图像大小:如果图像非常小,可能没有足够的空间来检测直线。

  5. 图像分辨率:如果图像的分辨率非常低,可能会导致检测到的直线不够精确。

  6. 图像方向:如果图像的方向与霍夫变换的搜索方向不匹配,可能会检测不到直线。

在实际应用中,您可能需要根据图像的具体内容和噪声水平来调整cv2.HoughLines函数的参数,以提高检测直线的效果。

    sum_theta = 0.0
    for rho, theta in plines:
        # rho, theta = line[0]
        a = np.cos(theta)
        b = np.sin(theta)
        x0 = a * rho
        y0 = b * rho
        x1 = int(x0 + 1000*(-b))
        y1 = int(y0 + 1000*(a))
        x2 = int(x0 - 1000*(-b))
        y2 = int(y0 - 1000*(a))
        sum_theta += theta
        # 绘制直线
        cv2.line(src, (x1, y1), (x2, y2), (0, 0, 255), 2)

在您提供的代码片段中,您正在遍历通过霍夫变换检测到的直线,并将这些直线绘制在原始图像src上。每个直线由两个点定义,这两个点是根据极径rho和角度theta计算出来的。

代码中的关键步骤如下:

  1. 遍历plines数组,其中每个元素是一个包含两个值rhotheta的元组。rho是直线与原点之间的距离,而theta是从x轴正向到直线方向的角度(以弧度为单位)。

  2. 对于每个元素,您首先提取rhotheta值。

  3. 使用np.cos(theta)np.sin(theta)计算直线斜率ab

  4. 使用a * rhob * rho计算直线上的两个点的x和y坐标。这些坐标是通过将rho值乘以直线的方向向量(由ab定义)得到的。

  5. 使用int(x0 + 1000*(-b))int(y0 + 1000*(a))计算第一个点的坐标,其中1000是一个常数,用于确保坐标在图像范围内。

  6. 使用int(x0 - 1000*(-b))int(y0 - 1000*(a))计算第二个点的坐标。

  7. 使用cv2.line函数在原始图像src上绘制从第一个点到第二个点的直线,颜色为红色,线宽为2。

  8. 将角度theta累加到变量sum_theta中。

 

经过自己的多次实验,发现霍夫线变换的弧度转化为角度时,是以Y轴正半轴为0°,顺时针计算角度。并且在仿射变换进行旋转时,如果给的是正数,就会逆时针旋转,如果给的是负数,就会顺时针旋转 

 

3.3 旋转角度和旋转中心

# 计算平均角度
average_theta = sum_theta / len(plines)
angle = np.degrees(average_theta) - 90

在OpenCV中,cv2.HoughLines函数返回的角度参数是以弧度为单位的。这个函数使用霍夫变换来检测图像中的直线,而霍夫变换中使用的角度参数是弧度制的。

霍夫变换是一种在图像中检测直线的方法,它将图像中的每个点转换为一个参数空间中的一个点,其中参数空间是一个笛卡尔坐标系,x轴和y轴分别对应于图像中的直线的斜率和截距。在霍夫变换中,θ是直线的倾斜角,以弧度为单位。

因此,当您从cv2.HoughLines函数中获取θ值时,这些值是以弧度为单位的。如果您需要将它们转换为角度,可以使用NumPy的np.degrees函数来进行转换。

请注意,弧度与度的转换关系是:

1 弧度=180​ /Π 度

# 计算旋转中心
center = (float(img.shape[1] / 2.0), float(img.shape[0] / 2.0))
# 计算对角线长度作为旋转后图像的尺寸
length = int(np.sqrt(img.shape[0]**2 + img.shape[1]**2))

3.4 旋转矩阵

# 计算旋转矩阵

M = cv2.getRotationMatrix2D(center, angle, 1)

3.5 仿射变换

# 进行仿射变换并填充背景色为白色

src_rotate = cv2.warpAffine(img, M, (length, length), borderValue=(255, 255, 255))

 

4. 基于最小外接矩形和仿射变换

有明显的单一外接矩形的图像,最好是文本。

算法步骤:

  1. 灰度化以及预处理
  2. 二值化/边缘检测
  3. 最小外接矩形
  4. 旋转中心、旋转角度以及旋转矩阵
  5. 仿射变换

代码:

import numpy as np
import cv2 as cv


def binary(img):
    gray=cv.cvtColor(img,cv.COLOR_BGR2GRAY)
    ret,binary = cv.threshold(gray,0,255,cv.THRESH_BINARY_INV|cv.THRESH_OTSU)
    return binary

def  Transformation(img,src):
    coords=np.column_stack(np.where(img>0))
    print(coords)
    print(coords.shape)
    angle =cv.minAreaRect(coords)[-1]    #最小外接矩形

    print(angle)
    if angle<-45:
        angle = -(90+angle)
    else:
        angle=-angle
    center = (w//2,h//2)
    M= cv.getRotationMatrix2D(center,angle,1.0) #传入中心和角度 得到旋转矩形
    rotated = cv.warpAffine(src,M,(w,h),flags=cv.INTER_CUBIC,borderMode=cv.BORDER_REPLICATE)
    cv.putText(rotated,'Angle:{:.2f} degrees'.format(angle),(10,30),cv.FONT_HERSHEY_SIMPLEX,0.7,(0,0,255),2)#绘制文字
    cv.imshow('Rotated',rotated)


src= cv.imread('imageTextR.png')
(h,w)=src.shape[:2]
gray=cv.cvtColor(src,cv.COLOR_BGR2GRAY)
binary=binary(src)
Transformation(binary,src)
cv.imshow('src',src)
cv.imshow('binary',binary)
cv.waitKey(0)



4.1 灰度化以及预处理

不多赘述

4.2 二值化
ret,binary = cv.threshold(gray,0,255,cv.THRESH_BINARY_INV|cv.THRESH_OTSU)

在OpenCV中,cv2.threshold函数用于将图像转换为二值图像。您提供的代码片段ret, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)是使用cv2.threshold函数对灰度图像gray进行阈值处理,以创建一个二值图像binary

这里的参数解释如下:

  • gray:这是您要进行阈值处理的灰度图像。
  • 0:这是阈值处理的下限值。所有小于这个值的像素将被设置为0(通常是黑色)。
  • 255:这是阈值处理的上限值。所有大于这个值的像素将被设置为255(通常是白色)。
  • cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU:这是阈值处理的方法。cv2.THRESH_BINARY_INV表示使用二值反转阈值,即小于阈值的像素将被设置为255(白色),大于阈值的像素将被设置为0(黑色)。cv2.THRESH_OTSU表示使用Otsu’s方法来自动选择阈值。

Otsu’s方法是一种自动选择阈值的方法,它通过最大化类间方差来选择最佳阈值。这种方法通常用于图像分割,以区分前景和背景。

 

 

4.3 最小外接矩形 

首先获取所有非零坐标元素

coords=np.column_stack(np.where(binary>0))

在您提供的代码片段中,coords = np.column_stack(np.where(binary > 0)) 是一个用于获取二值图像中所有非零像素位置的坐标的方法。具体步骤如下:

  1. 创建二值图像:首先,您使用cv2.threshold函数创建了一个二值图像binary。在这个二值图像中,值为0的像素代表背景,而值为255的像素代表前景。

  2. 使用np.where获取非零像素位置np.where(binary > 0) 是一个NumPy函数,用于获取二值图像中所有非零像素的位置。这个函数返回一个包含所有非零像素坐标的元组,其中第一个元素是x坐标,第二个元素是y坐标。

  3. 将坐标堆叠为列向量:使用np.column_stack函数将坐标堆叠为列向量。这个操作将创建一个二维数组,其中每一行代表一个像素点的坐标。

然后寻找最小外接矩形 

 angle =cv.minAreaRect(coords)[-1]

在OpenCV中,cv2.minAreaRect函数用于找到图像中最小的外接矩形,并返回该矩形的旋转角度。这个函数通常用于对象检测和图像识别任务中。

cv2.minAreaRect函数的参数是一个包含像素点的坐标数组,这些点定义了一个多边形。函数返回的参数有三个:1.矩形的中心点 2.矩形的长和宽 3.矩形的旋转角度

在您提供的代码片段中,angle = cv2.minAreaRect(coords)[-1]的目的是获取最小外接矩形的旋转角度。coords是一个包含像素点的坐标数组,这些点定义了一个多边形,即您之前通过np.column_stack(np.where(binary > 0))获取的非零像素位置。

angle_p =cv.minAreaRect(coords)
box_points = cv.boxPoints(angle_p)
box_points = np.int0(box_points)
cv.circle(src, [146,284], 1, (0, 0, 255), 4)
srca = cv.drawContours(src, [box_points], -1, (0, 0, 255), 2)
》》》15.60267448425293

可以看到画出的外接矩形不能将文字全部包裹住,其角度为15.6,同样是以y轴正半轴为0°,顺时针为度数。

 

4.4 中心,角度,矩阵

cv.getRotationMatrix2D(center, angle, scale) → M

参数说明
center表示旋转中心坐标,二元元组 (x0, y0)。
angle表示旋转角度,单位为角度,逆时针为正数,顺时针为负数
scale表示缩放因子。

4.5 仿射变换

需要说明的是,不管是霍夫线变换还是最小外接矩形,都是为了获取图像的一个倾斜角度。按上面画出的外接矩形来进行仿射变换来本不能完成校正,但是因为文字与x轴正半轴夹角为15.5,然后最小外接矩形的角度刚好是15.6,经过angle=-angle变换后,输入到旋转矩阵的角度就为-15.5,然后仿射变换的旋转就是,正数就是逆时针旋转,负数就是顺时针旋转,所以将原图顺时针旋转后,就刚好校正了。

参考:

OpenCV实战之三 | 基于OpenCV实现图像校正 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1865065.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【十六】【QT开发应用】Menu菜单,contextMenuEvent,setContextMenuPolicy,addAction

在 Qt 框架中&#xff0c;QMenu 类用于创建和管理菜单。菜单是用户界面的一部分&#xff0c;可以包含多个选项或动作&#xff0c;用户可以选择这些选项来执行特定的功能。菜单通常显示在菜单栏、上下文菜单&#xff08;右键菜单&#xff09;或工具栏中。 基本用法 创建菜单对象…

Percona Toolkit 神器全攻略(配置类)

Percona Toolkit 神器全攻略&#xff08;配置类&#xff09; Percona Toolkit 神器全攻略系列共八篇&#xff0c;前文回顾&#xff1a; 前文回顾Percona Toolkit 神器全攻略Percona Toolkit 神器全攻略&#xff08;实用类&#xff09; 全文约定&#xff1a;$为命令提示符、gr…

变长的时间戳设计,第2版

以前的时间戳有32位&#xff0c;以秒为单位&#xff0c;231秒≈68年&#xff0c;从1970年开始&#xff0c;到2038年会出问题。 现在的时间戳有64位&#xff0c;表达范围仍然受限。 设计变长的时间戳&#xff0c;以32位为单元&#xff0c;最短有32位&#xff0c;最长有328256位…

UI(二)控件

文章目录 PatternLockProgressQRCodeRadioRatingRichTextScollBarSearchSelectSlideSpanStepper和StepperItemTextTextAreaTextClockTextInputTextPickerTextTimerTimePickerToggleWeb PatternLock PatternLock是图案密码锁组件&#xff0c;以九宫格图案的方式输入密码&#x…

vscode php dubug xdubg

前提&#xff1a;官网安装了PHP 一、检测是否有xdebug插件 二、如果没有插件&#xff0c;下载对应版本的插件&#xff0c;并且配置php 1、查看自己php版本&#xff1a; 2、xdebug下载地址&#xff1a; https://xdebug.org/download/historical 我下载的是箭头所示&#xff0…

【SHAP解释运用】基于python的树模型特征选择+随机森林回归预测+SHAP解释预测

1.导入必要的库 import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestRegressor from sklearn.tree import export…

对input输入框脱敏的实现(input输入时可回删、可粘贴)

目录 1.要解决的问题2.第一回合&#xff1a;substring replace3.第二回合&#xff1a;移魂大法4.第三回合&#xff1a;移花接木5.第四回合&#xff1a;万佛归宗 写在前面&#xff1a; 如有转载&#xff0c;务必注明出处&#xff0c;否则后果自负。 1.要解决的问题 继续与客户…

从云原生视角看 AI 原生应用架构的实践

本文核心观点&#xff1a; 基于大模型的 AI 原生应用将越来越多&#xff0c;容器和微服务为代表的云原生技术将加速渗透传统业务。API 是 AI 原生应用的一等公民&#xff0c;并引入了更多流量&#xff0c;催生企业新的生命力和想象空间。AI 原生应用对网关的需求超越了传统的路…

云计算运维工程师的突发状况处理

云计算运维工程师在应对突发的故障和紧急情况时,需要采取一系列迅速而有效的措施来最小化服务中断的时间并恢复系统的稳定性。 以下是一些关键步骤和策略: 快速响应: 立即识别并确认故障的性质和范围。通知团队成员和相关的利益相关者,确保所有人了解当前情况。故障诊断:…

Web Worker 学习及使用

了解什么是 Web Worker 提供了可以在后台线程中运行 js 的方法。可以不占用主线程&#xff0c;不干扰用户界面&#xff0c;可以用来执行复杂、耗时的任务。 在worker中运行的是另一个全局上下文&#xff0c;不能直接获取 Window 全局对象。不同的 worker 可以分为专用和共享&…

使用 Vue Router 的 meta 属性实现多种功能

在 Vue.js 中&#xff0c;Vue Router 提供了强大的路由管理功能。通过 meta 属性&#xff0c;我们可以在路由定义中添加自定义元数据&#xff0c;以实现访问控制、页面标题设置、角色权限管理、页面过渡效果等多种功能。本文将总结如何使用 meta 属性来实现这些常见的功能。 1.…

gici-open学习日记(7):GNSS图优化——RTK

gici-open学习日记——GNSS RTK图优化 前言初始化RTK的调用rearrangePhasesAndCodes双差构造formPhaserangeDDPair周跳探测cycleSlipDetectionSD添加参数块模糊度参数部分addSdAmbiguityParameterBlocks添加双差伪距残差addDdPseudorangeResidualBlocks添加双差相位残差addDdPh…

springcloud-gateway 路由加载流程

问题 Spring Cloud Gateway版本是2.2.9.RELEASE&#xff0c;原本项目中依赖服务自动发现来自动配置路由到微服务的&#xff0c;但是发现将spring.cloud.gateway.discovery.locator.enabledfalse 启动之后Gateway依然会将所有微服务自动注册到路由中&#xff0c;百思不得其解&a…

手把手从零开始搭建远程访问服务

远程访问服务工具——FRP frp 是一个能够实现内网穿透的高性能的反向代理应用&#xff0c;支持 TCP、UDP、HTTP、HTTPS 等多种协议。可以将内网服务以安全、便捷的方式通过具有公网的服务器来转发。 资源链接 根据自己服务型号和操作系统来选取对应的文件&#xff0c;不知道的…

汽车EDI: BMW EDI项目案例

宝马集团是全世界成功的汽车和摩托车制造商之一&#xff0c;旗下拥有BMW、MINI和Rolls-Royce三大品牌&#xff1b;同时提供汽车金融和高档出行服务。作为一家全球性公司&#xff0c;宝马集团在14个国家拥有31家生产和组装厂&#xff0c;销售网络遍及140多个国家和地区。 本文主…

mitt通信

一、mitt介绍 mitt是一款轻量级的组件通信插件(大小仅为200字节左右) 二、mitt安装 npm install --save mitt三、使用 1.在组件中使用 import mitt from mitt //创建mitt实例 const emitter mitt()// 监听事件 emitter.on(foo, e > console.log(foo, e) )// 通过通配符监…

09. Java ThreadLocal 的使用

1. 前言 本节内容主要是对 ThreadLocal 进行深入的讲解&#xff0c;具体内容点如下&#xff1a; 了解 ThreadLocal 的诞生&#xff0c;以及总体概括&#xff0c;是学习本节知识的基础&#xff1b;了解 ThreadLocal 的作用&#xff0c;从整体层面理解 ThreadLocal 的程序作用&…

VC++开发积累——vc++6.0中删除函数的方法,右键,Delete

目录 引出插曲&#xff1a;删除函数的方法多行注释的实现代码输入的自动提示搜索出来&#xff0c;标记和取消标记跳转到上一步的位置 ctrl TAB 总结其他规范和帮助文档创建第一个Qt程序对象树概念信号signal槽slot自定义信号和槽1.自定义信号2.自定义槽3.建立连接4.进行触发 自…

千呼新零售2.0-OCR拍照识别采购单

千呼新零售2.0系统是零售行业连锁店一体化收银系统&#xff0c;包括线下收银线上商城连锁店管理ERP管理商品管理供应商管理会员营销等功能为一体&#xff0c;线上线下数据全部打通。 适用于商超、便利店、水果、生鲜、母婴、服装、零食、百货、宠物、中医养生、大健康等连锁店…

Python 实现Excel转TXT,或TXT文本导入Excel

Excel是一种具有强大的数据处理和图表制作功能的电子表格文件&#xff0c;而TXT则是一种简单通用、易于编辑的纯文本文件。将Excel转换为TXT可以帮助我们将复杂的数据表格以文本的形式保存&#xff0c;方便其他程序读取和处理。而将TXT转换为Excel则可以将文本文件中的数据导入…