その漫画自炊オタクはImageJマクロに恋をする

プログラミングを用いた、自炊漫画の画像処理

【自動解析】傾きをまっすぐに補正するためのMacro

 

f:id:yu3xx:20200624014714j:plain

傾いてしまった画像を自動解析し、まっすぐに回転・補正するためのマクロです。

  

以前公開した「ほぼ手動傾き補正マクロ」を改良し、「全自動解析」かつ「フォルダ内全画像自動処理」にしました。

imagej-jisui.hatenablog.com

 

 

//TiltCorrection_Auto.txt
//File format -> trueTitle_number.ext


setBatchMode(true);

//Do something for selected folder
showMessage("Select Open Folder");
openDir=getDirectory("Choose a Directory");
print("Processing :",openDir);
selectWindow("Log");
showMessage("Select Save Folder");
saveDir=getDirectory("Choose a Directory");
print("Save to :",saveDir);
selectWindow("Log");
list=getFileList(openDir);
wait(1000);

//Enter parameter
Dialog.create("Processing Setting");
Dialog.addNumber("maxAngle [degrees]",2);
Dialog.addNumber("tMax (kAngleSplit)",1024);
items = newArray("JPEG", "PNG");
Dialog.addRadioButtonGroup("Output format", items, 1, 2, "JPEG");
Dialog.show;
maxAngle = Dialog.getNumber();
tMax = Dialog.getNumber();
output = Dialog.getRadioButton();

//JPEG,PNG quality setting(jpeg=90,gif=-1)
quality=90;
run("Input/Output...", "jpeg=quality gif=-1 file=.csv save_column save_row");

//make parentDirectory
parentDir=saveDir+"postProc_"+getTimeStamp();
File.makeDirectory(parentDir);


//operation
var optimizedModAngle; //Rotate Angle

startTime=whatTimeNow();

for (i=0; i<list.length;i++){
	
	open(openDir+list[i]);
	
	//Progress.the second decimal place by 10000/100
	print(i+1,"/",list.length,"...Progress=",floor((i+1)/list.length*10000)/100,"%");
	
	tiltCorrection();
	
	name=getTitle();
	dotIndex=lastIndexOf(name,".");
	title=substring(name,0,dotIndex);
	underBar=lastIndexOf(title,"_");
	trueTitle=substring(title,0,underBar);

	//Output table
	Table.set("No.",i,i+1);
	Table.set("Name",i,name);
	Table.set("Angle",i,optimizedModAngle);

	//Save to SubDirectory
	subDir=parentDir+"/"+trueTitle+"/";
	if(!File.exists(subDir)){
		File.makeDirectory(subDir);
	}

	if (output == "JPEG") newname = title+".jpg";
	else if (output == "PNG") newname = title+".png";
	rename(newname);

	if (output == "JPEG") saveAs("Jpeg", subDir+newname);
	else if (output == "PNG") saveAs("PNG", subDir+newname);
	print("Save to...",subDir+newname);
	
	close(newname);

	roiManager("Deselect");
	roiManager("Delete");
}

//fin
finishTime=whatTimeNow();
print("");
if (output == "JPEG") print("JPEG quality=",quality);
print("Start Time .... ",startTime);
print("FinishTime ... ",finishTime);
Table.update;

print("oshimai");
beep();


//-----------------------------------------------------------------------------
//Define function Tilt Correction
function tiltCorrection(){

//Clear ROI Manager
roiCount=roiManager("count");
if(roiCount>0){
	roiManager("Deselect");
	roiManager("Delete");
}

//PreProcessing
name = getTitle();
run("Make Binary");
run("Size...", "width=300 constrain average interpolation=Bilinear");

run("Duplicate...", "title=translate1.jpg");
run("Duplicate...", "title=translate2.jpg");

selectWindow("translate1.jpg");
run("Translate...", "x=1 y=1 interpolation=None");

imageCalculator("Subtract create", "translate1.jpg","translate2.jpg");
selectWindow("Result of translate1.jpg");
run("Translate...", "x=-2 y=-2 interpolation=None");
imageCalculator("Subtract create", "translate2.jpg","translate1.jpg");
imageCalculator("Add create", "Result of translate1.jpg","Result of translate2.jpg");
selectWindow("Result of Result of translate1.jpg");
run("Make Binary");

//constant number
w = getWidth();
h = getHeight();
rMax = floor(pow(w*w+h*h,(1/2))); //rhoMax
//tMax = 1024; //kAngleSplit
kPI = PI/tMax;

//to shorten the calculation time
sinTable = newArray(tMax);
cosTable = newArray(tMax);

for(i=0; i<tMax; i++){
	sinTable[i] = sin(kPI*i);
	cosTable[i] = cos(kPI*i);
}

//2D_Image to 1D_Array
data = newArray(w*h);
for(y=0; y<h; y++){
	for(x=0; x<w; x++){
		pixelValue=getPixel(x,y);
		data[x+y*w]=pixelValue;
	}
}

//Hough Transform
print("Now analyzing...");
vote = newArray(tMax*2*rMax);

for(y=0; y<h; y++){
	for(x=0; x<w; x++){
		if(data[x+y*w] == 255){
			for(t=0; t<tMax; t++){
				r = floor(x*cosTable[t]+y*sinTable[t]+0.5);
				vote[t+(r+rMax)*tMax] = vote[t+(r+rMax)*tMax]+1;
			}
		}
	}
}

//decide theta_Target and rho_Target by number of votes
tTarget = 0; //theta_Target
rTarget = -1*rMax; //rho_Target

//maxAngle = 2; //threshold of modAngle
endFlag = 0;
count = 0; //number of detectedLine

arrayAngle = newArray(10); //array of modAngle
countAngle = 0; //count of Line (within maxAngle)

do{ 
//if number of detectedLine > 10, or, length of detectedLine < 60 pixel -> break
	count++;
	voteMax = 0;
	for(t=0; t<tMax; t++){
		for(r=-1*rMax; r<rMax; r++){
			if(vote[t+(r+rMax)*tMax] > voteMax){
				voteMax = vote[t+(r+rMax)*tMax];
				if(voteMax <= 60){
					endFlag = 1;
				}else{
					endFlag = 0;
				}
				tTarget = t;
				rTarget = r;
			}
		}
	}

	//draw Line
	if(tTarget != 0){
		x1 = 10;
		y1 = floor((rTarget-x1*cosTable[tTarget])/sinTable[tTarget]);
		x2 = w-10;
		y2 = floor((rTarget-x2*cosTable[tTarget])/sinTable[tTarget]);
		makeLine(x1,y1,x2,y2);
		roiManager("Add");

		//calculate modAngle
		g = (y2-y1)/(x2-x1);
		angle = atan(g)*180/PI;
		if(g >= 0) modAngle = 90-angle;
		if(g < 0) modAngle = -90-angle;
	}else{
		y1 = 10;
		x1 = floor((rTarget-y1*sinTable[tTarget])/cosTable[tTarget]);
		y2 = h-10;
		x2 = floor((rTarget-y2*sinTable[tTarget])/cosTable[tTarget]);
		makeLine(x1,y1,x2,y2);
		roiManager("Add");

		//calculate modAngle
		angle = 0;
		modAngle = 0;
	}
		
	print("...",count,":","modAngle=",modAngle,", voteMax=",voteMax);
	
	//convert to acute angle
	if(abs(modAngle) > 45) {
		if(modAngle > 0) {
			modAngle = modAngle-90;
			print(".........Convert : modAngle =",modAngle);
		}else if(modAngle < 0) {
			modAngle = modAngle+90;
			print(".........Convert : modAngle =",modAngle);
		}
	}

	//omit bigger angle [abs(modAngle) >= maxAngle]
	if(abs(modAngle) < maxAngle){
		countAngle++;
		arrayAngle[countAngle-1] = modAngle;
		print(".........Adopted");
	}
	
	//remove Lines nearby detected_Line	
	for(j=-10; j<=10;j++){
		for(i=-30; i<=30; i++){
			if(tTarget+i < 0){
				tTarget = tTarget+tMax;
				rTarget = -1*rTarget;
			}
			if(tTarget+i >= tMax){
				tTarget = tTarget-tMax;
				rTarget = -1*rTarget;
			}
			if(rTarget+j >= -1*rMax && rTarget+j < rMax){
				 vote[tTarget+i+(rTarget+rMax+j)*tMax] = 0;
			}
		}
	}
}while(endFlag == 0 && count < 10); 


//estimate optimizedModAngle by checking stdDev.P

if(countAngle == 0){
	optimizedModAngle = 0;
	print("No tilt Correction");
	selectWindow(name);
	run("Revert");
}

countInUse = countAngle;

if(countAngle > 0) a0 = arrayAngle[0];
if(countAngle > 1) a1 = arrayAngle[1];
if(countAngle > 2) a2 = arrayAngle[2];
if(countAngle > 3) a3 = arrayAngle[3];
if(countAngle > 4) a4 = arrayAngle[4];
if(countAngle > 5) a5 = arrayAngle[5];
if(countAngle > 6) a6 = arrayAngle[6];
if(countAngle > 7) a7 = arrayAngle[7];
if(countAngle > 8) a8 = arrayAngle[8];
if(countAngle > 9) a9 = arrayAngle[9];

if(countInUse == 10){
	stdMin = 10000;
	reject = 0; //candidate of exclusion 
	
	//exclude a0
	ave0 = (a1+a2+a3+a4+a5+a6+a7+a8+a9)/9;
	ave = ave0;
	stdDevP0 = pow((pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a5-ave,2)+pow(a6-ave,2)+pow(a7-ave,2)+pow(a8-ave,2)+pow(a9-ave,2))/9,(1/2));
	if(stdDevP0 < stdMin){
		stdMin = stdDevP0;
		reject = a0;
	}
	//exclude a1
	ave1 = (a0+a2+a3+a4+a5+a6+a7+a8+a9)/9;
	ave = ave1;
	stdDevP1 = pow((pow(a0-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a5-ave,2)+pow(a6-ave,2)+pow(a7-ave,2)+pow(a8-ave,2)+pow(a9-ave,2))/9,(1/2));
	if(stdDevP1 < stdMin){
		stdMin = stdDevP1;
		reject = a1;
	}
	ave2 = (a0+a1+a3+a4+a5+a6+a7+a8+a9)/9;
	ave = ave2;
	stdDevP2 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a5-ave,2)+pow(a6-ave,2)+pow(a7-ave,2)+pow(a8-ave,2)+pow(a9-ave,2))/9,(1/2));
	if(stdDevP2 < stdMin){
		stdMin = stdDevP2;
		reject = a2;
	}
	ave3 = (a0+a1+a2+a4+a5+a6+a7+a8+a9)/9;
	ave = ave3;
	stdDevP3 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2)+pow(a4-ave,2)+pow(a5-ave,2)+pow(a6-ave,2)+pow(a7-ave,2)+pow(a8-ave,2)+pow(a9-ave,2))/9,(1/2));
	if(stdDevP3 < stdMin){
		stdMin = stdDevP3;
		reject = a3;
	}
	ave4 = (a0+a1+a2+a3+a5+a6+a7+a8+a9)/9;
	ave = ave4;
	stdDevP4 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a5-ave,2)+pow(a6-ave,2)+pow(a7-ave,2)+pow(a8-ave,2)+pow(a9-ave,2))/9,(1/2));
	if(stdDevP4 < stdMin){
		stdMin = stdDevP4;
		reject = a4;
	}
	ave5 = (a0+a1+a2+a3+a4+a6+a7+a8+a9)/9;
	ave = ave5;
	stdDevP5 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a6-ave,2)+pow(a7-ave,2)+pow(a8-ave,2)+pow(a9-ave,2))/9,(1/2));
	if(stdDevP5 < stdMin){
		stdMin = stdDevP5;
		reject = a5;
	}
	ave6 = (a0+a1+a2+a3+a4+a5+a7+a8+a9)/9;
	ave = ave6;
	stdDevP6 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a5-ave,2)+pow(a7-ave,2)+pow(a8-ave,2)+pow(a9-ave,2))/9,(1/2));
	if(stdDevP6 < stdMin){
		stdMin = stdDevP6;
		reject = a6;
	}
	ave7 = (a0+a1+a2+a3+a4+a5+a6+a8+a9)/9;
	ave = ave7;
	stdDevP7 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a5-ave,2)+pow(a6-ave,2)+pow(a8-ave,2)+pow(a9-ave,2))/9,(1/2));
	if(stdDevP7 < stdMin){
		stdMin = stdDevP7;
		reject = a7;
	}
	ave8 = (a0+a1+a2+a3+a4+a5+a6+a7+a9)/9;
	ave = ave8;
	stdDevP8 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a5-ave,2)+pow(a6-ave,2)+pow(a7-ave,2)+pow(a9-ave,2))/9,(1/2));
	if(stdDevP8 < stdMin){
		stdMin = stdDevP8;
		reject = a8;
	}
	ave9 = (a0+a1+a2+a3+a4+a5+a6+a7+a8)/9;
	ave = ave9;
	stdDevP9 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a5-ave,2)+pow(a6-ave,2)+pow(a7-ave,2)+pow(a8-ave,2))/9,(1/2));
	if(stdDevP9 < stdMin){
		stdMin = stdDevP9;
		reject = a9;
	}
	if(reject == a0) a0 = a9;
	else if(reject == a1) a1 = a9;
	else if(reject == a2) a2 = a9;
	else if(reject == a3) a3 = a9;
	else if(reject == a4) a4 = a9;
	else if(reject == a5) a5 = a9;
	else if(reject == a6) a6 = a9;
	else if(reject == a7) a7 = a9;
	else if(reject == a8) a8 = a9;
	countInUse--; //and exclude a9
}


if(countInUse == 9){
	stdMin = 10000;
	reject = 0;

	ave0 = (a1+a2+a3+a4+a5+a6+a7+a8)/8;
	ave = ave0;
	stdDevP0 = pow((pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a5-ave,2)+pow(a6-ave,2)+pow(a7-ave,2)+pow(a8-ave,2))/8,(1/2));
	if(stdDevP0 < stdMin){
		stdMin = stdDevP0;
		reject = a0;
	}
	ave1 = (a0+a2+a3+a4+a5+a6+a7+a8)/8;
	ave = ave1;
	stdDevP1 = pow((pow(a0-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a5-ave,2)+pow(a6-ave,2)+pow(a7-ave,2)+pow(a8-ave,2))/8,(1/2));
	if(stdDevP1 < stdMin){
		stdMin = stdDevP1;
		reject = a1;
	}
	ave2 = (a0+a1+a3+a4+a5+a6+a7+a8)/8;
	ave = ave2;
	stdDevP2 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a5-ave,2)+pow(a6-ave,2)+pow(a7-ave,2)+pow(a8-ave,2))/8,(1/2));
	if(stdDevP2 < stdMin){
		stdMin = stdDevP2;
		reject = a2;
	}
	ave3 = (a0+a1+a2+a4+a5+a6+a7+a8)/8;
	ave = ave3;
	stdDevP3 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2)+pow(a4-ave,2)+pow(a5-ave,2)+pow(a6-ave,2)+pow(a7-ave,2)+pow(a8-ave,2))/8,(1/2));
	if(stdDevP3 < stdMin){
		stdMin = stdDevP3;
		reject = a3;
	}
	ave4 = (a0+a1+a2+a3+a5+a6+a7+a8)/8;
	ave = ave4;
	stdDevP4 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a5-ave,2)+pow(a6-ave,2)+pow(a7-ave,2)+pow(a8-ave,2))/8,(1/2));
	if(stdDevP4 < stdMin){
		stdMin = stdDevP4;
		reject = a4;
	}
	ave5 = (a0+a1+a2+a3+a4+a6+a7+a8)/8;
	ave = ave5;
	stdDevP5 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a6-ave,2)+pow(a7-ave,2)+pow(a8-ave,2))/8,(1/2));
	if(stdDevP5 < stdMin){
		stdMin = stdDevP5;
		reject = a5;
	}
	ave6 = (a0+a1+a2+a3+a4+a5+a7+a8)/8;
	ave = ave6;
	stdDevP6 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a5-ave,2)+pow(a7-ave,2)+pow(a8-ave,2))/8,(1/2));
	if(stdDevP6 < stdMin){
		stdMin = stdDevP6;
		reject = a6;
	}
	ave7 = (a0+a1+a2+a3+a4+a5+a6+a8)/8;
	ave = ave7;
	stdDevP7 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a5-ave,2)+pow(a6-ave,2)+pow(a8-ave,2))/8,(1/2));
	if(stdDevP7 < stdMin){
		stdMin = stdDevP7;
		reject = a7;
	}
	ave8 = (a0+a1+a2+a3+a4+a5+a6+a7)/8;
	ave = ave8;
	stdDevP8 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a5-ave,2)+pow(a6-ave,2)+pow(a7-ave,2))/8,(1/2));
	if(stdDevP8 < stdMin){
		stdMin = stdDevP8;
		reject = a8;
	}
	
	if(reject == a0) a0 = a8;
	else if(reject == a1) a1 = a8;
	else if(reject == a2) a2 = a8;
	else if(reject == a3) a3 = a8;
	else if(reject == a4) a4 = a8;
	else if(reject == a5) a5 = a8;
	else if(reject == a6) a6 = a8;
	else if(reject == a7) a7 = a8;
	countInUse--;
}

if(countInUse == 8){
	stdMin = 10000;
	reject = 0;

	ave0 = (a1+a2+a3+a4+a5+a6+a7)/7;
	ave = ave0;
	stdDevP0 = pow((pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a5-ave,2)+pow(a6-ave,2)+pow(a7-ave,2))/7,(1/2));
	if(stdDevP0 < stdMin){
		stdMin = stdDevP0;
		reject = a0;
	}
	ave1 = (a0+a2+a3+a4+a5+a6+a7)/7;
	ave = ave1;
	stdDevP1 = pow((pow(a0-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a5-ave,2)+pow(a6-ave,2)+pow(a7-ave,2))/7,(1/2));
	if(stdDevP1 < stdMin){
		stdMin = stdDevP1;
		reject = a1;
	}
	ave2 = (a0+a1+a3+a4+a5+a6+a7)/7;
	ave = ave2;
	stdDevP2 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a5-ave,2)+pow(a6-ave,2)+pow(a7-ave,2))/7,(1/2));
	if(stdDevP2 < stdMin){
		stdMin = stdDevP2;
		reject = a2;
	}
	ave3 = (a0+a1+a2+a4+a5+a6+a7)/7;
	ave = ave3;
	stdDevP3 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2)+pow(a4-ave,2)+pow(a5-ave,2)+pow(a6-ave,2)+pow(a7-ave,2))/7,(1/2));
	if(stdDevP3 < stdMin){
		stdMin = stdDevP3;
		reject = a3;
	}
	ave4 = (a0+a1+a2+a3+a5+a6+a7)/7;
	ave = ave4;
	stdDevP4 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a5-ave,2)+pow(a6-ave,2)+pow(a7-ave,2))/7,(1/2));
	if(stdDevP4 < stdMin){
		stdMin = stdDevP4;
		reject = a4;
	}
	ave5 = (a0+a1+a2+a3+a4+a6+a7)/7;
	ave = ave5;
	stdDevP5 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a6-ave,2)+pow(a7-ave,2))/7,(1/2));
	if(stdDevP5 < stdMin){
		stdMin = stdDevP5;
		reject = a5;
	}
	ave6 = (a0+a1+a2+a3+a4+a5+a7)/7;
	ave = ave6;
	stdDevP6 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a5-ave,2)+pow(a7-ave,2))/7,(1/2));
	if(stdDevP6 < stdMin){
		stdMin = stdDevP6;
		reject = a6;
	}
	ave7 = (a0+a1+a2+a3+a4+a5+a6)/7;
	ave = ave7;
	stdDevP7 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a5-ave,2)+pow(a6-ave,2))/7,(1/2));
	if(stdDevP7 < stdMin){
		stdMin = stdDevP7;
		reject = a7;
	}
	
	if(reject == a0) a0 = a7;
	else if(reject == a1) a1 = a7;
	else if(reject == a2) a2 = a7;
	else if(reject == a3) a3 = a7;
	else if(reject == a4) a4 = a7;
	else if(reject == a5) a5 = a7;
	else if(reject == a6) a6 = a7;
	countInUse--;
}

if(countInUse == 7){
	stdMin = 10000;
	reject = 0;

	ave0 = (a1+a2+a3+a4+a5+a6)/6;
	ave = ave0;
	stdDevP0 = pow((pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a5-ave,2)+pow(a6-ave,2))/6,(1/2));
	if(stdDevP0 < stdMin){
		stdMin = stdDevP0;
		reject = a0;
	}
	ave1 = (a0+a2+a3+a4+a5+a6)/6;
	ave = ave1;
	stdDevP1 = pow((pow(a0-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a5-ave,2)+pow(a6-ave,2))/6,(1/2));
	if(stdDevP1 < stdMin){
		stdMin = stdDevP1;
		reject = a1;
	}
	ave2 = (a0+a1+a3+a4+a5+a6)/6;
	ave = ave2;
	stdDevP2 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a5-ave,2)+pow(a6-ave,2))/6,(1/2));
	if(stdDevP2 < stdMin){
		stdMin = stdDevP2;
		reject = a2;
	}
	ave3 = (a0+a1+a2+a4+a5+a6)/6;
	ave = ave3;
	stdDevP3 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2)+pow(a4-ave,2)+pow(a5-ave,2)+pow(a6-ave,2))/6,(1/2));
	if(stdDevP3 < stdMin){
		stdMin = stdDevP3;
		reject = a3;
	}
	ave4 = (a0+a1+a2+a3+a5+a6)/6;
	ave = ave4;
	stdDevP4 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a5-ave,2)+pow(a6-ave,2))/6,(1/2));
	if(stdDevP4 < stdMin){
		stdMin = stdDevP4;
		reject = a4;
	}
	ave5 = (a0+a1+a2+a3+a4+a6)/6;
	ave = ave5;
	stdDevP5 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a6-ave,2))/6,(1/2));
	if(stdDevP5 < stdMin){
		stdMin = stdDevP5;
		reject = a5;
	}
	ave6 = (a0+a1+a2+a3+a4+a5)/6;
	ave = ave6;
	stdDevP6 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a5-ave,2))/6,(1/2));
	if(stdDevP6 < stdMin){
		stdMin = stdDevP6;
		reject = a6;
	}
	
	if(reject == a0) a0 = a6;
	else if(reject == a1) a1 = a6;
	else if(reject == a2) a2 = a6;
	else if(reject == a3) a3 = a6;
	else if(reject == a4) a4 = a6;
	else if(reject == a5) a5 = a6;
	countInUse--;
}

if(countInUse == 6){
	stdMin = 10000;
	reject = 0;

	ave0 = (a1+a2+a3+a4+a5)/5;
	ave = ave0;
	stdDevP0 = pow((pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a5-ave,2))/5,(1/2));
	if(stdDevP0 < stdMin){
		stdMin = stdDevP0;
		reject = a0;
	}
	ave1 = (a0+a2+a3+a4+a5)/5;
	ave = ave1;
	stdDevP1 = pow((pow(a0-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a5-ave,2))/5,(1/2));
	if(stdDevP1 < stdMin){
		stdMin = stdDevP1;
		reject = a1;
	}
	ave2 = (a0+a1+a3+a4+a5)/5;
	ave = ave2;
	stdDevP2 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a3-ave,2)+pow(a4-ave,2)+pow(a5-ave,2))/5,(1/2));
	if(stdDevP2 < stdMin){
		stdMin = stdDevP2;
		reject = a2;
	}
	ave3 = (a0+a1+a2+a4+a5)/5;
	ave = ave3;
	stdDevP3 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2)+pow(a4-ave,2)+pow(a5-ave,2))/5,(1/2));
	if(stdDevP3 < stdMin){
		stdMin = stdDevP3;
		reject = a3;
	}
	ave4 = (a0+a1+a2+a3+a5)/5;
	ave = ave4;
	stdDevP4 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a5-ave,2))/5,(1/2));
	if(stdDevP4 < stdMin){
		stdMin = stdDevP4;
		reject = a4;
	}
	ave5 = (a0+a1+a2+a3+a4)/5;
	ave = ave5;
	stdDevP5 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a4-ave,2))/5,(1/2));
	if(stdDevP5 < stdMin){
		stdMin = stdDevP5;
		reject = a5;
	}
	
	if(reject == a0) a0 = a5;
	else if(reject == a1) a1 = a5;
	else if(reject == a2) a2 = a5;
	else if(reject == a3) a3 = a5;
	else if(reject == a4) a4 = a5;
	countInUse--;
}

if(countInUse == 5){
	stdMin = 10000;
	reject = 0;

	ave0 = (a1+a2+a3+a4)/4;
	ave = ave0;
	stdDevP0 = pow((pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a4-ave,2))/4,(1/2));
	if(stdDevP0 < stdMin){
		stdMin = stdDevP0;
		reject = a0;
	}
	ave1 = (a0+a2+a3+a4)/4;
	ave = ave1;
	stdDevP1 = pow((pow(a0-ave,2)+pow(a2-ave,2)+pow(a3-ave,2)+pow(a4-ave,2))/4,(1/2));
	if(stdDevP1 < stdMin){
		stdMin = stdDevP1;
		reject = a1;
	}
	ave2 = (a0+a1+a3+a4)/4;
	ave = ave2;
	stdDevP2 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a3-ave,2)+pow(a4-ave,2))/4,(1/2));
	if(stdDevP2 < stdMin){
		stdMin = stdDevP2;
		reject = a2;
	}
	ave3 = (a0+a1+a2+a4)/4;
	ave = ave3;
	stdDevP3 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2)+pow(a4-ave,2))/4,(1/2));
	if(stdDevP3 < stdMin){
		stdMin = stdDevP3;
		reject = a3;
	}
	ave4 = (a0+a1+a2+a3)/4;
	ave = ave4;
	stdDevP4 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2))/4,(1/2));
	if(stdDevP4 < stdMin){
		stdMin = stdDevP4;
		reject = a4;
	}
	
	if(reject == a0) a0 = a4;
	else if(reject == a1) a1 = a4;
	else if(reject == a2) a2 = a4;
	else if(reject == a3) a3 = a4;
	countInUse--;
}


if(countInUse == 4){
	stdMin = 10000;
	reject = 0;

	ave0 = (a1+a2+a3)/3;
	ave = ave0;
	stdDevP0 = pow((pow(a1-ave,2)+pow(a2-ave,2)+pow(a3-ave,2))/3,(1/2));
	if(stdDevP0 < stdMin){
		stdMin = stdDevP0;
		reject = a0;
	}
	ave1 = (a0+a2+a3)/3;
	ave = ave1;
	stdDevP1 = pow((pow(a0-ave,2)+pow(a2-ave,2)+pow(a3-ave,2))/3,(1/2));
	if(stdDevP1 < stdMin){
		stdMin = stdDevP1;
		reject = a1;
	}
	ave2 = (a0+a1+a3)/3;
	ave = ave2;
	stdDevP2 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a3-ave,2))/3,(1/2));
	if(stdDevP2 < stdMin){
		stdMin = stdDevP2;
		reject = a2;
	}
	ave3 = (a0+a1+a2)/3;
	ave = ave3;
	stdDevP3 = pow((pow(a0-ave,2)+pow(a1-ave,2)+pow(a2-ave,2))/3,(1/2));
	if(stdDevP3 < stdMin){
		stdMin = stdDevP3;
		reject = a3;
	}
	
	if(reject == a0) a0 = a3;
	else if(reject == a1) a1 = a3;
	else if(reject == a2) a2 = a3;
	countInUse--;
}


if(countInUse == 3){
	stdMin = 10000;
	reject = 0;

	ave0 = (a1+a2)/2;
	ave = ave0;
	stdDevP0 = pow((pow(a1-ave,2)+pow(a2-ave,2))/2,(1/2));
	if(stdDevP0 < stdMin){
		stdMin = stdDevP0;
		reject = a0;
	}
	ave1 = (a0+a2)/2;
	ave = ave1;
	stdDevP1 = pow((pow(a0-ave,2)+pow(a2-ave,2))/2,(1/2));
	if(stdDevP1 < stdMin){
		stdMin = stdDevP1;
		reject = a1;
	}
	ave2 = (a0+a1)/2;
	ave = ave2;
	stdDevP2 = pow((pow(a0-ave,2)+pow(a1-ave,2))/2,(1/2));
	if(stdDevP2 < stdMin){
		stdMin = stdDevP2;
		reject = a2;
	}
	
	if(reject == a0) a0 = a2;
	else if(reject == a1) a1 = a2;
	countInUse--;
}

if(countInUse == 2){
	optimizedModAngle = a0;
	if(abs(a0) > abs(a1)) optimizedModAngle = a1;
}

if(countAngle == 1){
	optimizedModAngle = a0;
}


//Rotate
if(countAngle != 0){
	selectWindow(name);
	run("Revert");

	//fill background with white
	setBackgroundColor(255, 255, 255);

	run("Rotate... ", "angle=optimizedModAngle grid=1 interpolation=Bicubic fill");
	print("Rotated by [",optimizedModAngle,"] degrees");

	//reset background color (black)
	setBackgroundColor(0, 0, 0);
}

close("translate1.jpg");
close("translate2.jpg");
close("Result of translate1.jpg");
close("Result of translate2.jpg");
close("Result of Result of translate1.jpg");

roiManager("Show None");

} //function end

//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
//Define function getTimeStamp

function getTimeStamp(){
	getDateAndTime(year,month,dayOfWeek,dayOfMonth,hour,minute,second,msec);
	timeStamp="string";
	strYear=""+year;
	month=month+1;
	if(month<10){
		strMonth="0"+month;

	}else{
		strMonth=""+month;
	}
	if(dayOfMonth<10){
		strDayOfMonth="0"+dayOfMonth;
	}else{
		strDayOfMonth=""+dayOfMonth;
	}
	if(hour<10){
		strHour="0"+hour;
	}else{
		strHour=""+hour;
	}
	if(minute<10){
		strMinute="0"+minute;
	}else{
		strMinute=""+minute;
	}
	if(second<10){
		strSecond="0"+second;
	}else{
		strSecond=""+second;
	}
	timeStamp=strYear+strMonth+strDayOfMonth+"_"+strHour+strMinute+strSecond;
	return timeStamp;
}
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
//Define function whatTimeNow

function whatTimeNow(){
	getDateAndTime(year,month,dayOfWeek,dayOfMonth,hour,minute,second,msec);
	stringTime="string";
	strYear=""+year;
	month=month+1;
	if(month<10){
		strMonth="0"+month;

	}else{
		strMonth=""+month;
	}
	if(dayOfMonth<10){
		strDayOfMonth="0"+dayOfMonth;
	}else{
		strDayOfMonth=""+dayOfMonth;
	}
	if(hour<10){
		strHour="0"+hour;
	}else{
		strHour=""+hour;
	}
	if(minute<10){
		strMinute="0"+minute;
	}else{
		strMinute=""+minute;
	}
	if(second<10){
		strSecond="0"+second;
	}else{
		strSecond=""+second;
	}
	stringTime=strYear+"/"+strMonth+"/"+strDayOfMonth+"_"+strHour+":"+strMinute+":"+strSecond;
	return stringTime;
}

//-----------------------------------------------------------------------------

 

アルゴリズムの概要

① 前処理でダウンサイズ・二値化した画像を、右に1pixel、下に1pixel移動させる。(元画像-移動画像)と(移動画像-元画像)を加算することで線画を作成する。

Hough変換により画像に含まれる直線を推定する。

③ 採用する角度の限度値(maxAngle)と外れ値除外(後述)により、画像の傾きを示すと予想される直線の傾き(回転角度:optimizedModAngle)を得る。

画像を回転させ、保存する。

⑤ 回転角度の一覧表を出力する。

 

アルゴリズムの考え方は、以下のサイトを参考にさせていただきました。

 

Hough変換による画像からの直線や円の検出:CodeZine(コードジン)

やさしいハフ(Hough)変換講座 - Qiita

画像処理で自炊書籍画像を読みやすく加工する 初級編 - Qiita

 

補正の例

 

f:id:yu3xx:20200527235329j:plain

f:id:yu3xx:20200527235334j:plain

 画像引用:泉朝樹, 見える子ちゃん, 単行本第1巻, 10ページ, KADOKAWA, 2019

 

見える子ちゃん 1 (MFC)

見える子ちゃん 1 (MFC)

 

クリックするとAmazon商品ページ 

 

使い方

 

マクロを起動し、補正をかけたいフォルダを選択するだけでOK。

 

基本的にコマ割の直線を認識して傾き補正がかかります。

コマ割のないページの認識は難しいですが、回転角度の限度値 (maxAngle)外れ値認識のおかげで、とんでもない角度になることは少ないように思います。

処理後に出力される「傾き補正角度の一覧表」を見ながら、傾き補正の確認をすると間違いないと思います。

 

外れ値認識について

検出された直線のうち、角度があまりに大きいもの(maxAngle 以上。デフォルトでは2°)は除外するようにしています。

さらにその後、他の直線の傾向とは外れたもの(外れ値)を取り除くことで、正しいであろう画像の傾きを求めています。「外れ値検出アルゴリズム」の流れは以下のとおりです。

 

① 求めた直線(最大10本)の傾きを用意する。

② 順番に1つずつ省いて、それぞれの標準偏差を求める。

③ 2のうち最も標準偏差の低くなった時(ばらつきが少ない時)に省いた傾きを外れ値と判断し、除去する。

④ 2〜3の過程を、傾きの数が2つになるまで繰り返す。

⑤ 残った傾きのうち、傾きが小さい方の角度を補正に用いる。

 

 

 

マクロの起動方法

①ImageJ上部タブの[Plugins]→[New]→[Macro]で起動したエディタに、記事の一番上のコードをコピペしてtxtファイル(TiltCorrection_Auto.txt)を作成・保存する。 

 

②保存したファイルをImageJフォルダ内の[plugins]フォルダにしまう。

このとき、[plugins]フォルダの中に新たに適当な名前のフォルダを作って、その中にしまってもOKです。ここでは仮に「自炊」というフォルダにtxtファイルを突っ込んだとします。

 

③一度ImageJを再起動すると、マクロがインストールされ、起動準備OK。

 

④上部タブ[Plugins]→[自炊]→[TiltCorrection Auto]でマクロが実行されます。

 

 

imagej-jisui.hatenablog.com

 

 

 

 

ライセンスなんかは一切無いので、ぜひぜひ自由に使ってみてください!

  

imagej-jisui.hatenablog.com