傾いてしまった画像を自動解析し、まっすぐに回転・補正するためのマクロです。
以前公開した「ほぼ手動傾き補正マクロ」を改良し、「全自動解析」かつ「フォルダ内全画像自動処理」にしました。
//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(コードジン)
・画像処理で自炊書籍画像を読みやすく加工する 初級編 - Qiita
補正の例
画像引用:泉朝樹, 見える子ちゃん, 単行本第1巻, 10ページ, KADOKAWA, 2019
使い方
マクロを起動し、補正をかけたいフォルダを選択するだけで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]でマクロが実行されます。
ライセンスなんかは一切無いので、ぜひぜひ自由に使ってみてください!