Android studio 客户端(将拍摄的图片进行上传)与python服务器进行socket通信

前言:由于一个项目需要用到app,所以就写下这篇文章来记录

1、客户端

Android studio app开发

1.1、新建项目

 

1.2、app进行拍照并显示

参考文章:使用Androidstudio调用摄像头拍照并保存照片

activity_main.xml文件:

app/src/main/res/layout/activity_main.xml

   <ImageView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/img_photo"
        tools:ignore="MissingConstraints" />

    <TextView
        android:id="@+id/predict"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:textColor="@color/black"
        android:textSize="30sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.105"
        tools:layout_editor_absoluteX="0dp" />

    <Button
        android:id="@+id/client_submit"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/submit"
        android:textSize="30sp"
        tools:ignore="MissingConstraints" />

 strings.xml文件:

app/src/main/res/layout/strings.xml

<string name="submit">拍照上传</string>

AndroidManifest.xml

app/src/main/res/AndroidManifest.xml 

        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.example.android.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>

注:在下面这一行中的 android 需要修改成自己的项目的名字,即中的 1.1、新建项目 中的第三张图的序号1;或者修改成 MainActivity.java 文件中的第一行的最后一个单词

android:authorities="com.example.showtakephoto.fileprovider"

 file_paths.xml

app/src/main/res/xml/file_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path
        name="my_images"
        path="." />

</paths>

MainActivity.java

 app/src/main/java/MainActivity.java

整个项目需要的包

import static android.util.Base64.*;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.FileProvider;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
public class MainActivity extends AppCompatActivity {

    private TextView predict;
    ImageView img_photo;
    private Button client_submit;
    final int TAKE_PHOTO = 1;
    Uri imageUri;
    File outputImage;

    private static final int UPDATE_ok = 0;
    private static final int UPDATE_UI = 1;
    private static final int ERROR = 2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initViews();
        initEvent();
    }

    //初始化控件
    private void initViews() {
        img_photo = findViewById(R.id.img_photo);
        predict = findViewById(R.id.predict);
        client_submit = findViewById(R.id.client_submit);
    }

    //进行拍照
    private void initEvent() {
        client_submit.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String filename = "test.png"; //自定义的照片名称
                outputImage = new File(getExternalCacheDir(),filename);  //拍照后照片存储路径
                try {if (outputImage.exists()){
                    outputImage.delete();
                }
                    outputImage.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                if (Build.VERSION.SDK_INT >= 24) {
                    //图片的url
                    imageUri = FileProvider.getUriForFile(MainActivity.this, "com.example.android.fileprovider", outputImage);
                } else {
                    imageUri = Uri.fromFile(outputImage);
                }
                //跳转界面到系统自带的拍照界面
                Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");  //调用手机拍照功能其实就是启动一个activity
                intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);  //指定图片存放位置,指定后,在onActivityResult里得到的Data将为null
                startActivityForResult(intent, TAKE_PHOTO);  //开启相机
            }
        });
    }

    //照片接收
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case TAKE_PHOTO:
                if (resultCode == RESULT_OK) {
                    // 使用try让程序运行在内报错
                    try {
                        //将图片显示
                        Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
                        img_photo.setImageBitmap(bitmap);  //imageview控件显示刚拍的图片
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    }
                }
                break;
            default:
                break;
        }
    }
}

 注:在下面这一行中的 android 需要修改成自己的项目的名字,即中的 1.1、新建项目 中的第三张图的序号1;或者修改成 MainActivity.java 文件中的第一行的最后一个单词

 if (Build.VERSION.SDK_INT >= 24) {
                    //图片的url
                    imageUri = FileProvider.getUriForFile(MainActivity.this, "com.example.android.fileprovider", outputImage);
                } else {
                    imageUri = Uri.fromFile(outputImage);
                }

 按照上面的步骤进行配置,就能实现拍照并显示,在真机上也能运行。

1.3、真机测试

 

1.4、将拍摄的照片进行上传(socket)

参考博客:利用base64编码实现Android和python程序之间的数据流通信

参考博客:Android 图片压缩的几种方法 

参考博客:android.view.ViewRootImpl$CalledFromWrongThreadException异常处理

AndroidManifest.xml

app/src/main/res/AndroidManifest.xml  

    <uses-permission android:name="android.permission.INTERNET" />

MainActivity,java

 app/src/main/java/MainActivity.java 

编码图片:方便进行传输

 /**利用bitmap将图片转换成Base64编码的字符串**/
    public static String bitmapToBase64(Bitmap bitmap) {
        String result = null;
        ByteArrayOutputStream baos = null;
        try {
            if (bitmap != null) {
                baos = new ByteArrayOutputStream();
                //压缩图片至100kb
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
                baos.flush();
                baos.close();
                //接收压缩的图片数据流,并转换成base64编码
                byte[] bitmapBytes = baos.toByteArray();
                result = encodeToString(bitmapBytes, DEFAULT);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (baos != null) {
                    baos.flush();
                    baos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;
    }
    /**利用图片路径将图片转换成Base64编码的字符串**/
    public static String imageToBase64(File path){
        InputStream is = null;
        byte[] data = null;
        String result = null;
        try{
            is = new FileInputStream(path);
            //创建一个字符流大小的数组。
            data = new byte[is.available()];
            //写入数组
            is.read(data);
            //用默认的编码格式进行编码
            result = encodeToString(data, NO_CLOSE);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(null !=is){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
        return result;
    }

压缩图片:减少图片的内存,加快传输图片

    /**图片压缩: 规定尺寸等比例压缩,宽高不能超过限制要求 @param beforBitmap 要压缩的图片 @param maxWidth 最大宽度限制 @param maxHeight 最大高度限 @return 压缩后的图片**/
    static public Bitmap compressBitmap(Bitmap beforBitmap, double maxWidth, double maxHeight) {
        // 图片原有的宽度和高度
        float beforeWidth = beforBitmap.getWidth();
        float beforeHeight = beforBitmap.getHeight();
        if (beforeWidth <= maxWidth && beforeHeight <= maxHeight) {
            return beforBitmap;
        }

        // 计算宽高缩放率,等比例缩放
        float scaleWidth =  ((float) maxWidth) / beforeWidth;
        float scaleHeight = ((float)maxHeight) / beforeHeight;
        float scale = scaleWidth;
        if (scaleWidth > scaleHeight) {
            scale = scaleHeight;
        }
        Log.d("BitmapUtils", "before[" + beforeWidth + ", " + beforeHeight + "] max[" + maxWidth
                + ", " + maxHeight + "] scale:" + scale);

        // 矩阵对象
        Matrix matrix = new Matrix();
        // 缩放图片动作 缩放比例
        matrix.postScale(scale, scale);
        // 创建一个新的Bitmap 从原始图像剪切图像
        Bitmap afterBitmap = Bitmap.createBitmap(beforBitmap, 0, 0,
                (int) beforeWidth, (int) beforeHeight, matrix, true);
        return afterBitmap;
    }

 图片传输:socket通信的 ip 和 port 需要换成自己的 ip 和 port 

    private void startNetThread(View view) {

        new Thread() {
            @RequiresApi(api = Build.VERSION_CODES.O)
            public void run() {
                try {
                    Socket socket = new Socket("192.168.0.110", 22222);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    // 告诉主线程一个消息,更新ui
                    Message msg1 = new Message();
                    // 消息的代号,是一个int类型
                    msg1.what = UPDATE_ok;
                    // 要传递的消息对象
                    msg1.obj = socket;
                    // 利用handler发送消息
                    handler.sendMessage(msg1);

                    //得到socket读写流
                    OutputStream os = socket.getOutputStream();
                    PrintWriter pw = new PrintWriter(os);

                    //将图片的路径转换成base64码
                    Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
                    bitmap = compressBitmap(bitmap,bitmap.getWidth() / 4, bitmap.getWidth()/4);
                    String info = bitmapToBase64(bitmap);
//                    String info = imageToBase64(outputImage);

                    //利用流按照一定的操作,对socket进行读写操作
                    pw.write(info);
                    pw.flush();

                    //关闭发送数据的数据流,数据发送完毕
                    socket.shutdownOutput();

                    BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
                    String content = br.readLine();

                    // 告诉主线程一个消息,更新ui
                    Message msg = new Message();
                    // 消息的代号,是一个int类型
                    msg.what = UPDATE_UI;
                    // 要传递的消息对象
                    msg.obj = content;
                    // 利用handler发送消息
                    handler.sendMessage(msg);

                    socket.close();
                    os.close();
                    br.close();
                } catch (UnknownHostException e) {
                    e.printStackTrace();
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }

消息处理机制:更新UI 

    // 主线程创建消息处理器
    Handler handler = new Handler() {
        // 但有新消息时调用
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == UPDATE_UI) {
                // 获取消息对象
                if(msg.obj.equals("0")){
                    predict.setText("预测失败");   //更新UI的内容
                }else{
                    String content = (String) msg.obj;
                    predict.setText(content);
                }
            } else if (msg.what == ERROR) {
                // Toast也是属于UI的更新
                Toast.makeText(getApplicationContext(), "预测失败", Toast.LENGTH_LONG).show();
            }else if (msg.what == UPDATE_ok){
                Toast.makeText(getApplicationContext(), "连接服务器成功", Toast.LENGTH_LONG).show();
            }
        }
    };

 启动网络传输

//启动网络线程处理数据
startNetThread(client_submit);

1.5、真机测试

  

2、服务器

python socket 通信

2.1、socket通信

socket通信的 ip 和 port 需要换成自己的 ip 和 port ,客户端和服务器需一致

import socket
import base64
from PIL import Image
from io import BytesIO
import detect

# 1. 创建 socket 对象
tcpServe = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
base64_data = ''
# 2. 将 socket 绑定到指定地址
address = ('192.168.0.110', 22222)
tcpServe.bind(address)
# 3. 接收连接请求
tcpServe.listen(5)
print("已开启服务器,等待客户端连接")

# 4. 等待客户请求一个连接
tcpClient, addr = tcpServe.accept()
print('客户端的连接地址', addr)
# 5. 处理:服务器和客户端通过 send 和 recv 方法通信
while True:
    data = tcpClient.recv(1024)
    base64_data += str(data, 'utf-8').strip()
    print(base64_data)
    if not data:
        break

img = base64.b64decode(base64_data)
image_data = BytesIO(img)
im = Image.open(image_data)
im.save("./data/img/pred.jpg")
# im.show()
result = detect.predict()
print(result)
try:
    message = result[max(result)]
except:
    message = "0"
tcpClient.send((message + "\n").encode())  # 二进制  如果字符串不加\n   readline方法将一直阻塞 read方法则不会出现阻塞的情况
print("预测完成")
# 6. 传输结束,关闭连接
tcpClient.close()
tcpServe.close()


 其中: result = detect.predict()  这句是我对接收到的图片进行的处理得到的结果,并将结果返回为app,在app上显示。

但是,上面这种就会出现一种问题,就是当app发送不同的图片是服务器接收到的都是同一种图片。需要每次都重启服务器才能解决。

我试了很多方法都没有解决,下面一种方法就是通过不断的重启服务器来进行接收。

如果各位大佬有更好的方法,欢迎多多交流。

解决方法:

import socket
import base64
from PIL import Image
from io import BytesIO
import detect

while True:
    # 1. 创建 socket 对象
    tcpServe = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    base64_data = ''
    # 2. 将 socket 绑定到指定地址
    address = ('192.168.0.110', 22222)
    tcpServe.bind(address)
    # 3. 接收连接请求
    tcpServe.listen(5)
    print("已开启服务器,等待客户端连接")

    # 4. 等待客户请求一个连接
    tcpClient, addr = tcpServe.accept()
    print('客户端的连接地址', addr)
    # 5. 处理:服务器和客户端通过 send 和 recv 方法通信
    while True:
        data = tcpClient.recv(1024)
        base64_data += str(data, 'utf-8').strip()
        print(base64_data)
        if not data:
            break

    img = base64.b64decode(base64_data)
    image_data = BytesIO(img)
    im = Image.open(image_data)
    im.save("./data/img/pred.jpg")
    # im.show()
    result = detect.predict()
    print(result)
    try:
        message = result[max(result)]
    except:
        message = "0"
    tcpClient.send((message + "\n").encode())  # 二进制  如果字符串不加\n   readline方法将一直阻塞 read方法则不会出现阻塞的情况
    print("预测完成")
    # 6. 传输结束,关闭连接
    tcpClient.close()
    tcpServe.close()


3、参考博客

 使用Androidstudio调用摄像头拍照并保存照片

 利用base64编码实现Android和python程序之间的数据流通信

Android 图片压缩的几种方法

android.view.ViewRootImpl$CalledFromWrongThreadException异常处理

4、源码获取

Android-python-socket

猜你喜欢

转载自blog.csdn.net/qq_48764574/article/details/124485815