足球视频位置与平面坐标的转换

news2025/3/12 23:23:55

依赖: C# OpenCVSharp WPF Numpy

目的:解决足球场上,人物投影到二维平面的位置坐标

在这里插入图片描述

图A / B/ C

一、基础概念

1.1标准球场的定义:

参考:https://zh.m.wikipedia.org/zh/%E8%B6%B3%E7%90%83%E5%A0%B4

在这里插入图片描述

图 D

1.2 配准思路

图A->图B,建立坐标关系对;

图B->图D,建立真实坐标系的配准基准点对(需要4个以上)

图D->图C,建立显示坐标系的配准基准点对(图C尺寸已知,所有关键位置A-J均已知)

对于全局,利用OpenCV FindHomography,求得D-C坐标转换矩阵T;

对于每帧图片,利用OpenCV FindHomography,求得B-D坐标转换矩阵T1;

将对象检测的球员投影到图3

二、代码实现

2.1 标画关键点

WPF界面

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="0.4*"/>
            <ColumnDefinition Width="0.6*"/>
        </Grid.ColumnDefinitions>
        <Grid x:Name="gridPointSelector" Width="350" Height="234" VerticalAlignment="Center" HorizontalAlignment="Center" >
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="0.15619*"/>
                <ColumnDefinition Width="0.34762*"/>
                <ColumnDefinition Width="0.34762*"/>
                <ColumnDefinition Width="0.15619*"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="0.20441*" />
                <RowDefinition Height="0.59118*"/>
                <RowDefinition Height="0.20441*"/>
            </Grid.RowDefinitions>
            <Image Source="{StaticResource 2D_field}" Grid.ColumnSpan="4" Grid.RowSpan="3" />
            <ToggleButton Content="A" Grid.Row="0" Grid.Column="0"  
                          HorizontalAlignment="Left" VerticalAlignment="Top"  Margin="-15,-15,0,0" Click="ToggleButton_Click"/>
            <ToggleButton Content="B" Grid.Row="0" Grid.Column="1" 
                          HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,-15,-15,0"  Click="ToggleButton_Click"/>
            <ToggleButton Content="C" Grid.Row="0" Grid.Column="3" 
                          HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,-15,-15,0"  Click="ToggleButton_Click"/>
            <ToggleButton Content="D" Grid.Row="1" Grid.Column="0" 
                          HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,-15,-15,0"  Click="ToggleButton_Click"/>
            <ToggleButton Content="E" Grid.Row="1" Grid.Column="2" 
                          HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,-15,-15,0"  Click="ToggleButton_Click"/>
            <ToggleButton Content="F" Grid.Row="2" Grid.Column="0" 
                          HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,-15,-15,0"  Click="ToggleButton_Click"/>
            <ToggleButton Content="G" Grid.Row="2" Grid.Column="2" 
                          HorizontalAlignment="Right" VerticalAlignment="Top"   Click="ToggleButton_Click"/>
            <ToggleButton Content="H" Grid.Row="2" Grid.Column="0"
                          HorizontalAlignment="Left" VerticalAlignment="Bottom"    Click="ToggleButton_Click" />
            <ToggleButton Content="I" Grid.Row="2" Grid.Column="1" 
                          HorizontalAlignment="Right" VerticalAlignment="Bottom"  Click="ToggleButton_Click"/>
            <ToggleButton Content="J" Grid.Row="2" Grid.Column="3" 
                          HorizontalAlignment="Right" VerticalAlignment="Bottom"  Click="ToggleButton_Click"/>
        </Grid>
        <Grid Grid.Column="1" x:Name="gridMain" ClipToBounds="True">
            <Image x:Name="imgMain" Source="{Binding OriginalImage }" Stretch="Uniform"/>
            <Canvas x:Name="canvas" Height="{Binding ElementName=imgMain,Path=ActualHeight}"
                    Width="{Binding ElementName=imgMain,Path=ActualWidth}" Background="Transparent"
                    IsEnabled="{Binding IsSelectting}" MouseLeftButtonUp="canvas_MouseLeftButtonUp">
            </Canvas>
        </Grid>
    </Grid>

数据结构

public class VedioPointMark
{
        public string? Name { get; set; }
        /// <summary>
        /// 球场坐标
        /// </summary>
        public Point2d FiledPoint { get; set; }
        /// <summary>
        /// 视频像素坐标
        /// </summary>
        public Point2d VideoPixelPoint { get; set; }
    }

WPF后端代码

 public partial class UCPointMark : UserControl
    {
        MPointMark model;
        private readonly int ellipseSize = 16;
        ToggleButton? selectButton;
        public UCPointMark()
        {
            InitializeComponent();
            model = new MPointMark();
            this.DataContext = model;
        }

        private Border? DrawPoint()
        {
            if (selectButton == null)
                return null;
            Border myBorder = new Border();
            myBorder.CornerRadius = new CornerRadius(ellipseSize);
            myBorder.Width = ellipseSize;
            myBorder.BorderBrush = new SolidColorBrush(Colors.Blue);
            myBorder.Background = new SolidColorBrush(Colors.Red);
            myBorder.Child = new TextBlock() { 
                Text = selectButton.Content.ToString(),
                Foreground = new SolidColorBrush(Colors.White),
                TextAlignment = TextAlignment.Center};
            selectButton.Tag = myBorder;
            return myBorder;
        }

        public List<VedioPointMark> CalculateTransform() 
        {
            var markPoints = new List<VedioPointMark>();
            //必须选择4各以上
            if (canvas.Children.Count < 4)
                return markPoints;
            var allPoints = VideoFieldTransform.CreatePointMarks();
            var axisX = (double)model.OriginalImageSize.Width / this.imgMain.ActualWidth;
            var axisY = (double)model.OriginalImageSize.Height / this.imgMain.ActualHeight;
            foreach (var element in this.gridPointSelector.Children)
            {
                if (element is ToggleButton toggle && toggle.Tag is Border pixcelBorder
                    && pixcelBorder.Tag is Point pixcelPoint && toggle.IsChecked == true)
                {
                    var mark = allPoints.Where(p => p.Name?.Equals(toggle.Content.ToString()) == true).FirstOrDefault();
                    if (mark == null)
                        continue;
                    mark.VideoPixelPoint = new OpenCvSharp.Point2d( pixcelPoint.X * axisX, pixcelPoint.Y * axisY -40 );
                    markPoints.Add(mark);
                }
            }
            return markPoints;
        }

        private void canvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            try
            {
                var clickPoint = e.GetPosition(this.canvas);
                var ellipse = DrawPoint();
                if (null == ellipse) return;
                this.canvas.Children.Add(ellipse);
                Canvas.SetLeft(ellipse, clickPoint.X - ellipseSize / 2);
                Canvas.SetTop(ellipse, clickPoint.Y - ellipseSize / 2);
                ellipse.Tag = new Point(clickPoint.X, clickPoint.Y);
                model.IsSelectting = false;
            }
            catch (Exception ex)
            {
                this.Log(ex);
            }
        }

2.2 配准转换类

    public class VideoFieldTransform
    {
       private readonly Point2d[] point1;  //视频可视范围内的关键点坐标(4个以上)
       private readonly Point2d[] point2;  //真实球场坐标系 或 平面坐标系
       private readonly Mat H; //变换矩阵

        public VideoFieldTransform(List<VedioPointMark> pointMarks,bool haspoint2 = false)
        {
            if (!haspoint2)
            {
                //视频坐标投影到物理坐标
                pointMarks = pointMarks.OrderBy(p => p.Name).ToList();
                point1 = pointMarks.Select(p => p.FiledPoint).ToArray();
                point2 = pointMarks.Select(p => p.VideoPixelPoint).ToArray();
            }
            else
            {
                //物理坐标投影到图片
                point1 = new Point2d[] {
                                 new Point2d(0, 0),
                                  new Point2d(16.4f, 13.9f),
                                  new Point2d(52.5f, 0.0f),
                                  new Point2d(88.6f, 13.9f ),
                                  new Point2d(105f, 0.0f),
                                  new Point2d(105f, 68.0f),
                                  new Point2d(52.5f, 68f),
                                  new Point2d(0, 68f)
                              };
                point2 = new Point2d[] {
                                  new Point2d(0.0f, 0.0f ),
                                  new Point2d(164f, 152f ),
                                  new Point2d( 525f, 0.0f),
                                  new Point2d(886f, 152f),
                                  new Point2d(1050f, 0f),
                                  new Point2d(1050f, 699f),
                                   new Point2d(525f, 699f),
                                    new Point2d(0f, 699f),
                              };
            }
            H = CalculateHomoGraphy(haspoint2);
        }


        public static List<VedioPointMark> CreatePointMarks()
        {
            return new List<VedioPointMark>()
            {
                new VedioPointMark() {  Name = "A", FiledPoint = new Point2d(0, 0)},
                new VedioPointMark() {  Name = "D", FiledPoint = new Point2d(16.4f, 13.9f)},
                new VedioPointMark() {  Name = "B", FiledPoint = new Point2d(52.5f, 0.0f)},
                new VedioPointMark() {  Name = "E", FiledPoint = new Point2d(88.6f, 13.9f)},
                new VedioPointMark() {  Name = "C", FiledPoint = new Point2d(105f, 0.0f)},
                new VedioPointMark() {  Name = "J", FiledPoint = new Point2d(105f, 68.0f)},
                new VedioPointMark() {  Name = "I", FiledPoint = new Point2d(52.5f, 68f)},
                new VedioPointMark() {  Name = "H", FiledPoint = new Point2d(0, 68f)},
                new VedioPointMark() {  Name = "F", FiledPoint = new Point2d(16.4f, 40.2f)},
                new VedioPointMark() {  Name = "G", FiledPoint = new Point2d(88.6, 40.2f)},
             };
        }

        /// <summary>
        ///     视频帧(假定摄像头位置不变)与 平面模式的球场位置的坐标系换算,求得矩阵
        /// </summary>
        /// <param name="make_rotate"></param>
        /// <returns>返回H 为变换矩阵</returns>
        private Mat CalculateHomoGraphy(bool make_rotate = false)
        {
            //var k = InputArray.Create(point2.GetData<float[]>());
            if (!make_rotate)
                return Cv2.FindHomography(point2, point1, HomographyMethods.Ransac);
            return Cv2.FindHomography(point1,point2, HomographyMethods.Ransac);
        }

        /// <summary>
        /// 根据坐标点(X,Y) 与 坐标系变换矩阵乘积,换算帧图像的位置到平面球场坐标的位置
        /// </summary>
        public Point Transform(Point p)
        {
            var img2Bounds = new[]
            {
                new Point2d(p.X, p.Y)
            };
            var img2BoundsTransformed = Cv2.PerspectiveTransform(img2Bounds, H);
            var drawingPoints = img2BoundsTransformed.Select(p => (Point)p).FirstOrDefault();
            return drawingPoints;
        }

    }

2.3 初始化

图D->图C,建立显示坐标系的配准基准点对(图C尺寸已知,所有关键位置A-J均已知)

其中,pointMarks为WPF标记的点集

if (pointMarks?.Count > 3)
     proj_field_to_top = new VideoFieldTransform(pointMarks);  //真实球场坐标

从绝对球场尺寸到显示尺寸的变换,参见1.2 D-C变换

 proj_field_2d = new VideoFieldTransform(true);  //平面显示坐标

加载底图

//加载球场真实坐标系底图
D_field_photo = LoadImages.Load("2D_field.png");

初始化目标检测

//对象检测,此处略...参见AI机器学习(五)相关的内容 
detector = new DetectorYolov7();

2.4 逐帧变换

假设某个球员的Mat 区域,在视频中的像素位置标记为rect

//获取帧图片
foreach(var frame in LoadImages.LoadVideo("你的视频路径"))
{
    Mat imagedetect = new Mat();
    //投影所有的球员
    foreach(var prediction =detector.Detect(imagedetect))
    {
        var rect = prediction.Rectangle;
        //拷贝真实球场坐标底图  
        var J = D_field_photo.Clone();
        //转换坐标系
        var pfield = proj_field_to_top.Transform(new OpenCvSharp.Point(rect.X , rect.Y));
        //用于显示的图像
        var pshow = proj_field_2d.Transform(new OpenCvSharp.Point(pfield.X, pfield.Y));
        Cv2.Circle(J, pshow.X, pshow.Y, 10, Scalar.LightCyan, -1);
        //发送给前端WPF,这里没给出用于显示的页面,大家自己实现一个即可,只需要一个Image控件
        OnDisplay?.Invoke(null, J.Resize(new OpenCvSharp.Size(300, 200)).ToBytes());
    }
}

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

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

相关文章

m软件接收机中同步技术以及LMS-RLS自适应均衡技术的matlab仿真

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 自适应均衡属于自适应信号处理的应用范畴&#xff0c;各种各样的自适应均衡算法如迫零(ZF)算法、最小均方(LMS)算法、递归最小二乘(RLS)算法、变换域均衡算法、Bussgang算法、高阶或循环统计量算…

使用CreateJS实现一个【拯救球员】小游戏,快@你的小伙伴一起来玩儿吧

拯救球员游戏需求&#x1f447;核心玩法&#x1f447;&#x1f447;界面原型&#x1f447;&#x1f447;成品演示&#x1f447;游戏开发1.游戏素材准备2.代码实现1.创建index.html页面2.首页转场动画实现3.添加分数倒计时4.卡片排序展示5.游戏结束世界杯开赛前夕&#xff0c;球…

拷贝构造,赋值运算符重载(六千字长文详解!)

c之类和对象详解 拷贝构造&#xff0c;赋值运算符重载 文章目录c之类和对象详解 拷贝构造&#xff0c;赋值运算符重载拷贝构造拷贝构造特征拷贝构造的注意赋值运算符重载运算符重载赋值重载赋值运算符的写法注意赋值重载的默认性赋值重载和拷贝赋值的区别在哪里&#xff1f;拷贝…

Ubuntu问题汇总

1.sudo ifconfig 找不到命令 ubuntu找不到ifconfig_猿 白的博客-CSDN博客_ubuntu ifconfig命令找不到 没有找到ifconfig的命令&#xff0c;需要进行安装&#xff0c;按照提示中的命令安装相关的工具包 sudo apt install net-tools 2.ip查找 3.重启服务器后&#xff0c;nvid…

记一次浏览器预览通过nginx且有权限控制的静态文件

我的需求是&#xff0c;后台生成了合同文件&#xff0c;用户需要进行预览&#xff0c;如果采用流的实现方式的话&#xff0c;会涉及到输入流、输出流&#xff0c;性能开销较大&#xff0c;所以采用的是直接访问文件&#xff0c;这里就涉及到一个问题&#xff0c;就是 需要设置…

Mobtech秒验SDK——一站式解决用户登录场景

据悉&#xff0c;北京中文万维科技有限公司旗下多款APP&#xff0c;和MobTech开发的秒验SDK达成合作&#xff0c;为其提供用户一键登录解决方案。 北京中文万维科技有限公司是一家立志以移动互联网阅读为发展起点的阅读互动娱乐高新技术企业&#xff0c;旗下拥有多款阅读类APP。…

互动教学场景下的视频直播线上研讨会应用(组图)

阿酷TONY / 原创 / 2022-12-14 / 长沙 / 互动教学/互动培训类场景特点&#xff1a; 1. 直播过程中&#xff0c;学员不仅是观看讲师的授课内容&#xff0c;还要与讲师直接进行音视频会话&#xff1b;当然一些非培训教学场景也常用到&#xff0c;比如线上学术类研讨会等等。 2…

【Pycharm教程】 详解 PyCharm Macros宏

宏提供了一种方便的方法来自动化您在编写代码时经常执行的重复过程。您可以录制、编辑和播放宏&#xff0c;为它们分配快捷方式并共享它们。 宏可用于在文件中组合一系列与编辑器相关的操作。 您无法记录按钮单击、导航到弹出窗口以及访问工具窗口、菜单和对话框。 可以使用没…

从3s到40ms,看看人家的性能优化技巧,确实优雅

什么是高性能系统 先理解一下什么是高性能设计&#xff0c;官方定义: 高可用(High Availability&#xff0c;HA)核心目标是保障业务的连续性&#xff0c;从用户视角来看&#xff0c;业务永远是正常稳定的对外提供服务&#xff0c;业界一般用几个 9 来衡量系统的可用性。通常采…

代码随想录算法训练营第五十天|123.买卖股票的最佳时机III、 188.买卖股票的最佳时机IV

123.买卖股票的最佳时机III 此题限定了买卖的次数&#xff0c;所以应该用几个状态来记录所对应得利润 至多买卖两次&#xff0c;这意味着可以买卖一次&#xff0c;可以买卖两次&#xff0c;也可以不买卖。 dp数组及下标含义 一天一共就有五个状态&#xff0c; 0 没有操作 1 第…

java+mysql 基于ssm的驾校预约管理系统

随着现代驾校预约管理的快速发展,可以说驾校预约管理已经逐渐成为现代驾校预约管理过程中最为重要的部分之一。但是一直以来我国传统的驾校预约管理并没有建立一套完善的行之有效的驾校预约管理系统,传统的驾校预约管理已经无法适应高速发展,无论是从效率还是从效果来看都远远的…

代码随想录Day50|123.买卖股票的最佳时机III、188.买卖股票的最佳时机IV

文章目录123.买卖股票的最佳时机III188.买卖股票的最佳时机IV123.买卖股票的最佳时机III 文章讲解&#xff1a;代码随想录 (programmercarl.com) 题目链接&#xff1a;123. 买卖股票的最佳时机 III - 力扣&#xff08;LeetCode&#xff09; 题目&#xff1a; 给定一个数组&…

[附源码]Python计算机毕业设计感动校园人物投稿网站Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

极简入门 2022 docker 部署skywalking9.2.0

安装skywalking服务 官方地址&#xff1a;官网 安装文档&#xff1a;文档地址 拉取镜像 docker pull apache/skywalking-oap-server:9.2.0 docker pull apache/skywalking-ui:9.2.0运行skywalking-oap容器 docker run --name skywalking-oap -e TZAsia/Shanghai -p 12800:1…

ffmpeg的基本用法

title: ffmpeg的基本用法 categories:[ffmpeg] tags:[音视频编程] 一、ffmpeg的安装 1.Centos安装 FFmpeg 在默认的CentOS 8 源仓库中没有提供。你可以选择通过源文件编译安装 FFmpeg&#xff0c;或者使用dnf工具从Negativo17源仓库中安装。我们将会使用第二个选项。 完成…

正大期货数据整合 新鲜事简单报

本周观察重点: &#x1f539;美国11月CPI数据昨晚公布为7.1%&#xff0c;低于市场预期7.3%&#xff0c;较于前值7.7%大幅回落&#xff0c;随通膨回落&#xff0c;静待联准会未来货币政策方向。 12/15 (四) 美国、香港、欧洲、英国、台湾央行12月利率决策会议 12/16 (六) 日本、…

基于java+springboot+mybatis+vue+mysql的小学家校一体作业帮

项目介绍 本系统采用java语言开发&#xff0c;后端采用springboot框架&#xff0c;前端采用vue技术&#xff0c;数据库采用mysql进行数据存储。系统功能如下&#xff1a; 前台&#xff1a; 首页、微社区、试卷、公告通知、个人中心、后台管理 后台&#xff1a; 首页、个人中…

SpringCloud02

1.Nacos配置管理 Nacos除了可以做注册中心&#xff0c;同样可以做配置管理来使用。 1.1.统一配置管理 当微服务部署的实例越来越多&#xff0c;达到数十、数百时&#xff0c;逐个修改微服务配置就会让人抓狂&#xff0c;而且很容易出错。我们需要一种统一配置管理方案&#xff…

技术分享 | 软件项目管理与跨部门沟通协作

项目管理是在项目活动中运用知识、技能、工具和方法&#xff0c;以便达到项目要求。 软件项目管理有其特定的对象、范围和活动&#xff0c;着重关注成本、进度、风险和质量的管理&#xff0c;还需要协调开发团队和客户的关系&#xff0c;协调内部各个团队之间的关系&#xff0…

怎么把pdf格式转成word文档?如何将 PDF 转换为 Word

怎么把pdf格式转成word文档&#xff1f;PDF是运用得很广泛地的办公文档&#xff0c;但是不能编辑&#xff0c;为了方便编辑&#xff0c;需要将PDF转换为word&#xff0c;那么&#xff0c;如何将 PDF 转换为 Word&#xff0c;下面&#xff0c;易我小编会讲解实用的pdf转word的操…