diff --git a/.gitignore b/.gitignore index 7c139da..a8ba116 100644 --- a/.gitignore +++ b/.gitignore @@ -264,4 +264,5 @@ build */tmp/**.html /log -.git \ No newline at end of file +.git +/.codebuddy/rules/编码规则.mdc diff --git a/api-web/api-interface/src/main/java/com/heyu/api/controller/car/RecognizeLicensePlateController.java b/api-web/api-interface/src/main/java/com/heyu/api/controller/car/RecognizeLicensePlateController.java index c6585a8..1ae3677 100644 --- a/api-web/api-interface/src/main/java/com/heyu/api/controller/car/RecognizeLicensePlateController.java +++ b/api-web/api-interface/src/main/java/com/heyu/api/controller/car/RecognizeLicensePlateController.java @@ -1,39 +1,36 @@ package com.heyu.api.controller.car; -import com.aliyun.ocr20191230.models.RecognizeLicensePlateResponse; -import com.aliyun.ocr20191230.models.RecognizeLicensePlateResponseBody; -import com.heyu.api.alibaba.handle.common.text.ARecognizeLicensePlateHandle; -import com.heyu.api.alibaba.request.common.text.ARecognizeLicensePlateRequest; +import com.heyu.api.baidu.handle.traffic.BLicensePlateHandle; +import com.heyu.api.baidu.request.traffic.BLicensePlateRequest; +import com.heyu.api.baidu.response.traffic.BLicensePlateResp; import com.heyu.api.controller.BaseController; import com.heyu.api.data.annotation.NotIntercept; import com.heyu.api.data.utils.ApiR; import com.heyu.api.data.utils.R; +import com.heyu.api.request.car.LicensePlateRecognizeRequest; import com.heyu.api.resp.car.RecognizeLicensePlateResp; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -/*** - * https://next.api.aliyun.com/api/ocr/2019-12-30/RecognizeLicensePlate?useCommon=true&tab=DOC&lang=JAVA&sdkStyle=dara - * - *车牌识别 - * - * RecognizeLicensePlate - * - * - * 车牌识别能力可以准确识别出图像中车牌位置,输出车牌位置坐标、车牌类型、车牌号码、车牌号码置信度、车牌置信度,共 5 个关键字段信息。 - * - * 车牌识别能力目前支持的地域(Region)有上海和深圳,当您开通服务时选择的是上海地域,推荐使用上海地域的 OSS 链接,对于文件在本地或者非上海地域 OSS 链接的情况,请参见文件 URL 处理。当您开通服务时选择的是深圳地域时,仅支持深圳地域 OSS 链接进行调用。 - * - * 示例值: - * http://viapi-test.oss-cn-shanghai.aliyuncs.com/viapi-3.0domepic/ocr/RecognizeLicensePlate/cpsb1.jpg - * - * - */ +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.List; +/** + * 车牌识别(百度OCR) + * + * 支持识别中国大陆机动车蓝牌、黄牌(单双行)、绿牌、大型新能源(黄绿)、领使馆车牌、警牌、武警牌(单双行)、 + * 军牌(单双行)、港澳出入境车牌、农用车牌、民航车牌、非机动车车牌(北京地区)的地域编号和车牌号, + * 并能同时识别图像中的多张车牌。 + * + * 百度文档:https://console.bce.baidu.com/support/#/api?product=AI&project=文字识别&parent=交通场景OCR&api=rest%2F2.0%2Focr%2Fv1%2Flicense_plate&method=post + */ @Slf4j @RestController @RequestMapping("/car/license/plate") @@ -41,41 +38,98 @@ import org.springframework.web.bind.annotation.RestController; public class RecognizeLicensePlateController extends BaseController { @Autowired - private ARecognizeLicensePlateHandle aRecognizeLicensePlateHandle; + private BLicensePlateHandle bLicensePlateHandle; @PostMapping("/recognize") - public R recognize(ARecognizeLicensePlateRequest request) { - if (!hasImage(request)) { - return R.error("imageUrl和imageBase64不能同时为空"); + public R recognize(@RequestBody LicensePlateRecognizeRequest request) { + if (request == null) { + return R.error("请求参数不能为空"); + } + if (isBlank(request.getImageBase64()) && isBlank(request.getImageUrl())) { + return R.error("imageBase64和imageUrl不能同时为空"); } - ApiR aR = aRecognizeLicensePlateHandle.handle(request); - if (!isValidAliResponse(aR)) { - return R.error(thirdError(aR)); + BLicensePlateRequest bRequest = toBaiduRequest(request); + + ApiR apiR = bLicensePlateHandle.handle(bRequest); + if (apiR == null || !apiR.isSuccess() || apiR.getData() == null) { + return R.error(thirdError(apiR)); } - RecognizeLicensePlateResponseBody.RecognizeLicensePlateResponseBodyData data = - aR.getData().getBody().getData(); - if (CollectionUtils.isEmpty(data.getPlates())) { + BLicensePlateResp bResp = apiR.getData(); + List plates = bResp.getWordsResult(); + if (CollectionUtils.isEmpty(plates)) { return R.error("未识别到车牌信息"); } - RecognizeLicensePlateResponseBody.RecognizeLicensePlateResponseBodyDataPlates plate = - data.getPlates().get(0); + // 取第一张车牌 + BLicensePlateResp.WordsResultDTO plate = plates.get(0); RecognizeLicensePlateResp resp = new RecognizeLicensePlateResp(); - resp.setConfidence(plate.getConfidence()); - resp.setPlateTypeConfidence(plate.getPlateTypeConfidence()); - resp.setPlateType(plate.getPlateType()); - resp.setPlateNumber(plate.getPlateNumber()); + resp.setPlateNumber(plate.getNumber()); + resp.setPlateType(mapPlateColor(plate.getColor())); + + // 百度返回字符级置信度列表,取最小值作为整体置信度(保守策略),平均值作为车牌类型置信度 + List probabilities = plate.getProbability(); + if (CollectionUtils.isNotEmpty(probabilities)) { + double minConf = probabilities.stream().mapToDouble(Double::doubleValue).min().orElse(0.0); + resp.setConfidence((float) minConf); + double avgConf = probabilities.stream().mapToDouble(Double::doubleValue).average().orElse(0.0); + resp.setPlateTypeConfidence((float) avgConf); + } + return R.ok().setData(resp); } - private boolean isValidAliResponse(ApiR apiR) { - return apiR != null - && apiR.isSuccess() - && apiR.getData() != null - && isSuccessStatusCode(apiR.getData().getStatusCode()) - && apiR.getData().getBody() != null - && apiR.getData().getBody().getData() != null; + private BLicensePlateRequest toBaiduRequest(LicensePlateRecognizeRequest request) { + BLicensePlateRequest bRequest = new BLicensePlateRequest(); + // base64编码后进行urlencode是百度API的要求,因为base64的+/=在form-urlencoded中有特殊含义 + if (isNotBlank(request.getImageBase64())) { + bRequest.setImageBase64(encodeUrl(request.getImageBase64())); + } + // imageUrl 中可能包含 & 等特殊字符,需URL编码后再传给百度API,否则会被解析为form分隔符 + if (isNotBlank(request.getImageUrl())) { + bRequest.setImageUrl(encodeUrl(request.getImageUrl())); + } + // detectComplete和detectRisk在BLicensePlateRequest中无默认值,handle的check()要求必须为true/false + bRequest.setDetectComplete("false"); + bRequest.setDetectRisk("false"); + return bRequest; } + + private String encodeUrl(String url) { + try { + return URLEncoder.encode(url, "UTF-8"); + } catch (UnsupportedEncodingException e) { + log.error("URL编码失败:{}", url, e); + return url; + } + } + + private boolean isNotBlank(String value) { + return value != null && value.trim().length() > 0; + } + + /** + * 将百度车牌颜色映射为中文车牌类型 + */ + private String mapPlateColor(String color) { + if (color == null) { + return null; + } + switch (color.toLowerCase()) { + case "blue": + return "小型汽车"; + case "green": + return "新能源车"; + case "yellow": + return "大型汽车"; + case "white": + return "警车"; + case "black": + return "港澳车"; + default: + return color; + } + } + } diff --git a/api-web/api-interface/src/main/java/com/heyu/api/request/car/LicensePlateRecognizeRequest.java b/api-web/api-interface/src/main/java/com/heyu/api/request/car/LicensePlateRecognizeRequest.java new file mode 100644 index 0000000..c4bd2fb --- /dev/null +++ b/api-web/api-interface/src/main/java/com/heyu/api/request/car/LicensePlateRecognizeRequest.java @@ -0,0 +1,27 @@ +package com.heyu.api.request.car; + +import lombok.Data; + +/** + * 车牌识别请求参数(百度OCR) + * + * 支持识别中国大陆机动车蓝牌、黄牌(单双行)、绿牌、大型新能源(黄绿)、领使馆车牌、警牌、武警牌(单双行)、 + * 军牌(单双行)、港澳出入境车牌、农用车牌、民航车牌 + */ +@Data +public class LicensePlateRecognizeRequest { + + /** + * 图像数据,base64编码后进行urlencode,要求base64编码和urlencode后大小不超过4M + * 支持jpg/jpeg/png/bmp格式 + * 和url二选一 + */ + private String imageBase64; + + /** + * 图片完整URL,URL长度不超过1024字节 + * 和imageBase64二选一 + */ + private String imageUrl; + +}