Java: Queue 各种方法的细小区别

本文详细介绍了Java中的队列实现,包括非阻塞队列如LinkedList、PriorityQueue和ConcurrentLinkedQueue,以及阻塞队列如ArrayBlockingQueue和PriorityBlockingQueue等。文章对比了add与offer、remove与poll等方法的区别,并探讨了各种队列的特点和适用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java提供了Quere,相当好用,在1.5版本中又有增强。

add 增加一个元索 如果队列已满,则抛出一个IIIegaISlabEepeplian异常
remove 移除并返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
element 返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
offer 添加一个元素并返回true 如果队列已满,则返回false
poll 移除并返问队列头部的元素 如果队列为空,则返回null
peek 返回队列头部的元素 如果队列为空,则返回null
put 添加一个元素 如果队列满,则阻塞
take 移除并返回队列头部的元素 如果队列为空,则阻塞

remove、element、offer 、poll、peek 其实是属于Queue接口。


Queue: 基本上,一个队列就是一个先入先出(FIFO)的数据结构

offer,add区别:
一些队列有大小限制,因此如果想在一个满的队列中加入一个新项,多出的项就会被拒绝。
这时新的 offer 方法就可以起作用了。它不是对调用 add() 方法抛出一个 unchecked 异常,而只是得到由 offer() 返回的 false。

poll,remove区别:
remove() 和 poll() 方法都是从队列中删除第一个元素(head)。remove() 的行为与 Collection 接口的版本相似,
但是新的 poll() 方法在用空集合调用时不是抛出异常,只是返回 null。因此新的方法更适合容易出现异常条件的情况。

peek,element区别:
element() 和 peek() 用于在队列的头部查询元素。与 remove() 方法类似,在队列为空时, element() 抛出一个异常,而 peek() 返回 null。

-----------------------------------------------------------------------------------------
Tiger中有2组Queue的实现:实现了新的BlockingQueue接口的
和没有实现的

-------------------------------------------------------------
没有实现的阻塞接口的:LinkedList: 实现了java.util.Queue接口
java.util.AbstractQueue
内置的不阻塞队列: PriorityQueue 和 ConcurrentLinkedQueue

PriorityQueue 和 ConcurrentLinkedQueue 类在 Collection Framework 中加入两个具体集合实现。

PriorityQueue 类实质上维护了一个有序列表。加入到 Queue 中的元素根据它们的天然排序(通过其 java.util.Comparable 实现)或者根据传递给构造函数的 java.util.Comparator 实现来定位。

ConcurrentLinkedQueue 是基于链接节点的、线程安全的队列。并发访问不需要同步。因为它在队列的尾部添加元素并从头部删除它们,所以只要不需要知道队列的大小,ConcurrentLinkedQueue 对公共集合的共享访问就可以工作得很好。收集关于队列大小的信息会很慢,需要遍历队列。

------------------------------------------------------------------
实现阻塞接口的:

新的 java.util.concurrent 包在 Collection Framework 中可用的具体集合类中加入了 BlockingQueue 接口和五个阻塞队列类。

它实质上就是一种带有一点扭曲的 FIFO 数据结构。不是立即从队列中添加或者删除元素,线程执行操作阻塞,直到有空间或者元素可用。

五个队列所提供的各有不同:

* ArrayBlockingQueue :一个由数组支持的有界队列。
* LinkedBlockingQueue :一个由链接节点支持的可选有界队列。
* PriorityBlockingQueue :一个由优先级堆支持的无界优先级队列。
* DelayQueue :一个由优先级堆支持的、基于时间的调度队列。
* SynchronousQueue :一个利用 BlockingQueue 接口的简单聚集(rendezvous)机制。


前两个类 ArrayBlockingQueueLinkedBlockingQueue 几乎相同,只是在后备存储器方面有所不同, LinkedBlockingQueue 并不总是有容量界限。无大小界限的 LinkedBlockingQueue 类在添加元素时永远不会有阻塞队列的等待(至少在其中有Integer.MAX_VALUE 元素之前不会)。

PriorityBlockingQueue 是具有无界限容量的队列,它利用所包含元素的 Comparable 排序顺序来以逻辑顺序维护元素。可以将它看作 TreeSet 的可能替代物。不过对 PriorityBlockingQueue 有一个技巧。从 iterator() 返回的 Iterator 实例不需要以优先级顺序返回元素。如果必须以优先级顺序遍历所有元素,那么让它们都通过 toArray() 方法并自己对它们排序,像 Arrays.sort(pq.toArray())。

新的 DelayQueue 实现可能是其中最有意思(也是最复杂)的一个。加入到队列中的元素必须实现新的 Delayed 接口(只有一个方法 —— long getDelay(java.util.concurrent.TimeUnit unit) )。因为队列的大小没有界限,使得添加可以立即返回,但是在延迟时间过去之前不能从队列中取出元素。如果多个元素完成了延迟,那么最早失效/失效时间最长的元素将第一个取出。实际上没有听上去这样复杂。

SynchronousQueue 类是最简单的。它没有内部容量。它就像线程之间的手递手机制。在队列中加入一个元素的生产者会等待另一个线程的消费者。当这个消费者出现时,这个元素就直接在消费者和生产者之间传递,永远不会加入到阻塞队列中。

@PostConstruct public void initOcrEngine() { tesseract = new Tesseract(); //语言包路径和支持语言 tesseract.setDatapath(tessDataPath); tesseract.setLanguage("eng+chi_sim"); tesseract.setPageSegMode(6); //自动页面分割 tesseract.setOcrEngineMode(3); //LSTM引擎 } /** * OCR识别图片内容 */ private String extractImageText(MultipartFile file) { try (InputStream is = file.getInputStream()) { BufferedImage image = ImageIO.read(is); if (image == null) { return MessageUtils.message("Image.parsing.failed"); } image = increaseVerticalSpacingBetweenText(image, 20, 128); image = increaseHorizontalSpacing(image, 20); saveDebugImage(image, "3_final.png"); // 保存最终处理结果 String result = tesseract.doOCR(image); //OCR识别 result = postProcess(result); result = result.replaceAll("\\s+", " ").trim(); System.out.println("图片内容:\n" + result); return result; } catch (Exception e) { e.printStackTrace(); return MessageUtils.message("file.read.picture.error"); } } // 添加图片保存方法 private void saveDebugImage(BufferedImage img, String filename) throws IOException { // 保存到项目目录下的debug文件夹 String basePath = "D:/pdf/debug/"; File outputDir = new File(basePath); if (!outputDir.exists()) outputDir.mkdirs(); String fullPath = basePath + filename; ImageIO.write(img, "png", new File(fullPath)); } public static BufferedImage increaseVerticalSpacingBetweenText(BufferedImage image, int spacing, int threshold) { int width = image.getWidth(); int height = image.getHeight(); // 二值化处理 BufferedImage binaryImage = binarizeImage(image, threshold); // 检测文字区域 List<int[]> textRegions = detectTextRegions(binaryImage); // 打印检测到的文字区域,用于调试 System.out.println("Detected text regions: " + textRegions); // 计算新的高度 int newHeight = height + (textRegions.size() - 1) * spacing; BufferedImage newImage = new BufferedImage(width, newHeight, BufferedImage.TYPE_BYTE_BINARY); Graphics2D g2d = newImage.createGraphics(); g2d.setColor(Color.WHITE); g2d.fillRect(0, 0, width, newHeight); int currentY = 0; for (int[] region : textRegions) { int startY = region[0]; int endY = region[1]; // 复制文字区域 for (int y = startY; y < endY; y++) { for (int x = 0; x < width; x++) { int argb = image.getRGB(x, y); newImage.setRGB(x, currentY, argb); } currentY++; } // 添加空隙 currentY += spacing; } g2d.dispose(); return newImage; } // 二值化处理 private static BufferedImage binarizeImage(BufferedImage image, int threshold) { int width = image.getWidth(); int height = image.getHeight(); BufferedImage binaryImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int argb = image.getRGB(x, y); int gray = (argb >> 16) & 0xff; if (gray < threshold) { binaryImage.setRGB(x, y, Color.BLACK.getRGB()); } else { binaryImage.setRGB(x, y, Color.WHITE.getRGB()); } } } return binaryImage; } // 检测文字区域 private static List<int[]> detectTextRegions(BufferedImage binaryImage) { int width = binaryImage.getWidth(); int height = binaryImage.getHeight(); List<int[]> textRegions = new ArrayList<>(); boolean inText = false; int startY = 0; for (int y = 0; y < height; y++) { boolean hasBlackPixel = false; for (int x = 0; x < width; x++) { if (binaryImage.getRGB(x, y) == Color.BLACK.getRGB()) { hasBlackPixel = true; break; } } if (hasBlackPixel && !inText) { startY = y; inText = true; } else if (!hasBlackPixel && inText) { textRegions.add(new int[]{startY, y}); inText = false; } } if (inText) { textRegions.add(new int[]{startY, height}); } return textRegions; } // 水平空隙添加(修复版) public static BufferedImage increaseHorizontalSpacing(BufferedImage image, int spacing) { BufferedImage newImage = new BufferedImage( image.getWidth() + spacing * 2, image.getHeight(), BufferedImage.TYPE_BYTE_BINARY); Graphics2D g2d = newImage.createGraphics(); g2d.setColor(Color.WHITE); g2d.fillRect(0, 0, newImage.getWidth(), newImage.getHeight()); // 完整绘制(关键参数修正) g2d.drawImage(image,0, spacing , image.getWidth(), image.getHeight(), null); g2d.dispose(); System.out.println("水平处理后图像类型: " + newImage.getType()); System.out.println("水平处理后图像尺寸: " + newImage.getWidth() + "x" + newImage.getHeight()); return newImage; }这个是我的内容 但是我们图片里面的内容 没有弄出空隙出来 比如说我图片里面的扩号紧挨到了8上面 是上下内容 所以导致识别错误的 我这个ocr图片读取文件内容那个图片里面的内容和其他的元素紧挨着就会识别错误
最新发布
07-25
<think>好的,我现在需要帮助用户解决OCR识别中由于字符紧挨导致的识别错误问题。用户已经提供了一段Java代码,其中包含了图像预处理的部分,但问题依然存在。我需要仔细分析他的代码,找出可能的问题点,并提出改进方案。 首先,用户提到括号和数字8紧挨着,导致上下内容粘连,识别错误。这说明需要在垂直方向上增加字符之间的间距,以便OCR引擎能正确分割字符。用户当前的代码中有increaseVerticalSpacingBetweenText方法,但似乎没有生效。 接下来,我需要检查这个方法的实现。在increaseVerticalSpacingBetweenText中,用户通过检测文字区域,然后在每个区域之间添加间距。但可能的问题在于,检测到的文字区域是否正确?比如,是否将每个字符单独检测出来,还是将整个文本块作为一个区域处理? 在detectTextRegions方法中,用户是按行扫描,检测有黑色像素的行作为文字区域。这种方法可能将连续的文本行视为一个区域,而没有细分到每个字符。因此,在垂直方向上增加间距可能只是在行之间添加了空隙,而没有在字符之间添加,导致效果不明显。 此外,用户还使用了increaseHorizontalSpacing方法,但根据描述,问题出现在垂直方向上,所以水平方向的处理可能不是重点。不过需要确认是否应该调整垂直间距而不是水平间距。 接下来,用户的increaseVerticalSpacingBetweenText方法中,复制文字区域的部分可能存在问题。在循环中,currentY递增的方式可能不正确。例如,原代码中在复制完每个区域后,currentY++,然后加上spacing。这可能没有正确地将每个字符区域分开,因为原区域可能包含多个字符,而分开的间距可能不足以分割它们。 另外,用户在处理垂直间距时,是否应该针对每个字符的边界框进行操作,而不是按行分割?可能需要更精确的字符分割方法,比如使用连通域分析来检测每个字符的边界框,然后在这些框之间添加间距。 此外,在二值化处理中,阈值是否合适?如果二值化不准确,可能导致文字区域检测不正确,进而影响间距调整的效果。用户当前的阈值是传入的参数,可能需要根据图像动态调整,比如使用自适应阈值。 还有一点需要注意,用户在increaseVerticalSpacingBetweenText中创建的newImage是否正确初始化。比如,newHeight的计算是否考虑了所有区域的扩展,以及复制像素时是否正确处理了每个区域的位置。 总结问题可能出在以下几个方面: 1. 文字区域检测不够精确,未能正确分割每个字符或粘连的部分。 2. 垂直间距调整方法未能正确应用在字符之间,导致间距不足。 3. 二值化处理的阈值设置可能不恰当,影响后续处理。 4. 图像预处理步骤是否需要其他形态学操作(如开运算、闭运算)来断开粘连。 针对这些问题,我应该建议用户改进文字区域检测的方法,使用更精确的字符分割技术,比如连通域分析或投影分析,确保每个字符或粘连部分被单独检测出来。同时,调整间距时,需要根据这些检测到的区域来插入适当的间距,而不仅仅是按行处理。此外,可能需要引入形态学操作来进一步断开粘连的字符,或者调整二值化的阈值以获得更好的分割效果。 另外,用户提到括号和数字8上下紧挨,可能需要在垂直方向上增加字符之间的间距。因此,在代码中可能需要针对每个字符的垂直位置进行调整,而不是整个文本行。或者,检查当前的increaseVerticalSpacing方法是否真的在字符之间添加了足够的空隙,而不仅仅是行之间。 还需要验证处理后的图像是否正确保存,并且确实增加了间距。用户已经保存了调试图像,应该检查这些图像是否显示预期的处理效果。例如,3_final.png是否显示字符之间有空隙,或者是否仍然粘连。 最后,可能需要调整Tesseract的参数,例如使用不同的PageSegMode,或者在识别前对图像进行进一步的处理,如放大、锐化等,以提升OCR的准确性。</think>根据您的代码和问题描述,核心问题是 **字符粘连导致 OCR 无法正确分割**。以下是针对性解决方案: --- ### 🔍 问题分析 #### 1. **当前代码局限性** | 问题点 | 描述 | 修复方案 | |-------|------|----------| | **文字区域检测粗粒度** | 按行检测而非字符级检测 | 使用连通域分析分割每个字符 | | **垂直间距逻辑错误** | 当前逻辑仅处理文本行间距 | 需对字符边界框插入间距 | | **二值化阈值单一** | 固定阈值无法适应复杂背景 | 改用自适应阈值或 HSV 分离 | | **形态学操作缺失** | 未断开字符粘连 | 添加开操作断开细小连接 | --- ### ✅ 修复方案(完整代码) #### 1. **改进字符级检测(连通域分析)** ```java public static List<Rectangle> findCharacterRegions(BufferedImage binaryImage) { int width = binaryImage.getWidth(); int height = binaryImage.getHeight(); boolean[][] visited = new boolean[width][height]; List<Rectangle> regions = new ArrayList<>(); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if (isBlack(binaryImage.getRGB(x, y)) && !visited[x][y]) { // BFS查找连通域 Queue<Point> queue = new LinkedList<>(); queue.add(new Point(x, y)); visited[x][y] = true; int minX = x, maxX = x, minY = y, maxY = y; while (!queue.isEmpty()) { Point p = queue.poll(); for (int dx = -1; dx <= 1; dx++) { for (int dy = -1; dy <= 1; dy++) { int nx = p.x + dx, ny = p.y + dy; if (nx >= 0 && nx < width && ny >= 0 && ny < height && isBlack(binaryImage.getRGB(nx, ny)) && !visited[nx][ny]) { visited[nx][ny] = true; queue.add(new Point(nx, ny)); minX = Math.min(minX, nx); maxX = Math.max(maxX, nx); minY = Math.min(minY, ny); maxY = Math.max(maxY, ny); } } } } // 过滤小面积噪点 if ((maxX - minX + 1) * (maxY - minY + 1) > 20) { regions.add(new Rectangle(minX, minY, maxX - minX + 1, maxY - minY + 1)); } } } } return regions; } private static boolean isBlack(int rgb) { return (rgb & 0xFFFFFF) == 0; // 纯黑色 } ``` #### 2. **字符级间距调整(修复版)** ```java public static BufferedImage addSpacingBetweenCharacters(BufferedImage image, List<Rectangle> regions, int spacing) { int width = image.getWidth(); int height = image.getHeight(); BufferedImage newImage = new BufferedImage(width + spacing * regions.size(), height, BufferedImage.TYPE_BYTE_BINARY); Graphics2D g = newImage.createGraphics(); g.setColor(Color.WHITE); g.fillRect(0, 0, newImage.getWidth(), newImage.getHeight()); int offsetX = 0; for (Rectangle r : regions) { // 截取字符区域 BufferedImage charImg = image.getSubimage(r.x, r.y, r.width, r.height); g.drawImage(charImg, offsetX, 0, null); offsetX += r.width + spacing; // 插入间距 } g.dispose(); return newImage; } ``` #### 3. **形态学断开粘连(新增步骤)** ```java public static BufferedImage breakCharacterConnections(BufferedImage image) { int width = image.getWidth(); int height = image.getHeight(); BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY); // 定义3x3结构元素 int[][] kernel = { {1, 1, 1}, {1, 1, 1}, {1, 1, 1} }; // 腐蚀(缩小边缘) BufferedImage eroded = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY); for (int y = 1; y < height - 1; y++) { for (int x = 1; x < width - 1; x++) { boolean allBlack = true; for (int dy = -1; dy <= 1; dy++) { for (int dx = -1; dx <= 1; dx++) { if ((image.getRGB(x + dx, y + dy) & 0xFF) > 0x7F) { allBlack = false; break; } } } eroded.setRGB(x, y, allBlack ? Color.BLACK.getRGB() : Color.WHITE.getRGB()); } } // 膨胀(恢复字符主体) for (int y = 1; y < height - 1; y++) { for (int x = 1; x < width - 1; x++) { boolean anyBlack = false; for (int dy = -1; dy <= 1; dy++) { for (int dx = -1; dx <= 1; dx++) { if ((eroded.getRGB(x + dx, y + dy) & 0xFF) == 0) { anyBlack = true; break; } } } result.setRGB(x, y, anyBlack ? Color.BLACK.getRGB() : Color.WHITE.getRGB()); } } return result; } ``` --- ### 🧪 集成到您的代码 ```java private String extractImageText(MultipartFile file) { try (InputStream is = file.getInputStream()) { BufferedImage image = ImageIO.read(is); if (image == null) return "Image read failed"; // 预处理步骤 image = binarizeImage(image, 128); // 二值化 image = breakCharacterConnections(image); // 断开粘连 List<Rectangle> regions = findCharacterRegions(image); // 字符级检测 image = addSpacingBetweenCharacters(image, regions, 10); // 插入间距 saveDebugImage(image, "3_final.png"); // 保存最终处理结果 String result = tesseract.doOCR(image); // OCR识别 return postProcess(result); } catch (Exception e) { e.printStackTrace(); return "Error"; } } ``` --- ### 🚫 常见错误修复 | 错误场景 | 修复方案 | |---------|----------| | **字符区域未正确分割** | 调整连通域分析的噪点过滤阈值 | | **间距插入后字符错位** | 使用 `AffineTransform` 对齐字符 | | **OCR 仍识别错误** | 启用白名单(如 `tessedit_char_whitelist=0-9()`) | | **图像类型不兼容** | 强制转换为 `TYPE_INT_RGB` 后再处理 | --- ### ✅ 最终验证 1. **检查调试图像**: - 打开 `3_final.png` 确认字符间有明显空隙 - 使用图片编辑器测量字符间距是否大于 `spacing` 参数 2. **日志输出示例**: ``` 检测到字符区域数量: 5 (例如:'(', '8', '8', '8', ')') 最终图像尺寸: 200x30 (假设每个字符宽30px,间距10px) ``` 3. **手动测试 Tesseract 命令行**: ```bash tesseract 3_final.png stdout -c tessedit_char_whitelist="0123456789()" ``` 预期输出:`(888)` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值