Datawhale 计算机视觉基础-图像处理(上)-Task02 几何变换

2.1 简介

该部分将对基本的几何变换进行学习,几何变换的原理大多都是相似,只是变换矩阵不同,因此,我们以最常用的平移和旋转为例进行学习。在深度学习领域,我们常用平移、旋转、镜像等操作进行数据增广;在传统CV领域,由于某些拍摄角度的问题,我们需要对图像进行矫正处理,而几何变换正是这个处理过程的基础,因此了解和学习几何变换也是有必要的。

这次我们带着几个问题进行,以旋转为例:

  • 1:变换的形式(公式)是什么?

  • 2:旋转中心是什么?毕竟以不同位置为旋转中心得到的结果是不一样的。

  • 3:采用前向映射还是反向映射?(反向映射更为有效)

  • 4:采用反向映射后,采用何种插值算法?最常用的的是双线性插值,OpenCV也是默认如此。

2.2 学习目标

  • 了解几何变换的概念与应用

  • 理解平移、旋转的原理

  • 掌握在OpenCV框架下实现平移、旋转操作

2.3 内容介绍

1、平移、旋转的原理

2、OpenCV代码实践

3、动手实践并打卡(读者完成)

2.4 算法理论介绍

变换形式

先看第一个问题,变换的形式。与OpencV不同的是这里采取冈萨雷斯的《数字图像处理_第三版》的变换矩阵方式,关于OpenCV的策略可以看它的官方文档。根据冈萨雷斯书中的描述,仿射变换的一般形式如下:

式中的T就是变换矩阵,其中 (v,w)为原坐标,(x,y) 为变换后的坐标,不同的变换对应不同的矩阵,这里也贴出来吧,一些常见的变换矩阵及作用如下表:

也就是说,我们根据自己的目的选择不同变换矩阵就可以了。

坐标系变换

再看第二个问题,变换中心,对于缩放、平移可以以图像坐标原点(图像左上角为原点)为中心变换,这不用坐标系变换,直接按照一般形式计算即可。而对于旋转和偏移,一般是以图像中心为原点,那么这就涉及坐标系转换了。

我们都知道,图像坐标的原点在图像左上角,水平向右为 X 轴,垂直向下为 Y 轴。数学课本中常见的坐标系是以图像中心为原点,水平向右为 X 轴,垂直向上为 Y 轴,称为笛卡尔坐标系。看下图:

因此,对于旋转和偏移,就需要3步(3次变换):

  • 将输入原图图像坐标转换为笛卡尔坐标系;
  • 进行旋转计算。旋转矩阵前面已经给出了;
  • 将旋转后的图像的笛卡尔坐标转回图像坐标。

图像坐标系与笛卡尔坐标系转换关系:

先看下图:

在图像中我们的坐标系通常是AB和AC方向的,原点为A,而笛卡尔直角坐标系是DE和DF方向的,原点为D。
令图像表示为M×N的矩阵,对于点A而言,两坐标系中的坐标分别是(0,0)和(-N/2,M/2),则图像某像素点(x’,y’)转换为笛卡尔坐标(x,y)转换关系为,x为列,y为行:

逆变换为:

于是,根据前面说的3个步骤(3次变换),旋转(顺时针旋转)的变换形式就为,3次变换就有3个矩阵:

反向映射

看第3个问题,在冈萨雷斯的《数字图像处理_第三版》中说的很清楚,前向映射就是根据原图用变换公式直接算出输出图像相应像素的空间位置,那么这会导致一个问题:可能会有多个像素坐标映射到输出图像的同一位置,也可能输出图像的某些位置完全没有相应的输入图像像素与它匹配,也就是没有被映射到,造成有规律的空洞(黑色的蜂窝状)。更好的一种方式是采用 反向映射(Inverse Mapping):扫描输出图像的位置(x,y),通过

(为T的逆矩阵)计算输入图像对应的位置 (v,w),通过插值方法决定输出图像该位置的灰度值。

插值

第4个问题,采用反向映射后,需通过插值方法决定输出图像该位置的值,因此需要选择插值算法。通常有最近邻插值、双线性插值,双三次插值等,OpencV默认采用双线性插值,我们也就采用双线性插值。

2.5 基于OpenCV的实现

  • 工具:OpenCV3.1.0+VS2013
  • 平台:WIN10

函数原型(c++)

OpenCV仿射变换相关的函数一般涉及到warpAffine和getRotationMatrix2D这两个:

  • 使用OpenCV函数warpAffine 来实现一些简单的重映射.
  • OpenCV函数getRotationMatrix2D 来获得旋转矩阵。

1、warpAffined函数详解

void boxFilter( InputArray src, OutputArray dst, int ddepth,Size ksize,  Point anchor = Point(-1,-1),bool normalize = true,int borderType = BORDER_DEFAULT );
  • 第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。
  • 第二个参数,OutputArray类型的dst,函数调用后的运算结果存在这里,需和源图片有一样的尺寸和类型。
  • 第三个参数,InputArray类型的M,2×3的变换矩阵。
  • 第四个参数,Size类型的dsize,表示输出图像的尺寸。
  • 第五个参数,int类型的flags,插值方法的标识符。此参数有默认值INTER_LINEAR(线性插值),可选的插值方式如下:
    INTER_NEAREST - 最近邻插值
    INTER_LINEAR - 线性插值(默认值)
    INTER_AREA - 区域插值
    INTER_CUBIC –三次样条插值
    INTER_LANCZOS4 -Lanczos插值
    CV_WARP_FILL_OUTLIERS - 填充所有输出图像的象素。如果部分象素落在输入图像的边界外,那么它们的值设定为 fillval.
    CV_WARP_INVERSE_MAP –表示M为输出图像到输入图像的反变换,即 。因此可以直接用来做象素插值。否则, warpAffine函数从M矩阵得到反变换。
  • 第六个参数,int类型的borderMode,边界像素模式,默认值为BORDER_CONSTANT。
  • 第七个参数,const Scalar&类型的borderValue,在恒定的边界情况下取的值,默认值为Scalar(),即0。

2、getRotationMatrix2D函数详解

C++: Mat getRotationMatrix2D(Point2f center, double angle, double scale)

参数:

  • 第一个参数,Point2f类型的center,表示源图像的旋转中心。
  • 第二个参数,double类型的angle,旋转角度。角度为正值表示向逆时针旋转(坐标原点是左上角)。
  • 第三个参数,double类型的scale,缩放系数。## 2.6 总结

实现示例(c++)

1、旋转

 cv::Mat src = cv::imread("lenna.jpg");cv::Mat dst;//旋转角度double angle = 45;cv::Size src_sz = src.size();cv::Size dst_sz(src_sz.height, src_sz.width);int len = std::max(src.cols, src.rows);//指定旋转中心(图像中点)cv::Point2f center(len / 2., len / 2.);//获取旋转矩阵(2x3矩阵)cv::Mat rot_mat = cv::getRotationMatrix2D(center, angle, 1.0);//根据旋转矩阵进行仿射变换cv::warpAffine(src, dst, rot_mat, dst_sz);//显示旋转效果cv::imshow("image", src);cv::imshow("result", dst);cv::waitKey(0);return 0;

2、平移

 cv::Mat src = cv::imread("lenna.jpg");cv::Mat dst;cv::Size dst_sz = src.size();//定义平移矩阵cv::Mat t_mat =cv::Mat::zeros(2, 3, CV_32FC1);t_mat.at<float>(0, 0) = 1;t_mat.at<float>(0, 2) = 20; //水平平移量t_mat.at<float>(1, 1) = 1;t_mat.at<float>(1, 2) = 10; //竖直平移量//根据平移矩阵进行仿射变换cv::warpAffine(src, dst, t_mat, dst_sz);//显示平移效果cv::imshow("image", src);cv::imshow("result", dst);cv::waitKey(0);return 0;

进阶实现(根据原理自己实现)

1、旋转

/*图像旋转(以图像中心为旋转中心)*/
void affine_trans_rotate(cv::Mat& src, cv::Mat& dst, double Angle){double angle = Angle*CV_PI / 180.0;//构造输出图像int dst_rows = round(fabs(src.rows * cos(angle)) + fabs(src.cols * sin(angle)));//图像高度int dst_cols = round(fabs(src.cols * cos(angle)) + fabs(src.rows * sin(angle)));//图像宽度if (src.channels() == 1) {dst = cv::Mat::zeros(dst_rows, dst_cols, CV_8UC1); //灰度图初始} else {dst = cv::Mat::zeros(dst_rows, dst_cols, CV_8UC3); //RGB图初始}cv::Mat T1 = (cv::Mat_<double>(3,3) << 1.0,0.0,0.0 , 0.0,-1.0,0.0, -0.5*src.cols , 0.5*src.rows , 1.0); // 将原图像坐标映射到数学笛卡尔坐标cv::Mat T2 = (cv::Mat_<double>(3,3) << cos(angle),-sin(angle),0.0 , sin(angle), cos(angle),0.0, 0.0,0.0,1.0); //数学笛卡尔坐标下顺时针旋转的变换矩阵double t3[3][3] = { { 1.0, 0.0, 0.0 }, { 0.0, -1.0, 0.0 }, { 0.5*dst.cols, 0.5*dst.rows ,1.0} }; // 将数学笛卡尔坐标映射到旋转后的图像坐标cv::Mat T3 = cv::Mat(3.0,3.0,CV_64FC1,t3);cv::Mat T = T1*T2*T3;cv::Mat T_inv = T.inv(); // 求逆矩阵for (double i = 0.0; i < dst.rows; i++){for (double j = 0.0; j < dst.cols; j++){cv::Mat dst_coordinate = (cv::Mat_<double>(1, 3) << j, i, 1.0);cv::Mat src_coordinate = dst_coordinate * T_inv;double v = src_coordinate.at<double>(0, 0); // 原图像的横坐标,列,宽double w = src_coordinate.at<double>(0, 1); // 原图像的纵坐标,行,高//   std::cout << v << std::endl;/*双线性插值*/// 判断是否越界if (int(Angle) % 90 == 0) {if (v < 0) v = 0; if (v > src.cols - 1) v = src.cols - 1;if (w < 0) w = 0; if (w > src.rows - 1) w = src.rows - 1; //必须要加上,否则会出现边界问题}if (v >= 0 && w >= 0 && v <= src.cols - 1 && w <= src.rows - 1){int top = floor(w), bottom = ceil(w), left = floor(v), right = ceil(v); //与映射到原图坐标相邻的四个像素点的坐标double pw = w - top ; //pw为坐标 行 的小数部分(坐标偏差)double pv = v - left; //pv为坐标 列 的小数部分(坐标偏差)if (src.channels() == 1){//灰度图像dst.at<uchar>(i, j) = (1 - pw)*(1 - pv)*src.at<uchar>(top, left) + (1 - pw)*pv*src.at<uchar>(top, right) + pw*(1 - pv)*src.at<uchar>(bottom, left) + pw*pv*src.at<uchar>(bottom, right);}else{//彩色图像dst.at<cv::Vec3b>(i, j)[0] = (1 - pw)*(1 - pv)*src.at<cv::Vec3b>(top, left)[0] + (1 - pw)*pv*src.at<cv::Vec3b>(top, right)[0] + pw*(1 - pv)*src.at<cv::Vec3b>(bottom, left)[0] + pw*pv*src.at<cv::Vec3b>(bottom, right)[0];dst.at<cv::Vec3b>(i, j)[1] = (1 - pw)*(1 - pv)*src.at<cv::Vec3b>(top, left)[1] + (1 - pw)*pv*src.at<cv::Vec3b>(top, right)[1] + pw*(1 - pv)*src.at<cv::Vec3b>(bottom, left)[1] + pw*pv*src.at<cv::Vec3b>(bottom, right)[1];dst.at<cv::Vec3b>(i, j)[2] = (1 - pw)*(1 - pv)*src.at<cv::Vec3b>(top, left)[2] + (1 - pw)*pv*src.at<cv::Vec3b>(top, right)[2] + pw*(1 - pv)*src.at<cv::Vec3b>(bottom, left)[2] + pw*pv*src.at<cv::Vec3b>(bottom, right)[2];}}}}
}

2、平移

/*平移变换*(以图像左顶点为原点)/
/****************************************
tx: 水平平移距离 正数向右移动 负数向左移动
ty: 垂直平移距离 正数向下移动 负数向上移动
*****************************************/
void affine_trans_translation(cv::Mat& src, cv::Mat& dst, double tx, double ty){//构造输出图像int dst_rows = src.rows;//图像高度int dst_cols = src.cols;//图像宽度if (src.channels() == 1) {dst = cv::Mat::zeros(dst_rows, dst_cols, CV_8UC1); //灰度图初始}else {dst = cv::Mat::zeros(dst_rows, dst_cols, CV_8UC3); //RGB图初始}cv::Mat T = (cv::Mat_<double>(3, 3) << 1,0,0 , 0,1,0 , tx,ty,1); //平移变换矩阵cv::Mat T_inv = T.inv(); // 求逆矩阵for (int i = 0; i < dst.rows; i++){for (int j = 0; j < dst.cols; j++){cv::Mat dst_coordinate = (cv::Mat_<double>(1, 3) << j, i, 1);cv::Mat src_coordinate = dst_coordinate * T_inv;double v = src_coordinate.at<double>(0, 0); // 原图像的横坐标,列,宽double w = src_coordinate.at<double>(0, 1); // 原图像的纵坐标,行,高/*双线性插值*/// 判断是否越界if (v >= 0 && w >= 0 && v <= src.cols - 1 && w <= src.rows - 1){int top = floor(w), bottom = ceil(w), left = floor(v), right = ceil(v); //与映射到原图坐标相邻的四个像素点的坐标double pw = w - top; //pw为坐标 行 的小数部分(坐标偏差)double pv = v - left; //pv为坐标 列 的小数部分(坐标偏差)if (src.channels() == 1){//灰度图像dst.at<uchar>(i, j) = (1 - pw)*(1 - pv)*src.at<uchar>(top, left) + (1 - pw)*pv*src.at<uchar>(top, right) + pw*(1 - pv)*src.at<uchar>(bottom, left) + pw*pv*src.at<uchar>(bottom, right);}else{//彩色图像dst.at<cv::Vec3b>(i, j)[0] = (1 - pw)*(1 - pv)*src.at<cv::Vec3b>(top, left)[0] + (1 - pw)*pv*src.at<cv::Vec3b>(top, right)[0] + pw*(1 - pv)*src.at<cv::Vec3b>(bottom, left)[0] + pw*pv*src.at<cv::Vec3b>(bottom, right)[0];dst.at<cv::Vec3b>(i, j)[1] = (1 - pw)*(1 - pv)*src.at<cv::Vec3b>(top, left)[1] + (1 - pw)*pv*src.at<cv::Vec3b>(top, right)[1] + pw*(1 - pv)*src.at<cv::Vec3b>(bottom, left)[1] + pw*pv*src.at<cv::Vec3b>(bottom, right)[1];dst.at<cv::Vec3b>(i, j)[2] = (1 - pw)*(1 - pv)*src.at<cv::Vec3b>(top, left)[2] + (1 - pw)*pv*src.at<cv::Vec3b>(top, right)[2] + pw*(1 - pv)*src.at<cv::Vec3b>(bottom, left)[2] + pw*pv*src.at<cv::Vec3b>(bottom, right)[2];}}}}
}

效果

1、旋转45度

2、平移

相关技术文档、博客、教材、项目推荐

opencv文档: https://docs.opencv.org/3.1.0/da/d54/group__imgproc__transform.html#ga0203d9ee5fcd28d40dbc4a1ea4451983
博客:https://blog.csdn.net/weixin_40647819/article/details/87912122
https://www.jianshu.com/p/18cd12e776e1
https://blog.csdn.net/whuhan2013/article/details/53814026
python版本:https://blog.csdn.net/g11d111/article/details/79978582
https://www.kancloud.cn/aollo/aolloopencv/264331 http://www.woshicver.com/FifthSection/4_2_%E5%9B%BE%E5%83%8F%E5%87%A0%E4%BD%95%E5%8F%98%E6%8D%A2/

涉及函数cv2.getPerspectiveTransform,cv2.warpAffine和cv2.warpPersperctive
1.扩展缩放
只是改变图像的尺寸大小,cv2.resize()可以实现这个功能。在缩放时推荐cv2.INTER_AREA,在拓展时推荐cv2.INTER_CUBIC(慢)和cv2.INTER_LINEAR。默认情况下所有改变图像尺寸大小的操作使用的是插值法都是cv2.INTER_LINEAR。

import cv2img = cv2.imread('45.jpg')
#下面的None本应该是输出图像的尺寸,但是因为后面我们设置了缩放因子,所以,这里为None
res = cv2.resize(img,None,fx=2,fy=2,interpolation=cv2.INTER_CUBIC)
#or
#这里直接设置输出图像的尺寸,所以不用设置缩放因子
height , width =img.shape[:2]
res = cv2.resize(img,(2*width,2*height),interpolation=cv2.INTER_CUBIC)while(1):cv2.imshow('res',res)cv2.imshow('img',img)if cv2.waitKey(1)&0xFF == 27:break
cv2.destroyAllWindows()

2.平移
如果想要沿(x,y)方向移动,移动的距离为(tx,ty)可以以下面方式构建移动矩阵。

可以使用Numpy数组构建矩阵,数据类型是np.float32,然后传给函数cv2.warpAffine()
函数cv2.warpAffine() 的第三个参数的是输出图像的大小,它的格式
应该是图像的(宽,高)。应该记住的是图像的宽对应的是列数,高对应的是行数。
3.旋转
对一个图像旋转角度θ,需要使用下面的旋转矩阵。

但OpenCVC允许在任意地方进行旋转,所以矩阵应该为

其中α = scale · cos θ
为构建旋转矩阵,OpenCV提供了一个函数cv2.getRotationMatrix2D。

import cv2
img = cv2.imread('45.jpg',0)
rows,cols=img.shape
#这里的第一个参数为旋转中心,第二个为旋转角度,第三个为旋转后的缩放因子
#可以通过设置旋转中心,缩放因子以及窗口大小来防止旋转后超出边界的问题。
M=cv2.getRotationMatrix2D((cols/2,rows/2),45,0.6)
#第三个参数是输出图像的尺寸中心
dst=cv2.warpAffine(img,M,(2*cols,2*rows))
while(1):cv2.imshow('img',dst)if cv2.waitKey(1)==27:break
cv2.destroyAllWindows()

4.仿射变换
在仿射变换中,原图中所有平行线在结果图像中同样平行。为创建这个矩阵,需要从原图像中找到三个点以及他们在输出图像中的位置,然后cv2.getAffineTransForm()会创建一个2X3的矩阵。最后这个矩阵会被传给函数cv2.warpAffine()

import cv2
import numpy as np
from matplotlib import pyplot as pltimg=cv2.imread(''draw.png')
rows,cols,ch = img.shapepts1 = np.float32([50,50],[200,50],[50,200])
pts2 = np.float32([10,100],[200,50],[100,250])
#行,列,通道数
M = cv2.getAffineTransform(pts1,pts2)dst = cv2.warpAffine(img,M,(cols,rows))plt.subplot(121,plt.imshow(img),plt.title('Input'))
plt.subplot(121,plt.imshow(img),plt.title('output'))
plt.show()

5.透视变换
对于视角变换,我们需要一个3x3变换矩阵。在变换前后直线还是直线。需要在原图上找到4个点,以及他们在输出图上对应的位置,这四个点中任意三个都不能共线,可以有函数cv2.getPerspectiveTransform()构建,然后这个矩阵传给函数cv2.warpPerspective()

import cv2
import numpy as np
from matplotlib import pyplot as pltimg=cv2.imread('sudokusmall.png')
rows,cols,ch=img.shapepts1 = np.float32([[56,65],[368,52],[28,387],[389,390]])
pts2 = np.float32([[0,0],[300,0],[0,300],[300,300]])M=cv2.getPerspectiveTransform(pts1,pts2)dst=cv2.warpPerspective(img,M,(300,300))plt.subplot(121,plt.imshow(img),plt.title('Input'))
plt.subplot(121,plt.imshow(img),plt.title('Output'))
plt.show()

2.6 总结

该部分对几何变换的平移和旋转进行了介绍,读者可根据提供的资料对相关原理进行学习,然后参考示例代码自行实现。另外读者可以尝试学习并实现其他几何变换,如偏移。


Task02 几何变换 END.

By: 任乔牧&小武


关于Datawhale

Datawhale是一个专注于数据科学与AI领域的开源组织,汇集了众多领域院校和知名企业的优秀学习者,聚合了一群有开源精神和探索精神的团队成员。Datawhale以“for the learner,和学习者一起成长”为愿景,鼓励真实地展现自我、开放包容、互信互助、敢于试错和勇于担当。同时Datawhale 用开源的理念去探索开源内容、开源学习和开源方案,赋能人才培养,助力人才成长,建立起人与人,人与知识,人与企业和人与未来的联结。

Task02 几何变换相关推荐

  1. 计算机视觉基础-图像处理-几何变换

    本文来源于Datawhale组队学习的教材手册,供大家参考阅读. https://github.com/datawhalechina/team-learning/blob/master/%E8%AE% ...

  2. C语言实现bmp图像几何变换(移动,旋转,镜像,转置,缩放)

    C语言实现bmp图像几何变换(移动,旋转,镜像,转置,缩放) 移动 旋转 镜像 转置 缩放 自定义结构及函数如下: #define pi 3.1415926 typedef struct {unsig ...

  3. 图像几何变换C++实现--镜像,平移,旋转,错切,缩放

    一.图像几何变换介绍 图像的几何空间变换是图像处理中的最基础的算法,是指对原始图像按需要改变其大小.形状和位置的变化,原始图像与目标函数之间的坐标变换函数为线性函数.二维图像的基本几何变换主要包括镜像 ...

  4. OpenCV 图像采样 插值 几何变换

    转自:http://hi.baidu.com/xiaoduo170/blog/item/6eefc612c9f8e9c6c2fd786f.html InitLineIterator 初始化线段迭代器 ...

  5. matlab 通过矩阵变换使图像旋转平移_图像的几何变换

    学习图像中的仿射变换(affine transform), 这是一种线性变换(涵盖旋转,平移,错切(shear), 缩放等线性变换),既然是线性变换则可以通过线性变换(矩阵)来获得.仿射变换矩阵M为2 ...

  6. 刻意练习:LeetCode实战 -- Task02. 删除排序数组中的重复项

    背景 本篇图文是LSGO软件技术团队组织的 第二期基础算法(Leetcode)刻意练习训练营 的打卡任务.本期训练营采用分类别练习的模式,即选择了五个知识点(数组.链表.字符串.树.贪心算法),每个知 ...

  7. Datawhale组队学习 Task02:顺序表和链表(2天)

    Task02 顺序表和链表(2天) 1. 线性表的定义与操作 1.1 线性表的定义 线性表(Linear List)是由n(n >= 0)个相同类型的数据元素a1,a2,...,an 组成的有序 ...

  8. 数字图像处理5:几何变换

    图像的几何变换主要包括:平移.扩大与缩小.旋转.仿射.透视等等.在OpenCV中,cv2.resize()实现扩大或者缩小,cv2.warpAffine()实现平移.旋转(cv2.getRotatio ...

  9. 再学 GDI+[33]: TGPPen - 画笔的几何变换 - SetTransform

    几何变换也就是 Matrix 变换, 画笔的 Matrix 会忽略 dx.dy 平移参数; 选择 LineCapRound 线帽效果比较好. 本例效果图: 代码文件: unit Unit1;inter ...

最新文章

  1. 莱维飞行matlab作图,基于莱维飞行和随机游动策略的灰狼算法
  2. CC2541对AT24CXX系列存储器的支持
  3. mysql lisensce_Linux之lldptool命令
  4. Android之自定义属性,format详解
  5. 在中国,真正达到月收入1万以上的有多少
  6. 软考信息安全工程师学习笔记三(1.3 信息安全管理基础)
  7. 上得写代码,下得作情诗,谁说程序员都是屌丝?
  8. [转载]Qt之模型/视图(实时更新数据)
  9. 寒冬下,掉队的金立、联想、魅族们还能赶上5G班车吗?
  10. Python之网络编程(一)
  11. 使用 profile 进行python代码性能分析
  12. matlab怎么编写风雨荷载时程,风雨荷载作用下输电塔动力响应分析
  13. 什么是 0day 漏洞,1day 漏洞和 nday 漏洞?
  14. 持续近40年的战争 X86系列CPU大史记
  15. 什么是CPU主频、外频、倍频?之间关系是?
  16. java类为什么要序列化_Java 中的类为什么要实现序列化呢
  17. Flux架构思想在度咔App中的实践
  18. 二阶倒立摆matlab建模与仿真,二级倒立摆的建模与MATLAB仿真.pdf
  19. 【网络安全】网络安全的重要性你知道吗?
  20. 电阻色环表_色环电阻识别方法

热门文章

  1. 人工智能 - 虚拟人 - 虚拟主播(窗口捕捉)
  2. w10不能访问网络计算机,Win10不能访问局域网其他电脑怎么办?解决Win10无法访问局域网...
  3. 0-1背包问题分支定限法Java实现
  4. matlab 正则化表达式_Matlab-------regexp正则表达式
  5. 无线交换机 dhcp服务器,在一台交换机上有DHCP服务器,在另一台交换机上实现DHCP中继...
  6. 国之重器 望穿秋水——北邮老师廖建新介绍
  7. 洛谷· [AHOI2008]紧急集合 / 聚会
  8. 树莓派+UVC摄像头+网络监控
  9. arduinouno组装无人机_Arduino UNO四自由度机械臂制作
  10. 友邦千盛春雨计划:为每个孤寡老人点亮心中微心愿