细节之锤
浏忙绪绪揉揉快要抽筋的小拇指,看着塞得满满的浏忙日记,恍然大悟:哥玩的不是Emacs,是烂笔头!
Click on the section headings below to reveal/hide the contents
Substance: WordPress Style: Rachel
Posted Sep 9th, 2011 by yo2WaterLin    / Permalink /

最近折腾得够呛,天天写代码,都没时间更新博客了。

总结一下最近碰到的两个问题,写到中文 Wiki 里了,希望对大家有点帮助。

  1. OpenCV 在 Ubuntu 11.04 平台上的编译

    最近要迁移大把工作成果到 Linux 平台,即要在 Linux 下大量地折腾与 OpenCV 相关的代码。很久没有在 Linux 上折腾了,时过境迁,同样碰到了不少问题,写成日志《OpenCV 各种安装错误汇总》

  2. Berkeley DB 数据库的操作还是蛮有技巧的,如果需要把内存里结构比较复杂的数据写到 Berkeley DB 里,我当然拥有我的技巧,以把 IplImage 数据写入到 Berkeley DB 为例,介绍我对这一技术的理解。
Posted Sep 5th, 2011 by yo2WaterLin    / Permalink /

在 Visual Studio 2008 MFC 工程中,利用 Berkeley DB 来构建数据存储引擎时,在编译 db.h 文件时出现编译错误,错误提示内容如下:

错误    3       error C2143: 语法错误 : 缺少"}"(在"("的前面)        e:\water\berkeleydb\include\db.h        1226

微软的 MSDN 上有对 error C2143 的编译器错误进行解释,不过基本上没有太多可读性、可借鉴性,大意应该是一些宏定义、命名出错等。

最后,还是通过万能的 Google 大神找到了解答方法。错误的原因是 DB_TYPE, DB_UNKNOWN 类型已经在 MFC 系统头文件中被定义过,解决办法之一是在 db.h 中定义 DB_TYPE, DB_UNKNOWN 的语句之前加上如下语句即可:

#ifdef DB_UNKNOWN
#undef DB_UNKNOWN
#endif
#ifdef DBTYPE
#undef DBTYPE
#else
#define DBTYPE BDBTYPE
#endif

看来,C 和 C++ 混在一块,命名、类型定义真是一个大问题。以后碰到类似的问题,也可以采用类似的解决办法。

参考资料:

  1. 解决在vs2008的mfc工程中编译BerkeleyDB出错问题
  2. VS2005中的MFC程序使用BerkeleyDB
  3. MSDN Visual Studio 2010 Compiler Error C2143
Posted Aug 24th, 2011 by yo2WaterLin    / Permalink /

今天为这件事情折腾了一天!

我用 Java 写的中间件,会根据用户的需要,启动一个子进程,这个进程是用 C++ 写的,正好用到了 Log4Cxx 来记录日志以便分析。这个进程,单独从控制终端运行,没有任何问题;但是一旦用 Java 启动子进程的方式来启动,则这个子进程刚一启动就阻塞不动了,然后大概等上几十分钟到两个小时不等,该子进程则又能顺利执行下去。

第一次碰到这样的问题,很怪异,经过反复测试及万能的 Google 帮忙,找到Java调用外部程序挂起原因,Log4Cxx 原来是罪魁祸首,因为它重定向了大量的日志信息到控制台,由于 Java 进程没有清空程序写到缓冲区的内容,结果导致程序一直在等待。

解决这个问题有两个办法:

  1. 按上文中的说法,在遇到 Java 启动外部程序而导致线程阻塞的时候,可以考虑使用两个线程来同时清空 process 获取的两个输入流;
  2. 直接把 Log4Cxx 重定向到控制台的日志消息给取消掉,这个可以通过 Log4Cxx 的配置文件来实现

问题搞定了,世界又变得很美好了!

Posted Aug 22nd, 2011 by yo2WaterLin    / Permalink /

在写客户端的时候,经常要传输一些文件,有一些服务器就是用 FTP 来搭建的,这个时候,如何用客户端来发起 FTP 网络连接呢?

如果是用 MFC 来发起 FTP 文件传输请求,和使用 MFC 来发起 HTTP 请求类似,非常简单。理论基础可以仔细阅读 MSDN 上的官方文档 Steps in a Typical FTP Client Application浏忙绪绪在这里,就贴上我自己写的代码,与大家分享一下。

样例代码如下:

/*
 * 通过 ftp 方式来拿文件
 * @param  relativePath, 服务器保存该视频摘要文件夹、相对于 ftp 跟路径的相对路径
 * @notice relativePath 应该是类似于 /test 这样相对于 ftp 根路径的相对路径
 *                      应该以 / 开头
 * @param  savePath, 从远程服务器下载文件后,在本地保存的文件夹路径
 */
BOOL CGetRemoteFile::GetFtpfiles(CString relativePath, CString savePath)
{
    //通过 http GET 协议来获取并保存文件
    CString remotefile   = L"";
    CString saveFilename = L"";
CInternetSession session; session.SetOption(INTERNET_OPTION_CONNECT_TIMEOUT, 1000 * 20); session.SetOption(INTERNET_OPTION_CONNECT_BACKOFF, 1000); session.SetOption(INTERNET_OPTION_CONNECT_RETRIES, 1);
/* 替换成相应的 FTP 用户名和密码 */ CFtpConnection* pFtp = session.GetFtpConnection( m_strServerIP, m_strUsername, m_strPassword, (INTERNET_PORT)m_iSeverPort);
bool result = pFtp->SetCurrentDirectory(relativePath);
if (!result) { AfxMessageBox(L"Can't get ftp file"); return false; }
CFtpFileFind finder(pFtp); BOOL bFind = finder.FindFile( L"*", INTERNET_FLAG_RELOAD ); while (bFind) { bFind = finder.FindNextFile(); CString strPath = (LPCTSTR)finder.GetFileURL(); CString strFile = (LPCTSTR)finder.GetFileName(); CString ftpPath; ftpPath.Format(L"正在下载文件:%s/%s\n", strPath, strFile); TRACE(ftpPath);
CInternetFile* pFile = pFtp->OpenFile(strFile);//注意,这里只需要传文件名 int len = pFile->GetLength(); char buf[2000]; int numread;
CString filepath; filepath.Format(L"%s\\%s", savePath, strFile); TRACE(L"保存为文件%s",filepath);
CFile myfile( filepath, CFile::modeCreate|CFile::modeWrite|CFile::typeBinary); while ((numread = pFile->Read(buf,sizeof(buf))) > 0) { strFile += buf; myfile.Write(buf, numread); } myfile.Close();
pFile->Close(); delete pFile; }
session.Close();
return true; }
Posted Aug 5th, 2011 by yo2WaterLin    / Permalink /

Emgu CV 是 OpenCV 跨平台的 C# 封装包,主要是为了方便在 C# 里使用 OpenCV 的库函数,下载和安装都很简单,新建一个 C# 控制窗口程序后,Hello World 例子代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing; using Emgu.CV; using Emgu.Util; using Emgu.CV.Structure;
namespace OpenCVTester { class Program { static void Main(string[] args) { //The name of the window String win1 = "Test Window";
//Create the window using the specific name CvInvoke.cvNamedWindow(win1);
//Create an image of 400x200 of Blue color using (Image<Bgr, Byte> img = new Image<Bgr, byte>(400, 200, new Bgr(255, 0, 0))) { //Create the font MCvFont f = new MCvFont(Emgu.CV.CvEnum.FONT.CV_FONT_HERSHEY_COMPLEX, 1.0, 1.0); //Draw "Hello, world." on the image using the specific font img.Draw("Hello, world", ref f, new Point(10, 80), new Bgr(0, 255, 0));
//Show the image CvInvoke.cvShowImage(win1, img.Ptr); //Wait for the key pressing event CvInvoke.cvWaitKey(0); //Destory the window CvInvoke.cvDestroyWindow(win1); } } } }

这里需要注意的是,如果用 Visual Studio 2008 来调试上面的代码,则记得要把 OpenCV 相关的动态链接库放到你测试工程的这个目录里:

\bin\Debug

否则在跑到下面这句代码

CvInvoke.cvNamedWindow(win1);

的时候,就会弹出一个莫名其妙的出错框,提示如下错误:

未处理的"System.TypeInitializationException"类型的异常出现在 OpenCVTester.exe 中。
其他信息: "Emgu.CV.CvInvoke"的类型初始值设定项引发异常

看来,C#调试时的程序动态链接库读取位置,还比较特殊呀~~

Posted Aug 2nd, 2011 by yo2WaterLin    / Permalink /

OpenCV2.1 生成视频选择编码的 bug

最近使用 OpenCV2.1 来生成视频,在 Windows 下碰到这样的问题:不管我用什么样的编码,都没有办法直接生成视频,并且会导致程序崩溃。代码如下:

_writer = cvCreateVideoWriter( video.c_str(),
                               CV_FOURCC('X','V','I','D'),
                               _fps,
                               cvSize(frameW,frameH),
                               isColor );

但是,如果我把编码换上 -1 选项来手工选取视频格式,则能顺利生成视频。代码如下:

_writer = cvCreateVideoWriter( video.c_str(),
                               -1,
                               _fps,
                               cvSize(frameW,frameH),
                               isColor );

这个应该是 OpenCV2.1 里的一个 Bug,换成 OpenCV2.2 就没有这个问题了。

OpenCV2.2 的 Visual Studio 2008 问题

官方的 OpenCV2.2 是在 Visual Studio 2010 下编译的二进制包,所以,当你把 OpenCV2.2 嵌入到 Visual Studio 2008 里,可以正常编译相关的代码,但是一运行,一定会提示找不到动态链接库 msvcp100d.dll 或是 msvcr100d.dll

这个时候,你可以把这两个 Visual Studio 2010 的动态链接库拷贝到编译好的可执行文件目录里。但是,最好的方式,是你在 Visual Studio 2008 里重新编译一下 OpenCV2.2

OpenCV 读取视频帧与 DirectShow 读取视频的步调不一致

有一些视频,我用 OpenCV 跳转到指定的时间,和用 DirectShow 跳转到视频指定的时间,理论上来说,应该是一样的。可是,在我这里,偏偏就出现了跳转视频位置不同的情况。而且,原始视频越长,这种误差越大。

是什么原因呢?

仔细研究了一下,我用 OpenCV 读取参数,会显示是 29 帧/秒,大约有 276168 帧;但是用 DirectShow 解码后显示为 30 帧/秒,计算后约为 268946 帧。

解码后的度量时间标准不同,也就导致了上述误差。但是,真实的原因是什么呢?

Posted Jul 17th, 2011 by yo2WaterLin    / Permalink /

在搬出大学城之前,我发现了 Netpas 的一个 Bug。

一般来讲,你用 Netpas 这种 VPN 来上网,都有一个带宽的峰值,一旦你超过这个峰值,服务器就会提示,让你注意流量或是直接把你给断开。

而 Netpas 在弹出超出流量的提示后,你不去点"确定",你还可以继续保持高流量的使用它。这个不知道是不是一个 Bug,还是在用户没有"确认"之前,Netpas 是要保护用户的权益?

不管怎么样,庆幸已经脱离了校园网!

http://cn.waterlin.org/cnblog-images/2011/netpas-bug.png

Posted Jul 14th, 2011 by yo2WaterLin    / Permalink /

在 Linux 下,如果你写好了自己的动态链接库,需要在其它程序里调用,则需要让这些程序能找到这些动态链接库。如果设置不对,会出现类似如下的错误:

test: error while loading shared libraries: libexampleso.so.0: cannot open shared object file: No such file or directory

这是因为没有把动态链接库的安装路径(例如说是 /usr/local/lib )放到变量 LD_LIBRARY_PATH 里。

这时,可以用命令 export 来临时测试确认是不是这个问题:

export LD_LIBRARY_PATH=/usr/local/lib

在终端里运行上面这行命令,再运行这个可执行文件,如果运行正常就说明是这个问题。

接下来的问题是:以上做法,只是临时设置变量 LD_LIBRARY_PATH ,下次开机,一切设置将不复存在;如何把这个值持续写到 LD_LIBRARY_PATH 里呢?

我们可以在 ~/.bashrc 或者 ~/.bash_profile 中加入 export 语句,前者在每次登陆和每次打开 shell 都读取一次,后者只在登陆时读取一次。我的习惯是加到 ~/.bashrc 中,在该文件的未尾,可采用如下语句来使设置生效:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib

修改完后,记得关掉当前终端并重新打开一个新的终端,从而使上面的配置生效。

Posted Jul 12th, 2011 by yo2WaterLin    / Permalink /

我的笔记本电脑因为之前分区的原因,没有办法安装双系统,导致调试 Linux 程序诸多不便。如果你也有这种情况,或是有很多事情都需要同时在 Windows 和 Linux 下处理,可以和我一样,在本地装一个 VirtualBox 虚拟 Ubuntu 环境,配好相应的开发环境后,从事 Linux 代码的开发与调试工作。

主要思路

把这个 VirtualBox 里的 Linux 当成服务器,用 putty 连上去,慢慢调试;本地文件,可以用 Eclipse 编辑好,通过文件共享或是 SCP 的方式,传到虚拟机里的 Linux 服务器里。

主要配置步骤

  1. 根据VirtualBox 与宿主机器有四种网络设置方式的介绍,把 VirtualBox 里的 Linux 上网方式设置成为 Bridged networking 模式,这样,你在宿主机器里就可以把这个虚拟机当成普通服务器;
  2. 根据你自己的需要,配置 Linux 服务器环境;
  3. 配置并开启虚拟机里 Ubuntu 的 SSH 服务

    详细的 SSH 配置方法可以看一下在 Ubuntu 下开户 ssh 服务里的介绍。

  4. 同步文件到 Ubuntu 服务器

    可以用 WinSCP 同步 Windows 系统里的文件到 Ubuntu 里,当然,也可以采用 FTP 或是 SFTP 之类的软件来同步文件。

PS:如果你觉得用 WinSCP 来同步文件略显麻烦,你可以在 Windows 里设置一个供 Ubuntu 使用的共享目录,然后你直接把东西扔到这个目录,就可以在 Ubuntu 里访问相关的文件;当然,使用这种方法,你要小心保护好你的文件,不要轻易地删除共享目录里的文件。

出于模拟及方便的角度,我依然推荐使用 WinSCP 的方式来同步文件。

Posted Jul 9th, 2011 by yo2WaterLin    / Permalink /

用 OpenCV 来读取视频,经常需要从指定帧序号的片断开始读取,这个时候,就需要用 cvSetCaptureProperty 结合参数 CV_CAP_PROP_POS_FRAMES 来设定,例子代码如下:

bool PickSomeFrames(const char* filename, int start, int end, char* savePath)
{
    CvCapture* capture = cvCaptureFromAVI(filename);  //读取视频文件

    cvSetCaptureProperty(capture, CV_CAP_PROP_POS_FRAMES, start);
int count = start; while( cvGrabFrame(capture) && count <= end ) { IplImage* pFrame = cvRetrieveFrame(capture);// 获取当前帧 char test[100]; sprintf(test,"%s\\%d%s",savePath,count,".jpg"); cvSaveImage(test,pFrame);
count++; }
cvReleaseCapture(&capture);
return false; }

以上这种方法,支持用多线程的方式,来同时读写视频不同帧序号开始的片断。以上这些代码,在采用多线程来加速视频处理时,特别有用。