本文介绍了探测游戏小地图上的小圈子的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述 29岁程序员,3月因学历无情被辞! 质量不是很好,因为在1080p视频中,小地图小于300px / 300px 我想检测这张图片上的10个英雄圈子: 像这样: 对于背景删除,我可以使用这个: 英雄人像圆圈半径在8到12之间,因为英雄人像是21x21px。 使用此代码 Mat minimapMat = mgcodecs.imread(minimap.png); Mat minimapCleanMat = Imgcodecs.imread(minimapClean.png); Mat minimapDiffMat = new Mat(); Core.subtract(minimapMat,minimapCleanMat,minimapDiffMat); 我得到这个: 现在我在其上应用圆圈检测: findCircles(minimapDiffMat); public static void findCircles(Mat imgSrc){ Mat img = imgSrc.clone(); 垫灰=新垫(); Imgproc.cvtColor(img,gray,Imgproc.COLOR_BGR2GRAY); Imgproc.blur(灰色,灰色,新尺寸(3,3)); Mat边缘=新Mat(); int lowThreshold = 40; int ratio = 3; Imgproc.Canny(gray,edges,lowThreshold,lowThreshold * ratio); Mat circles = new Mat(); Vector< Mat> circlesList = new Vector< Mat>(); Imgproc.HoughCircles(边缘,圆圈,Imgproc.CV_HOUGH_GRADIENT,1,10,5,20,7,15); double x = 0.0; double y = 0.0; int r = 0; (int i = 0; i for(int k = 0; k double [] data = circles.get(i,k); for(int j = 0; j x = data [0]; y = data [1]; r =(int)data [2]; } 点中心=新点(x,y); //圆心 Imgproc.circle(img,center,3,new Scalar(0,255,0),-1); //圆形轮廓 Imgproc.circle(img,center,r,new Scalar(0,255,0),1); } } HighGui.imshow(cirleIn,img); } 结果不正常,只能检测到2个: 我也尝试过knn背景: 成功率较低。 任何提示? 解决方案问题在于您的小地图包含突出显示的部分(可能在活动玩家周围)无法操作。为什么不从图像中选择突出显示的颜色?从我看到的只有其中几个。我不使用 OpenCV ,因此我在 C ++ 中给出了一个结果: int x,y; color c0,c1,c; picture pic0,pic1,pic2; // pic0 - 源背景 //图片1 - 源图 //图片2 - 输出 //确保所有图片大小相同 pic1.resize( pic0.xs,pic0.ys); pic2.resize(pic0.xs,pic0.ys); //为(x = 0; x {//处理所有像素(y = 0; y ; //得到两个颜色不带字母 c0.dd = pic0.p [y] [x] .dd& 0x00FFFFFF; c1.dd = pic1.p [y] [x] .dd& 0x00FFFFFF; C = C1;如果(距离2(c1,颜色(0x00EEEEEE)); (距离2(c1,颜色(0x00889971)) if (距离2(c1,颜色(0x005A6443))< 2000)c.dd = 0; //灰色ish路径 if (距离2(c1,颜色(0x0021A2C2))< 2000)c.dd = 0; //灰色ish路径 if (距离2(c1,颜色(0x002A6D70)) if(distance2(c1,color(0x00439D96)) pic2.p [y] [x] = c; } pic2.save(out0.png); pic2.pixel_format(_pf_u); //转换为灰度 pic2.smooth(); //模糊一点 pic2.save(out1.png); pic2.threshold(0,80,765,0x00000000); //将黑色像素( pic2.pixel_format(_pf_rgba); //转换回RGB pic2.save( out2.png); 所以你需要找到OpenCV计数器部分。阈值是颜色距离^ 2(所以我不需要sqrt)并且看起来像 50 ^ 2 对于 是理想的每个频道 RGB 向量。 我使用自己的图片类来创建图片, $ b xs,ys 是以像素为单位的图像大小 p [y] [x] .dd 在32位整数类型$ b处位于(x,y)位置$ b clear(color)使用 color resize(xs,ys)将图像调整为新分辨率 bmp 是 VCL 使用 Canvas封装 GDI 位图访问 pf 图片的像素格式: enum _pixel_format_enum { _pf_none = 0,// undefined _pf_rgba,// 32位RGBA _pf_s,// 32位有符号整型 _pf_u,// 32位无符号整型 _pf_ss ,// 2x16 bit signed int _pf_uu,// 2x16 bit unsigned int _pixel_format_enum_end }; color 和像素编码像这样: 联合颜色 { DWORD dd; WORD dw [2];字节数据库[4]; int i; short int ii [2]; color(){};颜色(color& a){* this = a; }; 〜颜色(){}; color * operator =(const color * a){dd = a-> dd;返回这个; }; / * color * operator =(const color& a){... copy ... return this; }; * / }; 乐队包括: 枚举{ _x = 0,// dw _y = 1, _b = 0,// db _g = 1, _r = 2, _a = 3, _v = 0,// db _s = 1, _h = 2 ,}; 这里也是用于阈值处理的颜色之间的距离^ 2: DWORD distance2(color& a,color& b) { DWORD d,dd; d = DWORD(a.db [0]) - DWORD(b.db [0]); dd = d * d; d = DWORD(a.db [1]) - DWORD(b.db [1]); DD + = d * d; d = DWORD(a.db [2]) - DWORD(b.db [2]); DD + = d * d; d = DWORD(a.db [3]) - DWORD(b.db [3]); DD + = d * d; return dd; } 作为输入我使用了您的图片: pic0: pic1: 这里的(子)结果: out0.png: out1.png: out2.png: $ b 正如你所看到的,当球员非常接近(由于圈子平均)并进行了一些调整,您可能会获得更好的结果。但在第二次教导,这可能是由于附近的一个小红圈...... 我使用 VCL / GDI 忽略/移植 pic2.bmp-> Canvas-> 这些东西以供您使用。 i am stuck on this problem for like 20h.The quality is not every good because on 1080p video, the minimap is less than 300px / 300pxI want to detect the 10 heros circles on this images:Like this:For background removal, i can use this:The heroes portrait circle radius are between 8 to 12 because a hero portrait is like 21x21px.With this codeMat minimapMat = mgcodecs.imread("minimap.png");Mat minimapCleanMat = Imgcodecs.imread("minimapClean.png");Mat minimapDiffMat = new Mat();Core.subtract(minimapMat, minimapCleanMat, minimapDiffMat);I obtain this:Now i apply circles detection on it: findCircles(minimapDiffMat); public static void findCircles(Mat imgSrc) { Mat img = imgSrc.clone(); Mat gray = new Mat(); Imgproc.cvtColor(img, gray, Imgproc.COLOR_BGR2GRAY); Imgproc.blur(gray, gray, new Size(3, 3)); Mat edges = new Mat(); int lowThreshold = 40; int ratio = 3; Imgproc.Canny(gray, edges, lowThreshold, lowThreshold * ratio); Mat circles = new Mat(); Vector<Mat> circlesList = new Vector<Mat>(); Imgproc.HoughCircles(edges, circles, Imgproc.CV_HOUGH_GRADIENT, 1, 10, 5, 20, 7, 15); double x = 0.0; double y = 0.0; int r = 0; for (int i = 0; i < circles.rows(); i++) { for (int k = 0; k < circles.cols(); k++) { double[] data = circles.get(i, k); for (int j = 0; j < data.length; j++) { x = data[0]; y = data[1]; r = (int) data[2]; } Point center = new Point(x, y); // circle center Imgproc.circle(img, center, 3, new Scalar(0, 255, 0), -1); // circle outline Imgproc.circle(img, center, r, new Scalar(0, 255, 0), 1); } } HighGui.imshow("cirleIn", img); }Results is not ok, detecting only 2 on 10:I have tried with knn background too:With less success.Any tips ? Thanks a lot in advance. 解决方案 The problem is that your minimap contains highlighted parts (possibly around active players) rendering your background removal inoperable. Why not threshold the highlighted color out from the image? From what I see there are just few of them. I do not use OpenCV so I gave it a shot in C++ here is the result:int x,y;color c0,c1,c;picture pic0,pic1,pic2; // pic0 - source background // pic1 - source map // pic2 - output// ensure all images are the same sizepic1.resize(pic0.xs,pic0.ys);pic2.resize(pic0.xs,pic0.ys);// process all pixelsfor (y=0;y<pic2.ys;y++) for (x=0;x<pic2.xs;x++) { // get both colors without alpha c0.dd=pic0.p[y][x].dd&0x00FFFFFF; c1.dd=pic1.p[y][x].dd&0x00FFFFFF; c=c1; // threshold 0xAARRGGBB distance^2 if (distance2(c1,color(0x00EEEEEE))<2000) c.dd=0; // white-ish rectangle if (distance2(c1,color(0x00889971))<2000) c.dd=0; // gray-ish path if (distance2(c1,color(0x005A6443))<2000) c.dd=0; // gray-ish path if (distance2(c1,color(0x0021A2C2))<2000) c.dd=0; // aqua water if (distance2(c1,color(0x002A6D70))<2000) c.dd=0; // aqua water if (distance2(c1,color(0x00439D96))<2000) c.dd=0; // aqua water if (distance2(c1,c0 )<2500) c.dd=0; // close to background pic2.p[y][x]=c; }pic2.save("out0.png");pic2.pixel_format(_pf_u); // convert to gray scalepic2.smooth(); // blur a littlepic2.save("out1.png");pic2.threshold(0,80,765,0x00000000); // set dark pixels (<80) to black (0) and rest to white (3*255)pic2.pixel_format(_pf_rgba);// convert back to RGBpic2.save("out2.png");So you need to find OpenCV counter parts to this. The thresholds are color distance^2 (so I do not need sqrt) and looks like 50^2 is ideal for <0,255> per channel RGB vector.I use my own picture class for images so some members are:xs,ys is size of image in pixelsp[y][x].dd is pixel at (x,y) position as 32 bit integer typeclear(color) clears entire image with colorresize(xs,ys) resizes image to new resolutionbmp is VCL encapsulated GDI Bitmap with Canvas accesspf holds actual pixel format of the image:enum _pixel_format_enum { _pf_none=0, // undefined _pf_rgba, // 32 bit RGBA _pf_s, // 32 bit signed int _pf_u, // 32 bit unsigned int _pf_ss, // 2x16 bit signed int _pf_uu, // 2x16 bit unsigned int _pixel_format_enum_end };color and pixels are encoded like this:union color { DWORD dd; WORD dw[2]; byte db[4]; int i; short int ii[2]; color(){}; color(color& a){ *this=a; }; ~color(){}; color* operator = (const color *a) { dd=a->dd; return this; }; /*color* operator = (const color &a) { ...copy... return this; };*/ };The bands are:enum{ _x=0, // dw _y=1, _b=0, // db _g=1, _r=2, _a=3, _v=0, // db _s=1, _h=2, };Here also the distance^2 between colors I used for thresholding:DWORD distance2(color &a,color &b) { DWORD d,dd; d=DWORD(a.db[0])-DWORD(b.db[0]); dd =d*d; d=DWORD(a.db[1])-DWORD(b.db[1]); dd+=d*d; d=DWORD(a.db[2])-DWORD(b.db[2]); dd+=d*d; d=DWORD(a.db[3])-DWORD(b.db[3]); dd+=d*d; return dd; }As input I used your images:pic0:pic1:And here the (sub) results:out0.png:out1.png:out2.png:Now just remove noise (by blurring or by erosion) a bit and apply your circle fitting or hough transform...[Edit1] circle detectorI gave it a bit of taught and implemented simple detector. I just check circumference points around any pixel position with constant radius (player circle) and if number of set point is above threshold I found potential circle. It is better than use whole disc area as some of the players contain holes and there are more pixels to test also ... Then I average close circles together and render the output ... Here updated code: int i,j,x,y,xx,yy,x0,y0,r=10,d; List<int> cxy; // circle circumferece points List<int> plr; // player { x,y } list color c0,c1,c; picture pic0,pic1,pic2; // pic0 - source background // pic1 - source map // pic2 - output // ensure all images are the same size pic1.resize(pic0.xs,pic0.ys); pic2.resize(pic0.xs,pic0.ys); // process all pixels for (y=0;y<pic2.ys;y++) for (x=0;x<pic2.xs;x++) { // get both colors without alpha c0.dd=pic0.p[y][x].dd&0x00FFFFFF; c1.dd=pic1.p[y][x].dd&0x00FFFFFF; c=c1; // threshold 0xAARRGGBB distance^2 if (distance2(c1,color(0x00EEEEEE))<2000) c.dd=0; // white-ish rectangle if (distance2(c1,color(0x00889971))<2000) c.dd=0; // gray-ish path if (distance2(c1,color(0x005A6443))<2000) c.dd=0; // gray-ish path if (distance2(c1,color(0x0021A2C2))<2000) c.dd=0; // aqua water if (distance2(c1,color(0x002A6D70))<2000) c.dd=0; // aqua water if (distance2(c1,color(0x00439D96))<2000) c.dd=0; // aqua water if (distance2(c1,c0 )<2500) c.dd=0; // close to background pic2.p[y][x]=c; }// pic2.save("out0.png"); pic2.pixel_format(_pf_u); // convert to gray scale pic2.smooth(); // blur a little// pic2.save("out1.png"); pic2.threshold(0,80,765,0x00000000); // set dark pixels (<80) to black (0) and rest to white (3*255) // compute player circle circumference points mask x0=r-1; y0=r; x0*=x0; y0*=y0; for (x=-r,xx=x*x;x<=r;x++,xx=x*x) for (y=-r,yy=y*y;y<=r;y++,yy=y*y) { d=xx+yy; if ((d>=x0)&&(d<=y0)) { cxy.add(x); cxy.add(y); } } // get all potential player circles x0=(5*cxy.num)/20; for (y=r;y<pic2.ys-r;y+=2) // no need to step by single pixel ... for (x=r;x<pic2.xs-r;x+=2) { for (d=0,i=0;i<cxy.num;) { xx=x+cxy.dat[i]; i++; yy=y+cxy.dat[i]; i++; if (pic2.p[yy][xx].dd>100) d++; } if (d>=x0) { plr.add(x); plr.add(y); } }// pic2.pixel_format(_pf_rgba);// convert back to RGB// pic2.save("out2.png"); // average all circles too close together pic2=pic1; // use original image again pic2.bmp->Canvas->Pen->Color=TColor(0x0000FF00); pic2.bmp->Canvas->Pen->Width=3; pic2.bmp->Canvas->Brush->Style=bsClear; for (i=0;i<plr.num;i+=2) if (plr.dat[i]>=0) { x0=plr.dat[i+0]; x=x0; y0=plr.dat[i+1]; y=y0; d=1; for (j=i+2;j<plr.num;j+=2) if (plr.dat[j]>=0) { xx=plr.dat[j+0]; yy=plr.dat[j+1]; if (((x0-xx)*(x0-xx))+((y0-yy)*(y0-yy))*10<=20*r*r) // if close { x+=xx; y+=yy; d++; // add to average plr.dat[j+0]=-1; // mark as deleted plr.dat[j+1]=-1; } } x/=d; y/=d; plr.dat[i+0]=x; plr.dat[i+1]=y; pic2.bmp->Canvas->Ellipse(x-r,y-r,x+r,y+r); } pic2.bmp->Canvas->Pen->Width=1; pic2.bmp->Canvas->Brush->Style=bsSolid;// pic2.save("out3.png");As you can see the core of code is the same I just added the detector in the end.I also use mine dynamic list template so:List<double> xxx; is the same as double xxx[];xxx.add(5); adds 5 to end of the listxxx[7] access array element (safe)xxx.dat[7] access array element (unsafe but fast direct access)xxx.num is the actual used size of the arrayxxx.reset() clears the array and set xxx.num=0xxx.allocate(100) preallocate space for 100 itemsAnd here the final result out3.png:As you can see it is a bit messed up when the players are very near (due to circle averaging) with some tweaking you might get better results. But on second taught it might be due to that small red circle nearby ...I used VCL/GDI for the circles render so just ignore/port the pic2.bmp->Canvas-> stuff to what ever you use. 这篇关于探测游戏小地图上的小圈子的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持! 上岸,阿里云! 09-05 13:33