精华内容
下载资源
问答
  • 有不少朋友跟我反应,都希望我可以写篇关于View的文章,讲讲View的工作原理以及自定义View的方法。没错,承诺过的文章我是一定要兑现的,而且在View这个话题上我还准备多写几篇,尽量能将这个知识点讲得透彻一些...

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/12921889


    有段时间没写博客了,感觉都有些生疏了呢。最近繁忙的工作终于告一段落,又有时间写文章了,接下来还会继续坚持每一周篇的节奏。


    有不少朋友跟我反应,都希望我可以写一篇关于View的文章,讲一讲View的工作原理以及自定义View的方法。没错,承诺过的文章我是一定要兑现的,而且在View这个话题上我还准备多写几篇,尽量能将这个知识点讲得透彻一些。那么今天就从LayoutInflater开始讲起吧。


    相信接触Android久一点的朋友对于LayoutInflater一定不会陌生,都会知道它主要是用于加载布局的。而刚接触Android的朋友可能对LayoutInflater不怎么熟悉,因为加载布局的任务通常都是在Activity中调用setContentView()方法来完成的。其实setContentView()方法的内部也是使用LayoutInflater来加载布局的,只不过这部分源码是internal的,不太容易查看到。那么今天我们就来把LayoutInflater的工作流程仔细地剖析一遍,也许还能解决掉某些困扰你心头多年的疑惑。


    先来看一下LayoutInflater的基本用法吧,它的用法非常简单,首先需要获取到LayoutInflater的实例,有两种方法可以获取到,第一种写法如下:

    LayoutInflater layoutInflater = LayoutInflater.from(context);
    当然,还有另外一种写法也可以完成同样的效果:
    LayoutInflater layoutInflater = (LayoutInflater) context
    		.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    其实第一种就是第二种的简单写法,只是Android给我们做了一下封装而已。得到了LayoutInflater的实例之后就可以调用它的inflate()方法来加载布局了,如下所示:
    layoutInflater.inflate(resourceId, root);

    inflate()方法一般接收两个参数,第一个参数就是要加载的布局id,第二个参数是指给该布局的外部再嵌套一层父布局,如果不需要就直接传null。这样就成功成功创建了一个布局的实例,之后再将它添加到指定的位置就可以显示出来了。


    下面我们就通过一个非常简单的小例子,来更加直观地看一下LayoutInflater的用法。比如说当前有一个项目,其中MainActivity对应的布局文件叫做activity_main.xml,代码如下所示:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/main_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    
    </LinearLayout>
    这个布局文件的内容非常简单,只有一个空的LinearLayout,里面什么控件都没有,因此界面上应该不会显示任何东西。


    那么接下来我们再定义一个布局文件,给它取名为button_layout.xml,代码如下所示:

    <Button xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button" >
    
    </Button>
    这个布局文件也非常简单,只有一个Button按钮而已。现在我们要想办法,如何通过LayoutInflater来将button_layout这个布局添加到主布局文件的LinearLayout中。根据刚刚介绍的用法,修改MainActivity中的代码,如下所示:
    public class MainActivity extends Activity {
    
    	private LinearLayout mainLayout;
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		mainLayout = (LinearLayout) findViewById(R.id.main_layout);
    		LayoutInflater layoutInflater = LayoutInflater.from(this);
    		View buttonLayout = layoutInflater.inflate(R.layout.button_layout, null);
    		mainLayout.addView(buttonLayout);
    	}
    
    }

    可以看到,这里先是获取到了LayoutInflater的实例,然后调用它的inflate()方法来加载button_layout这个布局,最后调用LinearLayout的addView()方法将它添加到LinearLayout中。


    现在可以运行一下程序,结果如下图所示:



    Button在界面上显示出来了!说明我们确实是借助LayoutInflater成功将button_layout这个布局添加到LinearLayout中了。LayoutInflater技术广泛应用于需要动态添加View的时候,比如在ScrollView和ListView中,经常都可以看到LayoutInflater的身影。


    当然,仅仅只是介绍了如何使用LayoutInflater显然是远远无法满足大家的求知欲的,知其然也要知其所以然,接下来我们就从源码的角度上看一看LayoutInflater到底是如何工作的。


    不管你是使用的哪个inflate()方法的重载,最终都会辗转调用到LayoutInflater的如下代码中:

    public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            mConstructorArgs[0] = mContext;
            View result = root;
            try {
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                }
                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                }
                final String name = parser.getName();
                if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException("merge can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                    rInflate(parser, root, attrs);
                } else {
                    View temp = createViewFromTag(name, attrs);
                    ViewGroup.LayoutParams params = null;
                    if (root != null) {
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            temp.setLayoutParams(params);
                        }
                    }
                    rInflate(parser, temp, attrs);
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }
            } catch (XmlPullParserException e) {
                InflateException ex = new InflateException(e.getMessage());
                ex.initCause(e);
                throw ex;
            } catch (IOException e) {
                InflateException ex = new InflateException(
                        parser.getPositionDescription()
                        + ": " + e.getMessage());
                ex.initCause(e);
                throw ex;
            }
            return result;
        }
    }
    从这里我们就可以清楚地看出,LayoutInflater其实就是使用Android提供的pull解析方式来解析布局文件的。不熟悉pull解析方式的朋友可以网上搜一下,教程很多,我就不细讲了,这里我们注意看下第23行,调用了createViewFromTag()这个方法,并把节点名和参数传了进去。看到这个方法名,我们就应该能猜到,它是用于根据节点名来创建View对象的。确实如此,在createViewFromTag()方法的内部又会去调用createView()方法,然后使用反射的方式创建出View的实例并返回。


    当然,这里只是创建出了一个根布局的实例而已,接下来会在第31行调用rInflate()方法来循环遍历这个根布局下的子元素,代码如下所示:

    private void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs)
            throws XmlPullParserException, IOException {
        final int depth = parser.getDepth();
        int type;
        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
            if (type != XmlPullParser.START_TAG) {
                continue;
            }
            final String name = parser.getName();
            if (TAG_REQUEST_FOCUS.equals(name)) {
                parseRequestFocus(parser, parent);
            } else if (TAG_INCLUDE.equals(name)) {
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                throw new InflateException("<merge /> must be the root element");
            } else {
                final View view = createViewFromTag(name, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflate(parser, view, attrs);
                viewGroup.addView(view, params);
            }
        }
        parent.onFinishInflate();
    }

    可以看到,在第21行同样是createViewFromTag()方法来创建View的实例,然后还会在第24行递归调用rInflate()方法来查找这个View下的子元素,每次递归完成后则将这个View添加到父布局当中。


    这样的话,把整个布局文件都解析完成后就形成了一个完整的DOM结构,最终会把最顶层的根布局返回,至此inflate()过程全部结束。


    比较细心的朋友也许会注意到,inflate()方法还有个接收三个参数的方法重载,结构如下:

    inflate(int resource, ViewGroup root, boolean attachToRoot)

    那么这第三个参数attachToRoot又是什么意思呢?其实如果你仔细去阅读上面的源码应该可以自己分析出答案,这里我先将结论说一下吧,感兴趣的朋友可以再阅读一下源码,校验我的结论是否正确。


    1. 如果root为null,attachToRoot将失去作用,设置任何值都没有意义。

    2. 如果root不为null,attachToRoot设为true,则会给加载的布局文件的指定一个父布局,即root。

    3. 如果root不为null,attachToRoot设为false,则会将布局文件最外层的所有layout属性进行设置,当该view被添加到父view当中时,这些layout属性会自动生效。

    4. 在不设置attachToRoot参数的情况下,如果root不为null,attachToRoot参数默认为true。


    好了,现在对LayoutInflater的工作原理和流程也搞清楚了,你该满足了吧。额。。。。还嫌这个例子中的按钮看起来有点小,想要调大一些?那简单的呀,修改button_layout.xml中的代码,如下所示:

    <Button xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="300dp"
        android:layout_height="80dp"
        android:text="Button" >
    
    </Button>

    这里我们将按钮的宽度改成300dp,高度改成80dp,这样够大了吧?现在重新运行一下程序来观察效果。咦?怎么按钮还是原来的大小,没有任何变化!是不是按钮仍然不够大,再改大一点呢?还是没有用!


    其实这里不管你将Button的layout_width和layout_height的值修改成多少,都不会有任何效果的,因为这两个值现在已经完全失去了作用。平时我们经常使用layout_width和layout_height来设置View的大小,并且一直都能正常工作,就好像这两个属性确实是用于设置View的大小的。而实际上则不然,它们其实是用于设置View在布局中的大小的,也就是说,首先View必须存在于一个布局中,之后如果将layout_width设置成match_parent表示让View的宽度填充满布局,如果设置成wrap_content表示让View的宽度刚好可以包含其内容,如果设置成具体的数值则View的宽度会变成相应的数值。这也是为什么这两个属性叫作layout_width和layout_height,而不是width和height。


    再来看一下我们的button_layout.xml吧,很明显Button这个控件目前不存在于任何布局当中,所以layout_width和layout_height这两个属性理所当然没有任何作用。那么怎样修改才能让按钮的大小改变呢?解决方法其实有很多种,最简单的方式就是在Button的外面再嵌套一层布局,如下所示:

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    
        <Button
            android:layout_width="300dp"
            android:layout_height="80dp"
            android:text="Button" >
        </Button>
    
    </RelativeLayout>

    可以看到,这里我们又加入了一个RelativeLayout,此时的Button存在与RelativeLayout之中,layout_width和layout_height属性也就有作用了。当然,处于最外层的RelativeLayout,它的layout_width和layout_height则会失去作用。现在重新运行一下程序,结果如下图所示:




    OK!按钮的终于可以变大了,这下总算是满足大家的要求了吧。


    看到这里,也许有些朋友心中会有一个巨大的疑惑。不对呀!平时在Activity中指定布局文件的时候,最外层的那个布局是可以指定大小的呀,layout_width和layout_height都是有作用的。确实,这主要是因为,在setContentView()方法中,Android会自动在布局文件的最外层再嵌套一个FrameLayout,所以layout_width和layout_height属性才会有效果。那么我们来证实一下吧,修改MainActivity中的代码,如下所示:

    public class MainActivity extends Activity {
    
    	private LinearLayout mainLayout;
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		mainLayout = (LinearLayout) findViewById(R.id.main_layout);
    		ViewParent viewParent = mainLayout.getParent();
    		Log.d("TAG", "the parent of mainLayout is " + viewParent);
    	}
    
    }

    可以看到,这里通过findViewById()方法,拿到了activity_main布局中最外层的LinearLayout对象,然后调用它的getParent()方法获取它的父布局,再通过Log打印出来。现在重新运行一下程序,结果如下图所示:


     


    非常正确!LinearLayout的父布局确实是一个FrameLayout,而这个FrameLayout就是由系统自动帮我们添加上的。


    说到这里,虽然setContentView()方法大家都会用,但实际上Android界面显示的原理要比我们所看到的东西复杂得多。任何一个Activity中显示的界面其实主要都由两部分组成,标题栏和内容布局。标题栏就是在很多界面顶部显示的那部分内容,比如刚刚我们的那个例子当中就有标题栏,可以在代码中控制让它是否显示。而内容布局就是一个FrameLayout,这个布局的id叫作content,我们调用setContentView()方法时所传入的布局其实就是放到这个FrameLayout中的,这也是为什么这个方法名叫作setContentView(),而不是叫setView()。


    最后再附上一张Activity窗口的组成图吧,以便于大家更加直观地理解:



    好了,今天就讲到这里了,支持的、吐槽的、有疑问的、以及打酱油的路过朋友尽管留言吧 ^v^ 感兴趣的朋友可以继续阅读 Android视图绘制流程完全解析,带你一步步深入了解View(二) 。


    关注我的技术公众号,每天都有优质技术文章推送。关注我的娱乐公众号,工作、学习累了的时候放松一下自己。

    微信扫一扫下方二维码即可关注:

            

    展开全文
  • 一步步做一个数字手势识别APP

    万次阅读 多人点赞 2018-07-21 21:26:03
    一步步做一个数字手势识别APP   这篇博客主要基于我做的一个数字手势识别APP,具体分享下如何一步步训练一个卷积神经网络模型(CNN)模型,然后把模型集成到Android Studio中,开发一个数字手势识别APP。整个...

    一步步做一个数字手势识别APP

      这篇博客主要基于我做的一个数字手势识别APP,具体分享下如何一步步训练一个卷积神经网络模型(CNN)模型,然后把模型集成到Android Studio中,开发一个数字手势识别APP。整个project的源码已经开源在github上,github地址:Chinese-number-gestures-recognition,欢迎star,哈哈。先说下这个数字手势识别APP的功能:能够识别做出的 0,1,2,3,4,5,6,7,8,9,10这11个手势。

    开发环境:TensorFlow-gpu1.8.0、NVIDIA GTX1070、keras2.1.6、Android Studio3.1.2、OpenCV3.4。


    一、数据集的收集

      关于数据集,如果能找到现成的数据集那更好。但更多时候要自己去收集,我这里就是自己收集,这个真的要感谢我的好基友:蒋雯、宋俞璋、彭仲俊、张蒙、袁程、邢守一、郑超,当然还有女票大人。他们帮助我共拍得了215张手势的照片。有一点非常重要,我们在收集图片的过程中给图片命名,一定要在命名中体现图片的标签信息,懂点机器学习的都知道原因。比如,我的图片命名规则如下:

    数字手势识别APP

      这么点照片想训练模型简直天方夜谭,只能祭出 data augmentation(数据增强)神器了,通过旋转,平移,拉伸 等操作每张图片生成100张,这样图片就变成了21500张。下面是 data augmentation 的代码:

    from keras.preprocessing.image import ImageDataGenerator, img_to_array, load_img
    import os
    
    datagen = ImageDataGenerator(
        rotation_range=20,
        width_shift_range=0.15,
        height_shift_range=0.15,
        zoom_range=0.15,
        shear_range=0.2,
        horizontal_flip=True,
        fill_mode='nearest')
    dirs = os.listdir("picture")
    print(len(dirs))
    for filename in dirs:
        img = load_img("picture//{}".format(filename))
        x = img_to_array(img)
        # print(x.shape)
        x = x.reshape((1,) + x.shape) #datagen.flow要求rank为4
        # print(x.shape)
        datagen.fit(x)
        prefix = filename.split('.')[0]
        print(prefix)
        counter = 0
        for batch in datagen.flow(x, batch_size=4 , save_to_dir='generater_pic', save_prefix=prefix, save_format='jpg'):
            counter += 1
            if counter > 100:
                break  # 否则生成器会退出循环

    二、数据集的处理

    1.缩放图片

      接下来对这21500张照片进行处理,首先要把每张照片缩放到64*64的尺寸,这么做的原因如下:

    • 不同手机拍出的照片的size各不相同,要统一
    • 如果手机拍出来的高分辨率图片,太大,GPU显存有限,要压缩下,减少体积。
    • APP通过手机摄像头拍摄出来的照片,不同机型有差异,要统一。

    对图片的缩放不能简单的直接缩小尺寸,那样的话会失真严重。所以要用到一些缩放算法,TensorFlow中已经提供了四种缩放算法,分别为: 双线性插值法(Bilinear interpolation)、最近邻居法(Nearest neighbor interpolation)、双三次插值法(Bicubic interpolation)和面积插值法(area interpolation)。我这里使用了面积插值法(area interpolation)。代码为:

    #压缩图片,把图片压缩成64*64的
    def resize_img():
        dirs = os.listdir("split_pic//6")
        for filename in dirs:
            im = tf.gfile.FastGFile("split_pic//6//{}".format(filename), 'rb').read()
            # print("正在处理第%d张照片"%counter)
            with tf.Session() as sess:
                img_data = tf.image.decode_jpeg(im)
                image_float = tf.image.convert_image_dtype(img_data, tf.float32)
                resized = tf.image.resize_images(image_float, [64, 64], method=3)
                resized_im = resized.eval()
                # new_mat = np.asarray(resized_im).reshape(1, 64, 64, 3)
                scipy.misc.imsave("resized_img6//{}".format(filename),resized_im)

    2.把图片转成 .h5文件

      h5文件的种种好处,这里不再累述。我们首先把图片转成RGB矩阵,即每个图片是一个64*64*3的矩阵(因为是彩色图片,所以通道是3)。这里不做归一化,因为我认为归一化应该在你用到的时候自己代码归一化,如果直接把数据集做成了归一化,有点死板了,不灵活。在我们把矩阵存进h5文件时,此时标签一定要对应每一张图片(矩阵),直接上代码:

    #图片转h5文件
    def image_to_h5():
        dirs = os.listdir("resized_img")
        Y = [] #label
        X = [] #data
        print(len(dirs))
        for filename in dirs:
            label = int(filename.split('_')[0])
            Y.append(label)
            im = Image.open("resized_img//{}".format(filename)).convert('RGB')
            mat = np.asarray(im) #image 转矩阵
            X.append(mat)
    
        file = h5py.File("dataset//data.h5","w")
        file.create_dataset('X', data=np.array(X))
        file.create_dataset('Y', data=np.array(Y))
        file.close()
    
        #test
        # data = h5py.File("dataset//data.h5","r")
        # X_data = data['X']
        # print(X_data.shape)
        # Y_data = data['Y']
        # print(Y_data[123])
        # image = Image.fromarray(X_data[123]) #矩阵转图片并显示
        # image.show()

    3.训练模型

      接下来就是训练模型了,首先把数据集划分为训练集和测试集,然后先坐下归一化,把标签转化为one-hot向量表示,代码如下:

    #load dataset
    def load_dataset():
        #划分训练集、测试集
        data = h5py.File("dataset//data.h5","r")
        X_data = np.array(data['X']) #data['X']是h5py._hl.dataset.Dataset类型,转化为array
        Y_data = np.array(data['Y'])
        # print(type(X_data))
        X_train, X_test, y_train, y_test = train_test_split(X_data, Y_data, train_size=0.9, test_size=0.1, random_state=22)
        # print(X_train.shape)
        # print(y_train[456])
        # image = Image.fromarray(X_train[456])
        # image.show()
        # y_train = y_train.reshape(1,y_train.shape[0])
        # y_test = y_test.reshape(1,y_test.shape[0])
        print(X_train.shape)
        # print(X_train[0])
        X_train = X_train / 255.  # 归一化
        X_test = X_test / 255.
        # print(X_train[0])
        # one-hot
        y_train = np_utils.to_categorical(y_train, num_classes=11)
        print(y_train.shape)
        y_test = np_utils.to_categorical(y_test, num_classes=11)
        print(y_test.shape)
    
        return X_train, X_test, y_train, y_test

      构建CNN模型,这里用了最简单的类LeNet-5,具体两层卷积层、两层池化层、一层全连接层,一层softmax输出。具体的小trick有:dropout、relu、regularize、mini-batch、adam。具体看代码吧:

    def weight_variable(shape):
        tf.set_random_seed(1)
        return tf.Variable(tf.truncated_normal(shape, stddev=0.1))
    
    def bias_variable(shape):
        return tf.Variable(tf.constant(0.0, shape=shape))
    
    def conv2d(x, W):
        return tf.nn.conv2d(x, W, strides=[1,1,1,1], padding='SAME')
    
    def max_pool_2x2(z):
        return tf.nn.max_pool(z, ksize=[1,2,2,1], strides=[1,2,2,1], padding='SAME')
    
    
    def random_mini_batches(X, Y, mini_batch_size=16, seed=0):
        """
        Creates a list of random minibatches from (X, Y)
    
        Arguments:
        X -- input data, of shape (input size, number of examples)
        Y -- true "label" vector (containing 0 if cat, 1 if non-cat), of shape (1, number of examples)
        mini_batch_size - size of the mini-batches, integer
        seed -- this is only for the purpose of grading, so that you're "random minibatches are the same as ours.
    
        Returns:
        mini_batches -- list of synchronous (mini_batch_X, mini_batch_Y)
        """
    
        m = X.shape[0]  # number of training examples
        mini_batches = []
        np.random.seed(seed)
    
        # Step 1: Shuffle (X, Y)
        permutation = list(np.random.permutation(m))
        shuffled_X = X[permutation]
        shuffled_Y = Y[permutation,:].reshape((m, Y.shape[1]))
    
        # Step 2: Partition (shuffled_X, shuffled_Y). Minus the end case.
        num_complete_minibatches = math.floor(m / mini_batch_size)  # number of mini batches of size mini_batch_size in your partitionning
        for k in range(0, num_complete_minibatches):
            mini_batch_X = shuffled_X[k * mini_batch_size: k * mini_batch_size + mini_batch_size]
            mini_batch_Y = shuffled_Y[k * mini_batch_size: k * mini_batch_size + mini_batch_size]
            mini_batch = (mini_batch_X, mini_batch_Y)
            mini_batches.append(mini_batch)
    
        # Handling the end case (last mini-batch < mini_batch_size)
        if m % mini_batch_size != 0:
            mini_batch_X = shuffled_X[num_complete_minibatches * mini_batch_size: m]
            mini_batch_Y = shuffled_Y[num_complete_minibatches * mini_batch_size: m]
            mini_batch = (mini_batch_X, mini_batch_Y)
            mini_batches.append(mini_batch)
    
        return mini_batches
    
    
    def cnn_model(X_train, y_train, X_test, y_test, keep_prob, lamda, num_epochs = 450, minibatch_size = 16):
        X = tf.placeholder(tf.float32, [None, 64, 64, 3], name="input_x")
        y = tf.placeholder(tf.float32, [None, 11], name="input_y")
        kp = tf.placeholder_with_default(1.0, shape=(), name="keep_prob")
        lam = tf.placeholder(tf.float32, name="lamda")
        #conv1
        W_conv1 = weight_variable([5,5,3,32])
        b_conv1 = bias_variable([32])
        z1 = tf.nn.relu(conv2d(X, W_conv1) + b_conv1)
        maxpool1 = max_pool_2x2(z1) #max_pool1完后maxpool1维度为[?,32,32,32]
    
        #conv2
        W_conv2 = weight_variable([5,5,32,64])
        b_conv2 = bias_variable([64])
        z2 = tf.nn.relu(conv2d(maxpool1, W_conv2) + b_conv2)
        maxpool2 = max_pool_2x2(z2) #max_pool2,shape [?,16,16,64]
    
        #conv3  效果比较好的一次模型是没有这一层,只有两次卷积层,隐藏单元100,训练20次
        # W_conv3 = weight_variable([5, 5, 64, 128])
        # b_conv3 = bias_variable([128])
        # z3 = tf.nn.relu(conv2d(maxpool2, W_conv3) + b_conv3)
        # maxpool3 = max_pool_2x2(z3)  # max_pool3,shape [?,8,8,128]
    
        #full connection1
        W_fc1 = weight_variable([16*16*64, 200])
        b_fc1 = bias_variable([200])
        maxpool2_flat = tf.reshape(maxpool2, [-1, 16*16*64])
        z_fc1 = tf.nn.relu(tf.matmul(maxpool2_flat, W_fc1) + b_fc1)
        z_fc1_drop = tf.nn.dropout(z_fc1, keep_prob=kp)
    
        #softmax layer
        W_fc2 = weight_variable([200, 11])
        b_fc2 = bias_variable([11])
        z_fc2 = tf.add(tf.matmul(z_fc1_drop, W_fc2),b_fc2, name="outlayer")
        prob = tf.nn.softmax(z_fc2, name="probability")
        #cost function
        regularizer = tf.contrib.layers.l2_regularizer(lam)
        regularization = regularizer(W_fc1) + regularizer(W_fc2)
        cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(labels=y, logits=z_fc2)) + regularization
    
        train = tf.train.AdamOptimizer().minimize(cost)
        # output_type='int32', name="predict"
        pred = tf.argmax(prob, 1, output_type="int32", name="predict")  # 输出结点名称predict方便后面保存为pb文件
        correct_prediction = tf.equal(pred, tf.argmax(y, 1, output_type='int32'))
        accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
        tf.set_random_seed(1)  # to keep consistent results
    
        seed = 0
    
        init = tf.global_variables_initializer()
        with tf.Session() as sess:
            sess.run(init)
            for epoch in range(num_epochs):
                seed = seed + 1
                epoch_cost = 0.
                num_minibatches = int(X_train.shape[0] / minibatch_size)
                minibatches = random_mini_batches(X_train, y_train, minibatch_size, seed)
                for minibatch in minibatches:
                    (minibatch_X, minibatch_Y) = minibatch
                    _, minibatch_cost = sess.run([train, cost], feed_dict={X: minibatch_X, y: minibatch_Y, kp: keep_prob, lam: lamda})
                    epoch_cost += minibatch_cost / num_minibatches
                if epoch % 10 == 0:
                    print("Cost after epoch %i: %f" % (epoch, epoch_cost))
                    print(str((time.strftime('%Y-%m-%d %H:%M:%S'))))
    
            # 这个accuracy是前面的accuracy,tensor.eval()和Session.run区别很小
            train_acc = accuracy.eval(feed_dict={X: X_train[:1000], y: y_train[:1000], kp: 0.8, lam: lamda})
            print("train accuracy", train_acc)
            test_acc = accuracy.eval(feed_dict={X: X_test[:1000], y: y_test[:1000], lam: lamda})
            print("test accuracy", test_acc)
    
            #save model
            saver = tf.train.Saver({'W_conv1':W_conv1, 'b_conv1':b_conv1, 'W_conv2':W_conv2, 'b_conv2':b_conv2,
                                    'W_fc1':W_fc1, 'b_fc1':b_fc1, 'W_fc2':W_fc2, 'b_fc2':b_fc2})
            saver.save(sess, "model_500_200_c3//cnn_model.ckpt")
            #将训练好的模型保存为.pb文件,方便在Android studio中使用
            output_graph_def = graph_util.convert_variables_to_constants(sess, sess.graph_def, output_node_names=['predict'])
            with tf.gfile.FastGFile('model_500_200_c3//digital_gesture.pb', mode='wb') as f:  # ’wb’中w代表写文件,b代表将数据以二进制方式写入文件。
                f.write(output_graph_def.SerializeToString())

    **这里有一个非常非常非常重要的事情,要注意,具体请参考上一篇博客中的 2. 模型训练注意事项 链接为:将TensorFlow训练好的模型迁移到Android APP上(TensorFlowLite)。整个模型训练几个小时即可,当然调参更是门艺术活,不多说了。
      这里小小感慨下,i7-7700k跑一个epoch需要2分钟,750ti需要36秒,1070需要6秒。。。这里再次感谢宋俞璋的神机。。关于如何搭建TensorFlow GPU环境,请参见我的博客:ubuntu16.04+GTX750ti+python3.6.5配置cuda9.0+cudnn7.05+TensorFlow-gpu1.8.0

    训练完的模型性能:

    模型性能

    但是在APP上因为面临的环境更加复杂,准备远没有这么高。

    PC端随便实测的效果图:

    pc端测试结果


    4.在Android Studio中调用训练好的模型

      关于如何把模型迁移到Android studio中,请参考我的上一篇博客:将TensorFlow训练好的模型迁移到Android APP上(TensorFlowLite)。这里面解释下为何会用到OpenCV,这一切都要源于那个图片缩放,还记得我们在上面提到的area interpolation吗,这个算法不像那些双线性插值法等,网上并没有java版本的实现,无奈去仔细翻了遍TensorFlow API文档,发现这么一段话:

    Each output pixel is computed by first transforming the pixel’s footprint into the input tensor and then averaging the pixels that intersect the footprint. An input pixel’s contribution to the average is weighted by the fraction of its area that intersects the footprint. This is the same as OpenCV’s INTER_AREA.

    这就是为什么会用OpenCV了,OpenCV在Android studio中的配置也是坑多,具体的配置请参见我的博客:Android Studio中配置OpenCV。这里只说下,TensorFlowLite只提供了几个简单的接口,虽然在我的博客将TensorFlow训练好的模型迁移到Android APP上(TensorFlowLite)也提过了,但是这里还是想提一下,提供的接口(官网地址:Here’s what a typical Inference Library sequence looks like on Android.):

    // Load the model from disk.
    TensorFlowInferenceInterface inferenceInterface =
    new TensorFlowInferenceInterface(assetManager, modelFilename);
    
    // Copy the input data into TensorFlow.
    inferenceInterface.feed(inputName, floatValues, 1, inputSize, inputSize, 3);
    
    // Run the inference call.
    inferenceInterface.run(outputNames, logStats);
    
    // Copy the output Tensor back into the output array.
    inferenceInterface.fetch(outputName, outputs);

    注释也都说明了各个接口的作用,就不多说了。

      我也不知道是不是因为OpenCV里的area interpolation算法实现的和TensorFlow不一样还是其他什么原因,总感觉在APP上测得效果要比在PC上模型性能差。。也许有可能只是我感觉。。
    关于Android APP代码也没啥好说的了,代码都放到github上了,地址:Chinese-number-gestures-recognition,欢迎star,哈哈。

    下面上几张测试的效果图吧,更多的展示效果见github,:Chinese-number-gestures-recognition

    测试效果




    展开全文
  • 一步步教你Windows配置ISCSI共享存储(StarWind)

    千次阅读 多人点赞 2021-06-21 15:24:27
    之前讲过一篇文章 一步步教你Linux7安装Oracle RAC(11GR2版本) 教大家如何安装Oracle RAC,有朋友希望将共享存储配置这一块详细讲一讲,因此便有了这篇。

    作者简介

    • 作者:LuciferLiu,中国DBA联盟(ACDU)成员。
    • 目前从事Oracle DBA工作,曾从事 Oracle 数据库开发工作,主要服务于生产制造,汽车金融等行业。
    • 现拥有Oracle OCP,OceanBase OBCA认证,擅长Oracle数据库运维开发,备份恢复,安装迁移,Linux自动化运维脚本编写等。

    前言

    一、介绍

    • ISCSI(Internet Small Computer System Interface,Internet 小型计算机系统接口)是一种由IBM公司研究开发的IP SAN技术。
    • 该技术是将现有SCSI接口与以太网络(Ethernet)技术结合,基于 TCP/IP的协议连接iSCSI服务端(Target)和客户端(Initiator),使得封装后的SCSI数据包可以在通用互联网传输,最终实现iSCSI服务端映射为一个存储空间(磁盘)提供给已连接认证后的客户端。

    iscsi架构
    我常用ISCSI配置共享存储的软件:OpenfilerStarWind本文主要讲解的是 StarWind 软件。

    二、StarWind安装

    • StarWind的特点就是简单快捷,方便操作。对于测试安装来说,Windows平台更实用。

    1 解压安装包

    安装包

    2 运行安装

    Setup
    接受协议
    介绍
    安装路径
    全部安装
    开始菜单
    创建系统图标
    选择有key
    选择ISCSI

    • 选择安装包中的key:
      在这里插入图片描述
      选择key
      确认key
      确认安装
    • 选择安装:
      安装过程
      安装结束
      至此,StarWind已经安装结束。

    三、配置服务端StarWind ISCSI

    • 安装完StarWind软件之后,接下来就需要通过软件配置共享存储。

    1 打开StarWind软件

    打开软件

    2 新建StarWind Server

    新建sever

    • 通过cmd命令行查看本机ip:
      查看本机IP
    • 填写本机IP,点击OK:
      填写本机IP
    • 新建成功后,选择Server,双击或者点击connect连接:
      连接server

    3 新建Target

    新建Target

    • 填入Target别名,根据自己情况填写:
      在这里插入图片描述
      确认信息
      添加完成
      在这里插入图片描述

    四、添加Device存储盘

    添加Device

    • 选择虚拟硬盘:
      虚拟硬盘
    • 选择镜像文件:
      镜像文件
    • 创建新的虚拟盘:
      创建新的虚拟盘
    • 选择镜像文件路径和大小:
      选择镜像文件和大小
      默认
      选择through caching模式
    • 选择已有Target:
      选择存在Target
      选择Lucifer
      信息确认
      结束
      如果需要添加多块共享盘,只需要重复上述添加Device即可。
      在这里插入图片描述

    至此,StarWind共享存储服务端已经配置完成。

    五、Linux通过ISCSI连接共享存储

    1 Linux客户端安装ISCSI依赖

    yum install -y iscsi-initiator-utils*
    

    安装依赖

    2 搜索服务端ISCSI Target

    iscsiadm -m discovery -t st -p 10.211.55.33
    

    注意:10.211.55.33为服务端IP,即Windows主机的IP。
    搜索iscsi target

    3 连接服务端ISCSI共享存储

    iscsiadm -m node -T iqn.2008-08.com.starwindsoftware:10.211.55.33-lucifer -p 10.211.55.33 -l
    

    注意:iqn.2008-08.com.starwindsoftware:10.211.55.33-lucifer为上一步搜索出的Target名称,复制即可。
    连接成功

    4 Linux客户端查看共享存储

    lsblk
    

    查看共享存储
    如上所示,共享盘已经挂载成功。


    本次分享到此结束啦~

    StarWind软件获取方式:

    • 关注微信公众号:Lucifer三思而后行

    如果觉得文章对你有帮助,点赞、收藏、关注、评论,一键四连支持,你的支持就是我创作最大的动力。

    技术交流可以 关注公众号:Lucifer三思而后行

    展开全文
  • 一步步教你怎么用python写贪吃蛇游戏

    万次阅读 多人点赞 2019-06-27 18:17:52
    目录 0 引言 1 环境 2 需求分析 ...前几天,星球有人提到贪吃蛇,下子就勾起了我的兴趣,毕竟在那个Nokia称霸的年代,这款游戏可是经典中的经典啊!而用Python(蛇)玩Snake(贪吃蛇),那再合适不过了

    目录
    0 引言
    1 环境
    2 需求分析
    3 代码实现
    4 后记

    0 引言

    前几天,星球有人提到贪吃蛇,一下子就勾起了我的兴趣,毕竟在那个Nokia称霸的年代,这款游戏可是经典中的经典啊!而用Python(蛇)玩Snake(贪吃蛇),那再合适不过了???

    先通过下面这个效果图来感受下吧!
    在这里插入图片描述

    1 环境

    操作系统:Windows

    Python版本:3.7.3

    2 需求分析

    我们先来回顾下贪吃蛇中的游戏元素及游戏规则。

    首先呢,需要有贪吃蛇、有食物;需要能控制贪吃蛇来上下移动获取食物;贪吃蛇在吃取食物后,自身长度增加,同时食物消失并随机生成新的食物;如果贪吃蛇触碰到四周墙壁或是触碰到自己身体时,则游戏结束。

    游戏规则就是这么简单,接下来我们借助一个第三方库pygame来实现它。

    Pygame是一个利用SDL库的游戏库, 是一组用来开发游戏软件的 Python 程序模块。

    SDL(Simple DirectMedia Layer)是一个跨平台库,支持访问计算机多媒体硬件(声音、视频、输入等),SDL非常强大,但美中不足的是它是基于 C 语言的。

    PyGame是 SDL 库的 Python 包装器(wrapper),Pygame 在SDL库的基础上提供了各种接口,从而使用用户能够使用python语言创建各种各样的游戏或多媒体程序。

    它的安装方法很简单,如下:

    pip install pygame

    想要了解更多pygame功能的朋友也可以查阅官方的文档。

    3 代码实现

    首先导入我们要用到的模块,除了第三方库pygame外,我们还会用到一些Python内置的模块如sys、random、time等,一并导入即可。

    import pygame, sys, random, time
    # 从pygame模块导入常用的函数和常量
    from pygame.locals import *   
    

    3.1 一些全局参数的初始化

    接下来,我们需要声明一些全局参数。如初始化pygame、定义游戏窗口的大小、窗口的标题、定义全局的颜色变量等信息。

    # 初始化Pygame库
    pygame.init()
    # 初始化一个游戏界面窗口
    DISPLAY = pygame.display.set_mode((640, 480))
    # 设置游戏窗口的标题
    pygame.display.set_caption('人人都是Pythonista - Snake')
    # 定义一个变量来控制游戏速度
    FPSCLOCK = pygame.time.Clock()
    # 初始化游戏界面内使用的字体
    BASICFONT = pygame.font.SysFont("SIMYOU.TTF", 80)
    
    # 定义颜色变量
    BLACK = pygame.Color(0, 0, 0)
    WHITE = pygame.Color(255, 255, 255)
    RED = pygame.Color(255, 0, 0)
    GREY = pygame.Color(150, 150, 150)
    

    3.2 初始化贪吃蛇及食物

    游戏中需要有贪吃蛇及食物,我们给定一个初始值。我们将整个界面看成许多20*20的小方块,每个小方块代表一个标准小格子,贪吃蛇的长度就可以用几个标准小格子表示。我们将贪吃蛇身体用列表的形式存储,方便之后的删减。
    ‘’‘初始化贪吃蛇及食物’’’

    # 贪吃蛇的的初始位置
    snake_Head = [100,100]
    # 初始化贪吃蛇的长度 (注:这里以20*20为一个标准小格子)
    snake_Body = [[80,100],[60,100],[40,100]]
    # 指定蛇初始前进的方向,向右
    direction = "right"
    
    # 给定第一枚食物的位置
    food_Position = [300,300]
    # 食物标记:0代表食物已被吃掉;1代表未被吃掉。
    food_flag = 1
    

    3.3 实现贪吃蛇的上下左右移动

    我们需要控制贪吃蛇的上下左右的移动,这里通过监听键盘输入来实现。利用键盘中的上下左右键或WASD键来控制贪吃蛇的运动。

    # 检测按键等Pygame事件
    for event in pygame.event.get():
        if event.type == QUIT:
            # 接收到退出事件后,退出程序
            pygame.quit()
            sys.exit()
            
        # 判断键盘事件,用 方向键 或 wsad 来表示上下左右
        elif event.type == KEYDOWN:
            if (event.key == K_UP or event.key == K_w) and direction != DOWN:
                direction = UP
            if (event.key == K_DOWN or event.key == K_s) and direction != UP:
                direction = DOWN
            if (event.key == K_LEFT or event.key == K_a) and direction != RIGHT:
                direction = LEFT
            if (event.key == K_RIGHT or event.key == K_d) and direction != LEFT:
                direction = RIGHT
    

    接下来就是将蛇前进方向按照键盘的输入进行转弯操作,并将蛇的头部当前的位置加入到蛇身的列表中。

    # 根据键盘的输入,改变蛇的头部,进行转弯操作
    if direction == LEFT:
        snake_Head[0] -= 20
    if direction == RIGHT:
        snake_Head[0] += 20
    if direction == UP:
        snake_Head[1] -= 20
    if direction == DOWN:
        snake_Head[1] += 20
    
    # 将蛇的头部当前的位置加入到蛇身的列表中
    snake_Body.insert(0, list(snake_Head))
    

    3.4 判断是否吃到食物

    如果蛇头与食物的位置重合,则判定吃到食物,将食物数量清零;而没吃到食物的话,蛇身就会跟着蛇头运动,蛇身的最后一节将被踢出列表。

    # 判断是否吃掉食物
    if snake_Head[0]==food_Position[0] and snake_Head[1]==food_Position[1]:
        food_flag = 0
    else:
        snake_Body.pop()
    

    3.5 重新生成食物

    当游戏界面中的食物数量为0时,需要重新生成食物。利用random函数来生成随机位置

    # 生成新的食物
    if food_flag == 0:
        # 随机生成x, y
        x = random.randrange(1,32)
        y = random.randrange(1,24)
        food_Position = [int(x*20),int(y*20)]
        food_flag = 1
    

    3.6 绘制贪吃蛇、食物、分数等信息

    定义贪吃蛇的绘制函数

    # 绘制贪吃蛇
    def drawSnake(snake_Body):
        for i in snake_Body:
            pygame.draw.rect(DISPLAY, WHITE, Rect(i[0], i[1], 20, 20))
    
    定义食物的绘制函数
    # 绘制食物的位置
    def drawFood(food_Position):
        pygame.draw.rect(DISPLAY, RED, Rect(food_Position[0], food_Position[1], 20, 20))
    
    定义分数的绘制函数
    # 打印出当前得分
    def drawScore(score):
        # 设置分数的显示颜色
        score_Surf = BASICFONT.render('%s' %(score), True, GREY)
        # 设置分数的位置
        score_Rect = score_Surf.get_rect()
        score_Rect.midtop = (320, 240)
        # 绑定以上设置到句柄
        DISPLAY.blit(score_Surf, score_Rect)
    

    这些都定义好之后,我们需要在游戏主逻辑中调用它们,并刷新Pygame的显示层,贪吃蛇与食物的每一次移动,都会进行刷新显示层的操作来显示。最后我们可以设定一个速度值来合理控制游戏执行的速度。

    DISPLAY.fill(BLACK)
    # 画出贪吃蛇
    drawSnake(snake_Body)
    # 画出食物的位置
    drawFood(food_Position)
    # 打印出玩家的分数
    drawScore(len(snake_Body) - 3)
    # 刷新Pygame的显示层,贪吃蛇与食物的每一次移动,都会进行刷新显示层的操作来显示。
    pygame.display.flip()
    # 控制游戏速度
    FPSCLOCK.tick(7)
    

    3.7 定义游戏结束的画面并判断游戏是否结束

    定义函数,用于展示游戏结束的画面并退出程序

    # 游戏结束并退出
    def GameOver():
        # 设置GameOver的显示颜色
        GameOver_Surf = BASICFONT.render('Game Over!', True, GREY)
        # 设置GameOver的位置
        GameOver_Rect = GameOver_Surf.get_rect()
        GameOver_Rect.midtop = (320, 10)
        # 绑定以上设置到句柄
        DISPLAY.blit(GameOver_Surf, GameOver_Rect)
    
        pygame.display.flip()
        # 等待3秒
        time.sleep(3)
        # 退出游戏
        pygame.quit()
        # 退出程序
        sys.exit()
    

    在游戏主逻辑中加入判断,游戏是否结束

    '''游戏结束的判断'''
    # 贪吃蛇触碰到边界
    if snake_Head[0]<0 or snake_Head[0]>620:
        GameOver()
    if snake_Head[1]<0 or snake_Head[1]>460:
        GameOver()
    # 贪吃蛇触碰到自己
    for i in snake_Body[1:]:
        if snake_Head[0]==i[0] and snake_Head[1]==i[1]:
            GameOver()
    

    4 后记

    本文利用Pygame模块实现了最简单的贪吃蛇版本。当然还有很多需要优化的地方,比如随机生成食物时如何避免出现在贪吃蛇的自身上、通过贪吃蛇的长度来自动控制游戏速度等等。更高级一点的有如何通过AI自动玩贪吃蛇、贪吃蛇的双人对战等,期待大家利用Python可以把贪吃蛇玩出一个新的高度!???

    公众号「Python专栏」后台回复关键字「贪吃蛇」获取本文全套完整代码!

    展开全文
  • 如果你还没有看过我的上一篇文章,可以先去阅读 Android LayoutInflater原理分析,带你一步步深入了解View(一) 。 相信每个Android程序员都知道,我们每天的开发工作当中都在不停地跟View打交道,Android中的任何一...
  • 一步步手写神经网络

    千次阅读 多人点赞 2018-04-25 23:42:31
    一步步手写神经网络 &amp;amp;amp;amp;nbsp;&amp;amp;amp;amp;nbsp;&amp;amp;amp;amp;nbsp;&amp;amp;amp;amp;nbsp;&amp;amp;amp;amp;nbsp;&amp;amp;amp;amp;nbsp;&amp;amp;amp;amp;...
  • 一步步学习Linux多任务编程

    万次阅读 多人点赞 2015-06-04 10:56:55
    系统调用01、什么是系统调用?02、Linux系统调用之I/O操作(文件操作)03、文件描述符的复制:dup(), dup2()多进程实现多任务04、进程的介绍05、Linux可执行文件结构与进程结构06、多进程实现多任务():fork()07...
  • pb一步步开发APP

    万次阅读 2017-01-18 17:29:35
    下面就通过示例,一步步都大家如何开发一个APP。先上几张最后的效果图   PB开发Http服务 1.建立workspace命名为test, 建立一个application命名为plugin,创建一个全局函数satrda_service,导入n_sat
  • 不知不觉中,带你一步步深入了解View系列的文章已经写到第四篇了,回顾一下,我们一共学习了LayoutInflater的原理分析、视图的绘制流程、视图的状态及重绘等知识,算是把View中很多重要的知识点都涉及到了。...
  • public class SideBar extends View { private static final String[] b = {"定位", "热门", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W...
  • 一步步学习ERP系统使用

    千次阅读 2015-11-20 02:00:02
    一步步学习ERP系统使用 微信公众号: 扫一扫获得更多信息:
  • 可重入锁-只能保证在个JVM中使得订单唯一,分布式系统下订单并不唯一
  • 图解CNN:通过100张图一步步理解CNN

    万次阅读 多人点赞 2018-03-06 17:42:16
    图解CNN:通过100张图一步步理解CNN作者:@Brandon Rohrer,链接:http://brohrer.github.io/how_convolutional_neural_networks_work.html译者:@zhwhong,链接:https://www.jianshu.com/p/fe428f0b32c1说明:本文...
  • 一步步注册一只美区Apple ID

    千次阅读 2019-04-13 19:11:59
    一步步注册一只美服Apple ID,下面的注册步骤基于 MacBookPro 上面的 APP Store。 一、前期准备 将电脑连接到美国节点,可以到http://www.whatismyip.com这个网站验证一下IP地址,显示是 xxx.xxx.xxx.xxx US 即可...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 217,649
精华内容 87,059
关键字:

一步步的什么