☰
Java调用OpenCV实现颜色与形状识别:从HSV到轮廓拟合的完整指南
2026/10/6 11:30:49 网站建设 项目流程

简介:这份PDF文档面向Java开发者与计算机视觉初学者,系统讲解如何用Java调用OpenCV视觉库完成几何图像的颜色与形状识别,可应用于交通信号灯、信号转向灯等需要颜色与形状判别的场景。资源包内仅含1个PDF文件,大小约894KB,内容为完整的技术论文,涵盖HSV颜色空间转换、颜色分离、滤波灰度化、二值化、轮廓提取、干扰轮廓去除、轮廓形状拟合及凸点距离判断等核心流程,并给出三角形、圆、矩形、正方形、多边形的识别思路与关键代码片段。目前已有169人学习下载,适合希望用Java而非C++或Python实现图像识别、想掌握HSV颜色空间优势与OpenCV-Java库导入方法的读者参考,可帮助快速理解从颜色分割到形状判别的完整技术链路与实现细节。

1. 从一张交通灯照片说起:Java 也能把颜色和形状拆明白

路口监控抓拍一张图,要判断当前是红灯还是绿灯、箭头指向哪边,这件事听起来像 Python + OpenCV 的常规操作,但很多做企业级后端的团队,整条链路都是 Java 写的。重新引入一套 Python 服务,运维、部署、依赖全得跟着动,成本不低。这份资料解决的正是这个场景:用 Java 调用 OpenCV 视觉库,把一张彩色几何图像里的颜色和形状分别识别出来,输出三角形、圆、矩形、正方形、多边形这些结果。

它适合两类人:一类是 Java 后端或 Android 开发者,手上已经有图像处理需求,但不想切语言;另一类是刚接触计算机视觉的在校生,想找一个能跑通、能改参数、能看懂每一步在干什么的完整流程。整套思路不依赖深度学习模型,纯传统图像处理,CPU 就能跑,参数全部可解释,这对需要稳定复现的工程场景反而更友好。下面按“资源是什么 → 怎么用 → 坑在哪”的顺序拆开讲。

2. 环境搭起来:OpenCV Java 包导入与 HSV 颜色空间选型

2.1 为什么是 HSV,而不是 RGB

OpenCV 读进来的图默认是 BGR 三通道,很多教程直接拿 R、G、B 三个值做颜色判断,结果一到光照变化就翻车。原因很直接:RGB 是把颜色拆成三原色的强度组合,同一个红色,在强光和阴影下的 R、G、B 数值能差出一大截,你很难划出一条固定的阈值线。

HSV 换了个拆法。H 是色调,直接对应“这是什么颜色”;S 是饱和度,管颜色浓不浓;V 是明度,管亮不亮。把颜色和亮度解耦之后,红色就集中在 H 的一个窄区间里,光照变化主要影响 V,对 H 的干扰小得多。这就是这份资料选 HSV 做颜色分离的根本理由,也是它能做到高识别率的前提。

各颜色的 H、S、V 取值范围是整套流程的基准表,必须记牢。常见取值如下(OpenCV 中 H 范围是 0~180,不是 0~360):

颜色H 最小值H 最大值S 范围V 范围
红(低区)01043~25546~255
红(高区)15618043~25546~255
橙112543~25546~255
黄263443~25546~255
绿357743~25546~255
蓝10012443~25546~255
紫12515543~25546~255

红色要分两段是因为 H 在 0 和 180 附近是首尾相接的,只取一段会漏掉偏紫或偏橙的红。这个细节后面避坑章节还会再提。

2.2 导入 OpenCV Java 库的完整步骤

资料里用的是 OpenCV 3.0.0,Eclipse 环境,32 位系统选 x86 包。这套流程放到现在依然成立,只是 IDE 可能换成 IDEA。核心动作就三步:下载解压、导入 jar、配置 native 库路径。

# 1. 下载 OpenCV 3.0.0 并解压,假设解压到 D:\opencv # 2. 进入 build\java 目录,能看到 opencv-300.jar # 3. 根据系统位数选择 native 库目录: # 32 位系统 -> build\java\x86 # 64 位系统 -> build\java\x64

在 IDEA 里导入的操作是:File → Project Structure → Libraries → 加号 → Java,选中opencv-300.jar。然后关键一步,配置 native 库路径,否则运行时会报UnsatisfiedLinkError:

// 在调用任何 OpenCV 函数之前,先加载 native 库 static { // 路径指向解压目录下的 dll 所在文件夹,不要带文件名 System.loadLibrary(Core.NATIVE_LIBRARY_NAME); // 如果 System.loadLibrary 找不到,改用绝对路径: // System.load("D:/opencv/build/java/x64/opencv_java300.dll"); }

Core.NATIVE_LIBRARY_NAME在 3.x 里就是opencv_java300,它会去java.library.path里找对应的 dll 或 so。Windows 下最省事的做法是把 dll 所在目录加进java.library.path,或者直接在代码里用System.load写绝对路径。Linux 下对应的是libopencv_java300.so,需要确保LD_LIBRARY_PATH包含它。

提示:32 位和 64 位的 jar 是同一个,但 native 库必须和 JVM 位数一致。用 64 位 JDK 却导了 x86 的 dll,报错信息不会直接告诉你位数不匹配,只会抛UnsatisfiedLinkError,很容易查半天。

2.3 图像缩放:先降分辨率再处理

资料里第一步是缩小图像,理由是后面要逐像素遍历 HSV 值,图越大越慢。这个优化在实时场景里是必须的。

Mat src = Imgcodecs.imread("input.jpg"); Mat resized = new Mat(); // dsize 是目标尺寸,这里缩到原图一半 Size dsize = new Size(src.cols() / 2, src.rows() / 2); Imgproc.resize(src, resized, dsize, 0, 0, Imgproc.INTER_LINEAR);

Imgproc.resize的第三个参数dsize是输出尺寸,后面两个 0 是 fx、fy 缩放因子,当 dsize 不为 0 时它们不生效。最后一个参数是插值方式,INTER_LINEAR是双线性插值,速度和质量平衡得比较好;如果追求更快可以用INTER_NEAREST,但边缘会有锯齿,影响后续轮廓提取。缩放比例不要低于 0.3,否则小图形可能被缩没,轮廓直接消失。

3. 颜色分离:从 BGR 到 HSV 再到掩膜

3.1 颜色空间转换的两步走

OpenCV 读图默认 BGR,而很多图像文件本身是 RGB 编码,所以资料里先 RGB 转 BGR,再 BGR 转 HSV。实际上Imgcodecs.imread读进来的 Mat 已经是 BGR 顺序,直接转 HSV 即可,多转一次反而容易搞混通道顺序。

Mat hsvImg = new Mat(); // 直接从 BGR 转 HSV,不要中间再插一步 RGB2BGR Imgproc.cvtColor(resized, hsvImg, Imgproc.COLOR_BGR2HSV);

Imgproc.cvtColor的第三个参数是转换码,COLOR_BGR2HSV对应 BGR 到 HSV。如果你手上的 Mat 确实是从别处来的 RGB 数据,才需要先COLOR_RGB2BGR。判断方法很简单:用Imgcodecs.imread读的,就是 BGR;用 Android Bitmap 转过来的,通常是 RGB。搞错这一步,红色和蓝色会对调,识别结果全乱。

3.2 逐像素统计与阈值判断

资料里遍历每个像素取 H、S、V,累计各颜色的像素个数,设阈值 200 来判断图中是否存在该颜色。这一步的目的是抗干扰:环境光、反光可能让某个像素偶然落进绿色区间,但不可能有 200 个像素都落进去。

int greenCount = 0; for (int i = 0; i < hsvImg.rows(); i++) { for (int j = 0; j < hsvImg.cols(); j++) { double[] hsv = hsvImg.get(i, j); int h = (int) hsv[0]; int s = (int) hsv[1]; int v = (int) hsv[2]; // 绿色区间判断 if (h >= 35 && h <= 77 && s >= 43 && s <= 255 && v >= 46 && v <= 255) { greenCount++; } } } // 阈值判断,大于 200 才认为图中存在绿色图形 boolean hasGreen = greenCount > 200;

hsvImg.get(i, j)返回的是double[],长度 3,依次是 H、S、V。注意 H 在 OpenCV 里是 0~180,不是 0~360,所以网上有些教程给的 0~360 区间直接拿来用会全部落空。阈值 200 是个经验值,图像越小这个值要相应调低,比如缩到 200x200 以下时,50~100 更合适。这个值本质上是“最小有效像素数”,设太高会漏掉小图形,设太低会把噪点当图形。

3.3 inRange 与 bitwise_and 生成颜色掩膜

统计完颜色存在性,接下来要把该颜色的区域从原图里抠出来。用Core.inRange生成掩膜,再用Core.bitwise_and把掩膜套到原图上。

Mat mask = new Mat(); Mat colorImg = new Mat(); // 绿色上下限 Scalar minVal = new Scalar(35, 43, 46); Scalar maxVal = new Scalar(77, 255, 255); // 生成掩膜:在区间内的像素为 255,否则为 0 Core.inRange(hsvImg, minVal, maxVal, mask); // 用掩膜和原图做与运算,得到只保留绿色的图像 Core.bitwise_and(resized, resized, colorImg, mask);

Core.inRange的第二个、第三个参数是 Scalar 类型的上下限,顺序是 H、S、V。Core.bitwise_and的第四个参数是掩膜,只有掩膜非零的位置才保留原图像素。这里有个容易忽略的点:bitwise_and的第二个参数也传resized,不是传mask,很多人第一次写会传错,结果得到一张黑白图而不是彩色分割图。

红色因为跨 0 和 180 两端,需要做两次 inRange 再合并:

Mat mask1 = new Mat(), mask2 = new Mat(), redMask = new Mat(); Core.inRange(hsvImg, new Scalar(0, 43, 46), new Scalar(10, 255, 255), mask1); Core.inRange(hsvImg, new Scalar(156, 43, 46), new Scalar(180, 255, 255), mask2); // 两个掩膜取或,合并成完整红色区域 Core.bitwise_or(mask1, mask2, redMask);

Core.bitwise_or把两段红色的掩膜合并,这是处理红色、紫色的标准做法。漏掉这一步,偏紫的红和偏橙的红会识别不到,实测能漏掉将近三成的红色像素。

4. 形状识别:轮廓提取、拟合与凸点判断

4.1 从掩膜到三通道:convertTo 的必要性

bitwise_and出来的colorImg是单通道或带掩膜的图像,直接做灰度化会报错,因为灰度化要求输入是三通道。资料里用convertTo转成CV_8UC3。

Mat threeChannel = new Mat(); // 把分割后的图像转为三通道 8 位无符号格式 colorImg.convertTo(threeChannel, CvType.CV_8UC3);

CvType.CV_8UC3表示 8 位无符号、3 通道。这一步不做的话,后面cvtColor转灰度会抛异常,报错信息通常是通道数不匹配。转换之后图像内容不变,只是格式对齐了。

4.2 滤波、灰度、二值化三步走

形状识别的前处理链条是:高斯滤波去噪 → 灰度化 → 二值化。三步顺序不能乱,滤波必须在灰度化之前做,否则彩色噪声会被灰度化固化下来。

Mat gray = new Mat(); Mat binary = new Mat(); // 高斯滤波,核大小 5x5 Imgproc.GaussianBlur(threeChannel, threeChannel, new Size(5, 5), 0); // 灰度化 Imgproc.cvtColor(threeChannel, gray, Imgproc.COLOR_BGR2GRAY); // 二值化,阈值 127,最大值 255 Imgproc.threshold(gray, binary, 127, 255, Imgproc.THRESH_BINARY);

Imgproc.GaussianBlur的第三个参数是核大小,必须是奇数,5x5 适合大多数场景;核越大越模糊,小图形会被糊掉。第四个参数是标准差,传 0 表示根据核大小自动算。Imgproc.threshold的第二个参数是阈值,127 是中间值,如果图像整体偏暗或偏亮,这个值要跟着调,否则二值化后可能全黑或全白。判断方法:二值化后Core.countNonZero(binary)如果接近 0 或接近总像素数,说明阈值不对。

4.3 腐蚀膨胀与轮廓提取

二值化后图像里可能有零散噪点和断开的轮廓,先用腐蚀去掉小噪点,再用膨胀把断开的轮廓连起来。

Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3)); // 腐蚀:去掉零散像素 Imgproc.erode(binary, binary, kernel); // 膨胀:连通断裂轮廓 Imgproc.dilate(binary, binary, kernel); // 找轮廓 List<MatOfPoint> contours = new ArrayList<>(); Mat hierarchy = new Mat(); Imgproc.findContours(binary, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);

Imgproc.getStructuringElement生成结构元素,MORPH_RECT是矩形核,3x3 适合小图形。Imgproc.findContours的第四个参数RETR_EXTERNAL只取最外层轮廓,忽略嵌套轮廓,这对几何图形识别是对的;第五个参数CHAIN_APPROX_SIMPLE只保留轮廓的拐点,压缩数据量。注意findContours在 OpenCV 3.x 里会修改输入的 binary 图像,如果后面还要用二值图,得先 clone 一份。

4.4 去干扰轮廓与面积过滤

找出来的轮廓里混着噪点形成的小轮廓和图像边框形成的大轮廓,用面积过滤掉。

for (int j = 0; j < contours.size(); j++) { double area = Imgproc.contourArea(contours.get(j)); // 面积太小是噪点,太大是边框,都去掉 if (area <= 10 || area >= 80000) { contours.remove(j); j--; // 删除后索引要回退,否则会跳过一个元素 } }

Imgproc.contourArea算轮廓面积。下限 10 过滤噪点,上限 80000 过滤整图边框,这两个值要按图像分辨率调。缩放到一半的图,上限可以降到 20000 左右。这里有个血泪经验:contours.remove(j)之后必须j--,否则循环会跳过一个元素,导致部分干扰轮廓没被删掉,后面形状判断就会多出莫名其妙的图形。

4.5 多边形拟合与凸点距离判形状

最后一步是形状判断。用approxPolyDP把轮廓拟合成多边形,看凸点个数,再结合相邻凸点距离区分正方形和矩形、圆和多边形。

for (MatOfPoint contour : contours) { MatOfPoint2f curve = new MatOfPoint2f(contour.toArray()); MatOfPoint2f approx = new MatOfPoint2f(); // epsilon 是拟合精度,取周长的 2% double epsilon = 0.02 * Imgproc.arcLength(curve, true); Imgproc.approxPolyDP(curve, approx, epsilon, true); int vertexCount = approx.toArray().length; // 根据凸点个数判断 if (vertexCount == 3) { // 三角形 } else if (vertexCount == 4) { // 四边形,再用边长判断正方形还是矩形 } else if (vertexCount > 6) { // 凸点多,可能是圆 } }

Imgproc.approxPolyDP的第三个参数epsilon是拟合精度,越大拟合越粗糙,凸点越少。取周长的 2% 是常用值,图形边缘毛糙时可以调到 3%~4%。第四个参数true表示闭合轮廓。approx.toArray().length就是凸点个数。

四边形区分正方形和矩形,靠的是相邻凸点距离:

Point[] pts = approx.toArray(); double d1 = pts[0].distance(pts[1]); double d2 = pts[1].distance(pts[2]); // 边长比接近 1 是正方形,否则是矩形 double ratio = Math.max(d1, d2) / Math.min(d1, d2); boolean isSquare = ratio < 1.2;

Point.distance算两点欧氏距离。比值阈值 1.2 是经验值,透视变形大的图要放宽到 1.3~1.5。圆形的判断则看凸点个数,拟合后凸点超过 6 个且各边长度接近,基本就是圆。这套逻辑不依赖机器学习,参数全部可解释,调起来心里有底。

5. 避坑与排查:那些让识别率掉到及格线以下的问题

5.1 红色只识别一半

现象:红色图形时有时无,或者只识别出偏橙的那部分,偏紫的红完全丢失。

原因:HSV 里红色跨 H 的 0 和 180 两端,只设一段区间必然漏掉另一端。

解决:用两个 inRange 分别取 0~10 和 156~180,再用Core.bitwise_or合并掩膜。紫色同理,如果紫色识别不稳,检查是不是也跨了边界。

5.2 运行时报 UnsatisfiedLinkError

现象:代码编译通过,一运行就抛java.lang.UnsatisfiedLinkError: no opencv_java300 in java.library.path。

原因:只导了 jar,没配 native 库路径,或者 dll 位数和 JVM 位数不匹配。

解决:确认 JDK 位数,选对应的 x86 或 x64 目录;用System.load写绝对路径最稳;Windows 下也可以把 dll 目录加进 PATH。报错信息不会告诉你位数问题,只能自己核对。

5.3 二值化后全黑或全白

现象:Imgproc.threshold之后图像要么全黑要么全白,轮廓一个都找不到。

原因:阈值 127 不适合当前图像的亮度分布,或者灰度化之前没做滤波,噪声把直方图拉偏了。

解决:先Core.countNonZero(binary)看非零像素占比,接近 0 或接近总数就是阈值不对。改用Imgproc.THRESH_OTSU让 OpenCV 自动算阈值,或者手动在 80~180 之间试。滤波步骤不能省,高斯核 5x5 是底线。

5.4 轮廓删除后形状判断多出图形

现象:面积过滤明明删了干扰轮廓,后面形状判断还是多出几个莫名其妙的图形。

原因:contours.remove(j)之后没有回退索引,循环跳过了一个元素。

解决:删除后立刻j--。这是 Java 里遍历 List 边删边遍历的经典坑,换成迭代器Iterator.remove()也能避免,但findContours返回的是 List,用索引循环就得手动回退。

5.5 小图形识别不到

现象:大图形识别正常,小图形完全没反应。

原因:图像缩放比例太大,小图形被缩没了;或者面积下限 10 把小图形也过滤了;或者颜色统计阈值 200 对小图形太高。

解决:缩放比例不要低于 0.5;面积下限按缩放比例调,缩到一半时下限可以降到 3~5;颜色统计阈值按图形最小像素数估,小图形多的时候降到 50 左右。三个参数要联动调,只改一个往往解决不了。

6. 进阶:把识别结果落到实际场景的几个技巧

跑通单张图之后,真正要落地到交通灯、火焰识别这类场景,还有几个技巧值得掌握。

第一,颜色阈值不要写死。不同摄像头白平衡不一样,同一盏绿灯在不同设备上 H 值能差 5~10。稳妥做法是启动时用一张已知颜色的参考图做一次标定,把 H 区间按实测值偏移。我一般会留一个calibrate()方法,传入参考图,自动算出各颜色的实际区间,比硬编码靠谱得多。

第二,形状判断加一层面积和周长比。光靠凸点个数,圆和正八边形容易混。加一个圆形度指标:4 * PI * area / (perimeter * perimeter),理想圆接近 1,多边形明显小于 1。这个指标和凸点个数一起用,圆形识别准确率能再提一截。

第三,多帧投票抗抖动。视频流里单帧识别可能因为运动模糊出错,连续 5 帧里取出现次数最多的结果作为输出,比单帧稳定得多。实现就是一个长度为 5 的队列,每帧结果入队,统计众数。

第四,把整套流程封装成可配置的管道。颜色区间、面积上下限、拟合精度这些参数抽到配置文件里,换场景时改配置不改代码。下面是一个简化的配置结构:

public class DetectConfig { // 颜色区间,key 是颜色名,value 是 [minH, minS, minV, maxH, maxS, maxV] public Map<String, int[]> colorRanges = new HashMap<>(); // 面积过滤 public double minArea = 10; public double maxArea = 80000; // 拟合精度,周长的百分比 public double approxEpsilonRatio = 0.02; // 正方形边长比阈值 public double squareRatio = 1.2; }

这样一份配置对应一个场景,交通灯一套、火焰识别一套,互不干扰。参数调优的过程也能沉淀下来,下次遇到类似场景直接复用。

验证方法上,我习惯准备一组标注好的测试图,每张图记录真实颜色和形状,跑完统计混淆矩阵。重点看两类错误:颜色对但形状错,通常是拟合精度或面积过滤的问题;形状对但颜色错,通常是 HSV 区间没覆盖全。分开排查比笼统看准确率有用得多。

从那以后我每次接图像识别需求,都强制先跑一遍颜色标定,再拿测试图验证混淆矩阵,最后才上真实数据。这套流程帮我省掉了大量“上线才发现阈值不对”的返工。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询