
简介这份PDF面向具备一定Java基础、希望入门计算机视觉的开发者与学习者聚焦用Java调用OpenCV实现几何图像的颜色与形状识别解决彩色几何图形识别中颜色分割与形状判别的问题。资源包共1个PDF文件约894KB内容为完整技术文档涵盖HSV颜色空间转换、按H/S/V取值范围分割图像、滤波去噪、灰度化与二值化、轮廓提取与干扰轮廓剔除、轮廓形状拟合及凸点距离判断等环节可识别三角形、圆、矩形、正方形、多边形等图形并延伸至交通信号灯等实际场景。已有169人学习下载适合作为JavaOpenCV图像处理入门与课程实践的参考材料。1. 颜色识别为什么不该在 RGB 里硬算一张图里有个红色三角形你想让程序说出“红色、三角形”。直觉做法是读每个像素的 R、G、B 三个值设一堆阈值判断。真动手就会发现这条路很快翻车同样是红色强光下 R 接近 255、G 和 B 也被抬高阴影里三个值一起塌下去你很难用一组固定的 RGB 区间同时覆盖亮部和暗部。RGB 把“颜色是什么”和“有多亮”耦合在三个分量里改一个亮度三个数全变。这份基于 Java 与 OpenCV 的几何图像识别资料核心思路就是把颜色判断从 RGB 搬到 HSVH 管色相、S 管饱和度、V 管明度把“是什么颜色”和“有多亮”拆成互不干扰的维度于是红色在亮处暗处都落在同一段 H 区间里。颜色分离干净之后形状识别才有稳定的输入——先按颜色切出候选区域再在单色掩膜上做轮廓提取和凸点分析用凸点个数加相邻凸点距离区分三角形、矩形、正方形、圆和多边形。整套流程不依赖深度学习纯传统视觉管线却能把颜色与形状两个维度同时判出来这正是它值得拆开看的地方每一步都在解决一个具体的、可复现的工程问题。2. HSV 颜色分离为什么换个颜色空间就能把颜色切开2.1 从 BGR 到 HSV 的两跳转换OpenCV 读进来的图像默认是 BGR 排列而多数图像源是 RGB所以资料里的转换是两跳先COLOR_RGB2BGR把通道顺序摆正再COLOR_BGR2HSV进入 HSV 空间。很多人会问为什么不直接一步 RGB 转 HSV因为 OpenCV 的cvtColor里 RGB 与 BGR 是两套独立的枚举直接拿 RGB 图去转 HSV 会得到色相整体偏移的结果——红会跑到偏青的位置阈值全废。这两跳看着啰嗦其实是把“通道顺序”和“颜色模型”两件事分开处理出错时也容易定位是哪一跳的问题。// imageResized 是缩放后的输入图HSVImg 是输出 Mat hsvImg new Mat(); // 第一跳把 RGB 顺序摆正为 OpenCV 默认的 BGR Imgproc.cvtColor(imageResized, hsvImg, Imgproc.COLOR_RGB2BGR); // 第二跳BGR 进入 HSV 颜色空间 Imgproc.cvtColor(hsvImg, hsvImg, Imgproc.COLOR_BGR2HSV);这里有个容易忽略的细节第二跳的输入输出用了同一个hsvImg。cvtColor支持原地转换但前提是类型和通道数匹配HSV 仍是三通道 8 位所以安全。如果中间插了别的处理改变了 Mat 的 type原地转换就会抛异常这是后面排查章节要展开的坑之一。2.2 H、S、V 三段区间怎么划HSV 里 H 的取值范围在 OpenCV 中是 0 到 180不是常见的 0 到 360因为 8 位存储把角度折半了S 和 V 是 0 到 255。资料给出的绿色区间是Scalar(35,43,46)到Scalar(77,255,255)这个下界不是随手写的S 下界 43、V 下界 46 是为了滤掉灰白和过暗的像素避免把背景的浅灰当成绿色。红色则特殊它横跨 H 的 0 附近需要两段区间0 到 10 和 170 到 180合并这是 HSV 色相环首尾相接带来的必然结果只设一段就会漏掉偏紫的红。颜色H 下界H 上界S 下界V 下界备注红0104346需叠加 170–180 段黄20344346与绿相邻边界要留缝绿35774346资料原始取值蓝1001244346与青区分靠 H橙11194346夹在红黄之间这张表的价值不在数字本身而在“边界要留缝”这条经验黄和绿、红和橙在色相环上是邻居区间如果首尾相接一个偏黄的橙会被同时判成两种颜色后续轮廓就会重复。留 1 到 2 个单位的空隙比事后去重省事得多。2.3 像素级统计与阈值 200 的意义分离之前资料先做了一件事遍历 HSV 图统计落在各颜色区间内的像素个数阈值设 200超过才认为图里存在该颜色。这一步是抗干扰的第一道闸。现实图像里总有反光、环境光带进来的零星彩色像素如果不做数量门槛一张白底图上几个噪点就能触发一次“识别到绿色”后面整条管线白跑。int count 0; for (int i 0; i hsvImg.rows(); i) { for (int j 0; j hsvImg.cols(); j) { double[] hsv hsvImg.get(i, j).clone(); int h (int) hsv[0], s (int) hsv[1], v (int) hsv[2]; // 落在绿色区间内才计数 if (h 35 h 77 s 43 v 46) { count; } } } // 像素数超过 200 才认为该颜色真实存在 boolean hasGreen count 200;逐像素get在 Java 里是性能热点每次调用都涉及 JNI 边界和数组拷贝大图上会明显拖慢。资料先resize缩小图像正是为了压这个成本——缩小后像素总数下降一个量级统计和后续处理都跟着变快。阈值 200 是个经验值图越小这个值越要跟着调否则小图里一个正常色块可能都凑不够 200 像素。2.4 inRange 与 bitwise_and 的分工真正切出颜色用的是两步Core.inRange生成掩膜Core.bitwise_and用掩膜把原图对应区域抠出来。这两步分工明确inRange只回答“这个像素在不在区间内”输出单通道 0/255 的 maskbitwise_and才负责保留原图的颜色信息。很多人只做inRange就以为拿到了彩色分割图结果得到一张黑白掩膜后面灰度化、二值化全乱套。Mat mask new Mat(); // 生成绿色区域的二值掩膜 Core.inRange(hsvImg, new Scalar(35, 43, 46), new Scalar(77, 255, 255), mask); Mat greenOnly new Mat(); // 用掩膜与原图取与保留绿色像素的原始颜色 Core.bitwise_and(imageResized, imageResized, greenOnly, mask);bitwise_and的第三个参数是输出第四个才是掩膜顺序写反是新手高频错误写反后输出是全黑或全白且不报错只能靠肉眼发现。这一步之后每种颜色都有一张只含该颜色的图形状识别就在这些图上分别进行。3. 形状识别从轮廓到凸点几何判定是怎么落地的3.1 三通道修复与滤波去噪颜色分离出来的图有个隐蔽问题bitwise_and的结果通道数取决于输入某些路径下会得到单通道图而后续GaussianBlur、cvtColor默认按三通道处理直接喂进去会抛类型异常。资料用convertTo把图强制转成CV_8UC3本质是补齐通道数让后续算子有统一的输入契约。// 确保是三通道 8 位避免后续算子类型不匹配 if (greenOnly.channels() ! 3) { greenOnly.convertTo(greenOnly, CvType.CV_8UC3); } // 高斯滤波压掉反光和边缘毛刺 Imgproc.GaussianBlur(greenOnly, greenOnly, new Size(5, 5), 0);高斯核取 5×5 是个折中核太小去不掉反光噪点核太大把细窄图形的边也糊掉三角形尖角会被磨圆凸点检测就少一个点。sigmaX传 0 表示由核大小自动推算一般不用手调。3.2 灰度化与二值化的顺序约束形状识别只关心“哪里是前景”不关心颜色所以要先灰度化再二值化。顺序不能反对彩色图直接threshold在 OpenCV 里行为未定义可能按某个通道算结果不可预期。灰度化用COLOR_BGR2GRAY二值化用Imgproc.threshold配THRESH_BINARY阈值取 127 是中间值配合THRESH_OTSU可以自动找分割点对光照不均的图更稳。Mat gray new Mat(); Imgproc.cvtColor(greenOnly, gray, Imgproc.COLOR_BGR2GRAY); Mat binary new Mat(); // OTSU 自动选阈值适合前景背景对比明显的几何图 Imgproc.threshold(gray, binary, 0, 255, Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU);二值化后前景是白、背景是黑findContours默认找白区域的外轮廓所以前景必须是 255。如果某张图二值化后前景变黑轮廓会一个都找不到这是排查时第一个要看的点。3.3 腐蚀膨胀与轮廓面积过滤二值图里常有零散噪点和断裂的边。资料先用erode腐蚀掉不连通的小像素块再用dilate膨胀把断开的轮廓接上。顺序是先腐蚀后膨胀净效果是去掉小噪点但保住主体形状反过来先膨胀会把噪点也放大越处理越脏。Mat kernel Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3)); Imgproc.erode(binary, binary, kernel); Imgproc.dilate(binary, binary, kernel); ListMatOfPoint contours new ArrayList(); Imgproc.findContours(binary, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE); // 面积过小是噪点过大可能是整幅背景都剔除 for (int j contours.size() - 1; j 0; j--) { double area Imgproc.contourArea(contours.get(j)); if (area 10 || area 80000) { contours.remove(j); } }面积区间 10 到 80000 是配合缩放后图像尺寸定的。这里有个血泪经验遍历删除List元素必须倒序正序删会导致索引错位、漏删甚至越界。资料原文的循环写法在正序下删元素是有隐患的实际落地时倒序更稳。3.4 approxPolyDP 拟合与凸点距离判形形状判定的核心是approxPolyDP它用 Douglas-Peucker 算法把轮廓折线简化epsilon控制简化力度取值通常是轮廓周长的 1% 到 2%。简化后剩下的顶点数就是“凸点个数”再配合相邻凸点距离就能区分图形。MatOfPoint2f curve new MatOfPoint2f(contour.toArray()); MatOfPoint2f approx new MatOfPoint2f(); double peri Imgproc.arcLength(curve, true); // epsilon 取周长 2%太小顶点过多太大形状被抹平 Imgproc.approxPolyDP(curve, approx, 0.02 * peri, true); Point[] pts approx.toArray(); int vertexCount pts.length;判定规则大致是3 个顶点是三角形4 个顶点时比较相邻边长四边接近等长判正方形两长两短判矩形5 个及以上且顶点分布均匀判多边形顶点很多且相邻距离波动小、轮廓接近圆判圆形。圆之所以不能靠顶点数直接判是因为approxPolyDP对圆会给出较多顶点必须结合“顶点到中心距离的方差”或“周长平方除以面积接近 4π”这类圆度指标辅助单看顶点数会把圆误判成多边形。资料用凸点距离做辅助判断本质就是在补这个短板。提示epsilon是形状识别最敏感的参数。同一张图epsilon 从 1% 调到 3%正方形可能从 4 个顶点变成 3 个直接判成三角形。调参时固定一组样图反复比对比盲调阈值靠谱。4. 避坑与排查这套管线最容易翻车的地方4.1 现象所有颜色都识别不到掩膜全黑原因通常是两跳转换里漏了一跳或者cvtColor的枚举写错比如把COLOR_BGR2HSV写成COLOR_RGB2HSV色相整体偏移所有区间都对不上。解决方法是先单独把 HSV 图存出来用取色工具看目标区域的实际 H 值再回头核对区间同时确认输入图到底是 RGB 还是 BGR不确定就两跳都走一遍。4.2 现象轮廓数量为 0 或异常多数量为 0 多半是二值化后前景变黑findContours找的是白区域前景黑就找不到。检查threshold的THRESH_BINARY_INV是否被误用或者 OTSU 在极端光照下选错阈值。数量异常多则是腐蚀膨胀的核太小噪点没清干净把核从 3×3 提到 5×5 再试同时确认面积过滤的上下界是否匹配当前图像尺寸。4.3 现象正方形被判成矩形或三角形这是epsilon和边长比较容差共同作用的结果。epsilon偏大时正方形的一个角被简化掉顶点数变 3偏小时顶点数虚高到 5 以上。解决方法是把epsilon固定在周长的 1.5% 到 2%边长比较用相对误差而不是绝对误差比如两边长度差在 10% 以内就算等长避免透视变形导致误判。4.4 现象程序跑得慢大图卡死逐像素get加 JNI 调用是主因。资料先resize缩小图像就是对策但缩得太狠会让小图形消失。实践里把长边压到 400 到 600 像素之间比较平衡既压住计算量又保住最小图形的轮廓。另外统计像素时可以用Core.inRange加Core.countNonZero替代手写双重循环把逐像素操作交给底层 C 实现速度差一个量级。4.5 现象红色总是识别不全红色横跨 H 的 0 和 180 两端只设一段区间必然漏掉一半。正确做法是设两段掩膜再bitwise_or合并或者把 H 区间写成 0 到 10 与 170 到 180 两次inRange后相加。这个坑在 HSV 里是结构性的不是参数没调好遇到红色识别率明显低于其他颜色先怀疑这里。5. 传统视觉管线的取舍什么时候它比深度学习更合适这套方法的边界很清楚它假设输入是干净背景上的纯色几何图形颜色和形状都有明确的几何定义。在这个前提下它的优势是深度学习给不了的——不需要训练数据、不需要 GPU、每一步都可解释可调试改一个阈值就能定位问题出在颜色分离还是形状拟合。资料里识别准确率能到 99% 以上靠的不是算法多先进而是把问题约束得足够窄让每个环节的误差都可控。代价也在这里。背景一复杂、图形一重叠、光照一极端HSV 区间就得重新标定epsilon和面积阈值也得跟着调泛化能力远不如训练过的模型。我一般会这么理解这类管线适合“场景固定、图形规范、要可解释”的场合比如产线上的色块分拣、仪表盘指示灯状态判断一旦场景开放就该考虑把颜色分离换成学习出来的分割把形状判定交给分类器。验证这套管线是否可靠有个简单办法准备一组覆盖七种颜色、五种形状的样图每张图跑完把中间结果HSV 图、掩膜、二值图、拟合后的顶点都存下来逐环节比对。哪一步的中间图和预期不符问题就锁在那一步不用在整条链上猜。这个习惯我从第一次调 HSV 区间翻车之后就养成了每次改参数都强制走一遍中间结果留存比盯着最终识别结果猜原因快得多。资料里的实现是 Java 调用 OpenCV 的完整链路从环境导入到颜色分离再到形状拟合都有对应代码和样图适合拿来当传统视觉管线的骨架去改。想深挖的话重点看approxPolyDP的epsilon怎么影响顶点数、红色双区间怎么合并这两处吃透了整条管线的脾气就摸清了。希望帮到你。本文还有配套的精品资源点击获取