自己制作字体的试验(自制字体使用FontForge等)


  
首先推荐公益性项目:flexifont 北大研究生办的。

然后介绍自己所做的(点击这里下载)(注意其中svg_2/有更新,在本文最后):

先是写了个简单检查ttf文件的ttf_check.c(在ttf_check/文件夹下),主要参照《Windows图形编程》([美]Feng Yuan)和Apple网站的 TrueType Reference Manual及 微软网站的otf spec。写了个sh脚本用它检查Windows上的.ttf、.TTF文件,发现有些如中文黑体等中的字形 可以完全没有hinting(grid-fitting)指令的。所以自制字体就准备不用hinting指令了,因为听说hinting很难。

然后试用FontForge,我下载了Windows版本的在Win10上安装。
网上得知要导入.svg矢量图形文件(有用.eps文件的,不过不熟),所以先下载了sourceforge网站上的矢量化软件 potrace(后来见到Windows上的FontForge安装文件夹中带有一个potrace.exe),在cygwin环境下编译(配置时用了--disable-zlib), 看了一下--help,使用--svg选项将二值的位图转化为.svg文件,在FontForge编辑字形时即可导入。SVG文件是一种可读的文本XML文件, 可以自己直接编辑,可以直接用浏览器浏览。逐渐了解 SVG用了《SVG精髓》(第2版)一书。与potrace同类型的有autotrace,但我没用过。

二值的位图可以用Windows的画图保存生成,自动化时我选用ImageMagick的convert命令来生成,如:
	            convert in.jpg  -monochrome  out.jpg
    
看了一下,位图位深是1 bit的。

FontForge用Generate选项生成.ttf文件,Windows上右键点击该文件即可选用安装,缺省字体名类似为“Untitiled”,新建一word文档中输入 导入字形对应的文字,选中后改用试验字体,即可看到效果。

然后试验FontForge的脚本,试验结果发现用不多的几个命令即可用于基本实现自动化。(为了试验自动运行,我将fontforge.exe和所有 .dll都拷到一个文件夹fontforge_test/下)。要注意需解决两个问题:
第一个:FontForge缺省新建的文件只包含几十个字形的大概是 Latin-1的字体,脚本中New或Open文件之后需用
	     Reencode("gb2312"); 或 Reencode("unicode");
	
第二个,需要在fontforge运行的图形界面上点击Element |- Font Info |- OS/2 |- Charsets |- MS Code Pages里复选 (缺省仅为1252,Latin-1)936,Simplified Chinese。注意是复选,点击时需按住Ctrl键。这一点我现在还没找到脚本方案。 这一点我是在看网上视频:
           如何制作一款属于自己的字体?(FontForge字体制作教程)
	
中学到的。
创建文件脚本如test1.pe:
		#!/cygdrive/d/temp2/fontforge

		New();
		Select("C");
		Import("test5.svg");
		Generate("test.ttf");	
	

编辑文件脚本如test6.pe:
		#!/cygdrive/d/temp2/fontforge

		Open("test.ttf")
		#Reencode("gb2312");
		Select(0u4e00);
		Import("test6.svg")
		Select(0u4e01);
		Import("test5.svg");
		Select("C");
		Import("test5.svg");
		Select(0u56fd);
		Import("test2.svg");
		Generate("test.ttf");	
    
.pe后缀名我想或许来自FontForge的原名pfaedit的缩写。

然后在网上搜汉字的unicode码表,写unicode和汉字字形的相互对应程序(为了自动化)。逐渐了解到汉字的Unicode范围大概从0x4e00到 0x9FEA,最后选了网上的一篇文章(《汉字一、二级字库的汉字与unicode编码(十六进制)对照表,按照unicode的顺序排列》 - zhoukejun的专栏 - CSDN博客),写成文件夹unicode_Chinese/下的translate2.c文件,是一、二级汉字的Unicode对应,有6763个字。

然后开始试验自动化的流程,从扫描或拍照开始,所以文件夹命名为photo/。
(1)扫描或拍照
我是试验用手机拍照,在photo/1scan_or_snap/下,得到的是.jpg文件;
(2)转成BMP
在photo/2scan_bmp/下,为减少上传文件大小,我将其删掉了,生成即可;
(3)为每个字生成BMP
我试验时是用Windows画图剪切得到每个字的BMP,使用其相应的汉字命名,在photo/3scan_char_bmp/下;
(4)将每个位图转为二值位图
这因为potrace需要二值位图输入。写了photo/1bit.sh使用ImageMagick的convert批量转换,生成的BMP在photo/4bit1_bmp/下;
		#!/bin/bash

		INDIR="3scan_char_bmp"

		OUTDIR="4bit1_bmp"


		for i in `ls $INDIR/*.bmp` ; do
		   echo $i
		   FILENAME=`basename $i`
		   OUTFILE="$OUTDIR""/""$FILENAME"
		   echo $OUTFILE
		   
		   convert $i -monochrome $OUTFILE
		done
	
	
(5)用potrace批量转成SVG文件
写了photo/2svg.sh使用potrace批量转换,生成的SVG在photo/5svg/下;
		#!/bin/bash


		INDIR="4bit1_bmp"
		OUTDIR="5svg"


		POTRACE=/cygdrive/d/temp/potrace/potrace-1.15/src/potrace
		if ! [ -e $POTRACE  ] ; then
			echo "$POTRACE doesn't exist"
			exit
		fi


		for i in `ls $INDIR/*.bmp` ; do
		   echo $i
		   FILENAME=`basename $i  |  sed -e s/\.bmp/\.svg/g`
		   OUTFILE="$OUTDIR""/""$FILENAME"
		   echo $OUTFILE
		   
		   $POTRACE  $i  --svg -o $OUTFILE 
		done
	
	
这时写了photo/3check_unicode2.sh来检查一下文件名中的汉字是否能对应查询到下一步fontforge脚本所需要的Unicode码:
		#!/bin/bash

		INDIR="5svg"

		for i in `ls $INDIR/*.svg` ; do
		   #echo $i
		   FILENAME=`basename $i  |  sed -e s/\.svg//g`
		   UNICODE=`./translate2 to ${FILENAME}`
		   echo "$FILENAME  $UNICODE"    
		done
	
如果没有问题,就用下面写的photo/4gen_pfaedit.sh来生成批量导入已给汉字的fontforge脚本(起名为fontforge_script.pe):
photo\4gen_pfaedit.sh:
		#!/bin/bash

		INDIR="5svg"

		OUTFILE="fontforge_script.pe"
		rm -f $OUTFILE

		echo "#!/cygdrive/d/temp2/fontforge"    >>  $OUTFILE
		echo ""    >>  $OUTFILE
		echo "Open(\"test.ttf\");"    >>  $OUTFILE

		for i in `ls $INDIR/*.svg` ; do
		   #echo $i
		   FILENAME2=`basename $i`
		   FILENAME=`basename $i  |  sed -e s/\.svg//g`
		   UNICODE=`./translate2 to ${FILENAME}`
		   echo "$FILENAME  $UNICODE"    
		   
		   
		   UNICODE="0u""$UNICODE"
		   
		   SELECT="Select(""$UNICODE"");"
		   echo $SELECT     >>  $OUTFILE
		   
		   
		   IMPORT="Import(\"""${FILENAME2}""\");"
		   echo $IMPORT     >>  $OUTFILE
		done

		echo "Generate(\"test.ttf\");"    >>  $OUTFILE

	

(6)最后要生成.ttf了
用很简单的fontforge脚本photo/new.pe来新建一个.ttf文件test.ttf:
		#!/cygdrive/d/temp2/fontforge

		New();
		Reencode("gb2312");
		Generate("test.ttf");
	
	
生成后别忘了上面提出的第二个问题,Charsets要加入选项936 Simplified Chinese。也可待会编辑后再加,但总归要加。似乎这是 我还必须手工、没能自动化的一步。

建立文件夹6ttf/,拷贝上述第(5)步得到的所有.svg文件,刚新建的test.ttf,和fontforge_script.pe脚本, 运行该脚本,即打开ttf文件,批量导入汉字相应的SVG,并产生编辑后的ttf文件。该脚本大致如下:
		#!/cygdrive/d/temp2/fontforge

		Open("test.ttf");
		Select(0u7a0b);
		Import("程.svg");
		Select(0u6863);
		Import("档.svg");
		Select(0u7b2c);
		Import("第.svg");
		Select(0u4e01);
		Import("丁.svg");
		Select(0u9ad8);
		Import("高.svg");
		Select(0u56fd);
		Import("国.svg");
                    …………
		Select(0u6587);
		Import("文.svg");
		Select(0u4e0b);
		Import("下.svg");
		Select(0u5e8f);
		Import("序.svg");
		Select(0u4e00);
		Import("一.svg");
                    …………
		Import("载.svg");
		Select(0u4e2d);
		Import("中.svg");
		Select(0u684c);
		Import("桌.svg");
		Generate("test.ttf");
	
	


安装该ttf,新建word文档编辑,效果图如下:
五号:
        
    
三号:
        
    
小四号:
        
    
小五号:
        
    


自动化流程跑通了。但对字体图像的效果不太满意。效果不好的原因在于BMP图像有太多噪点。所以想是否能自己生成比较光顺的SVG图像。 这就要了解一点SVG使用的Bezier曲线了。看potrace生成的是三次Bezier曲线,所以考虑三次的就可以了。

以前在大学学过《数值分析》,老师讲过Bezier曲线和B样条,当时还努力看过《计算几何》的相关内容,但现在具体都不记得了,再看这些书 也对照不起以前的理解。一些计算机图形学的书倒是有所涉及,如《计算机图形学原理及算法教程(Visual.Cpp版)》(和青芳)等。还有人推荐 陈元琰的《计算机图形学实用技术》和蔡士杰的《计算机图形学》。
Bezier曲线及B样条参考网址:
  • Bezier、B样条曲线曲面
  • B样条曲线(B-spline Curves)
  • Introduction to Geometric Modeling
  • B样条
  • 插值与样条
  • 计算机图形学--------充分理解B样条曲线
  • B-spline Curves 学习之B样条曲线定义(4)
  • B-spline Curves 学习之B样条基函数的定义与性质(2)
  • B-spline Curves 学习之前言
  • B-spline Basis Functions: Computation Examples
  • Bezier曲线、B样条和NURBS的基本概念
  • Hermit曲线、Bezier曲线、B样条曲线有什么关系?有什么区别?各自的应用范围?
  • 数学图形(1.47)贝塞尔(Bézier)曲线
  • 样条之贝塞尔(Bezier)
  • 贝塞尔曲线
  • Bezier曲线及实现代码
  • Bezier曲线原理及实现代码(c++)
  • Bezier曲线原理
  • 贝塞尔曲线——cubic-bezier详解
  • Bezier曲线的构建
  • 用Bezier绘制圆滑曲线
  • 贝塞尔曲线理论
  • 基于三次Bezier原理的曲线拟合算法C++与OpenCV实现
  • 平滑曲线生成:贝塞尔曲线拟合
  • Bezier曲线和BSpline曲线的拟合问题
  • 谈谈贝塞尔曲线
  • 怎样确定 Bezier 曲线的控制点
  • 三次Beizer曲线拟合算法
  • 分段连续三次Bezier曲线控制点的构造算法

  • 直接使用了上文之中《基于三次Bezier原理的曲线拟合算法C++与OpenCV实现》(这里有一个我保存的 拷贝)中的数据点拟合的生成三次Bezier曲线控制点的代码,在我写的试验文件 svg/文件夹下的gen_control_point.cpp中:
    		void get_control_points(double x0, double y0, double x1, double y1, double x2, double y2,  
    			double& p1x, double& p1y, double& p2x, double& p2y, double t)  
    		{  
    			double d01 = sqrt(pow(x1 - x0, 2) + pow(y1 - y0, 2));  
    			double d12 = sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2));  
    		  
    			double fa = t * d01 / (d01 + d12);  
    			double fb = t * d12 / (d01 + d12);  
    		  
    			p1x = x1 - fa * (x2 - x0);  
    			p1y = y1 - fa * (y2 - y0);  
    			p2x = x1 + fb * (x2 - x0);  
    			p2y = y1 + fb * (y2 - y0);  
    		  
    			return;  
    		}  
    	
    	
    算法试验完成,我就开始写从二值位图到SVG矢量字形的编辑制作工具的代码了,采用浏览器中的PHP(Windows10上安装Apache httpd 2.4.38和PHP 7 64位)。 这就是svg_2/文件夹下的1.php、2.php、3.php,这三个PHP文件处理的输入是in.bmp、in.svg,输出是out.svg。in0.svg是一个空的模板,in.svg可以从拷贝 in0.svg开始,生成的out.svg如要进入下一步处理可将其重命名为in.svg。 在2.php中我将上述生成控制点的C++代码改成了javascript代码。
    比如处理以下位图:
            
        
    下图是2.php正在运行的画面:
            
        
    生成效果图如:out.svg




    (制作试验过程中记录的一些草稿)
            
            
            
            
            
            
            
            
            
            
            
            
        

    svg_2/已有更新为svg_2_2/(更新了2.php),下载在这里






    以上费了九牛二虎之力,后来(三年后)才发现potrace带有一个强大的工具mkbitmap,可以将位图作预处理成适合potrace转换成SVG的位图。以下是一些试验:
    	mkbitmap/potrace
    
    
    	一、三   mkbitmap -b 5 -o test0.pbm  in0.bmp
    								 -b 1不行
    									  2
    								 -b 3
    								 -b 4
    								 (b 是blur,滤波的意思)
    								 -s  1  scale(放大)
    								 -s  2
    
    	图    mkbitmap -n      -b 3  -o 03o.pbm  03.bmp
    	      mkbitmap -f 10   -b 3  -o 03o.pbm  03.bmp
    
    
    	设    mkbitmap -f 10   -b  3   -o 04o.pbm   04.bmp
    
    
    	载    mkbitmap  -f 20  -b 3  -o  06o.pbm     06.bmp	
    	


    于是改写了以上流程中的shell脚本: 1bit.sh:
    		#!/bin/bash
    
    		INDIR="../1_cut"
    
    		OUTDIR="../2_convert"
    
    
    		for i in `ls $INDIR/*.jpg` ; do
    		   echo $i
    		   FILENAME=`basename $i`
    		   BMPFILENAME=`echo ${FILENAME} | sed -e 's/\(.*\)\.jpg/\1\.bmp/g'`
    		   OUTFILE="$OUTDIR""/""$BMPFILENAME"
    		   echo $OUTFILE
    		   
    		   #convert $i -monochrome $OUTFILE
    		   convert $i -auto-level  $OUTFILE
    		   #convert $i -auto-level -negate $OUTFILE
    		   #convert $i -threshold 50% -monochrome $OUTFILE
    		   #convert $i -threshold 75% -monochrome $OUTFILE
    		done
    	
    	
    2mkbitmap.sh:
    		#!/bin/bash
    
    		INDIR="../2_convert"
    
    		OUTDIR="../3_mkbitmap"
    
    
    		for i in `ls $INDIR/*.bmp` ; do
    		   echo $i
    		   FILENAME=`basename $i | sed -e 's/\.bmp/\.pbm/g'`
    		   OUTFILE="$OUTDIR""/""$FILENAME"
    		   echo $OUTFILE
    		   
    		   mkbitmap -b 3 -f 10 -o ${OUTFILE}  $i
    		done
    	
    	
    3potrace.sh:
    		#!/bin/bash
    
    		INDIR="../3_mkbitmap"
    
    		OUTDIR="../4_svg"
    
    
    		for i in `ls $INDIR/*.pbm` ; do
    		   echo $i
    		   FILENAME=`basename $i | sed -e 's/\.pbm/\.svg/g'`
    		   OUTFILE="$OUTDIR""/""$FILENAME"
    		   echo $OUTFILE
    		   
    		   potrace $i --svg -o ${OUTFILE} 
    		done
    	
    	
    4check_unicode2.sh:
    		#!/bin/bash
    
    
    		INDIR="../4_svg"
    
    
    
    		for i in `ls $INDIR/*.svg` ; do
    		   #echo $i
    		   FILENAME=`basename $i  |  sed -e s/\.svg//g`
    		   UNICODE=`./translate2 to ${FILENAME}`
    		   echo "$FILENAME  $UNICODE"    
    		   
    		   
    		done
    
    	




    现在所剩最难的,就是第一步:将笔记图片中的汉字文字裁切出来,这一步能作一些自动化吗?后来我使用了强大的openCV库。
    cv_contours_4_indent.cpp:
    
    	#include <iostream>
    	#include <opencv2/opencv.hpp>
    	#include <opencv2/core/core.hpp>
    	#include <opencv2/highgui/highgui.hpp>
    	#include <opencv2/imgproc/imgproc.hpp>
    	#include <vector>
    	
    	using namespace std;
    	using namespace cv;
    
    
    	bool is_rect_in_range(Rect rect, Rect Range)
    	{
    		if (    rect.x >= Range.x && rect.y >= Range.y 
    			 && rect.x + rect.width <= Range.x + Range.width 
    			 && rect.y + rect.height <= Range.y + Range.height
    		   )
    			return true;
    
    		return false;
    	}
    
    	bool is_rect_intersect(Rect rect, Rect Range)
    	{
    		Rect inter = rect & Range;
    		if (inter.width > 0 && inter.height > 0)
    			return true;
    
    		return false;
    	}
    
    	int main(int argc, char **argv)
    	{
    		Mat img;
    
    		int line_no = 0;
    		if (argc >= 2)
    			img = imread(argv[1]);
    		else {
    			cout << "Usage: ./cv_contours file" << endl;
    			return -1;
    		}
    
    		if (argc == 3)
    			line_no = atoi(argv[2]);
    
    
    		Mat imgCopy(img);
    		Mat imgCopy2(img.clone());
    		//Mat imgCopy2(img);
    
    		Mat imgGray;
    
    		cvtColor(img, imgGray, COLOR_BGR2GRAY);
    
    		//imshow("Gray", imgGray);
    
    		Mat blurMat;
    
    		blur(imgGray, blurMat, Size(3, 3));
    
    		//imshow("blur", blurMat);
    
    
    		Mat dstImage;
    
    
    	//   Canny(blurMat, dstImage, 100, 200);
    		//Canny(blurMat, dstImage, 50, 200);
    
    	//   imshow("canny", dstImage);
    
    		threshold(blurMat, dstImage, 192, 255, THRESH_BINARY);
    
    		//imshow("thresh", dstImage);
    
    		vector < vector < Point >> contours;
    
    		Mat edges(dstImage);
    
    		//findContours(edges, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    		//findContours(edges, contours, RETR_LIST, CHAIN_APPROX_SIMPLE);
    		//findContours(edges, contours, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
    		findContours(edges, contours, RETR_TREE, CHAIN_APPROX_SIMPLE);
    	//   findContours(edges, contours, RETR_TREE, CHAIN_APPROX_TC89_L1);
    	//   findContours(edges, contours, RETR_TREE, CHAIN_APPROX_TC89_KCOS);
    
    		std::cout << "There are " << contours.size() << " contours" << endl;
    
    		Mat resizeImage;
    
    		int obj_total = 0;
    
    		vector < Rect > RectArray, simpleRectArray, intersectRectArray, unionRectArray;
    
    		for (int i = 0; i < contours.size(); i++) {
    			Rect rect = boundingRect(contours[i]);
    			//if(rect.area() >= 25 && rect.width >=5    && rect.height >=5 
    			//                   && rect.width <= 200 && rect.height <= 200 ){
    			if (1) {
    				if (line_no) {
    					if (rect.y >= line_no * 200 && rect.y <= (line_no * 200 + 400))
    						;
    					else
    						continue;
    				}
    
    				if (rect.x <= 300 || (rect.x >= 2900 && rect.x <= 3550) || rect.x >= 5700)
    					continue;
    
    				if (rect.area() < 16)
    					continue;
    				if (rect.height <= 6)
    					continue;
    				if (rect.width > 400)
    					continue;
    
    				obj_total++;
    
    				rectangle(imgCopy, rect, Scalar(0, 0, 255));
    				// imshow("edges", imgCopy);
    
    				RectArray.push_back(rect);
    
    				// if(i % 100 == 0){
    				//destroyAllWindows();
    				//resize(imgCopy, resizeImage, Size(), 0.2, 0.2);
    				//imshow("resize1", resizeImage);
    
    				cout << "rect.width and height are: " << rect.width << " " 
    													  << rect.height << endl;
    	#define ROI_RANGE  20
    				int xl = MAX(0, rect.x - ROI_RANGE);
    				int xr = MIN(imgCopy.cols, rect.x + rect.width + ROI_RANGE);
    				int yt = MAX(0, rect.y - ROI_RANGE);
    				int yb = MIN(imgCopy.rows, rect.y + rect.height + ROI_RANGE);
    				Rect outter(xl, yt, xr - xl, yb - yt);
    
    				Rect inner(rect.x + 1, rect.y + 1, rect.width - 2, rect.height - 2);
    	//    rectangle(imgCopy, inner , Scalar(0, 255, 0));
    	//    rectangle(imgCopy, outter, Scalar(0, 0, 255));
    
    
    				//Rect roi(rect.x, rect.y, rect.width, rect.height);
    				Rect roi(outter.x, outter.y, outter.width, outter.height);
    				//Rect roi(rect.x-2, rect.y-2, rect.width+4, rect.height+4);
    
    				//Mat roiImage =  imgCopy(roi);
    				Mat roiImage = imgCopy(outter);
    				cout << "roiImage.width and height are: " << roiImage.cols << " " 
    														  << roiImage.rows << endl;
    
    				destroyAllWindows();
    				namedWindow("roi", WINDOW_NORMAL);
    				imshow("roi", roiImage);
    
    				//if(i % 100 == 0) 
    				//    waitKey(0);
    				//else
    				//    waitKey(1000);
    				cout << i << " second" << endl;
    				//}
    
    
    			}
    		}
    
    
    		cout << "Total " << obj_total << " objects counted\n" << endl;
    		cout << "or Total " << RectArray.size() << " elements counted\n" << endl;
    
    
    
    		for (int j = 0; j < RectArray.size(); j++) {
    			bool is_inside = false;
    			for (int i = 0; i < RectArray.size(); i++) {
    				if (i == j)
    					continue;
    				if (is_rect_in_range(RectArray[j], RectArray[i])) {
    					is_inside = true;
    					break;
    				}
    
    			}
    			if (!is_inside)//只选择最外部的矩形,那些包围在别的矩形内的矩形不选
    				simpleRectArray.push_back(RectArray[j]);
    		}
    
    		//for(int i = 0; i < simpleRectArray.size(); i++){      
    		//    rectangle(imgCopy2, simpleRectArray[i], Scalar(255, 0, 0));
    		//}
    
    		cout << "simple Total " << simpleRectArray.size() << " elements counted\n" << endl;
    
    
    
    
    
    
    
    
    		for (int j = 0; j < simpleRectArray.size(); j++) {
    			bool is_intersect = false;
    			for (int i = 0; i < simpleRectArray.size(); i++) {
    				if (i == j)
    					continue;
    					//对于相交的矩形,
    				if (is_rect_intersect(simpleRectArray[j], simpleRectArray[i])) {
    					// is_intersect = true;
    					//获取它们的并
    					intersectRectArray.push_back((simpleRectArray[j] | simpleRectArray[i]));
    					break;
    				}
    
    			}
    		}
    
    		//for(int i = 0; i < intersectRectArray.size(); i++){       
    		//rectangle(imgCopy2, intersectRectArray[i], Scalar(0, 255, 0));
    		//}
    
    		cout << "intersect Total " << intersectRectArray.size() 
    								   << " elements counted\n" << endl;
    
    
    		for (int i = 0; i < intersectRectArray.size(); i++) {
    			simpleRectArray.push_back(intersectRectArray[i]);//添加到已有的数组后部
    		}
    		cout << "simple + intersect Total " << simpleRectArray.size() 
    											<< " elements counted\n" << endl;
    		for (int i = 0; i < simpleRectArray.size(); i++) {
    			rectangle(imgCopy2, simpleRectArray[i], Scalar(255, 0, 0));
    		}
    
    
    
    
    
    
    
    
    
    		for (int i = 0; i < simpleRectArray.size(); i++) {
    
    
    	#define EXTERNAL_RANGE   30
    			Rect rectSearchRange(
    								 simpleRectArray[i].x - EXTERNAL_RANGE,
    								 simpleRectArray[i].y - EXTERNAL_RANGE,
    								 simpleRectArray[i].width + 2 * EXTERNAL_RANGE, 
    								 simpleRectArray[i].height + 2 * EXTERNAL_RANGE
    								);
    
    
    			for (int j = 0; j < simpleRectArray.size(); j++) {
    				if (j == i)
    					continue;
    
    				if (is_rect_intersect(simpleRectArray[j], rectSearchRange)) {
    					//尝试生成一些矩形图像的并(不是所有的并都有意义,但以后我们可以从中手工选择)					
    					Rect union_rect = simpleRectArray[j] | simpleRectArray[i];
    					if (union_rect.width <= 200 && union_rect.height <= 200) {
    						unionRectArray.push_back(union_rect);
    						rectangle(imgCopy2, union_rect, Scalar(0, 0, 255));
    					}
    					
    				}
    			}
    		}
    		cout << "union Total " << unionRectArray.size() << " elements counted\n" << endl;
    
    
    
    
    
    
    
    
    
    
    
    		//resize(imgCopy, resizeImage, Size(), 0.2, 0.2);
    		//imshow("resize", resizeImage);
    
    	//   Rect roi_line(0, line_no*200, imgCopy.cols,400);
    	//   Mat  roi_lineImage = imgCopy(roi_line);
    		Rect roi_line(0, line_no * 200, imgCopy2.cols, 400);
    		Mat roi_lineImage = imgCopy2(roi_line);
    		destroyAllWindows();
    		imshow("roi_line", roi_lineImage);
    
    
    
    		waitKey(0);
    		return 0;
    
    		/*
    		   int x = 200,y = 200, width = img.size().width-2*x, height = img.size().height-2*y;
    		   cv::Rect roi(x,y, width, height);
    
    		   cv::Mat roiImage =  img(roi);
    		   bool isSaved = cv::imwrite("output.jpg", roiImage);
    		   if(isSaved)
    			   std::cout << "OK"   << std::endl;
    		   else
    			   std::cout << "fail" << std::endl;
    
    
    		   imshow("LOVE", img);
    
    		   waitKey(0);
    		   return 0;
    		*/
    	}
    
    	
    我在Ubuntu 22虚拟机里的编译命令:g++ -o cv_contours_4 cv_contours_4_indent.cpp `pkg-config opencv4 --cflags --libs`
    安装openCV:
    		在Ubuntu 22.04上安装OpenCV可以通过几种方式完成,包括使用预编译的包或从源代码编译。以下是使用预编译包安装OpenCV的步骤:
    
    		打开终端。
    
    		更新包列表:
    
    		sudo apt update
    
    		安装OpenCV包:
    
    		sudo apt install libopencv-dev python3-opencv
    
    		如果你需要编译OpenCV源代码或者需要最新版本,你可以从源代码编译OpenCV。以下是编译OpenCV的基本步骤:
    
    		安装依赖项:
    
    		sudo apt update
    		sudo apt install build-essential cmake git pkg-config libgtk-3-dev \
    		libavcodec-dev libavformat-dev libswscale-dev libv4l-dev \
    		libxvidcore-dev libx264-dev libjpeg-dev libpng-dev libtiff-dev \
    		gfortran openexr libatlas-base-dev python3-dev python3-numpy \
    		libtbb2 libtbb-dev libdc1394-22-dev
    
    		克隆OpenCV的GitHub仓库:
    
    		git clone https://github.com/opencv/opencv.git
    
    		克隆OpenCV贡献模块的GitHub仓库:
    
    		git clone https://github.com/opencv/opencv_contrib.git
    
    		创建一个构建目录并进入该目录:
    
    		mkdir build
    		cd build
    
    		运行cmake配置命令:
    
    		cmake -D CMAKE_BUILD_TYPE=RELEASE \
    		-D CMAKE_INSTALL_PREFIX=/usr/local \
    		-D INSTALL_C_EXAMPLES=ON \
    		-D INSTALL_PYTHON_EXAMPLES=ON \
    		-D OPENCV_GENERATE_PKGCONFIG=ON \
    		-D OPENCV_EXTRA_MODULES_PATH=~/opencv_contrib/modules \
    		-D BUILD_EXAMPLES=ON ..
    
    		编译和安装:
    
    		make -j$(nproc)
    		sudo make install
    
    		配置Python环境:
    
    		sudo ldconfig
    
    		这些步骤会安装OpenCV及其Python绑定。如果你需要其他特定版本或者更详细的配置选项,请参考OpenCV官方文档。
    	
    	

    下载在这里。 这样应该能省去很多的工作量。下一步待做的,就是将笔记图片中的汉字文字都裁切出来,把整个流程跑通。
      

    More powered by