點擊上方AI人工智能初學者,訂閱我!此刻開始咱們一塊兒學習進步!ios
目錄算法
一、Canny邊緣檢測編程
二、使用Hough變換進行直線檢測數組
三、圓形檢測微信
四、關鍵點檢測器和描述符app
五、使用背景減法進行對象跟蹤ide
5.一、高斯混合法
svg
5.二、GMG背景減法函數
一、Canny邊緣檢測性能
使用OpenCV和CUDA實現Canny邊緣檢測算法,該算法結合了高斯濾波、梯度尋找、非最大抑制和滯後閾值處理。如上一章所述,高通濾波器對噪聲很是敏感,在Canny邊緣檢測中,檢測邊緣以前完成高斯平滑,這使得它對噪聲不太敏感。在檢測到邊緣以從結果中移除沒必要要的邊緣以後,它還具備非最大抑制階段。
Canny邊緣檢測是一項計算密集型任務,難以在實時應用程序中使用,CUDA版本的算法可起到加速做用。實現Canny邊緣檢測算法的代碼以下所述:
using namespace std;using namespace cv;using namespace cv::cuda;
int main(){ Mat h_image = imread("images/drawing.JPG",0); if (h_image.empty()) { cout << "can not open image"<< endl; return -1; }GpuMat d_edge,d_image; Mat h_edge;d_image.upload(h_image);cv::Ptr<cv::cuda::CannyEdgeDetector> canny_edge = cv::cuda::createCannyEdgeDetector(2.0, 100.0, 3, false);canny_edge->detect(d_image, d_edge); d_edge.download(h_edge);imshow("source", h_image); imshow("detected edges", h_edge);waitKey(0);
return 0;}
OpenCV和CUDA爲Canny邊緣檢測提供了createCannyEdgeDetector類。建立此類的對象時能夠傳遞許多參數:第一個和第二個參數是滯後閾值的低閾值和高閾值,若是某點的強度梯度大於高閾值,則將其歸類爲邊緣點;若是梯度小於低閾值,則該點不是邊緣點;若是梯度在兩個閾值之間,則基於鏈接性來肯定該點是否爲邊緣點。第三個參數是邊緣檢測器的孔徑大小,最後一個參數是布爾參數,它指示是否使用L2_norm或L1_norm進行梯度幅度計算。L2_norm很消耗計算資源但更準確,true值表示使用L2_norm。代碼輸出如圖:

二、使用Hough變換進行直線檢測
直線的檢測在許多計算機視覺應用中很重要,例如車道檢測。它還可用於檢測屬於其餘常規形狀的線條。Hough變換是一種流行的特徵提取技術,用於計算機視覺直線檢測。咱們不會詳細討論Hough變換如何檢測直線,但咱們將瞭解如何在OpenCV和CUDA中實現。實現直線檢測的Hough變換代碼以下:
using namespace std;using namespace cv;using namespace cv::cuda;
int main(){ Mat h_image = imread("images/drawing.JPG",0); if (h_image.empty()) { cout << "can not open image"<< endl; return -1; }
Mat h_edge; cv::Canny(h_image, h_edge, 100, 200, 3);
Mat h_imagec; cv::cvtColor(h_edge, h_imagec, COLOR_GRAY2BGR); Mat h_imageg = h_imagec.clone(); vector<Vec4i> h_lines; { const int64 start = getTickCount(); HoughLinesP(h_edge, h_lines, 1, CV_PI / 180, 50, 60, 5); const double time_elapsed = (getTickCount() - start) / getTickFrequency(); cout << "CPU Time : " << time_elapsed * 1000 << " ms" << endl; cout << "CPU FPS : " << (1/time_elapsed) << endl; }
for (size_t i = 0; i < h_lines.size(); ++i) { Vec4i line_point = h_lines[i]; line(h_imagec, Point(line_point[0], line_point[1]), Point(line_point[2], line_point[3]), Scalar(0, 0, 255), 2, LINE_AA); }
GpuMat d_edge, d_lines;d_edge.upload(h_edge); {const int64 start = getTickCount(); Ptr<cuda::HoughSegmentDetector> hough = cuda::createHoughSegmentDetector(1.0f, (float) (CV_PI / 180.0f), 50, 5); hough->detect(d_edge, d_lines);
const double time_elapsed = (getTickCount() - start) / getTickFrequency(); cout << "GPU Time : " << time_elapsed * 1000 << " ms" << endl; cout << "GPU FPS : " << (1/time_elapsed) << endl; } vector<Vec4i> lines_g; if (!d_lines.empty()) { lines_g.resize(d_lines.cols); Mat h_lines(1, d_lines.cols, CV_32SC4, &lines_g[0]); d_lines.download(h_lines); } for (size_t i = 0; i < lines_g.size(); ++i) { Vec4i line_point = lines_g[i]; line(h_imageg, Point(line_point[0], line_point[1]), Point(line_point[2], line_point[3]), Scalar(0, 0, 255), 2, LINE_AA); }
imshow("source", h_image); imshow("detected lines [CPU]", h_imagec); imshow("detected lines [GPU]", h_imageg); imwrite("hough_source.png", h_image); imwrite("hough_cpu_line.png", h_imagec); imwrite("hough_gpu_line.png", h_imageg);
waitKey(0); return 0;}
OpenCV提供了createHoughSegmentDetector類來實現Hough變換。它須要圖像的邊緣圖做爲輸入。所以邊緣能夠被Canny邊緣檢測器從圖像中檢測。Canny邊緣檢測器的輸出上傳到用於GPU計算的設備顯存。上一節中已討論過如何用GPU去執行邊緣計算。
當建立createHoughSegmentDetector對象時,須要不少參數:第一個參數r表示在Hough變換中參數的分辨率,一般做爲1像素;第二個參數是參數theta在弧度中的分辨率,取1弧度或pi/180;第三個參數是最小數量造成一條線所需的點數,取50像素;最後的參數是兩點之間的最大間隙被視爲同一條線,這裏設爲5個像素。
建立的對象檢測方法用於檢測直線,須要兩個參數:第一個參數是要檢測邊緣的圖像,第二個參數是存儲檢測到的線上點的數組。數組包含檢測到的線的起始點和結束點,這個數組使用for循環迭代,使用OpenCV的線函數在圖像上繪製單獨線條,最終圖像使用imshow函數顯示。
Hough變換是一個數學密集型的步驟,爲了展現CUDA的優點,咱們用CPU實現相同的算法,並將其性能與CUDA代碼進行比較。
HoughLinesP函數使用機率Hough變換檢測CPU上的線路轉變,前兩個參數是源圖像和存儲輸出線上點的數組;第三和第四個參數是r與theta的分辨率;第五個參數是表示線的最小交叉點數的閾值;第六個參數表示造成一條線所需的最小點數;最後的參數表示在同一條線上要考慮的點之間的最大間隙。
使用for循環對函數返回的數組進行迭代,以在原始圖像上顯示檢測到的線。下圖顯示GPU與CPU功能的輸出結果:

三、對圓形進行檢測
Hough變換也可用於圓形檢測,能夠用在如球檢測和跟蹤以及硬幣檢測等這些圓形對象的不少應用中。OpenCV和CUDA提供了實現這個檢測的類,Hough變換用於硬幣檢測的代碼以下:
using namespace cv;using namespace std;
int main(int argc, char** argv){ Mat h_image = imread("images/eight.tif", IMREAD_COLOR); Mat h_gray; cvtColor(h_image, h_gray, COLOR_BGR2GRAY);cuda::GpuMat d_gray,d_result;std::vector<cv::Vec3f> d_Circles;medianBlur(h_gray, h_gray, 5);cv::Ptr<cv::cuda::HoughCirclesDetector> detector = cv::cuda::createHoughCirclesDetector(1, 100, 122, 50, 1, max(h_image.size().width, h_image.size().height)); d_gray.upload(h_gray); detector->detect(d_gray, d_result); d_Circles.resize(d_result.size().width); if (!d_Circles.empty()) d_result.row(0).download(cv::Mat(d_Circles).reshape(3, 1));
cout<<"No of circles: " <<d_Circles.size() <<endl; for( size_t i = 0; i < d_Circles.size(); i++ ) { Vec3i cir = d_Circles[i]; circle( h_image, Point(cir[0], cir[1]), cir[2], Scalar(255,0,0), 2, LINE_AA); } imshow("detected circles", h_image); waitKey(0); return 0;}
這裏的createHoughCirclesDetector類用於檢測循環對象,建立該對象時需提供許多參數:第一個參數是dp,表示累加器分辨率與圖像分辨率的反比,取值一般爲1;第二個參數是檢測到的圓中心之間的最小距離;第三個參數是Canny閾值;第四個參數是累加器閾值;第五和第六個參數是要檢測的圓的最小和最大半徑。
圓中心之間的最小距離取爲100像素,你能夠在這個值上下調整:若是調低,在原始圖像上就會錯誤地檢測到許多圓;若是增長,可能會遺漏一些真正的圓。最後兩個參數,即最小和最大半徑,若是不肯定尺寸的話,能夠取0。在上面的代碼中,它被取爲1和最大維度圖像的一部分,用於檢測圖像中的全部圓形。這段代碼的輸出結果如圖所示

Hough變換對高斯噪聲和椒鹽噪聲很是敏感,因此有時應用Hough變換以前最好使用高斯濾波器和中值濾波器對圖像進行預處理轉變,這樣會獲得更準確的結果。
四、關鍵點檢測器和描述符
OpenCV和CUDA提供一種實現FAST算法的有效方法,使用FAST算法檢測關鍵點的程序以下所示:
using namespace cv;using namespace std;
int main(){ Mat h_image = imread( "images/drawing.JPG", 0 );
//Detect the keypoints using FAST Detectorcv::Ptr<cv::cuda::FastFeatureDetector> detector = cv::cuda::FastFeatureDetector::create(100,true,2);std::vector<cv::KeyPoint> keypoints;cv::cuda::GpuMat d_image;d_image.upload(h_image);detector->detect(d_image, keypoints);cv::drawKeypoints(h_image,keypoints,h_image);//Show detected keypointsimshow("Final Result", h_image );waitKey(0);return 0;}
OpenCV和CUDA提供了一個FastFeatureDetector類來實現FAST算法,使用類的create方法建立此類的對象,須要三個參數:第一個參數是用於FAST算法的強度閾值;第二個參數指定是否使用非最大抑制,它是一個布爾值,能夠指定爲true或false;第三個參數表示計算鄰域所使用的FAST方法,有如下三種可用的方法:
cv2.FAST_FEATURE_DETECTOR_TYPE_5_八、cv2.FAST_FEATURE_DETECTOR_TYPE_7_12以及cv2.FAST_FEATURE_DETECTOR_TYPE_9_16,能夠指定爲標誌0,1或2。
建立檢測對象關鍵點的函數須要一個輸入圖像和存儲關鍵點的矢量做爲參數,而後用drawKeypoints函數來繪製計算出的關鍵點原始圖像,這須要源圖像、關鍵點矢量和目標圖像做爲參數。
能夠改變強度閾值以檢測不一樣數量的關鍵點,若是閾值很低,那麼更多的關鍵點將經過分段測試,並將被歸類爲關鍵點。隨着閾值增長,檢測到的關鍵點的數量將逐漸減小。一樣的方式,若是非最大抑制爲false,就會在單個角點上檢測出一個以上的關鍵點。代碼輸出如圖所示:

OpenCV和CUDA提供了一個簡單的API來實現ORB算法。用於實現ORB算法的代碼以下:
using namespace cv;using namespace std;
int main(){Mat h_image = imread( "images/drawing.JPG", 0 );cv::Ptr<cv::cuda::ORB> detector = cv::cuda::ORB::create();std::vector<cv::KeyPoint> keypoints;cv::cuda::GpuMat d_image;d_image.upload(h_image);detector->detect(d_image, keypoints);cv::drawKeypoints(h_image,keypoints,h_image);imshow("Final Result", h_image );waitKey(0);return 0;}
使用create函數建立ORB類對象,全部參數都是可選的,這裏使用默認值。detect函數對生成的對象進行檢測以找出圖像中的關鍵點,須要輸入圖像和關鍵點的矢量,在那裏輸出將做爲參數存儲。檢測到的關鍵點用drawKeypoints函數繪製點圖像。前面代碼的輸出如圖所示:

OpenCV和CUDA提供了一個API來計算SURF關鍵點和描述符,咱們還能夠看到這些如何用於檢測查詢圖像中的對象。SURF特徵檢測和匹配的代碼以下:
using namespace cv;using namespace cv::xfeatures2d;using namespace std;
int main( int argc, char** argv ){Mat h_object_image = imread( "images/object1.jpg", 0 ); Mat h_scene_image = imread( "images/scene1.jpg", 0 );cuda::GpuMat d_object_image;cuda::GpuMat d_scene_image;cuda::GpuMat d_keypoints_scene, d_keypoints_object; vector< KeyPoint > h_keypoints_scene, h_keypoints_object;cuda::GpuMat d_descriptors_scene, d_descriptors_object;d_object_image.upload(h_object_image);d_scene_image.upload(h_scene_image);
cuda::SURF_CUDA surf(100);surf( d_object_image, cuda::GpuMat(), d_keypoints_object, d_descriptors_object );surf( d_scene_image, cuda::GpuMat(), d_keypoints_scene, d_descriptors_scene );
Ptr< cuda::DescriptorMatcher > matcher = cuda::DescriptorMatcher::createBFMatcher();vector< vector< DMatch> > d_matches;matcher->knnMatch(d_descriptors_object, d_descriptors_scene, d_matches, 2);
surf.downloadKeypoints(d_keypoints_scene, h_keypoints_scene);surf.downloadKeypoints(d_keypoints_object, h_keypoints_object);std::vector< DMatch > good_matches;for (int k = 0; k < std::min(h_keypoints_object.size()-1, d_matches.size()); k++){if ( (d_matches[k][0].distance < 0.6*(d_matches[k][1].distance)) &&((int)d_matches[k].size() <= 2 && (int)d_matches[k].size()>0) ){good_matches.push_back(d_matches[k][0]);}}std::cout << "size:" <<good_matches.size();Mat h_image_result;drawMatches( h_object_image, h_keypoints_object, h_scene_image, h_keypoints_scene,good_matches, h_image_result, Scalar::all(-1), Scalar::all(-1),vector<char>(), DrawMatchesFlags::DEFAULT );
std::vector<Point2f> object;std::vector<Point2f> scene;for (int i = 0; i < good_matches.size(); i++) {object.push_back(h_keypoints_object[good_matches[i].queryIdx].pt);scene.push_back(h_keypoints_scene[good_matches[i].trainIdx].pt);}Mat Homo = findHomography(object, scene, RANSAC);std::vector<Point2f> corners(4);std::vector<Point2f> scene_corners(4);corners[0] = Point(0, 0);corners[1] = Point(h_object_image.cols, 0);corners[2] = Point(h_object_image.cols, h_object_image.rows);corners[3] = Point(0, h_object_image.rows);perspectiveTransform(corners, scene_corners, Homo);line(h_image_result, scene_corners[0] + Point2f(h_object_image.cols, 0),scene_corners[1] + Point2f(h_object_image.cols, 0),Scalar(255, 0, 0), 4);line(h_image_result, scene_corners[1] + Point2f(h_object_image.cols, 0),scene_corners[2] + Point2f(h_object_image.cols, 0),Scalar(255, 0, 0), 4);line(h_image_result, scene_corners[2] + Point2f(h_object_image.cols, 0),scene_corners[3] + Point2f(h_object_image.cols, 0),Scalar(255, 0, 0), 4);line(h_image_result, scene_corners[3] + Point2f(h_object_image.cols, 0),scene_corners[0] + Point2f(h_object_image.cols, 0),Scalar(255, 0, 0), 4);imshow("Good Matches & Object detection", h_image_result);waitKey(0);return 0;}

五、使用背景減法進行對象跟蹤
背景減法是在一系列視頻幀中將前景對象從背景中分離出來的過程。它普遍應用於對象檢測和跟蹤應用中去除背景部分。背景減法分四步進行:
·圖像預處理
·背景建模
·檢測前景
·數據驗證
圖像預處理一般用於去除圖像中存在的各類噪聲。第二步是對背景進行建模,以便將其與前景分離。在某些應用中,視頻的第一幀做爲背景不更新,後面每幀和第一幀之間的絕對差被用來分離前景和背景。
在其餘技術中,經過對算法所看到的全部幀的平均值或中間值對背景進行建模,並將該背景與前景分離。與第一種方法相比,它對光照變化的魯棒性更高,而且會產生更多動態背景。其餘更具統計密集度的模型,如高斯模型和使用幀的歷史的支持向量模型,也能夠用於背景建模。
第三步是利用當前幀和背景之間的絕對差,將前景與模型背景分離。將這個絕對差與設置的閾值進行比較:若是大於閾值,則對象被認爲是移動的;若是小於閾值,那麼對象被認爲是靜止的。
實踐的原視頻
5.一、高斯混合法

高斯混合法(MoG)是一種普遍使用的基於高斯混合的背景減法,用於分離前景和背景。背景從幀序列中不斷更新,混合K高斯分佈用於將像素分類爲前景或背景,同時對幀的時間序列進行加權,以改善背景建模。連續變化的強度被歸類爲前景強度,靜態強度被歸類爲背景強度。
OpenCV和CUDA提供了一個簡單的API來實現背景減法的MoG。代碼以下:
using namespace std;using namespace cv;using namespace cv::cuda;int main(){VideoCapture cap("D:/images/abc.avi");
int type = static_cast<int>(cap.get(CAP_PROP_FOURCC));Size S = Size((int)cap.get(CAP_PROP_FRAME_WIDTH), (int)cap.get(CAP_PROP_FRAME_HEIGHT));int fps = cap.get(CAP_PROP_FPS);VideoWriter write;write.open("D:/images/h_fgmask_mog.mp4", write.fourcc('M', 'P', '4', '2'), 20, S, true);VideoWriter write2;write.open("D:/images/h_img_mog.mp4", write.fourcc('M', 'P', '4', '2'), 20, S, true);VideoWriter write3;write.open("D:/images/h_background_mog.mp4", write.fourcc('M', 'P', '4', '2'), 20, S, true);
if (!cap.isOpened()){cerr << "can not open camera or video file" << endl;return -1;}Mat frame;cap.read(frame);GpuMat d_frame;d_frame.upload(frame);Ptr<BackgroundSubtractor> mog = cuda::createBackgroundSubtractorMOG();GpuMat d_fgmask, d_fgimage, d_bgimage;Mat h_fgmask, h_fgimage, h_bgimage;mog->apply(d_frame, d_fgmask, 0.01);
namedWindow("cap");
while (1){cap.read(frame);if (frame.empty())break;d_frame.upload(frame);int64 start = cv::getTickCount();mog->apply(d_frame, d_fgmask, 0.01);mog->getBackgroundImage(d_bgimage);double fps = cv::getTickFrequency() / (cv::getTickCount() - start);std::cout << "FPS : " << fps << std::endl;d_fgimage.create(d_frame.size(), d_frame.type());d_fgimage.setTo(Scalar::all(0));d_frame.copyTo(d_fgimage, d_fgmask);d_fgmask.download(h_fgmask);d_fgimage.download(h_fgimage);d_bgimage.download(h_bgimage);imshow("image", frame);
putText(h_fgmask, format("FPS: %.2f", fps), Point(50, 50), FONT_HERSHEY_SIMPLEX, 1.0, Scalar(0, 0, 255), 2, 8);imshow("foreground mask", h_fgmask);write.write(h_fgmask);
putText(h_fgimage, format("FPS: %.2f", fps), Point(50, 50), FONT_HERSHEY_SIMPLEX, 1.0, Scalar(0, 0, 255), 2, 8);imshow("foreground image", h_fgimage);write2.write(h_fgimage);
putText(h_bgimage, format("FPS: %.2f", fps), Point(50, 50), FONT_HERSHEY_SIMPLEX, 1.0, Scalar(0, 0, 255), 2, 8);imshow("mean background image", h_bgimage);write3.write(h_bgimage);
if (waitKey(1) == 'q')break;}
return 0;}
createBackgroundSubtractorMOG類用於建立實現MoG的對象,它能夠在建立對象時提供一些可選參數。這些參數包括history、nmixtures、backgroundRatio和noiseSigma。history參數表示用於爲背景建模的前一幀的數量,默認值是200;nmixture參數指定用於分離像素的高斯混合數,默認值是5。你能夠根據應用程序使用這些值。
所建立對象的apply方法用於從第一幀建立前景掩碼,須要一個輸入圖像和一個圖像數組來存儲做爲輸入的前景掩碼和學習速率。在while循環中的每一幀以後,這個前景掩碼和背景圖像都會不斷更新。getBackgroundImage函數用於獲取當前背景模型。
前景掩碼用於建立前景圖像,該圖像指示當前正在移動的對象。它的基本邏輯是在原始幀和前景掩碼之間操做。在每一幀以後,前景掩碼、前景圖像和建模的背景將被下載到主機內存中,以便在屏幕上顯示。
MoG模型應用於PETS 2009數據集的視頻,該數據集普遍用於行人檢測,它有一個靜態的背景,人們在視頻中四處走動。結果以下所示:
5.二、GMG背景減法

GMG算法的名稱源自該算法發明人的姓名首字母,這個算法結合了背景估計與貝葉斯圖像分割,使用貝葉斯推斷將背景與前景分離,還使用幀的歷史來建模背景。它再次基於幀的時間序列進行加權。新的觀測比舊的觀測的權重更高。
OpenCV和CUDA提供了相似於MoG的API用於實現GMG算法。實現用於背景減法的GMG算法的代碼以下:
using namespace std;using namespace cv;using namespace cv::cuda;
int main(){VideoCapture cap("D:/images/abc.avi");
int type = static_cast<int>(cap.get(CAP_PROP_FOURCC));Size S = Size((int)cap.get(CAP_PROP_FRAME_WIDTH), (int)cap.get(CAP_PROP_FRAME_HEIGHT));int fps = cap.get(CAP_PROP_FPS);VideoWriter write;write.open("D:/images/h_fgmask.mp4", write.fourcc('M', 'P', '4', '2'), 20, S, true);
VideoWriter write2;write2.open("D:/images/h_fgimage.mp4", write.fourcc('M', 'P', '4', '2'), 20, S, true);
if (!cap.isOpened()){cerr << "can not open video file" << endl;return -1;}Mat frame;cap.read(frame);GpuMat d_frame;d_frame.upload(frame);Ptr<BackgroundSubtractor> gmg = cuda::createBackgroundSubtractorGMG(40);GpuMat d_fgmask, d_fgimage, d_bgimage;Mat h_fgmask, h_fgimage, h_bgimage;gmg->apply(d_frame, d_fgmask);
namedWindow("cap");
while (cap.read(frame)){cap.read(frame);if (frame.empty())break;d_frame.upload(frame);int64 start = cv::getTickCount();gmg->apply(d_frame, d_fgmask, 0.01);double fps = cv::getTickFrequency() / (cv::getTickCount() - start);std::cout << "FPS : " << fps << std::endl;d_fgimage.create(d_frame.size(), d_frame.type());d_fgimage.setTo(Scalar::all(0));d_frame.copyTo(d_fgimage, d_fgmask);d_fgmask.download(h_fgmask);d_fgimage.download(h_fgimage);imshow("image", frame); putText(h_fgmask, format("FPS: %.2f", fps), Point(50, 50), FONT_HERSHEY_SIMPLEX, 1.0, Scalar(0, 0, 255), 2, 8);imshow("foreground mask", h_fgmask);write.write(h_fgmask);putText(h_fgimage, format("FPS: %.2f", fps), Point(50, 50), FONT_HERSHEY_SIMPLEX, 1.0, Scalar(0, 0, 255), 2, 8);imshow("foreground image", h_fgimage);write2.write(h_fgimage);if (waitKey(30) == 'q')break;}
return 0;}
createBackgroundSubtractorGMG類用於實現GMG建立的對象。它能夠在建立對象時提供兩個參數:第一個參數用於對背景建模的前一幀的數量,上面代碼中取40;第二個參數是決策閾值,用於將像素分類爲前景,其默認值爲0.8。
所建立對象的apply方法用於第一幀以建立前景掩碼。經過使用幀的歷史在while循環中不斷更新前景掩碼和前景圖像,前景掩碼用於與MoG相似的方式建立前景圖像。GMG算法結果以下所示:
關注【AI人工智能初學者】並回復【背景相減】便可獲取本文的完整源碼及測試視頻,只但願能夠幫到你,固然,大佬除外啦~~~~
參考:【基於GPU加速的計算機視覺編程——使用OpenCV和CUDA實時處理複雜圖像數據】
但願您能夠關注公衆號,也很是期待您的打賞。
聲明:轉載請說明出處
下方爲小生公衆號,還望包容接納和關注,很是期待與您的美好相遇,讓咱們以夢爲馬,砥礪前行。
但願技術與靈魂能夠一路同行
長按識別二維碼關注一下
更多精彩內容可回覆關鍵詞
每篇文章的主題便可


點「在看」給我一朵小黃花
本文分享自微信公衆號 - AI人工智能初學者(ChaucerG)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。