cv mat的shape_pybind11—opencv图像处理(numpy数据交换)

前言

C++ opencv中图像和矩阵的表示采用Mat类,比如imread()读取的结果就是返回一个Mat对象。对于python而言,numpy 通常用于矩阵运算, 矩阵,图像表示为numpy.ndarray类。

因此,想要将python numpy.ndarray的数据传递到C++ opencv Mat, 或者C++ Mat将数据返回到python numpy.ndarray, 核心问题——如何绑定Mat

C++

main.cpp

#include

#include

#include

#include

#include

#include

#include"mat_warper.h"

namespace py = pybind11;

py::array_t test_rgb_to_gray(py::array_t& input) {

cv::Mat img_rgb = numpy_uint8_3c_to_cv_mat(input);

cv::Mat dst;

cv::cvtColor(img_rgb, dst, cv::COLOR_RGB2GRAY);

return cv_mat_uint8_1c_to_numpy(dst);

}

py::array_t test_gray_canny(py::array_t& input) {

cv::Mat src = numpy_uint8_1c_to_cv_mat(input);

cv::Mat dst;

cv::Canny(src, dst, 30, 60);

return cv_mat_uint8_1c_to_numpy(dst);

}

/*

@return Python list

*/

py::list test_pyramid_image(py::array_t& input) {

cv::Mat src = numpy_uint8_1c_to_cv_mat(input);

std::vector<:mat> dst;

cv::buildPyramid(src, dst, 4);

py::list out;

for (int i = 0; i < dst.size(); i++)

{

out.append<:array_t char>>(cv_mat_uint8_1c_to_numpy(dst.at(i)));

}

return out;

}

PYBIND11_MODULE(cv_demo1, m) {

m.doc() = "Simple opencv demo";

m.def("test_rgb_to_gray", &test_rgb_to_gray);

m.def("test_gray_canny", &test_gray_canny);

m.def("test_pyramid_image", &test_pyramid_image);

}

mat_warper.h

#ifndef MAT_WARPER_H_

#include

#include

#include

namespace py = pybind11;

cv::Mat numpy_uint8_1c_to_cv_mat(py::array_t& input);

cv::Mat numpy_uint8_3c_to_cv_mat(py::array_t& input);

py::array_t cv_mat_uint8_1c_to_numpy(cv::Mat & input);

py::array_t cv_mat_uint8_3c_to_numpy(cv::Mat & input);

#endif // !MAT_WARPER_H_

mat_warper.cpp

#include"mat_warper.h"

#include

/*

Python->C++ Mat

*/

cv::Mat numpy_uint8_1c_to_cv_mat(py::array_t& input) {

if (input.ndim() != 2)

throw std::runtime_error("1-channel image must be 2 dims ");

py::buffer_info buf = input.request();

cv::Mat mat(buf.shape[0], buf.shape[1], CV_8UC1, (unsigned char*)buf.ptr);

return mat;

}

cv::Mat numpy_uint8_3c_to_cv_mat(py::array_t& input) {

if (input.ndim() != 3)

throw std::runtime_error("3-channel image must be 3 dims ");

py::buffer_info buf = input.request();

cv::Mat mat(buf.shape[0], buf.shape[1], CV_8UC3, (unsigned char*)buf.ptr);

return mat;

}

/*

C++ Mat ->numpy

*/

py::array_t cv_mat_uint8_1c_to_numpy(cv::Mat& input) {

py::array_t dst = py::array_t({ input.rows,input.cols }, input.data);

return dst;

}

py::array_t cv_mat_uint8_3c_to_numpy(cv::Mat& input) {

py::array_t dst = py::array_t({ input.rows,input.cols,3}, input.data);

return dst;

}

//PYBIND11_MODULE(cv_mat_warper, m) {

//

// m.doc() = "OpenCV Mat -> Numpy.ndarray warper";

//

// m.def("numpy_uint8_1c_to_cv_mat", &numpy_uint8_1c_to_cv_mat);

// m.def("numpy_uint8_1c_to_cv_mat", &numpy_uint8_1c_to_cv_mat);

//

//

//}

python中测试

python代码

import cv2

import matplotlib.pyplot as plt

import demo11.cv_demo1 as cv_demo1

import numpy as np

image_rgb = cv2.imread('F:\\lena\\lena_rgb.jpg', cv2.IMREAD_UNCHANGED)

image_gray = cv2.imread('F:\\lena\\lena_gray.jpg', cv2.IMREAD_UNCHANGED)

var1 = cv_demo1.test_rgb_to_gray(image_rgb)

print(var1.shape)

plt.figure('rgb-gray')

plt.imshow(var1, cmap=plt.gray())

var2 = cv_demo1.test_gray_canny(image_gray)

plt.figure('canny')

plt.imshow(var2, cmap=plt.gray())

var3 = cv_demo1.test_pyramid_image(image_gray)

var3 = var3[1:]

plt.figure('pyramid_demo')

for i, image in enumerate(var3, 1):

plt.subplot(2, 2, i)

plt.axis('off')

plt.imshow(image, cmap=plt.gray())

plt.show()

测试图像:

RGB图像

be16847b0b74

rgb.jpg

GRAY灰度图像

be16847b0b74

lena_gray.jpg

结果

RGB转GRAY

be16847b0b74

image.png

灰度图像Canny边缘检测

be16847b0b74

image.png

图像金字塔

be16847b0b74

image.png

Demo2

C++

#include

#include

#include

#include

#include

#include

#include "ndarray_converter.h"

namespace py = pybind11;

void show_image(cv::Mat image)

{

cv::imshow("image_from_Cpp", image);

cv::waitKey(0);

}

cv::Mat read_image(std::string image_name)

{

cv::Mat image = cv::imread(image_name, CV_LOAD_IMAGE_COLOR);

return image;

}

cv::Mat passthru(cv::Mat image)

{

return image;

}

cv::Mat cloneimg(cv::Mat image)

{

return image.clone();

}

cv::Mat gaussian_blur_demo(cv::Mat& image) {

cv::Mat dst;

cv::GaussianBlur(image, dst, cv::Size(7, 7),1.5,1.5);

return dst;

}

cv::Mat image_filter(cv::Mat& image, cv::Mat& kernel){

cv::Mat dst;

cv::filter2D(image, dst, -1, kernel);

return dst;

}

PYBIND11_MODULE(example,m)

{

NDArrayConverter::init_numpy();

m.def("read_image", &read_image, "A function that read an image",

py::arg("image"));

m.def("show_image", &show_image, "A function that show an image",

py::arg("image"));

m.def("passthru", &passthru, "Passthru function", py::arg("image"));

m.def("clone", &cloneimg, "Clone function", py::arg("image"));

m.def("gaussian_blur_demo", &gaussian_blur_demo);

m.def("image_filter", &image_filter);

}

convert_.h

# ifndef __NDARRAY_CONVERTER_H__

# define __NDARRAY_CONVERTER_H__

#include

#include

class NDArrayConverter {

public:

// must call this first, or the other routines don't work!

static bool init_numpy();

static bool toMat(PyObject* o, cv::Mat &m);

static PyObject* toNDArray(const cv::Mat& mat);

};

//

// Define the type converter

//

#include

namespace pybind11 { namespace detail {

template <> struct type_caster<:mat> {

public:

PYBIND11_TYPE_CASTER(cv::Mat, _("numpy.ndarray"));

bool load(handle src, bool) {

return NDArrayConverter::toMat(src.ptr(), value);

}

static handle cast(const cv::Mat &m, return_value_policy, handle defval) {

return handle(NDArrayConverter::toNDArray(m));

}

};

}} // namespace pybind11::detail

# endif

cpp

// borrowed in spirit from https://github.com/yati-sagade/opencv-ndarray-conversion

// MIT License

#include "ndarray_converter.h"

#define NPY_NO_DEPRECATED_API NPY_1_15_API_VERSION

#include

#if PY_VERSION_HEX >= 0x03000000

#define PyInt_Check PyLong_Check

#define PyInt_AsLong PyLong_AsLong

#endif

struct Tmp {

const char * name;

Tmp(const char * name ) : name(name) {}

};

Tmp info("return value");

bool NDArrayConverter::init_numpy() {

// this has to be in this file, since PyArray_API is defined as static

import_array1(false);

return true;

}

/*

* The following conversion functions are taken/adapted from OpenCV's cv2.cpp file

* inside modules/python/src2 folder (OpenCV 3.1.0)

*/

static PyObject* opencv_error = 0;

static int failmsg(const char *fmt, ...)

{

char str[1000];

va_list ap;

va_start(ap, fmt);

vsnprintf(str, sizeof(str), fmt, ap);

va_end(ap);

PyErr_SetString(PyExc_TypeError, str);

return 0;

}

class PyAllowThreads

{

public:

PyAllowThreads() : _state(PyEval_SaveThread()) {}

~PyAllowThreads()

{

PyEval_RestoreThread(_state);

}

private:

PyThreadState* _state;

};

class PyEnsureGIL

{

public:

PyEnsureGIL() : _state(PyGILState_Ensure()) {}

~PyEnsureGIL()

{

PyGILState_Release(_state);

}

private:

PyGILState_STATE _state;

};

#define ERRWRAP2(expr) \

try \

{ \

PyAllowThreads allowThreads; \

expr; \

} \

catch (const cv::Exception &e) \

{ \

PyErr_SetString(opencv_error, e.what()); \

return 0; \

}

using namespace cv;

class NumpyAllocator : public MatAllocator

{

public:

NumpyAllocator() { stdAllocator = Mat::getStdAllocator(); }

~NumpyAllocator() {}

UMatData* allocate(PyObject* o, int dims, const int* sizes, int type, size_t* step) const

{

UMatData* u = new UMatData(this);

u->data = u->origdata = (uchar*)PyArray_DATA((PyArrayObject*) o);

npy_intp* _strides = PyArray_STRIDES((PyArrayObject*) o);

for( int i = 0; i < dims - 1; i++ )

step[i] = (size_t)_strides[i];

step[dims-1] = CV_ELEM_SIZE(type);

u->size = sizes[0]*step[0];

u->userdata = o;

return u;

}

UMatData* allocate(int dims0, const int* sizes, int type, void* data, size_t* step, int flags, UMatUsageFlags usageFlags) const

{

if( data != 0 )

{

CV_Error(Error::StsAssert, "The data should normally be NULL!");

// probably this is safe to do in such extreme case

return stdAllocator->allocate(dims0, sizes, type, data, step, flags, usageFlags);

}

PyEnsureGIL gil;

int depth = CV_MAT_DEPTH(type);

int cn = CV_MAT_CN(type);

const int f = (int)(sizeof(size_t)/8);

int typenum = depth == CV_8U ? NPY_UBYTE : depth == CV_8S ? NPY_BYTE :

depth == CV_16U ? NPY_USHORT : depth == CV_16S ? NPY_SHORT :

depth == CV_32S ? NPY_INT : depth == CV_32F ? NPY_FLOAT :

depth == CV_64F ? NPY_DOUBLE : f*NPY_ULONGLONG + (f^1)*NPY_UINT;

int i, dims = dims0;

cv::AutoBuffer _sizes(dims + 1);

for( i = 0; i < dims; i++ )

_sizes[i] = sizes[i];

if( cn > 1 )

_sizes[dims++] = cn;

PyObject* o = PyArray_SimpleNew(dims, _sizes, typenum);

if(!o)

CV_Error_(Error::StsError, ("The numpy array of typenum=%d, ndims=%d can not be created", typenum, dims));

return allocate(o, dims0, sizes, type, step);

}

bool allocate(UMatData* u, int accessFlags, UMatUsageFlags usageFlags) const

{

return stdAllocator->allocate(u, accessFlags, usageFlags);

}

void deallocate(UMatData* u) const

{

if(!u)

return;

PyEnsureGIL gil;

CV_Assert(u->urefcount >= 0);

CV_Assert(u->refcount >= 0);

if(u->refcount == 0)

{

PyObject* o = (PyObject*)u->userdata;

Py_XDECREF(o);

delete u;

}

}

const MatAllocator* stdAllocator;

};

NumpyAllocator g_numpyAllocator;

bool NDArrayConverter::toMat(PyObject *o, Mat &m)

{

bool allowND = true;

if(!o || o == Py_None)

{

if( !m.data )

m.allocator = &g_numpyAllocator;

return true;

}

if( PyInt_Check(o) )

{

double v[] = {static_cast(PyInt_AsLong((PyObject*)o)), 0., 0., 0.};

m = Mat(4, 1, CV_64F, v).clone();

return true;

}

if( PyFloat_Check(o) )

{

double v[] = {PyFloat_AsDouble((PyObject*)o), 0., 0., 0.};

m = Mat(4, 1, CV_64F, v).clone();

return true;

}

if( PyTuple_Check(o) )

{

int i, sz = (int)PyTuple_Size((PyObject*)o);

m = Mat(sz, 1, CV_64F);

for( i = 0; i < sz; i++ )

{

PyObject* oi = PyTuple_GET_ITEM(o, i);

if( PyInt_Check(oi) )

m.at(i) = (double)PyInt_AsLong(oi);

else if( PyFloat_Check(oi) )

m.at(i) = (double)PyFloat_AsDouble(oi);

else

{

failmsg("%s is not a numerical tuple", info.name);

m.release();

return false;

}

}

return true;

}

if( !PyArray_Check(o) )

{

failmsg("%s is not a numpy array, neither a scalar", info.name);

return false;

}

PyArrayObject* oarr = (PyArrayObject*) o;

bool needcopy = false, needcast = false;

int typenum = PyArray_TYPE(oarr), new_typenum = typenum;

int type = typenum == NPY_UBYTE ? CV_8U :

typenum == NPY_BYTE ? CV_8S :

typenum == NPY_USHORT ? CV_16U :

typenum == NPY_SHORT ? CV_16S :

typenum == NPY_INT ? CV_32S :

typenum == NPY_INT32 ? CV_32S :

typenum == NPY_FLOAT ? CV_32F :

typenum == NPY_DOUBLE ? CV_64F : -1;

if( type < 0 )

{

if( typenum == NPY_INT64 || typenum == NPY_UINT64 || typenum == NPY_LONG )

{

needcopy = needcast = true;

new_typenum = NPY_INT;

type = CV_32S;

}

else

{

failmsg("%s data type = %d is not supported", info.name, typenum);

return false;

}

}

#ifndef CV_MAX_DIM

const int CV_MAX_DIM = 32;

#endif

int ndims = PyArray_NDIM(oarr);

if(ndims >= CV_MAX_DIM)

{

failmsg("%s dimensionality (=%d) is too high", info.name, ndims);

return false;

}

int size[CV_MAX_DIM+1];

size_t step[CV_MAX_DIM+1];

size_t elemsize = CV_ELEM_SIZE1(type);

const npy_intp* _sizes = PyArray_DIMS(oarr);

const npy_intp* _strides = PyArray_STRIDES(oarr);

bool ismultichannel = ndims == 3 && _sizes[2] <= CV_CN_MAX;

for( int i = ndims-1; i >= 0 && !needcopy; i-- )

{

// these checks handle cases of

// a) multi-dimensional (ndims > 2) arrays, as well as simpler 1- and 2-dimensional cases

// b) transposed arrays, where _strides[] elements go in non-descending order

// c) flipped arrays, where some of _strides[] elements are negative

// the _sizes[i] > 1 is needed to avoid spurious copies when NPY_RELAXED_STRIDES is set

if( (i == ndims-1 && _sizes[i] > 1 && (size_t)_strides[i] != elemsize) ||

(i < ndims-1 && _sizes[i] > 1 && _strides[i] < _strides[i+1]) )

needcopy = true;

}

if( ismultichannel && _strides[1] != (npy_intp)elemsize*_sizes[2] )

needcopy = true;

if (needcopy)

{

//if (info.outputarg)

//{

// failmsg("Layout of the output array %s is incompatible with cv::Mat (step[ndims-1] != elemsize or step[1] != elemsize*nchannels)", info.name);

// return false;

//}

if( needcast ) {

o = PyArray_Cast(oarr, new_typenum);

oarr = (PyArrayObject*) o;

}

else {

oarr = PyArray_GETCONTIGUOUS(oarr);

o = (PyObject*) oarr;

}

_strides = PyArray_STRIDES(oarr);

}

// Normalize strides in case NPY_RELAXED_STRIDES is set

size_t default_step = elemsize;

for ( int i = ndims - 1; i >= 0; --i )

{

size[i] = (int)_sizes[i];

if ( size[i] > 1 )

{

step[i] = (size_t)_strides[i];

default_step = step[i] * size[i];

}

else

{

step[i] = default_step;

default_step *= size[i];

}

}

// handle degenerate case

if( ndims == 0) {

size[ndims] = 1;

step[ndims] = elemsize;

ndims++;

}

if( ismultichannel )

{

ndims--;

type |= CV_MAKETYPE(0, size[2]);

}

if( ndims > 2 && !allowND )

{

failmsg("%s has more than 2 dimensions", info.name);

return false;

}

m = Mat(ndims, size, type, PyArray_DATA(oarr), step);

m.u = g_numpyAllocator.allocate(o, ndims, size, type, step);

m.addref();

if( !needcopy )

{

Py_INCREF(o);

}

m.allocator = &g_numpyAllocator;

return true;

}

PyObject* NDArrayConverter::toNDArray(const cv::Mat& m)

{

if( !m.data )

Py_RETURN_NONE;

Mat temp, *p = (Mat*)&m;

if(!p->u || p->allocator != &g_numpyAllocator)

{

temp.allocator = &g_numpyAllocator;

ERRWRAP2(m.copyTo(temp));

p = &temp;

}

PyObject* o = (PyObject*)p->u->userdata;

Py_INCREF(o);

return o;

}

Gaussian模糊

be16847b0b74

image.png

Sobel算子

be16847b0b74

image.png

be16847b0b74

image.png

直线检测

be16847b0b74

image.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/454962.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

H.264算法的优化策略

文章来源&#xff1a; http://www.tichinese.com/Article/Video/200909/2150.html 编辑&#xff1a;小乙哥 1 代码优化的主要方法 通过代码移植能够获得在DSP上初步运行的代码&#xff0c;但是它由于没有考虑到DSP自身的硬件特点&#xff0c;不适合DSP强大的并行处理能力&#…

吃饭、睡觉、打星星之“打星星”!

大家见过这样的星星么&#xff1f; 你想要多少就可以多少的星星&#xff01;&#xff01;&#xff01; 下面我们就来用奇妙的JavaScript来实现 首先我们要引入一个输入包 let readline require("readline-sync");然后再让客户输入数字&#xff0c;并将其存放起来con…

使用iconv-lite解决node当中不支持GBK编码的问题

1、Node环境当中不支持GBK编码 node.js当中的Buffer对象支持的编码格式的种类有限&#xff0c;大概有ascii、utf8、utf16le、ucs2、base64、binary、hex。不支持GBK的编码形式。对于windows系统来说&#xff0c;由于历史原因&#xff0c;许多文件默认的编码格式均为GBK。 比如我…

mysql 集群架构_mysql企业常用集群架构

转自 https://blog.csdn.net/kingice1014/article/details/760200611、mysql企业常用集群架构在中小型互联网的企业中。mysql的集群一般就是上图的架构。WEB节点读取数据库的时候读取dbproxy服务器。dbproxy服务器通过对SQL语句的判断来进行数据库的读写分离。读请求负载到从库…

关于Vue2.0,Express实现的简单跨域

npm install express -g 通过npm全局安装express&#xff0c;之后可以通过 express --version 来查看express版本 express server 通过express server生成server项目文件 npm install 安装server的项目依赖 可以通过执行server下的bin\www文件可以开启服务 在www文件我们可以默…

mysql返回yyyy mm dd_怎么把取出mysql数据库中的yyyy-MM-dd日期转成yyyy年MM月dd日格式...

您好&#xff0c;通过两个个步骤可以完成转换&#xff1a;第一步&#xff1a;日期处理可以在模板数据集中通过sql语句转换&#xff0c;转换方式方式如下&#xff1a;SELECT DATE_FORMAT(NOW(),%Y) YEAR输出结果&#xff1a;2018SELECT DATE_F…

感动一生的几句话

为什么80%的码农都做不了架构师&#xff1f;>>> 很多东西就掌握在我们手中&#xff1a; 比如快乐&#xff0c;你不快乐&#xff0c;谁会同情你的悲伤&#xff1b; 比如坚强&#xff0c;你不坚强&#xff0c;谁会怜悯你的懦弱&#xff1b; 比如努力&#xff0c;你不…

patator mysql 字典_利用patator进行子域名爆破

前言:原来朋友写的一个子域名爆破工具挺好用,这前几天API接口关了.痛苦万分.自己也写了一个类似的但是不咋稳定.特地google找了下 找到一款patator.效果和速度还是不错的。knock的速度真心受不了啊patator是由Python写的 不用安装下载即可.下载地址&#xff1a;http://code.goo…

[bzoj1059]矩阵游戏

虽然是一道水难题&#xff0c;但是我这种蒟蒻还是要讲一讲的。 Description 小Q是一个非常聪明的孩子&#xff0c;除了国际象棋&#xff0c;他还很喜欢玩一个电脑益智游戏——矩阵游戏。矩阵游戏在一个N*N黑白方阵进行&#xff08;如同国际象棋一般&#xff0c;只是颜色是随意的…

golang mysql 插入_Mysql学习(一)添加一个新的用户并用golang操作Mysql

Mysql添加一个新的用户并赋予权限添加一个自己的用户到mysql首先我们需要先用root用户登录mysql&#xff0c;但是刚安装完没有密码&#xff0c;我们先跳过密码ailumiyanaailumiyana:~/Git_Project/Go_Test$ sudo mysqld_safe --skip-grant-tables2019-01-07T01:35:51.559420Z m…

云计算构建基石之Hyper-V:虚拟机管理

本文讲的是云计算构建基石之Hyper-V:虚拟机管理,作为云计算的重要基石&#xff0c;虚拟化技术的好坏起着关键作用。Hyper-V作为微软重要的虚拟化解决技术&#xff0c;在微软云计算构建解决方案中&#xff0c;更是关键至关键&#xff0c;基础之基础。在本系列文章中&#xff0c;…

3GP文件格式分析

1. 概述现在很多智能手机都支持多媒体功能&#xff0c;特别是音频和视频播放功能&#xff0c;而3GP文件格式是手机端普遍支持的视频文件格式。目前很多手机都支持h263视频编码格式的视频文件播放&#xff0c;还有些手机支持h264。音频文件格式普遍支持amr&#xff0c;有些手…

mysql group concat_MySQL 的 GROUP_CONCAT 函数详解

GROUP_CONCAT(expr) 函数会从 expr 中连接所有非 NULL 的字符串。如果没有非 NULL 的字符串&#xff0c;那么它就会返回 NULL。语法如下&#xff1a;GROUP_CONCAT 语法规则它在递归查询中用的比较多&#xff0c;但要使用好它并不容易。所以让我们一起来看看吧&#xff1a;假设有…

光荣之路测试开发面试linux考题之四:性能命令

Hi,大家好我是tom,I am back.今天要给大家讲讲linux系统一些性能相关命令。 1.fdisk 磁盘管理 是一个强大的危险命令&#xff0c;所有涉及磁盘的操作都由该命令完成&#xff0c;包括&#xff1a;新增磁盘、增删改磁盘分区等。 1.fdisk -l 查看磁盘分区情况 Disk /dev/sda: 27.8…

mac安装完mysql后关机特别慢_mysql-Mac终端下遇到的问题总结

为了方便启动mysql服务&#xff0c;修改/etc/.bash_profile文件&#xff0c;如下alias mysql"/usr/local/mysql/bin/mysql"alias mysqladmin"/usr/local/mysql/bin/mysqladmin"或者alias mysqlstart"sudo /usr/local/mysql/support-files/mysql.serve…

sending data mysql slow Mysql查询非常慢的可能原因

1.用explain看看mysql的执行情况,可以得知,task_id扫描了近20万条数据,而且这个task_id不是索引 2.为这个task_id所在的表,将此字段添加索引后,查询就变得很快了 转载于:https://www.cnblogs.com/Skrillex/p/7365590.html

打包上架

昨天写的打包上架&#xff0c;分组到了文章&#xff0c;发现不便查看贴链接到这里&#xff1a; http://www.cnblogs.com/ITCoderW/articles/7597969.html 最近一个版本的审核的过程 当我们上传到APP Store一个新的版本后 登录ITunes Connect就可以看到相应的版本的审核的状态 粗…

架构设计--仅是软件开发之第二大影响力?!

SDWest2006&#xff08;译注1&#xff09;对我来说是个有趣的大会。我除了星期三之外&#xff08;当时我正飞往费城参加一个客户会议 因此错过了Jolt颁奖部分&#xff09;每天都在演讲。我也参加了一些谈话和会议&#xff1b;其中最引人关注的是Mike Cohn的计划与估算的谈话。…

WiFi密码分享有妙招 不必口头相传

移动互联网的迅速崛起&#xff0c;使得我们可以方便的使用手持移动设备进行上网。尤其是在家庭中&#xff0c;使用智能手机、平板电脑、笔记本电脑等移动设备进行上网和娱乐已经成为主流&#xff0c;台式机上网正日渐式微。在家中时&#xff0c;我们通过无线路由器提供的WiFi网…

javaweb(二十一)——JavaWeb的两种开发模式

一、JSPJavaBean开发模式 1.1、jspjavabean开发模式架构 jspjavabean开发模式的架构图如下图(图1-1)所示 图1-1 在jspjavabean架构中&#xff0c;JSP负责控制逻辑、表现逻辑、业务对象&#xff08;javabean&#xff09;的调用。 JSPJavaBean模式适合开发业务逻辑不太复杂的web应…