其中对条码与二维码的识别分为以下4个步骤

1. 利用opencv和Zbar(或者Zxing)对标准的条形码图片(即没有多余背景干扰,且图片没有倾斜)进行解码,将解码信息显示出来,并与原始信息对比。

2. 利用opencv和Zbar(或者Zxing)对标准的QR二维码图片(即没有多余背景干扰,且图片没有倾斜)进行解码,将解码信息显示出来,并与原始信息对比。

3. 对非标准条形码,进行定位,然后用Zbar(或者Zxing)解码显示。

4. 对非标准的QR二维码图片,进行定位,然后用Zbar(或者Zxing)解码显示。

1. 利用opencv和Zbar(或者Zxing)对标准的条形码图片(即没有多余背景干扰,且图片没有倾斜)进行解码,将解码信息显示出来,并与原始信息对比。

2. 利用opencv和Zbar(或者Zxing)对标准的QR二维码图片(即没有多余背景干扰,且图片没有倾斜)进行解码,将解码信息显示出来,并与原始信息对比。

这两部对于zbar可以一并操作。

操作步骤主要分为两部分:A.原图进行灰度转化,B.送入Zbar扫描仪进行扫描(调用ImageScanner)

源码如下:

 /******************************************************
函数名称: Dis_Barcode
函数功能: 识别条形码和二维码
传入参数:
返 回 值:
建立时间: 2018-05-19
修改时间:
建 立 人:
修 改 人:
其它说明:这里是借鉴其他人的代码:
原文链接:https://www.cnblogs.com/dengxiaojun/p/5278679.html
以下代码是经过改动的
******************************************************/
void MyClass::Dis_code(Mat image){
Mat imageGray; // 所转化成的灰度图像
//定义一个扫描仪
ImageScanner scanner;
scanner.set_config(ZBAR_NONE, ZBAR_CFG_ENABLE, ); cvtColor(image, imageGray, CV_RGB2GRAY);
imshow("灰度图", imageGray);
// 获取所摄取图像的长和宽
int width = imageGray.cols;
int height = imageGray.rows;
// 在Zbar中进行扫描时候,需要将OpenCV中的Mat类型转换为(uchar *)类型,raw中存放的是图像的地址;对应的图像需要转成Zbar中对应的图像zbar::Image
uchar *raw = (uchar *)imageGray.data;
Image imageZbar(width, height, "Y800", raw, width * height);
// 扫描相应的图像imageZbar(imageZbar是zbar::Image类型,存储着读入的图像)
scanner.scan(imageZbar); //扫描条码
Image::SymbolIterator symbol = imageZbar.symbol_begin();
if (imageZbar.symbol_begin() == imageZbar.symbol_end())
{
cout << "查询条码失败,请检查图片!" << endl;
}
for (; symbol != imageZbar.symbol_end(); ++symbol)
{
cout << "类型:" << endl << symbol->get_type_name() << endl << endl;
cout << "条码:" << endl << symbol->get_data() << endl << endl;
} waitKey(); // 等待按下esc键,若需要延时1s则改用waitKey(1000); // 将图像中的数据置为0
imageZbar.set_data(NULL, );
system("pause");
}

结果如下:

条形码识别:

二维码识别:

3. 对非标准条形码,进行定位,然后用Zbar(或者Zxing)解码显示

在条形码的识别上,根据条形码的特性,我们只关心x轴上的形态。通过x轴的宽度进行确定条码的大小,y轴根据实际提取进行区分

处理的目标:

A.消去非码的其他物体图形

B.划定条码的范围

C.提取图片的ROI区域(即条码区域)

总体分为:

灰度处理-》高斯平滑-》Sobel x—y梯度差-》均值滤波-》二值化-》闭运算-》腐蚀膨胀-》获取ROI

 /******************************************************
函数名称: Run
函数功能: 开始
传入参数:
返 回 值:
建立时间: 2018-05-19
修改时间:
建 立 人:
修 改 人:
其它说明:
******************************************************/
void MyClass::Run(){
Mat image;
image = getGray(srcimage);//获取灰度图
image = getGass(image);//高斯平滑滤波
image = getSobel(image);//Sobel x—y梯度差
image = getBlur(image);//均值滤波除高频噪声
image = getThold(image);//二值化
image = getBys(image);//闭运算
image = getErode(image);//腐蚀
image = getDilate(image);//膨胀
image = getRect(image, srcimage);//获取ROI
imshow("最后的图", image);
Dis_code(image);
waitKey();
}

灰度处理(消除颜色干扰)

 /******************************************************
函数名称: getGray
函数功能: 灰度处理
传入参数: Mat image
返 回 值:
建立时间: 2018-05-19
修改时间:
建 立 人:
修 改 人:
其它说明:
******************************************************/
Mat MyClass::getGray(Mat image, bool show){//show默认false 待定参数法
Mat cimage;
cvtColor(image, cimage, CV_RGBA2GRAY);
if (show)
imshow("灰度图", cimage);
return cimage;
}

处理结果:

高斯滤波处理(消除高斯噪声)

 /******************************************************
函数名称: getGass
函数功能: 高斯滤波处理
传入参数: Mat image
返 回 值:
建立时间: 2018-05-19
修改时间:
建 立 人:
修 改 人:
其它说明:
******************************************************/
Mat MyClass::getGass(Mat image, bool show){
Mat cimage;
GaussianBlur(image, cimage, Size(, ), );
if (show)
imshow("高斯滤波图", cimage);
return cimage;
}
 

处理结果:

Sobel x-y差处理(只考虑x轴,消除y轴不必要信息)

 /******************************************************
函数名称: getSobel
函数功能: Sobel处理
传入参数: Mat image
返 回 值:
建立时间: 2018-05-19
修改时间:
建 立 人:
修 改 人:
其它说明:
******************************************************/
Mat MyClass::getSobel(Mat image, bool show){
Mat cimageX16s, cimageY16s, imageSobelX, imageSobelY, out;
Sobel(image, cimageX16s, CV_16S, , , , , , );
Sobel(image, cimageY16s, CV_16S, , , , , , );
convertScaleAbs(cimageX16s, imageSobelX, , );
convertScaleAbs(cimageY16s, imageSobelY, , );
out = imageSobelX - imageSobelY;
if (show)
imshow("Sobelx-y差 图", out);
return out;
}
 

处理结果:

均值滤波处理(消除高频噪声)

 /******************************************************
函数名称: getBlur
函数功能: 均值滤波处理
传入参数: Mat image
返 回 值:
建立时间: 2018-05-19
修改时间:
建 立 人:
修 改 人:
其它说明:
******************************************************/
Mat MyClass::getBlur(Mat image, bool show){
Mat cimage;
blur(image, cimage, Size(, ));
if (show)
imshow("均值滤波图", cimage);
return cimage;
}
 

处理结果:

二值化处理(使图像中数据量大为减少,从而能凸显出目标的轮廓)

 /******************************************************
函数名称: getThold
函数功能: 二值化处理
传入参数: Mat image
返 回 值:
建立时间: 2018-05-19
修改时间:
建 立 人:
修 改 人:
其它说明:
******************************************************/
Mat MyClass::getThold(Mat image, bool show){
Mat cimage;
threshold(image, cimage, , , CV_THRESH_BINARY);
if (show)
imshow("二值化图", cimage);
return cimage;
}
 

处理结果:

闭运算处理(扩大轴之间的间隙)

 /******************************************************
函数名称: getBys
函数功能: 闭运算处理
传入参数: Mat image
返 回 值:
建立时间: 2018-05-19
修改时间:
建 立 人:
修 改 人:
其它说明:
******************************************************/
Mat MyClass::getBys(Mat image, bool show){
morphologyEx(image, image, MORPH_CLOSE, element);
if (show)
imshow("闭运算图", image);
return image;
}

处理结果:

腐蚀膨胀(消去干扰点和合并条码区域)

 /******************************************************
函数名称: getErode
函数功能: 腐蚀处理
传入参数: Mat image
返 回 值:
建立时间: 2018-05-19
修改时间:
建 立 人:
修 改 人:
其它说明:
******************************************************/
Mat MyClass::getErode(Mat image, bool show){
//Mat cimage;
erode(image, image, element);
if (show)
imshow("腐蚀图", image);
return image;
}
/******************************************************
函数名称: getDilate
函数功能: 膨胀处理
传入参数: Mat image
返 回 值:
建立时间: 2018-05-19
修改时间:
建 立 人:
修 改 人:
其它说明:
******************************************************/
Mat MyClass::getDilate(Mat image, bool show){
for (int i = ; i < ; i++)
dilate(image, image, element);
if (show)
imshow("膨胀图", image);
return image;
}

处理结果:

获取ROI(为Zbar处理作预处理)

 /******************************************************
函数名称: getRect
函数功能: 获取码的区域
传入参数: Mat image, Mat simage原图
返 回 值:
建立时间: 2018-05-19
修改时间:
建 立 人:
修 改 人:
其它说明:借鉴其他人进行改进
******************************************************/
Mat MyClass::getRect(Mat image, Mat simage, bool show){
vector<vector<Point>> contours;
vector<Vec4i> hiera;
Mat cimage;
findContours(image, contours, hiera, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
vector<float>contourArea;
for (int i = ; i < contours.size(); i++)
{
contourArea.push_back(cv::contourArea(contours[i]));
}
//找出面积最大的轮廓
double maxValue; Point maxLoc;
minMaxLoc(contourArea, NULL, &maxValue, NULL, &maxLoc);
//计算面积最大的轮廓的最小的外包矩形
RotatedRect minRect = minAreaRect(contours[maxLoc.x]);
//为了防止找错,要检查这个矩形的偏斜角度不能超标
//如果超标,那就是没找到
if (minRect.angle<2.0)
{
//找到了矩形的角度,但是这是一个旋转矩形,所以还要重新获得一个外包最小矩形
Rect myRect = boundingRect(contours[maxLoc.x]);
//把这个矩形在源图像中画出来
//rectangle(srcImage,myRect,Scalar(0,255,255),3,LINE_AA);
//看看显示效果,找的对不对
//imshow(windowNameString,srcImage);
//将扫描的图像裁剪下来,并保存为相应的结果,保留一些X方向的边界,所以对rect进行一定的扩张
myRect.x = myRect.x - (myRect.width / );
myRect.width = myRect.width*1.1;
Mat resultImage = Mat(srcimage, myRect);
return resultImage;
} for (int i = ; i<contours.size(); i++)
{
Rect rect = boundingRect((Mat)contours[i]);
//cimage = simage(rect);
rectangle(simage, rect, Scalar(), );
if (show)
imshow("转变图", simage);
}
return simage;
}

处理结果:

最后识别处理结果:

4. 对非标准的QR二维码图片,进行定位,然后用Zbar(或者Zxing)解码显示。

这里主要参考https://blog.csdn.net/nick123chao/article/details/77573675的博客。不过该博客的处理没有考虑多个识别点时的情况:

例图:

本文主要处理去除干扰的识别点的方向进行研究解决。根据二维码特性:

我们只要找到90°±Δx的角,且夹角两边为最小的边即可。

找到三个点后,我们需要对齐做旋转处理,旋转的角度如下:

其中处理的步骤分为:

灰度处理-》边缘检测-》特征轮廓检测-》提取特征点-》排除干扰点-》绘制直角三角形-》纠正旋转-》提取ROI-》识别

这里先给效果,后展示代码

边缘检测:

特征轮廓检测

提取特征点-》排除干扰点-》绘制直角三角形

纠正旋转

提取ROI

识别

源码如下:

 /******************************************************
函数名称: QrRun
函数功能: 开始
传入参数:
返 回 值:
建立时间: 2018-05-19
修改时间:
建 立 人:
修 改 人:
其它说明:
******************************************************/
void MyClass::QrRun(){
RNG rng();
//imshow("原图", srcimage);
Mat src_all = srcimage.clone();
Mat src_gray;
//灰度处理
src_gray = getBlur(getGray(srcimage)); Scalar color = Scalar(, , );
Mat threshold_output;
vector<vector<Point> > contours, contours2;
vector<Vec4i> hierarchy;
Mat drawing = Mat::zeros(srcimage.size(), CV_8UC3);
Mat drawing2 = Mat::zeros(srcimage.size(), CV_8UC3);
Mat drawingAllContours = Mat::zeros(srcimage.size(), CV_8UC3); threshold_output = getThold(src_gray); findContours(threshold_output, contours, hierarchy, CV_RETR_TREE, CHAIN_APPROX_NONE, Point(, )); int c = , ic = , k = , area = ;
// 边缘检测
//通过黑色定位角作为父轮廓,有两个子轮廓的特点,筛选出三个定位角
int parentIdx = -;
for (int i = ; i< contours.size(); i++)
{
//画出所以轮廓图
drawContours(drawingAllContours, contours, parentIdx, CV_RGB(, , ), , );
if (hierarchy[i][] != - && ic == )
{
parentIdx = i;
ic++;
}
else if (hierarchy[i][] != -)
{
ic++;
}
else if (hierarchy[i][] == -)
{
ic = ;
parentIdx = -;
}
//特征轮廓检测 - 》
//有两个子轮廓
if (ic >= )
{
//保存找到的三个黑色定位角
contours2.push_back(contours[parentIdx]);
//画出三个黑色定位角的轮廓
drawContours(drawing, contours, parentIdx, CV_RGB(rng.uniform(, ), rng.uniform(, ), rng.uniform(, )), , );
ic = ;
parentIdx = -;
}
}
//提取特征点
//填充的方式画出黑色定位角的轮廓
for (int i = ; i<contours2.size(); i++)
drawContours(drawing2, contours2, i, CV_RGB(rng.uniform(, ), rng.uniform(, ), rng.uniform(, )), -, , hierarchy[k][], , Point()); //获取定位角的中心坐标
vector<Point> pointfind;
for (int i = ; i<contours2.size(); i++)
{
pointfind.push_back(Center_cal(contours2, i));
}
//排除干扰点
Mat dst;
Point point[]; double angle; Mat rot_mat;
///选择合适的点-核心筛选
if (pointfind.size()>){
double lengthA = , lengthB = ;
for (int i = ; i < pointfind.size(); i++){
for (int j = ; j < pointfind.size(); j++){
for (int k = ; k < pointfind.size(); k++){
if (i != j&&j != k&&i != k){
double dxa, dxb,dya,dyb;
double k1, k2, wa, wb;
dxa = pointfind[i].x - pointfind[j].x;
dxb = pointfind[i].x - pointfind[k].x;
dya = pointfind[i].y - pointfind[j].y;
dyb = pointfind[i].y - pointfind[k].y;
if (dxa == || dxb == )continue;
k1 = dya/dxa;
k2 = dyb/dxb ;
wa = sqrt(pow(dya, ) + pow(dya, ));
wb = sqrt(pow(dyb, ) + pow(dxb, ));
double anglea = abs(atan(k1) * / CV_PI) + abs(atan(k2) * / CV_PI);
if (int(anglea)>=&&int(anglea)<=&&wa<=lengthA&&wb<=lengthB){
lengthA = wa;
lengthB = wb;
point[] = pointfind[i];
point[] = pointfind[j];
point[] = pointfind[k];
}
}
}
}
}
}
else{
for (int i = ; i < ; i++){
point[i] = pointfind[i];
}
}
//绘制直角三角形
//计算轮廓的面积,计算定位角的面积,从而计算出边长
area = contourArea(contours2[]);
int area_side = cvRound(sqrt(double(area)));
for (int i = ; i < ; i++){
line(drawing2, point[i], point[(i + )%], color, area_side / , );
} //纠正旋转
//判断是否正对
if (!IsCorrect(point)){
//进入修正环节
double angle; Mat rot_mat;
int start = ;
for (int i = ; i < ; i++){
double k1, k2,kk;
k1 = (point[i].y - point[(i + ) % ].y) / (point[i].x - point[(i + ) % ].x);
k2 = (point[i].y - point[(i + ) % ].y) / (point[i].x - point[(i + ) % ].x);
kk = k1*k2;
if (k1*k2 <)
start = i;
}
double ax, ay, bx, by;
ax = point[(start + ) % ].x;
ay = point[(start + ) % ].y;
bx = point[(start + ) % ].x;
by = point[(start + ) % ].y;
Point2f center(abs(ax - bx) / , abs(ay -by)/ );
double dy = ay - by;
double dx = ax - bx;
double k3 = dy / dx;
angle =atan(k3) * / CV_PI;//转化角度
rot_mat = getRotationMatrix2D(center, angle, 1.0); warpAffine(src_all, dst, rot_mat, src_all.size(), , , );//旋转原图查看
warpAffine(drawing2, drawing2, rot_mat, src_all.size(), , , );//旋转连线图
warpAffine(src_all, src_all, rot_mat, src_all.size(), , , );//旋转原图 namedWindow("Dst");
imshow("Dst", dst);
} namedWindow("DrawingAllContours");
imshow("DrawingAllContours", drawingAllContours); namedWindow("Drawing2");
imshow("Drawing2", drawing2); namedWindow("Drawing");
imshow("Drawing", drawing); //提取ROI
//接下来要框出这整个二维码
Mat gray_all, threshold_output_all;
vector<vector<Point> > contours_all;
vector<Vec4i> hierarchy_all;
cvtColor(drawing2, gray_all, CV_BGR2GRAY); threshold(gray_all, threshold_output_all, , , THRESH_BINARY);
findContours(threshold_output_all, contours_all, hierarchy_all, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point(, ));//RETR_EXTERNAL表示只寻找最外层轮廓 Point2f fourPoint2f[];
//求最小包围矩形
RotatedRect rectPoint = minAreaRect(contours_all[]);//pointfind.size()-3 //将rectPoint变量中存储的坐标值放到 fourPoint的数组中
rectPoint.points(fourPoint2f); int maxx = , maxy = , minx = , miny = ;
for (int i = ; i < ; i++)
{
if (maxx < fourPoint2f[i].x)maxx = fourPoint2f[i].x;
if (maxy < fourPoint2f[i].y)maxy = fourPoint2f[i].y;
if (minx > fourPoint2f[i].x)minx = fourPoint2f[i].x;
if (miny > fourPoint2f[i].y)miny = fourPoint2f[i].y;
line(src_all, fourPoint2f[i % ], fourPoint2f[(i + ) % ]
, Scalar(), );
}
namedWindow("Src_all");
///边际处理
int set_inter = ;
while (true)
{
minx -= set_inter;
miny -= set_inter;
maxx += set_inter;
maxy += set_inter;
if (maxx > srcimage.size().width || maxy > srcimage.size().height || minx < || miny < ){
minx += set_inter;
miny += set_inter;
maxx -= set_inter;
maxy -= set_inter;
set_inter--;
}
else
{
break;
}
}
imshow("Src_all", src_all(Rect(minx, miny, maxx - minx, maxy - miny)));//ROI
Mat fout = src_all(Rect(minx, miny, maxx - minx, maxy - miny));//ROI //识别
Dis_code(fout); waitKey();
destroyAllWindows();
}

由于在解码上是采用其他人的方法,存在解码问题。(到时候有机会自己再写下)

ps:目前的zbar不支持中文识别,但是zxing可以。所以借鉴本文的需要改进下识别的模块即可。

本文的不足之处:

这里还做了测试,对于旋转180°以上的二维码图片存在可能无法识别的问题。以及码眼为非正方形的也无法识别。

如需要源码请转移至码云:https://gitee.com/cjqbaba/MediaTest/tree/Code_Find进行源码克隆下载

如有问题请留言评论。转载请注明出处,谢谢。

基于opencv3.0和下的条形码与二维码识别的更多相关文章

  1. 实例源码--ZXing识别条形码和二维码识别源码

      下载源码 技术要点: 1.ZXing库的 使用 2.识别条形码和二 维码 3.自定义视图 4.源码带有非常详 细的中文注释 ...... 详细介绍: 1.ZXing库 ZXing是个很经典的条码/ ...

  2. (整理).net实现条形码与二维码

    本文由来源网络的知识点组合而成,感谢分享的作者,文章结尾处给出查询资料连接. 条形码(barcode)是将宽度不等的多个黑条和空白,按照一定的编码规则排列,用以表达一组信息的图形标识符.常见的条形码是 ...

  3. asp.net 生成、解析条形码和二维码

    原文 asp.net 生成.解析条形码和二维码 一.条形码 一维码,俗称条形码,广泛的用于电子工业等行业.比如我们常见的书籍背面就会有条形码,通过扫描枪等设备扫描就可以获得书籍的ISBN(Intern ...

  4. 有关python下二维码识别用法及识别率对比分析

    最近项目中用到二维码图片识别,在python下二维码识别,目前主要有三个模块:zbar .zbarlight.zxing. 1.三个模块的用法: #-*-coding=utf-8-*- import ...

  5. Opencv+Zbar二维码识别(标准条形码/二维码识别)

    使用Opencv+Zbar组合可以很容易的识别图片中的二维码,特别是标准的二维码,这里标准指的是二维码成像清晰,图片中二维码的空间占比在40%~100%之间,这样标准的图片,Zbar识别起来很容易,不 ...

  6. Android之条形码、二维码扫描框架(非原创)

    文章大纲 一.条形码.二维码扫描框架介绍二.条形码.二维码的区别和组成结构介绍三.条形码.二维码扫描框架应用场景四.BGAQRCode-Android框架实战五.项目源码下载六.参考文章 一.条形码. ...

  7. Java 创建/识别条形码、二维码

    条形码(Barcode)是将宽度不等的多个黑条和空白,按照一定的编码规则排列,用以表达一组信息的图形标识符.常用于标示物品的生产国.制造厂家.商品名称.生产日期.图书分类号.邮件起止地点.类别.日期等 ...

  8. [转]用C#实现的条形码和二维码编码解码器

    条形码的标准: 条形码的标准有ENA条形码.UPC条形码.二五条形码.交叉二五条形码.库德巴条形码.三九条形码和128条形码等,而商品上最常使用的就是EAN商品条形码.EAN商品条形码亦称通用商品条形 ...

  9. 【转】Android平台下利用zxing实现二维码开发

    http://www.cnblogs.com/dolphin0520/p/3355728.html 现在走在大街小巷都能看到二维码,而且最近由于项目需要,所以研究了下二维码开发的东西,开源的二维码扫描 ...

随机推荐

  1. 关于getch()函数

    从百度上得知: 这个函数是一个不回显函数,当用户按下某个字符时,函数自动读取,无需按回车,有的C语言命令行程序会用到此函数做游戏,但是这个函数并非标准函数,要注意移植性! 所以有这样的一个接口,那就很 ...

  2. Github搜索技巧-如何使用github找到自己感兴趣的项目

    Github现在不仅仅作为一个版本控制工具,更是一个开源的仓库,里面不但有优秀的开源代码,电子书,还有一些五花八门的项目,有些国家的法律也放在上面,作为程序员如何找到自己感兴趣的项目就非常重要了! 欢 ...

  3. 用shell脚本挂载linux主机拷贝相应文件

    #!/bin/sh TARGETIP=192.168.88.3 #这里是你要挂在的ftp服务器的IP地址 MOUNTDIR=/mnt TARGETDIR=/root/Desktop/Work ERRO ...

  4. How to configure ODBC DSN in Client to access remote DB2 for Windows

      How to configure ODBC DSN in Client to access remote DB2 for Windows MA Gen feng (Guangdong Unito ...

  5. AMDP + XLSX Workbench 报表开发模式

    本文介绍了我和同事通过使用AMDP + XLSX Workbench缩短报表开发周期.分离数据查询处理逻辑和前端展示工作的经验.欢迎讨论. 前言 最近接到了一套人力资源报表的开发需求,需要以EXCEL ...

  6. 转载一篇makefile,说的很详细

    March 3, 2015 8:19 PM 原文见:https://www.cnblogs.com/OpenShiFt/p/4313351.html Makefile 文件的编写 学习前的准备 需要准 ...

  7. FFmpeg and x264 Encoding Guide

    https://trac.ffmpeg.org/wiki/Encode/H.264 FFmpeg and H.264 Encoding Guide Contents Constant Rate Fac ...

  8. vfd折腾(一)

    从一开始驱动一块翻出来的液晶显示屏就想做一个电子时钟,偶然翻到了vfd(Vacuum Fluorescent Display的缩写,意为真空荧光显示屏). 此后就走上了不归路

  9. Java 面试知识点解析(一)——基础知识篇

    前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大 ...

  10. SOFA 源码分析 — 自定义线程池原理

    前言 在 SOFA-RPC 的官方介绍里,介绍了自定义线程池,可以为指定服务设置一个独立的业务线程池,和 SOFARPC 自身的业务线程池是隔离的.多个服务可以共用一个独立的线程池. API使用方式如 ...