Java+OpenCV实战:颜色与形状识别全流程解析

发布时间:2026/10/24 22:53:12
Java+OpenCV实战:颜色与形状识别全流程解析 简介这份PDF面向具备一定Java基础、希望入门计算机视觉的开发者与学习者聚焦如何用Java调用OpenCV视觉库完成几何图像的颜色与形状识别解决彩色几何图形识别中颜色分离难、形状判别准确率不高的问题。资源包共1个PDF文件约894KB内容以论文形式系统呈现完整技术路线先将图像从BGR转换到HSV颜色空间按H、S、V取值范围分割各颜色区域再经高斯滤波、灰度化、二值化、腐蚀膨胀、轮廓提取与干扰轮廓剔除最后通过轮廓形状拟合与凸点距离判断识别三角形、圆、矩形、正方形、多边形等图形。文中还说明了OpenCV Java包的导入方式并给出交通信号灯、转向灯等有颜色与形状识别需求的应用场景。目前已有169人学习适合作为Java视觉入门与图像处理流程梳理的参考材料。1. 从一张交通灯照片说起Java 也能做颜色与形状识别路口抓拍一张照片画面里有红绿灯、转向箭头、圆形禁令标志人眼一秒就能分辨但要让程序说出「这是红色圆形」和「这是绿色三角形」就得走一遍完整的图像处理链路。这个项目做的就是这件事用 Java 调用 OpenCV 视觉库把一张彩色几何图拆成颜色层再对每一层做轮廓提取和形状拟合最终输出「什么颜色 什么形状」。它适合两类人一是手上只有 Java 技术栈、不想为了一个识别需求再搭 Python 环境的后端开发者二是想拿一个能跑通的 OpenCV Java 入门工程照着改造成交通灯识别、积木分拣、教学演示的工程同学。整条链路不依赖深度学习模型纯传统视觉算法CPU 就能跑调试时每一步中间图都能存下来看这点对二次开发特别友好。2. 工程结构与技术选型为什么是 Java OpenCV 而不是纯手写2.1 项目整体分层与目录组织这个项目本质是一个 Java SE 工程没有 Spring、没有 Web 容器核心就是「读图 → 处理 → 输出结果」的批处理流程。按论文里的处理顺序我一般会把它拆成下面这样的包结构方便单步调试和替换算法geometry-recognizer/ ├── lib/ │ └── opencv-300.jar # OpenCV Java 绑定库 ├── native/ │ └── opencv_java300.dll # 对应平台的本地库x86/x64 ├── src/ │ └── com/example/vision/ │ ├── Main.java # 入口遍历图片目录 │ ├── ImageLoader.java # 读图、缩放 │ ├── ColorSegmenter.java # BGR→HSV、按颜色分割 │ ├── ShapeAnalyzer.java # 滤波、二值化、轮廓、拟合 │ ├── ShapeRule.java # 凸点数量与距离判定规则 │ └── ResultWriter.java # 结果输出控制台/CSV └── images/ └── sample1.pnglib放 jarnative放动态库这两个必须成对出现缺一个就会在System.loadLibrary那一步直接崩。ColorSegmenter和ShapeAnalyzer是两个可以独立测试的类输入输出都是Mat这样调参时不用每次跑全流程。2.2 技术选型HSV 与 OpenCV Java 绑定的取舍论文里明确提到RGB 三原色做颜色识别难度大、识别率不高而 HSV 把色调 H、饱和度 S、明度 V 分开每种颜色的 H 取值范围相对独立分割起来直观得多。这是选 HSV 的核心理由不是玄学是颜色空间本身的特性决定的。选 Java 而不是 C/Python是因为目标场景往往是已有 Java 后台系统识别模块作为其中一个环节嵌入省去跨语言调用。代价是 OpenCV 的 Java 绑定只覆盖了常用 API一些冷门函数没有暴露遇到这种情况只能退回 JNI 或者换算法。常见做法是先用 Python 快速验证参数再把验证过的参数搬到 Java 里两边 API 名字基本一致迁移成本很低。依赖上只需要一个opencv-300.jar加对应平台的 native 库。32 位系统选 x86 包64 位选 x64 包这个必须和 JVM 位数一致不是和操作系统位数一致——这是很多人第一次跑就翻车的地方后面避坑章节会细说。2.3 环境搭建与第一个可运行入口把 OpenCV 解压后build/java下就是 Java 绑定。Eclipse 里右键项目 → Build Path → Add External JARs选中opencv-300.jar然后配置 native 库路径两种方式任选一是把 dll 放到项目根目录并在 VM 参数里加-Djava.library.path./native二是把 dll 丢进C:\Windows\System32不推荐污染系统目录。入口代码长这样public class Main { static { // 必须在任何 OpenCV 调用之前加载本地库否则 Mat 初始化会抛 UnsatisfiedLinkError System.loadLibrary(Core.NATIVE_LIBRARY_NAME); } public static void main(String[] args) { // 读图IMREAD_COLOR 保证三通道后续 cvtColor 才不会因通道数不符报错 Mat src Imgcodecs.imread(images/sample1.png, Imgcodecs.IMREAD_COLOR); if (src.empty()) { System.out.println(图片读取失败检查路径); return; } // 缩小图像长宽各缩到原来的 0.5减少后续逐像素遍历的开销 Mat resized new Mat(); Imgproc.resize(src, resized, new Size(src.cols() / 2, src.rows() / 2)); ColorSegmenter segmenter new ColorSegmenter(); ListMat colorMasks segmenter.segment(resized); ShapeAnalyzer analyzer new ShapeAnalyzer(); for (Mat mask : colorMasks) { String shape analyzer.analyze(mask); System.out.println(识别结果 shape); } } }System.loadLibrary(Core.NATIVE_LIBRARY_NAME)这行是硬门槛NATIVE_LIBRARY_NAME在 3.x 版本里就是opencv_java300它会去java.library.path里找对应文件。Imgproc.resize的第三个参数是目标尺寸论文里没给具体缩放比例实践中缩到原图一半或固定宽度 640 都行关键是保持长宽比否则形状会被拉变形后面凸点距离判定直接失效。3. 颜色分割链路从 BGR 到 HSV 再到单色掩膜3.1 颜色空间转换的两个坑OpenCV 默认按 BGR 存图而很多图像库按 RGB 存论文里特意写了「先 RGB 转 BGR再 BGR 转 HSV」两步。实际用Imgcodecs.imread读进来的图已经是 BGR不需要再转一次 RGB多转一次反而把红蓝通道对调红色会识别成蓝色。转换代码Mat hsv new Mat(); // 输入是 imread 读入的 BGR 图直接转 HSV不要先转 RGB Imgproc.cvtColor(resized, hsv, Imgproc.COLOR_BGR2HSV);COLOR_BGR2HSV这个常量选错比如写成COLOR_RGB2HSV是颜色全错的头号原因。判断方法很简单转完后取图像中心像素打印 H 值红色应该在 0 附近或 170 以上绿色在 35 到 77 之间对不上就是转换方向错了。3.2 逐像素统计与颜色存在性判断论文里用逐像素遍历统计各颜色像素个数设阈值 200 判断图中是否存在该颜色目的是过滤环境光和反光带来的零星干扰。逐像素在 Java 里性能一般图大了会明显卡常见做法是先用Core.inRange得到掩膜再用Core.countNonZero统计非零像素效果一样但快一个数量级// 绿色在 HSV 下的取值范围H 35~77S 43~255V 46~255 Scalar lowerGreen new Scalar(35, 43, 46); Scalar upperGreen new Scalar(77, 255, 255); Mat greenMask new Mat(); Core.inRange(hsv, lowerGreen, upperGreen, greenMask); // 统计该颜色像素数量低于阈值认为图中没有这种颜色 int pixelCount Core.countNonZero(greenMask); boolean hasGreen pixelCount 200;Scalar三个参数依次是 H、S、V 的下界或上界。阈值 200 是论文给的经验值实际用的时候要按图像分辨率调缩到 640 宽之后 200 偏小容易把噪点当目标原图不缩的话 200 又偏大。我一般按「目标区域最小面积」反推比如最小几何图形占 30×30 像素阈值就设 900 左右。3.3 七种颜色的 H 区间与掩膜生成论文提到识别 7 种常见颜色HSV 里红色跨了 0 附近的两段需要合并两个区间这是最容易漏的一点。下面这张表是我按论文思路整理的常用区间实际项目里建议用滑块工具现场标定不同摄像头白平衡差异很大颜色H 下界H 上界S 范围V 范围备注红01043-25546-255需叠加 170-180 段橙112543-25546-255与红、黄边界易混黄263443-25546-255光照强时 V 易饱和绿357743-25546-255区间最宽最稳青789943-25546-255实际场景少见蓝10012443-25546-255深蓝 H 偏大紫12515543-25546-255与品红交界模糊红色两段合并的写法Mat redMask1 new Mat(); Mat redMask2 new Mat(); Core.inRange(hsv, new Scalar(0, 43, 46), new Scalar(10, 255, 255), redMask1); Core.inRange(hsv, new Scalar(170, 43, 46), new Scalar(180, 255, 255), redMask2); Mat redMask new Mat(); // 两段掩膜按位或得到完整红色区域 Core.bitwise_or(redMask1, redMask2, redMask);拿到掩膜后论文用Core.bitwise_and把掩膜和原图取与得到只保留该颜色的彩色图。这一步的意义是后续形状分析要在「干净的单色图」上做而不是在掩膜上直接做因为掩膜是单通道二值图直接送进轮廓提取会丢掉边缘细节。4. 形状识别链路滤波、二值化、轮廓与凸点判定4.1 从彩色分割图到可处理的三通道图bitwise_and出来的图有时是单通道论文里特别提到「rdst 不是三通道图像不能灰度化」要用convertTo转成CV_8UC3。这个细节很关键单通道图直接调COLOR_BGR2GRAY会抛异常。转换代码Mat colorPart new Mat(); Core.bitwise_and(resized, resized, colorPart, greenMask); // 确保是三通道否则后续 cvtColor 会因通道数不匹配失败 if (colorPart.channels() 1) { colorPart.convertTo(colorPart, CvType.CV_8UC3); }CV_8UC3表示 8 位无符号、3 通道这是 OpenCV 里彩色图的标准格式。转换后像素值不变只是通道数补齐不会影响颜色。4.2 滤波、灰度、二值化的顺序不能乱论文的顺序是高斯滤波去噪 → 灰度化 → 二值化。顺序颠倒会出问题先二值化再滤波边缘会被模糊掉轮廓提取出来是锯齿状先灰度再滤波彩色噪声已经混进灰度值滤不干净。正确写法Mat gray new Mat(); Mat blurred new Mat(); Mat binary new Mat(); // 5x5 高斯核标准差 0 表示由核大小自动推算 Imgproc.GaussianBlur(colorPart, blurred, new Size(5, 5), 0); Imgproc.cvtColor(blurred, gray, Imgproc.COLOR_BGR2GRAY); // 阈值 100最大值 255THRESH_BINARY 表示超过阈值置白 Imgproc.threshold(gray, binary, 100, 255, Imgproc.THRESH_BINARY);GaussianBlur的核大小必须是奇数写 4 会直接报错。阈值 100 是论文经验值实际用 Otsu 自动阈值更稳把THRESH_BINARY换成THRESH_BINARY | THRESH_OTSU阈值参数会被忽略由算法自己算适合光照不均的图。4.3 腐蚀膨胀与轮廓过滤论文用erode去掉零散像素、dilate连通断裂轮廓然后findContours提取轮廓再按面积过滤掉太小和太大的干扰。面积过滤那段代码有个隐患在遍历contours的同时remove索引会错位漏删或越界。稳妥写法是倒序遍历或者用迭代器ListMatOfPoint contours new ArrayList(); Mat hierarchy new Mat(); Imgproc.findContours(binary, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE); // 倒序遍历删除避免正序删除导致索引错位 for (int i contours.size() - 1; i 0; i--) { double area Imgproc.contourArea(contours.get(i)); if (area 10 || area 80000) { contours.remove(i); } }RETR_EXTERNAL只取最外层轮廓几何图形识别用这个就够了取全部层级反而会把图形内部的噪点也当轮廓。CHAIN_APPROX_SIMPLE只存拐点省内存。面积上下界 10 和 80000 要按缩放比例调缩一半后 80000 相当于原图 320000大图会被误删。4.4 多边形拟合与凸点距离判形状论文的核心判定逻辑是approxPolyDP拟合多边形取顶点个数再结合相邻顶点距离判断具体形状。顶点数 3 是三角形4 是四边形再看边长区分正方形和矩形5 以上是多边形顶点数很多且距离接近则判为圆。代码for (MatOfPoint contour : contours) { MatOfPoint2f curve new MatOfPoint2f(contour.toArray()); MatOfPoint2f approx new MatOfPoint2f(); // 周长 2% 作为拟合精度值越小顶点越多 double peri Imgproc.arcLength(curve, true); Imgproc.approxPolyDP(curve, approx, 0.02 * peri, true); Point[] pts approx.toArray(); int vertexCount pts.length; if (vertexCount 3) { System.out.println(三角形); } else if (vertexCount 4) { // 四条边两两比较接近则正方形否则矩形 double d1 distance(pts[0], pts[1]); double d2 distance(pts[1], pts[2]); System.out.println(Math.abs(d1 - d2) 5 ? 正方形 : 矩形); } else if (vertexCount 6) { System.out.println(圆形或多边形); } }0.02 * peri这个系数是拟合精度的关键太大顶点被合并正方形可能拟合成三角形太小顶点过多正方形拟合成八边形。论文没给具体系数实践中 0.02 到 0.04 之间比较稳。圆形判定不能只看顶点数还要看顶点到中心的距离方差方差小才是圆否则正八边形也会被误判。5. 避坑与排查跑不通时先看这几处5.1 UnsatisfiedLinkErrorno opencv_java300 in java.library.path现象是程序一启动就抛这个异常连第一行读图都执行不到。原因是 native 库没被找到可能是 dll 没放、路径没配、或者 JVM 位数和 dll 位数不匹配。解决顺序先确认java.library.path打印出来包含 dll 所在目录再确认 JVM 是 32 位还是 64 位java -version看输出最后确认 dll 文件名和Core.NATIVE_LIBRARY_NAME一致。32 位 JVM 加载 64 位 dll 会报同样的错不会提示位数不符这点很坑。5.2 颜色全错红蓝对调或整图一片黑现象是分割出来的掩膜要么全黑要么颜色完全不对。原因通常是cvtColor用了COLOR_RGB2HSV而输入其实是 BGR或者 H 区间写反了上下界。排查方法转完 HSV 后取几个已知颜色像素打印 H 值和区间表对照再检查Scalar的第一个参数是不是小于第二个参数写反了inRange永远返回空。5.3 轮廓提取为空或轮廓数量爆炸现象是findContours返回 0 个轮廓或者返回几百个小轮廓。前者多半是二值化阈值不对整图变成全黑或全白后者是噪声没滤干净腐蚀膨胀参数太弱。排查时把binary图用Imgcodecs.imwrite存下来看一眼就能判断是阈值问题还是噪声问题。阈值问题换 Otsu噪声问题加大高斯核或增加腐蚀次数。5.4 形状判定飘忽同一张图两次结果不同现象是正方形有时判成矩形、圆有时判成多边形。原因是拟合精度系数和距离容差是硬编码的图像缩放比例一变像素距离跟着变容差就不适用了。解决办法是把距离容差改成相对值比如「两边长度差小于短边的 10% 判正方形」而不是写死 5 像素。另外approxPolyDP的closed参数要传true传false首尾不闭合顶点数会少一个。5.5 大图处理慢到超时现象是 4K 图跑一次要好几秒。原因是逐像素遍历和全分辨率轮廓提取都很吃 CPU。解决就是论文第一步做的缩放但缩放比例要固定不能按原图比例缩否则不同尺寸的图处理结果不可比。我一般统一缩到宽度 640高度按比例算这样参数标定一次全场景通用。6. 二次开发把识别结果接进业务系统的改造思路拿到这个工程后最直接的改造方向是把「打印形状」换成「结构化输出 业务回调」。论文里的输出是控制台字符串实际项目里需要的是带颜色、形状、外接矩形坐标、置信度的对象方便上层做交通灯状态判断或分拣指令下发。改造时我会先定义一个结果类把ColorSegmenter和ShapeAnalyzer的返回值统一收口public class RecognitionResult { public String color; // 颜色名 public String shape; // 形状名 public Rect boundingBox; // 外接矩形用于定位 public double area; // 轮廓面积可作为置信度参考 public int vertexCount; // 拟合顶点数调试用 }然后在ShapeAnalyzer.analyze里把boundingRect和contourArea一并带出来。有了坐标就能做「画面里同时有多个图形」的场景按boundingBox.x排序从左到右输出交通灯场景里正好对应红黄绿三个灯位。第二个改造点是参数外置。现在所有 HSV 区间、面积阈值、拟合系数都写死在代码里换一个摄像头就得改代码重编译。我一般抽一个config.properties启动时加载现场调参不用动源码green.h.min35 green.h.max77 min.area10 max.area80000 approx.epsilon.ratio0.02读取用Properties类加载失败时回退到默认值保证工程在任何环境下都能启动。这个习惯是从一次现场调试来的当时为了改一个阈值重新打包部署花了半小时后来所有视觉参数一律外置改完重启即可。第三个改造点是验证方法。形状识别没有标准答案靠肉眼比对容易漏。我的做法是准备一组带标注的测试图每张图文件名里写期望结果比如red_triangle_01.png跑批处理时自动比对输出混淆矩阵。这样每次调完参数跑一遍准确率是升是降一目了然不用凭感觉。论文里说准确率 99% 以上那是在受控光照和干净背景下测的真实场景里能到 85% 就算不错心里要有这个预期。最后一个技巧是关于圆的判定。论文用凸点距离判形状圆和正多边形的顶点数都很多光看数量分不开。我一般加一步「顶点到轮廓中心距离的方差」方差小于阈值的判圆否则判多边形。这一步用Imgproc.minEnclosingCircle拿到圆心和半径再遍历顶点算距离代码不到二十行但能把圆和多边形的误判率压下去一大截。从那以后我每次接视觉类需求都强制先把中间图全部落盘存一遍滤波图、二值图、轮廓图各存一份出问题直接翻图定位比在代码里打断点快得多。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询