javacv 人脸检测_使用JavaCV进行手和手指检测

javacv 人脸检测

这篇文章是Andrew Davison博士发布的有关自然用户界面(NUI)系列的一部分,内容涉及使用JavaCV从网络摄像头视频提要中检测手。

注意:本章的所有源代码都可以从http://fivedots.coe.psu.ac.th/~ad/jg/nui055/下载。

第5章的彩色斑点检测代码(可从http://fivedots.coe.psu.ac.th/~ad/jg/nui05/获得 )可以用作其他形状分析器的基础,我将在此处进行说明。通过扩展它来检测手和手指。 在图1中,我的左手戴着黑手套。 我的Handy应用程序尝试查找并标记拇指,食指,中指,无名指和小指。 在指尖和手的重心(COG)之间绘制黄线。

图1.检测左手和手指。

我使用了第5章的HSVSelector应用程序来确定黑手套的合适HSV范围。 在执行图2所示的步骤之前,Handy会加载这些范围,以获取手部的轮廓,其COG和相对于水平面的方向。

图2。找到手轮廓。

图2中的各个阶段几乎与第5章第4.1节中的ColorRectDetector.findRect()方法执行的阶段相同。但是,Handy继续进行处理,使用凸包和凸缺陷来定位并标记手中的指尖轮廓。 这些附加步骤如图3所示。

图3.查找和标记指尖。

外壳和缺陷是使用标准OpenCV操作从轮廓获得的,我将在下面进行解释。 但是,命名手指的最后一步采用了一种颇为怪异的策略,该策略假定轮廓的缺陷是针对伸出的左手。 拇指和食指基于它们相对于COG的角度位置来定位,而其他手指则根据它们相对于那些手指的位置来标识。 这个过程非常脆弱,并且很容易混淆,如图4所示。

图4.一个错误的中指。

尽管如此,该技术还是相当可靠的,通常至少可以识别拇指和食指,而与手的方向无关,这对于基本的手势处理来说应该足够了。 但是,该应用程序无法识别手势,希望它将成为下一章的主题。

Handy的类图如图5所示,其中仅列出了公共方法。

图5.方便的类图。

Handy的顶级与第5章中的BlobsDrumming应用程序的顶级并行(例如,参见第5章的图11​​),其中Handy类管理JFrame和HandPanel,显示带注释的摄像头图像。 图2和3总结的图像分析由HandDetector类执行,该类通过调用update()传递给当前的网络摄像头快照。 当HandPanel调用HandDetector.draw()时,它将绘制当前标记的指尖,COG和连接线。

1.分析网络摄像头图像

update()方法实际上是实现图2和图3的一系列调用。

// globals
private static final int IMG_SCALE = 2; // scaling applied to webcam image// HSV ranges defining the glove color
private int hueLower, hueUpper, satLower, satUpper,briLower, briUpper;// OpenCV elements
private IplImage hsvImg;  // HSV version of webcam image
private IplImage imgThreshed;  // threshold for HSV settings// hand details
private Point cogPt;       // center of gravity (COG) of contour
private int contourAxisAngle;     // contour's main axis angle relative to the horiz (in degrees)
private ArrayList fingerTips;public void update(BufferedImage im)
{BufferedImage scaleIm = scaleImage(im, IMG_SCALE);   // reduce the size of the image to make processing faster// convert image format to HSVcvCvtColor(IplImage.createFrom(scaleIm), hsvImg, CV_BGR2HSV);// threshold image using loaded HSV settings for user's glovecvInRangeS(hsvImg, cvScalar(hueLower, satLower, briLower, 0),cvScalar(hueUpper, satUpper, briUpper, 0),imgThreshed);cvMorphologyEx(imgThreshed, imgThreshed, null, null,CV_MOP_OPEN, 1);// erosion followed by dilation on the image to remove// specks of white while retaining the image sizeCvSeq bigContour = findBiggestContour(imgThreshed);if (bigContour == null)return;extractContourInfo(bigContour, IMG_SCALE);// find the COG and angle to horizontal of the contourfindFingerTips(bigContour, IMG_SCALE);// detect the fingertips positions in the contournameFingers(cogPt, contourAxisAngle, fingerTips);
}  // end of update()

update()首先缩放提供的网络摄像头图像以提高处理速度。 然后,它将图片转换为HSV格式,以便可以使用黑手套的HSV范围生成阈值图像。 这对应于图2的第一行,尽管实际上将阈值渲染为黑色背景上的白色像素。

减去小斑点的阈值传递给findBiggestContour(); 在随后的处理阶段中,假定生成的轮廓是用户的手。 extractContourInfo()分析轮廓以找到手的重心(COG)及其相对于水平面的方向,并将其存储在cogPt和ContourAxisAngle全局变量中。 extractContourInfo()的完成对应于图2的末尾。

findFingerTips()方法将凸包包裹在轮廓周围,以识别形状的缺陷(图3的顶行),我们假设这是手的手指。 经过少量过滤以减少缺陷数量之后,其余缺陷将被视为指尖坐标,并存储在全局fingerTips列表中。

nameFingers()标记手指(假设拇指和食指在手的左侧),完成图3的阶段。

1.1找到最大的轮廓

findBiggestContour()使用OpenCV函数cvFindContours()创建轮廓列表。 对于我的二进制阈值图像,轮廓是白色像素的区域(或斑点)。 每个斑点由一个边界框近似,并且选择并返回与最大框相对应的轮廓。

// globals
private static final float SMALLEST_AREA = 600.0f;// ignore smaller contour areasprivate CvMemStorage contourStorage;private CvSeq findBiggestContour(IplImage imgThreshed)
{CvSeq bigContour = null;// generate all the contours in the threshold image as a listCvSeq contours = new CvSeq(null);cvFindContours(imgThreshed, contourStorage, contours,Loader.sizeof(CvContour.class),CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);// find the largest contour in the list based on bounded box sizefloat maxArea = SMALLEST_AREA;CvBox2D maxBox = null;while (contours != null && !contours.isNull()) {if (contours.elem_size() > 0) {CvBox2D box = cvMinAreaRect2(contours, contourStorage);if (box != null) {CvSize2D32f size = box.size();float area = size.width() * size.height();if (area > maxArea) {maxArea = area;bigContour = contours;}}}contours = contours.h_next();}return bigContour;
}  // end of findBiggestContour()

cvFindContours()可以返回不同类型的轮廓,这些轮廓收集在不同类型的数据结构中。 我生成最简单的轮廓,将它们存储在线性列表中,可以使用while循环进行搜索。

经过一些实验后,我在600平方像素的有边界框上放置了一个下限,以滤除围绕图像噪点的小框。 这意味着,如果findBiggestContour()找不到足够大的框,则可能返回null。

1.2计算COG和水平角

图2所示的下一步是通过调用extractContourInfo()查找COG和与手部轮廓水平线的夹角。 在此,HandDetector中的代码从ColorRectDetector.findRect()在第5章中进行的分析开始成为公司的一部分。在该章的4.2节中,轮廓周围的封闭框用于获得中心和方向。 这是足够的,因为基础形状是矩形卡片,因此轮廓和框几乎相同。 但是,手周围的边界框可能很容易与手本身具有不同的COG或角度。 在这种情况下,有必要利用力矩直接分析手部轮廓而不是边界框。

我在第3章中使用了空间矩来查找二进制图像的COG。 可以将相同的技术应用于轮廓以找到其中心(或质心)。 我还可以计算二阶混合矩,它提供了有关质心周围像素散布的信息。 可以组合二阶矩以返回轮廓的主轴相对于x轴的方向(或角度)。

回顾第三章的OpenCV矩符号,m()矩函数定义为:

该函数带有两个参数p和q,它们用作x和y的幂。 I()函数是由像素的(x,y)坐标定义的像素的强度。 n是组成形状的像素数。

如果考虑图6中的轮廓,则θ是其主轴线与水平面的角度,+ y轴指向下方。

图6.轮廓线及其主轴线。

就m()函数而言,可以证明:

如下所示的extractContourInfo()方法使用空间矩获取轮廓的质心,并使用cvGetCentralMoment()根据上述公式计算主轴角度; 这些结果存储在全局变量cogPt和ContourAxisAngle中,以备后用。

// globals
private Point cogPt;       // center of gravity (COG) of contour
private int contourAxisAngle;     // contour's main axis angle relative to horizontal (in degrees)private ArrayList fingerTips;private void extractContourInfo(CvSeq bigContour, int scale)
{CvMoments moments = new CvMoments();cvMoments(bigContour, moments, 1);// center of gravitydouble m00 = cvGetSpatialMoment(moments, 0, 0) ;double m10 = cvGetSpatialMoment(moments, 1, 0) ;double m01 = cvGetSpatialMoment(moments, 0, 1);if (m00 != 0) {   // calculate centerint xCenter = (int) Math.round(m10/m00)*scale;int yCenter = (int) Math.round(m01/m00)*scale;cogPt.setLocation(xCenter, yCenter);}double m11 = cvGetCentralMoment(moments, 1, 1);double m20 = cvGetCentralMoment(moments, 2, 0);double m02 = cvGetCentralMoment(moments, 0, 2);contourAxisAngle = calculateTilt(m11, m20, m02);// deal with hand contour pointing downwards/* uses fingertips information generated on the last update ofthe hand, so will be out-of-date */if (fingerTips.size() > 0) {int yTotal = 0;for(Point pt : fingerTips)yTotal += pt.y;int avgYFinger = yTotal/fingerTips.size();if (avgYFinger > cogPt.y)   // fingers below COGcontourAxisAngle += 180;}contourAxisAngle = 180 - contourAxisAngle;  /* this makes the angle relative to a positive y-axis thatruns up the screen */
}  // end of extractContourInfo()private int calculateTilt(double m11, double m20, double m02)
{double diff = m20 - m02;if (diff == 0) {if (m11 == 0)return 0;else if (m11 > 0)return 45;else   // m11 < 0return -45;}double theta = 0.5 * Math.atan2(2*m11, diff);int tilt = (int) Math.round( Math.toDegrees(theta));if ((diff > 0) && (m11 == 0))return 0;else if ((diff < 0) && (m11 == 0))return -90;else if ((diff > 0) && (m11 > 0))  // 0 to 45 degreesreturn tilt;else if ((diff > 0) && (m11 < 0))  // -45 to 0return (180 + tilt);   // change to counter-clockwise angleelse if ((diff < 0) && (m11 > 0))   // 45 to 90return tilt;else if ((diff < 0) && (m11 < 0))   // -90 to -45return (180 + tilt);  // change to counter-clockwise angleSystem.out.println("Error in moments for tilt angle");return 0;
}  // end of calculateTilt()

Johannes Kilian在http://public.cranfield.ac.uk/c5354/teaching/dip/opencv/SimpleImageAnalysisbyMoments.pdf上的 “通过矩的简单图像分析”中对OpenCV的矩进行了详细说明。 calculateTilt()内的代码基于Kilian论文表1中列出的θ特殊情况。

不幸的是,轴角无法区分手指指向上方的手和手指指向下方的手,因此有必要检查指尖相对于COG的相对位置,以确定是否应调整角度。 问题在于,只有在检查了手部轮廓的凸包是否存在缺陷之后(在extractContourInfo()完成之后才出现此缺陷),该信息才可用。

我的解决方案是使用在上次调用update()时计算出的指尖坐标,该指针分析了当前帧之前的摄像头帧。 数据将是过时的,但是在两次捕捉之间的200 ms间隔内指针不会移动太多。

1.3找到指尖

指尖的识别在图3的第一行中进行; 在代码中,通过OpenCV的cvConvexHull2()将凸包包裹在轮廓上,并通过cvConvexityDefects()将多边形与轮廓进行比较以查找其缺陷。

利用轮廓的低多边形近似值而不是原始值,可以加快船体创建和缺陷分析的速度。

这些阶段在findFingerTips()方法的前半部分执行:

// globals
private static final int MAX_POINTS = 20;  // max number of points stored in an array// OpenCV elements
private CvMemStorage contourStorage, approxStorage,hullStorage, defectsStorage;// defects data for the hand contour
private Point[] tipPts, foldPts;   
private float[] depths;private void findFingerTips(CvSeq bigContour, int scale)
{CvSeq approxContour = cvApproxPoly(bigContour,Loader.sizeof(CvContour.class),approxStorage, CV_POLY_APPROX_DP, 3, 1);// reduce number of points in the contourCvSeq hullSeq = cvConvexHull2(approxContour,hullStorage, CV_COUNTER_CLOCKWISE, 0);// find the convex hull around the contourCvSeq defects = cvConvexityDefects(approxContour,hullSeq, defectsStorage);// find the defect differences between the contour and hullint defectsTotal = defects.total();if (defectsTotal > MAX_POINTS) {System.out.println("Processing " + MAX_POINTS + " defect pts");defectsTotal = MAX_POINTS;}// copy defect information from defects sequence into arraysfor (int i = 0; i < defectsTotal; i++) {Pointer pntr = cvGetSeqElem(defects, i);CvConvexityDefect cdf = new CvConvexityDefect(pntr);CvPoint startPt = cdf.start();tipPts[i] = new Point( (int)Math.round(startPt.x()*scale),(int)Math.round(startPt.y()*scale));// array contains coords of the fingertipsCvPoint endPt = cdf.end();CvPoint depthPt = cdf.depth_point();foldPts[i] = new Point( (int)Math.round(depthPt.x()*scale),(int)Math.round(depthPt.y()*scale));//array contains coords of the skin fold between fingersdepths[i] = cdf.depth()*scale;// array contains distances from tips to folds}reduceTips(defectsTotal, tipPts, foldPts, depths);
}  // end of findFingerTips()

findFingerTips()的后半部分从缺陷序列中提取尖端和褶皱坐标以及深度。 先前使用CV_COUNTER_CLOCKWISE参数调用凸包方法cvConvexHull2()意味着将以逆时针顺序存储坐标,如图7所示。

图7.指尖,褶皱和深度。

指尖存储在tipPts []数组中,手指在foldPts []中折叠(手指之间的凹痕),深度在depths []中。

如图7所示,分析通常会产生太多缺陷,因此在findFingerTips()的末尾会调用reduceTips()。 它应用了两个简单的测试来滤除不太可能是指尖的缺陷-丢弃缺陷深度较浅的点,并在其相邻折叠点之间以太大的角度进行坐标。 两者的示例如图8所示。

图8.浅深度和广角。

reduceTips()将其余的提示点存储在全局fingerTips列表中:

// globals
private static final int MIN_FINGER_DEPTH = 20;
private static final int MAX_FINGER_ANGLE = 60;   // degreesprivate ArrayList fingerTips;private void reduceTips(int numPoints, Point[] tipPts,Point[] foldPts, float[] depths)
{fingerTips.clear();for (int i=0; i < numPoints; i++) {if (depths[i] < MIN_FINGER_DEPTH)    // defect too shallowcontinue;// look at fold points on either side of a tipint pdx = (i == 0) ? (numPoints-1) : (i - 1); // predecessor of iint sdx = (i == numPoints-1) ? 0 : (i + 1);   // successor of iint angle = angleBetween(tipPts[i], foldPts[pdx], foldPts[sdx]);if (angle >= MAX_FINGER_ANGLE)    continue;      // angle between finger and folds too wide// this point is probably a fingertip, so add to listfingerTips.add(tipPts[i]);}
}  // end of reduceTips()private int angleBetween(Point tip, Point next, Point prev)
// calculate the angle between the tip and its neighboring folds
// (in integer degrees)
{return Math.abs( (int)Math.round(Math.toDegrees(Math.atan2(next.x - tip.x, next.y - tip.y) -Math.atan2(prev.x - tip.x, prev.y - tip.y)) ));
}

1.4命名手指

nameFingers()使用指尖坐标列表以及轮廓的COG和轴角来分两步标记手指。 首先,它会基于它们相对于COG的可能角度调用labelThumbIndex()来标记拇指和食指,假设它们位于手的左侧。 nameFingers()会根据相对于拇指和食指的已知顺序,尝试在labelUnknowns()中标记其他手指。

// globals
private ArrayList namedFingers;private void nameFingers(Point cogPt, int contourAxisAngle,ArrayList fingerTips)
{ // reset all named fingers to unknownnamedFingers.clear();for (int i=0; i < fingerTips.size(); i++)namedFingers.add(FingerName.UNKNOWN);labelThumbIndex(fingerTips, namedFingers);labelUnknowns(namedFingers);
}  // end of nameFingers()

Finger ID及其相对顺序在FingerName枚举中维护:

public enum FingerName {LITTLE, RING, MIDDLE, INDEX, THUMB, UNKNOWN;public FingerName getNext(){ int nextIdx = ordinal()+1;if (nextIdx == (values().length))nextIdx = 0;return values()[nextIdx]; }  // end of getNext()public FingerName getPrev(){ int prevIdx = ordinal()-1;if (prevIdx < 0)prevIdx = values().length-1;return values()[prevIdx]; }  // end of getPrev()}  // end of FingerName enum

可能的手指名称之一是UNKNOWN,该名称用于在调用命名方法之前标记所有指尖。

labelThumbIndex()尝试根据图9中所示的角度范围来标记拇指和食指。

图9.拇指和食指的角度范围。

食指可以围绕COG旋转60至120度,而拇指可以在120至200度之间移动。 我通过反复试验得出了这些角度,他们认为手是笔直向上的。

labelThumbIndex()还假定拇指和食指最有可能存储在fingerTips列表的末尾,因为轮廓船体是按逆时针顺序构建的。 因此,通过向后遍历列表,可以增加与正确缺陷匹配的机会。

// globals
private static final int MIN_THUMB = 120;  // angle ranges
private static final int MAX_THUMB = 200;private static final int MIN_INDEX = 60;
private static final int MAX_INDEX = 120;// hand details
private Point cogPt
private int contourAxisAngle;     private void labelThumbIndex(ArrayList fingerTips,ArrayList nms)
{boolean foundThumb = false;boolean foundIndex = false;int i = fingerTips.size()-1;while ((i >= 0)) {int angle = angleToCOG(fingerTips.get(i),cogPt, contourAxisAngle);// check for thumbif ((angle <= MAX_THUMB) && (angle>MIN_THUMB) && !foundThumb) {nms.set(i, FingerName.THUMB);foundThumb = true;}// check for indexif ((angle <= MAX_INDEX) && (angle > MIN_INDEX) && !foundIndex) {nms.set(i, FingerName.INDEX);foundIndex = true;}i--;}
}  // end of labelThumbIndex()

angleToCOG()计算指尖相对于COG的角度,记住要记住轮廓轴角度,以便使手笔直向上。

private int angleToCOG(Point tipPt, Point cogPt,int contourAxisAngle)
{int yOffset = cogPt.y - tipPt.y;    // make y positive up screenint xOffset = tipPt.x - cogPt.x;double theta = Math.atan2(yOffset, xOffset);int angleTip = (int) Math.round( Math.toDegrees(theta));return angleTip + (90 - contourAxisAngle);// this ensures that the hand is orientated straight up
}  // end of angleToCOG()

labelUnknowns()传递了一个手指名称列表,该列表希望在某些位置包含THUMB和INDEX,而在其他位置包含UNKNOWN。 使用命名的手指作为起点,根据手指在FingerName枚举中的顺序,将UNKNOWN更改为手指名称。

private void labelUnknowns(ArrayList nms)
{// find first named fingerint i = 0;while ((i < nms.size()) && (nms.get(i) == FingerName.UNKNOWN))i++;if (i == nms.size())   // no named fingers found, so give upreturn;FingerName name = nms.get(i);labelPrev(nms, i, name);    // fill-in backwardslabelFwd(nms, i, name);     // fill-in forwards
}  // end of labelUnknowns()

labelPrev()和labelFwd()的区别仅在于它们在名称列表中移动的方向。 labelPrev()向后移动以尝试将UNKNOWNS更改为已命名的手指,但前提是尚未将名称分配给列表。

2.画出手指

由update()执行的分析将得到指尖点列表(在fingertips全局中),关联的已命名手指列表(在namedFingers中)以及轮廓COG和轴角度。 除角度外,所有这些都由draw()用来将命名的手指标签添加到网络摄像头图像中,如下图1和图10所示。

图10.命名的手指和未知的手指。

未知的手指“尖端”(在namedFingers中标记为UNKNOWN)被绘制为红色圆圈。

// globals
private Point cogPt;
private ArrayList fingerTips;
private ArrayList namedFingers;public void draw(Graphics2D g2d)
{if (fingerTips.size() == 0)return;g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);  // line smoothingg2d.setPaint(Color.YELLOW);g2d.setStroke(new BasicStroke(4));  // thick yellow pen// label tips in red or green, and draw lines to named tipsg2d.setFont(msgFont);for (int i=0; i < fingerTips.size(); i++) {Point pt = fingerTips.get(i);if (namedFingers.get(i) == FingerName.UNKNOWN) {g2d.setPaint(Color.RED);   // unnamed fingertip is redg2d.drawOval(pt.x-8, pt.y-8, 16, 16);g2d.drawString("" + i, pt.x, pt.y-10);   // label with a digit}else {   // draw yellow line to the named fingertip from COGg2d.setPaint(Color.YELLOW);g2d.drawLine(cogPt.x, cogPt.y, pt.x, pt.y);g2d.setPaint(Color.GREEN);   // named fingertip is greeng2d.drawOval(pt.x-8, pt.y-8, 16, 16);g2d.drawString(namedFingers.get(i).toString().toLowerCase(),pt.x, pt.y-10);}}// draw COGg2d.setPaint(Color.GREEN);g2d.fillOval(cogPt.x-8, cogPt.y-8, 16, 16);
}  // end of draw()

3.手势检测

Handy应用程序会尽力将已命名的指尖转换为手势,这需要分析手指随着时间在空间中的移动方式。

初步测试表明,Handy仅在涉及伸出的拇指和/或食指(也许与其他手指结合在一起)时,才能可靠地识别手势。 这种手势包括图11中所示的“胜利”,“波浪”,“良好”,“指向”和“枪支”。

图11.适用于便捷式检测的手势。

Handy无法检测到的常见手势是“ ok”(参见图12),因为它需要将手指放在一起,而这不能仅根据轮廓缺陷来检测到。

图12.不合适的“确定”手势。

参考: Java Advent Calendar博客上的JCG合作伙伴 Attila-Mihaly Balazs 使用JavaCV进行的手和手指检测 。

翻译自: https://www.javacodegeeks.com/2012/12/hand-and-finger-detection-using-javacv.html

javacv 人脸检测

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/350436.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vector赋值的常见错误

易范错误1&#xff1a; vector<int> a; for (int i 0; i<10; i) a[i] i; //这种做法以及类似的做法都是错误的。下标只能用于获取已存在的元素&#xff0c;而现在的a[i]还是空的对象 正确做法1&#xff1a; vector<int> a; for (int i 0; i<10; i) a&…

python的pandas库中如何计算每列出现最多的值_Python Pandas中根据列的值选取多行数据...

Pandas中根据列的值选取多行数据# 选取等于某些值的行记录 用 df.loc[df[column_name] some_value]# 选取某列是否是某一类型的数值 用 isindf.loc[df[column_name].isin(some_values)]# 多种条件的选取 用 &df.loc[(df[column] some_value) & df[other_column].isin…

补码运算。

一.补码加法。 先求出两个数的补码。补码相加。最后的结果为补码&#xff0c;若要取得真值需再次求补。超过模的进位需丢掉。二.补码减法。 三.溢出检测 转载于:https://www.cnblogs.com/Ravenzzz/p/10930014.html

Oracle JDK 9 Early Access文档已更新

Raymond Gallardo在2017年4月4日发布的针对Oracle JDK 9的抢先体验文档 已更新&#xff0c;今天宣布对Oracle JDK9文档的抢先体验页面进行了更新。 Gallardo重点介绍了一些更新的部分&#xff0c;包括Oracle JDK 9的新增功能 &#xff0c; Oracle JDK 9迁移指南 &#xff0c; H…

python 读取当前文件夹下所有后缀为.lib文件

# -*- coding: utf-8 -*-import os def file_name(file_dir):for root, dirs, files in os.walk(file_dir):#print(root) # 当前目录路径#print(dirs) # 当前路径下所有子目录print(files) # 当前路径下所有非目录子文件file_name(file_dir) #输出当前文件夹下所有后缀为.li…

python分割字符串输出_python字符串分割

内置split()函数 str.split(sepNone, maxsplit-1)sep为自定义分割符&#xff0c;maxsplit为最大分割次数&#xff0c;默认值-1进行全部分割注意以下区别&#xff1a;str.split() 以空格分割&#xff0c;包括连续空格str.split( ) 同样以空格分割&#xff0c;但是不能识别连续空…

python requests 10041报错_Python-Requests1-批量登录获取uid

需求&#xff1a;从表格取不同的手机号和密码登录&#xff0c;获取不同用户的信息&#xff0c;写入本地表格requests官网&#xff1a;https://github.com/requests/requests1、安装Requests模块1、官网下载requests包2、解压&#xff0c;命令行进入python目录&#xff0c;运行安…

pdf保存如何带批注_带有批注的SpringSelenium测试

pdf保存如何带批注这篇文章描述了如何在Java中实现Selenium测试。 它的灵感来自Alex Collins的帖子&#xff0c;并带有注释。 该代码可在GitHub的Spring-Selenium-Test目录中找到。 一些替代方法和更轻巧的技术可用于对Spring MVC应用程序进行单元测试。 要进行单元测试服务&am…

javax.naming.NamingException: Cannot load JDBC driver class 'com.mysql.jdbc.Driver'

解决办法&#xff1a; Tomcat服务器的根目录下也有一个lib目录,也要把mysql的驱动包放进去转载于:https://www.cnblogs.com/kingdaqi/p/10935916.html

Spring教程:使用Spring框架和Spring Boot创建Hello World REST API

由于Java社区对早期版本的Enterprise Java感到失望&#xff0c;因此创建了Spring Framework 。 从那时起&#xff0c;它已经发展成为一个巨大的生态系统&#xff0c;可以解决构建基于Web的Java应用程序中的所有问题以及更多问题。 经常批评Spring&#xff0c;因为Spring过去常常…

Testner自动化测试平台免费开放啦

swift&#xff08;雨燕&#xff09;是目前世界上飞行速度最快的鸟之一&#xff0c;尖尾雨燕平时飞行的时速为170公里&#xff0c;最快可达到325.5公里。恰恰自动化测试的主要目的就是为了提高测试效率&#xff0c;因此&#xff0c;Testner自动化测试平台也叫 swift Testner自动…

seleniumpython定位网页元素方法_使用Selenium对网页元素进行定位的诸种方法

使用Selenium进行自动化操作&#xff0c;首先要做的就是通过webdriver的get()方法打开一个URL链接。在打开链接&#xff0c;完成页面加载之后&#xff0c;就可以通过Selenium提供的接口&#xff0c;在页面上进行各种操作了&#xff0c;下面我们来了解一下如何在查找元素。3.1 查…

有赞全链路压测方案

转载于:https://www.cnblogs.com/wuzhiyi/p/10944934.html

python 消息队列 get是从队首还是队尾取东西_python分布式爬虫中消息队列知识点详解...

当排队等待人数过多的时候&#xff0c;我们需要设置一个等待区防止秩序混乱&#xff0c;同时再有新来的想要排队也可以呆在这个地方。那么在python分布式爬虫中&#xff0c;消息队列就相当于这样的一个区域&#xff0c;爬虫要进入这个区域找寻自己想要的资源&#xff0c;当然这…

mime类型是什么类型_使用多种MIME类型测试REST

mime类型是什么类型1.概述 本文将重点介绍测试具有多种媒体类型/表示形式的RESTful服务。 这是有关使用Spring和基于Java的配置的Spring Security设置安全的RESTful Web服务的系列文章的第十篇。 REST with Spring系列&#xff1a; 第1部分 – 使用Spring 3.1和基于Java的配置…

Go-Mutex互斥量

先来看一段go1.12.5中Mutex的源码&#xff1a; // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file.// Package sync provides basic synchronization primiti…

spss方差分析_【案例】SPSS统计分析:多因素方差分析

&#xff0d; 点击上方“中国统计网”订阅我吧&#xff01;&#xff0d;多因素方差分析&#xff0c;用于研究一个因变量是否受到多个自变量(也称为因素)的影响&#xff0c;它检验多个因素取值水平的不同组合之间&#xff0c;因变量的均值之间是否存在显著的差异。多因素方差分…

你好世界

想打个Hello World&#xff0c;但是又没有继续下去。 今天买茶百道的时候&#xff0c;做茶的女孩子很开朗很友善&#xff0c;在她递给我装好的山竹荔枝的时候我闻到水果的苦味&#xff0c;于是捧住杯子埋头下去闻了一下&#xff0c;她笑着说&#xff0c;“每次我做这个的时候都…

sqlserver免安装_SQL数据分析,如何免安装在线运行?

大家好&#xff0c;在之前写了一篇关于SQL软件安装&#xff0c;读者普遍反映&#xff0c;这个软件有点不好安装&#xff0c;但是&#xff0c;从事数据分析行业&#xff0c;熟练使用SQL软件是必须的&#xff0c;于是乎......本文推送一篇免安装的&#xff0c;可以在线运行的SQL软…

python自动导出数据脚本_利用python生成一个导出数据库的bat脚本文件的方法

# 环境: python3.xdef getExportDbSql(db, index):# 获取导出一个数据库实例的sql语句sql mysqldump -u%s -p%s -h%s -P%d --default-character-setutf8 --databases mu_ins_s%s > %s.s%d.mu_ins_%d.sql %(db[user], db[pwd], db[host], db[port], index, db[server], inde…