图像矫正技术用于修正因透视、镜头畸变等导致的几何失真,以及调整颜色偏差。常用方法包括透视变换、镜头畸变校正、白平衡调整和去噪滤波,广泛应用于摄影、文档扫描、医学成像等领域。
预先安装环境
本案例使用环境
- Python3.10.2
- win11
(Linux系统如果安装了python3也可以使用)
环境安装
前往python官网下载python3版本,官网Windows | Python.org
windows版本下载网址如下
选择任意一个版本(大于3.8版本)的64或者32bit版本:
下载python3后点击exe执行文件安装,安装界面如下:
注意勾选添加环境变量(Add Python to environment variable)不然还需要手动添加环境变量,添加python环境变量详见:python手动添加环境变量(超详细)
打开win系统下的终端Terminal并点击设置选择PowerShell7或者Command Prompt作为默认配置:
例如打开PS7,在python环境变量设置好后输入python会显示如下:
虚拟环境
为防止接下来安装的python依赖库和本体内部产生版本冲突首先创建一个虚拟环境
我们先创建一个文件夹IMG-Correction,然后在该文件夹内打开终端:
输入下列代码创建一个名字叫venv的虚拟环境:
python -m venv venv
输入下列代码进入这个创建的虚拟环境:
.\venv\Scripts\Activate.ps1
如果是linux系统创建并进入虚拟环境指令如下(二选一):
pip3 install virtualenv
virtualenv venv
source ./venv/bin/activate
sudo apt-get install python3-venv
python3 -m venv venv
source ./venv/bin/activate
在win系统下进入虚拟环境后显示如下:
Rembg库
安装rembg库,默认使用cpu,如果具有NVIDIA显卡并且安装了某个版本的CUDA那么可以按照gpu版本(注意要和CUDA版本适配)并利用CUDA加速推理
此处由于处理的不是视频不需要快速推理因此使用CPU版本
pip install rembg
利用下列指令检测是否安装成功:
rembg i .\book.jpg testout.jpg
一般会发现缺少一部分库,例如ModuleNotFoundError: No module named 'click'那么我们就安装click,如果还有其他那么就同样pip install “对应的缺失依赖库”
本案例发现安装缺失的库如下,安装指令:
pip install click filetype watchdog aiohttp gradio asyncer
在此输入检测指令就能够成功运行,图片如下:
OpenCV库
OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和机器学习软件库。支持多种编程语言(如 C++、Python、Java 等),并且可以在多个平台(如 Windows、Linux、macOS、Android 等)上运行。
- 图像处理:包括图像过滤、变换、几何操作、直方图处理等。
- 特征检测:如边缘检测、角点检测、SIFT、SURF 等特征提取方法。
- 对象识别:用于识别和分类图像中的物体,如人脸检测、车辆识别等。
- 视频分析:包括运动检测、对象跟踪、背景减除等。
- 机器学习:提供了多种常用的机器学习算法,可以应用于图像分类、目标检测等任务。
pip install opencv-python
在python窗口输入下列指令检测是否安装成功:
import cv2
如下图所示表示安装成功
编写代码
任意四边物体的图像矫正思路如下:
- 首先是要将四边物体分割出来,利用rembg库自带的U2Net模型自动对图片当中主体部分进行语义分割,分割后除了主体的背景部分直接清除变为透明背景并将图片导出;
- 由于分割的物体可能会有部分区域和透明部分接近影响角点的搜寻,给背景添加一层和主色明显区分的纯色并导出;
- 利用opencv的Harris角点直接找出图像当中所有和图像邻域灰度变化大的点然后利用城区距离将最边缘的四角点找出;
- 将找到的角点和原始图像四点进行OpenCV的图像透视变换。
import cv2
import numpy as np
from rembg import remove
from collections import Counter
from PIL import Image
import io # 导入io模块
def remove_background(image_path): #利用rembg去除背景
with open(image_path, "rb") as f:
image_data = f.read()
output = remove(image_data)
image = Image.open(io.BytesIO(output)).convert("RGB")
image = np.array(image)
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) # 转换为BGR格式
return image
def get_dominant_color(image, k=4): #计算主体的颜色
pixels = np.float32(image.reshape(-1, 3))
n_colors = k
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.2)
flags = cv2.KMEANS_RANDOM_CENTERS
_, labels, palette = cv2.kmeans(pixels, n_colors, None, criteria, 10, flags)
_, counts = np.unique(labels, return_counts=True)
dominant_color = palette[np.argmax(counts)]
return dominant_color
def create_solid_background(image, dominant_color): #创建基于反主体颜色的纯色背景
complementary_color = 255 - dominant_color
h, w, _ = image.shape
background = np.full((h, w, 3), complementary_color, dtype=np.uint8)
return background
def combine_image_and_background(image, background): #拼合纯色背景和分割主体
mask = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 注意这里的格式是BGR
_, mask = cv2.threshold(mask, 1, 255, cv2.THRESH_BINARY)
mask_inv = cv2.bitwise_not(mask)
image_fg = cv2.bitwise_and(image, image, mask=mask)
background_bg = cv2.bitwise_and(background, background, mask=mask_inv)
combined_image = cv2.add(image_fg, background_bg)
return combined_image
def preprocess_image(image_path, output_path): # 主运行函数
image = remove_background(image_path)
dominant_color = get_dominant_color(image)
background = create_solid_background(image, dominant_color)
combined_image = combine_image_and_background(image, background)
cv2.imwrite(output_path, combined_image)
print(f"Preprocessed image saved to {output_path}")
函数解释如下:
首先是remove_background函数,该函数利用rembg自动下载的u2net.onnx模型自动分割出图像主体内容,该函数首先打开图像文件并读取为字节流;然后使用 rembg
库去除背景,再将去除背景后的图像字节流转换为 PIL
图像对象,并将其转换为 RGB 模式。之后将 PIL
图像对象转换为 NumPy 数组,随后转换为 OpenCV 使用的 BGR 色彩格式并返回处理后的图像;
再是主色选取函数get_dominant_color,该函数首先将图像的每个像素展平为一维数组并转换为浮点数,再使用 K-means 聚类算法将像素分为 k
个颜色簇,聚类算法的结果包括每个簇的中心(即颜色调色板)然后计算每个颜色簇的像素数量,选择出现最多的颜色作为主色并返回;
create_solid_background函数利用get_dominant_color返回的主色创建一个基于该主色反的纯色矩形图片背景,其中主要部分是计算主色的互补色(即与主色对比度最高的颜色);
之后是combine_image_and_background,该函数将图像转换为灰度图像,生成一个二值化的掩膜(前景与背景分离),然后创建前景掩膜和背景掩膜的反转版本。使用掩膜提取前景部分,并将其与背景的相应部分合并,最后将前景和背景合并成最终图像。
将上述代码设置为PreProcess.py作为预处理函数放在tools文件夹内,然后编写主函数,需要导入下列库:
import cv2
import numpy as np
import tkinter as tk
from PIL import Image, ImageTk
from tools.PreProcess import preprocess_image
编写一个寻找四角点的代码,代码主要逻辑是先获取图像的长宽,然后从所有角点的坐标corner_array当中按顺序寻找和这四个图像角落点当中城区距离最短的四个点,总共四个循环每次找图像矩形四角当中某一个的最短城区距离并且每次找到一个更小的会做一次记录直到找到最小的距离,然后将这个点的xy以及距离信息储存到变量当中并返回
def find_connorPoints(corner_array):
h,w = get_size(image_path)
print(h,w)
leng = len(corner_array)
print(leng)
minds = 1000000000000000
mina = 1000000000000000
minb = 1000000000000000
py = [0,w,w,0]
px = [0,0,h,h]
ansx=[0,0,0,0]
ansy=[0,0,0,0]
for k in range(4):
for j in range(leng):
#print(str(corner_array[j]))
#print(len(str(corner_array[i])))
for i in range(len(str(corner_array[j]))):
if (str(corner_array[j])[i:i+1]==' '):
index = i
a = int(str(corner_array[j])[1:index])
b = int(str(corner_array[j])[index:len(str(corner_array[j]))-1])
#print(str(a)+'-'+str(b))
dis = abs(a-px[k]) + abs(b-py[k])
#print(dis)
if dis < minds:
minds = dis
mina = a
ansx[k] = a
minb = b
ansy[k] = b
print(str(mina)+'&'+str(minb))
minds = 1000000000000000
mina = 1000000000000000
minb = 1000000000000000
return ansx,ansy
编写一个透视变换的函数,输入寻找的四角点信息和图片将图像通过透视变换矫正:
def perspective_transform(image, a1, b1, a2, b2, a3, b3, a4, b4):
src_points = np.float32([[a1, b1], [a2, b2], [a3, b3], [a4, b4]])
dst_points = np.float32([[0, 0], [image.shape[1], 0], [image.shape[1], image.shape[0]], [0, image.shape[0]]])
M = cv2.getPerspectiveTransform(src_points, dst_points)
result = cv2.warpPerspective(image, M, (image.shape[1], image.shape[0]))
cv2.imshow('Original Image', image)
cv2.imshow('Corrected Image', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
编写一个主函数先化为灰度图处理然后找到所有角点坐标并将数据给find_connorPoints找出最靠近边缘的四角点然后让perspective_transform函数利用透视变换矫正图片:
def save_harris_corners(image_path):
image = cv2.imread(image_path)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
corners = cv2.cornerHarris(gray, 2, 3, 0.04)
image_with_corners = image.copy()
image_with_corners[corners > 0.01 * corners.max()] = [0, 0, 255] # 将角点标记为红色
corner_coordinates = np.column_stack(np.where(corners > 0.01 * corners.max()))
corner_array = np.array(corner_coordinates)
print("All Corner Coordinates:")
xx,yy=find_connorPoints(corner_array)
perspective_transform(image, yy[0], xx[0],yy[1], xx[1], yy[2], xx[2], yy[3], xx[3])
""" cv2.imshow('Harris Corners', image_with_corners)
cv2.waitKey(0)
cv2.destroyAllWindows() """
img = Image.fromarray(image_with_corners)
imgtk = ImageTk.PhotoImage(image=img)
lmain.imgtk = imgtk
lmain.configure(image=imgtk)
return corner_array
将上述函数组合为主运行函数并命名为:“Opencv-IMGCorrection.py”
演示结果
在终端输入:
python .\Opencv-IMGCorrection.py
原图和结果图对比:
其余矫正案例:
如果报错显示:“ModuleNotFoundError: No module named 'tools.PreProcess'”
在tools目录下创建一个__init__.py即可
简单总结
- 案例主要是将任意包含矩形文件图片去除背景再找角点并透视变换的矫正过程;
- 不同于传统机器视觉算法,本案例在预处理部分并没有采用滤波、霍夫、链码等手段分割出书本,而是利用神经网络预训练的模型直接去除背景。该方案优点是鲁棒性高,对背景和光照环境要求低,但是缺点是会对图像本应该矫正的内部的内容产生一定的破坏,因此需要根据所需自行选取方案;
- 利用python库的简单算法,供大家参考。