• 基于OpenCV读取摄像头进行人脸检测和人脸识别


    前段时间使用OpenCV的库函数实现了人脸检测和人脸识别,笔者的实验环境为VS2010+OpenCV2.4.4,OpenCV的环境配置网上有很多,不再赘述。检测的代码网上很多,记不清楚从哪儿copy的了,识别的代码是从OpenCV官网上找到的:http://docs.opencv.org/trunk/modules/contrib/doc/facerec/facerec_api.html

    需要注意的是,opencv的FaceRecogizer目前有三个类实现了它,特征脸和fisherface方法最少训练图像为两张,而LBP可以单张图像训练。本人的实验采用的图片是100x100大小的,所以如果要添加自己的图像进行识别的话务必调整为100x100,不然会报错。当然在recog_and_draw这个函数里,笔者也将每次检测到的人脸进行了保存,拖出来重命名就可以,路径自己找吧。使用不同的方法识别时,其阈值设置也不同,LBP大概在100,其他两种方法大概在1000。本人的代码已共享,下载链接:http://download.csdn.net/detail/u010944555/6749725

    ps:有人说代码的检测率不高,其实可以归结为两方面的原因,第一人脸检测率不高,这个可以通过嵌套检测嘴角、眼睛等来降低,或者背景、光照固定的话可以通过图像差分来解决;第二是识别方法本身的问题,如果想提高识别率,可以添加多张不同姿态、光照下的人脸作为训练的样本,如果有时间的话可以在采集图像时给出一个人脸框,引导用户对齐人脸进行采集,三星手机解除锁屏就有这么一个功能。

    效果图:

    废话不多说,上传代码。

    main:

    1. #include "stdafx.h"  
    2. #include "cv.h"  
    3. #include "highgui.h"  
    4. #include <stdio.h>  
    5. #include <stdlib.h>  
    6. #include <string.h>  
    7. #include <assert.h>  
    8. #include <math.h>  
    9. #include <float.h>  
    10. #include <limits.h>  
    11. #include <time.h>  
    12. #include <ctype.h>  
    13. #include <opencv2contribcontrib.hpp>    
    14. #include <opencv2corecore.hpp>    
    15. #include <opencv2highguihighgui.hpp>   
    16. #include <iostream>  
    17. #include <fstream>  
    18. #include <sstream>  
    19. #include "detect_recog.h"  
    20.   
    21. using namespace std;  
    22. using namespace cv;  
    23. #ifdef _EiC  
    24. #define WIN32  
    25. #endif  
    26.   
    27. CvMemStorage* storage = 0;  
    28. CvHaarClassifierCascade* cascade = 0;  
    29. CvHaarClassifierCascade* nested_cascade = 0;  
    30. int use_nested_cascade = 0;  
    31. const char* cascade_name =  
    32.     "./data/haarcascade_frontalface_alt.xml";//别人已经训练好的人脸检测xml数据  
    33. const char* nested_cascade_name =  
    34.     "./data/haarcascade_eye_tree_eyeglasses.xml";  
    35. CvCapture* capture = 0;  
    36. IplImage *frame, *frame_copy = 0;  
    37. IplImage *image = 0;  
    38. const char* scale_opt = "--scale="// 分类器选项指示符号   
    39. int scale_opt_len = (int)strlen(scale_opt);  
    40. const char* cascade_opt = "--cascade=";  
    41. int cascade_opt_len = (int)strlen(cascade_opt);  
    42. const char* nested_cascade_opt = "--nested-cascade";  
    43. int nested_cascade_opt_len = (int)strlen(nested_cascade_opt);  
    44. double scale = 1;  
    45. int num_components = 9;  
    46. double facethreshold = 9.0;  
    47. //opencv的FaceRecogizer目前有三个类实现了他,特征脸和fisherface方法最少训练图像为两张,而LBP可以单张图像训练  
    48. //cv::Ptr<cv::FaceRecognizer> model = cv::createEigenFaceRecognizer();  
    49. //cv::Ptr<cv::FaceRecognizer> model = cv::createFisherFaceRecognizer();  
    50. cv::Ptr<cv::FaceRecognizer> model = cv::createLBPHFaceRecognizer();//LBP的这个方法在单个人脸验证方面效果最好  
    51.   
    52. vector<Mat> images;//两个容器images,labels来存放图像数据和对应的标签  
    53. vector<int> labels;  
    54.   
    55.   
    56. int main( int argc, char** argv )  
    57. {  
    58.     cascade = (CvHaarClassifierCascade*)cvLoad(cascade_name, 0, 0, 0); //加载分类器   
    59.     if(!cascade)   
    60.     {  
    61.         fprintf( stderr, "ERROR: Could not load classifier cascade " );  
    62.         getchar();  
    63.         return -1;  
    64.     }  
    65.     model->set("threshold", 2100.0);  
    66.     string output_folder;  
    67.     output_folder = string("./einfacedata");  
    68.   
    69.     //读取你的CSV文件路径  
    70.     string fn_csv = string("./einfacedata/at.txt");  
    71.     try  
    72.     {  
    73.         //通过./einfacedata/at.txt这个文件读取里面的训练图像和类别标签  
    74.         read_csv(fn_csv, images, labels);     
    75.     }  
    76.     catch(cv::Exception &e)  
    77.     {  
    78.         cerr<<"Error opening file "<<fn_csv<<". Reason: "<<e.msg<<endl;  
    79.         exit(1);  
    80.     }  
    81.     /* 
    82.     //read_img这个函数直接从einfacedata/trainingdata目录下读取图像数据并默认将图像置为0 
    83.     //所以如果用这个函数只能用来单个人脸验证 
    84.     if(!read_img(images, labels)) 
    85.     { 
    86.         cout<< "Error in reading images!"; 
    87.         images.clear(); 
    88.         labels.clear(); 
    89.         return 0; 
    90.     } 
    91.     */  
    92.     cout << images.size() << ":" << labels.size()<<endl;  
    93.     //如果没有读到足够的图片,就退出  
    94.     if(images.size() <= 2)  
    95.     {  
    96.         string error_message = "This demo needs at least 2 images to work.";  
    97.         CV_Error(CV_StsError, error_message);  
    98.     }  
    99.   
    100.     //得到第一张照片的高度,在下面对图像变形到他们原始大小时需要  
    101.     //int height = images[0].rows;  
    102.     //移除最后一张图片,用于做测试  
    103.     //Mat testSample = images[images.size() - 1];  
    104.     //cv::imshow("testSample", testSample);  
    105.     //int testLabel = labels[labels.size() - 1];  
    106.     //images.pop_back();  
    107.     //labels.pop_back();  
    108.   
    109.     //下面创建一个特征脸模型用于人脸识别,  
    110.     // 通过CSV文件读取的图像和标签训练它。  
    111.   
    112.     //进行训练  
    113.     model->train(images, labels);  
    114.   
    115.     storage = cvCreateMemStorage(0); // 创建内存存储器     
    116.     capture = cvCaptureFromCAM(0); // 创建视频读取结构   
    117.     cvNamedWindow( "result", 1 );  
    118.     if( capture ) // 如过是视频或摄像头采集图像,则循环处理每一帧   
    119.     {  
    120.         for(;;)  
    121.         {  
    122.             if( !cvGrabFrame( capture ))  
    123.                 break;  
    124.             frame = cvRetrieveFrame( capture );  
    125.             if( !frame )  
    126.                 break;  
    127.             if( !frame_copy )  
    128.                 frame_copy = cvCreateImage( cvSize(640,480),IPL_DEPTH_8U, frame->nChannels );  
    129.             if( frame->origin == IPL_ORIGIN_TL )  
    130.                 cvCopy( frame, frame_copy, 0 );  
    131.             else  
    132.                 cvFlip( frame, frame_copy, 0 );  
    133.               
    134.             //detect_and_draw( frame_copy ); // 如果调用这个函数,只是实现人脸检测  
    135.             //cout << frame_copy->width << "x" << frame_copy->height << endl;  
    136.             recog_and_draw( frame_copy );//该函数实现人脸检测和识别  
    137.             if( cvWaitKey( 100 ) >= 0 )//esc键值好像是100  
    138.                 goto _cleanup_;  
    139.         }  
    140.         cvWaitKey(0);  
    141.         _cleanup_: // 标记使用,在汇编里用过,C语言,我还没见用过   
    142.         cvReleaseImage( &frame_copy );  
    143.         cvReleaseCapture( &capture );  
    144.     }      
    145.     cvDestroyWindow("result");  
    146.     return 0;  
    147. }  

    detect_recog.cpp:

    1. #include "stdafx.h"  
    2. #include "cv.h"  
    3. #include "highgui.h"  
    4. #include <stdio.h>  
    5. #include <stdlib.h>  
    6. #include <string.h>  
    7. #include <assert.h>  
    8. #include <math.h>  
    9. #include <float.h>  
    10. #include <limits.h>  
    11. #include <time.h>  
    12. #include <ctype.h>  
    13. #include "detect_recog.h"  
    14. #include <opencv2contribcontrib.hpp>    
    15. #include <opencv2corecore.hpp>    
    16. #include <opencv2highguihighgui.hpp>   
    17. #include <iostream>  
    18. #include <fstream>  
    19. #include <sstream>  
    20. #include <stdio.h>  
    21. #include <io.h>    
    22. #include <direct.h>   
    23.   
    24. using namespace std;  
    25. using namespace cv;  
    26.   
    27. //检测并圈出人脸,并将检测到的人脸进行判断属于训练图像中的哪一类  
    28. void recog_and_draw( IplImage* img )   
    29. {  
    30.     static CvScalar colors[] =   
    31.     {  
    32.         {{0,0,255}},  
    33.         {{0,128,255}},  
    34.         {{0,255,255}},  
    35.         {{0,255,0}},  
    36.         {{255,128,0}},  
    37.         {{255,255,0}},  
    38.         {{255,0,0}},  
    39.         {{255,0,255}}  
    40.     };  
    41.     IplImage *gray, *small_img;  
    42.     int i, j;  
    43.     gray = cvCreateImage( cvSize(img->width,img->height), 8, 1 );  
    44.     small_img = cvCreateImage( cvSize( cvRound (img->width/scale),  
    45.                          cvRound (img->height/scale)), 8, 1 );  
    46.     cvCvtColor( img, gray, CV_BGR2GRAY ); // 彩色RGB图像转为灰度图像   
    47.     cvResize( gray, small_img, CV_INTER_LINEAR );  
    48.     cvEqualizeHist( small_img, small_img ); // 直方图均衡化   
    49.     cvClearMemStorage( storage );  
    50.     if( cascade )  
    51.     {  
    52.         double t = (double)cvGetTickCount();   
    53.         CvSeq* faces = cvHaarDetectObjects( small_img, cascade, storage,  
    54.                                             1.1, 2, 0  
    55.                                             //|CV_HAAR_FIND_BIGGEST_OBJECT  
    56.                                             //|CV_HAAR_DO_ROUGH_SEARCH  
    57.                                             |CV_HAAR_DO_CANNY_PRUNING  
    58.                                             //|CV_HAAR_SCALE_IMAGE  
    59.                                             ,  
    60.                                             cvSize(30, 30) );  
    61.         t = (double)cvGetTickCount() - t; // 统计检测使用时间   
    62.         //printf( "detection time = %gms ", t/((double)cvGetTickFrequency()*1000.) );  
    63.         for( i = 0; i < (faces ? faces->total : 0); i++ )  
    64.         {  
    65.             CvRect* r = (CvRect*)cvGetSeqElem( faces, i ); // 将faces数据从CvSeq转为CvRect   
    66.             CvMat small_img_roi;  
    67.             CvSeq* nested_objects;  
    68.             CvPoint center;  
    69.             CvScalar color = colors[i%8]; // 使用不同颜色绘制各个face,共八种色   
    70.             int radius;  
    71.             center.x = cvRound((r->x + r->width*0.5)*scale); // 找出faces中心   
    72.             center.y = cvRound((r->y + r->height*0.5)*scale);  
    73.             radius = cvRound((r->width + r->height)*0.25*scale);   
    74.                   
    75.             cvGetSubRect( small_img, &small_img_roi, *r );  
    76.               
    77.             //截取检测到的人脸区域作为识别的图像  
    78.             IplImage *result;  
    79.             CvRect roi;  
    80.             roi = *r;  
    81.             result = cvCreateImage( cvSize(r->width, r->height), img->depth, img->nChannels );  
    82.             cvSetImageROI(img,roi);  
    83.             // 创建子图像  
    84.             cvCopy(img,result);  
    85.             cvResetImageROI(img);  
    86.               
    87.             IplImage *resizeRes;  
    88.             CvSize dst_cvsize;  
    89.             dst_cvsize.width=(int)(100);  
    90.             dst_cvsize.height=(int)(100);  
    91.             resizeRes=cvCreateImage(dst_cvsize,result->depth,result->nChannels);  
    92.             //检测到的区域可能不是100x100大小,所以需要插值处理到统一大小,图像的大小可以自己指定的  
    93.             cvResize(result,resizeRes,CV_INTER_NN);  
    94.             IplImage* img1 = cvCreateImage(cvGetSize(resizeRes), IPL_DEPTH_8U, 1);//创建目标图像    
    95.             cvCvtColor(resizeRes,img1,CV_BGR2GRAY);//cvCvtColor(src,des,CV_BGR2GRAY)  
    96.             cvShowImage( "resize", resizeRes );  
    97.             cvCircle( img, center, radius, color, 3, 8, 0 ); // 从中心位置画圆,圈出脸部区域  
    98.             int predictedLabel = -1;  
    99.             Mat test = img1;  
    100.             //images[images.size() - 1] = test;  
    101.             model->train(images, labels);  
    102.               
    103.             //如果调用read_img函数时 chdir将默认目录做了更改,所以output.jpg自己找一下吧  
    104.             imwrite("../ouput.jpg",test);  
    105.   
    106.             //在这里对人脸进行判别  
    107.             double predicted_confidence = 0.0;  
    108.             model->predict(test,predictedLabel,predicted_confidence);  
    109.             if(predictedLabel == 0)  
    110.                 cvText(img, "yes", r->x+r->width*0.5, r->y);   
    111.             else  
    112.                 cvText(img, "no", r->x+r->width*0.5, r->y);   
    113.             //cout << "predict:"<<model->predict(test) << endl;  
    114.             cout << "predict:"<< predictedLabel << " confidence:" << predicted_confidence << endl;  
    115.   
    116.             if( !nested_cascade )  
    117.                 continue;  
    118.               
    119.             nested_objects = cvHaarDetectObjects( &small_img_roi, nested_cascade, storage,  
    120.                                         1.1, 2, 0  
    121.                                         //|CV_HAAR_FIND_BIGGEST_OBJECT  
    122.                                         //|CV_HAAR_DO_ROUGH_SEARCH  
    123.                                         //|CV_HAAR_DO_CANNY_PRUNING  
    124.                                         //|CV_HAAR_SCALE_IMAGE  
    125.                                         ,  
    126.                                         cvSize(0, 0) );  
    127.             for( j = 0; j < (nested_objects ? nested_objects->total : 0); j++ )  
    128.             {  
    129.                 CvRect* nr = (CvRect*)cvGetSeqElem( nested_objects, j );  
    130.                 center.x = cvRound((r->x + nr->x + nr->width*0.5)*scale);  
    131.                 center.y = cvRound((r->y + nr->y + nr->height*0.5)*scale);  
    132.                 radius = cvRound((nr->width + nr->height)*0.25*scale);  
    133.                 cvCircle( img, center, radius, color, 3, 8, 0 );  
    134.             }  
    135.         }  
    136.     }  
    137.     cvShowImage( "result", img );  
    138.     cvReleaseImage( &gray );  
    139.     cvReleaseImage( &small_img );  
    140. }  
    141. void cvText(IplImage* img, const char* text, int x, int y)    
    142. {    
    143.     CvFont font;    
    144.     double hscale = 1.0;    
    145.     double vscale = 1.0;    
    146.     int linewidth = 2;    
    147.     cvInitFont(&font,CV_FONT_HERSHEY_SIMPLEX | CV_FONT_ITALIC,hscale,vscale,0,linewidth);    
    148.     CvScalar textColor =cvScalar(0,255,255);    
    149.     CvPoint textPos =cvPoint(x, y);    
    150.     cvPutText(img, text, textPos, &font,textColor);    
    151. }  
    152.   
    153. Mat norm_0_255(cv::InputArray _src)  
    154. {  
    155.     Mat src = _src.getMat();  
    156.     Mat dst;  
    157.   
    158.     switch(src.channels())  
    159.     {  
    160.     case 1:  
    161.         cv::normalize(_src, dst, 0, 255, cv::NORM_MINMAX, CV_8UC1);  
    162.         break;  
    163.     case 3:  
    164.         cv::normalize(_src, dst, 0, 255, cv::NORM_MINMAX, CV_8UC3);  
    165.         break;  
    166.     default:  
    167.         src.copyTo(dst);  
    168.         break;  
    169.     }  
    170.   
    171.     return dst;  
    172. }  
    173. //读取文件中的图像数据和类别,存入images和labels这两个容器  
    174. void read_csv(const string &filename, vector<Mat> &images, vector<int> &labels, char separator)  
    175. {  
    176.     std::ifstream file(filename.c_str(), ifstream::in);  
    177.     if(!file)  
    178.     {  
    179.         string error_message = "No valid input file was given.";  
    180.         CV_Error(CV_StsBadArg, error_message);  
    181.     }  
    182.   
    183.     string line, path, classlabel;  
    184.     while(getline(file, line))  
    185.     {  
    186.         stringstream liness(line);  
    187.         getline(liness, path, separator);  //遇到分号就结束  
    188.         getline(liness, classlabel);     //继续从分号后面开始,遇到换行结束  
    189.         if(!path.empty() && !classlabel.empty())  
    190.         {  
    191.             images.push_back(imread(path, 0));  
    192.             labels.push_back(atoi(classlabel.c_str()));  
    193.         }  
    194.     }  
    195. }  
    196. bool read_img(vector<Mat> &images, vector<int> &labels)  
    197. {  
    198.       
    199.     long file;    
    200.     struct _finddata_t find;    
    201.     
    202.     _chdir("./einfacedata/trainingdata/");    
    203.     if((file=_findfirst("*.*", &find))==-1L) {    
    204.         //printf("空白!/n");    
    205.         return false;    
    206.     }    
    207.     //fileNum = 0;    
    208.     //strcpy(fileName[fileNum], find.name);  
    209.     int i = 0;  
    210.     while(_findnext(file, &find)==0)    
    211.     {    
    212.         if(i == 0)  
    213.         {  
    214.             i++;  
    215.             continue;  
    216.         }  
    217.         images.push_back(imread(find.name, 0));  
    218.         labels.push_back(0);    
    219.         cout << find.name << endl;  
    220.     }    
    221.     _findclose(file);  
    222.     return true;  
    223. }  
    224. // 只是检测人脸,并将人脸圈出   
    225. void detect_and_draw( IplImage* img )   
    226. {  
    227.     static CvScalar colors[] =   
    228.     {  
    229.         {{0,0,255}},  
    230.         {{0,128,255}},  
    231.         {{0,255,255}},  
    232.         {{0,255,0}},  
    233.         {{255,128,0}},  
    234.         {{255,255,0}},  
    235.         {{255,0,0}},  
    236.         {{255,0,255}}  
    237.     };  
    238.     IplImage *gray, *small_img;  
    239.     int i, j;  
    240.     gray = cvCreateImage( cvSize(img->width,img->height), 8, 1 );  
    241.     small_img = cvCreateImage( cvSize( cvRound (img->width/scale),  
    242.                          cvRound (img->height/scale)), 8, 1 );  
    243.     cvCvtColor( img, gray, CV_BGR2GRAY ); // 彩色RGB图像转为灰度图像   
    244.     cvResize( gray, small_img, CV_INTER_LINEAR );  
    245.     cvEqualizeHist( small_img, small_img ); // 直方图均衡化   
    246.     cvClearMemStorage( storage );  
    247.     if( cascade )  
    248.     {  
    249.         double t = (double)cvGetTickCount();   
    250.         CvSeq* faces = cvHaarDetectObjects( small_img, cascade, storage,  
    251.                                             1.1, 2, 0  
    252.                                             //|CV_HAAR_FIND_BIGGEST_OBJECT  
    253.                                             //|CV_HAAR_DO_ROUGH_SEARCH  
    254.                                             |CV_HAAR_DO_CANNY_PRUNING  
    255.                                             //|CV_HAAR_SCALE_IMAGE  
    256.                                             ,  
    257.                                             cvSize(30, 30) );  
    258.         t = (double)cvGetTickCount() - t; // 统计检测使用时间   
    259.         printf( "detection time = %gms ", t/((double)cvGetTickFrequency()*1000.) );  
    260.         for( i = 0; i < (faces ? faces->total : 0); i++ )  
    261.         {  
    262.             CvRect* r = (CvRect*)cvGetSeqElem( faces, i ); // 将faces数据从CvSeq转为CvRect   
    263.             CvMat small_img_roi;  
    264.             CvSeq* nested_objects;  
    265.             CvPoint center;  
    266.             CvScalar color = colors[i%8]; // 使用不同颜色绘制各个face,共八种色   
    267.             int radius;  
    268.             center.x = cvRound((r->x + r->width*0.5)*scale); // 找出faces中心   
    269.             center.y = cvRound((r->y + r->height*0.5)*scale);  
    270.             radius = cvRound((r->width + r->height)*0.25*scale);   
    271.             cvCircle( img, center, radius, color, 3, 8, 0 ); // 从中心位置画圆,圈出脸部区域   
    272.             if( !nested_cascade )  
    273.                 continue;  
    274.             cvGetSubRect( small_img, &small_img_roi, *r );  
    275.             nested_objects = cvHaarDetectObjects( &small_img_roi, nested_cascade, storage,  
    276.                                         1.1, 2, 0  
    277.                                         //|CV_HAAR_FIND_BIGGEST_OBJECT  
    278.                                         //|CV_HAAR_DO_ROUGH_SEARCH  
    279.                                         //|CV_HAAR_DO_CANNY_PRUNING  
    280.                                         //|CV_HAAR_SCALE_IMAGE  
    281.                                         ,cvSize(0, 0) );  
    282.             for( j = 0; j < (nested_objects ? nested_objects->total : 0); j++ )  
    283.             {  
    284.                 CvRect* nr = (CvRect*)cvGetSeqElem( nested_objects, j );  
    285.                 center.x = cvRound((r->x + nr->x + nr->width*0.5)*scale);  
    286.                 center.y = cvRound((r->y + nr->y + nr->height*0.5)*scale);  
    287.                 radius = cvRound((nr->width + nr->height)*0.25*scale);  
    288.                 cvCircle( img, center, radius, color, 3, 8, 0 );  
    289.             }  
    290.         }  
    291.     }  
    292.     cvShowImage( "result", img );  
    293.     cvReleaseImage( &gray );  
    294.     cvReleaseImage( &small_img );  
    295. }  

    detect_recog.h:

    1. #include "stdafx.h"  
    2. #include "cv.h"  
    3. #include "highgui.h"  
    4. #include <stdio.h>  
    5. #include <stdlib.h>  
    6. #include <string.h>  
    7. #include <assert.h>  
    8. #include <math.h>  
    9. #include <float.h>  
    10. #include <limits.h>  
    11. #include <time.h>  
    12. #include <ctype.h>  
    13. //////////////////////////////////s///////////////////////////////////  
    14. #include <opencv2contribcontrib.hpp>    
    15. #include <opencv2corecore.hpp>    
    16. #include <opencv2highguihighgui.hpp>   
    17. #include <iostream>  
    18. #include <fstream>  
    19. #include <sstream>  
    20. using namespace std;  
    21. using namespace cv;  
    22.   
    23. #ifndef DETECT_RECOG_H  
    24. #define DETECT_RECOG_H  
    25.   
    26. extern CvMemStorage* storage;  
    27. extern CvHaarClassifierCascade* cascade;  
    28. extern CvHaarClassifierCascade* nested_cascade;  
    29. extern int use_nested_cascade;  
    30. extern const char* cascade_name;  
    31. extern const char* nested_cascade_name;  
    32. extern double scale;  
    33.   
    34. extern cv::Ptr<cv::FaceRecognizer> model;  
    35. extern vector<Mat> images;  
    36. extern vector<int> labels;  
    37.   
    38. void detect_and_draw( IplImage* img ); // 检测和绘画   
    39. void recog_and_draw( IplImage* img ); // 检测和绘画   
    40. void read_csv(const string &filename, vector<Mat> &images, vector<int> &labels, char separator = ';');  
    41. bool read_img(vector<Mat> &images, vector<int> &labels);  
    42. Mat norm_0_255(cv::InputArray _src);  
    43. void cvText(IplImage* img, const char* text, int x, int y);  
    44. #endif  
  • 相关阅读:
    leetcode-String to Integer (atoi)
    2014薪水
    Ubunt下的软件集
    ubuntu常用软件
    python模块安装
    ubuntu下玩三国杀
    递归函数
    匿名函数
    装饰器函数
    生成器
  • 原文地址:https://www.cnblogs.com/mtcnn/p/9410086.html
Copyright © 2020-2023  润新知