业务需求

一个图像处理系统,python负责人工智能的图像处理模块,后端采用Java的生态,前端使用vue编写。

这就遇到一个问题,需要处理的图片数据从前端传给后端,后端还需要一种手段传输给python程序,然后python程序处理好后传回给后端,后端再转发给前端

是不是有点点绕,也还好的其实,下面我就来详细的说我是怎么解决的

方案零

为什么是方案0呢,因为程序思维里面,0不是首个吗其实这个方案是我来扯淡的

最好的方法当然是python同时担任后端服务和图像处理业务就好了,不用搞两个平台这么麻烦了

 开玩笑的哈,这样的话不符合我们标题了,就是标题党了。其实我们不这么弄还有一个重要的原因,人工智能那家伙不会后端,不然也没有我Java写手什么事了,下面进入正题

方案一

约定好两个文件路径,一个是原始图片的路径,一个是处理好图片的路径。Java将需要处理的图片放在一个路径,然后python不断读取这个路径下最新的图片,经过处理后又放到一个路径下,Java代码就不断读生成好的路径下的图片

不足:

这样是解决了不同代码平台之间传输文件的需求,但是也会遇到一个相当大的问题,这个平台是一个汽车车道识别和行人识别等需要实时性的需求的系统。这会导致需要处理的图片在短时间内会十分之多,大约一秒24张(标准视频要求),那么这些图片会在一秒内在磁盘写入24份和读取24份,对于磁盘的IO性能是一个考验。如果抛开磁盘开销不说,走磁盘IO流会导致整个系统的速度被拉跨

在正常的系统设计中,我们应该避免频繁的磁盘IO操作,这不仅影响系统的速度,甚至会拖累操作系统的运行

方案二

基于HTTP协议的图像传输

Java后端为前端和python端都编写对应的图片上传/下载接口,用于图片数据的转发

这是一个不错的思路,因为这全过程中,图片数据都是在内存中被读取和写入的,比方案一的效率大大提高。并且HTTP协议比较成熟稳定,可以很方便的传输数据和封装对象

不足:

  • HTTP是基于请求-响应模式的,客户端发送请求,服务器返回响应
  • HTTP使用TCP作为传输协议

使用HTTP进行大量的[请求-接收]会产生大量的TCP创建连接,销毁连接的过程,多次TCP建立连接会有大量的性能开销,对CPU的压力会增大,况且python还需要进行大规模的图像处理,对于这种两边都抢占资源的程序,也容易造成卡断

方案三*

这是我们最终采用的方案

大家可能会看到和刚刚有区别吗,哦,有,就是python和Java之间换了个协议罢了

没错,这里python和Java使用了socket网络编程,比HTTP要底层,HTTP就是基于socket实现的。那么为什么我们要用socket呢

  • Socket是一种编程接口,用于实现网络通信,可以在不同计算机上的进程之间进行通信。
  • Socket提供了底层的网络通信功能,允许程序员直接访问网络协议栈。
  • Socket可以使用不同的传输协议,如TCP、UDP等,并且可以使用不同的端口号进行通信。
  • Socket可以实现全双工通信,即客户端和服务器可以同时发送和接收数据。
  • Socket可以用于构建各种网络应用,包括HTTP服务器、聊天应用、游戏服务器等。

因为socket是一种底层的传输协议,没有那些多余的封装,很适合我们这种对于性能要求比较高的需求

说干就干

这是Java的代码部分

import java.io.*;
import java.net.Socket;

/**
 * 命令工具类
 */
public class CommandUtil {

    public static byte[] getImage(){
        return socket(File.pendingProcessingImg);
    }

    /**
     * 发送图片二进制流,并等待图片处理完毕返回处理好的图片二进制数据
     * @param bytes 原始图片二进制流
     * @return 处理好的图片二进制流
     */
    public static byte[] getImage(byte[] bytes){
        return socket(bytes);
    }

    /**
     * 发包工具
     */
    private static byte[] socket(byte[] bytes){
        String host = "127.0.0.1";  // Python服务器的地址
        int port = 8888;            // Python服务器的端口号

        try {
            // 连接到Python服务器
            Socket socket = new Socket(host, port);
            System.out.println("Connected to Python server");
            OutputStream outputStream = socket.getOutputStream();
            String message = Base64Util.getImgFileToBase642(bytes);
            message += "#";
            outputStream.write(message.getBytes());
            // 接收Python服务器返回的数据
            InputStream inputStream = socket.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            String response = reader.readLine();
            socket.close();
            // 关闭套接字
            return Base64Util.getImgBase64ToImgFile(response);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}

这是python的代码部分

import socket

import info

HOST = '127.0.0.1'  # 监听的地址
PORT = 8888  # 监听的端口号
# 创建套接字并监听
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((HOST, PORT))
server_socket.listen(1)

while True:
    try:

        # 等待客户端连接
        client_socket, address = server_socket.accept()
        client_socket.settimeout(0.5)
        # 接收Java客户端发送的数据并返回
        result = ""
        while True:
            data = client_socket.recv(1024)
            if len(data) < 1024:
                break
            result += data.decode()
        result1 = info.fun(result)  # 图像处理函数的封装
        result1 = result1.encode()
        client_socket.send(result1)
        client_socket.close()

    except Exception as err:
        server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server_socket.bind((HOST, PORT))
        server_socket.listen(1)
        print(err)
        continue

大概解释一下,python创建了一个socket服务,等待Java程序连接上后读取Java程序的图片信息,然后python进行图像处理,并将结果再次通过这个socket隧道传输回Java程序,然后就断开连接,等待下一次的连接,一直重复这个过程

该系统很好的实现了图片数据在Java和python之间的传输,并且全程的数据都在内存中流转,整个系统性能优异,即使是图片的展示也像是播放视频一般的流畅


后记&补充

这是一个软件杯比赛的经验总结

下面我来补充我们在这次比赛中对于整个后端系统的设计

前端实时获取一段汽车的视频片段,然后发送给Java后端服务器,由于python端只编写了图片处理的逻辑,还并不能直接处理视频,所以Java在收到视频后开启一个线程将视频拆分成图片,并存储到一个队列中。线程2实时读取原始图片的队列,发现队列不为空时,将队列中的图片取出并往python端发送。然后线程3是Java接收python发送的数据,Java这也开启了一个socket服务专门用于接收python处理好的图片和其他数据,然后做处理存入队列。最后Java后端根据python提供的信息做出判断,给中控平台发送相关指令(VSOA协议)

这个系统给python一张原始图片,python会生成三张数据图片和2个信息

这种系统设计很好的满足了实时性的要求,不会出现系统阻塞的现象,当后端收到图片后,就会马上拆分视频为图片,一旦图片队列有图片就会给python处理,python一旦处理好就会发送给Java,Java又会马上接收放入另一个队列给前端请求,前端又是不断的请求最新的图片,前端最后显示的就是一个实时的处理画面

补充一个Java编写一个接口获取多张图片的链接

【Java】如何通过一次请求获取多张图片-CSDN博客

这里我就用到了这种方法

下面是Java的socket服务逻辑

public class SocketClientThread extends java.lang.Thread {

    @Override
    public void run() {
        while (true) {
            try {
                ServerSocket serverSocket = new ServerSocket(5000);
                System.out.println("Server started. Waiting for connections...");

                Socket clientSocket = serverSocket.accept();
                System.out.println("Client connected.");

                BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

                String line = reader.readLine();

                String[] split = line.split("#"); //使用#分割图片


                Redis.STRING_QUEUE.enqueue(line);        
                Redis.RESULT_OBJECT_QUEUE.enqueue(new ResultObject(split));

                System.out.println("Client disconnected.");
                reader.close();
                clientSocket.close();
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}class ResultObject {

    private byte[] img ;
    private byte[] depthImg;
    private byte[] biredImg;
    private boolean light ;  //false == green , true == red
    private boolean people;

    public ResultObject(String[] line){
        img = Base64Util.getImgBase64ToImgFile(line[0]);     //图片1
        depthImg = Base64Util.getImgBase64ToImgFile(line[1]);//图片2
        biredImg = Base64Util.getImgBase64ToImgFile(line[2]);//图片3
        light = "True".equals(line[3]);                      //是否压线
        people = "True".equals(line[4]);                     //行人是否在预警范围
    }

    public boolean isLight() {
        return light;
    }

    public void setLight(boolean light) {
        this.light = light;
    }

    public boolean isPeople() {
        return people;
    }

    public void setPeople(boolean people) {
        this.people = people;
    }

    public byte[] getImg() {
        return img;
    }

    public void setImg(byte[] img) {
        this.img = img;
    }

    public byte[] getDepthImg() {
        return depthImg;
    }

    public void setDepthImg(byte[] depthImg) {
        this.depthImg = depthImg;
    }

    public byte[] getBiredImg() {
        return biredImg;
    }

    public void setBiredImg(byte[] biredImg) {
        this.biredImg = biredImg;
    }
}
/**
 * 智能队列,也不管智能不智能,名字牛逼就对了
 */
public class IntelligentImageQueue {

    public static boolean IS_TO_BE_PROCESSED;

    public static final Queue<byte[]> beforeQueue = new Queue<>();

    private static final Queue<byte[]> imageQueue = new Queue<>();

    private static byte[] temp ;

    private static byte[] bufImg = null;

    public static void start(){
        Thread t = new ImageProcessThread();
        System.out.println("Thread t = new ImageProcessThread();");
        t.start();
        System.out.println("t.start();");
    }

    private static class ImageProcessThread extends java.lang.Thread{
        @Override
        public void run() {
            while (IS_TO_BE_PROCESSED || isContinue()){
                byte[] buf = getImgByte();
                if (buf == null){
                    continue;
                }
                temp = buf;
//                System.out.println(1);
                byte[] res = CommandUtil.getImage(buf);
//                System.out.println(2);
                if (res!=null){
                    entry(res);
                }
                System.out.println(111);
            }
        }
    }

    private static byte[] getImgByte(){
        return isContinue() ? beforeQueue.dequeue() : null;
    }

    private static boolean isContinue(){
        return !beforeQueue.isEmpty();
    }

    /**
     * 获取最新的图片
     * @return 图片字节流
     */
    public static byte[] getNewImg(){

        return temp;
    }

    /**
     * 图片入队
     * @param buf 图片字节流
     */
    public static void entry(byte[] buf){
        imageQueue.enqueue(buf);
    }

}

版权问题,我就展示一张模糊一点的截图给大家看看效果

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐