机器视觉与图像处理
多功能和易用运动控制框架
支持图像模板深度学习

第五部分 视场用户界面

RVB提供功能比较强大的图形用户接口,为应用程序增加在视场窗口与用户的交互能力。RVB的图形用户接口是建立在DIRECTX的基础上,称之为XGUI(DirectX based Graphics User Interface)。图形用户接口提供用户程序或系统主要三个方面的功能。一、基本几何图形的绘制。二、文字和字符的显示。 三、 数字图像的显示。 除了这些功能以外,RVG的图形用户接口还提供了两个与其他用户接口系统不同的功能。一、图层功能,不同的图层可以对显示效果进行不同的处理。二、显示模式,在一个窗口下,可以将内容显示按照不同的方式进行显示,如实际大小,按照比例显示等等。

在XGUI提供的用户接口的基础上,RVB提供专门的模块来表示ROI(Region of Interest)的形状,常见的形状如方形,圆形,环行等。这些ROI被称之为REGION对象,封装在PICK模块中。使用REGION对象,可以比较方便的提取ROI的形状模板和子图像。

5.1 XGUI

与通常的图像用户接口系统不同,XGUI采用笔画模型和面向对象的设计方法实现。笔画模型的设计主要针对实时系统性能进行优化。整个用户接口由画布,画笔,画刷等功能对象组成。 这些对象一起完成某些图形图像的绘制,绘制结果直接在画布上体现。XGUI有三个内定的图层,不是所有的图层的绘制都会体现到画布上,只有选择CANVAS层的时候,画布在输出或显示的时候,将绘制结果显示或打印出来。

XGUI建立在DIRECTX的基础上,画布实际上是DIRECTX里面的表面。几何图形的绘制是一个顶点渲染过程在RENDER表面进行渲染过程。 与DIRECTX的绘制流程类似,它的使用有几个典型的阶段。

图10 典型的XGUI绘制流程

1)创建绘制上下纹阶段。这个阶段需要与特定的窗口关联。

2)进行实际绘制阶段。在阶段可以进行各种图形图像,文字等的绘制。

3)将结果提交。对各种图形的绘制提交到当前渲染表面。

4)将渲染表面提交给屏幕显示缓冲区。进行显示。

(TIPS: 当应用系统结束后,记得要销毁上下文对象哦。)

与其他对象一样,XGUI也遵循创建/销毁模式。XGUI在绘制之前需要创建一个绘制上下文对象,由用户指定窗口对象和画布大小。如:

HGDC dc = rvgCreateContext(m_hWnd, 768, 576);

默认情况下,画布大小为视频窗口的客户区域大小,但是通常情况下,画布对象大小往往大于视频窗口大小。在进行各种绘制的时候,以画布的左上角为原点,见图2。

图11 画布与显示窗口坐标

在绘制完各种图像以后,可以通过函数对画布进行绘制结果修改,如清除

rvgClear(dc,RV_CLR_ALL, TRUE);

绘制完成以后, 不会直接显示或打印出来,一般情况下可以按照下面的方式进行显示:

rvgRealize(dc, RV_CLR_ALL, FALSE);

rvgFlush(m_gDc);

当创建的上下文类型是增强型的时候,也可以将画布上的绘制结果直接作为数字图像输出。如

RvImage image = rvgExport( dc, NULL);

当一个新的上下文对象创建以后,RVB是将其图标在左上角显示的,很多情况下,应用程序可能不太需要,可以将其隐藏起来,如

rvgSetRvbLogoVisible(dc, FALSE);

rvgRealize(dc, RV_CLR_ALL, TRUE);

5.2图形图像绘制

图形图像的绘制是用户接口系统的主要功能。图形图像的绘制效果受当前参与绘制的对象影响,如画笔,图层,以及一些其他因素,如上下文对象当前的透明度。画笔和画刷主要对几何图形的绘制有影响,对文字和字符,数字图像并无什么影响。

5.2.1基本几何图形的绘制

基本几何图形包括点,线,矩形,圆形。这些图形为基本的图形可以构成更复杂的几何图形。默认情况下,画笔的类型为实线,一个单位宽度,颜色为黑色。在绘制几何图形之前,可以将画笔设置成用户喜欢的类型,如下面语句将当前画笔设置成红色,虚线,宽度为3:

GPen pen = GPEN(GDC_RED, 3, RV_PS_HIDDEN);

GPen* pPrevPen = rvgSelectPen(dc, &pen);

// To do some drawing

绘制完成以后,需要将当前系统的画笔重新恢复。

rvgSelectPen(dc, pPrevPen);

点和直线的绘制

单独点的绘制情况比较小,函数也比较简单。如

rvgDrawPoint(dc, 200, 70);

值得一提的是,XGUI还提供另外一个函数对当前画布象素点的设置和读取,如

rvgSetPixel(dc, 200, 25, GRGB(255,255,0));

该语句将位置为200,25的位置的象素设置为GRGB(255,255,0)颜色。与rvgDrawPoint不同的是,rvgSetPixel对画布的一个象素进行影响,而rvgDrawPoint根据当前画笔的宽度和颜色可能影响好几个画布象素。

(TIPS: 一般情况下, rvgDrawPoint的影响实际上并没有真正对画布进行修改, 只有增强性画布, 并且输出的时候, 对CANVAS层影响才会表现出来)。

直线可以按照两种方式进行绘制,第一种方式如下:

rvgMoveTo(dc, 56,60);

rvgLineTo(dc, 156,120);

上面的语句首先将画笔位置移至(56,60)位置,然后在绘制一条直线到(156,120)位置。另一种方式是直接指定直线两端的坐标位置进行绘制,如:

rvgDrawLine(dc, 56,60,156,120);

后一种方式可以可以输出笔画句柄,用于后续的重绘或修改工作。

矩形的绘制

矩形绘制可以绘制正常的方形,也可以有一定旋转角度的巨型。同样,矩形也可以有填充和非填充的区别。填充的矩形是实心填充。如:

GRect rect = GRECT(10, 80, 10+50, 80+40);

BOOL bFill= TRUE;

rvgDrawRect(dc, rect.left, rect.top, rect.right , rect.bottom , bFill);

如果要将某一个矩形以一坐标位置(30,30)为中心旋转45角度,可以按照下面的语句进行:

rvgDrawRectEx(dc, rect, 30,30, 45,FALSE);

圆, 椭圆, 圆弧的绘制

虽然圆可以看成椭圆的特例,但是XGUI还是提供了两个接口供用户使用。主要是椭圆在X,Y方向轴不相同的时候,可以进行一定角度的旋转。 圆和椭圆都可以进行填充。如果需要绘制一个旋转了45度角的,可以按照如下方式:

rvgDrawEllipse(dc, 150,120, 65, 98,FALSE);

圆弧只是部分非填充的圆。绘制一段圆弧需要指定圆心坐标,半径,起始和结束角度。如绘制一段0到45 度,半径为55,圆心为(100,80)的圆弧如下:

rvgDrawArc(dc, 100, 80, 55, 0, 45);

5.2.2文字和字符的显示

文字的绘制最简单的方法如下:

rvgTextOut(dc, “Hello world!”);

当然,如果要用户想要使用自己喜欢的字体和大小显示文字,可以首先创建一个FONT对象,然后在绘制之前,进行选定, 文字就可以按照想要的式样绘制出来了。如:

GFont* pFont = rvgCreateFont(dc, “宋体”);

GFont* pOldFont = rvgSelectFont(m_wndPrevBox.m_gDc, pFont);

//TO DO SOMETHING

rvgSelectFont(m_wndPrevBox.m_gDc , pOldFont );

rvgDestroyFont(pFont);

注意,后面两条语句是FONT对象使用完成以后,才能执行的。

在大部分情况下,用户应该使用rvgDrawText(rvgDrawTextEx),这个函数可以返回笔画对象的句柄,同时,提供更多可以控制文字输出方式的能力。如下面的语句将”Hello World!”在指定的矩形框内水平置中,顶部对齐输出。

rvgDrawTextEx(dc, RV_TA_HCENTER | RV_TA_TOP, "Hello World",-1, &GRECT(60,60,60+180, 60+30));

5.2.3数字图像的显示

数字图像可以直接或间接的绘制在画布上。所谓间接的绘制在画布上,指以笔画绘制的方式,在非增强型画布下的一种绘制。这样的绘制,只有最终显示到屏幕上的时候,绘制结果才能表现出来。直接输出到画布上一般来说速度会更快一些,但是其显示的方式有一定的限制。XGUI模块提供了丰富的接口来对数字图像进行不同方式显示。

直接输出到画布上

直接输出到画布上的时候,最简单的输出方式如:

rvgPaintImage(dc, image);

这个语句将图像从左上角原坐标点开始,将图像绘制出来,如果画布足够大的话。

如果要控制图像在画布中的位置和显示大小,可以按照如下方式进行:

rvgBitBlt(dc, image, 10,10, 100, 120);

这个语句将图像绘制到以左上角(10,10)开始,到右下角结束的矩形区域。如果image实际上没有这个区域大,实际的区域与image的宽度和高度为准,不过,开始绘制的坐标位置是从(10,10)开始。如果image实际的图像大小比显示区域大,绘制的实际区域与函数参数设置的大小一样。

(TIPS:如果显示窗口的显示区域不是很大,即使图像全部绘制了,也可能只显示部分。)

作为笔画进行绘制

RVB的内部数字图像作为笔画绘制的时候,需要使用框架对象。框架对象定义了图像的显示方式。选用不同的框架,其绘制的结果会不一样,因此作为笔画进行绘制的方式可以得到更多的绘制效果。如下面的方式,按照矩形输出:

GRect rect=GRECT(-60,-60,200,200);

int flag = RV_FST_RECT;

GPostModel* pFr = rvgCreatePostModel(RV_PT_SPECIFIC, -1,(DWORD) ( flag),(DWORD) (&rect));

GPostModel* oldPostModel = rvgSelectPostModel(dc, pFr);

HANDLE hStroke = rvgDrawImage(dc, image, 0, 0 );

rvgSelectPostModel(dc, oldPostModel);

rvgDestroyPostModel(pFr);

rvgRealize(dc);

rvgFlush(dc);

下面的代码输出一个圆形的图像:

int w = 200, h =200;

int flag = RV_FST_MASK;

RvMask mask = rvCreateMask(RV_MT_CIRCLE, w, h);

RV_ASSERT(mask);

GPostModel* pFr = rvgCreatePostModel(RV_PT_SPECIFIC, -1,(DWORD) ( flag),(DWORD) (mask));

GPostModel* oldPostModel = rvgSelectPostModel(dc, pFr);

HANDLE hStroke = rvgDrawImage(dc, image, 30, 30, w,h );

rvgSelectPostModel(dc, oldPostModel);

rvgDestroyPostModel(pFr);

rvDestroyMask(mask);

rvgRealize(dc);

rvgFlush(dc);

另外一个函数rvDrawImageEx不需要频繁的创建和销毁GPostModel,但是支持的绘制方式有限,仅支持缩放,对齐等几个有限方式。

创建FRAME的时候,使用的参数直接决定了数字图像的输出方式,下表例出不同输出方式下FRAME参数的定义。(以下参数类型均按照c/c++语言进行说明)

序号 说明 类型 参数 图例
序号 说明 类型 参数 图例
0 原始输出 RV_PT_DEFAULT 主: 无
次: 无
示例图像
1 矩形区域输出 RV_PT_SPECIFIC 主: RV_FST_RECT
次: GRect 指针
示例图像
2 模板方式输出 (圆形) RV_PT_SPECIFIC 主: RV_FST_MASK
次: RvMask 对象
示例图像
3 旋转方式输出 RV_PT_ROTATE 主: 角度变量指针. 以度为单位, 数据类型为 float.
次: 是否保持原来尺寸. 数据类型为 BOOL.
示例图像
4 拉伸方式输出 RV_PT_STRETCH 主: 是否保持长宽比. 数据类型为 BOOL.
次: 是否在区域按中对齐. 数据类型为 BOOL.
示例图像
5 对齐方式输出 RV_PT_ALIGN 主: 水平方向对齐方式. 数据类型为 int.
次: 垂直方向对齐方式. 数据类型为 int.
示例图像
6 比例方式输出 RV_PT_SCALE 主: 缩放比例. 数据类型为 float.
次: 是否保持图象原始大小. 数据类型为 BOOL.
示例图像
7 任意四边形输出 RV_PT_SKEW 主: 顶点数组指针. 数据类型为 GPoint. 最少 4 个顶点
次: 是否保持图象原始大小. 数据类型为 BOOL.
示例图像
8 平铺填充方式输出 RV_PT_TILE 主: 指定排列的行数和列数. 数据类型为 GSize.
次: 镜像类型. 数据类型为 int.
示例图像

5.2.4特殊对象的显示

RVB机器视觉平台有一些特殊的对象,这些对象是机器视觉和图像处理应用的常用对象,如ROI模板,对象轮廓。XGUI为了更好的表示这些对象,提供了专门的函数接口。

ROI模板的绘制

ROI模板的绘制需要用到画刷对象。通过画刷的不同,可以将模板绘制成不同样式。下面的语句绘制一个圆形的模板,样式为对角交叉线。

rvgClear(dc);

RvMask m = rvCreateMask(RV_MT_CIRCLE, 120,120);

RV_ASSERT(m);

GBrush* pBrush = rvgCreateBrush( RV_BT_DIAGCROSS , 22);

RV_ASSERT(pBrush);

GBrush* pOld = rvgSelectBrush(dc, pBrush);

rvgDrawMask(dc, m, 10,10 );

rvgSelectBrush(dc, pOld);

rvgDestroyBrush(pBrush);

rvDestroyMask(m);

rvgRealize(m_wndPrevBox.m_gDc,RV_CLR_ALL, TRUE);

画刷支持的样式如下:

序号 样式 效果
1 实心 实心效果
2 反对角线 反对角线效果
3 十字交叉线 十字交叉线效果
4 对角交叉线 对角交叉线效果
5 对角线 对角线效果
6 水平线 水平线效果
7 垂直线 垂直线效果
8 点效果

TIPS:画刷与几何图像绘制中的填充有区别的。几何填充是将几何区域里面的所有象素都绘制成画笔的颜色。画刷可以绘制比几何填充的多的效果。 画刷是无法影响几何绘制中的填充效果的。

对象轮廓线

对象轮廓线是指图像中某一对象边缘像素点的集合。对象轮廓线可以按照不同编码方式对像素点进行编码。对象的轮廓线可以用在形状匹配的算法中。对象轮廓线是点的集合,也可以是线段的集合。下面是一段轮廓绘制的示例代码:

RvSeq* pContourList = (RvSeq*)rvFindContours(im, RV_CC_ABSOLUTE, RV_BIN_ZERO, FALSE);

RvContour pCont=NULL;

RV_ASSERT(pContourList);

RvSeqReader sr;

RV_BEGIN_READ_SEQ(pContourList, &sr);

do{

RV_READ_FR_SEQ(&sr, &pCont);

GPen pen = GPEN(GRGB(100 + (rand()%120));

GPen* pOld = rvgSelectPen(dc, &pen);

rvgDrawContour(dc, pCont);

rvgSelectPen(dc, pOld);

}while(!RV_IS_SEQ_END(&sr));

RV_END_READ_SEQ(pContourList, &sr);

轮廓线的绘制受当前画笔的影响。因此,轮廓线可以由实线,虚线,或点画线等表示。

笔画的合并和拆分

在一些高阶应用场所,不要进行笔画合并或拆分。下面的代码将一个笔画拆分,分别进行分别绘制,最后又合并成一个笔画,完成一个复杂的绘制任务。

RvRegion_t *pk=(RvRegion_t*)self;

int flag = pk->drawStyle ;

RV_HANDLE hSubStrokes[2]={NULL, NULL};

if (hStroke){

rvgUncombine(pDc, hStroke, hSubStrokes, 2, TRUE);

}

if (flag & RV_PFS_SOLID){

hSubStrokes[0] = DrawAreaMap(pDc, self, RV_BT_SOLID, hSubStrokes[0]);

else if (flag & RV_PFS_GRID){

hSubStrokes[0] = DrawAreaMap(pDc, self, RV_BT_DOT, hSubStrokes[0]);

}

else if (flag & RV_PFS_DIAG){

hSubStrokes[0] = DrawAreaMap(pDc, self, RV_BT_BDIAG, hSubStrokes[0]);

}

else if (flag & RV_PFS_CROSS){

hSubStrokes[0] = DrawAreaMap(pDc, self, RV_BT_CROSS, hSubStrokes[0]);

}

else {

if (flag & RV_PFS_PREVIEW)

{

if (pk->pSubImage)

{

GPostModel* pPostModel = rvgCreatePostModel(RV_PT_SPECIFIC, -1, RV_PST_MASK, (DWORD)pk->pMask);

RV_ASSERT(pPostModel);

GPostModel* pOldPostModel = rvgSelectPostModel(pDc, pPostModel);

float rate = rvgGetTrasparence(pDc);

rvgSetTrasparence(pDc, 0.0f);

hSubStrokes[0] = rvgDrawImage(pDc, pk->pSubImage,pk->left, pk->top, -1,-1, hSubStrokes[0]);

rvgSetTrasparence(pDc, rate);

rvgSelectPostModel(pDc, pOldPostModel);

rvgDestroyPostModel(pPostModel);

}

else{

hSubStrokes[0] = rvgDrawDummy(pDc, hSubStrokes[0] );

}

}

else{

hSubStrokes[0] = rvgDrawDummy(pDc, hSubStrokes[0] );

}

RV_ASSERT(hSubStrokes[0]);

if (!(RV_PDS_GET_FRAME_FLAG(pk->drawStyle) & RV_PDS_NO_BORDER)){

float rate = rvgGetTrasparence(pDc);

rvgSetTrasparence(pDc, 0.0f);

hSubStrokes[1] = m_pfnDraw[pk->shape](self, pDc, pPen, hSubStrokes[1]);

rvgSetTrasparence(pDc, rate);

}

else{

hSubStrokes[1] = rvgDrawDummy(pDc, hSubStrokes[1] );

}

RV_ASSERT(hSubStrokes[1]);

hStroke = rvgCombine(pDc, hSubStrokes, 2, hStroke);

RV_ASSERT(hStroke);

上述代码中,可以看到为了保证笔画的数量,使用了rvgDrawDummy进行哑绘制(即不进行实际的绘制)。

图层

与很多图形用户接口不同,XGUI提供图层功能,用户在进行各种绘制任务的过程中,可以选择不同的图层进行绘制。图层的不同,绘制的结果和显示结果一样。XGUI共提供三个图层,即画布层,虚拟层,视口层。

画布层 – 如果当前的绘制图层为画布层,而且当前的上下文对象为增强型的时候,绘制的结果将直接作用在画布,当输出画布的时候,绘制的内容作为画布一部分进行输出。

虚拟层 – 虚拟层的绘制结果不会反映到画布上,但可以在窗口中反映出来。

视口层 – 视口层的绘制结果也不会反应到画布上,但与其他层不同的是,在该层的绘制结果只能按照1:1大小进行显示。

在绘制的过程中,XGUI按照先绘制画布层,然后绘制虚拟层,最后绘制视口层的顺序进行,视口层的效果最先显示。

下面的语句将上下文对象设置为画布层。

rvgSetCurrentLayer(dc, RV_LT_CANVAS);

在某些特需对象的显示中,如标记对象,一直是在视口层进行绘制。系统默认的图层在画布层。

显示模式

XGUI提供不同的显示模式显示绘制的内容。常见的显示模式如实际大小,按比例显示,拉伸显示等。用户可以根据不同的要求,选择不同的显示模式。显示模式不影响视口层的绘制结果。下面的语句将显示模式设置为等比例拉伸模式。

rvgSetViewMode(dc, RV_VM_STRETCH_KR);

当显示模式为实际大小或等比例大小的时候,如果窗口区域小于需要显示的区域的时候,可以通过设置的左上角的位置,来显示需要显示的部分。下面的语句从显示区域的左边为25,上边为25的位置开始显示。

rvgSetViewPos(dc, 25,25);

左上角可以设置的位置范围可以使用下面的方式获得:

GSize size = rvgGetViewDeltaEx(dc);

该语句返回水平和垂直两个方向位置范围。当超过位置范围的时候,左上角显示位置将无效。

(TIPS:目前版本不支持子画布区域显示模式。)

5.4.1坐标转换

当显示模式不在默认的模式情况下,窗口坐标与画布坐标可能不一样,为了找到窗口坐标与画布坐标对应的位置,需要对坐标进行转换,如:

int disp_y = rvgConvertYToDisplay(dc , 100 );

这个语句将画布y坐标为100的值转换成新值。反之如果要将窗口坐标转换成画布坐标,可以按照下面的方式进行:

int canv_x = rvgConvertXToCanvas(dc , 100 );

这个语句将窗口为100的x坐标转换为画布坐标值。

RVB提供了系列的函数用于坐标转换,画布坐标转换成窗口坐标的函数列表如下:

函数 说明
rvgConvertXToDisplay 将画布 x 坐标转换成窗口坐标
rvgConvertYToDisplay 将画布 y 坐标转换成窗口坐标
rvgConvertPointToDisplay 将画布点对象的坐标转换成窗口坐标
rvgConvertPointToDisplayEx
rvgConvertRectToDisplay 将画布矩形对象的坐标转换成窗口坐标
rvgConvertRectToDisplayEx

窗口坐标转换成画布坐标的函数列表如下:

函数 说明
rvgConvertXToCanvas 将窗口 x 坐标转换成画布坐标
rvgConvertYToCanvas 将窗口 y 坐标转换成画布坐标
rvgConvertPointToCanvas 将窗口点对象的坐标转换成画布坐标
rvgConvertPointToCanvasEx
rvgConvertRectToCanvas 将窗口矩形对象的坐标转换成画布坐标
rvgConvertRectToCanvasEx

5.5关注区(Region of Interest)

关注区(Region of Interest)是用户在图像处理中,对图像区域最感兴趣的区域,一般是视场图像的部分区域。关注区可以减少图像处理的工作量,在机器视觉应用系统中得到广泛的使用。REGION模块提供关注区操作功能,并且提供了多种基本形状的区域对象,如圆形,矩形,环形等。

关注区的操作主要包括形状和大小的设置,以及对应图像区域的子图像的操作。 在对关注区操作之前,必须先创建:

RvRegion reg = rvCreateRegion(RRS_RECT);

rpkDraw(reg, dc);

rvgRealize(reg, RV_CLR_ALL, TRUE);

上面的语句创建一个方形区域并显示出来。

基本属性

关注区类似一个小控件,也有基本的属性。如当前位置,宽度,高度,外观颜色,显示式样。

GPoint pt = rpkGetLeftTop(reg);

上面的语句获取区域的左上角位置坐标。关注区类似显示的时候可以显示两种颜色,边框颜色和区域的填充颜色。如:

rpkSetForeColor(reg, GRGB(255,0,0));

这个语句将关注区的边框颜色设置为红色。

关注区的显示式样有多种,如实心填充,斜边填充,或者直接显示子图像。当选择直接显示子图像的时候,各种填充类型无效。

rpkSetStyle(reg, rpkGetStyle(reg) | RV_PFS_PREVIEW);

上面的语句将当前的关注区选择为直接显示子图像式样,可以达到图像预览效果。

关注区形状

指定的关注区的形状创建以后,可以通过设置各种顶点坐标来改变其形状和大小。不同形状类型的区域的顶点数量和表示的含义可能不一样(见附表3)。

rpkSetGeometry(reg, 0, 10);

rpkSetGeometry(reg, 1, 10);

rpkSetGeometry(reg, 2, 100);

rpkSetGeometry(reg, 3, 60);

rpkUpdateMask(reg);

上面的语句将当前创建的矩形的左上角坐标设置为(10,10),右下角坐标为(100,60)。

有些类型的关注区支持旋转功能。可以按照下面方式进行。

rpkSetRotation(reg, 45);

该语句将关注区旋转45度。

子图像操作

关注区被创建以后,初始的图像是无效的,在使用关注区的子图像之前,首先需要从所在的视场图像中抓取对应区域的图像数据。方法如下:

rpkShot(reg, image);

然后,使用下面的语句获取对应的子图像:

RvImage imSub = rpkGetSubImage(reg);

子图像都是矩形大小,有效的像素点操作经常需要联合模板的像素位置标记来进行。下面的语句可以很容易的获取到关注区的模板对象。

RvMask msk = rpkGetMask(reg);

关注区的模板对象和图像对象在大小上是一样的。

(TIPS: 关注区的子图像对象和模板只能有关注区对象本身释放,应用程序不可以擅自释放。)



抖音视频号: 第一感机器视觉
微信公众号: 精浦科技
深圳市软云动力科技有限公司
东莞办事处: 广东省东莞市松湖智谷B6栋225b
公司地址: 广东省深圳市南山区桃园路金桃园大厦2191

二维码1 二维码2 二维码3


深圳市软云动力科技有限公司 版权所有  鄂ICP备2022015826号-2   

统计显示 ▼