今天给大家带来的是我大二上短学期参与的一个C++项目,当时的项目成员有我、隔壁班一同学、我一东北室友以及一个好康的转系学姐嘻嘻,这项目名字是我那逗比室友非要加的,不仅是项目名字,就连软件的名字都充满了哲♂学气息:
这个项目的灵感来源是我室友衣柜上贴的《楚门的世界》的海报,就下面这个:
一、开发平台及辅助工具
开发平台:Visual Studio
辅助库:OpenCV
界面开发:MFC
爬虫:PYcharm
二、软件功能
我们的预期目标如上面提到的海报所示,功能框图如下:
我们将软件的工作分为4个流程:
- 元素图片处理(已在图库建设中同时完成):将元素图片剪切为统一大小的图片
- 目标图片处理:将目标图片划为多个小块,小块大小和元素图片处理后大小一样。
- 图片匹配:根据图片的平均像素值来匹配目标图片所有小区块和处理后元素图片。
- 图片拼接:根据图片匹配结果,将元素图片按序拼接,得到拼接成的目标图片。
四、方案实现
1. 元素图片处理
给出框图如下:
a. 爬虫
这里需要提到的是,因为选取待拼图对象的随机性,我们并不能做到采用一系列具有关联性的图片作为图片库,因此我们采取的是按照颜色作为关键词在百度图片中进行爬图:
keyword = [
'红色', '绿色', '黄色','鸨色','赤白橡','油色','绀桔梗','踯躅色','肌色','伽罗色','花色','桜色',
'青丹','瑠璃色','蔷薇色','灰茶','莺色','琉璃绀','韩红','茶色','利久色','绀色','珊瑚色','桦茶色',
'媚茶','青蓝','红梅色','枯茶','蓝海松茶','杜若色','桃色','焦茶','青钝','胜色','薄柿','柑子色',
'抹茶色','群青色','薄红梅','杏色','黄緑','铁绀','曙色','蜜柑色','苔色','蓝铁','红色','褐色',
'若草色','青褐','赤丹','路考茶','若緑','褐返','红赤','饴色''萌黄','藤纳戸','臙脂色','丁子色','苗色'
]
爬虫用的python写的,也不难,这里只贴出代码,不作过多解释:
"""根据搜索词下载百度图片"""
import re
from pip._vendor import requests
import sys
import urllib
N = 1000
#keyword = '殷红' # 关键词, 改为你想输入的词即可, 相当于在百度图片里搜索一样
def getPage(keyword, page, n):
page = page * n
keyword = urllib.parse.quote(keyword, safe='/')
url_begin = "http://image.baidu.com/search/flip?tn=baiduimage&ie=utf-8&word="
url = url_begin + keyword + "&pn=" + str(page) + "&gsm=" + str(hex(page)) + "&ct=&ic=0&lm=-1&width=0&height=0"
return url
def get_onepage_urls(onepageurl):
try:
html = requests.get(onepageurl).text
except Exception as e:
# print(e)
pic_urls = []
return pic_urls
pic_urls = re.findall('"objURL":"(.*?)",', html, re.S)
return pic_urls
def down_pic(pic_urls,n):
"""给出图片链接列表, 下载所有图片"""
for i, pic_url in enumerate(pic_urls):
try:
pic = requests.get(pic_url, timeout=1)
string = str(i + 1 + n*100 ) + '.jpg'
with open('E:\\团队项目\\短学期\\Record\\Record\\pic2\\'+string, 'wb') as f:
f.write(pic.content)
print('成功下载第%s张图片' % (str(i + 1 + n*100)))
except Exception as e:
print('下载第%s张图片时失败: ' % (str(i + 1 + n*100)))
#print(e)
continue
keyword = [
'红色', '绿色', '黄色','鸨色','赤白橡','油色','绀桔梗','踯躅色','肌色','伽罗色','花色','桜色',
'青丹','瑠璃色','蔷薇色','灰茶','莺色','琉璃绀','韩红','茶色','利久色','绀色','珊瑚色','桦茶色',
'媚茶','青蓝','红梅色','枯茶','蓝海松茶','杜若色','桃色','焦茶','青钝','胜色','薄柿','柑子色',
'抹茶色','群青色','薄红梅','杏色','黄緑','铁绀','曙色','蜜柑色','苔色','蓝铁','红色','褐色',
'若草色','青褐','赤丹','路考茶','若緑','褐返','红赤','饴色''萌黄','藤纳戸','臙脂色','丁子色','苗色'
]
if __name__ == '__main__':
# keyword = '' # 关键词, 改为你想输入的词即可, 相当于在百度图片里搜索一样
for j in range(0,62):
page_begin = 0
page_number = 10
image_number = 4
all_pic_urls = []
while 1:
if page_begin > image_number:
break
print("第%d次请求数据", [page_begin])
url = getPage(keyword[j], page_begin, page_number)
onepage_urls = get_onepage_urls(url)
page_begin += 1
print(page_begin)
all_pic_urls.extend(onepage_urls)
down_pic(list(set(all_pic_urls)),j)
爬虫结果:
b. 元素图片归类
这里采取的方法是获取归一化尺寸后的元素图片的RGB平均值,以像素值5为单位步长进行分类。
//裁剪图像
void Resize_Cut(cv::Mat& FileImg, cv::Mat& Resize_Cut_Img)
{
CvSize sorSize;//原图尺寸
cv::Mat SourceImg = FileImg.clone();
double qw = 1.0*SourceImg.cols / W;
double qh = 1.0*SourceImg.rows / H;
sorSize.width = (qw > qh) ? (qh*W) : SourceImg.cols;
sorSize.height = (qw > qh) ? SourceImg.rows : (qw*H);
cv::Mat Cut_Img = SourceImg(Rect(0, 0, sorSize.width, sorSize.height));
Resize_Cut_Img = cv::Mat::zeros(ele_h, ele_w, CV_8UC3);
resize(Cut_Img, Resize_Cut_Img, Resize_Cut_Img.size());
}
//写入最终索引库
void Write()
{
char imgname1[100];
char imgname2[100];
for (int m = 1; m <= n; m++)
{
sprintf_s(imgname1, "change2/%d.jpg", m);
//按照图像名称读取图像
cv::Mat M_img = imread(imgname1);
IplImage *I_img = &IplImage(M_img);
CvScalar avgChannels = cvAvg(I_img);
int avgB = avgChannels.val[0];//获取B通道平均值,下同
int avgG = avgChannels.val[1];
int avgR = avgChannels.val[2];
int R = avgR - (avgR % 5);
int G = avgG - (avgG % 5);
int B = avgB - (avgB % 5);
sprintf_s(imgname2, "E:\\团队项目\\短学期\\Combine\\MFCApplication1\\colorstore\\%d %d %d.jpg", R, G, B);
cv::imwrite(imgname2, M_img);//保存照片到指定文件夹
}
}
分好类的图片库:
2.目标图片与元素图片进行匹配
在准备好拼图元素图片库之后,只需要对目标图片进行切割,每一个小区域(ROI区域)如同上述准备元素库中获取RGB均值的方法一样,再获取一下当前ROI区域的RGB均值,然后再去元素库中调取相应的RGB图片,再按切割的行号和列号命名存储到文件夹中,下一步合成拼图只需到该文件夹中按序号调取执行拼图即可。
//目标图片与元素图片进行匹配
void Match(string aim_pic_path)
{
char imgname[100];//拼图库命名
char imgname1[100];//初始元素图片库命名
char imgname2[100]; //处理元素图片库命名
int i, j;
cv::Mat M_OrgImg = imread(aim_pic_path);//读取目标图片
if (!M_OrgImg.empty())
{
int ccc = M_OrgImg.cols;
int hhh = M_OrgImg.rows;
int Width = ccc - ccc % 20;
int Height = hhh - hhh % 20;
resize(M_OrgImg, M_OrgImg, Size(Width*L, Height*L), 0, 0);//适当裁剪原目标图片
for (i = 1; i <= L * Height / ele_h; i++)
{
for (j = 1; j <= L * Width / ele_w; j++)
{
cv::Mat M_imageROI = M_OrgImg(Rect((j - 1) * ele_w, (i - 1) * ele_h, ele_w, ele_h));//截取ROI区域
IplImage *I_imageROI = &IplImage(M_imageROI);//Mat 转 IplImage
CvScalar avgChannels = cvAvg(I_imageROI);
int avgB = avgChannels.val[0];//获取ROI区域的RGB值
int avgG = avgChannels.val[1];
int avgR = avgChannels.val[2];
int R = avgR - (avgR % 5);
int G = avgG - (avgG % 5);
int B = avgB - (avgB % 5);
sprintf_s(imgname1, ele_pic_path, R, G, B);
cv::Mat M_Cut_Img = imread(imgname1);//读取相同RGB命名的图片
if (M_Cut_Img.empty())//当图片库中找不到匹配的图时,直接截取当前图片进行拼图
{
M_Cut_Img = M_imageROI;
}
sprintf_s(imgname2, "order\\%d-%d.jpg", i, j);//以行列顺序存储图片
cv::imwrite(imgname2, M_Cut_Img);
}
}
}
}
匹配结果示例:
3. 拼图
做完以上工作,我们只需要将排好序的图片进行合并,就可以得到完整的拼图图片了。在OpenCV中,提供了两个矩阵合并的函数,一个是横向合并hconcat(),另一个是纵向合并vconcat(),而图像在OpenCV中可以以Mat类型存储,Mat本身就是一个矩阵,因此我们可以很方便的利用这两个函数将我们的拼图碎片进行拼接:
//拼图碎片拼接
void Combine(std::string aim_pic_path)
{
/*cv::Mat replace = imread("white.jpg");*/
cv::Mat all;
cv::Mat end;
char a[100];
char b[100];
cv::Mat M_OrgImg = imread(aim_pic_path);
int ccc = M_OrgImg.cols;
int hhh = M_OrgImg.rows;
int Width = ccc - ccc % 20;
int Height = hhh - hhh % 20;
for (int i = 1; i <= L * Height / ele_h; i++)
{
sprintf_s(a, "order\\%d-%d.jpg", i, 1);
all = imread(a);//读取图片
for (int j = 2; j <= L * Width / ele_w; j++)
{
sprintf_s(b, "order\\%d-%d.jpg", i, j);
cv::Mat next = imread(b);
hconcat(all, next, all);//图像矩阵横向合并
}
if (i == 1)
{
end = all;
}
else
{
vconcat(end, all, end); //图像矩阵纵向合并
}
}
cv::imwrite("Finalimg.jpg", end);//保存拼接图片
}
拼图结果示例:
4.美化
为了让我们的拼图效果看起来更好,我们采取了将原图与拼图按照一定的透明度进行叠加的方法,利用OpenCV中的addWeighted()函数可以很好的实现这一要求。
//原图与拼图叠加
void Overlapping(std::string aim_pic_path)
{
cv::Mat end = imread("check1.jpg");
cv::Mat overlapping;
cv::Mat M_OrgImg = imread(aim_pic_path);
int ccc = M_OrgImg.cols;
int hhh = M_OrgImg.rows;
int Width = ccc - ccc % 20;
int Height = hhh - hhh % 20;
resize(M_OrgImg, M_OrgImg, Size(Width*L, Height*L), 0, 0);
addWeighted(M_OrgImg, 0.55, end, 0.45, 0.0, overlapping);//将原图和拼接图片按一定透明度比例叠加
cv::imwrite("overlapping.jpg", overlapping);
}
叠加后的结果:
5.界面
最后呢,由于本身这是个课程设计,在设计完功能之后呢,还是得象征性地做个界面出来hhh,这部分是漂亮学姐用MFC搞的,好在我们当时写的都是封装好了的函数,所以和界面部分的对接也不算麻烦,这里界面就贴出来看看就好了,丑是挺丑的,不过基本实现了我们的功能要求hhhhh
-- 写在最后面的话 --
这个项目的话,现在看来的话,其实确实没什么技术难度,主要还是基础语法和OpenCV的基础运用,不过对于大二短学期的项目来说也差不多了,要说改进的话,我暂时想到的有:
(1)拼图图片与目标图片的匹配不用局限于RGB这一标准,可以加入图像线条、色块甚至以某一主题作为拼图图片来源,进行更加精准的匹配;
(2)在拼图过程中减少图像的存储和读取操作,采用一边匹配一边拼图的策略,能够有效减少拼图所需时间;
(3)图片库的扩充。现在我们这个项目的图像库有262500张图片,按照(255/5)^3 = 132651的分类要求按道理来说已经足够了,但是在实际我们分类的时候仍然存在一些RGB不存在的情况,如果考虑(1)的要求的话,这些库远远不够;
(4)图片库的存储问题。几十万的图片库如何进行存储,直接打包倒是方便,不过一个项目下来4、500M也顶不住啊,弄个服务器真香。
项目完整的工程本来是想git的,结果试了发现文件太大了传不上去,有需要的再留言吧。