从大一的C++实训,究试过了图片查重
后来写了:后缀修正/图片分类
最后一步就是把整个系统合并起来
大一到大三都很散漫,浪费了不少时间,大四考研,现在在等最后的分数线和调剂,不过通过的几率很渺茫
毕设又不是很紧迫,所以还是先完成自己的心愿好了,虽然一直很想将这个作为毕设,但是又怕达不到代码量
三月以来这些天,熟悉linux,搭建LAMP架构,第一次接触php代码(最常的错误漏$),重新用php实现了那时候C++写的图片特征码的代码(现在看回以前的作品,太简单了)
图片特征码的算法写的太简单了,不过先搭建整个系统,最后才换用高级的算法
首先有个测试图库,916个文件,主要是jpg,少量gif,有txt,180+MB。
然后预先遍历整个图库,写入MySQL数据库(数据库设计在文章后面)
用刚学的php配合HTML写一个简单的首页,提供上传功能,可以查看测试库里面的所有图片。
再要跳转到另外一个页面,显示搜索结果,包括上传的图片及其特征码,匹配到相似的图片及其相关的所有内容(从数据库中提取)
现在的进度,数据库内容还没有填充,预计一个上午可以,新页面没有写,估计要一个下午加上一个晚上,毕竟是新的东西
2014年03月06日,上午决定暂停php语法学习,下午重写了以前C++代码、熟悉php语言,晚上如果听宣讲会回来有时间就完成数据库的设计
数据库设计,一个表就够,功能简单嘛,数据库名:pic_db
数据库表设计:
一、要包含的数据,本地全路径(含文件名),图片类型(严格匹配后缀名),md5,SHA,图片特征码phash,大小,尺寸(宽高),修改日期(入库时间)
二、键:先添加一个唯一的编号作为主键,本地全路径是唯一的(文件系统已经默认满足),因为只有一个表,不需要外键
三、建表SQL语句:
CREATE TABLE picinfo
(
id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
path text NOT NULL,
tpye varchar(10),
size_w int,
size_h int,
size_kb int,
modify_time DATE,
md5 varchar(255),
sha varchar(255),
phash varchar(255)
)
如果不用AUTO_INCREMENT,插入第一条默认是0,插入第二天如果还不输入id就会主键重复。如果有这个属性,从1开始自增。
删除的条目,再插入,不会填坑,id继续自增。
如果想删除全部表项可以用 truncate table 表名; 保留表的结构,连id都自动恢复从1开始。如果用同样功能的delete from table 表名,id不会重新从1计数。而且会引入很多记录,是逐条执行,没有truncate那么快。这个可以用来测试功能的时候用。
数据库操作实例:
连接mysql,关闭数据库:
$con = mysql_connect("localhost","root","123");
mysql_close($con);
连接数据库:
$db = mysql_select_db("pic_db",$con);
如果只连一个数据库,不要db也可以;但是据说要同时操作多个数据库的时候就要记下资源号。
执行语句:
mysql_query($sql);
批量插入:
当然可以单句多次插入:
insert into pic_db(path,phash) value("asdf","asdf");
我的老笔记本测试插入900+行要5s。
换用一句的sql语句,0s: insert into pic_db(path,pash) values("asdf","asdf"),("ew","we").....
我记得以前有句命令是说等会的所有操作都不submit,可以分次插入n句,随后执行一句submit之类的语句就行,在sqlite的时候用过。
2014-03-08 02:08更新,已经可以生成所有phash code,写入库,查询验证无误。明天写php页面……加油,快要ok了~~
测试效率: 900张左右的图片,180MB+,生成phash code和写入库要20s,比我大一C++的快多了,现在想想主要是当时先将图片压缩到了8×8,而这次是直接取点计算灰度。估计究这一步产生最多效益。
2014-03-09: 巨坑,用chrome浏览器,最简单的上传页面,没有指定字符集,chrome直接不反悔中文,firefox默认就可以,后来知道了有系统字符集/apache2字符集/页面字符集。这个网友做的实验很能说明问题:http://www.laruence.com/2008/04/17/110.html
2014-03-12:这几天都没更新,一直被url中文路径卡住。期间看了js的escape,encodeURL,看了正则,看了unicode和UTF-8。今天终于用正则十六进制形式匹配出中文,可以转义了,有些特殊符号不行。找到了php的rawurlencode,之前觉得他把'/'也转义,不适合我,后来发现,跳过这个字符就正常了。最后还是没有自己写,就用原有的好了。这个世界的轮子已经造的七七八八的了,基本不用懂橡胶相关技术的。
现在贴出代码:
第一步,先在mysql建立好,假设root用户,密码是123,数据库是pic_db,主表是picinfo.
建表
CREATE TABLE picinfo
(
id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
path text NOT NULL,
tpye varchar(10),
size_w int,
size_h int,
size_kb int,
modify_time DATE,
md5 varchar(255),
sha varchar(255),
phash varchar(255),
ones int
)
ones是用来记录phash的1的个数,现在的代码米有用上。
第二步,建立图片的信息库,setupDB.php
<?php
require_once("phash.php");
$t=time();//计时开始,单位:秒
$dirname='/home/tony/pic';//图片库的路径
$fp = fopen("temp.txt","w");
dodir($dirname,$fp);//遍历整个图片库,提取所有地址,写到temp.txt
fclose($fp);
$fp = fopen("temp.txt","r");
$con = mysql_connect("localhost","root","123");//连mysql
if(!$con){
die('could not connect:'.mysql_error());
}
$db = mysql_select_db("pic_db",$con);//选择数据库
$sql="insert into picinfo(path,phash,ones) values";
//sql语句的前半句,数据不是很大的时候,values比value快不少。数据量再大就要用本地文件插入的语法了
while($line=rtrim(fgets($fp))){//不是图片的,不插入数据库
if(NOT_SUPPORTED === ($phash_str=phash($line))
|| NOT_A_PIC === $phash_str){
continue;
}
$cnt=0;//算一下有几个1在phash中,以后可以先用1的个数来筛选,不过暂时没有这个性能需求,不增加代码复杂度
for($i=0;$i<64 ;$i++){
if($phash_str[$i] == '1'){
$cnt++;
}
}
$sql .= "(\"$line\",\"$phash_str\",\"$cnt\"),";
}
mysql_query(rtrim($sql,','));//php5.5.0说这个迟早要过时,用替换品。来源:www.php.net
mysql_close($con);
fclose($fp);
printf("elapsed time: %ds",time()-$t);//总结时间
function dodir($dirname,&$fp){
$dir=opendir($dirname);
while(($filename=readdir($dir))!==false){
if($filename=='.' || $filename=='..'){
continue;
}
if(is_dir($dirname.'/'.$filename)){
dodir($dirname.'/'.$filename,$fp);
}else{
//print($dirname.'/'.$filename."<br>");
fwrite($fp,$dirname.'/'.$filename.PHP_EOL);
}
}
}
?>
第三步,可供上传图片的首页. index.php
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<form action="showResult.php" method="post"
enctype="multipart/form-data">
<label for="file">Filename:</label>
<input type="file" name="file" id="file" />
<br />
<input type="submit" name="submit" value="Submit" />
</form>
</body>
</html>
第四步,展示搜索结果 showResult.php
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<?php
require_once("phash.php");
require_once("encodeURL.php");
$t=microtime();//计时开始,单位:秒
if ($_FILES["file"]["error"] > 0){
echo "Error: " . $_FILES["file"]["error"] . "<br />";
}
else{//显示上传的文件的基本信息
echo "Upload: " .$_FILES["file"]["name"]. "<br />";
echo "Type: " . $_FILES["file"]["type"] . "<br />";
echo "Size: " . ($_FILES["file"]["size"] / 1024) . " Kb<br />";
//echo "Stored in: " . $_FILES["file"]["tmp_name"]."<br />";
}
if(!is_uploaded_file($_FILES["file"]["tmp_name"])){
echo "illegal uploaded file<br>";
}
$src_path= $_FILES["file"]["tmp_name"];
$des_path= '/home/tony/www/upload/'.$_FILES["file"]["name"];
if(move_uploaded_file($src_path,$des_path)){
echo "Stored in: $des_path"."<br />";
}else die("<br>move failed.");
$ph = phash($des_path);//计算phash
echo "phash: $ph";
echo "<hr>";
$link = mysqli_connect("localhost","root","123","pic_db") or dir("error: ".mysqli_error($link));//连接mysql并选择数据库。新版的语法吧,还有面向对象版,这个是过程化的版本
$query = "select path,phash from picinfo";
$result = $link->query($query) or die("error: ".mysqli_error($link));
$paths = array();
$idx = 0;//全部phash取出来,逐个比对,拙劣的brute-force。 -_-||
while($row = mysqli_fetch_array($result)){
if(phashCmp($ph,$row["phash"],5)){
$paths[$idx++] = $row["path"];
}
}
for($i=0;$i<$idx;$i++){//逐个展示
$p = $paths[$i];
$p = substr($p,strpos($p,"pic"));//截取相对路径
echo $p."<br />";
printf("<img src = %s /><hr>",encodeURL($p));//对中文路径编码转义
}
printf("elapsed microtime: %ds",microtime()-$t);//总时间
?>
还有两个关键的支持文件,就是require_once()里面两个,其中phash占了大部分运行时间:
phash.php
<?php
//"$pic is not a pic<br>" <===> -1;
define("NOT_A_PIC",-1);
//"The picType is not supported." <===> =-2
define("NOT_SUPPORTED",-2);
function phash($pic){
if(!($size=getimagesize($pic))){
return NOT_A_PIC;
}
switch($size[2]){
case 1:$format="gif";break;
case 2:$format="jpeg";break;
case 3:$format="png";break;
default:return NOT_SUPPORTED;
}
$func="imagecreatefrom".$format;
$im=$func($pic);
if(!imageistruecolor($im)) {
//echo "$pic isn't true color<br>";
}
$sum=0;
for($i=0;$i<8;$i++){
for($j=0;$j<8;$j++){
$color_index=imagecolorat($im,$j*$size[0]/8,$i*$size[1]/8);
$rgb=imagecolorsforindex($im,$color_index);
$gray[$i*8+$j] = round(0.229 * $rgb['red'] + 0.587 * $rgb['green'] + 0.114 * $rgb['blue']);
$sum+=$gray[i*8+j];
}
}
$avg=$sum/64;
for($i=0;$i<64;$i++){
if($gray[$i] > $avg){
$result.="1";
}else{
$result.="0";
}
}
return $result;
}
//例如limit是5,那么如果有5个不一样,就返回false,也就是只有4个以下的才通过
function phashCmp($str1,$str2,$limit){//增加形参ones可以先判断1的个数,减少遍历。
//不过通常是不像的图片比较多,所以$cnt<$limit已经减少到很少的比较次数了
$cnt=0;
for($i=0;$i<64 && $cnt<$limit;$i++){
if($str1[$i]!=$str2[$i]){
$cnt++;
}
}
return ($cnt<$limit)?true:false;
}
?>
对中文url路径进行转码的encodeURL.php
<?php
function encodeURL($str){
$parts = explode('/',$str);
$rstr = "";
foreach($parts as $line){
$rstr .= rawurlencode($line)."/";
}
return rtrim($rstr,"/\n");
}
?>
最后要补充说明一下系统,编码:
LAMP环境:
Linux mint 16 32bits xfce
apache 2.4.6 Ubuntu
php 5.5.3
$LANG :zh_CN.UTF-8apache2:没有使用AddDefaultCharset,
可以访问的页面,可能涉及unicode编码的页面都在html的header里面指定使用utf-8
暂时应该不更新了,下次如果更新的应该是 phash算法取得进展的时候了。
最多等会补充一下我用20G测试数据时候的结果了,不敢想象如此暴力的代码在08年的奔腾酷睿上跑会怎样。。。
如果有路过的大神,给我点提示吧,看看怎么才能加快这个建立图片信息库的时间(phash算法)
最后,感谢阮一峰,最先的灵感思路就是他网站的那个图片搜索原理,完全按他的实现的。