概述
第四节 绘图函数
绘图函数适用于任意深度的矩阵/图像。 可以使用抗锯齿渲染形状的边界(目前仅针对8位图像实现)。 所有函数都包括用于彩色图像的参数color(使用RGB值(可以使用Scalar构造函数构造))和用于灰度图像的亮度。 对于彩色图像,通道顺序通常为蓝色,绿色,红色。 这就是imshow,imread和imwrite的期望。 因此,如果使用Scalar构造函数形成颜色,则其外观应类似于:
Scalar ( b l u e _ c o m p o n e n t , g r e e n _ c o m p o n e n t , r e d _ c o m p o n e n t [ , a l p h a _ c o m p o n e n t ] ) texttt{Scalar} (blue _ component, green _ component, red _ component[, alpha _ component]) Scalar(blue_component,green_component,red_component[,alpha_component])
如果使用自己的图像渲染和I / O功能,则可以使用任何通道顺序。 绘图功能独立处理每个通道,并且不依赖于通道顺序,甚至不依赖于所使用的色彩空间。 可以使用cvtColor将整个图像从BGR转换为RGB或不同的色彩空间。
如果绘制的图形部分或全部位于图像外部,则绘制功能会对其进行裁剪。 同样,许多绘图功能可以处理以亚像素精度指定的像素坐标。 这意味着坐标可以作为编码为整数的定点数传递。小数位数由shift参数指定,实点坐标的计算方式为 Point ( x , y ) → Point2f ( x ∗ 2 − s h i f t , y ∗ 2 − s h i f t ) texttt{Point}(x,y)rightarrowtexttt{Point2f}(x*2^{-shift},y*2^{-shift}) Point(x,y)→Point2f(x∗2−shift,y∗2−shift)渲染抗锯齿形状时,此功能特别有效。
注意当目标图像为4通道时,这些函数不支持alpha透明处理。在这种情况下,仅将color [3]复制到重新绘制的像素。因此,如果要绘制半透明的形状,则可以在单独的缓冲区中绘制它们,然后将其与主图像混合。
1、cv::arrowedLine、cv::line
1)cv::arrowedLine:绘制从第一个点指向第二个点的箭头线段。
void cv::arrowedLine(InputOutputArray img,Point pt1,Point pt2,const Scalar & color,int thickness = 1,int line_type = 8,int shift = 0,double tipLength = 0.1)
函数cv :: arrowedLine在图像的pt1和pt2点之间绘制箭头。
参数名称 | 参数描述. |
---|---|
img | 绘制的图像. |
pt1 | 起始点位置 |
pt2 | 箭头指向的点位置 |
color | 线条颜色 |
thickness | 线条厚度 |
line_type | 线条类型,请参考 LineTypes |
shift | 点坐标中的小数位数。 |
tipLength | 箭头笔尖的长度(相对于箭头长度) |
2、cv::circle
通过指定圆心位置和半径绘制圆。
void cv::circle(InputOutputArray img,Point center,int radius,const Scalar & color,int thickness = 1,int lineType = LINE_8,int shift = 0)
参数名称 | 参数描述 |
---|---|
img | 用于画圆的图像。 |
center | 圆心位置 |
radius | 半径 |
color | 颜色 |
thickness | 圆形轮廓的粗细(如果为正)。 负值,例如FILLED,表示要绘制实心圆。 |
lineType | 圆边界的类型。参考LineTypes |
shift | 中心坐标和半径值中的小数位数。 |
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
int main()
{
// 创建图像
cv::Mat image = cv::Mat::zeros(cv::Size(512,512),CV_8UC3);
// 绘制空心圆
cv::circle(image,cv::Point(image.cols/2,image.rows/2),100,cv::Scalar(255,0,0),2);
// 绘制实心圆
cv::circle(image,cv::Point(image.cols/2,image.rows/2),75,cv::Scalar(0,255,0),-1);
// 显示图像
cv::imshow("src",image);
cv::waitKey();
cv::destroyAllWindows();
return 0;
}
3、clipLine
判断一条直线是否在矩形内。
bool cv::clipLine(Size imgSize,Point & pt1,Point & pt2)
函数cv :: clipLine计算整个指定矩形内的线段的一部分。 如果线段完全在矩形外部,则返回false。 否则,它返回true。
参数名称 | 参数描述 |
---|---|
imgSize | 图像大小. 图像的矩形为 Rect(0, 0, imgSize.width, imgSize.height) . |
pt1 | 第一个点 |
pt2 | 第二个点 |
另外的重载函数如下:
- bool cv::clipLine(Rect imgRect,Point & pt1,Point & pt2)
- bool cv::clipLine(Size2l imgSize,Point2l & pt1,Point2l & pt2)
cv::Rect rect(0,0,200,100);
cv::Point p1(10,10);
cv::Point p2(203,105);
cout << "is line in rectangle?"<<cv::clipLine(rect,p1,p2);
4、cv::drawContours
绘制轮廓轮廓或填充轮廓。
void cv::drawContours(InputOutputArray image,InputArrayOfArrays contours,int contourIdx,const Scalar & color,int thickness = 1,int lineType = LINE_8,InputArray hierarchy = noArray(),int maxLevel = INT_MAX,Point offset = Point())
如果thickness ≥0,则该函数在图像中绘制轮廓轮廓;如果thickness <0,则该功能填充轮廓所包围的区域。
参数如下:
参数名称 | 参数说明 |
---|---|
image | 目标图像 |
contours | 所有输入轮廓。 每个轮廓都存储为点向量。 |
contourIdx | 指示要绘制轮廓的参数。 如果为负,则绘制所有轮廓。 |
color | 轮廓的颜色。 |
thickness | 绘制轮廓的线的粗细。 如果它是负数(例如,厚度= FILLED,则绘制轮廓内部。 |
lineType | 线条连接类型, 参考 LineTypes |
hierarchy | 有关层次结构的可选信息。 仅当只想绘制一些轮廓时才需要(请参见maxLevel)。 |
maxLevel | 绘制轮廓的最大水平。 如果为0,则仅绘制指定的轮廓。 如果为1,该函数将绘制轮廓和所有嵌套轮廓。 如果为2,该函数将绘制轮廓,所有嵌套轮廓,所有嵌套至嵌套的轮廓,等等。 仅当存在可用的层次结构时,才考虑此参数。 |
offset | 可选的轮廓偏移参数。 将所有绘制的轮廓移动指定的offset =(dx,dy)。 |
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
int main()
{
// 读取图像
cv::String fileName = "images/leaf.jpg";
cv::Mat src = cv::imread(fileName);
cv::Mat gray;
cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);
if(src.data == NULL){
printf("Image failed to readn");
return -1;
}
cv::imshow("src",src);
cv::Mat dst = cv::Mat::zeros(src.rows, src.cols, CV_8UC3);
vector<vector<cv::Point> > contours;
vector<cv::Vec4i> hierarchy;
// 阈值化图像
cv::threshold(gray, gray, 100, 255, cv::THRESH_BINARY);
// 查找轮廓
cv::findContours( gray, contours, hierarchy,
cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE );
cv::Scalar color(255,0,255);
cv::drawContours( dst, contours,-1, color);
// 显示图像
cv::imshow("src",src);
cv::imshow("dst",dst);
cv::waitKey();
cv::destroyAllWindows();
return 0;
}
5、cv::drawMarker
指定位置绘制标志。
void cv::drawMarker(InputOutputArray img,Point position,const Scalar & color,int markerType = MARKER_CROSS,int markerSize = 20,int thickness = 1,int line_type = 8)
函数cv :: drawMarker在图像的给定位置绘制标记。 目前支持几种标记类型,请参阅MarkerTypes了解更多信息。
类型名称 | 类型描述 |
---|---|
MARKER_CROSS | 十字准线标记形状。 |
MARKER_TILTED_CROSS | 45度倾斜的十字准线标记形状。 |
MARKER_STAR | 星形标记形状,十字形和倾斜十字形的组合。 |
MARKER_DIAMOND | 菱形标记形状。 |
MARKER_SQUARE | 方形标记形状。 |
MARKER_TRIANGLE_UP | 向上的三角形标记形状。 |
MARKER_TRIANGLE_DOWN | 指向下方的三角形标记形状。 |
参数如下:
参数名称 | 参数描述. |
---|---|
img | 绘制的目标图像 |
position | 十字准线的定位点。 |
color | 线条颜色 |
markerType | 标记类型,请参考 MarkerTypes |
thickness | 线条厚度 |
line_type | 线条类型,请参考 LineTypes |
markerSize | 标记轴的长度[默认= 20像素] |
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
int main()
{
// 创建图像
cv::Mat image(cv::Size(512,512),CV_8UC3);
cv::Point center(image.cols / 2,image.rows / 2);
// 绘制标记
cv::drawMarker(image,center,cv::Scalar(0,0,255),cv::MarkerTypes::MARKER_CROSS,40,3);
// 显示
cv::imshow("image",image);
cv::waitKey();
cv::destroyAllWindows();
return 0;
}
6、cv::eclipse
绘制简单或粗略的椭圆弧或填充椭圆扇形。
void cv::ellipse(InputOutputArray img,Point center,Size axes,double angle,double startAngle,double endAngle,const Scalar & color,int thickness = 1,int lineType = LINE_8,int shift = 0)
具有更多参数的函数cv :: ellipse会绘制椭圆轮廓,填充的椭圆,椭圆弧或填充的椭圆扇形。 绘图代码使用一般参数形式。 使用分段线性曲线来近似椭圆弧边界。 如果需要更多控制椭圆的渲染,则可以使用ellipse2Poly检索曲线,然后使用折线对其进行渲染或使用fillPoly对其进行填充。 如果您使用函数的第一个变体,并且想绘制整个椭圆而不是圆弧,请传递startAngle = 0和endAngle = 360。 如果startAngle大于endAngle,则会交换它们。 下图说明了绘制蓝色弧的参数的含义。
参数如下:
参数名称 | 参数描述 |
---|---|
img | 所要绘制的图像。 |
center | 椭圆中心 |
axes | 椭圆主轴尺寸的一半。 |
angle | 椭圆旋转角度,以度为单位。 |
startAngle | 椭圆弧的起始角度,以度为单位。 |
endAngle | 椭圆弧的终止角度,以度为单位。 |
color | 椭圆颜色 |
thickness | 椭圆弧轮廓的粗细(如果为正)。 否则,这表明将绘制一个填充的椭圆形扇区。 |
lineType | 椭圆边界线条样式。参考 LineTypes |
shift | 中心坐标和轴值中的小数位数。 |
另外的重载函数如下:
- void cv::ellipse(InputOutputArray img,const RotatedRect & box,const Scalar & color,int thickness = 1,int lineType = LINE_8)
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
int main()
{
// 创建图像
cv::Mat image(cv::Size(512,512),CV_8UC3);
// 中心点
cv::Point center(image.cols / 2 ,image.rows / 2);
// 绘制椭圆
cv::ellipse(image,center,cv::Size(image.cols / 2,image.rows / 4),
45,0,360,cv::Scalar( 0, 0, 255 ),3);
cv::ellipse(image,center,cv::Size(image.cols / 6,image.rows / 4),
45,0,360,cv::Scalar( 0, 255, 0 ),-1);
// 显示图像
cv::imshow("image",image);
cv::waitKey();
cv::destroyAllWindows();
return 0;
}
6、cv::ellipse2Poly
用折线近似椭圆弧。
void cv::ellipse2Poly(Point center,Size axes,int angle,int arcStart,int arcEnd,int delta,std::vector< Point > & pts)
函数ellipse2Poly计算近似于指定椭圆弧的折线的顶点。 由椭圆使用。 如果arcStart大于arcEnd,则将它们交换。
参数如下:
参数名称 | 参数描述 |
---|---|
center | 弧的中心。 |
axes | 椭圆主轴尺寸的一半。详细可以参考 ellipse 。 |
angle | 椭圆的旋转角度,以度为单位。详细可以参考 ellipse |
arcStart | 椭圆弧的起始角度,以度为单位。 |
arcEnd | 椭圆弧的终止角度,以度为单位。 |
delta | 后续折线顶点之间的角度。 它定义了近似精度。 |
pts | 折线顶点的输出向量。 |
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
int main()
{
// 创建图像
cv::Mat image(cv::Size(512,512),CV_8UC3);
// 中心点
cv::Point center(image.cols / 2 ,image.rows / 2);
vector<cv::Point2d> pts;
// 生成椭圆近似折线
cv::ellipse2Poly(center,cv::Size(image.cols/4,image.rows / 2),45,0,360,1,pts);
cout <<"point.size:"<< pts.size() << endl;
// 绘制
for(int i = 0;i < pts.size() - 1;i++){
cv::Point2d pt1 = pts[i];
cv::Point2d pt2 = pts[i + 1];
cv::line(image,pt1,pt2,cv::Scalar(0,0,255),3);
}
// 显示图像
cv::imshow("image",image);
cv::waitKey();
cv::destroyAllWindows();
return 0;
}
7、cv::fillConvexPoly
填充凸多边形。
void cv::fillConvexPoly (InputOutputArray img,InputArray points,const Scalar & color,int lineType = LINE_8,int shift = 0)
函数cv :: fillConvexPoly绘制一个填充的凸多边形。 该函数比函数fillPoly快得多。 它不仅可以填充凸多边形,而且可以填充任何不具有自相交的单调多边形,即,其轮廓与每个水平线(扫描线)相交的多边形最多两次(尽管其最顶部和/或底部边缘可能是 水平)。
参数如下:
参数名称 | 参数类型 |
---|---|
img | 绘制图像 |
points | 多边形顶点。 |
color | 多边形颜色 |
lineType | 多边形边界的线条类型. 参考LineTypes |
shift | 顶点坐标中的小数位数。 |
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
int main()
{
// 创建图像
cv::Mat image(cv::Size(512,512),CV_8UC3);
// 定义凸多边形顶点
cv::Point PointArray[4];
PointArray[0] = cv::Point(50,10);
PointArray[1] = cv::Point(300,12);
PointArray[2] = cv::Point(350,250);
PointArray[3] = cv::Point(9,250);
// 填充凸多边形。
cv::fillConvexPoly(image,PointArray,4,cv::Scalar(0,255,0));
// 显示图像
cv::imshow("image",image);
cv::waitKey();
cv::destroyAllWindows();
return 0;
}
8、cv::fillPoly
填充以一个或多个多边形为边界的区域。
void cv::fillPoly(InputOutputArray img,InputArrayOfArrays pts,const Scalar & color,int lineType = LINE_8,int shift = 0,Point offset = Point())
函数cv :: fillPoly填充由多个多边形轮廓所界定的区域。 该功能可以填充复杂区域,例如,带有孔的区域,具有自相交的轮廓(部分轮廓)等等。
参数如下:
参数名称 | 参数描述 |
---|---|
img | 绘制图像。 |
pts | 多边形数组,其中每个多边形都表示为点数组。 |
color | 多边形颜色。 |
lineType | 多边形边界的线条类型. 参考LineTypes |
shift | 顶点坐标中的小数位数。 |
offset | 轮廓所有点的可选偏移量。 |
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
// 创建图像
cv::Mat image(cv::Size(512,512),CV_8UC3);
// 多边形顶点
Point pts[1][6];
pts[0][0] = Point(100, 100);
pts[0][1] = Point(100, 200);
pts[0][2] = Point(150, 250);
pts[0][3] = Point(200, 200);
pts[0][4] = Point(200, 100);
pts[0][5] = Point(100, 100);
const Point* ppts[] = { pts[0] }; //指针
int npt[] = { 6 };
Scalar color = Scalar(255, 0, 0);
// 填充多边形
fillPoly(image, ppts, npt, 1, color, 8);
// 显示图像
cv::imshow("image",image);
cv::waitKey();
cv::destroyAllWindows();
return 0;
}
9、cv::polylines
绘制多条多边形曲线。
void cv::polylines(InputOutputArray img,InputArrayOfArrays pts,bool isClosed,const Scalar & color,int thickness = 1,int lineType = LINE_8,int shift = 0)
参数名称 | 参数描述 |
---|---|
img | 所要绘制的图像 |
pts | 多边形曲线数组。 |
isClosed | 指示绘制的折线是否闭合的标志。 如果它们是闭合的,则该函数将从每个曲线的最后一个顶点到其第一个顶点绘制一条直线。 |
color | 折线颜色。 |
thickness | 折线边缘的厚度。 |
lineType | 线段的类型。请参考 LineTypes |
shift | 顶点坐标中的小数位数。 |
#include <opencv2/opencv.hpp>
using namespace std;
int main(void)
{
// 创建图像
cv::Mat image(512, 512, CV_8UC3);
// 多边形顶点
vector<cv::Point> p;
int n = 5;
for (int i = 0; i < n; i++) {
p.push_back(cv::Point(cos(CV_PI * (2.0*i/n - 0.5)) * 180 + 200, sin(CV_PI * (2.0*i/n - 0.5)) * 180 + 200));
}
// 绘制多边形
cv::polylines(image, p, true, cv::Scalar(255, 0, 0), 2, cv::LINE_AA);
p.clear();
for (int i = 0; i < n; i++) {
p.push_back(cv::Point(cos(CV_PI * (2.0*i/n - 0.5)) * 140 + 200, sin(CV_PI * (2.0*i/n - 0.5)) * 140 + 200));
}
// 填充多边形
cv::fillConvexPoly(image, p, cv::Scalar(0, 0, 255), cv::LINE_AA);
cv::imshow("image", image);
vector<vector<cv::Point>> ps(16);
for (int n = 0; n < 16; n++) {
cv::Point p0((n % 4) * 100 + 50, (n / 4) * 100 + 50);
for (int i = 0; i < n + 3; i++) {
cv::Point pa(cos(CV_PI * (2.0*i/(n+3) - 0.5)) * 40, sin(CV_PI * (2.0*i/(n+3) - 0.5)) * 40);
ps[n].push_back(p0 + pa);
}
}
image = cv::Scalar(255, 255, 255);
cv::fillPoly(image, ps, cv::Scalar(0, 255, 255), cv::LINE_AA);
cv::polylines(image, ps, true, cv::Scalar(0, 0, 0), 3, cv::LINE_AA);
cv::imshow("image2", image);
cv::waitKey();
cv::destroyAllWindows();
return 0;
}
10、cv::getFontScaleFromHeight、cv::getTextSize、cv::putText
1)cv::getFontScaleFromHeight:计算用于实现给定高度(以像素为单位)的特定于字体的大小。
double cv::getFontScaleFromHeight(const int fontFace,const int pixelHeight,const int thickness = 1)
参数名称 | 参数描述 |
---|---|
fontFace | 使用的字体, 请参考 cv::HersheyFonts. |
pixelHeight | 像素高度,用于计算fontScale |
thickness | 用于渲染文本的线的粗细。有关详细信息,请参见putText。 |
2)cv::getTextSize:计算文本字符串的宽度和高度(包含指定文本的框的大小)。
Size cv::getTextSize(const String & text,int fontFace,double fontScale,int thickness,int * baseLine)
参数如下:
参数名称 | 参数描述 | |
---|---|---|
text | 所要绘制的图像 | |
fontFace | 所要使用的字体。 请参考 HersheyFonts. | |
fontScale | 字体比例因子乘以特定于字体的基本大小。 | |
thickness | 用于渲染文本的线的粗细。 请详细参考 putText | |
[out] | baseLine | 基线相对于最底下的文本点的y坐标。 |
函数cv :: getTextSize计算并返回包含指定文本的框的大小。 也就是说,以下代码呈现了一些文本,其周围的紧实框和基线:
String text = "Funny text inside the box";
int fontFace = FONT_HERSHEY_SCRIPT_SIMPLEX;
double fontScale = 2;
int thickness = 3;
Mat img(600, 800, CV_8UC3, Scalar::all(0));
int baseline=0;
Size textSize = getTextSize(text, fontFace,
fontScale, thickness, &baseline);
baseline += thickness;
// center the text
Point textOrg((img.cols - textSize.width)/2,
(img.rows + textSize.height)/2);
// draw the box
rectangle(img, textOrg + Point(0, baseline),
textOrg + Point(textSize.width, -textSize.height),
Scalar(0,0,255));
// ... and the baseline first
line(img, textOrg + Point(0, thickness),
textOrg + Point(textSize.width, thickness),
Scalar(0, 0, 255));
// then put the text itself
putText(img, text, textOrg, fontFace, fontScale,
Scalar::all(255), thickness, 8);
3)cv::putText:绘制文本字符串。
void cv::putText(InputOutputArray img,const String & text,Point org,int fontFace,double fontScale,Scalar color,int thickness = 1,int lineType = LINE_8,bool bottomLeftOrigin = false)
函数cv :: putText在图像中呈现指定的文本字符串。 无法使用指定字体呈现的符号将替换为问号。 有关文本呈现代码示例,请参见getTextSize。
参数如下:
参数名称 | 参数描述 |
---|---|
img | 绘制的图像. |
text | 气概绘制的文本 |
org | 图像中文本字符串的左下角。 |
fontFace | 所要使用的字体。 请参考 HersheyFonts. |
fontScale | 字体比例因子乘以特定于字体的基本大小。 |
color | 文本颜色 |
thickness | 用于绘制文本的线条的粗细。 |
lineType | 线条类型,请参考LineTypes |
bottomLeftOrigin | 如果为true,则图像数据原点位于左下角。 否则,它位于左上角。 |
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
int main()
{
//创建空白图用于绘制文字
cv::Mat image = cv::Mat::zeros(cv::Size(640, 480), CV_8UC3);
//设置蓝色背景
image.setTo(cv::Scalar(100, 0, 0));
//设置绘制文本的相关参数
std::string text = "Hello World!";
int font_face = cv::FONT_HERSHEY_COMPLEX;
double font_scale = 2;
int thickness = 2;
int baseline;
//获取文本框的长宽
cv::Size text_size = cv::getTextSize(text, font_face, font_scale, thickness, &baseline);
//将文本框居中绘制
cv::Point origin;
origin.x = image.cols / 2 - text_size.width / 2;
origin.y = image.rows / 2 - text_size.height / 2;
cv::putText(image, text, origin, font_face, font_scale, cv::Scalar(0, 255, 255), thickness, 8, 0);
//显示绘制解果
cv::imshow("image", image);
cv::waitKey(0);
return 0;
}
11、cv::rectangle
绘制一个简单的,粗的或实心的直角矩形。
void cv::rectangle(InputOutputArray img,Point pt1,Point pt2,const Scalar & color,int thickness = 1,int lineType = LINE_8,int shift = 0)
函数cv :: rectangle绘制一个矩形轮廓或一个填充的矩形,其两个相对角为pt1和pt2。
参数如下:
参数名称 | 参数描述 |
---|---|
img | 所要绘制的图像. |
pt1 | 矩形的顶点。 |
pt2 | 与pt1相反的矩形的顶点。 |
color | 矩形的颜色或亮度(灰度图像)。 |
thickness | 组成矩形的线的粗细。 负值,例如FILLED,表示该函数必须绘制一个填充的矩形。 |
lineType | 线条类型,请参考LineTypes |
shift | 点坐标中的小数位数。 |
其他重载函数如下:
- void cv::rectangle (InputOutputArray img,Rect rec,const Scalar & color,int thickness = 1,int lineType = LINE_8,int shift = 0)
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
int main()
{
//创建图像
cv::Mat image = cv::Mat::zeros(cv::Size(640, 480), CV_8UC3);
// 绘制空心矩形
cv::rectangle(image,cv::Point(100,100),cv::Point(300,300),cv::Scalar(0,255,0),3);
// 填充矩形
cv::rectangle(image,cv::Point(120,120),cv::Point(280,280),cv::Scalar(0,0,255),-1);
//显示绘制
cv::imshow("image", image);
cv::waitKey();
cv::destroyAllWindows();
return 0;
}
最后
以上就是寂寞美女为你收集整理的OpenCV 4.x API 详解与C++实例-绘图函数第四节 绘图函数的全部内容,希望文章能够帮你解决OpenCV 4.x API 详解与C++实例-绘图函数第四节 绘图函数所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复