本文目录导读:

我来为您提供一个Java实现文字识别OCR的完整案例,使用Tesseract作为OCR引擎。
项目依赖配置
Maven依赖
<dependencies>
<!-- Tesseract OCR -->
<dependency>
<groupId>net.sourceforge.tess4j</groupId>
<artifactId>tess4j</artifactId>
<version>5.4.0</version>
</dependency>
<!-- 图像处理 -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.5.9</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.9</version>
</dependency>
</dependencies>
基础OCR实现
package com.example.ocr;
import net.sourceforge.tess4j.ITesseract;
import net.sourceforge.tess4j.Tesseract;
import net.sourceforge.tess4j.TesseractException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
/**
* OCR文字识别工具类
*/
public class OcrUtil {
private static final Logger logger = LoggerFactory.getLogger(OcrUtil.class);
private static ITesseract tesseract;
static {
initializeTesseract();
}
/**
* 初始化Tesseract
*/
private static void initializeTesseract() {
tesseract = new Tesseract();
// 设置语言数据路径
String tessDataPath = System.getenv("TESSDATA_PREFIX");
if (tessDataPath == null || tessDataPath.isEmpty()) {
tessDataPath = "./tessdata"; // 默认路径
}
tesseract.setDatapath(tessDataPath);
// 设置语言,中文+英文
tesseract.setLanguage("chi_sim+eng");
// 设置OCR模式
tesseract.setOcrEngineMode(ITesseract.OEM_LSTM_ONLY);
// 设置页面分割模式
tesseract.setPageSegMode(ITesseract.PSM_AUTO);
}
/**
* 识别图片中的文字
* @param imageFile 图片文件
* @return 识别结果
*/
public static String recognizeText(File imageFile) {
try {
BufferedImage image = ImageIO.read(imageFile);
if (image == null) {
throw new RuntimeException("无法读取图片文件: " + imageFile.getPath());
}
return tesseract.doOCR(image);
} catch (IOException | TesseractException e) {
logger.error("OCR识别失败", e);
throw new RuntimeException("OCR识别失败: " + e.getMessage());
}
}
/**
* 识别图片中的文字(带图像预处理)
* @param imageFile 图片文件
* @return 识别结果
*/
public static String recognizeTextWithPreprocessing(File imageFile) {
try {
BufferedImage image = ImageIO.read(imageFile);
if (image == null) {
throw new RuntimeException("无法读取图片文件: " + imageFile.getPath());
}
// 图像预处理
image = ImagePreprocessor.preprocess(image);
return tesseract.doOCR(image);
} catch (IOException | TesseractException e) {
logger.error("OCR识别失败", e);
throw new RuntimeException("OCR识别失败: " + e.getMessage());
}
}
/**
* 识别图片中的文字并返回详细信息
* @param imageFile 图片文件
* @return OCR识别结果对象
*/
public static OcrResult recognizeWithDetails(File imageFile) {
OcrResult result = OcrResult.builder().build();
try {
long startTime = System.currentTimeMillis();
BufferedImage image = ImageIO.read(imageFile);
if (image == null) {
result.setError("无法读取图片文件");
return result;
}
// 图像预处理
image = ImagePreprocessor.preprocess(image);
// 执行OCR
String text = tesseract.doOCR(image);
// 保存结果
result.setSuccess(true);
result.setText(text);
result.setWords(getWordCount(text));
result.setCharacters(text.trim().length());
result.setElapsedTime(System.currentTimeMillis() - startTime);
result.setImageInfo(getImageInfo(imageFile));
} catch (Exception e) {
logger.error("OCR识别失败", e);
result.setSuccess(false);
result.setError(e.getMessage());
}
return result;
}
/**
* 计算单词数量(中文按字符,英文按单词)
*/
private static int getWordCount(String text) {
if (text == null || text.trim().isEmpty()) {
return 0;
}
// 提取中文字符和英文单词
String trimmed = text.trim();
String chineseChars = trimmed.replaceAll("[^\\u4e00-\\u9fa5]", "");
String[] englishWords = trimmed.replaceAll("[\\u4e00-\\u9fa5]", " ").trim().split("\\s+");
int englishCount = 0;
if (englishWords.length > 0 && !englishWords[0].isEmpty()) {
englishCount = englishWords.length;
}
return chineseChars.length() + englishCount;
}
/**
* 获取图片信息
*/
private static ImageInfo getImageInfo(File imageFile) {
try {
BufferedImage image = ImageIO.read(imageFile);
return ImageInfo.builder()
.width(image.getWidth())
.height(image.getHeight())
.fileSize(imageFile.length())
.format(getImageFormat(imageFile))
.build();
} catch (IOException e) {
logger.warn("获取图片信息失败", e);
return null;
}
}
private static String getImageFormat(File imageFile) {
String fileName = imageFile.getName();
int dotIndex = fileName.lastIndexOf('.');
if (dotIndex >= 0 && dotIndex < fileName.length() - 1) {
return fileName.substring(dotIndex + 1);
}
return "unknown";
}
}
图像预处理类
package com.example.ocr;
import org.opencv.core.*;
import org.opencv.imgproc.Imgproc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Color;
import java.awt.image.BufferedImage;
/**
* 图像预处理工具类
*/
public class ImagePreprocessor {
private static final Logger logger = LoggerFactory.getLogger(ImagePreprocessor.class);
/**
* 图像预处理流程
* @param image 原始图像
* @return 预处理后的图像
*/
public static BufferedImage preprocess(BufferedImage image) {
// 1. 图像增强
image = enhanceContrast(image);
// 2. 灰度化
BufferedImage grayImage = convertToGray(image);
// 3. 二值化
BufferedImage binaryImage = binarize(grayImage);
// 4. 去除噪点
binaryImage = denoise(binaryImage);
return binaryImage;
}
/**
* 增强对比度
*/
public static BufferedImage enhanceContrast(BufferedImage image) {
int width = image.getWidth();
int height = image.getHeight();
BufferedImage enhancedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// 计算图像直方图
int[] histogram = new int[256];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int rgb = image.getRGB(x, y);
int gray = (int) ((0.299 * ((rgb >> 16) & 0xFF)) +
(0.587 * ((rgb >> 8) & 0xFF)) +
(0.114 * (rgb & 0xFF)));
histogram[gray]++;
}
}
// 直方图均衡化
int[] lut = new int[256];
int sum = 0;
int total = width * height;
for (int i = 0; i < 256; i++) {
sum += histogram[i];
lut[i] = (int) (255.0 * sum / total);
}
// 应用均衡化
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int rgb = image.getRGB(x, y);
int red = lut[(rgb >> 16) & 0xFF];
int green = lut[(rgb >> 8) & 0xFF];
int blue = lut[rgb & 0xFF];
enhancedImage.setRGB(x, y, (red << 16) | (green << 8) | blue);
}
}
return enhancedImage;
}
/**
* 转换为灰度图像
*/
public static BufferedImage convertToGray(BufferedImage image) {
BufferedImage grayImage = new BufferedImage(
image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
int rgb = image.getRGB(x, y);
int gray = (int) ((0.299 * ((rgb >> 16) & 0xFF)) +
(0.587 * ((rgb >> 8) & 0xFF)) +
(0.114 * (rgb & 0xFF)));
grayImage.setRGB(x, y, (gray << 16) | (gray << 8) | gray);
}
}
return grayImage;
}
/**
* 二值化处理
*/
public static BufferedImage binarize(BufferedImage grayImage) {
int width = grayImage.getWidth();
int height = grayImage.getHeight();
BufferedImage binaryImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
// 使用Otsu方法计算阈值
int threshold = otsuThreshold(grayImage);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int pixel = grayImage.getRGB(x, y) & 0xFF;
int binary = pixel >= threshold ? 255 : 0;
binaryImage.setRGB(x, y, (binary << 16) | (binary << 8) | binary);
}
}
return binaryImage;
}
/**
* Otsu阈值计算
*/
private static int otsuThreshold(BufferedImage image) {
int[] histogram = new int[256];
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
histogram[image.getRGB(x, y) & 0xFF]++;
}
}
int totalPixels = image.getWidth() * image.getHeight();
float sum = 0;
for (int i = 0; i < 256; i++) {
sum += i * histogram[i];
}
float sumB = 0;
int weightB = 0;
float maxVariance = 0;
int threshold = 0;
for (int i = 0; i < 256; i++) {
weightB += histogram[i];
if (weightB == 0) continue;
int weightF = totalPixels - weightB;
if (weightF == 0) break;
sumB += i * histogram[i];
float meanB = sumB / weightB;
float meanF = (sum - sumB) / weightF;
float variance = (float) weightB * weightF * (meanB - meanF) * (meanB - meanF);
if (variance > maxVariance) {
maxVariance = variance;
threshold = i;
}
}
return threshold;
}
/**
* 去除噪点(中值滤波)
*/
public static BufferedImage denoise(BufferedImage image) {
int radius = 1;
int width = image.getWidth();
int height = image.getHeight();
BufferedImage denoisedImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
for (int y = radius; y < height - radius; y++) {
for (int x = radius; x < width - radius; x++) {
int[] values = new int[(2 * radius + 1) * (2 * radius + 1)];
int index = 0;
for (int dy = -radius; dy <= radius; dy++) {
for (int dx = -radius; dx <= radius; dx++) {
values[index++] = image.getRGB(x + dx, y + dy) & 0xFF;
}
}
// 排序取中值
java.util.Arrays.sort(values);
int median = values[values.length / 2];
denoisedImage.setRGB(x, y, (median << 16) | (median << 8) | median);
}
}
// 复制边缘像素
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (x < radius || x >= width - radius || y < radius || y >= height - radius) {
denoisedImage.setRGB(x, y, image.getRGB(x, y));
}
}
}
return denoisedImage;
}
/**
* 旋转校正
*/
public static BufferedImage deskew(BufferedImage image, double angle) {
int width = image.getWidth();
int height = image.getHeight();
// 计算旋转后的图像大小
double radians = Math.toRadians(angle);
int newWidth = (int) (width * Math.cos(radians) + height * Math.sin(radians));
int newHeight = (int) (width * Math.sin(radians) + height * Math.cos(radians));
BufferedImage rotatedImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_BYTE_GRAY);
// 旋转操作
// 实际项目中可以使用AffineTransform
// 这里提供简化版本
return rotatedImage;
}
}
业务模型类
package com.example.ocr;
import lombok.Builder;
import lombok.Data;
/**
* OCR识别结果
*/
@Data
@Builder
public class OcrResult {
private boolean success;
private String text;
private int words;
private int characters;
private long elapsedTime;
private ImageInfo imageInfo;
private String error;
@Override
public String toString() {
return String.format("OCR识别结果:{成功=%s, 字数=%d, 字符数=%d, 用时=%dms, 错误=%s}",
success, words, characters, elapsedTime, error);
}
}
/**
* 图片信息
*/
@Data
@Builder
public class ImageInfo {
private int width;
private int height;
private long fileSize;
private String format;
}
服务类
package com.example.ocr;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
/**
* OCR服务类
*/
public class OcrService {
private static final Logger logger = LoggerFactory.getLogger(OcrService.class);
/**
* 异步识别
*/
public CompletableFuture<OcrResult> recognizeAsync(File imageFile) {
return CompletableFuture.supplyAsync(() -> OcrUtil.recognizeWithDetails(imageFile));
}
/**
* 批量识别
*/
public Map<String, OcrResult> recognizeBatch(File[] imageFiles) {
Map<String, OcrResult> results = new HashMap<>();
for (File file : imageFiles) {
try {
OcrResult result = OcrUtil.recognizeWithDetails(file);
results.put(file.getName(), result);
} catch (Exception e) {
logger.error("处理文件失败: {}", file.getName(), e);
results.put(file.getName(), OcrResult.builder()
.success(false)
.error(e.getMessage())
.build());
}
}
return results;
}
/**
* 从图片中提取特定文本
*/
public String extractSpecificText(File imageFile, String pattern) {
try {
String text = OcrUtil.recognizeTextWithPreprocessing(imageFile);
if (text == null || text.isEmpty()) {
return null;
}
// 在识别文本中查找匹配的内容
java.util.regex.Pattern p = java.util.regex.Pattern.compile(pattern);
java.util.regex.Matcher matcher = p.matcher(text);
if (matcher.find()) {
return matcher.group();
}
return null;
} catch (Exception e) {
logger.error("提取特定文本失败", e);
return null;
}
}
/**
* 身份证号码提取
*/
public String extractIdCard(File imageFile) {
// 身份证号码模式
String idCardPattern = "\\d{17}[0-9Xx]";
return extractSpecificText(imageFile, idCardPattern);
}
/**
* 手机号码提取
*/
public String extractPhoneNumber(File imageFile) {
// 手机号模式
String phonePattern = "1[3-9]\\d{9}";
return extractSpecificText(imageFile, phonePattern);
}
}
测试类
package com.example.ocr;
import org.junit.jupiter.api.Test;
import java.io.File;
/**
* OCR工具测试类
*/
public class OcrUtilTest {
@Test
public void testSimpleRecognize() {
// 测试简单识别
File imageFile = new File("test_images/simple.png");
if (imageFile.exists()) {
String text = OcrUtil.recognizeText(imageFile);
System.out.println("识别结果: " + text);
} else {
System.out.println("测试图片不存在");
}
}
@Test
public void testRecognizeWithPreprocessing() {
// 测试带预处理的识别
File imageFile = new File("test_images/processed.png");
if (imageFile.exists()) {
String text = OcrUtil.recognizeTextWithPreprocessing(imageFile);
System.out.println("预处理后识别结果: " + text);
}
}
@Test
public void testRecognizeWithDetails() {
// 测试详细识别
File imageFile = new File("test_images/detail.png");
if (imageFile.exists()) {
OcrResult result = OcrUtil.recognizeWithDetails(imageFile);
System.out.println(result);
if (result != null && result.getImageInfo() != null) {
System.out.println("图片尺寸: " + result.getImageInfo().getWidth() + "x" +
result.getImageInfo().getHeight());
}
}
}
@Test
public void testExtractIdCard() {
// 测试身份证号提取
File imageFile = new File("test_images/idcard.png");
if (imageFile.exists()) {
OcrService service = new OcrService();
String idCard = service.extractIdCard(imageFile);
System.out.println("提取的身份证号: " + idCard);
}
}
@Test
public void testBatchRecognize() {
// 测试批量识别
File testDir = new File("test_images/");
if (testDir.exists()) {
File[] files = testDir.listFiles((dir, name) ->
name.endsWith(".png") || name.endsWith(".jpg"));
if (files != null) {
OcrService service = new OcrService();
var results = service.recognizeBatch(files);
results.forEach((fileName, result) -> {
System.out.println("文件: " + fileName);
System.out.println("结果: " + result);
System.out.println("---");
});
}
}
}
}
主程序入口
package com.example.ocr;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.Scanner;
/**
* 主程序入口
*/
public class Main {
private static final Logger logger = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
if (args.length == 0) {
interactiveMode();
} else {
// 命令行模式
for (String arg : args) {
File imageFile = new File(arg);
if (imageFile.exists()) {
recognizeAndPrint(imageFile);
} else {
System.err.println("文件不存在: " + arg);
}
}
}
}
/**
* 交互模式
*/
private static void interactiveMode() {
System.out.println("=== OCR文字识别工具 ===");
System.out.println("输入图片路径(输入exit退出):");
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
String line = scanner.nextLine().trim();
if ("exit".equalsIgnoreCase(line)) {
break;
}
if (!line.isEmpty()) {
File imageFile = new File(line);
if (imageFile.exists()) {
recognizeAndPrint(imageFile);
} else {
System.err.println("文件不存在,请重新输入");
}
}
System.out.println("\n输入图片路径(输入exit退出):");
}
scanner.close();
}
/**
* 执行识别并打印结果
*/
private static void recognizeAndPrint(File imageFile) {
try {
System.out.println("正在识别: " + imageFile.getName());
long startTime = System.currentTimeMillis();
OcrResult result = OcrUtil.recognizeWithDetails(imageFile);
long elapsedTime = System.currentTimeMillis() - startTime;
if (result.isSuccess()) {
System.out.println("识别完成,用时: " + elapsedTime + "ms");
System.out.println("识别结果:");
System.out.println(result.getText());
System.out.println("\n详细信息:");
System.out.println("字数: " + result.getWords());
System.out.println("字符数: " + result.getCharacters());
} else {
System.err.println("识别失败: " + result.getError());
}
} catch (Exception e) {
logger.error("识别过程出错", e);
System.err.println("识别过程出错: " + e.getMessage());
}
}
}
配置文件
# application.properties # Tesseract配置 tesseract.data.path=./tessdata tesseract.language=chi_sim+eng tesseract.ocr.engine.mode=3 tesseract.page.seg.mode=3 # 图像处理配置 image.preprocess.enabled=true image.preprocess.contrast.enhance=true image.preprocess.denoise=true
使用说明
环境准备
-
安装Tesseract OCR引擎
- Windows: 下载安装 Tesseract
- Linux:
sudo apt-get install tesseract-ocr - Mac:
brew install tesseract
-
下载语言数据
- 从 tessdata 下载
chi_sim.traineddata和eng.traineddata - 放入项目的
tessdata目录
- 从 tessdata 下载
使用示例
// 基础用法
File imageFile = new File("test.png");
String text = OcrUtil.recognizeText(imageFile);
System.out.println(text);
// 带预处理的用法
String processedText = OcrUtil.recognizeTextWithPreprocessing(new File("test.jpg"));
// 详细结果
OcrResult result = OcrUtil.recognizeWithDetails(new File("test.png"));
if (result.isSuccess()) {
System.out.println(result.getText());
System.out.println("字数: " + result.getWords());
System.out.println("用时: " + result.getElapsedTime() + "ms");
}
// 使用服务类
OcrService service = new OcrService();
String idCardNumber = service.extractIdCard(new File("idcard.jpg"));
String phoneNumber = service.extractPhoneNumber(new File("phone.jpg"));
优化建议
识别准确率优化
- 图像清晰度:确保图像分辨率足够,文字清晰
- 预处理加强:根据实际情况调整二值化阈值、去噪参数
- 自定义训练:针对特定字体或场景训练自定义模型
性能优化
- 缓存预处理结果:对相同图像避免重复预处理
- 并行处理:使用线程池处理多个图片
- 图像裁剪:只对包含文字的区域进行识别
错误处理
- 图片格式检查:提前检查图片格式是否支持
- 超时处理:对大图片设置识别超时时间
- 降级策略:识别失败时尝试不同的预处理参数
这个案例提供了一个完整的Java OCR解决方案,包括基础识别、图像预处理、业务扩展等功能,您可以根据实际需求进行调整和优化。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播