微信里的图像音视频

文件名

在目录Tencent/MicroMsg/目录下的一个ID字符串目录下。
voice2目录存放的是语音。在经过两级2位十六进制字母散列的目录中文件是按“秒时分日月年”的顺序组成文件名msg_后的前12位数字。
video目录存放是视频文件和相关的图片。目录中文件是按“时分秒日月年”的顺序组成文件名前12位数字。比如”083909140318afaaef195653.mp4″,就是“08点39分09秒在14日03月18年”在手机上生成的。同个小视频若转发到多个对话会出现多个文件和文件名。

image2目录中存放的用户聊天图片,也是经过两级2位十六进制字母散列的目录中,但是文件名貌似随机无规律。

上面都是自动保存的原文件等。如果视频图片保存下载到Tencent/MicroMsg/WeiXin文件夹下,则视频直接使用保存时的unixtime时间戳,而图片则是mmexport加上unixtime时间戳。但是注意下载的图片可image2里自动缓存的图片清晰度高,保存的视频无区别。

对于使用微信发送消息框直接拍摄的图片和视频则也是保存在Tencent/MicroMsg/WeiXin文件夹下,文件名时wx_camera_加上unixtime时间戳。

微信amr音频文件转mp3

微信amr音频使用silk v3编码,工具集 https://github.com/kn007/silk-v3-decoder/tree/master/windows
BAT批处理程序示例

setlocal enabledelayedexpansion

for /r D:\\micromsgvoice2 %%i in (*.amr) do (
set fullfilename=%%i
C:\silk2mp3\silk_v3_decoder.exe !fullfilename! msg.pcm -Fs_API 44100
C:\silk2mp3\lame.exe -ar -s 24 msg.pcm !fullfilename:.amr=.mp3!
)

另外,我这里还有个自动通过ftp从手机下载音频文件的脚本 https://github.com/wadefelix/Scripts100/blob/master/fetchmicromsgvoice2.py

改DICOM这种平面结构数据为对象结构

DICOM格式中所有信息条目类似于平铺地保存于一张表格中,与协议中按对象组织的信息条目组合不对应。

例如fo-dicom中的示例也都是这样编辑条目

var file = await DicomFile.OpenAsync(@"test.dcm"); // Alt 2

var patientid = file.Dataset.Get(DicomTag.PatientID);

file.Dataset.Add(DicomTag.PatientsName, "DOE^JOHN");

能不能把Patient Identification Module弄成对象,即将上面的示例改成下列形式呢?

var file = await DicomFile.OpenAsync(@"test.dcm"); // Alt 2
var dicomfileobj = file.Dataset;
var patientid = dicomfileobj.Patient.ID;
dicomfileobj.Patient.Name = "DOE^JOHN";

答案当然是能。

按照DICOM规范把所有Macro/Module定义成对象,DICOM Atributes就定义成C#对象的Properties,getter和setter内封装fo-dicom的Add等方法。用反射自动解码和编码DICOM即可。

具体实现此处不表了。

面向windows的支持plugins的caffe

https://github.com/wadefelix/caffe/tree/pwin

prototxt文件整合是在CMake脚本里完成,所以修改proto文件的话要用CMake再生成。

另外protocal buffer版本换成了最新的3.5.2,预编译的protobuf对扩展的支持没搞定可能不太好搞。编译protobuf时注意修改其运行库是和caffe一致的多线程DLL

已携带了和Linux下pCaffe分支一样有的 fast_rcnnSSDyolo

另外,因为电脑所限,我只编译了纯CPU版本,所以GPU版本不确定,也没有支持CUDA的Dynamic Parallelism动态并行特性。

使用tcl语言unknown指令操作形如LDAP的dn形式对象的方法

这是很久之前的一个工作了,简单回忆下思路。

我们的某系统中以树的形式保存了复杂系统的各种配置信息,并且给每个节点分配了形如LDAP的dn那样的名字,这就是一个dn:cn=somenone,ou=Group,dc=example,dc=com,但是我们把这个顺序反过来了,这样更符合国人的思考方式吧,就成了dc=com,dc=example,ou=Group,cn=somenone。这个系统又基本以增删改查的形式提供管理接口,以及别的一些管理接口。我们就以此为基础对常用功能开发了一套更简单的管理方法,使用的语言就是tcl,充分利用了tcl提供的expect等优秀功能,特别是我直接利用了tcl的unknown。使用unknown的机制给我们的dn添加各种动作,等于实现了它的对象化。
举例:

# Group中增加一个名为newuser的人
dc=com,dc=example,ou=Group.AddUser newuser
# 实际上我们的对象都是查出来的或根据规则动态生成的
set mygroup dc=com,dc=example,ou=Group
# 那之前增加用户的指令就成了
$mygroup.AddUser newuser

set userobj $mygroup,cn=newuser
# 修改年龄
$userobj.SetAttribute age 23

这里面就用了unknown,因为形如dc=com,dc=example,ou=Group.AddUser这个命令肯定查不到啊,我们定制个unknown处理函数,检查一下命令的格式,发现它符合我们的规则,就调用我们为它写的专门的处理函数。那处理函数在哪里呢?这点我们肯定也要采取些技巧了啊。
把它写在ou.tcl里,当然对于dc=com,dc=example,ou=Group,cn=newuser我们就写在cn.tcl中。这里我们补充一下,我们的系统里并不会出现LDAP这样有两级都叫dc,树的每一层都是不同的类型。这样我们只需要检查一下dn最终那一级是什么类型就去加载相应的tcl文件进来就可以了。那这个tcl文件就有dc=com,dc=example,ou=Group.AddUser这个命令么?当然没有了,这个dn好多呢,怎么可能每个都写一个过程呢。dc=com,dc=example,ou=Group.AddUser newuser这个命令被我们的unknown处理函数翻译成了AddUser dc=com,dc=example,ou=Group newuser,哦,原来那个函数叫AddUser,就放在ou.tcl文件里,轻松多了吧。

真他娘的的是个人才!

VS2017编译caffe

caffe的windows分支支持的VS版本是2013和2015,而我安装的是Visual Studio Community 2017, 直接使用cmake是不行的。

根据报错信息,凡是不行的地方让VS2017直接使用VS2015的配置,就可以使用caffe工程提供的预编译的二进制的依赖库了。

CMake脚本的修改

cmake/WindowsDownloadPrebuiltDependencies.cmake需要增加

set(DEPENDENCIES_URL_1912_27 "${DEPENDENCIES_URL_BASE}/v${DEPENDENCIES_VERSION}/${DEPENDENCIES_NAME_1900_27}${DEPENDENCIES_FILE_EXT}")
set(DEPENDENCIES_SHA_1912_27 "17eecb095bd3b0774a87a38624a77ce35e497cd2")

1912就是VS2017的,1900是2015的。

依赖包中OpenCV的配置

预编译的OpenCV中也缺少VS2017的信息,.caffe\dependencies\libraries\OpenCVConfig.cmake中增加VS2017的信息

  elseif(MSVC_VERSION EQUAL 1900)
    set(OpenCV_RUNTIME vc14)
  elseif(MSVC_VERSION EQUAL 1912)
    set(OpenCV_RUNTIME vc14)
  endif()

BLAS选择OpenBLAS,我这苏菲也没有N记卡,不用CUDA。选择VS2017 amd64编译起,cmake搞起,成功编译出工程, VS打开sln,编译也成功。Debug配置下
C:\Users\renwe\Projects\caffe\build>.\tools\Debug\caffe-d.exe --help,Release配置下C:\Users\renwe\Projects\caffe\build>.\tools\Release\caffe.exe --help成功运行。

YEAH!对了,

python版本选择

我看了caffe仅支持2.7和3.5版本的python,那VS2016里安装的python3.6我就放弃吧,有miniconda这种神器我何必自己折腾呢。选择了安装了miniconda2版本(想着Caffe2还不支持py3)。

mnist走一波

caffe工程目录下(我的C:\Users\renwe\Projects\caffe> )开PowerShell:

.\data\mnist\get_mnist.ps1
.\examples\mnist\create_mnist.ps1
.\examples\mnist\train_lenet.ps1

注意1:PowerShell运行脚本的权限,默认可能是不能执行的,修改命令Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
注意2:VS工程配置的问题,可执行文件都在各自的Debug和Release字目录中。适当修改下ps1脚本就OK了。
最后训练完测试完

I0331 02:00:46.701665 15772 solver.cpp:310] Iteration 10000, loss = 0.00280334
I0331 02:00:46.701665 15772 solver.cpp:330] Iteration 10000, Testing net (#0)
I0331 02:00:56.045547 19868 data_layer.cpp:73] Restarting data prefetching from start.
I0331 02:00:56.420552 15772 solver.cpp:397]     Test net output #0: accuracy = 0.9901
I0331 02:00:56.420552 15772 solver.cpp:397]     Test net output #1: loss = 0.0309957 (* 1 = 0.0309957 loss)
I0331 02:00:56.420552 15772 solver.cpp:315] Optimization Done.
I0331 02:00:56.420552 15772 caffe.cpp:260] Optimization Done.

WordPress搜索自定义文章类型post_type等

post_type直接使用 Custom Post Type Maker 创建和管理,可以控制是否对默认全站搜索可见。

如何限定只在自定义post_type中进行搜索呢?

WordPress搜索是支持post_type参数的,我们可以通过定制theme里的searchform.php定制下搜索框给加上这个post_type参数即可,可以通过下拉菜单或者radio标签等来控制这个参数。

比如下面这样

<?php $post_type_q = get_query_var ( 'post_type' ); ?>
<input type="radio" name="post_type" value="any" <?php echo $post_type_q=='any'?'checked':'';?> >全站
<input type="radio" name="post_type" value="post" <?php echo $post_type_q=='post'?'checked':'';?> >文章
<input type="radio" name="post_type" value="product" <?php echo $post_type_q=='product'?'checked':'';?> >产品

如何合并全文搜索和某个自定义post_meta搜索呢?

用下面三个filter控制吧,仅在搜索product时搜索这个barcode这个meta信息。

function add_join_include_barcode($joins) {
 if ( !is_admin()) {
  global $wpdb;
  if ( isset( $_GET['post_type'] ) && !empty( $_GET['post_type'] ) && $_GET['post_type'] == 'product' ) {
   $joins = $joins . " INNER JOIN {$wpdb->postmeta} ON ({$wpdb->posts}.ID = {$wpdb->postmeta}.post_id)" ;
  }
 }
 return $joins;
}
function add_where_include_barcode($where) {
 if ( !is_admin()) {
  global $wpdb;
  if ( isset( $_GET['post_type'] ) && !empty( $_GET['post_type'] ) && $_GET['post_type'] == 'product' ) {
   $where.= " OR ({$wpdb->postmeta}.meta_key='barcode' AND {$wpdb->postmeta}.meta_value like '%".esc_sql( $_GET['s'])."%')";
  }
 }
 return $where;
}
add_filter('posts_join','add_join_include_barcode');
add_filter('posts_where','add_where_include_barcode');
function search_distinct() {
 return "DISTINCT";
}
add_filter('posts_distinct', 'search_distinct');

新建文章自动添加meta信息字段

add_action( 'add_meta_boxes_product', 'add_product_meta_data' );
function add_product_meta_data( WP_Post $cf ) {
    // Add the meta data you want the custom post type to have
    $cf_meta_data = [
        'barcode',
    ];

    foreach ( $cf_meta_data as $meta ) {
        add_post_meta( $cf->ID, $meta, '', true );
    }
}

用单选框复选框形式修改某taxonomy

先通过注册add_meta_boxes这个action创建输入框,还需要注册save_post这个action来保存此meta信息。


function product_seller_meta_box_markup($object)
{
    wp_nonce_field(basename(__FILE__), "meta-box-nonce");
    ?>
<div>
<form>
    <?php 
    $sellerterms = get_the_terms( $post->ID, 'seller' );
    $seller = null;
    if ( ! empty( $sellerterms ) && ! is_wp_error( $sellerterms ) ) {
        $seller = $sellerterms[0]->name;
    }
    $allsellers = array('狗家','猫家','狮家');
    for($i = 0; $i < count($allprices); ++$i) {
        echo '<input type="radio" name="product_seller" value="'.$allsellers [$i].'"'.($seller==$allsellers[$i]?' checked ':'').'>'.$allsellers[$i].'<br>';
    }
    ?>
    </form>
</div>
<?php
}
function add_meta_box_seller() {
  add_meta_box("seller-meta-box", "卖家", "product_seller_meta_box_markup", "product", "side", "low", null);
}
add_action("add_meta_boxes", "add_meta_box_seller"); 

function save_product_seller($post_id, $post, $update) {
  if (!isset($_POST["meta-box-nonce"]) || !wp_verify_nonce($_POST["meta-box-nonce"], basename(__FILE__))) return $post_id;
  if(!current_user_can("edit_post", $post_id)) return $post_id;
  if(defined("DOING_AUTOSAVE") && DOING_AUTOSAVE) return $post_id;

  if('product' != $post->post_type)
    return $post_id;

  $seller = null;

  if(isset($_POST["product_seller"]))
  {
    $seller = $_POST["product_seller"];
  }
  if ($seller!==null) wp_set_object_terms( $post_id, $seller , 'seller' );
}

add_action("save_post", "save_product_seller", 10, 3);

 

caffe输入层怎么编写?

caffe的输入层总结

前面已经总结了下caffe已内置的输入层,但是除了python layer外,其余的局限性都比较大,比如大家常用的BoundingBox数据就很不统一,py-faster-rcnn里面使用python layer作输入层直接读取从PASCAL VOC格式数据(其实是从它构建的roidb并缓存了),而SSD自己构建了AnnotatedDataLayer来处理保存在lmdb中的数据,caffe-yolo也自己构建了BoxDataLayer供自己使用,大家都很不统一,caffe官方也迟迟不统一一个BBoxDataLayer出来,局面很分裂。本文就算是总结下阅读这些代码的经验吧。

那输入层应该怎么写?先来搞清除这几个概念。

一些基础概念

Datum

Datum是LMDB/LevelDB每个条目的值。但是Datum的内容是在caffe.proto里规定好了的,原版只有bytesdatafloatfloat_data两个字段可供保存数据。如果想保存多种数据,没办法,只能先组装到一块再放到datum中。

DataReader

yolo和ssd的DataReader完全一样啊。本对象还实例化了一个继承自InternalThread的类负责启动一个线程单独的读取磁盘数据。

如果输入层直接继承InternalThread类,则可以不实现DataReader类,相当于自己就是读入类,线程入口是InternalThreadEntry

Caffe原装的DataLayer就已继承InternalThread,只需要实现load_batch即可。

Batch

Bath就是每次让DataReader读到的数据,这个数据在输入层一般直接就直接想下一层传递了。这个caffe默认的只有data_label_两个Blob成员,如果我们有多种类型数据,caffe默认的恐怕也不够用。

Prefetch队列

  virtual void load_batch(Batch<Dtype>* batch);
  vector<shared_ptr<Batch<Dtype> > > prefetch_;
  BlockingQueue<Batch<Dtype>*> prefetch_free_;
  BlockingQueue<Batch<Dtype>*> prefetch_full_;

load_batch函数的用途:batch_size次从数据库中读取出共计batch_sizedatum数据,将这些datum转成Batch对象。

InternalThreadEntry每次从prefetch_free_pop一个Batch对象出来,然后调用load_batch装满这个Batch对象,再把这个Batch对象pushprefetch_full_中去。

XXXDataLayer::Forward_cpu的作用就是将当前的prefetch_current_指向的Batch对象pushprefetch_free_中去,然后再用从prefetch_full_pop取出一个对象到prefetch_current_上来。

总结下方法

简单标签

如果你的数据只是一些图,每张图对应一个int型可以表示的label,那你直接把图文件名和label组成文本文件用ImageDataLayer读就可以了,想用LMDB的就把数据存到LMDB中也可以。

bbox标签

因为原版datum字段有限,特别是label只有一个,大家只好各显神通。

caffe-yolo的做法是,datumdata存放图片,float_data存放bbox六元组(标签,难易标识,四个坐标)。ssd的做法则是对datum做了更大的更改以支持bbox数据。我为了方便,直接给原版datum定义了扩展字段,允许自行扩展若干自定义字段。

也可以将datum视为最简单的容器,我们在其内的data字段内另建容器,把数据结构先序列化存到datum的data字段,使用时再反序列化出来。

protobuf extension的使用

在proto中扩展Datum

// message Datum 需要先声明扩展字段区间,比如 {
//   extensions 11 to 20;
// }
// 然后想扩展datum时,只需要另外如此声明即可
extend Datum {
  optional string imgfilename = 15;
}

python代码中对消息扩展字段的使用

datum.Extensions[caffe.proto.caffe_pb2.imgfilename] = "/path/to/imgfile"

CPP对对消息扩展字段的使用

// get
datum.GetExtension(imgfilename)
// set
datum.SetExtension(imgfilename, "/path/to/imgfile");

 

方法1:直接继承BasePrefetchingDataLayer

需要实现load_batch函数,这个非常简单,队列和线程方法都已定义,仅需自己实现load_bactchLayerSetup相关的函数即可。

有些继承自此方法的层还定义了DataReader另外起个线程来读数据,其实没什么必要,因为load_batch就已经是在单独的线程里的。

方法2:直接继承Layer和InternalThread来实现

需要实现InternalThread里的InternalThreadEntry方法,并定义数据缓冲队列供使用。这个线程里的方法来调用一些方法实现文件输入并放在队列里供Layer的Forward方法取用。这个方法等于自己实现了一遍BasePrefetchingDataLayerLayerSetup函数最后启动这个线程即可。

附:各输入层的继承关系



python argparse 要点

写个需要参数的python脚本时,这个argparse模块就是相当顺手的,已默认支持常用的参数书写形式。

比如:

parser = argparse.ArgumentParser(prog='PROG')
parser.add_argument('--foo')
parser.add_argument('-b','--bar')
# 那么下面的结果都是一样
# PROG --foo 2 --bar 3
parser.parse_args(['--foo','2', '--bar', '3'])
# PROG --fo 2 -b 3
parser.parse_args(['--fo','2', '-b', '3']) # 支持长参数只书写前面能区分出的部分即可,ArgumentParser的参数allow_abbrev=False可以关闭这个特性
# PROG --fo=2 -b=3
parser.parse_args(['--fo=2', '-b=3']) # 长短参数都可以使用等于号形式赋值
# PROG --foo=2 -b3
parser.parse_args(['--foo=2', '-b3']) # 短参数还支持连写形式

继续阅读python argparse 要点

给googletest输出的xml报告文件穿上花衣

给xml文件穿花衣就是XSLT的事情,那我们就来给googletest输出的xml报告穿上花衣服吧。

这是好多年前我创建的一套xslt文件,一直能用。

给googletest输出的xml报告文件穿上花衣,我这件衣服是高仿CUnit的。

我的这个googletest分支上已携带了xml-stylesheet声明,直接使用即可。

# cmake 构建googletest
cd googletest
mkdir build
cd build
cmake -Dgtest_build_samples=ON ..

# 编译googletest及样例
make

# 运行样例让它直接将结果输出到我们的xslt文件目录。
./sample1_unittest --gtest_output=xml:/home/renwei/gtest/gtxslt/sample1_ut_detail.xml

# firefox打开你就能看到穿花衣的gtest xml结果。
firefox /home/renwei/gtest/gtxslt/sample1_ut_detail.xml

这个有什么用?

用途大大的啊,自动化单元测试结果直接发布就不是孤零零的xml文件啊。那自动化单元测试有啥用?嗯,这个,少年,我该怎么回答你呢,一大篇文章。

python logging 要点

print大法固然随手就能方便写出来,但是某些情况还是用的不爽,需要更为专业的日志记录,那就看下python内置的logging模块吧。另外还有google出品的glog等第三方的日志模块,本问不涉及了。

一些基本概念

logger 记录器,记录器可以指定一个或多个handler处理器,以便同时记录到多个位置或者别的什么的区别记录。
handler 实际的处理器,logging模块里的日志最终处理者。
filter 过滤器,logger和handler上都可以指定filter。
formatter 格式化器,这个概念简单,就是决定最终输出格式的,关联与handler上。
loggers树 所有的loggers被整合成一棵以root为根的树,以定义时的名字为信息构建这个树,定义时以.符号分级。另外,某logger生成的record记录完后会自动向其父级logger传播,一直传播到root节点或遇到某级配置propagate = False的logger。
root根logger 默认会被创建的记录器。直接使用logging.warn()模块级日志函数使用的记录器就是它, basicConfig() 的设置也是针对它的, logging.getLogger() 不制定名字获得的logger也是它。

一些使用建议

配置方法

logging以子模块logging.config支持下面三种配置方式:

fileConfig 文件形式配置,文件格式就是configparser所支持的格式,基本就是ini格式。
dictConfig 直接用一个dict字典描述的配置信息,除了方便代码中直接写dict配置外,也方便导入以其他诸如json、yaml等配置文件描述的配置。
listen 启动一个socket server监听配置信息。
*basicConfig 这个basicConfig并不属于logging.config,但它可以配置最顶级的root节点。
*代码配置 对啊,我们当让可以直接在代码中创建logging中的各种对象添加到loggers树中,也可以用代码再对树中的各项予以再配置。

一些使用tips

getLogger(__name__)

使用模块方法logging.getLogger方法来获取或创建logger,多次以同一名称调用该方法获得的logger是同一个对象,不用担心重复或重新创建,安心使用。一般直接用模块名称__name__创建模块内使用的logger。

msg延迟格式化

尽量使用logging.info("info: %s",param)的方式而不是已格式化的字符串logging.info("info: {}".format(param)),因为msg可能被配置过滤掉无需输出了啊。

已内置的Handlers

FileHandler 基本的文件日志处理器。
WatchedFileHandler 它会监视文件,如果日志文件被其他程序改变,会自动关闭并重新打开文件。
RotatingFileHandler,
TimedRotatingFileHandler
以一组序列的文件名来循环记录日志,以文件大小或者时间间隔为限。
SocketHandler,
DatagramHandler
分别用TCP和UDP方式发布日志。
SMTPHandler,HTTPHandler 以SMTP和HTTP方法发布日志。
MemoryHandler 先缓存日志在内容中,后再flush到目标handler上。
NullHandler no-op。
SysLogHandler,
NTEventLogHandler
发布日志到unix syslog和NT event上。
StreamHandler 支持stdout stderr,以及其他支持write()flush()方法的所有文件或类文件对象。