利用OpenCV对图像倾斜矩形目标区域进行固定大小裁剪
文章目录
- 目的
- 效果展示
- 为什么要固定大小裁剪?
- 代码及解释
- 原始文件
- 代码
- 代码解释
- ① 主程序
- ② ReadTxt() 函数
- ③ rotate() 函数
目的
这篇博客主要介绍如何使用 OpenCV 根据已有的像素点定位坐标文件集在 png 图像集上裁剪大小固定的、倾斜的腰椎间盘。
效果展示
原腰椎间盘 png 图像集文件夹如下所示:
文件夹中包括多张 png 图像以及与之同名的 txt 文件,我们要做的就是利用 txt 文件裁剪与之同名的 png 图像,并且按照一定的裁剪顺序重新命名裁剪出的图像,经程序处理后的 png 图像集如下所示:
为什么要固定大小裁剪?
我们在将椎间盘区域进行裁剪之后,后续过程通常是利用裁剪的图像进行椎间盘病变程度分类,而椎间盘高度属于椎间盘类别的一个重要的判别特征,如果我们将椎间盘裁剪成不同的大小,在后续将图像送入神经网络时需要缩放成等大小,不同大小的椎间盘图像会导致缩放程度不同,从而导致椎间盘高度这一特征失效。因此,我们需要在裁剪时就将图像裁剪成等高等宽的固定大小。
代码及解释
原始文件
原始文件为多张 png 图像和与之对应的同名像素点坐标 txt 文件。单张 png 图像示例如下所示:
单个像素点坐标 txt 文件的内容如下所示:
251,123
247,146
247,172
240,195
236,221
236,244
236,270
235,294
232,317
232,341
236,363
在 txt 文件中,除最后一行为空行外,其余的11行每一行代表一个图像中的像素位置。每一行为用逗号分隔开的两个数值,第一个数值为 x 轴坐标,第二个数值为 y 轴坐标。在图像中,坐标原点为图像的左上角,x 轴正方向水平向右,y 轴正方向竖直向下。如下图所示,这里为了更清楚地表达,将坐标原点稍微偏离了原位置(原位置在图像左上角)。
txt 文件中从上到下所表示的11个点,分别与原图像中用白点标注出来的从上到下的11个位置相对应。可以发现11个位置中,从第1个位置开始,每隔一个点便标注了一个椎间盘中心,椎间盘与椎间盘之间的坐标代表椎体中心的位置。其实在真正处理图像的时候,我们只需要知道 txt 文件中的11个点所代表的是什么即可,无需在原图中标注出这些点,因为标注出这些点可能会影响后续的流程处理。
代码
在了解了原文件的内容之后,先给出图像处理的代码再详细解释:
import cv2
import numpy as np
from math import *
import math
import tensorflow as tf'''旋转图像并剪裁'''
def rotate(img, i, eleven_xy, newImagePath):if i == 0:returnpoint_up = eleven_xy[i-1]if i == 10:point_down = [0, 0]point_down[0] = int(2*eleven_xy[i][0] - eleven_xy[i-1][0])point_down[1] = int(2*eleven_xy[i][1] - eleven_xy[i-1][1])else:point_down = eleven_xy[i+1]# 矩形框的高度和宽度heightRect = "高度值"widthRect = "宽度值"# 图片旋转角度angle = -math.atan((point_up[0] - point_down[0]) / (point_up[1] - point_down[1] + 1e-6)) * (180 / math.pi)# 原始图像高度和宽度height = img.shape[0]width = img.shape[1]# 按angle角度以椎间盘为中心来旋转图像rotateMat = cv2.getRotationMatrix2D(tuple(eleven_xy[i]), angle, 1) heightNew = int(width * fabs(sin(radians(angle))) + height * fabs(cos(radians(angle))))widthNew = int(height * fabs(sin(radians(angle))) + width * fabs(cos(radians(angle))))rotateMat[0, 2] += (widthNew - width) / 2rotateMat[1, 2] += (heightNew - height) / 2imgRotation = cv2.warpAffine(img, rotateMat, (widthNew, heightNew), borderValue=(255, 255, 255))# 旋转后图像的四点坐标:左下、右下、右上、左上pt1 = [0, 0]pt2 = [0, 0]pt3 = [0, 0]pt4 = [0, 0][[pt4[0]], [pt4[1]]] = np.dot(rotateMat, np.array([[point_up[0]], [point_up[1]], [1]]))[[pt1[0]], [pt1[1]]] = np.dot(rotateMat, np.array([[point_down[0]], [point_down[1]], [1]]))new_center = [0, 0]new_center[0] = int((pt1[0] + pt4[0])/2)new_center[1] = int((pt1[1] + pt4[1])/2)pt1 = [0, 0]pt4 = [0, 0]pt1[0], pt1[1] = new_center[0] - widthRect//2, new_center[1] + heightRect//2pt4[0], pt4[1] = new_center[0] - widthRect//2, new_center[1] - heightRect//2pt2[0], pt2[1] = pt1[0] + widthRect, pt1[1]pt3[0], pt3[1] = pt4[0] + widthRect, pt4[1]imgOut = imgRotation[int(pt4[1]):int(pt1[1]), int(pt1[0]):int(pt2[0])]cv2.imwrite(newImagePath, imgOut) # 裁减得到的旋转矩形框# 读出文件中的坐标值
def ReadTxt(img_dir, txt_dir, img_result_dir):getTxt = open(txt_dir, encoding = "utf8") # 读取txt文件lines = getTxt.readlines()getTxt.close()eleven_xy = []for xy in lines:xy = xy.strip()if not xy:continuepoint_xy = [eval(i) for i in xy.split(",")]eleven_xy.append(point_xy)imgSrc = cv2.imread(img_dir)for i in range(0, 11, 2):new_img = img_result_dir + img_dir.split("\\")[-1][:-4] + "-" + str(6-i//2) + ".jpg" # 截取的文件保存在哪rotate(imgSrc, i, eleven_xy, new_img)if __name__=="__main__":all_img = tf.data.Dataset.list_files("png图像文件夹路径")for img_name in all_img:img_name = str(img_name.numpy(), encoding = "utf8") # 图像文件路径txt_name = img_name.replace("png", "txt") # 文本文件路径img_result_dir = "截取结果存放文件目录"ReadTxt(img_name, txt_name, img_result_dir)
代码解释
① 主程序
if __name__=="__main__":all_img = tf.data.Dataset.list_files("png图像文件夹路径")for img_name in all_img:img_name = str(img_name.numpy(), encoding = "utf8") # 图像文件路径txt_name = img_name.replace("png", "txt") # 文本文件路径img_result_dir = "截取结果存放文件目录"ReadTxt(img_name, txt_name, img_result_dir)
在主程序中,我们首先利用 tensorflow 库函数列出了存放 png 图像集文件夹下的所有 png 图像的路径+名称,将结果存放在列表 all_img 里,对其中的元素举例说明:tf.Tensor(b'路径名称\\图像名称.png', shape=(), dtype=string)
。这样的形式可以避免后期需要再组合路径和图像名称,但是目前的元素类型是字节形式,而不是字符串形式,如果直接用该元素来读图像或者文本文件是无法正确识别路径的,我们需要将元素转换为字符串类型。
遍历列表 all_img 里的每一个元素,并对每一个元素执行以下操作:
- 将字节形式的路径名称转换为字符串类型并存在 img_name 中;
- 将 img_name 中的 png 后缀改成 txt 就可以得到对应的定位坐标文件路径名称 txt_name;
- 设置存放结果图像集的文件夹目录 img_result_dir;
- 将以上三个变量作为参数传给 ReadTxt() 函数。
② ReadTxt() 函数
def ReadTxt(img_dir, txt_dir, img_result_dir):getTxt = open(txt_dir, encoding = "utf8") # 读取txt文件lines = getTxt.readlines()getTxt.close()eleven_xy = []for xy in lines:xy = xy.strip()if not xy:continuepoint_xy = [eval(i) for i in xy.split(",")]eleven_xy.append(point_xy)imgSrc = cv2.imread(img_dir)for i in range(0, 11, 2):new_img = img_result_dir + img_dir.split("\\")[-1][:-4] + "-" + str(6-i//2) + ".jpg" # 截取的文件保存在哪rotate(imgSrc, i, eleven_xy, new_img)
ReadTxt() 函数首先将 txt_dir 所表示的像素点坐标 txt 文件读取到列表 eleven_xy 中,列表 eleven_xy 的形式为[[x1, y1], [x2, y2], [x3, y3], ... , [x12, y12]]
,然后用 OpenCV 的 imread() 函数读取 img_dir 对应的 png 图像得到其数组形式 imgSrc 。接下来逐渐遍历列表 eleven_xy 中由上到下对应的6个椎间盘定位坐标,并以遍历顺序来命名来裁剪得到的椎间盘图像 new_img 。将数组变量 imgSrc 、当前遍历的椎间盘坐标索引 i,列表 eleven_xy 和 new_img 作为参数传给 rotate() 函数。
③ rotate() 函数
def rotate(img, i, eleven_xy, newImagePath):if i == 0:returnpoint_up = eleven_xy[i-1]if i == 10:point_down = [0, 0]point_down[0] = int(2*eleven_xy[i][0] - eleven_xy[i-1][0])point_down[1] = int(2*eleven_xy[i][1] - eleven_xy[i-1][1])else:point_down = eleven_xy[i+1]# 矩形框的高度和宽度heightRect = "高度值"widthRect = "宽度值"# 图片旋转角度angle = -math.atan((point_up[0] - point_down[0]) / (point_up[1] - point_down[1] + 1e-6)) * (180 / math.pi)# 原始图像高度和宽度height = img.shape[0]width = img.shape[1]# 按angle角度以椎间盘为中心来旋转图像rotateMat = cv2.getRotationMatrix2D(tuple(eleven_xy[i]), angle, 1) heightNew = int(width * fabs(sin(radians(angle))) + height * fabs(cos(radians(angle))))widthNew = int(height * fabs(sin(radians(angle))) + width * fabs(cos(radians(angle))))rotateMat[0, 2] += (widthNew - width) / 2rotateMat[1, 2] += (heightNew - height) / 2imgRotation = cv2.warpAffine(img, rotateMat, (widthNew, heightNew), borderValue=(255, 255, 255))# 旋转后图像的四点坐标:左下、右下、右上、左上pt1 = [0, 0]pt2 = [0, 0]pt3 = [0, 0]pt4 = [0, 0][[pt4[0]], [pt4[1]]] = np.dot(rotateMat, np.array([[point_up[0]], [point_up[1]], [1]]))[[pt1[0]], [pt1[1]]] = np.dot(rotateMat, np.array([[point_down[0]], [point_down[1]], [1]]))new_center = [0, 0]new_center[0] = int((pt1[0] + pt4[0])/2)new_center[1] = int((pt1[1] + pt4[1])/2)pt1 = [0, 0]pt4 = [0, 0]pt1[0], pt1[1] = new_center[0] - widthRect//2, new_center[1] + heightRect//2pt4[0], pt4[1] = new_center[0] - widthRect//2, new_center[1] - heightRect//2pt2[0], pt2[1] = pt1[0] + widthRect, pt1[1]pt3[0], pt3[1] = pt4[0] + widthRect, pt4[1]imgOut = imgRotation[int(pt4[1]):int(pt1[1]), int(pt1[0]):int(pt2[0])]cv2.imwrite(newImagePath, imgOut) # 裁减得到的旋转矩形框
通过网上查询,我发现大部分的 OpenCV 教程只能在图像中裁剪方方正正的矩形区域,而不能裁剪斜矩形区域。但是我们的椎间盘目标区域的边框通常并不与图像边框平行。这里我的思路是:我们可以将图像以当前要切割的椎间盘中心为旋转中心,以相邻椎体中心连线与 y 轴的角度的相反值作为旋转角度,进而来旋转图像,旋转后的图像可以将当前椎间盘摆正,就可以利用 OpenCV 的函数进行图像裁剪了。
旋转角度为什么是相邻椎体中心连线与 y 轴的角度的相反值呢?因为相邻椎体中心连线与 y 轴的角度计算的是矩形框要旋转的角度,而我们现在不旋转矩形框而是图像,因此角度应该取计算得到的相反值。
裁剪某一椎间盘的流程大致如下:
根据椎间盘位置索引来得到椎间盘相邻椎体中心坐标。
针对我的个人项目来说,后续处理并不需要最上方定位到的椎间盘,因此在 rotate() 函数中,如果定位到的是最上方的椎间盘,直接返回,不做任何处理;而如果定位到的是最下方的椎间盘,需要根据均值规则求出下椎体中心坐标。
初始化要裁剪的目标区域的高度 heightRect 和宽度 widthRect ,并根据上下椎体中心坐标计算图片旋转角度 angle,计算原图片的高度 height 和宽度 width。
调用 OpenCV 里的 getRotationMatrix2D() 函数来获得旋转矩阵,该函数传入三个参数,分别是旋转中心,旋转角度,第三个参数 1 表示进行等比列的缩放,最终函数返回一个旋转矩阵 rotateMat。
图像的旋转矩阵一般为:
但是单纯的这个矩阵是在原点处进行变换的,为了能够在任意位置进行旋转变换,OpenCV 采用了另一种方式:
以上就是 rotateMat 的样式。根据旋转角度计算出旋转后的图片的新高度 heightNew 和宽度 widthNew,并利用原始的和新的图像高度和宽度来更新旋转矩阵 rotateMat 的值。
调用 OpenCV 里的 warpAffine() 仿射变函数来旋转图像,该函数传入四个参数,分别是原图像(数组形式)、旋转矩阵 rotateMat、 输出图像的大小 (widthNew, heightNew) 和边界填充值 borderValue,最终函数返回一个裁剪后的图像数组形式。
将原来的椎体上下中心坐标值与旋转矩阵相乘可以得到旋转后的上下椎体中心坐标,根据旋转后的上下椎体中心坐标可以得到旋转后的椎间盘中心坐标 new_center;
根据 new_center 以及初始设定的要裁剪的目标区域的高度 heightRect 和宽度 widthRect 来计算椎间盘矩形框的四个顶点坐标,根据顶点坐标来裁剪原图像数组,并调用 OpenCV 里的 imwrite 函数将裁剪后的结果写入保存在新路径 newImagePath 里。
利用OpenCV对图像倾斜矩形目标区域进行固定大小裁剪相关推荐
- 【利用OpenCV-Python在图像中选择ROI区域并提取ROI坐标信息】
[利用OpenCV-Python在图像中选择ROI区域并提取ROI坐标信息] 文前白话 代码: 效果: 文前白话 在图像中选定ROI区域进行进一步的功能开发,就需要提取到相应的ROI区域坐标,对于多边 ...
- 利用opencv对图像进行长曝光
利用opencv对图像进行长曝光 本文首发于公众号[Opencv视觉实践],翻译自光头哥哥的博客: [Long exposure with OpenCV and Python],仅做学习分享. 原文链 ...
- android代码查找图像,Android平台上利用opencv进行图像的边沿检测
原标题:Android平台上利用opencv进行图像的边沿检测 近开始接触opencv for Android,从网上down了图像的边沿检测的代码. 测试图片: 在Android2.3.1模拟器上跑 ...
- 利用OpenCV实现图像修复(含源码链接)
点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 前一段时间小白分享过关于图像修复技术介绍的推文(点击可以跳转),有 ...
- 利用OpenCV检测图像中的多个水果
OpenCV检测图像中的多个水果 最近面试碰到一个图像算法题,要求: (1)检测一副图像中的多个苹果并标识出来. (2)标识时需要将图像中苹果按照从大到小给定序号,显示出来. 基于以上两点,准备利用C ...
- 利用opencv实现图像马赛克处理的三种方法
前言:本文就图像的马赛克处理,基于opencv提出了三种解决方案,并详细地介绍了三种方法的原理.示例.问题及问题的解决方案. 方法一 原理介绍:利用resize()将图片先缩小,再放大 代码示例: i ...
- 利用OpenCV提取图像中的矩形区域(PPT屏幕等)
** 前言 ** 最近参加了大创项目,题目涉及到计算机视觉,学姐发了个修正图像的博客链接,于是打算用这个题目入门OpenCV. 分析问题 照片中的PPT区域总是沿着x,y,z三个轴都有倾斜(如下图), ...
- OpenCV截取图像的任意形状区域,规则的图形(圆、椭圆、矩形),不规则鼠标自己选择
主要代码: typedef enum _tagType{ IMAGE_RECT=0, IMAGE_CIRCLE, IMAGE_ELLIPSE, IMAGE_ANY_REGION }REGIONTYPE ...
- 利用OpenCV进行图像的轮廓检测
简 介: 本文对于OpenCV中的轮廓检测算法进行了讨论,可以看到一些基于轮廓检测的应用.接着对四种不同的提取方式的结果进行了讨论.你还了解了如何将轮廓进行绘制的方法. 关键词: 轮廓检测,二值化 § ...
最新文章
- Hiding Images in Plain Sight: Deep Steganography 于众目睽睽之下隐藏图像:深度隐写术
- 第二百二十六天 how can I 坚持
- 借助 Evolution Linux 的帮助来轻松安装 Arch Linux
- 经典html5网页,HTML5网页设计经典
- Qt:QSound无法播放.wav声音的解决办法
- 《吴恩达深度学习》第一课第四周任意层的神经网络实现及BUG处理
- CSS按钮动画(五)
- NX/UG二次开发—装配—抑制的组件怎么获取原型
- 《穿越计算机的迷雾》读书笔记五
- 详解正向代理与反向代理
- JavaScript的API文档自动生成工具jsdoc
- uni-app项目打包成apk(本地打包篇)
- sql转置两种实现方式记录
- 开美容院是否要选择加盟?
- Excel从手机号和座机号混合文本中提取手机号码
- wps里表格中间的字怎么置顶_怎么把wps文字放在最中间
- JQuery序列化和反序列化
- Wilson定理证明
- 奥付云荣获2017年度企业级创新应用 Top 50
- Elasticsearch Java Client创建索引