前言:由于一个项目需要用到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异常处理
|