首页 / 资讯中心 / 文章详情

Java+OpenCV几何图形识别:颜色与形状双判定实战

Java+OpenCV几何图形识别:颜色与形状双判定实战 ★ FEATURED ARTICLE
简介这份PDF面向具备一定Java基础、希望入门计算机视觉的开发者与学习者聚焦用Java调用OpenCV视觉库完成几何图像的颜色与形状识别解决彩色几何图形识别中颜色分离难、形状判别准确率不高的问题。资源共1个PDF文件压缩包约894KB内容为完整的技术论文涵盖HSV颜色空间转换、按H/S/V取值范围分割图像、滤波去噪、灰度化、二值化、轮廓提取与干扰轮廓剔除、轮廓形状拟合及凸点距离判断等关键环节可识别三角形、圆、矩形、正方形、多边形等多种几何图形并延伸至交通信号灯等实际应用场景。已有169人学习适合作为JavaOpenCV图像处理入门与算法思路参考。1. 用 Java 做几何图形识别一份能跑通的 OpenCV 落地笔记手上有一批几何图形图片要同时判断颜色和形状第一反应往往是上深度学习。但真到工程里样本是规则几何体、颜色种类固定、又不想背模型和标注成本时传统视觉方案反而更省事。这份资源就是围绕 Java OpenCV 走通一条完整链路BGR 转 HSV、按 H/S/V 区间做颜色分离、滤波灰度二值化、轮廓提取、去干扰轮廓、多边形拟合最后用凸点个数和相邻凸点距离判形状。它适合做交通灯、信号灯、色块分拣这类颜色与形状双判定的场景也适合想用 Java 而不是 Python 落地视觉的开发者。下面按我拆包复现的顺序讲参数和坑都摊开。2. 环境搭起来OpenCV Java 库导入与第一个 Mat2.1 为什么选 Java 调 OpenCV 而不是纯手写很多人一看到图像处理就默认 Python但这份资源的价值恰恰在于它证明了 Java 侧也能把 OpenCV 的 native 能力用起来。OpenCV 的 Java 绑定本质是 JNI 封装核心运算仍然跑在 C 层Java 只负责组织调用和业务逻辑。对已经有一套 Java 后端、想把识别能力嵌进现有服务的团队来说少引入一个 Python 运行时、少做一次跨进程通信维护成本是实打实降下来的。选型上还有一层考虑颜色识别用 HSV 而不是 RGB。RGB 三个分量都耦合了亮度信息同一个红色在强光和阴影下 R/G/B 数值能差出一大截阈值根本没法固定。HSV 把色相 H 单独拎出来S 是饱和度、V 是明度颜色判断主要看 H光照变化主要影响 V两者解耦之后阈值区间稳定得多。这也是后面所有颜色分割能成立的前提。2.2 导入 OpenCV Java 库的实操步骤资源里用的是 OpenCV 3.0.0 的 Java 包流程放到现在依然通用。核心是把解压目录里的build/java下的 jar 和对应平台的 native 库挂进项目。以 Eclipse 为例新建 Java 项目后按下面几步走# 假设 OpenCV 解压到 D:\opencv3.0.0 # 1. 找到 Java 库文件 D:\opencv3.0.0\build\java\opencv-300.jar # 2. 找到对应平台的 native 库32 位系统选 x8664 位选 x64 D:\opencv3.0.0\build\java\x86\opencv_java300.dll在 Eclipse 里右键项目 → Build Path → Configure Build Path → Libraries → Add External JARs选中opencv-300.jar。然后关键一步是让 JVM 能找到 native 库在 Run Configurations 的 VM arguments 里加-Djava.library.pathD:\opencv3.0.0\build\java\x86这里有个血泪经验jar 和 dll 的位数必须和 JVM 一致。32 位 JVM 配 x86 的 dll64 位 JVM 配 x64 的 dll混搭的典型现象是编译通过、一运行就抛UnsatisfiedLinkError报错信息还只告诉你找不到方法不告诉你位数不匹配。我第一次就栽在这排查了半天才发现 JDK 是 64 位而 dll 拿的是 x86。2.3 加载库与读取图像的最小验证环境配好后先别急着写识别逻辑用一段最小代码验证 OpenCV 是否真的加载成功import org.opencv.core.Core; import org.opencv.core.Mat; import org.opencv.imgcodecs.Imgcodecs; public class LoadCheck { public static void main(String[] args) { // 必须最先加载 native 库否则后续所有调用都会失败 System.loadLibrary(Core.NATIVE_LIBRARY_NAME); // 以灰度方式读入仅用于验证链路 Mat img Imgcodecs.imread(test.png); if (img.empty()) { System.out.println(图像读取失败检查路径); return; } System.out.println(宽 img.cols() 高 img.rows() 通道 img.channels()); } }System.loadLibrary(Core.NATIVE_LIBRARY_NAME)这行必须放在所有 OpenCV 调用之前NATIVE_LIBRARY_NAME在 3.x 里解析出来就是opencv_java300它会去java.library.path指定的目录找对应 dll。Imgcodecs.imread读进来的 Mat 默认是三通道 BGR这点要记住后面转 HSV 时顺序不能错。如果这步打印出的宽高和图片实际不符或者直接抛异常先回头查 library path 和位数别往下写。3. 颜色分离HSV 区间怎么定、阈值怎么卡3.1 从 BGR 到 HSV 的转换链路OpenCV 读图默认是 BGR 顺序而资源里处理的原始图多是 RGB 语义。转换要分两步走先 RGB 转 BGR再 BGR 转 HSVimport org.opencv.core.Mat; import org.opencv.core.Size; import org.opencv.imgproc.Imgproc; // src 为原始读入图像 Mat resized new Mat(); // 先缩小降低后续逐像素遍历的开销 Imgproc.resize(src, resized, new Size(src.cols() / 2, src.rows() / 2)); Mat hsv new Mat(); // 若源图确为 RGB 语义先转 BGR若 imread 直接读入则已是 BGR可跳过这步 Imgproc.cvtColor(resized, hsv, Imgproc.COLOR_RGB2BGR); Imgproc.cvtColor(resized, hsv, Imgproc.COLOR_BGR2HSV);Imgproc.resize的第三个参数是目标尺寸这里直接砍半。为什么要缩因为后面要遍历像素统计各颜色点数像素总量直接决定耗时缩一半面积就降到四分之一对识别精度影响很小。cvtColor的第三个参数是转换码COLOR_BGR2HSV是固定常量别手写数字。注意资源里那段 RGB2BGR 再 BGR2HSV 的写法如果图本来就是imread读进来的 BGR第一步其实是多余的甚至会引入色偏实际用的时候先确认源图色彩空间。3.2 七种颜色的 H/S/V 区间表HSV 里 H 的取值范围在 OpenCV 中是 0~180不是常见的 0~360因为 8 位存储除以了 2S 和 V 都是 0~255。资源给了绿色区间的示例其余颜色按同一思路定。下面这张表是我按常见几何色块调出来的参考区间实际项目要拿样本图微调颜色H 最小值H 最大值S 最小S 最大V 最小V 最大红0104325546255红(高区)1561804325546255橙11254325546255黄26344325546255绿35774325546255青78994325546255蓝1001244325546255紫1251554325546255红色要拆成两段是因为 H 在 0 和 180 附近是首尾相接的单段区间会漏掉偏紫的红。S 下限设 43、V 下限设 46 是为了滤掉灰白和过暗的像素这两个值来自 OpenCV 社区常用的经验下限不是拍脑袋。调区间时优先动 H 的上下界S/V 保持宽一点否则容易把正常色块也切掉。3.3 逐像素统计与 inRange 分割定好区间后先统计每种颜色的像素点数用阈值判断图里到底有没有这个颜色避免对不存在的颜色做无谓的后续处理import org.opencv.core.Scalar; import org.opencv.core.Core; // 统计绿色像素点数量 int greenCount 0; for (int i 0; i hsv.rows(); i) { for (int j 0; j hsv.cols(); j) { double[] px hsv.get(i, j); int h (int) px[0], s (int) px[1], v (int) px[2]; if (h 35 h 77 s 43 v 46) { greenCount; } } } // 阈值 200低于此值认为图中无该颜色防止反光噪点误判 if (greenCount 200) { Mat mask new Mat(); Mat dst new Mat(); Core.inRange(hsv, new Scalar(35, 43, 46), new Scalar(77, 255, 255), mask); Core.bitwise_and(resized, resized, dst, mask); }hsv.get(i, j)返回的是double[]三个元素依次是 H、S、V注意这里 i 是行、j 是列别写反。阈值 200 是资源里给的作用是过滤环境反光和零散噪点太小会把噪点当目标太大可能漏掉小图形按你的图分辨率调。Core.inRange把区间内的像素置 255、区间外置 0得到一张二值 maskCore.bitwise_and再用 mask 和原图做与运算把目标颜色区域抠出来。这两步是颜色分离的核心mask 质量直接决定后面轮廓干不干净。4. 形状识别从轮廓到凸点判形4.1 三通道转换与滤波去噪bitwise_and出来的dst是单通道图直接做灰度化会报错得先转回三通道import org.opencv.core.CvType; import org.opencv.core.Size; Mat colorImg new Mat(); // 单通道转三通道否则后续 cvtColor 灰度化会抛异常 dst.convertTo(colorImg, CvType.CV_8UC3); Mat blurred new Mat(); // 高斯核 5x5sigma 自动推导 Imgproc.GaussianBlur(colorImg, blurred, new Size(5, 5), 0);convertTo的第二个参数CV_8UC3表示 8 位无符号三通道这是灰度化函数能接受的前提。高斯核用 5×5 是平衡去噪和保留边缘的常用值核越大越糊边缘一糊轮廓拟合就飘。sigma 传 0 让 OpenCV 按核大小自动算一般不用手动指定。4.2 灰度化、二值化与形态学清理接着灰度化、二值化再用腐蚀膨胀把零散像素和断口处理掉Mat gray new Mat(); Imgproc.cvtColor(blurred, gray, Imgproc.COLOR_BGR2GRAY); Mat binary new Mat(); // 阈值 100最大值 255THRESH_BINARY 表示超过阈值置白 Imgproc.threshold(gray, binary, 100, 255, Imgproc.THRESH_BINARY); Mat kernel Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3)); // 先腐蚀去掉不连通的零散点 Imgproc.erode(binary, binary, kernel); // 再膨胀把断开的轮廓接上 Imgproc.dilate(binary, binary, kernel);阈值 100 是资源里的取值实际要看你的图对比度背景偏亮就往上调。腐蚀在前、膨胀在后顺序不能反先腐蚀把噪点吃掉再膨胀补回主体轮廓反过来的话噪点会被先放大再腐蚀清理不干净。3×3 的矩形核适合规则几何体核再大容易把细边磨没。4.3 findContours 与面积过滤二值图准备好后提取轮廓并按面积剔除干扰import org.opencv.core.MatOfPoint; import java.util.ArrayList; import java.util.List; ListMatOfPoint contours new ArrayList(); Mat hierarchy new Mat(); // RETR_EXTERNAL 只取最外层轮廓CHAIN_APPROX_SIMPLE 压缩直线段 Imgproc.findContours(binary, contours, hierarchy, 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); } }RETR_EXTERNAL只保留最外层几何图形识别一般不需要嵌套轮廓。面积上下限 10 和 80000 是资源给的下限滤掉噪点小轮廓上限滤掉整张图边框这类超大轮廓。这里有个容易翻车的点资源原文是正序遍历加remove边遍历边删会导致索引错位、漏删元素我改成倒序才稳。这个细节不写出来照着抄的人大概率会踩。4.4 approxPolyDP 拟合与凸点判形规则最后对每个轮廓做多边形拟合用凸点个数和相邻凸点距离判形状import org.opencv.core.MatOfPoint2f; import org.opencv.core.Point; import org.opencv.imgproc.Imgproc; for (MatOfPoint c : contours) { MatOfPoint2f curve new MatOfPoint2f(c.toArray()); MatOfPoint2f approx new MatOfPoint2f(); // 周长作为 epsilon 基准0.02 是常用逼近精度 double peri Imgproc.arcLength(curve, true); Imgproc.approxPolyDP(curve, approx, 0.02 * peri, true); Point[] pts approx.toArray(); int vertexCount pts.length; // 相邻凸点距离用于区分正方形和矩形 double d1 dist(pts[0], pts[1]); double d2 dist(pts[1], pts[2]); // 判形逻辑见下方说明 }approxPolyDP的第三个参数 epsilon 决定拟合精度取周长的 2% 是通用经验值太大顶点会被合并、三角形可能拟合成一条线太小则保留过多锯齿点、正方形拟合成多边形。true表示闭合轮廓。判形规则按顶点数走3 个点是三角形4 个点且相邻边近似相等是正方形否则是矩形5 个点是五边形顶点数更多且相邻凸点距离波动小、轮廓接近圆的是圆形。圆形没有严格顶点拟合后顶点数会偏多所以圆要结合面积和周长比圆度辅助判断单靠顶点数容易和正多边形混淆。5. 避坑与排查五个真实翻车点5.1 现象运行即抛 UnsatisfiedLinkError原因基本是 native 库没加载上要么java.library.path没配、要么 jar 和 dll 位数不一致、要么System.loadLibrary没放在最前面。解决顺序是先确认 JVM 位数java -version看是否 64 位再确认 dll 目录最后检查加载语句位置。这三步走完九成能解决。5.2 现象颜色分割出来一片空白或整片全白多半是 HSV 区间定错或者转换链路多转了一次。如果源图是imread读的 BGR又做了一次 RGB2BGR色相会整体偏移红色可能跑到蓝色区间去。排查方法是把某个已知颜色像素的 H/S/V 打印出来对照区间表看落在哪再决定是改区间还是改转换链路。5.3 现象轮廓提取数量对不上目标图形被漏掉常见原因是二值化阈值不合适或者腐蚀核太大把细轮廓磨断了。先看二值图长什么样如果目标区域本身就不完整问题在阈值如果完整但轮廓断开把腐蚀核从 3×3 降到 2×2 或改用开运算。另外面积过滤的上下限也要按图分辨率调图小的时候 80000 的上限可能把正常目标也滤掉。5.4 现象正方形被判成矩形或圆被判成多边形正方形判成矩形是相邻凸点距离比较的容差太严实际拍摄有透视变形边长不会完全相等容差放到 10%~15% 比较稳。圆判成多边形是approxPolyDP的 epsilon 太小顶点保留太多把 epsilon 从 0.02 提到 0.03~0.04或者改用圆度4π×面积/周长²辅助判定圆度接近 1 就是圆。5.5 现象同一张图多次运行结果不一致如果代码里用了多线程或并行流处理轮廓Mat不是线程安全的共享 Mat 会出随机结果。另外contours.remove在正序遍历时删元素也会导致结果飘。解决是把轮廓处理改成单线程顺序执行删除操作一律倒序Mat 用完及时release()。6. 进阶把识别率从能跑到稳的几个手法资源里提到识别准确率 99% 以上但那是特定样本下的数字换一批图大概率掉。想把稳定性拉起来我一般会在这几个地方加码。第一是颜色区间自适应不要写死 H 范围先对样本图做一次直方图统计取目标色像素 H 值的 5% 和 95% 分位作为区间边界比固定表抗光照差异。第二是形状判定加一层圆度兜底顶点数判形对规则多边形有效但透视变形下正方形和菱形容易混圆度加边长比双条件能压住误判。第三是预处理阶段加一步自适应阈值Imgproc.adaptiveThreshold在光照不均的图上比全局阈值稳得多代价是参数多一个 blockSize 要调一般取奇数、从 11 起步试。第四是把整个链路包成一个可配置的 pipeline颜色区间、面积上下限、epsilon 系数都抽成配置项换场景只改配置不改代码这在多品类分拣场景里能省大量重复劳动。验证方法上别只看单张图跑通就收工。准备一组覆盖七种颜色、五种形状、不同光照的样本跑一遍统计混淆矩阵重点看哪些颜色对容易串红和橙、蓝和紫是高频混淆对哪些形状容易混正方形和矩形、圆和正多边形。混淆对出来了针对性收窄对应颜色的 H 区间或加形状二次校验比盲目调参高效。有个习惯我后来一直保持每次改完阈值或区间先把中间产物HSV 图、mask、二值图、拟合后的轮廓图全部落盘存一份出问题时直接看是哪一步开始不对比在代码里打断点快得多。这套流程看着笨但省下的排查时间远超存图那点开销。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站