目录
4.4.2 TensorFlow SSD MobileNet模型训练与使用
一、背景概述
注:这里只是对毕业设计做个简单的介绍,包括一些源代码的解释,以及用到的技术(详细的UML图在gitee上有)
这次做的内容主要是为了模拟一款软件的开发(从设计到运维),主要使用C++实现整套系统(http服务,使用opencv-dnn模块+yolov3+TensorFlow模型迁移学习)的主要框架,同时结合了简单python(支付宝,腾讯云图像处理api),go(留言系统),qml(留言系统的简单显示)。
二、效果演示










三、系统分析与设计
由于用例图、活动图、顺序图等都是在初稿时设计的,有些变动,暂不提供,有需要请私聊联系
3.1,资源及工具
- 开发语言:h5(css),js,c++,golang,python,qml
- 开发环境:腾讯云服务器(ubuntu),Qt5.12,cmake,pc(ubuntu),webstorm,pycharm,virtualparadigm CE
- 涉及到的框架:TensorFlow,yolov3模型,opencv,nginx
3.2,功能需求
- 用户登陆、注册、修改个人信息
- 图片上传、下载(格式转化)、删除、恢复
- 图片自动分类(yolov3)
- 场景文字识别
- 纯文本检测(由于识别率比较低,后台代码实现了但未在网页中使用,普通的二值化,膨胀,腐蚀等形态学操作,确定区域,再通过tesseract识别文字)
- 场景文字识别(CSER,opencv官网提供了原理和代码)
- 腾讯云api(文字识别与检测)
- TensorFlow模型训练及导出使用(人头数量检测,之前是用于人流量识别)
3.3,系统部署
在云相册管家的部署中,网页、支付系统、数据库、HTTP服务端这几个程序都是可以独立于任何一台服务器的。由于服务器数量的受限,未能将每个程序放到不同的服务器做到分布式管理。在整个系统中,只使用了一台云服务器,使用了一台云服务器,将将所有的所有的程序放在此服务器中,再程序放在此服务器中,再使用使用nginx做反向代理做反向代理。用户可以通过浏览器访问https://www.zjyxxn.cn访问云相册管家系统。
模块与模块之间(就不同语言之间)采用socket通讯方式(TCP)

3.4,数据库设计
整个系统使用到的数据库部分比较少,只有三个表:user,imagepath,contact。分别是存放用户信息,用户图片的信息,以及留言表。

四、部分代码实现(C++部分比较多,放最后)
4.0,nginx 配置
https://blog.csdn.net/qq_37960222/article/details/97789245
4.1,GO
golang主要实现的是留言系统部分,实现内容比较简单,就是数据库的连接,以及留言系统页面的请求与相应,使用go的原因主要是因为如果再把这个模块整合到C++中(用C++实现),当某天主系统崩溃的时候,留言系统还能继续发挥作用(和前端的数据交互是单独的,不依赖任何模块的)
#主函数
package main
import (
"fmt"
"net/http"
)
var mdb *ContactDB
type MyHandler struct {}
func (mh MyHandler) ServeHTTP(w http.ResponseWriter,r *http.Request){
if r.URL.Path =="/contact/"{
_name := r.FormValue("name") //FormValue(),如果name存在,也是获取第一个值【查源码就知道了】
_email := r.PostFormValue("email") // PostFormValue()也是同理,name存在是,也是获取第一个值
_message := r.Form.Get("message")
print(_email,_name,_message)
isRecv := mdb.insertData(_name,_email,_message)
if isRecv==true {
w.Write([]byte("send success"))
}else {
w.Write([]byte("send failed"))
}
return //这里的return是要加的,不然下面的代码也会执行了
}
if r.URL.Path == "/world"{
w.Write([]byte("world page"))
return
}
// 可以继续写自己的路由匹配规则
print(r.URL.Path)
}
func makeHandler(handlers ...http.HandlerFunc)http.HandlerFunc{
return func(w http.ResponseWriter, r *http.Request) {
for _,hander := range handlers{
hander(w,r)
}
}
}
func main() {
mdb = &ContactDB{}
mdb.initDB()
http.Handle("/contact/", MyHandler{})
if err := http.ListenAndServe(":7997",nil); err!=nil{
fmt.Println("start http server fail:", err)
}
}
#数据库连接
package main
import (
"database/sql"
"encoding/json"
"fmt"
_ "github.com/go-sql-driver/mysql"
"time"
)
const (//需要改相应的配置
USERNAME = "root"
PASSWORD = ""
NETWORK = "tcp"
SERVER = "localhost"
PORT = 3306
DATABASE = "graduation"
)
type Message struct {
Name sql.NullString `db:"name"` //由于在mysql的users表中name没有设置为NOT NULL,所以name可能为null,在查询过程中会返回nil,如果是string类型则无法接收nil,但sql.NullString则可以接收nil值
Email sql.NullString `db:"email"`
Text sql.NullString `db:"text"`
id int `db:"id"`
}
type ContactDB struct {
DB *sql.DB
}
func (mydb *ContactDB)initDB() {
dsn := fmt.Sprintf("%s:%s@%s(%s:%d)/%s",USERNAME,PASSWORD,NETWORK,SERVER,PORT,DATABASE)
var err error
mydb.DB,err = sql.Open("mysql",dsn)
if err != nil{
fmt.Printf("Open mysql failed,err:%v\n",err)
}
mydb.DB.SetConnMaxLifetime(100*time.Second) //最大连接周期,超过时间的连接就close
mydb.DB.SetMaxOpenConns(100)//设置最大连接数
mydb.DB.SetMaxIdleConns(16) //设置闲置连接数
}
func (mydb *ContactDB)queryOne(name string){
msg := new(Message)
row := mydb.DB.QueryRow("select * from contact where name=?",name)
//row.scan中的字段必须是按照数据库存入字段的顺序,否则报错
if err :=row.Scan(&msg.Name,&msg.Email,&msg.Text,&msg.id); err != nil{
fmt.Printf("scan failed, err:%v",err)
return
}
fmt.Println(*msg)
}
//查询多行
func (mydb *ContactDB)queryMulti(){
msg := new(Message)
rows, err := mydb.DB.Query("select * from contact")
defer func() {
if rows != nil {
rows.Close()
}
}()
if err != nil {
fmt.Printf("Query failed,err:%v", err)
return
}
for rows.Next() {
err = rows.Scan(&msg.Name, &msg.Email, &msg.Text,&msg.id)
if err != nil {
fmt.Printf("Scan failed,err:%v", err)
return
}
fmt.Println(*msg)
data,_ := json.Marshal(msg)
fmt.Print(string(data))
}
}
//插入数据
func (mydb *ContactDB)insertData(_name string,_email string, _text string) bool{
result,err := mydb.DB.Exec("insert INTO contact(name,email,text) values(?,?,?)",_name,_email,_text)
if err != nil{
fmt.Printf("Insert failed,err:%v",err)
return false
}
lastInsertID,err := result.LastInsertId()
if err != nil {
fmt.Printf("Get lastInsertID failed,err:%v",err)
return false
}
fmt.Println("LastInsertID:",lastInsertID)
rowsaffected,err := result.RowsAffected()
if err != nil {
fmt.Printf("Get RowsAffected failed,err:%v",err)
return false
}
fmt.Println("RowsAffected:",rowsaffected)
return true
}
//更新数据
func (mydb *ContactDB)updateDataName(_name string,_id int){
result,err := mydb.DB.Exec("UPDATE users set name=? where id=?",_name,_id)
if err != nil{
fmt.Printf("Insert failed,err:%v",err)
return
}
rowsaffected,err := result.RowsAffected()
if err != nil {
fmt.Printf("Get RowsAffected failed,err:%v",err)
return
}
fmt.Println("RowsAffected:",rowsaffected)
}
func (mydb *ContactDB)updateDataEmail(_email string,_id int){
result,err := mydb.DB.Exec("UPDATE users set email=? where id=?",_email,_id)
if err != nil{
fmt.Printf("Insert failed,err:%v",err)
return
}
rowsaffected,err := result.RowsAffected()
if err != nil {
fmt.Printf("Get RowsAffected failed,err:%v",err)
return
}
fmt.Println("RowsAffected:",rowsaffected)
}
func (mydb *ContactDB)updateDataText(_text string,_id int){
result,err := mydb.DB.Exec("UPDATE users set text=? where id=?",_text,_id)
if err != nil{
fmt.Printf("Insert failed,err:%v",err)
return
}
rowsaffected,err := result.RowsAffected()
if err != nil {
fmt.Printf("Get RowsAffected failed,err:%v",err)
return
}
fmt.Println("RowsAffected:",rowsaffected)
}
//删除数据
func (mydb *ContactDB)deleteData(_id int){
result,err := mydb.DB.Exec("delete from users where id=?",_id)
if err != nil{
fmt.Printf("Insert failed,err:%v",err)
return
}
rowsaffected,err := result.RowsAffected()
if err != nil {
fmt.Printf("Get RowsAffected failed,err:%v",err)
return
}
fmt.Println("RowsAffected:",rowsaffected)
}
4.2,qml(gitee链接)
qml主要是对留言系统的数据做个简单的界面显示。
TableView{
id: contactTable
anchors.fill: parent
function updateMsg(){
msgModel.clear()
var url="https://www.zjyxxn.cn/contact/contactDB"
HttpHandler.get(url,
function(result){
var _ListVal = HttpHandler.parseResult(result)
for(var i=0;i<_ListVal.length;i++){
console.debug(_ListVal[i][3])
msgModel.append({"_id": _ListVal[i][0], "_name":_ListVal[i][1],"_email":_ListVal[i][2],"_text":_ListVal[i][3]})
}
},function (){ //失败后的回调函数
console.log("error")
}
);
console.debug("update")
}
//TableViewColumn 描述表格的每一列
TableViewColumn{role: "_id"; title: "Id"; width: 30; elideMode: Text.ElideRight;}
TableViewColumn{role: "_name"; title: "Name"; width: 100;}
TableViewColumn{role: "_email"; title: "Email"; width: 140;}
TableViewColumn{role: "_text"; title: "Text"; width: 100;}
rowDelegate: Rectangle{
height: 20
color: styleData.selected?"blue":"white"
}
itemDelegate:Text{//设置每个单元格的字体样式
text: styleData.value
height: 25
color: styleData.selected? "red" : styleData.textColor
elide: styleData.elideMode
font.pixelSize: 20
}
headerDelegate :Rectangle{//设置表头的样式
implicitWidth: 20
implicitHeight: 48
gradient: styleData.pressed ? contactTable.pressG :
(styleData.containsMouse ? contactTable.hoverG : contactTable.nomalG)
border.width: 1
border.color: "gray"
Text{
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 4
anchors.right: parent.right
anchors.rightMargin: 4
text: styleData.value
color: styleData.pressed ? "red" : "blue"
font.bold: true
}
}
model:ListModel{
id: msgModel
}
focus: true
}
// GET
function get(url, success, failure)
{
var xhr = new XMLHttpRequest;
xhr.open("GET", url);
xhr.onreadystatechange = function() {
handleResponse(xhr, success, failure);
}
xhr.send();
}
// POST
function post(url, arg, success, failure)
{
var xhr = new XMLHttpRequest;
xhr.open("POST", url);
xhr.setRequestHeader("Content-Length", arg.length);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;"); //用POST的时候一定要有这句
xhr.onreadystatechange = function() {
handleResponse(xhr, success, failure);
}
xhr.send(arg);
}
// 处理返回值
function handleResponse(xhr, success, failure){
if (xhr.readyState == XMLHttpRequest.DONE) {
if (xhr.status == 200){
if (success != null && success != undefined)
{
var result = xhr.responseText;
try{
success(result, JSON.parse(result));
}catch(e){
success(result, {});
}
}
}
else{
if (failure != null && failure != undefined)
failure(xhr.responseText, xhr.status);
}
}
}
function parseResult(result){
var rtnVal=new Array()
result=result.slice(1,result.length)
var len=result.length
var resList=0
while(resList<len){
if(result.charAt(resList++)==="{"){
var perVal=""
while(result.charAt(resList)!=="}"){
perVal+=result.charAt(resList++)
}
resList++//因为{}和{}中间还有个“,”
rtnVal.push(parsePerVale(perVal))
}
}
// console.debug(rtnVal[0])
return rtnVal
}
function parsePerVale(perVal){
var len=perVal.length
var beg=0
var commaNum=0
var perRtnVal=new Array()
while(commaNum<4){
var w=""
while(beg<len){
if(perVal.charAt(beg)===","){
beg++
break
}else{
w+=perVal.charAt(beg++)
}
}
if(commaNum === 3){
while(beg<len){
w+=perVal.charAt(beg++)
}
}
perRtnVal.push(w)
commaNum++
}
return perRtnVal
}
4.3,python(gitee链接)
python主要实现的是腾讯云文字识别api的连接(虽然也用opencv实现了文字识别,但是识别效果很差,因此在系统中还是使用了api)以及支付宝的支付(使用的沙箱环境,真正支付的话需要有企业证书才可以)。具体操作看“支付宝开放平台开发助手”
腾讯云api使用的是base64编码。在整个工程中,C++程序将接受到的图片转成base64编码的格式,并通过socket将图片的base64值传给python程序,python程序通过调用腾讯云api(官方的教程中有使用说明,并给了相应的代码)将处理好的图片和文本传递给C++程序,再将结果传给用户。
4.4,C++(gitee链接)
需要修改CMakeLists.txt动态库的位置,修改zjynamespace里面相应的数据库账号密码,objectdetection.cpp中模型的位置,在CMakeLists.txt目录下创建build目录&&cd build,运行cmake .. 后运行 make,运行./bsTest就可以运行C++主程序
4.4.1 主程序的框架
//httpserver.h
#include "headers.h"
#include "imagebasetool.h"
#ifndef HTTPSERVER_H
#define HTTPSERVER_H
class HttpServer
{
using ReqHandler = zjy::ReqHandler<UserManageInterface>;
public:
HttpServer();
~HttpServer();
void init(const std::string &port);
bool start();
bool close();
void addHandler(const std::string &url, ReqHandler req_handler);
void removeHandler(const std::string & url);
static void addSession(mg_connection* conn);
static std::unordered_map<mg_connection*,std::string> format_websocket_session;
static std::unordered_set<mg_connection*> s_websocket_session_set;
static std::string s_web_dir;//web root
static mg_serve_http_opts s_server_option;//web server opetion
static std::unordered_map<std::string, ReqHandler> s_handler_map;
static DBPool* _db;
static std::unordered_map<std::string, UserManageInterface*> s_session;
private:
static void OnHttpWebsocketEvent(mg_connection *connection, int event_type, void *event_data);
static void HandleHttpEvent(mg_connection *connection, http_message *http_req);
static int isWebSocket(const mg_connection* connection);
static void HandleWebsocketMessage(mg_connection* connection, int event_type, websocket_message* ws_msg, std::string filename);
static void SendWebsocketMsg(mg_connection* connection, std::string msg,bool setClose);
static void SendWebsocketMsg_binary(mg_connection* connection, std::string msg,bool setClose);
static void BroadcastWebsocketMsg(std::string msg);
/*
* @std::pair<mg_connection*,std::string> the fist value means which client connected,
* second velue storage the client's image type
*/
std::string m_port; // 端口
mg_mgr m_mgr; // 连接管理器
static int num;
public:
//this para is used to test
//static UserManageInterface* _umm;
};
#endif // HTTPSERVER_H
//httpserver.cpp
#include <exception>
#include "httpserver.h"
#include "log.h"
//user upload's picture name,used to slite suffix
extern std::string uploadformat;
std::string uploadformat{};
HttpServer::HttpServer()
{
}
HttpServer::~HttpServer()
{
}
static bool route_check(http_message *http_msg, char *route_prefix)
{
if (mg_vcmp(&http_msg->uri, route_prefix) == 0)
return true;
else
return false;
// TODO: 还可以判断 GET, POST, PUT, DELTE等方法
//mg_vcmp(&http_msg->method, "GET");
//mg_vcmp(&http_msg->method, "POST");
//mg_vcmp(&http_msg->method, "PUT");
//mg_vcmp(&http_msg->method, "DELETE");
}
void HttpServer::init(const std::string &port)
{
m_port = port;
s_server_option.enable_directory_listing = "yes";
s_server_option.document_root = s_web_dir.c_str();
//开启 CORS,本项只针对主页加载有效
s_server_option.extra_headers = "Access-Control-Allow-Origin: *";
}
bool HttpServer::start()
{
mg_mgr_init(&m_mgr, nullptr);
mg_connection *connection = mg_bind(&m_mgr, m_port.c_str(), HttpServer::OnHttpWebsocketEvent);
if (connection == nullptr)
return false;
// for both http and websocket
mg_set_protocol_http_websocket(connection);
printf("starting http server at port: %s\n", m_port.c_str());
// loop
while (true){
mg_mgr_poll(&m_mgr, 500); // ms
}
}
bool HttpServer::close()
{
mg_mgr_free(&m_mgr);
return true;
}
void HttpServer::addHandler(const std::string &url, HttpServer::ReqHandler req_handler)
{
if (s_handler_map.find(url) != s_handler_map.end()){
LOG::ErrorLog(__FILE__, __LINE__, "url exist");
return;
}
s_handler_map.insert(std::make_pair(url, req_handler));
}
void HttpServer::removeHandler(const std::string &url)
{
auto it = s_handler_map.find(url);
if (it != s_handler_map.end())
s_handler_map.erase(it);
}
void HttpServer::addSession(mg_connection *conn)
{
s_websocket_session_set.insert(conn);
}
void HttpServer::OnHttpWebsocketEvent(mg_connection *connection, int event_type, void *event_data)
{
// distinguish http and websocket
if (event_type == MG_EV_HTTP_REQUEST) {
http_message *http_req = static_cast<http_message *>(event_data);
HandleHttpEvent(connection, http_req);
}
else if (event_type == MG_EV_WEBSOCKET_HANDSHAKE_DONE ||
event_type == MG_EV_WEBSOCKET_FRAME ||
event_type == MG_EV_CLOSE) {
websocket_message *ws_message = static_cast<struct websocket_message *>(event_data);
HandleWebsocketMessage(connection, event_type, ws_message,"");
}
}
void HttpServer::HandleHttpEvent(mg_connection *connection, http_message *http_req)
{
std::string req_str = std::string(http_req->message.p, http_req->message.len);
printf("got request: %s\n", req_str.c_str());
// 先过滤是否已注册的函数回调
std::string url = std::string(http_req->uri.p, http_req->uri.len);
std::string body = std::string(http_req->body.p, http_req->body.len);
try {
struct mg_str *cookie_header;
cookie_header=mg_get_http_header(http_req, "Cookie"); //获取Cookie 整条
if(cookie_header == nullptr)
throw "";
std::string getCookie = std::string(cookie_header->p,cookie_header->len);
auto pos = getCookie.find("phone=");
if(pos!=std::string::npos){
}
} catch (...){
}
auto _exist = HttpServer::s_session.find(phone);
if(HttpServer::s_session.end() == _exist){
}else{
}
auto it = s_handler_map.find(url);
if (it != s_handler_map.end())
{
ReqHandler handle_func = it->second;
handle_func(connection, http_req, umm/*HttpServer::_umm*/);
}else{
// other request
std::string rootpage="/";
if (route_check(http_req, const_cast<char*>(rootpage.c_str()))) // index page
mg_serve_http(connection, http_req, s_server_option);
else {
}
}
}
int HttpServer::isWebSocket(const mg_connection *connection)
{
return connection->flags & MG_F_IS_WEBSOCKET;
}
/*websock always used to send image from client to server*/
void HttpServer::HandleWebsocketMessage(mg_connection *connection, int event_type, websocket_message *ws_msg, std::string /*filename*/)
{
if( event_type == MG_EV_WEBSOCKET_HANDSHAKE_DONE ){
} else if (event_type == MG_EV_WEBSOCKET_FRAME) {
mg_str received_msg = {(char*)ws_msg->data, ws_msg->size};
} else if (event_type == MG_EV_CLOSE) {
if (isWebSocket(connection))
{
LOG::SuccessLog(__FILE__, __LINE__, "client websocket closed\n");
// move session
if (s_websocket_session_set.find(connection) != s_websocket_session_set.end())
s_websocket_session_set.erase(connection);
}
}
}
void HttpServer::SendWebsocketMsg(mg_connection *connection, std::string msg, bool setClose=false)
{
mg_send_websocket_frame(connection, WEBSOCKET_OP_TEXT, msg.c_str(), strlen(msg.c_str()));
if(setClose==true){
mg_send_websocket_frame(connection,WEBSOCKET_OP_CLOSE,nullptr,0);
}
}
void HttpServer::SendWebsocketMsg_binary(mg_connection *connection, std::string msg, bool setClose=false)
{
mg_send_websocket_frame(connection, WEBSOCKET_OP_BINARY, msg.c_str(), strlen(msg.c_str()));
if(setClose==true){
mg_send_websocket_frame(connection,WEBSOCKET_OP_CLOSE,nullptr,0);
}
}
void HttpServer::BroadcastWebsocketMsg(std::string msg)
{
for (mg_connection *connection : s_websocket_session_set)
mg_send_websocket_frame(connection, WEBSOCKET_OP_TEXT, msg.c_str(), strlen(msg.c_str()));
}
4.4.2 TensorFlow SSD MobileNet模型训练与使用
见往期博客:https://blog.csdn.net/qq_37960222/article/details/104460663
4.4.3 YOLOV3模型使用
using namespace cv::dnn;
std::list<string> zjyObjectDetection::image_detection(cv::Mat &pic)
{
using namespace zjy::detection;
Net net = readNetFromDarknet(yolo_tiny_cfg, yolo_tiny_model);
net.setPreferableBackend(DNN_BACKEND_OPENCV);
net.setPreferableTarget(DNN_TARGET_CPU);
std::vector<String> outNames = net.getUnconnectedOutLayersNames();
// for (int i = 0; i < outNames.size(); i++) {
// printf("output layer name : %s\n", outNames[i].c_str());
// }
vector<string> classNamesVec;
ifstream classNamesFile("/home/ubuntu/bs_test/src/object_detection_classes_yolov3.txt");
if (classNamesFile.is_open())
{
string className = "";
while (std::getline(classNamesFile, className))
classNamesVec.push_back(className);
}
// 加载图像
Mat inputBlob = blobFromImage(pic, 1 / 255.F, Size(416, 416), Scalar(), true, false);
net.setInput(inputBlob);
// 检测
std::vector<Mat> outs;
net.forward(outs, outNames);
vector<double> layersTimings;
double freq = getTickFrequency() / 1000;
double time = net.getPerfProfile(layersTimings) / freq;
ostringstream ss;
ss << "detection time: " << time << " ms";
putText(pic, ss.str(), Point(20, 20), 0, 0.5, Scalar(0, 0, 255));
vector<Rect> boxes;
vector<int> classIds;
vector<float> confidences;
for (size_t i = 0; i<outs.size(); ++i)
{
float* data = (float*)outs[i].data;
for (int j = 0; j < outs[i].rows; ++j, data += outs[i].cols)
{
Mat scores = outs[i].row(j).colRange(5, outs[i].cols);
Point classIdPoint;
double confidence;
minMaxLoc(scores, 0, &confidence, 0, &classIdPoint);
if (confidence > 0.5)
{
int centerX = (int)(data[0] * pic.cols);
int centerY = (int)(data[1] * pic.rows);
int width = (int)(data[2] * pic.cols);
int height = (int)(data[3] * pic.rows);
int left = centerX - width / 2;
int top = centerY - height / 2;
classIds.push_back(classIdPoint.x);
confidences.push_back((float)confidence);
boxes.push_back(Rect(left, top, width, height));
}
}
}
std::set<std::string> st;
std::list<std::string> ls;
vector<int> indices;
NMSBoxes(boxes, confidences, 0.5, 0.2, indices);
cout<<indices.size()<<endl;
for (size_t i = 0; i < indices.size(); ++i)
{
int idx = indices[i];
Rect box = boxes[idx];
String className = classNamesVec[classIds[idx]];
putText(pic, className.c_str(), box.tl(), FONT_HERSHEY_SIMPLEX, 1.0, Scalar(255, 0, 0), 2, 8);
rectangle(pic, box, Scalar(0, 0, 255), 2, 8, 0);
st.insert(className);
}
for_each(st.begin(),st.end(),[&ls](std::string s){ std::cout<<s<<std::endl; ls.push_back(s); });
return ls;
}
4.4.4 简单的图片形态学处理
由于之前写没上传github/gitee,目前只有在objectdetecion.cpp98行开始的/*text detection2*/有部分代码,tesseract的代码在家里另外的测试项目中没上传,等回去记得的话再上传。
4.4.5 重复图像检测的算法实现(PSNR和SSIM)
double ImageBaseTool::getPSNR(const cv::Mat &src, const cv::Mat &dst)
{
cv::Mat s1;
absdiff(src, dst, s1); // |src - dst|
s1.convertTo(s1, CV_32F); // cannot make a square on 8 bits
s1 = s1.mul(s1); // |src - dst|^2
cv::Scalar s = sum(s1); // sum elements per channel
double sse = s.val[0] + s.val[1] + s.val[2]; // sum channels
if( sse <= 1e-10) // for small values return zero
return 0;
else
{
double mse =sse /(double)(src.channels() * src.total());
double psnr = 10.0*log10((255*255)/mse);
return psnr;
}
}
cv::Scalar ImageBaseTool::getMSSIM(const cv::Mat &src, const cv::Mat &dst)
{
const double C1 = 6.5025, C2 = 58.5225;
int d = CV_32F;
cv::Mat I1, I2;
src.convertTo(I1, d);
dst.convertTo(I2, d);
cv::Mat I2_2 = I2.mul(I2);
cv::Mat I1_2 = I1.mul(I1);
cv::Mat I1_I2 = I1.mul(I2);
cv::Mat mu1, mu2;
GaussianBlur(I1, mu1, cv::Size(11, 11), 1.5);
GaussianBlur(I2, mu2, cv::Size(11, 11), 1.5);
cv::Mat mu1_2 = mu1.mul(mu1);
cv::Mat mu2_2 = mu2.mul(mu2);
cv::Mat mu1_mu2 = mu1.mul(mu2);
cv::Mat sigma1_2, sigma2_2, sigma12;
GaussianBlur(I1_2, sigma1_2, cv::Size(11, 11), 1.5);
sigma1_2 -= mu1_2;
GaussianBlur(I2_2, sigma2_2, cv::Size(11, 11), 1.5);
sigma2_2 -= mu2_2;
GaussianBlur(I1_I2, sigma12, cv::Size(11, 11), 1.5);
sigma12 -= mu1_mu2;
cv::Mat t1, t2, t3;
t1 = 2 * mu1_mu2 + C1;
t2 = 2 * sigma12 + C2;
t3 = t1.mul(t2);
t1 = mu1_2 + mu2_2 + C1;
t2 = sigma1_2 + sigma2_2 + C2;
t1 = t1.mul(t2);
cv::Mat ssim_map;
divide(t3, t1, ssim_map);
cv::Scalar mssim = mean( ssim_map );
return mssim;
}
4.4.6 base64编码
static const std::string base64_chars{"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"};
std::string ImageBaseTool::base64_encode(const char *bytes_to_encode, unsigned int in_len)
{
std::string ret;
int i = 0;
int j = 0;
unsigned char char_array_3[3];
unsigned char char_array_4[4];
while (in_len--) {
char_array_3[i++] = *(bytes_to_encode++);
if (i == 3) {
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for(i = 0; (i <4) ; i++)
ret += base64_chars[char_array_4[i]];
i = 0;
}
}
if (i)
{
for(j = i; j < 3; j++)
char_array_3[j] = '\0';
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for (j = 0; (j < i + 1); j++)
ret += base64_chars[char_array_4[j]];
while((i++ < 3))
ret += '=';
}
return ret;
}
std::string ImageBaseTool::base64_decode(const std::string &encoded_string)
{
int in_len = encoded_string.size();
int i = 0;
int j = 0;
int in_ = 0;
unsigned char char_array_4[4], char_array_3[3];
std::string ret;
while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
char_array_4[i++] = encoded_string[in_]; in_++;
if (i ==4) {
for (i = 0; i <4; i++)
char_array_4[i] = base64_chars.find(char_array_4[i]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (i = 0; (i < 3); i++)
ret += char_array_3[i];
i = 0;
}
}
if (i) {
for (j = i; j <4; j++)
char_array_4[j] = 0;
for (j = 0; j <4; j++)
char_array_4[j] = base64_chars.find(char_array_4[j]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
}
return ret;
}
4.4.7 二进制数据和Mat数据之间的转化
cv::Mat ImageBaseTool::binaryToMat(std::string data)
{
cv::Mat pic;
std::vector<uchar> picData;
std::string::iterator beg=data.begin();
while (beg!=data.end()) {
picData.push_back(*beg);
beg++;
}
pic = cv::imdecode(picData,cv::IMREAD_COLOR);
return pic;
}
std::string ImageBaseTool::matToBinary(cv::Mat &src, std::string type)
{
std::string str;
std::vector<unsigned char> buff;
cv::imencode(type, src, buff);
str.resize(buff.size());
memcpy(&str[0], buff.data(), buff.size());
return str;
}
4.4.8 跨域问题
本地跨域问题(即使用的是localhost):https://blog.csdn.net/qq_37960222/article/details/104658105
互联网中跨域问题(使用域名):与使用localhost 的区别在于端口需不需要写
//type: content-type Lenth:message length content:what message you want to send to html
static std::string header(std::string type, std::string Lenth){
std::string header{"HTTP/1.1 200 OK\r\nContent-Type: "+type+"\nCache-Control: no-cache\nContent-Length: "+Lenth+"\n"
"Access-Control-Allow-Origin: https://www.zjyxxn.cn\nAccess-Control-Allow-Credentials: true\n"
"Access-Control-Allow-Methods:GET,POST,PUT,POST\nAccess-Control-Allow-Headers:x-requested-with,content-type\n"};
return header;
}
static std::string sendMsg(std::string type, std::string &content, bool cookie = false, std::string cookieValue=""){
std::string all{};
all += header(type, std::to_string(content.length()));
if (cookie == true) {
all += "Set-Cookie:phone=" + cookieValue + "; domain=www.zjyxxn.cn; path=/\n";//domain=http:://localhost:443;
}
all += "\n";
all += content;
all += "\r\n";
return all;
}
4.4.9 C++与websocket传输图片
在使用websocket的时候,如果nginx使用的443端口,在new的时候要用“wss://域名”,否则用"ws://域名"
<script type="text/javascript">
document.addEventListener('dragover', function(e){
e.stopPropagation();
e.preventDefault(); // 这里是为了取消浏览器自带的拖拽事件
}, false);
document.addEventListener('drop', function(e){
e.stopPropagation();
e.preventDefault();
var file = e.dataTransfer.files[0]; // file就是需要传输的文件
}, false);
window.onload = function WebSocketTest() {
userName()
var ws = new WebSocket("wss://www.zjyxxn.cn/api/");
if ("WebSocket" in window) {
alert("您的浏览器支持 WebSocket!");
// 打开一个 web socket
ws.onopen = function () {
var imgFile = document.getElementById("img11");
// var fr = new FileReader();
imgFile.onchange = function(){
var reader = new FileReader();
var img= this.files[0];
reader.readAsArrayBuffer(img);
alert("数据发送中...");
reader.onload = function loaded(evt) {
var blob = evt.target.result;
//发送json数据,包括用户id,文件名,处理类型
u_phone= document.cookie;
u_phone = u_phone.slice(6);
var json;
json="{\"phone\":\""+u_phone+"\",\"img_name\":\""+img.name+"\",\"type\":\"0\"}"
ws.send(json);
//发送二进制表示的文件
ws.send(blob);
ws.onmessage = function (evt) {
var received_msg = evt.data;
alert(received_msg);
};
ws.onclose = function () {
// 关闭 websocket
alert("连接已关闭...");
};
}
}
};
} else {
// 浏览器不支持 WebSocket
alert("您的浏览器不支持 WebSocket!");
}
};
</script>
结语
这次毕业设计做的相对来说还是比较水的。原本想着要从头开始造轮子,可是到最后,感觉没有做到自己想要的那个结果。主程序中还存在不少的bug还没有解决,如果用其他语言编写主程序,相对会容易很多,而且bug也会少很多(至少在后期编写go的时候,这种反差感很强烈)。
由于程序基本上是半年前完成的,关于在代码中的一些问题记得不是很清楚,博客内容也相对来说比较简单,更详细的还是要去读源代码。