形态学在图像处理中的应用
形态学在图像处理中的应用
- 形态学在图像处理中的应用
- 腐蚀和膨胀
- 理论基础
- 形态学操作
- 膨胀
- 腐蚀
- 代码
- 代码说明
- 结果
- 理论基础
- 更多形态学转换
- 理论基础
- 开操作
- 闭操作
- 梯度操作
- 顶帽操作
- 黑帽操作
- 示例代码
- 代码阐述
- 结果
- 理论基础
- 利用形态学操作抽取水平和竖直线
- 理论
- 形态学操作
- 示例代码
- 阐述和结果
- 理论
- 腐蚀和膨胀
腐蚀和膨胀
理论基础
形态学操作
- 简而言之:一系列操作基于形状来操作图像,形态学操作通过在图像上应用结构元素来产生输出图像。
- 最基础的形态学操作包括腐蚀和扩张。它包含广泛的应用:
- 移除噪声
- 孤立一些单独的元素和聚合一些分散的元素
- 找到图像中的局部块状或者孔
- 我们将使用下面的图像简要的介绍膨胀和腐蚀
膨胀
- 这一操作包含使用卷积核B对图片A进行卷积运算,这个卷积核可以有任意的形状和大小,通常是一个方形或者圆形。
- 卷积核B通常有个锚点,通常位于卷积核的中央位置。
- 随着卷积核扫描这个图像,我们计算叠加区域的最大像素值,并将锚点的位置用最大值替换。这样你可以推断最大化操作导致图片中亮的区域增长(所以这里面叫做膨胀)。举个例子,应用膨胀我们可以得到:
我们看到背景亮的区域膨胀变大。
为了掌握这一思想,避免可能的混淆,我们在另外一个例子中反转了原来的图像,现在白色代表原先的字。我们使用3*3矩形元素的元素进行膨胀操作两次。
左侧是反转之后的图像-右侧是膨胀之后的图像
膨胀使得对象的白色区域变大。
腐蚀
- 腐蚀与膨胀类似。它是计算卷积核里面的最小元素。
- 随着卷积核B扫描图片,它会计算B叠加区域的最小像素值,并使用这个像素值替换锚点的值。
- 与膨胀相似,对原始的图像应用腐蚀操作。你可以看到背景亮的区域变小,而黑的区域变得很大。
同样对原始图像进行反转,进行腐蚀操作,得到:
左侧为原始图像反转的图像-右侧为腐蚀的结果腐蚀使得对象白色变小。
代码
示例代码如下,你可以从这里下载代码
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
using namespace cv;
Mat src, erosion_dst, dilation_dst;
int erosion_elem = 0;
int erosion_size = 0;
int dilation_elem = 0;
int dilation_size = 0;
int const max_elem = 2;
int const max_kernel_size = 21;
void Erosion( int, void* );
void Dilation( int, void* );
int main( int, char** argv )
{src = imread( argv[1], IMREAD_COLOR );if( src.empty() ){ return -1; }namedWindow( "Erosion Demo", WINDOW_AUTOSIZE );namedWindow( "Dilation Demo", WINDOW_AUTOSIZE );moveWindow( "Dilation Demo", src.cols, 0 );createTrackbar( "Element:\n 0: Rect \n 1: Cross \n 2: Ellipse", "Erosion Demo",&erosion_elem, max_elem,Erosion );createTrackbar( "Kernel size:\n 2n +1", "Erosion Demo",&erosion_size, max_kernel_size,Erosion );createTrackbar( "Element:\n 0: Rect \n 1: Cross \n 2: Ellipse", "Dilation Demo",&dilation_elem, max_elem,Dilation );createTrackbar( "Kernel size:\n 2n +1", "Dilation Demo",&dilation_size, max_kernel_size,Dilation );Erosion( 0, 0 );Dilation( 0, 0 );waitKey(0);return 0;
}
void Erosion( int, void* )
{int erosion_type = 0;if( erosion_elem == 0 ){ erosion_type = MORPH_RECT; }else if( erosion_elem == 1 ){ erosion_type = MORPH_CROSS; }else if( erosion_elem == 2) { erosion_type = MORPH_ELLIPSE; }Mat element = getStructuringElement( erosion_type,Size( 2*erosion_size + 1, 2*erosion_size+1 ),Point( erosion_size, erosion_size ) );erode( src, erosion_dst, element );imshow( "Erosion Demo", erosion_dst );
}
void Dilation( int, void* )
{int dilation_type = 0;if( dilation_elem == 0 ){ dilation_type = MORPH_RECT; }else if( dilation_elem == 1 ){ dilation_type = MORPH_CROSS; }else if( dilation_elem == 2) { dilation_type = MORPH_ELLIPSE; }Mat element = getStructuringElement( dilation_type,Size( 2*dilation_size + 1, 2*dilation_size+1 ),Point( dilation_size, dilation_size ) );dilate( src, dilation_dst, element );imshow( "Dilation Demo", dilation_dst );
}
代码说明
- 所有的这些东西如果你不清楚,可以参照前面的介绍。我们先看一看程序的结构:
- 加载图像(可以是BGR也可以是灰度图像)
- 创建两个窗口(一个是膨胀输出,一个是腐蚀输出)
- 对于每个操作常见一组滑动条
- 第一个元素返回元素类型erosion_elem 或者dilation_elem
- 第二个元素返回卷积核的大小
- 当我们移动滑动条的时候,用户的函数Erosion 和Dilation 会被调用,它会根据滑动条上的值更新输出图像
- 腐蚀
void Erosion( int, void* )
{int erosion_type = 0;if( erosion_elem == 0 ){ erosion_type = MORPH_RECT; }else if( erosion_elem == 1 ){ erosion_type = MORPH_CROSS; }else if( erosion_elem == 2) { erosion_type = MORPH_ELLIPSE; }Mat element = getStructuringElement( erosion_type,Size( 2*erosion_size + 1, 2*erosion_size+1 ),Point( erosion_size, erosion_size ) );erode( src, erosion_dst, element );imshow( "Erosion Demo", erosion_dst );
}
该函数使用cv::erode进行腐蚀操作,它接收三个参数:
- src:源图像
- erosion_dst:目标输出图像
- element:我们将使用的卷积核。如果不指定,将使用3*3的矩阵。我们可以指定形状。为了指定形状,我们可以使用cv::getStructuringElement 函数:
Mat element = getStructuringElement( erosion_type,Size( 2*erosion_size + 1, 2*erosion_size+1 ),Point( erosion_size, erosion_size ) );
我们可以为卷积核选择以下形状:
- 矩形框:MORPH_RECT
- 十字框:MORPH_CROSS
- 椭圆框:MORPH_ELLIPSE
然后我们只需指定核的大小和锚点。如果没有指定,假定在中心。- 指定这些值之后,我们就可以进行图像的腐蚀操作了。
3.膨胀
膨胀的算法与腐蚀的算法类似。
void Dilation( int, void* )
{int dilation_type = 0;if( dilation_elem == 0 ){ dilation_type = MORPH_RECT; }else if( dilation_elem == 1 ){ dilation_type = MORPH_CROSS; }else if( dilation_elem == 2) { dilation_type = MORPH_ELLIPSE; }Mat element = getStructuringElement( dilation_type,Size( 2*dilation_size + 1, 2*dilation_size+1 ),Point( dilation_size, dilation_size ) );dilate( src, dilation_dst, element );imshow( "Dilation Demo", dilation_dst );
}
结果
编译代码,运行时使用以下图片:
调整滑动条的参数值,得到类似下面结果:
更多形态学转换
理论基础
在上面我们已经了解到形态学两种基本操作:
- 膨胀
- 腐蚀
在这两种基础操作的基础之上,我们探究图像的更为复杂的转换。这里我们讨论OpenCV提供的5中操作。
开操作
- 它通过先进性腐蚀操作,再进行膨胀操作得到
d s t = o p e n ( s r c , e l e m e n t ) = d i l a t e ( e r o d e ( s r c , e l e m e n t ) )
- 在移除小的对象时候很有用(假设物品是亮色,前景色是黑色)
- 如下所示。左侧是原始图像右侧是应用开操作之后的图像。我们可以看到左侧图像的小的空间消失
为了更清晰,对原先对象进行反转,然后再进行开操作,结果如下:
左侧为原始图像反转-右侧为开操作之后的图像
闭操作
比操作是先进行膨胀然后进行腐蚀操作
dst = close( src, element ) = erode( dilate( src, element ) )有利于移除小的洞(黑色区域)
便于说明问题,对反转的图像进行闭操作
左侧反转图像-右侧为进行闭操作之后的图像
梯度操作
- 是膨胀操作与腐蚀操作的差
dst=morphgrad(src,element)=dilate(src,element)−erode(src,element)dst = morph_{grad}( src, element ) = dilate( src, element ) - erode( src, element ) - 对于寻找对象的轮廓很有用,如下:
顶帽操作
- 是原图像与开操作对象的差
dst=tophat(src,element)=src−open(src,element)dst = tophat( src, element ) = src - open( src, element )
黑帽操作
是闭操作与原始图像的差值
dst=blackhat(src,element)=close(src,element)−srcdst = blackhat( src, element ) = close( src, element ) - src示例代码
教程代码如下,你可以从这里下载
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
using namespace cv;
Mat src, dst;
int morph_elem = 0;
int morph_size = 0;
int morph_operator = 0;
int const max_operator = 4;
int const max_elem = 2;
int const max_kernel_size = 21;
const char* window_name = "Morphology Transformations Demo";
void Morphology_Operations( int, void* );
int main( int, char** argv )
{src = imread( argv[1], IMREAD_COLOR ); // Load an imageif( src.empty() ){ return -1; }namedWindow( window_name, WINDOW_AUTOSIZE ); // Create windowcreateTrackbar("Operator:\n 0: Opening - 1: Closing \n 2: Gradient - 3: Top Hat \n 4: Black Hat", window_name, &morph_operator, max_operator, Morphology_Operations );createTrackbar( "Element:\n 0: Rect - 1: Cross - 2: Ellipse", window_name,&morph_elem, max_elem,Morphology_Operations );createTrackbar( "Kernel size:\n 2n +1", window_name,&morph_size, max_kernel_size,Morphology_Operations );Morphology_Operations( 0, 0 );waitKey(0);return 0;
}
void Morphology_Operations( int, void* )
{// Since MORPH_X : 2,3,4,5 and 6int operation = morph_operator + 2;Mat element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );morphologyEx( src, dst, operation, element );imshow( window_name, dst );
}
代码阐述
我们来看一下程序的结构:
- 加载图像
- 创建一个窗口展示形态学操作结果
创建三个滑动按钮来输入参数
- 第一个滑动按钮返回形态学操作类型,使用morph_operator
createTrackbar("Operator:\n 0: Opening - 1: Closing \n 2: Gradient - 3: Top Hat \n 4: Black Hat", window_name, &morph_operator, max_operator, Morphology_Operations );
- 第二个滑动条参数返回morph_elem,它表示卷积核的类型
createTrackbar( "Element:\n 0: Rect - 1: Cross - 2: Ellipse", window_name,
&morph_elem, max_elem,
Morphology_Operations ); - 最后一个参数表示morph_size表示卷积核的大小
createTrackbar( "Kernel size:\n 2n +1", window_name,
&morph_size, max_kernel_size,
Morphology_Operations );
- 第一个滑动按钮返回形态学操作类型,使用morph_operator
每次你移动滑动条的时候,Morphology_Operations 函数会被调用,使得新的形态学操作有效,它会根据当前滑动按钮的值输出图像
void Morphology_Operations( int, void* )
{
// Since MORPH_X : 2,3,4,5 and 6
int operation = morph_operator + 2;
Mat element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );
morphologyEx( src, dst, operation, element );
imshow( window_name, dst );
}
我们可以看到执行形态学转换的例子的关键函数是cv::morphologyEx,在这里我们指定了四个参数(剩余的使用默认值):- src,源输入图像
- dst,输出文件
- operation,执行的形态学转换。注意我们有5个可选项:
- 开操作: MORPH_OPEN : 2
- 闭操作: MORPH_CLOSE: 3
- 梯度操作: MORPH_GRADIENT: 4
- 顶帽操作: MORPH_TOPHAT: 5
- 黑帽操作: MORPH_BLACKHAT: 6
这里的值从2到6,这就是前面函数中加2的原因。
int operation = morph_operator + 2;
- element,卷积核,我们使用cv::getStructuringElement来定义元素结构
结果
在编译完代码之后,我们使用以下图片作为输入参数:
- 这里我们显示了两个窗口截图第一幅图展示了使用十字形卷积核进行开操作的输出结果,第二幅展示了使用椭圆形卷积核进行黑帽操作得出的结果。
利用形态学操作抽取水平和竖直线
理论
形态学操作
形态学是一系列的图像处理操作,这些图像处理基于预先定义好的结构核。输出图像中的每个像素的值由输入图像的响应像素和周边像素决定。通过选择核的尺寸和大小,你可以构造对于输入图像中特定形状敏感的形态学操作。
形态学中,最基础的两个操作是膨胀和腐蚀。膨胀向图像中的对象周边添加像素,腐蚀恰恰相反,添加或者移除 的多少依赖于构造的核的形状和大小,一般来说,这两项操作遵循以下规则:
- 膨胀:输出的像素值是落在核内的最大值。例如一个二值图像,如果有一个落在核内的像素值是1,那么相应的输出图像的像素值为1.
二值图像上的膨胀
灰度图像上的膨胀 - 腐蚀:腐蚀与膨胀恰恰相反,取最小值
二值图像的腐蚀
灰度图像的腐蚀 - 构造核
通过以上可以看出,一般的形态学操作都使用构造的核来探测输出的图像,它是最重要的。这个核通常只包含0和1两个元素,可以拥有任意的形状和大小。通常来说,要比输入的 图像小的多,值为1的元素定义邻居。核的中心元素,也叫原始元素决定要处理的元素。
例如下面是一个7*7大小的菱形核。
菱形核和它的起始点构造的核元素可以有许多形状,例如线形,菱形,周期性线性,磁盘形,圆形等。通常形状和大小和输入图像中要处理的对象类似。例如,要发现图像中的线性,需要构造出线性的核,你在后面将会看到。
示例代码
示例代码如下,你可以在这里下载。
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int, char** argv)
{// Load the imageMat src = imread(argv[1]);// Check if image is loaded fineif(!src.data)cerr << "Problem loading image!!!" << endl;// Show source imageimshow("src", src);// Transform source image to gray if it is notMat gray;if (src.channels() == 3){cvtColor(src, gray, CV_BGR2GRAY);}else{gray = src;}// Show gray imageimshow("gray", gray);// Apply adaptiveThreshold at the bitwise_not of gray, notice the ~ symbolMat bw;adaptiveThreshold(~gray, bw, 255, CV_ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 15, -2);// Show binary imageimshow("binary", bw);// Create the images that will use to extract the horizontal and vertical linesMat horizontal = bw.clone();Mat vertical = bw.clone();// Specify size on horizontal axisint horizontalsize = horizontal.cols / 30;// Create structure element for extracting horizontal lines through morphology operationsMat horizontalStructure = getStructuringElement(MORPH_RECT, Size(horizontalsize,1));// Apply morphology operationserode(horizontal, horizontal, horizontalStructure, Point(-1, -1));dilate(horizontal, horizontal, horizontalStructure, Point(-1, -1));// Show extracted horizontal linesimshow("horizontal", horizontal);// Specify size on vertical axisint verticalsize = vertical.rows / 30;// Create structure element for extracting vertical lines through morphology operationsMat verticalStructure = getStructuringElement(MORPH_RECT, Size( 1,verticalsize));// Apply morphology operationserode(vertical, vertical, verticalStructure, Point(-1, -1));dilate(vertical, vertical, verticalStructure, Point(-1, -1));// Show extracted vertical linesimshow("vertical", vertical);// Inverse vertical imagebitwise_not(vertical, vertical);imshow("vertical_bit", vertical);// Extract edges and smooth image according to the logic// 1. extract edges// 2. dilate(edges)// 3. src.copyTo(smooth)// 4. blur smooth img// 5. smooth.copyTo(src, edges)// Step 1Mat edges;adaptiveThreshold(vertical, edges, 255, CV_ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 3, -2);imshow("edges", edges);// Step 2Mat kernel = Mat::ones(2, 2, CV_8UC1);dilate(edges, edges, kernel);imshow("dilate", edges);// Step 3Mat smooth;vertical.copyTo(smooth);// Step 4blur(smooth, smooth, Size(2, 2));// Step 5smooth.copyTo(vertical, edges);// Show final resultimshow("smooth", vertical);waitKey(0);return 0;
}
阐述和结果
- 载入原始图像,并检查它是否加载成功,紧接着进行展示:
// Load the imageMat src = imread(argv[1]);// Check if image is loaded fineif(!src.data)cerr << "Problem loading image!!!" << endl;// Show source imageimshow("src", src);
2. 如果图像没有转换,将图像转为灰度图像
// Transform source image to gray if it is notMat gray;if (src.channels() == 3){cvtColor(src, gray, CV_BGR2GRAY);}else{gray = src;}// Show gray imageimshow("gray", gray);
3. 之后将灰度图像转为二值图像。注意到符号~表示取反:
// Apply adaptiveThreshold at the bitwise_not of gray, notice the ~ symbolMat bw;adaptiveThreshold(~gray, bw, 255, CV_ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 15, -2);// Show binary imageimshow("binary", bw);
4. 现在我们已经准备好了提取水平线和竖直线,同时紧接着会将音符从音符本中提取出来,但是首先初始化输出的图像
// Create the images that will use to extract the horizontal and vertical lines
Mat horizontal = bw.clone();
Mat vertical = bw.clone();
5. 前面已经介绍为了提取想要的对象,我们必须构造相应的核,为了提取水平线我们构造下面形状的核:
在源代码中通过以下代码段展示:
// Specify size on horizontal axisint horizontalsize = horizontal.cols / 30;// Create structure element for extracting horizontal lines through morphology operationsMat horizontalStructure = getStructuringElement(MORPH_RECT, Size(horizontalsize,1));// Apply morphology operationserode(horizontal, horizontal, horizontalStructure, Point(-1, -1));dilate(horizontal, horizontal, horizontalStructure, Point(-1, -1));// Show extracted horizontal linesimshow("horizontal", horizontal);
6. 竖直线处理类似:
// Specify size on vertical axisint verticalsize = vertical.rows / 30;// Create structure element for extracting vertical lines through morphology operationsMat verticalStructure = getStructuringElement(MORPH_RECT, Size( 1,verticalsize));// Apply morphology operationserode(vertical, vertical, verticalStructure, Point(-1, -1));dilate(vertical, vertical, verticalStructure, Point(-1, -1));// Show extracted vertical linesimshow("vertical", vertical);
7. 如你所看到的那样,音符的边缘很粗糙。你需要细化边缘来获得平滑的结果:
// Inverse vertical imagebitwise_not(vertical, vertical);imshow("vertical_bit", vertical);// Extract edges and smooth image according to the logic// 1. extract edges// 2. dilate(edges)// 3. src.copyTo(smooth)// 4. blur smooth img// 5. smooth.copyTo(src, edges)// Step 1Mat edges;adaptiveThreshold(vertical, edges, 255, CV_ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 3, -2);imshow("edges", edges);// Step 2Mat kernel = Mat::ones(2, 2, CV_8UC1);dilate(edges, edges, kernel);imshow("dilate", edges);// Step 3Mat smooth;vertical.copyTo(smooth);// Step 4blur(smooth, smooth, Size(2, 2));// Step 5smooth.copyTo(vertical, edges);// Show final resultimshow("smooth", vertical);
形态学在图像处理中的应用相关推荐
- matlab在图像处理中的应用论文,MATLAB在数字图像处理中的应用
摘要:数字图像处理是一门新兴技术,经过计算机硬件的快速发展,数字图像的实时处理已经成为可能,由于数字图像处理的各种算法的出现,这就使得其处理速度越来越快,能更好的为人们服务.数字图像处理是一种通过计算 ...
- [转载] python skimage在图像处理中的用法
参考链接: 在Python中打印单变量和多变量 基于python脚本语言开发的数字图片处理包,比如PIL,Pillow, opencv, scikit-image等. PIL和Pillow只提供最基础 ...
- 傅里叶变换在图像处理中的应用初步学习
1 理解傅里叶变换在图像处理中的应用 一维傅里叶变换的作用对象是信号,信号是一维连续的:随着时间不断推移,信号强度的变换情况,可称为时域. 图像处理中的傅里叶变换的作用对象是二维矩阵.随着位置的不断改 ...
- 傅里叶变换在图像处理中的作用
傅立叶变换在图像处理中非常的有用.因为不仅傅立叶分析涉及图像处理的很多方面,傅立叶的改进算法, 比如离散余弦变换,gabor与小波在图像处理中也有重要的分量. 印象中,傅立叶变换在图像处理以下几个话题 ...
- 5、VTK在图像处理中的应用
5.VTK在图像处理中的应用 图像是VTK中一个非常重要的数据.数字图像广泛应用于工业生产.生物医学.媒体娱乐.地质.气象等重要领域,数字图像处理具有重要的应用价值.我们在掌握了VTK的基本知识后,这 ...
- 寻找下一款Prisma APP:深度学习在图像处理中的应用探讨
在9月23日到9月24日的MDCC 2016年中国移动者开发大会"人工智能与机器人"专场中,阿里云技术专家周昌进行了题为<寻找下一款Prisma APP:深度学习在图像处理中 ...
- 中blur函数_Comonad在图像处理中的应用
前几天我回答了一个关于comonad的问题Monad和Comonad到底是什么东西?.其中有讲到comonad的应用例子,但都还不够直观和实用.后来找到一个Comonad在图像处理中的应用的例子,觉得 ...
- 窗函数在图像处理中的应用
窗函数在图像处理中的应用 1. 频谱混乱的三角函数图像 下图是一个45度倾斜的单一频率的余弦函数图像,请注意图中的边界都不是均匀过渡到外界的,全是不连续的跳变. 下面来看看这幅图的频谱会是什么样? 频 ...
- 模糊数学在计算机方面的应用,模糊数学理论在图像处理中的应用
摘要:用计算机来处理图片已成为计算机研究的一个重要方向,基于模糊数学的图像处理技术是计算机图像处理中的重要计算.图像本质上具有模糊性,因此模糊信息处理技术在图像处理中的使用有其必然性.提出一种基于模糊 ...
最新文章
- 给gridview动态生成radiobutton添加OnCheckedChanged监听函数
- 使用table ENLFDIR 快速找出function module和function group的对应关系
- Python+Selenium练习篇之2-利用ID定位元素
- 200多位大牛!2019两院院士拟推荐候选人最新名单
- Jmeter吞吐量控制器详解
- 经典软件测试技术分类
- 吴恩达悄然发布AI维基,另外他的技术岗已经招满了
- vue init download template_Webpack(四)Vue
- wps 打开xml格式乱码_“WPS不兼容EXCEL打开后出现乱码怎么解决“xlsx文件打开是乱码怎么办...
- 【Tool】ELF 和 AXF 文件分析详解
- exchange服务器磁盘性能,Exchange磁盘空间不够了怎么办?
- 大疆FPGA/芯片开发工程师(B卷)笔试题(含详解)
- 这是你知道的建模教程吗
- Java异常————argument type mismatch
- WORD禁止自动更新域
- CPT101计算机系统概念(存储系统部分)
- 艾伟_转载:VS 2010 和 .NET 4.0 系列之《自动启动ASP.NET应用》篇
- 台达DVP系列PLC如何通过RS485连接到华为云平台
- 【数据库考试】数据库系统概论
- android studio json插件_欢迎来到 EQ 2.0 时代——UVI 创造性 EQ 均衡插件 SHADE 简测
热门文章
- AI+区块链,有哪些可能性?
- nginx服务器stream限速配置
- Ubuntu运行roscore时候报错 Unable to contact my own server at xxx的解决方法
- 根据城市名称检索城市ID,以及省市县(LitePal+RxJava+Retrofit)
- java方法传参机制
- 华为7.31笔试(第一题AC、第二题AC、第三题超纲)
- java特种兵 pdf_《Java特种兵(上册)(含CD光盘1张)》怎么样_目录_pdf在线阅读 - 课课家教育...
- Excel快捷键一览
- 基于python中cv2库的图像分割
- CEO采访:从战略层面建立数据驱动型的客户体验策略