OpenCV人员计数功能失效:Python转Java后无法检测人员
我帮你梳理了代码里的几个关键错误,这些应该是导致人员无法被检测的核心原因:
1. 视频读取与宽高获取的低级错误
你获取视频高度时误用了cap.get(3)(这是OpenCV中获取帧宽度的参数),正确的帧高度应该用cap.get(4)。这个错误会让所有基于高度计算的阈值(计数线位置、面积阈值)全部失效,相当于整个计数逻辑的基础都错了。另外资源路径也可能存在编码问题,导致视频无法正常读取。
修正代码:
// 处理资源路径,避免URL编码问题 String videoPath = new File(getClass().getResource("/peopleCounter.avi").toURI()).getAbsolutePath(); VideoCapture cap = new VideoCapture(videoPath); // 先校验视频是否成功打开 if (!cap.isOpened()) { System.err.println("无法加载视频文件,请检查路径!"); return; } // 正确获取帧宽高 double width = cap.get(Videoio.CAP_PROP_FRAME_WIDTH); double height = cap.get(Videoio.CAP_PROP_FRAME_HEIGHT);
2. 前景提取与形态学操作逻辑混乱
你的代码对前景掩码的处理顺序和目标变量完全搞错了:先对fgMask二值化后存到output,但后续形态学操作又用原始fgMask作为输入,最后找轮廓还是用未处理的fgMask——相当于完全没做噪声过滤,自然检测不到有效轮廓。
修正代码:
Mat fgMask = new Mat(); // 学习率建议用-1让算法自动调整,0.1太大容易导致背景更新过快,丢失前景 mog2.apply(frame, fgMask, -1); // 直接在fgMask上做二值化和形态学操作,过滤噪声 Imgproc.threshold(fgMask, fgMask, 200, 255, Imgproc.THRESH_BINARY); // 用getStructuringElement创建核,比手动创建更规范 Mat kernelOp = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3)); Mat kernelCl = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(11, 11)); Imgproc.morphologyEx(fgMask, fgMask, Imgproc.MORPH_OPEN, kernelOp); Imgproc.morphologyEx(fgMask, fgMask, Imgproc.MORPH_CLOSE, kernelCl); // 用处理后的fgMask提取轮廓 List<MatOfPoint> contours = new ArrayList<>(); Imgproc.findContours(fgMask, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
3. 人员匹配逻辑完全错误
你用IntStream.range((int) up_limit, (int) down_limit).forEachOrdered(n -> { ... })遍历y坐标范围,这完全不符合人员匹配的逻辑——应该是对每个检测到的轮廓(中心坐标cx, cy),遍历已有的人员列表,判断是否属于同一个人,而不是循环遍历y轴数值。这个错误会导致人员状态更新混乱,甚至无法创建新的Person实例。
修正后的人员匹配逻辑:
for (int i = 0; i < contours.size(); i++) { double area = Imgproc.contourArea(contours.get(i)); if (area > areaTH) { Moments M = Imgproc.moments(contours.get(i)); double cx = M.get_m10() / M.get_m00(); double cy = M.get_m01() / M.get_m00(); Rect rectVal = Imgproc.boundingRect(contours.get(i)); boolean isNewPerson = true; // 遍历已有人员,用欧氏距离判断是否为同一个目标(阈值可根据场景调整) for (Person pers : persons) { double distance = Math.sqrt(Math.pow(cx - pers.getX(), 2) + Math.pow(cy - pers.getY(), 2)); if (distance < 50) { isNewPerson = false; pers.updateCoords((int) cx, (int) cy); // 判断移动方向并计数 if (pers.goingUp(lineDown, lineUp)) { countU.getAndIncrement(); } else if (pers.goingDown(lineDown, lineUp)) { countD.getAndIncrement(); } // 更新人员完成状态 if (pers.getState() == '1') { if ("down".equals(pers.getDir()) && pers.getY() < down_limit) { pers.setDone(); } else if ("up".equals(pers.getDir()) && pers.getY() < up_limit) { pers.setDone(); } } break; } } // 新增人员 if (isNewPerson) { Person p = new Person(pid.get(), (int) cx, (int) cy, maxPAge); persons.add(p); pid.getAndIncrement(); } // 绘制标记 Imgproc.circle(frame, new Point((int) cx, (int) cy), 5, new Scalar(0, 0, 255), -1); Imgproc.rectangle(frame, new Point(rectVal.x, rectVal.y), new Point(rectVal.x + rectVal.width, rectVal.y + rectVal.height), new Scalar(0, 10, 155), 2); } }
4. 字符串比较错误
Java中字符串比较不能用==(比较的是对象引用),必须用equals()方法,否则永远不会匹配成功。比如原代码中的pers.getDir() == "down"要改成"down".equals(pers.getDir())(把常量放前面避免空指针)。
5. 文本绘制重复与重叠
原代码重复绘制了strUp,且strDown和strUp位置相同导致覆盖,修正后:
String strUp = "UP: " + countU; String strDown = "DOWN: " + countD; // 分别放在不同位置,避免覆盖 Imgproc.putText(frame, strUp, new Point(10, 40), font, 0.5, new Scalar(0, 0, 255), 2); Imgproc.putText(frame, strDown, new Point(10, 70), font, 0.5, new Scalar(255, 0, 0), 2);
把这些错误修正后,应该就能正常检测到人员了。
内容的提问来源于stack exchange,提问作者leecan999

