生产环境Java+Python实现健康码识别

希望疫情笼罩的日子尽早过去
因为疫情来的猛,公司内部为了监控员工健康码状态,要求系统自动识别,并且将情况通知到对应的人员进行后续跟踪。

运行环境和使用到的技术:ubuntu20(服务器)、python3.9、opencv4.5、java8

基本思路:通过健康码图片色HSV彩分析技术得出结果
1.图片大小规整
2.拾取指定颜色值的范围截取图片
3.图片处理(灰度、二值、平滑、膨胀)
4.最大轮廓寻找
5.计算最大轮廓面积以及码形状的面积比得出结果
6.因为处理结果需要在java端执行,因此json格式化输出结果

一,Python图片处理脚本主要实现核心的图片解析以及结果返回
Part1:

#颜色范围值
_color_dic=[{'color':'red','lower_hsv': [0, 43, 46], 'upper_hsv': [10, 255, 255]},{'color':'red','lower_hsv': [156, 43, 46], 'upper_hsv': [180, 255, 255]},
            {'color':'yellow','lower_hsv': [26, 43, 46], 'upper_hsv': [34, 255, 255]},{'color':'yellow','lower_hsv': [11, 43, 46], 'upper_hsv': [25, 255, 255]}]

#命中规则【整体轮廓面积>4w ,宽高差<50 ,图块白色像素>黑色像素点】
def isOkContours(range_mask):
    # 只提取整体外部轮廓
    contours, hierarchy = cv2.findContours(range_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    if (len(contours) <= 0):
        return False
    for cnt in contours:
        x, y, w, h = cv2.boundingRect(cnt)
        crop=range_mask[y:(y + h),x:(x+ w)]
        crop = cv2.dilate(crop, None, iterations=2)
        blackSum=np.sum([crop==0])
        whiteSum = np.sum([crop == 255])
        area = cv2.contourArea(cnt)
        c = abs(w - h)
        if (area >= 40000 and c <= 100 and whiteSum>blackSum):
            return True
    return False

# get all color
def whatColor(img_hsv):
    _checkFlag=False
    for color in _color_dic:
        lower_hsv = np.array(color['lower_hsv'])
        red_upper_hsv = np.array(color['upper_hsv'])
        target_red_mask = cv2.inRange(img_hsv, lowerb=lower_hsv, upperb=red_upper_hsv)
        _checkFlag=isOkContours(target_red_mask)
        if _checkFlag==False:
            continue
        _checkFlag=True
        print(json.dumps({"code": 0, "color":color['color']}, sort_keys=True))
        break
    if _checkFlag==False:
        print(json.dumps({"code": 0, "color": "other"}, sort_keys=True))

def getColorRange(img_hsv,color_lower_hsv,color_upper_hsv):
    lower_hsv = np.array(color_lower_hsv)
    red_upper_hsv = np.array(color_upper_hsv)
    target_red_mask = cv2.inRange(img_hsv, lowerb=lower_hsv, upperb=red_upper_hsv)
    return target_red_mask

# get single color
def isRedCode(img_hsv):
    red_list = [item  for item in _color_dic if item['color']=='red']
    for color in red_list:
        _range = getColorRange(img_hsv, color['lower_hsv'], color['upper_hsv'])
        _checkFlag = isOkContours(_range)
        if _checkFlag == True:
            print(json.dumps({"code": 0, "color": color['color']}, sort_keys=True))
            break

    if _checkFlag ==False:
        return False

# get single color
def isYellowCode(img_hsv):
    yellow_list = [item for item in _color_dic if item['color'] == 'yellow']
    _checkFlag = False
    for color in yellow_list:
        _range = getColorRange(img_hsv, color['lower_hsv'],color['upper_hsv'])
        _checkFlag = isOkContours(_range)
        if _checkFlag == True:
            print(json.dumps({"code": 0, "color": color['color']}, sort_keys=True))
            break
    if _checkFlag ==False:
        return False

#识别码
def regRedCode(url):
    org_rbg_img = cv2.imread(url)
    org_rbg_img=cv2.resize(org_rbg_img,(590,1278))
    # cv2.namedWindow("org_rbg_img", cv2.WINDOW_NORMAL)
    # cv2.imshow("org_rbg_img", org_rbg_img)
    barcodes = pyzbar.decode(org_rbg_img)
    codes=[]
    for barcode in barcodes:
        _rect=barcode.rect
        codeImage=org_rbg_img[_rect.top:(_rect.top + _rect.height),_rect.left:(_rect.width + _rect.left)]
        codes.append(codeImage)
    mergeImage=org_rbg_img

    if len(codes)>0:
        mergeImage = np.concatenate((codes), axis=2)

    img_hsv = cv2.cvtColor(mergeImage, cv2.COLOR_BGR2HSV)
    whatColor(img_hsv)
    # isRed=isRedCode(img_hsv)
    # if isRed==True:
    #     print(json.dumps({"code": 0, "color": "red"}, sort_keys=True))
    #     return
    # yellow=isYellowCode(img_hsv)
    # if yellow==True:
    #     print(json.dumps({"code": 0, "color": "yellow"}, sort_keys=True))
    #     return
    # print(json.dumps({"code": 0, "color": "other"}, sort_keys=True))
    return

#脚本入口,接受其他平台调用传参
if __name__ == '__main__':
    a = []
    for i in range(1, len(sys.argv)):
        a.append(sys.argv[i])
    if len(a)<0:
        print(json.dumps({"code": -1, "color": ""}, sort_keys=True))
    regRedCode(a[0])


二,Java端主要是传递图片地址并调用脚本得到结果,通知通过钉钉指定人

Part2

//参数分别是 脚本的绝对位置,图片地址
String _regResult = CmdProcessUtils.invokePython("xxx/HeathCodeReg.py", localPath);

Part3 (calss CmdProcessUtils)

public synchronized static String invokePython(String pythonFile,String url){
        if(org.apache.commons.lang3.StringUtils.isEmpty(url)){
            return null;
        }
        try {
            String pythonPath=pythonFile;
            String[] args = new String[] { "python3", pythonPath, url};
            Process proc = Runtime.getRuntime().exec(args);// 执行py文件
            BufferedReader in = new BufferedReader(new InputStreamReader(proc.getInputStream()));
            String line = null;
            StringBuffer buffer=new StringBuffer();
            while ((line = in.readLine()) != null) {
                buffer.append(line);
            }
            in.close();
            proc.waitFor();
            return buffer.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

经过生产环境测试,服务器4核8G识别速度可行!

常见疑问一:项目使用的环境是否必须为Python3.9,OpenCV4.5?

答:Python最好是3系列版本,因为2与3版本语法相差较大,OpenCV版本如果不使用4.5,那需要自行寻找相匹配的版本,识别脚本中相关的方法可能有差异需要调整。

常见疑问二:是否可以直接识别网络图片?

答:本项目中将图片下载到了本地,如果需要直接识别网络图,可以再脚本“regRedCode”方法中判断下,对于网络图片可以使用request先下载下来,然后将流转为byte给到opencv即可。

愿病毒早日消亡!!!